Bug 1499140 - Implement support for dynamic module import in the interpreter r=jandem
authorJon Coppeard <jcoppeard@mozilla.com>
Mon, 22 Oct 2018 11:28:16 +0100
changeset 490631 430db29f46858faff930e2ec3ed45fbf13a73a20
parent 490630 fa6b7a70f2db81835c314543031fb6ce251fafce
child 490632 6592655e860e73040175e3a0de9d7a1ade2dea89
push id247
push userfmarier@mozilla.com
push dateSat, 27 Oct 2018 01:06:44 +0000
reviewersjandem
bugs1499140
milestone64.0a1
Bug 1499140 - Implement support for dynamic module import in the interpreter r=jandem
js/src/builtin/ModuleObject.cpp
js/src/builtin/ModuleObject.h
js/src/builtin/Promise.cpp
js/src/builtin/Promise.h
js/src/builtin/Stream.cpp
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/FullParseHandler.h
js/src/frontend/Parser.cpp
js/src/jit/BaselineCompiler.cpp
js/src/jit/IonBuilder.cpp
js/src/js.msg
js/src/jsapi.cpp
js/src/jsapi.h
js/src/shell/js.cpp
js/src/vm/EnvironmentObject.cpp
js/src/vm/EnvironmentObject.h
js/src/vm/Interpreter.cpp
js/src/vm/Opcodes.h
js/src/vm/Runtime.cpp
js/src/vm/Runtime.h
js/src/vm/Scope.cpp
js/src/vm/Scope.h
js/src/vm/SelfHosting.cpp
js/src/vm/Stack.cpp
--- a/js/src/builtin/ModuleObject.cpp
+++ b/js/src/builtin/ModuleObject.cpp
@@ -3,16 +3,17 @@
  * 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 "builtin/ModuleObject.h"
 
 #include "mozilla/EnumSet.h"
 
+#include "builtin/Promise.h"
 #include "builtin/SelfHostingDefines.h"
 #include "frontend/ParseNode.h"
 #include "frontend/SharedContext.h"
 #include "gc/FreeOp.h"
 #include "gc/Policy.h"
 #include "gc/Tracer.h"
 #include "vm/AsyncFunction.h"
 #include "vm/AsyncIteration.h"
@@ -1800,8 +1801,93 @@ js::GetOrCreateModuleMetaObject(JSContex
     if (!func(cx, modulePrivate, metaObject)) {
         return nullptr;
     }
 
     module->setMetaObject(metaObject);
 
     return metaObject;
 }
+
+JSObject*
+js::CallModuleResolveHook(JSContext* cx, HandleValue referencingPrivate, HandleString specifier)
+{
+    JS::ModuleResolveHook moduleResolveHook = cx->runtime()->moduleResolveHook;
+    if (!moduleResolveHook) {
+        JS_ReportErrorASCII(cx, "Module resolve hook not set");
+        return nullptr;
+    }
+
+    RootedObject result(cx, moduleResolveHook(cx, referencingPrivate, specifier));
+    if (!result) {
+        return nullptr;
+    }
+
+    if (!result->is<ModuleObject>()) {
+        JS_ReportErrorASCII(cx, "Module resolve hook did not return Module object");
+        return nullptr;
+    }
+
+    return result;
+}
+
+JSObject*
+js::StartDynamicModuleImport(JSContext* cx, HandleValue referencingPrivate, HandleValue specifierArg)
+{
+    RootedObject promiseConstructor(cx, JS::GetPromiseConstructor(cx));
+    if (!promiseConstructor) {
+        return nullptr;
+    }
+
+    RootedObject promiseObject(cx, JS::NewPromiseObject(cx, nullptr));
+    if (!promiseObject) {
+        return nullptr;
+    }
+
+    Handle<PromiseObject*> promise = promiseObject.as<PromiseObject>();
+
+    RootedString specifier(cx, ToString(cx, specifierArg));
+    if (!specifier) {
+        if (!RejectPromiseWithPendingError(cx, promise))
+            return nullptr;
+        return promise;
+    }
+
+    JS::ModuleDynamicImportHook importHook = cx->runtime()->moduleDynamicImportHook;
+    MOZ_ASSERT(importHook);
+    if (!importHook(cx, referencingPrivate, specifier, promise)) {
+        if (!RejectPromiseWithPendingError(cx, promise))
+            return nullptr;
+        return promise;
+    }
+
+    return promise;
+}
+
+bool
+js::FinishDynamicModuleImport(JSContext* cx, HandleValue referencingPrivate, HandleString specifier,
+                              HandleObject promiseArg)
+{
+    Handle<PromiseObject*> promise = promiseArg.as<PromiseObject>();
+
+    if (cx->isExceptionPending()) {
+        return RejectPromiseWithPendingError(cx, promise);
+    }
+
+    RootedObject result(cx, CallModuleResolveHook(cx, referencingPrivate, specifier));
+    if (!result) {
+        return RejectPromiseWithPendingError(cx, promise);
+    }
+
+    RootedModuleObject module(cx, &result->as<ModuleObject>());
+    if (module->status() != MODULE_STATUS_EVALUATED) {
+        JS_ReportErrorASCII(cx, "Unevaluated or errored module returned by module resolve hook");
+        return RejectPromiseWithPendingError(cx, promise);
+    }
+
+    RootedObject ns(cx, ModuleObject::GetOrCreateModuleNamespace(cx, module));
+    if (!ns) {
+        return RejectPromiseWithPendingError(cx, promise);
+    }
+
+    RootedValue value(cx, ObjectValue(*ns));
+    return PromiseObject::resolve(cx, promise, value);
+}
--- a/js/src/builtin/ModuleObject.h
+++ b/js/src/builtin/ModuleObject.h
@@ -414,16 +414,26 @@ class MOZ_STACK_CLASS ModuleBuilder
     ArrayObject* createArray(const JS::Rooted<GCVector<T>>& vector);
     template <typename K, typename V>
     ArrayObject* createArray(const JS::Rooted<GCHashMap<K, V>>& map);
 };
 
 JSObject*
 GetOrCreateModuleMetaObject(JSContext* cx, HandleObject module);
 
+JSObject*
+CallModuleResolveHook(JSContext* cx, HandleValue referencingPrivate, HandleString specifier);
+
+JSObject*
+StartDynamicModuleImport(JSContext* cx, HandleValue referencingPrivate, HandleValue specifier);
+
+bool
+FinishDynamicModuleImport(JSContext* cx, HandleValue referencingPrivate, HandleString specifier,
+                          HandleObject promise);
+
 } // namespace js
 
 template<>
 inline bool
 JSObject::is<js::ModuleNamespaceObject>() const
 {
     return js::IsDerivedProxyObject(this, &js::ModuleNamespaceObject::proxyHandler);
 }
--- a/js/src/builtin/Promise.cpp
+++ b/js/src/builtin/Promise.cpp
@@ -3399,16 +3399,26 @@ OriginalPromiseThenBuiltin(JSContext* cx
     if (rvalUsed) {
         rval.setObject(*resultCapability.promise());
     } else {
         rval.setUndefined();
     }
     return true;
 }
 
+MOZ_MUST_USE bool
+js::RejectPromiseWithPendingError(JSContext* cx, Handle<PromiseObject*> promise)
+{
+    // Not much we can do about uncatchable exceptions, just bail.
+    RootedValue exn(cx);
+    if (!GetAndClearException(cx, &exn))
+        return false;
+    return PromiseObject::reject(cx, promise, exn);
+}
+
 static MOZ_MUST_USE bool PerformPromiseThenWithReaction(JSContext* cx,
                                                         Handle<PromiseObject*> promise,
                                                         Handle<PromiseReactionRecord*> reaction);
 
 // Some async/await functions are implemented here instead of
 // js/src/builtin/AsyncFunction.cpp, to call Promise internal functions.
 
 // ES 2018 draft 14.6.11 and 14.7.14 step 1.
--- a/js/src/builtin/Promise.h
+++ b/js/src/builtin/Promise.h
@@ -220,16 +220,18 @@ OriginalPromiseThen(JSContext* cx, Handl
  * PromiseResolve ( C, x )
  *
  * The abstract operation PromiseResolve, given a constructor and a value,
  * returns a new promise resolved with that value.
  */
 MOZ_MUST_USE JSObject*
 PromiseResolve(JSContext* cx, HandleObject constructor, HandleValue value);
 
+MOZ_MUST_USE bool
+RejectPromiseWithPendingError(JSContext* cx, Handle<PromiseObject*> promise);
 
 /**
  * Create the promise object which will be used as the return value of an async
  * function.
  */
 MOZ_MUST_USE PromiseObject*
 CreatePromiseObjectForAsync(JSContext* cx, HandleValue generatorVal);
 
--- a/js/src/builtin/Stream.cpp
+++ b/js/src/builtin/Stream.cpp
@@ -519,26 +519,16 @@ ReportArgTypeError(JSContext* cx, const 
         return;
     }
 
     JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_NOT_EXPECTED_TYPE, funName,
                              expectedType, bytes.get());
 }
 
 static MOZ_MUST_USE bool
-RejectWithPendingError(JSContext* cx, Handle<PromiseObject*> promise) {
-    // Not much we can do about uncatchable exceptions, just bail.
-    RootedValue exn(cx);
-    if (!GetAndClearException(cx, &exn)) {
-        return false;
-    }
-    return PromiseObject::reject(cx, promise, exn);
-}
-
-static MOZ_MUST_USE bool
 ReturnPromiseRejectedWithPendingError(JSContext* cx, const CallArgs& args)
 {
     JSObject* promise = PromiseRejectedWithPendingError(cx);
     if (!promise) {
         return false;
     }
 
     args.rval().setObject(*promise);
@@ -1456,17 +1446,17 @@ ReadableStreamTee_Cancel(JSContext* cx, 
 
         Rooted<PromiseObject*> promise(cx, teeState->promise());
 
         // Step b: Let cancelResult be ! ReadableStreamCancel(stream, compositeReason).
         RootedObject cancelResult(cx, ReadableStream::cancel(cx, stream, compositeReasonVal));
         {
             AutoRealm ar(cx, promise);
             if (!cancelResult) {
-                if (!RejectWithPendingError(cx, promise)) {
+                if (!RejectPromiseWithPendingError(cx, promise)) {
                     return nullptr;
                 }
             } else {
                 // Step c: Resolve teeState.[[promise]] with cancelResult.
                 RootedValue resultVal(cx, ObjectValue(*cancelResult));
                 if (!cx->compartment()->wrap(cx, &resultVal)) {
                     return nullptr;
                 }
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -8873,18 +8873,24 @@ BytecodeEmitter::emitTree(ParseNode* pn,
 
       case ParseNodeKind::ImportMeta:
         if (!emit1(JSOP_IMPORTMETA)) {
             return false;
         }
         break;
 
       case ParseNodeKind::CallImport:
-        reportError(nullptr, JSMSG_NO_DYNAMIC_IMPORT);
-        return false;
+        if (!cx->runtime()->moduleDynamicImportHook) {
+            reportError(nullptr, JSMSG_NO_DYNAMIC_IMPORT);
+            return false;
+        }
+        if (!emitTree(pn->as<BinaryNode>().right()) || !emit1(JSOP_DYNAMIC_IMPORT)) {
+            return false;
+        }
+        break;
 
       case ParseNodeKind::SetThis:
         if (!emitSetThis(&pn->as<BinaryNode>())) {
             return false;
         }
         break;
 
       case ParseNodeKind::PropertyName:
--- a/js/src/frontend/FullParseHandler.h
+++ b/js/src/frontend/FullParseHandler.h
@@ -568,17 +568,17 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS)
         return new_<NullaryNode>(ParseNodeKind::ExportBatchSpec, JSOP_NOP, pos);
     }
 
     BinaryNodeType newImportMeta(NullaryNodeType importHolder, NullaryNodeType metaHolder) {
         return new_<BinaryNode>(ParseNodeKind::ImportMeta, JSOP_NOP, importHolder, metaHolder);
     }
 
     BinaryNodeType newCallImport(NullaryNodeType importHolder, Node singleArg) {
-        return new_<BinaryNode>(ParseNodeKind::CallImport, JSOP_NOP, importHolder, singleArg);
+        return new_<BinaryNode>(ParseNodeKind::CallImport, JSOP_DYNAMIC_IMPORT, importHolder, singleArg);
     }
 
     UnaryNodeType newExprStatement(Node expr, uint32_t end) {
         MOZ_ASSERT(expr->pn_pos.end <= end);
         return new_<UnaryNode>(ParseNodeKind::ExpressionStatement,
                                TokenPos(expr->pn_pos.begin, end), expr);
     }
 
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -10908,17 +10908,17 @@ GeneralParser<ParseHandler, Unit>::impor
     } else if (next == TokenKind::LeftParen) {
         Node arg = assignExpr(InAllowed, yieldHandling, TripledotProhibited);
         if (!arg) {
             return null();
         }
 
         MUST_MATCH_TOKEN_MOD(TokenKind::RightParen, TokenStream::Operand, JSMSG_PAREN_AFTER_ARGS);
 
-        if (!abortIfSyntaxParser()) {
+        if (!context->runtime()->moduleDynamicImportHook && !abortIfSyntaxParser()) {
             return null();
         }
 
         return handler.newCallImport(importHolder, arg);
     } else {
         error(JSMSG_UNEXPECTED_TOKEN_NO_EXPECT, TokenKindToDesc(next));
         return null();
     }
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -1196,16 +1196,18 @@ BaselineCompiler::emitBody()
         }
 
         switch (op) {
           // ===== NOT Yet Implemented =====
           case JSOP_FORCEINTERPRETER:
             // Intentionally not implemented.
           case JSOP_SETINTRINSIC:
             // Run-once opcode during self-hosting initialization.
+          case JSOP_DYNAMIC_IMPORT:
+            // Dynamic module import.
           case JSOP_UNUSED126:
           case JSOP_UNUSED206:
           case JSOP_LIMIT:
             // === !! WARNING WARNING WARNING !! ===
             // Do you really want to sacrifice performance by not implementing
             // this operation in the BaselineCompiler?
             JitSpew(JitSpew_BaselineAbort, "Unhandled op: %s", CodeName[op]);
             return Method_CantCompile;
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -2552,16 +2552,17 @@ IonBuilder::inspectOpcode(JSOp op)
       // Misc
       case JSOP_DELNAME:
       case JSOP_FINALLY:
       case JSOP_GETRVAL:
       case JSOP_GOSUB:
       case JSOP_RETSUB:
       case JSOP_SETINTRINSIC:
       case JSOP_THROWMSG:
+      case JSOP_DYNAMIC_IMPORT:
         // === !! WARNING WARNING WARNING !! ===
         // Do you really want to sacrifice performance by not implementing this
         // operation in the optimizing compiler?
         break;
 
       case JSOP_FORCEINTERPRETER:
         // Intentionally not implemented.
         break;
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -610,16 +610,17 @@ MSG_DEF(JSMSG_BAD_DEFAULT_EXPORT,       
 MSG_DEF(JSMSG_MISSING_INDIRECT_EXPORT,   0, JSEXN_SYNTAXERR, "indirect export not found")
 MSG_DEF(JSMSG_AMBIGUOUS_INDIRECT_EXPORT, 0, JSEXN_SYNTAXERR, "ambiguous indirect export")
 MSG_DEF(JSMSG_MISSING_IMPORT,            0, JSEXN_SYNTAXERR, "import not found")
 MSG_DEF(JSMSG_AMBIGUOUS_IMPORT,          0, JSEXN_SYNTAXERR, "ambiguous import")
 MSG_DEF(JSMSG_MISSING_NAMESPACE_EXPORT,  0, JSEXN_SYNTAXERR, "export not found for namespace")
 MSG_DEF(JSMSG_MISSING_EXPORT,            1, JSEXN_SYNTAXERR, "local binding for export '{0}' not found")
 MSG_DEF(JSMSG_BAD_MODULE_STATUS,         0, JSEXN_INTERNALERR, "module record has unexpected status")
 MSG_DEF(JSMSG_NO_DYNAMIC_IMPORT,         0, JSEXN_SYNTAXERR, "dynamic module import is not implemented")
+MSG_DEF(JSMSG_IMPORT_SCRIPT_NOT_FOUND,   0, JSEXN_TYPEERR, "can't find referencing script for dynamic module import")
 
 // Promise
 MSG_DEF(JSMSG_CANNOT_RESOLVE_PROMISE_WITH_ITSELF,       0, JSEXN_TYPEERR, "A promise cannot be resolved with itself.")
 MSG_DEF(JSMSG_PROMISE_CAPABILITY_HAS_SOMETHING_ALREADY, 0, JSEXN_TYPEERR, "GetCapabilitiesExecutor function already invoked with non-undefined values.")
 MSG_DEF(JSMSG_PROMISE_RESOLVE_FUNCTION_NOT_CALLABLE,    0, JSEXN_TYPEERR, "A Promise subclass passed a non-callable value as the resolve function.")
 MSG_DEF(JSMSG_PROMISE_REJECT_FUNCTION_NOT_CALLABLE,     0, JSEXN_TYPEERR, "A Promise subclass passed a non-callable value as the reject function.")
 MSG_DEF(JSMSG_PROMISE_ERROR_IN_WRAPPED_REJECTION_REASON,0, JSEXN_INTERNALERR, "Promise rejection value is a non-unwrappable cross-compartment wrapper.")
 
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -4136,16 +4136,41 @@ JS::GetModuleMetadataHook(JSRuntime* rt)
 
 JS_PUBLIC_API(void)
 JS::SetModuleMetadataHook(JSRuntime* rt, JS::ModuleMetadataHook func)
 {
     AssertHeapIsIdle();
     rt->moduleMetadataHook = func;
 }
 
+JS_PUBLIC_API(JS::ModuleDynamicImportHook)
+JS::GetModuleDynamicImportHook(JSRuntime* rt)
+{
+    AssertHeapIsIdle();
+    return rt->moduleDynamicImportHook;
+}
+
+JS_PUBLIC_API(void)
+JS::SetModuleDynamicImportHook(JSRuntime* rt, JS::ModuleDynamicImportHook func)
+{
+    AssertHeapIsIdle();
+    rt->moduleDynamicImportHook = func;
+}
+
+JS_PUBLIC_API(bool)
+JS::FinishDynamicModuleImport(JSContext* cx, HandleValue referencingPrivate, HandleString specifier,
+                              HandleObject promise)
+{
+    AssertHeapIsIdle();
+    CHECK_THREAD(cx);
+    cx->check(referencingPrivate, promise);
+
+    return js::FinishDynamicModuleImport(cx, referencingPrivate, specifier, promise);
+}
+
 JS_PUBLIC_API(bool)
 JS::CompileModule(JSContext* cx, const ReadOnlyCompileOptions& options,
                   SourceBufferHolder& srcBuf, JS::MutableHandleObject module)
 {
     MOZ_ASSERT(!cx->zone()->isAtomsZone());
     AssertHeapIsIdle();
     CHECK_THREAD(cx);
 
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -3045,16 +3045,35 @@ GetModuleMetadataHook(JSRuntime* rt);
 
 /**
  * Set the hook for populating the import.meta metadata object to the given
  * function.
  */
 extern JS_PUBLIC_API(void)
 SetModuleMetadataHook(JSRuntime* rt, ModuleMetadataHook func);
 
+using ModuleDynamicImportHook = bool (*)(JSContext* cx, HandleValue referencingPrivate,
+                                         HandleString specifier, HandleObject promise);
+
+/**
+ * Get the HostResolveImportedModule hook for the runtime.
+ */
+extern JS_PUBLIC_API(ModuleDynamicImportHook)
+GetModuleDynamicImportHook(JSRuntime* rt);
+
+/**
+ * Set the HostResolveImportedModule hook for the runtime to the given function.
+ */
+extern JS_PUBLIC_API(void)
+SetModuleDynamicImportHook(JSRuntime* rt, ModuleDynamicImportHook func);
+
+extern JS_PUBLIC_API(bool)
+FinishDynamicModuleImport(JSContext* cx, HandleValue referencingPrivate, HandleString specifier,
+                          HandleObject promise);
+
 /**
  * Parse the given source buffer as a module in the scope of the current global
  * of cx and return a source text module record.
  */
 extern JS_PUBLIC_API(bool)
 CompileModule(JSContext* cx, const ReadOnlyCompileOptions& options,
               SourceBufferHolder& srcBuf, JS::MutableHandleObject moduleRecord);
 
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -4718,17 +4718,17 @@ SetModuleResolveHook(JSContext* cx, unsi
     Handle<GlobalObject*> global = cx->global();
     global->setReservedSlot(GlobalAppSlotModuleResolveHook, args[0]);
 
     args.rval().setUndefined();
     return true;
 }
 
 static JSObject*
-CallModuleResolveHook(JSContext* cx, HandleValue referencingPrivate, HandleString specifier)
+ShellModuleResolveHook(JSContext* cx, HandleValue referencingPrivate, HandleString specifier)
 {
     Handle<GlobalObject*> global = cx->global();
     RootedValue hookValue(cx, global->getReservedSlot(GlobalAppSlotModuleResolveHook));
     if (hookValue.isUndefined()) {
         JS_ReportErrorASCII(cx, "Module resolve hook not set");
         return nullptr;
     }
     MOZ_ASSERT(hookValue.toObject().is<JSFunction>());
@@ -10783,17 +10783,17 @@ main(int argc, char** argv, char** envp)
         JS_SetGCParameter(cx, JSGC_DYNAMIC_HEAP_GROWTH, 1);
         JS_SetGCParameter(cx, JSGC_DYNAMIC_MARK_SLICE, 1);
         JS_SetGCParameter(cx, JSGC_SLICE_TIME_BUDGET, 10);
     }
 #endif
 
     js::SetPreserveWrapperCallback(cx, DummyPreserveWrapperCallback);
 
-    JS::SetModuleResolveHook(cx->runtime(), CallModuleResolveHook);
+    JS::SetModuleResolveHook(cx->runtime(), ShellModuleResolveHook);
     JS::SetModuleMetadataHook(cx->runtime(), CallModuleMetadataHook);
 
     result = Shell(cx, &op, envp);
 
 #ifdef DEBUG
     if (OOM_printAllocationCount) {
         printf("OOM max count: %" PRIu64 "\n", js::oom::counter);
     }
--- a/js/src/vm/EnvironmentObject.cpp
+++ b/js/src/vm/EnvironmentObject.cpp
@@ -3472,16 +3472,33 @@ js::GetModuleObjectForScript(JSScript* s
     for (ScopeIter si(script); si; si++) {
         if (si.kind() == ScopeKind::Module) {
             return si.scope()->as<ModuleScope>().module();
         }
     }
     return nullptr;
 }
 
+Value
+js::FindScriptOrModulePrivateForScript(JSScript* script)
+{
+    while (script) {
+        ScriptSourceObject* sso = &script->scriptSourceUnwrap();
+        Value value = sso->getPrivate();
+        if (!value.isUndefined()) {
+            return value;
+        }
+
+        MOZ_ASSERT(sso->introductionScript() != script);
+        script = sso->introductionScript();
+    }
+
+    return UndefinedValue();
+}
+
 bool
 js::GetThisValueForDebuggerMaybeOptimizedOut(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc,
                                              MutableHandleValue res)
 {
     RootedObject scopeChain(cx);
     RootedScope scope(cx);
     if (!GetFrameEnvironmentAndScope(cx, frame, pc, &scopeChain, &scope)) {
         return false;
--- a/js/src/vm/EnvironmentObject.h
+++ b/js/src/vm/EnvironmentObject.h
@@ -1177,17 +1177,21 @@ IsFrameInitialEnvironment(AbstractFrameP
     return false;
 }
 
 extern bool
 CreateObjectsForEnvironmentChain(JSContext* cx, AutoObjectVector& chain,
                                  HandleObject terminatingEnv,
                                  MutableHandleObject envObj);
 
-ModuleObject* GetModuleObjectForScript(JSScript* script);
+ModuleObject*
+GetModuleObjectForScript(JSScript* script);
+
+Value
+FindScriptOrModulePrivateForScript(JSScript* script);
 
 ModuleEnvironmentObject* GetModuleEnvironmentForScript(JSScript* script);
 
 MOZ_MUST_USE bool
 GetThisValueForDebuggerMaybeOptimizedOut(JSContext* cx, AbstractFramePtr frame,
                                          jsbytecode* pc, MutableHandleValue res);
 
 MOZ_MUST_USE bool
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -4719,17 +4719,33 @@ CASE(JSOP_IMPORTMETA)
 
     JSObject* metaObject = GetOrCreateModuleMetaObject(cx, module);
     if (!metaObject) {
         goto error;
     }
 
     PUSH_OBJECT(*metaObject);
 }
-END_CASE(JSOP_NEWTARGET)
+END_CASE(JSOP_IMPORTMETA)
+
+CASE(JSOP_DYNAMIC_IMPORT)
+{
+    ReservedRooted<Value> referencingPrivate(&rootValue0);
+    referencingPrivate = FindScriptOrModulePrivateForScript(script);
+
+    ReservedRooted<Value> specifier(&rootValue1);
+    POP_COPY_TO(specifier);
+
+    JSObject* promise = StartDynamicModuleImport(cx, referencingPrivate, specifier);
+    if (!promise)
+        goto error;
+
+    PUSH_OBJECT(*promise);
+}
+END_CASE(JSOP_DYNAMIC_IMPORT)
 
 CASE(JSOP_SUPERFUN)
 {
     ReservedRooted<JSObject*> superEnvFunc(&rootObject0, &GetSuperEnvFunction(cx, REGS));
     ReservedRooted<JSObject*> superFun(&rootObject1);
     superFun = SuperFunOperation(cx, superEnvFunc);
     if (!superFun) {
         goto error;
--- a/js/src/vm/Opcodes.h
+++ b/js/src/vm/Opcodes.h
@@ -2359,24 +2359,33 @@ 1234567890123456789012345678901234567890
     /*
      * Push "import.meta"
      *
      *   Category: Variables and Scopes
      *   Type: Modules
      *   Operands:
      *   Stack: => import.meta
      */ \
-    macro(JSOP_IMPORTMETA,    232, "importmeta", NULL,      1,  0,  1,  JOF_BYTE)
+    macro(JSOP_IMPORTMETA,    232, "importmeta", NULL,      1,  0,  1,  JOF_BYTE) \
+    /*
+     * Dynamic import of the module specified by the string value on the top of
+     * the stack.
+     *
+     *   Category: Variables and Scopes
+     *   Type: Modules
+     *   Operands:
+     *   Stack: arg => rval
+     */ \
+    macro(JSOP_DYNAMIC_IMPORT, 233, "call-import", NULL,      1,  1,  1,  JOF_BYTE)
 
 /*
  * In certain circumstances it may be useful to "pad out" the opcode space to
  * a power of two.  Use this macro to do so.
  */
 #define FOR_EACH_TRAILING_UNUSED_OPCODE(macro) \
-    macro(233) \
     macro(234) \
     macro(235) \
     macro(236) \
     macro(237) \
     macro(238) \
     macro(239) \
     macro(240) \
     macro(241) \
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -171,17 +171,18 @@ JSRuntime::JSRuntime(JSRuntime* parentRu
     autoWritableJitCodeActive_(false),
     oomCallback(nullptr),
     debuggerMallocSizeOf(ReturnZeroSize),
     performanceMonitoring_(),
     stackFormat_(parentRuntime ? js::StackFormat::Default
                                : js::StackFormat::SpiderMonkey),
     wasmInstances(mutexid::WasmRuntimeInstances),
     moduleResolveHook(),
-    moduleMetadataHook()
+    moduleMetadataHook(),
+    moduleDynamicImportHook()
 {
     JS_COUNT_CTOR(JSRuntime);
     liveRuntimesCount++;
 
     lcovOutput().init();
 }
 
 JSRuntime::~JSRuntime()
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -976,16 +976,20 @@ struct JSRuntime : public js::MallocProv
 
     // The implementation-defined abstract operation HostResolveImportedModule.
     js::MainThreadData<JS::ModuleResolveHook> moduleResolveHook;
 
     // A hook that implements the abstract operations
     // HostGetImportMetaProperties and HostFinalizeImportMeta.
     js::MainThreadData<JS::ModuleMetadataHook> moduleMetadataHook;
 
+    // A hook that implements the abstract operation
+    // HostImportModuleDynamically.
+    js::MainThreadData<JS::ModuleDynamicImportHook> moduleDynamicImportHook;
+
   public:
 #if defined(JS_BUILD_BINAST)
     js::BinaryASTSupport& binast() {
         return binast_;
     }
   private:
     js::BinaryASTSupport binast_;
 #endif // defined(JS_BUILD_BINAST)
--- a/js/src/vm/Scope.cpp
+++ b/js/src/vm/Scope.cpp
@@ -1218,22 +1218,16 @@ ModuleScope::createWithData(JSContext* c
 
 /* static */ Shape*
 ModuleScope::getEmptyEnvironmentShape(JSContext* cx)
 {
     const Class* cls = &ModuleEnvironmentObject::class_;
     return EmptyEnvironmentShape(cx, cls, JSSLOT_FREE(cls), ModuleScopeEnvShapeFlags);
 }
 
-JSScript*
-ModuleScope::script() const
-{
-    return module()->script();
-}
-
 static const uint32_t WasmInstanceEnvShapeFlags =
     BaseShape::NOT_EXTENSIBLE | BaseShape::DELEGATE;
 
 
 template <size_t ArrayLength>
 static JSAtom*
 GenerateWasmName(JSContext* cx, const char (&prefix)[ArrayLength], uint32_t index)
 {
--- a/js/src/vm/Scope.h
+++ b/js/src/vm/Scope.h
@@ -1022,18 +1022,16 @@ class ModuleScope : public Scope
     uint32_t nextFrameSlot() const {
         return data().nextFrameSlot;
     }
 
     ModuleObject* module() const {
         return data().module;
     }
 
-    JSScript* script() const;
-
     static Shape* getEmptyEnvironmentShape(JSContext* cx);
 };
 
 class WasmInstanceScope : public Scope
 {
     friend class BindingIter;
     friend class Scope;
     friend class GCMarker;
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -2178,25 +2178,18 @@ intrinsic_NameForTypedArray(JSContext* c
 static bool
 intrinsic_HostResolveImportedModule(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     MOZ_ASSERT(args.length() == 2);
     RootedModuleObject module(cx, &args[0].toObject().as<ModuleObject>());
     RootedString specifier(cx, args[1].toString());
 
-    JS::ModuleResolveHook moduleResolveHook = cx->runtime()->moduleResolveHook;
-    if (!moduleResolveHook) {
-        JS_ReportErrorASCII(cx, "Module resolve hook not set");
-        return false;
-    }
-
-    RootedObject result(cx);
     RootedValue referencingPrivate(cx, JS::GetModulePrivate(module));
-    result = moduleResolveHook(cx, referencingPrivate, specifier);
+    RootedObject result(cx, CallModuleResolveHook(cx, referencingPrivate, specifier));
     if (!result) {
         return false;
     }
 
     if (!result->is<ModuleObject>()) {
         JS_ReportErrorASCII(cx, "Module resolve hook did not return Module object");
         return false;
     }
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -149,23 +149,21 @@ AssertScopeMatchesEnvironment(Scope* sco
                 env = &env->as<LexicalEnvironmentObject>().enclosingEnvironment();
                 MOZ_ASSERT(env->is<GlobalObject>());
                 break;
 
               case ScopeKind::NonSyntactic:
                 MOZ_CRASH("NonSyntactic should not have a syntactic environment");
                 break;
 
-              case ScopeKind::Module: {
-                  ModuleObject* module = &env->as<ModuleEnvironmentObject>().module();
-                  MOZ_ASSERT_IF(module->maybeScript(),
-                                module->script() == si.scope()->as<ModuleScope>().script());
+              case ScopeKind::Module:
+                MOZ_ASSERT(&env->as<ModuleEnvironmentObject>().module() ==
+                           si.scope()->as<ModuleScope>().module());
                 env = &env->as<ModuleEnvironmentObject>().enclosingEnvironment();
                 break;
-              }
 
               case ScopeKind::WasmInstance:
                 env = &env->as<WasmInstanceEnvironmentObject>().enclosingEnvironment();
                 break;
 
               case ScopeKind::WasmFunction:
                 env = &env->as<WasmFunctionCallObject>().enclosingEnvironment();
                 break;