feat(v4): the ROM's slot packer, word for word with asm.c

(OP) (LIT) (BRANCH) (BRANCH>) (RESOLVE) (LABEL), entries 6-11: the code
generator of any language built on the machine (MACHINE.md 2.1).  asm.c's
layout rules with text.c's branch placement; 400 random sequences agree
word for word with asm.c at both widths, memory-full and out-of-reach are
refused on both sides, and a ref with no branch is refused, not looped on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-15 11:58:47 -04:00
co-authored by Claude Opus 5.5
parent 07911dcf91
commit c233cb6536
5 changed files with 327 additions and 1 deletions
+13 -1
View File
@@ -105,7 +105,16 @@
#define SEND_PTR (BUF0_W - 101) /* where the next word of a text being made ready to send goes */
#define OUT_TEXT (OUT_W - 64) /* what was printed, four characters to a cell, as it goes on the wire: 64 cells */
#define SEND_TEXT (OUT_W - 400) /* the text of a message SEND is putting, four characters to a cell: 256 cells */
/* FREE, plain memory: BUF0_W - 32 .. - 21; - 122 .. - 113; BUF0_W - 144 .. - 141 (what a flush makes zero after
/* The ROM's slot packer (MACHINE.md 2.1), in what was free: BUF0_W - 32 .. - 23 and - 122 .. - 120. */
#define ROM_HERE (BUF0_W - 32) /* the next word address code goes to */
#define ROM_CUR (BUF0_W - 31) /* the instruction word being built */
#define ROM_SLOT (BUF0_W - 30) /* its next free slot, 0 .. 6 */
#define ROM_NLIT (BUF0_W - 29) /* how many literals it owes */
#define ROM_LITS (BUF0_W - 28) /* the literals, 6 cells */
#define ROM_PI (BUF0_W - 122) /* working cells */
#define ROM_RS (BUF0_W - 121)
#define ROM_RN (BUF0_W - 120)
/* FREE, plain memory: BUF0_W - 22 .. - 21; - 119 .. - 113; BUF0_W - 144 .. - 141 (what a flush makes zero after
* a full output buffer); and OUT_W - 144 .. OUT_W - 65. They and
* the cells above held the messages waiting, the refusals owed and what went with them (MESH.md 7a, 7b), which a
* node no longer keeps. */
@@ -159,6 +168,9 @@
#define ROM_ERR_BAD_NAME 3 /* an opcode, a reserved word, a label, a number or &...: "cannot be used as a name" */
#define ROM_ERR_DEFINED 4 /* "already defined" */
#define ROM_ERR_NAMES 5 /* the symbol table is full: "too many names" */
#define ROM_ERR_FIT 6 /* memory is full, or a branch cannot reach: "does not fit" */
#define ROM_ERR_OPCODE 7 /* not an opcode the packer takes there */
#define ROM_ADDR_TOP ((v4_cell)(V4_NODE_WORDS + V4_ROM_WORDS - 1u)) /* the top of the address space */
#define WORD_DEFINED (BUF0_W - 11) /* the xt of the word that is told of each new entry, or 0 */
#define WORD_FORGOTTEN (BUF0_W - 12) /* the xt of the word that is told when entries go, or 0 */
#define LOG_LEVEL (BUF0_W - 5) /* the level at or below which a message is printed */
+156
View File
@@ -0,0 +1,156 @@
/* test_host_rom_pack.c -- the ROM's slot packer, word for word with asm.c.
*
* Plan Task 5. (OP) ( op -- ), (LIT) ( v -- ), (BRANCH) ( op target -- ),
* (BRANCH>) ( op -- ref ), (RESOLVE) ( ref target -- ) and (LABEL)
* ( -- addr ) lay code down exactly as v4/src/asm.c does, with text.c's
* branch placement: a branch goes in a slot only if that slot reaches the
* whole address space. Random sequences are applied both ways -- asm.c on
* node A, the ROM on node B -- and memory is compared word for word; a
* sequence that is an error on one side must be an error on the other.
*/
#include "rom_harness.h"
static int failures = 0, checks = 0;
#define CHECK(c,...) do{checks++; if(!(c)){failures++; printf("FAIL %s:%d: ",__FILE__,__LINE__); printf(__VA_ARGS__); printf("\n");}}while(0)
static v4_node a, b;
static v4_asm as;
static unsigned long long seed = 20261015u;
static unsigned rnd(unsigned n) { seed = seed * 6364136223846793005ull + 1442695040888963407ull; return (unsigned)((seed >> 33) % n); }
/* text.c's place_branch, for the asm.c side */
static void place_branch(void)
{
if (as.slot == 0) return;
if (!v4_iword_branch_legal(as.slot) || (v4_ucell)v4_iword_slot_mask(as.slot) < (v4_ucell)ROM_ADDR_TOP)
(void)v4_asm_label(&as);
}
/* call a ROM routine with up to two arguments; 1 if it returned */
static int rom(const char *name, int argc, v4_cell x, v4_cell y)
{
if (argc > 0) v4_dstack_push(&b.ds, x);
if (argc > 1) v4_dstack_push(&b.ds, y);
if (rom_call(&b, name, 1000000)) return 1;
printf(" %s did not return: P %ld, stopped %d, fault kind %u at %ld\n", name, (long)b.p, b.stopped, b.fault_kind, (long)b.fault_addr);
return 0;
}
static int rom_failed(void) { return b.mem[ROM_FAILED] != 0; }
static const unsigned branch_ops[5] = { V4_OP_JUMP, V4_OP_CALL, V4_OP_NEXT, V4_OP_IF, V4_OP_MINUS_IF };
/* one random sequence from `origin`; 1 if both sides agree throughout */
static int trial(v4_cell origin, unsigned steps)
{
v4_asm_ref aref[64];
v4_cell bref[64], labels[64], ha, hb, w;
unsigned nref = 0, nlab = 0, i, k;
v4_node_reset(&a);
v4_asm_begin(&as, &a, origin);
rom_boot(&b);
v4_node_console_input_attach(&b, -1, -1); /* the packer reads no input: near the top of RAM, those are words */
b.mem[ROM_HERE] = origin;
for (i = 0; i < steps; i++) {
unsigned what = rnd(100);
int ea, eb;
if (what < 45) { /* an ordinary opcode */
unsigned op;
do op = rnd(32); while (v4_op_is_branch(op) || op == V4_OP_FETCH_P);
v4_asm_op(&as, op);
if (!rom("(OP)", 1, (v4_cell)op, 0)) return 0;
} else if (what < 70) { /* a literal */
v4_cell v = (v4_cell)rnd(2000) - 1000;
if (rnd(4) == 0) v = (v4_cell)((v4_ucell)0 - (v4_ucell)rnd(5)) ;
v4_asm_lit(&as, v);
if (!rom("(LIT)", 1, v, 0)) return 0;
} else if (what < 80) { /* a branch back, or anywhere */
unsigned op = branch_ops[rnd(5)];
v4_cell t = nlab && rnd(3) ? labels[rnd(nlab)] : (v4_cell)rnd((unsigned)ROM_ADDR_TOP + 1u);
if (rnd(40) == 0) t = 200000; /* out of reach of any slot but 0 */
place_branch();
v4_asm_branch(&as, op, t);
if (!rom("(BRANCH)", 2, (v4_cell)op, t)) return 0;
} else if (what < 90 && nref < 64) { /* a branch forward */
unsigned op = branch_ops[rnd(5)];
place_branch();
aref[nref] = v4_asm_branch_fwd(&as, op);
if (!rom("(BRANCH>)", 1, (v4_cell)op, 0)) return 0;
bref[nref] = v4_dstack_pop(&b.ds);
nref++;
} else if (nlab < 64) { /* a label, and some forward branches resolved to it */
v4_cell la = v4_asm_label(&as), lb;
if (!rom("(LABEL)", 0, 0, 0)) return 0;
lb = v4_dstack_pop(&b.ds);
if (la != lb) { printf(" label: asm %ld, ROM %ld\n", (long)la, (long)lb); return 0; }
labels[nlab++] = la;
for (k = 0; k < nref; ) {
if (rnd(2)) { k++; continue; }
v4_asm_resolve(&as, aref[k], la);
if (!rom("(RESOLVE)", 2, bref[k], la)) return 0;
aref[k] = aref[nref - 1]; bref[k] = bref[nref - 1]; nref--;
}
}
ea = as.error != 0;
eb = rom_failed();
if (ea != eb) { printf(" step %u: asm error %d, ROM error %d (code %ld)\n", i, ea, eb, (long)b.mem[ROM_FAILED]); return 0; }
if (ea) return 1; /* both refused: the sequence ends there */
if (b.ds.depth != 0) { printf(" step %u: the ROM left %u on the stack\n", i, b.ds.depth); return 0; }
}
ha = v4_asm_label(&as);
if (!rom("(LABEL)", 0, 0, 0)) return 0;
hb = v4_dstack_pop(&b.ds);
if (as.error || rom_failed()) return (as.error != 0) == rom_failed();
if (ha != hb) { printf(" end: asm here %ld, ROM %ld\n", (long)ha, (long)hb); return 0; }
for (w = origin; w < ha; w++)
if (a.mem[w] != b.mem[w]) { printf(" word %ld: asm %lx, ROM %lx\n", (long)w, (unsigned long)a.mem[w], (unsigned long)b.mem[w]); return 0; }
return 1;
}
int main(void)
{
unsigned t, good = 0;
for (t = 0; t < 400; t++) if (trial(16, 120 + rnd(200))) good++; else { CHECK(0, "trial %u disagrees", t); break; }
CHECK(good == 400, "400 random sequences agree word for word: %u", good);
/* memory runs out: a sequence that starts near the top of RAM */
CHECK(trial((v4_cell)V4_NODE_WORDS - 20, 200), "memory full is an error on both sides");
/* a forward branch that cannot reach (Review Focus 2) */
{
v4_asm_ref ar; v4_cell br;
v4_node_reset(&a); v4_asm_begin(&as, &a, 16);
rom_boot(&b); b.mem[ROM_HERE] = 16;
v4_asm_op(&as, V4_OP_DUP); v4_asm_op(&as, V4_OP_DUP);
rom("(OP)", 1, V4_OP_DUP, 0); rom("(OP)", 1, V4_OP_DUP, 0);
place_branch();
ar = v4_asm_branch_fwd(&as, V4_OP_JUMP);
rom("(BRANCH>)", 1, V4_OP_JUMP, 0); br = v4_dstack_pop(&b.ds);
v4_asm_resolve(&as, ar, 400000);
rom("(RESOLVE)", 2, br, 400000);
CHECK(as.error && b.mem[ROM_FAILED] == ROM_ERR_FIT, "out of reach: refused by both, ROM code %ld", (long)b.mem[ROM_FAILED]);
}
/* a ref to a word with no branch in it is refused, not looped on */
rom_boot(&b); b.mem[ROM_HERE] = 16;
rom("(OP)", 1, V4_OP_DUP, 0);
rom("(LABEL)", 0, 0, 0); (void)v4_dstack_pop(&b.ds);
CHECK(rom("(RESOLVE)", 2, 16, 40) && b.mem[ROM_FAILED] == ROM_ERR_FIT, "no branch at the ref: does not fit, code %ld", (long)b.mem[ROM_FAILED]);
/* opcodes the packer does not take as ordinary ones */
rom_boot(&b); b.mem[ROM_HERE] = 16;
rom("(OP)", 1, V4_OP_FETCH_P, 0);
CHECK(b.mem[ROM_FAILED] == ROM_ERR_OPCODE, "@p is written as a literal, not an opcode");
rom_boot(&b); b.mem[ROM_HERE] = 16;
rom("(OP)", 1, V4_OP_CALL, 0);
CHECK(b.mem[ROM_FAILED] == ROM_ERR_OPCODE, "a branch is not an ordinary opcode");
rom_boot(&b); b.mem[ROM_HERE] = 16;
rom("(OP)", 1, 32, 0);
CHECK(b.mem[ROM_FAILED] == ROM_ERR_OPCODE, "32 is not an opcode");
printf("test_host_rom_pack: %d checks, %d failures\n", checks, failures);
return failures != 0;
}
+12
View File
@@ -49,6 +49,18 @@ int main(void)
constant("ERR-BAD-NAME", (long)ROM_ERR_BAD_NAME);
constant("ERR-DEFINED", (long)ROM_ERR_DEFINED);
constant("ERR-NAMES", (long)ROM_ERR_NAMES);
constant("(HERE)", (long)ROM_HERE);
constant("(CUR)", (long)ROM_CUR);
constant("(SLOT)", (long)ROM_SLOT);
constant("(NLIT)", (long)ROM_NLIT);
constant("(LITS)", (long)ROM_LITS);
constant("(PI)", (long)ROM_PI);
constant("(RS)", (long)ROM_RS);
constant("(RN)", (long)ROM_RN);
constant("RAM-WORDS", (long)V4_NODE_WORDS);
constant("ADDR-TOP", (long)ROM_ADDR_TOP);
constant("ERR-FIT", (long)ROM_ERR_FIT);
constant("ERR-OPCODE", (long)ROM_ERR_OPCODE);
/* the cell width's constants (NUMBER) needs, written as signed cells */
constant("MSB", (long)(v4_cell)V4_MSB);
constant("DEC-LIMIT", (long)(v4_cell)(~(v4_ucell)0 / 10u));