feat(v4.0.0): a node asks its kernel by a blocking write to its port
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>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
5f1f60fc84
commit
8df6c16766
@@ -68,6 +68,7 @@
|
||||
#define EMIT_HOOK (BUF0_W - 7) /* the xt of a word EMIT gives its character to, or 0: the console */
|
||||
#define CATCH (BUF0_W - 8) /* non-zero: a line's error is recorded here, -1, and the line ends " ok" */
|
||||
#define LINE_STATUS (BUF0_W - 9) /* how the last line ended: 0 QUIT, 1 completed, 2 an error */
|
||||
#define PORT (BUF0_W - 10) /* the node's port: a write to it blocks until its neighbour, the kernel, has served it */
|
||||
#define LOG_LEVEL (BUF0_W - 5) /* the level at or below which a message is printed */
|
||||
#define BOOT_CELLS (BVARS + 14) /* (BOOT): DP and LATEST as the loader left them, 2 cells */
|
||||
#define BUF0_W (BVARS - 2 * 256) /* the two block buffers, 256 cells each */
|
||||
@@ -144,6 +145,7 @@ static int host_load(v4_text *tx, v4_node *n, const char *const *files, unsigned
|
||||
v4_text_constant(tx, "(EMIT-HOOK)", EMIT_HOOK);
|
||||
v4_text_constant(tx, "(CATCH)", CATCH);
|
||||
v4_text_constant(tx, "(LINE-STATUS)", LINE_STATUS);
|
||||
v4_text_constant(tx, "(PORT)", PORT);
|
||||
v4_text_constant(tx, "BLOCK-NUMBER", STORAGE_REG);
|
||||
v4_text_constant(tx, "BLOCK-ADDRESS", STORAGE_REG + 1);
|
||||
v4_text_constant(tx, "BLOCK-COMMAND", STORAGE_REG + 2);
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
/* 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;
|
||||
}
|
||||
Reference in New Issue
Block a user