Files
LithosAnanake/v4/src/iword.c
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

75 lines
2.5 KiB
C

/* iword.c -- instruction-word decode. See iword.h for the layout. */
#include "v4/iword.h"
/* The five opcodes that consume a target from the low bits of their slot
* (section 1.2): jump, call, next, if, -if. A table rather than a switch so
* that the set is visible in one place and can be counted in the tests. */
int v4_op_is_branch(unsigned op)
{
return op == V4_OP_JUMP || op == V4_OP_CALL || op == V4_OP_NEXT
|| op == V4_OP_IF || op == V4_OP_MINUS_IF;
}
int v4_iword_branch_legal(unsigned slot)
{
return slot < 4u; /* section 1.2: branches are legal in slots 0-3 only */
}
uint32_t v4_iword_slot_mask(unsigned slot)
{
if (slot >= V4_SLOT_COUNT) return 0u;
/* 27-5k address bits, e.g. 0x07FFFFFF for slot 0 down to 0x00000FFF for
* slot 3. For slots 4 and 5 the formula still produces 7 and 2 bits; the
* legality check is separate because those slots are simply not usable. */
return ((uint32_t)1u << V4_BRANCH_ADDR_BITS(slot)) - 1u;
}
unsigned v4_iword_op(v4_iword w, unsigned slot)
{
if (slot >= V4_SLOT_COUNT) return 0u;
return (unsigned)((w >> V4_SLOT_LOW_BIT(slot)) & 0x1Fu);
}
unsigned v4_iword_spare(v4_iword w)
{
return (unsigned)(w & ((1u << V4_SPARE_BITS) - 1u));
}
v4_iword v4_iword_target(v4_iword w, unsigned slot)
{
if (slot >= V4_SLOT_COUNT) return 0u;
return w & v4_iword_slot_mask(slot);
}
v4_iword v4_iword_assemble(const unsigned op[V4_SLOT_COUNT], unsigned spare)
{
v4_iword w = 0u;
for (unsigned k = 0; k < V4_SLOT_COUNT; k++) {
/* An opcode that does not fit in 5 bits is a bug in whoever built the
* word, not something to encode silently. Truncating here would let a
* malformed capsule execute as something else. */
if (op[k] > 0x1Fu) return 0xFFFFFFFFu;
w |= (v4_iword)(op[k] & 0x1Fu) << V4_SLOT_LOW_BIT(k);
}
if (spare > ((1u << V4_SPARE_BITS) - 1u)) return 0xFFFFFFFFu;
w |= (v4_iword)(spare & ((1u << V4_SPARE_BITS) - 1u));
return w;
}
v4_cell v4_iword_branch(v4_cell p, v4_iword w, unsigned slot)
{
uint32_t mask = v4_iword_slot_mask(slot);
v4_ucell up = (v4_ucell)p;
/* Page-relative, as in the F18: the target supplies the low
* V4_BRANCH_ADDR_BITS(slot) bits of P and the bits above them survive. The
* mask is computed in 32 bits and widened, so a 64-bit P keeps its high
* half -- which is the correct reading of "replaces the same low bits of P"
* at any cell width. */
up = (up & ~(v4_ucell)mask) | (v4_ucell)(w & mask);
return (v4_cell)up;
}