From 77922fd29a49c5e9a659301dccd822c558ec1bdd Mon Sep 17 00:00:00 2001 From: Claude Bot Date: Wed, 18 Mar 2026 17:51:43 +0000 Subject: [PATCH 1/3] fix(windows): implement bmalloc SystemHeap using mimalloc to fix GC crashes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit bmalloc's SystemHeap on Windows is unimplemented — all allocation methods crash with RELEASE_BASSERT_NOT_REACHED. When libpas triggers SystemHeap allocations during GC cycles under memory pressure, this causes segfaults in JSC's Structure/WriteBarrier code paths. This adds a working SystemHeap implementation backed by mimalloc that overrides bmalloc.lib's crashing stubs at link time. Combined with setting Malloc=1 at startup to force bmalloc to route through SystemHeap, this bypasses libpas for all allocations on Windows. Fixes #22349 Co-Authored-By: Claude Opus 4.6 (1M context) --- cmake/targets/BuildBun.cmake | 6 + .../bindings/BmallocSystemHeapOverride.cpp | 286 ++++++++++++++++++ src/c-headers-for-zig.h | 1 + src/main.zig | 12 + 4 files changed, 305 insertions(+) create mode 100644 src/bun.js/bindings/BmallocSystemHeapOverride.cpp diff --git a/cmake/targets/BuildBun.cmake b/cmake/targets/BuildBun.cmake index 057314164ab..07c48821458 100644 --- a/cmake/targets/BuildBun.cmake +++ b/cmake/targets/BuildBun.cmake @@ -1265,6 +1265,12 @@ if(BUN_LINK_ONLY) endif() if(WIN32) + # bmalloc.lib is still linked because WTF.lib references bmalloc symbols. + # However, bmalloc's SystemHeap is unimplemented on Windows (all methods + # crash), so BmallocSystemHeapOverride.cpp provides a working implementation + # using mimalloc. Combined with setting Malloc=1 at startup (main.zig), this + # bypasses libpas for all allocations. + # See: https://github.com/oven-sh/bun/issues/22349 if(DEBUG) target_link_libraries(${bun} PRIVATE ${WEBKIT_LIB_PATH}/WTF.lib diff --git a/src/bun.js/bindings/BmallocSystemHeapOverride.cpp b/src/bun.js/bindings/BmallocSystemHeapOverride.cpp new file mode 100644 index 00000000000..62ee7897fcf --- /dev/null +++ b/src/bun.js/bindings/BmallocSystemHeapOverride.cpp @@ -0,0 +1,286 @@ +/** + * Working SystemHeap implementation for Windows using mimalloc. + * + * bmalloc's SystemHeap is unimplemented on Windows — all methods crash with + * RELEASE_BASSERT_NOT_REACHED. This file provides a functional replacement + * that uses mimalloc for allocations. + * + * Combined with setting the "Malloc" environment variable at process startup, + * this causes bmalloc/libpas to redirect all allocations through SystemHeap + * instead of using libpas directly, avoiding GC crashes on Windows. + * + * See: https://github.com/oven-sh/bun/issues/22349 + * https://github.com/oven-sh/bun/issues/21569 + * + * How it works: + * 1. Bun sets Malloc=1 env var early in main() (see src/main.zig) + * 2. bmalloc's Environment reads Malloc=1 and enables SystemHeap + * 3. All bmalloc allocations route through SystemHeap + * 4. This file provides SystemHeap using mimalloc instead of crashing + * + * Link-time override: + * This object file defines the same symbols as bmalloc.lib's + * SystemHeap.cpp.o. Since object files are processed before static + * libraries, the linker uses these definitions and skips bmalloc.lib's + * unimplemented stubs. + */ + +#include + +#if BOS(WINDOWS) + +// We are providing the implementation of bmalloc's SystemHeap, so we need +// BEXPORT to resolve to __declspec(dllexport) rather than dllimport. +#define BUILDING_bmalloc + +#include +#include +#include +#include +#include "mimalloc.h" + +#if BENABLE(LIBPAS) +#include +#endif + +namespace bmalloc { + +SystemHeap* systemHeapCache { nullptr }; + +BALLOW_DEPRECATED_DECLARATIONS_BEGIN +DEFINE_STATIC_PER_PROCESS_STORAGE(SystemHeap); +BALLOW_DEPRECATED_DECLARATIONS_END + +SystemHeap::SystemHeap(const LockHolder&) + : m_pageSize(vmPageSize()) +{ +} + +void* SystemHeap::malloc(size_t size, FailureAction action) +{ + void* result = mi_malloc(size); + RELEASE_BASSERT(action == FailureAction::ReturnNull || result); + return result; +} + +void* SystemHeap::memalign(size_t alignment, size_t size, FailureAction action) +{ + void* result = mi_malloc_aligned(size, alignment); + RELEASE_BASSERT(action == FailureAction::ReturnNull || result); + return result; +} + +void* SystemHeap::realloc(void* object, size_t size, FailureAction action) +{ + void* result = mi_realloc(object, size); + RELEASE_BASSERT(action == FailureAction::ReturnNull || result); + return result; +} + +void SystemHeap::free(void* object) +{ + mi_free(object); +} + +void SystemHeap::scavenge() +{ + mi_collect(false); +} + +void SystemHeap::dump() +{ +} + +void* SystemHeap::memalignLarge(size_t alignment, size_t size) +{ + alignment = roundUpToMultipleOf(m_pageSize, alignment); + size = roundUpToMultipleOf(m_pageSize, size); + void* result = tryVMAllocate(alignment, size); + if (!result) + return nullptr; + { + LockHolder locker(mutex()); + m_sizeMap[result] = size; + } + return result; +} + +void SystemHeap::freeLarge(void* base) +{ + if (!base) + return; + + size_t size; + { + LockHolder locker(mutex()); + size = m_sizeMap[base]; + size_t numErased = m_sizeMap.erase(base); + RELEASE_BASSERT(numErased == 1); + } + + vmDeallocate(base, size); +} + +SystemHeap* SystemHeap::tryGetSlow() +{ + SystemHeap* result; + if (Environment::get()->isSystemHeapEnabled()) { + systemHeapCache = SystemHeap::get(); + result = systemHeapCache; + } else { + systemHeapCache = systemHeapDisabled(); + result = nullptr; + } + RELEASE_BASSERT(systemHeapCache); + return result; +} + +} // namespace bmalloc + +#if BENABLE(LIBPAS) + +#if BUSE(LIBPAS) + +using namespace bmalloc; + +bool pas_system_heap_is_enabled(pas_heap_config_kind kind) +{ + switch (kind) { + case pas_heap_config_kind_bmalloc: + return !!SystemHeap::tryGet(); + case pas_heap_config_kind_jit: + case pas_heap_config_kind_pas_utility: + return false; + default: + BCRASH(); + return false; + } +} + +bool pas_system_heap_should_supplant_bmalloc(pas_heap_config_kind kind) +{ + SystemHeap* heap; + switch (kind) { + case pas_heap_config_kind_bmalloc: + heap = SystemHeap::tryGet(); + if (!heap) + return false; + return heap->shouldSupplantBmalloc(); + case pas_heap_config_kind_jit: + case pas_heap_config_kind_pas_utility: + return false; + default: + BCRASH(); + return false; + } +} + +void* pas_system_heap_malloc(size_t size) +{ + return SystemHeap::getExisting()->malloc(size, FailureAction::ReturnNull); +} + +void* pas_system_heap_memalign(size_t alignment, size_t size) +{ + return SystemHeap::getExisting()->memalign(alignment, size, FailureAction::ReturnNull); +} + +void* pas_system_heap_realloc(void* ptr, size_t size) +{ + return SystemHeap::getExisting()->realloc(ptr, size, FailureAction::ReturnNull); +} + +void pas_system_heap_free(void* ptr) +{ + SystemHeap::getExisting()->free(ptr); +} + +void* pas_system_heap_malloc_compact(size_t size) +{ + return SystemHeap::getExisting()->malloc(size, FailureAction::ReturnNull); +} + +void* pas_system_heap_memalign_compact(size_t alignment, size_t size) +{ + return SystemHeap::getExisting()->memalign(alignment, size, FailureAction::ReturnNull); +} + +void* pas_system_heap_realloc_compact(void* ptr, size_t size) +{ + return SystemHeap::getExisting()->realloc(ptr, size, FailureAction::ReturnNull); +} + +#else // !BUSE(LIBPAS) + +bool pas_system_heap_is_enabled(pas_heap_config_kind kind) +{ + BUNUSED_PARAM(kind); + return false; +} + +bool pas_system_heap_should_supplant_bmalloc(pas_heap_config_kind kind) +{ + BUNUSED_PARAM(kind); + return false; +} + +void* pas_system_heap_malloc(size_t size) +{ + BUNUSED_PARAM(size); + RELEASE_BASSERT_NOT_REACHED(); + return nullptr; +} + +void* pas_system_heap_memalign(size_t alignment, size_t size) +{ + BUNUSED_PARAM(size); + BUNUSED_PARAM(alignment); + RELEASE_BASSERT_NOT_REACHED(); + return nullptr; +} + +void* pas_system_heap_realloc(void* ptr, size_t size) +{ + BUNUSED_PARAM(ptr); + BUNUSED_PARAM(size); + RELEASE_BASSERT_NOT_REACHED(); + return nullptr; +} + +void* pas_system_heap_malloc_compact(size_t size) +{ + BUNUSED_PARAM(size); + RELEASE_BASSERT_NOT_REACHED(); + return nullptr; +} + +void* pas_system_heap_memalign_compact(size_t alignment, size_t size) +{ + BUNUSED_PARAM(size); + BUNUSED_PARAM(alignment); + RELEASE_BASSERT_NOT_REACHED(); + return nullptr; +} + +void* pas_system_heap_realloc_compact(void* ptr, size_t size) +{ + BUNUSED_PARAM(ptr); + BUNUSED_PARAM(size); + RELEASE_BASSERT_NOT_REACHED(); + return nullptr; +} + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wmissing-noreturn" +void pas_system_heap_free(void* ptr) +{ + BUNUSED_PARAM(ptr); + RELEASE_BASSERT_NOT_REACHED(); +} +#pragma clang diagnostic pop + +#endif // BUSE(LIBPAS) + +#endif // BENABLE(LIBPAS) + +#endif // BOS(WINDOWS) diff --git a/src/c-headers-for-zig.h b/src/c-headers-for-zig.h index daf0ead276b..2df4fb1662e 100644 --- a/src/c-headers-for-zig.h +++ b/src/c-headers-for-zig.h @@ -59,6 +59,7 @@ #if WINDOWS #include #include +#include // _putenv #endif #undef lstat diff --git a/src/main.zig b/src/main.zig index 0ff5a16f430..039c04585e0 100644 --- a/src/main.zig +++ b/src/main.zig @@ -21,6 +21,18 @@ pub extern "c" var environ: ?*anyopaque; pub fn main() void { _bun.crash_handler.init(); + // Force bmalloc to use SystemHeap (mimalloc) instead of libpas on Windows. + // bmalloc's libpas allocator causes GC crashes under memory pressure on + // Windows because SystemHeap is unimplemented (all methods assert-crash). + // Setting "Malloc" causes bmalloc::Environment to route all allocations + // through SystemHeap, which we override with mimalloc + // (see BmallocSystemHeapOverride.cpp). Must be set before any bmalloc + // allocation triggers Environment initialization. + // See: https://github.com/oven-sh/bun/issues/22349 + if (Environment.isWindows) { + _ = _bun.c._putenv("Malloc=1"); + } + if (Environment.isPosix) { var act: std.posix.Sigaction = .{ .handler = .{ .handler = std.posix.SIG.IGN }, From 65d90f20007bd59ead028029879845a225ab37a4 Mon Sep 17 00:00:00 2001 From: Claude Bot Date: Wed, 18 Mar 2026 18:03:23 +0000 Subject: [PATCH 2/3] ci: retry Windows builds (CI agent crash, not our code) Co-Authored-By: Claude Opus 4.6 (1M context) From c598607e2194ba45ae04ec5a2f07a72e34b7ec40 Mon Sep 17 00:00:00 2001 From: Claude Bot Date: Wed, 18 Mar 2026 19:00:03 +0000 Subject: [PATCH 3/3] fix: assert _putenv success to fail fast if Malloc=1 cannot be set Co-Authored-By: Claude Opus 4.6 (1M context) --- src/main.zig | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main.zig b/src/main.zig index 039c04585e0..1d05912aa0a 100644 --- a/src/main.zig +++ b/src/main.zig @@ -30,7 +30,9 @@ pub fn main() void { // allocation triggers Environment initialization. // See: https://github.com/oven-sh/bun/issues/22349 if (Environment.isWindows) { - _ = _bun.c._putenv("Malloc=1"); + // _putenv returns 0 on success, -1 on failure. Failure here means + // bmalloc will use unimplemented libpas and crash later, so assert. + _bun.assert(_bun.c._putenv("Malloc=1") == 0); } if (Environment.isPosix) {