feat(v4.0.0): messages are whole and wait on the wire -- a node keeps none but the one it is doing

Step 6e, tasks 2 and 3 (docs/v4.0.0/MESH.md 7d).  The nucleus puts, looks
at, takes, moves and drops whole messages through the six addresses after
its ports; its own ring of messages waiting, the refusals owed, (GATE) and
the looking words' use are gone.  SEND, an answer, a NACK and a GONE go or
are refused at once; what a text prints waits for room; a text is not
begun until the wire its answer goes by has room for it, and that room is
kept.  The lone node's host (v4/system/boot.c) keeps its two wires and
does its operations; the test hosts' consoles put and take whole messages
through the fabric.  New: tests/test_host_depth.c, every message path of
a mesh node at every depth of both stacks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-08 15:59:37 -04:00
co-authored by Claude Opus 5.5
parent 83694b18be
commit b1d09af043
12 changed files with 1621 additions and 834 deletions
+90 -36
View File
@@ -42,6 +42,7 @@
*/
#include "v4/text.h"
#include "v4/message.h"
#include "v4/wire.h"
#include "v4/testcode.h"
#include <stdint.h>
#include <stdio.h>
@@ -63,7 +64,6 @@ static v4_heat h;
static v4_text tx;
static v4_cell w_key, w_key_end, w_fault, capsule_latest;
static v4_cell w_idle; /* (IDLE): where the node waits for a message */
static v4_cell w_idle_end; /* (PASS-ON): the word after it */
static char out[V4_CONSOLE_CAP + 1];
/* STORAGE (MESH.md section 8). The node asks its kernel for its blocks, and
@@ -190,17 +190,81 @@ static const char *node_name(v4_cell xt)
static char typed[8192]; /* typed and not yet taken */
static unsigned typed_len;
static int line_open; /* a line has been sent and has not ended */
static v4_message to_node, from_node; /* the message going in, and the one coming out */
static unsigned to_sent; /* how many words of to_node the node has taken */
static v4_message to_node; /* the message going in */
static char shown[V4_CONSOLE_CAP]; /* what the node has printed since the console last looked */
static unsigned shown_len;
static int shown_dropped;
static int text_ended; /* how the text ended, when it has */
/* 1 if the node is where it waits for a message, with nothing given it. */
/* THE WIRES (MESH.md 7d.5). The node is alone and has no fabric: these
* tests keep the queues of its console's wire, do what it asks of them, and
* are what is on the other end, as v4/system/boot.c is for the products.
* Every other port has no one on it. */
static v4_wire_queue wire_in, wire_out; /* to the node from its console, and from it */
static int span_ok(v4_cell addr, unsigned count)
{
if (count == 0) return 1;
if (addr < 0 || (v4_ucell)addr > (v4_ucell)(V4_NODE_WORDS - count)) return 0;
return !(addr <= PORT + (v4_cell)V4_PORT_HAVE && addr + (v4_cell)count > PORT);
}
/* Do the operation the node is blocked at. 0: it sleeps on. */
static int wire_do(void)
{
v4_wire_queue *rx = n.wire == CONSOLE_PORT ? &wire_in : 0, *tx = n.wire == CONSOLE_PORT ? &wire_out : 0;
v4_cell a = n.wire_a, b = n.wire_b, hd[V4_WIRE_HEADER], how = V4_WIRE_NO_ONE;
unsigned cells;
switch (n.do_op) {
case 1: if (rx) how = span_ok(a, V4_WIRE_HEADER) || rx->mark >= rx->used ? v4_wire_look(rx, span_ok(a, V4_WIRE_HEADER) ? &n.mem[a] : hd) : V4_WIRE_NOT_A_MESSAGE; break;
case 2:
if (!rx) break;
if (v4_wire_look(rx, hd) != V4_WIRE_DONE) { how = V4_WIRE_NO_MESSAGE; break; }
cells = v4_wire_cells(hd[6]) - V4_WIRE_HEADER;
how = span_ok(a, V4_WIRE_HEADER) && span_ok(b, cells) ? v4_wire_take(rx, &n.mem[a], cells ? &n.mem[b] : hd, cells) : V4_WIRE_NOT_A_MESSAGE;
break;
case 3: if (rx) how = rx->mark >= rx->used ? V4_WIRE_NO_MESSAGE : a == (v4_cell)CONSOLE_PORT ? v4_wire_move(rx, &wire_out) : V4_WIRE_NO_ONE; break;
case 4: if (rx) how = v4_wire_drop(rx); break;
case 5:
if (!tx) break;
cells = span_ok(a, V4_WIRE_HEADER) ? v4_wire_cells(n.mem[a + 6]) : 0;
how = cells && span_ok(b, cells - V4_WIRE_HEADER) ? v4_wire_put(tx, &n.mem[a], cells > V4_WIRE_HEADER ? &n.mem[b] : hd) : V4_WIRE_NOT_A_MESSAGE;
break;
case 6: if (rx) { v4_wire_first(rx); how = V4_WIRE_DONE; } break;
case 7: if (rx) how = v4_wire_next(rx); break;
case 8: if (!wire_in.arrived) return 0; how = V4_WIRE_DONE; break;
case 9:
if (!tx) break;
if (a < 0 || a > (v4_cell)V4_WIRE_CELLS) { how = V4_WIRE_NO_ROOM; break; }
if (v4_wire_room(tx, (unsigned)a) == V4_WIRE_DONE) { how = V4_WIRE_DONE; break; }
if (!wire_in.arrived) return 0;
how = V4_WIRE_NO_ROOM;
break;
case 10: if (tx) how = a < 0 ? V4_WIRE_NOT_A_MESSAGE : a <= (v4_cell)V4_WIRE_CELLS ? v4_wire_room(tx, (unsigned)a) : V4_WIRE_NO_ROOM; break;
default: how = V4_WIRE_NOT_A_MESSAGE; break;
}
v4_node_done(&n, how);
return 1;
}
/* One instruction word, and then what the node asked of its wires; as the
* fabric orders a step (fabric.c). */
static void node_step(void)
{
v4_node_have(&n, wire_in.used > 0 ? 1u << CONSOLE_PORT : 0u);
(void)v4_exec_step_word(&n, &es, &h);
if (n.have_fetched) { wire_in.arrived = 0; n.have_fetched = 0; }
if (v4_node_doing(&n)) (void)wire_do();
}
/* The console puts a line on the node's wire. */
static int console_put(const v4_message *m)
{
return v4_wire_put(&wire_in, m->word, m->word + V4_MSG_HEADER) == V4_WIRE_DONE;
}
/* 1 if the node is asleep with nothing to do: where it waits for a message. */
static int node_idle(void)
{
return n.reading && !n.given && n.read_port == V4_PORT_ANY && n.p > w_idle && n.p <= w_idle_end; /* in (IDLE), where it reads */
return v4_node_doing(&n) && n.do_op == 8 && wire_in.used == 0 && n.mem[AWAIT_FROM] == 0;
}
/* Run until the text ends, or until it has been inside KEY with nothing to
@@ -208,35 +272,27 @@ static int node_idle(void)
* for a character, 0: neither. */
static int run_line(long max)
{
static v4_cell hd[V4_WIRE_HEADER], text[V4_MSG_MAX_CHARS / 4u];
long steps = 0;
unsigned idle = 0;
while (steps < max && idle < 64) {
(void)v4_exec_step_word(&n, &es, &h);
node_step();
steps++;
if (n.asking && n.ask_port == 0) { kernel_serve(); v4_node_port_served(&n); continue; } /* a request */
if (n.asking && n.ask_port == CONSOLE_PORT) { /* a word of a message from the node */
v4_cell word = n.request;
v4_node_port_served(&n);
if (v4_message_word(&from_node, word)) {
unsigned i, chars = v4_message_length(&from_node);
if (v4_message_type(&from_node) == V4_MSG_OUTPUT) {
for (i = 0; i < chars; i++) {
if (shown_len < sizeof shown) shown[shown_len++] = v4_message_char(&from_node, i);
else shown_dropped = 1;
}
} else if (v4_message_type(&from_node) == V4_MSG_DONE) {
text_ended = (int)from_node.word[V4_MSG_HEADER];
from_node.count = 0;
last_steps = steps;
return 1;
if (n.asking) { (void)v4_node_port_gone(&n, 18); continue; } /* a port with no one on it */
v4_wire_first(&wire_out);
while (v4_wire_take(&wire_out, hd, text, V4_MSG_MAX_CHARS / 4u) == V4_WIRE_DONE) { /* what the node has sent */
if (hd[2] == V4_MSG_OUTPUT) {
unsigned i;
for (i = 0; i < (unsigned)hd[6]; i++) {
if (shown_len < sizeof shown) shown[shown_len++] = (char)(((v4_ucell)text[i / 4u] >> (8u * (i % 4u))) & 0xFFu);
else shown_dropped = 1;
}
from_node.count = 0;
} else if (hd[2] == V4_MSG_DONE) {
text_ended = (int)text[0];
last_steps = steps;
return 1;
}
continue;
}
if (n.reading && !n.given && to_sent < to_node.count && (n.read_port == CONSOLE_PORT || n.read_port == V4_PORT_ANY)) {
v4_node_port_give(&n, CONSOLE_PORT, to_node.word[to_sent++]); /* a word of the message to the node */
continue;
}
if (n.input_pos == n.input_len && n.p >= w_key && n.p < w_key_end) idle++; else idle = 0;
}
@@ -279,7 +335,7 @@ static void boot_with(unsigned depth)
for (i = 0; i < depth; i++) v4_dstack_push(&n.ds, (v4_cell)(0x5A000000 + i));
v4_dstack_push(&n.ds, CANARY);
v4_node_port_attach(&n, PORT);
v4_node_port_status(&n, 0, (1u | 1u << CONSOLE_PORT) * (1u + (1u << V4_PORTS))); /* the kernel and the console take what is written when it is written */
v4_wire_reset(&wire_in); v4_wire_reset(&wire_out);
n.mem[WORD_DEFINED] = 0; /* no one is told of entries, until a test says so */
n.mem[WORD_FORGOTTEN] = 0;
known_n = 0;
@@ -287,13 +343,13 @@ static void boot_with(unsigned depth)
n.mem[LINE_STATUS] = 1;
n.mem[ME] = 0; /* it has no number yet: a message to 0 is for it */
HOST_MESSAGES_EMPTY(&n);
n.mem[REPLY] = PORT + CONSOLE_PORT;
n.mem[REPLY] = CONSOLE_PORT;
n.p = w_idle; /* waiting for a message */
typed_len = 0;
line_open = 0;
to_node.count = 0; to_sent = 0; from_node.count = 0;
to_node.count = 0;
shown_len = 0; shown_dropped = 0;
for (i = 0; i < 64 && !n.reading; i++) (void)v4_exec_step_word(&n, &es, &h); /* no message waits: it reads its ports, and is blocked */
for (i = 0; i < 256 && !node_idle(); i++) node_step(); /* no message waits: it sleeps */
}
static void boot(void) { boot_with(0); }
/* The same with nothing at all on the data stack. */
@@ -335,7 +391,7 @@ static const char *say(const char *input)
if (!nl) break;
len = (unsigned)(nl - typed);
if (!v4_message_text(&to_node, 0, 1, V4_MSG_TEXT, typed, len)) return "(line too long)";
to_sent = 0;
if (!console_put(&to_node)) return "(no room on the wire)";
memmove(typed, nl + 1, typed_len - len - 1);
typed_len -= len + 1;
line_open = 1;
@@ -376,8 +432,7 @@ static int post_line(void *self, const char *text, unsigned text_len, char *o, u
(void)self;
*len = 0;
if (post_state_fails && text_len == 25 && memcmp(text, "DECIMAL FORTH DEFINITIONS", 25) == 0) text = "NOSUCHWORD-FOR-STATE ";
if (!v4_message_text(&to_node, 0, 1, V4_MSG_TEXT, text, text_len)) return -1;
to_sent = 0; from_node.count = 0;
if (!v4_message_text(&to_node, 0, 1, V4_MSG_TEXT, text, text_len) || !console_put(&to_node)) return -1;
shown_len = 0; shown_dropped = 0;
ended = run_line(post_step_limit);
if (ended != 1) return -1;
@@ -870,7 +925,6 @@ int main(void)
printf(" capsule: %ld words\n", (long)v4_text_here(&tx) - 16);
if (failures) { printf(" %d checks, %d failures\n", checks, failures); return 1; }
w_idle = v4_text_word(&tx, "(IDLE)");
w_idle_end = v4_text_word(&tx, "(PASS-ON)"); /* the word after (IDLE) in quit.v4 */
w_key = v4_text_word(&tx, "KEY");
w_key_end = v4_text_word(&tx, "CR"); /* the word after KEY in core.v4 */
w_fault = v4_text_word(&tx, "(FAULTS)");
@@ -1153,7 +1207,7 @@ int main(void)
(void)say("WORDS\n");
for (at = 0; at < plain; at++) if (n.mem[OUT_W + OUT_CELLS + at] != 0) { over++; worst = k; break; }
if (n.mem[OUT_PTR] > OUT_W + OUT_CELLS) { over++; worst = k; }
if (n.asking || n.doing) { over++; worst = k; } /* it stored to a port */
if (n.asking) { over++; worst = k; } /* it stored to a port */
CHECK(strcmp(say("1 2 + .\n"), "3 ok\nok> ") == 0, "WORDS with %u values on the stack: the node goes on", k);
}
CHECK(over == 0, "nothing was ever put past the end of the output buffer (it was, with %u values on the stack)", worst);