| 352 | } |
| 353 | |
| 354 | static void slab_free(void *ptr) { |
| 355 | if (!ptr) { |
| 356 | return; |
| 357 | } |
| 358 | uintptr_t base = (uintptr_t)ptr & SLAB_PAGE_MASK; |
| 359 | slab_page_t *page = slab_map_lookup(base); |
| 360 | if (!page) { |
| 361 | /* Not a slab chunk → plain heap pointer (>64B or grow-fallback). */ |
| 362 | free(ptr); |
| 363 | return; |
| 364 | } |
| 365 | |
| 366 | /* It is a slab chunk. The chunk itself keeps its page alive (refcount ≥ 1) |
| 367 | * until this very free, so the page cannot be freed underneath us. */ |
| 368 | slab_state_t *s = &tls_slab; |
| 369 | slab_free_node_t *node = (slab_free_node_t *)ptr; |
| 370 | |
| 371 | if (atomic_load_explicit(&page->owner, memory_order_relaxed) == s) { |
| 372 | /* Fast path: owner returns the chunk to its thread-local free list. */ |
| 373 | node->next = s->freelist; |
| 374 | s->freelist = node; |
| 375 | atomic_fetch_sub_explicit(&page->refcount, 1u, memory_order_acq_rel); |
| 376 | return; /* owner guard keeps refcount ≥ 1; page is reused, not freed */ |
| 377 | } |
| 378 | |
| 379 | /* Foreign (or retired) free: push onto the page's lock-free MPSC stack. */ |
| 380 | slab_free_node_t *head = atomic_load_explicit(&page->remote_free_head, memory_order_relaxed); |
| 381 | do { |
| 382 | node->next = head; |
| 383 | } while (!atomic_compare_exchange_weak_explicit(&page->remote_free_head, &head, node, |
| 384 | memory_order_release, memory_order_relaxed)); |
| 385 | unsigned prev = atomic_fetch_sub_explicit(&page->refcount, 1u, memory_order_acq_rel); |
| 386 | if (prev == 1u) { |
| 387 | /* We returned the final chunk of a retired page → release it. */ |
| 388 | slab_map_unregister_page(page); |
| 389 | cbm_aligned_free(page); |
| 390 | } |
| 391 | } |
| 392 | |
| 393 | /* ── Page-map registration wrappers (declared after slab_map_set) ── */ |
| 394 |
no test coverage detected