[JSC] Wasm module import should be done in sync when WebAssembly.instantiate gets module
https://bugs.webkit.org/show_bug.cgi?id=235506

Reviewed by Saam Barati.

LayoutTests/imported/w3c:

* web-platform-tests/wasm/jsapi/constructor/instantiate.any-expected.txt:
* web-platform-tests/wasm/jsapi/constructor/instantiate.any.worker-expected.txt:

Source/JavaScriptCore:

According to the spec, module imports need to be done synchronously when WebAssembly.instantiate
is called with a wasm module[1].

To align our implementation to this behavior, we split WebAssemblyModuleRecord::initializeImportsAndExports
into WebAssemblyModuleRecord::initializeImports and WebAssemblyModuleRecord::initializeExports. The former
does not require CalleeGroups so we can execute before compiling CalleeGroups.

[1]: https://webassembly.github.io/spec/js-api/#asynchronously-instantiate-a-webassembly-module

* runtime/AbstractModuleRecord.cpp:
(JSC::AbstractModuleRecord::evaluate):
* wasm/WasmInstance.h:
(JSC::Wasm::Instance::setOwner):
(JSC::Wasm::Instance::finalizeCreation): Deleted.
* wasm/WasmModuleInformation.h:
(JSC::Wasm::ModuleInformation::hasMemoryImport const):
* wasm/js/JSWebAssembly.cpp:
(JSC::instantiate):
(JSC::resolve): Deleted.
* wasm/js/JSWebAssemblyInstance.cpp:
(JSC::JSWebAssemblyInstance::JSWebAssemblyInstance):
(JSC::JSWebAssemblyInstance::initializeImports):
(JSC::JSWebAssemblyInstance::finalizeCreation):
* wasm/js/JSWebAssemblyInstance.h:
* wasm/js/WebAssemblyInstanceConstructor.cpp:
(JSC::JSC_DEFINE_HOST_FUNCTION):
* wasm/js/WebAssemblyModuleRecord.cpp:
(JSC::WebAssemblyModuleRecord::initializeImports):
(JSC::WebAssemblyModuleRecord::initializeExports):
(JSC::WebAssemblyModuleRecord::initializeImportsAndExports): Deleted.
* wasm/js/WebAssemblyModuleRecord.h:


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@288573 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/LayoutTests/imported/w3c/ChangeLog b/LayoutTests/imported/w3c/ChangeLog
index c1cb3ce..eaa6af6 100644
--- a/LayoutTests/imported/w3c/ChangeLog
+++ b/LayoutTests/imported/w3c/ChangeLog
@@ -1,3 +1,13 @@
+2022-01-25  Yusuke Suzuki  <ysuzuki@apple.com>
+
+        [JSC] Wasm module import should be done in sync when WebAssembly.instantiate gets module
+        https://bugs.webkit.org/show_bug.cgi?id=235506
+
+        Reviewed by Saam Barati.
+
+        * web-platform-tests/wasm/jsapi/constructor/instantiate.any-expected.txt:
+        * web-platform-tests/wasm/jsapi/constructor/instantiate.any.worker-expected.txt:
+
 2022-01-25  Antoine Quint  <graouts@webkit.org>
 
         Deduplication for @keyframes rules should account for animation-composition
diff --git a/LayoutTests/imported/w3c/web-platform-tests/wasm/jsapi/constructor/instantiate.any-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/wasm/jsapi/constructor/instantiate.any-expected.txt
index 8642a5e..1e08a0d 100644
--- a/LayoutTests/imported/w3c/web-platform-tests/wasm/jsapi/constructor/instantiate.any-expected.txt
+++ b/LayoutTests/imported/w3c/web-platform-tests/wasm/jsapi/constructor/instantiate.any-expected.txt
@@ -52,7 +52,7 @@
 PASS stray argument: BufferSource argument
 PASS stray argument: Module argument
 PASS Synchronous options handling: Buffer argument
-FAIL Synchronous options handling: Module argument assert_array_equals: lengths differ, expected array ["module getter", "global getter"] length 2, got [] length 0
+PASS Synchronous options handling: Module argument
 PASS Empty buffer
 PASS Invalid code
 PASS Changing the buffer
diff --git a/LayoutTests/imported/w3c/web-platform-tests/wasm/jsapi/constructor/instantiate.any.worker-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/wasm/jsapi/constructor/instantiate.any.worker-expected.txt
index 8642a5e..1e08a0d 100644
--- a/LayoutTests/imported/w3c/web-platform-tests/wasm/jsapi/constructor/instantiate.any.worker-expected.txt
+++ b/LayoutTests/imported/w3c/web-platform-tests/wasm/jsapi/constructor/instantiate.any.worker-expected.txt
@@ -52,7 +52,7 @@
 PASS stray argument: BufferSource argument
 PASS stray argument: Module argument
 PASS Synchronous options handling: Buffer argument
-FAIL Synchronous options handling: Module argument assert_array_equals: lengths differ, expected array ["module getter", "global getter"] length 2, got [] length 0
+PASS Synchronous options handling: Module argument
 PASS Empty buffer
 PASS Invalid code
 PASS Changing the buffer
diff --git a/Source/JavaScriptCore/ChangeLog b/Source/JavaScriptCore/ChangeLog
index 519c115..8339965 100644
--- a/Source/JavaScriptCore/ChangeLog
+++ b/Source/JavaScriptCore/ChangeLog
@@ -1,3 +1,42 @@
+2022-01-25  Yusuke Suzuki  <ysuzuki@apple.com>
+
+        [JSC] Wasm module import should be done in sync when WebAssembly.instantiate gets module
+        https://bugs.webkit.org/show_bug.cgi?id=235506
+
+        Reviewed by Saam Barati.
+
+        According to the spec, module imports need to be done synchronously when WebAssembly.instantiate
+        is called with a wasm module[1].
+
+        To align our implementation to this behavior, we split WebAssemblyModuleRecord::initializeImportsAndExports
+        into WebAssemblyModuleRecord::initializeImports and WebAssemblyModuleRecord::initializeExports. The former
+        does not require CalleeGroups so we can execute before compiling CalleeGroups.
+
+        [1]: https://webassembly.github.io/spec/js-api/#asynchronously-instantiate-a-webassembly-module
+
+        * runtime/AbstractModuleRecord.cpp:
+        (JSC::AbstractModuleRecord::evaluate):
+        * wasm/WasmInstance.h:
+        (JSC::Wasm::Instance::setOwner):
+        (JSC::Wasm::Instance::finalizeCreation): Deleted.
+        * wasm/WasmModuleInformation.h:
+        (JSC::Wasm::ModuleInformation::hasMemoryImport const):
+        * wasm/js/JSWebAssembly.cpp:
+        (JSC::instantiate):
+        (JSC::resolve): Deleted.
+        * wasm/js/JSWebAssemblyInstance.cpp:
+        (JSC::JSWebAssemblyInstance::JSWebAssemblyInstance):
+        (JSC::JSWebAssemblyInstance::initializeImports):
+        (JSC::JSWebAssemblyInstance::finalizeCreation):
+        * wasm/js/JSWebAssemblyInstance.h:
+        * wasm/js/WebAssemblyInstanceConstructor.cpp:
+        (JSC::JSC_DEFINE_HOST_FUNCTION):
+        * wasm/js/WebAssemblyModuleRecord.cpp:
+        (JSC::WebAssemblyModuleRecord::initializeImports):
+        (JSC::WebAssemblyModuleRecord::initializeExports):
+        (JSC::WebAssemblyModuleRecord::initializeImportsAndExports): Deleted.
+        * wasm/js/WebAssemblyModuleRecord.h:
+
 2022-01-24  Mark Lam  <mark.lam@apple.com>
 
         Rename Vector and FixedVector::findMatching to findIf to match stl naming.
diff --git a/Source/JavaScriptCore/runtime/AbstractModuleRecord.cpp b/Source/JavaScriptCore/runtime/AbstractModuleRecord.cpp
index 8fd767c..425b8fa 100644
--- a/Source/JavaScriptCore/runtime/AbstractModuleRecord.cpp
+++ b/Source/JavaScriptCore/runtime/AbstractModuleRecord.cpp
@@ -843,7 +843,9 @@
         // WebAssembly imports need to be supplied during evaluation so that, e.g.,
         // JS module exports are actually available to be read and installed as import
         // bindings.
-        wasmModuleRecord->initializeImportsAndExports(globalObject, nullptr, Wasm::CreationMode::FromModuleLoader);
+        wasmModuleRecord->initializeImports(globalObject, nullptr, Wasm::CreationMode::FromModuleLoader);
+        RETURN_IF_EXCEPTION(scope, jsUndefined());
+        wasmModuleRecord->initializeExports(globalObject);
         RETURN_IF_EXCEPTION(scope, jsUndefined());
         RELEASE_AND_RETURN(scope, wasmModuleRecord->evaluate(globalObject));
     }
diff --git a/Source/JavaScriptCore/wasm/WasmInstance.h b/Source/JavaScriptCore/wasm/WasmInstance.h
index 740e79c..710b876 100644
--- a/Source/JavaScriptCore/wasm/WasmInstance.h
+++ b/Source/JavaScriptCore/wasm/WasmInstance.h
@@ -56,7 +56,7 @@
 
     static Ref<Instance> create(Context*, Ref<Module>&&, EntryFrame** pointerToTopEntryFrame, void** pointerToActualStackLimit, StoreTopCallFrameCallback&&);
 
-    void finalizeCreation(void* owner)
+    void setOwner(void* owner)
     {
         m_owner = owner;
     }
diff --git a/Source/JavaScriptCore/wasm/WasmModuleInformation.h b/Source/JavaScriptCore/wasm/WasmModuleInformation.h
index 21a3927..a61f692 100644
--- a/Source/JavaScriptCore/wasm/WasmModuleInformation.h
+++ b/Source/JavaScriptCore/wasm/WasmModuleInformation.h
@@ -94,6 +94,8 @@
     bool isDeclaredException(uint32_t index) const { return m_declaredExceptions.contains(index); }
     void addDeclaredException(uint32_t index) { m_declaredExceptions.set(index); }
 
+    bool hasMemoryImport() const { return memory.isImport(); }
+
     Vector<Import> imports;
     Vector<SignatureIndex> importFunctionSignatureIndices;
     Vector<SignatureIndex> internalFunctionSignatureIndices;
diff --git a/Source/JavaScriptCore/wasm/js/JSWebAssembly.cpp b/Source/JavaScriptCore/wasm/js/JSWebAssembly.cpp
index 4d1ea41..82d6319 100644
--- a/Source/JavaScriptCore/wasm/js/JSWebAssembly.cpp
+++ b/Source/JavaScriptCore/wasm/js/JSWebAssembly.cpp
@@ -137,32 +137,6 @@
     return JSValue::encode(promise);
 }
 
-enum class Resolve { WithInstance, WithModuleRecord, WithModuleAndInstance };
-static void resolve(VM& vm, JSGlobalObject* globalObject, JSPromise* promise, JSWebAssemblyInstance* instance, JSWebAssemblyModule* module, JSObject* importObject, Ref<Wasm::CalleeGroup>&& calleeGroup, Resolve resolveKind, Wasm::CreationMode creationMode)
-{
-    auto scope = DECLARE_THROW_SCOPE(vm);
-    instance->finalizeCreation(vm, globalObject, WTFMove(calleeGroup), importObject, creationMode);
-    if (UNLIKELY(scope.exception())) {
-        promise->rejectWithCaughtException(globalObject, scope);
-        return;
-    }
-
-    scope.release();
-    if (resolveKind == Resolve::WithInstance)
-        promise->resolve(globalObject, instance);
-    else if (resolveKind == Resolve::WithModuleRecord) {
-        auto* moduleRecord = instance->moduleRecord();
-        if (UNLIKELY(Options::dumpModuleRecord()))
-            moduleRecord->dump();
-        promise->resolve(globalObject, moduleRecord);
-    } else {
-        JSObject* result = constructEmptyObject(globalObject);
-        result->putDirect(vm, Identifier::fromString(vm, "module"_s), module);
-        result->putDirect(vm, Identifier::fromString(vm, "instance"_s), instance);
-        promise->resolve(globalObject, result);
-    }
-}
-
 void JSWebAssembly::webAssemblyModuleValidateAsync(JSGlobalObject* globalObject, JSPromise* promise, Vector<uint8_t>&& source)
 {
     VM& vm = globalObject->vm();
@@ -186,6 +160,7 @@
     }));
 }
 
+enum class Resolve { WithInstance, WithModuleRecord, WithModuleAndInstance };
 static void instantiate(VM& vm, JSGlobalObject* globalObject, JSPromise* promise, JSWebAssemblyModule* module, JSObject* importObject, const Identifier& moduleKey, Resolve resolveKind, Wasm::CreationMode creationMode)
 {
     auto scope = DECLARE_THROW_SCOPE(vm);
@@ -197,18 +172,51 @@
         return;
     }
 
+    instance->initializeImports(globalObject, importObject, creationMode);
+    if (UNLIKELY(scope.exception())) {
+        promise->rejectWithCaughtException(globalObject, scope);
+        return;
+    }
+
     Vector<Strong<JSCell>> dependencies;
     // The instance keeps the module alive.
     dependencies.append(Strong<JSCell>(vm, promise));
-    dependencies.append(Strong<JSCell>(vm, importObject));
 
+    scope.release();
     auto ticket = vm.deferredWorkTimer->addPendingWork(vm, instance, WTFMove(dependencies));
     // Note: This completion task may or may not get called immediately.
-    module->module().compileAsync(&vm.wasmContext, instance->memoryMode(), createSharedTask<Wasm::CalleeGroup::CallbackType>([ticket, promise, instance, module, importObject, resolveKind, creationMode, &vm] (Ref<Wasm::CalleeGroup>&& refCalleeGroup) mutable {
+    module->module().compileAsync(&vm.wasmContext, instance->memoryMode(), createSharedTask<Wasm::CalleeGroup::CallbackType>([ticket, promise, instance, module, resolveKind, creationMode, &vm] (Ref<Wasm::CalleeGroup>&& refCalleeGroup) mutable {
         RefPtr<Wasm::CalleeGroup> calleeGroup = WTFMove(refCalleeGroup);
-        vm.deferredWorkTimer->scheduleWorkSoon(ticket, [promise, instance, module, importObject, resolveKind, creationMode, &vm, calleeGroup = WTFMove(calleeGroup)](DeferredWorkTimer::Ticket) mutable {
+        vm.deferredWorkTimer->scheduleWorkSoon(ticket, [promise, instance, module, resolveKind, creationMode, &vm, calleeGroup = WTFMove(calleeGroup)](DeferredWorkTimer::Ticket) mutable {
+            auto scope = DECLARE_THROW_SCOPE(vm);
             JSGlobalObject* globalObject = instance->globalObject();
-            resolve(vm, globalObject, promise, instance, module, importObject, calleeGroup.releaseNonNull(), resolveKind, creationMode);
+            instance->finalizeCreation(vm, globalObject, calleeGroup.releaseNonNull(), creationMode);
+            if (UNLIKELY(scope.exception())) {
+                promise->rejectWithCaughtException(globalObject, scope);
+                return;
+            }
+
+            scope.release();
+            switch (resolveKind) {
+            case Resolve::WithInstance: {
+                promise->resolve(globalObject, instance);
+                break;
+            }
+            case Resolve::WithModuleRecord: {
+                auto* moduleRecord = instance->moduleRecord();
+                if (UNLIKELY(Options::dumpModuleRecord()))
+                    moduleRecord->dump();
+                promise->resolve(globalObject, moduleRecord);
+                break;
+            }
+            case Resolve::WithModuleAndInstance: {
+                JSObject* result = constructEmptyObject(globalObject);
+                result->putDirect(vm, Identifier::fromString(vm, "module"_s), module);
+                result->putDirect(vm, Identifier::fromString(vm, "instance"_s), instance);
+                promise->resolve(globalObject, result);
+                break;
+            }
+            }
         });
     }));
 }
diff --git a/Source/JavaScriptCore/wasm/js/JSWebAssemblyInstance.cpp b/Source/JavaScriptCore/wasm/js/JSWebAssemblyInstance.cpp
index 7b86753..e75918b 100644
--- a/Source/JavaScriptCore/wasm/js/JSWebAssemblyInstance.cpp
+++ b/Source/JavaScriptCore/wasm/js/JSWebAssemblyInstance.cpp
@@ -53,6 +53,7 @@
     , m_globalObject(vm, this, structure->globalObject())
     , m_tables(m_instance->module().moduleInformation().tableCount())
 {
+    m_instance->setOwner(this);
     for (unsigned i = 0; i < this->instance().numImportFunctions(); ++i)
         new (this->instance().importFunction<WriteBarrier<JSObject>>(i)) WriteBarrier<JSObject>();
 }
@@ -104,10 +105,22 @@
 
 DEFINE_VISIT_CHILDREN(JSWebAssemblyInstance);
 
-void JSWebAssemblyInstance::finalizeCreation(VM& vm, JSGlobalObject* globalObject, Ref<Wasm::CalleeGroup>&& wasmCalleeGroup, JSObject* importObject, Wasm::CreationMode creationMode)
+void JSWebAssemblyInstance::initializeImports(JSGlobalObject* globalObject, JSObject* importObject, Wasm::CreationMode creationMode)
 {
-    m_instance->finalizeCreation(this);
+    VM& vm = globalObject->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
 
+    m_moduleRecord->prepareLink(vm, this);
+    if (creationMode == Wasm::CreationMode::FromJS) {
+        m_moduleRecord->link(globalObject, jsNull());
+        RETURN_IF_EXCEPTION(scope, void());
+        m_moduleRecord->initializeImports(globalObject, importObject, creationMode);
+        RETURN_IF_EXCEPTION(scope, void());
+    }
+}
+
+void JSWebAssemblyInstance::finalizeCreation(VM& vm, JSGlobalObject* globalObject, Ref<Wasm::CalleeGroup>&& wasmCalleeGroup, Wasm::CreationMode creationMode)
+{
     auto scope = DECLARE_THROW_SCOPE(vm);
 
     if (!wasmCalleeGroup->runnable()) {
@@ -121,31 +134,16 @@
     // results, so that later when memory imports become available, the appropriate CalleeGroup can be used.
     // If LLInt is disabled, we instead defer compilation to module evaluation.
     // If the code is already compiled, e.g. the module was already instantiated before, we do not re-initialize.
-    bool hasMemoryImport = module()->moduleInformation().memory.isImport();
-    if (Options::useWasmLLInt() && hasMemoryImport) {
-        Wasm::MemoryMode initialMode = Wasm::MemoryMode::BoundsChecking;
-        ASSERT(memoryMode() == initialMode);
-        module()->module().copyInitialCalleeGroupToAllMemoryModes(initialMode);
-
-        for (unsigned i = 0; i < Wasm::NumberOfMemoryModes; i++) {
-            if (i == static_cast<uint8_t>(initialMode))
-                continue;
-            Wasm::MemoryMode memoryMode = static_cast<Wasm::MemoryMode>(i);
-            module()->module().calleeGroupFor(memoryMode); // Materialize Wasm::CalleeGroup.
-        }
-    }
+    if (Options::useWasmLLInt() && module()->moduleInformation().hasMemoryImport())
+        module()->module().copyInitialCalleeGroupToAllMemoryModes(memoryMode());
 
     for (unsigned importFunctionNum = 0; importFunctionNum < instance().numImportFunctions(); ++importFunctionNum) {
         auto* info = instance().importFunctionInfo(importFunctionNum);
         info->wasmToEmbedderStub = m_module->wasmToEmbedderStub(importFunctionNum);
     }
 
-    m_moduleRecord->prepareLink(vm, this);
-
     if (creationMode == Wasm::CreationMode::FromJS) {
-        m_moduleRecord->link(globalObject, jsNull());
-        RETURN_IF_EXCEPTION(scope, void());
-        m_moduleRecord->initializeImportsAndExports(globalObject, importObject, creationMode);
+        m_moduleRecord->initializeExports(globalObject);
         RETURN_IF_EXCEPTION(scope, void());
 
         JSValue startResult = m_moduleRecord->evaluate(globalObject);
diff --git a/Source/JavaScriptCore/wasm/js/JSWebAssemblyInstance.h b/Source/JavaScriptCore/wasm/js/JSWebAssemblyInstance.h
index 0b1b7a7..d4151c3 100644
--- a/Source/JavaScriptCore/wasm/js/JSWebAssemblyInstance.h
+++ b/Source/JavaScriptCore/wasm/js/JSWebAssemblyInstance.h
@@ -67,7 +67,8 @@
 
     DECLARE_EXPORT_INFO;
 
-    void finalizeCreation(VM&, JSGlobalObject*, Ref<Wasm::CalleeGroup>&&, JSObject* importObject, Wasm::CreationMode);
+    void initializeImports(JSGlobalObject*, JSObject* importObject, Wasm::CreationMode);
+    void finalizeCreation(VM&, JSGlobalObject*, Ref<Wasm::CalleeGroup>&&, Wasm::CreationMode);
     
     Wasm::Instance& instance() { return m_instance.get(); }
     WebAssemblyModuleRecord* moduleRecord() { return m_moduleRecord.get(); }
diff --git a/Source/JavaScriptCore/wasm/js/WebAssemblyInstanceConstructor.cpp b/Source/JavaScriptCore/wasm/js/WebAssemblyInstanceConstructor.cpp
index 7a107e2..7523333 100644
--- a/Source/JavaScriptCore/wasm/js/WebAssemblyInstanceConstructor.cpp
+++ b/Source/JavaScriptCore/wasm/js/WebAssemblyInstanceConstructor.cpp
@@ -72,7 +72,10 @@
     JSWebAssemblyInstance* instance = JSWebAssemblyInstance::tryCreate(vm, globalObject, JSWebAssemblyInstance::createPrivateModuleKey(), module, importObject, instanceStructure, Ref<Wasm::Module>(module->module()), Wasm::CreationMode::FromJS);
     RETURN_IF_EXCEPTION(scope, { });
 
-    instance->finalizeCreation(vm, globalObject, module->module().compileSync(&vm.wasmContext, instance->memoryMode()), importObject, Wasm::CreationMode::FromJS);
+    instance->initializeImports(globalObject, importObject, Wasm::CreationMode::FromJS);
+    RETURN_IF_EXCEPTION(scope, { });
+
+    instance->finalizeCreation(vm, globalObject, module->module().compileSync(&vm.wasmContext, instance->memoryMode()), Wasm::CreationMode::FromJS);
     RETURN_IF_EXCEPTION(scope, { });
     return JSValue::encode(instance);
 }
diff --git a/Source/JavaScriptCore/wasm/js/WebAssemblyModuleRecord.cpp b/Source/JavaScriptCore/wasm/js/WebAssemblyModuleRecord.cpp
index 599e234f6..7fa81e7 100644
--- a/Source/JavaScriptCore/wasm/js/WebAssemblyModuleRecord.cpp
+++ b/Source/JavaScriptCore/wasm/js/WebAssemblyModuleRecord.cpp
@@ -113,11 +113,11 @@
     return Synchronousness::Sync;
 }
 
-void WebAssemblyModuleRecord::initializeImportsAndExports(JSGlobalObject* globalObject, JSObject* importObject, Wasm::CreationMode creationMode)
+// https://webassembly.github.io/spec/js-api/#read-the-imports
+void WebAssemblyModuleRecord::initializeImports(JSGlobalObject* globalObject, JSObject* importObject, Wasm::CreationMode creationMode)
 {
     VM& vm = globalObject->vm();
     auto scope = DECLARE_THROW_SCOPE(vm);
-    UNUSED_PARAM(scope);
 
     RELEASE_ASSERT(m_instance);
 
@@ -422,21 +422,39 @@
             // iii. Append v.[[Memory]] to imports.
             m_instance->setMemory(vm, memory);
             RETURN_IF_EXCEPTION(scope, void());
-
-            // Usually at this point the module's code block in any memory mode should be
-            // runnable due to the LLint tier code being shared among all modes. However,
-            // if LLInt is disabled, it is possible that the code needs to be compiled at
-            // this point when we know which memory mode to use.
-            Wasm::CalleeGroup* calleeGroup = m_instance->instance().calleeGroup();
-            if (!calleeGroup || !calleeGroup->runnable()) {
-                calleeGroup = m_instance->module()->module().compileSync(&vm.wasmContext, memory->memory().mode()).ptr();
-                if (!calleeGroup->runnable())
-                    return exception(createJSWebAssemblyLinkError(globalObject, vm, calleeGroup->errorMessage()));
-            }
-            RELEASE_ASSERT(calleeGroup->isSafeToRun(memory->memory().mode()));
             break;
         }
     }
+}
+
+// https://webassembly.github.io/spec/js-api/#create-an-exports-object
+void WebAssemblyModuleRecord::initializeExports(JSGlobalObject* globalObject)
+{
+    VM& vm = globalObject->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    RELEASE_ASSERT(m_instance);
+
+    JSWebAssemblyModule* module = m_instance->module();
+    const Wasm::ModuleInformation& moduleInformation = module->moduleInformation();
+
+    auto exception = [&] (JSObject* error) {
+        throwException(globalObject, scope, error);
+    };
+
+    if (moduleInformation.hasMemoryImport()) {
+        // Usually at this point the module's code block in any memory mode should be
+        // runnable due to the LLint tier code being shared among all modes. However,
+        // if LLInt is disabled, it is possible that the code needs to be compiled at
+        // this point when we know which memory mode to use.
+        Wasm::CalleeGroup* calleeGroup = m_instance->instance().calleeGroup();
+        if (!calleeGroup || !calleeGroup->runnable()) {
+            calleeGroup = m_instance->module()->module().compileSync(&vm.wasmContext, m_instance->instance().memory()->mode()).ptr();
+            if (!calleeGroup->runnable())
+                return exception(createJSWebAssemblyLinkError(globalObject, vm, calleeGroup->errorMessage()));
+        }
+        RELEASE_ASSERT(calleeGroup->isSafeToRun(m_instance->instance().memory()->mode()));
+    }
 
     for (unsigned i = 0; i < moduleInformation.tableCount(); ++i) {
         if (moduleInformation.tables[i].isImport()) {
diff --git a/Source/JavaScriptCore/wasm/js/WebAssemblyModuleRecord.h b/Source/JavaScriptCore/wasm/js/WebAssemblyModuleRecord.h
index f948ab0..fd92c94 100644
--- a/Source/JavaScriptCore/wasm/js/WebAssemblyModuleRecord.h
+++ b/Source/JavaScriptCore/wasm/js/WebAssemblyModuleRecord.h
@@ -60,7 +60,8 @@
 
     void prepareLink(VM&, JSWebAssemblyInstance*);
     Synchronousness link(JSGlobalObject*, JSValue scriptFetcher);
-    void initializeImportsAndExports(JSGlobalObject*, JSObject* importObject, Wasm::CreationMode);
+    void initializeImports(JSGlobalObject*, JSObject* importObject, Wasm::CreationMode);
+    void initializeExports(JSGlobalObject*);
     JS_EXPORT_PRIVATE JSValue evaluate(JSGlobalObject*);
 
     JSObject* exportsObject() const { return m_exportsObject.get(); }