summaryrefslogtreecommitdiffstats
path: root/js/src/vm/AsyncFunction.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/vm/AsyncFunction.cpp')
-rw-r--r--js/src/vm/AsyncFunction.cpp240
1 files changed, 240 insertions, 0 deletions
diff --git a/js/src/vm/AsyncFunction.cpp b/js/src/vm/AsyncFunction.cpp
new file mode 100644
index 000000000..bd0b4f32a
--- /dev/null
+++ b/js/src/vm/AsyncFunction.cpp
@@ -0,0 +1,240 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * 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/. */
+
+#include "vm/AsyncFunction.h"
+
+#include "jscompartment.h"
+
+#include "builtin/Promise.h"
+#include "vm/GeneratorObject.h"
+#include "vm/GlobalObject.h"
+#include "vm/Interpreter.h"
+#include "vm/SelfHosting.h"
+
+using namespace js;
+using namespace js::gc;
+
+/* static */ bool
+GlobalObject::initAsyncFunction(JSContext* cx, Handle<GlobalObject*> global)
+{
+ if (global->getReservedSlot(ASYNC_FUNCTION_PROTO).isObject())
+ return true;
+
+ RootedObject asyncFunctionProto(cx, NewSingletonObjectWithFunctionPrototype(cx, global));
+ if (!asyncFunctionProto)
+ return false;
+
+ if (!DefineToStringTag(cx, asyncFunctionProto, cx->names().AsyncFunction))
+ return false;
+
+ RootedValue function(cx, global->getConstructor(JSProto_Function));
+ if (!function.toObjectOrNull())
+ return false;
+ RootedObject proto(cx, &function.toObject());
+ RootedAtom name(cx, cx->names().AsyncFunction);
+ RootedObject asyncFunction(cx, NewFunctionWithProto(cx, AsyncFunctionConstructor, 1,
+ JSFunction::NATIVE_CTOR, nullptr, name,
+ proto));
+ if (!asyncFunction)
+ return false;
+ if (!LinkConstructorAndPrototype(cx, asyncFunction, asyncFunctionProto))
+ return false;
+
+ global->setReservedSlot(ASYNC_FUNCTION, ObjectValue(*asyncFunction));
+ global->setReservedSlot(ASYNC_FUNCTION_PROTO, ObjectValue(*asyncFunctionProto));
+ return true;
+}
+
+static MOZ_MUST_USE bool AsyncFunctionStart(JSContext* cx, Handle<PromiseObject*> resultPromise,
+ HandleValue generatorVal);
+
+#define UNWRAPPED_ASYNC_WRAPPED_SLOT 1
+#define WRAPPED_ASYNC_UNWRAPPED_SLOT 0
+
+// Async Functions proposal 1.1.8 and 1.2.14.
+static bool
+WrappedAsyncFunction(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ RootedFunction wrapped(cx, &args.callee().as<JSFunction>());
+ RootedValue unwrappedVal(cx, wrapped->getExtendedSlot(WRAPPED_ASYNC_UNWRAPPED_SLOT));
+ RootedFunction unwrapped(cx, &unwrappedVal.toObject().as<JSFunction>());
+ RootedValue thisValue(cx, args.thisv());
+
+ // Step 2.
+ // Also does a part of 2.2 steps 1-2.
+ RootedValue generatorVal(cx);
+ InvokeArgs args2(cx);
+ if (!args2.init(cx, argc))
+ return false;
+ for (size_t i = 0, len = argc; i < len; i++)
+ args2[i].set(args[i]);
+ if (Call(cx, unwrappedVal, thisValue, args2, &generatorVal)) {
+ // Step 1.
+ Rooted<PromiseObject*> resultPromise(cx, CreatePromiseObjectForAsync(cx, generatorVal));
+ if (!resultPromise)
+ return false;
+
+ // Step 3.
+ if (!AsyncFunctionStart(cx, resultPromise, generatorVal))
+ return false;
+
+ // Step 5.
+ args.rval().setObject(*resultPromise);
+ return true;
+ }
+
+ // Steps 1, 4.
+ RootedValue exc(cx);
+ if (!GetAndClearException(cx, &exc))
+ return false;
+ RootedObject rejectPromise(cx, PromiseObject::unforgeableReject(cx, exc));
+ if (!rejectPromise)
+ return false;
+
+ // Step 5.
+ args.rval().setObject(*rejectPromise);
+ return true;
+}
+
+// Async Functions proposal 2.1 steps 1, 3 (partially).
+// In the spec it creates a function, but we create 2 functions `unwrapped` and
+// `wrapped`. `unwrapped` is a generator that corresponds to
+// the async function's body, replacing `await` with `yield`. `wrapped` is a
+// function that is visible to the outside, and handles yielded values.
+JSObject*
+js::WrapAsyncFunction(JSContext* cx, HandleFunction unwrapped)
+{
+ MOZ_ASSERT(unwrapped->isStarGenerator());
+
+ // Create a new function with AsyncFunctionPrototype, reusing the name and
+ // the length of `unwrapped`.
+
+ // Step 1.
+ RootedObject proto(cx, GlobalObject::getOrCreateAsyncFunctionPrototype(cx, cx->global()));
+ if (!proto)
+ return nullptr;
+
+ RootedAtom funName(cx, unwrapped->name());
+ uint16_t length;
+ if (!unwrapped->getLength(cx, &length))
+ return nullptr;
+
+ // Steps 3 (partially).
+ RootedFunction wrapped(cx, NewFunctionWithProto(cx, WrappedAsyncFunction, length,
+ JSFunction::NATIVE_FUN, nullptr,
+ funName, proto,
+ AllocKind::FUNCTION_EXTENDED,
+ TenuredObject));
+ if (!wrapped)
+ return nullptr;
+
+ // Link them to each other to make GetWrappedAsyncFunction and
+ // GetUnwrappedAsyncFunction work.
+ unwrapped->setExtendedSlot(UNWRAPPED_ASYNC_WRAPPED_SLOT, ObjectValue(*wrapped));
+ wrapped->setExtendedSlot(WRAPPED_ASYNC_UNWRAPPED_SLOT, ObjectValue(*unwrapped));
+
+ return wrapped;
+}
+
+enum class ResumeKind {
+ Normal,
+ Throw
+};
+
+// Async Functions proposal 2.2 steps 3.f, 3.g.
+// Async Functions proposal 2.2 steps 3.d-e, 3.g.
+// Implemented in js/src/builtin/Promise.cpp
+
+// Async Functions proposal 2.2 steps 3-8, 2.4 steps 2-7, 2.5 steps 2-7.
+static bool
+AsyncFunctionResume(JSContext* cx, Handle<PromiseObject*> resultPromise, HandleValue generatorVal,
+ ResumeKind kind, HandleValue valueOrReason)
+{
+ // Execution context switching is handled in generator.
+ HandlePropertyName funName = kind == ResumeKind::Normal
+ ? cx->names().StarGeneratorNext
+ : cx->names().StarGeneratorThrow;
+ FixedInvokeArgs<1> args(cx);
+ args[0].set(valueOrReason);
+ RootedValue result(cx);
+ if (!CallSelfHostedFunction(cx, funName, generatorVal, args, &result))
+ return AsyncFunctionThrown(cx, resultPromise);
+
+ RootedObject resultObj(cx, &result.toObject());
+ RootedValue doneVal(cx);
+ RootedValue value(cx);
+ if (!GetProperty(cx, resultObj, resultObj, cx->names().done, &doneVal))
+ return false;
+ if (!GetProperty(cx, resultObj, resultObj, cx->names().value, &value))
+ return false;
+
+ if (doneVal.toBoolean())
+ return AsyncFunctionReturned(cx, resultPromise, value);
+
+ return AsyncFunctionAwait(cx, resultPromise, value);
+}
+
+// Async Functions proposal 2.2 steps 3-8.
+static MOZ_MUST_USE bool
+AsyncFunctionStart(JSContext* cx, Handle<PromiseObject*> resultPromise, HandleValue generatorVal)
+{
+ return AsyncFunctionResume(cx, resultPromise, generatorVal, ResumeKind::Normal, UndefinedHandleValue);
+}
+
+// Async Functions proposal 2.3 steps 1-8.
+// Implemented in js/src/builtin/Promise.cpp
+
+// Async Functions proposal 2.4.
+MOZ_MUST_USE bool
+js::AsyncFunctionAwaitedFulfilled(JSContext* cx, Handle<PromiseObject*> resultPromise,
+ HandleValue generatorVal, HandleValue value)
+{
+ // Step 1 (implicit).
+
+ // Steps 2-7.
+ return AsyncFunctionResume(cx, resultPromise, generatorVal, ResumeKind::Normal, value);
+}
+
+// Async Functions proposal 2.5.
+MOZ_MUST_USE bool
+js::AsyncFunctionAwaitedRejected(JSContext* cx, Handle<PromiseObject*> resultPromise,
+ HandleValue generatorVal, HandleValue reason)
+{
+ // Step 1 (implicit).
+
+ // Step 2-7.
+ return AsyncFunctionResume(cx, resultPromise, generatorVal, ResumeKind::Throw, reason);
+}
+
+JSFunction*
+js::GetWrappedAsyncFunction(JSFunction* unwrapped)
+{
+ MOZ_ASSERT(unwrapped->isAsync());
+ return &unwrapped->getExtendedSlot(UNWRAPPED_ASYNC_WRAPPED_SLOT).toObject().as<JSFunction>();
+}
+
+JSFunction*
+js::GetUnwrappedAsyncFunction(JSFunction* wrapped)
+{
+ MOZ_ASSERT(IsWrappedAsyncFunction(wrapped));
+ JSFunction* unwrapped = &wrapped->getExtendedSlot(WRAPPED_ASYNC_UNWRAPPED_SLOT).toObject().as<JSFunction>();
+ MOZ_ASSERT(unwrapped->isAsync());
+ return unwrapped;
+}
+
+bool
+js::IsWrappedAsyncFunction(JSFunction* fun)
+{
+ return fun->maybeNative() == WrappedAsyncFunction;
+}
+
+MOZ_MUST_USE bool
+js::CheckAsyncResumptionValue(JSContext* cx, HandleValue v)
+{
+ return CheckStarGeneratorResumptionValue(cx, v);
+}