Files
LithosAnanake/v4/tests/test_host_rom_asm.c
T
rajamesandClaude Opus 5.5 ff75e69e91 fix(v4): the final review's four defects in the ROM boot
- The boot left the source's last new line in the console input, and the
  first KEY after the boot read it.  What the ROM has not read when it
  starts the system is taken back (boot.c, rom_harness.h).  hosted-check:
  KEY after the boot reads the key typed.
- A comment or macro left open at the end of a blob ran on into the next,
  and the ROM accepted source text.c refuses.  The loader ends each blob
  with FS (V4_ROM_BLOB_END) after its new line; the ROM ends a blob there
  as text.c ends a file: an open comment or macro is an error, and the word
  being assembled ends.  test_host_rom_asm: seven blob pairs against text.c.
- store to an address outside RAM faulted the ROM with no message.  It is
  now refused, as text.c refuses it: ERR-RANGE, "address out of range".
- The kernel build did not regenerate capsules/v4/sys/map-<w>.f18 before
  packing the capsule directory; it now does.

ROM: 2084 words, rom_hash 0x4c458c6e48b35456.  dict_hash unchanged,
0xe5a57f3eb72ff94a.  make -C v4 test test-32 hosted-check pass.  QEMU:
amd64, aarch64, riscv64 to ok>, POST 538/538, POST: PASSED.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 16:35:07 -04:00

252 lines
12 KiB
C

/* test_host_rom_asm.c -- the ROM assembles text exactly as v4/src/text.c does.
*
* Plan Tasks 6 and 7. The ROM is started at its entry (12) and fed a text
* through the console, ending with `start`; text.c assembles the same text.
* Where text.c accepts it, the ROM must lay down the same words and jump to
* the word start names; where text.c refuses it, the ROM must stop at
* (ROM-FAILED) with the same class of error, on the same line. The texts
* are test_text.c's own (rom_asm_cases.h), test_text.c's four real
* definitions, and the ROM's own directives: set, here, store, start.
*/
#include "rom_harness.h"
#include "v4/text.h"
#include "rom_asm_cases.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)
#define VARS ((v4_cell)(V4_NODE_WORDS - 16u)) /* test_text.c's VAR */
static v4_node a, b;
static v4_text tx;
static char in[1u << 16];
/* the ROM error code for one of text.c's messages */
static v4_cell code_of(const char *m)
{
static const struct { const char *s; v4_cell c; } map[] = {
{ "label already defined", ROM_ERR_LABEL_DUP }, { "& needs", ROM_ERR_AMP },
{ "macros nested too deep", ROM_ERR_DEEP }, { "macro without", ROM_ERR_MACRO },
{ "endmacro without", ROM_ERR_MACRO }, { "nested too deep", ROM_ERR_FOR },
{ "NEXT without", ROM_ERR_FOR }, { "FOR without", ROM_ERR_FOR },
{ "already defined", ROM_ERR_DEFINED }, { "cannot be used as a name", ROM_ERR_BAD_NAME },
{ "does not fit a cell", ROM_ERR_BIG }, { "does not fit", ROM_ERR_FIT },
{ "branch cannot reach", ROM_ERR_FIT }, { "name too long", ROM_ERR_TOO_LONG },
{ "comment not closed", ROM_ERR_COMMENT }, { "not allowed in a macro", ROM_ERR_IN_MACRO },
{ "too many labels", ROM_ERR_LABELS }, { "must fit one instruction word", ROM_ERR_UNEXT },
{ "not something to branch to", ROM_ERR_TARGET }, { "never defined", ROM_ERR_UNDEFINED },
{ "write the number", ROM_ERR_FETCH_P }, { "too many unresolved", ROM_ERR_REFS },
{ "not a value", ROM_ERR_VALUE }, { "not a number", ROM_ERR_VALUE },
{ "too many names", ROM_ERR_NAMES }, { "out of name space", ROM_ERR_NAMES },
{ "address out of range", ROM_ERR_RANGE }, { "without", ROM_ERR_MISSING },
};
unsigned i;
for (i = 0; i < sizeof map / sizeof map[0]; i++) if (strstr(m, map[i].s)) return map[i].c;
return -1;
}
static unsigned line_of(const char *m)
{
unsigned l = 0;
return sscanf(m, "line %u:", &l) == 1 ? l : 0;
}
/* Run the ROM on `text` (EOT is added). 1: it started -- P left the ROM for
* RAM; 0: it stopped at (ROM-FAILED); -1: neither within the steps allowed. */
static int rom_run(const char *text)
{
static long failed_lo = -1, failed_hi = -1;
size_t len = strlen(text), fed = 0;
long steps = 0;
if (failed_lo < 0) { /* where (ROM-FAILED) is: looked up once */
FILE *f = fopen(V4_ROM_SYM, "r");
char sym[80]; long ad, lo = -1, hi = -1;
while (f && fscanf(f, "%79s %ld", sym, &ad) == 2) if (strcmp(sym, "(ROM-FAILED)") == 0) lo = ad;
if (f) rewind(f);
while (f && fscanf(f, "%79s %ld", sym, &ad) == 2) if (ad > lo && (hi < 0 || ad < hi)) hi = ad;
if (f) fclose(f);
failed_lo = lo; failed_hi = hi < 0 ? (long)V4_ROM_BASE + (long)ROM_IMAGE_WORDS : hi;
}
rom_boot(&b);
b.p = V4_ROM_BASE + 12;
while (steps++ < 50000000L) {
if (fed <= len) fed += v4_node_console_feed(&b, text + fed, (unsigned)(len + 1 - fed));
if (b.p < V4_ROM_BASE) return 1;
if ((long)b.p >= failed_lo && (long)b.p < failed_hi) return 0;
(void)v4_exec_step_word(&b, &rom_es, &rom_h);
if (b.stopped) return -1;
}
return -1;
}
/* text.c and the ROM on the same text, with a word to start at after it */
static void same(const char *text)
{
static char ta[1u << 15];
int okA, rb;
v4_cell w, hereA, endA;
snprintf(ta, sizeof ta, "constant VAR %ld\nconstant N-1 %d\n%s\n: ZZEND ;\n", (long)VARS, V4_CELL_BITS - 1, text);
v4_node_reset(&a);
v4_text_begin(&tx, &a, ROM_ORIGIN);
okA = v4_text_assemble(&tx, ta) && v4_text_finish(&tx);
snprintf(in, sizeof in, "%s", ta); /* start on ZZEND's line: the ROM sees as many lines as text.c */
in[strlen(in) - 1] = 0;
strcat(in, " start ZZEND\n\x04");
rb = rom_run(in);
if (okA) {
hereA = v4_text_here(&tx);
endA = v4_text_word(&tx, "ZZEND");
CHECK(rb == 1, "[%s] text.c accepts; the ROM %s (code %ld line %ld)", text,
rb == 0 ? "refused" : "did not finish", (long)b.mem[ROM_FAILED], (long)b.mem[ROM_ERRLINE] + 1);
if (rb != 1) return;
CHECK(b.p == endA, "[%s] the ROM started at ZZEND: P %ld, text.c %ld", text, (long)b.p, (long)endA);
CHECK(b.mem[ROM_HERE] == hereA, "[%s] here: ROM %ld, text.c %ld", text, (long)b.mem[ROM_HERE], (long)hereA);
for (w = ROM_ORIGIN; w < hereA; w++) if (a.mem[w] != b.mem[w]) break;
CHECK(w == hereA, "[%s] word %ld differs: text.c %lx, ROM %lx", text, (long)w,
(unsigned long)a.mem[w], (unsigned long)b.mem[w]);
} else {
const char *m = v4_text_error(&tx);
v4_cell want = code_of(m);
unsigned l = line_of(m);
CHECK(rb == 0, "[%s] text.c refuses (%s); the ROM %s", text, m, rb == 1 ? "accepted" : "did not finish");
if (rb != 0) return;
CHECK(b.mem[ROM_FAILED] == want, "[%s] text.c: %s; ROM code %ld, want %ld", text, m, (long)b.mem[ROM_FAILED], (long)want);
if (l > 2) CHECK((unsigned)b.mem[ROM_ERRLINE] + 1u == l, "[%s] line: text.c %u, ROM %ld", text, l, (long)b.mem[ROM_ERRLINE] + 1);
}
}
/* text.c on each blob as a file of its own, as the host tests and text.c
* read the system; the ROM on the same blobs as the boot feeds them, each
* followed by a new line and V4_ROM_BLOB_END (Review Focus 1: a blob that
* ends mid-comment, mid-macro or mid-word). */
static void same_blobs(const char *const *blobs, unsigned count)
{
unsigned i;
int okA = 1, rb;
v4_cell w, hereA;
v4_node_reset(&a);
v4_text_begin(&tx, &a, ROM_ORIGIN);
for (i = 0; i < count && okA; i++) okA = v4_text_assemble(&tx, blobs[i]);
okA = okA && v4_text_assemble(&tx, ": ZZEND ;") && v4_text_finish(&tx);
in[0] = 0;
for (i = 0; i < count; i++) {
size_t l = strlen(in);
snprintf(in + l, sizeof in - l, "%s\n%c", blobs[i], (char)V4_ROM_BLOB_END);
}
strcat(in, ": ZZEND ;\nstart ZZEND\n\x04");
rb = rom_run(in);
if (okA) {
hereA = v4_text_here(&tx);
CHECK(rb == 1, "[%s | %s] text.c accepts; the ROM %s (code %ld)", blobs[0], blobs[count - 1],
rb == 0 ? "refused" : "did not finish", (long)b.mem[ROM_FAILED]);
if (rb != 1) return;
CHECK(b.p == v4_text_word(&tx, "ZZEND"), "[%s | %s] the ROM started at ZZEND", blobs[0], blobs[count - 1]);
for (w = ROM_ORIGIN; w < hereA; w++) if (a.mem[w] != b.mem[w]) break;
CHECK(w == hereA, "[%s | %s] word %ld differs", blobs[0], blobs[count - 1], (long)w);
} else {
const char *m = v4_text_error(&tx);
CHECK(rb == 0, "[%s | %s] text.c refuses (%s); the ROM %s", blobs[0], blobs[count - 1], m,
rb == 1 ? "accepted" : "did not finish");
if (rb != 0) return;
CHECK(b.mem[ROM_FAILED] == code_of(m), "[%s | %s] text.c: %s; ROM code %ld, want %ld", blobs[0], blobs[count - 1], m,
(long)b.mem[ROM_FAILED], (long)code_of(m));
}
}
int main(void)
{
unsigned i;
for (i = 0; i < sizeof rom_asm_cases / sizeof rom_asm_cases[0]; i++) same(rom_asm_cases[i]);
/* test_text.c's four real definitions, and more of the notation together */
same("macro SWAP over push push drop pop pop endmacro\n"
": SWAP' ( a b -- b a ) SWAP ;\n"
": UM/MOD ( ulo uhi ud -- urem uquot )\n"
" a! N-1 FOR\n"
" -if L0\n"
" 2* over -if L1 drop 1 + jump L2\n"
" L1: drop L2: push 2* pop\n"
" jump SUB\n"
" L0:\n"
" 2* over -if L3 drop 1 + jump L4\n"
" L3: drop L4: push 2* pop\n"
" dup a xor -if L5\n"
" drop -if NOSUB jump SUB\n"
" L5: drop dup inv a + inv -if L6\n"
" drop jump NOSUB\n"
" L6: push drop pop jump SETBIT\n"
" SUB: inv a + inv\n"
" SETBIT: push 1 + pop\n"
" NOSUB:\n"
" NEXT\n"
" SWAP ;\n"
": C@ ( baddr -- c )\n"
" dup 2/ 2/ a! 3 and\n"
" if K0 -1 + if K1 -1 + if K2\n"
" drop @ 23 FOR 2/ UNEXT 255 and ;\n"
" K2: drop @ 15 FOR 2/ UNEXT 255 and ;\n"
" K1: drop @ 7 FOR 2/ UNEXT 255 and ;\n"
" K0: drop @ 255 and ;\n"
": C! ( c baddr -- )\n"
" dup 2/ 2/ a! 3 and push 255 and pop\n"
" if K0 -1 + if K1 -1 + if K2\n"
" drop 23 FOR 2* UNEXT @ 4278190080 inv and + ! ;\n"
" K2: drop 15 FOR 2* UNEXT @ -16711681 and + ! ;\n"
" K1: drop 7 FOR 2* UNEXT @ -65281 and + ! ;\n"
" K0: drop @ -256 and + ! ;\n"
": ROUNDTRIP ( c baddr -- c' ) dup push C! pop jump C@\n");
same(": A B C ; : B jump D ; : C if E D E: ; : D ; macro TWO dup dup endmacro macro FOUR TWO TWO endmacro\n"
": F FOUR &A VAR+3 \\ a comment\n ( and another ) ;\n#, 5 \", ( #, &F \", abcdefgh");
/* the ROM's own directives */
CHECK(rom_run("set S 5 set S 9 : T S ;\nstore 7000 S store 7001 here\n#, here\n: Z ;\nstart Z\n\x04") == 1,
"set, here and store assemble: code %ld", (long)b.mem[ROM_FAILED]);
CHECK(b.mem[7000] == 9 && b.mem[7001] == 18 && b.mem[18] == 18, "set takes its last value, here is the next word: %ld %ld %ld",
(long)b.mem[7000], (long)b.mem[7001], (long)b.mem[18]);
CHECK(rom_run(": T ;\n\x04") == 0 && b.mem[ROM_FAILED] == ROM_ERR_NO_START, "no start: refused");
CHECK(rom_run(": T ;\nconstant K 1\nstart K\n\x04") == 0 && b.mem[ROM_FAILED] == ROM_ERR_START, "start of a constant: refused");
CHECK(rom_run(": T ;\nstart\n\x04") == 0 && b.mem[ROM_FAILED] == ROM_ERR_MISSING, "start alone: refused");
CHECK(rom_run(": T here ;\n: Z ;\nstart Z\n\x04") == 0 && b.mem[ROM_FAILED] == ROM_ERR_TARGET, "here alone is not code");
CHECK(rom_run("store 9000\n: Z ;\nstart Z\n\x04") == 0, "store needs two values");
/* a blob split over two feeds, and a forward call resolved in the second (Review Focus 1) */
CHECK(rom_run(": A B ;\n: B 7 ;\n: Z ;\nstart Z\n\x04") == 1 && (b.mem[ROM_ORIGIN] & 0xFFFFFFFF) != 0, "a forward call across words");
/* the tables are cleared at start (Review Focus 4) */
rom_run(": A B ;\n: B 7 ;\nmacro M dup endmacro\n: Z M ;\nstart Z\n\x04");
for (i = 0; i < (unsigned)(DICT_END_W - DICT_W); i++) if (b.mem[DICT_W + (v4_cell)i] != 0) break;
CHECK(i == (unsigned)(DICT_END_W - DICT_W), "the symbol table is cleared: cell %u is not", i);
for (i = 0; i < 1024u; i++) if (b.mem[BUF0_W + (v4_cell)i] != 0) break;
CHECK(i == 1024u, "the block window is cleared: cell %u is not", i);
for (i = 0; i < 260u; i++) if (b.mem[TIB_W + (v4_cell)i] != 0) break;
CHECK(i == 260u, "the text input buffer is cleared: cell %u is not", i);
/* blobs: each ends as a file ends for text.c (Review Focus 1) */
{
static const char *const comment[] = { ": A ;\n( a comment left open", ": B 1 ; )" };
static const char *const macro[] = { "macro M 1", "endmacro : A M ;" };
static const char *const nfor[] = { ": A 3 FOR", "NEXT ;" };
static const char *const label[] = { ": A L1: 1", "jump L1 ;" };
static const char *const bsl[] = { ": A ;\n\\ a comment with no new line", ": B A ;" };
static const char *const word[] = { ": A 1", "2 ;" };
static const char *const closed[] = { "( closed ) : A ;", ": B A ;" };
same_blobs(comment, 2); same_blobs(macro, 2); same_blobs(nfor, 2); same_blobs(label, 2);
same_blobs(bsl, 2); same_blobs(word, 2); same_blobs(closed, 2);
}
/* store outside RAM: refused with an error, as text.c refuses it, not a fault */
{
static char t[80];
same("store -1 5");
snprintf(t, sizeof t, "store %ld 5", (long)V4_NODE_WORDS); same(t);
snprintf(t, sizeof t, "store %ld 5", (long)V4_NODE_WORDS - 1); same(t);
}
printf("test_host_rom_asm: %d checks, %d failures\n", checks, failures);
return failures != 0;
}