| // 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-gen.h" |
| #include "src/builtins/builtins-utils-gen.h" |
| #include "src/builtins/builtins.h" |
| #include "src/codegen/code-stub-assembler.h" |
| #include "src/objects/js-generator.h" |
| #include "src/objects/js-promise.h" |
| #include "src/objects/objects-inl.h" |
| |
| namespace v8 { |
| namespace internal { |
| |
| class AsyncFunctionBuiltinsAssembler : public AsyncBuiltinsAssembler { |
| public: |
| explicit AsyncFunctionBuiltinsAssembler(compiler::CodeAssemblerState* state) |
| : AsyncBuiltinsAssembler(state) {} |
| |
| protected: |
| template <typename Descriptor> |
| void AsyncFunctionAwait(const bool is_predicted_as_caught); |
| |
| void AsyncFunctionAwaitResumeClosure( |
| const TNode<Context> context, const TNode<Object> sent_value, |
| JSGeneratorObject::ResumeMode resume_mode); |
| }; |
| |
| void AsyncFunctionBuiltinsAssembler::AsyncFunctionAwaitResumeClosure( |
| TNode<Context> context, TNode<Object> sent_value, |
| JSGeneratorObject::ResumeMode resume_mode) { |
| DCHECK(resume_mode == JSGeneratorObject::kNext || |
| resume_mode == JSGeneratorObject::kThrow); |
| |
| TNode<JSAsyncFunctionObject> async_function_object = |
| CAST(LoadContextElement(context, Context::EXTENSION_INDEX)); |
| |
| // Push the promise for the {async_function_object} back onto the catch |
| // prediction stack to handle exceptions thrown after resuming from the |
| // await properly. |
| Label if_instrumentation(this, Label::kDeferred), |
| if_instrumentation_done(this); |
| Branch(IsDebugActive(), &if_instrumentation, &if_instrumentation_done); |
| BIND(&if_instrumentation); |
| { |
| TNode<JSPromise> promise = LoadObjectField<JSPromise>( |
| async_function_object, JSAsyncFunctionObject::kPromiseOffset); |
| CallRuntime(Runtime::kDebugAsyncFunctionResumed, context, promise); |
| Goto(&if_instrumentation_done); |
| } |
| BIND(&if_instrumentation_done); |
| |
| // Inline version of GeneratorPrototypeNext / GeneratorPrototypeReturn with |
| // unnecessary runtime checks removed. |
| |
| // Ensure that the {async_function_object} is neither closed nor running. |
| CSA_SLOW_ASSERT( |
| this, SmiGreaterThan( |
| LoadObjectField<Smi>(async_function_object, |
| JSGeneratorObject::kContinuationOffset), |
| SmiConstant(JSGeneratorObject::kGeneratorClosed))); |
| |
| // Remember the {resume_mode} for the {async_function_object}. |
| StoreObjectFieldNoWriteBarrier(async_function_object, |
| JSGeneratorObject::kResumeModeOffset, |
| SmiConstant(resume_mode)); |
| |
| // Resume the {receiver} using our trampoline. |
| Callable callable = CodeFactory::ResumeGenerator(isolate()); |
| CallStub(callable, context, sent_value, async_function_object); |
| |
| // 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(AsyncFunctionEnter, AsyncFunctionBuiltinsAssembler) { |
| auto closure = Parameter<JSFunction>(Descriptor::kClosure); |
| auto receiver = Parameter<Object>(Descriptor::kReceiver); |
| auto context = Parameter<Context>(Descriptor::kContext); |
| |
| // Compute the number of registers and parameters. |
| TNode<SharedFunctionInfo> shared = LoadObjectField<SharedFunctionInfo>( |
| closure, JSFunction::kSharedFunctionInfoOffset); |
| TNode<IntPtrT> formal_parameter_count = |
| ChangeInt32ToIntPtr(LoadObjectField<Uint16T>( |
| shared, SharedFunctionInfo::kFormalParameterCountOffset)); |
| TNode<BytecodeArray> bytecode_array = |
| LoadSharedFunctionInfoBytecodeArray(shared); |
| TNode<IntPtrT> frame_size = ChangeInt32ToIntPtr(LoadObjectField<Uint32T>( |
| bytecode_array, BytecodeArray::kFrameSizeOffset)); |
| TNode<IntPtrT> parameters_and_register_length = |
| Signed(IntPtrAdd(WordSar(frame_size, IntPtrConstant(kTaggedSizeLog2)), |
| formal_parameter_count)); |
| |
| // Allocate and initialize the register file. |
| TNode<FixedArrayBase> parameters_and_registers = |
| AllocateFixedArray(HOLEY_ELEMENTS, parameters_and_register_length, |
| kAllowLargeObjectAllocation); |
| FillFixedArrayWithValue(HOLEY_ELEMENTS, parameters_and_registers, |
| IntPtrConstant(0), parameters_and_register_length, |
| RootIndex::kUndefinedValue); |
| |
| // Allocate space for the promise, the async function object. |
| TNode<IntPtrT> size = IntPtrConstant(JSPromise::kSizeWithEmbedderFields + |
| JSAsyncFunctionObject::kHeaderSize); |
| TNode<HeapObject> base = AllocateInNewSpace(size); |
| |
| // Initialize the promise. |
| TNode<NativeContext> native_context = LoadNativeContext(context); |
| TNode<JSFunction> promise_function = |
| CAST(LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX)); |
| TNode<Map> promise_map = LoadObjectField<Map>( |
| promise_function, JSFunction::kPrototypeOrInitialMapOffset); |
| TNode<JSPromise> promise = UncheckedCast<JSPromise>( |
| InnerAllocate(base, JSAsyncFunctionObject::kHeaderSize)); |
| StoreMapNoWriteBarrier(promise, promise_map); |
| StoreObjectFieldRoot(promise, JSPromise::kPropertiesOrHashOffset, |
| RootIndex::kEmptyFixedArray); |
| StoreObjectFieldRoot(promise, JSPromise::kElementsOffset, |
| RootIndex::kEmptyFixedArray); |
| PromiseInit(promise); |
| |
| // Initialize the async function object. |
| TNode<Map> async_function_object_map = CAST(LoadContextElement( |
| native_context, Context::ASYNC_FUNCTION_OBJECT_MAP_INDEX)); |
| TNode<JSAsyncFunctionObject> async_function_object = |
| UncheckedCast<JSAsyncFunctionObject>(base); |
| StoreMapNoWriteBarrier(async_function_object, async_function_object_map); |
| StoreObjectFieldRoot(async_function_object, |
| JSAsyncFunctionObject::kPropertiesOrHashOffset, |
| RootIndex::kEmptyFixedArray); |
| StoreObjectFieldRoot(async_function_object, |
| JSAsyncFunctionObject::kElementsOffset, |
| RootIndex::kEmptyFixedArray); |
| StoreObjectFieldNoWriteBarrier( |
| async_function_object, JSAsyncFunctionObject::kFunctionOffset, closure); |
| StoreObjectFieldNoWriteBarrier( |
| async_function_object, JSAsyncFunctionObject::kContextOffset, context); |
| StoreObjectFieldNoWriteBarrier( |
| async_function_object, JSAsyncFunctionObject::kReceiverOffset, receiver); |
| StoreObjectFieldNoWriteBarrier(async_function_object, |
| JSAsyncFunctionObject::kInputOrDebugPosOffset, |
| SmiConstant(0)); |
| StoreObjectFieldNoWriteBarrier(async_function_object, |
| JSAsyncFunctionObject::kResumeModeOffset, |
| SmiConstant(JSAsyncFunctionObject::kNext)); |
| StoreObjectFieldNoWriteBarrier( |
| async_function_object, JSAsyncFunctionObject::kContinuationOffset, |
| SmiConstant(JSAsyncFunctionObject::kGeneratorExecuting)); |
| StoreObjectFieldNoWriteBarrier( |
| async_function_object, |
| JSAsyncFunctionObject::kParametersAndRegistersOffset, |
| parameters_and_registers); |
| StoreObjectFieldNoWriteBarrier( |
| async_function_object, JSAsyncFunctionObject::kPromiseOffset, promise); |
| |
| // Fire promise hooks if enabled and push the Promise under construction |
| // in an async function on the catch prediction stack to handle exceptions |
| // thrown before the first await. |
| Label if_instrumentation(this, Label::kDeferred), |
| if_instrumentation_done(this); |
| Branch(IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(), |
| &if_instrumentation, &if_instrumentation_done); |
| BIND(&if_instrumentation); |
| { |
| CallRuntime(Runtime::kDebugAsyncFunctionEntered, context, promise); |
| Goto(&if_instrumentation_done); |
| } |
| BIND(&if_instrumentation_done); |
| |
| Return(async_function_object); |
| } |
| |
| TF_BUILTIN(AsyncFunctionReject, AsyncFunctionBuiltinsAssembler) { |
| auto async_function_object = |
| Parameter<JSAsyncFunctionObject>(Descriptor::kAsyncFunctionObject); |
| auto reason = Parameter<Object>(Descriptor::kReason); |
| auto can_suspend = Parameter<Oddball>(Descriptor::kCanSuspend); |
| auto context = Parameter<Context>(Descriptor::kContext); |
| TNode<JSPromise> promise = LoadObjectField<JSPromise>( |
| async_function_object, JSAsyncFunctionObject::kPromiseOffset); |
| |
| // Reject the {promise} for the given {reason}, disabling the |
| // additional debug event for the rejection since a debug event |
| // already happend for the exception that got us here. |
| CallBuiltin(Builtins::kRejectPromise, context, promise, reason, |
| FalseConstant()); |
| |
| Label if_debugging(this, Label::kDeferred); |
| GotoIf(HasAsyncEventDelegate(), &if_debugging); |
| GotoIf(IsDebugActive(), &if_debugging); |
| Return(promise); |
| |
| BIND(&if_debugging); |
| TailCallRuntime(Runtime::kDebugAsyncFunctionFinished, context, can_suspend, |
| promise); |
| } |
| |
| TF_BUILTIN(AsyncFunctionResolve, AsyncFunctionBuiltinsAssembler) { |
| auto async_function_object = |
| Parameter<JSAsyncFunctionObject>(Descriptor::kAsyncFunctionObject); |
| auto value = Parameter<Object>(Descriptor::kValue); |
| auto can_suspend = Parameter<Oddball>(Descriptor::kCanSuspend); |
| auto context = Parameter<Context>(Descriptor::kContext); |
| TNode<JSPromise> promise = LoadObjectField<JSPromise>( |
| async_function_object, JSAsyncFunctionObject::kPromiseOffset); |
| |
| CallBuiltin(Builtins::kResolvePromise, context, promise, value); |
| |
| Label if_debugging(this, Label::kDeferred); |
| GotoIf(HasAsyncEventDelegate(), &if_debugging); |
| GotoIf(IsDebugActive(), &if_debugging); |
| Return(promise); |
| |
| BIND(&if_debugging); |
| TailCallRuntime(Runtime::kDebugAsyncFunctionFinished, context, can_suspend, |
| promise); |
| } |
| |
| // AsyncFunctionReject and AsyncFunctionResolve are both required to return |
| // the promise instead of the result of RejectPromise or ResolvePromise |
| // respectively from a lazy deoptimization. |
| TF_BUILTIN(AsyncFunctionLazyDeoptContinuation, AsyncFunctionBuiltinsAssembler) { |
| auto promise = Parameter<JSPromise>(Descriptor::kPromise); |
| Return(promise); |
| } |
| |
| TF_BUILTIN(AsyncFunctionAwaitRejectClosure, AsyncFunctionBuiltinsAssembler) { |
| CSA_ASSERT_JS_ARGC_EQ(this, 1); |
| const auto sentError = Parameter<Object>(Descriptor::kSentError); |
| const auto context = Parameter<Context>(Descriptor::kContext); |
| |
| AsyncFunctionAwaitResumeClosure(context, sentError, |
| JSGeneratorObject::kThrow); |
| Return(UndefinedConstant()); |
| } |
| |
| TF_BUILTIN(AsyncFunctionAwaitResolveClosure, AsyncFunctionBuiltinsAssembler) { |
| CSA_ASSERT_JS_ARGC_EQ(this, 1); |
| const auto sentValue = Parameter<Object>(Descriptor::kSentValue); |
| const auto context = Parameter<Context>(Descriptor::kContext); |
| |
| 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 value |
| // into |
| // yield AsyncFunctionAwait{Caught,Uncaught}(.generator_object, value) |
| // The 'value' parameter is the value; the .generator_object stands in |
| // for the asyncContext. |
| template <typename Descriptor> |
| void AsyncFunctionBuiltinsAssembler::AsyncFunctionAwait( |
| const bool is_predicted_as_caught) { |
| auto async_function_object = |
| Parameter<JSAsyncFunctionObject>(Descriptor::kAsyncFunctionObject); |
| auto value = Parameter<Object>(Descriptor::kValue); |
| auto context = Parameter<Context>(Descriptor::kContext); |
| |
| TNode<JSPromise> outer_promise = LoadObjectField<JSPromise>( |
| async_function_object, JSAsyncFunctionObject::kPromiseOffset); |
| |
| Label after_debug_hook(this), call_debug_hook(this, Label::kDeferred); |
| GotoIf(HasAsyncEventDelegate(), &call_debug_hook); |
| Goto(&after_debug_hook); |
| BIND(&after_debug_hook); |
| |
| TNode<SharedFunctionInfo> on_resolve_sfi = |
| AsyncFunctionAwaitResolveSharedFunConstant(); |
| TNode<SharedFunctionInfo> on_reject_sfi = |
| AsyncFunctionAwaitRejectSharedFunConstant(); |
| Await(context, async_function_object, value, outer_promise, on_resolve_sfi, |
| on_reject_sfi, is_predicted_as_caught); |
| |
| // Return outer promise to avoid adding an load of the outer promise before |
| // suspending in BytecodeGenerator. |
| Return(outer_promise); |
| |
| BIND(&call_debug_hook); |
| CallRuntime(Runtime::kDebugAsyncFunctionSuspended, context, outer_promise); |
| Goto(&after_debug_hook); |
| } |
| |
| // 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) { |
| static const bool kIsPredictedAsCaught = true; |
| AsyncFunctionAwait<Descriptor>(kIsPredictedAsCaught); |
| } |
| |
| // Called by the parser from the desugaring of 'await' when catch |
| // prediction indicates no locally surrounding catch block. |
| TF_BUILTIN(AsyncFunctionAwaitUncaught, AsyncFunctionBuiltinsAssembler) { |
| static const bool kIsPredictedAsCaught = false; |
| AsyncFunctionAwait<Descriptor>(kIsPredictedAsCaught); |
| } |
| |
| } // namespace internal |
| } // namespace v8 |