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:
co-authored by
Claude Opus 5.5
parent
83694b18be
commit
b1d09af043
+169
-47
@@ -8,6 +8,7 @@
|
||||
#include "v4/blocks.h"
|
||||
#include "v4/boot.h"
|
||||
#include "v4/message.h"
|
||||
#include "v4/wire.h"
|
||||
#include "starkernel/capsule.h"
|
||||
#include "starkernel/capsule_generated.h"
|
||||
#include "starkernel/capsule_sig.h"
|
||||
@@ -80,10 +81,11 @@ static uint32_t word_count(const v4_node *n, const v4_image *im)
|
||||
/* ---- handing the node a line ---------------------------------------------- */
|
||||
|
||||
/* The boot is the node's neighbour on two of its ports. On port 0 it is
|
||||
* the kernel: the node writes the number of a request there (ENGINE.md
|
||||
* 3.3) -- for a block (blocks.h), or for one of the host's words. On port
|
||||
* 1 it is the console: it writes the node a message of text and reads back
|
||||
* messages of what the node printed and of how the text ended (message.h). */
|
||||
* the kernel: the node writes the number of a request there, a word
|
||||
* (ENGINE.md 3.3) -- for a block (blocks.h), or for one of the host's
|
||||
* words. On port 1 it is the console: it puts a message of text on the
|
||||
* node's wire and takes off it the messages of what the node printed and
|
||||
* of how the text ended (message.h). */
|
||||
#define KERNEL_PORT 0u
|
||||
#define CONSOLE_PORT 1u
|
||||
#define CONSOLE_ID 1 /* the console's number as a sender; the node's is 0 until it is given one */
|
||||
@@ -91,23 +93,165 @@ static uint32_t word_count(const v4_node *n, const v4_image *im)
|
||||
/* What the kernel keeps of the node's block window (blocks.h). */
|
||||
static v4_blocks boot_blocks;
|
||||
|
||||
/* THE WIRES (docs/v4.0.0/MESH.md 7d.5). The lone node has no fabric: the
|
||||
* boot keeps the queues of its two wires, a queue each way (wire.h), and
|
||||
* does what the node asks of them, as a fabric does for a node of a mesh
|
||||
* (fabric.c). Every other port has no one on it. The kernel's wire has
|
||||
* its queues as any wire has; the kernel puts nothing on it and takes
|
||||
* nothing off: its requests are words, not messages. */
|
||||
static v4_wire_queue boot_wire[2][2]; /* by port: [0] to the node, [1] from it */
|
||||
#define TO_NODE(port) (&boot_wire[port][0])
|
||||
#define FROM_NODE(port) (&boot_wire[port][1])
|
||||
|
||||
/* 1 if `count` words from `addr` are the node's memory and not its ports
|
||||
* or the addresses after them: they may be copied to and from. */
|
||||
static int span_ok(const v4_node *n, v4_cell addr, unsigned count)
|
||||
{
|
||||
if (count == 0) return 1;
|
||||
if (count > V4_NODE_WORDS || addr < 0 || (v4_ucell)addr > (v4_ucell)(V4_NODE_WORDS - count)) return 0;
|
||||
if (n->port >= 0 && addr <= n->port + (v4_cell)V4_PORT_HAVE && addr + (v4_cell)count > n->port) return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int boot_arrived(void)
|
||||
{
|
||||
return TO_NODE(KERNEL_PORT)->arrived || TO_NODE(CONSOLE_PORT)->arrived;
|
||||
}
|
||||
|
||||
/* The node is blocked at an operation on a wire (node.h, v4_node_doing):
|
||||
* do it, whole, and let the node go on; the operations and their answers
|
||||
* are MESH.md 7d.3's, as fabric.c does them. Returns 0 if the node sleeps
|
||||
* on: it asked to sleep (8), and no message has come since it last asked
|
||||
* which wires have one; or for room (9) that is not there. */
|
||||
static int boot_do(const v4_boot *bt)
|
||||
{
|
||||
v4_node *n = bt->n;
|
||||
v4_wire_queue *rx = n->wire <= CONSOLE_PORT ? TO_NODE(n->wire) : 0, *tx = n->wire <= CONSOLE_PORT ? FROM_NODE(n->wire) : 0;
|
||||
v4_cell a = n->wire_a, b = n->wire_b, h[V4_WIRE_HEADER], how = V4_WIRE_NO_ONE;
|
||||
unsigned cells;
|
||||
|
||||
switch (n->do_op) {
|
||||
case 1: /* look */
|
||||
if (!rx) break;
|
||||
if (rx->mark >= rx->used) how = V4_WIRE_NO_MESSAGE;
|
||||
else how = span_ok(n, a, V4_WIRE_HEADER) ? v4_wire_look(rx, &n->mem[a]) : V4_WIRE_NOT_A_MESSAGE;
|
||||
break;
|
||||
case 2: /* take */
|
||||
if (!rx) break;
|
||||
if (v4_wire_look(rx, h) != V4_WIRE_DONE) { how = V4_WIRE_NO_MESSAGE; break; }
|
||||
cells = v4_wire_cells(h[6]) - V4_WIRE_HEADER;
|
||||
if (!span_ok(n, a, V4_WIRE_HEADER) || !span_ok(n, b, cells)) how = V4_WIRE_NOT_A_MESSAGE;
|
||||
else how = v4_wire_take(rx, &n->mem[a], cells ? &n->mem[b] : &n->mem[0], cells);
|
||||
break;
|
||||
case 3: /* move */
|
||||
if (!rx) break;
|
||||
if (rx->mark >= rx->used) how = V4_WIRE_NO_MESSAGE;
|
||||
else how = a >= 0 && a <= (v4_cell)CONSOLE_PORT ? v4_wire_move(rx, FROM_NODE((unsigned)a)) : V4_WIRE_NO_ONE;
|
||||
break;
|
||||
case 4: /* drop */
|
||||
if (rx) how = v4_wire_drop(rx);
|
||||
break;
|
||||
case 5: /* put */
|
||||
if (!tx) break;
|
||||
cells = span_ok(n, a, V4_WIRE_HEADER) ? v4_wire_cells(n->mem[a + 6]) : 0;
|
||||
if (cells == 0 || !span_ok(n, b, cells - V4_WIRE_HEADER)) how = V4_WIRE_NOT_A_MESSAGE;
|
||||
else how = v4_wire_put(tx, &n->mem[a], cells > V4_WIRE_HEADER ? &n->mem[b] : &n->mem[0]);
|
||||
break;
|
||||
case 6: /* first */
|
||||
if (rx) { v4_wire_first(rx); how = V4_WIRE_DONE; }
|
||||
break;
|
||||
case 7: /* next */
|
||||
if (rx) how = v4_wire_next(rx);
|
||||
break;
|
||||
case 8: /* sleep */
|
||||
if (!boot_arrived()) return 0;
|
||||
how = V4_WIRE_DONE;
|
||||
break;
|
||||
case 9: /* sleep for room */
|
||||
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 (!boot_arrived()) return 0;
|
||||
how = V4_WIRE_NO_ROOM;
|
||||
break;
|
||||
case 10: /* room */
|
||||
if (!tx) break;
|
||||
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: /* not an operation */
|
||||
how = V4_WIRE_NOT_A_MESSAGE;
|
||||
break;
|
||||
}
|
||||
v4_node_done(n, how);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* The node is told which of its wires have a message and executes one
|
||||
* instruction word; if it fetched which wires have one, it has seen what
|
||||
* had come. The first half of a fabric's step (fabric.c). */
|
||||
static void boot_exec(const v4_boot *b)
|
||||
{
|
||||
v4_node *n = b->n;
|
||||
v4_node_have(n, (TO_NODE(KERNEL_PORT)->used ? 1u << KERNEL_PORT : 0u) | (TO_NODE(CONSOLE_PORT)->used ? 1u << CONSOLE_PORT : 0u));
|
||||
(void)v4_exec_step_word(n, b->es, b->h);
|
||||
if (n->have_fetched) {
|
||||
TO_NODE(KERNEL_PORT)->arrived = 0;
|
||||
TO_NODE(CONSOLE_PORT)->arrived = 0;
|
||||
n->have_fetched = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Time passes for the node, in the order a fabric's step has: that, and
|
||||
* then what it asked of a wire is done. Returns 0 if it is asleep at a
|
||||
* wire and sleeps on. */
|
||||
static int boot_step(const v4_boot *b)
|
||||
{
|
||||
boot_exec(b);
|
||||
return v4_node_doing(b->n) ? boot_do(b) : 1;
|
||||
}
|
||||
|
||||
/* Hand the node a line and run it to its end. What it prints goes to the
|
||||
* console; or, if `keep` is given, into `keep`, at most `cap` characters,
|
||||
* and none of it is shown: *printed is then how many it printed in all. */
|
||||
* and none of it is shown: *printed is then how many it printed in all.
|
||||
* The console takes every message off the node's wire each time round, so
|
||||
* what the node prints never waits for room for long. */
|
||||
static int run_line(const v4_boot *b, const char *text, unsigned len, char *keep, unsigned cap, unsigned *printed)
|
||||
{
|
||||
static v4_message out, in; /* one line at a time: the boot is not re-entered */
|
||||
static v4_message out; /* one line at a time: the boot is not re-entered */
|
||||
static v4_cell in[V4_WIRE_HEADER], in_text[V4_MSG_MAX_CHARS / 4u];
|
||||
v4_node *n = b->n;
|
||||
uint64_t steps = 0;
|
||||
unsigned sent = 0, i;
|
||||
unsigned i;
|
||||
|
||||
if (!text) out.count = 0; /* no new line: the node has one it kept (boot.h) */
|
||||
else if (!v4_message_text(&out, 0, CONSOLE_ID, V4_MSG_TEXT, text, len)) return V4_BOOT_LINE_TOO_LONG;
|
||||
in.count = 0;
|
||||
if (text) { /* with none, the node has a line waiting on its wire (boot.h) */
|
||||
if (!v4_message_text(&out, 0, CONSOLE_ID, V4_MSG_TEXT, text, len)) return V4_BOOT_LINE_TOO_LONG;
|
||||
if (v4_wire_put(TO_NODE(CONSOLE_PORT), out.word, out.word + V4_MSG_HEADER) != V4_WIRE_DONE) return V4_BOOT_LINE_STOPPED;
|
||||
}
|
||||
for (;;) {
|
||||
(void)v4_exec_step_word(n, b->es, b->h);
|
||||
int awake = boot_step(b);
|
||||
if (n->stopped) return V4_BOOT_LINE_STOPPED;
|
||||
|
||||
v4_wire_first(FROM_NODE(CONSOLE_PORT)); /* what the node has sent its console, in the order it was sent */
|
||||
while (v4_wire_take(FROM_NODE(CONSOLE_PORT), in, in_text, V4_MSG_MAX_CHARS / 4u) == V4_WIRE_DONE) {
|
||||
if (in[2] == V4_MSG_OUTPUT) {
|
||||
for (i = 0; i < (unsigned)in[6]; i++) {
|
||||
char c = (char)(((v4_ucell)in_text[i / 4u] >> (8u * (i % 4u))) & 0xFFu);
|
||||
if (!keep) b->out(&c, 1);
|
||||
else { if (*printed < cap) keep[*printed] = c; (*printed)++; }
|
||||
}
|
||||
} else if (in[2] == V4_MSG_DONE) {
|
||||
/* The text has ended. The node goes on to where it next asks something of a wire, with
|
||||
* nothing of its own on either stack: whoever called may then look at its stacks, or
|
||||
* empty them. */
|
||||
for (i = 0; i < 4096u && !v4_node_doing(n) && !n->stopped && !n->asking; i++) boot_exec(b);
|
||||
return (int)in_text[0];
|
||||
}
|
||||
}
|
||||
if (!awake) {
|
||||
if (n->do_op == 9) continue; /* waiting for room, which the console has just made */
|
||||
return V4_BOOT_LINE_WAITING; /* waiting for an answer: only the console can end that */
|
||||
}
|
||||
|
||||
if (n->asking && n->ask_port == KERNEL_PORT) { /* a request: the kernel's turn */
|
||||
if (v4_blocks_serve(&boot_blocks, n, n->request)) { /* a block, as for any node */
|
||||
v4_node_port_served(n);
|
||||
@@ -122,36 +266,12 @@ static int run_line(const v4_boot *b, const char *text, unsigned len, char *keep
|
||||
}
|
||||
continue;
|
||||
}
|
||||
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(&in, word)) {
|
||||
unsigned chars = v4_message_length(&in);
|
||||
if (v4_message_type(&in) == V4_MSG_OUTPUT) {
|
||||
for (i = 0; i < chars; i++) {
|
||||
char c = v4_message_char(&in, i);
|
||||
if (!keep) b->out(&c, 1);
|
||||
else { if (*printed < cap) keep[*printed] = c; (*printed)++; }
|
||||
}
|
||||
} else if (v4_message_type(&in) == V4_MSG_DONE) {
|
||||
return (int)in.word[V4_MSG_HEADER];
|
||||
}
|
||||
in.count = 0;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (n->asking) { /* a port with no one on it: an error on the node */
|
||||
if (n->asking) { /* a word written to a port with no one on it: an error on the node */
|
||||
if (!v4_node_port_gone(n, V4_ERROR_NO_ONE)) return V4_BOOT_LINE_STOPPED;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (n->reading && !n->given) {
|
||||
if (sent < out.count && (n->read_port == CONSOLE_PORT || n->read_port == V4_PORT_ANY)) {
|
||||
v4_node_port_give(n, CONSOLE_PORT, out.word[sent++]); /* a word of the message to the node */
|
||||
continue;
|
||||
}
|
||||
if (n->read_port != CONSOLE_PORT && n->read_port != KERNEL_PORT && v4_node_port_gone(n, V4_ERROR_NO_ONE)) continue; /* reading one port, with no one on it */
|
||||
if (n->read_port == V4_PORT_ANY) return V4_BOOT_LINE_WAITING; /* waiting for an answer: only the console can end that */
|
||||
if (n->reading && !n->given) { /* a word read from a port: no one here writes one */
|
||||
if (n->read_port != V4_PORT_ANY && v4_node_port_gone(n, V4_ERROR_NO_ONE)) continue;
|
||||
return V4_BOOT_LINE_STOPPED; /* waiting for what will not come */
|
||||
}
|
||||
if (v4_image_waiting(n, b->im)) { /* the text is reading the keyboard */
|
||||
@@ -310,10 +430,14 @@ static int load_nucleus(const v4_boot *b)
|
||||
if (!p || cap->length % CELL_BYTES != 0) { say(b, "V4: capsule " NUCLEUS_NAME " is not whole words\n"); return 0; }
|
||||
words = cap->length / CELL_BYTES;
|
||||
|
||||
/* until it has taken every word and is waiting for a message: reading
|
||||
* its ports again, but from its memory now and not from the port */
|
||||
while (at < words || !(n->reading && !n->given && n->p != b->im->port + (v4_cell)V4_PORT_ANY)) {
|
||||
(void)v4_exec_step_word(n, b->es, b->h);
|
||||
/* until it has taken every word and is where the nucleus waits for a
|
||||
* message: asleep, no message having come */
|
||||
for (;;) {
|
||||
if (!boot_step(b)) {
|
||||
if (at >= words && n->do_op == 8) break;
|
||||
say(b, "V4: the node did not take the nucleus in\n");
|
||||
return 0;
|
||||
}
|
||||
if (n->stopped || n->asking || ++steps > STEP_LIMIT) {
|
||||
say(b, "V4: the node did not take the nucleus in\n");
|
||||
return 0;
|
||||
@@ -322,7 +446,6 @@ static int load_nucleus(const v4_boot *b)
|
||||
v4_ucell word = 0;
|
||||
unsigned i;
|
||||
if (at >= words) {
|
||||
if (n->p != b->im->port + (v4_cell)V4_PORT_ANY) break; /* it is waiting for a message */
|
||||
say(b, "V4: the node wants more than the nucleus holds\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -351,10 +474,9 @@ int v4_boot_run(const v4_boot *b)
|
||||
say(b, "V4: the nucleus is not for this build of the engine\nPARITY:FAIL\nPOST: FAILED\n");
|
||||
return 0;
|
||||
}
|
||||
/* what is on its two ports, the kernel and the console, takes a word
|
||||
* when it is written: the node need never wait before writing to them
|
||||
* (node.h, v4_node_port_status) */
|
||||
v4_node_port_status(b->n, 0, (1u << KERNEL_PORT | 1u << CONSOLE_PORT) * (1u + (1u << V4_PORTS))); /* each of them there, and taking */
|
||||
/* its two wires, the kernel's and the console's, with nothing on them */
|
||||
v4_wire_reset(TO_NODE(KERNEL_PORT)); v4_wire_reset(FROM_NODE(KERNEL_PORT));
|
||||
v4_wire_reset(TO_NODE(CONSOLE_PORT)); v4_wire_reset(FROM_NODE(CONSOLE_PORT));
|
||||
v4_blocks_init(&boot_blocks, b->im->block_window);
|
||||
if (!load_nucleus(b)) { say(b, "PARITY:FAIL\nPOST: FAILED\n"); return 0; }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user