summaryrefslogtreecommitdiffstats
path: root/js/src/vm/AsyncIteration.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/vm/AsyncIteration.cpp')
-rw-r--r--js/src/vm/AsyncIteration.cpp564
1 files changed, 564 insertions, 0 deletions
diff --git a/js/src/vm/AsyncIteration.cpp b/js/src/vm/AsyncIteration.cpp
new file mode 100644
index 000000000..04effe1b1
--- /dev/null
+++ b/js/src/vm/AsyncIteration.cpp
@@ -0,0 +1,564 @@
+/* -*- 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/AsyncIteration.h"
+
+#include "jsarray.h"
+#include "jscompartment.h"
+
+#include "builtin/Promise.h"
+#include "vm/GeneratorObject.h"
+#include "vm/GlobalObject.h"
+#include "vm/Interpreter.h"
+#include "vm/SelfHosting.h"
+
+#include "jscntxtinlines.h"
+#include "jsobjinlines.h"
+
+#include "vm/NativeObject-inl.h"
+
+using namespace js;
+using namespace js::gc;
+
+#define UNWRAPPED_ASYNC_WRAPPED_SLOT 1
+#define WRAPPED_ASYNC_UNWRAPPED_SLOT 0
+
+// Async Iteration proposal 2.3.10 Runtime Semantics: EvaluateBody.
+static bool
+WrappedAsyncGenerator(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 1.
+ 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))
+ return false;
+
+ // Step 2.
+ Rooted<AsyncGeneratorObject*> asyncGenObj(
+ cx, AsyncGeneratorObject::create(cx, wrapped, generatorVal));
+ if (!asyncGenObj)
+ return false;
+
+ // Step 3 (skipped).
+ // Done in AsyncGeneratorObject::create and generator.
+
+ // Step 4.
+ args.rval().setObject(*asyncGenObj);
+ return true;
+}
+
+JSObject*
+js::WrapAsyncGeneratorWithProto(JSContext* cx, HandleFunction unwrapped, HandleObject proto)
+{
+ MOZ_ASSERT(unwrapped->isAsync());
+ MOZ_ASSERT(proto, "We need an explicit prototype to avoid the default"
+ "%FunctionPrototype% fallback in NewFunctionWithProto().");
+
+ // Create a new function with AsyncGeneratorPrototype, reusing the name and
+ // the length of `unwrapped`.
+
+ RootedAtom funName(cx, unwrapped->explicitName());
+ uint16_t length;
+ if (!JSFunction::getLength(cx, unwrapped, &length))
+ return nullptr;
+
+ RootedFunction wrapped(cx, NewFunctionWithProto(cx, WrappedAsyncGenerator, length,
+ JSFunction::NATIVE_FUN, nullptr,
+ funName, proto,
+ AllocKind::FUNCTION_EXTENDED,
+ TenuredObject));
+ if (!wrapped)
+ return nullptr;
+
+ if (unwrapped->hasCompileTimeName())
+ wrapped->setCompileTimeName(unwrapped->compileTimeName());
+
+ // Link them to each other to make GetWrappedAsyncGenerator and
+ // GetUnwrappedAsyncGenerator work.
+ unwrapped->setExtendedSlot(UNWRAPPED_ASYNC_WRAPPED_SLOT, ObjectValue(*wrapped));
+ wrapped->setExtendedSlot(WRAPPED_ASYNC_UNWRAPPED_SLOT, ObjectValue(*unwrapped));
+
+ return wrapped;
+}
+
+JSObject*
+js::WrapAsyncGenerator(JSContext* cx, HandleFunction unwrapped)
+{
+ RootedObject proto(cx, GlobalObject::getOrCreateAsyncGenerator(cx, cx->global()));
+ if (!proto)
+ return nullptr;
+
+ return WrapAsyncGeneratorWithProto(cx, unwrapped, proto);
+}
+
+bool
+js::IsWrappedAsyncGenerator(JSFunction* fun)
+{
+ return fun->maybeNative() == WrappedAsyncGenerator;
+}
+
+JSFunction*
+js::GetWrappedAsyncGenerator(JSFunction* unwrapped)
+{
+ MOZ_ASSERT(unwrapped->isAsync());
+ return &unwrapped->getExtendedSlot(UNWRAPPED_ASYNC_WRAPPED_SLOT).toObject().as<JSFunction>();
+}
+
+JSFunction*
+js::GetUnwrappedAsyncGenerator(JSFunction* wrapped)
+{
+ MOZ_ASSERT(IsWrappedAsyncGenerator(wrapped));
+ JSFunction* unwrapped = &wrapped->getExtendedSlot(WRAPPED_ASYNC_UNWRAPPED_SLOT)
+ .toObject().as<JSFunction>();
+ MOZ_ASSERT(unwrapped->isAsync());
+ return unwrapped;
+}
+
+static MOZ_MUST_USE bool
+AsyncGeneratorResume(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj,
+ CompletionKind completionKind, HandleValue argument);
+
+// Async Iteration proposal 5.1.1 Await Fulfilled Functions.
+MOZ_MUST_USE bool
+js::AsyncGeneratorAwaitedFulfilled(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj,
+ HandleValue value)
+{
+ return AsyncGeneratorResume(cx, asyncGenObj, CompletionKind::Normal, value);
+}
+
+// Async Iteration proposal 5.1.2 Await Rejected Functions.
+MOZ_MUST_USE bool
+js::AsyncGeneratorAwaitedRejected(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj,
+ HandleValue reason)
+{
+ return AsyncGeneratorResume(cx, asyncGenObj, CompletionKind::Throw, reason);
+}
+
+// Async Iteration proposal 6.4.1.2 AsyncGenerator.prototype.next.
+static bool
+AsyncGeneratorNext(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Steps 1-3.
+ return AsyncGeneratorEnqueue(cx, args.thisv(), CompletionKind::Normal, args.get(0),
+ args.rval());
+}
+
+// Async Iteration proposal 6.4.1.3 AsyncGenerator.prototype.return.
+static bool
+AsyncGeneratorReturn(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Steps 1-3.
+ return AsyncGeneratorEnqueue(cx, args.thisv(), CompletionKind::Return, args.get(0),
+ args.rval());
+}
+
+// Async Iteration proposal 6.4.1.4 AsyncGenerator.prototype.throw.
+static bool
+AsyncGeneratorThrow(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Steps 1-3.
+ return AsyncGeneratorEnqueue(cx, args.thisv(), CompletionKind::Throw, args.get(0),
+ args.rval());
+}
+
+const Class AsyncGeneratorObject::class_ = {
+ "AsyncGenerator",
+ JSCLASS_HAS_RESERVED_SLOTS(AsyncGeneratorObject::Slots)
+};
+
+// ES 2017 draft 9.1.13.
+template <typename ProtoGetter>
+static JSObject*
+OrdinaryCreateFromConstructor(JSContext* cx, HandleFunction fun,
+ ProtoGetter protoGetter, const Class* clasp)
+{
+ // Step 1 (skipped).
+
+ // Step 2.
+ RootedValue protoVal(cx);
+ if (!GetProperty(cx, fun, fun, cx->names().prototype, &protoVal))
+ return nullptr;
+
+ RootedObject proto(cx, protoVal.isObject() ? &protoVal.toObject() : nullptr);
+ if (!proto) {
+ proto = protoGetter(cx, cx->global());
+ if (!proto)
+ return nullptr;
+ }
+
+ // Step 3.
+ return NewNativeObjectWithGivenProto(cx, clasp, proto);
+}
+
+/* static */ AsyncGeneratorObject*
+AsyncGeneratorObject::create(JSContext* cx, HandleFunction asyncGen, HandleValue generatorVal)
+{
+ MOZ_ASSERT(generatorVal.isObject());
+ MOZ_ASSERT(generatorVal.toObject().is<GeneratorObject>());
+
+ RootedObject obj(
+ cx, OrdinaryCreateFromConstructor(cx, asyncGen,
+ GlobalObject::getOrCreateAsyncGeneratorPrototype,
+ &class_));
+ if (!obj)
+ return nullptr;
+
+ Handle<AsyncGeneratorObject*> asyncGenObj = obj.as<AsyncGeneratorObject>();
+
+ // Async Iteration proposal 6.4.3.2 AsyncGeneratorStart.
+ // Step 6.
+ asyncGenObj->setGenerator(generatorVal);
+
+ // Step 7.
+ asyncGenObj->setSuspendedStart();
+
+ // Step 8.
+ asyncGenObj->clearSingleQueueRequest();
+
+ return asyncGenObj;
+}
+
+/* static */ MOZ_MUST_USE bool
+AsyncGeneratorObject::enqueueRequest(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj,
+ Handle<AsyncGeneratorRequest*> request)
+{
+ if (asyncGenObj->isSingleQueue()) {
+ if (asyncGenObj->isSingleQueueEmpty()) {
+ asyncGenObj->setSingleQueueRequest(request);
+ return true;
+ }
+
+ RootedArrayObject queue(cx, NewDenseEmptyArray(cx));
+ if (!queue)
+ return false;
+
+ if (!NewbornArrayPush(cx, queue, ObjectValue(*asyncGenObj->singleQueueRequest())))
+ return false;
+ if (!NewbornArrayPush(cx, queue, ObjectValue(*request)))
+ return false;
+
+ asyncGenObj->setQueue(queue);
+ return true;
+ }
+
+ RootedArrayObject queue(cx, asyncGenObj->queue());
+
+ FixedInvokeArgs<1> args(cx);
+ args[0].setObject(*request);
+ args.setThis(ObjectValue(*queue));
+ return CallJSNative(cx, array_push, args);
+}
+
+/* static */ AsyncGeneratorRequest*
+AsyncGeneratorObject::dequeueRequest(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj)
+{
+ if (asyncGenObj->isSingleQueue()) {
+ AsyncGeneratorRequest* request = asyncGenObj->singleQueueRequest();
+ asyncGenObj->clearSingleQueueRequest();
+ return request;
+ }
+
+ RootedArrayObject queue(cx, asyncGenObj->queue());
+
+ FixedInvokeArgs<0> args(cx);
+ args.setThis(ObjectValue(*queue));
+ if (!CallJSNative(cx, array_shift, args))
+ return nullptr;
+
+ return &args.rval().toObject().as<AsyncGeneratorRequest>();
+}
+
+/* static */ AsyncGeneratorRequest*
+AsyncGeneratorObject::peekRequest(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj)
+{
+ if (asyncGenObj->isSingleQueue())
+ return asyncGenObj->singleQueueRequest();
+
+ RootedArrayObject queue(cx, asyncGenObj->queue());
+
+ RootedValue requestVal(cx);
+ if (!GetElement(cx, queue, queue, 0, &requestVal))
+ return nullptr;
+
+ return &requestVal.toObject().as<AsyncGeneratorRequest>();
+}
+
+const Class AsyncGeneratorRequest::class_ = {
+ "AsyncGeneratorRequest",
+ JSCLASS_HAS_RESERVED_SLOTS(AsyncGeneratorRequest::Slots)
+};
+
+// Async Iteration proposal 6.4.3.1.
+/* static */ AsyncGeneratorRequest*
+AsyncGeneratorRequest::create(JSContext* cx, CompletionKind completionKind_,
+ HandleValue completionValue_, HandleObject promise_)
+{
+ RootedObject obj(cx, NewNativeObjectWithGivenProto(cx, &class_, nullptr));
+ if (!obj)
+ return nullptr;
+
+ Handle<AsyncGeneratorRequest*> request = obj.as<AsyncGeneratorRequest>();
+ request->setCompletionKind(completionKind_);
+ request->setCompletionValue(completionValue_);
+ request->setPromise(promise_);
+ return request;
+}
+
+// Async Iteration proposal 6.4.3.2 steps 5.d-g.
+static MOZ_MUST_USE bool
+AsyncGeneratorReturned(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj,
+ HandleValue value)
+{
+ // Step 5.d.
+ asyncGenObj->setCompleted();
+
+ // Step 5.e (done in bytecode).
+ // Step 5.f.i (implicit).
+
+ // Step 5.g.
+ return AsyncGeneratorResolve(cx, asyncGenObj, value, true);
+}
+
+// Async Iteration proposal 6.4.3.2 steps 5.d, f.
+static MOZ_MUST_USE bool
+AsyncGeneratorThrown(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj)
+{
+ // Step 5.d.
+ asyncGenObj->setCompleted();
+
+ // Not much we can do about uncatchable exceptions, so just bail.
+ if (!cx->isExceptionPending())
+ return false;
+
+ // Step 5.f.i.
+ RootedValue value(cx);
+ if (!GetAndClearException(cx, &value))
+ return false;
+
+ // Step 5.f.ii.
+ return AsyncGeneratorReject(cx, asyncGenObj, value);
+}
+
+// Async Iteration proposal 6.4.3.5.
+MOZ_MUST_USE bool
+js::AsyncGeneratorResumeNext(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj)
+{
+ // Step 1 (implicit).
+
+ // Steps 2-3.
+ MOZ_ASSERT(!asyncGenObj->isExecuting());
+
+ // Steps 4-5.
+ if (asyncGenObj->isQueueEmpty())
+ return true;
+
+ // Steps 6-7.
+ Rooted<AsyncGeneratorRequest*> request(
+ cx, AsyncGeneratorObject::peekRequest(cx, asyncGenObj));
+ if (!request)
+ return false;
+
+ // Step 8.
+ CompletionKind completionKind = request->completionKind();
+
+ // Step 9.
+ if (completionKind != CompletionKind::Normal) {
+ // Step 9.a.
+ if (asyncGenObj->isSuspendedStart())
+ asyncGenObj->setCompleted();
+
+ // Step 9.b.
+ if (asyncGenObj->isCompleted()) {
+ // Step 9.b.i.
+ RootedValue value(cx, request->completionValue());
+ if (completionKind == CompletionKind::Return)
+ return AsyncGeneratorResolve(cx, asyncGenObj, value, true);
+ // Step 9.b.ii.
+ return AsyncGeneratorReject(cx, asyncGenObj, value);
+ }
+ } else if (asyncGenObj->isCompleted()) {
+ // Step 10.
+ return AsyncGeneratorResolve(cx, asyncGenObj, UndefinedHandleValue, true);
+ }
+
+ // Step 11.
+ MOZ_ASSERT(asyncGenObj->isSuspendedStart() || asyncGenObj->isSuspendedYield());
+
+ // Step 15 (reordered).
+ asyncGenObj->setExecuting();
+
+ RootedValue argument(cx, request->completionValue());
+
+ // Steps 12-14, 16-20.
+ return AsyncGeneratorResume(cx, asyncGenObj, completionKind, argument);
+}
+
+// Async Iteration proposal 6.2.1.2 (partially).
+// Most steps are done in generator.
+static MOZ_MUST_USE bool
+AsyncGeneratorYield(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj,
+ HandleValue value, bool done)
+{
+ // Step 6.
+ asyncGenObj->setSuspendedYield();
+
+ // Step 10.c.
+ return AsyncGeneratorResolve(cx, asyncGenObj, value, done);
+}
+
+// Async Iteration proposal 6.4.3.5 steps 12-14, 16-20.
+// Async Iteration proposal 6.2.1.2 step 10.
+// Async Iteration proposal 6.4.3.2 step 5.f-g.
+// Async Iteration proposal 5.1 steps 2-9.
+// Execution context switching is handled in generator.
+static MOZ_MUST_USE bool
+AsyncGeneratorResume(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj,
+ CompletionKind completionKind, HandleValue argument)
+{
+ RootedValue generatorVal(cx, asyncGenObj->generatorVal());
+
+ // 6.4.3.5 steps 12-14, 16-20.
+ HandlePropertyName funName = completionKind == CompletionKind::Normal
+ ? cx->names().StarGeneratorNext
+ : completionKind == CompletionKind::Throw
+ ? cx->names().StarGeneratorThrow
+ : cx->names().StarGeneratorReturn;
+ FixedInvokeArgs<1> args(cx);
+ args[0].set(argument);
+ RootedValue result(cx);
+ if (!CallSelfHostedFunction(cx, funName, generatorVal, args, &result)) {
+ // 6.4.3.2 step 5.d, f.
+ return AsyncGeneratorThrown(cx, asyncGenObj);
+ }
+
+ // The following code corresponds to the following 3 cases:
+ // * yield
+ // * await
+ // * return
+ // For await and return, property access is done on an internal result
+ // object and it's not observable.
+ // For yield, it's done on an user-provided result object, and it's
+ // observable, so perform in that order in all cases.
+
+ RootedObject resultObj(cx, &result.toObject());
+ RootedValue value(cx);
+ RootedValue doneVal(cx);
+
+ // 6.2.1.2 step 10.a.
+ if (!GetProperty(cx, resultObj, resultObj, cx->names().value, &value))
+ return false;
+
+ // 6.2.1.2 step 10.b.
+ if (!GetProperty(cx, resultObj, resultObj, cx->names().done, &doneVal))
+ return false;
+
+ // 6.2.1.2 step 10.c.
+ if (asyncGenObj->generatorObj()->isAfterYield())
+ return AsyncGeneratorYield(cx, asyncGenObj, value, ToBoolean(doneVal));
+
+ // 6.4.3.2 step 5.d-g.
+ if (ToBoolean(doneVal)) {
+ MOZ_ASSERT(!asyncGenObj->generatorObj()->isAfterAwait());
+ return AsyncGeneratorReturned(cx, asyncGenObj, value);
+ }
+
+ MOZ_ASSERT(asyncGenObj->generatorObj()->isAfterAwait());
+
+ // 5.1 steps 2-9.
+ return AsyncGeneratorAwait(cx, asyncGenObj, value);
+}
+
+static const JSFunctionSpec async_iterator_proto_methods[] = {
+ JS_SELF_HOSTED_SYM_FN(asyncIterator, "AsyncIteratorIdentity", 0, 0),
+ JS_FS_END
+};
+
+static const JSFunctionSpec async_generator_methods[] = {
+ JS_FN("next", AsyncGeneratorNext, 1, 0),
+ JS_FN("throw", AsyncGeneratorThrow, 1, 0),
+ JS_FN("return", AsyncGeneratorReturn, 1, 0),
+ JS_FS_END
+};
+
+/* static */ MOZ_MUST_USE bool
+GlobalObject::initAsyncGenerators(JSContext* cx, Handle<GlobalObject*> global)
+{
+ if (global->getReservedSlot(ASYNC_ITERATOR_PROTO).isObject())
+ return true;
+
+ // Async Iteration proposal 6.1.2 %AsyncIteratorPrototype%.
+ RootedObject asyncIterProto(cx, GlobalObject::createBlankPrototype<PlainObject>(cx, global));
+ if (!asyncIterProto)
+ return false;
+ if (!DefinePropertiesAndFunctions(cx, asyncIterProto, nullptr, async_iterator_proto_methods))
+ return false;
+
+ // Async Iteration proposal 6.4.1 %AsyncGeneratorPrototype%.
+ RootedObject asyncGenProto(
+ cx, GlobalObject::createBlankPrototypeInheriting(cx, global, &PlainObject::class_,
+ asyncIterProto));
+ if (!asyncGenProto)
+ return false;
+ if (!DefinePropertiesAndFunctions(cx, asyncGenProto, nullptr, async_generator_methods) ||
+ !DefineToStringTag(cx, asyncGenProto, cx->names().AsyncGenerator))
+ {
+ return false;
+ }
+
+ // Async Iteration proposal 6.3.3 %AsyncGenerator%.
+ RootedObject asyncGenerator(cx, NewSingletonObjectWithFunctionPrototype(cx, global));
+ if (!asyncGenerator)
+ return false;
+ if (!JSObject::setDelegate(cx, asyncGenerator))
+ return false;
+ if (!LinkConstructorAndPrototype(cx, asyncGenerator, asyncGenProto, JSPROP_READONLY,
+ JSPROP_READONLY) ||
+ !DefineToStringTag(cx, asyncGenerator, cx->names().AsyncGeneratorFunction))
+ {
+ return false;
+ }
+
+ RootedValue function(cx, global->getConstructor(JSProto_Function));
+ if (!function.toObjectOrNull())
+ return false;
+ RootedObject proto(cx, &function.toObject());
+ RootedAtom name(cx, cx->names().AsyncGeneratorFunction);
+
+ // Async Iteration proposal 6.3.2 %AsyncGeneratorFunction%.
+ RootedObject asyncGenFunction(
+ cx, NewFunctionWithProto(cx, AsyncGeneratorConstructor, 1, JSFunction::NATIVE_CTOR,
+ nullptr, name, proto, gc::AllocKind::FUNCTION, SingletonObject));
+ if (!asyncGenFunction)
+ return false;
+ if (!LinkConstructorAndPrototype(cx, asyncGenFunction, asyncGenerator,
+ JSPROP_PERMANENT | JSPROP_READONLY, JSPROP_READONLY))
+ {
+ return false;
+ }
+
+ global->setReservedSlot(ASYNC_ITERATOR_PROTO, ObjectValue(*asyncIterProto));
+ global->setReservedSlot(ASYNC_GENERATOR, ObjectValue(*asyncGenerator));
+ global->setReservedSlot(ASYNC_GENERATOR_FUNCTION, ObjectValue(*asyncGenFunction));
+ global->setReservedSlot(ASYNC_GENERATOR_PROTO, ObjectValue(*asyncGenProto));
+ return true;
+}