feat(v4.0.0): the wait in the engine -- a node offers a word and sleeps

A node stores a word to offer on any of its ports and fetches the wait: it
is blocked until a word comes for it or one of its offers is taken, and
the fabric does the handing over in one step.  The port block moves in
the nucleus's map to make room for the new addresses.  Nothing uses it
yet.  MESH.md 7c; plan step 6d task 1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-07 23:15:11 -04:00
co-authored by Claude Opus 5.5
parent dfabfa46f6
commit 075385ab63
6 changed files with 275 additions and 8 deletions
+3 -1
View File
@@ -73,7 +73,7 @@
#define OWED (BUF0_W - 112) /* those: for each, the node it is owed to and the node the refused message was for */
#define ACL_HOOK (BUF0_W - 6) /* the xt of the access control recheck word, or 0 */
#define LINE_STATUS (BUF0_W - 9) /* how the last line ended: 0 QUIT, 1 completed, 2 an error */
#define PORT (BUF0_W - 32) /* the node's ports (node.h): V4_PORTS of them, then "any port" and "which port". Port 0 is where its requests go. */
#define PORT (BUF0_W - 144) /* the node's ports (node.h): V4_PORTS of them, then "any port", "which port", who writes, who reads, an offer for each port, and the wait: 2 * V4_PORTS + 5 cells, between the output buffer and A_FOUND. Port 0 is where its requests go. */
#define CONSOLE (BUF0_W - 16) /* the node what this one prints is sent to; 0: whoever sent the text being served */
#define ROUTE_COUNT (BUF0_W - 17) /* how many entries the table of ways holds */
#define ROUTE_DEFAULT (BUF0_W - 18) /* the port address for a node not in the table, or 0: there is none */
@@ -124,6 +124,8 @@ typedef char host_map_ports_fit[(V4_PORTS <= 8u) ? 1 : -1];
#endif
/* the messages waiting are above the dictionary */
typedef char host_map_queue_fits[(MQ_W >= DICT_END_W) ? 1 : -1];
/* the ports, with the offers and the wait after them, end below the cells that follow */
typedef char host_map_port_block_fits[(PORT + 2 * (v4_cell)V4_PORTS + 5 <= BUF0_W - 115) ? 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. */
+149 -1
View File
@@ -15,7 +15,7 @@ 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 where the ports are. */
#define PB ((v4_cell)(V4_NODE_WORDS - 16u)) /* port 0 */
#define PB ((v4_cell)(V4_NODE_WORDS - 32u)) /* port 0: the ports and what follows them end below the top of memory */
#define ANY (PB + (v4_cell)V4_PORT_ANY)
#define ORG ((v4_cell)16) /* where a test's programme starts */
#define OUT ((v4_cell)900) /* where it leaves what it found */
@@ -45,6 +45,31 @@ static unsigned loaded(unsigned k)
/* the programme ends by waiting at a port nothing is wired to */
static void wait_for_ever(void) { LIT(PB + 7); O(BANG_B); O(FETCH_B); (void)v4_asm_label(&as); } /* and the last word is written out */
/* ---- the wait (MESH.md 7c) ---- */
static void fresh(void)
{
unsigned k;
v4_fabric_init(&f, places, 4);
for (k = 0; k < 4; k++) v4_node_reset(&pool[k].n);
}
/* offer `word` on port k */
static void offer(unsigned k, v4_cell word) { LIT(word); LIT(PB + (v4_cell)V4_PORT_OFFER + (v4_cell)k); O(BANG_A); O(STORE_A); }
/* the wait: what it gives goes to `at`, and which port to `at + 1` */
static void wait_into(v4_cell at)
{
LIT(PB + (v4_cell)V4_PORT_WAIT); O(BANG_B); O(FETCH_B); LIT(at); O(BANG_A); O(STORE_A);
LIT(PB + (v4_cell)V4_PORTS + 1); O(BANG_B); O(FETCH_B); LIT(at + 1); O(BANG_A); O(STORE_A);
}
static void done_mark(void) { LIT(1); LIT(OUT + 2); O(BANG_A); O(STORE_A); }
static void count_for_ever(void)
{
v4_cell again;
LIT(OUT); O(BANG_A);
again = v4_asm_label(&as);
O(FETCH_A); LIT(1); O(ADD); O(STORE_A);
v4_asm_branch(&as, V4_OP_JUMP, again);
}
/* a device that says whether it has something pending */
static int *dev_has;
static int device_has(void *self) { (void)self; return *dev_has; }
@@ -423,6 +448,129 @@ int main(void)
CHECK(ND(a)->n.asking && ND(a)->n.request == 2 && ND(a)->n.faults == 0, "blocked on its second word with the device still pending, the writer waits");
}
/* ---- the wait (MESH.md 7c): a node offers a word and sleeps ---- */
{
static stream none;
static v4_device sink;
v4_uheat_t clock;
sink.take = stream_take; sink.give = 0; sink.self = &none;
/* taken by a reader */
fresh();
a = loaded(0); offer(0, 7); wait_into(OUT); done_mark(); wait_for_ever();
b = loaded(1); LIT(PB + 3); O(BANG_B); O(FETCH_B); LIT(OUT); O(BANG_A); O(STORE_A); wait_for_ever();
CHECK(v4_asm_ok(&as) && v4_fabric_wire(&f, a, 0, b, 3), "a node that offers, wired to one that reads");
(void)settle(300);
CHECK(ND(b)->n.mem[OUT] == 7, "the reader has the word offered: %ld", (long)ND(b)->n.mem[OUT]);
CHECK(ND(a)->n.mem[OUT] == 0 && ND(a)->n.mem[OUT + 1] == (v4_cell)V4_PORTS + 0 && ND(a)->n.mem[OUT + 2] == 1 && ND(a)->n.offers == 0,
"the one that offered is told its offer on port 0 was taken, and has none left: %ld from %ld", (long)ND(a)->n.mem[OUT], (long)ND(a)->n.mem[OUT + 1]);
/* taken by a device */
fresh(); none.took_n = 0;
a = loaded(0); offer(0, 7); wait_into(OUT); done_mark(); wait_for_ever();
CHECK(v4_fabric_wire_device(&f, a, 0, &sink), "the same, wired to a device that takes what is written");
(void)settle(300);
CHECK(none.took_n == 1 && none.took[0] == 7 && ND(a)->n.mem[OUT + 1] == (v4_cell)V4_PORTS + 0 && ND(a)->n.mem[OUT + 2] == 1, "the device has the word, and the node is told");
/* a neighbour that never reads */
fresh();
a = loaded(0); offer(0, 7); wait_into(OUT); done_mark(); wait_for_ever();
b = loaded(1); count_for_ever();
CHECK(v4_asm_ok(&as) && v4_fabric_wire(&f, a, 0, b, 3), "a node that offers, wired to one that never reads");
for (i = 0; i < 200; i++) (void)v4_fabric_step(&f);
clock = ND(a)->es.anticlock;
for (i = 0; i < 100; i++) (void)v4_fabric_step(&f);
CHECK(v4_node_in_wait(&ND(a)->n) && ND(a)->es.anticlock == clock && ND(a)->n.mem[OUT + 2] == 0, "it is in the wait, and executes nothing there");
CHECK((ND(b)->n.readers & (1u << 3)) != 0, "and shows to its neighbour as waiting to read");
/* withdrawn by a word that comes for it */
c = loaded(2); LIT(PB + 0); O(BANG_B); LIT(9); O(STORE_B); wait_for_ever();
CHECK(v4_asm_ok(&as) && v4_fabric_wire(&f, c, 0, a, 2), "a third node writes to it");
for (i = 0; i < 100; i++) (void)v4_fabric_step(&f);
CHECK(ND(a)->n.mem[OUT] == 9 && ND(a)->n.mem[OUT + 1] == 2 && ND(a)->n.mem[OUT + 2] == 1 && ND(a)->n.offers == 0,
"it gets that word, from that port, and its offer is withdrawn: %ld from %ld", (long)ND(a)->n.mem[OUT], (long)ND(a)->n.mem[OUT + 1]);
CHECK(!ND(b)->n.given && !ND(c)->n.asking, "the node offered to was given nothing, and the writer was served");
/* an offer that could be taken in the step a word comes: the word comes first */
fresh();
a = loaded(0); offer(0, 7); wait_into(OUT); offer(0, 7); wait_into(OUT + 3); done_mark(); wait_for_ever();
b = loaded(1); LIT(PB + 3); O(BANG_B); O(FETCH_B); LIT(OUT); O(BANG_A); O(STORE_A); wait_for_ever();
c = loaded(2); LIT(PB + 0); O(BANG_B); LIT(9); O(STORE_B); wait_for_ever();
CHECK(v4_asm_ok(&as) && v4_fabric_wire(&f, a, 0, b, 3) && v4_fabric_wire(&f, c, 0, a, 2), "one offers to a reader while a third writes to it");
v4_fabric_sleep(&f, a);
for (i = 0; i < 50; i++) (void)v4_fabric_step(&f);
CHECK(ND(b)->n.reading && ND(c)->n.asking, "the reader and the writer are both blocked");
v4_fabric_sleep(&f, b); v4_fabric_sleep(&f, c); v4_fabric_wake(&f, a);
for (i = 0; i < 50; i++) (void)v4_fabric_step(&f);
CHECK(v4_node_in_wait(&ND(a)->n), "and then the one that offers is in the wait");
v4_fabric_wake(&f, b); v4_fabric_wake(&f, c);
(void)v4_fabric_step(&f);
CHECK(!v4_node_in_wait(&ND(a)->n) && !ND(c)->n.asking && ND(b)->n.reading && !ND(b)->n.given, "in one step: the word for it comes first, and the reader is given nothing");
(void)settle(300);
CHECK(ND(a)->n.mem[OUT] == 9 && ND(a)->n.mem[OUT + 1] == 2 && ND(a)->n.mem[OUT + 3] == 0 && ND(a)->n.mem[OUT + 4] == (v4_cell)V4_PORTS + 0 && ND(b)->n.mem[OUT] == 7,
"it offers again, and the reader gets the word once");
/* two nodes each offering to the other */
fresh();
a = loaded(0); offer(0, 5); wait_into(OUT); done_mark(); wait_for_ever();
b = loaded(1); offer(3, 6); wait_into(OUT); done_mark(); wait_for_ever();
CHECK(v4_asm_ok(&as) && v4_fabric_wire(&f, a, 0, b, 3), "two nodes each offer to the other");
(void)settle(300);
CHECK(ND(a)->n.mem[OUT] == 0 && ND(a)->n.mem[OUT + 1] == (v4_cell)V4_PORTS + 0, "the one at the lower place has its offer taken");
CHECK(ND(b)->n.mem[OUT] == 5 && ND(b)->n.mem[OUT + 1] == 3, "the other gets the word, from the port it came by");
CHECK(ND(a)->n.offers == 0 && ND(b)->n.offers == 0 && ND(a)->n.mem[OUT + 2] == 1 && ND(b)->n.mem[OUT + 2] == 1, "and neither has an offer left");
/* three in a ring, each offering to the next */
fresh();
a = loaded(0); offer(0, 11); wait_into(OUT); done_mark(); wait_for_ever();
b = loaded(1); offer(0, 12); wait_into(OUT); done_mark(); wait_for_ever();
c = loaded(2); offer(0, 13); wait_into(OUT); done_mark(); wait_for_ever();
CHECK(v4_asm_ok(&as) && v4_fabric_wire(&f, a, 0, b, 1) && v4_fabric_wire(&f, b, 0, c, 1) && v4_fabric_wire(&f, c, 0, a, 1), "three nodes in a ring, each offering to the next");
CHECK(settle(1000) < 1000, "they come to rest");
CHECK(ND(a)->n.mem[OUT + 1] == (v4_cell)V4_PORTS + 0 && ND(b)->n.mem[OUT] == 11 && ND(b)->n.mem[OUT + 1] == 1, "the first's offer was taken by the second");
CHECK(v4_node_in_wait(&ND(c)->n) && ND(c)->n.offers == 1u && ND(c)->n.mem[OUT + 2] == 0 && !ND(a)->n.given, "the third still offers to the first, which is not reading: nothing was handed over twice");
/* a neighbour that is asleep */
fresh();
a = loaded(0); offer(0, 7); wait_into(OUT); done_mark(); wait_for_ever();
b = loaded(1); LIT(PB + 3); O(BANG_B); O(FETCH_B); LIT(OUT); O(BANG_A); O(STORE_A); wait_for_ever();
CHECK(v4_asm_ok(&as) && v4_fabric_wire(&f, a, 0, b, 3), "a node that offers, wired to a reader");
v4_fabric_sleep(&f, a);
for (i = 0; i < 50; i++) (void)v4_fabric_step(&f);
v4_fabric_sleep(&f, b); v4_fabric_wake(&f, a);
for (i = 0; i < 100; i++) (void)v4_fabric_step(&f);
CHECK(v4_node_in_wait(&ND(a)->n) && ND(b)->n.reading && !ND(b)->n.given, "while the reader is asleep the offer stands");
v4_fabric_wake(&f, b);
(void)settle(300);
CHECK(ND(b)->n.mem[OUT] == 7 && ND(a)->n.mem[OUT + 2] == 1, "woken, it takes it");
/* a port with nothing on it */
fresh();
a = loaded(0); offer(5, 7); wait_into(OUT); done_mark(); wait_for_ever();
CHECK(v4_asm_ok(&as), "a node that offers on a port nothing is wired to");
for (i = 0; i < 50; i++) (void)v4_fabric_step(&f);
v4_fabric_gone_error(&f, 18);
for (i = 0; i < 50; i++) (void)v4_fabric_step(&f);
CHECK(v4_node_in_wait(&ND(a)->n) && ND(a)->n.faults == 0, "a bare node waits, though the fabric has an error for it");
v4_node_error_attach(&ND(a)->n, OUT + 20);
v4_node_fault_attach(&ND(a)->n, 900);
(void)v4_fabric_step(&f);
CHECK(!v4_node_in_wait(&ND(a)->n) && ND(a)->n.fault_kind == V4_FAULT_RAISED && ND(a)->n.fault_addr == 18 && ND(a)->n.offers == 0,
"one that can take an error has it raised, and its offer is withdrawn");
/* a host with one node, whose devices always take */
fresh();
a = loaded(0); offer(1, 21); offer(3, 23); wait_into(OUT); done_mark(); wait_for_ever();
for (i = 0; i < 50; i++) (void)v4_fabric_step(&f);
CHECK(v4_node_in_wait(&ND(a)->n) && ND(a)->n.offers == ((1u << 1) | (1u << 3)), "a node offers on two ports and is in the wait");
CHECK(v4_node_offer_as_write(&ND(a)->n) && ND(a)->n.asking && ND(a)->n.ask_port == 1 && ND(a)->n.request == 21, "its host turns that into a write of the lower port's word");
v4_node_port_served(&ND(a)->n);
for (i = 0; i < 50; i++) (void)v4_fabric_step(&f);
CHECK(!ND(a)->n.asking && ND(a)->n.mem[OUT] == 0 && ND(a)->n.mem[OUT + 1] == (v4_cell)V4_PORTS + 1 && ND(a)->n.mem[OUT + 2] == 1 && ND(a)->n.offers == 0,
"and when it is served the node is told that offer was taken");
CHECK(!v4_node_offer_as_write(&ND(a)->n), "a node that is not in the wait is left alone");
}
printf(" %d checks, %d failures\n", checks, failures);
return failures != 0;
}