ENGINE.md step 2, the carrier. Ruled 2026-10-05 (V3-PARITY.md 1i), on DECOMPOSITION.md section 6: a write to a port blocks until the neighbour reads. - node: v4_node_port_attach, v4_node_port_served; a store to the port keeps the value as the request and blocks the node - exec: a blocked node executes nothing; served, it goes on from the opcode after the store, in the same instruction word; a fault meanwhile abandons the rest of the word - compile.v4: n KERNEL-WORD name makes a word whose body writes n to the port; its arguments and results are on the data stack - boot: the kernel's words are made by handing the node text, and requests are served between the node's opcodes; one no one serves is error 12 - BYE, the first kernel word: hosted it leaves the program, as hosted v3; on the lone node it is v3's cold restart - ENGINE.md 3a: multiuser, multitasking, preemptive and cooperative, and what that asks of the engine Verified: make -C v4 test passes at both widths, with tests/test_port.c; hosted-check passes on three ISAs; clean qemu with STARFORTH_V4=1 on amd64, aarch64 and riscv64 passes POST with the same hashes as hosted, and a kernel word no one serves and BYE typed at each prompt are answered (logs/20261005-185506, -185734, -190101; -185234 is an amd64 run in which those two lines were not typed). Not done: v3's own C functions serving a node. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
143 lines
5.5 KiB
C
143 lines
5.5 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 - 9u))
|
|
#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");
|
|
|
|
/* ---- 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;
|
|
}
|