/* StarForth — Steady-State Virtual Machine Runtime Copyright (c) 2023–2025 Robert A. James All rights reserved. This file is part of the StarForth project. Licensed under the StarForth License, Version 1.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at: https://github.com/star.4th@proton.me/StarForth/LICENSE.txt This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and noninfringement. See the License for the specific language governing permissions and limitations under the License. */ /* * platform/alloc_kernel.c - Kernel (bare-metal) memory allocator * * FABRIC-3.md §X.4, 2026-09-07: this used to be its own isolated static * 4MB arena (first-fit free list, no splitting/coalescing) -- sized small * enough, and fragile enough under concurrent VM churn, that a live * identity-heap-capacity test found the arena topping out around 6 * concurrent WIREBIND-born VMs, failing *before* true capacity exhaustion * (fragmentation from concurrent background allocation, no coalescing to * recover from it). Meanwhile `kmalloc.c` -- the kernel's general heap, * already initialised at boot (M6, well before any VM is ever born) -- * sits right next to it: reserved from real PMM-tracked physical memory * (not a fixed compile-time array), defaults to a 2 GiB floor explicitly * sized "for 256+ baby VMs" per its own comment, overridable via the * `--heap=` boot flag, and its free list *does* coalesce neighboring * blocks on every free. * * Every VM's word dictionary (`vm_create_word()`, the shared/vendored VM * core) allocates through this file's `sf_malloc()`/`sf_free()` -- * `platform_alloc.h`'s portable allocator abstraction, kernel-side. This * file now simply delegates to `kmalloc()`/`kfree()` instead of managing * its own separate, much smaller arena: same API contract callers already * depend on, real headroom (whatever the boot-time heap ends up being, * not a hardcoded 4MB), and real coalescing. No new allocator was * invented -- `kmalloc.c` already existed, was already boot-tested on all * three arches, and was simply never wired up as the backing store for * VM dictionaries specifically. * * `sf_alloc_init()` is now a no-op (see its own doc comment below): with * a *shared* heap serving every kernel subsystem, not an isolated arena * used only for VM dictionaries, "reset" is no longer a sane operation -- * nothing external ever called it anyway (confirmed: no callers besides * `sf_malloc()`'s own lazy-init guard, which this file's `sf_malloc()` * still keeps, now as a readiness check rather than an initializer). * * This file's own alignment guarantee (`SF_ALIGN`, 8 bytes) is preserved * via `kmalloc_aligned()` -- `kmalloc()`'s own default alignment * (`KMALLOC_MIN_ALIGN`, 16 bytes) already satisfies it, but asking * explicitly keeps this file's contract self-documenting rather than * relying on kmalloc.c's own default not changing under it. */ #include "platform_alloc.h" #include "starkernel/kmalloc.h" #include #include /* Alignment for all allocations (8 bytes for 64-bit safety) -- unchanged * from this file's previous arena-based implementation. */ #define SF_ALIGN 8 /* alloc_count/free_count have no kmalloc.c equivalent (it tracks bytes, * not call counts) -- kept here as simple diagnostic counters layered on * top of kmalloc's own byte-accurate stats, which sf_alloc_get_stats() * below reads fresh on every call rather than shadowing them locally. */ static size_t g_alloc_count = 0; static size_t g_free_count = 0; /** * @brief No-op: kept only for API compatibility with `platform_alloc.h`. * * The backing store is now the shared kernel heap (`kmalloc.c`), already * initialised at boot (M6) well before any VM allocation can occur -- * there is nothing left for this function to set up, and "resetting" a * heap shared by every other kernel subsystem would be actively wrong. * Kept callable (matches its documented "safe to call at any point" * contract) so no caller needs to change. * * @return 0 always (cannot fail) */ int sf_alloc_init(void) { return 0; } /* * @brief Allocate memory from the shared kernel heap. * * Delegates to `kmalloc_aligned()` (`SF_ALIGN`-byte aligned, matching * this file's previous guarantee). Returns @c NULL for zero-size * requests and whenever the shared heap itself returns NULL (not yet * initialised, or genuinely out of memory) -- both already part of this * function's documented contract. * * @param size Number of bytes to allocate. * @return Pointer to the allocated block on success, @c NULL on failure. */ void* sf_malloc(size_t size) { if (size == 0) return (void*)0; void *ptr = kmalloc_aligned(size, SF_ALIGN); if (ptr) g_alloc_count++; return ptr; } /* * @brief Allocate and zero-initialise a contiguous array from the kernel heap. * * Unchanged from this file's previous implementation: computes * @p count × @p size, checks for integer overflow via * @c total/size != count, then delegates to @c sf_malloc(). The returned * block is zeroed with a manual byte loop rather than @c memset so that * the kernel build remains freestanding with no libc dependency. * Returns @c NULL when either argument is zero, on overflow, or on heap * exhaustion. * * @param count Number of elements to allocate. * @param size Size of each element in bytes. * @return Pointer to the zeroed block on success, @c NULL on failure. */ void* sf_calloc(size_t count, size_t size) { if (count == 0 || size == 0) return (void*)0; size_t total = count * size; /* Check for overflow */ if (size != 0 && total / size != count) { return (void*)0; } void* ptr = sf_malloc(total); if (ptr) { /* Zero-initialize */ uint8_t* p = (uint8_t*)ptr; for (size_t i = 0; i < total; i++) { p[i] = 0; } } return ptr; } /* * @brief Resize an allocation in the kernel heap. * * Unchanged from this file's previous implementation: `kmalloc.c` has no * realloc-equivalent, so a fresh block of @p new_size bytes is allocated * and returned; the original block at @p ptr is left for the caller to * free explicitly (this function does not free it, matching this file's * prior documented behavior exactly -- not a regression introduced by * the kmalloc.c switch). * * Special cases match the C standard: * - @p ptr == @c NULL → equivalent to @c sf_malloc(@p new_size). * - @p new_size == 0 → returns @c NULL (caller treats old block as freed). * * The caller is responsible for copying content from the old block * before discarding the old pointer; this function does not perform the * copy. * * @param ptr Pointer to the existing allocation (may be @c NULL). * @param new_size Desired size of the new block in bytes. * @return Pointer to the new block on success, @c NULL on failure or when * @p new_size is zero. */ void* sf_realloc(void* ptr, size_t new_size) { if (!ptr) return sf_malloc(new_size); if (new_size == 0) return (void*)0; return sf_malloc(new_size); } /* * @brief Release a kernel heap allocation, making it available for reuse. * * Delegates to @c kfree(), which coalesces the freed block with any free * neighbors -- real reclamation, unlike this file's previous first-fit- * no-splitting free list (FABRIC-3.md §X.4: that lack of coalescing was * the proximate cause of a live-observed allocation failure under * concurrent VM churn even with more than enough free bytes overall). * * @param ptr Pointer previously returned by @c sf_malloc() / @c sf_calloc() * (may be @c NULL; silently ignored, matching @c kfree()'s own * contract). Must not be used again by the caller after this * call, and must not be freed twice. */ void sf_free(void* ptr) { if (!ptr) return; kfree(ptr); g_free_count++; } /* * @brief Retrieve a snapshot of kernel allocator statistics. * * Reads fresh from `kmalloc_get_stats()` on every call -- the shared heap * is the real source of truth, not a value shadowed locally. alloc_count/ * free_count (which kmalloc.c does not track) come from this file's own * counters instead. * * @param stats Output buffer to receive the statistics; silently returns * without writing if @p stats is @c NULL. */ void sf_alloc_get_stats(sf_alloc_stats_t* stats) { if (!stats) return; kmalloc_stats_t k = kmalloc_get_stats(); stats->total_bytes = (size_t)k.total_bytes; stats->used_bytes = (size_t)k.used_bytes; stats->peak_bytes = (size_t)k.peak_bytes; stats->alloc_count = g_alloc_count; stats->free_count = g_free_count; }