- 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
179 lines
7.2 KiB
C
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;
|
|
}
|