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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions cmake/targets/BuildMimalloc.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ register_repository(
REPOSITORY
oven-sh/mimalloc
COMMIT
1beadf9651a7bfdec6b5367c380ecc3fe1c40d1a
41c6f59487c7323412e4ae2ccb960846076bf832
)

set(MIMALLOC_CMAKE_ARGS
Expand All @@ -14,7 +14,8 @@ set(MIMALLOC_CMAKE_ARGS
-DMI_BUILD_TESTS=OFF
-DMI_USE_CXX=ON
-DMI_SKIP_COLLECT_ON_EXIT=ON

-DMI_PRIM_HAS_PROCESS_ATTACH=1

# ```
# ❯ mimalloc_allow_large_os_pages=0 BUN_PORT=3004 mem bun http-hello.js
# Started development server: http://localhost:3004
Expand Down Expand Up @@ -51,7 +52,7 @@ if(ENABLE_ASAN)
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_DEBUG_UBSAN=ON)
elseif(APPLE OR LINUX)
if(APPLE)
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_OVERRIDE=OFF)
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_OVERRIDE=OFF)
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_OSX_ZONE=OFF)
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_OSX_INTERPOSE=OFF)
else()
Expand All @@ -77,9 +78,9 @@ endif()

if(WIN32)
if(DEBUG)
set(MIMALLOC_LIBRARY mimalloc-static-debug)
set(MIMALLOC_LIBRARY mimalloc-debug)
else()
set(MIMALLOC_LIBRARY mimalloc-static)
set(MIMALLOC_LIBRARY mimalloc)
endif()
elseif(DEBUG)
if (ENABLE_ASAN)
Expand Down
154 changes: 98 additions & 56 deletions src/allocators/MimallocArena.zig
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ const Self = @This();
///
/// This type is a `GenericAllocator`; see `src/allocators.zig`.
pub const Default = struct {
pub fn allocator(self: Default) std.mem.Allocator {
_ = self;
return Borrowed.getDefault().allocator();
pub fn allocator(_: Default) std.mem.Allocator {
// Use global mimalloc functions which are thread-safe
return .{ .ptr = undefined, .vtable = &global_mimalloc_vtable };
}
};

Expand All @@ -23,11 +23,14 @@ pub const Borrowed = struct {
#heap: BorrowedHeap,

pub fn allocator(self: Borrowed) std.mem.Allocator {
return .{ .ptr = self.#heap, .vtable = &c_allocator_vtable };
return .{ .ptr = self.#heap, .vtable = &heap_allocator_vtable };
}

pub fn getDefault() Borrowed {
return .{ .#heap = getThreadHeap() };
// This is a legacy function - prefer using Default.allocator() or getThreadLocalDefault()
// For backwards compatibility, return a borrowed with undefined heap
// but callers should use the global allocator instead
return .{ .#heap = undefined };
}

pub fn gc(self: Borrowed) void {
Expand All @@ -41,15 +44,17 @@ pub const Borrowed = struct {
}
}

pub fn ownsPtr(self: Borrowed, ptr: *const anyopaque) bool {
return mimalloc.mi_heap_check_owned(self.getMimallocHeap(), ptr);
pub fn ownsPtr(_: Borrowed, ptr: *const anyopaque) bool {
// In mimalloc v3, mi_heap_check_owned was removed.
// Use mi_check_owned which checks if ptr is in any mimalloc heap.
return mimalloc.mi_check_owned(ptr);
}

fn fromOpaque(ptr: *anyopaque) Borrowed {
return .{ .#heap = @ptrCast(@alignCast(ptr)) };
}

fn getMimallocHeap(self: Borrowed) *mimalloc.Heap {
pub fn getMimallocHeap(self: Borrowed) *mimalloc.Heap {
return if (comptime safety_checks) self.#heap.inner else self.#heap;
}

Expand Down Expand Up @@ -98,19 +103,6 @@ const DebugHeap = struct {
pub const deinit = void;
};

threadlocal var thread_heap: if (safety_checks) ?DebugHeap else void = if (safety_checks) null;

fn getThreadHeap() BorrowedHeap {
if (comptime !safety_checks) return mimalloc.mi_heap_get_default();
if (thread_heap == null) {
thread_heap = .{
.inner = mimalloc.mi_heap_get_default(),
.thread_lock = .initLocked(),
};
}
return &thread_heap.?;
}

const log = bun.Output.scoped(.mimalloc, .hidden);

pub fn allocator(self: Self) std.mem.Allocator {
Expand All @@ -121,17 +113,15 @@ pub fn borrow(self: Self) Borrowed {
return .{ .#heap = if (comptime safety_checks) self.#heap.get() else self.#heap };
}

/// Internally, mimalloc calls mi_heap_get_default()
/// to get the default heap.
/// It uses pthread_getspecific to do that.
/// We can save those extra calls if we just do it once in here
/// Returns the default thread-local mimalloc allocator.
/// Uses global mimalloc functions which are thread-safe.
pub fn getThreadLocalDefault() std.mem.Allocator {
if (bun.Environment.enable_asan) return bun.default_allocator;
return Borrowed.getDefault().allocator();
return .{ .ptr = undefined, .vtable = &global_mimalloc_vtable };
}

pub fn backingAllocator(_: Self) std.mem.Allocator {
return getThreadLocalDefault();
return bun.default_allocator;
}

pub fn dumpThreadStats(_: Self) void {
Expand Down Expand Up @@ -191,19 +181,23 @@ fn alignedAllocSize(ptr: [*]u8) usize {
return mimalloc.mi_malloc_usable_size(ptr);
}

fn vtable_alloc(ptr: *anyopaque, len: usize, alignment: Alignment, _: usize) ?[*]u8 {
// ============================================================================
// VTable functions for owned heaps (created with mi_heap_new)
// ============================================================================

fn heap_vtable_alloc(ptr: *anyopaque, len: usize, alignment: Alignment, _: usize) ?[*]u8 {
const self: Borrowed = .fromOpaque(ptr);
self.assertThreadLock();
return self.alignedAlloc(len, alignment);
}

fn vtable_resize(ptr: *anyopaque, buf: []u8, _: Alignment, new_len: usize, _: usize) bool {
fn heap_vtable_resize(ptr: *anyopaque, buf: []u8, _: Alignment, new_len: usize, _: usize) bool {
const self: Borrowed = .fromOpaque(ptr);
self.assertThreadLock();
return mimalloc.mi_expand(buf.ptr, new_len) != null;
}

fn vtable_free(
fn heap_vtable_free(
_: *anyopaque,
buf: []u8,
alignment: Alignment,
Expand All @@ -223,26 +217,7 @@ fn vtable_free(
}
}

/// Attempt to expand or shrink memory, allowing relocation.
///
/// `memory.len` must equal the length requested from the most recent
/// successful call to `alloc`, `resize`, or `remap`. `alignment` must
/// equal the same value that was passed as the `alignment` parameter to
/// the original `alloc` call.
///
/// A non-`null` return value indicates the resize was successful. The
/// allocation may have same address, or may have been relocated. In either
/// case, the allocation now has size of `new_len`. A `null` return value
/// indicates that the resize would be equivalent to allocating new memory,
/// copying the bytes from the old memory, and then freeing the old memory.
/// In such case, it is more efficient for the caller to perform the copy.
///
/// `new_len` must be greater than zero.
///
/// `ret_addr` is optionally provided as the first return address of the
/// allocation call stack. If the value is `0` it means no return address
/// has been provided.
fn vtable_remap(ptr: *anyopaque, buf: []u8, alignment: Alignment, new_len: usize, _: usize) ?[*]u8 {
fn heap_vtable_remap(ptr: *anyopaque, buf: []u8, alignment: Alignment, new_len: usize, _: usize) ?[*]u8 {
const self: Borrowed = .fromOpaque(ptr);
self.assertThreadLock();
const heap = self.getMimallocHeap();
Expand All @@ -251,15 +226,82 @@ fn vtable_remap(ptr: *anyopaque, buf: []u8, alignment: Alignment, new_len: usize
return @ptrCast(value);
}

// ============================================================================
// VTable functions for global/default allocator (uses thread-local theap)
// ============================================================================

fn global_vtable_alloc(_: *anyopaque, len: usize, alignment: Alignment, _: usize) ?[*]u8 {
log("Global Malloc: {d}\n", .{len});

const ptr: ?*anyopaque = if (mimalloc.mustUseAlignedAlloc(alignment))
mimalloc.mi_malloc_aligned(len, alignment.toByteUnits())
else
mimalloc.mi_malloc(len);

if (comptime bun.Environment.isDebug) {
const usable = mimalloc.mi_malloc_usable_size(ptr);
if (usable < len) {
std.debug.panic("mimalloc: allocated size is too small: {d} < {d}", .{ usable, len });
}
}

return if (ptr) |p|
@as([*]u8, @ptrCast(p))
else
null;
}

fn global_vtable_resize(_: *anyopaque, buf: []u8, _: Alignment, new_len: usize, _: usize) bool {
return mimalloc.mi_expand(buf.ptr, new_len) != null;
}

fn global_vtable_free(
_: *anyopaque,
buf: []u8,
alignment: Alignment,
_: usize,
) void {
if (comptime bun.Environment.isDebug) {
assert(mimalloc.mi_is_in_heap_region(buf.ptr));
if (mimalloc.mustUseAlignedAlloc(alignment))
mimalloc.mi_free_size_aligned(buf.ptr, buf.len, alignment.toByteUnits())
else
mimalloc.mi_free_size(buf.ptr, buf.len);
} else {
mimalloc.mi_free(buf.ptr);
}
}

fn global_vtable_remap(_: *anyopaque, buf: []u8, alignment: Alignment, new_len: usize, _: usize) ?[*]u8 {
const aligned_size = alignment.toByteUnits();
const value = mimalloc.mi_realloc_aligned(buf.ptr, new_len, aligned_size);
return @ptrCast(value);
}

// ============================================================================
// VTables
// ============================================================================

pub fn isInstance(alloc: std.mem.Allocator) bool {
return alloc.vtable == &c_allocator_vtable;
return alloc.vtable == &heap_allocator_vtable or alloc.vtable == &global_mimalloc_vtable;
}

const c_allocator_vtable = std.mem.Allocator.VTable{
.alloc = vtable_alloc,
.resize = vtable_resize,
.remap = vtable_remap,
.free = vtable_free,
/// VTable for owned MimallocArena heaps (created with mi_heap_new).
/// Uses heap-specific mi_heap_* functions.
const heap_allocator_vtable = std.mem.Allocator.VTable{
.alloc = heap_vtable_alloc,
.resize = heap_vtable_resize,
.remap = heap_vtable_remap,
.free = heap_vtable_free,
};

/// VTable for global/default mimalloc allocator.
/// Uses global mi_malloc/mi_free functions which are thread-safe.
const global_mimalloc_vtable = std.mem.Allocator.VTable{
.alloc = global_vtable_alloc,
.resize = global_vtable_resize,
.remap = global_vtable_remap,
.free = global_vtable_free,
};

const std = @import("std");
Expand Down
24 changes: 17 additions & 7 deletions src/allocators/mimalloc.zig
Original file line number Diff line number Diff line change
Expand Up @@ -60,17 +60,28 @@ pub const Heap = opaque {
return mi_heap_realloc(self, p, newsize);
}

pub fn isOwned(self: *Heap, p: ?*anyopaque) bool {
return mi_heap_check_owned(self, p);
pub fn isOwned(self: *Heap, p: ?*const anyopaque) bool {
return mi_heap_contains(self, p);
}
Comment on lines +63 to 65

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

cd src/allocators && wc -l mimalloc.zig

Repository: oven-sh/bun

Length of output: 71


🏁 Script executed:

head -120 src/allocators/mimalloc.zig | tail -60

Repository: oven-sh/bun

Length of output: 4537


🏁 Script executed:

cat -n src/allocators/mimalloc.zig | sed -n '60,120p'

Repository: oven-sh/bun

Length of output: 5014


🌐 Web query:

mimalloc v3 mi_heap_contains API documentation

💡 Result:

There is no mi_heap_contains function in mimalloc v3. Use these heap-introspection APIs instead:

  • bool mi_heap_contains_block(mi_heap_t *heap, const void *p) — returns true if p (a pointer to a previously allocated block) is in heap. [1]

  • bool mi_heap_check_owned(mi_heap_t *heap, const void *p) — more general/expensive check: returns true if p points anywhere into the given heap (p need not be a previously allocated block). Note: linear in the pages in the heap. [1]

(See the v3 API docs for the Heap Introspection and Heap Allocation groups.) [1][2]

Sources:
[1] mimalloc — Heap Introspection (mi_heap_contains_block, mi_heap_check_owned).
[2] mimalloc — Heap Allocation (mi_heap_* APIs).


mi_heap_contains is not a valid mimalloc v3 API function. Use mi_heap_contains_block() to check if a pointer is a previously allocated block, or mi_heap_check_owned() for a more general check. The extern declaration at line 116 and the isOwned implementation at lines 63-65 both reference a non-existent function that will cause link or runtime failures.

🤖 Prompt for AI Agents
In `@src/allocators/mimalloc.zig` around lines 63 - 65, The code currently calls
the non-existent mimalloc v3 function mi_heap_contains from the Heap.isOwned
implementation and declares it as an extern; replace those references with the
correct API: call mi_heap_contains_block() if you only need to check whether a
pointer is a previously allocated block, or mi_heap_check_owned() for a broader
ownership check, and update the extern declaration accordingly (remove
mi_heap_contains extern and add the proper extern for mi_heap_contains_block or
mi_heap_check_owned used by isOwned).

};
pub extern fn mi_heap_new() ?*Heap;
pub extern fn mi_heap_delete(heap: *Heap) void;
pub extern fn mi_heap_destroy(heap: *Heap) void;
pub extern fn mi_heap_set_default(heap: *Heap) *Heap;
pub extern fn mi_heap_get_default() *Heap;
pub extern fn mi_heap_get_backing() *Heap;
pub extern fn mi_heap_collect(heap: *Heap, force: bool) void;
pub extern fn mi_heap_main() *Heap;

// Thread-local heap (theap) API - new in mimalloc v3
pub const THeap = opaque {};
pub extern fn mi_theap_get_default() *THeap;
pub extern fn mi_theap_set_default(theap: *THeap) *THeap;
pub extern fn mi_theap_collect(theap: *THeap, force: bool) void;
pub extern fn mi_theap_malloc(theap: *THeap, size: usize) ?*anyopaque;
pub extern fn mi_theap_zalloc(theap: *THeap, size: usize) ?*anyopaque;
pub extern fn mi_theap_calloc(theap: *THeap, count: usize, size: usize) ?*anyopaque;
pub extern fn mi_theap_malloc_small(theap: *THeap, size: usize) ?*anyopaque;
pub extern fn mi_theap_malloc_aligned(theap: *THeap, size: usize, alignment: usize) ?*anyopaque;
pub extern fn mi_theap_realloc(theap: *THeap, p: ?*anyopaque, newsize: usize) ?*anyopaque;
pub extern fn mi_heap_theap(heap: *Heap) *THeap;
Comment on lines +71 to +84

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

git ls-files | head -30

Repository: oven-sh/bun

Length of output: 911


🏁 Script executed:

fd -a 'mimalloc\.(zig|h)' .

Repository: oven-sh/bun

Length of output: 101


🏁 Script executed:

cat -n src/allocators/mimalloc.zig | head -100

Repository: oven-sh/bun

Length of output: 6735


🏁 Script executed:

fd -a '\.h$' | xargs grep -l 'mi_theap_malloc' 2>/dev/null | head -5

Repository: oven-sh/bun

Length of output: 37


🏁 Script executed:

find . -type f -name "*.h" | grep -i mimalloc | head -10

Repository: oven-sh/bun

Length of output: 96


🏁 Script executed:

find . -type d -name "mimalloc*" 2>/dev/null | head -10

Repository: oven-sh/bun

Length of output: 37


🏁 Script executed:

git config --file=.gitmodules --name-only --get-regexp path | head -10

Repository: oven-sh/bun

Length of output: 37


🏁 Script executed:

cat -n ./src/bun.js/bindings/MimallocWTFMalloc.h | head -100

Repository: oven-sh/bun

Length of output: 3139


🏁 Script executed:

rg -n 'mimalloc|v3\.|version|3\.2\.7' src/allocators/mimalloc.zig

Repository: oven-sh/bun

Length of output: 148


🌐 Web query:

mimalloc v3.2.7 mi_theap_malloc mi_theap_realloc function signatures C header

💡 Result:

Did you mean mi_heap_* (not "mi_theap")? The C signatures in mimalloc v3.2.7 are:

  • void* mi_heap_malloc(mi_heap_t* heap, size_t size); [1]
  • void* mi_heap_realloc(mi_heap_t* heap, void* p, size_t newsize); [1]

(These appear in the mimalloc heap API which also documents aligned/zero/calloc/realloc variants.) [1]

Sources:
[1] mimalloc Heap API (function list & signatures).


🌐 Web query:

"mi_theap_malloc" "mi_theap_realloc" mimalloc v3.2.7 official API

💡 Result:

Short answer: There is no mi_theap_malloc / mi_theap_realloc in the official mimalloc v3.2.7 API — the correct heap-specific function names use "mi_heap_*", e.g. mi_heap_malloc and mi_heap_realloc (and related variants mi_heap_reallocf, mi_heap_realloc_aligned, mi_heap_mallocn, etc.). See the official heap and basic-allocation docs and the v3.2.7 doxygen listing. [1][2][3]

Sources:

  • mimalloc heap API (mi_heap_malloc, mi_heap_realloc, etc.). [1]
  • mimalloc basic allocation (mi_realloc, mi_reallocf, mi_reallocn). [2]
  • Doxygen for mimalloc 3.2.7 (API index). [3]

🏁 Script executed:

fd 'mimalloc\.h' . -type f

Repository: oven-sh/bun

Length of output: 225


🏁 Script executed:

rg -n 'mi_theap' . --type zig

Repository: oven-sh/bun

Length of output: 1266


🏁 Script executed:

rg -n 'theap' . -A 2 -B 2

Repository: oven-sh/bun

Length of output: 2139


🏁 Script executed:

find . -path ./\.git -prune -o -type f -name "*.c" -o -name "*.h" | xargs grep -l 'mi_theap' 2>/dev/null | head -5

Repository: oven-sh/bun

Length of output: 37


🏁 Script executed:

rg 'mi_theap_' --type zig -A 2 -B 2

Repository: oven-sh/bun

Length of output: 1834


🏁 Script executed:

rg '\.mi_theap_|mi_theap_get_default|mi_theap_malloc|mi_theap_set_default|mi_theap_collect|mi_theap_zalloc|mi_theap_calloc|mi_theap_malloc_small|mi_theap_malloc_aligned|mi_theap_realloc|mi_heap_theap' src/allocators/ --type zig

Repository: oven-sh/bun

Length of output: 1162


🏁 Script executed:

rg -i 'fork|custom|patch|version.*mimalloc|mimalloc.*version' src/allocators/ --type zig -A 1

Repository: oven-sh/bun

Length of output: 284


Remove incorrect mi_theap_* extern declarations—these functions do not exist in official mimalloc v3.2.7.

The nine mi_theap_* function declarations (lines 76–84) reference a non-existent thread-local heap API. Official mimalloc v3.2.7 uses only mi_heap_* functions and does not provide mi_theap_* variants. These declarations are dead code and will fail at link time if called. Since MimallocArena.zig explicitly avoids using them in favor of mi_heap_main(), remove these unused externs or replace them with the correct v3.2.7 API.

🤖 Prompt for AI Agents
In `@src/allocators/mimalloc.zig` around lines 72 - 85, The nine mi_theap_* extern
declarations are incorrect for mimalloc v3.2.7 and should be removed; locate the
mi_theap_get_default/mi_theap_set_default/mi_theap_collect/mi_theap_malloc/mi_theap_zalloc/mi_theap_calloc/mi_theap_malloc_small/mi_theap_malloc_aligned/mi_theap_realloc
symbols in src/allocators/mimalloc.zig and delete those extern declarations,
leaving the valid mi_heap_main and mi_heap_* API declarations (and keeping any
references in MimallocArena.zig that already use mi_heap_main()); if any call
sites use the removed mi_theap_* names, update them to use the appropriate
mi_heap_* functions or mi_heap_theap/mi_heap_main as provided by v3.2.7.

pub extern fn mi_heap_malloc(heap: *Heap, size: usize) ?*anyopaque;
pub extern fn mi_heap_zalloc(heap: *Heap, size: usize) ?*anyopaque;
pub extern fn mi_heap_calloc(heap: *Heap, count: usize, size: usize) ?*anyopaque;
Expand Down Expand Up @@ -102,8 +113,7 @@ pub extern fn mi_heap_rezalloc_aligned(heap: *Heap, p: ?*anyopaque, newsize: usi
pub extern fn mi_heap_rezalloc_aligned_at(heap: *Heap, p: ?*anyopaque, newsize: usize, alignment: usize, offset: usize) ?*anyopaque;
pub extern fn mi_heap_recalloc_aligned(heap: *Heap, p: ?*anyopaque, newcount: usize, size: usize, alignment: usize) ?*anyopaque;
pub extern fn mi_heap_recalloc_aligned_at(heap: *Heap, p: ?*anyopaque, newcount: usize, size: usize, alignment: usize, offset: usize) ?*anyopaque;
pub extern fn mi_heap_contains_block(heap: *Heap, p: *const anyopaque) bool;
pub extern fn mi_heap_check_owned(heap: *Heap, p: *const anyopaque) bool;
pub extern fn mi_heap_contains(heap: *const Heap, p: ?*const anyopaque) bool;
pub extern fn mi_check_owned(p: ?*const anyopaque) bool;
pub const struct_mi_heap_area_s = extern struct {
blocks: ?*anyopaque,
Expand Down
Loading