A few fixes related to CommonJS module loading (#9540)

* Ensure we always deref the source code

* Move more work to concurrent transpiler

* Update NodeModuleModule.h

* Update string.zig

* Make `Bun.gc()` more effective

* Update text-loader-fixture-dynamic-import-stress.ts

* Update ZigSourceProvider.cpp

* Fixes #6946

* Update ModuleLoader.cpp

* Update ModuleLoader.cpp

* Fixes #8965

* Fixups

* Update ModuleLoader.cpp

* Update ModuleLoader.cpp

* Load it

* Update module_loader.zig

* Update module_loader.zig

---------

Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
This commit is contained in:
Jarred Sumner
2024-03-21 10:42:57 -07:00
committed by GitHub
parent 0948727243
commit e124f08caf
27 changed files with 428 additions and 189 deletions

View File

@@ -27,6 +27,7 @@
#include "JSDOMOperation.h"
#include "GCDefferalContext.h"
#include "wtf/text/StringImpl.h"
extern "C" void mi_free(void* ptr);
@@ -559,4 +560,9 @@ extern "C" bool WTFStringImpl__isThreadSafe(
// return wtf->characters8() == reinterpret_cast_ptr<const LChar*>(reinterpret_cast<const uint8_t*>(wtf) + tailOffset<const LChar*>());
// return wtf->characters16() == reinterpret_cast_ptr<const UChar*>(reinterpret_cast<const uint16_t*>(wtf) + tailOffset<const UChar*>());
}
extern "C" void Bun__WTFStringImpl__ensureHash(WTF::StringImpl* str)
{
str->hash();
}

View File

@@ -30,7 +30,10 @@
*/
#include "headers.h"
#include "JavaScriptCore/JSCast.h"
#include <JavaScriptCore/JSMapInlines.h>
#include "root.h"
#include "JavaScriptCore/SourceCode.h"
#include "headers-handwritten.h"
#include "ZigGlobalObject.h"
#include <JavaScriptCore/JSSourceCode.h>
@@ -67,6 +70,8 @@
#include <JavaScriptCore/LazyPropertyInlines.h>
#include <JavaScriptCore/HeapAnalyzer.h>
#include "PathInlines.h"
#include "wtf/NakedPtr.h"
#include "wtf/URL.h"
extern "C" bool Bun__isBunMain(JSC::JSGlobalObject* global, const BunString*);
@@ -97,10 +102,10 @@ extern "C" void Bun__VM__setEntryPointEvalResultCJS(void*, EncodedJSValue);
static bool evaluateCommonJSModuleOnce(JSC::VM& vm, Zig::GlobalObject* globalObject, JSCommonJSModule* moduleObject, JSString* dirname, JSValue filename, WTF::NakedPtr<Exception>& exception)
{
JSSourceCode* code = moduleObject->sourceCode.get();
SourceCode code = std::move(moduleObject->sourceCode);
// If an exception occurred somewhere else, we might have cleared the source code.
if (UNLIKELY(code == nullptr)) {
if (UNLIKELY(code.isNull())) {
auto throwScope = DECLARE_THROW_SCOPE(vm);
throwException(globalObject, throwScope, createError(globalObject, "Failed to evaluate module"_s));
exception = throwScope.exception();
@@ -132,25 +137,22 @@ static bool evaluateCommonJSModuleOnce(JSC::VM& vm, Zig::GlobalObject* globalObj
globalObject->putDirect(vm, Identifier::fromLatin1(vm, "__filename"_s), filename, 0);
globalObject->putDirect(vm, Identifier::fromLatin1(vm, "__dirname"_s), dirname, 0);
JSValue result = JSC::evaluate(globalObject, code->sourceCode(), jsUndefined(), exception);
JSValue result = JSC::evaluate(globalObject, code, jsUndefined(), exception);
if (UNLIKELY(exception.get() || result.isEmpty())) {
moduleObject->sourceCode.clear();
return false;
}
Bun__VM__setEntryPointEvalResultCJS(globalObject->bunVM(), JSValue::encode(result));
moduleObject->sourceCode.clear();
return true;
}
// This will return 0 if there was a syntax error or an allocation failure
JSValue fnValue = JSC::evaluate(globalObject, code->sourceCode(), jsUndefined(), exception);
JSValue fnValue = JSC::evaluate(globalObject, code, jsUndefined(), exception);
if (UNLIKELY(exception.get() || fnValue.isEmpty())) {
moduleObject->sourceCode.clear();
return false;
}
@@ -171,39 +173,47 @@ static bool evaluateCommonJSModuleOnce(JSC::VM& vm, Zig::GlobalObject* globalObj
JSC::call(globalObject, fn, callData, moduleObject, args, exception);
moduleObject->sourceCode.clear();
return exception.get() == nullptr;
}
JSC_DEFINE_HOST_FUNCTION(jsFunctionLoadModule, (JSGlobalObject * lexicalGlobalObject, CallFrame* callframe))
bool JSCommonJSModule::load(JSC::VM& vm, Zig::GlobalObject* globalObject, WTF::NakedPtr<JSC::Exception>& exception)
{
auto* globalObject = jsCast<Zig::GlobalObject*>(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(globalObject->vm());
JSCommonJSModule* moduleObject = jsDynamicCast<JSCommonJSModule*>(callframe->argument(0));
if (!moduleObject) {
RELEASE_AND_RETURN(throwScope, JSValue::encode(jsBoolean(true)));
if (this->hasEvaluated || this->sourceCode.isNull()) {
return true;
}
if (moduleObject->hasEvaluated || !moduleObject->sourceCode) {
RELEASE_AND_RETURN(throwScope, JSValue::encode(jsBoolean(true)));
}
WTF::NakedPtr<Exception> exception;
evaluateCommonJSModuleOnce(
globalObject->vm(),
jsCast<Zig::GlobalObject*>(globalObject),
moduleObject,
moduleObject->m_dirname.get(),
moduleObject->m_filename.get(),
this,
this->m_dirname.get(),
this->m_filename.get(),
exception);
if (exception.get()) {
// On error, remove the module from the require map/
// so that it can be re-evaluated on the next require.
globalObject->requireMap()->remove(globalObject, moduleObject->id());
globalObject->requireMap()->remove(globalObject, this->id());
return false;
}
return true;
}
JSC_DEFINE_HOST_FUNCTION(jsFunctionLoadModule, (JSGlobalObject * lexicalGlobalObject, CallFrame* callframe))
{
auto& vm = lexicalGlobalObject->vm();
auto* globalObject = jsCast<Zig::GlobalObject*>(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
JSCommonJSModule* moduleObject = jsDynamicCast<JSCommonJSModule*>(callframe->argument(0));
if (!moduleObject) {
RELEASE_AND_RETURN(throwScope, JSValue::encode(jsBoolean(true)));
}
WTF::NakedPtr<Exception> exception;
if (!moduleObject->load(vm, globalObject, exception)) {
throwException(globalObject, throwScope, exception.get());
exception.clear();
return JSValue::encode({});
@@ -487,15 +497,13 @@ JSC_DEFINE_HOST_FUNCTION(functionCommonJSModuleRecord_compile, (JSGlobalObject *
sourceString,
"\n})"_s);
SourceCode sourceCode = makeSource(
moduleObject->sourceCode = makeSource(
WTFMove(wrappedString),
SourceOrigin(URL::fileURLWithFileSystemPath(filenameString)),
JSC::SourceTaintedOrigin::Untainted,
filenameString,
WTF::TextPosition(),
JSC::SourceProviderSourceType::Program);
JSSourceCode* jsSourceCode = JSSourceCode::create(vm, WTFMove(sourceCode));
moduleObject->sourceCode.set(vm, moduleObject, jsSourceCode);
auto index = filenameString.reverseFind(PLATFORM_SEP, filenameString.length());
// filenameString is coming from js, any separator could be used
@@ -593,15 +601,14 @@ public:
const JSC::ClassInfo JSCommonJSModulePrototype::s_info = { "ModulePrototype"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSCommonJSModulePrototype) };
void JSCommonJSModule::finishCreation(JSC::VM& vm, JSC::JSString* id, JSValue filename, JSC::JSString* dirname, JSC::JSSourceCode* sourceCode)
void JSCommonJSModule::finishCreation(JSC::VM& vm, JSC::JSString* id, JSValue filename, JSC::JSString* dirname, const JSC::SourceCode& sourceCode)
{
Base::finishCreation(vm);
ASSERT(inherits(info()));
m_id.set(vm, this, id);
m_filename.set(vm, this, filename);
m_dirname.set(vm, this, dirname);
if (sourceCode)
this->sourceCode.set(vm, this, sourceCode);
this->sourceCode = sourceCode;
}
JSC::Structure* JSCommonJSModule::createStructure(
@@ -622,7 +629,7 @@ JSCommonJSModule* JSCommonJSModule::create(
JSC::JSString* id,
JSValue filename,
JSC::JSString* dirname,
JSC::JSSourceCode* sourceCode)
const JSC::SourceCode& sourceCode)
{
JSCommonJSModule* cell = new (NotNull, JSC::allocateCell<JSCommonJSModule>(vm)) JSCommonJSModule(vm, structure);
cell->finishCreation(vm, id, filename, dirname, sourceCode);
@@ -664,7 +671,7 @@ JSCommonJSModule* JSCommonJSModule::create(
auto* out = JSCommonJSModule::create(
vm,
globalObject->CommonJSModuleObjectStructure(),
requireMapKey, requireMapKey, dirname, nullptr);
requireMapKey, requireMapKey, dirname, SourceCode());
out->putDirect(
vm,
@@ -898,7 +905,6 @@ void JSCommonJSModule::visitChildrenImpl(JSCell* cell, Visitor& visitor)
ASSERT_GC_OBJECT_INHERITS(thisObject, info());
Base::visitChildren(thisObject, visitor);
visitor.append(thisObject->m_id);
visitor.append(thisObject->sourceCode);
visitor.append(thisObject->m_filename);
visitor.append(thisObject->m_dirname);
visitor.append(thisObject->m_paths);
@@ -996,19 +1002,16 @@ void RequireResolveFunctionPrototype::finishCreation(JSC::VM& vm)
bool JSCommonJSModule::evaluate(
Zig::GlobalObject* globalObject,
const WTF::String& key,
ResolvedSource source,
ResolvedSource& source,
bool isBuiltIn)
{
auto& vm = globalObject->vm();
auto sourceProvider = Zig::SourceProvider::create(jsCast<Zig::GlobalObject*>(globalObject), source, JSC::SourceProviderSourceType::Program, isBuiltIn);
this->ignoreESModuleAnnotation = source.tag == ResolvedSourceTagPackageJSONTypeModule;
JSC::SourceCode rawInputSource(
WTFMove(sourceProvider));
if (this->hasEvaluated)
return true;
this->sourceCode.set(vm, this, JSC::JSSourceCode::create(vm, WTFMove(rawInputSource)));
this->sourceCode = WTFMove(JSC::SourceCode(WTFMove(sourceProvider)));
WTF::NakedPtr<JSC::Exception> exception;
@@ -1031,18 +1034,16 @@ bool JSCommonJSModule::evaluate(
std::optional<JSC::SourceCode> createCommonJSModule(
Zig::GlobalObject* globalObject,
ResolvedSource source,
JSValue specifierValue,
ResolvedSource& source,
bool isBuiltIn)
{
JSCommonJSModule* moduleObject = nullptr;
WTF::String sourceURL = source.source_url.toWTFString();
JSValue specifierValue = Bun::toJS(globalObject, source.specifier);
JSValue entry = globalObject->requireMap()->get(globalObject, specifierValue);
auto sourceProvider = Zig::SourceProvider::create(jsCast<Zig::GlobalObject*>(globalObject), source, JSC::SourceProviderSourceType::Program, isBuiltIn);
bool ignoreESModuleAnnotation = source.tag == ResolvedSourceTagPackageJSONTypeModule;
SourceOrigin sourceOrigin = sourceProvider->sourceOrigin();
SourceOrigin sourceOrigin;
if (entry) {
moduleObject = jsDynamicCast<JSCommonJSModule*>(entry);
@@ -1050,7 +1051,7 @@ std::optional<JSC::SourceCode> createCommonJSModule(
if (!moduleObject) {
auto& vm = globalObject->vm();
auto* requireMapKey = jsStringWithCache(vm, sourceURL);
auto* requireMapKey = specifierValue.toString(globalObject);
auto index = sourceURL.reverseFind(PLATFORM_SEP, sourceURL.length());
JSString* dirname;
JSString* filename = requireMapKey;
@@ -1060,16 +1061,20 @@ std::optional<JSC::SourceCode> createCommonJSModule(
dirname = jsEmptyString(vm);
}
auto sourceProvider = Zig::SourceProvider::create(jsCast<Zig::GlobalObject*>(globalObject), source, JSC::SourceProviderSourceType::Program, isBuiltIn);
sourceOrigin = sourceProvider->sourceOrigin();
moduleObject = JSCommonJSModule::create(
vm,
globalObject->CommonJSModuleObjectStructure(),
requireMapKey, filename, dirname, JSC::JSSourceCode::create(vm, SourceCode(WTFMove(sourceProvider))));
requireMapKey, filename, dirname, WTFMove(JSC::SourceCode(WTFMove(sourceProvider))));
moduleObject->putDirect(vm,
WebCore::clientData(vm)->builtinNames().exportsPublicName(),
JSC::constructEmptyObject(globalObject, globalObject->objectPrototype()), 0);
globalObject->requireMap()->set(globalObject, requireMapKey, moduleObject);
} else {
sourceOrigin = Zig::toSourceOrigin(sourceURL, isBuiltIn);
}
moduleObject->ignoreESModuleAnnotation = ignoreESModuleAnnotation;
@@ -1110,6 +1115,8 @@ std::optional<JSC::SourceCode> createCommonJSModule(
moduleObject->toSyntheticSource(globalObject, moduleKey, exportNames, exportValues);
}
} else {
// require map was cleared of the entry
}
},
sourceOrigin,
@@ -1134,7 +1141,7 @@ JSObject* JSCommonJSModule::createBoundRequireFunction(VM& vm, JSGlobalObject* l
auto moduleObject = Bun::JSCommonJSModule::create(
vm,
globalObject->CommonJSModuleObjectStructure(),
filename, filename, dirname, nullptr);
filename, filename, dirname, SourceCode());
JSFunction* requireFunction = JSC::JSBoundFunction::create(vm,
globalObject,

View File

@@ -1,6 +1,8 @@
#pragma once
#include "JavaScriptCore/JSGlobalObject.h"
#include "root.h"
#include "headers-handwritten.h"
#include "wtf/NakedPtr.h"
namespace Zig {
class GlobalObject;
@@ -34,30 +36,34 @@ public:
mutable JSC::WriteBarrier<JSString> m_dirname;
mutable JSC::WriteBarrier<Unknown> m_paths;
mutable JSC::WriteBarrier<Unknown> m_parent;
mutable JSC::WriteBarrier<JSSourceCode> sourceCode;
bool ignoreESModuleAnnotation { false };
JSC::SourceCode sourceCode = JSC::SourceCode();
void setSourceCode(JSC::SourceCode&& sourceCode);
static void destroy(JSC::JSCell*);
~JSCommonJSModule();
void clearSourceCode() { sourceCode = JSC::SourceCode(); }
void finishCreation(JSC::VM& vm,
JSC::JSString* id, JSValue filename,
JSC::JSString* dirname, JSC::JSSourceCode* sourceCode);
JSC::JSString* dirname, const JSC::SourceCode& sourceCode);
static JSC::Structure* createStructure(JSC::JSGlobalObject* globalObject);
bool evaluate(Zig::GlobalObject* globalObject, const WTF::String& sourceURL, ResolvedSource resolvedSource, bool isBuiltIn);
inline bool evaluate(Zig::GlobalObject* globalObject, const WTF::String& sourceURL, ResolvedSource resolvedSource)
bool evaluate(Zig::GlobalObject* globalObject, const WTF::String& sourceURL, ResolvedSource& resolvedSource, bool isBuiltIn);
inline bool evaluate(Zig::GlobalObject* globalObject, const WTF::String& sourceURL, ResolvedSource& resolvedSource)
{
return evaluate(globalObject, sourceURL, resolvedSource, false);
}
bool evaluate(Zig::GlobalObject* globalObject, const WTF::String& key, const SyntheticSourceProvider::SyntheticSourceGenerator& generator);
bool evaluate(Zig::GlobalObject* globalObject, const WTF::String& key, JSSourceCode* sourceCode);
bool evaluate(Zig::GlobalObject* globalObject, const WTF::String& key, const JSC::SourceCode& sourceCode);
static JSCommonJSModule* create(JSC::VM& vm, JSC::Structure* structure,
JSC::JSString* id,
JSValue filename,
JSC::JSString* dirname, JSC::JSSourceCode* sourceCode);
JSC::JSString* dirname, const JSC::SourceCode& sourceCode);
static JSCommonJSModule* create(
Zig::GlobalObject* globalObject,
@@ -79,10 +85,11 @@ public:
JSValue exportsObject();
JSValue id();
bool load(JSC::VM& vm, Zig::GlobalObject* globalObject, WTF::NakedPtr<JSC::Exception>&);
DECLARE_INFO;
DECLARE_VISIT_CHILDREN;
static void analyzeHeap(JSCell*, JSC::HeapAnalyzer&);
template<typename, SubspaceAccess mode>
@@ -106,25 +113,21 @@ public:
}
};
JSCommonJSModule* createCommonJSModuleWithoutRunning(
Zig::GlobalObject* globalObject,
Ref<Zig::SourceProvider> sourceProvider,
const WTF::String& sourceURL,
ResolvedSource source);
JSC::Structure* createCommonJSModuleStructure(
Zig::GlobalObject* globalObject);
std::optional<JSC::SourceCode> createCommonJSModule(
Zig::GlobalObject* globalObject,
ResolvedSource source,
JSC::JSValue specifierValue,
ResolvedSource& source,
bool isBuiltIn);
inline std::optional<JSC::SourceCode> createCommonJSModule(
Zig::GlobalObject* globalObject,
ResolvedSource source)
JSC::JSValue specifierValue,
ResolvedSource& source)
{
return createCommonJSModule(globalObject, source, false);
return createCommonJSModule(globalObject, specifierValue, source, false);
}
class RequireResolveFunctionPrototype final : public JSC::JSNonFinalObject {

View File

@@ -1,8 +1,8 @@
#include "root.h"
#include "headers-handwritten.h"
#include "JavaScriptCore/JSGlobalObject.h"
#include "ModuleLoader.h"
#include "JavaScriptCore/Identifier.h"
#include "ZigGlobalObject.h"
#include <JavaScriptCore/JSCInlines.h>
#include <JavaScriptCore/JSNativeStdFunction.h>
@@ -290,8 +290,15 @@ static JSValue handleVirtualModuleResult(
auto onLoadResult = handleOnLoadResult(globalObject, virtualModuleResult, specifier, wasModuleMock);
JSC::VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
WTF::String sourceCodeStringForDeref;
const auto getSourceCodeStringForDeref = [&]() {
if (res->success && res->result.value.needsDeref && res->result.value.source_code.tag == BunStringTag::WTFStringImpl) {
res->result.value.needsDeref = false;
sourceCodeStringForDeref = String(res->result.value.source_code.impl.wtf);
}
};
auto reject = [&](JSC::JSValue exception) -> JSValue {
const auto reject = [&](JSC::JSValue exception) -> JSValue {
if constexpr (allowPromise) {
return rejectedInternalPromise(globalObject, exception);
} else {
@@ -300,7 +307,7 @@ static JSValue handleVirtualModuleResult(
}
};
auto resolve = [&](JSValue code) -> JSValue {
const auto resolve = [&](JSValue code) -> JSValue {
res->success = true;
if constexpr (allowPromise) {
scope.release();
@@ -310,7 +317,7 @@ static JSValue handleVirtualModuleResult(
}
};
auto rejectOrResolve = [&](JSValue code) -> JSValue {
const auto rejectOrResolve = [&](JSValue code) -> JSValue {
if (auto* exception = scope.exception()) {
if constexpr (allowPromise) {
scope.clearException();
@@ -336,6 +343,7 @@ static JSValue handleVirtualModuleResult(
if (!res->success) {
return reject(JSValue::decode(reinterpret_cast<EncodedJSValue>(res->result.err.ptr)));
}
getSourceCodeStringForDeref();
auto provider = Zig::SourceProvider::create(globalObject, res->result.value);
return resolve(JSC::JSSourceCode::create(vm, JSC::SourceCode(provider)));
@@ -383,38 +391,62 @@ static JSValue handleVirtualModuleResult(
}
extern "C" void Bun__onFulfillAsyncModule(
JSC::EncodedJSValue promiseValue,
Zig::GlobalObject* globalObject,
JSC::EncodedJSValue encodedPromiseValue,
ErrorableResolvedSource* res,
BunString* specifier,
BunString* referrer)
{
JSC::JSValue value = JSValue::decode(promiseValue);
JSC::JSInternalPromise* promise = jsCast<JSC::JSInternalPromise*>(value);
auto* globalObject = promise->globalObject();
WTF::String sourceCodeStringForDeref;
const auto getSourceCodeStringForDeref = [&]() {
if (res->result.value.needsDeref && res->result.value.source_code.tag == BunStringTag::WTFStringImpl) {
res->result.value.needsDeref = false;
sourceCodeStringForDeref = String(res->result.value.source_code.impl.wtf);
}
};
auto& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
JSC::JSInternalPromise* promise = jsCast<JSC::JSInternalPromise*>(JSC::JSValue::decode(encodedPromiseValue));
if (!res->success) {
throwException(scope, res->result.err, globalObject);
auto* exception = scope.exception();
scope.clearException();
return promise->reject(promise->globalObject(), exception);
return promise->reject(globalObject, exception);
}
if (res->result.value.commonJSExportsLen) {
auto created = Bun::createCommonJSModule(jsCast<Zig::GlobalObject*>(globalObject), res->result.value);
getSourceCodeStringForDeref();
auto specifierValue = Bun::toJS(globalObject, *specifier);
if (created.has_value()) {
return promise->resolve(promise->globalObject(), JSSourceCode::create(vm, WTFMove(created.value())));
if (auto entry = globalObject->esmRegistryMap()->get(globalObject, specifierValue)) {
if (res->result.value.commonJSExportsLen) {
if (entry.isObject()) {
if (auto isEvaluated = entry.getObject()->getIfPropertyExists(globalObject, Bun::builtinNames(vm).evaluatedPublicName())) {
if (isEvaluated.isTrue()) {
// it's a race! we lost.
// https://github.com/oven-sh/bun/issues/6946
return;
}
}
}
auto created = Bun::createCommonJSModule(jsCast<Zig::GlobalObject*>(globalObject), specifierValue, res->result.value);
if (created.has_value()) {
JSSourceCode* code = JSSourceCode::create(vm, WTFMove(created.value()));
promise->resolve(globalObject, code);
} else {
auto* exception = scope.exception();
scope.clearException();
promise->reject(globalObject, exception);
}
} else {
auto* exception = scope.exception();
scope.clearException();
return promise->reject(promise->globalObject(), exception);
auto&& provider = Zig::SourceProvider::create(jsDynamicCast<Zig::GlobalObject*>(globalObject), res->result.value);
promise->resolve(globalObject, JSC::JSSourceCode::create(vm, JSC::SourceCode(provider)));
}
} else {
// the module has since been deleted from the registry.
// let's not keep it forever for no reason.
}
auto provider = Zig::SourceProvider::create(jsDynamicCast<Zig::GlobalObject*>(globalObject), res->result.value);
promise->resolve(promise->globalObject(), JSC::JSSourceCode::create(vm, JSC::SourceCode(provider)));
}
extern "C" bool isBunTest;
@@ -432,7 +464,13 @@ JSValue fetchCommonJSModule(
auto scope = DECLARE_THROW_SCOPE(vm);
ErrorableResolvedSource resValue;
ErrorableResolvedSource* res = &resValue;
WTF::String sourceCodeStringForDeref;
const auto getSourceCodeStringForDeref = [&]() {
if (res->success && res->result.value.needsDeref && res->result.value.source_code.tag == BunStringTag::WTFStringImpl) {
res->result.value.needsDeref = false;
sourceCodeStringForDeref = String(res->result.value.source_code.impl.wtf);
}
};
auto& builtinNames = WebCore::clientData(vm)->builtinNames();
bool wasModuleMock = false;
@@ -543,7 +581,7 @@ JSValue fetchCommonJSModule(
JSMap* registry = globalObject->esmRegistryMap();
auto hasAlreadyLoadedESMVersionSoWeShouldntTranspileItTwice = [&]() -> bool {
const auto hasAlreadyLoadedESMVersionSoWeShouldntTranspileItTwice = [&]() -> bool {
JSValue entry = registry->get(globalObject, specifierValue);
if (!entry || !entry.isObject()) {
@@ -559,6 +597,7 @@ JSValue fetchCommonJSModule(
}
Bun__transpileFile(bunVM, globalObject, specifier, referrer, typeAttribute, res, false);
getSourceCodeStringForDeref();
if (res->success && res->result.value.commonJSExportsLen) {
target->evaluate(globalObject, specifier->toWTFString(BunString::ZeroCopy), res->result.value);
@@ -605,6 +644,7 @@ extern "C" bool isBunTest;
template<bool allowPromise>
static JSValue fetchESMSourceCode(
Zig::GlobalObject* globalObject,
JSC::JSValue specifierJS,
ErrorableResolvedSource* res,
BunString* specifier,
BunString* referrer,
@@ -614,16 +654,16 @@ static JSValue fetchESMSourceCode(
auto& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
auto reject = [&](JSC::JSValue exception) -> JSValue {
const auto reject = [&](JSC::JSValue exception) -> JSValue {
if constexpr (allowPromise) {
return rejectedInternalPromise(globalObject, exception);
} else {
throwException(globalObject, scope, exception);
return JSC::jsUndefined();
return {};
}
};
auto resolve = [&](JSValue code) -> JSValue {
const auto resolve = [&](JSValue code) -> JSValue {
if constexpr (allowPromise) {
auto* ret = resolvedInternalPromise(globalObject, code);
scope.release();
@@ -633,7 +673,7 @@ static JSValue fetchESMSourceCode(
}
};
auto rejectOrResolve = [&](JSValue code) -> JSValue {
const auto rejectOrResolve = [&](JSValue code) -> JSValue {
if (auto* exception = scope.exception()) {
if constexpr (!allowPromise) {
scope.release();
@@ -709,17 +749,27 @@ static JSValue fetchESMSourceCode(
}
}
WTF::String sourceCodeStringForDeref;
const auto getSourceCodeStringForDeref = [&]() {
if (res->success && res->result.value.needsDeref && res->result.value.source_code.tag == BunStringTag::WTFStringImpl) {
res->result.value.needsDeref = false;
sourceCodeStringForDeref = String(res->result.value.source_code.impl.wtf);
}
};
if constexpr (allowPromise) {
void* pendingCtx = Bun__transpileFile(bunVM, globalObject, specifier, referrer, typeAttribute, res, true);
auto* pendingCtx = Bun__transpileFile(bunVM, globalObject, specifier, referrer, typeAttribute, res, true);
getSourceCodeStringForDeref();
if (pendingCtx) {
return reinterpret_cast<JSC::JSInternalPromise*>(pendingCtx);
return pendingCtx;
}
} else {
Bun__transpileFile(bunVM, globalObject, specifier, referrer, typeAttribute, res, false);
getSourceCodeStringForDeref();
}
if (res->success && res->result.value.commonJSExportsLen) {
auto created = Bun::createCommonJSModule(globalObject, res->result.value);
auto created = Bun::createCommonJSModule(globalObject, specifierJS, res->result.value);
if (created.has_value()) {
return rejectOrResolve(JSSourceCode::create(vm, WTFMove(created.value())));
@@ -730,7 +780,7 @@ static JSValue fetchESMSourceCode(
scope.clearException();
return rejectedInternalPromise(globalObject, exception);
} else {
return JSC::jsUndefined();
return {};
}
}
@@ -769,8 +819,8 @@ static JSValue fetchESMSourceCode(
return rejectOrResolve(JSSourceCode::create(globalObject->vm(), WTFMove(source)));
}
auto&& provider = Zig::SourceProvider::create(globalObject, res->result.value);
return rejectOrResolve(JSC::JSSourceCode::create(vm, JSC::SourceCode(provider)));
return rejectOrResolve(JSC::JSSourceCode::create(vm,
JSC::SourceCode(Zig::SourceProvider::create(globalObject, res->result.value))));
}
extern "C" JSC::EncodedJSValue jsFunctionOnLoadObjectResultResolve(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame)
@@ -828,21 +878,23 @@ extern "C" JSC::EncodedJSValue jsFunctionOnLoadObjectResultReject(JSC::JSGlobalO
JSValue fetchESMSourceCodeSync(
Zig::GlobalObject* globalObject,
JSC::JSValue specifierJS,
ErrorableResolvedSource* res,
BunString* specifier,
BunString* referrer,
BunString* typeAttribute)
{
return fetchESMSourceCode<false>(globalObject, res, specifier, referrer, typeAttribute);
return fetchESMSourceCode<false>(globalObject, specifierJS, res, specifier, referrer, typeAttribute);
}
JSValue fetchESMSourceCodeAsync(
Zig::GlobalObject* globalObject,
JSC::JSValue specifierJS,
ErrorableResolvedSource* res,
BunString* specifier,
BunString* referrer,
BunString* typeAttribute)
{
return fetchESMSourceCode<true>(globalObject, res, specifier, referrer, typeAttribute);
return fetchESMSourceCode<true>(globalObject, specifierJS, res, specifier, referrer, typeAttribute);
}
}

View File

@@ -88,6 +88,7 @@ public:
JSValue fetchESMSourceCodeSync(
Zig::GlobalObject* globalObject,
JSValue spceifierJS,
ErrorableResolvedSource* res,
BunString* specifier,
BunString* referrer,
@@ -95,6 +96,7 @@ JSValue fetchESMSourceCodeSync(
JSValue fetchESMSourceCodeAsync(
Zig::GlobalObject* globalObject,
JSValue spceifierJS,
ErrorableResolvedSource* res,
BunString* specifier,
BunString* referrer,

View File

@@ -814,8 +814,9 @@ extern "C" JSC__JSGlobalObject* Zig__GlobalObject__create(void* console_client,
}
JSC_DEFINE_HOST_FUNCTION(functionFulfillModuleSync,
(JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
(JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame))
{
Zig::GlobalObject* globalObject = jsCast<Zig::GlobalObject*>(lexicalGlobalObject);
auto& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
@@ -836,7 +837,8 @@ JSC_DEFINE_HOST_FUNCTION(functionFulfillModuleSync,
res.result.err.ptr = nullptr;
JSValue result = Bun::fetchESMSourceCodeSync(
reinterpret_cast<Zig::GlobalObject*>(globalObject),
globalObject,
key,
&res,
&specifier,
&specifier,
@@ -4480,6 +4482,7 @@ JSC::JSInternalPromise* GlobalObject::moduleLoaderFetch(JSGlobalObject* globalOb
JSValue result = Bun::fetchESMSourceCodeAsync(
reinterpret_cast<Zig::GlobalObject*>(globalObject),
key,
&res,
&moduleKeyBun,
&source,

View File

@@ -6,6 +6,7 @@
#include <JavaScriptCore/BytecodeCacheError.h>
#include "ZigGlobalObject.h"
#include "wtf/Assertions.h"
#include <JavaScriptCore/Completion.h>
#include <wtf/Scope.h>
@@ -43,7 +44,7 @@ static uintptr_t getSourceProviderMapKey(ResolvedSource& resolvedSource)
}
}
static SourceOrigin toSourceOrigin(const String& sourceURL, bool isBuiltin)
SourceOrigin toSourceOrigin(const String& sourceURL, bool isBuiltin)
{
if (isBuiltin) {
if (sourceURL.startsWith("node:"_s)) {
@@ -55,6 +56,7 @@ static SourceOrigin toSourceOrigin(const String& sourceURL, bool isBuiltin)
}
}
ASSERT_WITH_MESSAGE(!sourceURL.startsWith("file://"_s), "sourceURL should not already be a file URL");
return SourceOrigin(WTF::URL::fileURLWithFileSystemPath(sourceURL));
}
@@ -92,7 +94,7 @@ JSC::SourceID sourceIDForSourceURL(const WTF::String& sourceURL)
extern "C" bool BunTest__shouldGenerateCodeCoverage(BunString sourceURL);
Ref<SourceProvider> SourceProvider::create(Zig::GlobalObject* globalObject, ResolvedSource resolvedSource, JSC::SourceProviderSourceType sourceType, bool isBuiltin)
Ref<SourceProvider> SourceProvider::create(Zig::GlobalObject* globalObject, ResolvedSource& resolvedSource, JSC::SourceProviderSourceType sourceType, bool isBuiltin)
{
auto string = resolvedSource.source_code.toWTFString(BunString::ZeroCopy);
@@ -103,8 +105,8 @@ Ref<SourceProvider> SourceProvider::create(Zig::GlobalObject* globalObject, Reso
bool shouldGenerateCodeCoverage = isCodeCoverageEnabled && !isBuiltin && BunTest__shouldGenerateCodeCoverage(resolvedSource.source_url);
if (resolvedSource.needsDeref && !isBuiltin) {
resolvedSource.needsDeref = false;
resolvedSource.source_code.deref();
// Do not deref either source_url or specifier
// Specifier's lifetime is the JSValue, mostly
// source_url is owned by the string above

View File

@@ -23,7 +23,7 @@ class GlobalObject;
void forEachSourceProvider(WTF::Function<void(JSC::SourceID)>);
JSC::SourceID sourceIDForSourceURL(const WTF::String& sourceURL);
void* sourceMappingForSourceURL(const WTF::String& sourceURL);
JSC::SourceOrigin toSourceOrigin(const String& sourceURL, bool isBuiltin);
class SourceProvider final : public JSC::SourceProvider {
WTF_MAKE_FAST_ALLOCATED;
using Base = JSC::SourceProvider;
@@ -36,7 +36,7 @@ class SourceProvider final : public JSC::SourceProvider {
using SourceOrigin = JSC::SourceOrigin;
public:
static Ref<SourceProvider> create(Zig::GlobalObject*, ResolvedSource resolvedSource, JSC::SourceProviderSourceType sourceType = JSC::SourceProviderSourceType::Module, bool isBuiltIn = false);
static Ref<SourceProvider> create(Zig::GlobalObject*, ResolvedSource& resolvedSource, JSC::SourceProviderSourceType sourceType = JSC::SourceProviderSourceType::Module, bool isBuiltIn = false);
~SourceProvider();
unsigned hash() const override;
StringView source() const override { return StringView(m_source.get()); }

View File

@@ -1,5 +1,7 @@
#include "root.h"
#include "JavaScriptCore/DeleteAllCodeEffort.h"
#include "headers.h"
#include "BunClientData.h"
@@ -3060,7 +3062,7 @@ void JSC__JSInternalPromise__rejectAsHandledException(JSC__JSInternalPromise* ar
JSC__JSInternalPromise* JSC__JSInternalPromise__rejectedPromise(JSC__JSGlobalObject* arg0,
JSC__JSValue JSValue1)
{
return reinterpret_cast<JSC::JSInternalPromise*>(
return jsCast<JSC::JSInternalPromise*>(
JSC::JSInternalPromise::rejectedPromise(arg0, JSC::JSValue::decode(JSValue1)));
}
@@ -4587,8 +4589,11 @@ JSC__JSValue JSC__VM__runGC(JSC__VM* vm, bool sync)
WTF::releaseFastMallocFreeMemory();
if (sync) {
vm->clearSourceProviderCaches();
vm->heap.deleteAllUnlinkedCodeBlocks(JSC::PreventCollectionAndDeleteAllCode);
vm->heap.collectNow(JSC::Sync, JSC::CollectionScope::Full);
} else {
vm->heap.deleteAllUnlinkedCodeBlocks(JSC::DeleteAllCodeIfNotCollecting);
vm->heap.collectSync(JSC::CollectionScope::Full);
}

View File

@@ -311,7 +311,7 @@ extern "C" JSC::EncodedJSValue Bun__runVirtualModule(
JSC::JSGlobalObject* global,
const BunString* specifier);
extern "C" void* Bun__transpileFile(
extern "C" JSC::JSInternalPromise* Bun__transpileFile(
void* bunVM,
JSC::JSGlobalObject* global,
const BunString* specifier,

View File

@@ -363,6 +363,7 @@ const ShellIOWriterAsyncDeinit = bun.shell.Interpreter.AsyncDeinitWriter;
const TimerReference = JSC.BunTimer.Timeout.TimerReference;
const ProcessWaiterThreadTask = if (Environment.isPosix) bun.spawn.WaiterThread.ProcessQueue.ResultTask else opaque {};
const ProcessMiniEventLoopWaiterThreadTask = if (Environment.isPosix) bun.spawn.WaiterThread.ProcessMiniEventLoopQueue.ResultTask else opaque {};
const RuntimeTranspilerStore = JSC.RuntimeTranspilerStore;
// Task.get(ReadFileTask) -> ?ReadFileTask
pub const Task = TaggedPointerUnion(.{
FetchTasklet,
@@ -434,6 +435,7 @@ pub const Task = TaggedPointerUnion(.{
TimerReference,
ProcessWaiterThreadTask,
RuntimeTranspilerStore,
});
const UnboundedQueue = @import("./unbounded_queue.zig").UnboundedQueue;
pub const ConcurrentTask = struct {
@@ -773,8 +775,7 @@ pub const EventLoop = struct {
defer this.debug.exit();
if (count == 1) {
this.global.vm().releaseWeakRefs();
this.drainMicrotasksWithGlobal(this.global);
this.drainMicrotasksWithGlobal(this.global, this.virtual_machine.jsc);
}
this.entered_event_loop_count -= 1;
@@ -807,9 +808,10 @@ pub const EventLoop = struct {
}
extern fn JSC__JSGlobalObject__drainMicrotasks(*JSC.JSGlobalObject) void;
fn drainMicrotasksWithGlobal(this: *EventLoop, globalObject: *JSC.JSGlobalObject) void {
pub fn drainMicrotasksWithGlobal(this: *EventLoop, globalObject: *JSC.JSGlobalObject, jsc_vm: *JSC.VM) void {
JSC.markBinding(@src());
jsc_vm.releaseWeakRefs();
JSC__JSGlobalObject__drainMicrotasks(globalObject);
this.deferred_tasks.run();
@@ -819,8 +821,7 @@ pub const EventLoop = struct {
}
pub fn drainMicrotasks(this: *EventLoop) void {
this.virtual_machine.jsc.releaseWeakRefs();
this.drainMicrotasksWithGlobal(this.global);
this.drainMicrotasksWithGlobal(this.global, this.virtual_machine.jsc);
}
/// When you call a JavaScript function from outside the event loop task
@@ -845,7 +846,7 @@ pub const EventLoop = struct {
pub fn tickQueueWithCount(this: *EventLoop, comptime queue_name: []const u8) u32 {
var global = this.global;
var global_vm = global.vm();
const global_vm = global.vm();
var counter: usize = 0;
if (comptime Environment.isDebug) {
@@ -1171,6 +1172,10 @@ pub const EventLoop = struct {
var any: *ProcessWaiterThreadTask = task.get(ProcessWaiterThreadTask).?;
any.runFromJSThread();
},
@field(Task.Tag, typeBaseName(@typeName(RuntimeTranspilerStore))) => {
var any: *RuntimeTranspilerStore = task.get(RuntimeTranspilerStore).?;
any.drain();
},
@field(Task.Tag, typeBaseName(@typeName(TimerReference))) => {
bun.markPosixOnly();
var any: *TimerReference = task.get(TimerReference).?;
@@ -1185,8 +1190,7 @@ pub const EventLoop = struct {
},
}
global_vm.releaseWeakRefs();
this.drainMicrotasksWithGlobal(global);
this.drainMicrotasksWithGlobal(global, global_vm);
}
@field(this, queue_name).head = if (@field(this, queue_name).count == 0) 0 else @field(this, queue_name).head;
@@ -1424,8 +1428,7 @@ pub const EventLoop = struct {
while (this.tickWithCount() > 0) : (this.global.handleRejectedPromises()) {
this.tickConcurrent();
} else {
global_vm.releaseWeakRefs();
this.drainMicrotasksWithGlobal(global);
this.drainMicrotasksWithGlobal(global, global_vm);
this.tickConcurrent();
if (this.tasks.count > 0) continue;
}

View File

@@ -86,6 +86,8 @@ const Dependency = @import("../install/dependency.zig");
const Async = bun.Async;
const String = bun.String;
const debug = Output.scoped(.ModuleLoader, true);
// Setting BUN_OVERRIDE_MODULE_PATH to the path to the bun repo will make it so modules are loaded
// from there instead of the ones embedded into the binary.
// In debug mode, this is set automatically for you, using the path relative to this file.
@@ -217,11 +219,12 @@ fn setBreakPointOnFirstLine() bool {
}
pub const RuntimeTranspilerStore = struct {
const debug = Output.scoped(.compile, false);
generation_number: std.atomic.Value(u32) = std.atomic.Value(u32).init(0),
store: TranspilerJob.Store,
enabled: bool = true,
queue: Queue = Queue{},
pub const Queue = bun.UnboundedQueue(TranspilerJob, .next);
pub fn init(allocator: std.mem.Allocator) RuntimeTranspilerStore {
return RuntimeTranspilerStore{
@@ -229,6 +232,29 @@ pub const RuntimeTranspilerStore = struct {
};
}
// Thsi is run at the top of the event loop on the JS thread.
pub fn drain(this: *RuntimeTranspilerStore) void {
var batch = this.queue.popBatch();
var iter = batch.iterator();
if (iter.next()) |job| {
// we run just one job first to see if there are more
job.runFromJSThread();
} else {
return;
}
var vm = @fieldParentPtr(JSC.VirtualMachine, "transpiler_store", this);
const event_loop = vm.eventLoop();
const global = vm.global;
const jsc_vm = vm.jsc;
while (iter.next()) |job| {
// if there are more, we need to drain the microtasks from the previous run
event_loop.drainMicrotasksWithGlobal(global, jsc_vm);
job.runFromJSThread();
}
// immediately after this is called, the microtasks will be drained again.
}
pub fn transpile(
this: *RuntimeTranspilerStore,
vm: *JSC.VirtualMachine,
@@ -236,7 +262,6 @@ pub const RuntimeTranspilerStore = struct {
path: Fs.Path,
referrer: []const u8,
) *anyopaque {
debug("transpile({s})", .{path.text});
var job: *TranspilerJob = this.store.get();
const owned_path = Fs.Path.init(bun.default_allocator.dupe(u8, path.text) catch unreachable);
const promise = JSC.JSInternalPromise.create(globalObject);
@@ -253,6 +278,8 @@ pub const RuntimeTranspilerStore = struct {
.file = {},
},
};
if (comptime Environment.allow_assert)
debug("transpile({s}, {s}, async)", .{ path.text, @tagName(job.loader) });
job.schedule();
return promise;
}
@@ -271,6 +298,7 @@ pub const RuntimeTranspilerStore = struct {
parse_error: ?anyerror = null,
resolved_source: ResolvedSource = ResolvedSource{},
work_task: JSC.WorkPoolTask = .{ .callback = runFromWorkerThread },
next: ?*TranspilerJob = null,
pub const Store = bun.HiveArray(TranspilerJob, 64).Fallback;
@@ -302,9 +330,8 @@ pub const RuntimeTranspilerStore = struct {
threadlocal var source_code_printer: ?*js_printer.BufferPrinter = null;
pub fn dispatchToMainThread(this: *TranspilerJob) void {
this.vm.eventLoop().enqueueTaskConcurrent(
JSC.ConcurrentTask.fromCallback(this, runFromJSThread),
);
this.vm.transpiler_store.queue.push(this);
this.vm.eventLoop().enqueueTaskConcurrent(JSC.ConcurrentTask.createFrom(&this.vm.transpiler_store));
}
pub fn runFromJSThread(this: *TranspilerJob) void {
@@ -413,7 +440,7 @@ pub const RuntimeTranspilerStore = struct {
.hot, .watch => {
if (vm.bun_watcher.indexOf(hash)) |index| {
const _fd = vm.bun_watcher.watchlist().items(.fd)[index];
fd = if (_fd.int() > 0) _fd else null;
fd = if (!_fd.isStdio()) _fd else null;
package_json = vm.bun_watcher.watchlist().items(.package_json)[index];
}
},
@@ -567,6 +594,7 @@ pub const RuntimeTranspilerStore = struct {
.source_url = duped.createIfDifferent(path.text),
.hash = 0,
};
this.resolved_source.source_code.value.WTFStringImpl.ensureHash();
return;
}
@@ -637,20 +665,31 @@ pub const RuntimeTranspilerStore = struct {
}
const duped = String.createUTF8(specifier);
const source_code = brk: {
const written = printer.ctx.getWritten();
const result = cache.output_code orelse bun.String.createLatin1(written);
if (written.len > 1024 * 1024 * 2 or vm.smol) {
printer.ctx.buffer.deinit();
source_code_printer.?.* = printer;
}
// In a benchmarking loading @babel/standalone 100 times:
//
// After ensureHash:
// 354.00 ms 4.2% 354.00 ms WTF::StringImpl::hashSlowCase() const
//
// Before ensureHash:
// 506.00 ms 6.1% 506.00 ms WTF::StringImpl::hashSlowCase() const
//
result.value.WTFStringImpl.ensureHash();
break :brk result;
};
this.resolved_source = ResolvedSource{
.allocator = null,
.source_code = brk: {
const written = printer.ctx.getWritten();
const result = cache.output_code orelse bun.String.createLatin1(written);
if (written.len > 1024 * 1024 * 2 or vm.smol) {
printer.ctx.buffer.deinit();
source_code_printer.?.* = printer;
}
break :brk result;
},
.source_code = source_code,
.specifier = duped,
.source_url = duped.createIfDifferent(path.text),
.commonjs_exports = null,
@@ -668,8 +707,6 @@ pub const ModuleLoader = struct {
transpile_source_code_arena: ?*bun.ArenaAllocator = null,
eval_source: ?*logger.Source = null,
const debug = Output.scoped(.ModuleLoader, true);
/// This must be called after calling transpileSourceCode
pub fn resetArena(this: *ModuleLoader, jsc_vm: *VirtualMachine) void {
std.debug.assert(&jsc_vm.module_loader == this);
@@ -1065,6 +1102,7 @@ pub const ModuleLoader = struct {
var spec = bun.String.init(ZigString.init(this.specifier).withEncoding());
var ref = bun.String.init(ZigString.init(this.referrer).withEncoding());
Bun__onFulfillAsyncModule(
this.globalThis,
this.promise.get().?,
&errorable,
&spec,
@@ -1107,6 +1145,7 @@ pub const ModuleLoader = struct {
log.deinit();
Bun__onFulfillAsyncModule(
globalThis,
promise,
&errorable,
&specifier,
@@ -1419,6 +1458,7 @@ pub const ModuleLoader = struct {
}
extern "C" fn Bun__onFulfillAsyncModule(
globalObject: *JSC.JSGlobalObject,
promiseValue: JSC.JSValue,
res: *JSC.ErrorableResolvedSource,
specifier: *bun.String,
@@ -2135,7 +2175,6 @@ pub const ModuleLoader = struct {
JSC.markBinding(@src());
var log = logger.Log.init(jsc_vm.bundler.allocator);
defer log.deinit();
debug("transpileFile: {any}", .{specifier_ptr.*});
var _specifier = specifier_ptr.toUTF8(jsc_vm.allocator);
var referrer_slice = referrer.toUTF8(jsc_vm.allocator);
@@ -2212,6 +2251,9 @@ pub const ModuleLoader = struct {
}
};
if (comptime Environment.allow_assert)
debug("transpile({s}, {s}, sync)", .{ specifier, @tagName(synchronous_loader) });
defer jsc_vm.module_loader.resetArena(jsc_vm);
var promise: ?*JSC.JSInternalPromise = null;

View File

@@ -124,7 +124,7 @@ JSC_DEFINE_HOST_FUNCTION(jsFunctionNodeModuleModuleConstructor,
}
auto *out = Bun::JSCommonJSModule::create(vm, structure, idString, jsNull(),
dirname, nullptr);
dirname, SourceCode());
if (!parentValue.isUndefined())
out->putDirect(vm, JSC::Identifier::fromString(vm, "parent"_s), parentValue,

View File

@@ -20,24 +20,10 @@ namespace WebCore {
using namespace JSC;
#define BUN_COMMON_PRIVATE_IDENTIFIERS_EACH_PROPERTY_NAME(macro) \
macro(AbortSignal) \
macro(Buffer) \
macro(Bun) \
macro(Loader) \
macro(ReadableByteStreamController) \
macro(ReadableStream) \
macro(ReadableStreamBYOBReader) \
macro(ReadableStreamBYOBRequest) \
macro(ReadableStreamDefaultController) \
macro(ReadableStreamDefaultReader) \
macro(TransformStream) \
macro(TransformStreamDefaultController) \
macro(WritableStream) \
macro(WritableStreamDefaultController) \
macro(WritableStreamDefaultWriter) \
macro(__esModule) \
macro(_events) \
macro(abortAlgorithm) \
macro(AbortSignal) \
macro(abortSteps) \
macro(addEventListener) \
macro(appendFromJS) \
@@ -49,6 +35,8 @@ using namespace JSC;
macro(backpressureChangePromise) \
macro(basename) \
macro(body) \
macro(Buffer) \
macro(Bun) \
macro(bunNativePtr) \
macro(bunNativeType) \
macro(byobRequest) \
@@ -58,11 +46,11 @@ using namespace JSC;
macro(cloneArrayBuffer) \
macro(close) \
macro(closeAlgorithm) \
macro(closeRequest) \
macro(closeRequested) \
macro(closed) \
macro(closedPromise) \
macro(closedPromiseCapability) \
macro(closeRequest) \
macro(closeRequested) \
macro(code) \
macro(connect) \
macro(consumeReadableStream) \
@@ -75,8 +63,8 @@ using namespace JSC;
macro(createInternalModuleById) \
macro(createNativeReadableStream) \
macro(createReadableStream) \
macro(createUsedReadableStream) \
macro(createUninitializedArrayBuffer) \
macro(createUsedReadableStream) \
macro(createWritableStreamFromInternal) \
macro(cwd) \
macro(data) \
@@ -95,6 +83,7 @@ using namespace JSC;
macro(errno) \
macro(errorSteps) \
macro(evaluateCommonJSModule) \
macro(evaluated) \
macro(execArgv) \
macro(exports) \
macro(extname) \
@@ -138,13 +127,14 @@ using namespace JSC;
macro(lazy) \
macro(lazyStreamPrototypeMap) \
macro(loadCJS2ESM) \
macro(Loader) \
macro(localStreams) \
macro(main) \
macro(makeDOMException) \
macro(makeGetterTypeError) \
macro(makeThisTypeError) \
macro(mockedFunction) \
macro(method) \
macro(mockedFunction) \
macro(nextTick) \
macro(normalize) \
macro(on) \
@@ -177,12 +167,18 @@ using namespace JSC;
macro(put) \
macro(queue) \
macro(read) \
macro(readIntoRequests) \
macro(readRequests) \
macro(readable) \
macro(ReadableByteStreamController) \
macro(ReadableStream) \
macro(ReadableStreamBYOBReader) \
macro(ReadableStreamBYOBRequest) \
macro(readableStreamController) \
macro(ReadableStreamDefaultController) \
macro(ReadableStreamDefaultReader) \
macro(readableStreamToArray) \
macro(reader) \
macro(readIntoRequests) \
macro(readRequests) \
macro(readyPromise) \
macro(readyPromiseCapability) \
macro(redirect) \
@@ -226,6 +222,8 @@ using namespace JSC;
macro(toNamespacedPath) \
macro(trace) \
macro(transformAlgorithm) \
macro(TransformStream) \
macro(TransformStreamDefaultController) \
macro(uncork) \
macro(underlyingByteSource) \
macro(underlyingSink) \
@@ -239,10 +237,13 @@ using namespace JSC;
macro(view) \
macro(whenSignalAborted) \
macro(writable) \
macro(WritableStream) \
macro(WritableStreamDefaultController) \
macro(WritableStreamDefaultWriter) \
macro(write) \
macro(writeAlgorithm) \
macro(writeRequests) \
macro(writer) \
macro(writeRequests) \
macro(writing) \
macro(written) \

View File

@@ -4,32 +4,63 @@ $visibility = "Private";
export function loadCJS2ESM(this: ImportMetaObject, resolvedSpecifier: string) {
var loader = Loader;
var queue = $createFIFO();
var key = resolvedSpecifier;
let key = resolvedSpecifier;
const registry = loader.registry;
while (key) {
// we need to explicitly check because state could be $ModuleFetch
// it will throw this error if we do not:
// $throwTypeError("Requested module is already fetched.");
var entry = loader.registry.$get(key)!;
let entry = registry.$get(key)!,
moduleRecordPromise,
state = 0,
// entry.fetch is a Promise<SourceCode>
// SourceCode is not a string, it's a JSC::SourceCode object
fetch: Promise<SourceCode> | undefined;
if ((entry?.state ?? 0) <= $ModuleFetch) {
$fulfillModuleSync(key);
entry = loader.registry.$get(key)!;
if (entry) {
({ state, fetch } = entry);
}
// entry.fetch is a Promise<SourceCode>
// SourceCode is not a string, it's a JSC::SourceCode object
// this pulls it out of the promise without delaying by a tick
// the promise is already fullfilled by $fullfillModuleSync
var sourceCodeObject = $getPromiseInternalField(entry.fetch, $promiseFieldReactionsOrResult);
// parseModule() returns a Promise, but the value is already fulfilled
// so we just pull it out of the promise here once again
// But, this time we do it a little more carefully because this is a JSC function call and not bun source code
var moduleRecordPromise = loader.parseModule(key, sourceCodeObject);
var mod = entry.module;
if (
!entry ||
// if we need to fetch it
(state <= $ModuleFetch &&
// either:
// - we've never fetched it
// - a fetch is in progress
(!$isPromise(fetch) ||
($getPromiseInternalField(fetch, $promiseFieldFlags) & $promiseStateMask) === $promiseStatePending))
) {
// force it to be no longer pending
$fulfillModuleSync(key);
entry = registry.$get(key)!;
// the state can transition here
// https://github.com/oven-sh/bun/issues/8965
if (entry) {
({ state = 0, fetch } = entry);
}
}
if (state < $ModuleLink && $isPromise(fetch)) {
// This will probably never happen, but just in case
if (($getPromiseInternalField(fetch, $promiseFieldFlags) & $promiseStateMask) === $promiseStatePending) {
throw new TypeError(`require() async module "${key}" is unsupported. use "await import()" instead.`);
}
// this pulls it out of the promise without delaying by a tick
// the promise is already fullfilled by $fullfillModuleSync
const sourceCodeObject = $getPromiseInternalField(fetch, $promiseFieldReactionsOrResult);
moduleRecordPromise = loader.parseModule(key, sourceCodeObject);
}
let mod = entry?.module;
if (moduleRecordPromise && $isPromise(moduleRecordPromise)) {
var reactionsOrResult = $getPromiseInternalField(moduleRecordPromise, $promiseFieldReactionsOrResult);
var flags = $getPromiseInternalField(moduleRecordPromise, $promiseFieldFlags);
var state = flags & $promiseStateMask;
let reactionsOrResult = $getPromiseInternalField(moduleRecordPromise, $promiseFieldReactionsOrResult);
let flags = $getPromiseInternalField(moduleRecordPromise, $promiseFieldFlags);
let state = flags & $promiseStateMask;
// this branch should never happen, but just to be safe
if (state === $promiseStatePending || (reactionsOrResult && $isPromise(reactionsOrResult))) {
throw new TypeError(`require() async module "${key}" is unsupported. use "await import()" instead.`);
@@ -51,15 +82,15 @@ export function loadCJS2ESM(this: ImportMetaObject, resolvedSpecifier: string) {
// This is very similar to "requestInstantiate" in ModuleLoader.js in JavaScriptCore.
$setStateToMax(entry, $ModuleLink);
var dependenciesMap = mod.dependenciesMap;
var requestedModules = loader.requestedModules(mod);
var dependencies = $newArrayWithSize<string>(requestedModules.length);
const dependenciesMap = mod.dependenciesMap;
const requestedModules = loader.requestedModules(mod);
const dependencies = $newArrayWithSize<string>(requestedModules.length);
for (var i = 0, length = requestedModules.length; i < length; ++i) {
var depName = requestedModules[i];
const depName = requestedModules[i];
// optimization: if it starts with a slash then it's an absolute path
// we don't need to run the resolver a 2nd time
var depKey = depName[0] === "/" ? depName : loader.resolve(depName, key);
var depEntry = loader.ensureRegistered(depKey);
const depKey = depName[0] === "/" ? depName : loader.resolve(depName, key);
const depEntry = loader.ensureRegistered(depKey);
if (depEntry.state < $ModuleLink) {
queue.push(depKey);
@@ -76,7 +107,7 @@ export function loadCJS2ESM(this: ImportMetaObject, resolvedSpecifier: string) {
entry.isSatisfied = true;
key = queue.shift();
while (key && (loader.registry.$get(key)?.state ?? $ModuleFetch) >= $ModuleLink) {
while (key && (registry.$get(key)?.state ?? $ModuleFetch) >= $ModuleLink) {
key = queue.shift();
}
}
@@ -90,7 +121,7 @@ export function loadCJS2ESM(this: ImportMetaObject, resolvedSpecifier: string) {
);
}
return loader.registry.$get(resolvedSpecifier);
return registry.$get(resolvedSpecifier);
}
$visibility = "Private";

View File

@@ -121,6 +121,13 @@ pub const WTFStringImplStruct = extern struct {
return ZigString.Slice.init(this.refCountAllocator(), this.latin1Slice());
}
extern fn Bun__WTFStringImpl__ensureHash(this: WTFStringImpl) void;
/// Compute the hash() if necessary
pub fn ensureHash(this: WTFStringImpl) void {
JSC.markBinding(@src());
Bun__WTFStringImpl__ensureHash(this);
}
pub fn toUTF8(this: WTFStringImpl, allocator: std.mem.Allocator) ZigString.Slice {
if (this.is8Bit()) {
if (bun.strings.toUTF8FromLatin1(allocator, this.latin1Slice()) catch bun.outOfMemory()) |utf8| {

View File

@@ -1,7 +1,8 @@
const count = process.platform === "win32" ? 10_000 : 100_000;
const count = process.platform === "win32" ? 1000 : 10_000;
for (let i = 0; i < count; i++) {
await import("./text-loader-fixture-text-file.txt?" + i++);
}
Bun.gc(true);
const { default: text } = await import("./text-loader-fixture-text-file.txt");

View File

@@ -0,0 +1,19 @@
import { test, expect } from "bun:test";
import { bunEnv, bunExe } from "harness";
import { join } from "path";
test("06946", async () => {
const buns = Array.from(
{ length: 25 },
() =>
Bun.spawn({
cmd: [bunExe(), join(import.meta.dir, "t.mjs")],
cwd: import.meta.dir,
stdio: ["inherit", "inherit", "inherit"],
env: bunEnv,
}).exited,
);
const exited = await Promise.all(buns);
expect(exited).toEqual(Array.from({ length: 25 }, () => 0));
});

View File

@@ -0,0 +1,3 @@
class Logger {}
exports.Logger = Logger;
console.log("l.js has loaded");

View File

@@ -0,0 +1,4 @@
import { Logger } from "./l";
import "./t2";
Logger.apply;
console.log("t1 end");

View File

@@ -0,0 +1,3 @@
console.log("t2 begin");
require("./t3");
console.log("t2 end");

View File

@@ -0,0 +1,5 @@
console.log("t3 begin");
import { Logger } from "./l";
console.log("t3 end");
Logger.apply;
console.log("t3 postend");

View File

@@ -0,0 +1,19 @@
import { test, expect } from "bun:test";
import { bunEnv, bunExe } from "harness";
import { join } from "path";
test("08965", async () => {
const buns = Array.from(
{ length: 25 },
() =>
Bun.spawn({
cmd: [bunExe(), join(import.meta.dir, "1.ts")],
cwd: import.meta.dir,
stdio: ["inherit", "inherit", "inherit"],
env: bunEnv,
}).exited,
);
const exited = await Promise.all(buns);
expect(exited).toEqual(Array.from({ length: 25 }, () => 0));
});

View File

@@ -0,0 +1,8 @@
import SomeClass from "./3";
import { config } from "./5";
const client = {};
console.log(SomeClass);
client.config = config;

View File

@@ -0,0 +1,3 @@
import somedata = require("./4");
export default class SomeClass {}

View File

@@ -0,0 +1,4 @@
// 4.js
const config = require(`./5`);
module.exports = {};

View File

@@ -0,0 +1,6 @@
const config_ = {};
type GeneratedConfigType = typeof config_;
export interface Config extends GeneratedConfigType {}
export const config = config_ as Config;