feat(v4.0.0): a 16384-word host node for the compiler tests

Node memory is a build parameter.  Tests named test_host_*.c are now
built with V4_NODE_WORDS=16384, the host node that will hold the
compiler, the dictionary and the text being compiled; every other test
keeps the 1024-word mesh node.

The first such test checks the node at that size and the text
assembler's branch placement, which only matters there: a branch slot
that cannot reach the whole node is not used.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-03 21:18:38 -04:00
co-authored by Claude Opus 5.5
parent 7b710aa322
commit 31fcdcd0a3
2 changed files with 134 additions and 2 deletions
+10 -2
View File
@@ -35,6 +35,14 @@ WIDTHS := 32 64
BINDIR := $(HERE)/build
# Node memory is a build parameter (V4_NODE_WORDS, node.h; 1024 words unless
# set). A mesh node is small, but the host node holds the compiler, the
# dictionary and the text being compiled (JUSTIFICATION.md section 9), which
# do not fit 1024 words. Tests named test_host_*.c are therefore built with a
# host node of HOST_WORDS words; every other test keeps the mesh-node size.
HOST_WORDS := 16384
node_size = $(if $(findstring /test_host_,$(1)),-DV4_NODE_WORDS=$(HOST_WORDS))
.PHONY: all test sanitize clean $(addprefix test-,$(WIDTHS))
all: test
@@ -63,13 +71,13 @@ endif
define TEST_RULE
$(BINDIR)/$(1)-$(notdir $(2)): $(2) $$(SRCS) $$(wildcard $(HERE)/include/v4/*.h) $(HERE)/Makefile
@mkdir -p $(BINDIR)
$$(CC) $$(CFLAGS) -I$(HERE)/include -DV4_CELL_BITS=$(1) \
$$(CC) $$(CFLAGS) -I$(HERE)/include -DV4_CELL_BITS=$(1) $(call node_size,$(2)) \
$(2) $$(SRCS) -o $$@
$(BINDIR)/san-$(1)-$(notdir $(2)): $(2) $$(SRCS) $$(wildcard $(HERE)/include/v4/*.h) $(HERE)/Makefile
@mkdir -p $(BINDIR)
$$(CC) $$(CSTD) $$(WARN) -O1 -g -fno-omit-frame-pointer \
-fsanitize=address,undefined -fno-sanitize-recover=all -I$(HERE)/include -DV4_CELL_BITS=$(1) \
-fsanitize=address,undefined -fno-sanitize-recover=all -I$(HERE)/include -DV4_CELL_BITS=$(1) $(call node_size,$(2)) \
$(2) $$(SRCS) -o $$@
endef
+124
View File
@@ -0,0 +1,124 @@
/* test_host_node.c -- the host node's size, and the text assembler on it.
*
* Tests named test_host_*.c are built with V4_NODE_WORDS = 16384 (Makefile):
* the host node, which holds the compiler, the dictionary and the text being
* compiled, and so cannot be a 1024-word mesh node.
*
* At 1024 words every slot a branch may sit in reaches the whole node. At
* 16384 slot 3 does not -- its address field is 12 bits -- so this is where
* the text assembler's placement rule (text.h) is exercised: a branch is put
* in a slot only if that slot reaches every word of the node.
*/
#include "v4/text.h"
#include "v4/testcode.h"
#include <stdio.h>
#include <string.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 CANARY ((v4_cell)0x0C0FFEE5)
#define ORIGIN 16
static v4_node n;
static v4_exec_state es;
static v4_heat h;
static v4_text tx;
static char src[8 * V4_NODE_WORDS + 1024];
static unsigned slot_op(v4_cell addr, unsigned slot)
{
return (unsigned)(((v4_ucell)n.mem[addr] >> V4_SLOT_LOW_BIT(slot)) & 0x1Fu);
}
static int assemble(const char *text)
{
v4_node_reset(&n);
v4_text_begin(&tx, &n, ORIGIN);
return v4_text_assemble(&tx, text) && v4_text_finish(&tx);
}
static v4_cell result(const char *name, v4_cell arg)
{
v4_cell r, w = v4_text_word(&tx, name);
v4_dstack_reset(&n.ds);
v4_rstack_reset(&n.rs);
v4_exec_reset(&es);
v4_heat_reset(&h);
v4_dstack_push(&n.ds, CANARY);
v4_dstack_push(&n.ds, arg);
if (w < 0 || v4_test_call(&n, &es, &h, w, 100000) <= 0) return (v4_cell)0x0BADBAD;
r = v4_dstack_pop(&n.ds);
return v4_dstack_pop(&n.ds) == CANARY ? r : (v4_cell)0x0BADBAD;
}
int main(void)
{
unsigned k, i;
printf("v4 host node tests: V4_CELL_BITS=%d, V4_NODE_WORDS=%u\n", V4_CELL_BITS, (unsigned)V4_NODE_WORDS);
CHECK(V4_NODE_WORDS == 16384u, "the host node is 16384 words");
/* the node itself at this size */
v4_node_reset(&n);
v4_node_store(&n, 0, 11);
v4_node_store(&n, (v4_cell)(V4_NODE_WORDS - 1u), 22);
CHECK(v4_node_load(&n, 0) == 11 && v4_node_load(&n, (v4_cell)(V4_NODE_WORDS - 1u)) == 22, "the first and last words");
CHECK(v4_node_guards_intact(&n), "guards intact around them");
CHECK(V4_TEST_HALT == (v4_cell)(V4_NODE_WORDS - 1u), "the test halt address is the last word");
/* which slots reach the whole node */
CHECK((v4_ucell)v4_iword_slot_mask(2) >= V4_NODE_WORDS - 1u, "a branch in slot 2 reaches the whole node");
CHECK((v4_ucell)v4_iword_slot_mask(3) < V4_NODE_WORDS - 1u, "a branch in slot 3 does not");
/* so a branch that would land in slot 3 starts a new word instead, and
* one in slots 0 .. 2 stays where it is */
for (k = 0; k <= 3; k++) {
char pad[32] = "";
for (i = 0; i < k; i++) strcat(pad, "dup ");
snprintf(src, sizeof src, ": T ( n -- x ) %sif Z ; Z: ;", pad);
CHECK(assemble(src), "a branch after %u opcodes assembles: %s", k, v4_text_error(&tx));
if (k < 3) {
CHECK(slot_op(ORIGIN, k) == V4_OP_IF, "after %u opcodes the branch is in slot %u of the same word", k, k);
} else {
CHECK(slot_op(ORIGIN, 3) == V4_OP_NOP && slot_op(ORIGIN + 1, 0) == V4_OP_IF,
"after 3 opcodes the branch starts the next word");
}
}
/* branches and calls from every slot position to the far end of the
* node, forward and backward, land where they should */
for (k = 0; k <= 3; k++) {
char pad[32] = "";
size_t at = 0;
for (i = 0; i < k; i++) strcat(pad, "nop ");
at += (size_t)snprintf(src + at, sizeof src - at, ": NEAR ( n -- 5 ) drop 5 ; ");
at += (size_t)snprintf(src + at, sizeof src - at, ": FWD ( n -- x ) %sif FAR drop 7 ; ", pad);
at += (size_t)snprintf(src + at, sizeof src - at, ": CALLS ( n -- x ) %sFAR 1 + ; ", pad);
at += (size_t)snprintf(src + at, sizeof src - at, ": FILL ");
for (i = 0; i < V4_NODE_WORDS - 400u; i++) at += (size_t)snprintf(src + at, sizeof src - at, "; ");
at += (size_t)snprintf(src + at, sizeof src - at, ": FAR ( n -- 99 ) drop 99 ; ");
at += (size_t)snprintf(src + at, sizeof src - at, ": BACK ( n -- x ) %sif NEAR drop 8 ; ", pad);
at += (size_t)snprintf(src + at, sizeof src - at, ": BACKCALL ( n -- x ) %sNEAR 1 + ; ", pad);
at += (size_t)snprintf(src + at, sizeof src - at, ": LOOP ( n -- 0 ) L: if DONE -1 + %sjump L DONE: ; ", pad);
CHECK(assemble(src), "the far text assembles with %u opcodes before each branch: %s", k, v4_text_error(&tx));
CHECK(v4_text_word(&tx, "FAR") > (v4_cell)(V4_NODE_WORDS - 400u), "FAR is at %ld", (long)v4_text_word(&tx, "FAR"));
CHECK(result("FWD", 0) == 99 && result("FWD", 3) == 7, "a forward branch across the node [%u]", k);
CHECK(result("CALLS", 3) == 100, "a forward call across the node [%u]", k);
CHECK(result("BACK", 0) == 5 && result("BACK", 3) == 8, "a backward branch across the node [%u]", k);
CHECK(result("BACKCALL", 3) == 6, "a backward call across the node [%u]", k);
CHECK(result("LOOP", 9) == 0, "a short backward jump near the top of the node [%u]", k);
}
/* the node fills up at 16384 words, not before */
{
size_t at = 0;
at += (size_t)snprintf(src + at, sizeof src - at, ": T ");
for (i = 0; i < V4_NODE_WORDS - 100u; i++) at += (size_t)snprintf(src + at, sizeof src - at, "; ");
CHECK(assemble(src), "16284 words of code fit: %s", v4_text_error(&tx));
for (i = 0; i < 200u; i++) at += (size_t)snprintf(src + at, sizeof src - at, "; ");
CHECK(!assemble(src) && strstr(v4_text_error(&tx), "does not fit") != NULL, "16484 do not");
CHECK(v4_node_guards_intact(&n), "and nothing was written outside the node");
}
printf(" %d checks, %d failures\n", checks, failures);
return failures ? 1 : 0;
}