- Text after start is refused, as text.c refuses it: before it jumps, the ROM reads on to the end of the source; blanks, comments and the ends of blobs may follow, a token is ERR-AFTER (26), "text after start". - A token too long where a name, a target or a value is wanted is "name too long", as text.c says: (MISSING) reports "missing" only when no error came first. `", keeps "missing", which is text.c's word for both. - text.c's "address out of range" names the address, not the value. - The system's source files are one list, v4/sys_files.h: boot.c, the harness and the nucleus test are made from it. - The ROM's error codes and words are one list, v4/rom_errors.h: boot.c's messages, host_map.h's ROM_ERR_* and mkmap's ERR-* are made from it. - node.h says what v4_node_store does with a ROM address (nothing, as for any address outside RAM, D-14); test_host_rom_range pins it. ROM: 2095 words, rom_hash 0x06d0dcb7a0cb41a1. 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>
170 lines
6.5 KiB
C
170 lines
6.5 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 "v4/sys_files.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, v4/sys_files.h 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. */
|
|
#define ROM_SYS_FILE(file) #file,
|
|
static const char *const rom_system_files[] = { V4_SYS_FILES(ROM_SYS_FILE) };
|
|
#undef ROM_SYS_FILE
|
|
#define ROM_SYS_FILES ((unsigned)(sizeof rom_system_files / sizeof rom_system_files[0]))
|
|
|
|
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 < ROM_SYS_FILES + 2u; 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 <= ROM_SYS_FILES) 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 */
|