From 5aabbc87bf5c6e2e6842f48bd83de41bfa675acc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Mar=C3=ADn?= Date: Tue, 7 Jul 2026 16:47:54 +0200 Subject: [PATCH] Fix `malloc_getcpu` on macOS to read the current CPU number correctly The Apple branch read the CPU number from the low 3 bits of `tpidrro_el0`. That layout no longer holds on Apple Silicon: those bits read back as 0, so `malloc_getcpu` returned 0 for every thread. `percpu_arena` then funneled all allocations into arena 0, or was disabled outright at init. Read the CPU number the same way as libplatform's `_os_cpu_number`: the low 12 bits of `tpidr_el0` on arm64, or of the IDT base (via `sidt`) on x86. Also move the Apple branch ahead of the `rdtscp` one so Apple x86 uses `sidt` rather than `rdtscp`, whose `ecx` is not the CPU id under XNU. https://github.com/apple-oss-distributions/xnu/blob/main/libsyscall/os/tsd.h Verified on a 12-CPU M2 Pro: with `percpu_arena:percpu`, `opt.percpu_arena` now stays `percpu` and `thread.arena` spreads across all 12 arenas {0..11}; the old code collapsed every thread onto arena 0. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../internal/jemalloc_internal_inlines_a.h | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/include/jemalloc/internal/jemalloc_internal_inlines_a.h b/include/jemalloc/internal/jemalloc_internal_inlines_a.h index 9912eff4..1e3b7972 100644 --- a/include/jemalloc/internal/jemalloc_internal_inlines_a.h +++ b/include/jemalloc/internal/jemalloc_internal_inlines_a.h @@ -18,15 +18,26 @@ malloc_getcpu(void) { return GetCurrentProcessorNumber(); #elif defined(JEMALLOC_HAVE_SCHED_GETCPU) return (malloc_cpuid_t)sched_getcpu(); +#elif defined(__APPLE__) + /* No sched_getcpu() on macOS; read the CPU number like _os_cpu_number(), see + * https://github.com/apple-oss-distributions/xnu/blob/main/libsyscall/os/tsd.h + * (it is no longer in tpidrro_el0's low bits on Apple Silicon). */ +# if defined(__aarch64__) + uint64_t cpu; + __asm__ __volatile__("mrs %0, tpidr_el0" : "=r"(cpu)); + return (malloc_cpuid_t)(cpu & 0xfff); +# elif defined(__x86_64__) || defined(__i386__) + struct { uintptr_t p1, p2; } idtr; + __asm__ __volatile__("sidt %0" : "=m"(idtr)); + return (malloc_cpuid_t)(idtr.p1 & 0xfff); +# else + not_reached(); + return -1; +# endif #elif defined(JEMALLOC_HAVE_RDTSCP) unsigned int ecx; asm volatile("rdtscp" : "=c"(ecx)::"eax", "edx"); return (malloc_cpuid_t)(ecx & 0xfff); -#elif defined(__aarch64__) && defined(__APPLE__) - /* Other oses most likely use tpidr_el0 instead */ - uintptr_t c; - asm volatile("mrs %x0, tpidrro_el0" : "=r"(c)::"memory"); - return (malloc_cpuid_t)(c & (1 << 3) - 1); #else not_reached(); return -1;