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>
65 lines
3.8 KiB
C
65 lines
3.8 KiB
C
/* opcode.h -- the 32 core opcodes.
|
|
*
|
|
* DECOMPOSITION.md section 1.3. "Opcode numbering follows the F18. Two names
|
|
* differ from Moore's: F18 `-` is renamed `inv` and F18 `or` (which is
|
|
* exclusive-or) is renamed `xor`, so instruction names never collide with
|
|
* FORTH-79 word names."
|
|
*
|
|
* Those two renames are load-bearing, not cosmetic: FORTH-79 has words named
|
|
* `-` and `OR`, and the DECOMPOSITION table would otherwise have two distinct
|
|
* meanings per name. The renames are therefore asserted against the FORTH
|
|
* name set in tests/test_iword.c, so a future edit that reintroduces a
|
|
* collision fails a test rather than silently producing an ambiguous table.
|
|
*
|
|
* Branch opcodes are marked because the instruction-word format depends on
|
|
* them: only these five take a target out of the low bits of their slot, and
|
|
* only they are legal outside the low four slots (section 1.2).
|
|
*/
|
|
#ifndef V4_OPCODE_H
|
|
#define V4_OPCODE_H
|
|
|
|
typedef enum {
|
|
V4_OP_SEMI = 0x00, /* ; return */
|
|
V4_OP_EX = 0x01, /* ex swap P and R */
|
|
V4_OP_JUMP = 0x02, /* jump a */
|
|
V4_OP_CALL = 0x03, /* call a */
|
|
V4_OP_UNEXT = 0x04, /* unext */
|
|
V4_OP_NEXT = 0x05, /* next a */
|
|
V4_OP_IF = 0x06, /* if a */
|
|
V4_OP_MINUS_IF = 0x07, /* -if a */
|
|
V4_OP_FETCH_P = 0x08, /* @p literal */
|
|
V4_OP_FETCH_INC= 0x09, /* @+ */
|
|
V4_OP_FETCH_B = 0x0A, /* @b */
|
|
V4_OP_FETCH_A = 0x0B, /* @ */
|
|
V4_OP_STORE_P = 0x0C, /* !p */
|
|
V4_OP_STORE_INC= 0x0D, /* !+ */
|
|
V4_OP_STORE_B = 0x0E, /* !b */
|
|
V4_OP_STORE_A = 0x0F, /* ! */
|
|
V4_OP_MUL_STEP = 0x10, /* +* multiply step, D-3 */
|
|
V4_OP_TWO_STAR = 0x11, /* 2* */
|
|
V4_OP_TWO_SLASH= 0x12, /* 2/ */
|
|
V4_OP_INV = 0x13, /* inv (F18 `-`) */
|
|
V4_OP_ADD = 0x14, /* + */
|
|
V4_OP_AND = 0x15, /* and */
|
|
V4_OP_XOR = 0x16, /* xor (F18 `or`) */
|
|
V4_OP_DROP = 0x17, /* drop */
|
|
V4_OP_DUP = 0x18, /* dup */
|
|
V4_OP_RPOP = 0x19, /* pop R to data stack */
|
|
V4_OP_OVER = 0x1A, /* over */
|
|
V4_OP_PUSH_A = 0x1B, /* a */
|
|
V4_OP_NOP = 0x1C, /* nop */
|
|
V4_OP_PUSH = 0x1D, /* push T to return stack */
|
|
V4_OP_BANG_B = 0x1E, /* b! B <- T */
|
|
V4_OP_BANG_A = 0x1F, /* a! A <- T */
|
|
|
|
V4_OPCODE_COUNT = 0x20
|
|
} v4_opcode;
|
|
|
|
/* True for the five opcodes that take a branch target from the low bits of
|
|
* their slot: jump, call, next, if, -if. Everything else ignores the low
|
|
* bits of its slot, which is what makes a non-branch slot's low bits
|
|
* free for the compiler to use as a compressed argument. */
|
|
int v4_op_is_branch(unsigned op);
|
|
|
|
#endif /* V4_OPCODE_H */
|