fix(v4.0.0): wire review round 1 -- tests for the wake-up order and for waking only on enough room; no overflow moving a message; sleep for impossible room answers at once

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-08 14:12:33 -04:00
co-authored by Claude Opus 5.5
parent d5cd64605d
commit 83694b18be
6 changed files with 68 additions and 6 deletions
+1 -1
View File
@@ -148,7 +148,7 @@ void v4_fabric_wake(v4_fabric *f, unsigned id);
* Sleep (8) and sleep for room (9) are the two that wait: the node stays
* blocked, executing nothing, until a message has come to one of its wires
* since it last fetched which wires have one -- or, for 9, until its wire
* has the room. A message has "come" when a put or a move or a device put
* has the room (a wire that can never have the room answers 1 at once). A message has "come" when a put or a move or a device put
* put it on the back of a queue; fetching Have clears that for all of the
* node's wires.
*
+3
View File
@@ -30,6 +30,9 @@
#define V4_WIRE_HEADER 7u /* the words of a message that are not its text */
/* a wire must be able to hold the largest message there is: 1,024 characters */
typedef char v4_wire_holds_a_message[(V4_WIRE_CELLS >= 7 + 1024 / 4) ? 1 : -1];
typedef struct {
v4_cell cell[V4_WIRE_CELLS]; /* the messages, one after another, going round */
unsigned head; /* where the front message begins */
+2 -2
View File
@@ -370,8 +370,8 @@ static int operate(v4_fabric *f, unsigned id, v4_cell *how)
break;
case 9: /* sleep for room */
if (!tx) { *how = V4_WIRE_NO_ONE; break; }
if (a < 0) { *how = V4_WIRE_NOT_A_MESSAGE; break; }
if (a <= (v4_cell)V4_WIRE_CELLS && v4_wire_room(tx, (unsigned)a) == V4_WIRE_DONE) { *how = V4_WIRE_DONE; break; }
if (a < 0 || a > (v4_cell)V4_WIRE_CELLS) { *how = V4_WIRE_NO_ROOM; break; } /* no waiting can make that room */
if (v4_wire_room(tx, (unsigned)a) == V4_WIRE_DONE) { *how = V4_WIRE_DONE; break; }
if (!arrived(f, id)) return 0;
*how = V4_WIRE_NO_ROOM;
break;
+1 -1
View File
@@ -118,7 +118,7 @@ int v4_wire_move(v4_wire_queue *from, v4_wire_queue *to)
if (from == to || v4_wire_room(to, n) != V4_WIRE_DONE) return V4_WIRE_NO_ROOM;
at = back(to, n);
for (i = 0; i < n; i++) to->cell[slot(to, at + i)] = from->cell[slot(from, from->mark + i)];
to->cell[slot(to, at + 3u)] = (v4_cell)(from->cell[slot(from, from->mark + 3u)] - 1);
to->cell[slot(to, at + 3u)] = (v4_cell)((v4_ucell)from->cell[slot(from, from->mark + 3u)] - 1u); /* in the unsigned type: no overflow at the least cell */
remove_marked(from);
return V4_WIRE_DONE;
}
+41 -2
View File
@@ -684,6 +684,23 @@ int main(void)
(void)v4_fabric_step(&f);
CHECK(!ND(a)->n.doing && ND(a)->n.how == 0, "and wakes it");
/* a message put in the same engine step as the node's fetch of "which wires" is not lost: the next sleep does not block */
v4_fabric_init(&f, places, 4);
v4_fabric_queues(&f, qpool, 8);
a = (unsigned)v4_fabric_add(&f, &pool[0], PB);
b = (unsigned)v4_fabric_add(&f, &pool[1], PB);
CHECK(v4_fabric_wire(&f, a, 0, b, 3), "two nodes for the same-step put");
(void)v4_node_fetch(&ND(a)->n, P_HAVE); /* the fetch is seen in the step's execute phase ... */
put_msg(&ND(b)->n, 100, 110, 3, 1);
v4_node_store(&ND(b)->n, P_WIRE, 3);
v4_node_store(&ND(b)->n, P_A, 100);
v4_node_store(&ND(b)->n, P_B, 110);
v4_node_store(&ND(b)->n, P_DO, 5); /* ... and the put is done in the same step's operations */
(void)v4_fabric_step(&f);
CHECK(!ND(b)->n.doing && ND(b)->n.how == 0 && f.place[a].wire[0].rx->used == 8 && f.place[a].wire[0].rx->arrived != 0,
"the put and the fetch were in one step: the message is on the wire and counts as arrived");
CHECK(ask(a, 0, 0, 0, 8) == 0, "so the sleep that follows does not block");
/* sleep for room */
v4_fabric_init(&f, places, 4);
v4_fabric_queues(&f, qpool, 8);
@@ -695,18 +712,40 @@ int main(void)
"room, with 100 cells free: for 50 and for 100, not for 101");
CHECK(ask(a, 5, 10, 0, 10) == 2 && ask(a, 0, -1, 0, 10) == 4, "room on a port with nothing is 2, for -1 cells 4");
CHECK(f.place[a].wire[0].tx->used == V4_WIRE_CELLS - 100 && !ND(a)->n.doing, "and it changes nothing");
CHECK(ask(a, 0, V4_WIRE_CELLS + 1, 0, 9) == 1 && ask(a, 0, 100000, 0, 9) == 1 && ask(a, 0, -1, 0, 9) == 1 && !ND(a)->n.doing,
"sleep for more room than the wire can ever have answers 1 at once, not blocked");
(void)v4_node_fetch(&ND(a)->n, P_HAVE);
(void)v4_fabric_step(&f);
CHECK(ask(a, 0, 200, 0, 9) == -99 && ND(a)->n.doing, "sleep for 200 cells with 100 free: blocked");
for (i = 0; i < 100; i++) (void)v4_fabric_step(&f);
CHECK(ND(a)->n.doing, "and still blocked");
v4_wire_first(f.place[b].wire[3].rx);
CHECK(ask(b, 3, 400, 420, 4) == 0, "the reader drops one message (which takes a few cells)");
CHECK(ND(a)->n.doing || f.place[a].wire[0].tx->used + 200 <= V4_WIRE_CELLS, "woken when the reader has taken enough, not before");
CHECK(ask(b, 3, 400, 420, 4) == 0, "the reader drops the front message (which is 263 cells)");
while (ND(a)->n.doing && f.place[b].wire[3].rx->used > 0) {
(void)ask(b, 3, 400, 420, 4);
}
CHECK(!ND(a)->n.doing && ND(a)->n.how == 0 && f.place[a].wire[0].tx->used + 200 <= V4_WIRE_CELLS, "woken, with how it went 0");
/* a small message in front: dropping it frees little, and the sleeper wants more than that leaves */
v4_wire_reset(f.place[a].wire[0].tx);
v4_wire_reset(f.place[b].wire[3].rx);
{
v4_cell hh[7] = { 1, 2, 3, 4, 5, 6, 12 };
static v4_cell tt[V4_WIRE_CELLS];
(void)v4_wire_put(f.place[a].wire[0].tx, hh, tt); /* 10 cells */
hh[6] = 1024;
for (i = 0; i < 3; i++) (void)v4_wire_put(f.place[a].wire[0].tx, hh, tt);
hh[6] = 4 * (35 - 7);
(void)v4_wire_put(f.place[a].wire[0].tx, hh, tt); /* 35 cells: 190 are free */
}
CHECK(V4_WIRE_CELLS - f.place[a].wire[0].tx->used == 190, "a wire with 190 free and a small message in front");
(void)v4_node_fetch(&ND(a)->n, P_HAVE);
(void)v4_fabric_step(&f);
CHECK(ask(a, 0, 250, 0, 9) == -99, "asleep for 250");
v4_wire_first(f.place[b].wire[3].rx);
CHECK(ask(b, 3, 400, 420, 4) == 0 && V4_WIRE_CELLS - f.place[a].wire[0].tx->used == 200, "the reader drops the small one: 200 are free");
for (i = 0; i < 50; i++) (void)v4_fabric_step(&f);
CHECK(ND(a)->n.doing, "and the sleeper, which wants 250, is not woken by a take that did not give it room");
CHECK(ask(b, 3, 400, 420, 4) == 0 && !ND(a)->n.doing && ND(a)->n.how == 0, "the next take does, with 0");
/* woken by a message coming instead */
v4_wire_reset(f.place[a].wire[0].tx);
fill(f.place[a].wire[0].tx, 100);
+20
View File
@@ -235,6 +235,26 @@ int main(void)
for (n = 0; n < 9 && t[n] == wt[n]; n++) { }
CHECK(n == 9, "and every word of its text");
}
/* the fourth word is one fewer for every value, the most negative cell included (no overflow) */
{
const v4_cell least = (v4_cell)((v4_ucell)1 << (V4_CELL_BITS - 1));
const v4_cell most = (v4_cell)(((v4_ucell)1 << (V4_CELL_BITS - 1)) - 1u);
static const int which[3] = { 0, 1, 2 };
unsigned m;
for (m = 0; m < 3; m++) {
v4_wire_reset(&q); v4_wire_reset(&other);
make(h, t, 8, 5);
h[3] = which[m] == 0 ? least : which[m] == 1 ? 0 : most;
CHECK(v4_wire_put(&q, h, t) == V4_WIRE_DONE, "(a message whose fourth word is an extreme)");
v4_wire_first(&q);
CHECK(v4_wire_move(&q, &other) == V4_WIRE_DONE, "moved");
v4_wire_first(&other);
CHECK(v4_wire_take(&other, h2, t2, V4_WIRE_CELLS) == V4_WIRE_DONE, "and taken");
CHECK(h2[3] == (which[m] == 0 ? most : which[m] == 1 ? (v4_cell)-1 : (v4_cell)(most - 1)),
"its fourth word is one fewer, wrapping at the least cell: %ld", (long)h2[3]);
}
}
/* no room in the other: 1, and neither changes */
v4_wire_reset(&q); v4_wire_reset(&other);
ok = 1;