summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGaming4JC <g4jc@hyperbola.info>2019-12-13 21:29:23 -0500
committerGaming4JC <g4jc@hyperbola.info>2019-12-17 06:25:27 -0500
commitd5086ac3aa308bb1ef177834366eeaf7b39bb17e (patch)
tree9b2c84158cf996b388bcb191130a5062fd7d6992
parent82f9efff935a4ac4379bdfddddc84aae84fc869e (diff)
downloaduxp-d5086ac3aa308bb1ef177834366eeaf7b39bb17e.tar.gz
Bug 1331092 - Part 2: Implement Async Generator except yield*.
Tag #1287
-rw-r--r--js/public/Class.h2
-rw-r--r--js/src/builtin/AsyncIteration.js7
-rw-r--r--js/src/builtin/Promise.h17
-rw-r--r--js/src/frontend/BytecodeCompiler.cpp13
-rw-r--r--js/src/frontend/BytecodeCompiler.h6
-rw-r--r--js/src/frontend/BytecodeEmitter.cpp26
-rw-r--r--js/src/frontend/BytecodeEmitter.h3
-rw-r--r--js/src/frontend/Parser.cpp22
-rw-r--r--js/src/js.msg3
-rw-r--r--js/src/jsapi.cpp6
-rw-r--r--js/src/jsfun.cpp96
-rw-r--r--js/src/jsfun.h3
-rw-r--r--js/src/jsiter.cpp28
-rw-r--r--js/src/jsiter.h4
-rw-r--r--js/src/moz.build2
-rw-r--r--js/src/shell/js.cpp3
-rw-r--r--js/src/tests/ecma_7/AsyncFunctions/syntax.js3
-rw-r--r--js/src/vm/AsyncIteration.cpp564
-rw-r--r--js/src/vm/AsyncIteration.h204
-rw-r--r--js/src/vm/CommonPropertyNames.h3
-rw-r--r--js/src/vm/GlobalObject.h30
-rw-r--r--js/src/vm/Interpreter.cpp14
-rw-r--r--js/src/vm/Opcodes.h10
23 files changed, 1014 insertions, 55 deletions
diff --git a/js/public/Class.h b/js/public/Class.h
index 67c0cbca85..a7250eac05 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 0000000000..029c7c0f81
--- /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 8f716dfbc5..680ab2abd9 100644
--- a/js/src/builtin/Promise.h
+++ b/js/src/builtin/Promise.h
@@ -151,6 +151,23 @@ AsyncFunctionThrown(JSContext* cx, Handle<PromiseObject*> resultPromise);
MOZ_MUST_USE bool
AsyncFunctionAwait(JSContext* cx, Handle<PromiseObject*> resultPromise, HandleValue value);
+class AsyncGeneratorObject;
+
+MOZ_MUST_USE bool
+AsyncGeneratorAwait(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj, HandleValue value);
+
+MOZ_MUST_USE bool
+AsyncGeneratorResolve(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj,
+ HandleValue value, bool done);
+
+MOZ_MUST_USE bool
+AsyncGeneratorReject(JSContext* cx, Handle<AsyncGeneratorObject*> 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 7760c4b2eb..ccfe3cd2e3 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<uint32_t> 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 0bc1ab2abf..029fbe632f 100644
--- a/js/src/frontend/BytecodeCompiler.h
+++ b/js/src/frontend/BytecodeCompiler.h
@@ -85,6 +85,12 @@ CompileStandaloneAsyncFunction(JSContext* cx, MutableHandleFunction fun,
mozilla::Maybe<uint32_t> parameterListEnd);
MOZ_MUST_USE bool
+CompileStandaloneAsyncGenerator(JSContext* cx, MutableHandleFunction fun,
+ const ReadOnlyCompileOptions& options,
+ JS::SourceBufferHolder& srcBuf,
+ mozilla::Maybe<uint32_t> parameterListEnd);
+
+MOZ_MUST_USE bool
CompileAsyncFunctionBody(JSContext* cx, MutableHandleFunction fun,
const ReadOnlyCompileOptions& options,
Handle<PropertyNameVector> formals, JS::SourceBufferHolder& srcBuf);
diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp
index a98016d630..9d6a686095 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 f3f78df164..67e3294890 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 3ae5890ac5..7704cf65a5 100644
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -3785,10 +3785,12 @@ Parser<ParseHandler>::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<ParseHandler>::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<ParseHandler>::propertyName(YieldHandling yieldHandling,
bool isGenerator = false;
bool isAsync = false;
- if (ltok == TOK_MUL) {
- isGenerator = true;
- if (!tokenStream.getToken(&ltok))
- return null();
- }
if (ltok == TOK_ASYNC) {
// AsyncMethod[Yield, Await]:
@@ -9500,9 +9499,16 @@ Parser<ParseHandler>::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(&ltok))
+ return null();
}
propAtom.set(nullptr);
diff --git a/js/src/js.msg b/js/src/js.msg
index 7465129bfb..2778085c7e 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 f9a0c6a6bc..6483641f32 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 65d44cba7a..ce1ca33fe0 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<JSFunction>();
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<GlobalObject*> 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();
@@ -1801,6 +1827,40 @@ js::AsyncFunctionConstructor(JSContext* cx, unsigned argc, Value* vp)
}
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<JSFunction>());
+ RootedObject wrapped(cx, WrapAsyncGeneratorWithProto(cx, unwrapped, proto));
+ if (!wrapped)
+ return false;
+
+ args.rval().setObject(*wrapped);
+ return true;
+}
+
+bool
JSFunction::isBuiltinFunctionConstructor()
{
return maybeNative() == Function || maybeNative() == Generator;
diff --git a/js/src/jsfun.h b/js/src/jsfun.h
index 9e50387d63..ea9e7af9bb 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 c4da86bdb7..c58f323825 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<PlainObject>(cx));
+ if (!resultObj)
return nullptr;
- RootedPlainObject obj(cx, NewObjectWithGivenProto<PlainObject>(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 52eb045c5b..17b7df38bf 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 1546b8351e..d6c2a426b8 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 51cd11fe83..33f7d6bdcc 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 28e5febc1b..0b4b50af4f 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 0000000000..04effe1b10
--- /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;
+}
diff --git a/js/src/vm/AsyncIteration.h b/js/src/vm/AsyncIteration.h
new file mode 100644
index 0000000000..c0135283fa
--- /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<AsyncGeneratorObject*> asyncGenObj,
+ HandleValue value);
+
+MOZ_MUST_USE bool
+AsyncGeneratorAwaitedRejected(JSContext* cx, Handle<AsyncGeneratorObject*> 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<int32_t>(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<CompletionKind>(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<State>(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<AsyncGeneratorRequest>();
+ }
+ 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<AsyncGeneratorRequest>();
+ }
+
+ ArrayObject* queue() const {
+ return &getFixedSlot(Slot_QueueOrRequest).toObject().as<ArrayObject>();
+ }
+ 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<GeneratorObject>();
+ }
+
+ static MOZ_MUST_USE bool
+ enqueueRequest(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj,
+ Handle<AsyncGeneratorRequest*> request);
+
+ static AsyncGeneratorRequest*
+ dequeueRequest(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj);
+
+ static AsyncGeneratorRequest*
+ peekRequest(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj);
+
+ bool isQueueEmpty() const {
+ if (isSingleQueue())
+ return isSingleQueueEmpty();
+ return queue()->length() == 0;
+ }
+};
+
+MOZ_MUST_USE bool
+AsyncGeneratorResumeNext(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj);
+
+} // namespace js
+
+#endif /* vm_AsyncIteration_h */
diff --git a/js/src/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h
index 99cb02e586..4a9c03e884 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 dd3f231517..ce802daba9 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<GlobalObject*> global)
+ {
+ return MaybeNativeObject(getOrCreateObject(cx, global, ASYNC_ITERATOR_PROTO,
+ initAsyncGenerators));
+ }
+
+ static NativeObject*
+ getOrCreateAsyncGenerator(JSContext* cx, Handle<GlobalObject*> global) {
+ return MaybeNativeObject(getOrCreateObject(cx, global, ASYNC_GENERATOR,
+ initAsyncGenerators));
+ }
+
+ static JSObject*
+ getOrCreateAsyncGeneratorFunction(JSContext* cx, Handle<GlobalObject*> global) {
+ return getOrCreateObject(cx, global, ASYNC_GENERATOR_FUNCTION, initAsyncGenerators);
+ }
+
+ static NativeObject*
+ getOrCreateAsyncGeneratorPrototype(JSContext* cx, Handle<GlobalObject*> global) {
+ return MaybeNativeObject(getOrCreateObject(cx, global, ASYNC_GENERATOR_PROTO,
+ initAsyncGenerators));
+ }
+
static JSObject*
getOrCreateMapIteratorPrototype(JSContext* cx, Handle<GlobalObject*> global) {
return getOrCreateObject(cx, global, MAP_ITERATOR_PROTO, initMapIteratorProto);
@@ -782,6 +810,8 @@ class GlobalObject : public NativeObject
static bool initAsyncFunction(JSContext* cx, Handle<GlobalObject*> global);
+ static bool initAsyncGenerators(JSContext* cx, Handle<GlobalObject*> global);
+
// Implemented in builtin/MapObject.cpp.
static bool initMapIteratorProto(JSContext* cx, Handle<GlobalObject*> global);
static bool initSetIteratorProto(JSContext* cx, Handle<GlobalObject*> global);
diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp
index 92bb18b367..4d59273243 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<JSFunction*> unwrapped(&rootFunction0,
+ &REGS.sp[-1].toObject().as<JSFunction>());
+ 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 583d32beb1..fc7fbbe662 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