fix(v4.0.0): step 6e review round 2 -- only the message looked at is taken, moved or dropped; the kernel's port has no queue in a fabric; an answer nobody awaits is not a loss

- Engine: a wire keeps whether the message at its mark is the one last
  looked at; a node's take, move and drop of any other answer 3 and write
  nothing (MESH.md 7d.3).  A wire cut and made again between a look and a
  take no longer has a NACK done as text, or 256 cells written where one
  was looked for.
- Engine: v4_fabric_wire_kernel wires a kernel to a port with no queues;
  a message for it is refused at once, error 18, on a mesh as on the
  products.
- The result of a drop is looked at; a take that does not take has the
  message dropped, or gone past: nothing goes round.
- An answer nobody waits for is let go uncounted; (LOST) counts what should
  have been delivered or acted on and was not.  The flood's account is
  exact.
- Error 18 from under a word that was printing empties the data stack, as
  a fault does: that word's own values are not left there.
- Tests: the take and drop windows cut at every step, with and without
  another message put; a refusal owed to a stuck node whose wire is full.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-08 22:36:59 -04:00
co-authored by Claude Opus 5.5
parent 6a7d0aa27f
commit f1419e739c
15 changed files with 345 additions and 63 deletions
Binary file not shown.
+17 -3
View File
@@ -800,6 +800,17 @@ the engine and happens whole.
- Each wire has a mark for the node that reads it: the message that
look, take, move and drop are about. First and next move it; with no
message there, those four answer 3.
- **Take, move and drop are done only on the message last looked at.**
A node looks, decides, and then takes; the wire may have been cut and
made again in between, and another message put where that one was. So
the wire keeps whether the message at its mark is the one last looked
at: a look that finds a message makes it so, and first, next, the
message's going, and the cutting of the wire undo it. Take, move and
drop of anything else answer 3 and change nothing, and a take writes
the cells of the message looked at and no more. *(Added 2026-10-08, fix
round 2 of tasks 2 and 3: a NACK was done as text, and a text of 1,024
characters taken where a NACK had been looked at overwrote the node's
variables.)*
- Only 8 and 9 block. A node blocked there executes nothing.
- **Sleep is woken by a message coming, not by one being there.** A node
may leave a message for itself on a wire -- text it will not begin
@@ -877,9 +888,12 @@ waiting, the words that turned it, the list of NACKs owed, and
### 7d.5 The products
The lone node on the hosted and bare-metal products is given the same
addresses by its host (`v4/system/boot.c`): its two wires are the
console and the kernel. Nothing else changes for it: the kernel's
requests stay a word at a time.
addresses by its host (`v4/system/boot.c`): its one wire is the
console's. **The kernel's port has no queue** *(ruled 2026-10-08)*, on
the products and in a fabric (`v4_fabric_wire_kernel`): the kernel is
asked by words written to its port, as before, and takes no messages. A
message for that port is refused at once -- error 18, "No one on that
port".
### 7d.6 What will be limits
+2 -2
View File
@@ -206,8 +206,8 @@ hosted-check: $(foreach i,$(HOSTED_ISAS),$(BINDIR)/boot-$(i).txt) $(BINDIR)/star
@grep -q '^ok> Stack overflow$$' $(BINDIR)/deep-amd64.txt && grep -q '^ok> 3 ok$$' $(BINDIR)/deep-amd64.txt || { echo "hosted-check: a line that left 29 values on the stack was not an error the node came back from"; tail -4 $(BINDIR)/deep-amd64.txt; exit 1; }
@printf '1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26\nWORDS\nDEPTH .\n' | $(BINDIR)/starforth4-amd64 > $(BINDIR)/deepwords-amd64.txt 2>&1; true
@grep -q 'DUP' $(BINDIR)/deepwords-amd64.txt && grep -q '^ok> 26 ok$$' $(BINDIR)/deepwords-amd64.txt || { echo "hosted-check: WORDS with 26 values on the stack did not print and come back"; tail -4 $(BINDIR)/deepwords-amd64.txt | cut -c1-200; exit 1; }
@printf '1 0 ROUTE\n65 EMIT\n: P 400 0 DO I . LOOP ; P\nNO-ROUTES\n1 2 + .\n' | timeout 120 $(BINDIR)/starforth4-amd64 > $(BINDIR)/kport-amd64.txt 2>&1; true
@test $$(grep -c '^ok> ERROR$$' $(BINDIR)/kport-amd64.txt) -eq 2 && grep -q '^ok> 3 ok$$' $(BINDIR)/kport-amd64.txt || { echo "hosted-check: with the way to the console set to the kernel's port, a line that prints did not end in an error at the prompt, or the node did not come back"; tail -6 $(BINDIR)/kport-amd64.txt; exit 1; }
@printf '1 0 ROUTE\n65 EMIT\n: P 400 0 DO I . LOOP ; P\nNO-ROUTES\nDEPTH .\n1 2 + .\n' | timeout 120 $(BINDIR)/starforth4-amd64 > $(BINDIR)/kport-amd64.txt 2>&1; true
@test $$(grep -c '^ok> ERROR$$' $(BINDIR)/kport-amd64.txt) -eq 2 && grep -q '^ok> 3 ok$$' $(BINDIR)/kport-amd64.txt && grep -q '^ok> 0 ok$$' $(BINDIR)/kport-amd64.txt || { echo "hosted-check: with the way to the console set to the kernel's port, a line that prints did not end in an error at the prompt, or left something on the stack, or the node did not come back"; tail -6 $(BINDIR)/kport-amd64.txt; exit 1; }
@printf ': S0 S" 1 DROP" 0 0 SEND-ON ; S0\n1 2 + .\n65 1 BLOCK C! 1 BLOCK C@ .\n' | timeout 120 $(BINDIR)/starforth4-amd64 > $(BINDIR)/ksend-amd64.txt 2>&1; true
@grep -q '^ok> No one on that port$$' $(BINDIR)/ksend-amd64.txt && grep -q '^ok> 3 ok$$' $(BINDIR)/ksend-amd64.txt && grep -q '^ok> 65 ok$$' $(BINDIR)/ksend-amd64.txt || { echo "hosted-check: a message sent to the kernel's port was not refused with No one on that port, or the kernel's requests no longer work"; tail -6 $(BINDIR)/ksend-amd64.txt; exit 1; }
@python3 $(HERE)/tools/depthsweep.py $(BINDIR)/starforth4-amd64 $$(( $(HOST_DATA_RING) + 2 )) $$(( $(HOST_RET_RING) + 1 )) || { echo "hosted-check: at some depth of a stack a line was not answered, or the node did not come back"; exit 1; }
+21 -3
View File
@@ -229,8 +229,11 @@ header CMOVE
\ wire would be; or, if it is about the node this one is waiting on,
\ (A-SELF) is set for AWAIT to find (quit.v4).
\ (WIRE) is not what it was afterwards: the NACK went by another.
\ If the drop does not drop -- the wire was cut after the message was looked
\ at, which let it go already -- nothing is counted and no one is told.
: (REFUSE)
4 (DO) drop
4 (DO) if DROPPED drop ; \ it was not there to drop -- its wire has been cut: nothing is counted or told
DROPPED: drop
(LOST) a! @ 1 + !
(IN)+2 a! @ -4 + if NOT -1 + if NOT 2 + if ANSWER
drop (IN) a! @ (ONE) a! ! (IN)+1 a! @ jump TELL \ about whom it was for; to its sender
@@ -350,6 +353,17 @@ header CMOVE
\ ( -- ) go past the message (MINE) found: it stays on its wire
: (SKIP) (S-WIRE) a! @ (WIRE!) 7 (DO) drop ;
\ ( -- ) A TAKE DID NOT TAKE the message (MINE) found. The fabric takes
\ only the message last looked at (MESH.md 7d.3), so either the wire has
\ been cut -- and there is nothing there, or another message that has not
\ been looked at -- or the message cannot be taken at all. It is dropped,
\ and counted in (LOST); if it cannot be dropped either it is gone past.
\ Either way whoever goes through the messages next is at another one:
\ nothing goes round on one that will not be taken.
: (UNTAKEN)
(S-WIRE) a! @ (WIRE!) 4 (DO) if GONE drop jump (SKIP)
GONE: drop (LOST) a! @ 1 + ! ;
\ ( -- how ) take the message (MINE) found, which has one word of text:
\ its seven words stay in (IN), and its word goes to (ONE). 0: it is
\ taken. Anything else: it is not -- the wire was cut after the message
@@ -388,7 +402,11 @@ header CMOVE
\ The three cells and two return entries this needs are tried first.
\ WITH NO ONE ON THE PORT -- when it waits for room, or when it comes to
\ put -- what was printed cannot be sent. While the text is being done
\ that is its error, 18. Once it has been finished with, what it printed
\ that is its error, 18; and the data stack is emptied first, as a fault
\ empties it. A word that raises an error has taken its own arguments
\ off; this one comes from under whatever word was printing, in the
\ middle of its work, and what that word had on the stack is nothing its
\ caller can use (as first built those values were left there). Once it has been finished with, what it printed
\ is let go and counted in (LOST), and the text is said to have ended in
\ an error: (LINE-STATUS), which is the word then sent of how it ended.
: (FLUSH-OUT)
@@ -401,7 +419,7 @@ header CMOVE
(WAIT-ROOM) if ROOM
NOONE: drop (QUIET) a! @ if TELL
drop (OUT) (OUT^) a! ! (LOST) a! @ 1 + ! 2 (LINE-STATUS) a! ! ;
TELL: drop NODE-ERROR b! 18 !b ;
TELL: drop DSTACK-DEPTH b! a !b NODE-ERROR b! 18 !b ; \ the data stack is emptied, as by a fault: see below
ROOM: drop
(PRINT-TO) a! @ 2 (HDR)
(OUT^) a! @ a! 0 !+ 0 !+ 0 !
+17 -11
View File
@@ -107,12 +107,15 @@ macro R-CLEAR RSTACK-DEPTH b! a !b endmacro
\ the answer has gone.
\ a NACK is taken and counted in (REFUSED)
\ a GONE is taken, and believed if this node's centre sent it
\ any other is dropped, and counted in (LOST): an answer no one waits
\ for, what a node printed that came here, or what is not as
\ long as its type says.
\ an answer no one is waiting for is dropped: a SEND that is not waited
\ on is ordinary use, and its answer is not a loss.
\ any other is dropped, and counted in (LOST): what a node printed
\ that came here, a type there is none of, or a NACK or GONE
\ that is not as long as one is. (LOST) counts what should
\ have been delivered or acted on and was not.
\ A TAKE THAT DOES NOT TAKE -- the wire was cut after the message was
\ looked at -- leaves nothing to act on: no text is done and nothing is
\ counted or believed for it.
\ believed for it, (UNTAKEN). The same for a drop that does not drop.
\ With nothing to begin it sleeps, executing nothing, until a message
\ comes; or, if text is waiting for room for its answer, or (SERVE) left a
\ message for want of room, until there is room on any wire that leads
@@ -129,12 +132,15 @@ macro R-CLEAR RSTACK-DEPTH b! a !b endmacro
(SERVE) (MINE0)
M: (MINE) if REST
drop (IN)+2 a! @ -1 + if TEXT -3 + if NACK -1 + if GONE
OTHER: drop 4 (DO) drop (LOST) a! @ 1 + ! jump M \ let go, and counted
OTHER: drop 4 (DO) if LETGO drop (SKIP) jump M \ it was not there to drop: nothing is counted
LETGO: drop (IN)+2 a! @ -3 + if ANSWER \ let go. An answer no one waits for is ordinary:
drop (LOST) a! @ 1 + ! jump M \ anything else should not have come here, and is counted
ANSWER: drop jump M
NACK: drop (IN)+6 a! @ -4 + if N1 jump OTHER
N1: drop (TAKE1) if N2 drop jump M \ not taken: nothing is done for it
N1: drop (TAKE1) if N2 drop (UNTAKEN) jump M \ not taken: nothing is done for it
N2: drop (REFUSED) a! @ 1 + ! jump M \ a message of this node's was refused: counted
GONE: drop (IN)+6 a! @ -4 + if G1 jump OTHER
G1: drop (TAKE1) if G2 drop jump M
G1: drop (TAKE1) if G2 drop (UNTAKEN) jump M
G2: drop (GONE?) drop jump M
TEXT: drop
(IN)+1 a! @ (WAY) -if T1 drop (S-WIRE) a! @ \ the way back; with none known, the wire it came on
@@ -150,7 +156,7 @@ macro R-CLEAR RSTACK-DEPTH b! a !b endmacro
START: drop
(S-WIRE) a! @ dup (REPLY) a! ! (WIRE!)
(MSG) (WIRE-A) b! !b TIB 2/ 2/ (WIRE-B) b! !b 2 (DO) \ its seven words, and its text
if TAKEN drop jump L \ not taken: the wire was cut since it was looked at
if TAKEN drop (UNTAKEN) jump M \ not taken: the wire was cut since it was looked at
TAKEN: drop
0 (MSG)+6 a! @ TIB + C! \ a zero after the text
(MSG)+6 a! @ SPAN a! !
@@ -353,15 +359,15 @@ header AWAIT
BREAK: drop 4 jump (A-END)
ANSWER: drop (IN)+1 a! @ (AWAIT-FROM) a! @ xor if A1 jump KEEP
A1: drop (IN)+6 a! @ -4 + if A2 jump KEEP
A2: drop (TAKE1) if A3 drop jump M \ not taken: nothing is done for it
A2: drop (TAKE1) if A3 drop (UNTAKEN) jump M \ not taken: nothing is done for it
A3: drop 1 jump (A-END)
NACK: drop (IN)+6 a! @ -4 + if N1 jump KEEP
N1: drop (TAKE1) if N0 drop jump M
N1: drop (TAKE1) if N0 drop (UNTAKEN) jump M
N0: drop (ONE) a! @ (AWAIT-FROM) a! @ xor if N2 \ about the node waited for?
drop (REFUSED) a! @ 1 + ! jump M \ another: counted
N2: drop 2 jump (A-END)
GONE: drop (IN)+6 a! @ -4 + if G1 jump KEEP
G1: drop (TAKE1) if G0 drop jump M
G1: drop (TAKE1) if G0 drop (UNTAKEN) jump M
G0: drop (GONE?) if M0 \ only its centre is believed
drop (ONE) a! @ (AWAIT-FROM) a! @ xor if G2 drop jump M
G2: drop 3 jump (A-END)
+10
View File
@@ -134,6 +134,13 @@ v4_fabric_node *v4_fabric_node_at(const v4_fabric *f, unsigned id);
* ends. Each returns 1, or 0 if a node or a port is not there. */
int v4_fabric_wire(v4_fabric *f, unsigned a, unsigned pa, unsigned b, unsigned pb);
int v4_fabric_wire_device(v4_fabric *f, unsigned a, unsigned pa, const v4_device *device);
/* THE KERNEL ON A PORT (MESH.md 7d.5, ruled 2026-10-08). A node asks its
* kernel by words written to a port, and the kernel takes no messages: its
* port is wired to the device as above but is given no queues. Words are
* handed over as for any device; every operation on whole messages there
* answers that no one is on the port, so that a message sent to it is
* refused at once and nothing waits for it. */
int v4_fabric_wire_kernel(v4_fabric *f, unsigned a, unsigned pa, const v4_device *device);
int v4_fabric_unwire(v4_fabric *f, unsigned a, unsigned pa);
/* A node that is asleep executes nothing, and nothing is handed to it or
@@ -145,6 +152,9 @@ void v4_fabric_wake(v4_fabric *f, unsigned id);
/* WHOLE MESSAGES. A node that has stored an operation to Do (node.h) is
* blocked, and in the step in which it did the operation is done, whole, and
* the node is let go: put, take, move and drop and the rest (MESH.md 7d.3).
* Take, move and drop are done only on the message the node last looked at
* on that wire (wire.h, THE LOOK): if the mark is at any other -- the wire
* was cut and made again since -- they answer 3 and nothing is written.
* 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. A message has "come" when a
+12 -1
View File
@@ -16,6 +16,16 @@
* and those four answer V4_WIRE_NO_MESSAGE. A message put on the back of a
* queue whose mark is at no message is the one the mark is then at.
*
* THE LOOK. Whoever reads a queue looks at a message, decides, and then
* takes it, moves it or drops it; and the queue may have been emptied and
* filled again in between -- its wire cut and made again. So the queue
* keeps whether the message its mark is at is the one last looked at:
* `looked` is set by a look that finds a message, and cleared whenever the
* mark is moved, the marked message goes, or the queue is reset. A node's
* take, move and drop are done only while it is set (fabric.c): they are
* then about the very message that was looked at, of the length the look
* gave, and no other.
*
* Nothing here allocates, and nothing uses the C library: whoever owns the
* queue supplies its memory.
*/
@@ -40,6 +50,7 @@ typedef struct {
unsigned mark; /* how many cells from the front the reader's mark is; == used: at no message */
unsigned arrived; /* not 0: a message has come since the reader last asked which wires have one */
unsigned freed; /* not 0: a message has come off since the writer was last woken from a sleep for room */
unsigned looked; /* not 0: the message the mark is at is the one last looked at: see THE LOOK */
} v4_wire_queue;
enum { V4_WIRE_DONE = 0, V4_WIRE_NO_ROOM = 1, V4_WIRE_NO_ONE = 2, V4_WIRE_NO_MESSAGE = 3, V4_WIRE_NOT_A_MESSAGE = 4 };
@@ -70,7 +81,7 @@ int v4_wire_next(v4_wire_queue *q);
* `text_cap` cells (V4_WIRE_NOT_A_MESSAGE if that is too few); taken off and
* gone. After a take or a drop the mark is at the message that followed.
* V4_WIRE_NO_MESSAGE with the mark at no message. */
int v4_wire_look(const v4_wire_queue *q, v4_cell *header7);
int v4_wire_look(v4_wire_queue *q, v4_cell *header7);
int v4_wire_take(v4_wire_queue *q, v4_cell *header7, v4_cell *text, unsigned text_cap);
int v4_wire_drop(v4_wire_queue *q);
+12 -3
View File
@@ -157,6 +157,15 @@ int v4_fabric_wire(v4_fabric *f, unsigned a, unsigned pa, unsigned b, unsigned p
return 1;
}
int v4_fabric_wire_kernel(v4_fabric *f, unsigned a, unsigned pa, const v4_device *device)
{
if (!there(f, a, pa) || !device) return 0;
cut(f, a, pa);
f->place[a].wire[pa].kind = V4_WIRE_DEVICE;
f->place[a].wire[pa].device = device; /* and no queues: it takes no messages */
return 1;
}
int v4_fabric_wire_device(v4_fabric *f, unsigned a, unsigned pa, const v4_device *device)
{
if (!there(f, a, pa) || !device) return 0;
@@ -346,7 +355,7 @@ static int operate(v4_fabric *f, unsigned id, v4_cell *how)
break;
case 2: /* take */
if (!rx) { *how = V4_WIRE_NO_ONE; break; }
if (rx->mark >= rx->used) { *how = V4_WIRE_NO_MESSAGE; break; }
if (rx->mark >= rx->used || !rx->looked) { *how = V4_WIRE_NO_MESSAGE; break; } /* only the message last looked at (wire.h) */
(void)v4_wire_look(rx, h);
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; break; }
@@ -354,12 +363,12 @@ static int operate(v4_fabric *f, unsigned id, v4_cell *how)
break;
case 3: /* move */
if (!rx) { *how = V4_WIRE_NO_ONE; break; }
if (rx->mark >= rx->used) { *how = V4_WIRE_NO_MESSAGE; break; }
if (rx->mark >= rx->used || !rx->looked) { *how = V4_WIRE_NO_MESSAGE; break; }
to = a >= 0 && a < (v4_cell)V4_PORTS ? going(f, id, (unsigned)a) : 0;
*how = to ? v4_wire_move(rx, to) : V4_WIRE_NO_ONE;
break;
case 4: /* drop */
*how = rx ? v4_wire_drop(rx) : V4_WIRE_NO_ONE;
*how = !rx ? V4_WIRE_NO_ONE : rx->looked ? v4_wire_drop(rx) : V4_WIRE_NO_MESSAGE;
break;
case 5: /* put */
if (!tx) { *how = V4_WIRE_NO_ONE; break; }
+6 -1
View File
@@ -14,6 +14,7 @@ void v4_wire_reset(v4_wire_queue *q)
q->mark = 0;
q->arrived = 0;
q->freed = 0;
q->looked = 0;
}
unsigned v4_wire_cells(v4_cell length)
@@ -62,21 +63,24 @@ int v4_wire_put(v4_wire_queue *q, const v4_cell *header7, const v4_cell *text)
void v4_wire_first(v4_wire_queue *q)
{
q->looked = 0;
q->mark = 0;
}
int v4_wire_next(v4_wire_queue *q)
{
q->looked = 0;
if (q->mark >= q->used) return V4_WIRE_NO_MESSAGE;
q->mark += size_at(q, q->mark);
return q->mark < q->used ? V4_WIRE_DONE : V4_WIRE_NO_MESSAGE;
}
int v4_wire_look(const v4_wire_queue *q, v4_cell *header7)
int v4_wire_look(v4_wire_queue *q, v4_cell *header7)
{
unsigned i;
if (q->mark >= q->used) return V4_WIRE_NO_MESSAGE;
for (i = 0; i < V4_WIRE_HEADER; i++) header7[i] = q->cell[slot(q, q->mark + i)];
q->looked = 1;
return V4_WIRE_DONE;
}
@@ -91,6 +95,7 @@ static void remove_marked(v4_wire_queue *q)
}
q->used -= n;
q->freed = 1;
q->looked = 0;
}
int v4_wire_take(v4_wire_queue *q, v4_cell *header7, v4_cell *text, unsigned text_cap)
+3 -3
View File
@@ -138,18 +138,18 @@ static int boot_do(const v4_boot *bt)
break;
case 2: /* take */
if (!rx) break;
if (v4_wire_look(rx, h) != V4_WIRE_DONE) { how = V4_WIRE_NO_MESSAGE; break; }
if (!rx->looked || v4_wire_look(rx, h) != V4_WIRE_DONE) { how = V4_WIRE_NO_MESSAGE; break; } /* only the message last looked at (wire.h) */
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;
if (rx->mark >= rx->used || !rx->looked) how = V4_WIRE_NO_MESSAGE;
else how = a == (v4_cell)CONSOLE_PORT ? v4_wire_move(rx, FROM_NODE) : V4_WIRE_NO_ONE;
break;
case 4: /* drop */
if (rx) how = v4_wire_drop(rx);
if (rx) how = rx->looked ? v4_wire_drop(rx) : V4_WIRE_NO_MESSAGE;
break;
case 5: /* put */
if (!tx) break;
+76 -13
View File
@@ -165,6 +165,14 @@ static v4_cell ask(unsigned id, v4_cell wire, v4_cell a, v4_cell b, v4_cell op)
return n->doing ? -99 : v4_node_fetch(n, P_HOW);
}
/* The same for a take, a move or a drop, which are done only on the message
* last looked at: the node looks first, as a node does. */
static v4_cell seen(unsigned id, v4_cell wire, v4_cell a, v4_cell b, v4_cell op)
{
(void)ask(id, wire, 500, 0, 1);
return ask(id, wire, a, b, op);
}
int main(void)
{
unsigned a, b, c, i, steps;
@@ -507,6 +515,7 @@ int main(void)
LIT(P_DO); O(BANG_B); LIT(8); O(STORE_B);
LIT(P_HAVE); O(BANG_B); O(FETCH_B); LIT(OUT + 1); O(BANG_A); O(STORE_A);
LIT(P_A); O(BANG_B); LIT(200); O(STORE_B);
LIT(P_DO); O(BANG_B); LIT(1); O(STORE_B); /* it looks, and then takes what it looked at */
LIT(P_B); O(BANG_B); LIT(220); O(STORE_B);
LIT(P_DO); O(BANG_B); LIT(2); O(STORE_B);
LIT(P_HOW); O(BANG_B); O(FETCH_B); LIT(OUT); O(BANG_A); O(STORE_A);
@@ -573,6 +582,7 @@ int main(void)
for (i = 0; i < 7; i++) t[i] = ND(a)->n.mem[100 + i];
for (i = 0; i < 8; i++) t[8 + i] = ND(a)->n.mem[110 + i];
CHECK(v4_wire_put(f.place[a].wire[0].rx, t, t + 8) == V4_WIRE_DONE, "(a message waits)");
f.place[a].wire[0].rx->looked = 1; /* and has been looked at */
memcpy(snap, ND(a)->n.mem, sizeof snap);
memcpy(&before_rx, f.place[a].wire[0].rx, sizeof before_rx);
memcpy(&before_tx, f.place[a].wire[0].tx, sizeof before_tx);
@@ -592,6 +602,7 @@ int main(void)
for (i = 0; i < 7; i++) t[i] = ND(a)->n.mem[100 + i];
for (i = 0; i < 8; i++) t[8 + i] = ND(a)->n.mem[110 + i];
(void)v4_wire_put(f.place[a].wire[0].rx, t, t + 8);
f.place[a].wire[0].rx->looked = 1;
memcpy(snap, ND(a)->n.mem, sizeof snap);
memcpy(&before_rx, f.place[a].wire[0].rx, sizeof before_rx);
CHECK(ask(a, 0, 300, bad[k], 2) == 4 && memcmp(snap, ND(a)->n.mem, sizeof snap) == 0 && memcmp(&before_rx, f.place[a].wire[0].rx, sizeof before_rx) == 0,
@@ -621,9 +632,9 @@ int main(void)
CHECK(ask(a, 0, 200, 220, 7) == 0 && ask(a, 0, 200, 220, 1) == 0 && ND(a)->n.mem[200] == 200, "next: the second");
CHECK(ask(a, 0, 200, 220, 2) == 0 && msg_is(&ND(a)->n, 200, 220, 21, 2), "take of the second");
CHECK(ask(a, 0, 200, 220, 1) == 0 && ND(a)->n.mem[200] == 300, "the mark is at the third");
CHECK(ask(a, 0, 200, 220, 6) == 0 && ask(a, 0, 200, 220, 4) == 0 && ask(a, 0, 200, 220, 1) == 0 && ND(a)->n.mem[200] == 300, "first and drop: the first is gone, the third is left");
CHECK(ask(a, 0, 200, 220, 6) == 0 && seen(a, 0, 200, 220, 4) == 0 && ask(a, 0, 200, 220, 1) == 0 && ND(a)->n.mem[200] == 300, "first and drop: the first is gone, the third is left");
CHECK(ask(a, 0, 200, 220, 7) == 3 && ask(a, 0, 200, 220, 1) == 3, "next at the end answers 3 and the mark is at no message");
CHECK(ask(a, 0, 200, 220, 6) == 0 && ask(a, 0, 200, 220, 2) == 0 && msg_is(&ND(a)->n, 200, 220, 4, 3) && f.place[a].wire[0].rx->used == 0, "and the third is taken");
CHECK(ask(a, 0, 200, 220, 6) == 0 && seen(a, 0, 200, 220, 2) == 0 && msg_is(&ND(a)->n, 200, 220, 4, 3) && f.place[a].wire[0].rx->used == 0, "and the third is taken");
/* "which wires have a message": a bit to a wire; fetching it clears arrived */
v4_fabric_init(&f, places, 4);
@@ -720,9 +731,9 @@ int main(void)
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 the front message (which is 263 cells)");
CHECK(seen(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);
(void)seen(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 */
@@ -742,12 +753,12 @@ int main(void)
(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");
CHECK(seen(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");
CHECK(!ND(a)->n.doing && ND(a)->n.how == 1, "the sleeper, which wants 250, is woken, and told 1: a wire of its own has gained room, and not enough");
CHECK(ask(a, 0, 250, 0, 9) == -99, "it asks again, and sleeps");
for (i = 0; i < 50; i++) (void)v4_fabric_step(&f);
CHECK(ND(a)->n.doing, "and sleeps on while nothing changes");
CHECK(ask(b, 3, 400, 420, 4) == 0 && !ND(a)->n.doing && ND(a)->n.how == 0, "the next take gives it the room: 0");
CHECK(seen(b, 3, 400, 420, 4) == 0 && !ND(a)->n.doing && ND(a)->n.how == 0, "the next take gives it the room: 0");
/* woken by a message coming instead */
v4_wire_reset(f.place[a].wire[0].tx);
fill(f.place[a].wire[0].tx, 100);
@@ -775,14 +786,14 @@ int main(void)
for (i = 0; i < 100; i++) (void)v4_fabric_step(&f);
CHECK(ND(a)->n.doing, "and it sleeps on while nothing changes");
v4_wire_first(f.place[c].wire[2].rx);
CHECK(ask(c, 2, 400, 420, 4) == 0, "the reader of wire 1 drops a message");
CHECK(seen(c, 2, 400, 420, 4) == 0, "the reader of wire 1 drops a message");
CHECK(!ND(a)->n.doing && ND(a)->n.how == 1 && f.place[a].wire[0].tx->used == V4_WIRE_CELLS, "it wakes, told 1: the wire it named has no room, but something has changed");
CHECK(ask(a, 0, 100, 0, 10) == 1 && ask(a, 1, 100, 0, 10) == 0, "it asks which: wire 1 has the room");
CHECK(ask(a, 0, 100, 0, 9) == -99, "asleep for room on wire 0 again: that room on wire 1 was seen");
for (i = 0; i < 100; i++) (void)v4_fabric_step(&f);
CHECK(ND(a)->n.doing, "and it is not woken twice for it");
/* the room comes in the very step the node goes to sleep: the reader's take is done before the sleep is looked at */
v4_node_store(&ND(c)->n, P_WIRE, 2); v4_node_store(&ND(c)->n, P_DO, 4);
(void)ask(c, 2, 500, 0, 1); v4_node_store(&ND(c)->n, P_WIRE, 2); v4_node_store(&ND(c)->n, P_DO, 4);
ND(a)->n.doing = 0;
v4_node_store(&ND(a)->n, P_WIRE, 0); v4_node_store(&ND(a)->n, P_A, 100); v4_node_store(&ND(a)->n, P_DO, 9);
(void)v4_fabric_step(&f);
@@ -802,7 +813,58 @@ int main(void)
(void)v4_node_fetch(&ND(a)->n, P_HAVE);
(void)v4_fabric_step(&f);
CHECK(ask(a, 0, 0, 0, 8) == -99, "asleep, for a message");
CHECK(ask(c, 2, 400, 420, 4) == 0 && ND(a)->n.doing, "room on a wire does not wake a node that sleeps for a message only");
CHECK(seen(c, 2, 400, 420, 4) == 0 && ND(a)->n.doing, "room on a wire does not wake a node that sleeps for a message only");
/* TAKE, MOVE AND DROP ARE ABOUT THE MESSAGE LAST LOOKED AT (MESH.md 7d.3). A node looks, decides, and
* then takes: if the wire was cut and made again in between, and another message put, what is at the
* mark is not what it looked at. Then each answers 3, no such message, and nothing is written or moved. */
v4_fabric_init(&f, more_places, 8);
v4_fabric_queues(&f, qpool, 8);
a = (unsigned)v4_fabric_add(&f, &pool[0], PB);
b = (unsigned)v4_fabric_add(&f, &pool[1], PB);
c = (unsigned)v4_fabric_add(&f, &pool[2], PB);
CHECK(v4_fabric_wire(&f, a, 0, b, 3) && v4_fabric_wire(&f, a, 1, c, 2), "a node between two");
put_msg(&ND(b)->n, 100, 110, 4, 1);
CHECK(ask(b, 3, 100, 110, 5) == 0 && ask(a, 0, 0, 0, 6) == 0, "a message of four characters comes, and the mark is at it");
memcpy(snap, ND(a)->n.mem, sizeof snap);
CHECK(ask(a, 0, 200, 220, 2) == 3 && ask(a, 0, 1, 0, 3) == 3 && ask(a, 0, 0, 0, 4) == 3 && f.place[a].wire[0].rx->used == 8 && memcmp(snap, ND(a)->n.mem, sizeof snap) == 0,
"not looked at: take, move and drop each answer 3, and nothing is written or taken");
CHECK(ask(a, 0, 200, 220, 1) == 0 && ND(a)->n.mem[206] == 4, "it is looked at: four characters");
/* the wire is cut and made again, and a message of 1,024 characters put where the short one was */
CHECK(v4_fabric_unwire(&f, a, 0) && v4_fabric_wire(&f, a, 0, b, 3), "the wire is cut, and made again");
put_msg(&ND(b)->n, 100, 110, 1024, 2);
CHECK(ask(b, 3, 100, 110, 5) == 0 && f.place[a].wire[0].rx->used == 263, "and a message of 1,024 characters is put on it");
memcpy(snap, ND(a)->n.mem, sizeof snap);
CHECK(ask(a, 0, 200, 220, 2) == 3 && memcmp(snap, ND(a)->n.mem, sizeof snap) == 0 && f.place[a].wire[0].rx->used == 263,
"a take answers 3: it is not the message that was looked at. Nothing is written: not one cell, where 256 would have been");
CHECK(ask(a, 0, 1, 0, 3) == 3 && ask(a, 0, 0, 0, 4) == 3 && f.place[a].wire[0].rx->used == 263 && f.place[a].wire[1].tx->used == 0, "nor is it moved, or dropped");
CHECK(ask(a, 0, 200, 220, 1) == 0 && ND(a)->n.mem[206] == 1024 && ask(a, 0, 200, 220, 2) == 0 && msg_is(&ND(a)->n, 200, 220, 1024, 2), "looked at, it is taken");
/* a look is good for one take, move or drop, and not after the mark has been moved */
put_msg(&ND(b)->n, 100, 110, 4, 3); CHECK(ask(b, 3, 100, 110, 5) == 0, "another comes");
put_msg(&ND(b)->n, 100, 110, 8, 4); CHECK(ask(b, 3, 100, 110, 5) == 0, "and another behind it");
CHECK(ask(a, 0, 200, 220, 1) == 0 && ask(a, 0, 0, 0, 7) == 0 && ask(a, 0, 200, 220, 2) == 3, "looked at, then the mark moved on with next: a take answers 3");
CHECK(ask(a, 0, 0, 0, 6) == 0 && ask(a, 0, 200, 220, 1) == 0 && ask(a, 0, 0, 0, 6) == 0 && ask(a, 0, 0, 0, 4) == 3, "looked at, then first: a drop answers 3");
CHECK(ask(a, 0, 200, 220, 1) == 0 && ask(a, 0, 0, 0, 4) == 0 && ask(a, 0, 0, 0, 4) == 3 && f.place[a].wire[0].rx->used == 9, "looked at and dropped; the one that followed has not been looked at, and is not dropped");
CHECK(ask(a, 0, 200, 220, 1) == 0 && ND(a)->n.mem[206] == 8 && ask(a, 0, 1, 0, 3) == 0 && f.place[a].wire[1].tx->used == 9, "looked at, it is moved");
/* a message put behind the one looked at does not undo the look */
put_msg(&ND(b)->n, 100, 110, 4, 5); CHECK(ask(b, 3, 100, 110, 5) == 0 && ask(a, 0, 200, 220, 1) == 0, "one comes and is looked at");
put_msg(&ND(b)->n, 100, 110, 4, 6); CHECK(ask(b, 3, 100, 110, 5) == 0 && ask(a, 0, 200, 220, 2) == 0 && msg_is(&ND(a)->n, 200, 220, 4, 5), "another comes behind it: the first is still the one looked at, and is taken");
/* THE KERNEL ON A PORT has no queue: it is asked by words, and takes no messages */
{
static stream ks;
static v4_device kern;
unsigned free_before = f.queue_free;
ks.took_n = 0; kern.take = stream_take; kern.give = 0; kern.self = &ks; kern.pending = 0;
CHECK(v4_fabric_wire_kernel(&f, a, 5, &kern) && f.queue_free == free_before && !f.place[a].wire[5].rx && !f.place[a].wire[5].tx, "a kernel is wired to a port, and is given no queues");
put_msg(&ND(a)->n, 100, 110, 4, 7);
CHECK(ask(a, 5, 100, 110, 5) == 2 && ask(a, 5, 8, 0, 10) == 2 && ask(a, 5, 8, 0, 9) == 2 && ask(a, 5, 200, 220, 1) == 2 && ask(a, 5, 0, 0, 6) == 2 && !ND(a)->n.doing,
"a message put to it, room asked of it, a sleep for room on it, a look: each answers 2 at once -- no one takes messages there");
v4_node_store(&ND(a)->n, PB + 5, 77);
(void)v4_fabric_step(&f);
CHECK(!ND(a)->n.asking && ks.took_n == 1 && ks.took[0] == 77, "a word written to it is handed over as before");
CHECK(v4_fabric_unwire(&f, a, 5) && f.queue_free == free_before, "and cutting it gives back no queue that it never had");
}
/* move from one of a node's wires to another */
v4_fabric_init(&f, more_places, 8);
@@ -814,19 +876,20 @@ int main(void)
put_msg(&ND(b)->n, 100, 110, 21, 4);
ND(b)->n.mem[103] = 9;
CHECK(ask(b, 3, 100, 110, 5) == 0 && ask(a, 0, 0, 0, 6) == 0, "a message comes on wire 0");
CHECK(ask(a, 0, 1, 0, 3) == 0, "and is moved to wire 1");
CHECK(seen(a, 0, 1, 0, 3) == 0, "and is moved to wire 1");
CHECK(f.place[a].wire[0].rx->used == 0 && f.place[c].wire[2].rx->used == 7 + 6, "it is off the one and on the other");
CHECK(ask(c, 2, 200, 220, 6) == 0 && ask(c, 2, 200, 220, 2) == 0 && ND(c)->n.mem[203] == 8 && msg_is_but(&ND(c)->n, 200, 220, 21, 4, 3),
CHECK(ask(c, 2, 200, 220, 6) == 0 && seen(c, 2, 200, 220, 2) == 0 && ND(c)->n.mem[203] == 8 && msg_is_but(&ND(c)->n, 200, 220, 21, 4, 3),
"and arrives whole, its fourth word one fewer");
/* the other full: 1, and nothing moved */
put_msg(&ND(b)->n, 100, 110, 21, 4);
CHECK(ask(b, 3, 100, 110, 5) == 0 && ask(a, 0, 0, 0, 6) == 0, "another comes");
fill(f.place[a].wire[1].tx, 0);
(void)ask(a, 0, 500, 0, 1);
memcpy(&copy, f.place[a].wire[0].rx, sizeof copy);
{
v4_wire_queue copy2;
memcpy(&copy2, f.place[a].wire[1].tx, sizeof copy2);
CHECK(ask(a, 0, 1, 0, 3) == 1, "moved to a wire with no room: 1");
CHECK(ask(a, 0, 1, 0, 3) == 1, "looked at, and moved to a wire with no room: 1");
CHECK(memcmp(&copy, f.place[a].wire[0].rx, sizeof copy) == 0 && memcmp(&copy2, f.place[a].wire[1].tx, sizeof copy2) == 0, "and neither wire has changed");
}
CHECK(ask(a, 0, 5, 0, 3) == 2 && ask(a, 0, -1, 0, 3) == 2 && ask(a, 5, 1, 0, 3) == 2, "move to a port with nothing, or from one: 2");
@@ -872,7 +935,7 @@ int main(void)
for (w = 0; w < 7; w++) h[w] = ND(a)->n.mem[100 + w];
for (w = 0; w < 5; w++) t[w] = ND(a)->n.mem[110 + w];
CHECK(v4_fabric_device_put(&f, a, 4, h, t) == 0, "the device puts a message");
CHECK(ask(a, 4, 200, 220, 6) == 0 && ask(a, 4, 200, 220, 2) == 0 && msg_is(&ND(a)->n, 200, 220, 17, 8), "and the node takes it, whole");
CHECK(ask(a, 4, 200, 220, 6) == 0 && seen(a, 4, 200, 220, 2) == 0 && msg_is(&ND(a)->n, 200, 220, 17, 8), "and the node takes it, whole");
put_msg(&ND(a)->n, 100, 110, 3, 9);
CHECK(ask(a, 4, 100, 110, 5) == 0, "the node puts one");
memset(h, 0, sizeof h); memset(t, 0, sizeof t);
+4 -2
View File
@@ -273,6 +273,7 @@ static int one(unsigned path, const char *what, unsigned depth, const char *text
v4_cell lost11 = pool[mid].n.mem[LOST], refused12 = pool[far].n.mem[REFUSED], ways12;
int how11, works = 0, clean_error = 0;
unsigned left;
v4_uheat_t clock12;
if (!sixhundred[0]) memset(sixhundred, 'A', 600);
runs++;
@@ -281,6 +282,7 @@ static int one(unsigned path, const char *what, unsigned depth, const char *text
ways12 = pool[far].n.mem[ROUTE_COUNT];
forget();
saw_room_wait = 0; saw_passed = 0;
clock12 = pool[far].es.anticlock;
if (why) { }
else if (path == 4) { /* node 12 is busy for a long while and sends no answer; node 11 waits; its centre says 12 is gone */
stuck = 1u << far;
@@ -319,8 +321,8 @@ static int one(unsigned path, const char *what, unsigned depth, const char *text
switch (path) {
case 0: works = how11 == V4_TEXT_COMPLETED && strcmp(printed, sixhundred) == 0 && left == values;
clean_error = over && is_cut_short(printed, sixhundred, over); break;
case 1: works = how11 == V4_TEXT_COMPLETED && printed_len == 0 && left == values && pool[mid].n.mem[LOST] == lost11 + 1; /* node 12's answer, which it did not wait for */
clean_error = over && strcmp(printed, over) == 0 && pool[mid].n.mem[LOST] == lost11; break;
case 1: works = how11 == V4_TEXT_COMPLETED && printed_len == 0 && left == values && pool[far].es.anticlock != clock12 && pool[mid].n.mem[LOST] == lost11; /* node 12 did the text */
clean_error = over && strcmp(printed, over) == 0 && pool[far].es.anticlock == clock12; break; /* nothing was sent */
case 2: works = how11 == V4_TEXT_ERROR && strcmp(printed, "Argument out of range\n") == 0 && left == values;
clean_error = over && strcmp(printed, over) == 0; break;
case 3: works = how11 == V4_TEXT_COMPLETED && printed_len == 0 && left == values && pool[mid].n.mem[LOST] == lost11;
+156 -13
View File
@@ -39,6 +39,10 @@ static v4_wire_queue queues[QUEUES];
/* ---- the console: a device on a port ---- */
#define CONSOLE_ID 1
static const v4_device console = { 0, 0, 0, 0 }; /* it takes and gives no words: only whole messages */
/* a kernel on a node's port 0: it takes the words written to it, and no messages (MESH.md 7d.5) */
static unsigned kernel_words;
static int kernel_take(void *self, v4_cell value) { (void)self; (void)value; kernel_words++; return 1; }
static const v4_device kernel_dev = { kernel_take, 0, 0, 0 };
static unsigned hera, mid, far; /* where the three nodes are: 10, 11, 12 */
static char printed[32768]; /* what has come back since the console last sent */
static unsigned printed_len;
@@ -227,6 +231,10 @@ static void put_back(void) { memcpy(pool, kept_pool, sizeof pool); memcpy(places
static int far_has_one(void) { return coming_to(far, 2) && coming_to(far, 2)->used > 0; }
static int far_has_nack(void) { return far_has_one() && coming_to(far, 2)->cell[(coming_to(far, 2)->head + 2u) % V4_WIRE_CELLS] == V4_MSG_NACK; }
static int far_awaits_and_has_one(void) { return pool[far].n.mem[AWAIT_FROM] == 11 && far_has_one(); }
static v4_cell mid_for; /* whom the message waited for at node 11's wire from node 12 is for */
static int mid_has_one(void) { return coming_to(mid, 3) && coming_to(mid, 3)->used > 0 && coming_to(mid, 3)->cell[coming_to(mid, 3)->head] == mid_for; }
static v4_cell far_lost_wanted;
static int far_has_lost(void) { return pool[far].n.mem[LOST] >= far_lost_wanted && asleep(far); }
static int mid_holds(void) { return pool[mid].n.doing && pool[mid].n.do_op == 9 && pool[mid].n.mem[H_PORT] >= 0; }
static unsigned saw_mid_hold;
static void watch_mid_holds(void) { if (pool[mid].n.doing && pool[mid].n.do_op == 9 && pool[mid].n.mem[H_PORT] >= 0) saw_mid_hold++; }
@@ -317,7 +325,8 @@ int main(void)
CHECK(pool[hera].n.mem[LOST] == (v4_cell)i + 1, "and that node has counted it");
CHECK(strcmp(tell(11, "(LOST) @ ."), "0 ") == 0 && strcmp(tell(12, "(LOST) @ ."), "0 ") == 0, "the others have lost nothing");
i = (unsigned)pool[hera].n.mem[LOST];
CHECK(told(10, ": TELL2 S\" 1 DROP\" 12 SEND ; TELL2") && pool[hera].n.mem[LOST] == (v4_cell)i + 1, "an answer that comes to a node that is not waiting for it is let go, and counted");
CHECK(told(10, ": TELL2 S\" 1 DROP\" 12 SEND ; TELL2") && pool[hera].n.mem[LOST] == (v4_cell)i && coming_to(hera, 2)->used == 0,
"an answer that comes to a node that is not waiting for it is let go, and is not counted: a SEND that is not waited on is ordinary");
/* ---- waiting ---- */
{
@@ -358,10 +367,8 @@ int main(void)
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(asleep(hera) && asleep(mid) && asleep(far), "all three come to rest");
/* each has let go, and counted, the answers to what it sent, which it did not wait for: node 10 to its
* 12 messages and its two texts, and two from before and the message for 99; the others to 12 */
CHECK(strcmp(tell(10, "GOT @ . (LOST) @ ."), "12 17 ") == 0 && strcmp(tell(11, "GOT @ . (LOST) @ ."), "12 12 ") == 0 && strcmp(tell(12, "GOT @ . (LOST) @ ."), "12 13 ") == 0,
"every one of them arrived; what each has counted is the answers it did not wait for: \"%s\"", printed);
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);
}
/* ---- THE FLOOD: more than the wires hold (MESH.md 7d.6, acceptance 2) ----
@@ -369,12 +376,13 @@ int main(void)
* word that sends ends there, error 19, and the refusal is counted. A message a middle node has no room
* to pass on is let go there, counted, and its sender told. Nothing waits, and it comes to rest. */
{
long got = 0, lost = 0, refused = 0, sent = 0, tried = 0, v;
long got = 0, lost = 0, refused = 0, sent = 0, tried = 0, v, let_go, orders = 0;
unsigned k, q, left = 0;
static const v4_cell who[3] = { 10, 11, 12 };
const char *r;
CHECK(is_empty(tell(10, "0 GOT ! 0 SENT ! 0 TRIED !")) && is_empty(tell(11, "0 GOT ! 0 SENT ! 0 TRIED !")) && is_empty(tell(12, "0 GOT ! 0 SENT ! 0 TRIED !")), "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");
for (k = 0; k < 3; k++) { lost -= pool[k].n.mem[LOST]; refused -= pool[k].n.mem[REFUSED]; }
r = tell(10, ": GO2 S\" 200 12 BURST 200 10 BURST\" 11 SEND S\" 200 10 BURST 200 11 BURST\" 12 SEND 200 12 BURST 200 11 BURST ; GO2");
CHECK(strcmp(r, "(still running)") != 0, "two hundred from each to each: it ends");
CHECK(asleep(hera) && asleep(mid) && asleep(far), "and all three come to rest, asleep");
for (k = 0; k < 3; k++) for (q = 0; q < V4_PORTS; q++) if (coming_to(k, q)) left += coming_to(k, q)->used;
@@ -383,15 +391,24 @@ int main(void)
v = strtol(tell(who[k], "GOT @ ."), NULL, 10); got += v;
CHECK(v >= 0 && v <= 400, "node %ld did %ld of the messages for it", (long)who[k], v);
sent += strtol(tell(who[k], "SENT @ ."), NULL, 10);
tried += strtol(tell(who[k], "TRIED @ ."), NULL, 10);
v = strtol(tell(who[k], "TRIED @ ."), NULL, 10); tried += v;
if (v == 0) orders++; /* it never began to send: the text that told it to was itself let go on the way */
lost += pool[k].n.mem[LOST];
refused += pool[k].n.mem[REFUSED];
}
printf(" the flood: %ld sends tried, %ld on a wire, %ld refused at once; %ld done; (LOST) %ld, (REFUSED) %ld in all\n", tried, sent, tried - sent, got, lost, refused);
/* A SEND refused at once ends the word that sends, so the flood holds itself back: what got on a wire
* is what there was room for. (LOST) counts, with what was let go on the way, every answer that
* came to a node that did not wait for it, so it is not a count of what was lost. */
CHECK(tried > sent && sent >= got && got > 0 && refused >= tried - sent, "there was not room for all of them: some were refused at once, and counted");
let_go = sent - got;
printf(" the flood: %ld sends tried: %ld refused at once, %ld let go on the way, %ld done; %ld of the two texts that set a node sending let go; (LOST) %ld, (REFUSED) %ld\n",
tried, tried - sent, let_go, got, orders, lost, refused);
/* THE ACCOUNT IS EXACT. Every send tried was refused at once (counted once, in (REFUSED), and the word
* that sent ended there); or got on a wire and was done; or got on a wire and was let go on the way by
* a node with no room to pass it on -- counted there in (LOST), and once more where its NACK ended: in
* (REFUSED) at the node that sent it, or in (LOST) if the NACK could not go either. That is so too of the
* text that sets a node sending, which is one more message on those wires. Nothing else is
* counted: an answer waits for room and is not let go, and one that nobody waits for is not a loss. */
CHECK(tried > sent && got > 0 && let_go >= 0, "there was not room for all of them: some were refused at once");
CHECK(lost + refused == (tried - sent) + 2 * (let_go + orders), "every message is accounted for, exactly: (LOST) + (REFUSED) is %ld; %ld refused at once and twice %ld let go is %ld",
lost + refused, tried - sent, let_go + orders, (tried - sent) + 2 * (let_go + orders));
CHECK(let_go > 0, "and some were let go on the way, so that is not an account of nothing");
CHECK(strcmp(tell(12, "7 8 * ."), "56 ") == 0 && printed_from == 12, "and the nodes go on as before");
}
@@ -449,6 +466,41 @@ int main(void)
CHECK(strcmp(tell(12, "7 8 * ."), "56 ") == 0 && printed_from == 12 && strcmp(tell(10, "1 2 + ."), "3 ") == 0, "and it and node 10 answer: \"%s\"", printed);
}
/* the same with the wire to the stuck node full: the NACK cannot go, and is counted */
CHECK(triangle(), "the same made afresh");
{
v4_cell lost;
v4_uheat_t c;
char text[64];
CHECK(told(12, "NO-ROUTES 1 3 ROUTE 10 3 ROUTE 11 2 ROUTE : F 0 DO S\" HIT\" 11 SEND LOOP ;") && told(11, "55 3 ROUTE"), "node 12 has no way for 55, and a word that sends node 11 short texts");
stuck = 1u << mid;
(void)tell(11, ": C2 400000 0 DO LOOP S\" 1 DROP\" 55 SEND BEGIN 0 UNTIL ; C2");
snprintf(text, sizeof text, "%u F", (unsigned)(V4_WIRE_CELLS / 8u));
CHECK(told(12, text) && going_from(far, 2)->used == V4_WIRE_CELLS, "node 11 is busy; node 12 fills the wire to it");
lost = pool[far].n.mem[LOST];
far_lost_wanted = lost + 1;
CHECK(run_until(far_has_lost, 30000000) && run() && pool[far].n.mem[LOST] == lost + 2 && going_from(far, 2)->used == V4_WIRE_CELLS && coming_to(far, 2)->used == 0,
"node 11 then sends 55 a message by node 12 and sticks: node 12 lets it go, and the NACK it owes has no room on the wire: both are counted: %ld", (long)(pool[far].n.mem[LOST] - lost));
c = clock_of(far);
for (i = 0; i < 500; i++) (void)v4_fabric_step(&f);
CHECK(asleep(far) && clock_of(far) == c && strcmp(tell(12, "7 8 * ."), "56 ") == 0 && strcmp(tell(10, "1 2 + ."), "3 ") == 0, "node 12 is asleep, owing nothing, and it and node 10 answer");
}
/* ---- THE KERNEL ON A NODE'S PORT TAKES NO MESSAGES (MESH.md 7d.5, ruled 2026-10-08) ---- */
CHECK(triangle() && v4_fabric_wire_kernel(&f, far, 0, &kernel_dev), "the same made afresh, with a kernel on node 12's port 0");
{
kernel_words = 0;
CHECK(strcmp(tell(12, "7 0 PORT! 1 2 + ."), "3 ") == 0 && kernel_words == 1, "a word written to the kernel's port is taken, as ever");
CHECK(strstr(tell(12, ": S0 S\" 1 DROP\" 0 0 SEND-ON ; S0"), "No one on that port") != NULL && ended == 1 && ended_how == V4_TEXT_ERROR && kernel_words == 1,
"a message sent by that port is refused at once: \"%s\"", printed);
CHECK(told(12, "1 0 ROUTE"), "node 12 is told the way to its console is the kernel's port");
CHECK(is_empty(tell(12, "65 EMIT")) && ended == 1 && ended_from == 12 && ended_how == V4_TEXT_ERROR && asleep(far),
"a text that prints cannot send what it printed: it ends in an error, said by the wire the text came on, and node 12 sleeps");
CHECK(is_empty(tell(12, ": P 3000 0 DO I . LOOP ; P")) && ended == 1 && ended_how == V4_TEXT_ERROR && asleep(far) && pool[far].n.ds.depth == 0,
"one that prints more than the buffer holds ends in error 18 and does not wait: %ld", (long)pool[far].n.mem[NODE_ERROR]);
CHECK(told(12, "1 NO-ROUTE") && strcmp(tell(12, "7 8 * ."), "56 ") == 0 && kernel_words == 1, "told to forget that way, it prints again; the kernel was sent nothing");
}
/* ---- acceptance 5: PRINTING IS NOT LOST ---- */
CHECK(triangle(), "the same made afresh");
{
@@ -566,6 +618,7 @@ int main(void)
"node 11 is holding output, asleep for room");
CHECK(v4_fabric_remove(&f, mid) == &pool[1] && run(), "node 11 is removed, and the two that are left come to rest");
CHECK(asleep(far) && pool[far].n.mem[NODE_ERROR] == 18 && pool[far].n.mem[LINE_STATUS] == 2 && pool[far].n.mem[KEEP] == -1, "node 12's text has ended in error 18, No one on that port, and it sleeps");
CHECK(pool[far].n.ds.depth == 0, "with nothing left on its data stack of what the word that was printing had there: %u", (unsigned)pool[far].n.ds.depth);
CHECK(strncmp(printed, want, printed_len) == 0 && strcmp(tell(10, "1 2 + ."), "3 ") == 0 && asleep(hera), "what had arrived was in order, and node 10 goes on");
/* the node that prints removed while the middle node holds its output: what is on the wires arrives */
@@ -655,6 +708,96 @@ int main(void)
CHECK(wrong == 0 && cut_before > 20 && cut_after > 0, "a GONE: cut before it was taken (%u times) no way was forgotten; cut after (%u), one: %u wrong", cut_before, cut_after, wrong);
}
/* ---- ONLY WHAT WAS LOOKED AT IS TAKEN (fix round 2, NP1; MESH.md 7d.3) ----
* Between the look and the take the wire is cut, made again, and a different message put on it. */
{
unsigned k, tried = 0, wrong = 0, hung = 0;
static v4_message big;
static char blanks[1025];
/* text was looked at; a NACK is there now. The NACK must not be done as text: it is a refusal, and counted as one. */
CHECK(row() && told(12, "VARIABLE N : T1 1 N +! ; : T2 10 N +! ;") && told(12, "T1") && put_text(12, "T2") && run_until(far_has_one, 1000000), "text has come to node 12's wire");
keep();
for (k = 0; k < 200; k++) {
static const v4_cell hd[V4_WIRE_HEADER] = { 12, 11, V4_MSG_NACK, 16, 0, 0, 4 }, t[1] = { 'T' | ('2' << 8) };
v4_cell refused;
put_back();
refused = pool[far].n.mem[REFUSED];
for (i = 0; i < k; i++) { (void)v4_fabric_step(&f); (void)console_takes(); }
if (!far_has_one()) break;
if (!v4_fabric_unwire(&f, mid, 3) || !v4_fabric_wire(&f, mid, 3, far, 2) || v4_wire_put(places[far].wire[2].rx, hd, t) != V4_WIRE_DONE) { wrong += 1000; break; }
tried++;
if (!run()) { hung++; continue; }
if (strcmp(tell(12, "N @ ."), "1 ") != 0 || pool[far].n.mem[REFUSED] != refused + 1 || pool[far].n.mem[ME] != 12) wrong++;
}
CHECK(tried > 100 && wrong == 0 && hung == 0, "text looked at, and a NACK there when it comes to take: at each of %u steps the NACK was counted as a refusal and nothing was done as text: %u wrong, %u hung", tried, wrong, hung);
/* a NACK was looked at; a text of 1,024 characters is there now. Its 256 cells must not be taken where one was looked for. */
memset(blanks, ' ', 1024);
CHECK(row() && told(12, ": N77 S\" 1 DROP\" 77 SEND ;") && put_text(12, "N77") && run_until(far_has_nack, 1000000) && v4_message_text(&big, 12, 11, V4_MSG_TEXT, blanks, 1024), "a NACK has come to node 12's wire");
keep();
tried = wrong = hung = 0;
for (k = 0; k < 200; k++) {
static v4_cell before[V4_NODE_WORDS];
v4_cell at;
unsigned changed = 0;
put_back();
for (i = 0; i < k; i++) { (void)v4_fabric_step(&f); (void)console_takes(); }
if (!far_has_nack()) break;
memcpy(before, pool[far].n.mem, sizeof before);
if (!v4_fabric_unwire(&f, mid, 3) || !v4_fabric_wire(&f, mid, 3, far, 2) || v4_wire_put(places[far].wire[2].rx, big.word, big.word + V4_MSG_HEADER) != V4_WIRE_DONE) { wrong += 1000; break; }
tried++;
if (!run()) { hung++; continue; }
/* the cells after (ONE), as far as the variables go, are as they were: the text went to TIB, where text goes */
for (at = ONE + 1; at < BUF0_W; at++) if (at != LINE_STATUS && at != QUIET && at != DONE_PORT && at != KEEP && at != REPLY && !(at >= MSG && at < MSG + 7) && at != PRINT_TO && at != PRINT_PORT
&& !(at >= S_ON && at <= S_HAVE) && at != DEFER && pool[far].n.mem[at] != before[at]) changed++;
if (changed || pool[far].n.mem[ME] != 12 || strcmp(tell(12, "1 2 + ."), "3 ") != 0) wrong++;
}
CHECK(tried > 100 && wrong == 0 && hung == 0, "a NACK looked at, and a text of 1,024 characters there when it comes to take: at each of %u steps nothing was overwritten, and node 12 answers: %u wrong, %u hung", tried, wrong, hung);
}
/* ---- A DROP THAT DOES NOT DROP (fix round 2, NP3) ----
* The wire is cut between the look and the drop: the cut let the message go, and nothing is counted or told for it. */
{
unsigned k, cut_before = 0, cut_after = 0, wrong = 0;
/* (REFUSE): node 11 has no way for what node 12 sends 99 */
mid_for = 99;
CHECK(row() && told(11, "NO-ROUTES 1 2 ROUTE 10 2 ROUTE 12 3 ROUTE") && told(12, ": S99 S\" 1 DROP\" 99 SEND ;") && put_text(12, "S99") && run_until(mid_has_one, 1000000), "a message node 11 must refuse has come to its wire");
keep();
for (k = 0; k < 320; k++) {
int there;
v4_cell lost;
put_back();
lost = pool[mid].n.mem[LOST];
for (i = 0; i < k; i++) { (void)v4_fabric_step(&f); (void)console_takes(); }
there = mid_has_one();
(void)v4_fabric_unwire(&f, mid, 3);
if (!run() || !asleep(mid)) { wrong += 1000; break; }
if (there) cut_before++; else cut_after++;
/* cut after the drop the message is counted; and its NACK too, if the cut came before that was put */
if (there ? pool[mid].n.mem[LOST] != lost : !(pool[mid].n.mem[LOST] == lost + 1 || pool[mid].n.mem[LOST] == lost + 2)) wrong++;
}
CHECK(wrong == 0 && cut_before > 20 && cut_after > 0, "a refusal: cut before the drop (%u times) nothing was counted; cut after (%u), the message, and its NACK if it could not go: %u wrong", cut_before, cut_after, wrong);
/* (IDLE): what node 12 prints comes to node 11, which is not a console: it is let go, and counted */
cut_before = cut_after = wrong = 0;
mid_for = 11;
CHECK(row() && told(12, "11 (CONSOLE) !") && put_text(12, "65 EMIT") && run_until(mid_has_one, 1000000), "what a node printed has come to the wire of a node that is no console");
keep();
for (k = 0; k < 320; k++) {
int there;
v4_cell lost;
put_back();
lost = pool[mid].n.mem[LOST];
for (i = 0; i < k; i++) { (void)v4_fabric_step(&f); (void)console_takes(); }
there = mid_has_one();
(void)v4_fabric_unwire(&f, mid, 3);
if (!run() || !asleep(mid)) { wrong += 1000; break; }
if (there) cut_before++; else cut_after++;
if (pool[mid].n.mem[LOST] != lost + (there ? 0 : 1)) wrong++;
}
CHECK(wrong == 0 && cut_before > 20 && cut_after > 0, "what should not have come: cut before the drop (%u times) nothing was counted; cut after (%u), one: %u wrong", cut_before, cut_after, wrong);
}
/* ---- Review Focus 4: THE ANSWER HAS ROOM KEPT ----
* Node 11 sends the console 127 short messages by way of node 12, which passes them on to the wire to
* node 10 while node 10 is busy: that wire is then full but for 8 cells. Node 10 has sent node 12 text
+3 -3
View File
@@ -219,12 +219,12 @@ static int wire_do(void)
case 1: if (rx) how = span_ok(a, V4_WIRE_HEADER) || rx->mark >= rx->used ? v4_wire_look(rx, span_ok(a, V4_WIRE_HEADER) ? &n.mem[a] : hd) : V4_WIRE_NOT_A_MESSAGE; break;
case 2:
if (!rx) break;
if (v4_wire_look(rx, hd) != V4_WIRE_DONE) { how = V4_WIRE_NO_MESSAGE; break; }
if (!rx->looked || v4_wire_look(rx, hd) != V4_WIRE_DONE) { how = V4_WIRE_NO_MESSAGE; break; }
cells = v4_wire_cells(hd[6]) - V4_WIRE_HEADER;
how = span_ok(a, V4_WIRE_HEADER) && span_ok(b, cells) ? v4_wire_take(rx, &n.mem[a], cells ? &n.mem[b] : hd, cells) : V4_WIRE_NOT_A_MESSAGE;
break;
case 3: if (rx) how = rx->mark >= rx->used ? V4_WIRE_NO_MESSAGE : a == (v4_cell)CONSOLE_PORT ? v4_wire_move(rx, &wire_out) : V4_WIRE_NO_ONE; break;
case 4: if (rx) how = v4_wire_drop(rx); break;
case 3: if (rx) how = rx->mark >= rx->used || !rx->looked ? V4_WIRE_NO_MESSAGE : a == (v4_cell)CONSOLE_PORT ? v4_wire_move(rx, &wire_out) : V4_WIRE_NO_ONE; break;
case 4: if (rx) how = rx->looked ? v4_wire_drop(rx) : V4_WIRE_NO_MESSAGE; break;
case 5:
if (!tx) break;
cells = span_ok(a, V4_WIRE_HEADER) ? v4_wire_cells(n.mem[a + 6]) : 0;
+6 -5
View File
@@ -143,7 +143,7 @@ static int host_born(void *self)
v4_blocks_init(&node_blocks[k], BUF0_W);
if (k != 0) { /* Hera's kernel is wired where she is made */
node_kernel[k].take = node_kernel_take; node_kernel[k].give = 0; node_kernel[k].self = &pool[k]; node_kernel[k].pending = 0;
if (!v4_fabric_wire_device(&f, (unsigned)place, 0, &node_kernel[k])) return -1;
if (!v4_fabric_wire_kernel(&f, (unsigned)place, 0, &node_kernel[k])) return -1; /* it takes words, and no messages */
}
return place;
}
@@ -405,7 +405,7 @@ int main(void)
hera_place = (unsigned)place;
for (i = 0; i < 4096 && pool[0].n.mem[i] == 0; i++) { }
CHECK(i == 4096 && pool[0].n.p == PORT + (v4_cell)V4_PORT_ANY, "with nothing in it, listening at its ports");
CHECK(v4_fabric_wire_device(&f, hera_place, 0, &kernel) && v4_fabric_wire_device(&f, hera_place, 1, &console), "the kernel is on its port 0 and the console on its port 1");
CHECK(v4_fabric_wire_kernel(&f, hera_place, 0, &kernel) && v4_fabric_wire_device(&f, hera_place, 1, &console), "the kernel is on its port 0 and the console on its port 1");
for (i = 0; i < 400000 && v4_fabric_step(&f) != 0; i++) { }
CHECK(given_cells == f18_count && pool[0].n.doing && pool[0].n.do_op == 8 && !pool[0].n.stopped,
"it takes in the nucleus through its port, a word at a time, and sleeps until a message comes: %u of %u cells", given_cells, f18_count);
@@ -490,8 +490,7 @@ int main(void)
CHECK(strcmp(tell(11, ": X S\" 66 EMIT\" 14 SEND ; X"), "B") == 0 && printed_from == 14, "and to the one across from it, which it is not wired to: \"%s\"", printed);
CHECK(clock_of(10) > hera, "that went by a node in between");
}
CHECK(strcmp(tell(10, "(LOST) @ ."), "0 ") == 0 && strcmp(tell(11, "(LOST) @ ."), "2 ") == 0 && strcmp(tell(14, "(LOST) @ ."), "0 ") == 0,
"nothing was let go but the two answers that came back to node 11, which did not wait for them: \"%s\"", printed);
CHECK(strcmp(tell(10, "(LOST) @ ."), "0 ") == 0 && strcmp(tell(11, "(LOST) @ ."), "0 ") == 0 && strcmp(tell(14, "(LOST) @ ."), "0 ") == 0, "nothing was let go");
/* ---- they share the store (acceptance 3) ---- */
step_limit = 30000000ul;
@@ -515,7 +514,7 @@ int main(void)
CHECK(told(10, ": GO S\" W\" 12 SEND W ; GO"), "Hera sets node 12 writing and writes her own meanwhile: \"%s\"", printed);
for (i = 0; i < 10 && common_dev[(12 + i) * V4_BLOCK_BYTES] == 'a' && common_dev[(22 + i) * V4_BLOCK_BYTES] == 'b'; i++) { }
CHECK(i == 10, "all twenty blocks reached the store: the first that did not is %u", i);
CHECK(strcmp(tell(10, "(LOST) @ ."), "1 ") == 0 && strcmp(tell(12, "(LOST) @ ."), "0 ") == 0, "and no message was let go but node 12's answer to Hera, who did not wait for it: \"%s\"", printed);
CHECK(strcmp(tell(10, "(LOST) @ ."), "0 ") == 0 && strcmp(tell(12, "(LOST) @ ."), "0 ") == 0, "and no message was let go");
CHECK(waiting(10) && waiting(11) && waiting(12) && waiting(13) && waiting(14), "all five are waiting at their ports again");
/* ---- Hera kills a node by number, and tells the others it is gone (MESH.md 7b, 7d) ----
@@ -548,6 +547,8 @@ int main(void)
CHECK(waiting(12) && waiting(11) && waiting(10), "every node that is not stuck is asleep again");
CHECK(strcmp(tell(12, "12 100 * ."), "1200 ") == 0 && strcmp(tell(11, "11 100 * ."), "1100 ") == 0 && strcmp(tell(10, "1 2 + ."), "3 ") == 0, "and everything else keeps running: \"%s\"", printed);
CHECK(strstr(tell(12, "5 3 PORT!"), "No one on that port") != NULL && waiting(12), "a write to the port node 14 was on is an error at once, not a wait: \"%s\"", printed);
CHECK(strstr(tell(12, ": K0 S\" 1 DROP\" 0 0 SEND-ON ; K0"), "No one on that port") != NULL && strcmp(tell(12, "2100 BLOCK C@ ."), "65 ") == 0,
"a message sent by the port node 12's kernel is on is refused at once -- the kernel takes no messages -- and its blocks are asked for as before: \"%s\"", printed);
{
v4_cell refused = node_numbered(12)->mem[REFUSED], r11 = node_numbered(11)->mem[REFUSED];
CHECK(tell(12, ": AGAIN14 S\" 65 EMIT\" 14 SEND ; AGAIN14")[0] == 0 && node_numbered(12)->mem[REFUSED] == refused + 1 && waiting(12),