Skip to content

Commit a440e18

Browse files
TwoFXclaude
andcommitted
perf: inline mimalloc's allocation fast path into the runtime entry point
This PR compiles mimalloc and the fused small-object allocation entry point in a single translation unit, so that compiled code reaches memory for a small object with one call that inlines mimalloc's fast path. The new `runtime/mimalloc.cpp` #includes all of mimalloc (`static.c`) followed by `lean_alloc_small_object_core`, replacing `static.c` as the source of the mimalloc build variants; GCC additionally needs `-fno-semantic-interposition` to inline mimalloc's exported functions under -fPIC. Second step extracted from #14950; the mimalloc source patch (`mi_malloc_small_aligned`, `MI_BLOCK_SCRUB`) and the `object_rc.cpp` split stay there. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KEqhVRwDgbXCrsn7AxsMdX
1 parent 811c0bc commit a440e18

4 files changed

Lines changed: 52 additions & 26 deletions

File tree

src/include/lean/lean.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -484,9 +484,10 @@ static inline unsigned lean_get_slot_idx(unsigned sz) {
484484
LEAN_EXPORT void lean_inc_heartbeat(void);
485485

486486
#ifdef LEAN_MIMALLOC
487-
/* Fused small-object allocation entry point implemented in `runtime/alloc.cpp`: a single call
488-
covering the heartbeat update and the mimalloc allocation. Requires `sz` to be a positive
489-
multiple of `LEAN_OBJECT_SIZE_DELTA` of at most `MI_SMALL_SIZE_MAX`; initializes `m_cs_sz`. */
487+
/* Fused small-object allocation entry point implemented in `runtime/mimalloc.cpp`: a single call
488+
covering the heartbeat update and the (inlined) mimalloc fast path. Requires `sz` to be a
489+
positive multiple of `LEAN_OBJECT_SIZE_DELTA` of at most `MI_SMALL_SIZE_MAX`; initializes
490+
`m_cs_sz`. */
490491
LEAN_EXPORT LEAN_ATTR_MALLOC lean_object * lean_alloc_small_object_core(unsigned sz);
491492
#endif
492493

src/runtime/CMakeLists.txt

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
if(USE_MIMALLOC)
2-
set(MIMALLOC_SRC ${LEAN_BINARY_DIR}/../mimalloc/src/mimalloc/src/static.c)
3-
# (C flags are incomplete, compile as C++ instead like everything else). A source property rather
4-
# than a target one, but it is the same for every variant below.
5-
set_source_files_properties(${MIMALLOC_SRC} PROPERTIES LANGUAGE CXX)
2+
# `mimalloc.cpp` #includes all of mimalloc (`static.c`, compiled as C++ like everything else)
3+
# together with the runtime's fused allocation entry point so mimalloc's fast path inlines into
4+
# it.
5+
set(MIMALLOC_SRC ${CMAKE_CURRENT_SOURCE_DIR}/mimalloc.cpp)
66
# Make all symbols visible, always build with optimizations as otherwise Lean becomes too slow.
77
# `MI_MAX_ALIGN_SIZE=8` means that an object with two fields will take 24 bytes instead of 32 bytes.
88
# The default value 16 is only necessary for mimalloc to be compatible with the system allocator, which
@@ -11,15 +11,24 @@ if(USE_MIMALLOC)
1111
if(CMAKE_CXX_COMPILER_ID MATCHES "AppleClang|Clang")
1212
list(APPEND MIMALLOC_OPTS -Wno-deprecated)
1313
endif()
14+
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
15+
# GCC refuses to inline mimalloc's exported allocation functions into the entry point when
16+
# compiling with -fPIC unless told that no symbol will be interposed (clang's default)
17+
list(APPEND MIMALLOC_OPTS -fno-semantic-interposition)
18+
endif()
1419

1520
# One target per (mitigation level, TLS model) the build needs, each carrying its own
16-
# `target_compile_options`. The flags cannot live on the shared source instead: source properties
17-
# are directory-scoped and CMake emits them *after* target options, so a target trying to override
18-
# the mitigation level would silently lose to whatever the source property set.
21+
# `target_compile_options`.
1922
function(add_mimalloc_variant name type secure)
2023
add_library(${name} ${type} ${MIMALLOC_SRC})
21-
# Lean code includes it as `lean/mimalloc.h`, but `static.c` itself wants the original directory
22-
target_include_directories(${name} PRIVATE ${LEAN_BINARY_DIR}/../mimalloc/src/mimalloc/include)
24+
# Lean code includes it as `lean/mimalloc.h`, but `static.c` itself wants the original include
25+
# directory, and `mimalloc.cpp` includes `static.c` from the source directory
26+
target_include_directories(
27+
${name}
28+
PRIVATE
29+
${LEAN_BINARY_DIR}/../mimalloc/src/mimalloc/include
30+
${LEAN_BINARY_DIR}/../mimalloc/src/mimalloc/src
31+
)
2332
target_compile_options(${name} PRIVATE ${MIMALLOC_OPTS} -DMI_SECURE=${secure} ${ARGN})
2433
endfunction()
2534

src/runtime/alloc.cpp

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -39,18 +39,4 @@ uint64_t get_num_heartbeats() {
3939
return lean_g_heartbeat;
4040
}
4141

42-
#ifdef LEAN_MIMALLOC
43-
extern "C" LEAN_EXPORT LEAN_ATTR_MALLOC lean_object * lean_alloc_small_object_core(unsigned sz) {
44-
lean_g_heartbeat++;
45-
// the callers guarantee `sz > 0 && sz % LEAN_OBJECT_SIZE_DELTA == 0 && sz <= MI_SMALL_SIZE_MAX`
46-
void * mem = mi_malloc_small(sz);
47-
if (LEAN_UNLIKELY(mem == NULL)) lean_internal_panic_out_of_memory();
48-
lean_object * o = (lean_object *)mem;
49-
/* `m_cs_sz` must be the exact (aligned) requested size, not mimalloc's potentially larger
50-
block size: `lean_small_object_size` and `leangz` rely on it. */
51-
o->m_cs_sz = sz;
52-
return o;
53-
}
54-
#endif
55-
5642
}

src/runtime/mimalloc.cpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
Copyright (c) 2026 Lean FRO, LLC. All rights reserved.
3+
Released under Apache 2.0 license as described in the file LICENSE.
4+
5+
Author: Julia M. Himmel
6+
*/
7+
/*
8+
Compiles all of mimalloc (`static.c`) together with the runtime's small-object allocation entry
9+
point in a single translation unit so that mimalloc's allocation fast path inlines into it:
10+
compiled Lean code reaches the allocator, including the heartbeat update, with a single call.
11+
`static.c` must come first so that mimalloc's headers configure themselves from this variant's
12+
`MI_*` flags (see `add_mimalloc_variant` in `CMakeLists.txt`) before `lean/mimalloc.h` is seen.
13+
*/
14+
#include <static.c>
15+
// mimalloc's `atomic.h` already defined `_Atomic` for C++; `lean.h` redefines it identically
16+
#undef _Atomic
17+
#include <lean/lean.h>
18+
#include "runtime/alloc.h"
19+
20+
extern "C" LEAN_EXPORT LEAN_ATTR_MALLOC lean_object * lean_alloc_small_object_core(unsigned sz) {
21+
lean_g_heartbeat++;
22+
// the callers guarantee `sz > 0 && sz % LEAN_OBJECT_SIZE_DELTA == 0 && sz <= MI_SMALL_SIZE_MAX`
23+
void * mem = mi_malloc_small(sz);
24+
if (LEAN_UNLIKELY(mem == NULL)) lean_internal_panic_out_of_memory();
25+
lean_object * o = (lean_object *)mem;
26+
/* `m_cs_sz` must be the exact (aligned) requested size, not mimalloc's potentially larger
27+
block size: `lean_small_object_size` and `leangz` rely on it. */
28+
o->m_cs_sz = sz;
29+
return o;
30+
}

0 commit comments

Comments
 (0)