/* test_host_rom_math.c -- the ROMs' shared math (docs/v4.0.0/MACHINE.md 2.2). * * Both ROMs begin with the same six entries (v4/include/v4/rom_entries.h): * UM*, UM/MOD, D+, DNEGATE, Q.* and Q./. Each is called through its entry, * on the host ROM and on the mesh ROM, with the values below, and must give * what the system's words of those names gave before they moved into the * ROM. The tables are those words' results as text.c assembled the system * on 2026-10-15, before the move, at each width. */ #include "v4/rom_entries.h" #include "v4/iword.h" #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) #define M ((v4_cell)V4_MSB) /* the most negative cell */ #define X ((v4_cell)~(v4_ucell)V4_MSB) /* the largest */ #define ONES (~(v4_ucell)0) #define CANARY ((v4_cell)0x0C0FFEE5) /* entry, arguments (deepest first), results (deepest first), NODE-ERROR after */ typedef struct { unsigned entry, argc; v4_cell in[4]; unsigned outc; v4_cell out[2]; v4_cell error; } math_case; static const math_case cases[] = { { 0, 2, {0, 0}, 2, {0, 0}, 0 }, { 0, 2, {1, -1}, 2, {-1, 0}, 0 }, { 0, 2, {-1, -1}, 2, {1, -2}, 0 }, { 0, 2, {M, 2}, 2, {0, 1}, 0 }, { 0, 2, {12345, 67890}, 2, {838102050, 0}, 0 }, { 0, 2, {-1, M}, 2, {M, X}, 0 }, { 0, 2, {X, X}, 2, {1, X / 2}, 0 }, { 1, 3, {0, 0, 1}, 2, {0, 0}, 0 }, { 1, 3, {100, 0, 7}, 2, {2, 14}, 0 }, { 1, 3, {-1, -2, -1}, 2, {-2, -1}, 0 }, { 1, 3, {0, 1, 3}, 2, {1, (v4_cell)(ONES / 3u)}, 0 }, { 1, 3, {-1, 0, 10}, 2, {5, (v4_cell)(ONES / 10u)}, 0 }, #if V4_CELL_BITS == 64 { 1, 3, {12345, 6, 7}, 2, {2, -2635249153387077039LL}, 0 }, #else { 1, 3, {12345, 6, 7}, 2, {0, -613564993L}, 0 }, #endif { 1, 3, {M, X - 1, X}, 2, {X - 1, -2}, 0 }, { 1, 3, {-1, X, M}, 2, {X, -1}, 0 }, { 2, 4, {-1, 0, 1, 0}, 2, {0, 1}, 0 }, { 2, 4, {-1, -1, 1, 0}, 2, {0, 0}, 0 }, { 2, 4, {M, 0, M, 0}, 2, {0, 1}, 0 }, { 2, 4, {5, -1, -6, 0}, 2, {-1, -1}, 0 }, { 2, 4, {0, X, 0, 1}, 2, {0, M}, 0 }, { 2, 4, {123, 4, -200, -5}, 2, {-77, -1}, 0 }, { 3, 2, {0, 0}, 2, {0, 0}, 0 }, { 3, 2, {1, 0}, 2, {-1, -1}, 0 }, { 3, 2, {0, 1}, 2, {0, -1}, 0 }, { 3, 2, {-1, -1}, 2, {1, 0}, 0 }, { 3, 2, {0, M}, 2, {0, M}, 0 }, { 3, 2, {5, 0}, 2, {-5, -1}, 0 }, { 4, 4, {65536, 0, 65536, 0}, 2, {65536, 0}, 0 }, { 4, 4, {163840, 0, -196608, -1}, 2, {-491520, -1}, 0 }, { 4, 4, {-65536, -1, -65536, -1}, 2, {65536, 0}, 0 }, { 4, 4, {32768, 0, 32768, 0}, 2, {16384, 0}, 0 }, { 4, 4, {0, 0, 65536, 0}, 2, {0, 0}, 0 }, { 4, 4, {0, 1, 0, 1}, 2, {0, (v4_cell)1 << (V4_CELL_BITS - 16)}, 0 }, { 4, 4, {-1, X, 131072, 0}, 2, {-2, -1}, 0 }, { 4, 4, {1, 0, 1, 0}, 2, {0, 0}, 0 }, { 5, 4, {65536, 0, 196608, 0}, 2, {21845, 0}, 0 }, { 5, 4, {-491520, -1, 131072, 0}, 2, {-245760, -1}, 0 }, { 5, 4, {65536, 0, 0, 0}, 2, {-1, X}, 11 }, { 5, 4, {-65536, -1, 0, 0}, 2, {0, M}, 11 }, { 5, 4, {0, 0, 0, 0}, 2, {0, 0}, 11 }, { 5, 4, {0, 1, 1, 0}, 2, {0, 65536}, 0 }, { 5, 4, {-1, X, 32768, 0}, 2, {-1, X}, 0 }, { 5, 4, {M, -1, -65536, -1}, 2, {M, 0}, 0 }, { 5, 4, {1, 0, 65536, 0}, 2, {1, 0}, 0 }, }; #define CASES (sizeof cases / sizeof cases[0]) static v4_node n; /* Call entry c->entry of `rom`. 1 if it returned with the canary under its * results; got[] the results, deepest first, *err NODE-ERROR. */ static int run(const v4_cell *rom, unsigned words, const math_case *c, v4_cell *got, v4_cell *err) { unsigned i; rom_boot(&n); v4_node_rom_attach(&n, rom, words); v4_dstack_push(&n.ds, CANARY); for (i = 0; i < c->argc; i++) v4_dstack_push(&n.ds, c->in[i]); if (v4_test_call(&n, &rom_es, &rom_h, V4_ROM_BASE + (v4_cell)c->entry, 10000000L) <= 0) return 0; for (i = c->outc; i-- > 0; ) got[i] = v4_dstack_pop(&n.ds); *err = n.mem[NODE_ERROR]; return v4_dstack_pop(&n.ds) == CANARY; } static void cases_on(const char *which, const v4_cell *rom, unsigned words) { unsigned k; for (k = 0; k < CASES; k++) { const math_case *c = &cases[k]; v4_cell got[2] = { 0, 0 }, err = -99; int ok = run(rom, words, c, got, &err); CHECK(ok && got[0] == c->out[0] && got[1] == c->out[1] && err == c->error, "%s ROM, entry %u, case %u: got %lld %lld error %lld, want %lld %lld error %lld", which, c->entry, k, (long long)got[0], (long long)got[1], (long long)err, (long long)c->out[0], (long long)c->out[1], (long long)c->error); } } /* The address of `name` in a ROM's symbol file, or -1. */ static long sym_addr(const char *path, const char *name) { FILE *f = fopen(path, "r"); char sym[80]; long addr, found = -1; if (!f) { printf(" cannot read %s\n", path); return -1; } while (fscanf(f, "%79s %ld", sym, &addr) == 2) if (strcmp(sym, name) == 0) found = addr; fclose(f); return found; } /* Entry i of `rom` is a jump, in slot 0, to the routine `routine`. */ static int entry_jumps_to(const v4_cell *rom, unsigned i, const char *sym, const char *routine) { v4_iword w = (v4_iword)((v4_ucell)rom[i] & 0xFFFFFFFFu); long target = sym_addr(sym, routine); return target >= 0 && v4_iword_op(w, 0) == V4_OP_JUMP && v4_iword_branch(V4_ROM_BASE + (v4_cell)i, w, 0) == (v4_cell)target; } /* THE SYSTEM CALLS THE ROM. The system as text.c builds it (host_load, the * ROM attached): each of the six words is a literal of its entry's address, * then push, then ; -- it goes to the entry and the entry's ; returns to the * word's caller -- and it gives the table's results. On a node with no ROM * the call faults rather than computing anything. */ static v4_text tx; static int system_cases(void) { static const char *const files[] = { #define SYS_FILE_NAME(file) #file ".f18", V4_SYS_FILES(SYS_FILE_NAME) #undef SYS_FILE_NAME }; unsigned k; if (!host_load(&tx, &n, files, (unsigned)(sizeof files / sizeof files[0])) || !v4_text_finish(&tx)) { printf(" the system does not assemble: %s\n", v4_text_error(&tx)); return 0; } #define CHECK_CALLS(i, routine, forth, effect) { \ v4_cell w = v4_text_word(&tx, forth); \ v4_iword iw = w >= 0 ? (v4_iword)((v4_ucell)n.mem[w] & 0xFFFFFFFFu) : 0; \ CHECK(w >= 0 && v4_iword_op(iw, 0) == V4_OP_FETCH_P && v4_iword_op(iw, 1) == V4_OP_PUSH && v4_iword_op(iw, 2) == V4_OP_SEMI \ && n.mem[w + 1] == V4_ROM_BASE + (v4_cell)(i), "the system's %s is the ROM's entry %d, then push and ;", forth, i); } V4_ROM_MATH_ENTRIES(CHECK_CALLS) #undef CHECK_CALLS for (k = 0; k < CASES; k++) { static const char *const forth_of[] = { #define FORTH_NAME(i, routine, forth, effect) forth, V4_ROM_MATH_ENTRIES(FORTH_NAME) #undef FORTH_NAME }; const math_case *c = &cases[k]; v4_cell got[2] = { 0, 0 }; unsigned i; int ok; v4_dstack_reset(&n.ds); v4_rstack_reset(&n.rs); v4_exec_reset(&rom_es); v4_heat_reset(&rom_h); n.mem[NODE_ERROR] = 0; v4_dstack_push(&n.ds, CANARY); for (i = 0; i < c->argc; i++) v4_dstack_push(&n.ds, c->in[i]); ok = v4_test_call(&n, &rom_es, &rom_h, v4_text_word(&tx, forth_of[c->entry]), 10000000L) > 0; for (i = c->outc; i-- > 0; ) got[i] = v4_dstack_pop(&n.ds); CHECK(ok && v4_dstack_pop(&n.ds) == CANARY && got[0] == c->out[0] && got[1] == c->out[1] && n.mem[NODE_ERROR] == c->error, "the system's %s, case %u: got %lld %lld error %lld", forth_of[c->entry], k, (long long)got[0], (long long)got[1], (long long)n.mem[NODE_ERROR]); } /* the same system with its ROM taken away: UM* does not return */ v4_node_rom_attach(&n, ROM_IMAGE, 0u); v4_dstack_reset(&n.ds); v4_rstack_reset(&n.rs); v4_exec_reset(&rom_es); v4_heat_reset(&rom_h); v4_dstack_push(&n.ds, 12345); v4_dstack_push(&n.ds, 67890); { unsigned faults = n.faults; long r = v4_test_call(&n, &rom_es, &rom_h, v4_text_word(&tx, "UM*"), 100000L); CHECK(r <= 0 && n.faults > faults && n.fault_kind == V4_FAULT_ADDRESS && n.fault_addr == V4_ROM_BASE, "with no ROM, the system's UM* faults at the ROM's address and computes nothing: returned %ld, %u faults, kind %u at %lld", r, n.faults - faults, n.fault_kind, (long long)n.fault_addr); } return 1; } int main(void) { printf("v4 ROM math tests: V4_CELL_BITS=%d\n", V4_CELL_BITS); /* ---- one entry list, in both ROMs ---- */ { #define CHECK_ENTRY(i, routine, forth, effect) \ CHECK(entry_jumps_to(ROM_IMAGE, i, V4_ROM_SYM, routine), "host ROM entry %d jumps to %s", i, routine); \ CHECK(entry_jumps_to(MESH_ROM, i, V4_MESH_ROM_SYM, routine), "mesh ROM entry %d jumps to %s", i, routine); V4_ROM_MATH_ENTRIES(CHECK_ENTRY) #undef CHECK_ENTRY } CHECK(entry_jumps_to(ROM_IMAGE, V4_ROM_START, V4_ROM_SYM, "(ROM)"), "the host ROM starts at entry %u, (ROM)", (unsigned)V4_ROM_START); CHECK(entry_jumps_to(ROM_IMAGE, V4_ROM_MATH_COUNT, V4_ROM_SYM, "(KEY)"), "the assembler's entries follow the math's"); /* ---- the mesh ROM is small and holds nothing of the assembler ---- */ CHECK(MESH_ROM_WORDS < 300u, "the mesh ROM is small: %u words", MESH_ROM_WORDS); CHECK(sym_addr(V4_MESH_ROM_SYM, "(TOKEN)") < 0 && sym_addr(V4_MESH_ROM_SYM, "(KEY)") < 0 && sym_addr(V4_MESH_ROM_SYM, "(ROM)") < 0, "the mesh ROM has none of the assembler's routines"); printf(" the host ROM: %u words; the mesh ROM: %u words\n", ROM_IMAGE_WORDS, MESH_ROM_WORDS); /* ---- each entry, on each ROM, gives what the system's words gave ---- */ cases_on("host", ROM_IMAGE, ROM_IMAGE_WORDS); cases_on("mesh", MESH_ROM, MESH_ROM_WORDS); CHECK(system_cases(), "the system the tests build with text.c"); printf(" %d checks, %d failures\n", checks, failures); return failures != 0; }