// Copyright 2017 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "src/builtins/builtins-async.h" #include "src/builtins/builtins-utils.h" #include "src/builtins/builtins.h" #include "src/code-stub-assembler.h" #include "src/objects-inl.h" namespace v8 { namespace internal { typedef compiler::Node Node; typedef CodeStubAssembler::ParameterMode ParameterMode; typedef compiler::CodeAssemblerState CodeAssemblerState; class AsyncFunctionBuiltinsAssembler : public AsyncBuiltinsAssembler { public: explicit AsyncFunctionBuiltinsAssembler(CodeAssemblerState* state) : AsyncBuiltinsAssembler(state) {} protected: void AsyncFunctionAwait(Node* const context, Node* const generator, Node* const awaited, Node* const outer_promise, const bool is_predicted_as_caught); void AsyncFunctionAwaitResumeClosure( Node* const context, Node* const sent_value, JSGeneratorObject::ResumeMode resume_mode); }; namespace { // Describe fields of Context associated with AsyncFunctionAwait resume // closures. // TODO(jgruber): Refactor to reuse code for upcoming async-generators. class AwaitContext { public: enum Fields { kGeneratorSlot = Context::MIN_CONTEXT_SLOTS, kLength }; }; } // anonymous namespace void AsyncFunctionBuiltinsAssembler::AsyncFunctionAwaitResumeClosure( Node* context, Node* sent_value, JSGeneratorObject::ResumeMode resume_mode) { DCHECK(resume_mode == JSGeneratorObject::kNext || resume_mode == JSGeneratorObject::kThrow); Node* const generator = LoadContextElement(context, AwaitContext::kGeneratorSlot); CSA_SLOW_ASSERT(this, HasInstanceType(generator, JS_GENERATOR_OBJECT_TYPE)); // Inline version of GeneratorPrototypeNext / GeneratorPrototypeReturn with // unnecessary runtime checks removed. // TODO(jgruber): Refactor to reuse code from builtins-generator.cc. // Ensure that the generator is neither closed nor running. CSA_SLOW_ASSERT( this, SmiGreaterThan( LoadObjectField(generator, JSGeneratorObject::kContinuationOffset), SmiConstant(JSGeneratorObject::kGeneratorClosed))); // Resume the {receiver} using our trampoline. Callable callable = CodeFactory::ResumeGenerator(isolate()); CallStub(callable, context, sent_value, generator, SmiConstant(resume_mode)); // The resulting Promise is a throwaway, so it doesn't matter what it // resolves to. What is important is that we don't end up keeping the // whole chain of intermediate Promises alive by returning the return value // of ResumeGenerator, as that would create a memory leak. } TF_BUILTIN(AsyncFunctionAwaitRejectClosure, AsyncFunctionBuiltinsAssembler) { CSA_ASSERT_JS_ARGC_EQ(this, 1); Node* const sentError = Parameter(1); Node* const context = Parameter(4); AsyncFunctionAwaitResumeClosure(context, sentError, JSGeneratorObject::kThrow); Return(UndefinedConstant()); } TF_BUILTIN(AsyncFunctionAwaitResolveClosure, AsyncFunctionBuiltinsAssembler) { CSA_ASSERT_JS_ARGC_EQ(this, 1); Node* const sentValue = Parameter(1); Node* const context = Parameter(4); AsyncFunctionAwaitResumeClosure(context, sentValue, JSGeneratorObject::kNext); Return(UndefinedConstant()); } // ES#abstract-ops-async-function-await // AsyncFunctionAwait ( value ) // Shared logic for the core of await. The parser desugars // await awaited // into // yield AsyncFunctionAwait{Caught,Uncaught}(.generator, awaited, .promise) // The 'awaited' parameter is the value; the generator stands in // for the asyncContext, and .promise is the larger promise under // construction by the enclosing async function. void AsyncFunctionBuiltinsAssembler::AsyncFunctionAwait( Node* const context, Node* const generator, Node* const awaited, Node* const outer_promise, const bool is_predicted_as_caught) { CSA_SLOW_ASSERT(this, HasInstanceType(generator, JS_GENERATOR_OBJECT_TYPE)); CSA_SLOW_ASSERT(this, HasInstanceType(outer_promise, JS_PROMISE_TYPE)); NodeGenerator1 create_closure_context = [&](Node* native_context) -> Node* { Node* const context = CreatePromiseContext(native_context, AwaitContext::kLength); StoreContextElementNoWriteBarrier(context, AwaitContext::kGeneratorSlot, generator); return context; }; // TODO(jgruber): AsyncBuiltinsAssembler::Await currently does not reuse // the awaited promise if it is already a promise. Reuse is non-spec compliant // but part of our old behavior gives us a couple of percent // performance boost. // TODO(jgruber): Use a faster specialized version of // InternalPerformPromiseThen. Node* const result = Await( context, generator, awaited, outer_promise, create_closure_context, Context::ASYNC_FUNCTION_AWAIT_RESOLVE_SHARED_FUN, Context::ASYNC_FUNCTION_AWAIT_REJECT_SHARED_FUN, is_predicted_as_caught); Return(result); } // Called by the parser from the desugaring of 'await' when catch // prediction indicates that there is a locally surrounding catch block. TF_BUILTIN(AsyncFunctionAwaitCaught, AsyncFunctionBuiltinsAssembler) { CSA_ASSERT_JS_ARGC_EQ(this, 3); Node* const generator = Parameter(1); Node* const awaited = Parameter(2); Node* const outer_promise = Parameter(3); Node* const context = Parameter(6); static const bool kIsPredictedAsCaught = true; AsyncFunctionAwait(context, generator, awaited, outer_promise, kIsPredictedAsCaught); } // Called by the parser from the desugaring of 'await' when catch // prediction indicates no locally surrounding catch block. TF_BUILTIN(AsyncFunctionAwaitUncaught, AsyncFunctionBuiltinsAssembler) { CSA_ASSERT_JS_ARGC_EQ(this, 3); Node* const generator = Parameter(1); Node* const awaited = Parameter(2); Node* const outer_promise = Parameter(3); Node* const context = Parameter(6); static const bool kIsPredictedAsCaught = false; AsyncFunctionAwait(context, generator, awaited, outer_promise, kIsPredictedAsCaught); } TF_BUILTIN(AsyncFunctionPromiseCreate, AsyncFunctionBuiltinsAssembler) { CSA_ASSERT_JS_ARGC_EQ(this, 0); Node* const context = Parameter(3); Node* const promise = AllocateAndInitJSPromise(context); Label if_is_debug_active(this, Label::kDeferred); GotoIf(IsDebugActive(), &if_is_debug_active); // Early exit if debug is not active. Return(promise); Bind(&if_is_debug_active); { // Push the Promise under construction in an async function on // the catch prediction stack to handle exceptions thrown before // the first await. // Assign ID and create a recurring task to save stack for future // resumptions from await. CallRuntime(Runtime::kDebugAsyncFunctionPromiseCreated, context, promise); Return(promise); } } TF_BUILTIN(AsyncFunctionPromiseRelease, AsyncFunctionBuiltinsAssembler) { CSA_ASSERT_JS_ARGC_EQ(this, 1); Node* const promise = Parameter(1); Node* const context = Parameter(4); Label if_is_debug_active(this, Label::kDeferred); GotoIf(IsDebugActive(), &if_is_debug_active); // Early exit if debug is not active. Return(UndefinedConstant()); Bind(&if_is_debug_active); { // Pop the Promise under construction in an async function on // from catch prediction stack. CallRuntime(Runtime::kDebugPopPromise, context); Return(promise); } } } // namespace internal } // namespace v8