Skip to content

feat: k_mem_slab fixed-size block allocator (#46a)#51

Open
swoisz wants to merge 2 commits into
mainfrom
feature/k-mem-slab
Open

feat: k_mem_slab fixed-size block allocator (#46a)#51
swoisz wants to merge 2 commits into
mainfrom
feature/k-mem-slab

Conversation

@swoisz

@swoisz swoisz commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

Summary

Ports Zephyr's k_mem_slab fixed-size block allocator — the low-risk half of #46 (split from k_fifo/k_lifo, which carries the object-lifetime risk). Independent reimplementation over the Boreas substrate: the free-block count and blocking ride the owned, notification-backed k_sem, with a portMUX guarding the intrusive free list and usage counters.

Design

  • Free list: upstream's intrusive singly-linked scheme — the next-pointer lives in each free block's first word (*(char**)block), threaded back-to-front. block_size must be >= sizeof(void*) and word-aligned. alloc returns uninitialized memory (never zeroed), matching upstream.
  • Blocking + count via k_sem: alloc = k_sem_take(timeout) then pop under the lock; free = push then k_sem_give. Reusing k_sem inherits its hardened, targeted wake — a give with a waiter present wakes that waiter without bumping the count, so a freed block is reserved for the woken allocator and a racing K_NO_WAIT alloc correctly gets -ENOMEM. This reproduces upstream's direct hand-off with no new wait-queue code.
  • Return codes match upstream: 0 / -ENOMEM (K_NO_WAIT, none free) / -EAGAIN (timeout) / -EINVAL (bad params).
  • ISR-safe alloc(K_NO_WAIT) and free (IRAM/K_ISR_SAFE, verified in the ELF).
  • K_MEM_SLAB_DEFINE rounds block_size/buffer-align up to a pointer word (upstream WB_UP), so the same DEFINE compiles on 32- and 64-bit targets (the linux test host). The embedded sem is compile-time-initialized (like K_TIMER_DEFINE's), so the only lazy first-use step is free-list threading — pure IRAM-safe pointer work, safe from any context including an ISR. (Upstream threads the list from a PRE_KERNEL SYS_INIT, which doesn't run on the linux target.)

API

k_mem_slab_init, k_mem_slab_alloc(timeout), k_mem_slab_free, k_mem_slab_num_used_get / num_free_get / max_used_get, K_MEM_SLAB_DEFINE[_STATIC].

Review

Boreas-conformance + adversarial-trace fan-out. The adversarial trace found no correctness bugs — all 7 interleavings (count/list desync, the hand-off steal, lazy-init publication barriers, free-before-init, dual-core ISR-free) verified safe, including that free-before-init is a benign no-op (not a crash) because sys_dlist_is_empty on a zeroed list returns NULL rather than dereferencing. Two robustness blockers folded: the embedded sem is now compile-time-initialized (removing flash-resident k_sem_init from the lazy/ISR path) and free also threads the list defensively.

Test plan

  • linux: 231/0 ×3 (224 + 7; ISR test compiles out on linux)
  • clang-format 21.1.8 clean; alloc/free confirmed IRAM-resident, init in flash
  • ESP32-S3 hardware flash (pending)

Tests: init/accounting, -EINVAL on bad params, alloc distinctness/in-bounds/no-overlap + exhaustion -ENOMEM + max_used high-water, blocking-alloc timeout -EAGAIN, K_MEM_SLAB_DEFINE lazy-init usable + not-zeroed-on-realloc, blocking alloc woken by free (MT), N>blocks multi-waiter conservation (MT), FromISR free wakes a blocked allocator (HW-gated).

Refs #46 (the k_fifo/k_lifo half lands separately)

🤖 Generated with Claude Code

swoisz and others added 2 commits June 10, 2026 15:12
Independent reimplementation of upstream Zephyr's k_mem_slab API over
the Boreas substrate. The low-risk half of #46 (split from k_fifo).

Design: the free-block count and blocking ride the owned,
notification-backed k_sem (count = free blocks); a portMUX guards the
intrusive free list and the usage counters. Reusing k_sem inherits its
hardened wake protocol -- a give targets the highest-priority waiter
without bumping the count, so a freed block is reserved for the woken
allocator and a racing K_NO_WAIT alloc correctly gets -ENOMEM. This
matches upstream's direct hand-off semantics with no new wait-queue
code.

- Free list: upstream's intrusive singly-linked scheme (next-pointer in
  each free block's first word, threaded back-to-front). block_size
  must be >= sizeof(void*) and word-aligned; alloc returns uninitialized
  memory (never zeroed).
- API: k_mem_slab_init, k_mem_slab_alloc(timeout), k_mem_slab_free,
  num_used/num_free/max_used_get, K_MEM_SLAB_DEFINE[_STATIC].
- Return codes match upstream: 0 / -ENOMEM (K_NO_WAIT, none free) /
  -EAGAIN (timeout) / -EINVAL (bad params); k_sem_take's -EBUSY is
  mapped to -ENOMEM.
- ISR-safe alloc(K_NO_WAIT) and free (IRAM/K_ISR_SAFE), like the other
  primitives.
- K_MEM_SLAB_DEFINE rounds block_size and buffer alignment up to a
  pointer word (upstream WB_UP), so the same DEFINE compiles on 32- and
  64-bit targets (the linux test host). The free list is threaded
  lazily on first use, since upstream's PRE_KERNEL SYS_INIT threading
  does not run on the linux target -- DEFINE'd slabs must therefore be
  first touched from thread context (documented).

Divergence from upstream noted on the declarations; k_mem_slab_init
keeps upstream's strict word-alignment -EINVAL contract (no rounding).

Tests (7): init/accounting, -EINVAL on bad params, alloc distinctness/
in-bounds/no-overlap + exhaustion -ENOMEM + max_used high-water,
blocking-alloc timeout -EAGAIN, K_MEM_SLAB_DEFINE lazy-init usable,
blocking alloc woken by free (MT), N>blocks multi-waiter conservation
(MT), and FromISR free wakes a blocked allocator (HW-gated).

linux suite: 231/0 x3 (224 + 7; ISR test compiles out on linux).

Refs #46

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s list

Folded both review blockers (no correctness bugs were found by the
adversarial trace -- all 7 interleavings verified safe; these are
robustness/quality fixes):

- The embedded `avail` sem is now compile-time-initialized via
  Z_SEM_INITIALIZER in the DEFINE macro (like K_TIMER_DEFINE's embedded
  sem), so k_sem_init no longer runs on the lazy first-use path. That
  path is now pure free-list threading (IRAM-safe pointer work), which
  makes the K_ISR_SAFE annotations honest and lets a DEFINE'd slab be
  first-touched from an ISR -- the "first use from thread context"
  caveat is dropped.
- k_mem_slab_free now calls z_mem_slab_ensure_threaded too, so a
  DEFINE'd slab freed before any alloc (caller error) can't push onto
  an unthreaded list.
- BUILD_ASSERT num_blocks >= 1 in the DEFINE macros.
- ensure_threaded's under-lock re-check uses __atomic_load_n (TSAN
  cleanliness; it was already correct under the portMUX barrier).
- Comment the intentional num_used transient-skew during the free->
  woken-alloc hand-off.
- Test: assert a freed-then-realloc'd block keeps its bytes (alloc
  returns uninitialized memory, never zeroed).

linux suite: 231/0 x3.

Refs #46

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant