From d5086ac3aa308bb1ef177834366eeaf7b39bb17e Mon Sep 17 00:00:00 2001 From: Gaming4JC Date: Fri, 13 Dec 2019 21:29:23 -0500 Subject: Bug 1331092 - Part 2: Implement Async Generator except yield*. Tag #1287 --- js/public/Class.h | 2 +- js/src/builtin/AsyncIteration.js | 7 + js/src/builtin/Promise.h | 17 + js/src/frontend/BytecodeCompiler.cpp | 13 + js/src/frontend/BytecodeCompiler.h | 6 + js/src/frontend/BytecodeEmitter.cpp | 26 +- js/src/frontend/BytecodeEmitter.h | 3 +- js/src/frontend/Parser.cpp | 22 +- js/src/js.msg | 3 + js/src/jsapi.cpp | 6 + js/src/jsfun.cpp | 96 ++++- js/src/jsfun.h | 3 + js/src/jsiter.cpp | 28 +- js/src/jsiter.h | 4 +- js/src/moz.build | 2 + js/src/shell/js.cpp | 3 + js/src/tests/ecma_7/AsyncFunctions/syntax.js | 3 - js/src/vm/AsyncIteration.cpp | 564 +++++++++++++++++++++++++++ js/src/vm/AsyncIteration.h | 204 ++++++++++ js/src/vm/CommonPropertyNames.h | 3 + js/src/vm/GlobalObject.h | 30 ++ js/src/vm/Interpreter.cpp | 14 +- js/src/vm/Opcodes.h | 10 +- 23 files changed, 1014 insertions(+), 55 deletions(-) create mode 100644 js/src/builtin/AsyncIteration.js create mode 100644 js/src/vm/AsyncIteration.cpp create mode 100644 js/src/vm/AsyncIteration.h diff --git a/js/public/Class.h b/js/public/Class.h index 67c0cbca8..a7250eac0 100644 --- a/js/public/Class.h +++ b/js/public/Class.h @@ -779,7 +779,7 @@ struct JSClass { // application. #define JSCLASS_GLOBAL_APPLICATION_SLOTS 5 #define JSCLASS_GLOBAL_SLOT_COUNT \ - (JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 40) + (JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 44) #define JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(n) \ (JSCLASS_IS_GLOBAL | JSCLASS_HAS_RESERVED_SLOTS(JSCLASS_GLOBAL_SLOT_COUNT + (n))) #define JSCLASS_GLOBAL_FLAGS \ diff --git a/js/src/builtin/AsyncIteration.js b/js/src/builtin/AsyncIteration.js new file mode 100644 index 000000000..029c7c0f8 --- /dev/null +++ b/js/src/builtin/AsyncIteration.js @@ -0,0 +1,7 @@ +/* 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/. */ + +function AsyncIteratorIdentity() { + return this; +} diff --git a/js/src/builtin/Promise.h b/js/src/builtin/Promise.h index 8f716dfbc..680ab2abd 100644 --- a/js/src/builtin/Promise.h +++ b/js/src/builtin/Promise.h @@ -151,6 +151,23 @@ AsyncFunctionThrown(JSContext* cx, Handle resultPromise); MOZ_MUST_USE bool AsyncFunctionAwait(JSContext* cx, Handle resultPromise, HandleValue value); +class AsyncGeneratorObject; + +MOZ_MUST_USE bool +AsyncGeneratorAwait(JSContext* cx, Handle asyncGenObj, HandleValue value); + +MOZ_MUST_USE bool +AsyncGeneratorResolve(JSContext* cx, Handle asyncGenObj, + HandleValue value, bool done); + +MOZ_MUST_USE bool +AsyncGeneratorReject(JSContext* cx, Handle asyncGenObj, + HandleValue exception); + +MOZ_MUST_USE bool +AsyncGeneratorEnqueue(JSContext* cx, HandleValue asyncGenVal, CompletionKind completionKind, + HandleValue completionValue, MutableHandleValue result); + /** * A PromiseTask represents a task that can be dispatched to a helper thread * (via StartPromiseTask), executed (by implementing PromiseTask::execute()), diff --git a/js/src/frontend/BytecodeCompiler.cpp b/js/src/frontend/BytecodeCompiler.cpp index 7760c4b2e..ccfe3cd2e 100644 --- a/js/src/frontend/BytecodeCompiler.cpp +++ b/js/src/frontend/BytecodeCompiler.cpp @@ -729,3 +729,16 @@ frontend::CompileStandaloneAsyncFunction(JSContext* cx, MutableHandleFunction fu TraceLogger_ParserCompileFunction); return compiler.compileStandaloneFunction(fun, NotGenerator, AsyncFunction, parameterListEnd); } + +bool +frontend::CompileStandaloneAsyncGenerator(JSContext* cx, MutableHandleFunction fun, + const ReadOnlyCompileOptions& options, + JS::SourceBufferHolder& srcBuf, + Maybe parameterListEnd) +{ + RootedScope emptyGlobalScope(cx, &cx->global()->emptyGlobalScope()); + + BytecodeCompiler compiler(cx, cx->tempLifoAlloc(), options, srcBuf, emptyGlobalScope, + TraceLogger_ParserCompileFunction); + return compiler.compileStandaloneFunction(fun, StarGenerator, AsyncFunction, parameterListEnd); +} diff --git a/js/src/frontend/BytecodeCompiler.h b/js/src/frontend/BytecodeCompiler.h index 0bc1ab2ab..029fbe632 100644 --- a/js/src/frontend/BytecodeCompiler.h +++ b/js/src/frontend/BytecodeCompiler.h @@ -84,6 +84,12 @@ CompileStandaloneAsyncFunction(JSContext* cx, MutableHandleFunction fun, JS::SourceBufferHolder& srcBuf, mozilla::Maybe parameterListEnd); +MOZ_MUST_USE bool +CompileStandaloneAsyncGenerator(JSContext* cx, MutableHandleFunction fun, + const ReadOnlyCompileOptions& options, + JS::SourceBufferHolder& srcBuf, + mozilla::Maybe parameterListEnd); + MOZ_MUST_USE bool CompileAsyncFunctionBody(JSContext* cx, MutableHandleFunction fun, const ReadOnlyCompileOptions& options, diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index a98016d63..9d6a68609 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -8067,7 +8067,8 @@ BytecodeEmitter::emitFunction(ParseNode* pn, bool needsProto) MOZ_ASSERT(fun->isArrow() == (pn->getOp() == JSOP_LAMBDA_ARROW)); if (funbox->isAsync()) { MOZ_ASSERT(!needsProto); - return emitAsyncWrapper(index, funbox->needsHomeObject(), fun->isArrow()); + return emitAsyncWrapper(index, funbox->needsHomeObject(), fun->isArrow(), + fun->isStarGenerator()); } if (fun->isArrow()) { @@ -8122,8 +8123,11 @@ BytecodeEmitter::emitFunction(ParseNode* pn, bool needsProto) MOZ_ASSERT(pn->getOp() == JSOP_NOP); switchToPrologue(); if (funbox->isAsync()) { - if (!emitAsyncWrapper(index, fun->isMethod(), fun->isArrow())) + if (!emitAsyncWrapper(index, fun->isMethod(), fun->isArrow(), + fun->isStarGenerator())) + { return false; + } } else { if (!emitIndex32(JSOP_LAMBDA, index)) return false; @@ -8139,10 +8143,12 @@ BytecodeEmitter::emitFunction(ParseNode* pn, bool needsProto) // initialize the binding name of the function in the current scope. bool isAsync = funbox->isAsync(); - auto emitLambda = [index, isAsync](BytecodeEmitter* bce, const NameLocation&, bool) { + bool isStarGenerator = funbox->isStarGenerator(); + auto emitLambda = [index, isAsync, isStarGenerator](BytecodeEmitter* bce, + const NameLocation&, bool) { if (isAsync) { return bce->emitAsyncWrapper(index, /* needsHomeObject = */ false, - /* isArrow = */ false); + /* isArrow = */ false, isStarGenerator); } return bce->emitIndexOp(JSOP_LAMBDA, index); }; @@ -8177,7 +8183,8 @@ BytecodeEmitter::emitAsyncWrapperLambda(unsigned index, bool isArrow) { } bool -BytecodeEmitter::emitAsyncWrapper(unsigned index, bool needsHomeObject, bool isArrow) +BytecodeEmitter::emitAsyncWrapper(unsigned index, bool needsHomeObject, bool isArrow, + bool isStarGenerator) { // needsHomeObject can be true for propertyList for extended class. // In that case push both unwrapped and wrapped function, in order to @@ -8209,8 +8216,13 @@ BytecodeEmitter::emitAsyncWrapper(unsigned index, bool needsHomeObject, bool isA if (!emit1(JSOP_DUP)) return false; } - if (!emit1(JSOP_TOASYNC)) - return false; + if (isStarGenerator) { + if (!emit1(JSOP_TOASYNCGEN)) + return false; + } else { + if (!emit1(JSOP_TOASYNC)) + return false; + } return true; } diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index f3f78df16..67e329489 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -620,7 +620,8 @@ struct MOZ_STACK_CLASS BytecodeEmitter MOZ_MUST_USE bool emitPropIncDec(ParseNode* pn); MOZ_MUST_USE bool emitAsyncWrapperLambda(unsigned index, bool isArrow); - MOZ_MUST_USE bool emitAsyncWrapper(unsigned index, bool needsHomeObject, bool isArrow); + MOZ_MUST_USE bool emitAsyncWrapper(unsigned index, bool needsHomeObject, bool isArrow, + bool isStarGenerator); MOZ_MUST_USE bool emitComputedPropertyName(ParseNode* computedPropName); diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 3ae5890ac..7704cf65a 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -3785,10 +3785,12 @@ Parser::functionStmt(uint32_t toStringStart, YieldHandling yieldHa GeneratorKind generatorKind = NotGenerator; if (tt == TOK_MUL) { +#ifdef RELEASE_OR_BETA if (asyncKind != SyncFunction) { error(JSMSG_ASYNC_GENERATOR); return null(); } +#endif generatorKind = StarGenerator; if (!tokenStream.getToken(&tt)) return null(); @@ -3857,10 +3859,12 @@ Parser::functionExpr(uint32_t toStringStart, InvokedPrediction inv return null(); if (tt == TOK_MUL) { +#ifdef RELEASE_OR_BETA if (asyncKind != SyncFunction) { error(JSMSG_ASYNC_GENERATOR); return null(); } +#endif generatorKind = StarGenerator; if (!tokenStream.getToken(&tt)) return null(); @@ -9468,11 +9472,6 @@ Parser::propertyName(YieldHandling yieldHandling, bool isGenerator = false; bool isAsync = false; - if (ltok == TOK_MUL) { - isGenerator = true; - if (!tokenStream.getToken(<ok)) - return null(); - } if (ltok == TOK_ASYNC) { // AsyncMethod[Yield, Await]: @@ -9500,9 +9499,16 @@ Parser::propertyName(YieldHandling yieldHandling, } } - if (isAsync && isGenerator) { - error(JSMSG_ASYNC_GENERATOR); - return null(); + if (ltok == TOK_MUL) { +#ifdef RELEASE_OR_BETA + if (isAsync) { + error(JSMSG_ASYNC_GENERATOR); + return null(); + } +#endif + isGenerator = true; + if (!tokenStream.getToken(<ok)) + return null(); } propAtom.set(nullptr); diff --git a/js/src/js.msg b/js/src/js.msg index 7465129bf..2778085c7 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -593,3 +593,6 @@ MSG_DEF(JSMSG_PROMISE_ERROR_IN_WRAPPED_REJECTION_REASON,0, JSEXN_INTERNALERR, "P // Iterator MSG_DEF(JSMSG_RETURN_NOT_CALLABLE, 0, JSEXN_TYPEERR, "property 'return' of iterator is not callable") MSG_DEF(JSMSG_ITERATOR_NO_THROW, 0, JSEXN_TYPEERR, "iterator does not have a 'throw' method") + +// Async Iteration +MSG_DEF(JSMSG_NOT_AN_ASYNC_GENERATOR, 0, JSEXN_TYPEERR, "Not an async generator") diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index f9a0c6a6b..6483641f3 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -70,6 +70,7 @@ #include "js/StructuredClone.h" #include "js/Utility.h" #include "vm/AsyncFunction.h" +#include "vm/AsyncIteration.h" #include "vm/DateObject.h" #include "vm/Debugger.h" #include "vm/EnvironmentObject.h" @@ -3591,6 +3592,11 @@ CloneFunctionObject(JSContext* cx, HandleObject funobj, HandleObject env, Handle return nullptr; } + if (IsWrappedAsyncGenerator(fun)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_CLONE_OBJECT); + return nullptr; + } + if (CanReuseScriptForClone(cx->compartment(), fun, env)) { // If the script is to be reused, either the script can already handle // non-syntactic scopes, or there is only the standard global lexical diff --git a/js/src/jsfun.cpp b/js/src/jsfun.cpp index 65d44cba7..ce1ca33fe 100644 --- a/js/src/jsfun.cpp +++ b/js/src/jsfun.cpp @@ -41,6 +41,7 @@ #include "js/CallNonGenericMethod.h" #include "js/Proxy.h" #include "vm/AsyncFunction.h" +#include "vm/AsyncIteration.h" #include "vm/Debugger.h" #include "vm/GlobalObject.h" #include "vm/Interpreter.h" @@ -312,6 +313,8 @@ CallerGetterImpl(JSContext* cx, const CallArgs& args) JSFunction* callerFun = &callerObj->as(); if (IsWrappedAsyncFunction(callerFun)) callerFun = GetUnwrappedAsyncFunction(callerFun); + else if (IsWrappedAsyncGenerator(callerFun)) + callerFun = GetUnwrappedAsyncGenerator(callerFun); MOZ_ASSERT(!callerFun->isBuiltin(), "non-builtin iterator returned a builtin?"); if (callerFun->strict()) { @@ -364,13 +367,15 @@ static const JSPropertySpec function_properties[] = { static bool ResolveInterpretedFunctionPrototype(JSContext* cx, HandleFunction fun, HandleId id) { - MOZ_ASSERT(fun->isInterpreted() || fun->isAsmJSNative()); + bool isAsyncGenerator = IsWrappedAsyncGenerator(fun); + + MOZ_ASSERT_IF(!isAsyncGenerator, fun->isInterpreted() || fun->isAsmJSNative()); MOZ_ASSERT(id == NameToId(cx->names().prototype)); // Assert that fun is not a compiler-created function object, which // must never leak to script or embedding code and then be mutated. // Also assert that fun is not bound, per the ES5 15.3.4.5 ref above. - MOZ_ASSERT(!IsInternalFunctionObject(*fun)); + MOZ_ASSERT_IF(!isAsyncGenerator, !IsInternalFunctionObject(*fun)); MOZ_ASSERT(!fun->isBoundFunction()); // Make the prototype object an instance of Object with the same parent as @@ -380,7 +385,9 @@ ResolveInterpretedFunctionPrototype(JSContext* cx, HandleFunction fun, HandleId bool isStarGenerator = fun->isStarGenerator(); Rooted global(cx, &fun->global()); RootedObject objProto(cx); - if (isStarGenerator) + if (isAsyncGenerator) + objProto = GlobalObject::getOrCreateAsyncGeneratorPrototype(cx, global); + else if (isStarGenerator) objProto = GlobalObject::getOrCreateStarGeneratorObjectPrototype(cx, global); else objProto = GlobalObject::getOrCreateObjectPrototype(cx, global); @@ -396,7 +403,7 @@ ResolveInterpretedFunctionPrototype(JSContext* cx, HandleFunction fun, HandleId // non-enumerable, and writable. However, per the 15 July 2013 ES6 draft, // section 15.19.3, the .prototype of a generator function does not link // back with a .constructor. - if (!isStarGenerator) { + if (!isStarGenerator && !isAsyncGenerator) { RootedValue objVal(cx, ObjectValue(*fun)); if (!DefineProperty(cx, proto, cx->names().constructor, objVal, nullptr, nullptr, 0)) return false; @@ -446,11 +453,13 @@ fun_resolve(JSContext* cx, HandleObject obj, HandleId id, bool* resolvedp) * - Arrow functions * - Function.prototype */ - if (fun->isBuiltin()) - return true; - if (!fun->isConstructor()) { - if (!fun->isStarGenerator() && !fun->isLegacyGenerator() && !fun->isAsync()) + if (!IsWrappedAsyncGenerator(fun)) { + if (fun->isBuiltin()) return true; + if (!fun->isConstructor()) { + if (!fun->isStarGenerator() && !fun->isLegacyGenerator() && !fun->isAsync()) + return true; + } } if (!ResolveInterpretedFunctionPrototype(cx, fun, id)) @@ -946,6 +955,11 @@ js::FunctionToString(JSContext* cx, HandleFunction fun, bool prettyPrint) return FunctionToString(cx, unwrapped, prettyPrint); } + if (IsWrappedAsyncGenerator(fun)) { + RootedFunction unwrapped(cx, GetUnwrappedAsyncGenerator(fun)); + return FunctionToString(cx, unwrapped, prettyPrint); + } + StringBuffer out(cx); RootedScript script(cx); @@ -1606,10 +1620,14 @@ FunctionConstructor(JSContext* cx, const CallArgs& args, GeneratorKind generator &mutedErrors); const char* introductionType = "Function"; - if (isAsync) - introductionType = "AsyncFunction"; - else if (generatorKind != NotGenerator) + if (isAsync) { + if (isStarGenerator) + introductionType = "AsyncGenerator"; + else + introductionType = "AsyncFunction"; + } else if (generatorKind != NotGenerator) { introductionType = "GeneratorFunction"; + } const char* introducerFilename = filename; if (maybeScript && maybeScript->scriptSource()->introducerFilename()) @@ -1737,12 +1755,20 @@ FunctionConstructor(JSContext* cx, const CallArgs& args, GeneratorKind generator : SourceBufferHolder::NoOwnership; bool ok; SourceBufferHolder srcBuf(chars.begin().get(), chars.length(), ownership); - if (isAsync) - ok = frontend::CompileStandaloneAsyncFunction(cx, &fun, options, srcBuf, parameterListEnd); - else if (isStarGenerator) - ok = frontend::CompileStandaloneGenerator(cx, &fun, options, srcBuf, parameterListEnd); - else - ok = frontend::CompileStandaloneFunction(cx, &fun, options, srcBuf, parameterListEnd); + if (isAsync) { + if (isStarGenerator) { + ok = frontend::CompileStandaloneAsyncGenerator(cx, &fun, options, srcBuf, + parameterListEnd); + } else { + ok = frontend::CompileStandaloneAsyncFunction(cx, &fun, options, srcBuf, + parameterListEnd); + } + } else { + if (isStarGenerator) + ok = frontend::CompileStandaloneGenerator(cx, &fun, options, srcBuf, parameterListEnd); + else + ok = frontend::CompileStandaloneFunction(cx, &fun, options, srcBuf, parameterListEnd); + } // Step 33. args.rval().setObject(*fun); @@ -1768,7 +1794,7 @@ js::AsyncFunctionConstructor(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); - // Save the callee before its reset in FunctionConstructor(). + // Save the callee before it's reset in FunctionConstructor(). RootedObject newTarget(cx); if (args.isConstructing()) newTarget = &args.newTarget().toObject(); @@ -1800,6 +1826,40 @@ js::AsyncFunctionConstructor(JSContext* cx, unsigned argc, Value* vp) return true; } +bool +js::AsyncGeneratorConstructor(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + // Save the callee before its reset in FunctionConstructor(). + RootedObject newTarget(cx); + if (args.isConstructing()) + newTarget = &args.newTarget().toObject(); + else + newTarget = &args.callee(); + + if (!FunctionConstructor(cx, args, StarGenerator, AsyncFunction)) + return false; + + RootedObject proto(cx); + if (!GetPrototypeFromConstructor(cx, newTarget, &proto)) + return false; + + if (!proto) { + proto = GlobalObject::getOrCreateAsyncGenerator(cx, cx->global()); + if (!proto) + return false; + } + + RootedFunction unwrapped(cx, &args.rval().toObject().as()); + RootedObject wrapped(cx, WrapAsyncGeneratorWithProto(cx, unwrapped, proto)); + if (!wrapped) + return false; + + args.rval().setObject(*wrapped); + return true; +} + bool JSFunction::isBuiltinFunctionConstructor() { diff --git a/js/src/jsfun.h b/js/src/jsfun.h index 9e50387d6..ea9e7af9b 100644 --- a/js/src/jsfun.h +++ b/js/src/jsfun.h @@ -660,6 +660,9 @@ Generator(JSContext* cx, unsigned argc, Value* vp); extern bool AsyncFunctionConstructor(JSContext* cx, unsigned argc, Value* vp); +extern bool +AsyncGeneratorConstructor(JSContext* cx, unsigned argc, Value* vp); + // Allocate a new function backed by a JSNative. Note that by default this // creates a singleton object. extern JSFunction* diff --git a/js/src/jsiter.cpp b/js/src/jsiter.cpp index c4da86bdb..c58f32382 100644 --- a/js/src/jsiter.cpp +++ b/js/src/jsiter.cpp @@ -916,28 +916,30 @@ js::GetIteratorObject(JSContext* cx, HandleObject obj, uint32_t flags) return iterator; } +// ES 2017 draft 7.4.7. JSObject* -js::CreateItrResultObject(JSContext* cx, HandleValue value, bool done) +js::CreateIterResultObject(JSContext* cx, HandleValue value, bool done) { - // FIXME: We can cache the iterator result object shape somewhere. - AssertHeapIsIdle(cx); + // Step 1 (implicit). - RootedObject proto(cx, GlobalObject::getOrCreateObjectPrototype(cx, cx->global())); - if (!proto) + // Step 2. + RootedObject resultObj(cx, NewBuiltinClassInstance(cx)); + if (!resultObj) return nullptr; - RootedPlainObject obj(cx, NewObjectWithGivenProto(cx, proto)); - if (!obj) - return nullptr; - - if (!DefineProperty(cx, obj, cx->names().value, value)) + // Step 3. + if (!DefineProperty(cx, resultObj, cx->names().value, value)) return nullptr; - RootedValue doneBool(cx, BooleanValue(done)); - if (!DefineProperty(cx, obj, cx->names().done, doneBool)) + // Step 4. + if (!DefineProperty(cx, resultObj, cx->names().done, + done ? TrueHandleValue : FalseHandleValue)) + { return nullptr; + } - return obj; + // Step 5. + return resultObj; } bool diff --git a/js/src/jsiter.h b/js/src/jsiter.h index 52eb045c5..17b7df38b 100644 --- a/js/src/jsiter.h +++ b/js/src/jsiter.h @@ -216,10 +216,10 @@ ThrowStopIteration(JSContext* cx); /* * Create an object of the form { value: VALUE, done: DONE }. - * ES6 draft from 2013-09-05, section 25.4.3.4. + * ES 2017 draft 7.4.7. */ extern JSObject* -CreateItrResultObject(JSContext* cx, HandleValue value, bool done); +CreateIterResultObject(JSContext* cx, HandleValue value, bool done); extern JSObject* InitLegacyIteratorClass(JSContext* cx, HandleObject obj); diff --git a/js/src/moz.build b/js/src/moz.build index 1546b8351..d6c2a426b 100644 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -305,6 +305,7 @@ UNIFIED_SOURCES += [ 'vm/ArgumentsObject.cpp', 'vm/ArrayBufferObject.cpp', 'vm/AsyncFunction.cpp', + 'vm/AsyncIteration.cpp', 'vm/Caches.cpp', 'vm/CallNonGenericMethod.cpp', 'vm/CharacterEncoding.cpp', @@ -747,6 +748,7 @@ selfhosted.inputs = [ 'builtin/SelfHostingDefines.h', 'builtin/Utilities.js', 'builtin/Array.js', + 'builtin/AsyncIteration.js', 'builtin/Classes.js', 'builtin/Date.js', 'builtin/Error.js', diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index 51cd11fe8..33f7d6bdc 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -85,6 +85,7 @@ #include "threading/Thread.h" #include "vm/ArgumentsObject.h" #include "vm/AsyncFunction.h" +#include "vm/AsyncIteration.h" #include "vm/Compression.h" #include "vm/Debugger.h" #include "vm/HelperThreads.h" @@ -2304,6 +2305,8 @@ ValueToScript(JSContext* cx, HandleValue v, JSFunction** funp = nullptr) // Get unwrapped async function. if (IsWrappedAsyncFunction(fun)) fun = GetUnwrappedAsyncFunction(fun); + if (IsWrappedAsyncGenerator(fun)) + fun = GetUnwrappedAsyncGenerator(fun); if (!fun->isInterpreted()) { JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr, JSSMSG_SCRIPTS_ONLY); diff --git a/js/src/tests/ecma_7/AsyncFunctions/syntax.js b/js/src/tests/ecma_7/AsyncFunctions/syntax.js index 28e5febc1..0b4b50af4 100644 --- a/js/src/tests/ecma_7/AsyncFunctions/syntax.js +++ b/js/src/tests/ecma_7/AsyncFunctions/syntax.js @@ -9,9 +9,6 @@ if (typeof Reflect !== "undefined" && Reflect.parse) { assertEq(Reflect.parse("async function a() {}").body[0].async, true); assertEq(Reflect.parse("() => {}").body[0].async, undefined); - // Async generators are not allowed (with regards to spec) - assertThrows(() => Reflect.parse("async function* a() {}"), SyntaxError); - // No line terminator after async assertEq(Reflect.parse("async\nfunction a(){}").body[0].expression.name, "async"); 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()); + RootedValue unwrappedVal(cx, wrapped->getExtendedSlot(WRAPPED_ASYNC_UNWRAPPED_SLOT)); + RootedFunction unwrapped(cx, &unwrappedVal.toObject().as()); + 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 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* +js::GetUnwrappedAsyncGenerator(JSFunction* wrapped) +{ + MOZ_ASSERT(IsWrappedAsyncGenerator(wrapped)); + JSFunction* unwrapped = &wrapped->getExtendedSlot(WRAPPED_ASYNC_UNWRAPPED_SLOT) + .toObject().as(); + MOZ_ASSERT(unwrapped->isAsync()); + return unwrapped; +} + +static MOZ_MUST_USE bool +AsyncGeneratorResume(JSContext* cx, Handle asyncGenObj, + CompletionKind completionKind, HandleValue argument); + +// Async Iteration proposal 5.1.1 Await Fulfilled Functions. +MOZ_MUST_USE bool +js::AsyncGeneratorAwaitedFulfilled(JSContext* cx, Handle 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 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 +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()); + + RootedObject obj( + cx, OrdinaryCreateFromConstructor(cx, asyncGen, + GlobalObject::getOrCreateAsyncGeneratorPrototype, + &class_)); + if (!obj) + return nullptr; + + Handle asyncGenObj = obj.as(); + + // 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 asyncGenObj, + Handle 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 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(); +} + +/* static */ AsyncGeneratorRequest* +AsyncGeneratorObject::peekRequest(JSContext* cx, Handle 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(); +} + +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 request = obj.as(); + 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 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 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 asyncGenObj) +{ + // Step 1 (implicit). + + // Steps 2-3. + MOZ_ASSERT(!asyncGenObj->isExecuting()); + + // Steps 4-5. + if (asyncGenObj->isQueueEmpty()) + return true; + + // Steps 6-7. + Rooted 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 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 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 global) +{ + if (global->getReservedSlot(ASYNC_ITERATOR_PROTO).isObject()) + return true; + + // Async Iteration proposal 6.1.2 %AsyncIteratorPrototype%. + RootedObject asyncIterProto(cx, GlobalObject::createBlankPrototype(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; +} diff --git a/js/src/vm/AsyncIteration.h b/js/src/vm/AsyncIteration.h new file mode 100644 index 000000000..c0135283f --- /dev/null +++ b/js/src/vm/AsyncIteration.h @@ -0,0 +1,204 @@ +/* -*- 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/. */ + +#ifndef vm_AsyncIteration_h +#define vm_AsyncIteration_h + +#include "jscntxt.h" +#include "jsobj.h" + +#include "builtin/Promise.h" +#include "vm/GeneratorObject.h" + +namespace js { + +// Async generator consists of 2 functions, |wrapped| and |unwrapped|. +// |unwrapped| is a generator function compiled from async generator script, +// |await| behaves just like |yield| there. |unwrapped| isn't exposed to user +// script. +// |wrapped| is a native function that is the value of async generator. + +JSObject* +WrapAsyncGeneratorWithProto(JSContext* cx, HandleFunction unwrapped, HandleObject proto); + +JSObject* +WrapAsyncGenerator(JSContext* cx, HandleFunction unwrapped); + +bool +IsWrappedAsyncGenerator(JSFunction* fun); + +JSFunction* +GetWrappedAsyncGenerator(JSFunction* unwrapped); + +JSFunction* +GetUnwrappedAsyncGenerator(JSFunction* wrapped); + +MOZ_MUST_USE bool +AsyncGeneratorAwaitedFulfilled(JSContext* cx, Handle asyncGenObj, + HandleValue value); + +MOZ_MUST_USE bool +AsyncGeneratorAwaitedRejected(JSContext* cx, Handle asyncGenObj, + HandleValue reason); + +class AsyncGeneratorRequest : public NativeObject +{ + private: + enum AsyncGeneratorRequestSlots { + Slot_CompletionKind = 0, + Slot_CompletionValue, + Slot_Promise, + Slots, + }; + + void setCompletionKind(CompletionKind completionKind_) { + setFixedSlot(Slot_CompletionKind, + Int32Value(static_cast(completionKind_))); + } + void setCompletionValue(HandleValue completionValue_) { + setFixedSlot(Slot_CompletionValue, completionValue_); + } + void setPromise(HandleObject promise_) { + setFixedSlot(Slot_Promise, ObjectValue(*promise_)); + } + + public: + static const Class class_; + + static AsyncGeneratorRequest* + create(JSContext* cx, CompletionKind completionKind, HandleValue completionValue, + HandleObject promise); + + CompletionKind completionKind() const { + return static_cast(getFixedSlot(Slot_CompletionKind).toInt32()); + } + JS::Value completionValue() const { + return getFixedSlot(Slot_CompletionValue); + } + JSObject* promise() const { + return &getFixedSlot(Slot_Promise).toObject(); + } +}; + +class AsyncGeneratorObject : public NativeObject +{ + private: + enum AsyncGeneratorObjectSlots { + Slot_State = 0, + Slot_Generator, + Slot_QueueOrRequest, + Slots + }; + + enum State { + State_SuspendedStart, + State_SuspendedYield, + State_Executing, + State_Completed + }; + + State state() const { + return static_cast(getFixedSlot(Slot_State).toInt32()); + } + void setState(State state_) { + setFixedSlot(Slot_State, Int32Value(state_)); + } + + void setGenerator(const Value& value) { + setFixedSlot(Slot_Generator, value); + } + + // Queue is implemented in 2 ways. If only one request is queued ever, + // request is stored directly to the slot. Once 2 requests are queued, an + // array is created and requests are pushed into it, and the array is + // stored to the slot. + + bool isSingleQueue() const { + return getFixedSlot(Slot_QueueOrRequest).isNull() || + getFixedSlot(Slot_QueueOrRequest).toObject().is(); + } + bool isSingleQueueEmpty() const { + return getFixedSlot(Slot_QueueOrRequest).isNull(); + } + void setSingleQueueRequest(AsyncGeneratorRequest* request) { + setFixedSlot(Slot_QueueOrRequest, ObjectValue(*request)); + } + void clearSingleQueueRequest() { + setFixedSlot(Slot_QueueOrRequest, NullHandleValue); + } + AsyncGeneratorRequest* singleQueueRequest() const { + return &getFixedSlot(Slot_QueueOrRequest).toObject().as(); + } + + ArrayObject* queue() const { + return &getFixedSlot(Slot_QueueOrRequest).toObject().as(); + } + void setQueue(JSObject* queue_) { + setFixedSlot(Slot_QueueOrRequest, ObjectValue(*queue_)); + } + + public: + static const Class class_; + + static AsyncGeneratorObject* + create(JSContext* cx, HandleFunction asyncGen, HandleValue generatorVal); + + bool isSuspendedStart() const { + return state() == State_SuspendedStart; + } + bool isSuspendedYield() const { + return state() == State_SuspendedYield; + } + bool isExecuting() const { + return state() == State_Executing; + } + bool isCompleted() const { + return state() == State_Completed; + } + + void setSuspendedStart() { + setState(State_SuspendedStart); + } + void setSuspendedYield() { + setState(State_SuspendedYield); + } + void setExecuting() { + setState(State_Executing); + } + void setCompleted() { + setState(State_Completed); + } + + JS::Value generatorVal() const { + return getFixedSlot(Slot_Generator); + } + GeneratorObject* generatorObj() const { + return &getFixedSlot(Slot_Generator).toObject().as(); + } + + static MOZ_MUST_USE bool + enqueueRequest(JSContext* cx, Handle asyncGenObj, + Handle request); + + static AsyncGeneratorRequest* + dequeueRequest(JSContext* cx, Handle asyncGenObj); + + static AsyncGeneratorRequest* + peekRequest(JSContext* cx, Handle asyncGenObj); + + bool isQueueEmpty() const { + if (isSingleQueue()) + return isSingleQueueEmpty(); + return queue()->length() == 0; + } +}; + +MOZ_MUST_USE bool +AsyncGeneratorResumeNext(JSContext* cx, Handle asyncGenObj); + +} // namespace js + +#endif /* vm_AsyncIteration_h */ diff --git a/js/src/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h index 99cb02e58..4a9c03e88 100644 --- a/js/src/vm/CommonPropertyNames.h +++ b/js/src/vm/CommonPropertyNames.h @@ -30,6 +30,8 @@ macro(as, as, "as") \ macro(Async, Async, "Async") \ macro(AsyncFunction, AsyncFunction, "AsyncFunction") \ + macro(AsyncGenerator, AsyncGenerator, "AsyncGenerator") \ + macro(AsyncGeneratorFunction, AsyncGeneratorFunction, "AsyncGeneratorFunction") \ macro(AsyncWrapped, AsyncWrapped, "AsyncWrapped") \ macro(async, async, "async") \ macro(await, await, "await") \ @@ -311,6 +313,7 @@ macro(star, star, "*") \ macro(starDefaultStar, starDefaultStar, "*default*") \ macro(StarGeneratorNext, StarGeneratorNext, "StarGeneratorNext") \ + macro(StarGeneratorReturn, StarGeneratorReturn, "StarGeneratorReturn") \ macro(StarGeneratorThrow, StarGeneratorThrow, "StarGeneratorThrow") \ macro(startTimestamp, startTimestamp, "startTimestamp") \ macro(state, state, "state") \ diff --git a/js/src/vm/GlobalObject.h b/js/src/vm/GlobalObject.h index dd3f23151..ce802daba 100644 --- a/js/src/vm/GlobalObject.h +++ b/js/src/vm/GlobalObject.h @@ -100,6 +100,10 @@ class GlobalObject : public NativeObject STAR_GENERATOR_FUNCTION, ASYNC_FUNCTION_PROTO, ASYNC_FUNCTION, + ASYNC_ITERATOR_PROTO, + ASYNC_GENERATOR, + ASYNC_GENERATOR_FUNCTION, + ASYNC_GENERATOR_PROTO, MAP_ITERATOR_PROTO, SET_ITERATOR_PROTO, COLLATOR_PROTO, @@ -624,6 +628,30 @@ class GlobalObject : public NativeObject return getOrCreateObject(cx, global, ASYNC_FUNCTION, initAsyncFunction); } + static NativeObject* + getOrCreateAsyncIteratorPrototype(JSContext* cx, Handle global) + { + return MaybeNativeObject(getOrCreateObject(cx, global, ASYNC_ITERATOR_PROTO, + initAsyncGenerators)); + } + + static NativeObject* + getOrCreateAsyncGenerator(JSContext* cx, Handle global) { + return MaybeNativeObject(getOrCreateObject(cx, global, ASYNC_GENERATOR, + initAsyncGenerators)); + } + + static JSObject* + getOrCreateAsyncGeneratorFunction(JSContext* cx, Handle global) { + return getOrCreateObject(cx, global, ASYNC_GENERATOR_FUNCTION, initAsyncGenerators); + } + + static NativeObject* + getOrCreateAsyncGeneratorPrototype(JSContext* cx, Handle global) { + return MaybeNativeObject(getOrCreateObject(cx, global, ASYNC_GENERATOR_PROTO, + initAsyncGenerators)); + } + static JSObject* getOrCreateMapIteratorPrototype(JSContext* cx, Handle global) { return getOrCreateObject(cx, global, MAP_ITERATOR_PROTO, initMapIteratorProto); @@ -782,6 +810,8 @@ class GlobalObject : public NativeObject static bool initAsyncFunction(JSContext* cx, Handle global); + static bool initAsyncGenerators(JSContext* cx, Handle global); + // Implemented in builtin/MapObject.cpp. static bool initMapIteratorProto(JSContext* cx, Handle global); static bool initSetIteratorProto(JSContext* cx, Handle global); diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index 92bb18b36..4d5927324 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -39,6 +39,7 @@ #include "jit/Ion.h" #include "jit/IonAnalysis.h" #include "vm/AsyncFunction.h" +#include "vm/AsyncIteration.h" #include "vm/Debugger.h" #include "vm/GeneratorObject.h" #include "vm/Opcodes.h" @@ -1939,7 +1940,6 @@ CASE(EnableInterruptsPseudoOpcode) CASE(JSOP_NOP) CASE(JSOP_NOP_DESTRUCTURING) CASE(JSOP_UNUSED126) -CASE(JSOP_UNUSED192) CASE(JSOP_UNUSED210) CASE(JSOP_UNUSED211) CASE(JSOP_TRY_DESTRUCTURING_ITERCLOSE) @@ -3584,6 +3584,18 @@ CASE(JSOP_TOASYNC) } END_CASE(JSOP_TOASYNC) +CASE(JSOP_TOASYNCGEN) +{ + ReservedRooted unwrapped(&rootFunction0, + ®S.sp[-1].toObject().as()); + JSObject* wrapped = WrapAsyncGenerator(cx, unwrapped); + if (!wrapped) + goto error; + + REGS.sp[-1].setObject(*wrapped); +} +END_CASE(JSOP_TOASYNCGEN) + CASE(JSOP_SETFUNNAME) { MOZ_ASSERT(REGS.stackDepth() >= 2); diff --git a/js/src/vm/Opcodes.h b/js/src/vm/Opcodes.h index 583d32beb..fc7fbbe66 100644 --- a/js/src/vm/Opcodes.h +++ b/js/src/vm/Opcodes.h @@ -1952,7 +1952,15 @@ * Stack: this => this */ \ macro(JSOP_CHECKTHISREINIT,191,"checkthisreinit",NULL,1, 1, 1, JOF_BYTE) \ - macro(JSOP_UNUSED192, 192,"unused192", NULL, 1, 0, 0, JOF_BYTE) \ + /* + * Pops the top of stack value as 'unwrapped', converts it to async + * generator 'wrapped', and pushes 'wrapped' back on the stack. + * Category: Statements + * Type: Generator + * Operands: + * Stack: unwrapped => wrapped + */ \ + macro(JSOP_TOASYNCGEN, 192, "toasyncgen", NULL, 1, 1, 1, JOF_BYTE) \ \ /* * Pops the top two values on the stack as 'propval' and 'obj', pushes -- cgit v1.2.3