Skip to content

Commit a2f8580

Browse files
authored
Revert mimalloc v3 (dev3) upgrade (#29353)
## Summary - Reverts e0cc98d ("We cannot use MI_OSX_ZONE") - Reverts ccbaed9 ("deps: bump mimalloc to dev3 (bun-dev3-v2)", #29251) We are seeing regressions with the mimalloc v3 upgrade, so this rolls back to the previous pin (`1beadf9651a7bfdec6b5367c380ecc3fe1c40d1a`) and restores the matching `MimallocArena` / `mimalloc.zig` bindings, build script, static-initializer counts, aarch64 baseline allowlist, and `process.versions` snapshot. The `heapStats({dump})` mimalloc integration and its test are removed as they depend on dev3-only APIs. The only manual conflict resolution was in `test/js/node/process/process.test.js`, where the boringssl/libarchive version lines adjacent to the mimalloc line had since changed; those were kept at their current `main` values. ## Test plan - [ ] CI builds green on all platforms - [ ] `test/js/bun/perf/static-initializers.test.ts` passes (initializer count back to 1/2) - [ ] `test/js/node/process/process.test.js` `process.versions` passes - [ ] `scripts/verify-baseline-static` aarch64 allowlist check passes
1 parent 443ba81 commit a2f8580

File tree

8 files changed

+50
-164
lines changed

8 files changed

+50
-164
lines changed

scripts/build/deps/mimalloc.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import type { Dependency, NestedCmakeBuild, Provides } from "../source.ts";
77

8-
const MIMALLOC_COMMIT = "9a5e1f52cdf4662f9590b69de104a4469140796f";
8+
const MIMALLOC_COMMIT = "1beadf9651a7bfdec6b5367c380ecc3fe1c40d1a";
99

1010
export const mimalloc: Dependency = {
1111
name: "mimalloc",
@@ -68,7 +68,6 @@ export const mimalloc: Dependency = {
6868
// so UBSan doesn't false-positive on mimalloc's type punning.
6969
args.MI_DEBUG_UBSAN = "ON";
7070
} else if (cfg.darwin) {
71-
// We cannot use MI_OSX_ZONE because it breaks NAPI addons.
7271
args.MI_OVERRIDE = "OFF";
7372
args.MI_OSX_ZONE = "OFF";
7473
args.MI_OSX_INTERPOSE = "OFF";
@@ -92,10 +91,10 @@ export const mimalloc: Dependency = {
9291
args.MI_TRACK_VALGRIND = "ON";
9392
}
9493

95-
// dev3 grew MI_OPT_ARCH which sets -march=armv8.1-a on arm64 — that
96-
// SIGILLs on ARMv8.0 CPUs. Explicitly disable it; our global
97-
// -march=armv8-a+crc (via CMAKE_CXX_FLAGS) is sufficient.
98-
args.MI_NO_OPT_ARCH = "ON";
94+
// If mimalloc gets bumped to a version with MI_OPT_ARCH: pass
95+
// MI_NO_OPT_ARCH=ON to stop it setting -march=armv8.1-a on arm64
96+
// (SIGILLs on ARMv8.0 CPUs). Current pin has no arch-detection logic
97+
// so our global -march=armv8-a+crc (via CMAKE_CXX_FLAGS) is sufficient.
9998

10099
// ─── Windows: silence the vendored-C-as-C++ warning flood ───
101100
// MI_USE_CXX=ON means .c files compile as C++. clang-cl then complains
@@ -122,7 +121,7 @@ export const mimalloc: Dependency = {
122121
// to override this, so we have to mirror its naming logic.
123122
let libname: string;
124123
if (cfg.windows) {
125-
libname = cfg.debug ? "mimalloc-debug" : "mimalloc";
124+
libname = cfg.debug ? "mimalloc-static-debug" : "mimalloc-static";
126125
} else if (cfg.debug) {
127126
libname = cfg.asan ? "mimalloc-asan-debug" : "mimalloc-debug";
128127
} else {

scripts/verify-baseline-static/allowlist-aarch64.txt

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,6 @@ __aarch64_ldclr1_acq_rel [LSE]
9494
__aarch64_ldclr2_acq_rel [LSE]
9595
__aarch64_ldclr4_acq_rel [LSE]
9696
__aarch64_ldclr8_acq_rel [LSE]
97-
__aarch64_ldclr8_relax [LSE]
9897
__aarch64_ldeor1_acq_rel [LSE]
9998
__aarch64_ldeor2_acq_rel [LSE]
10099
__aarch64_ldeor4_acq_rel [LSE]
@@ -103,7 +102,6 @@ __aarch64_ldset1_acq_rel [LSE]
103102
__aarch64_ldset2_acq_rel [LSE]
104103
__aarch64_ldset4_acq_rel [LSE]
105104
__aarch64_ldset8_acq_rel [LSE]
106-
__aarch64_ldset8_relax [LSE]
107105
__aarch64_swp1_acq [LSE]
108106
__aarch64_swp1_acq_rel [LSE]
109107
__aarch64_swp1_relax [LSE]
@@ -112,8 +110,6 @@ __aarch64_swp4_acq [LSE]
112110
__aarch64_swp4_acq_rel [LSE]
113111
__aarch64_swp4_rel [LSE]
114112
__aarch64_swp8_acq_rel [LSE]
115-
__aarch64_swp8_rel [LSE]
116-
__aarch64_swp8_relax [LSE]
117113

118114

119115
# ----------------------------------------------------------------------------

src/allocators/MimallocArena.zig

Lines changed: 28 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ const Self = @This();
88
///
99
/// This type is a `GenericAllocator`; see `src/allocators.zig`.
1010
pub const Default = struct {
11-
pub fn allocator(_: Default) std.mem.Allocator {
12-
return .{ .ptr = undefined, .vtable = &global_mimalloc_vtable };
11+
pub fn allocator(self: Default) std.mem.Allocator {
12+
_ = self;
13+
return Borrowed.getDefault().allocator();
1314
}
1415
};
1516

@@ -22,20 +23,11 @@ pub const Borrowed = struct {
2223
#heap: BorrowedHeap,
2324

2425
pub fn allocator(self: Borrowed) std.mem.Allocator {
25-
return .{ .ptr = self.#heap, .vtable = &heap_allocator_vtable };
26+
return .{ .ptr = self.#heap, .vtable = &c_allocator_vtable };
2627
}
2728

28-
/// Prefer `Default.allocator()` / `getThreadLocalDefault()` for a thread-safe
29-
/// global allocator. This returns the process-wide main heap so that
30-
/// `gc()` / `ownsPtr()` on the result remain meaningful.
3129
pub fn getDefault() Borrowed {
32-
const heap = mimalloc.mi_heap_main();
33-
if (comptime !safety_checks) return .{ .#heap = heap };
34-
const S = struct {
35-
threadlocal var dbg: ?DebugHeap = null;
36-
};
37-
if (S.dbg == null) S.dbg = .{ .inner = heap, .thread_lock = .initLocked() };
38-
return .{ .#heap = &S.dbg.? };
30+
return .{ .#heap = getThreadHeap() };
3931
}
4032

4133
pub fn gc(self: Borrowed) void {
@@ -50,7 +42,7 @@ pub const Borrowed = struct {
5042
}
5143

5244
pub fn ownsPtr(self: Borrowed, ptr: *const anyopaque) bool {
53-
return mimalloc.mi_heap_contains(self.getMimallocHeap(), ptr);
45+
return mimalloc.mi_heap_check_owned(self.getMimallocHeap(), ptr);
5446
}
5547

5648
fn fromOpaque(ptr: *anyopaque) Borrowed {
@@ -89,8 +81,8 @@ pub const Borrowed = struct {
8981

9082
pub fn downcast(std_alloc: std.mem.Allocator) Borrowed {
9183
bun.assertf(
92-
std_alloc.vtable == &heap_allocator_vtable,
93-
"not an owned MimallocArena heap (vtable is {*})",
84+
isInstance(std_alloc),
85+
"not a MimallocArena (vtable is {*})",
9486
.{std_alloc.vtable},
9587
);
9688
return .fromOpaque(std_alloc.ptr);
@@ -106,6 +98,19 @@ const DebugHeap = struct {
10698
pub const deinit = void;
10799
};
108100

101+
threadlocal var thread_heap: if (safety_checks) ?DebugHeap else void = if (safety_checks) null;
102+
103+
fn getThreadHeap() BorrowedHeap {
104+
if (comptime !safety_checks) return mimalloc.mi_heap_get_default();
105+
if (thread_heap == null) {
106+
thread_heap = .{
107+
.inner = mimalloc.mi_heap_get_default(),
108+
.thread_lock = .initLocked(),
109+
};
110+
}
111+
return &thread_heap.?;
112+
}
113+
109114
const log = bun.Output.scoped(.mimalloc, .hidden);
110115

111116
pub fn allocator(self: Self) std.mem.Allocator {
@@ -116,11 +121,13 @@ pub fn borrow(self: Self) Borrowed {
116121
return .{ .#heap = if (comptime safety_checks) self.#heap.get() else self.#heap };
117122
}
118123

119-
/// In v3, `mi_malloc`/`mi_free` are already thread-local-fast — there is no
120-
/// per-thread default heap to cache. Route through the global vtable.
124+
/// Internally, mimalloc calls mi_heap_get_default()
125+
/// to get the default heap.
126+
/// It uses pthread_getspecific to do that.
127+
/// We can save those extra calls if we just do it once in here
121128
pub fn getThreadLocalDefault() std.mem.Allocator {
122129
if (bun.Environment.enable_asan) return bun.default_allocator;
123-
return .{ .ptr = undefined, .vtable = &global_mimalloc_vtable };
130+
return Borrowed.getDefault().allocator();
124131
}
125132

126133
pub fn backingAllocator(_: Self) std.mem.Allocator {
@@ -244,43 +251,17 @@ fn vtable_remap(ptr: *anyopaque, buf: []u8, alignment: Alignment, new_len: usize
244251
return @ptrCast(value);
245252
}
246253

247-
fn global_vtable_alloc(_: *anyopaque, len: usize, alignment: Alignment, _: usize) ?[*]u8 {
248-
log("Malloc: {d}\n", .{len});
249-
const ptr: ?*anyopaque = if (mimalloc.mustUseAlignedAlloc(alignment))
250-
mimalloc.mi_malloc_aligned(len, alignment.toByteUnits())
251-
else
252-
mimalloc.mi_malloc(len);
253-
return if (ptr) |p| @ptrCast(p) else null;
254-
}
255-
256-
fn global_vtable_resize(_: *anyopaque, buf: []u8, _: Alignment, new_len: usize, _: usize) bool {
257-
return mimalloc.mi_expand(buf.ptr, new_len) != null;
258-
}
259-
260-
fn global_vtable_remap(_: *anyopaque, buf: []u8, alignment: Alignment, new_len: usize, _: usize) ?[*]u8 {
261-
return @ptrCast(mimalloc.mi_realloc_aligned(buf.ptr, new_len, alignment.toByteUnits()));
262-
}
263-
264254
pub fn isInstance(alloc: std.mem.Allocator) bool {
265-
return alloc.vtable == &heap_allocator_vtable or alloc.vtable == &global_mimalloc_vtable;
255+
return alloc.vtable == &c_allocator_vtable;
266256
}
267257

268-
/// VTable for owned heaps created with `mi_heap_new`.
269-
const heap_allocator_vtable = std.mem.Allocator.VTable{
258+
const c_allocator_vtable = std.mem.Allocator.VTable{
270259
.alloc = vtable_alloc,
271260
.resize = vtable_resize,
272261
.remap = vtable_remap,
273262
.free = vtable_free,
274263
};
275264

276-
/// VTable for the process-wide default allocator (`mi_malloc`/`mi_free`).
277-
const global_mimalloc_vtable = std.mem.Allocator.VTable{
278-
.alloc = global_vtable_alloc,
279-
.resize = global_vtable_resize,
280-
.remap = global_vtable_remap,
281-
.free = vtable_free,
282-
};
283-
284265
const std = @import("std");
285266
const Alignment = std.mem.Alignment;
286267

src/allocators/mimalloc.zig

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,15 +60,16 @@ pub const Heap = opaque {
6060
return mi_heap_realloc(self, p, newsize);
6161
}
6262

63-
pub fn isOwned(self: *Heap, p: ?*const anyopaque) bool {
64-
return mi_heap_contains(self, p);
63+
pub fn isOwned(self: *Heap, p: ?*anyopaque) bool {
64+
return mi_heap_check_owned(self, p);
6565
}
6666
};
6767
pub extern fn mi_heap_new() ?*Heap;
6868
pub extern fn mi_heap_delete(heap: *Heap) void;
6969
pub extern fn mi_heap_destroy(heap: *Heap) void;
70-
pub extern fn mi_heap_main() *Heap;
71-
pub extern fn mi_heap_contains(heap: *const Heap, p: ?*const anyopaque) bool;
70+
pub extern fn mi_heap_set_default(heap: *Heap) *Heap;
71+
pub extern fn mi_heap_get_default() *Heap;
72+
pub extern fn mi_heap_get_backing() *Heap;
7273
pub extern fn mi_heap_collect(heap: *Heap, force: bool) void;
7374
pub extern fn mi_heap_malloc(heap: *Heap, size: usize) ?*anyopaque;
7475
pub extern fn mi_heap_zalloc(heap: *Heap, size: usize) ?*anyopaque;
@@ -101,6 +102,8 @@ pub extern fn mi_heap_rezalloc_aligned(heap: *Heap, p: ?*anyopaque, newsize: usi
101102
pub extern fn mi_heap_rezalloc_aligned_at(heap: *Heap, p: ?*anyopaque, newsize: usize, alignment: usize, offset: usize) ?*anyopaque;
102103
pub extern fn mi_heap_recalloc_aligned(heap: *Heap, p: ?*anyopaque, newcount: usize, size: usize, alignment: usize) ?*anyopaque;
103104
pub extern fn mi_heap_recalloc_aligned_at(heap: *Heap, p: ?*anyopaque, newcount: usize, size: usize, alignment: usize, offset: usize) ?*anyopaque;
105+
pub extern fn mi_heap_contains_block(heap: *Heap, p: *const anyopaque) bool;
106+
pub extern fn mi_heap_check_owned(heap: *Heap, p: *const anyopaque) bool;
104107
pub extern fn mi_check_owned(p: ?*const anyopaque) bool;
105108
pub const struct_mi_heap_area_s = extern struct {
106109
blocks: ?*anyopaque,
@@ -109,7 +112,6 @@ pub const struct_mi_heap_area_s = extern struct {
109112
used: usize,
110113
block_size: usize,
111114
full_block_size: usize,
112-
reserved1: ?*anyopaque,
113115
};
114116
pub const mi_heap_area_t = struct_mi_heap_area_s;
115117
pub const mi_block_visit_fun = *const fn (?*const Heap, [*c]const mi_heap_area_t, ?*anyopaque, usize, ?*anyopaque) callconv(.c) bool;

src/bun.js/modules/BunJSCModule.h

Lines changed: 1 addition & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,6 @@
4646
#include "JSDOMConvertBase.h"
4747
#include "ZigSourceProvider.h"
4848
#include "mimalloc.h"
49-
extern "C" char* mi_stats_get_json(size_t, char*);
50-
extern "C" char* mi_heap_dump_json(bool include_blocks, bool hash_addresses);
5149

5250
#include <JavaScriptCore/ControlFlowProfiler.h>
5351

@@ -221,7 +219,7 @@ createMemoryFootprintStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject)
221219

222220
JSC_DECLARE_HOST_FUNCTION(functionMemoryUsageStatistics);
223221
JSC_DEFINE_HOST_FUNCTION(functionMemoryUsageStatistics,
224-
(JSGlobalObject * globalObject, CallFrame* callFrame))
222+
(JSGlobalObject * globalObject, CallFrame*))
225223
{
226224

227225
auto& vm = JSC::getVM(globalObject);
@@ -358,34 +356,6 @@ JSC_DEFINE_HOST_FUNCTION(functionMemoryUsageStatistics,
358356
#endif
359357
#endif
360358

361-
mi_collect(false);
362-
if (char* json = mi_stats_get_json(0, nullptr)) {
363-
JSValue parsed = JSONParse(globalObject, String::fromUTF8(json));
364-
mi_free(json);
365-
object->putDirect(vm, Identifier::fromString(vm, "mimalloc"_s),
366-
parsed.isEmpty() ? jsNull() : parsed);
367-
}
368-
369-
// heapStats({ dump: true | "blocks" }) -> per-heap/per-page (and per-block) live snapshot.
370-
JSValue arg0 = callFrame->argument(0);
371-
if (arg0.isObject()) {
372-
JSValue dump = arg0.getObject()->get(globalObject, Identifier::fromString(vm, "dump"_s));
373-
if (dump.toBoolean(globalObject)) {
374-
const bool includeBlocks = dump.isString() && dump.toWTFString(globalObject) == "blocks"_s;
375-
#if BUN_DEBUG
376-
const bool hashAddresses = false;
377-
#else
378-
const bool hashAddresses = true;
379-
#endif
380-
if (char* json = mi_heap_dump_json(includeBlocks, hashAddresses)) {
381-
JSValue parsed = JSONParse(globalObject, String::fromUTF8(json));
382-
mi_free(json);
383-
object->putDirect(vm, Identifier::fromString(vm, "mimallocDump"_s),
384-
parsed.isEmpty() ? jsNull() : parsed);
385-
}
386-
}
387-
}
388-
389359
return JSValue::encode(object);
390360
}
391361

test/js/bun/jsc/heapStats-mimalloc.test.ts

Lines changed: 0 additions & 65 deletions
This file was deleted.

test/js/bun/perf/static-initializers.test.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,15 @@ describe("static initializers", () => {
5959
.map(a => a.trim())
6060
.filter(line => line.includes("running initializer") && line.includes(bunExe()));
6161

62-
// mimalloc v3 with MI_OSX_ZONE=ON contributes three: __GLOBAL__sub_I_static.c,
63-
// mi_process_attach, and _mi_macos_override_malloc (the zone-swap constructor).
64-
// On x86_64 there is also ___cpu_indicator_init from CPU feature detection.
62+
// On both architectures, we have one initializer "__GLOBAL__sub_I_static.c".
63+
// On arm64, mimalloc v3 adds one more static initializer (total: 2).
64+
// On x86_64, we also have:
65+
// - one from ___cpu_indicator_init due to our CPU feature detection
66+
// - one from mimalloc v3
67+
// (total: 3)
6568
expect(
6669
bunInitializers.length,
6770
`Do not add static initializers to Bun. Static initializers are called when Bun starts up, regardless of whether you use the variables or not. This makes Bun slower.`,
68-
).toBe(process.arch === "arm64" ? 3 : 4);
71+
).toBe(process.arch === "arm64" ? 1 : 2);
6972
});
7073
});

test/js/node/process/process.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ it("process.versions", () => {
275275
const expectedVersions = {
276276
boringssl: "0c5fce43b7ed5eb6001487ee48ac65766f5ddcd1",
277277
libarchive: "ded82291ab41d5e355831b96b0e1ff49e24d8939",
278-
mimalloc: "9a5e1f52cdf4662f9590b69de104a4469140796f",
278+
mimalloc: "1beadf9651a7bfdec6b5367c380ecc3fe1c40d1a",
279279
picohttpparser: "066d2b1e9ab820703db0837a7255d92d30f0c9f5",
280280
zlib: "886098f3f339617b4243b286f5ed364b9989e245",
281281
tinycc: "12882eee073cfe5c7621bcfadf679e1372d4537b",

0 commit comments

Comments
 (0)