Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
1 change: 1 addition & 0 deletions src/bun_core/env_var.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ pub mod feature_flag {
new_feature_flag!(pub BUN_FEATURE_FLAG_DISABLE_ADDRCONFIG, "BUN_FEATURE_FLAG_DISABLE_ADDRCONFIG", {});
new_feature_flag!(pub BUN_FEATURE_FLAG_DISABLE_ASYNC_TRANSPILER, "BUN_FEATURE_FLAG_DISABLE_ASYNC_TRANSPILER", {});
new_feature_flag!(pub BUN_FEATURE_FLAG_DISABLE_ISOLATION_SOURCE_CACHE, "BUN_FEATURE_FLAG_DISABLE_ISOLATION_SOURCE_CACHE", {});
new_feature_flag!(pub BUN_FEATURE_FLAG_DISABLE_ISOLATION_GLOBAL_REUSE, "BUN_FEATURE_FLAG_DISABLE_ISOLATION_GLOBAL_REUSE", {});
new_feature_flag!(pub BUN_FEATURE_FLAG_DISABLE_DNS_CACHE, "BUN_FEATURE_FLAG_DISABLE_DNS_CACHE", {});
new_feature_flag!(pub BUN_FEATURE_FLAG_DISABLE_DNS_CACHE_LIBINFO, "BUN_FEATURE_FLAG_DISABLE_DNS_CACHE_LIBINFO", {});
// Force the event loop to use epoll_pwait(2) instead of epoll_pwait2(2).
Expand Down
6 changes: 6 additions & 0 deletions src/js/internal-for-testing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,12 @@ export const dnsCacheSeed = $newRustFunction("runtime/dns_jsc/dns.rs", "internal
addresses: string[],
) => number[];

export const testIsolationResetStats = $newCppFunction(
"InternalForTesting.cpp",
"jsFunction_testIsolationResetStats",
0,
) as () => { reuse: number; swap: number };

export const fetchH2Internals = {
liveCounts: $newRustFunction("http/H2Client.rs", "TestingAPIs.liveCounts", 0) as () => {
sessions: number;
Expand Down
10 changes: 10 additions & 0 deletions src/jsc/JSGlobalObject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1386,6 +1386,14 @@ impl JSGlobalObject {
Zig__GlobalObject__createForTestIsolation(old_global, console)
}

pub(crate) fn capture_test_isolation_baseline(global: &JSGlobalObject) {
Zig__GlobalObject__captureTestIsolationBaseline(global)
}

pub(crate) fn try_reset_for_test_isolation(global: &JSGlobalObject) -> bool {
Zig__GlobalObject__tryResetForTestIsolation(global)
}

pub fn report_uncaught_exception_from_error(&self, proof: JsError) {
crate::mark_binding();
let exc = self
Expand Down Expand Up @@ -1649,6 +1657,8 @@ unsafe extern "C" {
old_global: &JSGlobalObject,
console: *mut c_void,
) -> *mut JSGlobalObject;
safe fn Zig__GlobalObject__captureTestIsolationBaseline(global: &JSGlobalObject);
safe fn Zig__GlobalObject__tryResetForTestIsolation(global: &JSGlobalObject) -> bool;
}

impl ScriptExecutionContextIdentifier {
Expand Down
25 changes: 21 additions & 4 deletions src/jsc/VirtualMachine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,9 @@ pub struct VirtualMachine {
#[derive(Default)]
pub struct TestIsolationState {
pub saved_cwd: Option<Box<[u8]>>,
/// Cleared on every full swap so the next file re-captures its baseline.
pub baseline_captured: bool,
pub force_full_swap: bool,
}

// ──────────────────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -4454,6 +4457,11 @@ impl VirtualMachine {
}
}

if self.test_isolation_enabled && !self.test_isolation_state.baseline_captured {
JSGlobalObject::capture_test_isolation_baseline(self.global());
self.test_isolation_state.baseline_captured = true;
}
Comment thread
claude[bot] marked this conversation as resolved.
Outdated

// Note: reshaped for borrowck.
let global = self.global;
let main_str = bun_core::String::from_bytes(self.main());
Expand Down Expand Up @@ -4690,13 +4698,22 @@ impl VirtualMachine {
self.unhandled_error_counter = 0;

let old_global = self.global;
let old_global_ref = JSGlobalObject::opaque_ref(old_global);

// Scrub and reuse the global if the file left it in its post-preload
// shape; node_modules CodeBlocks and JIT'd code then survive.
Comment thread
robobun marked this conversation as resolved.
Outdated
if !self.test_isolation_state.force_full_swap
&& JSGlobalObject::try_reset_for_test_isolation(old_global_ref)
{
return;
}
Comment thread
robobun marked this conversation as resolved.

// `old_global` valid for VM lifetime (safe ZST-handle deref);
// `console` is the live per-VM ConsoleObject.
let new_global: *mut JSGlobalObject = JSGlobalObject::create_for_test_isolation(
JSGlobalObject::opaque_ref(old_global),
self.console.cast(),
);
let new_global: *mut JSGlobalObject =
JSGlobalObject::create_for_test_isolation(old_global_ref, self.console.cast());
self.global = new_global;
self.test_isolation_state.baseline_captured = false;
VMHolder::set_cached_global_object(Some(new_global));
self.regular_event_loop.global = NonNull::new(new_global);
self.macro_event_loop.global = NonNull::new(new_global);
Expand Down
14 changes: 14 additions & 0 deletions src/jsc/bindings/BunClientData.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ class GlobalObject;

namespace Bun {
class StrongRootBlock;
struct TestIsolationBaseline;
struct InternalModuleExecutableCache;
}

namespace WebCore {
Expand Down Expand Up @@ -141,6 +143,18 @@ class JSVMClientData : public JSC::VM::ClientData {
// after every swap.
WTF::UncheckedKeyHashMap<WTF::String, RefPtr<JSC::SourceProvider>> isolationSourceProviderCache;

// See Zig__GlobalObject__captureTestIsolationBaseline (ZigGlobalObject.cpp).
struct TestIsolationBaselineDeleter {
void operator()(Bun::TestIsolationBaseline*) const;
};
std::unique_ptr<Bun::TestIsolationBaseline, TestIsolationBaselineDeleter> testIsolationBaseline;

// See InternalModuleExecutableCache (InternalModuleRegistry.cpp).
struct InternalModuleExecutableCacheDeleter {
void operator()(Bun::InternalModuleExecutableCache*) const;
};
std::unique_ptr<Bun::InternalModuleExecutableCache, InternalModuleExecutableCacheDeleter> internalModuleExecutableCache;

private:
bool isWebCoreJSClientData() const final { return true; }

Expand Down
1 change: 1 addition & 0 deletions src/jsc/bindings/BunProcess.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ class Process : public WebCore::JSEventEmitter {

JSString* cachedCwd() { return m_cachedCwd.get(); }
void setCachedCwd(JSC::VM& vm, JSString* cwd) { m_cachedCwd.set(vm, this, cwd); }
void clearCachedCwd() { m_cachedCwd.clear(); }

JSValue getArgv(JSGlobalObject* globalObject);
void setArgv(JSGlobalObject* globalObject, JSValue argv);
Expand Down
12 changes: 12 additions & 0 deletions src/jsc/bindings/InternalForTesting.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#endif

extern "C" void BunString__toThreadSafe(BunString* str);
extern "C" void Zig__GlobalObject__testIsolationResetStats(Zig::GlobalObject*, uint32_t*, uint32_t*);

namespace Bun {

Expand All @@ -31,6 +32,17 @@ JSC_DEFINE_HOST_FUNCTION(jsFunction_lowercaseHeaderNameSIMD, (JSC::JSGlobalObjec
return JSC::JSValue::encode(JSC::jsString(vm, WebCore::lowercaseHeaderName(string)));
}

JSC_DEFINE_HOST_FUNCTION(jsFunction_testIsolationResetStats, (JSC::JSGlobalObject * globalObject, JSC::CallFrame*))
{
auto& vm = globalObject->vm();
uint32_t reuse = 0, swap = 0;
Zig__GlobalObject__testIsolationResetStats(defaultGlobalObject(globalObject), &reuse, &swap);
auto* obj = JSC::constructEmptyObject(globalObject);
obj->putDirect(vm, Identifier::fromString(vm, "reuse"_s), jsNumber(reuse));
obj->putDirect(vm, Identifier::fromString(vm, "swap"_s), jsNumber(swap));
return JSValue::encode(obj);
}

JSC_DEFINE_HOST_FUNCTION(jsFunction_arrayBufferViewHasBuffer, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
{
auto value = callFrame->argument(0);
Expand Down
1 change: 1 addition & 0 deletions src/jsc/bindings/InternalForTesting.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ JSC_DECLARE_HOST_FUNCTION(jsFunction_BunString_toThreadSafeRefCountDelta);
JSC_DECLARE_HOST_FUNCTION(jsFunction_lowercaseHeaderNameSIMD);
JSC_DECLARE_HOST_FUNCTION(jsFunction_emitMemoryPressure);
JSC_DECLARE_HOST_FUNCTION(jsFunction_isMemoryPressureWatcherInstalled);
JSC_DECLARE_HOST_FUNCTION(jsFunction_testIsolationResetStats);

}
84 changes: 64 additions & 20 deletions src/jsc/bindings/InternalModuleRegistry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,34 @@
#include <JavaScriptCore/LazyPropertyInlines.h>
#include <JavaScriptCore/VMTrapsInlines.h>
#include <JavaScriptCore/JSModuleLoader.h>
#include <JavaScriptCore/StrongInlines.h>
#include <JavaScriptCore/Debugger.h>
#include <utility>

#include "InternalModuleRegistryConstants.h"
#include "wtf/Forward.h"

#include "NativeModuleImpl.h"

extern "C" bool isBunTest;
extern "C" bool Bun__VM__useIsolationSourceProviderCache(void* bunVM);

namespace Bun {

// createBuiltinExecutable bypasses CodeCache, so without this per-VM cache
// every --isolate global re-parses + re-bytecodegens each internal module body.
// Strong: a Weak would clear once the outgoing global's JSFunction is collected.
Comment thread
robobun marked this conversation as resolved.
Outdated
struct InternalModuleExecutableCache {
WTF_DEPRECATED_MAKE_FAST_ALLOCATED(InternalModuleExecutableCache);

public:
struct Entry {
JSC::SourceCode source;
JSC::Strong<JSC::UnlinkedFunctionExecutable> executable;
};
std::array<Entry, BUN_INTERNAL_MODULE_COUNT> entries {};
};

extern "C" bool BunTest__shouldGenerateCodeCoverage(BunString sourceURL);
extern "C" void ByteRangeMapping__generate(BunString sourceURL, BunString code, int sourceID);

Expand All @@ -33,24 +52,44 @@ static void maybeAddCodeCoverage(JSC::VM& vm, const JSC::SourceCode& code)
// JS builtin that acts as a module. In debug mode, we use a different implementation that reads
// from the developer's filesystem. This allows reloading code without recompiling bindings.

JSC::JSValue generateModule(JSC::JSGlobalObject* globalObject, JSC::VM& vm, const String& SOURCE, const String& moduleName, const String& urlString)
JSC::JSValue generateModule(JSC::JSGlobalObject* globalObject, JSC::VM& vm, InternalModuleRegistry::Field id, const String& SOURCE, const String& moduleName, const String& urlString)
{
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto&& origin = SourceOrigin(WTF::URL(urlString));
SourceCode source = JSC::makeSource(SOURCE, origin, JSC::SourceTaintedOrigin::Untainted, moduleName);
maybeAddCodeCoverage(vm, source);
JSFunction* func
= JSFunction::create(
vm, globalObject,
createBuiltinExecutable(
vm, source,
Identifier::fromString(vm, moduleName),
ImplementationVisibility::Public,
ConstructorKind::None,
ConstructAbility::CannotConstruct,
InlineAttribute::None)
->link(vm, nullptr, source),
static_cast<JSC::JSGlobalObject*>(globalObject));
auto* clientData = WebCore::clientData(vm);

JSC::UnlinkedFunctionExecutable* unlinked = nullptr;
JSC::SourceCode source;
if (auto* cache = clientData->internalModuleExecutableCache.get()) {
auto& entry = cache->entries[static_cast<unsigned>(id)];
if (entry.executable) {
unlinked = entry.executable.get();
source = entry.source;
}
}
if (!unlinked) {
auto&& origin = SourceOrigin(WTF::URL(urlString));
source = JSC::makeSource(SOURCE, origin, JSC::SourceTaintedOrigin::Untainted, moduleName);
maybeAddCodeCoverage(vm, source);
unlinked = createBuiltinExecutable(
vm, source,
Identifier::fromString(vm, moduleName),
ImplementationVisibility::Public,
ConstructorKind::None,
ConstructAbility::CannotConstruct,
InlineAttribute::None);
if (isBunTest && Bun__VM__useIsolationSourceProviderCache(clientData->bunVM)) {
if (!clientData->internalModuleExecutableCache)
clientData->internalModuleExecutableCache.reset(new InternalModuleExecutableCache);
auto& entry = clientData->internalModuleExecutableCache->entries[static_cast<unsigned>(id)];
entry.source = source;
entry.executable.set(vm, unlinked);
}
}

JSFunction* func = JSFunction::create(
Comment thread
claude[bot] marked this conversation as resolved.
Outdated
vm, globalObject,
unlinked->link(vm, nullptr, source),
static_cast<JSC::JSGlobalObject*>(globalObject));

RETURN_IF_EXCEPTION(throwScope, {});
if (globalObject->hasDebugger() && globalObject->debugger()->isInteractivelyDebugging()) [[unlikely]] {
Expand Down Expand Up @@ -98,12 +137,12 @@ ALWAYS_INLINE JSC::JSValue generateNativeModule(
}

#ifdef BUN_DYNAMIC_JS_LOAD_PATH
JSValue initializeInternalModuleFromDisk(JSGlobalObject* globalObject, VM& vm, const WTF::String& moduleName, WTF::String fileBase, const WTF::String& urlString)
JSValue initializeInternalModuleFromDisk(JSGlobalObject* globalObject, VM& vm, InternalModuleRegistry::Field id, const WTF::String& moduleName, WTF::String fileBase, const WTF::String& urlString)
{
WTF::String file = makeString(ASCIILiteral::fromLiteralUnsafe(BUN_DYNAMIC_JS_LOAD_PATH), "/"_s, WTF::move(fileBase));
if (auto contents = WTF::FileSystemImpl::readEntireFile(file)) {
auto string = WTF::String::fromUTF8(contents.value());
return generateModule(globalObject, vm, string, moduleName, urlString);
return generateModule(globalObject, vm, id, string, moduleName, urlString);
} else {
printf("\nFATAL: bun-debug failed to load bundled version of \"%s\" at \"%s\" (was it deleted?)\n"
"Please re-compile Bun to continue.\n\n",
Expand All @@ -112,15 +151,15 @@ JSValue initializeInternalModuleFromDisk(JSGlobalObject* globalObject, VM& vm, c
}
}
#define INTERNAL_MODULE_REGISTRY_GENERATE(globalObject, vm, moduleId, filename, OFFSET, LENGTH, urlString) \
return initializeInternalModuleFromDisk(globalObject, vm, moduleId, filename, urlString)
return initializeInternalModuleFromDisk(globalObject, vm, id, moduleId, filename, urlString)
#else

// The module sources are linked as one read-only blob (bun_internal_modules_data,
// see the generated InternalModuleRegistryConstants.S); each module is a span at
// a known offset/length. createWithoutCopying is the same path the old
// ASCIILiteral → String conversion took.
#define INTERNAL_MODULE_REGISTRY_GENERATE(globalObject, vm, moduleId, filename, OFFSET, LENGTH, urlString) \
return generateModule(globalObject, vm, \
return generateModule(globalObject, vm, id, \
WTF::String(WTF::StringImpl::createWithoutCopying(std::span<const char>(bun_internal_modules_data + (OFFSET), (LENGTH)))), \
moduleId, urlString)
#endif
Expand Down Expand Up @@ -197,4 +236,9 @@ JSC_DEFINE_HOST_FUNCTION(InternalModuleRegistry::jsCreateInternalModuleById, (JS

} // namespace Bun

void WebCore::JSVMClientData::InternalModuleExecutableCacheDeleter::operator()(Bun::InternalModuleExecutableCache* p) const
{
delete p;
}

#undef INTERNAL_MODULE_REGISTRY_GENERATE
Loading
Loading