Files
LithosAnanke/v4/include/v4/guard.h
T
rajamesandClaude Opus 5.5 067f317c47 feat(v4.0.0): hosted golden model, single node
First code for StarForth v4 (JUSTIFICATION.md section 10, step 1): one node
of the 32-instruction core as a C99 model, with cell width as a build
parameter.

- Node: P, A, B, F18 circular stacks (10 and 9 deep, D-2), word-addressed
  memory (D-1), 5% guard bands on every bounded list.
- Instruction word: six 5-bit slots in 32 bits at every cell width.
- Executor: all 32 opcodes of DECOMPOSITION.md 1.3. Cell arithmetic wraps
  explicitly; no signed overflow or implementation-defined shift.
- Heat: per-opcode and per-call-target counters and the anti-clock, driven
  by instruction retirement (1.4, D-6 interim).
- Slot packer and runner for tests, and a reference unsigned multiply in
  plain C99 with no 128-bit type.

Tests run at 32- and 64-bit cells, and under ASan and UBSan. They cover
every opcode and execute the first section 4 definitions (NIP SWAP OR
NEGATE ROT 0< 0= 2DUP - U<) against the C operation each stands for.

UM* as written in section 4 is exact only while u1 <= 2^(n-2). Two known
failing cases are pinned in test_foundation.c until it is rewritten.

DECOMPOSITION.md: record D-9, the instruction word is 32 bits at every
cell width (ruled 2026-10-02).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 14:38:27 -04:00

93 lines
4.5 KiB
C

/* guard.h -- 5% safety boundaries at each end of every bounded list.
*
* The convention used throughout this project is that a bounded list carries a
* safety boundary at each end rather than trusting its own bounds. The kernel
* arena is the reference instance (kernel/src/vm/arena.c):
*
* #define SK_VM_GUARD_SIZE PMM_PAGE_SIZE
* #define SK_VM_GUARD_PATTERN_HEAD 0x5ac0ffeed0c0ffeeULL
* #define SK_VM_GUARD_PATTERN_TAIL 0x0bad0badf00df00dULL
*
* with a guard_fill / guard_check pair, and total size = payload + 2 * guard.
* Two properties of that instance are kept here deliberately:
*
* - HEAD and TAIL carry *different* patterns, so a check that fails can say
* which end was overrun. A single pattern tells you that something ran
* off the end and nothing about where.
* - the boundary is placed immediately adjacent to the payload, because a
* boundary separated from what it protects catches nothing.
*
* This header supplies the same facility at a size that scales with the list
* (5% at each end) rather than a fixed page, and reduces it to two helpers so
* that no list in v4 has to hand-roll the arithmetic.
*
* WHY THIS IS LOAD-BEARING HERE, NOT DECORATIVE. AddressSanitizer already
* catches a genuine out-of-struct access, so a guard band cannot be justified
* as "catching overflow" -- ASan does that. What ASan does *not* catch is an
* index that is wrong but still lands inside the same object, which is exactly
* the failure mode of a circular buffer driven by unsigned index arithmetic:
*
* head = (head + V4_DATA_RING - 1u) % V4_DATA_RING; correct
* head = (head + V4_DATA_RING - 1u) & (V4_DATA_RING - 1u); correct today
*
* The second line is identical today *only because 8 is a power of two*. It
* is the sort of "simplification" that gets made, it is right for the current
* depths, and it silently breaks the first time a ring size stops being a power
* of two. Both forms stay in range, so ASan stays quiet. A guard band with a
* 5% margin either side is what turns "the tests happened to cover it" into a
* check that fires. The tests in tests/test_stack.c exercise that directly:
* they index past each end on purpose and require the check to catch it.
*
* The boundary is sized with ceiling division and a floor of one element, so a
* short list still gets a real boundary. A 5% margin on a 3-element list
* rounds to 0, and a zero-width guard is not a guard -- it is a comment.
*/
#ifndef V4_GUARD_H
#define V4_GUARD_H
#include "v4/cell.h"
/* Fraction of the payload reserved as a boundary at each end. */
#define V4_GUARD_PCT 5u
/* Boundary width, in elements, for a payload of n elements: 5% rounded up,
* never less than one. Written as a macro so that a struct can size its own
* inline boundary at compile time -- the boundary has to be storage the object
* owns, not something a helper allocates later, because a mesh node has no
* heap to allocate it from.
*
* n = 8 -> 1 (5% = 0.4, rounds up)
* n = 32 -> 2 (5% = 1.6, rounds up)
* n = 100 -> 5
* n = 1000 -> 50
* n = 3 -> 1 (5% = 0.15, rounds up to 1 -- not 0)
*/
#define V4_GUARD_ELEMS(n) \
(((n) * V4_GUARD_PCT + 99u) / 100u)
/* The kernel arena's patterns, carried over so that a hex dump from a mesh
* node and one from the kernel can be read with the same key. Casting to
* v4_ucell truncates cleanly at 32-bit cells: the arena's own constants are
* 64-bit, and their low halves are as distinct as the full words --
* 0xd0c0ffee against 0xf00df00d -- so "which end" stays answerable at both
* widths. */
#define V4_GUARD_PATTERN_HEAD ((v4_ucell)0x5ac0ffeed0c0ffeeULL)
#define V4_GUARD_PATTERN_TAIL ((v4_ucell)0x0bad0badf00df00dULL)
/* If these ever compare equal the boundary has stopped saying which end was
* overrun, which is half its purpose. C99 has no _Static_assert. */
typedef char v4_guard_patterns_are_distinct
[(V4_GUARD_PATTERN_HEAD != V4_GUARD_PATTERN_TAIL) ? 1 : -1];
/* Write one boundary band. `band` is the start of the band, `n` its width in
* elements. */
void v4_guard_fill(v4_cell *band, unsigned n, v4_ucell pattern);
/* 1 if the whole band still holds `pattern`, else 0. Checks every element, not
* just the ends: the band is 5% wide precisely so there is something to check
* in it, and a single word is what the kernel arena can afford at page
* granularity. */
int v4_guard_intact(const v4_cell *band, unsigned n, v4_ucell pattern);
#endif /* V4_GUARD_H */