Phase 12: Shared SuperSlab Pool implementation (WIP - runtime crash)

## Summary
Implemented Phase 12 Shared SuperSlab Pool (mimalloc-style) to address
SuperSlab allocation churn (877 SuperSlabs → 100-200 target).

## Implementation (ChatGPT + Claude)
1. **Metadata changes** (superslab_types.h):
   - Added class_idx to TinySlabMeta (per-slab dynamic class)
   - Removed size_class from SuperSlab (no longer per-SuperSlab)
   - Changed owner_tid (16-bit) → owner_tid_low (8-bit)

2. **Shared Pool** (hakmem_shared_pool.{h,c}):
   - Global pool shared by all size classes
   - shared_pool_acquire_slab() - Get free slab for class_idx
   - shared_pool_release_slab() - Return slab when empty
   - Per-class hints for fast path optimization

3. **Integration** (23 files modified):
   - Updated all ss->size_class → meta->class_idx
   - Updated all meta->owner_tid → meta->owner_tid_low
   - superslab_refill() now uses shared pool
   - Free path releases empty slabs back to pool

4. **Build system** (Makefile):
   - Added hakmem_shared_pool.o to OBJS_BASE and TINY_BENCH_OBJS_BASE

## Status: ⚠️ Build OK, Runtime CRASH

**Build**:  SUCCESS
- All 23 files compile without errors
- Only warnings: superslab_allocate type mismatch (legacy code)

**Runtime**:  SEGFAULT
- Crash location: sll_refill_small_from_ss()
- Exit code: 139 (SIGSEGV)
- Test case: ./bench_random_mixed_hakmem 1000 256 42

## Known Issues
1. **SEGFAULT in refill path** - Likely shared_pool_acquire_slab() issue
2. **Legacy superslab_allocate()** still exists (type mismatch warning)
3. **Remaining TODOs** from design doc:
   - SuperSlab physical layout integration
   - slab_handle.h cleanup
   - Remove old per-class head implementation

## Next Steps
1. Debug SEGFAULT (gdb backtrace shows sll_refill_small_from_ss)
2. Fix shared_pool_acquire_slab() or superslab_init_slab()
3. Basic functionality test (1K → 100K iterations)
4. Measure SuperSlab count reduction (877 → 100-200)
5. Performance benchmark (+650-860% expected)

## Files Changed (25 files)
core/box/free_local_box.c
core/box/free_remote_box.c
core/box/front_gate_classifier.c
core/hakmem_super_registry.c
core/hakmem_tiny.c
core/hakmem_tiny_bg_spill.c
core/hakmem_tiny_free.inc
core/hakmem_tiny_lifecycle.inc
core/hakmem_tiny_magazine.c
core/hakmem_tiny_query.c
core/hakmem_tiny_refill.inc.h
core/hakmem_tiny_superslab.c
core/hakmem_tiny_superslab.h
core/hakmem_tiny_tls_ops.h
core/slab_handle.h
core/superslab/superslab_inline.h
core/superslab/superslab_types.h
core/tiny_debug.h
core/tiny_free_fast.inc.h
core/tiny_free_magazine.inc.h
core/tiny_remote.c
core/tiny_superslab_alloc.inc.h
core/tiny_superslab_free.inc.h
Makefile

## New Files (3 files)
PHASE12_SHARED_SUPERSLAB_POOL_DESIGN.md
core/hakmem_shared_pool.c
core/hakmem_shared_pool.h

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: ChatGPT <chatgpt@openai.com>
This commit is contained in:
Moe Charm (CI)
2025-11-13 16:33:03 +09:00
parent 2b9a03fa8b
commit 03df05ec75
29 changed files with 1390 additions and 1302 deletions

View File

@ -26,14 +26,15 @@ static inline void hak_tiny_free_superslab(void* ptr, SuperSlab* ss) {
uintptr_t ss_base = (uintptr_t)ss;
if (__builtin_expect(slab_idx < 0, 0)) {
uintptr_t aux = tiny_remote_pack_diag(0xBAD1u, ss_base, ss_size, (uintptr_t)ptr);
tiny_debug_ring_record(TINY_RING_EVENT_REMOTE_INVALID, (uint16_t)ss->size_class, ptr, aux);
tiny_debug_ring_record(TINY_RING_EVENT_REMOTE_INVALID, 0xFFu, ptr, aux);
if (g_tiny_safe_free_strict) { raise(SIGUSR2); return; }
return;
}
TinySlabMeta* meta = &ss->slabs[slab_idx];
uint8_t cls = meta->class_idx;
// Debug: Log first C7 alloc/free for path verification
if (ss->size_class == 7) {
if (cls == 7) {
static _Atomic int c7_free_count = 0;
int count = atomic_fetch_add_explicit(&c7_free_count, 1, memory_order_relaxed);
if (count == 0) {
@ -45,20 +46,20 @@ static inline void hak_tiny_free_superslab(void* ptr, SuperSlab* ss) {
if (__builtin_expect(tiny_remote_watch_is(ptr), 0)) {
tiny_remote_watch_note("free_enter", ss, slab_idx, ptr, 0xA240u, tiny_self_u32(), 0);
extern __thread TinyTLSSlab g_tls_slabs[];
tiny_alloc_dump_tls_state(ss->size_class, "watch_free_enter", &g_tls_slabs[ss->size_class]);
tiny_alloc_dump_tls_state(cls, "watch_free_enter", &g_tls_slabs[cls]);
#if !HAKMEM_BUILD_RELEASE
extern __thread TinyTLSMag g_tls_mags[];
TinyTLSMag* watch_mag = &g_tls_mags[ss->size_class];
TinyTLSMag* watch_mag = &g_tls_mags[cls];
fprintf(stderr,
"[REMOTE_WATCH_MAG] cls=%u mag_top=%d cap=%d\n",
ss->size_class,
cls,
watch_mag->top,
watch_mag->cap);
#endif
}
// BUGFIX: Validate size_class before using as array index (prevents OOB)
if (__builtin_expect(ss->size_class < 0 || ss->size_class >= TINY_NUM_CLASSES, 0)) {
tiny_debug_ring_record(TINY_RING_EVENT_REMOTE_INVALID, 0xF1, ptr, (uintptr_t)ss->size_class);
// BUGFIX (Phase 12): Validate class_idx before using as array index
if (__builtin_expect(cls >= TINY_NUM_CLASSES, 0)) {
tiny_debug_ring_record(TINY_RING_EVENT_REMOTE_INVALID, 0xF1, ptr, (uintptr_t)cls);
if (g_tiny_safe_free_strict) { raise(SIGUSR2); return; }
return;
}
@ -67,7 +68,7 @@ static inline void hak_tiny_free_superslab(void* ptr, SuperSlab* ss) {
// Expected impact: -10~-15% CPU (eliminates O(n) duplicate scan)
#if !HAKMEM_BUILD_RELEASE
if (__builtin_expect(g_tiny_safe_free, 0)) {
size_t blk = g_tiny_class_sizes[ss->size_class];
size_t blk = g_tiny_class_sizes[cls];
uint8_t* base = tiny_slab_base_for(ss, slab_idx);
uintptr_t delta = (uintptr_t)ptr - (uintptr_t)base;
int cap_ok = (meta->capacity > 0) ? 1 : 0;
@ -78,17 +79,17 @@ static inline void hak_tiny_free_superslab(void* ptr, SuperSlab* ss) {
if (align_ok) code |= 0x2u;
if (range_ok) code |= 0x1u;
uintptr_t aux = tiny_remote_pack_diag(code, ss_base, ss_size, (uintptr_t)ptr);
tiny_debug_ring_record(TINY_RING_EVENT_REMOTE_INVALID, (uint16_t)ss->size_class, ptr, aux);
tiny_debug_ring_record(TINY_RING_EVENT_REMOTE_INVALID, (uint16_t)cls, ptr, aux);
if (g_tiny_safe_free_strict) { raise(SIGUSR2); return; }
return;
}
// Duplicate in freelist (best-effort scan up to 64)
// NOTE: This O(n) scan is VERY expensive (can scan 64 pointers per free!)
void* scan = meta->freelist; int scanned = 0; int dup = 0;
while (scan && scanned < 64) { if (scan == base) { dup = 1; break; } scan = tiny_next_read(ss->size_class, scan); scanned++; }
while (scan && scanned < 64) { if (scan == base) { dup = 1; break; } scan = tiny_next_read(cls, scan); scanned++; }
if (dup) {
uintptr_t aux = tiny_remote_pack_diag(0xDFu, ss_base, ss_size, (uintptr_t)ptr);
tiny_debug_ring_record(TINY_RING_EVENT_REMOTE_INVALID, (uint16_t)ss->size_class, ptr, aux);
tiny_debug_ring_record(TINY_RING_EVENT_REMOTE_INVALID, (uint16_t)cls, ptr, aux);
if (g_tiny_safe_free_strict) { raise(SIGUSR2); return; }
return;
}
@ -97,8 +98,8 @@ static inline void hak_tiny_free_superslab(void* ptr, SuperSlab* ss) {
// Phase E1-CORRECT: C7 now has headers like other classes
// Validation must check base pointer (ptr-1) alignment, not user pointer
if (__builtin_expect(ss->size_class == 7, 0)) {
size_t blk = g_tiny_class_sizes[ss->size_class];
if (__builtin_expect(cls == 7, 0)) {
size_t blk = g_tiny_class_sizes[cls];
uint8_t* slab_base = tiny_slab_base_for(ss, slab_idx);
uintptr_t delta = (uintptr_t)base - (uintptr_t)slab_base;
int cap_ok = (meta->capacity > 0) ? 1 : 0;
@ -106,7 +107,7 @@ static inline void hak_tiny_free_superslab(void* ptr, SuperSlab* ss) {
int range_ok = cap_ok && (delta / blk) < meta->capacity;
if (!align_ok || !range_ok) {
uintptr_t aux = tiny_remote_pack_diag(0xA107u, ss_base, ss_size, (uintptr_t)ptr);
tiny_debug_ring_record(TINY_RING_EVENT_REMOTE_INVALID, (uint16_t)ss->size_class, ptr, aux);
tiny_debug_ring_record(TINY_RING_EVENT_REMOTE_INVALID, (uint16_t)cls, ptr, aux);
#if !HAKMEM_BUILD_RELEASE
// Debug build: Print diagnostic info before failing
fprintf(stderr, "[C7_ALIGN_CHECK_FAIL] ptr=%p base=%p slab_base=%p\n", ptr, base, (void*)slab_base);
@ -122,25 +123,26 @@ static inline void hak_tiny_free_superslab(void* ptr, SuperSlab* ss) {
}
}
// Phase 6.23: Same-thread check
// Phase 6.23: Same-thread check (Phase 12: owner_tid_low)
uint32_t my_tid = tiny_self_u32();
uint8_t my_tid_low = (uint8_t)my_tid;
const int debug_guard = g_debug_remote_guard;
static __thread int g_debug_free_count = 0;
// If owner is not set yet, claim ownership to avoid spurious remote path in 1T
if (!g_tiny_force_remote && meta->owner_tid == 0) {
meta->owner_tid = my_tid;
// If owner is not set yet, claim ownership (low 8 bits) to avoid spurious remote path in 1T
if (!g_tiny_force_remote && meta->owner_tid_low == 0) {
meta->owner_tid_low = my_tid_low;
}
if (!g_tiny_force_remote && meta->owner_tid != 0 && meta->owner_tid == my_tid) {
if (!g_tiny_force_remote && meta->owner_tid_low != 0 && meta->owner_tid_low == my_tid_low) {
ROUTE_MARK(17); // free_same_thread
// Fast path: Direct freelist push (same-thread)
if (0 && debug_guard && g_debug_free_count < 1) {
fprintf(stderr, "[FREE_SS] SAME-THREAD: owner=%u my=%u\n",
meta->owner_tid, my_tid);
meta->owner_tid_low, my_tid);
g_debug_free_count++;
}
if (__builtin_expect(meta->used == 0, 0)) {
uintptr_t aux = tiny_remote_pack_diag(0x00u, ss_base, ss_size, (uintptr_t)ptr);
tiny_debug_ring_record(TINY_RING_EVENT_REMOTE_INVALID, (uint16_t)ss->size_class, ptr, aux);
tiny_debug_ring_record(TINY_RING_EVENT_REMOTE_INVALID, (uint16_t)cls, ptr, aux);
if (g_tiny_safe_free_strict) { raise(SIGUSR2); return; }
return;
}
@ -150,13 +152,13 @@ static inline void hak_tiny_free_superslab(void* ptr, SuperSlab* ss) {
int transitioned = tiny_free_remote_box(ss, slab_idx, meta, base, my_tid);
if (transitioned) {
extern unsigned long long g_remote_free_transitions[];
g_remote_free_transitions[ss->size_class]++;
g_remote_free_transitions[cls]++;
// Free-side route: remote transition observed
do {
static int g_route_free = -1; if (__builtin_expect(g_route_free == -1, 0)) {
const char* e = getenv("HAKMEM_TINY_ROUTE_FREE");
g_route_free = (e && *e && *e != '0') ? 1 : 0; }
if (g_route_free) route_free_commit((int)ss->size_class, (1ull<<18), 0xE2);
if (g_route_free) route_free_commit((int)cls, (1ull<<18), 0xE2);
} while (0);
}
return;
@ -169,9 +171,9 @@ static inline void hak_tiny_free_superslab(void* ptr, SuperSlab* ss) {
g_free_to_ss = (e && *e && *e != '0') ? 1 : 0; // default OFF
}
if (!g_free_to_ss) {
int cls = (int)ss->size_class;
if (midtc_enabled() && cls >= 4) {
if (midtc_push(cls, base)) {
int mid_cls = (int)cls;
if (midtc_enabled() && mid_cls >= 4) {
if (midtc_push(mid_cls, base)) {
// Treat as returned to TLS cache (not SS freelist)
meta->used--;
ss_active_dec_one(ss);
@ -188,39 +190,43 @@ static inline void hak_tiny_free_superslab(void* ptr, SuperSlab* ss) {
if (prev_before == NULL) {
ROUTE_MARK(19); // first_free_transition
extern unsigned long long g_first_free_transitions[];
g_first_free_transitions[ss->size_class]++;
g_first_free_transitions[cls]++;
ROUTE_MARK(20); // mailbox_publish
// Free-side route commit (one-shot)
do {
static int g_route_free = -1; if (__builtin_expect(g_route_free == -1, 0)) {
const char* e = getenv("HAKMEM_TINY_ROUTE_FREE");
g_route_free = (e && *e && *e != '0') ? 1 : 0; }
int cls = (int)ss->size_class;
if (g_route_free) route_free_commit(cls, (1ull<<19) | (1ull<<20), 0xE1);
} while (0);
}
if (__builtin_expect(debug_guard, 0)) {
fprintf(stderr, "[REMOTE_LOCAL] cls=%u slab=%d owner=%u my=%u ptr=%p prev=%p used=%u\n",
ss->size_class, slab_idx, meta->owner_tid, my_tid, ptr, prev_before, meta->used);
fprintf(stderr, "[REMOTE_LOCAL] cls=%u slab=%d owner=%u my=%u ptr=%p prev=%p used=%u\n",
cls, slab_idx, meta->owner_tid_low, my_tid, ptr, prev_before, meta->used);
}
// 空検出は別途(ホットパス除外)
// Phase 12: slab empty → shared pool に返却
if (meta->used == 0) {
extern void shared_pool_release_slab(SuperSlab* ss, int slab_idx);
shared_pool_release_slab(ss, slab_idx);
}
} else {
ROUTE_MARK(18); // free_remote_transition
if (__builtin_expect(meta->owner_tid == my_tid && meta->owner_tid == 0, 0)) {
if (__builtin_expect(meta->owner_tid_low == my_tid_low && meta->owner_tid_low == 0, 0)) {
uintptr_t aux = tiny_remote_pack_diag(0xA300u, ss_base, ss_size, (uintptr_t)ptr);
tiny_debug_ring_record(TINY_RING_EVENT_REMOTE_INVALID, (uint16_t)ss->size_class, ptr, aux);
tiny_debug_ring_record(TINY_RING_EVENT_REMOTE_INVALID, (uint16_t)cls, ptr, aux);
if (debug_guard) {
fprintf(stderr, "[REMOTE_OWNER_ZERO] cls=%u slab=%d ptr=%p my=%u used=%u\n",
ss->size_class, slab_idx, ptr, my_tid, (unsigned)meta->used);
cls, slab_idx, ptr, my_tid, (unsigned)meta->used);
}
}
tiny_remote_track_expect_alloc(ss, slab_idx, ptr, "remote_free_enter", my_tid);
// Slow path: Remote free (cross-thread)
if (0 && debug_guard && g_debug_free_count < 5) {
fprintf(stderr, "[FREE_SS] CROSS-THREAD: owner=%u my=%u slab_idx=%d\n",
meta->owner_tid, my_tid, slab_idx);
meta->owner_tid_low, my_tid, slab_idx);
g_debug_free_count++;
}
if (__builtin_expect(g_tiny_safe_free, 0)) {
@ -232,7 +238,7 @@ static inline void hak_tiny_free_superslab(void* ptr, SuperSlab* ss) {
while (cur && scanned < 64) {
if ((cur < base) || (cur >= base + ss_size)) {
uintptr_t aux = tiny_remote_pack_diag(0xA200u, base, ss_size, cur);
tiny_debug_ring_record(TINY_RING_EVENT_REMOTE_INVALID, (uint16_t)ss->size_class, (void*)cur, aux);
tiny_debug_ring_record(TINY_RING_EVENT_REMOTE_INVALID, (uint16_t)cls, (void*)cur, aux);
if (g_tiny_safe_free_strict) { raise(SIGUSR2); return; }
break;
}
@ -240,20 +246,21 @@ static inline void hak_tiny_free_superslab(void* ptr, SuperSlab* ss) {
if (__builtin_expect(g_remote_side_enable, 0)) {
if (!tiny_remote_sentinel_ok((void*)cur)) {
uintptr_t aux = tiny_remote_pack_diag(0xA202u, base, ss_size, cur);
tiny_debug_ring_record(TINY_RING_EVENT_REMOTE_INVALID, (uint16_t)ss->size_class, (void*)cur, aux);
tiny_debug_ring_record(TINY_RING_EVENT_REMOTE_INVALID, (uint16_t)cls, (void*)cur, aux);
tiny_debug_ring_record(TINY_RING_EVENT_REMOTE_INVALID, (uint16_t)cls, (void*)cur, aux);
uintptr_t observed = atomic_load_explicit((_Atomic uintptr_t*)(void*)cur, memory_order_relaxed);
tiny_remote_report_corruption("scan", (void*)cur, observed);
if (__builtin_expect(g_debug_remote_guard, 0)) {
fprintf(stderr,
"[REMOTE_SENTINEL] cls=%u slab=%d cur=%p head=%p ptr=%p scanned=%d observed=0x%016" PRIxPTR " owner=%u used=%u freelist=%p remote_head=%p\n",
ss->size_class,
cls,
slab_idx,
(void*)cur,
(void*)head,
ptr,
scanned,
observed,
meta->owner_tid,
meta->owner_tid_low,
(unsigned)meta->used,
meta->freelist,
(void*)atomic_load_explicit(&ss->remote_heads[slab_idx], memory_order_relaxed));
@ -265,24 +272,24 @@ static inline void hak_tiny_free_superslab(void* ptr, SuperSlab* ss) {
} else {
if ((cur & (uintptr_t)(sizeof(void*) - 1)) != 0) {
uintptr_t aux = tiny_remote_pack_diag(0xA201u, base, ss_size, cur);
tiny_debug_ring_record(TINY_RING_EVENT_REMOTE_INVALID, (uint16_t)ss->size_class, (void*)cur, aux);
tiny_debug_ring_record(TINY_RING_EVENT_REMOTE_INVALID, (uint16_t)cls, (void*)cur, aux);
if (g_tiny_safe_free_strict) { raise(SIGUSR2); return; }
break;
}
cur = (uintptr_t)tiny_next_read(ss->size_class, (void*)cur);
cur = (uintptr_t)tiny_next_read(cls, (void*)cur);
}
scanned++;
}
if (dup) {
uintptr_t aux = tiny_remote_pack_diag(0xD1u, ss_base, ss_size, (uintptr_t)ptr);
tiny_debug_ring_record(TINY_RING_EVENT_REMOTE_INVALID, (uint16_t)ss->size_class, ptr, aux);
tiny_debug_ring_record(TINY_RING_EVENT_REMOTE_INVALID, (uint16_t)cls, ptr, aux);
if (g_tiny_safe_free_strict) { raise(SIGUSR2); return; }
return;
}
}
if (__builtin_expect(meta->used == 0, 0)) {
uintptr_t aux = tiny_remote_pack_diag(0x01u, ss_base, ss_size, (uintptr_t)ptr);
tiny_debug_ring_record(TINY_RING_EVENT_REMOTE_INVALID, (uint16_t)ss->size_class, ptr, aux);
tiny_debug_ring_record(TINY_RING_EVENT_REMOTE_INVALID, (uint16_t)cls, ptr, aux);
if (g_tiny_safe_free_strict) { raise(SIGUSR2); return; }
return;
}
@ -310,9 +317,9 @@ static inline void hak_tiny_free_superslab(void* ptr, SuperSlab* ss) {
// Use remote queue
uintptr_t head_word = __atomic_load_n((uintptr_t*)base, __ATOMIC_RELAXED);
if (debug_guard) fprintf(stderr, "[REMOTE_PUSH_CALL] cls=%u slab=%d owner=%u my=%u ptr=%p used=%u remote_count=%u head=%p word=0x%016" PRIxPTR "\n",
ss->size_class,
cls,
slab_idx,
meta->owner_tid,
meta->owner_tid_low,
my_tid,
ptr,
(unsigned)meta->used,
@ -330,13 +337,13 @@ static inline void hak_tiny_free_superslab(void* ptr, SuperSlab* ss) {
uintptr_t aux = tiny_remote_pack_diag(0xA214u, ss_base, ss_size, (uintptr_t)ptr);
tiny_remote_watch_mark(ptr, "dup_prevent", my_tid);
tiny_remote_watch_note("dup_prevent", ss, slab_idx, ptr, 0xA214u, my_tid, 0);
tiny_debug_ring_record(TINY_RING_EVENT_REMOTE_INVALID, (uint16_t)ss->size_class, ptr, aux);
tiny_debug_ring_record(TINY_RING_EVENT_REMOTE_INVALID, (uint16_t)cls, ptr, aux);
tiny_failfast_abort_ptr("double_free_remote", ss, slab_idx, ptr, "remote_side_contains");
}
if (__builtin_expect(g_remote_side_enable && (head_word & 0xFFFFu) == 0x6261u, 0)) {
// TLS guard scribble detected on the node's first word → same-pointer double free across routes
uintptr_t aux = tiny_remote_pack_diag(0xA213u, ss_base, ss_size, (uintptr_t)ptr);
tiny_debug_ring_record(TINY_RING_EVENT_REMOTE_INVALID, (uint16_t)ss->size_class, ptr, aux);
tiny_debug_ring_record(TINY_RING_EVENT_REMOTE_INVALID, (uint16_t)cls, ptr, aux);
tiny_remote_watch_mark(ptr, "pre_push", my_tid);
tiny_remote_watch_note("pre_push", ss, slab_idx, ptr, 0xA231u, my_tid, 0);
tiny_remote_report_corruption("pre_push", ptr, head_word);
@ -350,16 +357,17 @@ static inline void hak_tiny_free_superslab(void* ptr, SuperSlab* ss) {
// ss_active_dec_one(ss); // REMOVED: Already called inside ss_remote_push()
if (was_empty) {
extern unsigned long long g_remote_free_transitions[];
g_remote_free_transitions[ss->size_class]++;
ss_partial_publish((int)ss->size_class, ss);
g_remote_free_transitions[cls]++;
g_remote_free_transitions[cls]++;
ss_partial_publish((int)cls, ss);
}
} else {
// Fallback: direct freelist push (legacy)
if (debug_guard) fprintf(stderr, "[FREE_SS] Using LEGACY freelist push (not remote queue)\n");
void* prev = meta->freelist;
tiny_next_write(ss->size_class, base, prev);
tiny_next_write(cls, base, prev);
meta->freelist = base;
tiny_failfast_log("free_local_legacy", ss->size_class, ss, meta, ptr, prev);
tiny_failfast_log("free_local_legacy", cls, ss, meta, ptr, prev);
do {
static int g_mask_en = -1;
if (__builtin_expect(g_mask_en == -1, 0)) {
@ -374,10 +382,15 @@ static inline void hak_tiny_free_superslab(void* ptr, SuperSlab* ss) {
meta->used--;
ss_active_dec_one(ss);
if (prev == NULL) {
ss_partial_publish((int)ss->size_class, ss);
ss_partial_publish((int)cls, ss);
}
}
// 空検出は別途(ホットパス除外)
// Phase 12: slab empty → shared pool に返却
if (meta->used == 0) {
extern void shared_pool_release_slab(SuperSlab* ss, int slab_idx);
shared_pool_release_slab(ss, slab_idx);
}
}
}