diff --git a/Source/JavaScriptCore/parser/SourceProvider.h b/Source/JavaScriptCore/parser/SourceProvider.h index 238f38ccb192c..2c3aaf1dfcec9 100644 --- a/Source/JavaScriptCore/parser/SourceProvider.h +++ b/Source/JavaScriptCore/parser/SourceProvider.h @@ -176,10 +176,20 @@ class StringSourceProvider : public SourceProvider { class SyntheticSourceProvider final : public SourceProvider { public: using SyntheticSourceGenerator = WTF::Function& exportNames, MarkedArgumentBuffer& exportValues)>; + // Same contract as SyntheticSourceGenerator, except that an export may be declared without a value (an empty + // JSValue appended to exportValues). Such exports are read from the returned object the first time something + // binds to them; see SyntheticModuleRecord::tryCreateWithExportNamesAndValues(..., JSObject* lazyExportsSource). + // The generator returns nullptr when it provided every value. + using LazySyntheticSourceGenerator = WTF::Function& exportNames, MarkedArgumentBuffer& exportValues)>; static Ref create(SyntheticSourceGenerator&& generator, const SourceOrigin& sourceOrigin, String sourceURL) { - return adoptRef(*new SyntheticSourceProvider(WTF::move(generator), sourceOrigin, WTF::move(sourceURL))); + return adoptRef(*new SyntheticSourceProvider(WTF::move(generator), nullptr, sourceOrigin, WTF::move(sourceURL))); + } + + static Ref createWithLazyExports(LazySyntheticSourceGenerator&& generator, const SourceOrigin& sourceOrigin, String sourceURL) + { + return adoptRef(*new SyntheticSourceProvider(nullptr, WTF::move(generator), sourceOrigin, WTF::move(sourceURL))); } unsigned hash() const final @@ -192,21 +202,28 @@ class StringSourceProvider : public SourceProvider { return m_source; } - void generate(JSGlobalObject* globalObject, Identifier moduleKey, Vector& exportNames, MarkedArgumentBuffer& exportValues) { + // Returns the object that exports declared without a value are read from, or nullptr if there are none. + JSObject* generate(JSGlobalObject* globalObject, Identifier moduleKey, Vector& exportNames, MarkedArgumentBuffer& exportValues) + { + if (m_lazyGenerator) + return m_lazyGenerator(globalObject, moduleKey, exportNames, exportValues); m_generator(globalObject, moduleKey, exportNames, exportValues); + return nullptr; } private: - JS_EXPORT_PRIVATE SyntheticSourceProvider(SyntheticSourceGenerator&& generator, const SourceOrigin& sourceOrigin, String&& sourceURL, String&& preRedirectURL = String()) + JS_EXPORT_PRIVATE SyntheticSourceProvider(SyntheticSourceGenerator&& generator, LazySyntheticSourceGenerator&& lazyGenerator, const SourceOrigin& sourceOrigin, String&& sourceURL, String&& preRedirectURL = String()) : SourceProvider(sourceOrigin, WTF::move(sourceURL), WTF::move(preRedirectURL), SourceTaintedOrigin::Untainted, TextPosition(), SourceProviderSourceType::Synthetic) , m_source("[native code]"_s) , m_generator(WTF::move(generator)) + , m_lazyGenerator(WTF::move(lazyGenerator)) { } String m_source; SyntheticSourceGenerator m_generator; + LazySyntheticSourceGenerator m_lazyGenerator; }; #if ENABLE(WEBASSEMBLY) diff --git a/Source/JavaScriptCore/runtime/CyclicModuleRecord.cpp b/Source/JavaScriptCore/runtime/CyclicModuleRecord.cpp index cf70fd38708f2..06d21f87495e6 100644 --- a/Source/JavaScriptCore/runtime/CyclicModuleRecord.cpp +++ b/Source/JavaScriptCore/runtime/CyclicModuleRecord.cpp @@ -40,6 +40,9 @@ #include "ModuleProgramExecutable.h" #include "SourceProfiler.h" #include "SymbolTableInlines.h" +#if USE(BUN_JSC_ADDITIONS) +#include "SyntheticModuleRecord.h" +#endif #include "UnlinkedModuleProgramCodeBlock.h" #include "WebAssemblyModuleRecord.h" #include @@ -258,6 +261,12 @@ void CyclicModuleRecord::initializeEnvironment(JSGlobalObject* globalObject, Ref } else { // 7.c.iv.1. Perform CreateImportBinding(env, in.[[LocalName]], resolution.[[Module]], resolution.[[BindingName]]). // (Already handled through lazy resolution.) +#if USE(BUN_JSC_ADDITIONS) + // Reads of the import binding go straight to the exporting environment's slot, so a lazy export of a + // SyntheticModuleRecord has to be given its value now, while this module is being linked to it. + SyntheticModuleRecord::materializeLazyExport(globalObject, resolution.moduleRecord, resolution.localName); + RETURN_IF_EXCEPTION(scope, void()); +#endif } break; } diff --git a/Source/JavaScriptCore/runtime/JSModuleLoader.cpp b/Source/JavaScriptCore/runtime/JSModuleLoader.cpp index 0bc095c90b246..0abfe4ebdb63c 100644 --- a/Source/JavaScriptCore/runtime/JSModuleLoader.cpp +++ b/Source/JavaScriptCore/runtime/JSModuleLoader.cpp @@ -1267,10 +1267,10 @@ JSPromise* JSModuleLoader::makeModule(JSGlobalObject* globalObject, const Identi SyntheticSourceProvider* syntheticSourceProvider = reinterpret_cast(sourceCode.provider()); MarkedArgumentBuffer args; Vector exportNames; - syntheticSourceProvider->generate(globalObject, moduleKey, exportNames, args); + JSObject* lazyExportsSource = syntheticSourceProvider->generate(globalObject, moduleKey, exportNames, args); RETURN_IF_EXCEPTION(scope, promise->rejectWithCaughtException(vm, scope)); - auto* moduleRecord = SyntheticModuleRecord::tryCreateWithExportNamesAndValues(globalObject, moduleKey, exportNames, args); + auto* moduleRecord = SyntheticModuleRecord::tryCreateWithExportNamesAndValues(globalObject, moduleKey, exportNames, args, lazyExportsSource); RETURN_IF_EXCEPTION(scope, promise->rejectWithCaughtException(vm, scope)); scope.release(); diff --git a/Source/JavaScriptCore/runtime/JSModuleNamespaceObject.cpp b/Source/JavaScriptCore/runtime/JSModuleNamespaceObject.cpp index 18c7e9afd5e31..6b2c96688dd9c 100644 --- a/Source/JavaScriptCore/runtime/JSModuleNamespaceObject.cpp +++ b/Source/JavaScriptCore/runtime/JSModuleNamespaceObject.cpp @@ -31,6 +31,9 @@ #include "JSCInlines.h" #include "JSModuleEnvironment.h" #include "JSModuleRecord.h" +#if USE(BUN_JSC_ADDITIONS) +#include "SyntheticModuleRecord.h" +#endif namespace JSC { @@ -188,6 +191,15 @@ bool JSModuleNamespaceObject::getOwnPropertySlotCommon(JSGlobalObject* globalObj JSModuleEnvironment* environment = exportEntry.moduleRecord->moduleEnvironment(); ScopeOffset scopeOffset; JSValue value = getValue(environment, exportEntry.localName, scopeOffset); +#if USE(BUN_JSC_ADDITIONS) + if (!value) [[unlikely]] { + // Same idea as the *namespace* case above: a lazy export of a SyntheticModuleRecord is materialized on + // first read, then looked up from the scope again so that the module namespace object IC applies to it. + SyntheticModuleRecord::materializeLazyExport(globalObject, exportEntry.moduleRecord.get(), exportEntry.localName); + RETURN_IF_EXCEPTION(scope, false); + value = getValue(environment, exportEntry.localName, scopeOffset); + } +#endif // If the value is filled with TDZ value, throw a reference error. if (!value) { RefPtr uid = propertyName.uid(); diff --git a/Source/JavaScriptCore/runtime/SyntheticModuleRecord.cpp b/Source/JavaScriptCore/runtime/SyntheticModuleRecord.cpp index 895711336e910..a95a024e4a2f9 100644 --- a/Source/JavaScriptCore/runtime/SyntheticModuleRecord.cpp +++ b/Source/JavaScriptCore/runtime/SyntheticModuleRecord.cpp @@ -73,6 +73,9 @@ void SyntheticModuleRecord::visitChildrenImpl(JSCell* cell, Visitor& visitor) SyntheticModuleRecord* thisObject = uncheckedDowncast(cell); ASSERT_GC_OBJECT_INHERITS(thisObject, info()); Base::visitChildren(thisObject, visitor); +#if USE(BUN_JSC_ADDITIONS) + visitor.append(thisObject->m_lazyExportsSource); +#endif } DEFINE_VISIT_CHILDREN(SyntheticModuleRecord); @@ -87,7 +90,16 @@ JSValue SyntheticModuleRecord::evaluate(JSGlobalObject*) return jsUndefined(); } +#if USE(BUN_JSC_ADDITIONS) SyntheticModuleRecord* SyntheticModuleRecord::tryCreateWithExportNamesAndValues(JSGlobalObject* globalObject, const Identifier& moduleKey, const Vector& exportNames, const MarkedArgumentBuffer& exportValues) +{ + return tryCreateWithExportNamesAndValues(globalObject, moduleKey, exportNames, exportValues, nullptr); +} + +SyntheticModuleRecord* SyntheticModuleRecord::tryCreateWithExportNamesAndValues(JSGlobalObject* globalObject, const Identifier& moduleKey, const Vector& exportNames, const MarkedArgumentBuffer& exportValues, JSObject* lazyExportsSource) +#else +SyntheticModuleRecord* SyntheticModuleRecord::tryCreateWithExportNamesAndValues(JSGlobalObject* globalObject, const Identifier& moduleKey, const Vector& exportNames, const MarkedArgumentBuffer& exportValues) +#endif { VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); @@ -110,9 +122,21 @@ SyntheticModuleRecord* SyntheticModuleRecord::tryCreateWithExportNamesAndValues( moduleRecord->setModuleEnvironment(globalObject, moduleEnvironment); RETURN_IF_EXCEPTION(scope, { }); +#if USE(BUN_JSC_ADDITIONS) + bool hasLazyExports = false; +#endif for (unsigned index = 0; index < exportNames.size(); ++index) { PropertyName exportName = exportNames[index]; JSValue exportValue = exportValues.at(index); +#if USE(BUN_JSC_ADDITIONS) + if (!exportValue) { + // Lazy export: JSModuleEnvironment::create() above initialized the binding to the TDZ value, and it stays + // that way until materializeLazyExport() fills it in. + ASSERT(lazyExportsSource); + hasLazyExports = true; + continue; + } +#endif constexpr bool shouldThrowReadOnlyError = false; constexpr bool ignoreReadOnlyErrors = true; bool putResult = false; @@ -121,10 +145,70 @@ SyntheticModuleRecord* SyntheticModuleRecord::tryCreateWithExportNamesAndValues( ASSERT(putResult); } +#if USE(BUN_JSC_ADDITIONS) + if (hasLazyExports) + moduleRecord->m_lazyExportsSource.set(vm, moduleRecord, lazyExportsSource); +#endif + return moduleRecord; } +#if USE(BUN_JSC_ADDITIONS) +void SyntheticModuleRecord::materializeLazyExport(JSGlobalObject* globalObject, PropertyName localName) +{ + JSObject* source = m_lazyExportsSource.get(); + if (!source) + return; + + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + // *namespace* lives in the same symbol table but is not an export; getModuleNamespace() owns that binding. + if (localName == vm.propertyNames->starNamespacePrivateName) + return; + + JSModuleEnvironment* environment = moduleEnvironment(); + SymbolTable* symbolTable = environment->symbolTable(); + ScopeOffset scopeOffset; + { + ConcurrentJSLocker locker(symbolTable->m_lock); + auto iter = symbolTable->find(locker, localName.uid()); + if (iter == symbolTable->end(locker)) + return; + scopeOffset = iter->value.scopeOffset(); + } + + // Either the value was provided up front, an earlier call materialized it, or something wrote the binding + // directly (JSModuleNamespaceObject::overrideExportValue). In all of those cases the binding is what it should be. + if (environment->variableAt(scopeOffset).get()) + return; + + JSValue value = source->get(globalObject, localName); + RETURN_IF_EXCEPTION(scope, void()); + + // The getter may have re-entered and filled this binding itself. Whatever got there first is what any binding + // created in the meantime has observed, so keep it. + if (environment->variableAt(scopeOffset).get()) + return; + + constexpr bool shouldThrowReadOnlyError = false; + constexpr bool ignoreReadOnlyErrors = true; + bool putResult = false; + symbolTablePutTouchWatchpointSet(environment, globalObject, localName, value, shouldThrowReadOnlyError, ignoreReadOnlyErrors, putResult); + RETURN_IF_EXCEPTION(scope, void()); + ASSERT(putResult); +} + +void SyntheticModuleRecord::materializeLazyExport(JSGlobalObject* globalObject, AbstractModuleRecord* moduleRecord, PropertyName localName) +{ + auto* syntheticModuleRecord = dynamicDowncast(moduleRecord); + if (!syntheticModuleRecord || !syntheticModuleRecord->hasLazyExports()) [[likely]] + return; + syntheticModuleRecord->materializeLazyExport(globalObject, localName); +} +#endif + SyntheticModuleRecord* SyntheticModuleRecord::tryCreateDefaultExportSyntheticModule(JSGlobalObject* globalObject, const Identifier& moduleKey, JSValue defaultExport) { VM& vm = globalObject->vm(); diff --git a/Source/JavaScriptCore/runtime/SyntheticModuleRecord.h b/Source/JavaScriptCore/runtime/SyntheticModuleRecord.h index 47ac7856ebf15..f85eb5285496a 100644 --- a/Source/JavaScriptCore/runtime/SyntheticModuleRecord.h +++ b/Source/JavaScriptCore/runtime/SyntheticModuleRecord.h @@ -61,12 +61,34 @@ class SyntheticModuleRecord final : public AbstractModuleRecord { JS_EXPORT_PRIVATE static SyntheticModuleRecord* tryCreateWithExportNamesAndValues(JSGlobalObject*, const Identifier& moduleKey, const Vector& exportNames, const MarkedArgumentBuffer& exportValues); +#if USE(BUN_JSC_ADDITIONS) + // Like the overload above, but an empty JSValue in exportValues declares a lazy export: its binding is left + // uninitialized and is filled in by materializeLazyExport(), which reads the property of the same name off + // lazyExportsSource. That happens the first time something binds to the export, i.e. when an importing module + // links a named import of it or when it is read off a module namespace object. Exports whose values are + // provided behave exactly as in the overload above. + JS_EXPORT_PRIVATE static SyntheticModuleRecord* tryCreateWithExportNamesAndValues(JSGlobalObject*, const Identifier& moduleKey, const Vector& exportNames, const MarkedArgumentBuffer& exportValues, JSObject* lazyExportsSource); + + bool hasLazyExports() const { return !!m_lazyExportsSource; } + + // No-op unless this record has lazy exports and localName is one of them that nobody has materialized (or + // overridden through JSModuleNamespaceObject::overrideExportValue) yet. May run arbitrary JS and throw. + JS_EXPORT_PRIVATE void materializeLazyExport(JSGlobalObject*, PropertyName localName); + + // Convenience for code holding a Resolution: materializes the binding if it points into a lazy synthetic module. + static void materializeLazyExport(JSGlobalObject*, AbstractModuleRecord*, PropertyName localName); +#endif + private: SyntheticModuleRecord(VM&, Structure*, const Identifier& moduleKey); static SyntheticModuleRecord* tryCreateDefaultExportSyntheticModule(JSGlobalObject*, const Identifier& moduleKey, JSValue); void finishCreation(JSGlobalObject*, VM&); + +#if USE(BUN_JSC_ADDITIONS) + WriteBarrier m_lazyExportsSource; +#endif }; } // namespace JSC diff --git a/Source/JavaScriptCore/wasm/js/WebAssemblyModuleRecord.cpp b/Source/JavaScriptCore/wasm/js/WebAssemblyModuleRecord.cpp index fc9c5796f2715..cbc9362e1014a 100644 --- a/Source/JavaScriptCore/wasm/js/WebAssemblyModuleRecord.cpp +++ b/Source/JavaScriptCore/wasm/js/WebAssemblyModuleRecord.cpp @@ -39,6 +39,9 @@ #include "JSWebAssemblyModule.h" #include "JSWebAssemblyTag.h" #include "ObjectConstructor.h" +#if USE(BUN_JSC_ADDITIONS) +#include "SyntheticModuleRecord.h" +#endif #include "VariableWriteFireDetailInlines.h" #include "WasmConstExprGenerator.h" #include "WasmOperationsInlines.h" @@ -229,6 +232,11 @@ void WebAssemblyModuleRecord::initializeImports(JSGlobalObject* globalObject, JS } AbstractModuleRecord* importedRecord = resolution.moduleRecord; +#if USE(BUN_JSC_ADDITIONS) + // The snapshot below reads the binding's slot directly, so a lazy export has to be filled in first. + SyntheticModuleRecord::materializeLazyExport(globalObject, importedRecord, resolution.localName); + RETURN_IF_EXCEPTION(scope, void()); +#endif JSModuleEnvironment* importedEnvironment = importedRecord->moduleEnvironmentMayBeNull(); // It means that target module is not linked yet. In wasm loading, we allow this since we do not solve cyclic resolution as if JS's bindings. // At that time, error occurs since |value| is an empty, and later |value| becomes an undefined.