Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions Source/JavaScriptCore/parser/SourceProvider.h
Original file line number Diff line number Diff line change
Expand Up @@ -176,10 +176,20 @@ class StringSourceProvider : public SourceProvider {
class SyntheticSourceProvider final : public SourceProvider {
public:
using SyntheticSourceGenerator = WTF::Function<void(JSGlobalObject*, Identifier, Vector<Identifier, 4>& 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<JSObject*(JSGlobalObject*, Identifier, Vector<Identifier, 4>& exportNames, MarkedArgumentBuffer& exportValues)>;

static Ref<SyntheticSourceProvider> 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<SyntheticSourceProvider> createWithLazyExports(LazySyntheticSourceGenerator&& generator, const SourceOrigin& sourceOrigin, String sourceURL)
{
return adoptRef(*new SyntheticSourceProvider(nullptr, WTF::move(generator), sourceOrigin, WTF::move(sourceURL)));
}

unsigned hash() const final
Expand All @@ -192,21 +202,28 @@ class StringSourceProvider : public SourceProvider {
return m_source;
}

void generate(JSGlobalObject* globalObject, Identifier moduleKey, Vector<Identifier, 4>& 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<Identifier, 4>& 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)
Expand Down
9 changes: 9 additions & 0 deletions Source/JavaScriptCore/runtime/CyclicModuleRecord.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <wtf/Scope.h>
Expand Down Expand Up @@ -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;
}
Expand Down
4 changes: 2 additions & 2 deletions Source/JavaScriptCore/runtime/JSModuleLoader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1267,10 +1267,10 @@ JSPromise* JSModuleLoader::makeModule(JSGlobalObject* globalObject, const Identi
SyntheticSourceProvider* syntheticSourceProvider = reinterpret_cast<SyntheticSourceProvider*>(sourceCode.provider());
MarkedArgumentBuffer args;
Vector<Identifier, 4> 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();
Expand Down
12 changes: 12 additions & 0 deletions Source/JavaScriptCore/runtime/JSModuleNamespaceObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@
#include "JSCInlines.h"
#include "JSModuleEnvironment.h"
#include "JSModuleRecord.h"
#if USE(BUN_JSC_ADDITIONS)
#include "SyntheticModuleRecord.h"
#endif

namespace JSC {

Expand Down Expand Up @@ -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();
Expand Down
84 changes: 84 additions & 0 deletions Source/JavaScriptCore/runtime/SyntheticModuleRecord.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ void SyntheticModuleRecord::visitChildrenImpl(JSCell* cell, Visitor& visitor)
SyntheticModuleRecord* thisObject = uncheckedDowncast<SyntheticModuleRecord>(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);
Expand All @@ -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<Identifier, 4>& exportNames, const MarkedArgumentBuffer& exportValues)
{
return tryCreateWithExportNamesAndValues(globalObject, moduleKey, exportNames, exportValues, nullptr);
}

SyntheticModuleRecord* SyntheticModuleRecord::tryCreateWithExportNamesAndValues(JSGlobalObject* globalObject, const Identifier& moduleKey, const Vector<Identifier, 4>& exportNames, const MarkedArgumentBuffer& exportValues, JSObject* lazyExportsSource)
#else
SyntheticModuleRecord* SyntheticModuleRecord::tryCreateWithExportNamesAndValues(JSGlobalObject* globalObject, const Identifier& moduleKey, const Vector<Identifier, 4>& exportNames, const MarkedArgumentBuffer& exportValues)
#endif
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
Expand All @@ -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;
Expand All @@ -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<SyntheticModuleRecord>(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();
Expand Down
22 changes: 22 additions & 0 deletions Source/JavaScriptCore/runtime/SyntheticModuleRecord.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,34 @@ class SyntheticModuleRecord final : public AbstractModuleRecord {

JS_EXPORT_PRIVATE static SyntheticModuleRecord* tryCreateWithExportNamesAndValues(JSGlobalObject*, const Identifier& moduleKey, const Vector<Identifier, 4>& 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<Identifier, 4>& 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<JSObject> m_lazyExportsSource;
#endif
};

} // namespace JSC
8 changes: 8 additions & 0 deletions Source/JavaScriptCore/wasm/js/WebAssemblyModuleRecord.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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.
Expand Down
Loading