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>
75 lines
2.5 KiB
C
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;
|
|
}
|