feat(v4.0.0): a node looks before it writes; two neighbours no longer stop each other
MESH.md step 4, the fault found there, as ruled (section 7a): a node writes only to a neighbour that is reading, keeps what it takes in meanwhile, and loses and counts what it has no room for. Engine: two more addresses after a node's ports -- which ports have a neighbour waiting to write to it, which to read from it. Nucleus: (GATE) before every message; the messages waiting, a ring of 400 cells, dealt with when the node is idle. Of two neighbours the lower number may wait to write (ruled after it was built); NEIGHBOUR tells a node who is on each port. test_host_mesh.c, 44 checks: the case that stopped the nodes passes; six messages from each node to each at once all arrive; 800 at once, the nodes come to rest and every message arrived or was counted (287 arrived, 714 of all kinds let go). test_fabric.c: the two looks. Both widths, ASan+UBSan. POST: twelve cases handed HERE, a cell address on v4, to words that take a byte address, and so wrote into or read from the nucleus's code at cell HERE/4. Ruled: left out, marked OPEN, until HERE and the byte words are made to agree as its own step. POST is 538 cases. hosted-check on three ISAs, 538/538. Bare metal: logs/20261006-134817 (amd64), -135044 (aarch64), -135440 (riscv64). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
630d03fa4e
commit
f9c034cadd
+31
-3
@@ -74,8 +74,18 @@
|
||||
#define ROUTE_DEFAULT (BUF0_W - 18) /* the port address for a node not in the table, or 0: there is none */
|
||||
#define LOST (BUF0_W - 19) /* how many messages have been let go for want of a way */
|
||||
#define PRINT_TO (BUF0_W - 20) /* where what the text being served prints is to go: the node, */
|
||||
#define PRINT_PORT (BUF0_W - 21) /* and the port address that leads there */
|
||||
#define DONE_PORT (BUF0_W - 22) /* the port address that leads back to whoever sent the text being served */
|
||||
#define WRITERS (PORT + (v4_cell)V4_PORTS + 2) /* which ports have a neighbour waiting to write to this node (node.h) */
|
||||
#define READERS (PORT + (v4_cell)V4_PORTS + 3) /* and which have one waiting to read from it */
|
||||
#define PRINT_PORT (BUF0_W - 33) /* and the port address that leads there */
|
||||
#define DONE_PORT (BUF0_W - 34) /* the port address that leads back to whoever sent the text being served */
|
||||
#define MQ_HEAD (BUF0_W - 35) /* the messages waiting to be dealt with: where the oldest begins, */
|
||||
#define MQ_TAIL (BUF0_W - 36) /* where the next will go, */
|
||||
#define MQ_COUNT (BUF0_W - 37) /* and how many cells they take */
|
||||
#define GATE_PORT (BUF0_W - 38) /* the port address a message is about to be begun on */
|
||||
#define NEAR (BUF0_W - 64) /* for each port, the number of the node on the other end; 0: not told */
|
||||
#define MQ_HDR (BUF0_W - 56) /* a message being taken in: its seven words and the port it came on */
|
||||
#define MQ_W (OUT_W - 400) /* the messages waiting: each is its seven words, the port it came on, its text */
|
||||
#define MQ_CELLS 400
|
||||
#define ROUTES (BUF0_W - 96) /* the table of ways: 16 entries of a node and the port address that leads to it */
|
||||
#define ROUTE_MAX 16
|
||||
#define ME (BUF0_W - 13) /* this node's number: a message is for it when its first word is this */
|
||||
@@ -90,6 +100,10 @@
|
||||
#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 */
|
||||
#define BUF1_W (BUF0_W + 256)
|
||||
/* the ports' ten addresses and the two that follow them end below BUF0_W - 20 */
|
||||
typedef char host_map_ports_fit[(V4_PORTS <= 8u) ? 1 : -1];
|
||||
/* Nothing printed and no message waiting: a node's variables for them, as at switch-on. */
|
||||
#define HOST_MESSAGES_EMPTY(n) do { (n)->mem[OUT_PTR] = OUT_W; (n)->mem[MQ_HEAD] = MQ_W; (n)->mem[MQ_TAIL] = MQ_W; (n)->mem[MQ_COUNT] = 0; } while (0)
|
||||
#define QVARS (BUF0_W - 4) /* (Q): quit.v4, system.v4 and blocks.v4, 4 cells */
|
||||
#define WBUF (WBUF_W * 4)
|
||||
#define TIB (TIB_W * 4)
|
||||
@@ -103,6 +117,8 @@
|
||||
#ifndef DICT_END_W
|
||||
#define DICT_END_W ((v4_cell)14336)
|
||||
#endif
|
||||
/* the messages waiting are above the dictionary */
|
||||
typedef char host_map_queue_fits[(MQ_W >= DICT_END_W) ? 1 : -1];
|
||||
|
||||
/* How many slots, from slot 0, a branch may sit in on a node this size: those
|
||||
* whose address field reaches every word of it. */
|
||||
@@ -123,7 +139,7 @@ static int host_load(v4_text *tx, v4_node *n, const char *const *files, unsigned
|
||||
unsigned i;
|
||||
|
||||
v4_node_reset(n);
|
||||
n->mem[OUT_PTR] = OUT_W; /* nothing has been printed: what is, is kept from here (core.v4) */
|
||||
HOST_MESSAGES_EMPTY(n); /* nothing has been printed and no message waits (core.v4) */
|
||||
v4_text_begin(tx, n, 16);
|
||||
v4_text_constant(tx, "N-1", V4_CELL_BITS - 1);
|
||||
v4_text_constant(tx, "NODE-ERROR", NODE_ERROR);
|
||||
@@ -173,6 +189,18 @@ static int host_load(v4_text *tx, v4_node *n, const char *const *files, unsigned
|
||||
v4_text_constant(tx, "(PRINT-PORT)", PRINT_PORT);
|
||||
v4_text_constant(tx, "(DONE-PORT)", DONE_PORT);
|
||||
v4_text_constant(tx, "(ROUTES)", ROUTES);
|
||||
v4_text_constant(tx, "(WRITERS)", WRITERS);
|
||||
v4_text_constant(tx, "(READERS)", READERS);
|
||||
v4_text_constant(tx, "-PORTS", -(v4_cell)V4_PORTS);
|
||||
v4_text_constant(tx, "(NEAR)", NEAR);
|
||||
v4_text_constant(tx, "(GATE-PORT)", GATE_PORT);
|
||||
v4_text_constant(tx, "(MQ)", MQ_W);
|
||||
v4_text_constant(tx, "(MQ-END)", MQ_W + MQ_CELLS);
|
||||
v4_text_constant(tx, "-MQ-ROOM", -(v4_cell)(MQ_CELLS - 7)); /* cells taken + a text's words + this is below zero while the message fits */
|
||||
v4_text_constant(tx, "(MQ-HEAD)", MQ_HEAD);
|
||||
v4_text_constant(tx, "(MQ-TAIL)", MQ_TAIL);
|
||||
v4_text_constant(tx, "(MQ#)", MQ_COUNT);
|
||||
v4_text_constant(tx, "(MQ-HDR)", MQ_HDR);
|
||||
v4_text_constant(tx, "(OUT^)", OUT_PTR);
|
||||
v4_text_constant(tx, "(REPLY)", REPLY);
|
||||
v4_text_constant(tx, "(MSG)", MSG);
|
||||
|
||||
@@ -331,6 +331,29 @@ int main(void)
|
||||
"a port that is not there, a node that is not there, a port to itself: refused");
|
||||
CHECK(v4_fabric_wire(&f, a, 1, a, 2), "but one port of a node may be wired to another of the same node");
|
||||
|
||||
/* ---- a node looks at its neighbours before it writes (MESH.md 7a) ---- */
|
||||
v4_fabric_init(&f, places, 4);
|
||||
a = loaded(0); LIT(PB + 0); O(BANG_B); LIT(5); O(STORE_B); wait_for_ever(); /* writes to its port 0 */
|
||||
c = loaded(1); LIT(PB + 1); O(BANG_B); O(FETCH_B); LIT(OUT); O(BANG_A); O(STORE_A); wait_for_ever(); /* reads its port 1 */
|
||||
for (i = 0; i < 10; i++) (void)v4_fabric_step(&f);
|
||||
CHECK(ND(a)->n.asking && ND(c)->n.reading, "one node is waiting to write and another to read");
|
||||
b = loaded(2);
|
||||
LIT(PB + (v4_cell)V4_PORTS + 2); O(BANG_B); O(FETCH_B); LIT(OUT); O(BANG_A); O(STORE_A);
|
||||
LIT(PB + (v4_cell)V4_PORTS + 3); O(BANG_B); O(FETCH_B); LIT(OUT + 1); O(BANG_A); O(STORE_A);
|
||||
wait_for_ever();
|
||||
CHECK(v4_fabric_wire(&f, b, 2, a, 0) && v4_fabric_wire(&f, b, 4, c, 1), "a third is wired to both");
|
||||
{
|
||||
static stream none;
|
||||
static v4_device sink;
|
||||
sink.take = stream_take; sink.give = 0; sink.self = &none;
|
||||
CHECK(v4_fabric_wire_device(&f, b, 6, &sink), "and to a device that takes what is written to it");
|
||||
}
|
||||
for (i = 0; i < 20; i++) (void)v4_fabric_step(&f);
|
||||
CHECK(ND(b)->n.mem[OUT] == (1 << 2), "it sees which of its ports has a neighbour waiting to write to it: %ld", (long)ND(b)->n.mem[OUT]);
|
||||
CHECK(ND(b)->n.mem[OUT + 1] == ((1 << 4) | (1 << 6)), "and which have one waiting to read from it, or a device: %ld", (long)ND(b)->n.mem[OUT + 1]);
|
||||
CHECK(ND(a)->n.asking && ND(a)->n.request == 5 && ND(c)->n.reading && ND(c)->n.mem[OUT] == 0, "looking takes nothing and gives nothing: both wait as they did");
|
||||
CHECK(!ND(b)->n.stopped && ND(b)->n.reading, "and the one that looked did not wait to look");
|
||||
|
||||
printf(" %d checks, %d failures\n", checks, failures);
|
||||
return failures != 0;
|
||||
}
|
||||
|
||||
+62
-15
@@ -14,6 +14,7 @@
|
||||
#include "v4/message.h"
|
||||
#include "v4/text.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "host_map.h"
|
||||
@@ -126,7 +127,7 @@ int main(void)
|
||||
nucleus.mem[CURRENT] = LATEST;
|
||||
nucleus.mem[SRC] = TIB;
|
||||
nucleus.mem[LINE_STATUS] = 1;
|
||||
nucleus.mem[OUT_PTR] = OUT_W;
|
||||
HOST_MESSAGES_EMPTY(&nucleus);
|
||||
|
||||
v4_fabric_init(&f, places, 3);
|
||||
hera = starforth_node(0);
|
||||
@@ -196,22 +197,68 @@ int main(void)
|
||||
CHECK(strcmp(tell(12, "5 5 + ."), "10 ") == 0 && pool[1].es.anticlock == before, "text for it no longer passes through the middle node, which executes nothing");
|
||||
}
|
||||
|
||||
/* ---- WHAT DOES NOT WORK: two neighbours that write to each other at once ----
|
||||
/* ---- two neighbours with something for each other at the same moment ----
|
||||
* A write blocks until the neighbour reads, and a node that is writing
|
||||
* is not reading. Here the far node sends text to the middle one and
|
||||
* then goes to send word of how its own text ended, which goes by the
|
||||
* middle one; the middle one does the text and goes to answer the far
|
||||
* one. Each waits for the other to read, for ever. It is the same
|
||||
* whenever messages go both ways on a wire at once, and it is because
|
||||
* passing a message on is done here by the node's own programme
|
||||
* (v4/capsule/quit.v4, (PASS-ON)), which cannot pass anything on while
|
||||
* it waits to write. docs/v4.0.0/MESH.md, step 4. This check is here
|
||||
* so that the fault is on record and is seen to go when it is put
|
||||
* right. */
|
||||
* is not reading: so a node looks before it begins a message, and of
|
||||
* two neighbours only the one with the lower number may wait to write
|
||||
* to the other (v4/capsule/core.v4, (GATE); docs/v4.0.0/MESH.md 7a).
|
||||
* Each node is told who is on each of its ports. */
|
||||
CHECK(is_empty(tell(10, "NO-ROUTES 1 1 ROUTE 11 2 ROUTE 12 2 ROUTE")) && is_empty(tell(12, "NO-ROUTES 2 DEFAULT-ROUTE")), "the row of three again");
|
||||
(void)tell(12, ": HI S\" 72 EMIT\" 11 SEND ; HI");
|
||||
CHECK(pool[1].n.asking && pool[1].n.ask_port == 3 && pool[2].n.asking && pool[2].n.ask_port == 2,
|
||||
"KNOWN FAULT: two neighbours each writing to the other wait for ever");
|
||||
CHECK(is_empty(tell(10, "11 2 NEIGHBOUR 0 3 NEIGHBOUR")) && is_empty(tell(11, "10 2 NEIGHBOUR 12 3 NEIGHBOUR")) && is_empty(tell(12, "11 2 NEIGHBOUR 0 3 NEIGHBOUR")),
|
||||
"each is told who is on the other end of its ports");
|
||||
CHECK(strstr(tell(10, "11 8 NEIGHBOUR"), "Argument out of range") != NULL && strstr(tell(10, "11 -1 NEIGHBOUR"), "Argument out of range") != NULL,
|
||||
"a port a node has not got is an error");
|
||||
/* the far node sends the middle one text, and then word of how its own
|
||||
* text ended, which goes by the middle one; the middle one does the
|
||||
* text and answers the far one: the two messages meet on one wire */
|
||||
CHECK(strcmp(tell(12, ": HI S\" 72 EMIT\" 11 SEND ; HI"), "H") == 0 && printed_from == 11 && ended == 1 && ended_from == 12,
|
||||
"from the far end to the middle, with the answers crossing: \"%s\" from %ld", printed, (long)printed_from);
|
||||
CHECK(waiting(hera) && waiting(mid) && waiting(far), "and all three are waiting again");
|
||||
|
||||
/* every node sends every other a number of messages, all at once: each
|
||||
* counts what it is sent */
|
||||
CHECK(is_empty(tell(10, "VARIABLE GOT : HIT 1 GOT +! ; VARIABLE WHO VARIABLE SENT : BURST WHO ! 0 DO S\" HIT\" WHO @ SEND 1 SENT +! LOOP ;")) &&
|
||||
is_empty(tell(11, "VARIABLE GOT : HIT 1 GOT +! ; VARIABLE WHO VARIABLE SENT : BURST WHO ! 0 DO S\" HIT\" WHO @ SEND 1 SENT +! LOOP ;")) &&
|
||||
is_empty(tell(12, "VARIABLE GOT : HIT 1 GOT +! ; VARIABLE WHO VARIABLE SENT : BURST WHO ! 0 DO S\" HIT\" WHO @ SEND 1 SENT +! LOOP ;")), "each node is given a word to count with and one to send with");
|
||||
{
|
||||
const char *r = tell(10, ": GO S\" 6 10 BURST 6 12 BURST\" 11 SEND S\" 6 11 BURST 6 10 BURST\" 12 SEND 6 11 BURST 6 12 BURST ; GO");
|
||||
CHECK(is_empty(r), "six messages from each node to each other, all at once: \"%s\"", r);
|
||||
CHECK(waiting(hera) && waiting(mid) && waiting(far), "all three come to rest");
|
||||
CHECK(strcmp(tell(10, "GOT @ . (LOST) @ ."), "12 1 ") == 0 && strcmp(tell(11, "GOT @ . (LOST) @ ."), "12 0 ") == 0 && strcmp(tell(12, "GOT @ . (LOST) @ ."), "12 0 ") == 0,
|
||||
"every one of them arrived, and none was let go: \"%s\"", printed);
|
||||
}
|
||||
|
||||
/* ---- more than a node has room to keep ----
|
||||
* A node that takes messages in while it waits to write keeps them, and
|
||||
* when it has no room for one more it lets that one go and counts it.
|
||||
* Far more are sent here than the nodes can keep. */
|
||||
{
|
||||
long got = 0, lost = 0, sent = 0, v;
|
||||
unsigned k;
|
||||
static const v4_cell who[3] = { 10, 11, 12 };
|
||||
const char *r;
|
||||
CHECK(is_empty(tell(10, "0 GOT ! 0 SENT !")) && is_empty(tell(11, "0 GOT ! 0 SENT !")) && is_empty(tell(12, "0 GOT ! 0 SENT !")), "the counts begin again");
|
||||
r = tell(10, ": GO2 S\" 200 10 BURST 200 12 BURST\" 11 SEND S\" 200 11 BURST 200 10 BURST\" 12 SEND 200 11 BURST 200 12 BURST ; GO2");
|
||||
CHECK(strcmp(r, "(still running)") != 0, "two hundred from each to each: it ends");
|
||||
CHECK(waiting(hera) && waiting(mid) && waiting(far), "and all three come to rest, none waiting to write");
|
||||
for (k = 0; k < 3; k++) {
|
||||
r = tell(who[k], "GOT @ .");
|
||||
v = strtol(r, NULL, 10); got += v;
|
||||
CHECK(v > 0 && v <= 400, "node %ld took %ld of the 400 for it", (long)who[k], v);
|
||||
r = tell(who[k], "(LOST) @ .");
|
||||
lost += strtol(r, NULL, 10);
|
||||
r = tell(who[k], "SENT @ .");
|
||||
sent += strtol(r, NULL, 10);
|
||||
CHECK(pool[k].n.mem[MQ_COUNT] == 0, "node %ld has none left waiting", (long)who[k]);
|
||||
}
|
||||
lost -= 1; /* the one for node 99, earlier */
|
||||
/* what is let go is of every kind: the text that would have set a
|
||||
* node sending, the answers to text, as well as what was sent */
|
||||
printf(" of %ld messages the nodes sent each other at once %ld arrived; %ld messages of all kinds were let go for want of room\n", sent, got, lost);
|
||||
CHECK(lost > 0, "there was not room for all of them");
|
||||
CHECK(got <= sent && got + lost >= sent, "every message sent either arrived or was counted: %ld sent, %ld arrived, %ld let go", sent, got, lost);
|
||||
CHECK(strcmp(tell(12, "7 8 * ."), "56 ") == 0 && printed_from == 12, "and the nodes go on as before");
|
||||
}
|
||||
|
||||
printf(" %d checks, %d failures\n", checks, failures);
|
||||
return failures != 0;
|
||||
|
||||
@@ -160,7 +160,7 @@ 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. */
|
||||
static int node_idle(void)
|
||||
{
|
||||
return n.reading && !n.given && n.read_port == V4_PORT_ANY && n.p > w_idle && n.p <= w_idle + 3;
|
||||
return n.reading && !n.given && n.read_port == V4_PORT_ANY && n.p > w_idle && n.p <= w_idle + 8; /* in the first words of (IDLE), where it reads */
|
||||
}
|
||||
|
||||
/* Run until the text ends, or until it has been inside KEY with nothing to
|
||||
@@ -239,20 +239,21 @@ 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); /* the kernel and the console take what is written when it is written */
|
||||
n.mem[WORD_DEFINED] = 0; /* no one is told of entries, until a test says so */
|
||||
n.mem[WORD_FORGOTTEN] = 0;
|
||||
known_n = 0;
|
||||
last_request = 0;
|
||||
n.mem[LINE_STATUS] = 1;
|
||||
n.mem[ME] = 0; /* it has no number yet: a message to 0 is for it */
|
||||
n.mem[OUT_PTR] = OUT_W;
|
||||
HOST_MESSAGES_EMPTY(&n);
|
||||
n.mem[REPLY] = PORT + 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;
|
||||
shown_len = 0; shown_dropped = 0;
|
||||
(void)v4_exec_step_word(&n, &es, &h); /* it reads its ports, and is blocked */
|
||||
for (i = 0; i < 8 && !n.reading; i++) (void)v4_exec_step_word(&n, &es, &h); /* no message waits: it reads its ports, and is blocked */
|
||||
}
|
||||
static void boot(void) { boot_with(0); }
|
||||
/* The same with nothing at all on the data stack. */
|
||||
|
||||
Reference in New Issue
Block a user