From a3b3e5a10e9821a976066e2a3c83d6692440aed3 Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Thu, 27 Aug 2026 09:57:06 +0000 Subject: [PATCH 01/16] [JSC] Module graph instances: instantiate a linked module graph more than once in one global object Behind Options::useModuleGraphInstances() (off by default; nothing changes when it is off apart from one metadata field on op_resolve_scope). A ModuleGraphInstance (new cell) is one further instantiation of a module graph in a global object. It maps each module record instantiated for it to a ModuleRecordInstance (new cell) holding that record's JSModuleEnvironment in the instance and its evaluation state there ([[Status]], [[DFSAncestorIndex]], [[CycleRoot]], [[AsyncEvaluationOrder]], [[PendingAsyncDependencies]], [[AsyncParentModules]], [[TopLevelCapability]], [[EvaluationError]], the generator state of a top-level-await body, the deferred namespace object). Records an instance does not instantiate are shared with the primary graph. The ModuleRecordInstance is also the generator object / for-await driver of the instance's module body and the context of its asynchronous completion microtasks. clear() releases an instance's state, rejects its pending top-level evaluation promises, is deferred while an evaluation step runs against the instance (BusyScope), and a cleared instance creates no further state. Module records: JSModuleRecord::createInstanceEnvironment builds a record's environment for an instance the way link() builds the primary one (function declarations from the retained unlinked executables, namespace and namespace-resolving imports bound to the instance's namespaces). Imported bindings resolve through per-environment import slots (JSModuleEnvironment::importedEnvironmentFor) rather than the record's single environment, so the same linked CodeBlocks serve every instance. CyclicModuleRecord::evaluate / InnerModuleEvaluation / ExecuteAsyncModule / AsyncModuleExecution{Fulfilled,Rejected} / GatherAvailableAncestors and the import-defer helpers take the ModuleGraphInstance being evaluated (null: the primary graph) and read/write the record's state in that instance. SyntheticModuleRecord (JSON and host-provided modules) gets a per-instance environment with regenerated (or structurally cloned plain-data) values when its provider asks for it; a host provider may defer producing the primary's values until the primary is first used. ModuleRegistryEntry::hasSettledFailure lets a load on behalf of an instance retry an entry whose load failed earlier. Loader / global object: JSModuleLoader::linkWithoutEvaluating fetches and links a graph as a template without evaluating the primary; loadModuleForGraphInstance / instantiateLoadedModuleIntoGraphInstance / importIntoGraphInstance instantiate a loaded graph into an instance and run Evaluate() against it. Module namespace objects are per (record, instance) and read bindings from the instance's environments; import() and import.meta from instance code resolve into the caller's instance (JSGlobalObject::graphInstanceForScope via the callee scope). A global object can configure a scope overlay: a lexical environment with a fixed set of names inserted under module environments (one per instance, one for the primary graph) whose slots shadow those global identifiers for module code; it must be configured before the first module is linked. ModuleVar resolution: op_resolve_scope for a ModuleVar carries the import slot index in its metadata; slot 0 keeps the constant-environment fast path, otherwise the environment is loaded from the current module environment's import slot (LLInt and baseline inline; DFG folds it to a constant when the exporter's symbol table proves a single environment and otherwise emits the slot load); the slow paths and JSScope resolve through JSModuleEnvironment::importedEnvironmentFor. Also: an exception check after FunctionExecutable::fromGlobalCode in the Function constructor (independent; host error-info hooks may declare throw scopes), and $vm.instantiateModuleGraph as a test hook. --- Source/JavaScriptCore/CMakeLists.txt | 2 + Source/JavaScriptCore/Sources.txt | 1 + Source/JavaScriptCore/builtins/BuiltinNames.h | 1 + .../JavaScriptCore/bytecode/BytecodeList.rb | 1 + Source/JavaScriptCore/bytecode/CodeBlock.cpp | 1 + .../JavaScriptCore/dfg/DFGByteCodeParser.cpp | 46 ++- Source/JavaScriptCore/heap/Heap.cpp | 1 + Source/JavaScriptCore/heap/Heap.h | 2 + .../JavaScriptCore/interpreter/CallFrame.cpp | 34 ++ Source/JavaScriptCore/interpreter/CallFrame.h | 5 + .../interpreter/Interpreter.cpp | 17 +- .../JavaScriptCore/interpreter/Interpreter.h | 3 + Source/JavaScriptCore/jit/JITOperations.cpp | 22 +- .../JavaScriptCore/jit/JITPropertyAccess.cpp | 26 +- .../llint/LowLevelInterpreter64.asm | 19 + Source/JavaScriptCore/parser/SourceProvider.h | 8 + .../runtime/AbstractModuleRecord.cpp | 211 ++++++++--- .../runtime/AbstractModuleRecord.h | 34 +- .../runtime/CommonSlowPaths.cpp | 21 +- .../runtime/CyclicModuleRecord.cpp | 329 +++++++++++++----- .../runtime/CyclicModuleRecord.h | 52 ++- .../runtime/FunctionConstructor.cpp | 2 + Source/JavaScriptCore/runtime/GetPutInfo.h | 3 + .../runtime/JSAsyncGeneratorInlines.h | 3 +- .../JavaScriptCore/runtime/JSGlobalObject.cpp | 139 ++++++++ .../JavaScriptCore/runtime/JSGlobalObject.h | 27 ++ .../runtime/JSGlobalObjectFunctions.cpp | 15 + Source/JavaScriptCore/runtime/JSMicrotask.cpp | 61 +++- Source/JavaScriptCore/runtime/JSMicrotask.h | 3 + .../runtime/JSModuleEnvironment.cpp | 84 ++++- .../runtime/JSModuleEnvironment.h | 61 +++- .../JavaScriptCore/runtime/JSModuleLoader.cpp | 202 ++++++++++- .../JavaScriptCore/runtime/JSModuleLoader.h | 17 +- .../runtime/JSModuleNamespaceObject.cpp | 46 ++- .../runtime/JSModuleNamespaceObject.h | 13 + .../JavaScriptCore/runtime/JSModuleRecord.cpp | 326 ++++++++++++++++- .../JavaScriptCore/runtime/JSModuleRecord.h | 38 ++ Source/JavaScriptCore/runtime/JSScope.cpp | 2 + .../runtime/ModuleGraphInstance.cpp | 194 +++++++++++ .../runtime/ModuleGraphInstance.h | 204 +++++++++++ .../runtime/ModuleGraphInstanceInlines.h | 43 +++ .../runtime/ModuleRegistryEntry.cpp | 10 + .../runtime/ModuleRegistryEntry.h | 3 + Source/JavaScriptCore/runtime/OptionsList.h | 1 + .../runtime/SyntheticModuleRecord.cpp | 262 +++++++++++++- .../runtime/SyntheticModuleRecord.h | 23 ++ Source/JavaScriptCore/runtime/VM.cpp | 3 + Source/JavaScriptCore/runtime/VM.h | 1 + Source/JavaScriptCore/tools/JSDollarVM.cpp | 38 ++ 49 files changed, 2477 insertions(+), 183 deletions(-) create mode 100644 Source/JavaScriptCore/runtime/ModuleGraphInstance.cpp create mode 100644 Source/JavaScriptCore/runtime/ModuleGraphInstance.h create mode 100644 Source/JavaScriptCore/runtime/ModuleGraphInstanceInlines.h diff --git a/Source/JavaScriptCore/CMakeLists.txt b/Source/JavaScriptCore/CMakeLists.txt index fa79d0d4aade..8bc110ead1ab 100644 --- a/Source/JavaScriptCore/CMakeLists.txt +++ b/Source/JavaScriptCore/CMakeLists.txt @@ -1594,6 +1594,8 @@ set(JavaScriptCore_PRIVATE_FRAMEWORK_HEADERS runtime/ModuleMap.h runtime/ModuleProgramExecutable.h runtime/ModuleProgramExecutableInlines.h + runtime/ModuleGraphInstance.h + runtime/ModuleGraphInstanceInlines.h runtime/ModuleRegistryEntry.h runtime/NarrowingNumberPredictionFuzzerAgent.h runtime/NativeCallee.h diff --git a/Source/JavaScriptCore/Sources.txt b/Source/JavaScriptCore/Sources.txt index 23315183aaba..c24c5b8055e6 100644 --- a/Source/JavaScriptCore/Sources.txt +++ b/Source/JavaScriptCore/Sources.txt @@ -1009,6 +1009,7 @@ runtime/ModuleGraphLoadingState.cpp runtime/ModuleLoadingContext.cpp runtime/ModuleLoaderPayload.cpp runtime/ModuleProgramExecutable.cpp +runtime/ModuleGraphInstance.cpp runtime/ModuleRegistryEntry.cpp runtime/NarrowingNumberPredictionFuzzerAgent.cpp runtime/NativeCallee.cpp diff --git a/Source/JavaScriptCore/builtins/BuiltinNames.h b/Source/JavaScriptCore/builtins/BuiltinNames.h index 51c5992c124c..e7e3f6a72e59 100644 --- a/Source/JavaScriptCore/builtins/BuiltinNames.h +++ b/Source/JavaScriptCore/builtins/BuiltinNames.h @@ -194,6 +194,7 @@ namespace JSC { macro(copyDataProperties) \ macro(cloneObject) \ macro(meta) \ + macro(moduleGraphInstance) \ macro(instanceFieldInitializer) \ macro(privateBrand) \ macro(privateClassBrand) \ diff --git a/Source/JavaScriptCore/bytecode/BytecodeList.rb b/Source/JavaScriptCore/bytecode/BytecodeList.rb index 08ad777dbbd9..c8f5e9f3c949 100644 --- a/Source/JavaScriptCore/bytecode/BytecodeList.rb +++ b/Source/JavaScriptCore/bytecode/BytecodeList.rb @@ -522,6 +522,7 @@ }, metadata: { resolveType: ResolveType, # offset 4 + moduleImportSlot: unsigned, # ModuleVar: 1 + import slot ScopeOffset in the importing environment, 0 = none _0: { # offset 5 localScopeDepth: unsigned, globalLexicalBindingEpoch: unsigned, diff --git a/Source/JavaScriptCore/bytecode/CodeBlock.cpp b/Source/JavaScriptCore/bytecode/CodeBlock.cpp index d8915876a702..4212a6ae7592 100644 --- a/Source/JavaScriptCore/bytecode/CodeBlock.cpp +++ b/Source/JavaScriptCore/bytecode/CodeBlock.cpp @@ -587,6 +587,7 @@ bool CodeBlock::finishCreation(VM& vm, ScriptExecutable* ownerExecutable, Unlink metadata.m_resolveType = op.type; metadata.m_localScopeDepth = op.depth; + metadata.m_moduleImportSlot = op.moduleImportSlot; if (op.lexicalEnvironment) { if (op.type == ModuleVar) { // Keep the linked module environment strongly referenced. diff --git a/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp b/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp index b57e685f8bae..b8e2a629637c 100644 --- a/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp +++ b/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp @@ -10411,6 +10411,7 @@ void ByteCodeParser::parseBlock(unsigned limit) ResolveType resolveType; unsigned depth; + unsigned moduleImportSlot = 0; JSScope* constantScope = nullptr; JSCell* lexicalEnvironment = nullptr; SymbolTable* symbolTable = nullptr; @@ -10418,6 +10419,7 @@ void ByteCodeParser::parseBlock(unsigned limit) ConcurrentJSLocker locker(m_inlineStackTop->m_profiledBlock->m_lock); resolveType = metadata.m_resolveType; depth = metadata.m_localScopeDepth; + moduleImportSlot = metadata.m_moduleImportSlot; switch (resolveType) { case GlobalProperty: case GlobalVar: @@ -10471,11 +10473,45 @@ void ByteCodeParser::parseBlock(unsigned limit) break; } case ModuleVar: { - // Module environment is already strongly referenced by the CodeBlock. - set(bytecode.m_dst, weakJSConstant(lexicalEnvironment)); - // BytecodeUseDef reports m_scope as a use regardless of resolve type, - // so we need to keep it OSR-available even though LLInt won't read it. - addToGraph(Phantom, get(bytecode.m_scope)); + Node* localBase = get(bytecode.m_scope); + addToGraph(Phantom, localBase); + if (!moduleImportSlot) { + // No import slot (module graph instances off): the linked + // exporter environment is the only one there is. + set(bytecode.m_dst, weakJSConstant(lexicalEnvironment)); + break; + } + // Module graph instances: the exporter environment is the importing + // environment's import slot — a closure-variable-like load. It folds + // to the linked exporter environment while that is the exporting + // module's only environment (its symbol table's singleton; the + // watchpoint fires when an instance creates a second one), and to + // the slot's value when the importing environment is a constant here. + ScopeOffset slot(moduleImportSlot - 1); + if (lexicalEnvironment) { + SymbolTable* exporterSymbolTable = uncheckedDowncast(lexicalEnvironment)->symbolTable(); + if (exporterSymbolTable->singleton().inferredValue() == lexicalEnvironment) { + m_graph.watchpoints().addLazily(m_graph, exporterSymbolTable); + set(bytecode.m_dst, weakJSConstant(lexicalEnvironment)); + break; + } + } + if (JSScope* constantScope = localBase->dynamicCastConstant()) { + for (unsigned n = depth; n--;) + constantScope = constantScope->next(); + if (auto* importer = dynamicDowncast(constantScope)) { + if (JSValue exporter = importer->variableAt(slot).get()) { + set(bytecode.m_dst, weakJSConstant(exporter.asCell())); + break; + } + } + } + for (unsigned n = depth; n--;) + localBase = addToGraph(SkipScope, localBase); + Node* exporter = addToGraph(GetClosureVar, OpInfo(slot.offset()), OpInfo(SpecObjectOther), localBase); + addToGraph(CheckNotEmpty, exporter); + addToGraph(Check, Edge(exporter, CellUse)); + set(bytecode.m_dst, exporter); break; } case ResolvedClosureVar: diff --git a/Source/JavaScriptCore/heap/Heap.cpp b/Source/JavaScriptCore/heap/Heap.cpp index 2b2046fd4f3d..6e0ccb214570 100644 --- a/Source/JavaScriptCore/heap/Heap.cpp +++ b/Source/JavaScriptCore/heap/Heap.cpp @@ -75,6 +75,7 @@ #include "MarkedSpaceInlines.h" #include "MarkingConstraintSet.h" #include "MegamorphicCache.h" +#include "ModuleGraphInstance.h" #include "ModuleLoadingContext.h" #include "ModuleProgramExecutable.h" #include "ModuleRegistryEntry.h" diff --git a/Source/JavaScriptCore/heap/Heap.h b/Source/JavaScriptCore/heap/Heap.h index 61ee1f3769ea..f9db1836a747 100644 --- a/Source/JavaScriptCore/heap/Heap.h +++ b/Source/JavaScriptCore/heap/Heap.h @@ -274,6 +274,8 @@ class Heap; v(jsModuleRecordSpace, jsModuleRecordHeapCellType, JSModuleRecord) \ v(moduleRegistryEntrySpace, destructibleCellHeapCellType, ModuleRegistryEntry) \ v(moduleLoadingContextSpace, destructibleCellHeapCellType, ModuleLoadingContext) \ + v(moduleGraphInstanceSpace, destructibleObjectHeapCellType, ModuleGraphInstance) \ + v(moduleRecordInstanceSpace, destructibleCellHeapCellType, ModuleRecordInstance) \ v(sentinelSpace, cellHeapCellType, JSSentinel) \ v(syntheticModuleRecordSpace, syntheticModuleRecordHeapCellType, SyntheticModuleRecord) \ v(jsMicrotaskDispatcherSpace, destructibleCellHeapCellType, JSMicrotaskDispatcher) \ diff --git a/Source/JavaScriptCore/interpreter/CallFrame.cpp b/Source/JavaScriptCore/interpreter/CallFrame.cpp index 9a325c190752..9544993c1040 100644 --- a/Source/JavaScriptCore/interpreter/CallFrame.cpp +++ b/Source/JavaScriptCore/interpreter/CallFrame.cpp @@ -31,6 +31,7 @@ #include "ExecutableAllocator.h" #include "InlineCallFrame.h" #include "JSCInlines.h" +#include "JSCallee.h" #include "JSWebAssemblyInstance.h" #include "JSWebAssemblyModule.h" #include "LLIntPCRanges.h" @@ -194,6 +195,39 @@ SUPPRESS_ASAN CallFrame* CallFrame::unsafeCallerFrame(EntryFrame*& currEntryFram return static_cast(unsafeCallerFrameOrEntryFrame()); } + +JSScope* CallFrame::callerScope(VM& vm) +{ + RELEASE_ASSERT(callee().isCell()); + JSScope* found = nullptr; + bool haveSkippedFirstFrame = false; + StackVisitor::visit(this, vm, [&](StackVisitor& visitor) { + if (!std::exchange(haveSkippedFirstFrame, true)) + return IterationStatus::Continue; + switch (visitor->codeType()) { + case StackVisitor::Frame::CodeType::Native: + case StackVisitor::Frame::CodeType::Wasm: + return IterationStatus::Continue; + case StackVisitor::Frame::CodeType::Function: + case StackVisitor::Frame::CodeType::Module: + case StackVisitor::Frame::CodeType::Eval: + case StackVisitor::Frame::CodeType::Global: + break; + } + // The callee carries the scope its code was created with at every tier + // (the scope register is not materialized in optimized frames). + JSCell* calleeCell = visitor->callee().asCell(); + if (auto* function = dynamicDowncast(calleeCell)) { + if (!function->isHostFunction() && function->jsExecutable()->isPrivateBuiltinFunction()) + return IterationStatus::Continue; + found = function->scope(); + } else if (auto* callee = dynamicDowncast(calleeCell)) + found = callee->scope(); + return IterationStatus::Done; + }); + return found; +} + SourceOrigin CallFrame::callerSourceOrigin(VM& vm) { RELEASE_ASSERT(callee().isCell()); diff --git a/Source/JavaScriptCore/interpreter/CallFrame.h b/Source/JavaScriptCore/interpreter/CallFrame.h index 517d88a61d59..6312eaa76088 100644 --- a/Source/JavaScriptCore/interpreter/CallFrame.h +++ b/Source/JavaScriptCore/interpreter/CallFrame.h @@ -228,6 +228,11 @@ using JSInstruction = BaseInstruction; JS_EXPORT_PRIVATE CallFrame* callerFrame(EntryFrame*&) const; JS_EXPORT_PRIVATE SourceOrigin callerSourceOrigin(VM&); + // Module graph instances (prototype): the module environment the calling JS + // code closes over (its own for module code; via the callee's scope chain for + // functions), or null. Lets import() load into the caller's graph instance. + // The scope the calling JS code closes over (null for native callers). + JS_EXPORT_PRIVATE JSScope* callerScope(VM&); static constexpr ptrdiff_t callerFrameOffset() { return OBJECT_OFFSETOF(CallerFrameAndPC, callerFrame); } diff --git a/Source/JavaScriptCore/interpreter/Interpreter.cpp b/Source/JavaScriptCore/interpreter/Interpreter.cpp index 32e6f65ca0af..bd365ec0287f 100644 --- a/Source/JavaScriptCore/interpreter/Interpreter.cpp +++ b/Source/JavaScriptCore/interpreter/Interpreter.cpp @@ -73,6 +73,7 @@ #include "LLIntThunks.h" #include "LiteralParser.h" #include "MicrotaskCall.h" +#include "ModuleGraphInstance.h" #include "ModuleProgramCodeBlock.h" #include "NativeCallee.h" #include "ProgramCodeBlock.h" @@ -1708,6 +1709,11 @@ JSValue Interpreter::executeEval(EvalExecutable* eval, JSValue thisValue, JSScop } JSValue Interpreter::executeModuleProgram(JSModuleRecord* record, ModuleProgramExecutable* executable, JSGlobalObject* lexicalGlobalObject, JSModuleEnvironment* scope, JSValue sentValue, JSValue resumeMode) +{ + return executeModuleProgram(record, record, executable, lexicalGlobalObject, scope, sentValue, resumeMode); +} + +JSValue Interpreter::executeModuleProgram(JSModuleRecord* record, JSObject* generatorState, ModuleProgramExecutable* executable, JSGlobalObject* lexicalGlobalObject, JSModuleEnvironment* scope, JSValue sentValue, JSValue resumeMode) { VM& vm = this->vm(); auto throwScope = DECLARE_THROW_SCOPE(vm); @@ -1737,9 +1743,14 @@ JSValue Interpreter::executeModuleProgram(JSModuleRecord* record, ModuleProgramE RefPtr jitCode; ProtoCallFrame protoCallFrame; + auto stateField = [&]() -> WriteBarrier& { + if (generatorState == record) + return record->internalField(JSModuleRecord::Field::State); + return uncheckedDowncast(generatorState)->internalField(ModuleRecordInstance::Field::State); + }; EncodedJSValue args[numberOfArguments] = { - JSValue::encode(record), - JSValue::encode(record->internalField(JSModuleRecord::Field::State).get()), + JSValue::encode(generatorState), + JSValue::encode(stateField().get()), JSValue::encode(sentValue), JSValue::encode(resumeMode), JSValue::encode(scope), @@ -1767,7 +1778,7 @@ JSValue Interpreter::executeModuleProgram(JSModuleRecord* record, ModuleProgramE protoCallFrame.init(codeBlock, globalObject, callee, jsUndefined(), nullptr, numberOfArguments + 1, args); } - record->internalField(JSModuleRecord::Field::State).set(vm, record, jsNumber(static_cast(JSModuleRecord::State::Executing))); + stateField().set(vm, generatorState, jsNumber(static_cast(JSModuleRecord::State::Executing))); } // Execute the code: diff --git a/Source/JavaScriptCore/interpreter/Interpreter.h b/Source/JavaScriptCore/interpreter/Interpreter.h index ca093c05f926..27ffd1f290ac 100644 --- a/Source/JavaScriptCore/interpreter/Interpreter.h +++ b/Source/JavaScriptCore/interpreter/Interpreter.h @@ -161,6 +161,9 @@ using JSOrWasmInstruction = Variantas(); const Identifier& ident = codeBlock->identifier(bytecode.m_var); JSScope* environment = callFrame->uncheckedR(bytecode.m_scope).Register::scope(); + auto& metadata = bytecode.metadata(codeBlock); + + if (metadata.m_resolveType == ModuleVar) { + // See slow_path_resolve_scope: the importing module environment on this + // scope chain decides which graph instance's exporter environment to use. + JSModuleEnvironment* linkedExporter = uncheckedDowncast(metadata.m_lexicalEnvironment.get()); + JSScope* cursor = environment; + for (unsigned i = 0; i < metadata.m_localScopeDepth; ++i) + cursor = cursor->next(); + JSObject* result = linkedExporter; + if (auto* importer = dynamicDowncast(cursor); importer && importer->graphInstance()) + result = importer->importedEnvironmentFor(globalObject, linkedExporter->moduleRecord()); + OPERATION_RETURN_IF_EXCEPTION(scope, encodedJSValue()); + OPERATION_RETURN(scope, JSValue::encode(result)); + } + JSObject* resolvedScope = JSScope::resolve(globalObject, environment, ident); // Proxy can throw an error here, e.g. Proxy in with statement's @unscopables. OPERATION_RETURN_IF_EXCEPTION(scope, encodedJSValue()); - auto& metadata = bytecode.metadata(codeBlock); ResolveType resolveType = metadata.m_resolveType; - // ModuleVar does not keep the scope register value alive in DFG. - ASSERT(resolveType != ModuleVar); - switch (resolveType) { case GlobalProperty: case GlobalPropertyWithVarInjectionChecks: diff --git a/Source/JavaScriptCore/jit/JITPropertyAccess.cpp b/Source/JavaScriptCore/jit/JITPropertyAccess.cpp index e8e5e0835fb6..5da43c354fdb 100644 --- a/Source/JavaScriptCore/jit/JITPropertyAccess.cpp +++ b/Source/JavaScriptCore/jit/JITPropertyAccess.cpp @@ -896,9 +896,27 @@ void JIT::emit_op_resolve_scope(const JSInstruction* currentInstruction) // If we profile certain resolve types, we're guaranteed all linked code will have the same // resolve type. - if (profiledResolveType == ModuleVar) - loadPtrFromMetadata(bytecode, Metadata::offsetOfLexicalEnvironment(), returnValueGPR); - else if (profiledResolveType == ClosureVar) { + if (profiledResolveType == ModuleVar) { + unsigned moduleImportSlot = bytecode.metadata(m_profiledCodeBlock).m_moduleImportSlot; + if (!moduleImportSlot) { + // No import slot (module graph instances off, or a binding that is + // not an import): the exporter environment is a link-time constant. + loadPtrFromMetadata(bytecode, Metadata::offsetOfLexicalEnvironment(), returnValueGPR); + } else { + // Module graph instances: the exporter environment is read from the + // importing environment's import slot, so the same code serves every + // instance. Walk to the importing environment and load the slot; an + // unfilled slot goes to the slow path. + emitGetVirtualRegister(scope, scopeGPR); + static_assert(scopeGPR == returnValueGPR); + unsigned localScopeDepth = bytecode.metadata(m_profiledCodeBlock).m_localScopeDepth; + for (unsigned index = 0; index < localScopeDepth; ++index) + loadPtr(Address(returnValueGPR, JSScope::offsetOfNext()), returnValueGPR); + static_assert(sizeof(WriteBarrier) == 8); + load64(Address(returnValueGPR, JSLexicalEnvironment::offsetOfVariables() + (moduleImportSlot - 1) * sizeof(WriteBarrier)), returnValueGPR); + addSlowCase(branchIfEmpty(returnValueGPR)); + } + } else if (profiledResolveType == ClosureVar) { emitGetVirtualRegister(scope, scopeGPR); static_assert(scopeGPR == returnValueGPR); unsigned localScopeDepth = bytecode.metadata(m_profiledCodeBlock).m_localScopeDepth; @@ -1093,10 +1111,10 @@ MacroAssemblerCodeRef JIT::generateOpResolveScopeThunk(VM& vm) emitResolveClosure(needsVarInjectionChecks(resolveType)); break; case Dynamic: + case ModuleVar: slowCase.append(jit.jump()); break; case ResolvedClosureVar: - case ModuleVar: case UnresolvedProperty: case UnresolvedPropertyWithVarInjectionChecks: RELEASE_ASSERT_NOT_REACHED(); diff --git a/Source/JavaScriptCore/llint/LowLevelInterpreter64.asm b/Source/JavaScriptCore/llint/LowLevelInterpreter64.asm index 57d8b0403d8e..7bf8870e3236 100644 --- a/Source/JavaScriptCore/llint/LowLevelInterpreter64.asm +++ b/Source/JavaScriptCore/llint/LowLevelInterpreter64.asm @@ -2849,6 +2849,25 @@ llintOpWithMetadata(op_resolve_scope, OpResolveScope, macro (size, get, dispatch .rModuleVar: bineq t0, ModuleVar, .rGlobalPropertyWithVarInjectionChecks + # With module graph instances the exporting environment is per instance: it + # is the importing environment's import slot (walk localScopeDepth to the + # importing module environment, load the slot; empty = not filled yet). + loadi OpResolveScope::Metadata::m_moduleImportSlot[t5], t1 + btiz t1, .rModuleVarConstant + loadi OpResolveScope::Metadata::m_localScopeDepth[t5], t2 + get(m_scope, t0) + loadq [cfr, t0, 8], t0 + btiz t2, .rModuleVarLoad +.rModuleVarWalk: + loadp JSScope::m_next[t0], t0 + subi 1, t2 + btinz t2, .rModuleVarWalk +.rModuleVarLoad: + subi 1, t1 + loadq JSLexicalEnvironment_variables[t0, t1, 8], t0 + btqz t0, .rDynamic + return(t0) +.rModuleVarConstant: returnConstantScope() .rGlobalPropertyWithVarInjectionChecks: diff --git a/Source/JavaScriptCore/parser/SourceProvider.h b/Source/JavaScriptCore/parser/SourceProvider.h index cbb77358b111..e023f18fe800 100644 --- a/Source/JavaScriptCore/parser/SourceProvider.h +++ b/Source/JavaScriptCore/parser/SourceProvider.h @@ -211,6 +211,13 @@ class StringSourceProvider : public SourceProvider { return adoptRef(*new SyntheticSourceProvider(nullptr, WTF::move(generator), sourceOrigin, WTF::move(sourceURL))); } + // Module graph instances: a generator that yields a fresh module per call + // and honours JSGlobalObject::currentGraphInstanceForLoading() (e.g. a + // CommonJS module evaluated in that graph's cache) — such records get + // their own environment per graph and a lazily produced primary. + void setRegeneratesPerGraphInstance(bool value) { m_regeneratesPerGraphInstance = value; } + bool regeneratesPerGraphInstance() const { return m_regeneratesPerGraphInstance; } + unsigned hash() const final { return m_source.impl()->hash(); @@ -243,6 +250,7 @@ class StringSourceProvider : public SourceProvider { String m_source; SyntheticSourceGenerator m_generator; LazySyntheticSourceGenerator m_lazyGenerator; + bool m_regeneratesPerGraphInstance { false }; }; #if ENABLE(WEBASSEMBLY) diff --git a/Source/JavaScriptCore/runtime/AbstractModuleRecord.cpp b/Source/JavaScriptCore/runtime/AbstractModuleRecord.cpp index 38addc466613..9948fdf7cd36 100644 --- a/Source/JavaScriptCore/runtime/AbstractModuleRecord.cpp +++ b/Source/JavaScriptCore/runtime/AbstractModuleRecord.cpp @@ -35,6 +35,7 @@ #include "JSModuleLoader.h" #include "JSModuleNamespaceObject.h" #include "JSModuleRecord.h" +#include "ModuleGraphInstance.h" #include "JSPromise.h" #include "ObjectConstructor.h" #include "SyntheticModuleRecord.h" @@ -99,6 +100,8 @@ void AbstractModuleRecord::visitChildrenImpl(JSCell* cell, Visitor& visitor) visitor.append(thisObject->m_topLevelCapability); visitor.append(thisObject->m_asyncCapability); Locker locker { thisObject->cellLock() }; + for (auto& record : thisObject->m_importedRecords) + visitor.append(record); visitor.append(thisObject->m_asyncParentModules.begin(), thisObject->m_asyncParentModules.end()); for (const auto& [key, loadedModule] : thisObject->m_loadedModules) visitor.append(loadedModule.m_module); @@ -828,6 +831,94 @@ auto AbstractModuleRecord::resolveExport(JSGlobalObject* globalObject, const Ide RELEASE_AND_RETURN(scope, resolveExportImpl(globalObject, ResolveQuery(this, exportName.impl()))); } + +JSModuleEnvironment* AbstractModuleRecord::graphInstanceEnvironment(JSGlobalObject* globalObject, ModuleGraphInstance* instance, bool createForSynthetic) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + if (JSModuleEnvironment* existing = instance->environment(this)) + return existing; + if (!createForSynthetic) + return nullptr; + if (instance->isCleared()) { + throwTypeError(globalObject, scope, "Module graph instance was disposed"_s); + return nullptr; + } + auto* synthetic = dynamicDowncast(this); + if (!synthetic || !synthetic->hasPerGraphInstanceState()) + return nullptr; + ModuleGraphInstance* previousLoadingInstance = globalObject->currentGraphInstanceForLoading(); + globalObject->setCurrentGraphInstanceForLoading(vm, instance); + JSModuleEnvironment* environment = synthetic->createGraphInstanceEnvironment(globalObject); + globalObject->setCurrentGraphInstanceForLoading(vm, previousLoadingInstance); + RETURN_IF_EXCEPTION(scope, nullptr); + environment->setGraphInstance(vm, instance); + instance->add(vm, this, environment); + return environment; +} + +// GetModuleNamespace for the record as instantiated in `instance`: one namespace +// object per (instance, module), cached in the instance environment's +// *namespace* binding (deferred namespaces on the ModuleRecordInstance). +// Records the instance shares with the primary graph answer the primary's. +JSModuleNamespaceObject* AbstractModuleRecord::getModuleNamespace(JSGlobalObject* globalObject, ModuleGraphInstance* instance, ModulePhase phase) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + if (!instance) + RELEASE_AND_RETURN(scope, getModuleNamespace(globalObject, phase)); + if (instance->isCleared()) { + throwTypeError(globalObject, scope, "Module graph instance was disposed"_s); + return nullptr; + } + JSModuleEnvironment* environment = graphInstanceEnvironment(globalObject, instance, true); + RETURN_IF_EXCEPTION(scope, nullptr); + if (!environment) + RELEASE_AND_RETURN(scope, getModuleNamespace(globalObject, phase)); + ModuleRecordInstance* recordInstance = instance->recordInstance(this); + ASSERT(recordInstance); + if (phase == ModulePhase::Defer) { + if (JSModuleNamespaceObject* deferred = recordInstance->deferredNamespaceObject()) + return deferred; + } + ScopeOffset namespaceOffset; + { + SymbolTable* symbolTable = environment->symbolTable(); + ConcurrentJSLocker locker(symbolTable->m_lock); + auto iterator = symbolTable->find(locker, vm.propertyNames->starNamespacePrivateName.impl()); + ASSERT(iterator != symbolTable->end(locker)); + namespaceOffset = iterator->value.scopeOffset(); + } + if (phase != ModulePhase::Defer) { + JSValue cached = environment->variableAt(namespaceOffset).get(); + if (cached && cached.isCell()) { + if (auto* namespaceObject = dynamicDowncast(cached.asCell())) + return namespaceObject; + } + } + // Same exported names as the primary namespace (which caches the resolutions). + JSModuleNamespaceObject* primary = getModuleNamespace(globalObject); + RETURN_IF_EXCEPTION(scope, nullptr); + PropertyNameArrayBuilder names(vm, PropertyNameMode::Strings, PrivateSymbolMode::Exclude); + JSModuleNamespaceObject::getOwnPropertyNames(primary, globalObject, names, DontEnumPropertiesMode::Include); + RETURN_IF_EXCEPTION(scope, nullptr); + Vector> resolutions; + for (auto& name : names) { + Resolution resolution = resolveExport(globalObject, name); + RETURN_IF_EXCEPTION(scope, nullptr); + if (resolution.type == Resolution::Type::Resolved) + resolutions.append({ name, resolution }); + } + auto* object = JSModuleNamespaceObject::create(globalObject, globalObject->moduleNamespaceObjectStructure(), this, WTF::move(resolutions), true, phase == ModulePhase::Defer); + RETURN_IF_EXCEPTION(scope, nullptr); + object->setGraphInstance(vm, instance, environment); + if (phase == ModulePhase::Defer) + recordInstance->setDeferredNamespaceObject(vm, object); + else + environment->variableAt(namespaceOffset).set(vm, environment, object); + return object; +} + JSModuleNamespaceObject* AbstractModuleRecord::getModuleNamespace(JSGlobalObject* globalObject, ModulePhase phase, bool shouldPreventExtensions) { VM& vm = globalObject->vm(); @@ -980,7 +1071,7 @@ JSModuleNamespaceObject* AbstractModuleRecord::getModuleNamespace(JSGlobalObject } // https://tc39.es/proposal-defer-import-eval/#sec-GatherAsynchronousTransitiveDependencies -void AbstractModuleRecord::gatherAsynchronousTransitiveDependencies(OrderedHashSet& result, UncheckedKeyHashSet& seen) +void AbstractModuleRecord::gatherAsynchronousTransitiveDependencies(OrderedHashSet& result, UncheckedKeyHashSet& seen, ModuleGraphInstance* instance) { // The spec text is recursive; we use an explicit work list to avoid native stack overflow on // deep graphs. Children are pushed in reverse to preserve the spec's pre-order discovery order. @@ -997,7 +1088,7 @@ void AbstractModuleRecord::gatherAsynchronousTransitiveDependencies(OrderedHashS if (!cyclic) continue; // 6. If module.[[Status]] is either EVALUATING or IsModuleSCCEvaluated(module), return result. - if (cyclic->status() == CyclicModuleRecord::Status::Evaluating || cyclic->isSCCEvaluated()) + if (cyclic->status(instance) == CyclicModuleRecord::Status::Evaluating || cyclic->isSCCEvaluated(instance)) continue; // 7. If module.[[HasTLA]] is true, then if (cyclic->hasTLA()) { @@ -1018,7 +1109,7 @@ void AbstractModuleRecord::gatherAsynchronousTransitiveDependencies(OrderedHashS } // https://tc39.es/proposal-defer-import-eval/#sec-ReadyForSyncExecution -bool AbstractModuleRecord::readyForSyncExecution() +bool AbstractModuleRecord::readyForSyncExecution(ModuleGraphInstance* instance) { // The spec text is recursive; we use an explicit work list to avoid native stack overflow on deep graphs. UncheckedKeyHashSet seen; @@ -1035,16 +1126,16 @@ bool AbstractModuleRecord::readyForSyncExecution() if (!seen.add(module).isNewEntry) continue; // 5. If IsModuleSCCEvaluated(module), return true. - if (cyclic->isSCCEvaluated()) + if (cyclic->isSCCEvaluated(instance)) continue; // 6. If module.[[Status]] is either EVALUATING or EVALUATING-ASYNC, return false. - if (cyclic->status() == CyclicModuleRecord::Status::Evaluating || cyclic->status() == CyclicModuleRecord::Status::EvaluatingAsync) + if (cyclic->status(instance) == CyclicModuleRecord::Status::Evaluating || cyclic->status(instance) == CyclicModuleRecord::Status::EvaluatingAsync) return false; // 7. Assert: module.[[Status]] is LINKED or EVALUATED. // EVALUATED is reachable for a module whose own body has run inside a cycle that is still // awaiting; the walk below then reaches its EVALUATING-ASYNC cycle root and returns false. // https://github.com/tc39/proposal-defer-import-eval/issues/86 - ASSERT(cyclic->status() == CyclicModuleRecord::Status::Linked || cyclic->status() == CyclicModuleRecord::Status::Evaluated); + ASSERT(cyclic->status(instance) == CyclicModuleRecord::Status::Linked || cyclic->status(instance) == CyclicModuleRecord::Status::Evaluated); // 8. If module.[[HasTLA]] is true, return false. if (cyclic->hasTLA()) return false; @@ -1059,17 +1150,26 @@ bool AbstractModuleRecord::readyForSyncExecution() } // https://tc39.es/proposal-defer-import-eval/#sec-EvaluateModuleSync -void AbstractModuleRecord::evaluateSync(JSGlobalObject* globalObject) +void AbstractModuleRecord::evaluateSync(JSGlobalObject* globalObject, ModuleGraphInstance* instance) { + ModuleGraphInstance::BusyScope busy(globalObject, instance); VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); // 1. If ReadyForSyncExecution(module) is false, throw a TypeError exception. - if (!readyForSyncExecution()) { + if (!readyForSyncExecution(instance)) { throwTypeError(globalObject, scope, "Unable to synchronously evaluate deferred module"_s); return; } // 2. Let promise be ! module.Evaluate(). - JSPromise* promise = evaluate(globalObject); + JSPromise* promise = nullptr; + if (auto* cyclic = dynamicDowncast(this); cyclic && instance) { +#if USE(BUN_JSC_ADDITIONS) + promise = cyclic->evaluate(globalObject, -1, instance); +#else + promise = cyclic->evaluate(globalObject, instance); +#endif + } else + promise = evaluate(globalObject); RETURN_IF_EXCEPTION(scope, void()); // 3. Assert: promise.[[PromiseState]] is either FULFILLED or REJECTED. ASSERT(promise->status() != JSPromise::Status::Pending); @@ -1084,6 +1184,19 @@ void AbstractModuleRecord::evaluateSync(JSGlobalObject* globalObject) // 5. Return UNUSED. } +void AbstractModuleRecord::setImportedRecords(VM& vm, const Vector& records) +{ + ASSERT(!m_importedRecordsSet || m_importedRecords.size() == records.size()); + if (std::exchange(m_importedRecordsSet, true)) + return; + auto importedRecords = WTF::map(records, [&](AbstractModuleRecord* record) { + return WriteBarrier(vm, this, record); + }); + // The concurrent marker iterates m_importedRecords under the cell lock. + Locker locker { cellLock() }; + m_importedRecords = WTF::move(importedRecords); +} + JSPromise* AbstractModuleRecord::asyncCapability() const { return m_asyncCapability.get(); @@ -1198,9 +1311,9 @@ static void checkSafeToRecurse(JSGlobalObject* globalObject, ThrowScope& scope) } #if USE(BUN_JSC_ADDITIONS) -unsigned AbstractModuleRecord::innerModuleEvaluation(JSGlobalObject* globalObject, Vector& stack, unsigned index, int64_t referrerAsyncOrder) +unsigned AbstractModuleRecord::innerModuleEvaluation(JSGlobalObject* globalObject, Vector& stack, unsigned index, int64_t referrerAsyncOrder, ModuleGraphInstance* instance) #else -unsigned AbstractModuleRecord::innerModuleEvaluation(JSGlobalObject* globalObject, Vector& stack, unsigned index) +unsigned AbstractModuleRecord::innerModuleEvaluation(JSGlobalObject* globalObject, Vector& stack, unsigned index, ModuleGraphInstance* instance) #endif { // InnerModuleEvaluation(module, stack, index) @@ -1216,15 +1329,19 @@ unsigned AbstractModuleRecord::innerModuleEvaluation(JSGlobalObject* globalObjec // 1. If module is not a Cyclic Module Record, then if (!module) { // 1.a. Perform ? EvaluateModuleSync(module). - evaluateModuleSync(globalObject); + // A record with its own environment in the instance was given its values + // when that environment was created; only records the instance shares + // with the primary graph evaluate (the primary). + if (!instance || !instance->recordInstance(this)) + evaluateModuleSync(globalObject); RETURN_IF_EXCEPTION(scope, invalid); // 1.b. Return index. return index; } // 2. If module.[[Status]] is either EVALUATING-ASYNC or EVALUATED, then - if (auto status = module->status(); status == Status::EvaluatingAsync || status == Status::Evaluated) { + if (auto status = module->status(instance); status == Status::EvaluatingAsync || status == Status::Evaluated) { // 2.a. If module.[[EvaluationError]] is EMPTY, return index. - JSValue evaluationError = module->evaluationError(); + JSValue evaluationError = module->evaluationError(instance); if (!evaluationError) RELEASE_AND_RETURN(scope, index); // 2.b. Otherwise, return ? module.[[EvaluationError]]. @@ -1232,18 +1349,18 @@ unsigned AbstractModuleRecord::innerModuleEvaluation(JSGlobalObject* globalObjec return invalid; } // 3. If module.[[Status]] is EVALUATING, return index. - if (module->status() == Status::Evaluating) + if (module->status(instance) == Status::Evaluating) RELEASE_AND_RETURN(scope, index); // 4. Assert: module.[[Status]] is LINKED. - ASSERT(module->status() == Status::Linked); + ASSERT(module->status(instance) == Status::Linked); // 5. Set module.[[Status]] to EVALUATING. - module->setStatus(Status::Evaluating); + module->setStatus(instance, Status::Evaluating); // 6. Let moduleIndex be index. unsigned moduleIndex = index; // 7. Set module.[[DFSAncestorIndex]] to index. - module->setDFSAncestorIndex(index); + module->setDFSAncestorIndex(instance, index); // 8. Set module.[[PendingAsyncDependencies]] to 0. - module->setPendingAsyncDependencies(0); + module->setPendingAsyncDependencies(instance, 0); // 9. Set index to index + 1. ++index; // 10. Append module to stack. @@ -1261,7 +1378,7 @@ unsigned AbstractModuleRecord::innerModuleEvaluation(JSGlobalObject* globalObjec // 11.b.ii. For each Module Record additionalModule of additionalModules, do // 11.b.ii.1. If evaluationList does not contain additionalModule, then append additionalModule to evaluationList. UncheckedKeyHashSet seen; - requiredModule->gatherAsynchronousTransitiveDependencies(evaluationList, seen); + requiredModule->gatherAsynchronousTransitiveDependencies(evaluationList, seen, instance); } else { // 11.c. Else if evaluationList does not contain requiredModule, then // 11.c.i. Append requiredModule to evaluationList. @@ -1274,9 +1391,9 @@ unsigned AbstractModuleRecord::innerModuleEvaluation(JSGlobalObject* globalObjec RETURN_IF_EXCEPTION(scope, invalid); // 12.a. Set index to ? InnerModuleEvaluation(requiredModule, stack, index). #if USE(BUN_JSC_ADDITIONS) - unsigned result = requiredModule->innerModuleEvaluation(globalObject, stack, index, referrerAsyncOrder); + unsigned result = requiredModule->innerModuleEvaluation(globalObject, stack, index, referrerAsyncOrder, instance); #else - unsigned result = requiredModule->innerModuleEvaluation(globalObject, stack, index); + unsigned result = requiredModule->innerModuleEvaluation(globalObject, stack, index, instance); #endif RETURN_IF_EXCEPTION(scope, invalid); index = result; @@ -1295,32 +1412,32 @@ unsigned AbstractModuleRecord::innerModuleEvaluation(JSGlobalObject* globalObjec // its DFSAncestorIndex into our inner SCC would taint the SCC // linearization. The outer DFS owns the module's evaluation // lifecycle; our inner pass treats it as a satisfied dependency. - bool depInOuterSCC = cyclic->status() == Status::Evaluating && !stack.contains(requiredModule); + bool depInOuterSCC = cyclic->status(instance) == Status::Evaluating && !stack.contains(requiredModule); if (!depInOuterSCC) { #endif // 12.b.i. Assert: requiredModule.[[Status]] is one of EVALUATING, EVALUATING-ASYNC, or EVALUATED. - ASSERT(cyclic->status() == Status::Evaluating || cyclic->status() == Status::EvaluatingAsync || cyclic->status() == Status::Evaluated); + ASSERT(cyclic->status(instance) == Status::Evaluating || cyclic->status(instance) == Status::EvaluatingAsync || cyclic->status(instance) == Status::Evaluated); // 12.b.ii. Assert: requiredModule.[[Status]] is EVALUATING if and only if stack contains requiredModule. - ASSERT(stack.contains(requiredModule) == (cyclic->status() == Status::Evaluating)); + ASSERT(stack.contains(requiredModule) == (cyclic->status(instance) == Status::Evaluating)); // 12.b.iii. If requiredModule.[[Status]] is EVALUATING, then - if (cyclic->status() == Status::Evaluating) { + if (cyclic->status(instance) == Status::Evaluating) { // 12.b.iii.1. Set module.[[DFSAncestorIndex]] to min(module.[[DFSAncestorIndex]], requiredModule.[[DFSAncestorIndex]]). - module->setDFSAncestorIndex(std::min(module->dfsAncestorIndex(), cyclic->dfsAncestorIndex())); + module->setDFSAncestorIndex(instance, std::min(module->dfsAncestorIndex(instance), cyclic->dfsAncestorIndex(instance))); // 12.b.iv. Else, } else { // 12.b.iv.1. Set requiredModule to requiredModule.[[CycleRoot]]. - cyclic = requiredModule->cycleRoot(); + cyclic = cyclic->cycleRoot(instance); requiredModule = cyclic; // 12.b.iv.2. Assert: requiredModule.[[Status]] is either EVALUATING-ASYNC or EVALUATED. - ASSERT(cyclic->status() == Status::EvaluatingAsync || cyclic->status() == Status::Evaluated); + ASSERT(cyclic->status(instance) == Status::EvaluatingAsync || cyclic->status(instance) == Status::Evaluated); // 12.b.iv.3. If requiredModule.[[EvaluationError]] is not empty, return ? requiredModule.[[EvaluationError]]. - if (JSValue error = cyclic->evaluationError()) { + if (JSValue error = cyclic->evaluationError(instance)) { scope.throwException(globalObject, error); return invalid; } } // 12.b.v. If requiredModule.[[AsyncEvaluationOrder]] is an integer, then - if (cyclic->asyncEvaluationOrder().hasOrder()) { + if (cyclic->asyncEvaluationOrder(instance).hasOrder()) { #if USE(BUN_JSC_ADDITIONS) // Spec says wait on this dep. That's a guaranteed deadlock when // the dep is the very module whose TLA continuation called the @@ -1331,12 +1448,12 @@ unsigned AbstractModuleRecord::innerModuleEvaluation(JSGlobalObject* globalObjec // (-1 when the referrer was not EvaluatingAsync). It is a // VM-unique identity, so equality is exact — siblings that // happen to be EvaluatingAsync (#30259, #30634) never match. - if (cyclic->asyncEvaluationOrder().order() != referrerAsyncOrder) { + if (cyclic->asyncEvaluationOrder(instance).order() != referrerAsyncOrder) { #endif // 12.b.v.1. Set module.[[PendingAsyncDependencies]] to module.[[PendingAsyncDependencies]] + 1. - module->setPendingAsyncDependencies(module->pendingAsyncDependencies().value() + 1); + module->setPendingAsyncDependencies(instance, module->pendingAsyncDependencies(instance).value() + 1); // 12.b.v.2. Append module to requiredModule.[[AsyncParentModules]]. - cyclic->appendAsyncParentModule(vm, module); + cyclic->appendAsyncParentModule(vm, instance, module); #if USE(BUN_JSC_ADDITIONS) } #endif @@ -1347,29 +1464,29 @@ unsigned AbstractModuleRecord::innerModuleEvaluation(JSGlobalObject* globalObjec } } // 12. If module.[[PendingAsyncDependencies]] > 0 or module.[[HasTLA]] is true, then - if (module->pendingAsyncDependencies() > 0 || module->hasTLA()) { + if (module->pendingAsyncDependencies(instance) > 0 || module->hasTLA()) { // 12.a. Assert: module.[[AsyncEvaluationOrder]] is UNSET. - ASSERT(module->asyncEvaluationOrder().isUnset()); + ASSERT(module->asyncEvaluationOrder(instance).isUnset()); // 12.b. Set module.[[AsyncEvaluationOrder]] to IncrementModuleAsyncEvaluationCount(). - module->setAsyncEvaluationOrder(vm.incrementModuleAsyncEvaluationCount()); + module->setAsyncEvaluationOrder(instance, vm.incrementModuleAsyncEvaluationCount()); // 12.c. If module.[[PendingAsyncDependencies]] = 0, perform ExecuteAsyncModule(module). - if (std::optional deps = module->pendingAsyncDependencies(); deps && !*deps) { - module->executeAsync(globalObject); + if (std::optional deps = module->pendingAsyncDependencies(instance); deps && !*deps) { + module->executeAsync(globalObject, instance); RETURN_IF_EXCEPTION(scope, invalid); } // 13. Else, } else { // 13.a. Perform ? module.ExecuteModule(). - module->execute(globalObject); + module->execute(globalObject, nullptr, instance); RETURN_IF_EXCEPTION(scope, invalid); } // 14. Assert: module occurs exactly once in stack. ASSERT(stack.contains(module)); ASSERT(stack.find(module) == stack.reverseFind(module)); // 15. Assert: module.[[DFSAncestorIndex]] <= moduleIndex. - ASSERT(module->dfsAncestorIndex() <= moduleIndex); + ASSERT(module->dfsAncestorIndex(instance) <= moduleIndex); // 16. If module.[[DFSAncestorIndex]] = moduleIndex, then - if (module->dfsAncestorIndex() == moduleIndex) { + if (module->dfsAncestorIndex(instance) == moduleIndex) { // 16.a. Let done be false. bool done = false; // 16.b. Repeat, while done is false, @@ -1380,17 +1497,17 @@ unsigned AbstractModuleRecord::innerModuleEvaluation(JSGlobalObject* globalObjec // 16.b.iii. Assert: requiredModule is a Cyclic Module Record. auto* cyclic = uncheckedDowncast(requiredModule); // cyclic is a downcasted alias of requiredModule. // 16.b.iv. Assert: requiredModule.[[AsyncEvaluationOrder]] is either an integer or UNSET. - ASSERT(cyclic->asyncEvaluationOrder().hasOrder() || cyclic->asyncEvaluationOrder().isUnset()); + ASSERT(cyclic->asyncEvaluationOrder(instance).hasOrder() || cyclic->asyncEvaluationOrder(instance).isUnset()); // 16.b.v. If requiredModule.[[AsyncEvaluationOrder]] is UNSET, set requiredModule.[[Status]] to EVALUATED. - if (cyclic->asyncEvaluationOrder().isUnset()) { - cyclic->setStatus(Status::Evaluated); + if (cyclic->asyncEvaluationOrder(instance).isUnset()) { + cyclic->setStatus(instance, Status::Evaluated); // 16.b.vi. Otherwise, set requiredModule.[[Status]] to EVALUATING-ASYNC. } else - cyclic->setStatus(Status::EvaluatingAsync); + cyclic->setStatus(instance, Status::EvaluatingAsync); // 16.b.vii. If requiredModule and module are the same Module Record, set done to true. done = requiredModule == module; // 16.b.viii. Set requiredModule.[[CycleRoot]] to module. - requiredModule->setCycleRoot(vm, module); + cyclic->setCycleRoot(vm, instance, module); } while (!done); } // 17. Return index. diff --git a/Source/JavaScriptCore/runtime/AbstractModuleRecord.h b/Source/JavaScriptCore/runtime/AbstractModuleRecord.h index d39d150fa990..ff827a94dae7 100644 --- a/Source/JavaScriptCore/runtime/AbstractModuleRecord.h +++ b/Source/JavaScriptCore/runtime/AbstractModuleRecord.h @@ -40,6 +40,7 @@ namespace JSC { class CyclicModuleRecord; class JSModuleEnvironment; class JSModuleNamespaceObject; +class ModuleGraphInstance; class JSMap; class JSPromise; @@ -188,6 +189,23 @@ class AbstractModuleRecord : public JSInternalFieldObjectImpl<2> { const Identifier& moduleKey() const { return m_moduleKey; } ScriptFetchParameters::Type moduleType() const; const Vector& requestedModules() const LIFETIME_BOUND { return m_requestedModules; } + + // Import slots (module graph instances): the distinct records this module's + // named imports resolve to, fixed at link time. Every JSModuleEnvironment of + // this record carries one trailing slot per entry holding the environment of + // that exporter in the same graph instance, so a ModuleVar access is + // "walk to the importing environment, load slot" in every tier. + unsigned importSlotCount() const { return m_importedRecords.size(); } + AbstractModuleRecord* importedRecordAt(unsigned index) const { return m_importedRecords[index].get(); } + std::optional importSlotIndexFor(AbstractModuleRecord* exporter) const + { + for (unsigned i = 0; i < m_importedRecords.size(); ++i) { + if (m_importedRecords[i].get() == exporter) + return i; + } + return std::nullopt; + } + void setImportedRecords(VM&, const Vector&); ModuleMap& loadedModules() LIFETIME_BOUND { return m_loadedModules; } const ModuleMap& loadedModules() const LIFETIME_BOUND { return m_loadedModules; } const ExportEntries& exportEntries() const LIFETIME_BOUND { return m_exportEntries; } @@ -233,6 +251,10 @@ class AbstractModuleRecord : public JSInternalFieldObjectImpl<2> { void setImportedModule(JSGlobalObject*, const ModuleRequest&, AbstractModuleRecord*); JSModuleNamespaceObject* getModuleNamespace(JSGlobalObject*, ModulePhase = ModulePhase::Evaluation, bool shouldPreventExtensions = true); + // Module graph instances (prototype): a namespace object bound to `instance`'s environments (not cached on the record). + JS_EXPORT_PRIVATE JSModuleNamespaceObject* getModuleNamespace(JSGlobalObject*, ModuleGraphInstance*, ModulePhase = ModulePhase::Evaluation); + // This record's environment in `instance` (creating it for synthetic records with per-graph state when asked), or null. + JS_EXPORT_PRIVATE JSModuleEnvironment* graphInstanceEnvironment(JSGlobalObject*, ModuleGraphInstance*, bool createForSynthetic); #if USE(BUN_JSC_ADDITIONS) JSModuleNamespaceObject* getModuleNamespace(JSGlobalObject* globalObject, bool shouldPreventExtensions) { @@ -240,9 +262,9 @@ class AbstractModuleRecord : public JSInternalFieldObjectImpl<2> { } #endif - void gatherAsynchronousTransitiveDependencies(OrderedHashSet& result, UncheckedKeyHashSet& seen); - bool readyForSyncExecution(); - void evaluateSync(JSGlobalObject*); + void gatherAsynchronousTransitiveDependencies(OrderedHashSet& result, UncheckedKeyHashSet& seen, ModuleGraphInstance* = nullptr); + bool readyForSyncExecution(ModuleGraphInstance* = nullptr); + void evaluateSync(JSGlobalObject*, ModuleGraphInstance* = nullptr); JSPromise* asyncCapability() const; void asyncCapability(VM&, JSPromise*); @@ -265,9 +287,9 @@ class AbstractModuleRecord : public JSInternalFieldObjectImpl<2> { void evaluateModuleSync(JSGlobalObject*); #if USE(BUN_JSC_ADDITIONS) - unsigned innerModuleEvaluation(JSGlobalObject*, Vector& stack, unsigned index, int64_t referrerAsyncOrder); + unsigned innerModuleEvaluation(JSGlobalObject*, Vector& stack, unsigned index, int64_t referrerAsyncOrder, ModuleGraphInstance*); #else - unsigned innerModuleEvaluation(JSGlobalObject*, Vector& stack, unsigned index); + unsigned innerModuleEvaluation(JSGlobalObject*, Vector& stack, unsigned index, ModuleGraphInstance*); #endif unsigned innerModuleLinking(JSGlobalObject*, Vector& stack, unsigned index, RefPtr); @@ -339,6 +361,8 @@ class AbstractModuleRecord : public JSInternalFieldObjectImpl<2> { std::optional m_pendingAsyncDependencies; bool m_hasTLA { false }; + Vector> m_importedRecords; + bool m_importedRecordsSet { false }; SourceProviderSourceType m_sourceType; }; diff --git a/Source/JavaScriptCore/runtime/CommonSlowPaths.cpp b/Source/JavaScriptCore/runtime/CommonSlowPaths.cpp index 3557e2387c8f..9a7f5e6fa221 100644 --- a/Source/JavaScriptCore/runtime/CommonSlowPaths.cpp +++ b/Source/JavaScriptCore/runtime/CommonSlowPaths.cpp @@ -47,6 +47,7 @@ #include "JSCellButterfly.h" #include "JSIteratorHelper.h" #include "JSLexicalEnvironment.h" +#include "JSModuleEnvironment.h" #include "JSMap.h" #include "JSMapIterator.h" #include "JSPromise.h" @@ -1390,15 +1391,29 @@ JSC_DEFINE_COMMON_SLOW_PATH(slow_path_resolve_scope) auto& metadata = bytecode.metadata(codeBlock); const Identifier& ident = codeBlock->identifier(bytecode.m_var); JSScope* scope = callFrame->uncheckedR(bytecode.m_scope).Register::scope(); + + if (metadata.m_resolveType == ModuleVar) { + // The CodeBlock was linked against one instantiation of the importing + // module; other instantiations of the same graph share it. Walk to the + // importing module environment on THIS scope chain and pick the + // exporter's environment from the same graph instance. + JSModuleEnvironment* linkedExporter = uncheckedDowncast(metadata.m_lexicalEnvironment.get()); + JSScope* cursor = scope; + for (unsigned i = 0; i < metadata.m_localScopeDepth; ++i) + cursor = cursor->next(); + JSObject* result = linkedExporter; + if (auto* importer = dynamicDowncast(cursor); importer && importer->graphInstance()) + result = importer->importedEnvironmentFor(globalObject, linkedExporter->moduleRecord()); + CHECK_EXCEPTION(); + RETURN(result); + } + JSObject* resolvedScope = JSScope::resolve(globalObject, scope, ident); // Proxy can throw an error here, e.g. Proxy in with statement's @unscopables. CHECK_EXCEPTION(); ResolveType resolveType = metadata.m_resolveType; - // ModuleVar does not keep the scope register value alive in DFG. - ASSERT(resolveType != ModuleVar); - switch (resolveType) { case GlobalProperty: case GlobalPropertyWithVarInjectionChecks: diff --git a/Source/JavaScriptCore/runtime/CyclicModuleRecord.cpp b/Source/JavaScriptCore/runtime/CyclicModuleRecord.cpp index 4a808be9dc80..370afcf53c84 100644 --- a/Source/JavaScriptCore/runtime/CyclicModuleRecord.cpp +++ b/Source/JavaScriptCore/runtime/CyclicModuleRecord.cpp @@ -36,6 +36,8 @@ #include "JSModuleLoader.h" #include "JSModuleNamespaceObject.h" #include "JSModuleRecord.h" +#include "ModuleGraphInstance.h" +#include "Options.h" #include "JSPromise.h" #include "ModuleProgramExecutable.h" #include "SourceProfiler.h" @@ -50,6 +52,138 @@ namespace JSC { +// Evaluation state of this record in a module graph instance. A record the +// instance did not instantiate (shared with the primary graph) uses its own. +static ModuleRecordInstance* recordInstanceFor(const CyclicModuleRecord* record, ModuleGraphInstance* instance) +{ + // A cleared instance has no state; falling back to the record's own state + // would read or write the primary graph's. + ASSERT(!instance || !instance->isCleared()); + return instance ? instance->recordInstance(const_cast(record)) : nullptr; +} + +CyclicModuleRecord::Status CyclicModuleRecord::status(ModuleGraphInstance* instance) const +{ + if (ModuleRecordInstance* state = recordInstanceFor(this, instance)) + return state->status(); + return status(); +} + +JSValue CyclicModuleRecord::evaluationError(ModuleGraphInstance* instance) const +{ + if (ModuleRecordInstance* state = recordInstanceFor(this, instance)) + return state->evaluationError(); + return evaluationError(); +} + +unsigned CyclicModuleRecord::dfsAncestorIndex(ModuleGraphInstance* instance) const +{ + if (ModuleRecordInstance* state = recordInstanceFor(this, instance)) + return state->dfsAncestorIndex(); + return dfsAncestorIndex(); +} + +CyclicModuleRecord* CyclicModuleRecord::cycleRoot(ModuleGraphInstance* instance) const +{ + if (ModuleRecordInstance* state = recordInstanceFor(this, instance)) + return state->cycleRoot(); + return AbstractModuleRecord::cycleRoot(); +} + +AbstractModuleRecord::AsyncEvaluationOrder CyclicModuleRecord::asyncEvaluationOrder(ModuleGraphInstance* instance) const +{ + if (ModuleRecordInstance* state = recordInstanceFor(this, instance)) + return state->asyncEvaluationOrder(); + return AbstractModuleRecord::asyncEvaluationOrder(); +} + +std::optional CyclicModuleRecord::pendingAsyncDependencies(ModuleGraphInstance* instance) const +{ + if (ModuleRecordInstance* state = recordInstanceFor(this, instance)) + return state->pendingAsyncDependencies(); + return AbstractModuleRecord::pendingAsyncDependencies(); +} + +JSPromise* CyclicModuleRecord::topLevelCapability(ModuleGraphInstance* instance) const +{ + if (ModuleRecordInstance* state = recordInstanceFor(this, instance)) + return state->topLevelCapability(); + return AbstractModuleRecord::topLevelCapability(); +} + +void CyclicModuleRecord::setStatus(ModuleGraphInstance* instance, Status newStatus) +{ + if (ModuleRecordInstance* state = recordInstanceFor(this, instance)) + state->setStatus(newStatus); + else + setStatus(newStatus); +} + +void CyclicModuleRecord::setEvaluationError(VM& vm, ModuleGraphInstance* instance, JSValue error) +{ + if (ModuleRecordInstance* state = recordInstanceFor(this, instance)) + state->setEvaluationError(vm, error); + else + setEvaluationError(vm, error); +} + +void CyclicModuleRecord::setDFSAncestorIndex(ModuleGraphInstance* instance, unsigned index) +{ + if (ModuleRecordInstance* state = recordInstanceFor(this, instance)) + state->setDFSAncestorIndex(index); + else + setDFSAncestorIndex(index); +} + +void CyclicModuleRecord::setCycleRoot(VM& vm, ModuleGraphInstance* instance, CyclicModuleRecord* root) +{ + if (ModuleRecordInstance* state = recordInstanceFor(this, instance)) + state->setCycleRoot(vm, root); + else + AbstractModuleRecord::setCycleRoot(vm, root); +} + +void CyclicModuleRecord::setAsyncEvaluationOrder(ModuleGraphInstance* instance, AsyncEvaluationOrder order) +{ + if (ModuleRecordInstance* state = recordInstanceFor(this, instance)) + state->setAsyncEvaluationOrder(order); + else + AbstractModuleRecord::setAsyncEvaluationOrder(order); +} + +void CyclicModuleRecord::setPendingAsyncDependencies(ModuleGraphInstance* instance, std::optional value) +{ + if (ModuleRecordInstance* state = recordInstanceFor(this, instance)) + state->setPendingAsyncDependencies(value); + else + AbstractModuleRecord::setPendingAsyncDependencies(value); +} + +void CyclicModuleRecord::appendAsyncParentModule(VM& vm, ModuleGraphInstance* instance, AbstractModuleRecord* parent) +{ + if (ModuleRecordInstance* state = recordInstanceFor(this, instance)) + state->appendAsyncParentModule(vm, parent); + else + AbstractModuleRecord::appendAsyncParentModule(vm, parent); +} + +void CyclicModuleRecord::setTopLevelCapability(VM& vm, ModuleGraphInstance* instance, JSPromise* capability) +{ + if (ModuleRecordInstance* state = recordInstanceFor(this, instance)) + state->setTopLevelCapability(vm, capability); + else + AbstractModuleRecord::setTopLevelCapability(vm, capability); +} + +template +void CyclicModuleRecord::forEachAsyncParentModule(ModuleGraphInstance* instance, const Functor& functor) const +{ + const Vector>& parents = recordInstanceFor(this, instance) ? recordInstanceFor(this, instance)->asyncParentModules() : asyncParentModules(); + for (const WriteBarrier& parent : parents) + functor(uncheckedDowncast(parent.get())); +} + + const ClassInfo CyclicModuleRecord::s_info = { "CyclicModuleRecord"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(CyclicModuleRecord) }; CyclicModuleRecord::CyclicModuleRecord(VM& vm, Structure* structure, const Identifier& moduleKey, SourceProviderSourceType sourceType) @@ -149,11 +283,29 @@ void CyclicModuleRecord::initializeEnvironment(JSGlobalObject* globalObject, Ref // 4. Assert: realm is not undefined. SymbolTable* symbolTable = nullptr; if (jsModule) { + // Module graph instances: fix the import slots (distinct exporter records + // of the named imports) before the environment is sized. + if (Options::useModuleGraphInstances()) { + Vector importedRecords; + for (const auto& [key, in] : importEntries()) { + if (in.type != ImportEntryType::Single) + continue; + AbstractModuleRecord* importedModule = hostResolveImportedModule(globalObject, in.moduleRequest, in.moduleRequestType); + RETURN_IF_EXCEPTION(scope, void()); + Resolution resolution = importedModule->resolveExport(globalObject, in.importName); + RETURN_IF_EXCEPTION(scope, void()); + if (resolution.type != Resolution::Type::Resolved) + continue; + if (!importedRecords.contains(resolution.moduleRecord)) + importedRecords.append(resolution.moduleRecord); + } + setImportedRecords(vm, importedRecords); + } // 5. Let env be NewModuleEnvironment(realm.[[GlobalEnv]]). moduleProgramExecutable = jsModule->getOrMakeExecutable(globalObject); RETURN_IF_EXCEPTION(scope, void()); symbolTable = moduleProgramExecutable->moduleEnvironmentSymbolTable(); - env = JSModuleEnvironment::create(vm, globalObject, globalObject->globalLexicalEnvironment(), symbolTable, jsTDZValue(), this); + env = JSModuleEnvironment::create(vm, globalObject, globalObject->moduleEnvironmentParentScope(), symbolTable, jsTDZValue(), this); RETURN_IF_EXCEPTION(scope, void()); // 6. Set module.[[Environment]] to env. setModuleEnvironment(globalObject, env); @@ -331,6 +483,9 @@ void CyclicModuleRecord::initializeEnvironment(JSGlobalObject* globalObject, Ref // 22. Let lexDeclarations be the LexicallyScopedDeclarations of code. // 23. Let privateEnv be null. // 24. For each element d of lexDeclarations, do + Vector> functionDeclExecutables; + if (Options::useModuleGraphInstances()) + functionDeclExecutables.grow(unlinkedCodeBlock->numberOfFunctionDecls()); for (size_t i = 0, numberOfFunctions = unlinkedCodeBlock->numberOfFunctionDecls(); i < numberOfFunctions; ++i) { // 24.a. For each element dn of the BoundNames of d, do // 24.a.i. If IsConstantDeclaration of d is true, then @@ -351,6 +506,8 @@ void CyclicModuleRecord::initializeEnvironment(JSGlobalObject* globalObject, Ref // 24.a.iii.1. Let fo be InstantiateFunctionObject of d with arguments env and privateEnv. auto* executable = unlinkedFunctionExecutable->link(vm, moduleProgramExecutable, moduleProgramExecutable->source()); RETURN_IF_EXCEPTION(scope, void()); + if (Options::useModuleGraphInstances()) + functionDeclExecutables[i].setWithoutWriteBarrier(executable); SourceParseMode parseMode = executable->parseMode(); JSFunction* function = nullptr; if (isAsyncGeneratorWrapperParseMode(parseMode)) @@ -369,6 +526,9 @@ void CyclicModuleRecord::initializeEnvironment(JSGlobalObject* globalObject, Ref } } + if (Options::useModuleGraphInstances()) + jsModule->retainForGraphInstances(vm, moduleProgramExecutable, WTF::move(functionDeclExecutables)); + if (jsModule->features() & ImportMetaFeature) { JSObject* metaProperties = globalObject->moduleLoader()->createImportMetaProperties(globalObject, identifierToJSValue(vm, moduleKey()), jsModule, scriptFetcher); RETURN_IF_EXCEPTION(scope, void()); @@ -419,11 +579,12 @@ void CyclicModuleRecord::link(JSGlobalObject* globalObject, RefPtrvm(); @@ -432,23 +593,23 @@ JSPromise* CyclicModuleRecord::evaluate(JSGlobalObject* globalObject) // 1. Assert: This call to Evaluate is not happening at the same time as another call to Evaluate within the surrounding agent. // FIXME: is this needed? // 2. Assert: module.[[Status]] is one of LINKED, EVALUATING-ASYNC, or EVALUATED. - ASSERT(m_status == Status::Linked || m_status == Status::EvaluatingAsync || m_status == Status::Evaluated); + ASSERT(status(instance) == Status::Linked || status(instance) == Status::EvaluatingAsync || status(instance) == Status::Evaluated); CyclicModuleRecord* module = this; // 3. If module.[[Status]] is either EVALUATING-ASYNC or EVALUATED, then - if (m_status == Status::EvaluatingAsync || m_status == Status::Evaluated) { + if (status(instance) == Status::EvaluatingAsync || status(instance) == Status::Evaluated) { // 3.a. If module.[[CycleRoot]] is not EMPTY, then - if (CyclicModuleRecord* root = m_cycleRoot.get()) { + if (CyclicModuleRecord* root = cycleRoot(instance)) { // 3.a.i. Set module to module.[[CycleRoot]]. module = root; // 3.b. Else, } else { // 3.b.i. Assert: module.[[Status]] is EVALUATED and module.[[EvaluationError]] is a throw completion. - ASSERT(m_status == Status::Evaluated); - ASSERT(m_evaluationError); + ASSERT(status(instance) == Status::Evaluated); + ASSERT(evaluationError(instance)); } } // 4. If module.[[TopLevelCapability]] is not EMPTY, then - if (JSPromise* promise = module->topLevelCapability()) { + if (JSPromise* promise = module->topLevelCapability(instance)) { // 4.a. Return module.[[TopLevelCapability]].[[Promise]]. RELEASE_AND_RETURN(scope, promise); } @@ -457,12 +618,12 @@ JSPromise* CyclicModuleRecord::evaluate(JSGlobalObject* globalObject) // 6. Let capability be ! NewPromiseCapability(%Promise%). JSPromise* capability = JSPromise::create(vm, globalObject->promiseStructure()); // 7. Set module.[[TopLevelCapability]] to capability. - module->setTopLevelCapability(vm, capability); + module->setTopLevelCapability(vm, instance, capability); // 8. Let result be Completion(InnerModuleEvaluation(module, stack, 0)). #if USE(BUN_JSC_ADDITIONS) - module->innerModuleEvaluation(globalObject, stack, 0, referrerAsyncOrder); + module->innerModuleEvaluation(globalObject, stack, 0, referrerAsyncOrder, instance); #else - module->innerModuleEvaluation(globalObject, stack, 0); + module->innerModuleEvaluation(globalObject, stack, 0, instance); #endif // 9. If result is an abrupt completion, then if (Exception* exception = scope.exception()) { @@ -471,28 +632,28 @@ JSPromise* CyclicModuleRecord::evaluate(JSGlobalObject* globalObject) for (AbstractModuleRecord* abstractRecord : stack) { // 9.a.i. Assert: m.[[Status]] is EVALUATING. auto* cyclic = uncheckedDowncast(abstractRecord); - ASSERT(cyclic->status() == Status::Evaluating); + ASSERT(cyclic->status(instance) == Status::Evaluating); // 9.a.ii. Set m.[[Status]] to EVALUATED. - cyclic->setStatus(Status::Evaluated); + cyclic->setStatus(instance, Status::Evaluated); // 9.a.iii. Set m.[[EvaluationError]] to result. - cyclic->setEvaluationError(vm, exception->value()); + cyclic->setEvaluationError(vm, instance, exception->value()); } // 9.b. Assert: module.[[Status]] is EVALUATED. - ASSERT(module->status() == Status::Evaluated); + ASSERT(module->status(instance) == Status::Evaluated); // 9.c. Assert: module.[[EvaluationError]] and result are the same Completion Record. - ASSERT(module->evaluationError() == exception->value()); + ASSERT(module->evaluationError(instance) == exception->value()); // 9.d. Perform ! Call(capability.[[Reject]], undefined, « result.[[Value]] »). capability->rejectWithCaughtException(vm, scope); // 10. Else, } else { // 10.a. Assert: module.[[Status]] is either EVALUATING-ASYNC or EVALUATED. - ASSERT(module->status() == Status::EvaluatingAsync || module->status() == Status::Evaluated); + ASSERT(module->status(instance) == Status::EvaluatingAsync || module->status(instance) == Status::Evaluated); // 10.b. Assert: module.[[EvaluationError]] is EMPTY. - ASSERT(module->evaluationError() == nullptr); + ASSERT(!module->evaluationError(instance)); // 10.c. If module.[[Status]] is EVALUATED, then - if (module->status() == Status::Evaluated) { + if (module->status(instance) == Status::Evaluated) { // 10.c.i. Assert: module.[[AsyncEvaluationOrder]] is either UNSET or DONE. - ASSERT(module->asyncEvaluationOrder().isUnset() || module->asyncEvaluationOrder().isDone()); + ASSERT(module->asyncEvaluationOrder(instance).isUnset() || module->asyncEvaluationOrder(instance).isDone()); // 10.c.ii. NOTE: module.[[AsyncEvaluationOrder]] is DONE if and only if module had already been evaluated and that evaluation was asynchronous. // 10.c.iii. Perform ! Call(capability.[[Resolve]], undefined, « undefined »). capability->fulfill(vm, jsUndefined()); @@ -504,11 +665,14 @@ JSPromise* CyclicModuleRecord::evaluate(JSGlobalObject* globalObject) RELEASE_AND_RETURN(scope, capability); } -void CyclicModuleRecord::execute(JSGlobalObject* globalObject, JSPromise* capability) +void CyclicModuleRecord::execute(JSGlobalObject* globalObject, JSPromise* capability, ModuleGraphInstance* instance) { VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); + if (ModuleRecordInstance* recordInstance = recordInstanceFor(this, instance)) + RELEASE_AND_RETURN(scope, uncheckedDowncast(this)->executeInstance(globalObject, recordInstance, capability)); + #if ENABLE(WEBASSEMBLY) if (auto* wasmModule = dynamicDowncast(this)) { wasmModule->initializeImports(globalObject, nullptr, Wasm::CreationMode::FromModuleLoader); @@ -530,7 +694,7 @@ void CyclicModuleRecord::execute(JSGlobalObject* globalObject, JSPromise* capabi RELEASE_AND_RETURN(scope, uncheckedDowncast(this)->execute(globalObject, capability)); } -void CyclicModuleRecord::executeAsync(JSGlobalObject* globalObject) +void CyclicModuleRecord::executeAsync(JSGlobalObject* globalObject, ModuleGraphInstance* instance) { // ExecuteAsyncModule(module) // https://tc39.es/ecma262/#sec-execute-async-module @@ -539,7 +703,7 @@ void CyclicModuleRecord::executeAsync(JSGlobalObject* globalObject) auto scope = DECLARE_THROW_SCOPE(vm); // 1. Assert: module.[[Status]] is either EVALUATING or EVALUATING-ASYNC. - ASSERT(status() == Status::Evaluating || status() == Status::EvaluatingAsync); + ASSERT(status(instance) == Status::Evaluating || status(instance) == Status::EvaluatingAsync); // 2. Assert: module.[[HasTLA]] is true. ASSERT(hasTLA()); // 3. Let capability be ! NewPromiseCapability(%Promise%). @@ -555,15 +719,19 @@ void CyclicModuleRecord::executeAsync(JSGlobalObject* globalObject) // 7. Let onRejected be CreateBuiltinFunction(rejectedClosure, 0, "", « »). // Also handled in JSMicrotask.cpp. // 8. Perform PerformPromiseThen(capability.[[Promise]], onFulfilled, onRejected). - promise->performPromiseThenWithInternalMicrotask(vm, InternalMicrotask::AsyncModuleExecutionDone, nullptr, this); + // The context names the module and, for a module graph instance, the instance (its ModuleRecordInstance). + JSCell* context = this; + if (ModuleRecordInstance* recordInstance = recordInstanceFor(this, instance)) + context = recordInstance; + promise->performPromiseThenWithInternalMicrotask(vm, InternalMicrotask::AsyncModuleExecutionDone, nullptr, context); // 9. Perform ! module.ExecuteModule(capability). - execute(globalObject, promise); + execute(globalObject, promise, instance); RETURN_IF_EXCEPTION(scope, void()); // 10. Return UNUSED. RELEASE_AND_RETURN(scope, void()); } -static void gatherAvailableAncestors(CyclicModuleRecord* module, Vector& execList) +static void gatherAvailableAncestors(CyclicModuleRecord* module, Vector& execList, ModuleGraphInstance* instance) { // GatherAvailableAncestors(module, execList) // https://tc39.es/ecma262/#sec-gather-available-ancestors @@ -581,30 +749,29 @@ static void gatherAvailableAncestors(CyclicModuleRecord* module, Vector& barrier : record->asyncParentModules()) { - auto* m = uncheckedDowncast(barrier.get()); + record->forEachAsyncParentModule(instance, [&](CyclicModuleRecord* m) { // 1.a. If execList does not contain m and m.[[CycleRoot]].[[EvaluationError]] is empty, then // (Probable spec bug (https://github.com/tc39/ecma262/issues/3766). We need an additional check here that m.[[CycleRoot]] isn't empty.) - ASSERT_IMPLIES(!m->cycleRoot(), m->evaluationError()); - CyclicModuleRecord* root = m->cycleRoot(); - if (!root || root->evaluationError() != nullptr) - continue; - auto pending = m->pendingAsyncDependencies(); + ASSERT_IMPLIES(!m->cycleRoot(instance), m->evaluationError(instance)); + CyclicModuleRecord* root = m->cycleRoot(instance); + if (!root || !!root->evaluationError(instance)) + return; + auto pending = m->pendingAsyncDependencies(instance); // Verify the invariant in debug: execList ∋ m iff pending == 0 (under cycleRoot OK). ASSERT(pending); ASSERT(execList.contains(m) == !*pending); if (!*pending) - continue; + return; // 1.a.i. Assert: m.[[Status]] is EVALUATING-ASYNC. - ASSERT(m->status() == CyclicModuleRecord::Status::EvaluatingAsync); + ASSERT(m->status(instance) == CyclicModuleRecord::Status::EvaluatingAsync); // 1.a.ii. Assert: m.[[EvaluationError]] is EMPTY. - ASSERT(m->evaluationError() == nullptr); + ASSERT(!m->evaluationError(instance)); // 1.a.iii. Assert: m.[[AsyncEvaluationOrder]] is an integer. - ASSERT(m->asyncEvaluationOrder().hasOrder()); + ASSERT(m->asyncEvaluationOrder(instance).hasOrder()); // 1.a.iv. Assert: m.[[PendingAsyncDependencies]] > 0. (Implied by *pending != 0 above.) // 1.a.v. Set m.[[PendingAsyncDependencies]] to m.[[PendingAsyncDependencies]] - 1. int newDependencies = *pending - 1; - m->setPendingAsyncDependencies(newDependencies); + m->setPendingAsyncDependencies(instance, newDependencies); // 1.a.vi. If m.[[PendingAsyncDependencies]] = 0, then if (!newDependencies) { // 1.a.vi.1. Append m to execList. @@ -613,12 +780,12 @@ static void gatherAvailableAncestors(CyclicModuleRecord* module, VectorhasTLA()) worklist.append(m); } - } + }); } // 2. Return UNUSED. } -void CyclicModuleRecord::asyncExecutionRejected(JSGlobalObject* globalObject, JSValue error) +void CyclicModuleRecord::asyncExecutionRejected(JSGlobalObject* globalObject, JSValue error, ModuleGraphInstance* instance) { // AsyncModuleExecutionRejected(module, error) // https://tc39.es/ecma262/#sec-async-module-execution-rejected @@ -632,41 +799,45 @@ void CyclicModuleRecord::asyncExecutionRejected(JSGlobalObject* globalObject, JS while (!stack.isEmpty()) { CyclicModuleRecord* module = stack.takeLast(); // 1. If module.[[Status]] is EVALUATED, then - if (module->status() == CyclicModuleRecord::Status::Evaluated) { + if (module->status(instance) == CyclicModuleRecord::Status::Evaluated) { // 1.a. Assert: module.[[EvaluationError]] is not EMPTY. - ASSERT(module->evaluationError() != nullptr); + ASSERT(!!module->evaluationError(instance)); // 1.b. Return UNUSED. continue; } // 2. Assert: module.[[Status]] is EVALUATING-ASYNC. - ASSERT(module->status() == CyclicModuleRecord::Status::EvaluatingAsync); + ASSERT(module->status(instance) == CyclicModuleRecord::Status::EvaluatingAsync); // 3. Assert: module.[[AsyncEvaluationOrder]] is an integer. - ASSERT(module->asyncEvaluationOrder().hasOrder()); + ASSERT(module->asyncEvaluationOrder(instance).hasOrder()); // 4. Assert: module.[[EvaluationError]] is EMPTY. - ASSERT(module->evaluationError() == nullptr); + ASSERT(!module->evaluationError(instance)); // 5. Set module.[[EvaluationError]] to ThrowCompletion(error). - module->setEvaluationError(vm, error); + module->setEvaluationError(vm, instance, error); // 6. Set module.[[Status]] to EVALUATED. - module->setStatus(CyclicModuleRecord::Status::Evaluated); + module->setStatus(instance, CyclicModuleRecord::Status::Evaluated); // 7. Set module.[[AsyncEvaluationOrder]] to DONE. - module->setAsyncEvaluationOrder(AbstractModuleRecord::AsyncEvaluationOrder::done()); + module->setAsyncEvaluationOrder(instance, AbstractModuleRecord::AsyncEvaluationOrder::done()); // 8. NOTE: module.[[AsyncEvaluationOrder]] is set to DONE for symmetry with AsyncModuleExecutionFulfilled. In InnerModuleEvaluation, the value of a module's [[AsyncEvaluationOrder]] internal slot is unused when its [[EvaluationError]] internal slot is not EMPTY. // 9. If module.[[TopLevelCapability]] is not EMPTY, then - if (auto* topLevel = module->topLevelCapability()) { + if (auto* topLevel = module->topLevelCapability(instance)) { // 9.a. Assert: module.[[CycleRoot]] and module are the same Module Record. - ASSERT(module->cycleRoot() == module); + ASSERT(module->cycleRoot(instance) == module); // 9.b. Perform ! Call(module.[[TopLevelCapability]].[[Reject]], undefined, « error »). topLevel->reject(vm, error); } // 10. For each Cyclic Module Record m of module.[[AsyncParentModules]], do // 10.a. Perform AsyncModuleExecutionRejected(m, error). - for (const WriteBarrier& m : module->asyncParentModules() | std::views::reverse) - stack.append(uncheckedDowncast(m.get())); + { + Vector parents; + module->forEachAsyncParentModule(instance, [&](CyclicModuleRecord* m) { parents.append(m); }); + for (CyclicModuleRecord* m : parents | std::views::reverse) + stack.append(m); + } } // 11. Return UNUSED. } -void CyclicModuleRecord::asyncExecutionFulfilled(JSGlobalObject* globalObject) +void CyclicModuleRecord::asyncExecutionFulfilled(JSGlobalObject* globalObject, ModuleGraphInstance* instance) { // AsyncModuleExecutionFulfilled(module) // https://tc39.es/ecma262/#sec-async-module-execution-fulfilled @@ -675,26 +846,26 @@ void CyclicModuleRecord::asyncExecutionFulfilled(JSGlobalObject* globalObject) auto scope = DECLARE_THROW_SCOPE(vm); // 1. If module.[[Status]] is EVALUATED, then - if (status() == CyclicModuleRecord::Status::Evaluated) { + if (status(instance) == CyclicModuleRecord::Status::Evaluated) { // 1.a. Assert: module.[[EvaluationError]] is not EMPTY. - ASSERT(evaluationError() != nullptr); + ASSERT(!!evaluationError(instance)); // 1.b. Return UNUSED. RELEASE_AND_RETURN(scope, void()); } // 2. Assert: module.[[Status]] is EVALUATING-ASYNC. - ASSERT(status() == CyclicModuleRecord::Status::EvaluatingAsync); + ASSERT(status(instance) == CyclicModuleRecord::Status::EvaluatingAsync); // 3. Assert: module.[[AsyncEvaluationOrder]] is an integer. - ASSERT(asyncEvaluationOrder().hasOrder()); + ASSERT(asyncEvaluationOrder(instance).hasOrder()); // 4. Assert: module.[[EvaluationError]] is EMPTY. - ASSERT(evaluationError() == nullptr); + ASSERT(!evaluationError(instance)); // 5. Set module.[[AsyncEvaluationOrder]] to DONE. - setAsyncEvaluationOrder(AbstractModuleRecord::AsyncEvaluationOrder::done()); + setAsyncEvaluationOrder(instance, AbstractModuleRecord::AsyncEvaluationOrder::done()); // 6. Set module.[[Status]] to EVALUATED. - setStatus(CyclicModuleRecord::Status::Evaluated); + setStatus(instance, CyclicModuleRecord::Status::Evaluated); // 7. If module.[[TopLevelCapability]] is not EMPTY, then - if (auto* capability = topLevelCapability()) { + if (auto* capability = topLevelCapability(instance)) { // 7.a. Assert: module.[[CycleRoot]] and module are the same Module Record. - ASSERT(cycleRoot() == this); + ASSERT(cycleRoot(instance) == this); // 7.b. Perform ! Call(module.[[TopLevelCapability]].[[Resolve]], undefined, « undefined »). capability->fulfill(vm, jsUndefined()); } @@ -702,54 +873,54 @@ void CyclicModuleRecord::asyncExecutionFulfilled(JSGlobalObject* globalObject) // (Note: it's safe to use a Vector instead of a MarkedArgumentsBuffer here because all the contents are accessed through WriteBarriers starting at `this`.) Vector execList; // 9. Perform GatherAvailableAncestors(module, execList). - gatherAvailableAncestors(this, execList); + gatherAvailableAncestors(this, execList, instance); // 10. Assert: All elements of execList have their [[AsyncEvaluationOrder]] field set to an integer, [[PendingAsyncDependencies]] field set to 0, and [[EvaluationError]] field set to EMPTY. #if ASSERT_ENABLED for (CyclicModuleRecord* element : execList) { - ASSERT(element->asyncEvaluationOrder().hasOrder()); - ASSERT(element->pendingAsyncDependencies() && !*element->pendingAsyncDependencies()); - ASSERT(!element->evaluationError()); + ASSERT(element->asyncEvaluationOrder(instance).hasOrder()); + ASSERT(element->pendingAsyncDependencies(instance) && !*element->pendingAsyncDependencies(instance)); + ASSERT(!element->evaluationError(instance)); } #endif // 11. Let sortedExecList be a List whose elements are the elements of execList, sorted by their [[AsyncEvaluationOrder]] field in ascending order. - std::ranges::sort(execList, [](CyclicModuleRecord* left, CyclicModuleRecord* right) { - return left->asyncEvaluationOrder().order() < right->asyncEvaluationOrder().order(); + std::ranges::sort(execList, [instance](CyclicModuleRecord* left, CyclicModuleRecord* right) { + return left->asyncEvaluationOrder(instance).order() < right->asyncEvaluationOrder(instance).order(); }); // 12. For each Cyclic Module Record m of sortedExecList, do for (CyclicModuleRecord* m : execList) { // 12.a. If m.[[Status]] is EVALUATED, then - if (m->status() == CyclicModuleRecord::Status::Evaluated) { + if (m->status(instance) == CyclicModuleRecord::Status::Evaluated) { // 12.a.i. Assert: m.[[EvaluationError]] is not EMPTY. - ASSERT(m->evaluationError() != nullptr); + ASSERT(!!m->evaluationError(instance)); // 12.b. Else if m.[[HasTLA]] is true, then } else if (m->hasTLA()) { // 12.b.i. Perform ExecuteAsyncModule(m). - m->executeAsync(globalObject); + m->executeAsync(globalObject, instance); if (Exception* exception = scope.exception()) { JSValue error = exception->value(); TRY_CLEAR_EXCEPTION(scope, void()); - m->asyncExecutionRejected(globalObject, error); + m->asyncExecutionRejected(globalObject, error, instance); } // 12.c. Else, } else { // 12.c.i. Let result be m.ExecuteModule(). - m->execute(globalObject); + m->execute(globalObject, nullptr, instance); // 12.c.ii. If result is an abrupt completion, then if (Exception* exception = scope.exception()) { JSValue error = exception->value(); TRY_CLEAR_EXCEPTION(scope, void()); // 12.c.ii.1. Perform AsyncModuleExecutionRejected(m, result.[[Value]]). - m->asyncExecutionRejected(globalObject, error); + m->asyncExecutionRejected(globalObject, error, instance); // 12.c.iii. Else, } else { // 12.c.iii.1. Set m.[[AsyncEvaluationOrder]] to DONE. - m->setAsyncEvaluationOrder(AbstractModuleRecord::AsyncEvaluationOrder::done()); + m->setAsyncEvaluationOrder(instance, AbstractModuleRecord::AsyncEvaluationOrder::done()); // 12.c.iii.2. Set m.[[Status]] to EVALUATED. - m->setStatus(CyclicModuleRecord::Status::Evaluated); + m->setStatus(instance, CyclicModuleRecord::Status::Evaluated); // 12.c.iii.3. If m.[[TopLevelCapability]] is not EMPTY, then - if (auto* capability = m->topLevelCapability()) { + if (auto* capability = m->topLevelCapability(instance)) { // 12.c.iii.3.a. Assert: m.[[CycleRoot]] and m are the same Module Record. - ASSERT(m->cycleRoot() == m); + ASSERT(m->cycleRoot(instance) == m); // 12.c.iii.3.b. Perform ! Call(m.[[TopLevelCapability]].[[Resolve]], undefined, « undefined »). capability->fulfill(vm, jsUndefined()); } diff --git a/Source/JavaScriptCore/runtime/CyclicModuleRecord.h b/Source/JavaScriptCore/runtime/CyclicModuleRecord.h index f415ab035bde..3bb3a58183d0 100644 --- a/Source/JavaScriptCore/runtime/CyclicModuleRecord.h +++ b/Source/JavaScriptCore/runtime/CyclicModuleRecord.h @@ -29,6 +29,8 @@ namespace JSC { +class ModuleGraphInstance; + class ErrorInstance; class CyclicModuleRecord : public AbstractModuleRecord { @@ -57,33 +59,63 @@ class CyclicModuleRecord : public AbstractModuleRecord { void initializeEnvironment(JSGlobalObject*, RefPtr); void link(JSGlobalObject*, RefPtr); + // Every evaluation entry point takes the module graph instance being + // evaluated (null: the primary instantiation, whose state lives on the + // record itself). See ModuleGraphInstance. #if USE(BUN_JSC_ADDITIONS) - JSPromise* evaluate(JSGlobalObject*, int64_t referrerAsyncOrder = -1); + JSPromise* evaluate(JSGlobalObject*, int64_t referrerAsyncOrder = -1, ModuleGraphInstance* = nullptr); #else - JSPromise* evaluate(JSGlobalObject*); + JSPromise* evaluate(JSGlobalObject*, ModuleGraphInstance* = nullptr); #endif - void execute(JSGlobalObject*, JSPromise* = nullptr); - void executeAsync(JSGlobalObject*); - void asyncExecutionFulfilled(JSGlobalObject*); - void asyncExecutionRejected(JSGlobalObject*, JSValue); + void execute(JSGlobalObject*, JSPromise* = nullptr, ModuleGraphInstance* = nullptr); + void executeAsync(JSGlobalObject*, ModuleGraphInstance* = nullptr); + void asyncExecutionFulfilled(JSGlobalObject*, ModuleGraphInstance* = nullptr); + void asyncExecutionRejected(JSGlobalObject*, JSValue, ModuleGraphInstance* = nullptr); Status status() const { return m_status; } JSValue evaluationError() const { return m_evaluationError.get(); } unsigned dfsAncestorIndex() const { return m_dfsAncestorIndex; } + // Evaluation state of this record in `instance` (null, or a record the + // instance shares with the primary graph: the record's own state). + using AbstractModuleRecord::cycleRoot; + using AbstractModuleRecord::asyncEvaluationOrder; + using AbstractModuleRecord::pendingAsyncDependencies; + using AbstractModuleRecord::topLevelCapability; + using AbstractModuleRecord::setCycleRoot; + using AbstractModuleRecord::setAsyncEvaluationOrder; + using AbstractModuleRecord::setPendingAsyncDependencies; + using AbstractModuleRecord::appendAsyncParentModule; + using AbstractModuleRecord::setTopLevelCapability; + Status status(ModuleGraphInstance*) const; + JSValue evaluationError(ModuleGraphInstance*) const; + unsigned dfsAncestorIndex(ModuleGraphInstance*) const; + CyclicModuleRecord* cycleRoot(ModuleGraphInstance*) const; + AsyncEvaluationOrder asyncEvaluationOrder(ModuleGraphInstance*) const; + std::optional pendingAsyncDependencies(ModuleGraphInstance*) const; + JSPromise* topLevelCapability(ModuleGraphInstance*) const; + void setStatus(ModuleGraphInstance*, Status); + void setEvaluationError(VM&, ModuleGraphInstance*, JSValue); + void setDFSAncestorIndex(ModuleGraphInstance*, unsigned); + void setCycleRoot(VM&, ModuleGraphInstance*, CyclicModuleRecord*); + void setAsyncEvaluationOrder(ModuleGraphInstance*, AsyncEvaluationOrder); + void setPendingAsyncDependencies(ModuleGraphInstance*, std::optional); + void appendAsyncParentModule(VM&, ModuleGraphInstance*, AbstractModuleRecord*); + void setTopLevelCapability(VM&, ModuleGraphInstance*, JSPromise*); + template void forEachAsyncParentModule(ModuleGraphInstance*, const Functor&) const; // https://tc39.es/proposal-defer-import-eval/#sec-IsModuleSCCEvaluated // A module in an import cycle reaches EVALUATED once its own body has run, so only its cycle // root reaching EVALUATED tells you the whole cycle is done. - bool isSCCEvaluated() const + bool isSCCEvaluated(ModuleGraphInstance* instance = nullptr) const { // 1. If module.[[CycleRoot]] is not EMPTY, then // 1.a. If module.[[CycleRoot]].[[Status]] is EVALUATED, return true. // 1.b. Return false. - if (CyclicModuleRecord* root = cycleRoot()) - return root->status() == Status::Evaluated; + if (CyclicModuleRecord* root = cycleRoot(instance)) + return root->status(instance) == Status::Evaluated; // 2. If module.[[Status]] is EVALUATED, return true. // 3. Return false. - return status() == Status::Evaluated; + return status(instance) == Status::Evaluated; } void setStatus(Status newStatus) { m_status = newStatus; } diff --git a/Source/JavaScriptCore/runtime/FunctionConstructor.cpp b/Source/JavaScriptCore/runtime/FunctionConstructor.cpp index 4e289bae6609..9735d9e0d6c2 100644 --- a/Source/JavaScriptCore/runtime/FunctionConstructor.cpp +++ b/Source/JavaScriptCore/runtime/FunctionConstructor.cpp @@ -221,6 +221,8 @@ JSObject* constructFunctionSkippingEvalEnabledCheck(JSGlobalObject* globalObject JSObject* exception = nullptr; FunctionExecutable* function = FunctionExecutable::fromGlobalCode(functionName, globalObject, WTF::move(program), sourceOrigin, taintedOrigin, sourceURL, position, lexicallyScopedFeatures, exception, overrideLineNumber, functionConstructorParametersEndPosition, functionConstructionMode); + // Creating the error object for a parse failure runs host error-info hooks that may themselves throw. + RETURN_IF_EXCEPTION(scope, nullptr); if (!function) [[unlikely]] { ASSERT(exception); throwException(globalObject, scope, exception); diff --git a/Source/JavaScriptCore/runtime/GetPutInfo.h b/Source/JavaScriptCore/runtime/GetPutInfo.h index 8233aa1f0b6f..af0c44b06e3d 100644 --- a/Source/JavaScriptCore/runtime/GetPutInfo.h +++ b/Source/JavaScriptCore/runtime/GetPutInfo.h @@ -218,6 +218,9 @@ struct ResolveOp { InlineWatchpointSet* watchpointSet; uintptr_t operand; RefPtr importedName; + // ModuleVar: 1 + the import slot's ScopeOffset in the importing environment + // (0 = none; resolve to the linked exporter environment as a constant). + unsigned moduleImportSlot { 0 }; }; class GetPutInfo { diff --git a/Source/JavaScriptCore/runtime/JSAsyncGeneratorInlines.h b/Source/JavaScriptCore/runtime/JSAsyncGeneratorInlines.h index 3e83f0451baa..41caff3bc255 100644 --- a/Source/JavaScriptCore/runtime/JSAsyncGeneratorInlines.h +++ b/Source/JavaScriptCore/runtime/JSAsyncGeneratorInlines.h @@ -27,6 +27,7 @@ #include "JSAsyncFunctionGenerator.h" #include "JSAsyncGenerator.h" +#include "ModuleGraphInstance.h" #include "JSInternalFieldObjectImplInlines.h" #include "JSModuleRecord.h" #include "JSPromise.h" @@ -36,7 +37,7 @@ namespace JSC { ALWAYS_INLINE void JSAsyncGenerator::enqueue(VM& vm, JSValue value, int32_t mode, JSObject* settlementTarget) { - ASSERT(settlementTarget->inherits() || settlementTarget->inherits() || settlementTarget->inherits() || settlementTarget->inherits()); + ASSERT(settlementTarget->inherits() || settlementTarget->inherits() || settlementTarget->inherits() || settlementTarget->inherits() || settlementTarget->inherits()); if (isQueueEmpty()) [[likely]] { setResumeValue(vm, value); setResumeMode(mode); diff --git a/Source/JavaScriptCore/runtime/JSGlobalObject.cpp b/Source/JavaScriptCore/runtime/JSGlobalObject.cpp index a0097ce72112..87348f7b3802 100644 --- a/Source/JavaScriptCore/runtime/JSGlobalObject.cpp +++ b/Source/JavaScriptCore/runtime/JSGlobalObject.cpp @@ -180,6 +180,7 @@ #include "JSModuleLoaderInlines.h" #include "JSModuleNamespaceObjectInlines.h" #include "JSModuleRecord.h" +#include "ModuleGraphInstanceInlines.h" #include "JSModuleRecordInlines.h" #include "JSNativeStdFunctionInlines.h" #include "JSONObjectInlines.h" @@ -687,6 +688,47 @@ JSC_DEFINE_HOST_FUNCTION(disableSuperSampler, (JSGlobalObject*, CallFrame*)) return JSValue::encode(jsUndefined()); } + +void JSGlobalObject::setCurrentGraphInstanceForLoading(VM& vm, ModuleGraphInstance* instance) +{ + if (instance) + m_currentGraphInstanceForLoading.set(vm, this, instance); + else + m_currentGraphInstanceForLoading.clear(); +} + +ModuleGraphInstance* JSGlobalObject::graphInstanceForScope(JSScope* scope, JSScope** overlayOut) +{ + // The innermost module environment on the chain names its instance directly; + // an overlay (when configured) names it for non-module code scoped to the + // instance. Either is decisive: the first one found ends the walk. + SymbolTable* overlayTable = m_moduleScopeOverlaySymbolTable.get(); + for (; scope; scope = scope->next()) { + if (auto* moduleEnvironment = dynamicDowncast(scope)) { + ModuleGraphInstance* instance = moduleEnvironment->graphInstance(); + if (overlayOut) { + *overlayOut = nullptr; + for (JSScope* outer = scope->next(); overlayTable && outer; outer = outer->next()) { + auto* environment = dynamicDowncast(outer); + if (environment && environment->symbolTable() == overlayTable) { + *overlayOut = environment; + break; + } + } + } + return instance; + } + auto* environment = dynamicDowncast(scope); + if (!overlayTable || !environment || environment->symbolTable() != overlayTable) + continue; + JSValue instance = environment->variableAt(ScopeOffset(0)).get(); + if (overlayOut) + *overlayOut = environment; + return instance && instance.isCell() ? dynamicDowncast(instance.asCell()) : nullptr; + } + return nullptr; +} + } // namespace JSC #include "JSGlobalObject.lut.h" @@ -1371,6 +1413,10 @@ void JSGlobalObject::init(VM& vm) [] (const Initializer& init) { init.set(JSModuleRecord::createStructure(init.vm, init.owner, jsNull())); }); + m_moduleGraphInstanceStructure.initLater( + [] (const Initializer& init) { + init.set(ModuleGraphInstance::createStructure(init.vm, init.owner, jsNull())); + }); m_syntheticModuleRecordStructure.initLater( [] (const Initializer& init) { init.set(SyntheticModuleRecord::createStructure(init.vm, init.owner, jsNull())); @@ -3106,6 +3152,9 @@ void JSGlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor) visitor.append(thisObject->m_regExpProtoSymbolReplace); thisObject->m_throwTypeErrorArgumentsCalleeGetterSetter.visit(visitor); thisObject->m_moduleLoader.visit(visitor); + visitor.append(thisObject->m_moduleScopeOverlaySymbolTable); + visitor.append(thisObject->m_currentGraphInstanceForLoading); + visitor.append(thisObject->m_primaryModuleScopeOverlay); visitor.append(thisObject->m_objectPrototype); visitor.append(thisObject->m_functionPrototype); @@ -3194,6 +3243,7 @@ void JSGlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor) visitor.append(thisObject->m_regExpMatchesArrayWithIndicesStructure); visitor.append(thisObject->m_regExpMatchesIndicesArrayStructure); thisObject->m_moduleRecordStructure.visit(visitor); + thisObject->m_moduleGraphInstanceStructure.visit(visitor); thisObject->m_syntheticModuleRecordStructure.visit(visitor); thisObject->m_moduleNamespaceObjectStructure.visit(visitor); thisObject->m_proxyObjectStructure.visit(visitor); @@ -4034,4 +4084,93 @@ Inspector::JSGlobalObjectInspectorController& JSGlobalObject::inspectorControlle } #endif + +void JSGlobalObject::configureModuleScopeOverlay(const Vector& names) +{ + RELEASE_ASSERT(Options::useModuleGraphInstances()); + VM& vm = this->vm(); + if (m_moduleScopeOverlaySymbolTable) + return; + SymbolTable* symbolTable = SymbolTable::create(vm); + symbolTable->setScopeType(SymbolTable::ScopeType::LexicalScope); + // Slot 0: the module graph instance this overlay belongs to — lets any code + // running in a graph (module code or CJS wrappers scoped to the overlay) be + // attributed to it by walking the scope chain. Empty in the primary overlay. + { + auto offset = symbolTable->takeNextScopeOffset(NoLockingNecessary); + ASSERT_UNUSED(offset, !offset.offset()); + SymbolTableEntry entry(VarOffset(ScopeOffset(0)), static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::DontEnum)); + symbolTable->set(NoLockingNecessary, vm.propertyNames->builtinNames().moduleGraphInstancePrivateName().impl(), WTF::move(entry)); + } + for (auto& name : names) { + if (name.isPrivateName()) + continue; + auto offset = symbolTable->takeNextScopeOffset(NoLockingNecessary); + symbolTable->set(NoLockingNecessary, name.impl(), SymbolTableEntry(VarOffset(offset))); + } + m_moduleScopeOverlaySymbolTable.set(vm, this, symbolTable); + JSLexicalEnvironment* primary = JSLexicalEnvironment::create(vm, this, globalLexicalEnvironment(), symbolTable, jsUndefined()); + Vector> slots; + { + ConcurrentJSLocker locker(symbolTable->m_lock); + for (auto iter = symbolTable->begin(locker), end = symbolTable->end(locker); iter != end; ++iter) + slots.append({ Identifier::fromUid(vm, iter->key.get()), iter->value.scopeOffset() }); + } + { + auto catchScope = DECLARE_TOP_EXCEPTION_SCOPE(vm); + for (auto& [name, offset] : slots) { + if (name.isPrivateName()) + continue; + JSValue value = get(this, name); + if (catchScope.exception()) { + catchScope.clearException(); + value = jsUndefined(); + } + primary->variableAt(offset).set(vm, primary, value ? value : jsUndefined()); + } + } + m_primaryModuleScopeOverlay.set(vm, this, primary); +} + +JSScope* JSGlobalObject::moduleEnvironmentParentScope() +{ + if (auto* overlay = m_primaryModuleScopeOverlay.get()) + return overlay; + return globalLexicalEnvironment(); +} + +JSLexicalEnvironment* JSGlobalObject::createModuleScopeOverlay(JSObject* values, ModuleGraphInstance* instance) +{ + VM& vm = this->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + SymbolTable* symbolTable = m_moduleScopeOverlaySymbolTable.get(); + if (!symbolTable) { + throwTypeError(this, scope, "module scope overlay is not configured"_s); + return nullptr; + } + JSLexicalEnvironment* primary = m_primaryModuleScopeOverlay.get(); + JSLexicalEnvironment* overlay = JSLexicalEnvironment::create(vm, this, globalLexicalEnvironment(), symbolTable, jsUndefined()); + Vector> slots; + { + ConcurrentJSLocker locker(symbolTable->m_lock); + for (auto iter = symbolTable->begin(locker), end = symbolTable->end(locker); iter != end; ++iter) + slots.append({ Identifier::fromUid(vm, iter->key.get()), iter->value.scopeOffset() }); + } + for (auto& [name, offset] : slots) { + if (name.isPrivateName()) { + overlay->variableAt(offset).set(vm, overlay, instance ? JSValue(instance) : jsUndefined()); + continue; + } + JSValue value; + if (values) { + value = values->get(this, name); + RETURN_IF_EXCEPTION(scope, nullptr); + } + if (!value || value.isUndefined()) + value = primary->variableAt(offset).get(); + overlay->variableAt(offset).set(vm, overlay, value); + } + return overlay; +} + } // namespace JSC diff --git a/Source/JavaScriptCore/runtime/JSGlobalObject.h b/Source/JavaScriptCore/runtime/JSGlobalObject.h index 2cfbfffa98f7..930796c32ffa 100644 --- a/Source/JavaScriptCore/runtime/JSGlobalObject.h +++ b/Source/JavaScriptCore/runtime/JSGlobalObject.h @@ -99,6 +99,7 @@ class JSIteratorConstructor; class JSIteratorHelperPrototype; class JSIteratorPrototype; class JSModuleLoader; +class ModuleGraphInstance; class JSModuleRecord; class JSPromise; class JSPromiseConstructor; @@ -352,6 +353,9 @@ class JSGlobalObject : public JSSegmentedVariableObject { LazyProperty m_throwTypeErrorArgumentsCalleeGetterSetter; LazyProperty m_moduleLoader; + WriteBarrier m_moduleScopeOverlaySymbolTable; + WriteBarrier m_currentGraphInstanceForLoading; + WriteBarrier m_primaryModuleScopeOverlay; WriteBarrier m_objectPrototype; WriteBarrier m_functionPrototype; @@ -456,6 +460,7 @@ class JSGlobalObject : public JSSegmentedVariableObject { LazyProperty m_accessorPropertyDescriptorObjectStructure; LazyProperty m_promiseCapabilityObjectStructure; LazyProperty m_moduleRecordStructure; + LazyProperty m_moduleGraphInstanceStructure; LazyProperty m_syntheticModuleRecordStructure; LazyProperty m_moduleNamespaceObjectStructure; LazyProperty m_proxyObjectStructure; @@ -912,6 +917,27 @@ class JSGlobalObject : public JSSegmentedVariableObject { JSModuleLoader* moduleLoader() const LIFETIME_BOUND { return m_moduleLoader.get(this); } + // Module graph instances (prototype): an optional lexical environment placed + // between every module environment and the global lexical environment, so a + // fixed set of free identifiers (e.g. `process`) resolve as closure variables + // that each graph instance can give its own values. Configured once, before + // the first module is linked, with the identifier list; the primary overlay + // holds the global's own values. + JS_EXPORT_PRIVATE void configureModuleScopeOverlay(const Vector& names); + SymbolTable* moduleScopeOverlaySymbolTable() const { return m_moduleScopeOverlaySymbolTable.get(); } + JSLexicalEnvironment* primaryModuleScopeOverlay() const { return m_primaryModuleScopeOverlay.get(); } + // Parent scope for module environments: the primary overlay if configured, else the global lexical environment. + JS_EXPORT_PRIVATE JSScope* moduleEnvironmentParentScope(); + // A new overlay for a graph instance: same shape as the primary, values from `values` (own properties by name), defaulting to the primary's. + JS_EXPORT_PRIVATE JSLexicalEnvironment* createModuleScopeOverlay(JSObject* values, ModuleGraphInstance* = nullptr); + // The graph instance (and its overlay) that code with this scope chain runs in, or null (primary / not configured). + JS_EXPORT_PRIVATE ModuleGraphInstance* graphInstanceForScope(JSScope*, JSScope** overlayOut = nullptr); + // Module graph instances: while a graph loads/links modules (synchronously), + // the host's fetch hooks attribute host-side module objects they create + // (e.g. CommonJS modules behind an ESM import) to this instance. + ModuleGraphInstance* currentGraphInstanceForLoading() const { return m_currentGraphInstanceForLoading.get(); } + JS_EXPORT_PRIVATE void setCurrentGraphInstanceForLoading(VM&, ModuleGraphInstance*); + ObjectPrototype* objectPrototype() const LIFETIME_BOUND { return m_objectPrototype.get(); } FunctionPrototype* functionPrototype() const LIFETIME_BOUND { return m_functionPrototype.get(); } ArrayPrototype* arrayPrototype() const LIFETIME_BOUND { return m_arrayPrototype.get(); } @@ -1059,6 +1085,7 @@ class JSGlobalObject : public JSSegmentedVariableObject { Structure* regExpStringIteratorStructure() const { return m_regExpStringIteratorStructure.get(); } Structure* remoteFunctionStructure() const { return m_remoteFunctionStructure.get(this); } Structure* moduleRecordStructure() const { return m_moduleRecordStructure.get(this); } + Structure* moduleGraphInstanceStructure() const { return m_moduleGraphInstanceStructure.get(this); } Structure* syntheticModuleRecordStructure() const { return m_syntheticModuleRecordStructure.get(this); } Structure* moduleNamespaceObjectStructure() const { return m_moduleNamespaceObjectStructure.get(this); } Structure* proxyObjectStructure() const { return m_proxyObjectStructure.get(this); } diff --git a/Source/JavaScriptCore/runtime/JSGlobalObjectFunctions.cpp b/Source/JavaScriptCore/runtime/JSGlobalObjectFunctions.cpp index d8a594ae545d..fadce2e216e2 100644 --- a/Source/JavaScriptCore/runtime/JSGlobalObjectFunctions.cpp +++ b/Source/JavaScriptCore/runtime/JSGlobalObjectFunctions.cpp @@ -34,6 +34,8 @@ #include "IntlDateTimeFormat.h" #include "JSCInlines.h" #include "JSModuleLoader.h" +#include "ModuleGraphInstance.h" +#include "JSModuleEnvironment.h" #include "JSPromise.h" #include "JSSet.h" #include "Lexer.h" @@ -819,6 +821,19 @@ JSC_DEFINE_HOST_FUNCTION(globalFuncImportModule, (JSGlobalObject* globalObject, // we should retrieve this from the arguments. JSValue parameters = callFrame->argument(1); bool deferred = callFrame->argument(2).isTrue(); + + // Module graph instances: import() from code that belongs to an instance + // loads the requested graph as a template (no evaluation of the primary) + // and instantiates it into the caller's instance. + if (Options::useModuleGraphInstances()) [[unlikely]] { + if (ModuleGraphInstance* instance = globalObject->graphInstanceForScope(callFrame->callerScope(vm))) { + JSPromise* promise = JSModuleLoader::importIntoGraphInstance(globalObject, specifier, parameters, sourceOrigin, instance, deferred); + if (scope.exception()) [[unlikely]] + return rejectWithCaughtException(); + return JSValue::encode(promise); + } + } + auto* importPromise = globalObject->moduleLoader()->importModule(globalObject, specifier, parameters, sourceOrigin, deferred); if (scope.exception()) [[unlikely]] return rejectWithCaughtException(); diff --git a/Source/JavaScriptCore/runtime/JSMicrotask.cpp b/Source/JavaScriptCore/runtime/JSMicrotask.cpp index 1b15d8377a73..1bf92d92f1d3 100644 --- a/Source/JavaScriptCore/runtime/JSMicrotask.cpp +++ b/Source/JavaScriptCore/runtime/JSMicrotask.cpp @@ -49,6 +49,7 @@ #include "JSModuleLoader.h" #include "JSModuleNamespaceObject.h" #include "JSModuleRecord.h" +#include "ModuleGraphInstance.h" #include "JSPromise.h" #include "JSPromiseCombinatorsGlobalContext.h" #include "JSPromiseConstructor.h" @@ -950,15 +951,16 @@ static void promiseFinallyReactionJob(JSGlobalObject* globalObject, VM& vm, JSPr promiseResolveThenableJob(globalObject, resolutionObject, then, resolve, reject, microtaskCallCache); } -static void asyncModuleExecutionDone(JSGlobalObject* globalObject, JSModuleRecord* module, JSValue value, JSPromise::Status status) +static void asyncModuleExecutionDone(JSGlobalObject* globalObject, JSModuleRecord* module, JSValue value, JSPromise::Status status, ModuleGraphInstance* instance = nullptr) { + ModuleGraphInstance::BusyScope busy(globalObject, instance); if (status == JSPromise::Status::Fulfilled) { - module->asyncExecutionFulfilled(globalObject); + module->asyncExecutionFulfilled(globalObject, instance); return; } ASSERT(status == JSPromise::Status::Rejected); - module->asyncExecutionRejected(globalObject, value); + module->asyncExecutionRejected(globalObject, value, instance); } void asyncModuleResolveEvaluation(JSGlobalObject* globalObject, VM& vm, ThrowScope& scope, JSModuleRecord* module, JSValue result) @@ -982,6 +984,24 @@ void asyncModuleResolveEvaluation(JSGlobalObject* globalObject, VM& vm, ThrowSco JSPromise::resolveWithInternalMicrotaskForAsyncAwait(globalObject, vm, result, InternalMicrotask::AsyncModuleExecutionResume, module); } +void asyncModuleResolveEvaluation(JSGlobalObject* globalObject, VM& vm, ThrowScope& scope, ModuleRecordInstance* recordInstance, JSValue result) +{ + auto* capability = recordInstance->asyncCapability(); + + if (scope.exception()) [[unlikely]] { + capability->rejectWithCaughtException(vm, scope); + return; + } + + if (result == vm.fastAsyncGeneratorSentinel()) + return; + + if (recordInstance->isExecutionFinished()) + capability->resolve(globalObject, vm, result); + else + JSPromise::resolveWithInternalMicrotaskForAsyncAwait(globalObject, vm, result, InternalMicrotask::AsyncModuleExecutionResume, recordInstance); +} + static void asyncModuleExecutionResume(JSGlobalObject* globalObject, VM& vm, JSModuleRecord* module, JSValue resolution, JSPromise::Status status) { auto scope = DECLARE_THROW_SCOPE(vm); @@ -994,6 +1014,23 @@ static void asyncModuleExecutionResume(JSGlobalObject* globalObject, VM& vm, JSM asyncModuleResolveEvaluation(globalObject, vm, scope, module, result); } +static void asyncModuleExecutionResume(JSGlobalObject* globalObject, VM& vm, ModuleRecordInstance* recordInstance, JSValue resolution, JSPromise::Status status) +{ + if (recordInstance->graphInstance()->isCleared()) + return; // the instance was disposed while this module was suspended + ModuleGraphInstance::BusyScope busy(globalObject, recordInstance->graphInstance()); + + auto scope = DECLARE_THROW_SCOPE(vm); + + JSValue resumeMode = jsNumber(status == JSPromise::Status::Fulfilled + ? static_cast(JSGenerator::ResumeMode::NormalMode) + : static_cast(JSGenerator::ResumeMode::ThrowMode)); + + auto* module = uncheckedDowncast(recordInstance->record()); + JSValue result = module->evaluateInstance(globalObject, recordInstance, resolution, resumeMode); + asyncModuleResolveEvaluation(globalObject, vm, scope, recordInstance, result); +} + static void moduleRegistryFetchSettled(JSGlobalObject* globalObject, VM& vm, ThrowScope& scope, std::span arguments, uint8_t payload) { // arguments[0] = pre-created modulePromise @@ -1776,8 +1813,14 @@ static void asyncGeneratorDriverResume(VM& vm, JSValue context, JSValue resoluti return; } - // The only remaining for-await driver kind is a top-level-await module. Any other context type - // reaching here means a new driver was wired up without a branch above. + // The only remaining for-await driver kind is a top-level-await module (in the primary graph or + // in a module graph instance). Any other context type reaching here means a new driver was wired up + // without a branch above. + if (auto* recordInstance = dynamicDowncast(context)) { + if (!recordInstance->graphInstance()->isCleared()) + asyncModuleExecutionResume(uncheckedDowncast(recordInstance->record())->realm(), vm, recordInstance, resolution, status); + return; + } auto* module = dynamicDowncast(context); RELEASE_ASSERT(module); asyncModuleExecutionResume(module->realm(), vm, module, resolution, status); @@ -2182,6 +2225,12 @@ void runInternalMicrotask(JSGlobalObject* globalObject, VM& vm, InternalMicrotas #endif case InternalMicrotask::AsyncModuleExecutionDone: { + if (auto* recordInstance = dynamicDowncast(arguments[2])) { + if (recordInstance->graphInstance()->isCleared()) + return; // disposed while evaluating: nothing left to complete + auto* module = uncheckedDowncast(recordInstance->record()); + RELEASE_AND_RETURN(scope, asyncModuleExecutionDone(module->realm(), module, arguments[1], static_cast(payload), recordInstance->graphInstance())); + } auto* module = uncheckedDowncast(arguments[2]); RELEASE_AND_RETURN(scope, asyncModuleExecutionDone(module->realm(), module, arguments[1], static_cast(payload))); } @@ -2194,6 +2243,8 @@ void runInternalMicrotask(JSGlobalObject* globalObject, VM& vm, InternalMicrotas #if USE(BUN_JSC_ADDITIONS) AsyncContextSwapScope asyncContextScope(vm, globalObject, AsyncContextSwapScope::unwrapContextTuple(contextArg)); #endif + if (auto* recordInstance = dynamicDowncast(contextArg)) + RELEASE_AND_RETURN(scope, asyncModuleExecutionResume(globalObject, vm, recordInstance, arguments[1], static_cast(payload))); auto* module = uncheckedDowncast(contextArg); RELEASE_AND_RETURN(scope, asyncModuleExecutionResume(module->realm(), vm, module, arguments[1], static_cast(payload))); } diff --git a/Source/JavaScriptCore/runtime/JSMicrotask.h b/Source/JavaScriptCore/runtime/JSMicrotask.h index ed78a91cf17c..3b3fd828a015 100644 --- a/Source/JavaScriptCore/runtime/JSMicrotask.h +++ b/Source/JavaScriptCore/runtime/JSMicrotask.h @@ -34,11 +34,14 @@ namespace JSC { class MicrotaskCallCache; class JSAsyncGenerator; class JSModuleRecord; +class ModuleRecordInstance; class ThrowScope; void runInternalMicrotask(JSGlobalObject*, VM&, InternalMicrotask, uint8_t, std::span, MicrotaskCallCache* = nullptr); void asyncModuleResolveEvaluation(JSGlobalObject*, VM&, ThrowScope&, JSModuleRecord*, JSValue result); +// Same, for a module body running in a module graph instance (its ModuleRecordInstance). +void asyncModuleResolveEvaluation(JSGlobalObject*, VM&, ThrowScope&, ModuleRecordInstance*, JSValue result); // https://tc39.es/ecma262/#sec-asyncgeneratorresume and #sec-asyncgeneratorawaitreturn — used by the C++ // %AsyncGeneratorPrototype%.return / .throw host functions to drive a non-busy generator. diff --git a/Source/JavaScriptCore/runtime/JSModuleEnvironment.cpp b/Source/JavaScriptCore/runtime/JSModuleEnvironment.cpp index f2f0ce896e14..4ba331c5e144 100644 --- a/Source/JavaScriptCore/runtime/JSModuleEnvironment.cpp +++ b/Source/JavaScriptCore/runtime/JSModuleEnvironment.cpp @@ -32,6 +32,9 @@ #include "AbstractModuleRecord.h" #include "JSCInlines.h" #include "JSLexicalEnvironmentInlines.h" +#include "ModuleGraphInstance.h" +#include "JSModuleRecord.h" +#include "SyntheticModuleRecord.h" namespace JSC { @@ -51,11 +54,16 @@ JSModuleEnvironment* JSModuleEnvironment::create( // // JSModuleEnvironment: // [ JSLexicalEnvironment ][ variable slots ][ additional slots for JSModuleEnvironment ] + // ... [ module record ][ graph instance ][ import slots (importSlotCount) ] + unsigned importSlotCount = moduleRecord ? moduleRecord->importSlotCount() : 0; JSModuleEnvironment* result = new ( NotNull, - allocateCell(vm, JSModuleEnvironment::allocationSize(symbolTable))) + allocateCell(vm, JSModuleEnvironment::allocationSize(symbolTable, importSlotCount))) JSModuleEnvironment(vm, structure, currentScope, symbolTable, initialValue, moduleRecord); + result->importSlotCountSlot() = importSlotCount; + for (unsigned i = 0; i < importSlotCount; ++i) + result->importSlot(i).setStartingValue(JSValue()); result->finishCreation(vm); return result; } @@ -68,6 +76,77 @@ void JSModuleEnvironment::visitChildrenImpl(JSCell* cell, Visitor& visitor) Base::visitChildren(thisObject, visitor); visitor.appendValues(thisObject->variables(), thisObject->symbolTable()->scopeSize()); visitor.append(thisObject->moduleRecordSlot()); + visitor.append(thisObject->graphInstanceSlot()); + if (unsigned count = thisObject->importSlotCount()) + visitor.appendValues(std::bit_cast*>(std::bit_cast(thisObject) + offsetOfImportSlot(thisObject->symbolTable(), 0)), count); +} + +void JSModuleEnvironment::fillImportSlots(JSGlobalObject* globalObject) +{ + VM& vm = globalObject->vm(); + AbstractModuleRecord* record = moduleRecord(); + if (!record) + return; + UNUSED_PARAM(globalObject); + ModuleGraphInstance* instance = graphInstance(); + ASSERT(importSlotCount() == record->importSlotCount()); + unsigned count = std::min(importSlotCount(), record->importSlotCount()); + for (unsigned i = 0; i < count; ++i) { + if (importSlot(i).get()) + continue; + AbstractModuleRecord* exporter = record->importedRecordAt(i); + JSModuleEnvironment* target = nullptr; + if (instance) { + if (JSModuleEnvironment* found = instance->environment(exporter)) + target = found; + else if (auto* synthetic = dynamicDowncast(exporter); synthetic && !synthetic->hasPerGraphInstanceState()) + target = exporter->moduleEnvironmentMayBeNull(); // stateless synthetic exporters are shared with the primary graph + else if (!dynamicDowncast(exporter) && !dynamicDowncast(exporter)) + target = exporter->moduleEnvironmentMayBeNull(); + } else + target = exporter->moduleEnvironmentMayBeNull(); + if (target) + importSlot(i).set(vm, this, target); + } +} + +JSModuleEnvironment* JSModuleEnvironment::importedEnvironmentFor(JSGlobalObject* globalObject, AbstractModuleRecord* exporter) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + ModuleGraphInstance* instance = graphInstance(); + if (!instance) + return exporter->moduleEnvironment(); + // A binding of this module itself resolves to this environment, and an + // import whose slot has been filled resolves through the slot: code that + // runs from an instance keeps resolving within that instance whatever the + // instance map holds by then (an embedder may clear it once the instance + // is no longer wanted for new imports). + AbstractModuleRecord* record = moduleRecord(); + if (record == exporter) + return this; + if (record) { + if (auto slotIndex = record->importSlotIndexFor(exporter)) { + if (JSValue filled = importSlot(*slotIndex).get(); filled.isCell()) + return uncheckedDowncast(filled); + } + } + JSModuleEnvironment* environment = exporter->graphInstanceEnvironment(globalObject, instance, true); + RETURN_IF_EXCEPTION(scope, nullptr); + if (environment) + return environment; + return exporter->moduleEnvironment(); +} + +ModuleGraphInstance* JSModuleEnvironment::graphInstance() +{ + JSValue value = graphInstanceSlot().get(); + return value && value.isCell() ? uncheckedDowncast(value.asCell()) : nullptr; +} + +void JSModuleEnvironment::setGraphInstance(VM& vm, ModuleGraphInstance* instance) +{ + graphInstanceSlot().set(vm, this, instance ? JSValue(instance) : JSValue()); } DEFINE_VISIT_CHILDREN(JSModuleEnvironment); @@ -81,7 +160,8 @@ bool JSModuleEnvironment::getOwnPropertySlot(JSObject* cell, JSGlobalObject* glo RETURN_IF_EXCEPTION(scope, false); if (resolution.type == AbstractModuleRecord::Resolution::Type::Resolved) { // When resolveImport resolves the resolution, the imported module environment must have the binding. - JSModuleEnvironment* importedModuleEnvironment = resolution.moduleRecord->moduleEnvironment(); + JSModuleEnvironment* importedModuleEnvironment = thisObject->importedEnvironmentFor(globalObject, resolution.moduleRecord); + RETURN_IF_EXCEPTION(scope, false); PropertySlot redirectSlot(importedModuleEnvironment, PropertySlot::InternalMethodType::Get); bool result = importedModuleEnvironment->methodTable()->getOwnPropertySlot(importedModuleEnvironment, globalObject, resolution.localName, redirectSlot); ASSERT_UNUSED(result, result); diff --git a/Source/JavaScriptCore/runtime/JSModuleEnvironment.h b/Source/JavaScriptCore/runtime/JSModuleEnvironment.h index 4fc646ba9ae7..ec023b36c4b7 100644 --- a/Source/JavaScriptCore/runtime/JSModuleEnvironment.h +++ b/Source/JavaScriptCore/runtime/JSModuleEnvironment.h @@ -35,6 +35,7 @@ WTF_ALLOW_UNSAFE_BUFFER_USAGE_BEGIN namespace JSC { class AbstractModuleRecord; +class ModuleGraphInstance; class Register; class JSModuleEnvironment final : public JSLexicalEnvironment { @@ -63,16 +64,64 @@ class JSModuleEnvironment final : public JSLexicalEnvironment { return offset; } - static size_t allocationSize(SymbolTable* symbolTable) + static size_t offsetOfGraphInstance(SymbolTable* symbolTable) { return offsetOfModuleRecord(symbolTable) + sizeof(WriteBarrier); } + // Number of import slots this environment was allocated with (raw word). + static size_t offsetOfImportSlotCount(SymbolTable* symbolTable) + { + return offsetOfGraphInstance(symbolTable) + sizeof(WriteBarrier); + } + + static size_t offsetOfImportSlot(SymbolTable* symbolTable, unsigned index) + { + return offsetOfImportSlotCount(symbolTable) + sizeof(uintptr_t) + sizeof(WriteBarrier) * index; + } + // Import slot `index` addressed as a variable of this environment (past the + // symbol table's own variables), so JITs can treat it like a closure variable. + static ScopeOffset importSlotScopeOffset(SymbolTable* symbolTable, unsigned index) + { + size_t byteOffset = offsetOfImportSlot(symbolTable, index) - offsetOfVariables(); + ASSERT(!(byteOffset % sizeof(WriteBarrier))); + return ScopeOffset(byteOffset / sizeof(WriteBarrier)); + } + + static size_t allocationSize(SymbolTable* symbolTable, unsigned importSlotCount = 0) + { + return offsetOfImportSlot(symbolTable, importSlotCount); + } + + unsigned importSlotCount() { return static_cast(importSlotCountSlot()); } + WriteBarrierBase& importSlot(unsigned index) + { + ASSERT(index < importSlotCount()); + return *std::bit_cast*>(std::bit_cast(this) + offsetOfImportSlot(symbolTable(), index)); + } + // Point every import slot at the exporter's environment in this graph + // instance (or the exporter's primary environment). Slots whose exporter has + // no environment yet (link-time cycles) stay empty and are filled on first use. + void fillImportSlots(JSGlobalObject*); + AbstractModuleRecord* moduleRecord() { return moduleRecordSlot().get(); } + // Module graph instances (prototype): a secondary instantiation of a module + // graph shares every record, executable and CodeBlock with the primary one + // and differs only in its environments. Each secondary environment points at + // its instance's map (a JSMap: AbstractModuleRecord → JSModuleEnvironment) + // so ModuleVar resolution can find the importing instance's copy of the + // exporting module's environment. Null on primary environments. + // The module graph instance this environment belongs to (null: the primary instantiation). + ModuleGraphInstance* graphInstance(); + void setGraphInstance(VM&, ModuleGraphInstance*); + // The environment of `exporter` in the same graph instance as this one + // (the exporter's primary environment if this is a primary environment). + JSModuleEnvironment* importedEnvironmentFor(JSGlobalObject*, AbstractModuleRecord* exporter); + static bool getOwnPropertySlot(JSObject*, JSGlobalObject*, PropertyName, PropertySlot&); static void getOwnSpecialPropertyNames(JSObject*, JSGlobalObject*, PropertyNameArrayBuilder&, DontEnumPropertiesMode); static bool put(JSCell*, JSGlobalObject*, PropertyName, JSValue, PutPropertySlot&); @@ -89,12 +138,22 @@ class JSModuleEnvironment final : public JSLexicalEnvironment { { return *std::bit_cast*>(std::bit_cast(this) + offsetOfModuleRecord(symbolTable())); } + WriteBarrierBase& graphInstanceSlot() + { + return *std::bit_cast*>(std::bit_cast(this) + offsetOfGraphInstance(symbolTable())); + } + uintptr_t& importSlotCountSlot() + { + return *std::bit_cast(std::bit_cast(this) + offsetOfImportSlotCount(symbolTable())); + } }; inline JSModuleEnvironment::JSModuleEnvironment(VM& vm, Structure* structure, JSScope* currentScope, SymbolTable* symbolTable, JSValue initialValue, AbstractModuleRecord* moduleRecord) : Base(vm, structure, currentScope, symbolTable, initialValue) { this->moduleRecordSlot().setWithoutWriteBarrier(moduleRecord); + this->graphInstanceSlot().setWithoutWriteBarrier(JSValue()); + this->importSlotCountSlot() = 0; } } // namespace JSC diff --git a/Source/JavaScriptCore/runtime/JSModuleLoader.cpp b/Source/JavaScriptCore/runtime/JSModuleLoader.cpp index 2763b587dc23..6193b0c18f6e 100644 --- a/Source/JavaScriptCore/runtime/JSModuleLoader.cpp +++ b/Source/JavaScriptCore/runtime/JSModuleLoader.cpp @@ -35,6 +35,11 @@ #include "JSMicrotask.h" #include "JSModuleNamespaceObject.h" #include "JSModuleRecord.h" +#include "ModuleGraphInstance.h" +#include "StrongInlines.h" +#include "JSModuleEnvironment.h" +#include "JSMapInlines.h" +#include "JSNativeStdFunction.h" #include "JSPromise.h" #include "JSSourceCode.h" #include "JSWebAssembly.h" @@ -441,6 +446,174 @@ JSPromise* JSModuleLoader::linkAndEvaluateModule(JSGlobalObject* globalObject, c return promise; } +JSPromise* JSModuleLoader::loadModuleForGraphInstance(JSGlobalObject* globalObject, const Identifier& key, RefPtr&& parameters, ModuleGraphInstance* instance) +{ + VM& vm = globalObject->vm(); + // A registry entry whose fetch or instantiation failed is another program + // run's failure (possibly produced on behalf of another instance, e.g. by a + // host module provider that runs code at fetch time): this instance loads + // afresh. Evaluation failures stay (instances evaluate separately anyway). + { + JSModuleLoader* loader = globalObject->moduleLoader(); + Locker locker { loader->cellLock() }; + if (ModuleRegistryEntry* entry = loader->registryEntry(key)) { + if (entry->hasSettledFailure()) + loader->removeEntry(key); + } + } + auto scope = DECLARE_THROW_SCOPE(vm); + ModuleGraphInstance* previous = globalObject->currentGraphInstanceForLoading(); + globalObject->setCurrentGraphInstanceForLoading(vm, instance); + JSPromise* promise = globalObject->moduleLoader()->loadModuleSync(globalObject, key, WTF::move(parameters), nullptr, { }); + globalObject->setCurrentGraphInstanceForLoading(vm, previous); + RELEASE_AND_RETURN(scope, promise); +} + +// Continuations below keep what they need as properties of the function +// object (visited by the GC), never as Strong<> captures: an evaluation that +// never settles must not root the instance. +static JSC_DECLARE_HOST_FUNCTION(moduleGraphInstanceNamespaceContinuation); +static JSC_DECLARE_HOST_FUNCTION(moduleGraphInstanceInstantiateContinuation); + +JSC_DEFINE_HOST_FUNCTION(moduleGraphInstanceNamespaceContinuation, (JSGlobalObject* globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + JSObject* callee = callFrame->jsCallee(); + auto* record = uncheckedDowncast(callee->getDirect(vm, Identifier::fromString(vm, "record"_s))); + auto* instance = uncheckedDowncast(callee->getDirect(vm, vm.propertyNames->builtinNames().moduleGraphInstancePrivateName())); + bool deferred = callee->getDirect(vm, vm.propertyNames->builtinNames().deferPrivateName()).isTrue(); + JSModuleNamespaceObject* ns = record->getModuleNamespace(globalObject, instance, deferred ? AbstractModuleRecord::ModulePhase::Defer : AbstractModuleRecord::ModulePhase::Evaluation); + RETURN_IF_EXCEPTION(scope, { }); + return JSValue::encode(ns); +} + +JSC_DEFINE_HOST_FUNCTION(moduleGraphInstanceInstantiateContinuation, (JSGlobalObject* globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + JSObject* callee = callFrame->jsCallee(); + auto* instance = uncheckedDowncast(callee->getDirect(vm, vm.propertyNames->builtinNames().moduleGraphInstancePrivateName())); + Identifier key = callee->getDirect(vm, Identifier::fromString(vm, "key"_s)).toPropertyKey(globalObject); + RETURN_IF_EXCEPTION(scope, { }); + auto type = static_cast(callee->getDirect(vm, Identifier::fromString(vm, "type"_s)).asInt32()); + bool deferred = callee->getDirect(vm, vm.propertyNames->builtinNames().deferPrivateName()).isTrue(); + JSPromise* ns = JSModuleLoader::instantiateLoadedModuleIntoGraphInstance(globalObject, key, instance, type, deferred); + RETURN_IF_EXCEPTION(scope, { }); + return JSValue::encode(ns); +} + +JSPromise* JSModuleLoader::instantiateLoadedModuleIntoGraphInstance(JSGlobalObject* globalObject, const Identifier& key, ModuleGraphInstance* instance, ScriptFetchParameters::Type type, bool deferred) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + if (instance->isCleared()) + RELEASE_AND_RETURN(scope, JSPromise::rejectedPromise(globalObject, createTypeError(globalObject, "Module graph instance has been disposed"_s))); + // The loading instance is current only while linking (host-provided + // synthetic modules consult it); evaluating the instance below runs script, + // and loads that script triggers must not be attributed to this instance. + ModuleGraphInstance* previousLoadingInstance = globalObject->currentGraphInstanceForLoading(); + globalObject->setCurrentGraphInstanceForLoading(vm, instance); + AbstractModuleRecord* record = globalObject->moduleLoader()->linkWithoutEvaluating(globalObject, key, nullptr, type); + globalObject->setCurrentGraphInstanceForLoading(vm, previousLoadingInstance); + RETURN_IF_EXCEPTION(scope, nullptr); + auto phase = deferred ? AbstractModuleRecord::ModulePhase::Defer : AbstractModuleRecord::ModulePhase::Evaluation; + auto* sourceRecord = dynamicDowncast(record); + if (!sourceRecord) { + // Synthetic modules: an environment in the instance when they carry + // per-instance state, otherwise shared with the primary graph. + JSModuleNamespaceObject* ns = record->getModuleNamespace(globalObject, instance, phase); + RETURN_IF_EXCEPTION(scope, nullptr); + RELEASE_AND_RETURN(scope, JSPromise::resolvedPromise(globalObject, ns)); + } + JSPromise* evaluated = sourceRecord->instantiateIntoGraphInstanceAsync(globalObject, instance, phase); + RETURN_IF_EXCEPTION(scope, nullptr); + JSFunction* toNamespace = JSFunction::create(vm, globalObject, 1, String(), moduleGraphInstanceNamespaceContinuation, ImplementationVisibility::Private); + toNamespace->putDirect(vm, Identifier::fromString(vm, "record"_s), sourceRecord); + toNamespace->putDirect(vm, vm.propertyNames->builtinNames().moduleGraphInstancePrivateName(), instance); + toNamespace->putDirect(vm, vm.propertyNames->builtinNames().deferPrivateName(), jsBoolean(deferred)); + RELEASE_AND_RETURN(scope, uncheckedDowncast(evaluated->then(globalObject, toNamespace, jsUndefined()))); +} + +JSPromise* JSModuleLoader::importIntoGraphInstance(JSGlobalObject* globalObject, JSString* specifierValue, JSValue parameters, const SourceOrigin& referrer, ModuleGraphInstance* instance, bool deferred) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + JSPromise* result = JSPromise::create(vm, globalObject->promiseStructure()); + + auto specifier = specifierValue->value(globalObject); + RETURN_IF_EXCEPTION(scope, nullptr); + // Import attributes (`with { type }`) select the fetch parameters like a + // regular import() does. + RefPtr fetchParameters; + { + auto attributes = retrieveImportAttributesFromDynamicImportOptions(globalObject, parameters, { vm.propertyNames->type.impl() }); + if (scope.exception()) [[unlikely]] { + result->rejectWithCaughtException(vm, scope); + RELEASE_AND_RETURN(scope, result); + } + auto type = retrieveTypeImportAttribute(globalObject, attributes); + if (scope.exception()) [[unlikely]] { + result->rejectWithCaughtException(vm, scope); + RELEASE_AND_RETURN(scope, result); + } + if (type) { +#if USE(BUN_JSC_ADDITIONS) + if (type.value() == ScriptFetchParameters::Type::HostDefined) + fetchParameters = ScriptFetchParameters::create(attributes.get(vm.propertyNames->type.impl())); + else +#endif + fetchParameters = ScriptFetchParameters::create(type.value()); + } + } + Identifier referrerKey = referrer.url().isValid() ? Identifier::fromString(vm, referrer.url().fileSystemPath()) : Identifier::fromString(vm, referrer.string()); + Identifier key = globalObject->moduleLoader()->resolve(globalObject, Identifier::fromString(vm, specifier), referrerKey, nullptr, false); + if (scope.exception()) [[unlikely]] { + result->rejectWithCaughtException(vm, scope); + RELEASE_AND_RETURN(scope, result); + } + auto fetchType = fetchParameters ? fetchParameters->type() : ScriptFetchParameters::Type::JavaScript; + // Fetch + parse synchronously with this graph as the loading instance, so + // host-side module objects created on the way (CommonJS behind an import) + // belong to this graph. Evaluation stays asynchronous. + JSPromise* loaded = loadModuleForGraphInstance(globalObject, key, WTF::move(fetchParameters), instance); + RETURN_IF_EXCEPTION(scope, nullptr); + JSFunction* onLoaded = JSFunction::create(vm, globalObject, 1, String(), moduleGraphInstanceInstantiateContinuation, ImplementationVisibility::Private); + onLoaded->putDirect(vm, vm.propertyNames->builtinNames().moduleGraphInstancePrivateName(), instance); + onLoaded->putDirect(vm, Identifier::fromString(vm, "key"_s), identifierToJSValue(vm, key)); + onLoaded->putDirect(vm, Identifier::fromString(vm, "type"_s), jsNumber(static_cast(fetchType))); + onLoaded->putDirect(vm, vm.propertyNames->builtinNames().deferPrivateName(), jsBoolean(deferred)); + RELEASE_AND_RETURN(scope, uncheckedDowncast(loaded->then(globalObject, onLoaded, jsUndefined()))); +} + +AbstractModuleRecord* JSModuleLoader::linkWithoutEvaluating(JSGlobalObject* globalObject, const Identifier& moduleKey, RefPtr scriptFetcher, ScriptFetchParameters::Type type) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + ModuleRegistryEntry* entry = ensureRegistered(globalObject, moduleKey, type); + RETURN_IF_EXCEPTION(scope, nullptr); + AbstractModuleRecord* record = entry->record(); + if (!record) { + throwTypeError(globalObject, scope, makeString("Module '"_s, moduleKey.string(), "' has not been fetched"_s)); + return nullptr; + } + JSValue error = entry->error(globalObject); + RETURN_IF_EXCEPTION(scope, nullptr); + if (error) { + scope.throwException(globalObject, error); + return nullptr; + } + record->link(globalObject, WTF::move(scriptFetcher)); + if (Exception* exception = scope.exception()) { + attachErrorInfo(globalObject, scope, record, entry->key(), entry->moduleType(), ModuleFailure::Kind::Instantiation); + entry->setInstantiationError(globalObject, exception->value()); + if (auto* cyclic = dynamicDowncast(record)) + cyclic->setEvaluationError(vm, exception->value()); + return nullptr; + } + return record; +} + JSPromise* JSModuleLoader::requestImportModule(JSGlobalObject* globalObject, const Identifier& moduleName, const Identifier& referrer, RefPtr parameters, RefPtr scriptFetcher, bool deferred, int64_t referrerAsyncOrder) { VM& vm = globalObject->vm(); @@ -1220,7 +1393,7 @@ void JSModuleLoader::drainSynchronousModuleQueue(JSGlobalObject* globalObject) tasks.shrink(0); } -JSPromise* JSModuleLoader::loadModuleSync(JSGlobalObject* globalObject, const Identifier& moduleName, RefPtr&& parameters, RefPtr&& scriptFetcher) +JSPromise* JSModuleLoader::loadModuleSync(JSGlobalObject* globalObject, const Identifier& moduleName, RefPtr&& parameters, RefPtr&& scriptFetcher, OptionSet flags) { VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); @@ -1232,7 +1405,7 @@ JSPromise* JSModuleLoader::loadModuleSync(JSGlobalObject* globalObject, const Id vm.m_synchronousModuleQueue = queue.prev; }); - JSPromise* result = loadModule(globalObject, moduleName, WTF::move(parameters), WTF::move(scriptFetcher), { ModuleLoadFlag::Evaluate }); + JSPromise* result = loadModule(globalObject, moduleName, WTF::move(parameters), WTF::move(scriptFetcher), flags); RETURN_IF_EXCEPTION(scope, result); scope.release(); @@ -1294,8 +1467,31 @@ JSPromise* JSModuleLoader::makeModule(JSGlobalObject* globalObject, const Identi JSObject* lazyExportsSource = syntheticSourceProvider->generate(globalObject, moduleKey, exportNames, args); RETURN_IF_EXCEPTION(scope, promise->rejectWithCaughtException(vm, scope)); - auto* moduleRecord = SyntheticModuleRecord::tryCreateWithExportNamesAndValues(globalObject, moduleKey, exportNames, args, lazyExportsSource); + ModuleGraphInstance* loadingInstance = syntheticSourceProvider->regeneratesPerGraphInstance() ? globalObject->currentGraphInstanceForLoading() : nullptr; + MarkedArgumentBuffer primaryValues; + if (loadingInstance) { + // Generated on behalf of a graph: those values are the graph's; the + // primary's are produced later, if the primary ever links to them. + for (unsigned i = 0; i < args.size(); ++i) + primaryValues.append(JSValue()); + } + auto* moduleRecord = SyntheticModuleRecord::tryCreateWithExportNamesAndValues(globalObject, moduleKey, exportNames, loadingInstance ? primaryValues : args, lazyExportsSource ? lazyExportsSource : (loadingInstance ? globalObject->globalThis() : nullptr)); RETURN_IF_EXCEPTION(scope, promise->rejectWithCaughtException(vm, scope)); + if (moduleRecord) { + moduleRecord->setSyntheticSourceProvider(syntheticSourceProvider, !!loadingInstance); + if (loadingInstance) { + JSModuleEnvironment* environment = JSModuleEnvironment::create(vm, globalObject, nullptr, moduleRecord->moduleEnvironment()->symbolTable(), jsTDZValue(), moduleRecord); + RETURN_IF_EXCEPTION(scope, promise->rejectWithCaughtException(vm, scope)); + for (unsigned i = 0; i < exportNames.size(); ++i) { + bool putResult = false; + symbolTablePutTouchWatchpointSet(environment, globalObject, exportNames[i], args.at(i) ? args.at(i) : jsUndefined(), false, true, putResult); + RETURN_IF_EXCEPTION(scope, promise->rejectWithCaughtException(vm, scope)); + } + environment->setGraphInstance(vm, loadingInstance); + loadingInstance->add(vm, moduleRecord, environment); + RETURN_IF_EXCEPTION(scope, promise->rejectWithCaughtException(vm, scope)); + } + } scope.release(); promise->fulfill(vm, moduleRecord); diff --git a/Source/JavaScriptCore/runtime/JSModuleLoader.h b/Source/JavaScriptCore/runtime/JSModuleLoader.h index f0f1da668531..be324e49923d 100644 --- a/Source/JavaScriptCore/runtime/JSModuleLoader.h +++ b/Source/JavaScriptCore/runtime/JSModuleLoader.h @@ -39,8 +39,11 @@ namespace JSC { class ErrorInstance; class JSPromise; class JSModuleNamespaceObject; +class ModuleGraphInstance; class JSModuleRecord; class JSSourceCode; +class JSMap; +class JSModuleEnvironment; class ModuleRegistryEntry; class SourceOrigin; @@ -94,6 +97,18 @@ class JSModuleLoader final : public JSCell { void provideFetch(JSGlobalObject*, const Identifier& key, ScriptFetchParameters::Type, JSSourceCode*); JSPromise* loadModule(JSGlobalObject*, const Identifier& moduleName, RefPtr, RefPtr, OptionSet, int64_t referrerAsyncOrder = -1); JSPromise* linkAndEvaluateModule(JSGlobalObject*, const Identifier& moduleKey, RefPtr, RefPtr); + // Module graph instances (prototype): link a fetched module graph without + // evaluating it, so it can serve as the template for instantiateIntoGraphInstance. + JS_EXPORT_PRIVATE AbstractModuleRecord* linkWithoutEvaluating(JSGlobalObject*, const Identifier& moduleKey, RefPtr, ScriptFetchParameters::Type = ScriptFetchParameters::Type::JavaScript); + // import() from inside a graph instance: load `specifier` (resolved against + // `referrer`) as a template and instantiate it into the instance that + // `callerEnvironment` belongs to; resolves with a per-instance namespace object. + JS_EXPORT_PRIVATE static JSPromise* importIntoGraphInstance(JSGlobalObject*, JSString* specifier, JSValue parameters, const SourceOrigin& referrer, ModuleGraphInstance*, bool deferred = false); + // Shared tail of the above and the embedder entry point: link `key` as a template, instantiate into `instance` with `overlay`, return the namespace. + // loadModule without evaluation, synchronously, with `instance` as the current loading graph instance. + JS_EXPORT_PRIVATE static JSPromise* loadModuleForGraphInstance(JSGlobalObject*, const Identifier& key, RefPtr&&, ModuleGraphInstance*); + // Resolves with the per-instance namespace object once the (possibly async) instance evaluation completes. + JS_EXPORT_PRIVATE static JSPromise* instantiateLoadedModuleIntoGraphInstance(JSGlobalObject*, const Identifier& key, ModuleGraphInstance*, ScriptFetchParameters::Type = ScriptFetchParameters::Type::JavaScript, bool deferred = false); JSPromise* requestImportModule(JSGlobalObject*, const Identifier& moduleName, const Identifier& referrer, RefPtr, RefPtr, bool deferred = false, int64_t referrerAsyncOrder = -1); #if USE(BUN_JSC_ADDITIONS) JS_EXPORT_PRIVATE int64_t asyncEvaluationOrderForKey(const Identifier& key); @@ -208,7 +223,7 @@ class JSModuleLoader final : public JSCell { m_moduleMap.clear(); m_resolutionFailures.clear(); } - JS_EXPORT_PRIVATE JSPromise* loadModuleSync(JSGlobalObject*, const Identifier& moduleName, RefPtr&&, RefPtr&&); + JS_EXPORT_PRIVATE JSPromise* loadModuleSync(JSGlobalObject*, const Identifier& moduleName, RefPtr&&, RefPtr&&, OptionSet = { ModuleLoadFlag::Evaluate }); JS_EXPORT_PRIVATE static void drainSynchronousModuleQueue(JSGlobalObject*); #endif diff --git a/Source/JavaScriptCore/runtime/JSModuleNamespaceObject.cpp b/Source/JavaScriptCore/runtime/JSModuleNamespaceObject.cpp index 6b2c96688dd9..a3300c9464d0 100644 --- a/Source/JavaScriptCore/runtime/JSModuleNamespaceObject.cpp +++ b/Source/JavaScriptCore/runtime/JSModuleNamespaceObject.cpp @@ -30,6 +30,7 @@ #include "CyclicModuleRecord.h" #include "JSCInlines.h" #include "JSModuleEnvironment.h" +#include "ModuleGraphInstance.h" #include "JSModuleRecord.h" #if USE(BUN_JSC_ADDITIONS) #include "SyntheticModuleRecord.h" @@ -98,12 +99,44 @@ void JSModuleNamespaceObject::visitChildrenImpl(JSCell* cell, Visitor& visitor) ASSERT_GC_OBJECT_INHERITS(thisObject, info()); Base::visitChildren(thisObject, visitor); visitor.append(thisObject->m_moduleRecord); + visitor.append(thisObject->m_graphInstance); + visitor.append(thisObject->m_instanceEnvironment); for (auto& entry : thisObject->m_exports.values()) visitor.appendHidden(entry.moduleRecord); } DEFINE_VISIT_CHILDREN(JSModuleNamespaceObject); +void JSModuleNamespaceObject::setGraphInstance(VM& vm, ModuleGraphInstance* instance, JSModuleEnvironment* environment) +{ + m_graphInstance.setMayBeNull(vm, this, instance); + m_instanceEnvironment.setMayBeNull(vm, this, environment); +} + +JSModuleEnvironment* JSModuleNamespaceObject::environmentFor(JSGlobalObject* globalObject, AbstractModuleRecord* record) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + if (ModuleGraphInstance* instance = m_graphInstance.get()) { + // Own exports: this instance's environment; re-exports: through its + // import slots; then the instance. A namespace stays bound to the + // instance it was created for. + if (JSModuleEnvironment* own = m_instanceEnvironment.get()) { + if (record == m_moduleRecord.get()) + return own; + RELEASE_AND_RETURN(scope, own->importedEnvironmentFor(globalObject, record)); + } + if (JSModuleEnvironment* found = instance->environment(record)) + return found; + if (instance->isCleared()) { + throwTypeError(globalObject, scope, "Module namespace belongs to a module graph instance that was disposed"_s); + return nullptr; + } + // Otherwise the record is one the instance shares with the primary graph. + } + return record->moduleEnvironment(); +} + // https://tc39.es/proposal-defer-import-eval/#sec-IsSymbolLikeNamespaceKey ALWAYS_INLINE bool JSModuleNamespaceObject::isSymbolLikeNamespaceKey(VM& vm, PropertyName propertyName) { @@ -117,18 +150,20 @@ void JSModuleNamespaceObject::ensureDeferredNamespaceEvaluation(JSGlobalObject* { // 1. If O.[[Deferred]] is true, then ASSERT(m_isDeferred); + // A namespace of a module graph instance evaluates its module in that instance. + ModuleGraphInstance* instance = m_graphInstance.get(); // Fast path: if the module's cycle has already successfully evaluated, EvaluateModuleSync would // observe a fulfilled promise and return without throwing, so we can skip the work entirely. // We must consult [[CycleRoot]] here because Evaluate() redirects to it; for a non-root SCC // member, status/evaluationError on the module itself may not reflect the cycle's outcome. if (auto* cyclic = dynamicDowncast(m_moduleRecord.get())) { - CyclicModuleRecord* root = cyclic->cycleRoot() ? cyclic->cycleRoot() : cyclic; - if (root->status() == CyclicModuleRecord::Status::Evaluated && !root->evaluationError()) + CyclicModuleRecord* root = cyclic->cycleRoot(instance) ? cyclic->cycleRoot(instance) : cyclic; + if (root->status(instance) == CyclicModuleRecord::Status::Evaluated && !root->evaluationError(instance)) return; } // 1.a. Let m be O.[[Module]]. // 1.b. Perform ? EvaluateModuleSync(m). - m_moduleRecord->evaluateSync(globalObject); + m_moduleRecord->evaluateSync(globalObject, instance); // 2. Return O.[[Exports]]. } @@ -185,10 +220,11 @@ bool JSModuleNamespaceObject::getOwnPropertySlotCommon(JSGlobalObject* globalObj // 10. If binding.[[BindingName]] is "*namespace*", then // a. Return ? GetModuleNamespace(targetModule). // We call getModuleNamespace() to ensure materialization. And after that, looking up the value from the scope to encourage module namespace object IC. - exportEntry.moduleRecord->getModuleNamespace(globalObject); + exportEntry.moduleRecord->getModuleNamespace(globalObject, m_graphInstance.get()); RETURN_IF_EXCEPTION(scope, false); } - JSModuleEnvironment* environment = exportEntry.moduleRecord->moduleEnvironment(); + JSModuleEnvironment* environment = environmentFor(globalObject, exportEntry.moduleRecord.get()); + RETURN_IF_EXCEPTION(scope, false); ScopeOffset scopeOffset; JSValue value = getValue(environment, exportEntry.localName, scopeOffset); #if USE(BUN_JSC_ADDITIONS) diff --git a/Source/JavaScriptCore/runtime/JSModuleNamespaceObject.h b/Source/JavaScriptCore/runtime/JSModuleNamespaceObject.h index 9e3e15b746a7..7274bd77586e 100644 --- a/Source/JavaScriptCore/runtime/JSModuleNamespaceObject.h +++ b/Source/JavaScriptCore/runtime/JSModuleNamespaceObject.h @@ -31,6 +31,10 @@ namespace JSC { +class ModuleGraphInstance; + +class JSModuleEnvironment; + class JSModuleNamespaceObject final : public JSNonFinalObject { public: using Base = JSNonFinalObject; @@ -73,6 +77,11 @@ class JSModuleNamespaceObject final : public JSNonFinalObject { inline static Structure* createStructure(VM&, JSGlobalObject*, JSValue); AbstractModuleRecord* moduleRecord() LIFETIME_BOUND { return m_moduleRecord.get(); } + // Module graph instances (prototype): when set (a JSMap record → environment), + // bindings are read from that instance's environments instead of the records'. + ModuleGraphInstance* graphInstance() const { return m_graphInstance.get(); } + void setGraphInstance(VM&, ModuleGraphInstance*, JSModuleEnvironment*); + JSModuleEnvironment* environmentFor(JSGlobalObject*, AbstractModuleRecord*); #if USE(BUN_JSC_ADDITIONS) WTF::TriState m_hasESModuleMarker = WTF::TriState::Indeterminate; @@ -94,6 +103,10 @@ class JSModuleNamespaceObject final : public JSNonFinalObject { ExportMap m_exports; WriteBarrier m_moduleRecord; + WriteBarrier m_graphInstance; + // This namespace's own module environment in that instance: local exports and + // (through its import slots) re-exports resolve here without the instance map. + WriteBarrier m_instanceEnvironment; const bool m_isDeferred; #if USE(BUN_JSC_ADDITIONS) bool m_isOverridingValue = false; diff --git a/Source/JavaScriptCore/runtime/JSModuleRecord.cpp b/Source/JavaScriptCore/runtime/JSModuleRecord.cpp index 300745243adb..6f2e89c0a450 100644 --- a/Source/JavaScriptCore/runtime/JSModuleRecord.cpp +++ b/Source/JavaScriptCore/runtime/JSModuleRecord.cpp @@ -27,7 +27,14 @@ #include "JSModuleRecord.h" #include "BuiltinNames.h" +#include "IdentifierInlines.h" #include "Interpreter.h" +#include +#include +#include "StrongInlines.h" +#include "JSGenerator.h" +#include "JSNativeStdFunction.h" +#include "InternalFieldTuple.h" #include "JSAsyncFunction.h" #include "JSAsyncGeneratorFunction.h" #include "JSCInlines.h" @@ -36,8 +43,14 @@ #include "JSModuleEnvironment.h" #include "JSModuleLoader.h" #include "JSModuleNamespaceObject.h" +#include "ModuleGraphInstance.h" +#include "JSMapInlines.h" +#include "JSLexicalEnvironmentInlines.h" +#include "SymbolTableInlines.h" +#include "SyntheticModuleRecord.h" #include "JSPromise.h" #include "ModuleProgramExecutable.h" +#include "ModuleProgramCodeBlock.h" #include "SourceProfiler.h" #include "UnlinkedModuleProgramCodeBlock.h" #include @@ -92,6 +105,13 @@ void JSModuleRecord::visitChildrenImpl(JSCell* cell, Visitor& visitor) ASSERT_GC_OBJECT_INHERITS(thisObject, info()); Base::visitChildren(thisObject, visitor); visitor.append(thisObject->m_moduleProgramExecutable); + visitor.append(thisObject->m_retainedExecutable); + visitor.append(thisObject->m_retainedCodeBlock); + { + Locker locker { thisObject->cellLock() }; + for (auto& barrier : thisObject->m_functionDeclExecutables) + visitor.append(barrier); + } #if USE(BUN_JSC_ADDITIONS) visitor.reportExtraMemoryVisited(thisObject->sourceCode().memoryCost()); @@ -108,14 +128,27 @@ bool JSModuleRecord::isTopLevelExecutionFinished() const JSValue JSModuleRecord::evaluate(JSGlobalObject* globalObject, JSValue sentValue, JSValue resumeMode) { + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + // Module graph instances: by the time a module evaluates, its dependencies' + // environments exist; fill the import slots the fast paths read. + if (m_moduleEnvironment && m_moduleEnvironment->importSlotCount() && internalField(Field::State).get() == jsNumber(static_cast(State::Init))) { + for (unsigned i = 0; i < importSlotCount(); ++i) { + if (auto* synthetic = dynamicDowncast(importedRecordAt(i))) { + synthetic->materializePrimaryIfPending(globalObject); + RETURN_IF_EXCEPTION(scope, { }); + } + } + m_moduleEnvironment->fillImportSlots(globalObject); + RETURN_IF_EXCEPTION(scope, { }); + } + if (!m_moduleProgramExecutable) { ASSERT_NOT_REACHED_WITH_MESSAGE("Can't evaluate a JSModuleRecord that has no executable"); return jsUndefined(); } - VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - if (JSValue error = evaluationError()) { scope.throwException(globalObject, error); return { }; @@ -125,6 +158,8 @@ JSValue JSModuleRecord::evaluate(JSGlobalObject* globalObject, JSValue sentValue JSValue resultOrAwaitedValue = vm.interpreter.executeModuleProgram(this, executable, globalObject, moduleEnvironment(), sentValue, resumeMode); RETURN_IF_EXCEPTION(scope, { }); + if (m_retainedExecutable) + pinRetainedCodeBlock(vm); if (isTopLevelExecutionFinished()) m_moduleProgramExecutable.clear(); @@ -172,6 +207,291 @@ void JSModuleRecord::execute(JSGlobalObject* globalObject, JSPromise* capability // 11. Return unused. } +void JSModuleRecord::retainForGraphInstances(VM& vm, ModuleProgramExecutable* executable, Vector>&& functionDeclExecutables) +{ + m_retainedExecutable.set(vm, this, executable); + pinRetainedCodeBlock(vm); + { + // The concurrent marker iterates m_functionDeclExecutables under the cell lock. + Locker locker { cellLock() }; + m_functionDeclExecutables = WTF::move(functionDeclExecutables); + } + for (auto& barrier : m_functionDeclExecutables) { + if (barrier) + vm.writeBarrier(this, barrier.get()); + } +} + +// InitializeEnvironment steps 5-24 against a fresh environment that belongs to +// `instance`, reusing everything the primary instantiation linked. Recursively +// instantiates every source text dependency into the instance first. +JSModuleEnvironment* JSModuleRecord::createInstanceEnvironment(JSGlobalObject* globalObject, ModuleGraphInstance* instance, Vector& created) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + if (instance->isCleared()) { + throwTypeError(globalObject, scope, "Module graph instance was disposed"_s); + return nullptr; + } + + if (JSModuleEnvironment* existing = instance->environment(this)) + return existing; + + ModuleProgramExecutable* executable = m_retainedExecutable.get(); + if (!executable) { + throwTypeError(globalObject, scope, makeString("Module '"_s, moduleKey().string(), "' cannot be instantiated again: it was linked before module graph instances were enabled"_s)); + return nullptr; + } + + SymbolTable* symbolTable = executable->moduleEnvironmentSymbolTable(); + JSScope* parentScope = instance->parentScope() ? instance->parentScope() : globalObject->moduleEnvironmentParentScope(); + JSModuleEnvironment* env = JSModuleEnvironment::create(vm, globalObject, parentScope, symbolTable, jsTDZValue(), this); + RETURN_IF_EXCEPTION(scope, nullptr); + env->setGraphInstance(vm, instance); + // Register before recursing so import cycles terminate (status Linked). + instance->add(vm, this, env); + + for (const auto& request : requestedModules()) { + AbstractModuleRecord* imported = hostResolveImportedModule(globalObject, request.m_specifier, request.type()); + RETURN_IF_EXCEPTION(scope, nullptr); + if (auto* importedSource = dynamicDowncast(imported)) { + importedSource->createInstanceEnvironment(globalObject, instance, created); + RETURN_IF_EXCEPTION(scope, nullptr); + } else { + // Synthetic records with per-instance state get an environment in + // the instance; others are shared with the primary graph. + imported->graphInstanceEnvironment(globalObject, instance, true); + RETURN_IF_EXCEPTION(scope, nullptr); + } + } + + // 7.c. Namespace imports bind the exporter's namespace in this instance; + // single imports that resolve to a namespace (export * as ns from) get that + // namespace materialised in its module's instance environment. + for (const auto& [key, in] : importEntries()) { + AbstractModuleRecord* importedModule = hostResolveImportedModule(globalObject, in.moduleRequest, in.moduleRequestType); + RETURN_IF_EXCEPTION(scope, nullptr); + if (in.type == ImportEntryType::Namespace) { + JSModuleNamespaceObject* ns = importedModule->getModuleNamespace(globalObject, instance, in.phase); + RETURN_IF_EXCEPTION(scope, nullptr); + bool putResult = false; + symbolTablePutTouchWatchpointSet(env, globalObject, in.localName, ns, false, true, putResult); + RETURN_IF_EXCEPTION(scope, nullptr); + continue; + } + Resolution resolution = importedModule->resolveExport(globalObject, in.importName); + RETURN_IF_EXCEPTION(scope, nullptr); + if (resolution.type == Resolution::Type::Resolved && resolution.localName == vm.propertyNames->starNamespacePrivateName) { + resolution.moduleRecord->getModuleNamespace(globalObject, instance); + RETURN_IF_EXCEPTION(scope, nullptr); + } + } + + // 21. var declarations start as undefined (lexical ones stay in TDZ). + UnlinkedModuleProgramCodeBlock* unlinkedCodeBlock = executable->unlinkedCodeBlock(); + for (const auto& variable : unlinkedCodeBlock->variableDeclarations()) { + SymbolTableEntry::Fast entry = symbolTable->get(variable.key.get()); + if (!entry.varOffset().isStack()) { + bool putResult = false; + symbolTablePutTouchWatchpointSet(env, globalObject, Identifier::fromUid(vm, variable.key.get()), jsUndefined(), false, true, putResult); + RETURN_IF_EXCEPTION(scope, nullptr); + } + } + + // 24. Function declarations: new function objects over the executables the + // primary instantiation linked, closed over this environment. + for (size_t i = 0, count = unlinkedCodeBlock->numberOfFunctionDecls(); i < count; ++i) { + FunctionExecutable* functionExecutable = i < m_functionDeclExecutables.size() ? m_functionDeclExecutables[i].get() : nullptr; + if (!functionExecutable) + continue; + SourceParseMode parseMode = functionExecutable->parseMode(); + JSFunction* function = nullptr; + if (isAsyncGeneratorWrapperParseMode(parseMode)) + function = JSAsyncGeneratorFunction::create(vm, globalObject, functionExecutable, env); + else if (isGeneratorWrapperParseMode(parseMode)) + function = JSGeneratorFunction::create(vm, globalObject, functionExecutable, env); + else if (isAsyncFunctionWrapperParseMode(parseMode)) + function = JSAsyncFunction::create(vm, globalObject, functionExecutable, env); + else + function = JSFunction::create(vm, globalObject, functionExecutable, env); + RETURN_IF_EXCEPTION(scope, nullptr); + bool putResult = false; + symbolTablePutTouchWatchpointSet(env, globalObject, unlinkedCodeBlock->functionDecl(i)->name(), function, false, true, putResult); + RETURN_IF_EXCEPTION(scope, nullptr); + } + + // import.meta: a fresh object per instance, carrying the instance for the host. + if (m_features & ImportMetaFeature) { + JSObject* meta = globalObject->moduleLoader()->createImportMetaProperties(globalObject, identifierToJSValue(vm, moduleKey()), this, nullptr); + RETURN_IF_EXCEPTION(scope, nullptr); + meta->putDirect(vm, vm.propertyNames->builtinNames().moduleGraphInstancePrivateName(), instance, static_cast(PropertyAttribute::DontEnum)); + bool putResult = false; + symbolTablePutTouchWatchpointSet(env, globalObject, vm.propertyNames->builtinNames().metaPrivateName(), meta, false, true, putResult); + RETURN_IF_EXCEPTION(scope, nullptr); + } + + created.append(this); + return env; +} + +static void fillGraphInstanceImportSlots(JSGlobalObject* globalObject, const Vector& created, ModuleGraphInstance* instance) +{ + for (JSModuleRecord* record : created) { + if (JSModuleEnvironment* environment = instance->environment(record)) + environment->fillImportSlots(globalObject); + } +} + +JSModuleEnvironment* JSModuleRecord::instantiateIntoGraphInstance(JSGlobalObject* globalObject, ModuleGraphInstance* instance, ModulePhase phase) +{ + ModuleGraphInstance::BusyScope busy(globalObject, instance); + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + Vector created; + JSModuleEnvironment* env = createInstanceEnvironment(globalObject, instance, created); + RETURN_IF_EXCEPTION(scope, nullptr); + fillGraphInstanceImportSlots(globalObject, created, instance); + if (phase == ModulePhase::Defer) { + // import defer: only the asynchronous transitive dependencies evaluate + // now; that must complete synchronously here. + OrderedHashSet asyncDependencies; + UncheckedKeyHashSet seen; + gatherAsynchronousTransitiveDependencies(asyncDependencies, seen, instance); + for (AbstractModuleRecord* dependency : asyncDependencies) { + if (auto* cyclic = dynamicDowncast(dependency)) { +#if USE(BUN_JSC_ADDITIONS) + cyclic->evaluate(globalObject, -1, instance); +#else + cyclic->evaluate(globalObject, instance); +#endif + RETURN_IF_EXCEPTION(scope, nullptr); + } + } + return env; + } +#if USE(BUN_JSC_ADDITIONS) + JSPromise* promise = CyclicModuleRecord::evaluate(globalObject, -1, instance); +#else + JSPromise* promise = CyclicModuleRecord::evaluate(globalObject, instance); +#endif + RETURN_IF_EXCEPTION(scope, nullptr); + switch (promise->status()) { + case JSPromise::Status::Fulfilled: + return env; + case JSPromise::Status::Rejected: + promise->markAsHandled(); + scope.throwException(globalObject, promise->result()); + return nullptr; + case JSPromise::Status::Pending: + // Top-level await somewhere in the sub-graph: a synchronous caller + // cannot wait for it (the asynchronous form can). + throwTypeError(globalObject, scope, makeString("Module '"_s, moduleKey().string(), "' or one of its dependencies uses top-level await and cannot be evaluated synchronously"_s)); + return nullptr; + } + return env; +} + +JSPromise* JSModuleRecord::instantiateIntoGraphInstanceAsync(JSGlobalObject* globalObject, ModuleGraphInstance* instance, ModulePhase phase) +{ + ModuleGraphInstance::BusyScope busy(globalObject, instance); + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + Vector created; + createInstanceEnvironment(globalObject, instance, created); + if (scope.exception()) [[unlikely]] { + JSPromise* rejected = JSPromise::create(vm, globalObject->promiseStructure()); + rejected->rejectWithCaughtException(vm, scope); + return rejected; + } + fillGraphInstanceImportSlots(globalObject, created, instance); + if (phase == ModulePhase::Defer) { + // import defer: evaluate the asynchronous transitive dependencies now + // and wait for all of them; the rest runs on first namespace access. + OrderedHashSet asyncDependencies; + UncheckedKeyHashSet seen; + gatherAsynchronousTransitiveDependencies(asyncDependencies, seen, instance); + MarkedArgumentBuffer promises; + for (AbstractModuleRecord* dependency : asyncDependencies) { + auto* cyclic = dynamicDowncast(dependency); + if (!cyclic) + continue; +#if USE(BUN_JSC_ADDITIONS) + JSPromise* promise = cyclic->evaluate(globalObject, -1, instance); +#else + JSPromise* promise = cyclic->evaluate(globalObject, instance); +#endif + if (scope.exception()) [[unlikely]] { + JSPromise* rejected = JSPromise::create(vm, globalObject->promiseStructure()); + rejected->rejectWithCaughtException(vm, scope); + return rejected; + } + promises.append(promise); + } + // All must fulfil (first rejection rejects): fold them into one chain. + JSPromise* result = JSPromise::resolvedPromise(globalObject, jsUndefined()); + RETURN_IF_EXCEPTION(scope, nullptr); + for (unsigned i = 0; i < promises.size(); ++i) { + JSValue next = promises.at(i); + auto* waitNext = JSNativeStdFunction::create(vm, globalObject, 0, String(), [next = Strong(vm, next)](JSGlobalObject*, CallFrame*) -> EncodedJSValue { + return JSValue::encode(next.get()); + }); + result = uncheckedDowncast(result->then(globalObject, waitNext, jsUndefined())); + RETURN_IF_EXCEPTION(scope, nullptr); + } + RELEASE_AND_RETURN(scope, result); + } +#if USE(BUN_JSC_ADDITIONS) + JSPromise* promise = CyclicModuleRecord::evaluate(globalObject, -1, instance); +#else + JSPromise* promise = CyclicModuleRecord::evaluate(globalObject, instance); +#endif + if (scope.exception()) [[unlikely]] { + JSPromise* rejected = JSPromise::create(vm, globalObject->promiseStructure()); + rejected->rejectWithCaughtException(vm, scope); + return rejected; + } + return promise; +} + +// ExecuteModule for a module graph instance: the record's body against its +// environment in the instance, with the instance's own execution state for a +// body with top-level await. +void JSModuleRecord::executeInstance(JSGlobalObject* globalObject, ModuleRecordInstance* recordInstance, JSPromise* capability) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + if (!hasTLA()) { + ASSERT(!capability); + vm.interpreter.executeModuleProgram(this, m_retainedExecutable.get(), globalObject, recordInstance->environment(), jsUndefined(), jsNumber(static_cast(ResumeMode::NormalMode))); + pinRetainedCodeBlock(vm); + RETURN_IF_EXCEPTION(scope, void()); + return; + } + ASSERT(capability); + recordInstance->setAsyncCapability(vm, capability); + JSValue result = evaluateInstance(globalObject, recordInstance, jsUndefined(), jsNumber(static_cast(ResumeMode::NormalMode))); + asyncModuleResolveEvaluation(globalObject, vm, scope, recordInstance, result); +} + +// One step of a top-level-await module body in an instance (first run or a +// resumption after an await). +JSValue JSModuleRecord::evaluateInstance(JSGlobalObject* globalObject, ModuleRecordInstance* recordInstance, JSValue sentValue, JSValue resumeMode) +{ + VM& vm = globalObject->vm(); + JSValue result = vm.interpreter.executeModuleProgram(this, recordInstance, m_retainedExecutable.get(), globalObject, recordInstance->environment(), sentValue, resumeMode); + pinRetainedCodeBlock(vm); + return result; +} + +void JSModuleRecord::pinRetainedCodeBlock(VM& vm) +{ + if (m_retainedCodeBlock || !m_retainedExecutable) + return; + if (ModuleProgramCodeBlock* codeBlock = m_retainedExecutable->codeBlock()) + m_retainedCodeBlock.set(vm, this, codeBlock); +} + ModuleProgramExecutable* JSModuleRecord::getOrMakeExecutable(JSGlobalObject* globalObject) { ModuleProgramExecutable* executable = m_moduleProgramExecutable.get(); diff --git a/Source/JavaScriptCore/runtime/JSModuleRecord.h b/Source/JavaScriptCore/runtime/JSModuleRecord.h index 6dc3030056bb..8e82e047cbf0 100644 --- a/Source/JavaScriptCore/runtime/JSModuleRecord.h +++ b/Source/JavaScriptCore/runtime/JSModuleRecord.h @@ -32,10 +32,19 @@ namespace JSC { +class ModuleGraphInstance; class ModuleProgramExecutable; +class ModuleRecordInstance; // Based on the Source Text Module Record // http://www.ecma-international.org/ecma-262/6.0/#sec-source-text-module-records +class JSMap; +class FunctionExecutable; +class JSPromise; +class InternalFieldTuple; +class CodeBlock; + + class JSModuleRecord final : public CyclicModuleRecord { friend class LLIntOffsetsExtractor; public: @@ -71,13 +80,42 @@ class JSModuleRecord final : public CyclicModuleRecord { ModuleProgramExecutable* getOrMakeExecutable(JSGlobalObject*); + // Module graph instances. Instantiate this module and, recursively, every + // source text module it depends on, a further time into `instance`, reusing + // this record's ModuleProgramExecutable, CodeBlock and function executables, + // then evaluate the instance with the module evaluation algorithm against + // the instance's state (CyclicModuleRecord::evaluate(..., instance)). + // The synchronous form requires the evaluation to complete synchronously + // (throws for top-level await) and returns this module's environment in the + // instance; the asynchronous form returns the evaluation promise. + JS_EXPORT_PRIVATE JSModuleEnvironment* instantiateIntoGraphInstance(JSGlobalObject*, ModuleGraphInstance*, ModulePhase = ModulePhase::Evaluation); + JS_EXPORT_PRIVATE JSPromise* instantiateIntoGraphInstanceAsync(JSGlobalObject*, ModuleGraphInstance*, ModulePhase = ModulePhase::Evaluation); + // ExecuteModule against this record's environment in `instance`. + void executeInstance(JSGlobalObject*, ModuleRecordInstance*, JSPromise* capability); + JSValue evaluateInstance(JSGlobalObject*, ModuleRecordInstance*, JSValue sentValue, JSValue resumeMode); + ModuleProgramExecutable* retainedExecutable() const { return m_retainedExecutable.get(); } + void pinRetainedCodeBlock(VM&); + // Once a record may be instantiated again it must keep its executable + // (normally dropped after evaluation) and its top-level function executables. + void retainForGraphInstances(VM&, ModuleProgramExecutable*, Vector>&&); + private: JSModuleRecord(VM&, Structure*, const Identifier&, const SourceCode&, CodeFeatures); void finishCreation(JSGlobalObject*, VM&); + JSModuleEnvironment* createInstanceEnvironment(JSGlobalObject*, ModuleGraphInstance*, Vector& created); + SourceCode m_sourceCode; WriteBarrier m_moduleProgramExecutable; + // Kept for graph instances: the executable past evaluation, and the linked + // executables of top-level function declarations (index = functionDecl(i)). + WriteBarrier m_retainedExecutable; + // The program CodeBlock whose constants own the nested FunctionExecutables + // every instance shares; held strongly so old-age jettison cannot re-link it + // (which would mint fresh executables for later instances). + WriteBarrier m_retainedCodeBlock; + Vector> m_functionDeclExecutables; CodeFeatures m_features; }; diff --git a/Source/JavaScriptCore/runtime/JSScope.cpp b/Source/JavaScriptCore/runtime/JSScope.cpp index 2963f80fa70e..3b2767c7ba84 100644 --- a/Source/JavaScriptCore/runtime/JSScope.cpp +++ b/Source/JavaScriptCore/runtime/JSScope.cpp @@ -97,6 +97,8 @@ static inline bool abstractAccess(JSGlobalObject* globalObject, JSScope* scope, SymbolTableEntry& entry = iter->value; ASSERT(!entry.isNull()); op = ResolveOp(makeType(ModuleVar, needsVarInjectionChecks), depth, nullptr, importedEnvironment, entry.watchpointSet(), entry.scopeOffset().offset(), resolution.localName.impl()); + if (auto slotIndex = moduleRecord->importSlotIndexFor(importedRecord)) + op.moduleImportSlot = 1 + JSModuleEnvironment::importSlotScopeOffset(moduleEnvironment->symbolTable(), *slotIndex).offset(); return true; } } diff --git a/Source/JavaScriptCore/runtime/ModuleGraphInstance.cpp b/Source/JavaScriptCore/runtime/ModuleGraphInstance.cpp new file mode 100644 index 000000000000..a8a696833017 --- /dev/null +++ b/Source/JavaScriptCore/runtime/ModuleGraphInstance.cpp @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2026 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "ModuleGraphInstance.h" + +#include "Error.h" +#include "JSCInlines.h" +#include "JSInternalFieldObjectImplInlines.h" +#include "JSModuleEnvironment.h" +#include "JSModuleNamespaceObject.h" +#include "JSPromise.h" +#include "ModuleGraphInstanceInlines.h" + +namespace JSC { + +const ClassInfo ModuleRecordInstance::s_info = { "ModuleRecordInstance"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(ModuleRecordInstance) }; +const ClassInfo ModuleGraphInstance::s_info = { "ModuleGraphInstance"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(ModuleGraphInstance) }; + +ModuleRecordInstance::ModuleRecordInstance(VM& vm, Structure* structure) + : Base(vm, structure) +{ +} + +void ModuleRecordInstance::destroy(JSCell* cell) +{ + SUPPRESS_MEMORY_UNSAFE_CAST auto* thisObject = static_cast(cell); + thisObject->~ModuleRecordInstance(); +} + +ModuleRecordInstance* ModuleRecordInstance::create(VM& vm, ModuleGraphInstance* graphInstance, AbstractModuleRecord* record, JSModuleEnvironment* environment) +{ + Structure* structure = vm.moduleRecordInstanceStructure.get(); + ModuleRecordInstance* instance = new (NotNull, allocateCell(vm)) ModuleRecordInstance(vm, structure); + instance->finishCreation(vm, graphInstance, record, environment); + return instance; +} + +void ModuleRecordInstance::finishCreation(VM& vm, ModuleGraphInstance* graphInstance, AbstractModuleRecord* record, JSModuleEnvironment* environment) +{ + Base::finishCreation(vm); + ASSERT(inherits(info())); + auto initialValues = AbstractModuleRecord::initialValues(); + for (unsigned index = 0; index < numberOfInternalFields; ++index) + internalField(static_cast(index)).set(vm, this, initialValues[index]); + m_graphInstance.set(vm, this, graphInstance); + m_record.set(vm, this, record); + m_environment.setMayBeNull(vm, this, environment); +} + +template +void ModuleRecordInstance::visitChildrenImpl(JSCell* cell, Visitor& visitor) +{ + auto* thisObject = uncheckedDowncast(cell); + ASSERT_GC_OBJECT_INHERITS(thisObject, info()); + Base::visitChildren(thisObject, visitor); + visitor.append(thisObject->m_record); + visitor.append(thisObject->m_graphInstance); + visitor.append(thisObject->m_environment); + visitor.append(thisObject->m_evaluationError); + visitor.append(thisObject->m_cycleRoot); + visitor.append(thisObject->m_topLevelCapability); + visitor.append(thisObject->m_asyncCapability); + visitor.append(thisObject->m_deferredNamespaceObject); + Locker locker { thisObject->cellLock() }; + visitor.append(thisObject->m_asyncParentModules.begin(), thisObject->m_asyncParentModules.end()); +} + +DEFINE_VISIT_CHILDREN(ModuleRecordInstance); + +void ModuleRecordInstance::appendAsyncParentModule(VM& vm, AbstractModuleRecord* record) +{ + Locker locker { cellLock() }; + m_asyncParentModules.append(WriteBarrier(vm, this, record)); +} + +bool ModuleRecordInstance::isExecutionFinished() const +{ + JSValue state = internalField(Field::State).get(); + return !state.isNumber() || state.asInt32AsAnyInt() == std::to_underlying(AbstractModuleRecord::State::Executing); +} + +void ModuleRecordInstance::setDeferredNamespaceObject(VM& vm, JSModuleNamespaceObject* namespaceObject) +{ + m_deferredNamespaceObject.setMayBeNull(vm, this, namespaceObject); +} + +ModuleGraphInstance::ModuleGraphInstance(VM& vm, Structure* structure) + : Base(vm, structure) +{ +} + +ModuleGraphInstance* ModuleGraphInstance::create(VM& vm, JSGlobalObject* globalObject, JSScope* parentScope) +{ + Structure* structure = globalObject->moduleGraphInstanceStructure(); + ModuleGraphInstance* instance = new (NotNull, allocateCell(vm)) ModuleGraphInstance(vm, structure); + instance->finishCreation(vm, parentScope); + return instance; +} + +void ModuleGraphInstance::finishCreation(VM& vm, JSScope* parentScope) +{ + Base::finishCreation(vm); + ASSERT(inherits(info())); + m_parentScope.setMayBeNull(vm, this, parentScope); +} + +template +void ModuleGraphInstance::visitChildrenImpl(JSCell* cell, Visitor& visitor) +{ + auto* thisObject = uncheckedDowncast(cell); + ASSERT_GC_OBJECT_INHERITS(thisObject, info()); + Base::visitChildren(thisObject, visitor); + visitor.append(thisObject->m_parentScope); + Locker locker { thisObject->cellLock() }; + for (auto& [record, instance] : thisObject->m_records) + visitor.append(instance); +} + +DEFINE_VISIT_CHILDREN(ModuleGraphInstance); + +ModuleRecordInstance* ModuleGraphInstance::recordInstance(AbstractModuleRecord* record) const +{ + auto iterator = m_records.find(record); + return iterator == m_records.end() ? nullptr : iterator->value.get(); +} + +ModuleRecordInstance* ModuleGraphInstance::add(VM& vm, AbstractModuleRecord* record, JSModuleEnvironment* environment) +{ + ASSERT(!m_records.contains(record)); + ASSERT(!m_cleared); // callers check isCleared() and throw first + ModuleRecordInstance* instance = ModuleRecordInstance::create(vm, this, record, environment); + Locker locker { cellLock() }; + m_records.add(record, WriteBarrier(vm, this, instance)); + return instance; +} + +bool ModuleGraphInstance::remove(AbstractModuleRecord* record) +{ + Locker locker { cellLock() }; + return m_records.remove(record); +} + +void ModuleGraphInstance::clear(JSGlobalObject* globalObject) +{ + if (m_busy) { + // An evaluation step is running against this instance (its module code + // asked for the disposal): finish that step coherently, clear after it. + m_clearPending = true; + return; + } + m_clearPending = false; + VM& vm = globalObject->vm(); + Vector pending; + { + Locker locker { cellLock() }; + m_cleared = true; + for (auto& entry : m_records) { + JSPromise* capability = entry.value->topLevelCapability(); + if (capability && capability->status() == JSPromise::Status::Pending) + pending.append(capability); + } + m_records.clear(); + } + if (pending.isEmpty()) + return; + JSObject* error = createTypeError(globalObject, "Module graph instance was disposed during evaluation"_s); + for (JSPromise* capability : pending) + capability->reject(vm, JSValue(error)); +} + +} // namespace JSC diff --git a/Source/JavaScriptCore/runtime/ModuleGraphInstance.h b/Source/JavaScriptCore/runtime/ModuleGraphInstance.h new file mode 100644 index 000000000000..a89875b08920 --- /dev/null +++ b/Source/JavaScriptCore/runtime/ModuleGraphInstance.h @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2026 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include "AbstractModuleRecord.h" +#include "CyclicModuleRecord.h" +#include "JSInternalFieldObjectImpl.h" +#include "JSDestructibleObject.h" +#include + +namespace JSC { + +class JSModuleEnvironment; +class JSModuleNamespaceObject; +class JSPromise; +class JSScope; +class ModuleGraphInstance; + +// The state of one module record within one ModuleGraphInstance: its module +// environment there and the record's evaluation state for that instance (the +// fields the module evaluation algorithm keeps on a Cyclic Module Record). Also +// the context object of that instance's asynchronous evaluation steps. +// It is also the generator object of the instance's module body (internal +// fields State and Frame, as on AbstractModuleRecord) and therefore the driver +// a top-level `for await` in that body resumes. +class ModuleRecordInstance final : public JSInternalFieldObjectImpl<2> { +public: + using Base = JSInternalFieldObjectImpl<2>; + static constexpr unsigned StructureFlags = Base::StructureFlags | StructureIsImmortal; + using Field = AbstractModuleRecord::Field; + static_assert(numberOfInternalFields == AbstractModuleRecord::numberOfInternalFields); + WriteBarrier& internalField(Field field) { return Base::internalField(static_cast(field)); } + const WriteBarrier& internalField(Field field) const { return Base::internalField(static_cast(field)); } + static constexpr DestructionMode needsDestruction = NeedsDestruction; + static void destroy(JSCell*); + + DECLARE_EXPORT_INFO; + DECLARE_VISIT_CHILDREN; + + template + static GCClient::IsoSubspace* subspaceFor(VM& vm) + { + return vm.moduleRecordInstanceSpace(); + } + + inline static Structure* createStructure(VM&, JSGlobalObject*, JSValue); + static ModuleRecordInstance* create(VM&, ModuleGraphInstance*, AbstractModuleRecord*, JSModuleEnvironment*); + + AbstractModuleRecord* record() const { return m_record.get(); } + ModuleGraphInstance* graphInstance() const { return m_graphInstance.get(); } + JSModuleEnvironment* environment() const { return m_environment.get(); } + + CyclicModuleRecord::Status status() const { return m_status; } + void setStatus(CyclicModuleRecord::Status status) { m_status = status; } + JSValue evaluationError() const { return m_evaluationError.get(); } + void setEvaluationError(VM& vm, JSValue error) { m_evaluationError.set(vm, this, error); } + unsigned dfsAncestorIndex() const { return m_dfsAncestorIndex; } + void setDFSAncestorIndex(unsigned index) { m_dfsAncestorIndex = index; } + CyclicModuleRecord* cycleRoot() const { return m_cycleRoot.get(); } + void setCycleRoot(VM& vm, CyclicModuleRecord* root) { m_cycleRoot.setMayBeNull(vm, this, root); } + AbstractModuleRecord::AsyncEvaluationOrder asyncEvaluationOrder() const { return m_asyncEvaluationOrder; } + void setAsyncEvaluationOrder(AbstractModuleRecord::AsyncEvaluationOrder order) { m_asyncEvaluationOrder = order; } + std::optional pendingAsyncDependencies() const { return m_pendingAsyncDependencies; } + void setPendingAsyncDependencies(std::optional value) { m_pendingAsyncDependencies = value; } + const Vector>& asyncParentModules() const LIFETIME_BOUND { return m_asyncParentModules; } + void appendAsyncParentModule(VM&, AbstractModuleRecord*); + JSPromise* topLevelCapability() const { return m_topLevelCapability.get(); } + void setTopLevelCapability(VM& vm, JSPromise* capability) { m_topLevelCapability.setMayBeNull(vm, this, capability); } + JSPromise* asyncCapability() const { return m_asyncCapability.get(); } + void setAsyncCapability(VM& vm, JSPromise* capability) { m_asyncCapability.setMayBeNull(vm, this, capability); } + // Generator state of a module body with top-level await (Field::State). + bool isExecutionFinished() const; + JSModuleNamespaceObject* deferredNamespaceObject() const { return m_deferredNamespaceObject.get(); } + void setDeferredNamespaceObject(VM&, JSModuleNamespaceObject*); + +private: + ModuleRecordInstance(VM&, Structure*); + void finishCreation(VM&, ModuleGraphInstance*, AbstractModuleRecord*, JSModuleEnvironment*); + + WriteBarrier m_record; + WriteBarrier m_graphInstance; + WriteBarrier m_environment; + WriteBarrier m_evaluationError; + WriteBarrier m_cycleRoot; + WriteBarrier m_topLevelCapability; + WriteBarrier m_asyncCapability; + WriteBarrier m_deferredNamespaceObject; + Vector> m_asyncParentModules; + AbstractModuleRecord::AsyncEvaluationOrder m_asyncEvaluationOrder { }; + std::optional m_pendingAsyncDependencies; + unsigned m_dfsAncestorIndex { 0 }; + CyclicModuleRecord::Status m_status { CyclicModuleRecord::Status::Linked }; +}; + +// One instantiation of a module graph in a global object beyond the primary +// one: maps each module record instantiated for it to its ModuleRecordInstance +// (environment + evaluation state). Records not in the map are shared with the +// primary graph (their own environment and state apply). +class ModuleGraphInstance final : public JSDestructibleObject { +public: + using Base = JSDestructibleObject; + static constexpr unsigned StructureFlags = Base::StructureFlags; + + DECLARE_EXPORT_INFO; + DECLARE_VISIT_CHILDREN; + + template + static GCClient::IsoSubspace* subspaceFor(VM& vm) + { + return vm.moduleGraphInstanceSpace(); + } + + inline static Structure* createStructure(VM&, JSGlobalObject*, JSValue); + JS_EXPORT_PRIVATE static ModuleGraphInstance* create(VM&, JSGlobalObject*, JSScope* parentScope); + + // The scope module environments of this instance are created under: an + // embedder-provided scope (e.g. a scope overlay) or the global object's + // module environment parent scope. + JSScope* parentScope() const { return m_parentScope.get(); } + void setParentScope(VM& vm, JSScope* scope) { m_parentScope.setMayBeNull(vm, this, scope); } + + ModuleRecordInstance* recordInstance(AbstractModuleRecord*) const; + JSModuleEnvironment* environment(AbstractModuleRecord* record) const + { + ModuleRecordInstance* instance = recordInstance(record); + return instance ? instance->environment() : nullptr; + } + ModuleRecordInstance* add(VM&, AbstractModuleRecord*, JSModuleEnvironment*); + bool remove(AbstractModuleRecord*); + // Drops every record's environment and state (the embedder is done with the + // instance; code of the instance that still runs keeps what it closes over). + // Releases every record's state; pending top-level evaluation promises of + // this instance are rejected and later asynchronous completions of its + // modules are dropped. + JS_EXPORT_PRIVATE void clear(JSGlobalObject*); + bool isCleared() const { return m_cleared; } + // Disposed by its owner: cleared, or to be cleared as soon as the evaluation + // step currently running against it returns (see BusyScope). + bool isDisposed() const { return m_cleared || m_clearPending; } + + // Brackets an evaluation step that runs against this instance (Evaluate(), + // an asynchronous completion or resumption); a clear() requested meanwhile + // is performed when the outermost step returns. + class BusyScope { + public: + BusyScope(JSGlobalObject* globalObject, ModuleGraphInstance* instance) + : m_globalObject(globalObject), m_instance(instance) + { + if (m_instance) + ++m_instance->m_busy; + } + ~BusyScope() + { + if (m_instance && !--m_instance->m_busy && m_instance->m_clearPending) + m_instance->clear(m_globalObject); + } + private: + JSGlobalObject* m_globalObject; + ModuleGraphInstance* m_instance; + }; + template void forEachRecord(const Functor&) const; + +private: + ModuleGraphInstance(VM&, Structure*); + void finishCreation(VM&, JSScope* parentScope); + + WriteBarrier m_parentScope; + UncheckedKeyHashMap> m_records; + bool m_cleared { false }; + bool m_clearPending { false }; + unsigned m_busy { 0 }; +}; + +template +void ModuleGraphInstance::forEachRecord(const Functor& functor) const +{ + for (auto& [record, instance] : m_records) + functor(*record, *instance.get()); +} + +} // namespace JSC diff --git a/Source/JavaScriptCore/runtime/ModuleGraphInstanceInlines.h b/Source/JavaScriptCore/runtime/ModuleGraphInstanceInlines.h new file mode 100644 index 000000000000..6fcf7fb2b2d0 --- /dev/null +++ b/Source/JavaScriptCore/runtime/ModuleGraphInstanceInlines.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2026 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include "ModuleGraphInstance.h" +#include "StructureCreateInlines.h" + +namespace JSC { + +Structure* ModuleRecordInstance::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) +{ + return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info()); +} + +Structure* ModuleGraphInstance::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) +{ + return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info()); +} + +} // namespace JSC diff --git a/Source/JavaScriptCore/runtime/ModuleRegistryEntry.cpp b/Source/JavaScriptCore/runtime/ModuleRegistryEntry.cpp index 9cdf88a96178..6a950a6585fc 100644 --- a/Source/JavaScriptCore/runtime/ModuleRegistryEntry.cpp +++ b/Source/JavaScriptCore/runtime/ModuleRegistryEntry.cpp @@ -169,6 +169,16 @@ auto ModuleRegistryEntry::status() const -> Status return m_status; } +bool ModuleRegistryEntry::hasSettledFailure() const +{ + if (m_status == Status::FetchFailed || m_status == Status::InstantiationFailed) + return true; + if (m_record) + return false; + auto rejected = [](JSPromise* promise) { return promise && promise->status() == JSPromise::Status::Rejected; }; + return rejected(m_fetchPromise.get()) || rejected(m_modulePromise.get()) || rejected(m_loadPromise.get()); +} + void ModuleRegistryEntry::setRecord(VM& vm, AbstractModuleRecord* record) { m_record.set(vm, this, record); diff --git a/Source/JavaScriptCore/runtime/ModuleRegistryEntry.h b/Source/JavaScriptCore/runtime/ModuleRegistryEntry.h index 05c78dddebde..f74f04c0f552 100644 --- a/Source/JavaScriptCore/runtime/ModuleRegistryEntry.h +++ b/Source/JavaScriptCore/runtime/ModuleRegistryEntry.h @@ -76,6 +76,9 @@ class ModuleRegistryEntry final : public JSCell { JSValue error(JSGlobalObject*) const; JSValue fetchError() const; Status status() const; + // A finished, failed load: fetch or record creation / instantiation failed and + // nothing is in flight (an entry another program run may retry from scratch). + bool hasSettledFailure() const; void setRecord(VM&, AbstractModuleRecord*); void setLoadPromise(VM&, JSPromise*); diff --git a/Source/JavaScriptCore/runtime/OptionsList.h b/Source/JavaScriptCore/runtime/OptionsList.h index bb22bd7a8b5c..2795332849ec 100644 --- a/Source/JavaScriptCore/runtime/OptionsList.h +++ b/Source/JavaScriptCore/runtime/OptionsList.h @@ -86,6 +86,7 @@ bool hasCapacityToUseLargeGigacage(); v(Bool, verboseFFI, false, Normal, "dataLog on FFI thunk/stub/signature creation"_s) #define FOR_EACH_JSC_CODEBLOCK_AGING_OPTION(v) \ v(Bool, useExecutionCountForCodeBlockAging, false, Normal, "If true, an LLInt/Baseline CodeBlock whose execution counter has advanced since the last old-age check is treated as still in use and its TTL is renewed instead of being jettisoned."_s) \ + v(Bool, useModuleGraphInstances, false, Normal, "Prototype: keep module executables so a module graph can be instantiated again in the same global (JSModuleRecord::instantiateIntoGraphInstance)."_s) \ v(Double, codeBlockAgingLeaseMultiplier, 3.0, Normal, "When useExecutionCountForCodeBlockAging proves a CodeBlock is still active, renew its old-age TTL to this many multiples of timeToLive for its tier."_s) #define FOR_EACH_JSC_BYTECODE_CACHE_DECODER_OPTION(v) \ v(Bool, useLeanBytecodeCacheDecoder, true, Normal, "If true, the bytecode cache Decoder skips bookkeeping that is only needed for decoded objects shared by multiple references."_s) \ diff --git a/Source/JavaScriptCore/runtime/SyntheticModuleRecord.cpp b/Source/JavaScriptCore/runtime/SyntheticModuleRecord.cpp index 79fec402d118..babf12dbf8a3 100644 --- a/Source/JavaScriptCore/runtime/SyntheticModuleRecord.cpp +++ b/Source/JavaScriptCore/runtime/SyntheticModuleRecord.cpp @@ -25,6 +25,11 @@ #include "config.h" #include "SyntheticModuleRecord.h" +#include "SourceProvider.h" +#include "StructureInlines.h" +#include "ArrayConstructor.h" +#include "ObjectConstructor.h" +#include "JSArray.h" #include "ArgList.h" #include "BuiltinNames.h" @@ -85,8 +90,14 @@ Synchronousness SyntheticModuleRecord::link(JSGlobalObject*, RefPtrvm(); + auto scope = DECLARE_THROW_SCOPE(vm); + // A record first produced for a module graph instance gives the primary its + // own values when the primary graph evaluates it. + materializePrimaryIfPending(globalObject); + RETURN_IF_EXCEPTION(scope, { }); return jsUndefined(); } @@ -174,6 +185,9 @@ void SyntheticModuleRecord::materializeLazyExport(JSGlobalObject* globalObject, if (localName == vm.propertyNames->starNamespacePrivateName) return; + if (m_primaryPending) + RELEASE_AND_RETURN(scope, materializePrimaryIfPending(globalObject)); + JSModuleEnvironment* environment = moduleEnvironment(); SymbolTable* symbolTable = environment->symbolTable(); ScopeOffset scopeOffset; @@ -236,7 +250,251 @@ SyntheticModuleRecord* SyntheticModuleRecord::parseJSONModule(JSGlobalObject* gl JSValue result = JSONParseWithException(globalObject, sourceCode.view()); RETURN_IF_EXCEPTION(scope, { }); - RELEASE_AND_RETURN(scope, SyntheticModuleRecord::tryCreateDefaultExportSyntheticModule(globalObject, moduleKey, result, SourceProviderSourceType::JSON)); + SyntheticModuleRecord* record = SyntheticModuleRecord::tryCreateDefaultExportSyntheticModule(globalObject, moduleKey, result, SourceProviderSourceType::JSON); + RETURN_IF_EXCEPTION(scope, { }); + if (record && Options::useModuleGraphInstances()) + record->m_jsonSource = WTF::move(sourceCode); + RELEASE_AND_RETURN(scope, record); +} + +// Plain data = null/undefined/booleans/numbers/strings/bigints, arrays of plain +// data, and ordinary objects (Object.prototype or null prototype, data +// properties only) of plain data. Bounded so pathological modules count as "no". +static bool isPlainData(JSGlobalObject* globalObject, JSValue value, unsigned depth, unsigned& budget) +{ + if (!value || !budget--) + return false; + if (!value.isCell() || value.isString() || value.isBigInt() || value.isSymbol()) + return !value.isSymbol(); + if (depth > 64) + return false; + JSObject* object = value.getObject(); + if (!object || object->type() == JSFunctionType) + return false; + VM& vm = globalObject->vm(); + if (isJSArray(object)) { + if (object->type() != ArrayType || object->getPrototypeDirect() != globalObject->arrayPrototype()) + return false; + JSArray* array = uncheckedDowncast(object); + for (unsigned i = 0; i < array->length(); ++i) { + JSValue element = array->canGetIndexQuickly(i) ? array->getIndexQuickly(i) : JSValue(); + if (!element) + return false; + if (!isPlainData(globalObject, element, depth + 1, budget)) + return false; + } + return true; + } + if ((object->type() != FinalObjectType && object->type() != ObjectType) || object->inlineTypeFlags() & OverridesGetOwnPropertySlot) { + dataLogLnIf(Options::dumpModuleLoadingState(), "[graph-instance] not plain: type=", object->type()); + return false; + } + JSValue prototype = object->getPrototypeDirect(); + if (!prototype.isNull() && prototype != globalObject->objectPrototype()) { + dataLogLnIf(Options::dumpModuleLoadingState(), "[graph-instance] not plain: prototype"); + return false; + } + Structure* structure = object->structure(); + if (structure->hasAnyKindOfGetterSetterProperties() || structure->isUncacheableDictionary() || hasIndexedProperties(object->indexingType())) { + dataLogLnIf(Options::dumpModuleLoadingState(), "[graph-instance] not plain: getters=", structure->hasAnyKindOfGetterSetterProperties(), " uncacheableDict=", structure->isUncacheableDictionary(), " indexed=", hasIndexedProperties(object->indexingType())); + return false; + } + bool ok = true; + structure->forEachProperty(vm, [&](const PropertyTableEntry& entry) -> bool { + if (entry.attributes() & PropertyAttribute::Accessor) { + ok = false; + return false; + } + if (!isPlainData(globalObject, object->getDirect(entry.offset()), depth + 1, budget)) { + ok = false; + return false; + } + return true; + }); + return ok; +} + +static JSValue clonePlainData(JSGlobalObject* globalObject, JSValue value) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + if (!value.isCell() || value.isString() || value.isBigInt()) + return value; + JSObject* object = value.getObject(); + if (isJSArray(object)) { + JSArray* source = uncheckedDowncast(object); + MarkedArgumentBuffer elements; + for (unsigned i = 0; i < source->length(); ++i) { + JSValue element = clonePlainData(globalObject, source->canGetIndexQuickly(i) ? source->getIndexQuickly(i) : jsUndefined()); + RETURN_IF_EXCEPTION(scope, { }); + elements.append(element); + } + RELEASE_AND_RETURN(scope, constructArray(globalObject, static_cast(nullptr), elements)); + } + JSValue prototype = object->getPrototypeDirect(); + JSObject* copy = prototype.isNull() ? constructEmptyObject(vm, globalObject->nullPrototypeObjectStructure()) : constructEmptyObject(globalObject); + RETURN_IF_EXCEPTION(scope, { }); + Vector, 8> properties; + object->structure()->forEachProperty(vm, [&](const PropertyTableEntry& entry) -> bool { + properties.append({ entry.key(), entry.offset() }); + return true; + }); + for (auto& [name, offset] : properties) { + JSValue cloned = clonePlainData(globalObject, object->getDirect(offset)); + RETURN_IF_EXCEPTION(scope, { }); + copy->putDirect(vm, name, cloned); + } + return copy; +} + +void SyntheticModuleRecord::materializePrimaryIfPending(JSGlobalObject* globalObject) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + // While a graph is loading, the primary is nobody's business yet. + if (!m_primaryPending || !m_provider || globalObject->currentGraphInstanceForLoading()) + return; + m_primaryPending = false; + MarkedArgumentBuffer values; + Vector names; + m_provider->generate(globalObject, moduleKey(), names, values); + RETURN_IF_EXCEPTION(scope, void()); + JSModuleEnvironment* environment = moduleEnvironment(); + SymbolTable* symbolTable = environment->symbolTable(); + for (const auto& [key, entry] : exportEntries()) { + SymbolTableEntry::Fast symbolEntry = symbolTable->get(entry.localName.impl()); + if (symbolEntry.isNull()) + continue; + JSValue value = jsUndefined(); + for (unsigned i = 0; i < names.size(); ++i) { + if (names[i] == entry.localName) { + value = values.at(i); + break; + } + } + environment->variableAt(symbolEntry.scopeOffset()).set(vm, environment, value ? value : jsUndefined()); + } +} + +bool SyntheticModuleRecord::hasPerGraphInstanceState() +{ + if (!m_jsonSource.isNull()) + return true; + if (m_provider && m_provider->regeneratesPerGraphInstance()) + return true; + if (m_primaryPending) + return false; + if (m_plainDataState != PlainDataState::Unknown) + return m_plainDataState == PlainDataState::Yes; + m_plainDataState = PlainDataState::No; +#if USE(BUN_JSC_ADDITIONS) + if (hasLazyExports()) + return false; +#endif + JSModuleEnvironment* environment = moduleEnvironmentMayBeNull(); + if (!environment || exportEntries().isEmpty()) + return false; + JSGlobalObject* globalObject = environment->globalObject(); + unsigned budget = 100000; + for (const auto& [key, entry] : exportEntries()) { + SymbolTableEntry::Fast symbolEntry = environment->symbolTable()->get(entry.localName.impl()); + if (symbolEntry.isNull()) { + dataLogLnIf(Options::dumpModuleLoadingState(), "[graph-instance] synthetic ", moduleKey().string(), ": export ", entry.localName.string(), " has no slot"); + return false; + } + JSValue value = environment->variableAt(symbolEntry.scopeOffset()).get(); + if (!isPlainData(globalObject, value, 0, budget)) { + dataLogLnIf(Options::dumpModuleLoadingState(), "[graph-instance] synthetic ", moduleKey().string(), ": export ", entry.localName.string(), " is not plain data"); + return false; + } + } + m_plainDataState = PlainDataState::Yes; + return true; +} + +JSModuleEnvironment* SyntheticModuleRecord::createGraphInstanceEnvironment(JSGlobalObject* globalObject) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + ASSERT(hasPerGraphInstanceState()); + JSModuleEnvironment* primary = moduleEnvironment(); + JSModuleEnvironment* environment = JSModuleEnvironment::create(vm, globalObject, nullptr, primary->symbolTable(), jsTDZValue(), this); + RETURN_IF_EXCEPTION(scope, nullptr); + if (!m_jsonSource.isNull()) { + JSValue value = JSONParseWithException(globalObject, m_jsonSource.view()); + RETURN_IF_EXCEPTION(scope, nullptr); + bool putResult = false; + symbolTablePutTouchWatchpointSet(environment, globalObject, vm.propertyNames->defaultKeyword, value, false, true, putResult); + RETURN_IF_EXCEPTION(scope, nullptr); + return environment; + } + if (m_provider && m_provider->regeneratesPerGraphInstance()) { + // The host produces this graph's values (the caller has set the graph as + // the current loading instance); names not produced stay undefined. + MarkedArgumentBuffer values; + Vector names; + m_provider->generate(globalObject, moduleKey(), names, values); + RETURN_IF_EXCEPTION(scope, nullptr); + SymbolTable* symbolTable = primary->symbolTable(); + for (const auto& [key, entry] : exportEntries()) { + SymbolTableEntry::Fast symbolEntry = symbolTable->get(entry.localName.impl()); + JSValue value = jsUndefined(); + for (unsigned i = 0; i < names.size(); ++i) { + if (names[i] == entry.localName) { + value = values.at(i); + break; + } + } + environment->variableAt(symbolEntry.scopeOffset()).set(vm, environment, value ? value : jsUndefined()); + } + return environment; + } + // Deep-copy every export. `default` and named exports of a data module are + // usually the same object graph (named = default's properties); clone + // `default` once and re-derive the named exports from the copy when they + // alias, so the aliasing survives. The primary's values may have been + // mutated by script since they were judged plain data: re-check right here + // (no script runs between this check and the copy) and hand this instance + // the primary's values unchanged if they no longer qualify. + SymbolTable* symbolTable = primary->symbolTable(); + { + unsigned budget = 100000; + bool stillPlainData = true; + for (const auto& [key, entry] : exportEntries()) { + SymbolTableEntry::Fast symbolEntry = symbolTable->get(entry.localName.impl()); + if (symbolEntry.isNull() || !isPlainData(globalObject, primary->variableAt(symbolEntry.scopeOffset()).get(), 0, budget)) { + stillPlainData = false; + break; + } + } + if (!stillPlainData) { + for (const auto& [key, entry] : exportEntries()) { + SymbolTableEntry::Fast symbolEntry = symbolTable->get(entry.localName.impl()); + if (!symbolEntry.isNull()) + environment->variableAt(symbolEntry.scopeOffset()).set(vm, environment, primary->variableAt(symbolEntry.scopeOffset()).get()); + } + return environment; + } + } + SymbolTableEntry::Fast defaultEntry = symbolTable->get(vm.propertyNames->defaultKeyword.impl()); + JSValue defaultOriginal = defaultEntry.isNull() ? JSValue() : primary->variableAt(defaultEntry.scopeOffset()).get(); + JSValue defaultCopy = defaultOriginal ? clonePlainData(globalObject, defaultOriginal) : JSValue(); + RETURN_IF_EXCEPTION(scope, nullptr); + for (const auto& [key, entry] : exportEntries()) { + SymbolTableEntry::Fast symbolEntry = symbolTable->get(entry.localName.impl()); + JSValue original = primary->variableAt(symbolEntry.scopeOffset()).get(); + JSValue copy; + if (entry.localName == vm.propertyNames->defaultKeyword) + copy = defaultCopy; + else if (defaultOriginal && defaultOriginal.isObject() && defaultCopy.isObject()) { + JSValue aliased = defaultOriginal.getObject()->getDirect(vm, entry.localName); + copy = aliased == original ? defaultCopy.getObject()->getDirect(vm, entry.localName) : clonePlainData(globalObject, original); + } else + copy = clonePlainData(globalObject, original); + RETURN_IF_EXCEPTION(scope, nullptr); + environment->variableAt(symbolEntry.scopeOffset()).set(vm, environment, copy); + } + return environment; } SyntheticModuleRecord* SyntheticModuleRecord::createTextModule(JSGlobalObject* globalObject, const Identifier& moduleKey, SourceCode&& sourceCode) diff --git a/Source/JavaScriptCore/runtime/SyntheticModuleRecord.h b/Source/JavaScriptCore/runtime/SyntheticModuleRecord.h index beede61817b9..553cd37b224b 100644 --- a/Source/JavaScriptCore/runtime/SyntheticModuleRecord.h +++ b/Source/JavaScriptCore/runtime/SyntheticModuleRecord.h @@ -31,6 +31,8 @@ namespace JSC { +class SyntheticSourceProvider; + class JSGlobalObject; // https://tc39.es/proposal-json-modules/#sec-synthetic-module-records @@ -56,6 +58,22 @@ class SyntheticModuleRecord final : public AbstractModuleRecord { static SyntheticModuleRecord* create(JSGlobalObject*, VM&, Structure*, const Identifier& moduleKey, SourceProviderSourceType); static SyntheticModuleRecord* parseJSONModule(JSGlobalObject*, const Identifier& moduleKey, SourceCode&&); + // Module graph instances: JSON modules carry mutable state (their parsed + // value), so each graph gets its own environment with a fresh parse. + // True for data modules: a JSON source to re-parse, or exports that are all + // plain data (primitives / arrays / plain objects), deep-copied per graph. + // Native/builtin modules (functions, host objects, lazy exports) are shared. + bool hasPerGraphInstanceState(); + JSModuleEnvironment* createGraphInstanceEnvironment(JSGlobalObject*); + // Host synthetic modules with per-graph state of their own (a CommonJS + // module behind an ESM import): the provider regenerates per graph, and if + // the record was first created for a graph, the primary bindings stay lazy + // until the primary graph links to them (then the provider runs for it). + void setSyntheticSourceProvider(RefPtr&& provider, bool primaryPending) { m_provider = WTF::move(provider); m_primaryPending = primaryPending; } + // The primary environment's values are produced on first use by the primary graph. + bool primaryPending() const { return m_primaryPending; } + SyntheticSourceProvider* syntheticSourceProvider() const { return m_provider.get(); } + void materializePrimaryIfPending(JSGlobalObject*); static SyntheticModuleRecord* createTextModule(JSGlobalObject*, const Identifier& moduleKey, SourceCode&&); Synchronousness link(JSGlobalObject*, RefPtr = nullptr); @@ -92,6 +110,11 @@ class SyntheticModuleRecord final : public AbstractModuleRecord { #if USE(BUN_JSC_ADDITIONS) WriteBarrier m_lazyExportsSource; + SourceCode m_jsonSource; + RefPtr m_provider; + bool m_primaryPending { false }; + enum class PlainDataState : uint8_t { Unknown, Yes, No }; + PlainDataState m_plainDataState { PlainDataState::Unknown }; #endif }; diff --git a/Source/JavaScriptCore/runtime/VM.cpp b/Source/JavaScriptCore/runtime/VM.cpp index 9252010d8989..35a14da65361 100644 --- a/Source/JavaScriptCore/runtime/VM.cpp +++ b/Source/JavaScriptCore/runtime/VM.cpp @@ -103,6 +103,7 @@ #include "ModuleLoaderPayloadInlines.h" #include "ModuleProgramCodeBlockInlines.h" #include "ModuleProgramExecutableInlines.h" +#include "ModuleGraphInstanceInlines.h" #include "ModuleRegistryEntryInlines.h" #include "NarrowingNumberPredictionFuzzerAgent.h" #include "NativeExecutable.h" @@ -365,6 +366,7 @@ VM::VM(VMType vmType, HeapType heapType, WTF::RunLoop* runLoop, bool* success) moduleLoaderStructure.setWithoutWriteBarrier(JSModuleLoader::createStructure(*this, nullptr, jsNull())); moduleRegistryEntryStructure.setWithoutWriteBarrier(ModuleRegistryEntry::createStructure(*this, nullptr, jsNull())); moduleLoadingContextStructure.setWithoutWriteBarrier(ModuleLoadingContext::createStructure(*this, nullptr, jsNull())); + moduleRecordInstanceStructure.setWithoutWriteBarrier(ModuleRecordInstance::createStructure(*this, nullptr, jsNull())); moduleLoaderPayloadStructure.setWithoutWriteBarrier(ModuleLoaderPayload::createStructure(*this, nullptr, jsNull())); moduleGraphLoadingStateStructure.setWithoutWriteBarrier(ModuleGraphLoadingState::createStructure(*this, nullptr, jsNull())); promiseCombinatorsContextStructure.setWithoutWriteBarrier(JSPromiseCombinatorsContext::createStructure(*this, nullptr, jsNull())); @@ -2043,6 +2045,7 @@ void VM::visitAggregateImpl(Visitor& visitor) visitor.append(moduleLoaderStructure); visitor.append(moduleRegistryEntryStructure); visitor.append(moduleLoadingContextStructure); + visitor.append(moduleRecordInstanceStructure); visitor.append(moduleLoaderPayloadStructure); visitor.append(moduleGraphLoadingStateStructure); visitor.append(promiseCombinatorsContextStructure); diff --git a/Source/JavaScriptCore/runtime/VM.h b/Source/JavaScriptCore/runtime/VM.h index 2577c4b2c26e..99ce58fc3bda 100644 --- a/Source/JavaScriptCore/runtime/VM.h +++ b/Source/JavaScriptCore/runtime/VM.h @@ -553,6 +553,7 @@ class VM : public ThreadSafeRefCountedWithSuppressingSaferCPPChecking { WriteBarrier moduleLoaderStructure; WriteBarrier moduleRegistryEntryStructure; WriteBarrier moduleLoadingContextStructure; + WriteBarrier moduleRecordInstanceStructure; WriteBarrier moduleLoaderPayloadStructure; WriteBarrier moduleGraphLoadingStateStructure; WriteBarrier promiseCombinatorsContextStructure; diff --git a/Source/JavaScriptCore/tools/JSDollarVM.cpp b/Source/JavaScriptCore/tools/JSDollarVM.cpp index 07f44dc33940..550366f790d1 100644 --- a/Source/JavaScriptCore/tools/JSDollarVM.cpp +++ b/Source/JavaScriptCore/tools/JSDollarVM.cpp @@ -54,6 +54,11 @@ #include "JSCInlines.h" #include "JSGlobalProxyInlines.h" #include "JSONObject.h" +#include "JSMapInlines.h" +#include "JSModuleEnvironment.h" +#include "JSModuleRecord.h" +#include "ModuleGraphInstance.h" +#include "JSModuleNamespaceObject.h" #include "JSPromise.h" #include "JSString.h" #include "LinkBuffer.h" @@ -2240,6 +2245,7 @@ static JSC_DECLARE_HOST_FUNCTION(functionEnableDebuggerModeWhenIdle); static JSC_DECLARE_HOST_FUNCTION(functionDisableDebuggerModeWhenIdle); static JSC_DECLARE_HOST_FUNCTION(functionDeleteAllCodeWhenIdle); static JSC_DECLARE_HOST_FUNCTION(functionGlobalObjectCount); +static JSC_DECLARE_HOST_FUNCTION(functionInstantiateModuleGraph); static JSC_DECLARE_HOST_FUNCTION(functionGlobalObjectForObject); static JSC_DECLARE_HOST_FUNCTION(functionGetGetterSetter); static JSC_DECLARE_HOST_FUNCTION(functionLoadGetterFromGetterSetter); @@ -3909,6 +3915,37 @@ JSC_DEFINE_HOST_FUNCTION(functionDeleteAllCodeWhenIdle, (JSGlobalObject* globalO return JSValue::encode(jsUndefined()); } +// $vm.instantiateModuleGraph(moduleNamespaceObject[, instance]) +// Instantiates (and evaluates) the namespace's module and its source text +// dependencies a further time into `instance` (a ModuleGraphInstance; a new one +// if omitted) and returns { namespace, environment, instance }: the module's +// namespace object and environment in that instance. +JSC_DEFINE_HOST_FUNCTION(functionInstantiateModuleGraph, (JSGlobalObject* globalObject, CallFrame* callFrame)) +{ + DollarVMAssertScope assertScope; + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + auto* ns = dynamicDowncast(callFrame->argument(0)); + if (!ns) + return throwVMTypeError(globalObject, scope, "expected a module namespace object"_s); + auto* record = dynamicDowncast(ns->moduleRecord()); + if (!record) + return throwVMTypeError(globalObject, scope, "namespace does not belong to a source text module"_s); + auto* instance = dynamicDowncast(callFrame->argument(1)); + if (!instance) + instance = ModuleGraphInstance::create(vm, globalObject, nullptr); + JSModuleEnvironment* environment = record->instantiateIntoGraphInstance(globalObject, instance); + RETURN_IF_EXCEPTION(scope, {}); + JSModuleNamespaceObject* namespaceObject = record->getModuleNamespace(globalObject, instance); + RETURN_IF_EXCEPTION(scope, {}); + JSObject* result = constructEmptyObject(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + result->putDirect(vm, Identifier::fromString(vm, "namespace"_s), namespaceObject); + result->putDirect(vm, Identifier::fromString(vm, "environment"_s), environment); + result->putDirect(vm, Identifier::fromString(vm, "instance"_s), instance); + return JSValue::encode(result); +} + JSC_DEFINE_HOST_FUNCTION(functionGlobalObjectCount, (JSGlobalObject* globalObject, CallFrame*)) { DollarVMAssertScope assertScope; @@ -5556,6 +5593,7 @@ void JSDollarVM::finishCreation(VM& vm) addFunction(vm, alwaysAllow, "deleteAllCodeWhenIdle"_s, functionDeleteAllCodeWhenIdle, 0); addFunction(vm, allowIfNotFuzz, "globalObjectCount"_s, functionGlobalObjectCount, 0); + addFunction(vm, allowIfNotFuzz, "instantiateModuleGraph"_s, functionInstantiateModuleGraph, 2); addFunction(vm, allowIfNotFuzz, "globalObjectForObject"_s, functionGlobalObjectForObject, 1); addFunction(vm, allowIfNotFuzz, "getGetterSetter"_s, functionGetGetterSetter, 2); From 0cda1e04210b73a17588941d583d09efb92c7a17 Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Thu, 27 Aug 2026 10:39:06 +0000 Subject: [PATCH 02/16] [JSC] Module graph instances: review fixes (1) - Loader continuations for instance import() are internal microtasks (ModuleGraphInstanceLoadSettled / EvaluateSettled / DependencySettled); no species-observable JSPromise::then, no function objects, no Strong<>. - Baseline JIT reads the import slot index from metadata (baseline code is shared between CodeBlocks of one UnlinkedCodeBlock); LOLJIT asserts the option is off; DFG indexes import-slot storage directly. - Instance evaluation never falls back to the primary graph's state for a Cyclic Module Record: instantiation gives every Cyclic record reached its own state or throws (WebAssembly records are refused), recordInstanceFor release-asserts, cleared instances are refused at Evaluate/EvaluateSync/ deferred namespace access/loader entry points. - createInstanceEnvironment: recursion check, Evaluate() step 2 on the template, parent-scope shape check, and roll-back of every record of a failed instantiation (Link() step 4.a). - linkWithoutEvaluating / loadModule(ForGraphInstance): a fetch or instantiation failure makes the template unusable, the primary graph's evaluation error does not. - ModuleGraphInstance: destroy(); clear() keeps pending capabilities in a MarkedArgumentBuffer and rejects under DeferTermination+SuspendException; add() is idempotent, release-checks cleared, sets the environment's back-pointer. - configureModuleScopeOverlay: snapshot values first (propagating exceptions), publish symbol table and primary overlay together, assert it runs once and before any module environment exists; isModuleScopeOverlay. - graphInstanceEnvironment / makeModule re-check the instance after host generate(); executeInstance passes the record instance as generator state in the non-TLA path too; sync import-defer checks each dependency's result. --- .../JavaScriptCore/dfg/DFGByteCodeParser.cpp | 3 +- .../JavaScriptCore/jit/JITPropertyAccess.cpp | 25 ++++-- Source/JavaScriptCore/lol/LOLJIT.cpp | 5 +- .../runtime/AbstractModuleRecord.cpp | 14 ++- .../runtime/CyclicModuleRecord.cpp | 16 +++- .../JavaScriptCore/runtime/JSGlobalObject.cpp | 70 +++++++++------ .../JavaScriptCore/runtime/JSGlobalObject.h | 3 + Source/JavaScriptCore/runtime/JSMicrotask.cpp | 77 ++++++++++++++++ .../JavaScriptCore/runtime/JSModuleLoader.cpp | 90 ++++++++----------- .../JavaScriptCore/runtime/JSModuleLoader.h | 4 + .../runtime/JSModuleNamespaceObject.cpp | 6 ++ .../JavaScriptCore/runtime/JSModuleRecord.cpp | 89 ++++++++++++++---- Source/JavaScriptCore/runtime/Microtask.h | 3 + .../runtime/ModuleGraphInstance.cpp | 29 ++++-- .../runtime/ModuleGraphInstance.h | 2 + 15 files changed, 312 insertions(+), 124 deletions(-) diff --git a/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp b/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp index b8e2a629637c..d242ea945e4b 100644 --- a/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp +++ b/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp @@ -10500,7 +10500,8 @@ void ByteCodeParser::parseBlock(unsigned limit) for (unsigned n = depth; n--;) constantScope = constantScope->next(); if (auto* importer = dynamicDowncast(constantScope)) { - if (JSValue exporter = importer->variableAt(slot).get()) { + // Import slots sit past the symbol table's scope size; index the storage directly. + if (JSValue exporter = importer->variables()[slot.offset()].get()) { set(bytecode.m_dst, weakJSConstant(exporter.asCell())); break; } diff --git a/Source/JavaScriptCore/jit/JITPropertyAccess.cpp b/Source/JavaScriptCore/jit/JITPropertyAccess.cpp index 5da43c354fdb..faf1cd3b0f96 100644 --- a/Source/JavaScriptCore/jit/JITPropertyAccess.cpp +++ b/Source/JavaScriptCore/jit/JITPropertyAccess.cpp @@ -897,24 +897,31 @@ void JIT::emit_op_resolve_scope(const JSInstruction* currentInstruction) // resolve type. if (profiledResolveType == ModuleVar) { - unsigned moduleImportSlot = bytecode.metadata(m_profiledCodeBlock).m_moduleImportSlot; - if (!moduleImportSlot) { - // No import slot (module graph instances off, or a binding that is - // not an import): the exporter environment is a link-time constant. + if (!Options::useModuleGraphInstances()) { + // The exporter environment is a link-time constant. loadPtrFromMetadata(bytecode, Metadata::offsetOfLexicalEnvironment(), returnValueGPR); } else { - // Module graph instances: the exporter environment is read from the - // importing environment's import slot, so the same code serves every - // instance. Walk to the importing environment and load the slot; an - // unfilled slot goes to the slow path. + // Module graph instances: the import slot index (like the exporter + // environment) is a per-CodeBlock link-time value, and baseline code + // is shared between CodeBlocks of one UnlinkedCodeBlock, so read it + // from metadata. Slot 0: constant environment. Otherwise walk to the + // importing module environment (a lexical depth) and load its import + // slot; an unfilled slot goes to the slow path. + load32FromMetadata(bytecode, Metadata::offsetOfModuleImportSlot(), scratch1GPR); + Jump hasImportSlot = branchTest32(NonZero, scratch1GPR); + loadPtrFromMetadata(bytecode, Metadata::offsetOfLexicalEnvironment(), returnValueGPR); + Jump done = jump(); + hasImportSlot.link(this); emitGetVirtualRegister(scope, scopeGPR); static_assert(scopeGPR == returnValueGPR); unsigned localScopeDepth = bytecode.metadata(m_profiledCodeBlock).m_localScopeDepth; for (unsigned index = 0; index < localScopeDepth; ++index) loadPtr(Address(returnValueGPR, JSScope::offsetOfNext()), returnValueGPR); static_assert(sizeof(WriteBarrier) == 8); - load64(Address(returnValueGPR, JSLexicalEnvironment::offsetOfVariables() + (moduleImportSlot - 1) * sizeof(WriteBarrier)), returnValueGPR); + sub32(TrustedImm32(1), scratch1GPR); + load64(BaseIndex(returnValueGPR, scratch1GPR, TimesEight, JSLexicalEnvironment::offsetOfVariables()), returnValueGPR); addSlowCase(branchIfEmpty(returnValueGPR)); + done.link(this); } } else if (profiledResolveType == ClosureVar) { emitGetVirtualRegister(scope, scopeGPR); diff --git a/Source/JavaScriptCore/lol/LOLJIT.cpp b/Source/JavaScriptCore/lol/LOLJIT.cpp index 19efeace325b..e64772feaa84 100644 --- a/Source/JavaScriptCore/lol/LOLJIT.cpp +++ b/Source/JavaScriptCore/lol/LOLJIT.cpp @@ -3597,9 +3597,10 @@ void LOLJIT::emit_op_resolve_scope(const JSInstruction* currentInstruction) // If we profile certain resolve types, we're guaranteed all linked code will have the same // resolve type. - if (profiledResolveType == ModuleVar) + if (profiledResolveType == ModuleVar) { + RELEASE_ASSERT(!Options::useModuleGraphInstances()); // import slots not implemented here loadPtrFromMetadata(bytecode, Metadata::offsetOfLexicalEnvironment(), destRegs.payloadGPR()); - else if (profiledResolveType == ClosureVar) { + } else if (profiledResolveType == ClosureVar) { move(scopeRegs.payloadGPR(), destRegs.payloadGPR()); unsigned localScopeDepth = bytecode.metadata(m_profiledCodeBlock).m_localScopeDepth; if (localScopeDepth < 8) { diff --git a/Source/JavaScriptCore/runtime/AbstractModuleRecord.cpp b/Source/JavaScriptCore/runtime/AbstractModuleRecord.cpp index 9948fdf7cd36..d0309807ee4b 100644 --- a/Source/JavaScriptCore/runtime/AbstractModuleRecord.cpp +++ b/Source/JavaScriptCore/runtime/AbstractModuleRecord.cpp @@ -852,9 +852,13 @@ JSModuleEnvironment* AbstractModuleRecord::graphInstanceEnvironment(JSGlobalObje JSModuleEnvironment* environment = synthetic->createGraphInstanceEnvironment(globalObject); globalObject->setCurrentGraphInstanceForLoading(vm, previousLoadingInstance); RETURN_IF_EXCEPTION(scope, nullptr); - environment->setGraphInstance(vm, instance); - instance->add(vm, this, environment); - return environment; + // The provider ran host code: the instance may have been disposed, or this + // record instantiated into it re-entrantly (add() then returns that one). + if (instance->isCleared()) { + throwTypeError(globalObject, scope, "Module graph instance was disposed"_s); + return nullptr; + } + return instance->add(vm, this, environment)->environment(); } // GetModuleNamespace for the record as instantiated in `instance`: one namespace @@ -1155,6 +1159,10 @@ void AbstractModuleRecord::evaluateSync(JSGlobalObject* globalObject, ModuleGrap ModuleGraphInstance::BusyScope busy(globalObject, instance); VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); + if (instance && instance->isCleared()) { + throwTypeError(globalObject, scope, "Module graph instance was disposed"_s); + return; + } // 1. If ReadyForSyncExecution(module) is false, throw a TypeError exception. if (!readyForSyncExecution(instance)) { throwTypeError(globalObject, scope, "Unable to synchronously evaluate deferred module"_s); diff --git a/Source/JavaScriptCore/runtime/CyclicModuleRecord.cpp b/Source/JavaScriptCore/runtime/CyclicModuleRecord.cpp index 370afcf53c84..88eb74279328 100644 --- a/Source/JavaScriptCore/runtime/CyclicModuleRecord.cpp +++ b/Source/JavaScriptCore/runtime/CyclicModuleRecord.cpp @@ -56,10 +56,15 @@ namespace JSC { // instance did not instantiate (shared with the primary graph) uses its own. static ModuleRecordInstance* recordInstanceFor(const CyclicModuleRecord* record, ModuleGraphInstance* instance) { - // A cleared instance has no state; falling back to the record's own state - // would read or write the primary graph's. - ASSERT(!instance || !instance->isCleared()); - return instance ? instance->recordInstance(const_cast(record)) : nullptr; + if (!instance) + return nullptr; + // Evaluating an instance never reads or writes the primary graph's state: + // instantiation gives every Cyclic Module Record the instance reaches its + // own state (JSModuleRecord::createInstanceEnvironment), and a cleared + // instance is refused before evaluation starts. + ModuleRecordInstance* recordInstance = instance->recordInstance(const_cast(record)); + RELEASE_ASSERT(recordInstance, "module graph instance has no state for a cyclic module record it evaluates"); + return recordInstance; } CyclicModuleRecord::Status CyclicModuleRecord::status(ModuleGraphInstance* instance) const @@ -590,6 +595,9 @@ JSPromise* CyclicModuleRecord::evaluate(JSGlobalObject* globalObject, ModuleGrap VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); + if (instance && instance->isCleared()) [[unlikely]] + RELEASE_AND_RETURN(scope, JSPromise::rejectedPromise(globalObject, createTypeError(globalObject, "Module graph instance was disposed"_s))); + // 1. Assert: This call to Evaluate is not happening at the same time as another call to Evaluate within the surrounding agent. // FIXME: is this needed? // 2. Assert: module.[[Status]] is one of LINKED, EVALUATING-ASYNC, or EVALUATED. diff --git a/Source/JavaScriptCore/runtime/JSGlobalObject.cpp b/Source/JavaScriptCore/runtime/JSGlobalObject.cpp index 87348f7b3802..a17527249c4d 100644 --- a/Source/JavaScriptCore/runtime/JSGlobalObject.cpp +++ b/Source/JavaScriptCore/runtime/JSGlobalObject.cpp @@ -4087,58 +4087,69 @@ Inspector::JSGlobalObjectInspectorController& JSGlobalObject::inspectorControlle void JSGlobalObject::configureModuleScopeOverlay(const Vector& names) { - RELEASE_ASSERT(Options::useModuleGraphInstances()); VM& vm = this->vm(); - if (m_moduleScopeOverlaySymbolTable) - return; + auto scope = DECLARE_THROW_SCOPE(vm); + RELEASE_ASSERT(Options::useModuleGraphInstances()); + // Module CodeBlocks are linked against the scope chain shape this sets up + // and are shared by every instance, so it is configured once, before any + // module environment exists in this global object. + RELEASE_ASSERT(!m_moduleScopeOverlaySymbolTable, "module scope overlay configured twice"); + RELEASE_ASSERT(!m_hasCreatedModuleEnvironment, "module scope overlay configured after a module was linked"); + + // 1. Snapshot the global's current values for the overlaid names (getters + // may run script; nothing is published yet if one throws). + Vector overlaid; + MarkedArgumentBuffer values; + for (auto& name : names) { + if (name.isPrivateName()) + continue; + JSValue value = get(this, name); + RETURN_IF_EXCEPTION(scope, void()); + overlaid.append(name); + values.append(value); + } + + // 2. The symbol table every overlay shares. Slot 0 holds the module graph + // instance an overlay belongs to (empty in the primary graph's), so code + // running under an overlay can be attributed to its instance from the + // scope chain alone. SymbolTable* symbolTable = SymbolTable::create(vm); symbolTable->setScopeType(SymbolTable::ScopeType::LexicalScope); - // Slot 0: the module graph instance this overlay belongs to — lets any code - // running in a graph (module code or CJS wrappers scoped to the overlay) be - // attributed to it by walking the scope chain. Empty in the primary overlay. { auto offset = symbolTable->takeNextScopeOffset(NoLockingNecessary); ASSERT_UNUSED(offset, !offset.offset()); SymbolTableEntry entry(VarOffset(ScopeOffset(0)), static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::DontEnum)); symbolTable->set(NoLockingNecessary, vm.propertyNames->builtinNames().moduleGraphInstancePrivateName().impl(), WTF::move(entry)); } - for (auto& name : names) { - if (name.isPrivateName()) - continue; + Vector offsets; + for (auto& name : overlaid) { auto offset = symbolTable->takeNextScopeOffset(NoLockingNecessary); symbolTable->set(NoLockingNecessary, name.impl(), SymbolTableEntry(VarOffset(offset))); + offsets.append(offset); } - m_moduleScopeOverlaySymbolTable.set(vm, this, symbolTable); + + // 3. The primary graph's overlay carries the snapshot; publish both together. JSLexicalEnvironment* primary = JSLexicalEnvironment::create(vm, this, globalLexicalEnvironment(), symbolTable, jsUndefined()); - Vector> slots; - { - ConcurrentJSLocker locker(symbolTable->m_lock); - for (auto iter = symbolTable->begin(locker), end = symbolTable->end(locker); iter != end; ++iter) - slots.append({ Identifier::fromUid(vm, iter->key.get()), iter->value.scopeOffset() }); - } - { - auto catchScope = DECLARE_TOP_EXCEPTION_SCOPE(vm); - for (auto& [name, offset] : slots) { - if (name.isPrivateName()) - continue; - JSValue value = get(this, name); - if (catchScope.exception()) { - catchScope.clearException(); - value = jsUndefined(); - } - primary->variableAt(offset).set(vm, primary, value ? value : jsUndefined()); - } - } + for (unsigned i = 0; i < offsets.size(); ++i) + primary->variableAt(offsets[i]).set(vm, primary, values.at(i)); + m_moduleScopeOverlaySymbolTable.set(vm, this, symbolTable); m_primaryModuleScopeOverlay.set(vm, this, primary); } JSScope* JSGlobalObject::moduleEnvironmentParentScope() { + m_hasCreatedModuleEnvironment = true; if (auto* overlay = m_primaryModuleScopeOverlay.get()) return overlay; return globalLexicalEnvironment(); } +bool JSGlobalObject::isModuleScopeOverlay(JSScope* scope) const +{ + auto* environment = dynamicDowncast(scope); + return environment && m_moduleScopeOverlaySymbolTable && environment->symbolTable() == m_moduleScopeOverlaySymbolTable.get() && environment->next() == m_globalLexicalEnvironment.get(); +} + JSLexicalEnvironment* JSGlobalObject::createModuleScopeOverlay(JSObject* values, ModuleGraphInstance* instance) { VM& vm = this->vm(); @@ -4149,6 +4160,7 @@ JSLexicalEnvironment* JSGlobalObject::createModuleScopeOverlay(JSObject* values, return nullptr; } JSLexicalEnvironment* primary = m_primaryModuleScopeOverlay.get(); + ASSERT(primary); JSLexicalEnvironment* overlay = JSLexicalEnvironment::create(vm, this, globalLexicalEnvironment(), symbolTable, jsUndefined()); Vector> slots; { diff --git a/Source/JavaScriptCore/runtime/JSGlobalObject.h b/Source/JavaScriptCore/runtime/JSGlobalObject.h index 930796c32ffa..8e654648720d 100644 --- a/Source/JavaScriptCore/runtime/JSGlobalObject.h +++ b/Source/JavaScriptCore/runtime/JSGlobalObject.h @@ -356,6 +356,7 @@ class JSGlobalObject : public JSSegmentedVariableObject { WriteBarrier m_moduleScopeOverlaySymbolTable; WriteBarrier m_currentGraphInstanceForLoading; WriteBarrier m_primaryModuleScopeOverlay; + bool m_hasCreatedModuleEnvironment { false }; WriteBarrier m_objectPrototype; WriteBarrier m_functionPrototype; @@ -925,6 +926,8 @@ class JSGlobalObject : public JSSegmentedVariableObject { // holds the global's own values. JS_EXPORT_PRIVATE void configureModuleScopeOverlay(const Vector& names); SymbolTable* moduleScopeOverlaySymbolTable() const { return m_moduleScopeOverlaySymbolTable.get(); } + // True for a scope created by createModuleScopeOverlay (or the primary overlay). + bool isModuleScopeOverlay(JSScope*) const; JSLexicalEnvironment* primaryModuleScopeOverlay() const { return m_primaryModuleScopeOverlay.get(); } // Parent scope for module environments: the primary overlay if configured, else the global lexical environment. JS_EXPORT_PRIVATE JSScope* moduleEnvironmentParentScope(); diff --git a/Source/JavaScriptCore/runtime/JSMicrotask.cpp b/Source/JavaScriptCore/runtime/JSMicrotask.cpp index 1bf92d92f1d3..e76af68e48c7 100644 --- a/Source/JavaScriptCore/runtime/JSMicrotask.cpp +++ b/Source/JavaScriptCore/runtime/JSMicrotask.cpp @@ -1637,6 +1637,68 @@ static void dynamicImportEvaluateSettled(JSGlobalObject* globalObject, VM& vm, T capabilityPromise->reject(vm, arguments[1]); } +// import() into a module graph instance (JSModuleLoader::importIntoGraphInstance): +// the load settled. arguments[0] = result promise, [1] = key or error, +// [2] = context object { @moduleGraphInstance, name: key, type, @defer }. +static void moduleGraphInstanceLoadSettled(JSGlobalObject* globalObject, VM& vm, ThrowScope& scope, std::span arguments, uint8_t payload) +{ + auto* resultPromise = uncheckedDowncast(arguments[0]); + if (static_cast(payload) != JSPromise::Status::Fulfilled) { + resultPromise->reject(vm, arguments[1]); + return; + } + JSObject* context = asObject(arguments[2]); + auto* instance = uncheckedDowncast(context->getDirect(vm, vm.propertyNames->builtinNames().moduleGraphInstancePrivateName())); + Identifier key = context->getDirect(vm, vm.propertyNames->name).toPropertyKey(globalObject); + RETURN_IF_EXCEPTION(scope, void()); + auto type = static_cast(context->getDirect(vm, vm.propertyNames->type).asInt32()); + bool deferred = context->getDirect(vm, vm.propertyNames->builtinNames().deferPrivateName()).isTrue(); + JSPromise* namespacePromise = JSModuleLoader::instantiateLoadedModuleIntoGraphInstance(globalObject, key, instance, type, deferred); + if (scope.exception()) [[unlikely]] { + resultPromise->rejectWithCaughtException(vm, scope); + return; + } + resultPromise->pipeFrom(vm, namespacePromise); +} + +// The instance's evaluation of the imported module settled: resolve with the +// namespace object for (record, instance). arguments as above plus context.value = record. +static void moduleGraphInstanceEvaluateSettled(JSGlobalObject* globalObject, VM& vm, ThrowScope& scope, std::span arguments, uint8_t payload) +{ + auto* resultPromise = uncheckedDowncast(arguments[0]); + if (static_cast(payload) != JSPromise::Status::Fulfilled) { + resultPromise->reject(vm, arguments[1]); + return; + } + JSObject* context = asObject(arguments[2]); + auto* instance = uncheckedDowncast(context->getDirect(vm, vm.propertyNames->builtinNames().moduleGraphInstancePrivateName())); + auto* record = uncheckedDowncast(context->getDirect(vm, vm.propertyNames->value)); + bool deferred = context->getDirect(vm, vm.propertyNames->builtinNames().deferPrivateName()).isTrue(); + JSModuleNamespaceObject* moduleNamespace = record->getModuleNamespace(globalObject, instance, deferred ? AbstractModuleRecord::ModulePhase::Defer : AbstractModuleRecord::ModulePhase::Evaluation); + if (scope.exception()) [[unlikely]] { + resultPromise->rejectWithCaughtException(vm, scope); + return; + } + resultPromise->resolve(globalObject, vm, moduleNamespace); +} + +// AND-join over the asynchronous transitive dependencies of a deferred import +// into an instance (JSModuleRecord::instantiateIntoGraphInstanceAsync). +// arguments[0] = result promise, [1] = value or error, [2] = JSPromiseCombinatorsGlobalContext (count). +static void moduleGraphInstanceDependencySettled(JSGlobalObject* globalObject, VM& vm, ThrowScope&, std::span arguments, uint8_t payload) +{ + auto* resultPromise = uncheckedDowncast(arguments[0]); + auto* joinContext = uncheckedDowncast(arguments[2]); + if (static_cast(payload) != JSPromise::Status::Fulfilled) { + resultPromise->reject(vm, arguments[1]); // first rejection wins + return; + } + uint64_t remaining = joinContext->remainingElementsCount() - 1; + joinContext->setRemainingElementsCount(remaining); + if (!remaining) + resultPromise->resolve(globalObject, vm, jsUndefined()); +} + static void importModuleNamespace(JSGlobalObject* globalObject, VM& vm, ThrowScope&, std::span arguments, uint8_t payload) { // requestImportModule: namespace getter @@ -2344,6 +2406,21 @@ void runInternalMicrotask(JSGlobalObject* globalObject, VM& vm, InternalMicrotas return; } + case InternalMicrotask::ModuleGraphInstanceLoadSettled: { + moduleGraphInstanceLoadSettled(globalObject, vm, scope, arguments, payload); + return; + } + + case InternalMicrotask::ModuleGraphInstanceEvaluateSettled: { + moduleGraphInstanceEvaluateSettled(globalObject, vm, scope, arguments, payload); + return; + } + + case InternalMicrotask::ModuleGraphInstanceDependencySettled: { + moduleGraphInstanceDependencySettled(globalObject, vm, scope, arguments, payload); + return; + } + case InternalMicrotask::ImportModuleNamespace: { importModuleNamespace(globalObject, vm, scope, arguments, payload); return; diff --git a/Source/JavaScriptCore/runtime/JSModuleLoader.cpp b/Source/JavaScriptCore/runtime/JSModuleLoader.cpp index 6193b0c18f6e..20353c573a3e 100644 --- a/Source/JavaScriptCore/runtime/JSModuleLoader.cpp +++ b/Source/JavaScriptCore/runtime/JSModuleLoader.cpp @@ -369,7 +369,7 @@ JSPromise* JSModuleLoader::loadModule(JSGlobalObject* globalObject, const Identi if (entry->fetchError()) removeFailedFetchEntry(entry); else { - JSValue error = entry->error(globalObject); + JSValue error = flags.contains(ModuleLoadFlag::ForGraphInstance) && entry->status() != ModuleRegistryEntry::Status::InstantiationFailed ? JSValue() : entry->error(globalObject); RETURN_IF_EXCEPTION(scope, nullptr); if (error) return JSPromise::rejectedPromise(globalObject, error); @@ -449,6 +449,8 @@ JSPromise* JSModuleLoader::linkAndEvaluateModule(JSGlobalObject* globalObject, c JSPromise* JSModuleLoader::loadModuleForGraphInstance(JSGlobalObject* globalObject, const Identifier& key, RefPtr&& parameters, ModuleGraphInstance* instance) { VM& vm = globalObject->vm(); + if (instance->isCleared()) + return JSPromise::rejectedPromise(globalObject, createTypeError(globalObject, "Module graph instance has been disposed"_s)); // A registry entry whose fetch or instantiation failed is another program // run's failure (possibly produced on behalf of another instance, e.g. by a // host module provider that runs code at fetch time): this instance loads @@ -464,43 +466,25 @@ JSPromise* JSModuleLoader::loadModuleForGraphInstance(JSGlobalObject* globalObje auto scope = DECLARE_THROW_SCOPE(vm); ModuleGraphInstance* previous = globalObject->currentGraphInstanceForLoading(); globalObject->setCurrentGraphInstanceForLoading(vm, instance); - JSPromise* promise = globalObject->moduleLoader()->loadModuleSync(globalObject, key, WTF::move(parameters), nullptr, { }); + JSPromise* promise = globalObject->moduleLoader()->loadModuleSync(globalObject, key, WTF::move(parameters), nullptr, { ModuleLoadFlag::ForGraphInstance }); globalObject->setCurrentGraphInstanceForLoading(vm, previous); RELEASE_AND_RETURN(scope, promise); } -// Continuations below keep what they need as properties of the function -// object (visited by the GC), never as Strong<> captures: an evaluation that -// never settles must not root the instance. -static JSC_DECLARE_HOST_FUNCTION(moduleGraphInstanceNamespaceContinuation); -static JSC_DECLARE_HOST_FUNCTION(moduleGraphInstanceInstantiateContinuation); - -JSC_DEFINE_HOST_FUNCTION(moduleGraphInstanceNamespaceContinuation, (JSGlobalObject* globalObject, CallFrame* callFrame)) -{ - VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - JSObject* callee = callFrame->jsCallee(); - auto* record = uncheckedDowncast(callee->getDirect(vm, Identifier::fromString(vm, "record"_s))); - auto* instance = uncheckedDowncast(callee->getDirect(vm, vm.propertyNames->builtinNames().moduleGraphInstancePrivateName())); - bool deferred = callee->getDirect(vm, vm.propertyNames->builtinNames().deferPrivateName()).isTrue(); - JSModuleNamespaceObject* ns = record->getModuleNamespace(globalObject, instance, deferred ? AbstractModuleRecord::ModulePhase::Defer : AbstractModuleRecord::ModulePhase::Evaluation); - RETURN_IF_EXCEPTION(scope, { }); - return JSValue::encode(ns); -} - -JSC_DEFINE_HOST_FUNCTION(moduleGraphInstanceInstantiateContinuation, (JSGlobalObject* globalObject, CallFrame* callFrame)) +// The continuations of an instance import() are internal microtasks +// (InternalMicrotask::ModuleGraphInstanceLoadSettled / EvaluateSettled in +// JSMicrotask.cpp): nothing script-observable (no species lookup, no function +// objects), and nothing Strong<>-rooted, so an evaluation that never settles +// does not keep the instance alive. +JSObject* JSModuleLoader::createGraphInstanceImportContext(JSGlobalObject* globalObject, ModuleGraphInstance* instance, const Identifier& key, ScriptFetchParameters::Type type, bool deferred) { VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - JSObject* callee = callFrame->jsCallee(); - auto* instance = uncheckedDowncast(callee->getDirect(vm, vm.propertyNames->builtinNames().moduleGraphInstancePrivateName())); - Identifier key = callee->getDirect(vm, Identifier::fromString(vm, "key"_s)).toPropertyKey(globalObject); - RETURN_IF_EXCEPTION(scope, { }); - auto type = static_cast(callee->getDirect(vm, Identifier::fromString(vm, "type"_s)).asInt32()); - bool deferred = callee->getDirect(vm, vm.propertyNames->builtinNames().deferPrivateName()).isTrue(); - JSPromise* ns = JSModuleLoader::instantiateLoadedModuleIntoGraphInstance(globalObject, key, instance, type, deferred); - RETURN_IF_EXCEPTION(scope, { }); - return JSValue::encode(ns); + JSObject* context = constructEmptyObject(vm, globalObject->nullPrototypeObjectStructure()); + context->putDirect(vm, vm.propertyNames->builtinNames().moduleGraphInstancePrivateName(), instance); + context->putDirect(vm, vm.propertyNames->name, identifierToJSValue(vm, key)); + context->putDirect(vm, vm.propertyNames->type, jsNumber(static_cast(type))); + context->putDirect(vm, vm.propertyNames->builtinNames().deferPrivateName(), jsBoolean(deferred)); + return context; } JSPromise* JSModuleLoader::instantiateLoadedModuleIntoGraphInstance(JSGlobalObject* globalObject, const Identifier& key, ModuleGraphInstance* instance, ScriptFetchParameters::Type type, bool deferred) @@ -528,17 +512,19 @@ JSPromise* JSModuleLoader::instantiateLoadedModuleIntoGraphInstance(JSGlobalObje } JSPromise* evaluated = sourceRecord->instantiateIntoGraphInstanceAsync(globalObject, instance, phase); RETURN_IF_EXCEPTION(scope, nullptr); - JSFunction* toNamespace = JSFunction::create(vm, globalObject, 1, String(), moduleGraphInstanceNamespaceContinuation, ImplementationVisibility::Private); - toNamespace->putDirect(vm, Identifier::fromString(vm, "record"_s), sourceRecord); - toNamespace->putDirect(vm, vm.propertyNames->builtinNames().moduleGraphInstancePrivateName(), instance); - toNamespace->putDirect(vm, vm.propertyNames->builtinNames().deferPrivateName(), jsBoolean(deferred)); - RELEASE_AND_RETURN(scope, uncheckedDowncast(evaluated->then(globalObject, toNamespace, jsUndefined()))); + JSObject* context = createGraphInstanceImportContext(globalObject, instance, key, type, deferred); + context->putDirect(vm, vm.propertyNames->value, sourceRecord); + JSPromise* result = JSPromise::create(vm, globalObject->promiseStructure()); + evaluated->performPromiseThenWithInternalMicrotask(vm, InternalMicrotask::ModuleGraphInstanceEvaluateSettled, result, context); + return result; } JSPromise* JSModuleLoader::importIntoGraphInstance(JSGlobalObject* globalObject, JSString* specifierValue, JSValue parameters, const SourceOrigin& referrer, ModuleGraphInstance* instance, bool deferred) { VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); + if (instance->isCleared()) + RELEASE_AND_RETURN(scope, JSPromise::rejectedPromise(globalObject, createTypeError(globalObject, "Module graph instance has been disposed"_s))); JSPromise* result = JSPromise::create(vm, globalObject->promiseStructure()); auto specifier = specifierValue->value(globalObject); @@ -578,12 +564,9 @@ JSPromise* JSModuleLoader::importIntoGraphInstance(JSGlobalObject* globalObject, // belong to this graph. Evaluation stays asynchronous. JSPromise* loaded = loadModuleForGraphInstance(globalObject, key, WTF::move(fetchParameters), instance); RETURN_IF_EXCEPTION(scope, nullptr); - JSFunction* onLoaded = JSFunction::create(vm, globalObject, 1, String(), moduleGraphInstanceInstantiateContinuation, ImplementationVisibility::Private); - onLoaded->putDirect(vm, vm.propertyNames->builtinNames().moduleGraphInstancePrivateName(), instance); - onLoaded->putDirect(vm, Identifier::fromString(vm, "key"_s), identifierToJSValue(vm, key)); - onLoaded->putDirect(vm, Identifier::fromString(vm, "type"_s), jsNumber(static_cast(fetchType))); - onLoaded->putDirect(vm, vm.propertyNames->builtinNames().deferPrivateName(), jsBoolean(deferred)); - RELEASE_AND_RETURN(scope, uncheckedDowncast(loaded->then(globalObject, onLoaded, jsUndefined()))); + JSObject* context = createGraphInstanceImportContext(globalObject, instance, key, fetchType, deferred); + loaded->performPromiseThenWithInternalMicrotask(vm, InternalMicrotask::ModuleGraphInstanceLoadSettled, result, context); + return result; } AbstractModuleRecord* JSModuleLoader::linkWithoutEvaluating(JSGlobalObject* globalObject, const Identifier& moduleKey, RefPtr scriptFetcher, ScriptFetchParameters::Type type) @@ -597,18 +580,19 @@ AbstractModuleRecord* JSModuleLoader::linkWithoutEvaluating(JSGlobalObject* glob throwTypeError(globalObject, scope, makeString("Module '"_s, moduleKey.string(), "' has not been fetched"_s)); return nullptr; } - JSValue error = entry->error(globalObject); - RETURN_IF_EXCEPTION(scope, nullptr); - if (error) { - scope.throwException(globalObject, error); + // Only a fetch or instantiation failure makes the template unusable; the + // primary graph's evaluation error is its own (an instance evaluates the + // record separately, with its own [[EvaluationError]]). + if (entry->status() == ModuleRegistryEntry::Status::FetchFailed || entry->status() == ModuleRegistryEntry::Status::InstantiationFailed) { + JSValue error = entry->error(globalObject); + RETURN_IF_EXCEPTION(scope, nullptr); + scope.throwException(globalObject, error ? error : createError(globalObject, makeString("Module '"_s, moduleKey.string(), "' failed to load"_s))); return nullptr; } record->link(globalObject, WTF::move(scriptFetcher)); if (Exception* exception = scope.exception()) { attachErrorInfo(globalObject, scope, record, entry->key(), entry->moduleType(), ModuleFailure::Kind::Instantiation); entry->setInstantiationError(globalObject, exception->value()); - if (auto* cyclic = dynamicDowncast(record)) - cyclic->setEvaluationError(vm, exception->value()); return nullptr; } return record; @@ -1487,9 +1471,11 @@ JSPromise* JSModuleLoader::makeModule(JSGlobalObject* globalObject, const Identi symbolTablePutTouchWatchpointSet(environment, globalObject, exportNames[i], args.at(i) ? args.at(i) : jsUndefined(), false, true, putResult); RETURN_IF_EXCEPTION(scope, promise->rejectWithCaughtException(vm, scope)); } - environment->setGraphInstance(vm, loadingInstance); - loadingInstance->add(vm, moduleRecord, environment); - RETURN_IF_EXCEPTION(scope, promise->rejectWithCaughtException(vm, scope)); + // The instance may have been disposed while the host generated + // this module (that ran script): the record is still created (it + // is the primary's too), just not filed under the instance. + if (!loadingInstance->isCleared()) + loadingInstance->add(vm, moduleRecord, environment); } } diff --git a/Source/JavaScriptCore/runtime/JSModuleLoader.h b/Source/JavaScriptCore/runtime/JSModuleLoader.h index be324e49923d..c32be3cdafc7 100644 --- a/Source/JavaScriptCore/runtime/JSModuleLoader.h +++ b/Source/JavaScriptCore/runtime/JSModuleLoader.h @@ -52,6 +52,9 @@ enum class ModuleLoadFlag : uint8_t { Dynamic = 1 << 1, UseImportMap = 1 << 2, Deferred = 1 << 3, + // Loading a template for a module graph instance: the primary graph's + // evaluation error for an already-loaded module is not this load's failure. + ForGraphInstance = 1 << 4, }; class JSModuleLoader final : public JSCell { @@ -109,6 +112,7 @@ class JSModuleLoader final : public JSCell { JS_EXPORT_PRIVATE static JSPromise* loadModuleForGraphInstance(JSGlobalObject*, const Identifier& key, RefPtr&&, ModuleGraphInstance*); // Resolves with the per-instance namespace object once the (possibly async) instance evaluation completes. JS_EXPORT_PRIVATE static JSPromise* instantiateLoadedModuleIntoGraphInstance(JSGlobalObject*, const Identifier& key, ModuleGraphInstance*, ScriptFetchParameters::Type = ScriptFetchParameters::Type::JavaScript, bool deferred = false); + static JSObject* createGraphInstanceImportContext(JSGlobalObject*, ModuleGraphInstance*, const Identifier& key, ScriptFetchParameters::Type, bool deferred); JSPromise* requestImportModule(JSGlobalObject*, const Identifier& moduleName, const Identifier& referrer, RefPtr, RefPtr, bool deferred = false, int64_t referrerAsyncOrder = -1); #if USE(BUN_JSC_ADDITIONS) JS_EXPORT_PRIVATE int64_t asyncEvaluationOrderForKey(const Identifier& key); diff --git a/Source/JavaScriptCore/runtime/JSModuleNamespaceObject.cpp b/Source/JavaScriptCore/runtime/JSModuleNamespaceObject.cpp index a3300c9464d0..268580ac873a 100644 --- a/Source/JavaScriptCore/runtime/JSModuleNamespaceObject.cpp +++ b/Source/JavaScriptCore/runtime/JSModuleNamespaceObject.cpp @@ -152,6 +152,12 @@ void JSModuleNamespaceObject::ensureDeferredNamespaceEvaluation(JSGlobalObject* ASSERT(m_isDeferred); // A namespace of a module graph instance evaluates its module in that instance. ModuleGraphInstance* instance = m_graphInstance.get(); + if (instance && instance->isCleared()) { + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + throwTypeError(globalObject, scope, "Module namespace belongs to a module graph instance that was disposed"_s); + return; + } // Fast path: if the module's cycle has already successfully evaluated, EvaluateModuleSync would // observe a fulfilled promise and return without throwing, so we can skip the work entirely. // We must consult [[CycleRoot]] here because Evaluate() redirects to it; for a non-root SCC diff --git a/Source/JavaScriptCore/runtime/JSModuleRecord.cpp b/Source/JavaScriptCore/runtime/JSModuleRecord.cpp index 6f2e89c0a450..2066890d78d8 100644 --- a/Source/JavaScriptCore/runtime/JSModuleRecord.cpp +++ b/Source/JavaScriptCore/runtime/JSModuleRecord.cpp @@ -49,6 +49,7 @@ #include "SymbolTableInlines.h" #include "SyntheticModuleRecord.h" #include "JSPromise.h" +#include "JSPromiseCombinatorsGlobalContext.h" #include "ModuleProgramExecutable.h" #include "ModuleProgramCodeBlock.h" #include "SourceProfiler.h" @@ -237,6 +238,15 @@ JSModuleEnvironment* JSModuleRecord::createInstanceEnvironment(JSGlobalObject* g if (JSModuleEnvironment* existing = instance->environment(this)) return existing; + if (!vm.isSafeToRecurseSoft()) [[unlikely]] { + throwStackOverflowError(globalObject, scope); + return nullptr; + } + // Evaluate() step 2 for the template: it must have completed Link(). + if (status() == Status::New || status() == Status::Unlinked || status() == Status::Linking) { + throwTypeError(globalObject, scope, makeString("Module '"_s, moduleKey().string(), "' is not linked and cannot be instantiated into a module graph instance"_s)); + return nullptr; + } ModuleProgramExecutable* executable = m_retainedExecutable.get(); if (!executable) { throwTypeError(globalObject, scope, makeString("Module '"_s, moduleKey().string(), "' cannot be instantiated again: it was linked before module graph instances were enabled"_s)); @@ -244,12 +254,24 @@ JSModuleEnvironment* JSModuleRecord::createInstanceEnvironment(JSGlobalObject* g } SymbolTable* symbolTable = executable->moduleEnvironmentSymbolTable(); + // The record's CodeBlocks are shared by every instance and were linked + // against the primary environment's scope chain, so an instance's parent + // scope must have the same shape: the global's module environment parent + // scope, or an overlay from createModuleScopeOverlay when the primary graph + // is under one too. JSScope* parentScope = instance->parentScope() ? instance->parentScope() : globalObject->moduleEnvironmentParentScope(); + if (instance->parentScope() && !(globalObject->isModuleScopeOverlay(parentScope) && globalObject->primaryModuleScopeOverlay())) { + throwTypeError(globalObject, scope, "A module graph instance's parent scope must be a module scope overlay of a global object whose modules are under one"_s); + return nullptr; + } JSModuleEnvironment* env = JSModuleEnvironment::create(vm, globalObject, parentScope, symbolTable, jsTDZValue(), this); RETURN_IF_EXCEPTION(scope, nullptr); env->setGraphInstance(vm, instance); - // Register before recursing so import cycles terminate (status Linked). + // Register before recursing so import cycles terminate (status Linked), and + // in `created` so a failure further on can roll every record of this + // instantiation back out of the instance (Link() step 4.a). instance->add(vm, this, env); + created.append(this); for (const auto& request : requestedModules()) { AbstractModuleRecord* imported = hostResolveImportedModule(globalObject, request.m_specifier, request.type()); @@ -257,6 +279,12 @@ JSModuleEnvironment* JSModuleRecord::createInstanceEnvironment(JSGlobalObject* g if (auto* importedSource = dynamicDowncast(imported)) { importedSource->createInstanceEnvironment(globalObject, instance, created); RETURN_IF_EXCEPTION(scope, nullptr); + } else if (is(imported)) { + // Every Cyclic Module Record an instance reaches must have its own + // state in the instance (the evaluation algorithm never falls back to + // the primary graph's); only Source Text Module Records can today. + throwTypeError(globalObject, scope, makeString("Module '"_s, imported->moduleKey().string(), "' cannot be instantiated into a module graph instance (only JavaScript and synthetic modules can)"_s)); + return nullptr; } else { // Synthetic records with per-instance state get an environment in // the instance; others are shared with the primary graph. @@ -330,10 +358,18 @@ JSModuleEnvironment* JSModuleRecord::createInstanceEnvironment(JSGlobalObject* g RETURN_IF_EXCEPTION(scope, nullptr); } - created.append(this); return env; } +// Link() step 4.a for an instance: an instantiation that failed part-way leaves +// nothing behind, so a retry starts clean instead of evaluating half-initialised +// environments. +static void rollBackInstantiation(ModuleGraphInstance* instance, const Vector& created) +{ + for (JSModuleRecord* record : created) + instance->remove(record); +} + static void fillGraphInstanceImportSlots(JSGlobalObject* globalObject, const Vector& created, ModuleGraphInstance* instance) { for (JSModuleRecord* record : created) { @@ -350,7 +386,10 @@ JSModuleEnvironment* JSModuleRecord::instantiateIntoGraphInstance(JSGlobalObject Vector created; JSModuleEnvironment* env = createInstanceEnvironment(globalObject, instance, created); - RETURN_IF_EXCEPTION(scope, nullptr); + if (scope.exception()) [[unlikely]] { + rollBackInstantiation(instance, created); + return nullptr; + } fillGraphInstanceImportSlots(globalObject, created, instance); if (phase == ModulePhase::Defer) { // import defer: only the asynchronous transitive dependencies evaluate @@ -359,13 +398,25 @@ JSModuleEnvironment* JSModuleRecord::instantiateIntoGraphInstance(JSGlobalObject UncheckedKeyHashSet seen; gatherAsynchronousTransitiveDependencies(asyncDependencies, seen, instance); for (AbstractModuleRecord* dependency : asyncDependencies) { - if (auto* cyclic = dynamicDowncast(dependency)) { + auto* cyclic = dynamicDowncast(dependency); + if (!cyclic) + continue; #if USE(BUN_JSC_ADDITIONS) - cyclic->evaluate(globalObject, -1, instance); + JSPromise* promise = cyclic->evaluate(globalObject, -1, instance); #else - cyclic->evaluate(globalObject, instance); + JSPromise* promise = cyclic->evaluate(globalObject, instance); #endif - RETURN_IF_EXCEPTION(scope, nullptr); + RETURN_IF_EXCEPTION(scope, nullptr); + switch (promise->status()) { + case JSPromise::Status::Fulfilled: + continue; + case JSPromise::Status::Rejected: + promise->markAsHandled(); + scope.throwException(globalObject, promise->result()); + return nullptr; + case JSPromise::Status::Pending: + throwTypeError(globalObject, scope, makeString("Module '"_s, cyclic->moduleKey().string(), "' uses top-level await and cannot be evaluated synchronously"_s)); + return nullptr; } } return env; @@ -400,6 +451,7 @@ JSPromise* JSModuleRecord::instantiateIntoGraphInstanceAsync(JSGlobalObject* glo Vector created; createInstanceEnvironment(globalObject, instance, created); if (scope.exception()) [[unlikely]] { + rollBackInstantiation(instance, created); JSPromise* rejected = JSPromise::create(vm, globalObject->promiseStructure()); rejected->rejectWithCaughtException(vm, scope); return rejected; @@ -428,18 +480,17 @@ JSPromise* JSModuleRecord::instantiateIntoGraphInstanceAsync(JSGlobalObject* glo } promises.append(promise); } - // All must fulfil (first rejection rejects): fold them into one chain. - JSPromise* result = JSPromise::resolvedPromise(globalObject, jsUndefined()); - RETURN_IF_EXCEPTION(scope, nullptr); - for (unsigned i = 0; i < promises.size(); ++i) { - JSValue next = promises.at(i); - auto* waitNext = JSNativeStdFunction::create(vm, globalObject, 0, String(), [next = Strong(vm, next)](JSGlobalObject*, CallFrame*) -> EncodedJSValue { - return JSValue::encode(next.get()); - }); - result = uncheckedDowncast(result->then(globalObject, waitNext, jsUndefined())); - RETURN_IF_EXCEPTION(scope, nullptr); + // SafePerformPromiseAll: an AND-join through internal microtasks (first + // rejection rejects). Nothing here is script-observable or Strong<>-rooted. + JSPromise* result = JSPromise::create(vm, globalObject->promiseStructure()); + if (promises.isEmpty()) { + result->resolve(globalObject, vm, jsUndefined()); + RELEASE_AND_RETURN(scope, result); } - RELEASE_AND_RETURN(scope, result); + auto* joinContext = JSPromiseCombinatorsGlobalContext::create(vm, result, jsUndefined(), promises.size()); + for (unsigned i = 0; i < promises.size(); ++i) + uncheckedDowncast(promises.at(i))->performPromiseThenWithInternalMicrotask(vm, InternalMicrotask::ModuleGraphInstanceDependencySettled, result, joinContext); + return result; } #if USE(BUN_JSC_ADDITIONS) JSPromise* promise = CyclicModuleRecord::evaluate(globalObject, -1, instance); @@ -463,7 +514,7 @@ void JSModuleRecord::executeInstance(JSGlobalObject* globalObject, ModuleRecordI auto scope = DECLARE_THROW_SCOPE(vm); if (!hasTLA()) { ASSERT(!capability); - vm.interpreter.executeModuleProgram(this, m_retainedExecutable.get(), globalObject, recordInstance->environment(), jsUndefined(), jsNumber(static_cast(ResumeMode::NormalMode))); + vm.interpreter.executeModuleProgram(this, recordInstance, m_retainedExecutable.get(), globalObject, recordInstance->environment(), jsUndefined(), jsNumber(static_cast(ResumeMode::NormalMode))); pinRetainedCodeBlock(vm); RETURN_IF_EXCEPTION(scope, void()); return; diff --git a/Source/JavaScriptCore/runtime/Microtask.h b/Source/JavaScriptCore/runtime/Microtask.h index 73b5f8d3114c..86bd4d44d232 100644 --- a/Source/JavaScriptCore/runtime/Microtask.h +++ b/Source/JavaScriptCore/runtime/Microtask.h @@ -82,6 +82,9 @@ enum class InternalMicrotask : uint8_t { DynamicImportEvaluateSettled, DynamicImportDeferLoadSettled, DynamicImportDeferDependencySettled, + ModuleGraphInstanceLoadSettled, + ModuleGraphInstanceEvaluateSettled, + ModuleGraphInstanceDependencySettled, ImportModuleNamespace, #if ENABLE(WEBASSEMBLY) WebAssemblyCompileStreaming, diff --git a/Source/JavaScriptCore/runtime/ModuleGraphInstance.cpp b/Source/JavaScriptCore/runtime/ModuleGraphInstance.cpp index a8a696833017..4662c350b27c 100644 --- a/Source/JavaScriptCore/runtime/ModuleGraphInstance.cpp +++ b/Source/JavaScriptCore/runtime/ModuleGraphInstance.cpp @@ -26,7 +26,9 @@ #include "config.h" #include "ModuleGraphInstance.h" +#include "DeferTermination.h" #include "Error.h" +#include "FrameTracers.h" #include "JSCInlines.h" #include "JSInternalFieldObjectImplInlines.h" #include "JSModuleEnvironment.h" @@ -149,9 +151,13 @@ ModuleRecordInstance* ModuleGraphInstance::recordInstance(AbstractModuleRecord* ModuleRecordInstance* ModuleGraphInstance::add(VM& vm, AbstractModuleRecord* record, JSModuleEnvironment* environment) { - ASSERT(!m_records.contains(record)); - ASSERT(!m_cleared); // callers check isCleared() and throw first + RELEASE_ASSERT(!m_cleared); // callers check isCleared() and throw first + // Idempotent: host code that runs while a record is instantiated (a + // synthetic module's generator) may have instantiated it re-entrantly. + if (ModuleRecordInstance* existing = recordInstance(record)) + return existing; ModuleRecordInstance* instance = ModuleRecordInstance::create(vm, this, record, environment); + environment->setGraphInstance(vm, this); Locker locker { cellLock() }; m_records.add(record, WriteBarrier(vm, this, instance)); return instance; @@ -163,6 +169,12 @@ bool ModuleGraphInstance::remove(AbstractModuleRecord* record) return m_records.remove(record); } +void ModuleGraphInstance::destroy(JSCell* cell) +{ + SUPPRESS_MEMORY_UNSAFE_CAST auto* thisObject = static_cast(cell); + thisObject->~ModuleGraphInstance(); +} + void ModuleGraphInstance::clear(JSGlobalObject* globalObject) { if (m_busy) { @@ -173,7 +185,10 @@ void ModuleGraphInstance::clear(JSGlobalObject* globalObject) } m_clearPending = false; VM& vm = globalObject->vm(); - Vector pending; + // Pending top-level evaluation promises are rejected below; keep them alive + // (they were reachable only through the records' state) across the + // allocation of the error. + MarkedArgumentBuffer pending; { Locker locker { cellLock() }; m_cleared = true; @@ -186,9 +201,13 @@ void ModuleGraphInstance::clear(JSGlobalObject* globalObject) } if (pending.isEmpty()) return; + // May run as a deferred clear when an evaluation step unwinds with an + // exception pending (BusyScope): rejecting is bookkeeping, not a new throw. + DeferTerminationForAWhile deferTermination(vm); + SuspendExceptionScope suspendException(vm); JSObject* error = createTypeError(globalObject, "Module graph instance was disposed during evaluation"_s); - for (JSPromise* capability : pending) - capability->reject(vm, JSValue(error)); + for (unsigned i = 0; i < pending.size(); ++i) + uncheckedDowncast(pending.at(i))->reject(vm, JSValue(error)); } } // namespace JSC diff --git a/Source/JavaScriptCore/runtime/ModuleGraphInstance.h b/Source/JavaScriptCore/runtime/ModuleGraphInstance.h index a89875b08920..dff77f546502 100644 --- a/Source/JavaScriptCore/runtime/ModuleGraphInstance.h +++ b/Source/JavaScriptCore/runtime/ModuleGraphInstance.h @@ -126,6 +126,8 @@ class ModuleGraphInstance final : public JSDestructibleObject { DECLARE_EXPORT_INFO; DECLARE_VISIT_CHILDREN; + static constexpr DestructionMode needsDestruction = NeedsDestruction; + static void destroy(JSCell*); template static GCClient::IsoSubspace* subspaceFor(VM& vm) From aab59bd0f2016edd10fe6e02bff32c43fb7bbfc6 Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Thu, 27 Aug 2026 10:44:55 +0000 Subject: [PATCH 03/16] [JSC] Module graph instances: review fixes (2) - GraphInstanceLoadingScope (RAII) for the loading bracket; a primary-graph import() issued while an instance load is in flight is bracketed as the primary's. - importedEnvironmentFor fills the import slot it resolves, so later accesses take the interpreter/JIT fast path. - CallFrame::callerScope attributes eval code to the frame that called eval (the shared eval callee's scope is transient). - SyntheticModuleRecord::materializePrimaryIfPending retries after a throwing generator. - JSModuleNamespaceObject::overrideExportValue on an instance namespace overrides within that instance. --- .../JavaScriptCore/interpreter/CallFrame.cpp | 5 ++++- .../runtime/AbstractModuleRecord.cpp | 9 +++++---- Source/JavaScriptCore/runtime/JSGlobalObject.h | 14 ++++++++++++++ .../runtime/JSGlobalObjectFunctions.cpp | 5 +++++ .../runtime/JSModuleEnvironment.cpp | 18 ++++++++++-------- .../JavaScriptCore/runtime/JSModuleLoader.cpp | 18 ++++++++++-------- .../runtime/JSModuleNamespaceObject.cpp | 8 ++++++-- .../runtime/SyntheticModuleRecord.cpp | 7 +++++-- 8 files changed, 59 insertions(+), 25 deletions(-) diff --git a/Source/JavaScriptCore/interpreter/CallFrame.cpp b/Source/JavaScriptCore/interpreter/CallFrame.cpp index 9544993c1040..04ff91cecd1a 100644 --- a/Source/JavaScriptCore/interpreter/CallFrame.cpp +++ b/Source/JavaScriptCore/interpreter/CallFrame.cpp @@ -207,10 +207,13 @@ JSScope* CallFrame::callerScope(VM& vm) switch (visitor->codeType()) { case StackVisitor::Frame::CodeType::Native: case StackVisitor::Frame::CodeType::Wasm: + // Eval code runs in its caller's scope, but its callee is the global + // object's shared eval callee whose scope is only set while the eval is + // being entered: attribute to the frame that called eval instead. + case StackVisitor::Frame::CodeType::Eval: return IterationStatus::Continue; case StackVisitor::Frame::CodeType::Function: case StackVisitor::Frame::CodeType::Module: - case StackVisitor::Frame::CodeType::Eval: case StackVisitor::Frame::CodeType::Global: break; } diff --git a/Source/JavaScriptCore/runtime/AbstractModuleRecord.cpp b/Source/JavaScriptCore/runtime/AbstractModuleRecord.cpp index d0309807ee4b..08ac8573447c 100644 --- a/Source/JavaScriptCore/runtime/AbstractModuleRecord.cpp +++ b/Source/JavaScriptCore/runtime/AbstractModuleRecord.cpp @@ -847,10 +847,11 @@ JSModuleEnvironment* AbstractModuleRecord::graphInstanceEnvironment(JSGlobalObje auto* synthetic = dynamicDowncast(this); if (!synthetic || !synthetic->hasPerGraphInstanceState()) return nullptr; - ModuleGraphInstance* previousLoadingInstance = globalObject->currentGraphInstanceForLoading(); - globalObject->setCurrentGraphInstanceForLoading(vm, instance); - JSModuleEnvironment* environment = synthetic->createGraphInstanceEnvironment(globalObject); - globalObject->setCurrentGraphInstanceForLoading(vm, previousLoadingInstance); + JSModuleEnvironment* environment = nullptr; + { + JSGlobalObject::GraphInstanceLoadingScope loading(globalObject, instance); + environment = synthetic->createGraphInstanceEnvironment(globalObject); + } RETURN_IF_EXCEPTION(scope, nullptr); // The provider ran host code: the instance may have been disposed, or this // record instantiated into it re-entrantly (add() then returns that one). diff --git a/Source/JavaScriptCore/runtime/JSGlobalObject.h b/Source/JavaScriptCore/runtime/JSGlobalObject.h index 8e654648720d..d368657eb557 100644 --- a/Source/JavaScriptCore/runtime/JSGlobalObject.h +++ b/Source/JavaScriptCore/runtime/JSGlobalObject.h @@ -940,6 +940,20 @@ class JSGlobalObject : public JSSegmentedVariableObject { // (e.g. CommonJS modules behind an ESM import) to this instance. ModuleGraphInstance* currentGraphInstanceForLoading() const { return m_currentGraphInstanceForLoading.get(); } JS_EXPORT_PRIVATE void setCurrentGraphInstanceForLoading(VM&, ModuleGraphInstance*); + // Brackets a load performed on behalf of `instance` (host-provided + // synthetic modules created meanwhile belong to it); restores on exit. + class GraphInstanceLoadingScope { + public: + GraphInstanceLoadingScope(JSGlobalObject* globalObject, ModuleGraphInstance* instance) + : m_globalObject(globalObject), m_previous(globalObject->currentGraphInstanceForLoading()) + { + globalObject->setCurrentGraphInstanceForLoading(globalObject->vm(), instance); + } + ~GraphInstanceLoadingScope() { m_globalObject->setCurrentGraphInstanceForLoading(m_globalObject->vm(), m_previous); } + private: + JSGlobalObject* m_globalObject; + ModuleGraphInstance* m_previous; + }; ObjectPrototype* objectPrototype() const LIFETIME_BOUND { return m_objectPrototype.get(); } FunctionPrototype* functionPrototype() const LIFETIME_BOUND { return m_functionPrototype.get(); } diff --git a/Source/JavaScriptCore/runtime/JSGlobalObjectFunctions.cpp b/Source/JavaScriptCore/runtime/JSGlobalObjectFunctions.cpp index fadce2e216e2..7fe3227f5d5d 100644 --- a/Source/JavaScriptCore/runtime/JSGlobalObjectFunctions.cpp +++ b/Source/JavaScriptCore/runtime/JSGlobalObjectFunctions.cpp @@ -834,6 +834,11 @@ JSC_DEFINE_HOST_FUNCTION(globalFuncImportModule, (JSGlobalObject* globalObject, } } + // A primary-graph import() issued by host code that runs during an + // instance's load is the primary's, not that instance's. + std::optional primaryLoading; + if (Options::useModuleGraphInstances() && globalObject->currentGraphInstanceForLoading()) [[unlikely]] + primaryLoading.emplace(globalObject, nullptr); auto* importPromise = globalObject->moduleLoader()->importModule(globalObject, specifier, parameters, sourceOrigin, deferred); if (scope.exception()) [[unlikely]] return rejectWithCaughtException(); diff --git a/Source/JavaScriptCore/runtime/JSModuleEnvironment.cpp b/Source/JavaScriptCore/runtime/JSModuleEnvironment.cpp index 4ba331c5e144..05e333614c19 100644 --- a/Source/JavaScriptCore/runtime/JSModuleEnvironment.cpp +++ b/Source/JavaScriptCore/runtime/JSModuleEnvironment.cpp @@ -125,17 +125,19 @@ JSModuleEnvironment* JSModuleEnvironment::importedEnvironmentFor(JSGlobalObject* AbstractModuleRecord* record = moduleRecord(); if (record == exporter) return this; - if (record) { - if (auto slotIndex = record->importSlotIndexFor(exporter)) { - if (JSValue filled = importSlot(*slotIndex).get(); filled.isCell()) - return uncheckedDowncast(filled); - } + std::optional slotIndex = record ? record->importSlotIndexFor(exporter) : std::nullopt; + if (slotIndex) { + if (JSValue filled = importSlot(*slotIndex).get(); filled.isCell()) + return uncheckedDowncast(filled); } JSModuleEnvironment* environment = exporter->graphInstanceEnvironment(globalObject, instance, true); RETURN_IF_EXCEPTION(scope, nullptr); - if (environment) - return environment; - return exporter->moduleEnvironment(); + if (!environment) + environment = exporter->moduleEnvironment(); // shared with the primary graph + // Fill the slot so the interpreter and JIT fast paths take over from here. + if (slotIndex && environment) + importSlot(*slotIndex).set(vm, this, environment); + return environment; } ModuleGraphInstance* JSModuleEnvironment::graphInstance() diff --git a/Source/JavaScriptCore/runtime/JSModuleLoader.cpp b/Source/JavaScriptCore/runtime/JSModuleLoader.cpp index 20353c573a3e..fe3ff1da36ea 100644 --- a/Source/JavaScriptCore/runtime/JSModuleLoader.cpp +++ b/Source/JavaScriptCore/runtime/JSModuleLoader.cpp @@ -464,10 +464,11 @@ JSPromise* JSModuleLoader::loadModuleForGraphInstance(JSGlobalObject* globalObje } } auto scope = DECLARE_THROW_SCOPE(vm); - ModuleGraphInstance* previous = globalObject->currentGraphInstanceForLoading(); - globalObject->setCurrentGraphInstanceForLoading(vm, instance); - JSPromise* promise = globalObject->moduleLoader()->loadModuleSync(globalObject, key, WTF::move(parameters), nullptr, { ModuleLoadFlag::ForGraphInstance }); - globalObject->setCurrentGraphInstanceForLoading(vm, previous); + JSPromise* promise = nullptr; + { + JSGlobalObject::GraphInstanceLoadingScope loading(globalObject, instance); + promise = globalObject->moduleLoader()->loadModuleSync(globalObject, key, WTF::move(parameters), nullptr, { ModuleLoadFlag::ForGraphInstance }); + } RELEASE_AND_RETURN(scope, promise); } @@ -496,10 +497,11 @@ JSPromise* JSModuleLoader::instantiateLoadedModuleIntoGraphInstance(JSGlobalObje // The loading instance is current only while linking (host-provided // synthetic modules consult it); evaluating the instance below runs script, // and loads that script triggers must not be attributed to this instance. - ModuleGraphInstance* previousLoadingInstance = globalObject->currentGraphInstanceForLoading(); - globalObject->setCurrentGraphInstanceForLoading(vm, instance); - AbstractModuleRecord* record = globalObject->moduleLoader()->linkWithoutEvaluating(globalObject, key, nullptr, type); - globalObject->setCurrentGraphInstanceForLoading(vm, previousLoadingInstance); + AbstractModuleRecord* record = nullptr; + { + JSGlobalObject::GraphInstanceLoadingScope loading(globalObject, instance); + record = globalObject->moduleLoader()->linkWithoutEvaluating(globalObject, key, nullptr, type); + } RETURN_IF_EXCEPTION(scope, nullptr); auto phase = deferred ? AbstractModuleRecord::ModulePhase::Defer : AbstractModuleRecord::ModulePhase::Evaluation; auto* sourceRecord = dynamicDowncast(record); diff --git a/Source/JavaScriptCore/runtime/JSModuleNamespaceObject.cpp b/Source/JavaScriptCore/runtime/JSModuleNamespaceObject.cpp index 268580ac873a..6eb29f33f8a8 100644 --- a/Source/JavaScriptCore/runtime/JSModuleNamespaceObject.cpp +++ b/Source/JavaScriptCore/runtime/JSModuleNamespaceObject.cpp @@ -493,12 +493,16 @@ bool JSModuleNamespaceObject::overrideExportValue(JSGlobalObject* globalObject, } auto* record = resolution.moduleRecord; - auto* moduleNamespaceObject = record->getModuleNamespace(globalObject); + // A namespace of a module graph instance overrides within that instance. + ModuleGraphInstance* instance = m_graphInstance.get(); + auto* moduleNamespaceObject = instance ? record->getModuleNamespace(globalObject, instance) : record->getModuleNamespace(globalObject); RETURN_IF_EXCEPTION(scope, false); bool putResult = false; moduleNamespaceObject->m_isOverridingValue = true; - if (JSModuleEnvironment* moduleEnvironment = record->moduleEnvironmentMayBeNull()) { + JSModuleEnvironment* moduleEnvironment = instance ? environmentFor(globalObject, record) : record->moduleEnvironmentMayBeNull(); + RETURN_IF_EXCEPTION(scope, {}); + if (moduleEnvironment) { symbolTablePutTouchWatchpointSet(moduleEnvironment, globalObject, resolution.localName, value, false, true, putResult); RETURN_IF_EXCEPTION(scope, {}); } diff --git a/Source/JavaScriptCore/runtime/SyntheticModuleRecord.cpp b/Source/JavaScriptCore/runtime/SyntheticModuleRecord.cpp index babf12dbf8a3..c080019304a7 100644 --- a/Source/JavaScriptCore/runtime/SyntheticModuleRecord.cpp +++ b/Source/JavaScriptCore/runtime/SyntheticModuleRecord.cpp @@ -354,11 +354,14 @@ void SyntheticModuleRecord::materializePrimaryIfPending(JSGlobalObject* globalOb // While a graph is loading, the primary is nobody's business yet. if (!m_primaryPending || !m_provider || globalObject->currentGraphInstanceForLoading()) return; - m_primaryPending = false; + m_primaryPending = false; // before generate(): the provider may re-enter MarkedArgumentBuffer values; Vector names; m_provider->generate(globalObject, moduleKey(), names, values); - RETURN_IF_EXCEPTION(scope, void()); + if (scope.exception()) [[unlikely]] { + m_primaryPending = true; // a later use retries + return; + } JSModuleEnvironment* environment = moduleEnvironment(); SymbolTable* symbolTable = environment->symbolTable(); for (const auto& [key, entry] : exportEntries()) { From 217c231bc7ee078103a6c85665d5b912028edf9c Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Thu, 27 Aug 2026 10:58:19 +0000 Subject: [PATCH 04/16] =?UTF-8?q?[JSC]=20Module=20graph=20instances:=20Mod?= =?UTF-8?q?uleRegistryEntry::error(IncludeEvaluationError)=20=E2=80=94=20i?= =?UTF-8?q?nstance=20loads=20honour=20stored=20load=20errors=20(fetch,=20i?= =?UTF-8?q?nstantiation,=20dependency)=20but=20not=20the=20primary=20graph?= =?UTF-8?q?'s=20evaluation=20failure?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Source/JavaScriptCore/runtime/JSModuleLoader.cpp | 10 +++++----- Source/JavaScriptCore/runtime/ModuleRegistryEntry.cpp | 6 ++++-- Source/JavaScriptCore/runtime/ModuleRegistryEntry.h | 6 +++++- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/Source/JavaScriptCore/runtime/JSModuleLoader.cpp b/Source/JavaScriptCore/runtime/JSModuleLoader.cpp index fe3ff1da36ea..0ae94587232a 100644 --- a/Source/JavaScriptCore/runtime/JSModuleLoader.cpp +++ b/Source/JavaScriptCore/runtime/JSModuleLoader.cpp @@ -369,7 +369,7 @@ JSPromise* JSModuleLoader::loadModule(JSGlobalObject* globalObject, const Identi if (entry->fetchError()) removeFailedFetchEntry(entry); else { - JSValue error = flags.contains(ModuleLoadFlag::ForGraphInstance) && entry->status() != ModuleRegistryEntry::Status::InstantiationFailed ? JSValue() : entry->error(globalObject); + JSValue error = entry->error(globalObject, flags.contains(ModuleLoadFlag::ForGraphInstance) ? ModuleRegistryEntry::IncludeEvaluationError::No : ModuleRegistryEntry::IncludeEvaluationError::Yes); RETURN_IF_EXCEPTION(scope, nullptr); if (error) return JSPromise::rejectedPromise(globalObject, error); @@ -585,10 +585,10 @@ AbstractModuleRecord* JSModuleLoader::linkWithoutEvaluating(JSGlobalObject* glob // Only a fetch or instantiation failure makes the template unusable; the // primary graph's evaluation error is its own (an instance evaluates the // record separately, with its own [[EvaluationError]]). - if (entry->status() == ModuleRegistryEntry::Status::FetchFailed || entry->status() == ModuleRegistryEntry::Status::InstantiationFailed) { - JSValue error = entry->error(globalObject); - RETURN_IF_EXCEPTION(scope, nullptr); - scope.throwException(globalObject, error ? error : createError(globalObject, makeString("Module '"_s, moduleKey.string(), "' failed to load"_s))); + JSValue error = entry->error(globalObject, ModuleRegistryEntry::IncludeEvaluationError::No); + RETURN_IF_EXCEPTION(scope, nullptr); + if (error) { + scope.throwException(globalObject, error); return nullptr; } record->link(globalObject, WTF::move(scriptFetcher)); diff --git a/Source/JavaScriptCore/runtime/ModuleRegistryEntry.cpp b/Source/JavaScriptCore/runtime/ModuleRegistryEntry.cpp index 6a950a6585fc..ec710942b69a 100644 --- a/Source/JavaScriptCore/runtime/ModuleRegistryEntry.cpp +++ b/Source/JavaScriptCore/runtime/ModuleRegistryEntry.cpp @@ -139,10 +139,12 @@ JSPromise* ModuleRegistryEntry::loadPromise() const return m_loadPromise.get(); } -JSValue ModuleRegistryEntry::error(JSGlobalObject* globalObject) const +JSValue ModuleRegistryEntry::error(JSGlobalObject* globalObject, IncludeEvaluationError includeEvaluationError) const { VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); + if (includeEvaluationError == IncludeEvaluationError::No && m_status == Status::EvaluationFailed) + return { }; if (JSValue error = m_error.get()) { if (m_status == Status::FetchFailed) { if (auto* errorInstance = dynamicDowncast(error)) @@ -150,7 +152,7 @@ JSValue ModuleRegistryEntry::error(JSGlobalObject* globalObject) const } RELEASE_AND_RETURN(scope, error); } - if (m_record) { + if (m_record && includeEvaluationError == IncludeEvaluationError::Yes) { if (auto* cyclic = dynamicDowncast(m_record.get())) RELEASE_AND_RETURN(scope, cyclic->evaluationError()); } diff --git a/Source/JavaScriptCore/runtime/ModuleRegistryEntry.h b/Source/JavaScriptCore/runtime/ModuleRegistryEntry.h index f74f04c0f552..c26fbc5e1ae6 100644 --- a/Source/JavaScriptCore/runtime/ModuleRegistryEntry.h +++ b/Source/JavaScriptCore/runtime/ModuleRegistryEntry.h @@ -73,7 +73,11 @@ class ModuleRegistryEntry final : public JSCell { JSPromise* ensureFetchPromise(JSGlobalObject*); JSPromise* ensureModulePromise(JSGlobalObject*); JSPromise* loadPromise() const; - JSValue error(JSGlobalObject*) const; + // The error this entry settled with: a fetch, instantiation or dependency + // load error stored on the entry, else (unless excluded) the record's + // evaluation error. + enum class IncludeEvaluationError : bool { No, Yes }; + JSValue error(JSGlobalObject*, IncludeEvaluationError = IncludeEvaluationError::Yes) const; JSValue fetchError() const; Status status() const; // A finished, failed load: fetch or record creation / instantiation failed and From 87c6201c207881fe96e813eb321db3f003cd39d9 Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Thu, 27 Aug 2026 11:15:16 +0000 Subject: [PATCH 05/16] [JSC] Module graph instances: review fixes (3) - hostLoadImportedModule: a host fetch hook that throws synchronously marks the new registry entry FetchFailed instead of leaving it New. - ModuleRegistryEntry::error(IncludeEvaluationError::No) excludes only the record's own evaluation failure (status Evaluated with an error), not a dependency load failure recorded on the entry. - Non-BUN_JSC_ADDITIONS builds: useModuleGraphInstances moves to the main option list; SyntheticModuleRecord's per-instance members are unguarded; the synchronous-loader import()-from-instance path (importIntoGraphInstance, loadModuleForGraphInstance, instantiateLoadedModuleIntoGraphInstance and their microtasks) is guarded as embedder-only. - DFG: rename the shadowing resolvedScope local. --- Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp | 6 +++--- .../runtime/JSGlobalObjectFunctions.cpp | 2 ++ Source/JavaScriptCore/runtime/JSMicrotask.cpp | 11 +++++++++++ Source/JavaScriptCore/runtime/JSModuleLoader.cpp | 12 +++++++++++- Source/JavaScriptCore/runtime/JSModuleLoader.h | 13 +++++++------ .../JavaScriptCore/runtime/ModuleRegistryEntry.cpp | 9 +++++++-- Source/JavaScriptCore/runtime/OptionsList.h | 2 +- .../JavaScriptCore/runtime/SyntheticModuleRecord.h | 4 +++- 8 files changed, 45 insertions(+), 14 deletions(-) diff --git a/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp b/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp index d242ea945e4b..72b1edbc4327 100644 --- a/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp +++ b/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp @@ -10496,10 +10496,10 @@ void ByteCodeParser::parseBlock(unsigned limit) break; } } - if (JSScope* constantScope = localBase->dynamicCastConstant()) { + if (JSScope* resolvedScope = localBase->dynamicCastConstant()) { for (unsigned n = depth; n--;) - constantScope = constantScope->next(); - if (auto* importer = dynamicDowncast(constantScope)) { + resolvedScope = resolvedScope->next(); + if (auto* importer = dynamicDowncast(resolvedScope)) { // Import slots sit past the symbol table's scope size; index the storage directly. if (JSValue exporter = importer->variables()[slot.offset()].get()) { set(bytecode.m_dst, weakJSConstant(exporter.asCell())); diff --git a/Source/JavaScriptCore/runtime/JSGlobalObjectFunctions.cpp b/Source/JavaScriptCore/runtime/JSGlobalObjectFunctions.cpp index 7fe3227f5d5d..fe71457094dc 100644 --- a/Source/JavaScriptCore/runtime/JSGlobalObjectFunctions.cpp +++ b/Source/JavaScriptCore/runtime/JSGlobalObjectFunctions.cpp @@ -822,6 +822,7 @@ JSC_DEFINE_HOST_FUNCTION(globalFuncImportModule, (JSGlobalObject* globalObject, JSValue parameters = callFrame->argument(1); bool deferred = callFrame->argument(2).isTrue(); +#if USE(BUN_JSC_ADDITIONS) // Module graph instances: import() from code that belongs to an instance // loads the requested graph as a template (no evaluation of the primary) // and instantiates it into the caller's instance. @@ -833,6 +834,7 @@ JSC_DEFINE_HOST_FUNCTION(globalFuncImportModule, (JSGlobalObject* globalObject, return JSValue::encode(promise); } } +#endif // A primary-graph import() issued by host code that runs during an // instance's load is the primary's, not that instance's. diff --git a/Source/JavaScriptCore/runtime/JSMicrotask.cpp b/Source/JavaScriptCore/runtime/JSMicrotask.cpp index e76af68e48c7..75e62c071d47 100644 --- a/Source/JavaScriptCore/runtime/JSMicrotask.cpp +++ b/Source/JavaScriptCore/runtime/JSMicrotask.cpp @@ -1637,6 +1637,7 @@ static void dynamicImportEvaluateSettled(JSGlobalObject* globalObject, VM& vm, T capabilityPromise->reject(vm, arguments[1]); } +#if USE(BUN_JSC_ADDITIONS) // import() into a module graph instance (JSModuleLoader::importIntoGraphInstance): // the load settled. arguments[0] = result promise, [1] = key or error, // [2] = context object { @moduleGraphInstance, name: key, type, @defer }. @@ -1682,6 +1683,8 @@ static void moduleGraphInstanceEvaluateSettled(JSGlobalObject* globalObject, VM& resultPromise->resolve(globalObject, vm, moduleNamespace); } +#endif + // AND-join over the asynchronous transitive dependencies of a deferred import // into an instance (JSModuleRecord::instantiateIntoGraphInstanceAsync). // arguments[0] = result promise, [1] = value or error, [2] = JSPromiseCombinatorsGlobalContext (count). @@ -2407,12 +2410,20 @@ void runInternalMicrotask(JSGlobalObject* globalObject, VM& vm, InternalMicrotas } case InternalMicrotask::ModuleGraphInstanceLoadSettled: { +#if USE(BUN_JSC_ADDITIONS) moduleGraphInstanceLoadSettled(globalObject, vm, scope, arguments, payload); +#else + RELEASE_ASSERT_NOT_REACHED(); +#endif return; } case InternalMicrotask::ModuleGraphInstanceEvaluateSettled: { +#if USE(BUN_JSC_ADDITIONS) moduleGraphInstanceEvaluateSettled(globalObject, vm, scope, arguments, payload); +#else + RELEASE_ASSERT_NOT_REACHED(); +#endif return; } diff --git a/Source/JavaScriptCore/runtime/JSModuleLoader.cpp b/Source/JavaScriptCore/runtime/JSModuleLoader.cpp index 0ae94587232a..48d6e12a5442 100644 --- a/Source/JavaScriptCore/runtime/JSModuleLoader.cpp +++ b/Source/JavaScriptCore/runtime/JSModuleLoader.cpp @@ -446,6 +446,7 @@ JSPromise* JSModuleLoader::linkAndEvaluateModule(JSGlobalObject* globalObject, c return promise; } +#if USE(BUN_JSC_ADDITIONS) JSPromise* JSModuleLoader::loadModuleForGraphInstance(JSGlobalObject* globalObject, const Identifier& key, RefPtr&& parameters, ModuleGraphInstance* instance) { VM& vm = globalObject->vm(); @@ -571,6 +572,8 @@ JSPromise* JSModuleLoader::importIntoGraphInstance(JSGlobalObject* globalObject, return result; } +#endif // USE(BUN_JSC_ADDITIONS) + AbstractModuleRecord* JSModuleLoader::linkWithoutEvaluating(JSGlobalObject* globalObject, const Identifier& moduleKey, RefPtr scriptFetcher, ScriptFetchParameters::Type type) { VM& vm = globalObject->vm(); @@ -939,7 +942,14 @@ JSPromise* JSModuleLoader::hostLoadImportedModule(JSGlobalObject* globalObject, if (mapEntry->status() == ModuleRegistryEntry::Status::New) { JSPromise* promise = fetch(globalObject, identifierToJSValue(vm, resolved), moduleRequest.m_attributes, scriptFetcher); - RETURN_IF_EXCEPTION(scope, nullptr); + if (Exception* exception = scope.exception()) [[unlikely]] { + // A host fetch hook that throws synchronously: record the failure on + // the entry (a later load of this key sees it, or retries after the + // entry is dropped) rather than leaving a New entry in the map. + if (!vm.isTerminationException(exception)) + mapEntry->setFetchError(globalObject, exception->value()); + return nullptr; + } mapEntry->setStatus(ModuleRegistryEntry::Status::Fetching); mapEntry->ensureFetchPromise(globalObject)->pipeFrom(vm, promise); diff --git a/Source/JavaScriptCore/runtime/JSModuleLoader.h b/Source/JavaScriptCore/runtime/JSModuleLoader.h index c32be3cdafc7..35f49c453b76 100644 --- a/Source/JavaScriptCore/runtime/JSModuleLoader.h +++ b/Source/JavaScriptCore/runtime/JSModuleLoader.h @@ -103,16 +103,17 @@ class JSModuleLoader final : public JSCell { // Module graph instances (prototype): link a fetched module graph without // evaluating it, so it can serve as the template for instantiateIntoGraphInstance. JS_EXPORT_PRIVATE AbstractModuleRecord* linkWithoutEvaluating(JSGlobalObject*, const Identifier& moduleKey, RefPtr, ScriptFetchParameters::Type = ScriptFetchParameters::Type::JavaScript); - // import() from inside a graph instance: load `specifier` (resolved against - // `referrer`) as a template and instantiate it into the instance that - // `callerEnvironment` belongs to; resolves with a per-instance namespace object. +#if USE(BUN_JSC_ADDITIONS) + // import() from code of a module graph instance: fetch and link the graph as + // a template (through the synchronous loader), instantiate it into the + // instance, and resolve with the instance's namespace object. (An + // asynchronous-loader form is needed for ports without loadModuleSync.) JS_EXPORT_PRIVATE static JSPromise* importIntoGraphInstance(JSGlobalObject*, JSString* specifier, JSValue parameters, const SourceOrigin& referrer, ModuleGraphInstance*, bool deferred = false); - // Shared tail of the above and the embedder entry point: link `key` as a template, instantiate into `instance` with `overlay`, return the namespace. - // loadModule without evaluation, synchronously, with `instance` as the current loading graph instance. JS_EXPORT_PRIVATE static JSPromise* loadModuleForGraphInstance(JSGlobalObject*, const Identifier& key, RefPtr&&, ModuleGraphInstance*); - // Resolves with the per-instance namespace object once the (possibly async) instance evaluation completes. + // Resolves with the instance's namespace object once its (possibly asynchronous) evaluation completes. JS_EXPORT_PRIVATE static JSPromise* instantiateLoadedModuleIntoGraphInstance(JSGlobalObject*, const Identifier& key, ModuleGraphInstance*, ScriptFetchParameters::Type = ScriptFetchParameters::Type::JavaScript, bool deferred = false); static JSObject* createGraphInstanceImportContext(JSGlobalObject*, ModuleGraphInstance*, const Identifier& key, ScriptFetchParameters::Type, bool deferred); +#endif JSPromise* requestImportModule(JSGlobalObject*, const Identifier& moduleName, const Identifier& referrer, RefPtr, RefPtr, bool deferred = false, int64_t referrerAsyncOrder = -1); #if USE(BUN_JSC_ADDITIONS) JS_EXPORT_PRIVATE int64_t asyncEvaluationOrderForKey(const Identifier& key); diff --git a/Source/JavaScriptCore/runtime/ModuleRegistryEntry.cpp b/Source/JavaScriptCore/runtime/ModuleRegistryEntry.cpp index ec710942b69a..1e3b4920100d 100644 --- a/Source/JavaScriptCore/runtime/ModuleRegistryEntry.cpp +++ b/Source/JavaScriptCore/runtime/ModuleRegistryEntry.cpp @@ -143,8 +143,13 @@ JSValue ModuleRegistryEntry::error(JSGlobalObject* globalObject, IncludeEvaluati { VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); - if (includeEvaluationError == IncludeEvaluationError::No && m_status == Status::EvaluationFailed) - return { }; + if (includeEvaluationError == IncludeEvaluationError::No && m_status == Status::EvaluationFailed) { + // Only the record's own evaluation having thrown is excluded; a load + // failure of a dependency recorded on this entry still counts. + auto* cyclic = dynamicDowncast(m_record.get()); + if (cyclic && cyclic->status() == CyclicModuleRecord::Status::Evaluated && cyclic->evaluationError()) + return { }; + } if (JSValue error = m_error.get()) { if (m_status == Status::FetchFailed) { if (auto* errorInstance = dynamicDowncast(error)) diff --git a/Source/JavaScriptCore/runtime/OptionsList.h b/Source/JavaScriptCore/runtime/OptionsList.h index 2795332849ec..ca80432a7906 100644 --- a/Source/JavaScriptCore/runtime/OptionsList.h +++ b/Source/JavaScriptCore/runtime/OptionsList.h @@ -86,7 +86,6 @@ bool hasCapacityToUseLargeGigacage(); v(Bool, verboseFFI, false, Normal, "dataLog on FFI thunk/stub/signature creation"_s) #define FOR_EACH_JSC_CODEBLOCK_AGING_OPTION(v) \ v(Bool, useExecutionCountForCodeBlockAging, false, Normal, "If true, an LLInt/Baseline CodeBlock whose execution counter has advanced since the last old-age check is treated as still in use and its TTL is renewed instead of being jettisoned."_s) \ - v(Bool, useModuleGraphInstances, false, Normal, "Prototype: keep module executables so a module graph can be instantiated again in the same global (JSModuleRecord::instantiateIntoGraphInstance)."_s) \ v(Double, codeBlockAgingLeaseMultiplier, 3.0, Normal, "When useExecutionCountForCodeBlockAging proves a CodeBlock is still active, renew its old-age TTL to this many multiples of timeToLive for its tier."_s) #define FOR_EACH_JSC_BYTECODE_CACHE_DECODER_OPTION(v) \ v(Bool, useLeanBytecodeCacheDecoder, true, Normal, "If true, the bytecode cache Decoder skips bookkeeping that is only needed for decoded objects shared by multiple references."_s) \ @@ -598,6 +597,7 @@ bool hasCapacityToUseLargeGigacage(); v(Bool, dumpModuleRecord, false, Normal, nullptr) \ v(Bool, dumpModuleLoadingState, false, Normal, nullptr) \ v(Bool, exposeInternalModuleLoader, false, Normal, "expose the internal module loader object to the global space for debugging"_s) \ + v(Bool, useModuleGraphInstances, false, Normal, "Allow a linked module graph to be instantiated more than once per global object (ModuleGraphInstance)"_s) \ \ v(Bool, exposePrivateIdentifiers, false, Normal, "Allow non-builtin scripts to use private identifiers. Mostly useful to expose @superSamplerBegin/End intrinsics for profiling"_s) \ \ diff --git a/Source/JavaScriptCore/runtime/SyntheticModuleRecord.h b/Source/JavaScriptCore/runtime/SyntheticModuleRecord.h index 553cd37b224b..ea9f0688ba30 100644 --- a/Source/JavaScriptCore/runtime/SyntheticModuleRecord.h +++ b/Source/JavaScriptCore/runtime/SyntheticModuleRecord.h @@ -110,12 +110,14 @@ class SyntheticModuleRecord final : public AbstractModuleRecord { #if USE(BUN_JSC_ADDITIONS) WriteBarrier m_lazyExportsSource; +#endif + // Module graph instances: how to produce this record's bindings again for + // another instance (JSON source to re-parse, or the host's provider). SourceCode m_jsonSource; RefPtr m_provider; bool m_primaryPending { false }; enum class PlainDataState : uint8_t { Unknown, Yes, No }; PlainDataState m_plainDataState { PlainDataState::Unknown }; -#endif }; } // namespace JSC From 1e71dd2168903e3a9d7775746f2eab451c1cb4f9 Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Thu, 27 Aug 2026 18:26:21 +0000 Subject: [PATCH 06/16] [JSC] Module graph instances: drop the plain-data heuristic for synthetic modules A synthetic module is instantiated per graph instance only on an explicit signal: a JSON module (re-parsed from its source) or a host provider that declares regeneratesPerGraphInstance(). Everything else is shared with the primary graph. Removes isPlainData/clonePlainData and PlainDataState. --- .../runtime/SyntheticModuleRecord.cpp | 174 +----------------- .../runtime/SyntheticModuleRecord.h | 4 +- 2 files changed, 8 insertions(+), 170 deletions(-) diff --git a/Source/JavaScriptCore/runtime/SyntheticModuleRecord.cpp b/Source/JavaScriptCore/runtime/SyntheticModuleRecord.cpp index c080019304a7..e932597176c1 100644 --- a/Source/JavaScriptCore/runtime/SyntheticModuleRecord.cpp +++ b/Source/JavaScriptCore/runtime/SyntheticModuleRecord.cpp @@ -257,96 +257,6 @@ SyntheticModuleRecord* SyntheticModuleRecord::parseJSONModule(JSGlobalObject* gl RELEASE_AND_RETURN(scope, record); } -// Plain data = null/undefined/booleans/numbers/strings/bigints, arrays of plain -// data, and ordinary objects (Object.prototype or null prototype, data -// properties only) of plain data. Bounded so pathological modules count as "no". -static bool isPlainData(JSGlobalObject* globalObject, JSValue value, unsigned depth, unsigned& budget) -{ - if (!value || !budget--) - return false; - if (!value.isCell() || value.isString() || value.isBigInt() || value.isSymbol()) - return !value.isSymbol(); - if (depth > 64) - return false; - JSObject* object = value.getObject(); - if (!object || object->type() == JSFunctionType) - return false; - VM& vm = globalObject->vm(); - if (isJSArray(object)) { - if (object->type() != ArrayType || object->getPrototypeDirect() != globalObject->arrayPrototype()) - return false; - JSArray* array = uncheckedDowncast(object); - for (unsigned i = 0; i < array->length(); ++i) { - JSValue element = array->canGetIndexQuickly(i) ? array->getIndexQuickly(i) : JSValue(); - if (!element) - return false; - if (!isPlainData(globalObject, element, depth + 1, budget)) - return false; - } - return true; - } - if ((object->type() != FinalObjectType && object->type() != ObjectType) || object->inlineTypeFlags() & OverridesGetOwnPropertySlot) { - dataLogLnIf(Options::dumpModuleLoadingState(), "[graph-instance] not plain: type=", object->type()); - return false; - } - JSValue prototype = object->getPrototypeDirect(); - if (!prototype.isNull() && prototype != globalObject->objectPrototype()) { - dataLogLnIf(Options::dumpModuleLoadingState(), "[graph-instance] not plain: prototype"); - return false; - } - Structure* structure = object->structure(); - if (structure->hasAnyKindOfGetterSetterProperties() || structure->isUncacheableDictionary() || hasIndexedProperties(object->indexingType())) { - dataLogLnIf(Options::dumpModuleLoadingState(), "[graph-instance] not plain: getters=", structure->hasAnyKindOfGetterSetterProperties(), " uncacheableDict=", structure->isUncacheableDictionary(), " indexed=", hasIndexedProperties(object->indexingType())); - return false; - } - bool ok = true; - structure->forEachProperty(vm, [&](const PropertyTableEntry& entry) -> bool { - if (entry.attributes() & PropertyAttribute::Accessor) { - ok = false; - return false; - } - if (!isPlainData(globalObject, object->getDirect(entry.offset()), depth + 1, budget)) { - ok = false; - return false; - } - return true; - }); - return ok; -} - -static JSValue clonePlainData(JSGlobalObject* globalObject, JSValue value) -{ - VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - if (!value.isCell() || value.isString() || value.isBigInt()) - return value; - JSObject* object = value.getObject(); - if (isJSArray(object)) { - JSArray* source = uncheckedDowncast(object); - MarkedArgumentBuffer elements; - for (unsigned i = 0; i < source->length(); ++i) { - JSValue element = clonePlainData(globalObject, source->canGetIndexQuickly(i) ? source->getIndexQuickly(i) : jsUndefined()); - RETURN_IF_EXCEPTION(scope, { }); - elements.append(element); - } - RELEASE_AND_RETURN(scope, constructArray(globalObject, static_cast(nullptr), elements)); - } - JSValue prototype = object->getPrototypeDirect(); - JSObject* copy = prototype.isNull() ? constructEmptyObject(vm, globalObject->nullPrototypeObjectStructure()) : constructEmptyObject(globalObject); - RETURN_IF_EXCEPTION(scope, { }); - Vector, 8> properties; - object->structure()->forEachProperty(vm, [&](const PropertyTableEntry& entry) -> bool { - properties.append({ entry.key(), entry.offset() }); - return true; - }); - for (auto& [name, offset] : properties) { - JSValue cloned = clonePlainData(globalObject, object->getDirect(offset)); - RETURN_IF_EXCEPTION(scope, { }); - copy->putDirect(vm, name, cloned); - } - return copy; -} - void SyntheticModuleRecord::materializePrimaryIfPending(JSGlobalObject* globalObject) { VM& vm = globalObject->vm(); @@ -379,40 +289,14 @@ void SyntheticModuleRecord::materializePrimaryIfPending(JSGlobalObject* globalOb } } -bool SyntheticModuleRecord::hasPerGraphInstanceState() +bool SyntheticModuleRecord::hasPerGraphInstanceState() const { + // Two explicit signals: a JSON module re-parses its source per instance, and + // a host provider that says so regenerates its values per instance. Every + // other synthetic module is shared with the primary graph. if (!m_jsonSource.isNull()) return true; - if (m_provider && m_provider->regeneratesPerGraphInstance()) - return true; - if (m_primaryPending) - return false; - if (m_plainDataState != PlainDataState::Unknown) - return m_plainDataState == PlainDataState::Yes; - m_plainDataState = PlainDataState::No; -#if USE(BUN_JSC_ADDITIONS) - if (hasLazyExports()) - return false; -#endif - JSModuleEnvironment* environment = moduleEnvironmentMayBeNull(); - if (!environment || exportEntries().isEmpty()) - return false; - JSGlobalObject* globalObject = environment->globalObject(); - unsigned budget = 100000; - for (const auto& [key, entry] : exportEntries()) { - SymbolTableEntry::Fast symbolEntry = environment->symbolTable()->get(entry.localName.impl()); - if (symbolEntry.isNull()) { - dataLogLnIf(Options::dumpModuleLoadingState(), "[graph-instance] synthetic ", moduleKey().string(), ": export ", entry.localName.string(), " has no slot"); - return false; - } - JSValue value = environment->variableAt(symbolEntry.scopeOffset()).get(); - if (!isPlainData(globalObject, value, 0, budget)) { - dataLogLnIf(Options::dumpModuleLoadingState(), "[graph-instance] synthetic ", moduleKey().string(), ": export ", entry.localName.string(), " is not plain data"); - return false; - } - } - m_plainDataState = PlainDataState::Yes; - return true; + return m_provider && m_provider->regeneratesPerGraphInstance(); } JSModuleEnvironment* SyntheticModuleRecord::createGraphInstanceEnvironment(JSGlobalObject* globalObject) @@ -452,52 +336,8 @@ JSModuleEnvironment* SyntheticModuleRecord::createGraphInstanceEnvironment(JSGlo } return environment; } - // Deep-copy every export. `default` and named exports of a data module are - // usually the same object graph (named = default's properties); clone - // `default` once and re-derive the named exports from the copy when they - // alias, so the aliasing survives. The primary's values may have been - // mutated by script since they were judged plain data: re-check right here - // (no script runs between this check and the copy) and hand this instance - // the primary's values unchanged if they no longer qualify. - SymbolTable* symbolTable = primary->symbolTable(); - { - unsigned budget = 100000; - bool stillPlainData = true; - for (const auto& [key, entry] : exportEntries()) { - SymbolTableEntry::Fast symbolEntry = symbolTable->get(entry.localName.impl()); - if (symbolEntry.isNull() || !isPlainData(globalObject, primary->variableAt(symbolEntry.scopeOffset()).get(), 0, budget)) { - stillPlainData = false; - break; - } - } - if (!stillPlainData) { - for (const auto& [key, entry] : exportEntries()) { - SymbolTableEntry::Fast symbolEntry = symbolTable->get(entry.localName.impl()); - if (!symbolEntry.isNull()) - environment->variableAt(symbolEntry.scopeOffset()).set(vm, environment, primary->variableAt(symbolEntry.scopeOffset()).get()); - } - return environment; - } - } - SymbolTableEntry::Fast defaultEntry = symbolTable->get(vm.propertyNames->defaultKeyword.impl()); - JSValue defaultOriginal = defaultEntry.isNull() ? JSValue() : primary->variableAt(defaultEntry.scopeOffset()).get(); - JSValue defaultCopy = defaultOriginal ? clonePlainData(globalObject, defaultOriginal) : JSValue(); - RETURN_IF_EXCEPTION(scope, nullptr); - for (const auto& [key, entry] : exportEntries()) { - SymbolTableEntry::Fast symbolEntry = symbolTable->get(entry.localName.impl()); - JSValue original = primary->variableAt(symbolEntry.scopeOffset()).get(); - JSValue copy; - if (entry.localName == vm.propertyNames->defaultKeyword) - copy = defaultCopy; - else if (defaultOriginal && defaultOriginal.isObject() && defaultCopy.isObject()) { - JSValue aliased = defaultOriginal.getObject()->getDirect(vm, entry.localName); - copy = aliased == original ? defaultCopy.getObject()->getDirect(vm, entry.localName) : clonePlainData(globalObject, original); - } else - copy = clonePlainData(globalObject, original); - RETURN_IF_EXCEPTION(scope, nullptr); - environment->variableAt(symbolEntry.scopeOffset()).set(vm, environment, copy); - } - return environment; + RELEASE_ASSERT_NOT_REACHED(); + return nullptr; } SyntheticModuleRecord* SyntheticModuleRecord::createTextModule(JSGlobalObject* globalObject, const Identifier& moduleKey, SourceCode&& sourceCode) diff --git a/Source/JavaScriptCore/runtime/SyntheticModuleRecord.h b/Source/JavaScriptCore/runtime/SyntheticModuleRecord.h index ea9f0688ba30..ef1c11139afe 100644 --- a/Source/JavaScriptCore/runtime/SyntheticModuleRecord.h +++ b/Source/JavaScriptCore/runtime/SyntheticModuleRecord.h @@ -63,7 +63,7 @@ class SyntheticModuleRecord final : public AbstractModuleRecord { // True for data modules: a JSON source to re-parse, or exports that are all // plain data (primitives / arrays / plain objects), deep-copied per graph. // Native/builtin modules (functions, host objects, lazy exports) are shared. - bool hasPerGraphInstanceState(); + bool hasPerGraphInstanceState() const; JSModuleEnvironment* createGraphInstanceEnvironment(JSGlobalObject*); // Host synthetic modules with per-graph state of their own (a CommonJS // module behind an ESM import): the provider regenerates per graph, and if @@ -116,8 +116,6 @@ class SyntheticModuleRecord final : public AbstractModuleRecord { SourceCode m_jsonSource; RefPtr m_provider; bool m_primaryPending { false }; - enum class PlainDataState : uint8_t { Unknown, Yes, No }; - PlainDataState m_plainDataState { PlainDataState::Unknown }; }; } // namespace JSC From 2b82fdfdc64efbecf68fa4937416706ac041afe1 Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Thu, 27 Aug 2026 19:09:04 +0000 Subject: [PATCH 07/16] [JSC] Module graph instances: review fixes (4) - importedEnvironmentFor: an unfilled import slot holds the empty value, which isCell() accepts; test for empty first. - Import-slot collection admits ImportEntryType::SingleTypeScript (Bun) like link() does, so such an import that resolves gets a slot too. - hasOverflowed() checks on the new MarkedArgumentBuffers. --- Source/JavaScriptCore/runtime/CyclicModuleRecord.cpp | 7 +++++++ Source/JavaScriptCore/runtime/JSGlobalObject.cpp | 4 ++++ Source/JavaScriptCore/runtime/JSModuleEnvironment.cpp | 2 +- Source/JavaScriptCore/runtime/JSModuleLoader.cpp | 4 ++++ Source/JavaScriptCore/runtime/JSModuleRecord.cpp | 6 ++++++ Source/JavaScriptCore/runtime/ModuleGraphInstance.cpp | 1 + Source/JavaScriptCore/runtime/SyntheticModuleRecord.cpp | 9 +++++++++ 7 files changed, 32 insertions(+), 1 deletion(-) diff --git a/Source/JavaScriptCore/runtime/CyclicModuleRecord.cpp b/Source/JavaScriptCore/runtime/CyclicModuleRecord.cpp index 88eb74279328..811ecde9b0e9 100644 --- a/Source/JavaScriptCore/runtime/CyclicModuleRecord.cpp +++ b/Source/JavaScriptCore/runtime/CyclicModuleRecord.cpp @@ -293,8 +293,15 @@ void CyclicModuleRecord::initializeEnvironment(JSGlobalObject* globalObject, Ref if (Options::useModuleGraphInstances()) { Vector importedRecords; for (const auto& [key, in] : importEntries()) { +#if USE(BUN_JSC_ADDITIONS) + // SingleTypeScript: a named import that may be absent; when it + // does resolve it is bound (and needs a slot) exactly like Single. + if (in.type != ImportEntryType::Single && in.type != ImportEntryType::SingleTypeScript) + continue; +#else if (in.type != ImportEntryType::Single) continue; +#endif AbstractModuleRecord* importedModule = hostResolveImportedModule(globalObject, in.moduleRequest, in.moduleRequestType); RETURN_IF_EXCEPTION(scope, void()); Resolution resolution = importedModule->resolveExport(globalObject, in.importName); diff --git a/Source/JavaScriptCore/runtime/JSGlobalObject.cpp b/Source/JavaScriptCore/runtime/JSGlobalObject.cpp index a17527249c4d..a25738c6c2d5 100644 --- a/Source/JavaScriptCore/runtime/JSGlobalObject.cpp +++ b/Source/JavaScriptCore/runtime/JSGlobalObject.cpp @@ -4108,6 +4108,10 @@ void JSGlobalObject::configureModuleScopeOverlay(const Vector& names overlaid.append(name); values.append(value); } + if (values.hasOverflowed()) [[unlikely]] { + throwOutOfMemoryError(this, scope); + return; + } // 2. The symbol table every overlay shares. Slot 0 holds the module graph // instance an overlay belongs to (empty in the primary graph's), so code diff --git a/Source/JavaScriptCore/runtime/JSModuleEnvironment.cpp b/Source/JavaScriptCore/runtime/JSModuleEnvironment.cpp index 05e333614c19..79c1747db2d3 100644 --- a/Source/JavaScriptCore/runtime/JSModuleEnvironment.cpp +++ b/Source/JavaScriptCore/runtime/JSModuleEnvironment.cpp @@ -127,7 +127,7 @@ JSModuleEnvironment* JSModuleEnvironment::importedEnvironmentFor(JSGlobalObject* return this; std::optional slotIndex = record ? record->importSlotIndexFor(exporter) : std::nullopt; if (slotIndex) { - if (JSValue filled = importSlot(*slotIndex).get(); filled.isCell()) + if (JSValue filled = importSlot(*slotIndex).get(); filled && filled.isCell()) return uncheckedDowncast(filled); } JSModuleEnvironment* environment = exporter->graphInstanceEnvironment(globalObject, instance, true); diff --git a/Source/JavaScriptCore/runtime/JSModuleLoader.cpp b/Source/JavaScriptCore/runtime/JSModuleLoader.cpp index 48d6e12a5442..d8652b0f0326 100644 --- a/Source/JavaScriptCore/runtime/JSModuleLoader.cpp +++ b/Source/JavaScriptCore/runtime/JSModuleLoader.cpp @@ -1470,6 +1470,10 @@ JSPromise* JSModuleLoader::makeModule(JSGlobalObject* globalObject, const Identi // primary's are produced later, if the primary ever links to them. for (unsigned i = 0; i < args.size(); ++i) primaryValues.append(JSValue()); + if (primaryValues.hasOverflowed()) [[unlikely]] { + throwOutOfMemoryError(globalObject, scope); + return promise->rejectWithCaughtException(vm, scope); + } } auto* moduleRecord = SyntheticModuleRecord::tryCreateWithExportNamesAndValues(globalObject, moduleKey, exportNames, loadingInstance ? primaryValues : args, lazyExportsSource ? lazyExportsSource : (loadingInstance ? globalObject->globalThis() : nullptr)); RETURN_IF_EXCEPTION(scope, promise->rejectWithCaughtException(vm, scope)); diff --git a/Source/JavaScriptCore/runtime/JSModuleRecord.cpp b/Source/JavaScriptCore/runtime/JSModuleRecord.cpp index 2066890d78d8..1a90b1d3720d 100644 --- a/Source/JavaScriptCore/runtime/JSModuleRecord.cpp +++ b/Source/JavaScriptCore/runtime/JSModuleRecord.cpp @@ -480,6 +480,12 @@ JSPromise* JSModuleRecord::instantiateIntoGraphInstanceAsync(JSGlobalObject* glo } promises.append(promise); } + if (promises.hasOverflowed()) [[unlikely]] { + throwOutOfMemoryError(globalObject, scope); + JSPromise* rejected = JSPromise::create(vm, globalObject->promiseStructure()); + rejected->rejectWithCaughtException(vm, scope); + return rejected; + } // SafePerformPromiseAll: an AND-join through internal microtasks (first // rejection rejects). Nothing here is script-observable or Strong<>-rooted. JSPromise* result = JSPromise::create(vm, globalObject->promiseStructure()); diff --git a/Source/JavaScriptCore/runtime/ModuleGraphInstance.cpp b/Source/JavaScriptCore/runtime/ModuleGraphInstance.cpp index 4662c350b27c..2b5eb5620cbd 100644 --- a/Source/JavaScriptCore/runtime/ModuleGraphInstance.cpp +++ b/Source/JavaScriptCore/runtime/ModuleGraphInstance.cpp @@ -199,6 +199,7 @@ void ModuleGraphInstance::clear(JSGlobalObject* globalObject) } m_records.clear(); } + RELEASE_ASSERT(!pending.hasOverflowed()); if (pending.isEmpty()) return; // May run as a deferred clear when an evaluation step unwinds with an diff --git a/Source/JavaScriptCore/runtime/SyntheticModuleRecord.cpp b/Source/JavaScriptCore/runtime/SyntheticModuleRecord.cpp index e932597176c1..02305ee4e199 100644 --- a/Source/JavaScriptCore/runtime/SyntheticModuleRecord.cpp +++ b/Source/JavaScriptCore/runtime/SyntheticModuleRecord.cpp @@ -272,6 +272,11 @@ void SyntheticModuleRecord::materializePrimaryIfPending(JSGlobalObject* globalOb m_primaryPending = true; // a later use retries return; } + if (values.hasOverflowed()) [[unlikely]] { + m_primaryPending = true; + throwOutOfMemoryError(globalObject, scope); + return; + } JSModuleEnvironment* environment = moduleEnvironment(); SymbolTable* symbolTable = environment->symbolTable(); for (const auto& [key, entry] : exportEntries()) { @@ -322,6 +327,10 @@ JSModuleEnvironment* SyntheticModuleRecord::createGraphInstanceEnvironment(JSGlo Vector names; m_provider->generate(globalObject, moduleKey(), names, values); RETURN_IF_EXCEPTION(scope, nullptr); + if (values.hasOverflowed()) [[unlikely]] { + throwOutOfMemoryError(globalObject, scope); + return nullptr; + } SymbolTable* symbolTable = primary->symbolTable(); for (const auto& [key, entry] : exportEntries()) { SymbolTableEntry::Fast symbolEntry = symbolTable->get(entry.localName.impl()); From 2c9a5499a4761aa63f0ac71ccbd57f88ab3b4385 Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Thu, 27 Aug 2026 19:41:09 +0000 Subject: [PATCH 08/16] [JSC] Module graph instances: resume an instance's TLA module body in its record's realm (consistency with the sibling branches) --- Source/JavaScriptCore/runtime/JSMicrotask.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/JavaScriptCore/runtime/JSMicrotask.cpp b/Source/JavaScriptCore/runtime/JSMicrotask.cpp index 75e62c071d47..601c642adffc 100644 --- a/Source/JavaScriptCore/runtime/JSMicrotask.cpp +++ b/Source/JavaScriptCore/runtime/JSMicrotask.cpp @@ -2309,7 +2309,7 @@ void runInternalMicrotask(JSGlobalObject* globalObject, VM& vm, InternalMicrotas AsyncContextSwapScope asyncContextScope(vm, globalObject, AsyncContextSwapScope::unwrapContextTuple(contextArg)); #endif if (auto* recordInstance = dynamicDowncast(contextArg)) - RELEASE_AND_RETURN(scope, asyncModuleExecutionResume(globalObject, vm, recordInstance, arguments[1], static_cast(payload))); + RELEASE_AND_RETURN(scope, asyncModuleExecutionResume(uncheckedDowncast(recordInstance->record())->realm(), vm, recordInstance, arguments[1], static_cast(payload))); auto* module = uncheckedDowncast(contextArg); RELEASE_AND_RETURN(scope, asyncModuleExecutionResume(module->realm(), vm, module, arguments[1], static_cast(payload))); } From e8688046ddaa12cbb396cefcb03a015751f99b23 Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Thu, 27 Aug 2026 20:18:04 +0000 Subject: [PATCH 09/16] [JSC] Module graph instances: skip export entries without a symbol table slot in the regenerate path too --- Source/JavaScriptCore/runtime/SyntheticModuleRecord.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/JavaScriptCore/runtime/SyntheticModuleRecord.cpp b/Source/JavaScriptCore/runtime/SyntheticModuleRecord.cpp index 02305ee4e199..c27c7b1068bf 100644 --- a/Source/JavaScriptCore/runtime/SyntheticModuleRecord.cpp +++ b/Source/JavaScriptCore/runtime/SyntheticModuleRecord.cpp @@ -334,6 +334,8 @@ JSModuleEnvironment* SyntheticModuleRecord::createGraphInstanceEnvironment(JSGlo SymbolTable* symbolTable = primary->symbolTable(); for (const auto& [key, entry] : exportEntries()) { SymbolTableEntry::Fast symbolEntry = symbolTable->get(entry.localName.impl()); + if (symbolEntry.isNull()) + continue; JSValue value = jsUndefined(); for (unsigned i = 0; i < names.size(); ++i) { if (names[i] == entry.localName) { From 1b7be2f931242cb5cd66ab1d46093b37a0f34b53 Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Thu, 27 Aug 2026 20:23:17 +0000 Subject: [PATCH 10/16] [JSC] Module graph instances: review fixes (5) - LOLJIT: import-slot resolution for ModuleVar (same shape as baseline) instead of asserting the option is off. - executeModuleProgram: pick the state field by the generator object's type. - Import-slot collection dedups with a set; forEachAsyncParentModule looks the record instance up once. - createModuleScopeOverlay reads the supplied bindings as own properties. - The primary import() loading bracket sits inside the embedder guard with the rest of the synchronous-loader path. - Dependency join asserts a positive count; makeModule checks the generated args for overflow; overrideExportValue restores m_isOverridingValue on every exit (SetForScope); BusyScope is non-copyable; $vm.instantiateModuleGraph rejects a non-instance second argument. --- .../interpreter/Interpreter.cpp | 7 +++--- Source/JavaScriptCore/lol/LOLJIT.cpp | 22 +++++++++++++++++-- .../runtime/CyclicModuleRecord.cpp | 6 +++-- .../JavaScriptCore/runtime/JSGlobalObject.cpp | 7 +++++- .../runtime/JSGlobalObjectFunctions.cpp | 3 +-- Source/JavaScriptCore/runtime/JSMicrotask.cpp | 4 +++- .../JavaScriptCore/runtime/JSModuleLoader.cpp | 4 ++++ .../runtime/JSModuleNamespaceObject.cpp | 4 ++-- .../runtime/ModuleGraphInstance.h | 1 + Source/JavaScriptCore/tools/JSDollarVM.cpp | 8 +++++-- 10 files changed, 51 insertions(+), 15 deletions(-) diff --git a/Source/JavaScriptCore/interpreter/Interpreter.cpp b/Source/JavaScriptCore/interpreter/Interpreter.cpp index bd365ec0287f..b17456005573 100644 --- a/Source/JavaScriptCore/interpreter/Interpreter.cpp +++ b/Source/JavaScriptCore/interpreter/Interpreter.cpp @@ -1744,9 +1744,10 @@ JSValue Interpreter::executeModuleProgram(JSModuleRecord* record, JSObject* gene ProtoCallFrame protoCallFrame; auto stateField = [&]() -> WriteBarrier& { - if (generatorState == record) - return record->internalField(JSModuleRecord::Field::State); - return uncheckedDowncast(generatorState)->internalField(ModuleRecordInstance::Field::State); + if (auto* recordInstance = dynamicDowncast(generatorState)) + return recordInstance->internalField(ModuleRecordInstance::Field::State); + ASSERT(generatorState == record); + return record->internalField(JSModuleRecord::Field::State); }; EncodedJSValue args[numberOfArguments] = { JSValue::encode(generatorState), diff --git a/Source/JavaScriptCore/lol/LOLJIT.cpp b/Source/JavaScriptCore/lol/LOLJIT.cpp index e64772feaa84..83e885699e32 100644 --- a/Source/JavaScriptCore/lol/LOLJIT.cpp +++ b/Source/JavaScriptCore/lol/LOLJIT.cpp @@ -3598,8 +3598,26 @@ void LOLJIT::emit_op_resolve_scope(const JSInstruction* currentInstruction) // resolve type. if (profiledResolveType == ModuleVar) { - RELEASE_ASSERT(!Options::useModuleGraphInstances()); // import slots not implemented here - loadPtrFromMetadata(bytecode, Metadata::offsetOfLexicalEnvironment(), destRegs.payloadGPR()); + if (!Options::useModuleGraphInstances()) + loadPtrFromMetadata(bytecode, Metadata::offsetOfLexicalEnvironment(), destRegs.payloadGPR()); + else { + // Module graph instances (see JIT::emit_op_resolve_scope): slot index + // from metadata; 0 = constant environment, otherwise walk to the + // importing module environment and load its import slot (empty = slow). + load32FromMetadata(bytecode, Metadata::offsetOfModuleImportSlot(), s_scratch); + Jump hasImportSlot = branchTest32(NonZero, s_scratch); + loadPtrFromMetadata(bytecode, Metadata::offsetOfLexicalEnvironment(), destRegs.payloadGPR()); + Jump done = jump(); + hasImportSlot.link(this); + move(scopeRegs.payloadGPR(), destRegs.payloadGPR()); + unsigned localScopeDepth = bytecode.metadata(m_profiledCodeBlock).m_localScopeDepth; + for (unsigned index = 0; index < localScopeDepth; ++index) + loadPtr(Address(destRegs.payloadGPR(), JSScope::offsetOfNext()), destRegs.payloadGPR()); + sub32(TrustedImm32(1), s_scratch); + load64(BaseIndex(destRegs.payloadGPR(), s_scratch, TimesEight, JSLexicalEnvironment::offsetOfVariables()), destRegs.payloadGPR()); + addSlowCase(branchIfEmpty(destRegs.payloadGPR())); + done.link(this); + } } else if (profiledResolveType == ClosureVar) { move(scopeRegs.payloadGPR(), destRegs.payloadGPR()); unsigned localScopeDepth = bytecode.metadata(m_profiledCodeBlock).m_localScopeDepth; diff --git a/Source/JavaScriptCore/runtime/CyclicModuleRecord.cpp b/Source/JavaScriptCore/runtime/CyclicModuleRecord.cpp index 811ecde9b0e9..588cf9681252 100644 --- a/Source/JavaScriptCore/runtime/CyclicModuleRecord.cpp +++ b/Source/JavaScriptCore/runtime/CyclicModuleRecord.cpp @@ -183,7 +183,8 @@ void CyclicModuleRecord::setTopLevelCapability(VM& vm, ModuleGraphInstance* inst template void CyclicModuleRecord::forEachAsyncParentModule(ModuleGraphInstance* instance, const Functor& functor) const { - const Vector>& parents = recordInstanceFor(this, instance) ? recordInstanceFor(this, instance)->asyncParentModules() : asyncParentModules(); + ModuleRecordInstance* state = recordInstanceFor(this, instance); + const Vector>& parents = state ? state->asyncParentModules() : asyncParentModules(); for (const WriteBarrier& parent : parents) functor(uncheckedDowncast(parent.get())); } @@ -292,6 +293,7 @@ void CyclicModuleRecord::initializeEnvironment(JSGlobalObject* globalObject, Ref // of the named imports) before the environment is sized. if (Options::useModuleGraphInstances()) { Vector importedRecords; + UncheckedKeyHashSet seenRecords; for (const auto& [key, in] : importEntries()) { #if USE(BUN_JSC_ADDITIONS) // SingleTypeScript: a named import that may be absent; when it @@ -308,7 +310,7 @@ void CyclicModuleRecord::initializeEnvironment(JSGlobalObject* globalObject, Ref RETURN_IF_EXCEPTION(scope, void()); if (resolution.type != Resolution::Type::Resolved) continue; - if (!importedRecords.contains(resolution.moduleRecord)) + if (seenRecords.add(resolution.moduleRecord).isNewEntry) importedRecords.append(resolution.moduleRecord); } setImportedRecords(vm, importedRecords); diff --git a/Source/JavaScriptCore/runtime/JSGlobalObject.cpp b/Source/JavaScriptCore/runtime/JSGlobalObject.cpp index a25738c6c2d5..746f34135e67 100644 --- a/Source/JavaScriptCore/runtime/JSGlobalObject.cpp +++ b/Source/JavaScriptCore/runtime/JSGlobalObject.cpp @@ -4179,8 +4179,13 @@ JSLexicalEnvironment* JSGlobalObject::createModuleScopeOverlay(JSObject* values, } JSValue value; if (values) { - value = values->get(this, name); + PropertySlot slot(values, PropertySlot::InternalMethodType::GetOwnProperty); + bool hasOwn = values->methodTable()->getOwnPropertySlot(values, this, name, slot); RETURN_IF_EXCEPTION(scope, nullptr); + if (hasOwn) { + value = slot.getValue(this, name); + RETURN_IF_EXCEPTION(scope, nullptr); + } } if (!value || value.isUndefined()) value = primary->variableAt(offset).get(); diff --git a/Source/JavaScriptCore/runtime/JSGlobalObjectFunctions.cpp b/Source/JavaScriptCore/runtime/JSGlobalObjectFunctions.cpp index fe71457094dc..45be04ee6ed1 100644 --- a/Source/JavaScriptCore/runtime/JSGlobalObjectFunctions.cpp +++ b/Source/JavaScriptCore/runtime/JSGlobalObjectFunctions.cpp @@ -834,13 +834,12 @@ JSC_DEFINE_HOST_FUNCTION(globalFuncImportModule, (JSGlobalObject* globalObject, return JSValue::encode(promise); } } -#endif - // A primary-graph import() issued by host code that runs during an // instance's load is the primary's, not that instance's. std::optional primaryLoading; if (Options::useModuleGraphInstances() && globalObject->currentGraphInstanceForLoading()) [[unlikely]] primaryLoading.emplace(globalObject, nullptr); +#endif auto* importPromise = globalObject->moduleLoader()->importModule(globalObject, specifier, parameters, sourceOrigin, deferred); if (scope.exception()) [[unlikely]] return rejectWithCaughtException(); diff --git a/Source/JavaScriptCore/runtime/JSMicrotask.cpp b/Source/JavaScriptCore/runtime/JSMicrotask.cpp index 601c642adffc..6231193824dd 100644 --- a/Source/JavaScriptCore/runtime/JSMicrotask.cpp +++ b/Source/JavaScriptCore/runtime/JSMicrotask.cpp @@ -1696,7 +1696,9 @@ static void moduleGraphInstanceDependencySettled(JSGlobalObject* globalObject, V resultPromise->reject(vm, arguments[1]); // first rejection wins return; } - uint64_t remaining = joinContext->remainingElementsCount() - 1; + uint64_t count = joinContext->remainingElementsCount(); + ASSERT(count > 0); + uint64_t remaining = count - 1; joinContext->setRemainingElementsCount(remaining); if (!remaining) resultPromise->resolve(globalObject, vm, jsUndefined()); diff --git a/Source/JavaScriptCore/runtime/JSModuleLoader.cpp b/Source/JavaScriptCore/runtime/JSModuleLoader.cpp index d8652b0f0326..c052670a212e 100644 --- a/Source/JavaScriptCore/runtime/JSModuleLoader.cpp +++ b/Source/JavaScriptCore/runtime/JSModuleLoader.cpp @@ -1462,6 +1462,10 @@ JSPromise* JSModuleLoader::makeModule(JSGlobalObject* globalObject, const Identi Vector exportNames; JSObject* lazyExportsSource = syntheticSourceProvider->generate(globalObject, moduleKey, exportNames, args); RETURN_IF_EXCEPTION(scope, promise->rejectWithCaughtException(vm, scope)); + if (args.hasOverflowed()) [[unlikely]] { + throwOutOfMemoryError(globalObject, scope); + return promise->rejectWithCaughtException(vm, scope); + } ModuleGraphInstance* loadingInstance = syntheticSourceProvider->regeneratesPerGraphInstance() ? globalObject->currentGraphInstanceForLoading() : nullptr; MarkedArgumentBuffer primaryValues; diff --git a/Source/JavaScriptCore/runtime/JSModuleNamespaceObject.cpp b/Source/JavaScriptCore/runtime/JSModuleNamespaceObject.cpp index 6eb29f33f8a8..0e373d1275fd 100644 --- a/Source/JavaScriptCore/runtime/JSModuleNamespaceObject.cpp +++ b/Source/JavaScriptCore/runtime/JSModuleNamespaceObject.cpp @@ -29,6 +29,7 @@ #include "AbstractModuleRecord.h" #include "CyclicModuleRecord.h" #include "JSCInlines.h" +#include #include "JSModuleEnvironment.h" #include "ModuleGraphInstance.h" #include "JSModuleRecord.h" @@ -499,7 +500,7 @@ bool JSModuleNamespaceObject::overrideExportValue(JSGlobalObject* globalObject, RETURN_IF_EXCEPTION(scope, false); bool putResult = false; - moduleNamespaceObject->m_isOverridingValue = true; + SetForScope overridingValue(moduleNamespaceObject->m_isOverridingValue, true); JSModuleEnvironment* moduleEnvironment = instance ? environmentFor(globalObject, record) : record->moduleEnvironmentMayBeNull(); RETURN_IF_EXCEPTION(scope, {}); if (moduleEnvironment) { @@ -509,7 +510,6 @@ bool JSModuleNamespaceObject::overrideExportValue(JSGlobalObject* globalObject, JSC::PutPropertySlot putter = JSC::PutPropertySlot(moduleNamespaceObject, false); putResult = moduleNamespaceObject->put(moduleNamespaceObject, globalObject, name, value, putter); RETURN_IF_EXCEPTION(scope, {}); - moduleNamespaceObject->m_isOverridingValue = false; return putResult; } diff --git a/Source/JavaScriptCore/runtime/ModuleGraphInstance.h b/Source/JavaScriptCore/runtime/ModuleGraphInstance.h index dff77f546502..6304088dec4e 100644 --- a/Source/JavaScriptCore/runtime/ModuleGraphInstance.h +++ b/Source/JavaScriptCore/runtime/ModuleGraphInstance.h @@ -167,6 +167,7 @@ class ModuleGraphInstance final : public JSDestructibleObject { // an asynchronous completion or resumption); a clear() requested meanwhile // is performed when the outermost step returns. class BusyScope { + WTF_MAKE_NONCOPYABLE(BusyScope); public: BusyScope(JSGlobalObject* globalObject, ModuleGraphInstance* instance) : m_globalObject(globalObject), m_instance(instance) diff --git a/Source/JavaScriptCore/tools/JSDollarVM.cpp b/Source/JavaScriptCore/tools/JSDollarVM.cpp index 550366f790d1..3b1f30b7a09b 100644 --- a/Source/JavaScriptCore/tools/JSDollarVM.cpp +++ b/Source/JavaScriptCore/tools/JSDollarVM.cpp @@ -3931,9 +3931,13 @@ JSC_DEFINE_HOST_FUNCTION(functionInstantiateModuleGraph, (JSGlobalObject* global auto* record = dynamicDowncast(ns->moduleRecord()); if (!record) return throwVMTypeError(globalObject, scope, "namespace does not belong to a source text module"_s); - auto* instance = dynamicDowncast(callFrame->argument(1)); - if (!instance) + JSValue instanceValue = callFrame->argument(1); + auto* instance = dynamicDowncast(instanceValue); + if (!instance) { + if (!instanceValue.isUndefined()) + return throwVMTypeError(globalObject, scope, "expected a ModuleGraphInstance"_s); instance = ModuleGraphInstance::create(vm, globalObject, nullptr); + } JSModuleEnvironment* environment = record->instantiateIntoGraphInstance(globalObject, instance); RETURN_IF_EXCEPTION(scope, {}); JSModuleNamespaceObject* namespaceObject = record->getModuleNamespace(globalObject, instance); From caa81f4279ca6c0098fe6043a771b03bface3ac4 Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Thu, 27 Aug 2026 21:25:46 +0000 Subject: [PATCH 11/16] [JSC] Module graph instances: one ModuleVar slow path for LLInt, baseline and LOL JSModuleEnvironment::resolveModuleVarScope holds the import-slot slow path; slow_path_resolve_scope, operationResolveScopeForBaseline and operationResolveScopeForLOL (previously not updated, so the LOL fast path's slow case asserted / resolved the importing environment) all call it. --- Source/JavaScriptCore/jit/JITOperations.cpp | 10 +--------- Source/JavaScriptCore/lol/LOLJITOperations.cpp | 15 +++++++++++---- Source/JavaScriptCore/runtime/CommonSlowPaths.cpp | 12 +----------- .../runtime/JSModuleEnvironment.cpp | 13 +++++++++++++ .../JavaScriptCore/runtime/JSModuleEnvironment.h | 5 +++++ 5 files changed, 31 insertions(+), 24 deletions(-) diff --git a/Source/JavaScriptCore/jit/JITOperations.cpp b/Source/JavaScriptCore/jit/JITOperations.cpp index 8185c2a7dff8..baa469cff4fe 100644 --- a/Source/JavaScriptCore/jit/JITOperations.cpp +++ b/Source/JavaScriptCore/jit/JITOperations.cpp @@ -4596,15 +4596,7 @@ JSC_DEFINE_JIT_OPERATION(operationResolveScopeForBaseline, EncodedJSValue, (JSGl auto& metadata = bytecode.metadata(codeBlock); if (metadata.m_resolveType == ModuleVar) { - // See slow_path_resolve_scope: the importing module environment on this - // scope chain decides which graph instance's exporter environment to use. - JSModuleEnvironment* linkedExporter = uncheckedDowncast(metadata.m_lexicalEnvironment.get()); - JSScope* cursor = environment; - for (unsigned i = 0; i < metadata.m_localScopeDepth; ++i) - cursor = cursor->next(); - JSObject* result = linkedExporter; - if (auto* importer = dynamicDowncast(cursor); importer && importer->graphInstance()) - result = importer->importedEnvironmentFor(globalObject, linkedExporter->moduleRecord()); + JSObject* result = JSModuleEnvironment::resolveModuleVarScope(globalObject, environment, metadata.m_localScopeDepth, uncheckedDowncast(metadata.m_lexicalEnvironment.get())); OPERATION_RETURN_IF_EXCEPTION(scope, encodedJSValue()); OPERATION_RETURN(scope, JSValue::encode(result)); } diff --git a/Source/JavaScriptCore/lol/LOLJITOperations.cpp b/Source/JavaScriptCore/lol/LOLJITOperations.cpp index e786d8175331..b70fdda4e751 100644 --- a/Source/JavaScriptCore/lol/LOLJITOperations.cpp +++ b/Source/JavaScriptCore/lol/LOLJITOperations.cpp @@ -37,6 +37,7 @@ #include "DFGOSREntry.h" #include "DFGThunks.h" #include "Debugger.h" +#include "JSModuleEnvironment.h" #include "EnsureStillAliveHere.h" #include "ExceptionFuzz.h" #include "FrameTracers.h" @@ -85,17 +86,23 @@ JSC_DEFINE_JIT_OPERATION(operationResolveScopeForLOL, EncodedJSValue, (CallFrame const JSInstruction* pc = codeBlock->instructionAt(BytecodeIndex(bytecodeOffset)); auto bytecode = pc->as(); + auto& metadata = bytecode.metadata(codeBlock); + + // ModuleVar reaches here only with module graph instances (an unfilled + // import slot); see slow_path_resolve_scope. + if (metadata.m_resolveType == ModuleVar) { + JSObject* result = JSModuleEnvironment::resolveModuleVarScope(globalObject, environment, metadata.m_localScopeDepth, uncheckedDowncast(metadata.m_lexicalEnvironment.get())); + OPERATION_RETURN_IF_EXCEPTION(scope, encodedJSValue()); + OPERATION_RETURN(scope, JSValue::encode(result)); + } + const Identifier& ident = codeBlock->identifier(bytecode.m_var); JSObject* resolvedScope = JSScope::resolve(globalObject, environment, ident); // Proxy can throw an error here, e.g. Proxy in with statement's @unscopables. OPERATION_RETURN_IF_EXCEPTION(scope, encodedJSValue()); - auto& metadata = bytecode.metadata(codeBlock); ResolveType resolveType = metadata.m_resolveType; - // ModuleVar does not keep the scope register value alive in DFG. - ASSERT(resolveType != ModuleVar); - switch (resolveType) { case GlobalProperty: case GlobalPropertyWithVarInjectionChecks: diff --git a/Source/JavaScriptCore/runtime/CommonSlowPaths.cpp b/Source/JavaScriptCore/runtime/CommonSlowPaths.cpp index 9a7f5e6fa221..8d5142080910 100644 --- a/Source/JavaScriptCore/runtime/CommonSlowPaths.cpp +++ b/Source/JavaScriptCore/runtime/CommonSlowPaths.cpp @@ -1393,17 +1393,7 @@ JSC_DEFINE_COMMON_SLOW_PATH(slow_path_resolve_scope) JSScope* scope = callFrame->uncheckedR(bytecode.m_scope).Register::scope(); if (metadata.m_resolveType == ModuleVar) { - // The CodeBlock was linked against one instantiation of the importing - // module; other instantiations of the same graph share it. Walk to the - // importing module environment on THIS scope chain and pick the - // exporter's environment from the same graph instance. - JSModuleEnvironment* linkedExporter = uncheckedDowncast(metadata.m_lexicalEnvironment.get()); - JSScope* cursor = scope; - for (unsigned i = 0; i < metadata.m_localScopeDepth; ++i) - cursor = cursor->next(); - JSObject* result = linkedExporter; - if (auto* importer = dynamicDowncast(cursor); importer && importer->graphInstance()) - result = importer->importedEnvironmentFor(globalObject, linkedExporter->moduleRecord()); + JSObject* result = JSModuleEnvironment::resolveModuleVarScope(globalObject, scope, metadata.m_localScopeDepth, uncheckedDowncast(metadata.m_lexicalEnvironment.get())); CHECK_EXCEPTION(); RETURN(result); } diff --git a/Source/JavaScriptCore/runtime/JSModuleEnvironment.cpp b/Source/JavaScriptCore/runtime/JSModuleEnvironment.cpp index 79c1747db2d3..8f8e3205e9d2 100644 --- a/Source/JavaScriptCore/runtime/JSModuleEnvironment.cpp +++ b/Source/JavaScriptCore/runtime/JSModuleEnvironment.cpp @@ -140,6 +140,19 @@ JSModuleEnvironment* JSModuleEnvironment::importedEnvironmentFor(JSGlobalObject* return environment; } +JSObject* JSModuleEnvironment::resolveModuleVarScope(JSGlobalObject* globalObject, JSScope* scope, unsigned depth, JSModuleEnvironment* linkedExporter) +{ + // The CodeBlock was linked against one instantiation of the importing + // module; other instantiations share it. The importing module environment + // on THIS scope chain decides which instance's exporter environment applies. + JSScope* cursor = scope; + for (unsigned i = 0; i < depth; ++i) + cursor = cursor->next(); + if (auto* importer = dynamicDowncast(cursor); importer && importer->graphInstance()) + return importer->importedEnvironmentFor(globalObject, linkedExporter->moduleRecord()); + return linkedExporter; +} + ModuleGraphInstance* JSModuleEnvironment::graphInstance() { JSValue value = graphInstanceSlot().get(); diff --git a/Source/JavaScriptCore/runtime/JSModuleEnvironment.h b/Source/JavaScriptCore/runtime/JSModuleEnvironment.h index ec023b36c4b7..48fefbdd3c8b 100644 --- a/Source/JavaScriptCore/runtime/JSModuleEnvironment.h +++ b/Source/JavaScriptCore/runtime/JSModuleEnvironment.h @@ -121,6 +121,11 @@ class JSModuleEnvironment final : public JSLexicalEnvironment { // The environment of `exporter` in the same graph instance as this one // (the exporter's primary environment if this is a primary environment). JSModuleEnvironment* importedEnvironmentFor(JSGlobalObject*, AbstractModuleRecord* exporter); + // op_resolve_scope slow path for a ModuleVar under module graph instances: + // walk `depth` scopes from `scope` to the importing module environment and + // return the exporter's environment in that environment's instance + // (`linkedExporter` — the one the CodeBlock was linked against — otherwise). + static JSObject* resolveModuleVarScope(JSGlobalObject*, JSScope*, unsigned depth, JSModuleEnvironment* linkedExporter); static bool getOwnPropertySlot(JSObject*, JSGlobalObject*, PropertyName, PropertySlot&); static void getOwnSpecialPropertyNames(JSObject*, JSGlobalObject*, PropertyNameArrayBuilder&, DontEnumPropertiesMode); From 35df9d70042e66e64702f5c3bd65f0b143b4dd62 Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Mon, 31 Aug 2026 20:19:16 +0000 Subject: [PATCH 12/16] [JSC] Module graph instances: review fixes (6) - createModuleScopeOverlay: an own property of the values object wins even when its value is undefined; only absent names default to the primary's. - ModuleRegistryEntry::error(IncludeEvaluationError::No) also excludes the primary's evaluation error for synthetic records with per-instance state (they regenerate in the instance); shared synthetics keep it. - JSModuleEnvironment::importSlot bounds check is a RELEASE_ASSERT. - LOL JIT resolve_scope thunk: ModuleVar takes the slow case like the baseline thunk (codegen parity; the case is not reached). - $vm.instantiateModuleGraph throws when useModuleGraphInstances is off. - ModuleRecordInstance::isTopLevelExecutionFinished: renamed to match JSModuleRecord and documented (same generator-state encoding). - Comments: callerScope, rollBackInstantiation and synthetic environments. --- Source/JavaScriptCore/interpreter/CallFrame.h | 8 ++++---- Source/JavaScriptCore/lol/LOLJIT.cpp | 2 +- Source/JavaScriptCore/runtime/JSGlobalObject.cpp | 4 +++- Source/JavaScriptCore/runtime/JSMicrotask.cpp | 2 +- Source/JavaScriptCore/runtime/JSModuleEnvironment.h | 2 +- Source/JavaScriptCore/runtime/JSModuleRecord.cpp | 7 +++++-- Source/JavaScriptCore/runtime/ModuleGraphInstance.cpp | 6 +++++- Source/JavaScriptCore/runtime/ModuleGraphInstance.h | 2 +- Source/JavaScriptCore/runtime/ModuleRegistryEntry.cpp | 7 +++++++ Source/JavaScriptCore/tools/JSDollarVM.cpp | 2 ++ 10 files changed, 30 insertions(+), 12 deletions(-) diff --git a/Source/JavaScriptCore/interpreter/CallFrame.h b/Source/JavaScriptCore/interpreter/CallFrame.h index 6312eaa76088..e51b8428b261 100644 --- a/Source/JavaScriptCore/interpreter/CallFrame.h +++ b/Source/JavaScriptCore/interpreter/CallFrame.h @@ -228,10 +228,10 @@ using JSInstruction = BaseInstruction; JS_EXPORT_PRIVATE CallFrame* callerFrame(EntryFrame*&) const; JS_EXPORT_PRIVATE SourceOrigin callerSourceOrigin(VM&); - // Module graph instances (prototype): the module environment the calling JS - // code closes over (its own for module code; via the callee's scope chain for - // functions), or null. Lets import() load into the caller's graph instance. - // The scope the calling JS code closes over (null for native callers). + // Module graph instances: the scope the calling JS code closes over (the + // module environment for module code; the callee's scope chain for + // functions), or null for native callers. Lets import() load into the + // caller's graph instance. JS_EXPORT_PRIVATE JSScope* callerScope(VM&); static constexpr ptrdiff_t callerFrameOffset() { return OBJECT_OFFSETOF(CallerFrameAndPC, callerFrame); } diff --git a/Source/JavaScriptCore/lol/LOLJIT.cpp b/Source/JavaScriptCore/lol/LOLJIT.cpp index 46295921c734..aefe4c1d9cd8 100644 --- a/Source/JavaScriptCore/lol/LOLJIT.cpp +++ b/Source/JavaScriptCore/lol/LOLJIT.cpp @@ -3841,10 +3841,10 @@ MacroAssemblerCodeRef LOLJIT::generateOpResolveScopeThunk(VM& vm emitResolveClosure(needsVarInjectionChecks(resolveType)); break; case Dynamic: + case ModuleVar: slowCase.append(jit.jump()); break; case ResolvedClosureVar: - case ModuleVar: case UnresolvedProperty: case UnresolvedPropertyWithVarInjectionChecks: RELEASE_ASSERT_NOT_REACHED(); diff --git a/Source/JavaScriptCore/runtime/JSGlobalObject.cpp b/Source/JavaScriptCore/runtime/JSGlobalObject.cpp index 746f34135e67..ab00c4cad5f7 100644 --- a/Source/JavaScriptCore/runtime/JSGlobalObject.cpp +++ b/Source/JavaScriptCore/runtime/JSGlobalObject.cpp @@ -4187,7 +4187,9 @@ JSLexicalEnvironment* JSGlobalObject::createModuleScopeOverlay(JSObject* values, RETURN_IF_EXCEPTION(scope, nullptr); } } - if (!value || value.isUndefined()) + // An own property of `values` wins even when undefined; only an absent + // name defaults to the primary's value. + if (!value) value = primary->variableAt(offset).get(); overlay->variableAt(offset).set(vm, overlay, value); } diff --git a/Source/JavaScriptCore/runtime/JSMicrotask.cpp b/Source/JavaScriptCore/runtime/JSMicrotask.cpp index 39a0ec0232d1..fb3c4f4039b3 100644 --- a/Source/JavaScriptCore/runtime/JSMicrotask.cpp +++ b/Source/JavaScriptCore/runtime/JSMicrotask.cpp @@ -996,7 +996,7 @@ void asyncModuleResolveEvaluation(JSGlobalObject* globalObject, VM& vm, ThrowSco if (result == vm.fastAsyncGeneratorSentinel()) return; - if (recordInstance->isExecutionFinished()) + if (recordInstance->isTopLevelExecutionFinished()) capability->resolve(globalObject, vm, result); else JSPromise::resolveWithInternalMicrotaskForAsyncAwait(globalObject, vm, result, InternalMicrotask::AsyncModuleExecutionResume, recordInstance); diff --git a/Source/JavaScriptCore/runtime/JSModuleEnvironment.h b/Source/JavaScriptCore/runtime/JSModuleEnvironment.h index 48fefbdd3c8b..103ca3c36020 100644 --- a/Source/JavaScriptCore/runtime/JSModuleEnvironment.h +++ b/Source/JavaScriptCore/runtime/JSModuleEnvironment.h @@ -96,7 +96,7 @@ class JSModuleEnvironment final : public JSLexicalEnvironment { unsigned importSlotCount() { return static_cast(importSlotCountSlot()); } WriteBarrierBase& importSlot(unsigned index) { - ASSERT(index < importSlotCount()); + RELEASE_ASSERT(index < importSlotCount()); return *std::bit_cast*>(std::bit_cast(this) + offsetOfImportSlot(symbolTable(), index)); } // Point every import slot at the exporter's environment in this graph diff --git a/Source/JavaScriptCore/runtime/JSModuleRecord.cpp b/Source/JavaScriptCore/runtime/JSModuleRecord.cpp index 1a90b1d3720d..3405fa521297 100644 --- a/Source/JavaScriptCore/runtime/JSModuleRecord.cpp +++ b/Source/JavaScriptCore/runtime/JSModuleRecord.cpp @@ -362,8 +362,11 @@ JSModuleEnvironment* JSModuleRecord::createInstanceEnvironment(JSGlobalObject* g } // Link() step 4.a for an instance: an instantiation that failed part-way leaves -// nothing behind, so a retry starts clean instead of evaluating half-initialised -// environments. +// no source-text environment behind, so a retry starts clean instead of +// evaluating half-initialised environments. Synthetic per-instance environments +// created on the way are complete on creation (their bindings are set when the +// environment is made) and stay in the instance, as an import() of that module +// alone would have left them. static void rollBackInstantiation(ModuleGraphInstance* instance, const Vector& created) { for (JSModuleRecord* record : created) diff --git a/Source/JavaScriptCore/runtime/ModuleGraphInstance.cpp b/Source/JavaScriptCore/runtime/ModuleGraphInstance.cpp index 2b5eb5620cbd..d6924bf29385 100644 --- a/Source/JavaScriptCore/runtime/ModuleGraphInstance.cpp +++ b/Source/JavaScriptCore/runtime/ModuleGraphInstance.cpp @@ -98,7 +98,11 @@ void ModuleRecordInstance::appendAsyncParentModule(VM& vm, AbstractModuleRecord* m_asyncParentModules.append(WriteBarrier(vm, this, record)); } -bool ModuleRecordInstance::isExecutionFinished() const +// Same encoding as JSModuleRecord::isTopLevelExecutionFinished(): Field::State is +// the module body generator's resume point — a body that ran to completion leaves +// it at Executing (nothing to resume), a body suspended at a top-level await +// stores its resume label instead. +bool ModuleRecordInstance::isTopLevelExecutionFinished() const { JSValue state = internalField(Field::State).get(); return !state.isNumber() || state.asInt32AsAnyInt() == std::to_underlying(AbstractModuleRecord::State::Executing); diff --git a/Source/JavaScriptCore/runtime/ModuleGraphInstance.h b/Source/JavaScriptCore/runtime/ModuleGraphInstance.h index 6304088dec4e..c4408a7e0ed9 100644 --- a/Source/JavaScriptCore/runtime/ModuleGraphInstance.h +++ b/Source/JavaScriptCore/runtime/ModuleGraphInstance.h @@ -92,7 +92,7 @@ class ModuleRecordInstance final : public JSInternalFieldObjectImpl<2> { JSPromise* asyncCapability() const { return m_asyncCapability.get(); } void setAsyncCapability(VM& vm, JSPromise* capability) { m_asyncCapability.setMayBeNull(vm, this, capability); } // Generator state of a module body with top-level await (Field::State). - bool isExecutionFinished() const; + bool isTopLevelExecutionFinished() const; JSModuleNamespaceObject* deferredNamespaceObject() const { return m_deferredNamespaceObject.get(); } void setDeferredNamespaceObject(VM&, JSModuleNamespaceObject*); diff --git a/Source/JavaScriptCore/runtime/ModuleRegistryEntry.cpp b/Source/JavaScriptCore/runtime/ModuleRegistryEntry.cpp index 8abe5c503c63..a8b74ec85251 100644 --- a/Source/JavaScriptCore/runtime/ModuleRegistryEntry.cpp +++ b/Source/JavaScriptCore/runtime/ModuleRegistryEntry.cpp @@ -25,6 +25,7 @@ #include "config.h" #include "ModuleRegistryEntry.h" +#include "SyntheticModuleRecord.h" #include "JSCInlines.h" #include "JSModuleLoader.h" @@ -159,6 +160,12 @@ JSValue ModuleRegistryEntry::error(JSGlobalObject* globalObject, IncludeEvaluati auto* cyclic = dynamicDowncast(m_record.get()); if (cyclic && cyclic->status() == CyclicModuleRecord::Status::Evaluated && cyclic->evaluationError()) return { }; + // A synthetic record with per-instance state is regenerated in each + // instance, so the primary's evaluation failure is not the instance's + // either; one shared with the primary graph keeps it. + auto* synthetic = dynamicDowncast(m_record.get()); + if (synthetic && synthetic->hasPerGraphInstanceState()) + return { }; } if (JSValue error = m_error.get()) { if (m_status == Status::FetchFailed) { diff --git a/Source/JavaScriptCore/tools/JSDollarVM.cpp b/Source/JavaScriptCore/tools/JSDollarVM.cpp index 93f9e5d06968..3abd81c1012c 100644 --- a/Source/JavaScriptCore/tools/JSDollarVM.cpp +++ b/Source/JavaScriptCore/tools/JSDollarVM.cpp @@ -3931,6 +3931,8 @@ JSC_DEFINE_HOST_FUNCTION(functionInstantiateModuleGraph, (JSGlobalObject* global DollarVMAssertScope assertScope; VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); + if (!Options::useModuleGraphInstances()) + return throwVMTypeError(globalObject, scope, "useModuleGraphInstances is disabled"_s); auto* ns = dynamicDowncast(callFrame->argument(0)); if (!ns) return throwVMTypeError(globalObject, scope, "expected a module namespace object"_s); From d21196e2a263f34b16a668f479e8a27849d947d3 Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Mon, 31 Aug 2026 20:22:57 +0000 Subject: [PATCH 13/16] [JSC] graphInstanceForScope: initialize *overlayOut on every path --- Source/JavaScriptCore/runtime/JSGlobalObject.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/JavaScriptCore/runtime/JSGlobalObject.cpp b/Source/JavaScriptCore/runtime/JSGlobalObject.cpp index ab00c4cad5f7..4018f353b03b 100644 --- a/Source/JavaScriptCore/runtime/JSGlobalObject.cpp +++ b/Source/JavaScriptCore/runtime/JSGlobalObject.cpp @@ -703,6 +703,8 @@ ModuleGraphInstance* JSGlobalObject::graphInstanceForScope(JSScope* scope, JSSco // an overlay (when configured) names it for non-module code scoped to the // instance. Either is decisive: the first one found ends the walk. SymbolTable* overlayTable = m_moduleScopeOverlaySymbolTable.get(); + if (overlayOut) + *overlayOut = nullptr; for (; scope; scope = scope->next()) { if (auto* moduleEnvironment = dynamicDowncast(scope)) { ModuleGraphInstance* instance = moduleEnvironment->graphInstance(); From 037935fb3232ab5abf0a00788efea923c2a3b7ec Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Tue, 1 Sep 2026 01:57:51 +0000 Subject: [PATCH 14/16] [JSC] Module graph instances: instance-aware import-promise TLA gating walk importPromiseGatesAsyncDependency (from main's dynamic-import TLA deadlock check) walked the record's own [[AsyncParentModules]]; in a graph instance those live on the ModuleRecordInstance, and an AsyncModuleExecutionResume reaction's driver is the ModuleRecordInstance rather than the record. Take the instance and use its state for both. (The instance evaluation path passes no import promise today, so this is for when it does.) --- .../runtime/AbstractModuleRecord.cpp | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/Source/JavaScriptCore/runtime/AbstractModuleRecord.cpp b/Source/JavaScriptCore/runtime/AbstractModuleRecord.cpp index 39d38ef218d3..8695b63a456c 100644 --- a/Source/JavaScriptCore/runtime/AbstractModuleRecord.cpp +++ b/Source/JavaScriptCore/runtime/AbstractModuleRecord.cpp @@ -1333,8 +1333,10 @@ static void checkSafeToRecurse(JSGlobalObject* globalObject, ThrowScope& scope) } #if USE(BUN_JSC_ADDITIONS) -static bool importPromiseGatesAsyncDependency(JSPromise* importPromise, CyclicModuleRecord* dependency) +static bool importPromiseGatesAsyncDependency(JSPromise* importPromise, CyclicModuleRecord* dependency, ModuleGraphInstance* instance) { + // In a graph instance the [[AsyncParentModules]] of a record live on its + // ModuleRecordInstance; the primary graph's are on the record. auto resumesDependency = [&](AbstractModuleRecord* module) -> bool { UncheckedKeyHashSet seen; Vector work; @@ -1345,11 +1347,23 @@ static bool importPromiseGatesAsyncDependency(JSPromise* importPromise, CyclicMo return true; if (!seen.add(current).isNewEntry) continue; - for (auto& parent : current->asyncParentModules()) + ModuleRecordInstance* state = instance ? instance->recordInstance(current) : nullptr; + for (auto& parent : state ? state->asyncParentModules() : current->asyncParentModules()) work.append(parent.get()); } return false; }; + // An AsyncModuleExecutionResume reaction's driver is the record in the + // primary graph and the ModuleRecordInstance in a graph instance; only one + // of this evaluation's instance can gate the dependency. + auto moduleForDriver = [&](JSCell* driver) -> AbstractModuleRecord* { + if (!driver) + return nullptr; + if (auto* recordInstance = dynamicDowncast(driver)) + return recordInstance->graphInstance() == instance ? recordInstance->record() : nullptr; + auto* module = dynamicDowncast(driver); + return module && !instance ? module : nullptr; + }; auto cellOf = [](JSValue value) -> JSCell* { if (value.isEmpty() || !value.isCell()) @@ -1385,13 +1399,12 @@ static bool importPromiseGatesAsyncDependency(JSPromise* importPromise, CyclicMo break; if (auto* generator = dynamicDowncast(driver)) follow(generator->context()); - else if (auto* module = dynamicDowncast(driver)) + else if (AbstractModuleRecord* module = moduleForDriver(driver)) found = resumesDependency(module); break; } case InternalMicrotask::AsyncModuleExecutionResume: { - JSCell* driver = unwrapContext(context); - if (auto* module = driver ? dynamicDowncast(driver) : nullptr) + if (AbstractModuleRecord* module = moduleForDriver(unwrapContext(context))) found = resumesDependency(module); break; } @@ -1582,7 +1595,7 @@ unsigned AbstractModuleRecord::innerModuleEvaluation(JSGlobalObject* globalObjec // 12.b.v. If requiredModule.[[AsyncEvaluationOrder]] is an integer, then if (cyclic->asyncEvaluationOrder(instance).hasOrder()) { #if USE(BUN_JSC_ADDITIONS) - if (!dynamicImportPromise || !importPromiseGatesAsyncDependency(dynamicImportPromise, cyclic)) { + if (!dynamicImportPromise || !importPromiseGatesAsyncDependency(dynamicImportPromise, cyclic, instance)) { #endif // 12.b.v.1. Set module.[[PendingAsyncDependencies]] to module.[[PendingAsyncDependencies]] + 1. module->setPendingAsyncDependencies(instance, module->pendingAsyncDependencies(instance).value() + 1); From eaee8d0b217b22a38ccda99963ab6eb4d71f5e79 Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Tue, 1 Sep 2026 02:14:19 +0000 Subject: [PATCH 15/16] [JSC] Module graph instances: review fixes (7) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - import() from instance code passes its result promise down to the instance evaluation (instantiateLoadedModuleIntoGraphInstance → instantiateIntoGraphInstanceAsync → evaluate), so main's top-level-await deadlock check (importPromiseGatesAsyncDependency) applies inside graph instances as it does in the primary graph. - import() from instance code of a Cyclic Module Record that is not a Source Text Module Record (WebAssembly) is a TypeError, as for a static import, instead of resolving with the primary graph's unevaluated namespace. --- Source/JavaScriptCore/runtime/JSMicrotask.cpp | 4 +++- Source/JavaScriptCore/runtime/JSModuleLoader.cpp | 12 ++++++++++-- Source/JavaScriptCore/runtime/JSModuleLoader.h | 2 +- Source/JavaScriptCore/runtime/JSModuleRecord.cpp | 7 ++++--- Source/JavaScriptCore/runtime/JSModuleRecord.h | 5 ++++- 5 files changed, 22 insertions(+), 8 deletions(-) diff --git a/Source/JavaScriptCore/runtime/JSMicrotask.cpp b/Source/JavaScriptCore/runtime/JSMicrotask.cpp index 03357a35370b..027276d86054 100644 --- a/Source/JavaScriptCore/runtime/JSMicrotask.cpp +++ b/Source/JavaScriptCore/runtime/JSMicrotask.cpp @@ -1656,7 +1656,9 @@ static void moduleGraphInstanceLoadSettled(JSGlobalObject* globalObject, VM& vm, RETURN_IF_EXCEPTION(scope, void()); auto type = static_cast(context->getDirect(vm, vm.propertyNames->type).asInt32()); bool deferred = context->getDirect(vm, vm.propertyNames->builtinNames().deferPrivateName()).isTrue(); - JSPromise* namespacePromise = JSModuleLoader::instantiateLoadedModuleIntoGraphInstance(globalObject, key, instance, type, deferred); + // resultPromise is the import() promise the importing code awaits: the + // evaluation's top-level-await deadlock check needs it. + JSPromise* namespacePromise = JSModuleLoader::instantiateLoadedModuleIntoGraphInstance(globalObject, key, instance, type, deferred, resultPromise); if (scope.exception()) [[unlikely]] { resultPromise->rejectWithCaughtException(vm, scope); return; diff --git a/Source/JavaScriptCore/runtime/JSModuleLoader.cpp b/Source/JavaScriptCore/runtime/JSModuleLoader.cpp index 6d83ec39b608..9116db792cf0 100644 --- a/Source/JavaScriptCore/runtime/JSModuleLoader.cpp +++ b/Source/JavaScriptCore/runtime/JSModuleLoader.cpp @@ -499,7 +499,7 @@ JSObject* JSModuleLoader::createGraphInstanceImportContext(JSGlobalObject* globa return context; } -JSPromise* JSModuleLoader::instantiateLoadedModuleIntoGraphInstance(JSGlobalObject* globalObject, const Identifier& key, ModuleGraphInstance* instance, ScriptFetchParameters::Type type, bool deferred) +JSPromise* JSModuleLoader::instantiateLoadedModuleIntoGraphInstance(JSGlobalObject* globalObject, const Identifier& key, ModuleGraphInstance* instance, ScriptFetchParameters::Type type, bool deferred, JSPromise* dynamicImportPromise) { VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); @@ -517,13 +517,21 @@ JSPromise* JSModuleLoader::instantiateLoadedModuleIntoGraphInstance(JSGlobalObje auto phase = deferred ? AbstractModuleRecord::ModulePhase::Defer : AbstractModuleRecord::ModulePhase::Evaluation; auto* sourceRecord = dynamicDowncast(record); if (!sourceRecord) { + if (is(record)) { + // As for a static import (JSModuleRecord::createInstanceEnvironment): + // only Source Text Module Records can be instantiated per instance, + // and sharing the primary's copy of another Cyclic Module Record + // (WebAssembly) would hand out a namespace nobody evaluated. + throwTypeError(globalObject, scope, makeString("Module '"_s, key.string(), "' cannot be instantiated into a module graph instance (only JavaScript and synthetic modules can)"_s)); + return nullptr; + } // Synthetic modules: an environment in the instance when they carry // per-instance state, otherwise shared with the primary graph. JSModuleNamespaceObject* ns = record->getModuleNamespace(globalObject, instance, phase); RETURN_IF_EXCEPTION(scope, nullptr); RELEASE_AND_RETURN(scope, JSPromise::resolvedPromise(globalObject, ns)); } - JSPromise* evaluated = sourceRecord->instantiateIntoGraphInstanceAsync(globalObject, instance, phase); + JSPromise* evaluated = sourceRecord->instantiateIntoGraphInstanceAsync(globalObject, instance, phase, dynamicImportPromise); RETURN_IF_EXCEPTION(scope, nullptr); JSObject* context = createGraphInstanceImportContext(globalObject, instance, key, type, deferred); context->putDirect(vm, vm.propertyNames->value, sourceRecord); diff --git a/Source/JavaScriptCore/runtime/JSModuleLoader.h b/Source/JavaScriptCore/runtime/JSModuleLoader.h index 116e1d982552..8f61efe76cbb 100644 --- a/Source/JavaScriptCore/runtime/JSModuleLoader.h +++ b/Source/JavaScriptCore/runtime/JSModuleLoader.h @@ -111,7 +111,7 @@ class JSModuleLoader final : public JSCell { JS_EXPORT_PRIVATE static JSPromise* importIntoGraphInstance(JSGlobalObject*, JSString* specifier, JSValue parameters, const SourceOrigin& referrer, ModuleGraphInstance*, bool deferred = false); JS_EXPORT_PRIVATE static JSPromise* loadModuleForGraphInstance(JSGlobalObject*, const Identifier& key, RefPtr&&, ModuleGraphInstance*); // Resolves with the instance's namespace object once its (possibly asynchronous) evaluation completes. - JS_EXPORT_PRIVATE static JSPromise* instantiateLoadedModuleIntoGraphInstance(JSGlobalObject*, const Identifier& key, ModuleGraphInstance*, ScriptFetchParameters::Type = ScriptFetchParameters::Type::JavaScript, bool deferred = false); + JS_EXPORT_PRIVATE static JSPromise* instantiateLoadedModuleIntoGraphInstance(JSGlobalObject*, const Identifier& key, ModuleGraphInstance*, ScriptFetchParameters::Type = ScriptFetchParameters::Type::JavaScript, bool deferred = false, JSPromise* dynamicImportPromise = nullptr); static JSObject* createGraphInstanceImportContext(JSGlobalObject*, ModuleGraphInstance*, const Identifier& key, ScriptFetchParameters::Type, bool deferred); #endif JSPromise* requestImportModule(JSGlobalObject*, const Identifier& moduleName, const Identifier& referrer, RefPtr, RefPtr, bool deferred = false); diff --git a/Source/JavaScriptCore/runtime/JSModuleRecord.cpp b/Source/JavaScriptCore/runtime/JSModuleRecord.cpp index bea4f9327943..86f7794c01c5 100644 --- a/Source/JavaScriptCore/runtime/JSModuleRecord.cpp +++ b/Source/JavaScriptCore/runtime/JSModuleRecord.cpp @@ -446,7 +446,7 @@ JSModuleEnvironment* JSModuleRecord::instantiateIntoGraphInstance(JSGlobalObject return env; } -JSPromise* JSModuleRecord::instantiateIntoGraphInstanceAsync(JSGlobalObject* globalObject, ModuleGraphInstance* instance, ModulePhase phase) +JSPromise* JSModuleRecord::instantiateIntoGraphInstanceAsync(JSGlobalObject* globalObject, ModuleGraphInstance* instance, ModulePhase phase, JSPromise* dynamicImportPromise) { ModuleGraphInstance::BusyScope busy(globalObject, instance); VM& vm = globalObject->vm(); @@ -472,7 +472,7 @@ JSPromise* JSModuleRecord::instantiateIntoGraphInstanceAsync(JSGlobalObject* glo if (!cyclic) continue; #if USE(BUN_JSC_ADDITIONS) - JSPromise* promise = cyclic->evaluate(globalObject, nullptr, instance); + JSPromise* promise = cyclic->evaluate(globalObject, dynamicImportPromise, instance); #else JSPromise* promise = cyclic->evaluate(globalObject, instance); #endif @@ -502,8 +502,9 @@ JSPromise* JSModuleRecord::instantiateIntoGraphInstanceAsync(JSGlobalObject* glo return result; } #if USE(BUN_JSC_ADDITIONS) - JSPromise* promise = CyclicModuleRecord::evaluate(globalObject, nullptr, instance); + JSPromise* promise = CyclicModuleRecord::evaluate(globalObject, dynamicImportPromise, instance); #else + UNUSED_PARAM(dynamicImportPromise); JSPromise* promise = CyclicModuleRecord::evaluate(globalObject, instance); #endif if (scope.exception()) [[unlikely]] { diff --git a/Source/JavaScriptCore/runtime/JSModuleRecord.h b/Source/JavaScriptCore/runtime/JSModuleRecord.h index 8e82e047cbf0..6a2155651b93 100644 --- a/Source/JavaScriptCore/runtime/JSModuleRecord.h +++ b/Source/JavaScriptCore/runtime/JSModuleRecord.h @@ -89,7 +89,10 @@ class JSModuleRecord final : public CyclicModuleRecord { // (throws for top-level await) and returns this module's environment in the // instance; the asynchronous form returns the evaluation promise. JS_EXPORT_PRIVATE JSModuleEnvironment* instantiateIntoGraphInstance(JSGlobalObject*, ModuleGraphInstance*, ModulePhase = ModulePhase::Evaluation); - JS_EXPORT_PRIVATE JSPromise* instantiateIntoGraphInstanceAsync(JSGlobalObject*, ModuleGraphInstance*, ModulePhase = ModulePhase::Evaluation); + // `dynamicImportPromise` is the import() promise this evaluation settles, + // when there is one, for the top-level-await deadlock check in + // innerModuleEvaluation (BUN_JSC_ADDITIONS); null otherwise. + JS_EXPORT_PRIVATE JSPromise* instantiateIntoGraphInstanceAsync(JSGlobalObject*, ModuleGraphInstance*, ModulePhase = ModulePhase::Evaluation, JSPromise* dynamicImportPromise = nullptr); // ExecuteModule against this record's environment in `instance`. void executeInstance(JSGlobalObject*, ModuleRecordInstance*, JSPromise* capability); JSValue evaluateInstance(JSGlobalObject*, ModuleRecordInstance*, JSValue sentValue, JSValue resumeMode); From ef690566d1839e752caaf93099546353f88d02d6 Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Tue, 1 Sep 2026 03:48:11 +0000 Subject: [PATCH 16/16] Resolve the origin/main merge (8efef1eb2d12 was committed with conflict markers) The graph-instance evaluate/innerModuleEvaluation overloads carry (referrerAsyncOrder, dynamicImportPromise, ModuleGraphInstance*) and compare asyncEvaluationOrder(instance); loadModuleForGraphInstance's failed-entry retry check uses getRegisteredMayBeNull(key, type). --- .../runtime/AbstractModuleRecord.cpp | 22 +++++-------------- .../runtime/AbstractModuleRecord.h | 6 +---- .../runtime/CyclicModuleRecord.cpp | 12 ++-------- .../runtime/CyclicModuleRecord.h | 6 +---- .../JavaScriptCore/runtime/JSModuleLoader.cpp | 7 ++---- .../JavaScriptCore/runtime/JSModuleLoader.h | 4 ---- .../JavaScriptCore/runtime/JSModuleRecord.cpp | 8 +++---- 7 files changed, 15 insertions(+), 50 deletions(-) diff --git a/Source/JavaScriptCore/runtime/AbstractModuleRecord.cpp b/Source/JavaScriptCore/runtime/AbstractModuleRecord.cpp index 91db3860ef2f..76cd64be24bc 100644 --- a/Source/JavaScriptCore/runtime/AbstractModuleRecord.cpp +++ b/Source/JavaScriptCore/runtime/AbstractModuleRecord.cpp @@ -1178,7 +1178,7 @@ void AbstractModuleRecord::evaluateSync(JSGlobalObject* globalObject, ModuleGrap JSPromise* promise = nullptr; if (auto* cyclic = dynamicDowncast(this); cyclic && instance) { #if USE(BUN_JSC_ADDITIONS) - promise = cyclic->evaluate(globalObject, nullptr, instance); + promise = cyclic->evaluate(globalObject, -1, nullptr, instance); #else promise = cyclic->evaluate(globalObject, instance); #endif @@ -1467,11 +1467,7 @@ static bool importPromiseGatesAsyncDependency(JSPromise* importPromise, CyclicMo #endif #if USE(BUN_JSC_ADDITIONS) -<<<<<<< ours -unsigned AbstractModuleRecord::innerModuleEvaluation(JSGlobalObject* globalObject, Vector& stack, unsigned index, JSPromise* dynamicImportPromise, ModuleGraphInstance* instance) -======= -unsigned AbstractModuleRecord::innerModuleEvaluation(JSGlobalObject* globalObject, Vector& stack, unsigned index, int64_t referrerAsyncOrder, JSPromise* dynamicImportPromise) ->>>>>>> theirs +unsigned AbstractModuleRecord::innerModuleEvaluation(JSGlobalObject* globalObject, Vector& stack, unsigned index, int64_t referrerAsyncOrder, JSPromise* dynamicImportPromise, ModuleGraphInstance* instance) #else unsigned AbstractModuleRecord::innerModuleEvaluation(JSGlobalObject* globalObject, Vector& stack, unsigned index, ModuleGraphInstance* instance) #endif @@ -1551,11 +1547,7 @@ unsigned AbstractModuleRecord::innerModuleEvaluation(JSGlobalObject* globalObjec RETURN_IF_EXCEPTION(scope, invalid); // 12.a. Set index to ? InnerModuleEvaluation(requiredModule, stack, index). #if USE(BUN_JSC_ADDITIONS) -<<<<<<< ours - unsigned result = requiredModule->innerModuleEvaluation(globalObject, stack, index, dynamicImportPromise, instance); -======= - unsigned result = requiredModule->innerModuleEvaluation(globalObject, stack, index, referrerAsyncOrder, dynamicImportPromise); ->>>>>>> theirs + unsigned result = requiredModule->innerModuleEvaluation(globalObject, stack, index, referrerAsyncOrder, dynamicImportPromise, instance); #else unsigned result = requiredModule->innerModuleEvaluation(globalObject, stack, index, instance); #endif @@ -1603,14 +1595,10 @@ unsigned AbstractModuleRecord::innerModuleEvaluation(JSGlobalObject* globalObjec // 12.b.v. If requiredModule.[[AsyncEvaluationOrder]] is an integer, then if (cyclic->asyncEvaluationOrder(instance).hasOrder()) { #if USE(BUN_JSC_ADDITIONS) -<<<<<<< ours - if (!dynamicImportPromise || !importPromiseGatesAsyncDependency(dynamicImportPromise, cyclic, instance)) { -======= // referrerAsyncOrder covers an import() whose promise reaches the suspended referrer only through native code (an HTTP round trip, a captured resolver), where the walk cannot follow. - bool deadlocks = cyclic->asyncEvaluationOrder().order() == referrerAsyncOrder - || (dynamicImportPromise && importPromiseGatesAsyncDependency(dynamicImportPromise, cyclic)); + bool deadlocks = cyclic->asyncEvaluationOrder(instance).order() == referrerAsyncOrder + || (dynamicImportPromise && importPromiseGatesAsyncDependency(dynamicImportPromise, cyclic, instance)); if (!deadlocks) { ->>>>>>> theirs #endif // 12.b.v.1. Set module.[[PendingAsyncDependencies]] to module.[[PendingAsyncDependencies]] + 1. module->setPendingAsyncDependencies(instance, module->pendingAsyncDependencies(instance).value() + 1); diff --git a/Source/JavaScriptCore/runtime/AbstractModuleRecord.h b/Source/JavaScriptCore/runtime/AbstractModuleRecord.h index 699cab786dfb..da53c5bde0af 100644 --- a/Source/JavaScriptCore/runtime/AbstractModuleRecord.h +++ b/Source/JavaScriptCore/runtime/AbstractModuleRecord.h @@ -296,11 +296,7 @@ class AbstractModuleRecord : public JSInternalFieldObjectImpl<2> { void evaluateModuleSync(JSGlobalObject*); #if USE(BUN_JSC_ADDITIONS) -<<<<<<< ours - unsigned innerModuleEvaluation(JSGlobalObject*, Vector& stack, unsigned index, JSPromise* dynamicImportPromise, ModuleGraphInstance*); -======= - unsigned innerModuleEvaluation(JSGlobalObject*, Vector& stack, unsigned index, int64_t referrerAsyncOrder, JSPromise* dynamicImportPromise); ->>>>>>> theirs + unsigned innerModuleEvaluation(JSGlobalObject*, Vector& stack, unsigned index, int64_t referrerAsyncOrder, JSPromise* dynamicImportPromise, ModuleGraphInstance*); #else unsigned innerModuleEvaluation(JSGlobalObject*, Vector& stack, unsigned index, ModuleGraphInstance*); #endif diff --git a/Source/JavaScriptCore/runtime/CyclicModuleRecord.cpp b/Source/JavaScriptCore/runtime/CyclicModuleRecord.cpp index b28501d39bb8..4a4cd35795cb 100644 --- a/Source/JavaScriptCore/runtime/CyclicModuleRecord.cpp +++ b/Source/JavaScriptCore/runtime/CyclicModuleRecord.cpp @@ -593,11 +593,7 @@ void CyclicModuleRecord::link(JSGlobalObject* globalObject, RefPtr>>>>>> theirs +JSPromise* CyclicModuleRecord::evaluate(JSGlobalObject* globalObject, int64_t referrerAsyncOrder, JSPromise* dynamicImportPromise, ModuleGraphInstance* instance) #else JSPromise* CyclicModuleRecord::evaluate(JSGlobalObject* globalObject, ModuleGraphInstance* instance) #endif @@ -642,11 +638,7 @@ JSPromise* CyclicModuleRecord::evaluate(JSGlobalObject* globalObject, ModuleGrap module->setTopLevelCapability(vm, instance, capability); // 8. Let result be Completion(InnerModuleEvaluation(module, stack, 0)). #if USE(BUN_JSC_ADDITIONS) -<<<<<<< ours - module->innerModuleEvaluation(globalObject, stack, 0, dynamicImportPromise, instance); -======= - module->innerModuleEvaluation(globalObject, stack, 0, referrerAsyncOrder, dynamicImportPromise); ->>>>>>> theirs + module->innerModuleEvaluation(globalObject, stack, 0, referrerAsyncOrder, dynamicImportPromise, instance); #else module->innerModuleEvaluation(globalObject, stack, 0, instance); #endif diff --git a/Source/JavaScriptCore/runtime/CyclicModuleRecord.h b/Source/JavaScriptCore/runtime/CyclicModuleRecord.h index 8c17401ead18..1c2a6b74212a 100644 --- a/Source/JavaScriptCore/runtime/CyclicModuleRecord.h +++ b/Source/JavaScriptCore/runtime/CyclicModuleRecord.h @@ -63,11 +63,7 @@ class CyclicModuleRecord : public AbstractModuleRecord { // evaluated (null: the primary instantiation, whose state lives on the // record itself). See ModuleGraphInstance. #if USE(BUN_JSC_ADDITIONS) -<<<<<<< ours - JSPromise* evaluate(JSGlobalObject*, JSPromise* dynamicImportPromise = nullptr, ModuleGraphInstance* = nullptr); -======= - JSPromise* evaluate(JSGlobalObject*, int64_t referrerAsyncOrder = -1, JSPromise* dynamicImportPromise = nullptr); ->>>>>>> theirs + JSPromise* evaluate(JSGlobalObject*, int64_t referrerAsyncOrder = -1, JSPromise* dynamicImportPromise = nullptr, ModuleGraphInstance* = nullptr); #else JSPromise* evaluate(JSGlobalObject*, ModuleGraphInstance* = nullptr); #endif diff --git a/Source/JavaScriptCore/runtime/JSModuleLoader.cpp b/Source/JavaScriptCore/runtime/JSModuleLoader.cpp index 71594778ff4b..2ce70ad6a052 100644 --- a/Source/JavaScriptCore/runtime/JSModuleLoader.cpp +++ b/Source/JavaScriptCore/runtime/JSModuleLoader.cpp @@ -456,7 +456,6 @@ static String moduleReferrer(const Identifier& referrerKey) return referrerKey.string(); } -<<<<<<< ours #if USE(BUN_JSC_ADDITIONS) JSPromise* JSModuleLoader::loadModuleForGraphInstance(JSGlobalObject* globalObject, const Identifier& key, RefPtr&& parameters, ModuleGraphInstance* instance) { @@ -469,8 +468,9 @@ JSPromise* JSModuleLoader::loadModuleForGraphInstance(JSGlobalObject* globalObje // afresh. Evaluation failures stay (instances evaluate separately anyway). { JSModuleLoader* loader = globalObject->moduleLoader(); + auto type = parameters ? parameters->type() : ScriptFetchParameters::Type::JavaScript; Locker locker { loader->cellLock() }; - if (ModuleRegistryEntry* entry = loader->registryEntry(key)) { + if (ModuleRegistryEntry* entry = loader->getRegisteredMayBeNull(key, type)) { if (entry->hasSettledFailure()) loader->removeEntry(key); } @@ -622,10 +622,7 @@ AbstractModuleRecord* JSModuleLoader::linkWithoutEvaluating(JSGlobalObject* glob return record; } -JSPromise* JSModuleLoader::requestImportModule(JSGlobalObject* globalObject, const Identifier& moduleName, const Identifier& referrer, RefPtr parameters, RefPtr scriptFetcher, bool deferred) -======= JSPromise* JSModuleLoader::requestImportModule(JSGlobalObject* globalObject, const Identifier& moduleName, const Identifier& referrer, RefPtr parameters, RefPtr scriptFetcher, bool deferred, int64_t referrerAsyncOrder) ->>>>>>> theirs { VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); diff --git a/Source/JavaScriptCore/runtime/JSModuleLoader.h b/Source/JavaScriptCore/runtime/JSModuleLoader.h index 1a0c1b063d49..746feb95b836 100644 --- a/Source/JavaScriptCore/runtime/JSModuleLoader.h +++ b/Source/JavaScriptCore/runtime/JSModuleLoader.h @@ -100,7 +100,6 @@ class JSModuleLoader final : public JSCell { void provideFetch(JSGlobalObject*, const Identifier& key, ScriptFetchParameters::Type, JSSourceCode*); JSPromise* loadModule(JSGlobalObject*, const Identifier& moduleName, RefPtr, RefPtr, OptionSet, int64_t referrerAsyncOrder = -1, const String& referrer = { }); JSPromise* linkAndEvaluateModule(JSGlobalObject*, const Identifier& moduleKey, RefPtr, RefPtr); -<<<<<<< ours // Module graph instances (prototype): link a fetched module graph without // evaluating it, so it can serve as the template for instantiateIntoGraphInstance. JS_EXPORT_PRIVATE AbstractModuleRecord* linkWithoutEvaluating(JSGlobalObject*, const Identifier& moduleKey, RefPtr, ScriptFetchParameters::Type = ScriptFetchParameters::Type::JavaScript); @@ -115,10 +114,7 @@ class JSModuleLoader final : public JSCell { JS_EXPORT_PRIVATE static JSPromise* instantiateLoadedModuleIntoGraphInstance(JSGlobalObject*, const Identifier& key, ModuleGraphInstance*, ScriptFetchParameters::Type = ScriptFetchParameters::Type::JavaScript, bool deferred = false, JSPromise* dynamicImportPromise = nullptr); static JSObject* createGraphInstanceImportContext(JSGlobalObject*, ModuleGraphInstance*, const Identifier& key, ScriptFetchParameters::Type, bool deferred); #endif - JSPromise* requestImportModule(JSGlobalObject*, const Identifier& moduleName, const Identifier& referrer, RefPtr, RefPtr, bool deferred = false); -======= JSPromise* requestImportModule(JSGlobalObject*, const Identifier& moduleName, const Identifier& referrer, RefPtr, RefPtr, bool deferred = false, int64_t referrerAsyncOrder = -1); ->>>>>>> theirs #if USE(BUN_JSC_ADDITIONS) JS_EXPORT_PRIVATE int64_t asyncEvaluationOrderForKey(const Identifier& key); #endif diff --git a/Source/JavaScriptCore/runtime/JSModuleRecord.cpp b/Source/JavaScriptCore/runtime/JSModuleRecord.cpp index 86f7794c01c5..00b8432f6354 100644 --- a/Source/JavaScriptCore/runtime/JSModuleRecord.cpp +++ b/Source/JavaScriptCore/runtime/JSModuleRecord.cpp @@ -405,7 +405,7 @@ JSModuleEnvironment* JSModuleRecord::instantiateIntoGraphInstance(JSGlobalObject if (!cyclic) continue; #if USE(BUN_JSC_ADDITIONS) - JSPromise* promise = cyclic->evaluate(globalObject, nullptr, instance); + JSPromise* promise = cyclic->evaluate(globalObject, -1, nullptr, instance); #else JSPromise* promise = cyclic->evaluate(globalObject, instance); #endif @@ -425,7 +425,7 @@ JSModuleEnvironment* JSModuleRecord::instantiateIntoGraphInstance(JSGlobalObject return env; } #if USE(BUN_JSC_ADDITIONS) - JSPromise* promise = CyclicModuleRecord::evaluate(globalObject, nullptr, instance); + JSPromise* promise = CyclicModuleRecord::evaluate(globalObject, -1, nullptr, instance); #else JSPromise* promise = CyclicModuleRecord::evaluate(globalObject, instance); #endif @@ -472,7 +472,7 @@ JSPromise* JSModuleRecord::instantiateIntoGraphInstanceAsync(JSGlobalObject* glo if (!cyclic) continue; #if USE(BUN_JSC_ADDITIONS) - JSPromise* promise = cyclic->evaluate(globalObject, dynamicImportPromise, instance); + JSPromise* promise = cyclic->evaluate(globalObject, -1, dynamicImportPromise, instance); #else JSPromise* promise = cyclic->evaluate(globalObject, instance); #endif @@ -502,7 +502,7 @@ JSPromise* JSModuleRecord::instantiateIntoGraphInstanceAsync(JSGlobalObject* glo return result; } #if USE(BUN_JSC_ADDITIONS) - JSPromise* promise = CyclicModuleRecord::evaluate(globalObject, dynamicImportPromise, instance); + JSPromise* promise = CyclicModuleRecord::evaluate(globalObject, -1, dynamicImportPromise, instance); #else UNUSED_PARAM(dynamicImportPromise); JSPromise* promise = CyclicModuleRecord::evaluate(globalObject, instance);