diff options
Diffstat (limited to 'js/src/vm/AsyncFunction.h')
-rw-r--r-- | js/src/vm/AsyncFunction.h | 328 |
1 files changed, 328 insertions, 0 deletions
diff --git a/js/src/vm/AsyncFunction.h b/js/src/vm/AsyncFunction.h new file mode 100644 index 0000000000..8e31450f06 --- /dev/null +++ b/js/src/vm/AsyncFunction.h @@ -0,0 +1,328 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef vm_AsyncFunction_h +#define vm_AsyncFunction_h + +#include "js/Class.h" +#include "vm/GeneratorObject.h" +#include "vm/JSObject.h" +#include "vm/PromiseObject.h" + +// [SMDOC] Async functions +// +// # Implementation +// +// Async functions are implemented based on generators, in terms of +// suspend/resume. +// Instead of returning the generator object itself, they return the async +// function's result promise to the caller. +// +// The async function's result promise is stored in the generator object +// (js::AsyncFunctionGeneratorObject) and retrieved from it whenever the +// execution needs it. +// +// +// # Start +// +// When an async function is called, it synchronously runs until the first +// `await` or `return`. This works just like a normal function. +// +// This corresponds to steps 1-3, 5-9 of AsyncFunctionStart. +// +// AsyncFunctionStart ( promiseCapability, asyncFunctionBody ) +// https://tc39.es/ecma262/#sec-async-functions-abstract-operations-async-function-start +// +// 1. Let runningContext be the running execution context. +// 2. Let asyncContext be a copy of runningContext. +// 3. NOTE: Copying the execution state is required for the step below to +// resume its execution. It is ill-defined to resume a currently executing +// context. +// ... +// 5. Push asyncContext onto the execution context stack; asyncContext is now +// the running execution context. +// 6. Resume the suspended evaluation of asyncContext. Let result be the value +// returned by the resumed computation. +// 7. Assert: When we return here, asyncContext has already been removed from +// the execution context stack and runningContext is the currently running +// execution context. +// 8. Assert: result is a normal completion with a value of undefined. The +// possible sources of completion values are Await or, if the async +// function doesn't await anything, step 4.g above. +// 9. Return. +// +// Unlike generators, async functions don't contain JSOp::InitialYield and +// don't suspend immediately when call. +// +// +// # Return +// +// Explicit/implicit `return` is implemented with the following bytecode +// sequence: +// +// ``` +// GetAliasedVar ".generator" # VALUE .generator +// AsyncResolve # PROMISE +// SetRval # +// GetAliasedVar ".generator" # .generator +// FinalYieldRval # +// ``` +// +// JSOp::Resolve (js::AsyncFunctionResolve) resolves the current async +// function's result promise. Then this sets it as the function's return value. +// (The return value is observable if the caller is still on the stack-- +// that is, the async function is returning without ever awaiting. +// Otherwise we're returning to the microtask loop, which ignores the +// return value.) +// +// This corresponds to AsyncFunctionStart steps 4.a-e. 4.g. +// +// 4. Set the code evaluation state of asyncContext such that when evaluation +// is resumed for that execution context the following steps will be +// performed: +// a. Let result be the result of evaluating asyncFunctionBody. +// b. Assert: If we return here, the async function either threw an +// exception or performed an implicit or explicit return; all awaiting +// is done. +// c. Remove asyncContext from the execution context stack and restore the +// execution context that is at the top of the execution context stack as +// the running execution context. +// d. If result.[[Type]] is normal, then +// i. Perform +// ! Call(promiseCapability.[[Resolve]], undefined, «undefined»). +// e. Else if result.[[Type]] is return, then +// i. Perform +// ! Call(promiseCapability.[[Resolve]], undefined, +// «result.[[Value]]»). +// ... +// g. Return. +// +// +// # Throw +// +// The body part of an async function is enclosed by an implicit try-catch +// block, to catch `throw` completion of the function body. +// +// If an exception is thrown by the function body, the catch block catches it +// and rejects the async function's result promise. +// +// If there's an expression in parameters, the entire parameters part is also +// enclosed by a separate implicit try-catch block. +// +// ``` +// Try # +// (parameter expressions here) # +// Goto BODY # +// +// JumpTarget from try # +// ExceptionAndStack # EXCEPTION STACK +// GetAliasedVar ".generator" # EXCEPTION STACK .generator +// AsyncReject # PROMISE +// SetRval # +// GetAliasedVar ".generator" # .generator +// FinalYieldRval # +// +// BODY: +// JumpTarget # +// Try # +// (body here) # +// +// JumpTarget from try # +// ExceptionAndStack # EXCEPTION STACK +// GetAliasedVar ".generator" # EXCEPTION STACK .generator +// AsyncReject # PROMISE +// SetRval # +// GetAliasedVar ".generator" # .generator +// FinalYieldRval # +// ``` +// +// This corresponds to AsyncFunctionStart steps 4.f-g. +// +// 4. ... +// f. Else, +// i. Assert: result.[[Type]] is throw. +// ii. Perform +// ! Call(promiseCapability.[[Reject]], undefined, +// «result.[[Value]]»). +// g. Return. +// +// +// # Await +// +// `await` is implemented with the following bytecode sequence: +// (ignoring CanSkipAwait for now, see "Optimization for await" section) +// +// ``` +// (operand here) # VALUE +// GetAliasedVar ".generator" # VALUE .generator +// AsyncAwait # PROMISE +// +// GetAliasedVar ".generator" # PROMISE .generator +// Await 0 # RVAL GENERATOR RESUMEKIND +// +// AfterYield # RVAL GENERATOR RESUMEKIND +// CheckResumeKind # RVAL +// ``` +// +// JSOp::AsyncAwait corresponds to Await steps 1-9, and JSOp::Await corresponds +// to Await steps 10-12 in the spec. +// +// See the next section for JSOp::CheckResumeKind. +// +// After them, the async function is suspended, and if this is the first await +// in the execution, the async function's result promise is returned to the +// caller. +// +// Await +// https://tc39.es/ecma262/#await +// +// 1. Let asyncContext be the running execution context. +// 2. Let promise be ? PromiseResolve(%Promise%, value). +// 3. Let stepsFulfilled be the algorithm steps defined in Await Fulfilled +// Functions. +// 4. Let onFulfilled be ! CreateBuiltinFunction(stepsFulfilled, « +// [[AsyncContext]] »). +// 5. Set onFulfilled.[[AsyncContext]] to asyncContext. +// 6. Let stepsRejected be the algorithm steps defined in Await Rejected +// Functions. +// 7. Let onRejected be ! CreateBuiltinFunction(stepsRejected, « +// [[AsyncContext]] »). +// 8. Set onRejected.[[AsyncContext]] to asyncContext. +// 9. Perform ! PerformPromiseThen(promise, onFulfilled, onRejected). +// 10. Remove asyncContext from the execution context stack and restore the +// execution context that is at the top of the execution context stack as +// the running execution context. +// 11. Set the code evaluation state of asyncContext such that when evaluation +// is resumed with a Completion completion, the following steps of the +// algorithm that invoked Await will be performed, with completion +// available. +// 12. Return. +// 13. NOTE: This returns to the evaluation of the operation that had most +// previously resumed evaluation of asyncContext. +// +// (See comments above AsyncAwait and Await in js/src/vm/Opcodes.h for more +// details) +// +// +// # Reaction jobs and resume after await +// +// When an async function performs `await` and the operand becomes settled, a +// new reaction job for the operand is enqueued to the job queue. +// +// The reaction record for the job is marked as "this is for async function" +// (see js::AsyncFunctionAwait), and handled specially in +// js::PromiseReactionJob. +// +// When the await operand resolves (either with fulfillment or rejection), +// the async function is resumed from the job queue, by calling +// js::AsyncFunctionAwaitedFulfilled or js::AsyncFunctionAwaitedRejected +// from js::AsyncFunctionPromiseReactionJob. +// +// The execution resumes from JSOp::AfterYield, with the resolved value +// and the resume kind, either normal or throw, corresponds to fulfillment or +// rejection, on the stack. +// +// The resume kind is handled by JSOp::CheckResumeKind after that. +// +// If the resume kind is normal (=fulfillment), the async function resumes +// the execution with the resolved value as the result of `await`. +// +// If the resume kind is throw (=rejection), it throws the resolved value, +// and it will be caught by the try-catch explained above. +// +// +// # Optimization for await +// +// Suspending the execution and going into the embedding's job queue is slow +// and hard to optimize. +// +// If the following conditions are met, we don't have to perform the above +// but just use the await operand as the result of await. +// +// 1. The await operand is either non-promise or already-fulfilled promise, +// so that the result value is already known +// 2. There's no jobs in the job queue, +// so that we don't have to perform other jobs before resuming from +// await +// 3. Promise constructor/prototype are not modified, +// so that the optimization isn't visible to the user code +// +// This is implemented by the following bytecode sequence: +// +// ``` +// (operand here) # VALUE +// +// CanSkipAwait # VALUE, CAN_SKIP +// MaybeExtractAwaitValue # VALUE_OR_RVAL, CAN_SKIP +// JumpIfTrue END # VALUE +// +// JumpTarget # VALUE +// GetAliasedVar ".generator" # VALUE .generator +// Await 0 # RVAL GENERATOR RESUMEKIND +// AfterYield # RVAL GENERATOR RESUMEKIND +// CheckResumeKind # RVAL +// +// END: +// JumpTarget # RVAL +// ``` +// +// JSOp::CanSkipAwait checks the above conditions. MaybeExtractAwaitValue will +// replace Value if it can be skipped, and then the await is jumped over. + +namespace js { + +class AsyncFunctionGeneratorObject; + +extern const JSClass AsyncFunctionClass; + +// Resume the async function when the `await` operand resolves. +// Split into two functions depending on whether the awaited value was +// fulfilled or rejected. +[[nodiscard]] bool AsyncFunctionAwaitedFulfilled( + JSContext* cx, Handle<AsyncFunctionGeneratorObject*> generator, + HandleValue value); + +[[nodiscard]] bool AsyncFunctionAwaitedRejected( + JSContext* cx, Handle<AsyncFunctionGeneratorObject*> generator, + HandleValue reason); + +// Resolve the async function's promise object with the given value and then +// return the promise object. +JSObject* AsyncFunctionResolve(JSContext* cx, + Handle<AsyncFunctionGeneratorObject*> generator, + HandleValue value); + +// Reject the async function's promise object with the given value and then +// return the promise object. +JSObject* AsyncFunctionReject(JSContext* cx, + Handle<AsyncFunctionGeneratorObject*> generator, + HandleValue reason, HandleValue stack); + +class AsyncFunctionGeneratorObject : public AbstractGeneratorObject { + public: + enum { + PROMISE_SLOT = AbstractGeneratorObject::RESERVED_SLOTS, + + RESERVED_SLOTS + }; + + static const JSClass class_; + static const JSClassOps classOps_; + + static AsyncFunctionGeneratorObject* create(JSContext* cx, + HandleFunction asyncGen); + + static AsyncFunctionGeneratorObject* create(JSContext* cx, + Handle<ModuleObject*> module); + + PromiseObject* promise() { + return &getFixedSlot(PROMISE_SLOT).toObject().as<PromiseObject>(); + } +}; + +} // namespace js + +#endif /* vm_AsyncFunction_h */ |