- 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>
167 lines
6.4 KiB
C
167 lines
6.4 KiB
C
/* rom_harness.h -- run the ROM's routines on a host node, for tests.
|
|
*
|
|
* The ROM (capsules/v4/rom, built by tools/mkrom.c into $(BINDIR)/rom-<w>.c)
|
|
* is attached to a host node as the machine's ROM. A routine is called
|
|
* through the ROM's entry table, as a payload calls it (MACHINE.md 2.1): the
|
|
* table's order is fixed, and is rom_entries[] below. Where a test needs to
|
|
* know whether P is inside a routine, the ROM's symbols are read from
|
|
* $(BINDIR)/rom-<w>.sym (V4_ROM_SYM).
|
|
*/
|
|
#ifndef V4_TESTS_ROM_HARNESS_H
|
|
#define V4_TESTS_ROM_HARNESS_H
|
|
|
|
#include "v4/testcode.h"
|
|
#include "host_map.h"
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#if V4_CELL_BITS == 64
|
|
extern const v4_cell v4_rom_64[];
|
|
extern const unsigned v4_rom_64_words;
|
|
#define ROM_IMAGE v4_rom_64
|
|
#define ROM_IMAGE_WORDS v4_rom_64_words
|
|
#else
|
|
extern const v4_cell v4_rom_32[];
|
|
extern const unsigned v4_rom_32_words;
|
|
#define ROM_IMAGE v4_rom_32
|
|
#define ROM_IMAGE_WORDS v4_rom_32_words
|
|
#endif
|
|
|
|
/* The entry table, in its fixed order: entry i is at V4_ROM_BASE + i. */
|
|
static const char *const rom_entries[] = {
|
|
"(KEY)", "(TOKEN)", "(NUMBER)", "(FIND)", "(DEFINE)", "(OPCODE)",
|
|
"(OP)", "(LIT)", "(BRANCH)", "(BRANCH>)", "(RESOLVE)", "(LABEL)", "(ROM)"
|
|
};
|
|
#define ROM_ENTRIES (sizeof rom_entries / sizeof rom_entries[0])
|
|
|
|
static v4_exec_state rom_es;
|
|
static v4_heat rom_h;
|
|
|
|
/* The address a payload calls for the routine `name`: its entry. */
|
|
static v4_cell rom_entry(const char *name)
|
|
{
|
|
unsigned i;
|
|
for (i = 0; i < ROM_ENTRIES; i++) if (strcmp(rom_entries[i], name) == 0) return V4_ROM_BASE + (v4_cell)i;
|
|
printf("rom_harness: %s is not in the entry table\n", name);
|
|
return -1;
|
|
}
|
|
|
|
/* Call the routine through its entry. 1 if it returned within `max_words`
|
|
* instruction words; 0 if not, and the node is left where it got to, to be
|
|
* fed and resumed. */
|
|
static int rom_call(v4_node *n, const char *name, long max_words)
|
|
{
|
|
return v4_test_call(n, &rom_es, &rom_h, rom_entry(name), max_words) >= 0;
|
|
}
|
|
|
|
/* Go on with a call that had not returned. 1 if it now has. */
|
|
static int rom_resume(v4_node *n, long max_words)
|
|
{
|
|
long words = 0;
|
|
while (n->p != V4_TEST_HALT) {
|
|
if (words++ >= max_words || n->stopped) return 0;
|
|
(void)v4_exec_step_word(n, &rom_es, &rom_h);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/* 1 if P is inside the ROM's word `name`: at or after it, and before the
|
|
* next word the ROM has. */
|
|
static int rom_in(const v4_node *n, const char *name)
|
|
{
|
|
FILE *f = fopen(V4_ROM_SYM, "r");
|
|
char sym[80];
|
|
long addr, start = -1, next = -1;
|
|
if (!f) { printf("rom_harness: cannot read %s\n", V4_ROM_SYM); return 0; }
|
|
while (fscanf(f, "%79s %ld", sym, &addr) == 2)
|
|
if (strcmp(sym, name) == 0) start = addr;
|
|
rewind(f);
|
|
while (fscanf(f, "%79s %ld", sym, &addr) == 2)
|
|
if (start >= 0 && addr > start && (next < 0 || addr < next)) next = addr;
|
|
fclose(f);
|
|
if (start < 0) return 0;
|
|
if (next < 0) next = (long)V4_ROM_BASE + (long)ROM_IMAGE_WORDS;
|
|
return (long)n->p >= start && (long)n->p < next;
|
|
}
|
|
|
|
static char *rom_system_text(size_t *len);
|
|
static int rom_build_system(v4_node *n, long *words);
|
|
|
|
/* Reset `n`, attach the ROM and the console's receive registers. */
|
|
static void rom_boot(v4_node *n)
|
|
{
|
|
(void)rom_build_system;
|
|
(void)host_load; /* host_map.h's loader is not used here */
|
|
(void)host_branch_slots;
|
|
(void)rom_call; /* not every test uses every helper */
|
|
(void)rom_resume;
|
|
(void)rom_in;
|
|
v4_node_reset(n);
|
|
v4_exec_reset(&rom_es);
|
|
v4_heat_reset(&rom_h);
|
|
v4_node_rom_attach(n, ROM_IMAGE, ROM_IMAGE_WORDS);
|
|
v4_node_console_input_attach(n, CONSOLE_RX, CONSOLE_ST);
|
|
}
|
|
|
|
/* THE SYSTEM, AS THE MACHINE BUILDS IT (MACHINE.md section 6, step 1). The
|
|
* system's source, capsules/v4/sys (V4_SYS_DIR): the map, the fourteen and
|
|
* the end, each followed by a new line and V4_ROM_BLOB_END, then EOT -- what v4/system/boot.c
|
|
* feeds the console. rom_system_text gives it whole, *len its length;
|
|
* rom_build_system has `n`, booted with its ROM, assemble it, and runs it to
|
|
* its start: 1 if it started, with *words the instruction words it took. */
|
|
static const char *const rom_system_files[] = { "core", "input", "dict", "codegen", "compile", "quit", "forth",
|
|
"numout", "words", "system", "qmath", "blocks", "log", "acl" };
|
|
|
|
static char *rom_system_text(size_t *len)
|
|
{
|
|
size_t total = 0, cap = 1u << 20;
|
|
char *text = malloc(cap);
|
|
unsigned i;
|
|
if (!text) return NULL;
|
|
for (i = 0; i < 16; i++) {
|
|
char path[512];
|
|
FILE *f;
|
|
long n;
|
|
if (i == 0) snprintf(path, sizeof path, "%s/map-%d.f18", V4_SYS_DIR, V4_CELL_BITS);
|
|
else if (i <= 14) snprintf(path, sizeof path, "%s/%s.f18", V4_SYS_DIR, rom_system_files[i - 1]);
|
|
else snprintf(path, sizeof path, "%s/end.f18", V4_SYS_DIR);
|
|
f = fopen(path, "rb");
|
|
if (!f) { printf(" cannot read %s\n", path); free(text); return NULL; }
|
|
fseek(f, 0, SEEK_END); n = ftell(f); fseek(f, 0, SEEK_SET);
|
|
if (n < 0 || total + (size_t)n + 3 > cap || fread(text + total, 1, (size_t)n, f) != (size_t)n) {
|
|
fclose(f); free(text); return NULL;
|
|
}
|
|
fclose(f);
|
|
total += (size_t)n;
|
|
text[total++] = '\n'; /* a blob that does not end with a new line still ends a token */
|
|
text[total++] = V4_ROM_BLOB_END; /* and the blob ends, as a file ends for text.c */
|
|
}
|
|
text[total++] = 4; /* EOT */
|
|
*len = total;
|
|
return text;
|
|
}
|
|
|
|
static int rom_build_system(v4_node *n, long *words)
|
|
{
|
|
size_t total, fed = 0;
|
|
char *text = rom_system_text(&total);
|
|
long w = 0;
|
|
if (!text) return 0;
|
|
rom_boot(n);
|
|
v4_node_stack_regs_attach(n, DSTACK_REG, RSTACK_REG);
|
|
n->p = V4_ROM_BASE + 12; /* entry 12, (ROM): where the machine starts */
|
|
while (n->p >= V4_ROM_BASE && w < 400000000L && !n->stopped) {
|
|
if (fed < total && n->input_pos == n->input_len) fed += v4_node_console_feed(n, text + fed, (unsigned)(total - fed));
|
|
(void)v4_exec_step_word(n, &rom_es, &rom_h);
|
|
w++;
|
|
if (w % 1000 == 0 && n->mem[ROM_FAILED] != 0) break;
|
|
}
|
|
free(text);
|
|
n->input_pos = n->input_len = 0; /* what was not read is the source's: boot.c takes it back */
|
|
if (words) *words = w;
|
|
return n->p < V4_ROM_BASE && n->mem[ROM_FAILED] == 0;
|
|
}
|
|
|
|
#endif /* V4_TESTS_ROM_HARNESS_H */
|