summaryrefslogtreecommitdiffstats
path: root/js/src/vm/AsyncFunction.h
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/vm/AsyncFunction.h')
-rw-r--r--js/src/vm/AsyncFunction.h328
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 */