@@ -6,40 +6,46 @@ use cmake::Config;
66/// `MI_TLS_MODEL_THREAD_LOCAL` with `MI_TLS_RECURSE_GUARD` instead of the
77/// upstream default `MI_TLS_MODEL_FIXED_SLOT`.
88///
9- /// Background:
10- /// - FIXED_SLOT (default) hardcodes the per-thread `theap` pointer at
11- /// TCB[108]/[109]. When multiple statically-linked instances of
12- /// mimalloc-safe live in the same process (e.g. multiple Node.js
13- /// napi addons), every instance reads and writes the same slot on
14- /// any thread that calls into more than one of them (the main JS
15- /// thread in particular) — instances overwrite each other's `theap`
16- /// pointers and corrupt each other's heaps.
17- /// - The other obvious workaround, `MI_TLS_MODEL_DYNAMIC_PTHREADS`
18- /// (activated by passing `-DMI_HAS_TLS_SLOT=0`), turned out to have
19- /// poor macOS testing for long-lived non-tokio threads — notably
20- /// rayon workers pulled in via `oxc_cfg → oxc_index → rayon` — and
21- /// caused SIGABRT crashes in rolldown's CLI tests.
9+ /// This fixes two distinct SIGABRT failure modes on macOS:
2210///
23- /// THREAD_LOCAL + RECURSE_GUARD avoids both failure modes:
24- /// - `__thread` + `mi_decl_hidden` (already how upstream declares
25- /// `__mi_theap_default` / `__mi_theap_cached` in `init.c`) gives
26- /// per-image per-thread storage allocated by dyld's TLV system — no
27- /// FIXED_SLOT sharing across instances.
28- /// - RECURSE_GUARD uses a non-TLS `_mi_process_is_initialized` bool to
29- /// short-circuit the fast path before process init completes, so a
30- /// first `__thread` access triggering dyld's TLV malloc cannot
31- /// recurse into mimalloc itself.
32- /// - `MI_HAS_TLS_SLOT` stays at default 1, so the *thread-id* fast
33- /// path (`prim.h` line ~304) still uses `mi_prim_tls_slot(0)`
34- /// (Apple's system-defined thread-id TSD slot, shared semantically
35- /// across all consumers — no conflict).
11+ /// Mode A — FIXED_SLOT (upstream default, 100% crash):
12+ /// TCB[108]/[109] is a hardcoded slot shared by all loaded images.
13+ /// When a second napi addon loads, its mimalloc reads the first
14+ /// addon's heap pointer from TCB[108], sees it's non-NULL, and
15+ /// skips its own initialization (`init.c:717` early-return). The
16+ /// second addon then runs with the first's heap data but its own
17+ /// uninitialized internal state — the mismatch causes an immediate
18+ /// SIGABRT on load.
19+ ///
20+ /// Mode B — DYNAMIC_PTHREADS (#67's workaround, 5-15% crash):
21+ /// `-DMI_HAS_TLS_SLOT=0` routes Apple to DYNAMIC_PTHREADS, which
22+ /// stores heap pointers via `pthread_setspecific`. This fixes Mode A,
23+ /// but at process exit the destructor (`init.c:1190`) deletes the
24+ /// pthread key and zeros it. The one-time `mi_atomic_do_once` flag
25+ /// that guards key creation never resets, so the key can never be
26+ /// recreated. Background threads (rayon workers from `oxc_cfg →
27+ /// oxc_index → rayon`) are still alive during exit — their next
28+ /// alloc finds key=0, allocates a heap but can't store it, and
29+ /// returns NULL → SIGABRT.
30+ ///
31+ /// THREAD_LOCAL + RECURSE_GUARD avoids both:
32+ /// - `__thread` + `mi_decl_hidden` gives per-image per-thread storage
33+ /// via dyld TLV. Different addons have different variables (fixes
34+ /// Mode A). No pthread key involved, so nothing to delete on exit
35+ /// (fixes Mode B).
36+ /// - RECURSE_GUARD short-circuits the fast path with a non-TLS bool
37+ /// (`_mi_process_is_initialized`) before process init completes,
38+ /// preventing recursion when dyld's TLV allocator calls `calloc`
39+ /// on first `__thread` access.
40+ /// - `MI_HAS_TLS_SLOT` stays at default 1, so the thread-id fast path
41+ /// still uses `mi_prim_tls_slot(0)` (Apple's system TSD slot — no
42+ /// conflict).
3643///
3744/// Idempotent: detects an already-patched file by a marker comment and
3845/// returns early. Panics loudly if the upstream selector block can't be
3946/// located — that means mimalloc changed `prim.h` and this patch needs
40- /// to be updated. Leaves the submodule's working tree marked dirty
41- /// after a fresh clone+build; reset with `git submodule update --force`
42- /// if needed (next build will re-apply the patch).
47+ /// to be updated. Submodule stays pristine in git; `git submodule
48+ /// update --force` resets it, next build re-applies the patch.
4349fn patch_apple_tls_model_for_v3 ( ) {
4450 let prim_h = "c_src/mimalloc3/include/mimalloc/prim.h" ;
4551 let content = std:: fs:: read_to_string ( prim_h) . unwrap_or_else ( |e| {
0 commit comments