diff --git a/js/src/builtin/Module.js b/js/src/builtin/Module.js index f3df7d3fdea6..d5a04d916191 100644 --- a/js/src/builtin/Module.js +++ b/js/src/builtin/Module.js @@ -586,12 +586,33 @@ function ModuleEvaluate() ThrowInternalError(JSMSG_BAD_MODULE_STATUS); } + // Top-level Await Step 4 + if (module.status === MODULE_STATUS_EVALUATED) { + module = GetAsyncCycleRoot(module); + } + + // Top-level Await Step 5 + if (module.topLevelCapability) { + return module.topLevelCapability; + } + + let capability = CreateTopLevelCapability(module); + // Step 4 let stack = []; // Steps 5-6 try { InnerModuleEvaluation(module, stack, 0); + if (!module.asyncEvaluating) { + ModuleTopLevelCapabilityResolve(module); + } + // Steps 7-8 + assert(module.status === MODULE_STATUS_EVALUATED, + "Bad module status after successful evaluation"); + assert(stack.length === 0, + "Stack should be empty after successful evaluation"); + } catch (error) { for (let i = 0; i < stack.length; i++) { let m = stack[i]; @@ -607,17 +628,11 @@ function ModuleEvaluate() assert(module.status === MODULE_STATUS_EVALUATED_ERROR, "Bad module status after failed evaluation"); - throw error; + ModuleTopLevelCapabilityReject(module, error); } - // Steps 7-8 - assert(module.status === MODULE_STATUS_EVALUATED, - "Bad module status after successful evaluation"); - assert(stack.length === 0, - "Stack should be empty after successful evaluation"); - // Step 9 - return undefined; + return capability; } // https://tc39.es/ecma262/#sec-innermoduleevaluation @@ -648,6 +663,7 @@ function InnerModuleEvaluation(module, stack, index) // Steps 6-8 UnsafeSetReservedSlot(module, MODULE_OBJECT_DFS_INDEX_SLOT, index); UnsafeSetReservedSlot(module, MODULE_OBJECT_DFS_ANCESTOR_INDEX_SLOT, index); + UnsafeSetReservedSlot(module, MODULE_OBJECT_PENDING_ASYNC_DEPENDENCIES_SLOT, 0); index++; // Step 9 @@ -677,11 +693,32 @@ function InnerModuleEvaluation(module, stack, index) UnsafeSetReservedSlot(module, MODULE_OBJECT_DFS_ANCESTOR_INDEX_SLOT, std_Math_min(module.dfsAncestorIndex, requiredModule.dfsAncestorIndex)); + } else { + requiredModule = GetAsyncCycleRoot(requiredModule); + assert(requiredModule.status === MODULE_STATUS_EVALUATED, + `Bad module status in InnerModuleEvaluation: ${requiredModule.status}`); + if (requiredModule.evaluationError) { + throw GetModuleEvaluationError(module); + } + } + if (requiredModule.asyncEvaluating) { + UnsafeSetReservedSlot(module, + MODULE_OBJECT_PENDING_ASYNC_DEPENDENCIES_SLOT, + module.pendingAsyncDependencies + 1); + AppendAsyncParentModule(requiredModule, module); } } - // Step 11 - ExecuteModule(module); + if (module.pendingAsyncDependencies > 0) { + UnsafeSetReservedSlot(module, MODULE_OBJECT_ASYNC_EVALUATING_SLOT, true); + } else { + if (module.async) { + ExecuteAsyncModule(module); + } else { + // Step 11 + ExecuteModule(module); + } + } // Step 12 assert(CountArrayValues(stack, module) === 1, @@ -703,3 +740,17 @@ function InnerModuleEvaluation(module, stack, index) // Step 15 return index; } + +// https://tc39.es/proposal-top-level-await/#sec-execute-async-module +function ExecuteAsyncModule(module) { + // Steps 1-3. + assert(module.status == MODULE_STATUS_EVALUATING || + module.status == MODULE_STATUS_EVALUATED, "bad status for async module"); + assert(module.async, "module is not async"); + UnsafeSetReservedSlot(module, MODULE_OBJECT_ASYNC_EVALUATING_SLOT, true); + + // Step 4-11 done in AsyncAwait opcode + + ExecuteModule(module); +} + diff --git a/js/src/builtin/ModuleObject.cpp b/js/src/builtin/ModuleObject.cpp index 8d22fb4b3656..45cd5d17ff36 100644 --- a/js/src/builtin/ModuleObject.cpp +++ b/js/src/builtin/ModuleObject.cpp @@ -31,6 +31,7 @@ #include "vm/JSObject-inl.h" #include "vm/JSScript-inl.h" +#include "vm/List-inl.h" #include "vm/NativeObject-inl.h" using namespace js; @@ -835,8 +836,13 @@ void ModuleObject::initFunctionDeclarations( *functionDeclarations() = std::move(decls); } -void ModuleObject::initAsyncSlots(JSContext* cx, bool isAsync) { +bool ModuleObject::initAsyncSlots(JSContext* cx, bool isAsync, + HandleObject asyncParentModulesList) { initReservedSlot(AsyncSlot, BooleanValue(isAsync)); + initReservedSlot(AsyncEvaluatingSlot, BooleanValue(false)); + initReservedSlot(AsyncParentModulesSlot, + ObjectValue(*asyncParentModulesList)); + return true; } void ModuleObject::initScriptSlots(HandleScript script) { @@ -967,10 +973,73 @@ bool ModuleObject::isAsync() const { return getReservedSlot(AsyncSlot).toBoolean(); } +bool ModuleObject::isAsyncEvaluating() const { + return getReservedSlot(AsyncEvaluatingSlot).toBoolean(); +} + +void ModuleObject::setAsyncEvaluating(bool isEvaluating) { + return setReservedSlot(AsyncEvaluatingSlot, BooleanValue(isEvaluating)); +} + +uint32_t ModuleObject::dfsIndex() const { + return getReservedSlot(DFSIndexSlot).toInt32(); +} + +uint32_t ModuleObject::dfsAncestorIndex() const { + return getReservedSlot(DFSAncestorIndexSlot).toInt32(); +} + +JSObject* ModuleObject::topLevelCapability() const { + return &getReservedSlot(TopLevelCapabilitySlot).toObject(); +} + +PromiseObject* ModuleObject::createTopLevelCapability( + JSContext* cx, HandleModuleObject module) { + MOZ_ASSERT(module->getReservedSlot(TopLevelCapabilitySlot).isUndefined()); + Rooted resultPromise(cx, CreatePromiseObjectForAsync(cx)); + if (!resultPromise) { + return nullptr; + } + module->setInitialTopLevelCapability(resultPromise); + return resultPromise; +} + +void ModuleObject::setInitialTopLevelCapability(HandleObject promiseObj) { + initReservedSlot(TopLevelCapabilitySlot, ObjectValue(*promiseObj)); +} + +inline ListObject* ModuleObject::asyncParentModules() const { + return &getReservedSlot(AsyncParentModulesSlot).toObject().as(); +} + +bool ModuleObject::appendAsyncParentModule(JSContext* cx, + HandleModuleObject self, + HandleModuleObject parent) { + Rooted parentValue(cx, ObjectValue(*parent)); + return self->asyncParentModules()->append(cx, parentValue); +} + +uint32_t ModuleObject::pendingAsyncDependencies() const { + return getReservedSlot(PendingAsyncDependenciesSlot).toInt32(); +} + +void ModuleObject::setPendingAsyncDependencies(uint32_t newValue) { + return setReservedSlot(PendingAsyncDependenciesSlot, NumberValue(newValue)); +} + +bool ModuleObject::hasTopLevelCapability() const { + return !getReservedSlot(TopLevelCapabilitySlot).isUndefined(); +} + bool ModuleObject::hadEvaluationError() const { return status() == MODULE_STATUS_EVALUATED_ERROR; } +void ModuleObject::setEvaluationError(HandleValue newValue) { + setReservedSlot(StatusSlot, Int32Value(MODULE_STATUS_EVALUATED_ERROR)); + return setReservedSlot(EvaluationErrorSlot, newValue); +} + Value ModuleObject::evaluationError() const { MOZ_ASSERT(hadEvaluationError()); return getReservedSlot(EvaluationErrorSlot); @@ -1053,7 +1122,8 @@ bool ModuleObject::instantiateFunctionDeclarations(JSContext* cx, bool ModuleObject::execute(JSContext* cx, HandleModuleObject self, MutableHandleValue rval) { #ifdef DEBUG - MOZ_ASSERT(self->status() == MODULE_STATUS_EVALUATING); + MOZ_ASSERT(self->status() == MODULE_STATUS_EVALUATING || + self->status() == MODULE_STATUS_EVALUATED); if (!AssertFrozen(cx, self)) { return false; } @@ -1158,6 +1228,13 @@ DEFINE_GETTER_FUNCTIONS(ModuleObject, starExportEntries, StarExportEntriesSlot) DEFINE_GETTER_FUNCTIONS(ModuleObject, dfsIndex, DFSIndexSlot) DEFINE_GETTER_FUNCTIONS(ModuleObject, dfsAncestorIndex, DFSAncestorIndexSlot) DEFINE_GETTER_FUNCTIONS(ModuleObject, async, AsyncSlot) +DEFINE_GETTER_FUNCTIONS(ModuleObject, topLevelCapability, + TopLevelCapabilitySlot) +DEFINE_GETTER_FUNCTIONS(ModuleObject, asyncEvaluating, AsyncEvaluatingSlot) +DEFINE_GETTER_FUNCTIONS(ModuleObject, asyncParentModules, + AsyncParentModulesSlot) +DEFINE_GETTER_FUNCTIONS(ModuleObject, pendingAsyncDependencies, + PendingAsyncDependenciesSlot) /* static */ bool GlobalObject::initModuleProto(JSContext* cx, @@ -1175,6 +1252,11 @@ bool GlobalObject::initModuleProto(JSContext* cx, JS_PSG("dfsIndex", ModuleObject_dfsIndexGetter, 0), JS_PSG("dfsAncestorIndex", ModuleObject_dfsAncestorIndexGetter, 0), JS_PSG("async", ModuleObject_asyncGetter, 0), + JS_PSG("topLevelCapability", ModuleObject_topLevelCapabilityGetter, 0), + JS_PSG("asyncEvaluating", ModuleObject_asyncEvaluatingGetter, 0), + JS_PSG("asyncParentModules", ModuleObject_asyncParentModulesGetter, 0), + JS_PSG("pendingAsyncDependencies", + ModuleObject_pendingAsyncDependenciesGetter, 0), JS_PS_END}; static const JSFunctionSpec protoFunctions[] = { @@ -1420,6 +1502,15 @@ bool frontend::StencilModuleMetadata::initModule( } module->initFunctionDeclarations(std::move(functionDeclsCopy)); + Rooted asyncParentModulesList(cx, ListObject::create(cx)); + if (!asyncParentModulesList) { + return false; + } + + if (!module->initAsyncSlots(cx, isAsync, asyncParentModulesList)) { + return false; + } + module->initImportExportData( requestedModulesObject, importEntriesObject, localExportEntriesObject, indirectExportEntriesObject, starExportEntriesObject); @@ -1845,6 +1936,193 @@ JSObject* js::CallModuleResolveHook(JSContext* cx, return result; } +// https://tc39.es/proposal-top-level-await/#sec-getasynccycleroot +ModuleObject* js::GetAsyncCycleRoot(ModuleObject* module) { + // Step 1. + MOZ_ASSERT(module->status() == MODULE_STATUS_EVALUATED); + + // Step 2. + if (module->asyncParentModules()->empty()) { + return module; + } + + // Step 3. + ModuleObject* currentModule = module; + while (currentModule->dfsIndex() > currentModule->dfsAncestorIndex()) { + MOZ_ASSERT(!currentModule->asyncParentModules()->empty()); + ModuleObject* nextCycleModule = ¤tModule->asyncParentModules() + ->get(0) + .toObject() + .as(); + MOZ_ASSERT(nextCycleModule->dfsAncestorIndex() <= + currentModule->dfsAncestorIndex()); + currentModule = nextCycleModule; + } + + // Step 4. + MOZ_ASSERT(currentModule->dfsIndex() == currentModule->dfsAncestorIndex()); + + // Step 5. + return currentModule; +} + +bool js::AsyncModuleExecutionFulfilledHandler(JSContext* cx, unsigned argc, + Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + JSFunction& func = args.callee().as(); + + Rooted module( + cx, &func.getExtendedSlot(FunctionExtended::MODULE_SLOT) + .toObject() + .as()); + AsyncModuleExecutionFulfilled(cx, module); + args.rval().setUndefined(); + return true; +} + +bool js::AsyncModuleExecutionRejectedHandler(JSContext* cx, unsigned argc, + Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + JSFunction& func = args.callee().as(); + Rooted module( + cx, &func.getExtendedSlot(FunctionExtended::MODULE_SLOT) + .toObject() + .as()); + AsyncModuleExecutionRejected(cx, module, args.get(0)); + args.rval().setUndefined(); + return true; +} + +// Top Level Await +// https://tc39.es/proposal-top-level-await/#sec-asyncmodulexecutionfulfilled +void js::AsyncModuleExecutionFulfilled(JSContext* cx, + HandleModuleObject module) { + // Step 1. + MOZ_ASSERT(module->status() == MODULE_STATUS_EVALUATED); + + // Step 2. + if (!module->isAsyncEvaluating()) { + MOZ_ASSERT(module->hadEvaluationError()); + return; + } + + // Step 3. + MOZ_ASSERT(!module->hadEvaluationError()); + + // Step 4. + module->setAsyncEvaluating(false); + + // Step 5. + uint32_t length = module->asyncParentModules()->length(); + Rooted m(cx); + Rooted cycleRoot(cx); + RootedValue result(cx); + for (uint32_t i = 0; i < length; i++) { + m = &module->asyncParentModules()->get(i).toObject().as(); + + if (module->dfsIndex() != module->dfsAncestorIndex()) { + MOZ_ASSERT(m->dfsAncestorIndex() <= module->dfsAncestorIndex()); + } + + m->setPendingAsyncDependencies(m->pendingAsyncDependencies() - 1); + + if (m->pendingAsyncDependencies() == 0 && !m->hadEvaluationError()) { + MOZ_ASSERT(m->isAsyncEvaluating()); + + cycleRoot = GetAsyncCycleRoot(m); + + if (cycleRoot->hadEvaluationError()) { + return; + } + + if (m->isAsync()) { + // Steps for ExecuteAsyncModule + MOZ_ASSERT(m->status() == MODULE_STATUS_EVALUATING || + m->status() == MODULE_STATUS_EVALUATED); + MOZ_ASSERT(m->isAsync()); + MOZ_ASSERT(m->isAsyncEvaluating()); + ModuleObject::execute(cx, m, &result); + } else { + if (ModuleObject::execute(cx, m, &result)) { + AsyncModuleExecutionFulfilled(cx, m); + } else { + AsyncModuleExecutionRejected(cx, m, result); + } + } + } + } + + // Step 6. + if (module->hasTopLevelCapability()) { + MOZ_ASSERT(module->dfsIndex() == module->dfsAncestorIndex()); + ModuleObject::topLevelCapabilityResolve(cx, module); + } + + // Return undefined. +} + +// https://tc39.es/proposal-top-level-await/#sec-asyncmodulexecutionrejected +void js::AsyncModuleExecutionRejected(JSContext* cx, HandleModuleObject module, + HandleValue error) { + // Step 1. + MOZ_ASSERT(module->status() == MODULE_STATUS_EVALUATED); + + // Step 2. + if (!module->isAsyncEvaluating()) { + MOZ_ASSERT(module->hadEvaluationError()); + return; + } + + // Step 3. + MOZ_ASSERT(!module->hadEvaluationError()); + + // Step 4. + module->setEvaluationError(error); + + // Step 5. + module->setAsyncEvaluating(false); + + // Step 6. + uint32_t length = module->asyncParentModules()->length(); + Rooted parent(cx); + for (uint32_t i = 0; i < length; i++) { + parent = + &module->asyncParentModules()->get(i).toObject().as(); + if (module->dfsIndex() != module->dfsAncestorIndex()) { + MOZ_ASSERT(parent->dfsAncestorIndex() == module->dfsAncestorIndex()); + } + AsyncModuleExecutionRejected(cx, parent, error); + } + + // Step 7. + if (module->hasTopLevelCapability()) { + MOZ_ASSERT(module->dfsIndex() == module->dfsAncestorIndex()); + ModuleObject::topLevelCapabilityReject(cx, module, error); + } + + // Return undefined. +} + +bool ModuleObject::topLevelCapabilityResolve(JSContext* cx, + HandleModuleObject module) { + RootedValue rval(cx); + Rooted promise( + cx, &module->topLevelCapability()->as()); + return AsyncFunctionReturned(cx, promise, rval); +} + +bool ModuleObject::topLevelCapabilityReject(JSContext* cx, + HandleModuleObject module, + HandleValue error) { + Rooted promise( + cx, &module->topLevelCapability()->as()); + if (!AsyncFunctionThrown(cx, promise, error)) { + return false; + } + MOZ_ASSERT(promise->state() == JS::PromiseState::Rejected); + return true; +} + JSObject* js::StartDynamicModuleImport(JSContext* cx, HandleScript script, HandleValue specifierArg) { RootedObject promiseConstructor(cx, JS::GetPromiseConstructor(cx)); @@ -2230,7 +2508,12 @@ XDRResult js::XDRModuleObject(XDRState* xdr, MOZ_TRY(xdr->codeUint8(&isAsyncModule)); if (mode == XDR_DECODE) { - module->initAsyncSlots(cx, isAsyncModule == 1); + Rooted asyncParentModulesList(cx, ListObject::create(cx)); + if (!asyncParentModulesList) { + return xdr->fail(JS::TranscodeResult_Throw); + } + + module->initAsyncSlots(cx, isAsyncModule == 1, asyncParentModulesList); } modp.set(module); diff --git a/js/src/builtin/ModuleObject.h b/js/src/builtin/ModuleObject.h index 753dc26463bd..eb08e1a406f1 100644 --- a/js/src/builtin/ModuleObject.h +++ b/js/src/builtin/ModuleObject.h @@ -19,7 +19,9 @@ #include "js/Modules.h" #include "js/UniquePtr.h" #include "vm/JSAtom.h" +#include "vm/List.h" #include "vm/NativeObject.h" +#include "vm/PromiseObject.h" // js::PromiseObject #include "vm/ProxyObject.h" namespace js { @@ -249,6 +251,10 @@ class ModuleObject : public NativeObject { DFSIndexSlot, DFSAncestorIndexSlot, AsyncSlot, + AsyncEvaluatingSlot, + TopLevelCapabilitySlot, + AsyncParentModulesSlot, + PendingAsyncDependenciesSlot, SlotCount }; @@ -262,6 +268,14 @@ class ModuleObject : public NativeObject { "DFSIndexSlot must match self-hosting define"); static_assert(DFSAncestorIndexSlot == MODULE_OBJECT_DFS_ANCESTOR_INDEX_SLOT, "DFSAncestorIndexSlot must match self-hosting define"); + static_assert(AsyncEvaluatingSlot == MODULE_OBJECT_ASYNC_EVALUATING_SLOT, + "AsyncEvaluatingSlot must match self-hosting define"); + static_assert(TopLevelCapabilitySlot == + MODULE_OBJECT_TOP_LEVEL_CAPABILITY_SLOT, + "topLevelCapabilitySlot must match self-hosting define"); + static_assert(PendingAsyncDependenciesSlot == + MODULE_OBJECT_PENDING_ASYNC_DEPENDENCIES_SLOT, + "PendingAsyncDependenciesSlot must match self-hosting define"); static const JSClass class_; @@ -294,7 +308,8 @@ class ModuleObject : public NativeObject { ModuleEnvironmentObject* environment() const; ModuleNamespaceObject* namespace_(); ModuleStatus status() const; - bool isAsync() const; + uint32_t dfsIndex() const; + uint32_t dfsAncestorIndex() const; bool hadEvaluationError() const; Value evaluationError() const; JSObject* metaObject() const; @@ -306,6 +321,28 @@ class ModuleObject : public NativeObject { ArrayObject& starExportEntries() const; IndirectBindingMap& importBindings(); + static PromiseObject* createTopLevelCapability(JSContext* cx, + HandleModuleObject module); + bool isAsync() const; + void setAsync(bool isAsync); + bool isAsyncEvaluating() const; + void setAsyncEvaluating(bool isEvaluating); + void setEvaluationError(HandleValue newValue); + void setPendingAsyncDependencies(uint32_t newValue); + void setInitialTopLevelCapability(HandleObject promiseObj); + bool hasTopLevelCapability() const; + JSObject* topLevelCapability() const; + ListObject* asyncParentModules() const; + uint32_t pendingAsyncDependencies() const; + + static bool appendAsyncParentModule(JSContext* cx, HandleModuleObject self, + HandleModuleObject parent); + + static bool topLevelCapabilityResolve(JSContext* cx, + HandleModuleObject module); + static bool topLevelCapabilityReject(JSContext* cx, HandleModuleObject module, + HandleValue error); + static bool Instantiate(JSContext* cx, HandleModuleObject self); static bool Evaluate(JSContext* cx, HandleModuleObject self); @@ -332,7 +369,8 @@ class ModuleObject : public NativeObject { frontend::FunctionDeclarationVector* functionDeclarations(); void initFunctionDeclarations(frontend::FunctionDeclarationVector&& decls); - void initAsyncSlots(JSContext* cx, bool isAsync); + bool initAsyncSlots(JSContext* cx, bool isAsync, + HandleObject asyncParentModulesList); private: static const JSClassOps classOps_; @@ -348,6 +386,24 @@ JSObject* GetOrCreateModuleMetaObject(JSContext* cx, HandleObject module); JSObject* CallModuleResolveHook(JSContext* cx, HandleValue referencingPrivate, HandleString specifier); +// https://tc39.es/proposal-top-level-await/#sec-getasynccycleroot +ModuleObject* GetAsyncCycleRoot(ModuleObject* module); + +// https://tc39.es/proposal-top-level-await/#sec-asyncmodulexecutionfulfilled +void AsyncModuleExecutionFulfilled(JSContext* cx, HandleModuleObject module); + +// https://tc39.es/proposal-top-level-await/#sec-asyncmodulexecutionrejected +void AsyncModuleExecutionRejected(JSContext* cx, HandleModuleObject module, + HandleValue error); + +// https://tc39.es/proposal-top-level-await/#sec-asyncmodulexecutionfulfilled +bool AsyncModuleExecutionFulfilledHandler(JSContext* cx, unsigned argc, + Value* vp); + +// https://tc39.es/proposal-top-level-await/#sec-asyncmodulexecutionrejected +bool AsyncModuleExecutionRejectedHandler(JSContext* cx, unsigned argc, + Value* vp); + JSObject* StartDynamicModuleImport(JSContext* cx, HandleScript script, HandleValue specifier); diff --git a/js/src/builtin/Promise.cpp b/js/src/builtin/Promise.cpp index 6cd525747af2..66a2b0878c1a 100644 --- a/js/src/builtin/Promise.cpp +++ b/js/src/builtin/Promise.cpp @@ -5702,7 +5702,13 @@ static MOZ_MUST_USE bool IsTopMostAsyncFunctionCall(JSContext* cx) { if (iter.done()) { return false; } - MOZ_ASSERT(iter.isFunctionFrame()); + + if (!iter.isFunctionFrame() && iter.isModuleFrame()) { + // The iterator is not a function frame, it is a module frame. + // Ignore this optimization for now. + return true; + } + MOZ_ASSERT(iter.calleeTemplate()->isAsync()); #ifdef DEBUG diff --git a/js/src/builtin/SelfHostingDefines.h b/js/src/builtin/SelfHostingDefines.h index 254766046611..dd23cfbd8e91 100644 --- a/js/src/builtin/SelfHostingDefines.h +++ b/js/src/builtin/SelfHostingDefines.h @@ -110,6 +110,9 @@ #define MODULE_OBJECT_EVALUATION_ERROR_SLOT 4 #define MODULE_OBJECT_DFS_INDEX_SLOT 14 #define MODULE_OBJECT_DFS_ANCESTOR_INDEX_SLOT 15 +#define MODULE_OBJECT_ASYNC_EVALUATING_SLOT 17 +#define MODULE_OBJECT_TOP_LEVEL_CAPABILITY_SLOT 18 +#define MODULE_OBJECT_PENDING_ASYNC_DEPENDENCIES_SLOT 20 // rev b012019fea18f29737a67c36911340a3e25bfc63 // 15.2.1.16 Cyclic Module Records diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index ee7afbf40119..77beba622203 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -2909,8 +2909,11 @@ bool BytecodeEmitter::emitIteratorNext( const Maybe& callSourceCoordOffset, IteratorKind iterKind /* = IteratorKind::Sync */, bool allowSelfHosted /* = false */) { - MOZ_ASSERT(allowSelfHosted || emitterMode != BytecodeEmitter::SelfHosting, - ".next() iteration is prohibited in self-hosted code because it " + // TODO: migrate Module code to cpp, to avoid having the extra check here. + MOZ_ASSERT(allowSelfHosted || emitterMode != BytecodeEmitter::SelfHosting || + (sc->isModuleContext() && sc->asModuleContext()->isAsync()), + ".next() iteration is prohibited in non-module self-hosted code " + "because it" "can run user-modifiable iteration code"); // [stack] ... NEXT ITER diff --git a/js/src/vm/AsyncFunction.cpp b/js/src/vm/AsyncFunction.cpp index bfebb0126f5e..61e3496fc9af 100644 --- a/js/src/vm/AsyncFunction.cpp +++ b/js/src/vm/AsyncFunction.cpp @@ -225,6 +225,22 @@ AsyncFunctionGeneratorObject* AsyncFunctionGeneratorObject::create( return obj; } +JSFunction* NewHandler(JSContext* cx, Native handler, + JS::Handle target) { + cx->check(target); + + JS::Handle funName = cx->names().empty; + JS::Rooted handlerFun( + cx, NewNativeFunction(cx, handler, 0, funName, + gc::AllocKind::FUNCTION_EXTENDED, GenericObject)); + if (!handlerFun) { + return nullptr; + } + handlerFun->setExtendedSlot(FunctionExtended::MODULE_SLOT, + JS::ObjectValue(*target)); + return handlerFun; +} + AsyncFunctionGeneratorObject* AsyncFunctionGeneratorObject::create( JSContext* cx, HandleModuleObject module) { // TODO: Module is currently hitching a ride with @@ -247,6 +263,23 @@ AsyncFunctionGeneratorObject* AsyncFunctionGeneratorObject::create( } obj->initFixedSlot(PROMISE_SLOT, ObjectValue(*resultPromise)); + RootedObject onFulfilled( + cx, NewHandler(cx, AsyncModuleExecutionFulfilledHandler, module)); + if (!onFulfilled) { + return nullptr; + } + + RootedObject onRejected( + cx, NewHandler(cx, AsyncModuleExecutionRejectedHandler, module)); + if (!onRejected) { + return nullptr; + } + + if (!JS::AddPromiseReactionsIgnoringUnhandledRejection( + cx, resultPromise, onFulfilled, onRejected)) { + return nullptr; + } + // Starts in the running state. obj->setResumeIndex(AbstractGeneratorObject::RESUME_INDEX_RUNNING); diff --git a/js/src/vm/JSFunction.h b/js/src/vm/JSFunction.h index 00772ec47ba2..c0bcf3212255 100644 --- a/js/src/vm/JSFunction.h +++ b/js/src/vm/JSFunction.h @@ -826,6 +826,9 @@ class FunctionExtended : public JSFunction { // asm.js module functions store their WasmModuleObject in the first slot. static const unsigned ASMJS_MODULE_SLOT = 0; + // Async module callback handlers store their ModuleObject in the first slot. + static const unsigned MODULE_SLOT = 0; + static inline size_t offsetOfExtendedSlot(unsigned which) { MOZ_ASSERT(which < NUM_EXTENDED_SLOTS); return offsetof(FunctionExtended, extendedSlots) + diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp index 2b9841edf887..aad882cecaf8 100644 --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -1998,6 +1998,63 @@ static bool intrinsic_ExecuteModule(JSContext* cx, unsigned argc, Value* vp) { return ModuleObject::execute(cx, module, args.rval()); } +static bool intrinsic_GetAsyncCycleRoot(JSContext* cx, unsigned argc, + Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 1); + RootedModuleObject module(cx, &args[0].toObject().as()); + JSObject* result = js::GetAsyncCycleRoot(module); + if (!result) { + return false; + } + args.rval().setObject(*result); + return true; +} + +static bool intrinsic_AppendAsyncParentModule(JSContext* cx, unsigned argc, + Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 2); + RootedModuleObject self(cx, &args[0].toObject().as()); + RootedModuleObject parent(cx, &args[1].toObject().as()); + return ModuleObject::appendAsyncParentModule(cx, self, parent); +} + +static bool intrinsic_CreateTopLevelCapability(JSContext* cx, unsigned argc, + Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 1); + RootedModuleObject self(cx, &args[0].toObject().as()); + PromiseObject* result = ModuleObject::createTopLevelCapability(cx, self); + if (!result) { + return false; + } + args.rval().setObject(*result); + return true; +} + +static bool intrinsic_ModuleTopLevelCapabilityResolve(JSContext* cx, + unsigned argc, + Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 1); + RootedModuleObject module(cx, &args[0].toObject().as()); + ModuleObject::topLevelCapabilityResolve(cx, module); + args.rval().setUndefined(); + return true; +} + +static bool intrinsic_ModuleTopLevelCapabilityReject(JSContext* cx, + unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 2); + RootedModuleObject module(cx, &args[0].toObject().as()); + HandleValue error = args[1]; + ModuleObject::topLevelCapabilityReject(cx, module, error); + args.rval().setUndefined(); + return true; +} + static bool intrinsic_NewModuleNamespace(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); @@ -2582,6 +2639,13 @@ static const JSFunctionSpec intrinsic_functions[] = { JS_FN("InstantiateModuleFunctionDeclarations", intrinsic_InstantiateModuleFunctionDeclarations, 1, 0), JS_FN("ExecuteModule", intrinsic_ExecuteModule, 1, 0), + JS_FN("GetAsyncCycleRoot", intrinsic_GetAsyncCycleRoot, 1, 0), + JS_FN("AppendAsyncParentModule", intrinsic_AppendAsyncParentModule, 2, 0), + JS_FN("CreateTopLevelCapability", intrinsic_CreateTopLevelCapability, 1, 0), + JS_FN("ModuleTopLevelCapabilityResolve", + intrinsic_ModuleTopLevelCapabilityResolve, 1, 0), + JS_FN("ModuleTopLevelCapabilityReject", + intrinsic_ModuleTopLevelCapabilityReject, 2, 0), JS_FN("NewModuleNamespace", intrinsic_NewModuleNamespace, 2, 0), JS_FN("AddModuleNamespaceBinding", intrinsic_AddModuleNamespaceBinding, 4, 0),