Files
LithosAnanke/v4/tests/test_port.c
T
rajamesandClaude Opus 5.5 fd2088255d fix(v4): overlong lines are refused whole; a refused store moves neither A nor P
- hosted (v4/tools/hosted.c): a line longer than the buffer is read to its
  end and refused as too long. Before, fgets handed it over in 1025-byte
  pieces and the last piece ran as a line of its own.
- bare metal (kernel/src/v4/sk_v4.c): the line editor takes 1024
  characters, as the boot does, and a line typed past that is refused as
  too long instead of being cut at 255 and run. V4_BOOT_LINE_TOO_LONG is
  now reported at the prompt and while a line waits.
- engine (v4/src/exec.c): a store the node itself refuses (to "any port",
  "which port", How or Have) left the opcode running on: !+ advanced A,
  against node.h's FAULTS, and !p advanced P past the handler, into the
  next fault kind's entry. A refused store now does nothing more; a store
  that raises an error (D-18) still counts as made, but P stays the
  handler's.
- test_port.c: both cases; they fail on the old exec.c (A 1017, P 901).

Engine-level tests (all v4/tests/test_*.c but test_host_*) pass at 32 and
64 bits; test_port also under ASan/UBSan. Not yet run: make -C v4 test,
test-32, hosted-check, and the three QEMU acceptance boots.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GE3SDvQn5EoxdGufk84zz2
2026-11-06 00:40:48 +00:00

179 lines
7.2 KiB
C

/* test_port.c -- the node's port: a write blocks the node until its
* neighbour has served it (node.h; DECOMPOSITION.md section 6).
*
* At the level of opcodes: no word of the vocabulary is defined here.
*/
#include "v4/asm.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)
/* The memory map is open (D-4); the test chooses the address. */
#define PORT ((v4_cell)(V4_NODE_WORDS - 16u))
#define MARK ((v4_cell)0x1234)
#define HOME ((v4_cell)8) /* where a word returns to when the test called it */
static v4_node n;
static v4_exec_state es;
static v4_heat h;
static v4_asm as;
#define O(name) v4_asm_op(&as, V4_OP_##name)
#define LIT(v) v4_asm_lit(&as, (v4_cell)(v))
static void start(v4_cell word)
{
v4_dstack_reset(&n.ds);
v4_rstack_reset(&n.rs);
v4_exec_reset(&es);
v4_heat_reset(&h);
v4_rstack_push(&n.rs, HOME);
n.p = word;
}
/* Run until the node is blocked, is back at HOME, or `max` words. */
static unsigned run(unsigned max)
{
unsigned words = 0;
while (words < max && !n.asking && n.p != HOME) { (void)v4_exec_step_word(&n, &es, &h); words++; }
return words;
}
int main(void)
{
v4_cell w_ask, w_mid, w_two;
v4_uheat_t clock;
printf("v4 port tests: V4_CELL_BITS=%d\n", V4_CELL_BITS);
v4_asm_begin(&as, &n, 16);
/* what KERNEL-WORD lays down: the request, the port, b! !b ; */
w_ask = v4_asm_label(&as);
LIT(7); LIT(PORT); O(BANG_B); O(STORE_B); O(SEMI);
/* a write with more of the instruction word after it: dup + */
w_mid = v4_asm_label(&as);
LIT(5); LIT(PORT); O(BANG_B); O(STORE_B); O(DUP); O(ADD); O(SEMI);
/* two writes one after the other */
w_two = v4_asm_label(&as);
LIT(PORT); O(BANG_B); LIT(1); O(STORE_B); LIT(2); O(STORE_B); O(SEMI);
CHECK(v4_asm_ok(&as), "the test words assemble");
/* ---- no port: the address is memory ---- */
v4_node_reset(&n);
v4_asm_begin(&as, &n, 16);
w_ask = v4_asm_label(&as);
LIT(7); LIT(PORT); O(BANG_B); O(STORE_B); O(SEMI);
w_mid = v4_asm_label(&as);
LIT(5); LIT(PORT); O(BANG_B); O(STORE_B); O(DUP); O(ADD); O(SEMI);
w_two = v4_asm_label(&as);
LIT(PORT); O(BANG_B); LIT(1); O(STORE_B); LIT(2); O(STORE_B); O(SEMI);
CHECK(n.port == -1 && !n.asking, "a node has no port until one is attached");
n.mem[PORT] = MARK;
start(w_ask);
(void)run(100);
CHECK(n.p == HOME && !n.asking && n.mem[PORT] == 7, "with none, a store to the address writes memory and the word returns");
/* ---- the write blocks ---- */
v4_node_port_attach(&n, PORT);
n.mem[PORT] = MARK;
start(w_ask);
v4_dstack_push(&n.ds, 11);
v4_dstack_push(&n.ds, 22);
(void)run(100);
CHECK(n.asking && n.request == 7, "a store to the port blocks the node, with what it wrote: %ld", (long)n.request);
CHECK(n.mem[PORT] == MARK, "and does not write memory");
CHECK(n.p != HOME, "the word has not returned");
CHECK(n.ds.depth == 2, "what was on the data stack is there for its neighbour");
clock = es.anticlock;
CHECK(v4_exec_step_word(&n, &es, &h) == 0 && v4_exec_step_word(&n, &es, &h) == 0, "a blocked node executes nothing");
CHECK(es.anticlock == clock && n.asking, "however often it is stepped: the anti-clock does not move");
/* the neighbour takes two values and leaves one, then says it is served */
{
v4_cell b = v4_dstack_pop(&n.ds), a = v4_dstack_pop(&n.ds);
v4_dstack_push(&n.ds, a + b);
}
v4_node_port_served(&n);
(void)run(100);
CHECK(n.p == HOME && !n.asking, "served, the word goes on and returns");
CHECK(n.ds.depth == 1 && v4_dstack_pop(&n.ds) == 33, "with what its neighbour left on the stack");
/* ---- the rest of the instruction word waits ---- */
start(w_mid);
v4_dstack_push(&n.ds, 100); /* to be replaced while the node is blocked */
(void)run(100);
CHECK(n.asking && n.request == 5, "blocked in the middle of an instruction word");
(void)v4_dstack_pop(&n.ds);
v4_dstack_push(&n.ds, 21); /* the neighbour's result */
v4_node_port_served(&n);
(void)run(100);
CHECK(n.p == HOME && n.ds.depth == 1 && v4_dstack_pop(&n.ds) == 42,
"the opcodes after the store run when it is served, on what the neighbour left: dup + of 21");
/* ---- one write after another ---- */
start(w_two);
(void)run(100);
CHECK(n.asking && n.request == 1, "the first write blocks");
v4_node_port_served(&n);
(void)run(100);
CHECK(n.asking && n.request == 2, "served, the node runs to the second and blocks again");
v4_node_port_served(&n);
(void)run(100);
CHECK(n.p == HOME && !n.asking, "and returns when that is served");
/* ---- a fault while blocked abandons the rest of the word ---- */
start(w_mid);
v4_dstack_push(&n.ds, 1);
(void)run(100);
CHECK(n.asking, "blocked again");
v4_node_port_served(&n);
v4_node_fault(&n, V4_FAULT_RAISED, 12); /* no handler attached: the node stops */
CHECK(v4_exec_step_word(&n, &es, &h) == 0 && n.stopped, "a node its neighbour faults does not go on with the word");
/* ---- a store the node refuses moves neither A nor P (node.h, FAULTS) ---- */
{
const v4_cell table = 900, any = PORT + (v4_cell)V4_PORT_ANY;
v4_cell w_inc, w_storep;
v4_asm_begin(&as, &n, 200);
w_inc = v4_asm_label(&as);
LIT(any); O(BANG_A); LIT(9); O(STORE_INC); O(SEMI);
w_storep = v4_asm_label(&as); /* a word a neighbour sends: !p ; */
O(STORE_P); O(SEMI);
CHECK(v4_asm_ok(&as), "the refused-store words assemble");
v4_node_fault_attach(&n, table);
start(w_inc);
{
unsigned words; /* to the fault and no further: the table is empty */
for (words = 0; words < 100 && n.faults == 0; words++) (void)v4_exec_step_word(&n, &es, &h);
}
CHECK(n.faults == 1 && n.fault_kind == V4_FAULT_ADDRESS && n.fault_addr == any,
"!+ to \"any port\" is an address fault");
CHECK(n.a == any, "and A is untouched: %ld", (long)n.a);
CHECK(n.p == table + (v4_cell)V4_FAULT_ADDRESS, "P is the address fault's handler: %ld", (long)n.p);
/* P at "any port", as a node newly born executes what it is sent
* (node.h, v4_node_born): the `!p` it is sent stores at P */
v4_node_fault_attach(&n, table);
start(any);
v4_dstack_push(&n.ds, 9);
v4_node_port_give(&n, 0, n.mem[w_storep]);
(void)v4_exec_step_word(&n, &es, &h);
CHECK(n.faults == 1 && n.fault_kind == V4_FAULT_ADDRESS && n.fault_addr == any,
"!p with P at \"any port\" is an address fault");
CHECK(n.p == table + (v4_cell)V4_FAULT_ADDRESS,
"and P is the address fault's handler, not the next one's: %ld", (long)n.p);
}
/* ---- detaching ---- */
v4_node_reset(&n);
CHECK(n.port == -1 && !n.asking, "reset detaches the port");
printf(" %d checks, %d failures\n", checks, failures);
return failures != 0;
}