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
+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;
}