diff --git a/capsules/v4/nucleus-64.f18 b/capsules/v4/nucleus-64.f18 index e3657eb6..fa7a270f 100644 Binary files a/capsules/v4/nucleus-64.f18 and b/capsules/v4/nucleus-64.f18 differ diff --git a/docs/v4.0.0/MESH.md b/docs/v4.0.0/MESH.md index 97ac8d79..864e5d32 100644 --- a/docs/v4.0.0/MESH.md +++ b/docs/v4.0.0/MESH.md @@ -794,7 +794,7 @@ the engine and happens whole. | 6, first | The mark is put at the message at the front of the wire. | | 7, next | The mark is put at the message after the one it is at. | | 8, sleep | The node is blocked until a message has come to one of its wires since it last fetched "which wires have a message". | -| 9, sleep for room | The node is blocked until the wire has A cells free, or a message has come as in 8, whichever is first. | +| 9, sleep for room | If the wire has A cells free it answers 0 at once. If not, the node is blocked until a message has come as in 8, or a message has come off any of the wires that lead from it since it was last woken that way: it is then answered 0 if the wire has the A cells and 1 if not, and asks which wire has what (10). *(Changed 2026-10-08, fix round 1 of tasks 2 and 3: it was woken by room on the named wire only. A node may be waiting to put on several wires -- output it is passing on, the answers of texts it has not begun -- and can name one.)* | | 10, room | Has the wire A cells free? It answers at once and changes nothing. | - Each wire has a mark for the node that reads it: the message that @@ -835,6 +835,15 @@ waiting, the words that turned it, the list of NACKs owed, and it has been passed on 16 times, it is dropped, counted in `(LOST)`, and a NACK is put on the wire back toward its sender. A NACK or a GONE that cannot go is dropped and counted; nothing is owed for it. + *(Ruled 2026-10-08, fix round 1 of tasks 2 and 3:)* **but what a + node printed (type 2) and its word of how text ended (type 3) are + not dropped for want of room.** The message is left on the wire it + came by and gone past with next; what is behind it is served, but + for what goes the same way, which is left too so that the order is + kept; and it is moved when room appears. The node then sleeps for + room (9), which room on any of its wires ends. The code before step + 6e waited here; letting output go while the line said ok was a + step back. - *For itself:* it is taken and dealt with as now: text is done; a NACK is counted; a GONE from its centre is believed. - *`SEND`, how text ended, a NACK, a GONE* are built in the node's own @@ -842,10 +851,13 @@ waiting, the words that turned it, the list of NACKs owed, and others are counted in `(LOST)`. - *What text prints* is put when the output buffer fills and when the text ends. If the wire has no room the node sleeps for room (9). Woken - by a message instead, it passes on what is for other nodes, as above, - and sleeps again; text for itself from its console ends the text it is - doing in error 21, "Interrupted"; anything else for itself stays on - the wire. + with no room yet, it passes on what is for other nodes, as above, and + sleeps again; everything for itself stays on the wire. *(Reworded + 2026-10-08, ruled by Captain Bob in fix round 1 of tasks 2 and 3: as + first ruled, text from its console ended the text in error 21. A line + typed ahead while a text printed then cut the printing short. A text + waiting for room to print is not interrupted; a console line ends an + `AWAIT` only.)* - *`AWAIT`* sleeps (8) and, woken, goes through each wire with first and next: what is for other nodes is passed on; what would end its wait is taken -- the answer, a NACK or a believed GONE about the node waited diff --git a/v4/Makefile b/v4/Makefile index a0f8053e..7476317d 100644 --- a/v4/Makefile +++ b/v4/Makefile @@ -206,6 +206,10 @@ 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 ': 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; } @$(BINDIR)/starforth4-postfail < /dev/null > $(BINDIR)/postfail.txt; test $$? -ne 0 || { echo "hosted-check: a boot with a failing case did not fail"; exit 1; } @grep -q '^POST FAIL: fail.wrong out<5 5 $$' $(BINDIR)/postfail.txt || { echo "hosted-check: the failing case was not named"; cat $(BINDIR)/postfail.txt; exit 1; } diff --git a/v4/capsule/core.v4 b/v4/capsule/core.v4 index a50f52c5..1d0ce132 100644 --- a/v4/capsule/core.v4 +++ b/v4/capsule/core.v4 @@ -246,12 +246,28 @@ header CMOVE MINE: drop 1 (A-SELF) a! ! ; NOT: drop ; +\ ( port -- bit ) the bit for a port, by its number, in a word of a bit to a port +: (WBIT) + push 1 pop if Z -1 + FOR 2* NEXT ; + Z: drop ; + \ ( -- ) A MESSAGE FOR ANOTHER NODE is passed on (MESH.md section 7): the \ one the mark of the wire in (WIRE) is at, whose seven words are in (IN), \ is moved, whole, to the wire that leads toward that node. It is refused -\ instead if it has been passed on as often as a message may be; if there -\ is no way, or no one on the port; if that wire has no room for it; or if -\ that wire is the one room is kept on and it would take that room. +\ instead if it has been passed on as often as a message may be; or if +\ there is no way, or no one on the port. +\ WITH NO ROOM on that wire -- or none but the room kept on it, (KEEP) -- +\ text, a NACK and a GONE are refused, at once. But what a node printed +\ (type 2) and its word of how text ended (type 3) are NOT let go for want +\ of room: the message is left where it is, on the wire it came by, and +\ gone past; what is behind it is served; and it is moved on a later pass, +\ when there is room. Printing waits; it is not lost (MESH.md 7d.1, +\ ruling 3). (H-PORT) and (H-NEED) are the port of the first message left +\ so on this pass and the cells it needs there: a node that then sleeps, +\ sleeps for that room, (SLEEP), and is woken by room on any wire. +\ (H-MASK) has a bit for each port something was left for: what is behind +\ it for the same port is left too, though it be shorter and would fit, so +\ that what a node printed goes on in the order it came. \ HOW OFTEN. A message's fourth word is how many more nodes may pass it \ on; a node sends it with 16, and a move takes one off. The node that \ finds 1 there refuses it. A device sends it with 0, which is taken for @@ -262,13 +278,31 @@ header CMOVE 14 + -if OK jump SPENT POS: if OK -1 + if SPENT -16 + -if SPENT OK: drop - (IN) a! @ (WAY) dup (P-WAY) a! ! - (KEEP) a! @ xor if KEPT - GO: drop (P-WAY) a! @ (WIRE-A) b! !b 3 (DO) if MOVED + (IN) a! @ (WAY) (P-WAY) a! ! + (IN)+6 a! @ 3 + 2/ 2/ 7 + \ the cells it takes, + (P-WAY) a! @ (KEEP) a! @ xor if KEPT drop jump NEED + KEPT: drop 8 + \ and the room kept, if it is that wire + NEED: (P-NEED) a! ! + (P-WAY) a! @ -if WAY \ no way: refused SPENT: drop jump (REFUSE) + WAY: (WBIT) (H-MASK) a! @ and if CLEAR \ something is left for that port already? + FULL: drop (IN)+2 a! @ -2 + if HOLD -1 + if HOLD jump SPENT \ no room: only output and answers wait + CLEAR: drop + (P-NEED) a! @ (P-WAY) a! @ (ROOM?) (S-WIRE) a! @ (WIRE!) + if GO -1 + if FULL jump SPENT \ no one on that port: refused + GO: drop (P-WAY) a! @ (WIRE-A) b! !b 3 (DO) if MOVED jump SPENT MOVED: drop ; - KEPT: drop (IN)+6 a! @ 3 + 2/ 2/ 15 + (P-WAY) a! @ (ROOM?) - (S-WIRE) a! @ (WIRE!) if GO jump SPENT + HOLD: drop + (P-WAY) a! @ (WBIT) dup inv (H-MASK) a! @ and + ! + (H-PORT) a! @ -if HELD drop (P-WAY) a! @ (H-PORT) a! ! (P-NEED) a! @ (H-NEED) a! ! 7 (DO) drop ; + HELD: drop 7 (DO) drop ; + +\ ( -- ) SLEEP until a message comes -- or, if (SERVE) left a message +\ on a wire for want of room, until there is that room or a message comes +\ or any wire of this node's gains room (operation 9, MESH.md 7d.3). +: (SLEEP) + (H-PORT) a! @ -if ROOM drop 8 (DO) drop ; + ROOM: (WIRE!) (H-NEED) a! @ (WIRE-A) b! !b 9 (DO) drop ; \ ( -- ) ONE PASS OVER EVERY WIRE THAT HAS A MESSAGE. What is for another \ node is passed on, or refused; what is for this node is left where it @@ -278,7 +312,7 @@ header CMOVE \ looks for what is this node's own, (MINE). : (SERVE) (WIRE-HAVE) b! @b dup (S-HAVE) a! ! (S-MASK) a! ! - 0 (S-WIRE) a! ! + 0 (S-WIRE) a! ! -1 (H-PORT) a! ! 0 (H-MASK) a! ! W: (S-MASK) a! @ if END dup 2/ ! 1 and if NEXTW drop (S-WIRE) a! @ (WIRE!) 6 (DO) drop @@ -316,44 +350,29 @@ header CMOVE \ ( -- ) go past the message (MINE) found: it stays on its wire : (SKIP) (S-WIRE) a! @ (WIRE!) 7 (DO) drop ; -\ ( -- ) take the message (MINE) found, which has one word of text: its -\ seven words stay in (IN), and its word goes to (ONE) -: (TAKE1) (S-WIRE) a! @ (WIRE!) (IN) (WIRE-A) b! !b (ONE) (WIRE-B) b! !b 2 (DO) drop ; - -\ ( -- n ) the number of this node's console: the one it was told of, or -\ with none told of, whoever sent the text it is doing -: (A-CONSOLE) - (CONSOLE) a! @ if C0 ; - C0: drop (MSG)+1 a! @ ; - -\ ( -- ) A LINE FROM THE CONSOLE BREAKS A WAIT FOR ROOM (MESH.md 7d.4). -\ If text for this node from its console is on a wire, the text being -\ done ends in error 21, Interrupted; the text from the console stays -\ where it is, to be done when this one has been finished with. Only -\ text that is still being done is broken: not once it has ended in an -\ error -- NODE-ERROR holds the code from then until the next text begins -\ -- or been finished with, (QUIET), when what waits for room is only what -\ it printed and the word of how it ended. -: (BREAK?) - (QUIET) a! @ if TEXT drop ; - TEXT: drop NODE-ERROR b! @b if CLEAN drop ; - CLEAN: drop (MINE0) - L: (MINE) if NO - drop (IN)+2 a! @ -1 + if T - KEEP: drop (SKIP) jump L - T: drop (A-CONSOLE) (IN)+1 a! @ xor if BRK jump KEEP - BRK: drop NODE-ERROR b! 21 !b ; - NO: drop ; +\ ( -- 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 +\ was looked at -- and (ONE) is not its word: nothing is to be done for it. +: (TAKE1) (S-WIRE) a! @ (WIRE!) (IN) (WIRE-A) b! !b (ONE) (WIRE-B) b! !b 2 jump (DO) \ ( -- how ) WAIT UNTIL THE WIRE of the port (W-PORT) has (W-NEED) cells \ free. 0: it has. 2: no one is on that port. The node sleeps until -\ there is room or a message comes; woken by a message it passes on what -\ is for other nodes, (SERVE), and sleeps again. It is the one thing a -\ node waits to put: what a text prints (MESH.md 7d.1, ruling 3). +\ there is room, or a message comes, or another wire of its own gains +\ room; woken with no room yet, it passes on what is for other nodes, +\ (SERVE), and sleeps again. It is the one thing a node waits to put: +\ what a text prints (MESH.md 7d.1, ruling 3). +\ NOTHING BREAKS THIS WAIT but the room coming or the wire being cut +\ (ruled 2026-10-08): text for this node that comes meanwhile, from its +\ console or anyone, stays on its wire and is done when this text has been +\ finished with. A text whose printing can never go, because a stuck node +\ is on the way, waits until that node is killed (MESH.md 7d.6). : (WAIT-ROOM) L: (W-PORT) a! @ (WIRE!) (W-NEED) a! @ (WIRE-A) b! !b 9 (DO) if DONE -1 + if WOKEN drop 2 ; - WOKEN: drop (ROOM-SERVE) (SERVE) (BREAK?) jump L + WOKEN: drop (WIRE-HAVE) b! @b if QUIET \ no wire has a message: there is nothing to pass on, + drop (ROOM-SERVE) (SERVE) jump L \ and no more of the stacks is asked for than a wait needs + QUIET: drop jump L DONE: ; \ ( -- ) send what has been printed, to where (PRINT-TO) says, by the port @@ -363,10 +382,15 @@ header CMOVE \ wire is the one the answer goes by. The characters are made ready in \ (OUT-TEXT), not where they are: until the message is on the wire the \ buffer is as it was, and a fault on the way leaves it to be sent again. +\ The three cells after the last character are made zero first, so that +\ nothing printed earlier goes on the wire in the last word (they are in +\ the buffer, or the four free cells after it). \ The three cells and two return entries this needs are tried first. -\ WITH NO ONE ON THE PORT what was printed cannot be sent. While the text -\ is being done that is its error, 18; once it has been finished with -\ there is no one to tell: it is let go and counted in (LOST). +\ 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 +\ 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) (OUT^) a! @ (OUT) xor if NONE drop 0 0 0 drop drop drop 0 push 0 push pop drop pop drop @@ -375,16 +399,17 @@ header CMOVE SAME: drop 8 + NEED: (W-NEED) a! ! (PRINT-PORT) a! @ (W-PORT) a! ! (WAIT-ROOM) if ROOM - drop (QUIET) a! @ if TELL - LOSE: drop (OUT) (OUT^) a! ! (LOST) a! @ 1 + ! ; + NOONE: drop (QUIET) a! @ if TELL + drop (OUT) (OUT^) a! ! (LOST) a! @ 1 + ! 2 (LINE-STATUS) a! ! ; TELL: drop NODE-ERROR b! 18 !b ; ROOM: drop (PRINT-TO) a! @ 2 (HDR) + (OUT^) a! @ a! 0 !+ 0 !+ 0 ! (OUT^) a! @ (OUT) - dup (OUT-HDR)+6 a! ! 3 + 2/ 2/ -1 + push (OUT-TEXT) pop (OUT) a! FOR @+ @+ 8* + @+ 8* 8* + @+ 8* 8* 8* + over b! !b 1 + NEXT drop - (OUT-TEXT) (PRINT-PORT) a! @ (PUT-ON) if SENT jump LOSE + (OUT-TEXT) (PRINT-PORT) a! @ (PUT-ON) if SENT jump NOONE SENT: drop (OUT) (OUT^) a! ! ; NONE: drop ; diff --git a/v4/capsule/quit.v4 b/v4/capsule/quit.v4 index a1058e47..0f9f7dc5 100644 --- a/v4/capsule/quit.v4 +++ b/v4/capsule/quit.v4 @@ -80,13 +80,17 @@ macro R-CLEAR RSTACK-DEPTH b! a !b endmacro OK: drop 1 jump (FINISH) GO: drop 0 jump (FINISH) -\ ( -- flag ) A GONE has been taken: its seven words are in (IN) and the -\ node it is about in (ONE). It is believed only if this node's centre -\ sent it (MESH.md 7b.4): the way to the node is then forgotten, and the -\ flag is -1. Otherwise nothing is done, and 0. +\ ( -- flag ) A GONE has been taken: its seven words are in (IN), the +\ node it is about in (ONE), and (S-WIRE) is the wire it came by. It is +\ believed only if it came by the port that leads to this node's centre +\ and says it is from the centre (MESH.md 7b.4): the way to the node is +\ then forgotten, and the flag is -1. Otherwise nothing is done, and 0. +\ (As first built only what it said it was from was looked at, and one +\ that came by another port was believed.) : (GONE?) - (CENTRE) if NOT (IN)+1 a! @ xor if YES drop 0 ; + (CENTRE) if NOT (IN)+1 a! @ xor if FROM drop 0 ; NOT: ; + FROM: drop (ROUTE-DEFAULT) a! @ (PORT) - (S-WIRE) a! @ xor if YES drop 0 ; YES: drop (ONE) a! @ NO-ROUTE -1 ; \ ( -- ) WAITING (MESH.md 7d.4). A node with nothing to do passes on @@ -94,17 +98,25 @@ macro R-CLEAR RSTACK-DEPTH b! a !b endmacro \ come for itself, the lowest port first and each wire from its front: \ text is taken, into TIB, and done: (LINE), and then (FINISH) says \ how it ended. But not unless the wire its answer goes by -- -\ the way to whoever sent it, or with none known the wire it -\ came on -- has room for the answer, 8 cells. If it has not, -\ the text stays on its wire and the node looks further; that -\ room is kept from then until the answer has gone. +\ the way to whoever sent it; with none known, or no one on +\ that port, the wire it came on -- has room for the answer, +\ 8 cells. If it has not, the text stays on its wire and the +\ node looks further: there may be several such, on wires +\ whose answers go different ways, and each is begun when +\ its own has the room. That room is kept from then until +\ 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: an answer no one waits for, or what is not as +\ 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. +\ 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. \ With nothing to begin it sleeps, executing nothing, until a message -\ comes; or, if text is waiting for room for its answer, until that wire -\ has the room or a message comes. Then it does all of this again. +\ 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 +\ from it or a message comes. Then it does all of this again. \ It has fetched which wires have a message before it looks at any, and \ sleeps after: a message that comes while it looks wakes it at once, and \ one it left on a wire does not keep it awake (MESH.md 7d.3). @@ -117,23 +129,29 @@ 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 jump M \ let go + OTHER: drop 4 (DO) drop (LOST) a! @ 1 + ! jump M \ let go, and counted NACK: drop (IN)+6 a! @ -4 + if N1 jump OTHER - N1: drop (TAKE1) (REFUSED) a! @ 1 + ! jump M \ a message of this node's was refused: counted + N1: drop (TAKE1) if N2 drop 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) (GONE?) drop jump M + G1: drop (TAKE1) if G2 drop 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 T1: (DONE-PORT) a! ! - 8 (DONE-PORT) a! @ (ROOM?) -1 + if FULL drop jump START + 8 (DONE-PORT) a! @ (ROOM?) if START -1 + if FULL + drop (S-WIRE) a! @ (DONE-PORT) a! ! \ no one is on that port: back by the wire it came on, + 8 (S-WIRE) a! @ (ROOM?) -1 + if FULL jump START \ if that has the room -- or has no one on it either FULL: drop (DEFER) a! @ -if LATER drop (DONE-PORT) a! @ (DEFER) a! ! (SKIP) jump M LATER: drop (SKIP) jump M REST: drop - (DEFER) a! @ -if ROOM drop 8 (DO) drop jump L + (DEFER) a! @ -if ROOM drop (SLEEP) jump L ROOM: (WIRE!) 8 (WIRE-A) b! !b 9 (DO) drop jump L - START: + START: drop (S-WIRE) a! @ dup (REPLY) a! ! (WIRE!) - (MSG) (WIRE-A) b! !b TIB 2/ 2/ (WIRE-B) b! !b 2 (DO) drop \ its seven words, and its text + (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 + TAKEN: drop 0 (MSG)+6 a! @ TIB + C! \ a zero after the text (MSG)+6 a! @ SPAN a! ! (DONE-PORT) a! @ (KEEP) a! ! \ the room for its answer is kept from here @@ -230,7 +248,9 @@ header NO-ROUTES \ (REFUSED), as a refusal that came back would be, and is error 19, Message \ refused; with no one on the port, error 18. Nothing is on a \ wire until the put, the last thing done. More characters than a message -\ carries, or fewer than none, is error 12. +\ carries, or fewer than none, is error 12. What lies after the text in +\ this node's memory does not go with it: the last word is made zero past +\ the last character. : (SEND-ON) push 1 (HDR) -if POS @@ -242,9 +262,16 @@ header NO-ROUTES 3 + 2/ 2/ if NONE -1 + FOR dup C@ over 1 + C@ 8* + over 2 + C@ 8* 8* + over 3 + C@ 8* 8* 8* + (SEND^) b! @b a! !+ a !b 4 + NEXT - drop jump PUT - NONE: drop drop - PUT: (SEND-TEXT) pop (PUT-ON) + drop + (OUT-HDR)+6 a! @ 3 and if PUT \ the last word: nothing in it past the last character + (SEND^) b! @b -1 + a! + -1 + if M1 -1 + if M2 + drop @ 16777215 and ! jump PUT0 + M2: drop @ 65535 and ! jump PUT0 + M1: drop @ 255 and ! jump PUT0 + NONE: drop + PUT: drop + PUT0: (SEND-TEXT) pop (PUT-ON) if SENT -1 + if FULL drop NODE-ERROR b! 18 !b ; FULL: drop (REFUSED) a! @ 1 + ! NODE-ERROR b! 19 !b ; SENT: drop ; @@ -273,6 +300,12 @@ header SEND-ON (ROUTE-DEFAULT) a! @ if NONE (PORT) - (NEAR) + a! @ ; NONE: ; +\ ( -- n ) the number of this node's console: the one it was told of, or +\ with none told of, whoever sent the text it is doing +: (A-CONSOLE) + (CONSOLE) a! @ if C0 ; + C0: drop (MSG)+1 a! @ ; + \ ( what -- how | ) the wait is over: 1 the answer has come, and its \ word, in (ONE), is left; 2 refused, error 19; 3 the node is gone, error \ 20; 4 a line from the console, error 21 @@ -308,8 +341,8 @@ header SEND-ON \ has left too little ends "Stack overflow" with nothing taken off a wire. header AWAIT : AWAIT - (AWAIT-FROM) a! ! 0 (A-SELF) a! ! - (ROOM) + (ONE) a! ! (ROOM) \ if this faults, no one is waited for: + (ONE) a! @ (AWAIT-FROM) a! ! 0 (A-SELF) a! ! \ (AWAIT-FROM) is set only when the stack has been tried L: (SERVE) (A-SELF) a! @ if SCAN drop 2 jump (A-END) \ this node's own message for that node came back to it SCAN: drop (MINE0) @@ -320,17 +353,20 @@ 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) 1 jump (A-END) + A2: drop (TAKE1) if A3 drop 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) (ONE) a! @ (AWAIT-FROM) a! @ xor if N2 \ about the node waited for? + N1: drop (TAKE1) if N0 drop 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) (GONE?) if M0 \ only its centre is believed + G1: drop (TAKE1) if G0 drop 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) M0: drop jump M - SLEEP: drop 8 (DO) drop jump L + SLEEP: drop (SLEEP) jump L \ ( w port -- ) write a cell to the port, by its number. It waits until \ the neighbour has taken it. This is how a node is sent a capsule of F18 diff --git a/v4/include/v4/fabric.h b/v4/include/v4/fabric.h index 12ae10c5..f321d054 100644 --- a/v4/include/v4/fabric.h +++ b/v4/include/v4/fabric.h @@ -147,10 +147,18 @@ void v4_fabric_wake(v4_fabric *f, unsigned id); * the node is let go: put, take, move and drop and the rest (MESH.md 7d.3). * 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 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. + * since it last fetched which wires have one. 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. + * SLEEP FOR ROOM (9) answers 0 at once if the wire it names has the room, + * and 1 at once if it can never have it. Otherwise the node sleeps until a + * message has come, as for 8, or a message has come off ANY of the wires + * that lead from it -- taken, moved on or dropped by whoever reads that wire + * -- since it was last woken that way; then it is answered 0 if the wire it + * named now has the room and 1 if not, and it asks which wire has what + * (10). A node may be waiting to put on several wires at once and can name + * only one. A wake-up that finds nothing to do is answered by asking + * again. * * Time passes: every node that is awake and not blocked executes one * instruction word; then every write with a reader waiting on the other end diff --git a/v4/include/v4/wire.h b/v4/include/v4/wire.h index 859d0b62..ad588b2f 100644 --- a/v4/include/v4/wire.h +++ b/v4/include/v4/wire.h @@ -39,6 +39,7 @@ typedef struct { unsigned used; /* how many cells they take */ 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 */ } 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 }; diff --git a/v4/src/fabric.c b/v4/src/fabric.c index 0c167ad3..74028a58 100644 --- a/v4/src/fabric.c +++ b/v4/src/fabric.c @@ -316,6 +316,17 @@ static int arrived(const v4_fabric *f, unsigned id) return 0; } +/* has a message come off any of the wires that lead from the node since it was last woken by that */ +static int freed(const v4_fabric *f, unsigned id) +{ + unsigned k; + for (k = 0; k < V4_PORTS; k++) { + const v4_wire_queue *q = f->place[id].wire[k].tx; + if (q && q->freed) return 1; + } + return 0; +} + /* Node `id` is doing an operation. Returns 1 and puts how it went in * *how, or returns 0: it waits (8 and 9 only). Nothing is copied, put or * taken unless the answer is 0 -- done. */ @@ -372,7 +383,9 @@ static int operate(v4_fabric *f, unsigned id, v4_cell *how) if (!tx) { *how = V4_WIRE_NO_ONE; 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; + if (!arrived(f, id) && !freed(f, id)) return 0; + for (cells = 0; cells < V4_PORTS; cells++) /* woken: the room that has come on any of its wires is seen */ + if (f->place[id].wire[cells].tx) f->place[id].wire[cells].tx->freed = 0; *how = V4_WIRE_NO_ROOM; break; case 10: /* room */ diff --git a/v4/src/wire.c b/v4/src/wire.c index d524aa1d..7903077d 100644 --- a/v4/src/wire.c +++ b/v4/src/wire.c @@ -13,6 +13,7 @@ void v4_wire_reset(v4_wire_queue *q) q->used = 0; q->mark = 0; q->arrived = 0; + q->freed = 0; } unsigned v4_wire_cells(v4_cell length) @@ -89,6 +90,7 @@ static void remove_marked(v4_wire_queue *q) for (i = q->mark; i + n < q->used; i++) q->cell[slot(q, i)] = q->cell[slot(q, i + n)]; } q->used -= n; + q->freed = 1; } int v4_wire_take(v4_wire_queue *q, v4_cell *header7, v4_cell *text, unsigned text_cap) diff --git a/v4/system/boot.c b/v4/system/boot.c index 49ff118b..124e18ce 100644 --- a/v4/system/boot.c +++ b/v4/system/boot.c @@ -93,15 +93,18 @@ static uint32_t word_count(const v4_node *n, const v4_image *im) /* What the kernel keeps of the node's block window (blocks.h). */ static v4_blocks boot_blocks; -/* THE WIRES (docs/v4.0.0/MESH.md 7d.5). The lone node has no fabric: the - * boot keeps the queues of its two wires, a queue each way (wire.h), and - * does what the node asks of them, as a fabric does for a node of a mesh - * (fabric.c). Every other port has no one on it. The kernel's wire has - * its queues as any wire has; the kernel puts nothing on it and takes - * nothing off: its requests are words, not messages. */ -static v4_wire_queue boot_wire[2][2]; /* by port: [0] to the node, [1] from it */ -#define TO_NODE(port) (&boot_wire[port][0]) -#define FROM_NODE(port) (&boot_wire[port][1]) +/* THE WIRE (docs/v4.0.0/MESH.md 7d.5). The lone node has no fabric: the + * boot keeps the two queues of its console's wire, one each way (wire.h), + * and does what the node asks of them, as a fabric does for a node of a + * mesh (fabric.c). + * THE KERNEL'S PORT HAS NO QUEUE (ruled 2026-10-08). The kernel is asked by + * words written to port 0 and takes no messages: a message for that port + * -- SEND-ON to it, or what a text prints when the way to its console has + * been set there -- is refused at once, as on any port with no one to take + * messages: the node's error 18, "No one on that port". */ +static v4_wire_queue boot_wire[2]; /* the console's: [0] to the node, [1] from it */ +#define TO_NODE (&boot_wire[0]) +#define FROM_NODE (&boot_wire[1]) /* 1 if `count` words from `addr` are the node's memory and not its ports * or the addresses after them: they may be copied to and from. */ @@ -113,20 +116,17 @@ static int span_ok(const v4_node *n, v4_cell addr, unsigned count) return 1; } -static int boot_arrived(void) -{ - return TO_NODE(KERNEL_PORT)->arrived || TO_NODE(CONSOLE_PORT)->arrived; -} /* The node is blocked at an operation on a wire (node.h, v4_node_doing): * do it, whole, and let the node go on; the operations and their answers * are MESH.md 7d.3's, as fabric.c does them. Returns 0 if the node sleeps * on: it asked to sleep (8), and no message has come since it last asked - * which wires have one; or for room (9) that is not there. */ + * which wires have one; or for room (9) that is not there, and nothing + * has come off its wire either. */ static int boot_do(const v4_boot *bt) { v4_node *n = bt->n; - v4_wire_queue *rx = n->wire <= CONSOLE_PORT ? TO_NODE(n->wire) : 0, *tx = n->wire <= CONSOLE_PORT ? FROM_NODE(n->wire) : 0; + v4_wire_queue *rx = n->wire == CONSOLE_PORT ? TO_NODE : 0, *tx = n->wire == CONSOLE_PORT ? FROM_NODE : 0; v4_cell a = n->wire_a, b = n->wire_b, h[V4_WIRE_HEADER], how = V4_WIRE_NO_ONE; unsigned cells; @@ -146,7 +146,7 @@ static int boot_do(const v4_boot *bt) case 3: /* move */ if (!rx) break; if (rx->mark >= rx->used) how = V4_WIRE_NO_MESSAGE; - else how = a >= 0 && a <= (v4_cell)CONSOLE_PORT ? v4_wire_move(rx, FROM_NODE((unsigned)a)) : V4_WIRE_NO_ONE; + 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); @@ -164,14 +164,15 @@ static int boot_do(const v4_boot *bt) if (rx) how = v4_wire_next(rx); break; case 8: /* sleep */ - if (!boot_arrived()) return 0; + if (!TO_NODE->arrived) return 0; how = V4_WIRE_DONE; break; case 9: /* sleep for room */ if (!tx) break; if (a < 0 || a > (v4_cell)V4_WIRE_CELLS) { how = V4_WIRE_NO_ROOM; break; } if (v4_wire_room(tx, (unsigned)a) == V4_WIRE_DONE) { how = V4_WIRE_DONE; break; } - if (!boot_arrived()) return 0; + if (!TO_NODE->arrived && !FROM_NODE->freed) return 0; /* a message has come, or the console has taken one */ + FROM_NODE->freed = 0; how = V4_WIRE_NO_ROOM; break; case 10: /* room */ @@ -192,11 +193,10 @@ static int boot_do(const v4_boot *bt) static void boot_exec(const v4_boot *b) { v4_node *n = b->n; - v4_node_have(n, (TO_NODE(KERNEL_PORT)->used ? 1u << KERNEL_PORT : 0u) | (TO_NODE(CONSOLE_PORT)->used ? 1u << CONSOLE_PORT : 0u)); + v4_node_have(n, TO_NODE->used ? 1u << CONSOLE_PORT : 0u); (void)v4_exec_step_word(n, b->es, b->h); if (n->have_fetched) { - TO_NODE(KERNEL_PORT)->arrived = 0; - TO_NODE(CONSOLE_PORT)->arrived = 0; + TO_NODE->arrived = 0; n->have_fetched = 0; } } @@ -225,14 +225,14 @@ static int run_line(const v4_boot *b, const char *text, unsigned len, char *keep if (text) { /* with none, the node has a line waiting on its wire (boot.h) */ if (!v4_message_text(&out, 0, CONSOLE_ID, V4_MSG_TEXT, text, len)) return V4_BOOT_LINE_TOO_LONG; - if (v4_wire_put(TO_NODE(CONSOLE_PORT), out.word, out.word + V4_MSG_HEADER) != V4_WIRE_DONE) return V4_BOOT_LINE_STOPPED; + if (v4_wire_put(TO_NODE, out.word, out.word + V4_MSG_HEADER) != V4_WIRE_DONE) return V4_BOOT_LINE_STOPPED; } for (;;) { int awake = boot_step(b); if (n->stopped) return V4_BOOT_LINE_STOPPED; - v4_wire_first(FROM_NODE(CONSOLE_PORT)); /* what the node has sent its console, in the order it was sent */ - while (v4_wire_take(FROM_NODE(CONSOLE_PORT), in, in_text, V4_MSG_MAX_CHARS / 4u) == V4_WIRE_DONE) { + v4_wire_first(FROM_NODE); /* what the node has sent its console, in the order it was sent */ + while (v4_wire_take(FROM_NODE, in, in_text, V4_MSG_MAX_CHARS / 4u) == V4_WIRE_DONE) { if (in[2] == V4_MSG_OUTPUT) { for (i = 0; i < (unsigned)in[6]; i++) { char c = (char)(((v4_ucell)in_text[i / 4u] >> (8u * (i % 4u))) & 0xFFu); @@ -248,7 +248,10 @@ static int run_line(const v4_boot *b, const char *text, unsigned len, char *keep } } if (!awake) { - if (n->do_op == 9) continue; /* waiting for room, which the console has just made */ + if (n->do_op == 9) { /* waiting for room, which the console has just made */ + if (++steps > STEP_LIMIT) return V4_BOOT_LINE_STOPPED; + continue; + } return V4_BOOT_LINE_WAITING; /* waiting for an answer: only the console can end that */ } @@ -474,9 +477,8 @@ int v4_boot_run(const v4_boot *b) say(b, "V4: the nucleus is not for this build of the engine\nPARITY:FAIL\nPOST: FAILED\n"); return 0; } - /* its two wires, the kernel's and the console's, with nothing on them */ - v4_wire_reset(TO_NODE(KERNEL_PORT)); v4_wire_reset(FROM_NODE(KERNEL_PORT)); - v4_wire_reset(TO_NODE(CONSOLE_PORT)); v4_wire_reset(FROM_NODE(CONSOLE_PORT)); + /* its console's wire, with nothing on it */ + v4_wire_reset(TO_NODE); v4_wire_reset(FROM_NODE); v4_blocks_init(&boot_blocks, b->im->block_window); if (!load_nucleus(b)) { say(b, "PARITY:FAIL\nPOST: FAILED\n"); return 0; } diff --git a/v4/tests/host_map.h b/v4/tests/host_map.h index aaffb1f3..df9bce4a 100644 --- a/v4/tests/host_map.h +++ b/v4/tests/host_map.h @@ -95,12 +95,17 @@ #define OUT_HDR (BUF0_W - 112) /* the seven words of a message this node is putting on a wire */ #define W_NEED (BUF0_W - 105) /* the wait for room: how many cells, */ #define W_PORT (BUF0_W - 104) /* and on which port's wire */ -#define P_WAY (BUF0_W - 103) /* the port a message being passed on goes by */ +#define P_WAY (BUF0_W - 103) /* the port a message being passed on goes by, */ +#define P_NEED (BUF0_W - 100) /* and the cells it needs there */ +#define H_PORT (BUF0_W - 99) /* the port of the first message left on its wire for want of room on this pass; -1: none */ +#define H_NEED (BUF0_W - 98) /* the cells it needs there */ +#define H_MASK (BUF0_W - 97) /* and a bit for each port something was left for */ #define A_SELF (BUF0_W - 102) /* 1: a message of this node's own, for the node it waits on, came back and was let go here */ #define SEND_PTR (BUF0_W - 101) /* where the next word of a text being made ready to send goes */ #define OUT_TEXT (OUT_W - 64) /* what was printed, four characters to a cell, as it goes on the wire: 64 cells */ #define SEND_TEXT (OUT_W - 400) /* the text of a message SEND is putting, four characters to a cell: 256 cells */ -/* FREE, plain memory: BUF0_W - 8; - 32 .. - 21; - 100 .. - 97; - 115 .. - 113; and OUT_W - 144 .. OUT_W - 65. They and +/* FREE, plain memory: BUF0_W - 8; - 32 .. - 21; - 122 .. - 113; BUF0_W - 144 .. - 141 (what a flush makes zero after + * a full output buffer); and OUT_W - 144 .. OUT_W - 65. They and * the cells above held the messages waiting, the refusals owed and what went with them (MESH.md 7a, 7b), which a * node no longer keeps. */ #define AWAIT_FROM (BUF0_W - 39) /* the node whose word of how text ended is waited for */ @@ -228,6 +233,10 @@ static int host_load(v4_text *tx, v4_node *n, const char *const *files, unsigned v4_text_constant(tx, "(W-NEED)", W_NEED); v4_text_constant(tx, "(W-PORT)", W_PORT); v4_text_constant(tx, "(P-WAY)", P_WAY); + v4_text_constant(tx, "(P-NEED)", P_NEED); + v4_text_constant(tx, "(H-PORT)", H_PORT); + v4_text_constant(tx, "(H-NEED)", H_NEED); + v4_text_constant(tx, "(H-MASK)", H_MASK); v4_text_constant(tx, "(A-SELF)", A_SELF); v4_text_constant(tx, "(SEND^)", SEND_PTR); v4_text_constant(tx, "(OUT-TEXT)", OUT_TEXT); diff --git a/v4/tests/test_fabric.c b/v4/tests/test_fabric.c index f59db470..39339ae3 100644 --- a/v4/tests/test_fabric.c +++ b/v4/tests/test_fabric.c @@ -743,9 +743,11 @@ int main(void) 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(!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 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"); + 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"); /* woken by a message coming instead */ v4_wire_reset(f.place[a].wire[0].tx); fill(f.place[a].wire[0].tx, 100); @@ -757,6 +759,51 @@ int main(void) (void)v4_fabric_step(&f); CHECK(!ND(a)->n.doing && ND(a)->n.how == 1 && f.place[a].wire[0].tx->used + 200 > V4_WIRE_CELLS, "it wakes, and how it went says 1: no room, but a message"); + /* SLEEP FOR ROOM IS WOKEN BY ROOM ON ANY OF THE NODE'S WIRES (MESH.md 7d.3): a node may be waiting to + * put on more than one, and names only one */ + 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 with two wires"); + fill(f.place[a].wire[0].tx, 0); + fill(f.place[a].wire[1].tx, 0); + (void)v4_node_fetch(&ND(a)->n, P_HAVE); + (void)v4_fabric_step(&f); + CHECK(ask(a, 0, 100, 0, 9) == -99, "both full: asleep for room on wire 0"); + 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(!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); + 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); + CHECK(!ND(c)->n.doing && !ND(a)->n.doing && ND(a)->n.how == 1, "room that comes in the step the node goes to sleep wakes it: no wake-up is lost"); + /* a device's take is a take */ + { + static const v4_device dev = { 0, 0, 0, 0 }; + v4_cell hh[7], tt[V4_WIRE_CELLS / 4]; + CHECK(v4_fabric_wire_device(&f, a, 4, &dev), "a device on a third wire"); + fill(f.place[a].wire[4].tx, 0); + CHECK(ask(a, 0, 100, 0, 9) == -99, "asleep for room on wire 0"); + CHECK(v4_fabric_device_take(&f, a, 4, hh, tt, V4_WIRE_CELLS / 4) == V4_WIRE_DONE, "the device takes a message between steps"); + (void)v4_fabric_step(&f); + CHECK(!ND(a)->n.doing && ND(a)->n.how == 1, "and that wakes it"); + } + /* a plain sleep is not woken by room */ + (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"); + /* move from one of a node's wires to another */ v4_fabric_init(&f, more_places, 8); v4_fabric_queues(&f, qpool, 8); diff --git a/v4/tests/test_host_depth.c b/v4/tests/test_host_depth.c index bb662fe9..f56b3845 100644 --- a/v4/tests/test_host_depth.c +++ b/v4/tests/test_host_depth.c @@ -7,11 +7,19 @@ * attempt at step 6d was tested on a shallow stack with every node there, * and hung on a deep one. Here node 11 does each thing a node does with a * message -- print, SEND, SEND with no way, AWAIT answered, AWAIT ended by a - * GONE, pass a message on while its own text waits for room -- with its data - * stack holding 20 to 32 values, and from 20 to 32 calls deep. After each, - * every node must answer, and node 11's stack must be as deep as the text - * made it, or its text must have ended in an error it reported. It may - * refuse; it may not hang, and it may not swallow a line. + * GONE, pass a message on while its own text waits for room, AWAIT broken + * by a line from the console, refuse a message while its own text waits for + * room, send a GONE -- with its data stack holding 20 to 32 values, and from + * 20 to 33 calls deep. + * + * EVERY RUN ENDS ONE OF TWO WAYS, and which is checked: the path WORKS, all + * of it, as it does on a shallow stack; or node 11's text ends in "Stack + * overflow" or "Return stack overflow" and nothing else -- with nothing + * half done: no message lost that was owed, no line swallowed. Where the + * stack is shallow enough it must work. And after each, every node must + * answer. "It came back" is not enough: with the nucleus's tries of the + * stack taken out -- (ROOM-SERVE), (ROOM) -- this test fails (it was + * mutated to see). */ #include "v4/image.h" #include "v4/fabric.h" @@ -173,16 +181,32 @@ static int row(void) } /* ---- the sweep ---- */ -static unsigned runs, bad; +static unsigned runs, bad, worked, refused_clean; -/* what the last path watches for: node 11 asleep for room, and, while it is, a message it has passed on to - * the wire to node 12 */ +/* what the paths that wait for room watch for: node 11 asleep for room, and, while it is, a message it has + * passed on to the wire to node 12 */ static unsigned saw_room_wait, saw_passed; static void watch_mid(void) { if (pool[mid].n.doing && pool[mid].n.do_op == 9) { saw_room_wait++; if (places[mid].wire[3].tx && places[mid].wire[3].tx->used > 0) saw_passed++; } } +/* A MESSAGE THAT HAS BEEN PASSED ON AS OFTEN AS IT MAY BE comes to node 11 from node 12, for a node 99: its + * fourth word is 1. No row of three can make one -- it takes sixteen nodes, or a ring -- so it is put on the + * wire here, once, the first time node 11 is asleep for room. Node 11 refuses it without looking for its way: + * the one refusal in which a message is dropped before the node's deepest use of its stacks. */ +static int spent_put; +static void put_spent(void) +{ + static const v4_cell hd[V4_WIRE_HEADER] = { 99, 12, V4_MSG_TEXT, 1, 0, 0, 4 }, text[1] = { 0x31 }; + if (!spent_put && places[mid].wire[3].rx) spent_put = v4_wire_put(places[mid].wire[3].rx, hd, text) == V4_WIRE_DONE; +} +static void watch_mid_spent(void) +{ + watch_mid(); + if (saw_room_wait) put_spent(); +} + /* words that push 1, 2, 4, 8 and 16 values and call nothing while they do */ static void fill(char *text, size_t cap, unsigned count) { @@ -193,17 +217,27 @@ static void fill(char *text, size_t cap, unsigned count) for (bit = 8; bit; bit >>= 1) if (count >= bit) { at += (size_t)snprintf(text + at, cap - at, "P%u ", bit); count -= bit; } } +#define PATHS 10u +static const char *const path_name[PATHS] = { "a text that prints", "SEND", "SEND with no way", "AWAIT answered", "AWAIT ended by a GONE", "passing on while its text waits for room", + "AWAIT broken by a line from the console", "a refusal while its text waits for room", "GONE", + "a spent message refused while its text waits" }; +static char numbers[8192]; /* what 1200 0 DO I . LOOP prints */ + /* A row made afresh for one path; node 11 is given the word ACT that takes it, and the words that fill the * stack and that call each other. */ static int made(unsigned path) { - static const char *const act[] = { + static const char *const act[PATHS] = { ": ACT 600 0 DO 65 EMIT LOOP ;", ": ACT S\" 1 DROP\" 12 SEND ;", ": ACT S\" 1 DROP\" 99 SEND ;", ": ACT S\" 1 DROP\" 12 SEND 12 AWAIT DROP ;", ": ACT 12 AWAIT DROP ;", ": ACT 1200 0 DO I . LOOP ;", + ": ACT 12 AWAIT DROP ;", + ": ACT 1200 0 DO I . LOOP ;", + ": ACT 44 12 GONE ;", + ": ACT 1200 0 DO I . LOOP ;", }; char line[64]; unsigned k; @@ -216,30 +250,60 @@ static int made(unsigned path) if (path == 2 && !told(11, "NO-ROUTES 1 2 ROUTE 10 2 ROUTE 12 3 ROUTE")) return 0; /* no way for any node it has not been told of */ if (path == 4 && !told(12, ": LL 400000 0 DO LOOP ;")) return 0; if (path == 5 && !told(10, ": SLOW 500000 0 DO LOOP ; : BUSY SLOW S\" 1 DROP\" 12 SEND SLOW ;")) return 0; + if (path == 9 && !told(10, ": SLOW 500000 0 DO LOOP ; : BUSY SLOW SLOW ;")) return 0; + /* 320 characters: more than fits beside what node 11 itself is waiting to print */ + if (path == 7 && !(told(10, ": SLOW 500000 0 DO LOOP ; : BUSY SLOW SLOW ;") && told(12, ": LATE 250000 0 DO LOOP PAD 320 BLANK PAD 320 99 SEND ;"))) return 0; return 1; } -/* Node 11 takes the path with `text`; `values` is how many values the text leaves on its stack if it completes. */ -static int one(unsigned path, const char *what, unsigned depth, const char *text, unsigned values) +/* 1 if `s` is `lead`, then some of `body` from its start, then exactly `end`. */ +static int is_cut_short(const char *s, const char *body, const char *end) { - static const char *const name[] = { "a text that prints", "SEND", "SEND with no way", "AWAIT answered", "AWAIT ended by a GONE", "passing on while its text waits for room" }; - const char *why = 0; - int how11; + size_t n = strlen(s), e = strlen(end); + return n >= e && strcmp(s + n - e, end) == 0 && strncmp(s, body, n - e) == 0 && n - e <= strlen(body); +} + +/* Node 11 takes the path with `text`; `values` is how many values the text leaves on its stack if it completes; + * `must` says the stack is shallow enough that it must work. Returns 1 if it worked, 0 if it ended in a clean + * stack error, and counts a failure if it did neither. */ +static int one(unsigned path, const char *what, unsigned depth, const char *text, unsigned values, int must) +{ + static char sixhundred[601]; + const char *why = 0, *over = 0; + v4_cell lost11 = pool[mid].n.mem[LOST], refused12 = pool[far].n.mem[REFUSED], ways12; + int how11, works = 0, clean_error = 0; unsigned left; + if (!sixhundred[0]) memset(sixhundred, 'A', 600); runs++; - forget(); step_cap = 60000000; - if (path == 4) { /* node 12 is busy for a long while and sends no answer; node 11 waits; its centre says 12 is gone */ + if (path == 8 && !told(12, "44 2 ROUTE")) why = "node 12 did not take a way"; + ways12 = pool[far].n.mem[ROUTE_COUNT]; + forget(); + saw_room_wait = 0; saw_passed = 0; + 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; if (!put_text(12, "LL") || !put_text(11, text) || !run()) why = "it did not come to wait"; else if (!put_text(10, "12 11 GONE") || !run()) why = "the GONE was not done"; stuck = 0; if (!why && !run()) why = "the nodes did not come to rest"; } else if (path == 5) { /* node 10, by which node 11 prints, is busy, and sends node 12 a message by node 11 meanwhile */ - saw_room_wait = 0; saw_passed = 0; watch = watch_mid; + watch = watch_mid; if (!put_text(11, text) || !put_text(10, "BUSY") || !run()) why = "the nodes did not come to rest"; watch = 0; + } else if (path == 6) { /* node 12 sends no answer; node 11 waits, and is typed to */ + if (!put_text(11, text) || !run()) why = "it did not come to wait"; + else if (!put_text(11, "77 .") || !run()) why = "the nodes did not come to rest"; + } else if (path == 7) { /* node 10 is busy; node 12 sends a message for 99 by node 11, whose way for it is the full wire */ + watch = watch_mid; + if (!put_text(11, text) || !put_text(10, "BUSY") || !put_text(12, "LATE") || !run()) why = "the nodes did not come to rest"; + watch = 0; + } else if (path == 9) { /* node 10 is busy; a spent message comes while node 11 waits for room -- or, if it never does, afterwards */ + spent_put = 0; watch = watch_mid_spent; + if (!put_text(11, text) || !put_text(10, "BUSY") || !run()) why = "the nodes did not come to rest"; + watch = 0; + if (!why && !spent_put) { put_spent(); if (!run()) why = "the nodes did not come to rest"; } } else { if (!put_text(11, text) || !run()) why = "the nodes did not come to rest"; } @@ -247,15 +311,43 @@ static int one(unsigned path, const char *what, unsigned depth, const char *text left = (unsigned)pool[mid].n.ds.depth; if (!why && !(asleep(hera) && asleep(mid) && asleep(far))) why = "a node is not asleep"; if (!why && ended < 1) why = "node 11's text was not answered"; - if (!why && how11 == V4_TEXT_COMPLETED && left != values) why = "its stack is not as deep as the text made it"; - if (!why && how11 == V4_TEXT_ERROR && !(strstr(printed, "overflow") || strstr(printed, "Argument out of range") || strstr(printed, "Node gone") || strstr(printed, "Message refused"))) - why = "its text ended in an error it did not report"; - if (!why && how11 != V4_TEXT_COMPLETED && how11 != V4_TEXT_ERROR) why = "its text ended neither way"; - if (!why && path == 0 && how11 == V4_TEXT_COMPLETED && printed_len != 600) why = "not all it printed arrived"; - if (!why && path == 5 && how11 == V4_TEXT_COMPLETED && !strstr(printed, " 1199 ")) why = "not all it printed arrived"; - if (!why && path == 5 && how11 == V4_TEXT_COMPLETED && !(saw_room_wait > 0 && saw_passed > 0)) why = "it did not wait for room, or passed nothing on while it did"; + + /* which of the two ways did it end? */ + if (!why) { + if (strstr(printed, "Return stack overflow\n")) over = "Return stack overflow\n"; + else if (strstr(printed, "Stack overflow\n")) over = "Stack overflow\n"; + 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 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; + clean_error = over && strcmp(printed, over) == 0; break; + case 4: works = how11 == V4_TEXT_ERROR && strcmp(printed, "Node gone\n") == 0 && left == values; + clean_error = over && strcmp(printed, over) == 0; break; + case 5: works = how11 == V4_TEXT_COMPLETED && strcmp(printed, numbers) == 0 && left == values && saw_room_wait > 0 && saw_passed > 0; + clean_error = over && is_cut_short(printed, numbers, over); break; + case 6: works = strcmp(printed, "Interrupted\n77 ") == 0 && ended == 2; + if (over) { char want[64]; snprintf(want, sizeof want, "%s77 ", over); clean_error = strcmp(printed, want) == 0 && ended == 2; } + break; + case 7: works = how11 == V4_TEXT_COMPLETED && strcmp(printed, numbers) == 0 && left == values && saw_room_wait > 0 + && pool[mid].n.mem[LOST] == lost11 + 1 && pool[far].n.mem[REFUSED] == refused12 + 1; + clean_error = over && is_cut_short(printed, numbers, over) && pool[far].n.mem[REFUSED] == refused12 + 1; break; /* whoever refused it, node 12 was told */ + case 8: works = how11 == V4_TEXT_COMPLETED && printed_len == 0 && left == values && pool[far].n.mem[ROUTE_COUNT] == ways12 - 1; + clean_error = over && strcmp(printed, over) == 0; break; + case 9: works = how11 == V4_TEXT_COMPLETED && strcmp(printed, numbers) == 0 && left == values && saw_room_wait > 0 + && pool[mid].n.mem[LOST] == lost11 + 1 && pool[far].n.mem[REFUSED] == refused12 + 1; + clean_error = over && is_cut_short(printed, numbers, over) && pool[mid].n.mem[LOST] == lost11 + 1 && pool[far].n.mem[REFUSED] == refused12 + 1; break; /* it was dropped: its sender was told */ + } + if (clean_error && !(path == 6 || (how11 == V4_TEXT_ERROR && left == 0))) clean_error = 0; /* a stack fault empties the stack */ + if (works && over) works = 0; + if (!works && !clean_error) why = "it neither worked nor ended in a stack error and nothing else"; + else if (!works && must) why = "it ended in a stack error where the stack is shallow enough for it to work"; + } /* every node comes back */ - if (!why && !(strcmp(tell(11, "ABORT"), "") == 0 && ended == 1 && pool[mid].n.ds.depth == 0)) why = "node 11 did not answer ABORT"; + if (!why && !(strcmp(tell(11, "ABORT"), "") == 0 && ended == 1 && pool[mid].n.ds.depth == 0 && pool[mid].n.mem[AWAIT_FROM] == 0)) why = "node 11 did not answer ABORT"; if (!why && path == 4 && !told(11, "12 3 ROUTE")) why = "node 11 did not take the way to 12 again"; if (!why && strcmp(tell(11, "1 2 + ."), "3 ") != 0) why = "node 11 did not answer"; if (!why && strcmp(tell(10, "1 2 + ."), "3 ") != 0) why = "node 10 did not answer"; @@ -264,11 +356,14 @@ static int one(unsigned path, const char *what, unsigned depth, const char *text checks++; if (why) { failures++; bad++; - printf("FAIL %s, %s %u: %s: \"%.60s\"\n", name[path], what, depth, why, printed); + printf("FAIL %s, %s %u: %s: how %d, %u left, lost %+ld, refused %+ld, waits %u/%u: \"%.50s\"\n", path_name[path], what, depth, why, how11, left, + (long)(pool[mid].n.mem[LOST] - lost11), (long)(pool[far].n.mem[REFUSED] - refused12), saw_room_wait, saw_passed, printed); show_stuck(text); (void)made(path); /* the next begins on a row that is whole */ + return -1; } - return how11; + if (works) worked++; else refused_clean++; + return works; } int main(void) @@ -299,30 +394,39 @@ int main(void) nucleus.mem[SRC] = TIB; nucleus.mem[LINE_STATUS] = 1; HOST_MESSAGES_EMPTY(&nucleus); + { size_t at = 0; for (k = 0; k < 1200; k++) at += (size_t)snprintf(numbers + at, sizeof numbers - at, "%u ", k); } - for (path = 0; path < 6; path++) { - unsigned before = bad, done = 0, errors = 0; + for (path = 0; path < PATHS; path++) { + unsigned before = bad, w0 = worked, e0 = refused_clean, first_error = 0; + int r; CHECK(made(path), "a row for path %u", path); if (failures > (int)bad) break; - /* the data stack at every depth from 20 to full */ + /* the data stack at every depth from 20 to full: with 24 or fewer every path must work */ for (k = V4_DATA_DEPTH - 12u; k <= V4_DATA_DEPTH; k++) { size_t at; fill(text, sizeof text, k); at = strlen(text); snprintf(text + at, sizeof text - at, "ACT"); - if (one(path, "values on the stack", k, text, k) == V4_TEXT_COMPLETED) done++; else errors++; + r = one(path, "values on the stack", k, text, k, k + 8u <= V4_DATA_DEPTH); + if (r == 0 && !first_error) first_error = k; } - /* and from every depth of calls from 20 to past the end of the return stack */ - for (k = V4_RET_DEPTH - 12u; k <= V4_RET_DEPTH + 1u; k++) { + printf(" %-42s %2u..%u values: works to %u, then a stack error and nothing else;", path_name[path], (unsigned)(V4_DATA_DEPTH - 12u), (unsigned)V4_DATA_DEPTH, first_error ? first_error - 1u : (unsigned)V4_DATA_DEPTH); + /* and from every depth of calls from 14 to past the end of the return stack: 21 deep or less every path + * must work -- 16 or less for the two that print and are woken while they wait for room, which go + * deepest: the loop, the word that prints, EMIT, the wait, and (SERVE) under it */ + first_error = 0; + for (k = V4_RET_DEPTH - 18u; k <= V4_RET_DEPTH + 1u; k++) { snprintf(text, sizeof text, "N%u", k); - (void)one(path, "calls deep", k, text, 0); + r = one(path, "calls deep", k, text, 0, k + ((path == 5 || path == 7 || path == 9) ? 16u : 11u) <= V4_RET_DEPTH); + if (r == 0 && !first_error) first_error = k; } - printf(" path %u: with %u to %u values on the stack, %u completed and %u ended in an error reported; %u failures\n", path, (unsigned)(V4_DATA_DEPTH - 12u), (unsigned)V4_DATA_DEPTH, done, errors, bad - before); + printf(" calls: works to %u deep; %u worked, %u clean errors, %u failures\n", first_error ? first_error - 1u : (unsigned)(V4_RET_DEPTH + 1u), worked - w0, refused_clean - e0, bad - before); } - /* ---- between texts: node 11 passes on, and refuses, with as many values waiting on its stack as a text - * may leave (MESH.md 7d.4: the rule of 28). Not one fault. ---- */ + + /* ---- between texts: node 11 passes on, refuses, and holds what it has no room to pass on, with as many + * values waiting on its stack as a text may leave (MESH.md 7d.4: the rule of 28). Not one fault. ---- */ CHECK(row() && told(11, ": P1 7 ; : P2 7 7 ; : P4 7 7 7 7 ; : P8 7 7 7 7 7 7 7 7 ;") && told(11, ": P16 P8 P8 ;") && told(11, "77 5 ROUTE") - && told(12, ": S77 S\" 1 DROP\" 77 SEND ;"), "a row; node 11's way to 77 is a port with no one on it"); + && told(12, ": S77 S\" 1 DROP\" 77 SEND ; : PR 1200 0 DO I . LOOP ;") && told(10, ": SLOW 500000 0 DO LOOP ; : BUSY SLOW SLOW ;"), "a row; node 11's way to 77 is a port with no one on it"); for (k = V4_DATA_DEPTH - 12u; k + 4u <= V4_DATA_DEPTH; k++) { unsigned faults; v4_cell lost = pool[mid].n.mem[LOST], refused = pool[far].n.mem[REFUSED]; @@ -331,11 +435,16 @@ int main(void) faults = pool[mid].n.faults; CHECK(strcmp(tell(12, "1 2 + ."), "3 ") == 0 && ended == 1, "with %u values waiting it passes text on to node 12, and what comes back", k); CHECK(told(12, "S77") && pool[mid].n.mem[LOST] == lost + 1 && pool[far].n.mem[REFUSED] == refused + 1, "and refuses a message it has no way for, and tells node 12"); + forget(); + saw_room_wait = 0; watch = watch_mid; + CHECK(put_text(12, "PR") && put_text(10, "BUSY") && run() && strcmp(printed, numbers) == 0 && saw_room_wait > 0, + "and holds what node 12 prints while node 10 is busy, asleep for room, and passes all of it on: %u characters", printed_len); + watch = 0; CHECK(pool[mid].n.faults == faults && pool[mid].n.ds.depth == k && asleep(mid), "without a fault, its stack as it was: %u values", (unsigned)pool[mid].n.ds.depth); CHECK(strcmp(tell(11, "ABORT"), "") == 0 && strcmp(tell(11, "1 2 + ."), "3 ") == 0, "and it goes on"); } - printf(" %u runs; %u in which a node did not come back\n", runs, bad); + printf(" %u runs: %u worked, %u ended in a stack error and nothing else, %u did neither\n", runs, worked, refused_clean, bad); printf(" %d checks, %d failures\n", checks, failures); return failures != 0; } diff --git a/v4/tests/test_host_mesh.c b/v4/tests/test_host_mesh.c index 4b0ca921..bc5d0436 100644 --- a/v4/tests/test_host_mesh.c +++ b/v4/tests/test_host_mesh.c @@ -46,6 +46,7 @@ static v4_cell printed_from; /* which node the last of it came from static int ended; /* how many messages of how text ended have come, */ static v4_cell ended_from, ended_how; /* and the last of them */ static unsigned ended_at; /* and how much had been printed when it came */ +static unsigned stale_out; /* output messages whose last word had anything in it past the last character */ static int nacks; /* how many NACKs have come to the console, */ static v4_cell nack_from, nack_about; /* and the last of them: who refused, and the node the refused message was for */ @@ -60,6 +61,7 @@ static unsigned console_takes(void) for (i = 0; i < (unsigned)hd[6] && printed_len + 1 < sizeof printed; i++) printed[printed_len++] = (char)(((v4_ucell)text[i / 4u] >> (8u * (i % 4u))) & 0xFFu); printed[printed_len] = 0; printed_from = hd[1]; + if (hd[6] % 4 != 0 && ((v4_ucell)text[hd[6] / 4] >> (8u * (unsigned)(hd[6] % 4))) != 0) stale_out++; } else if (hd[2] == V4_MSG_DONE) { ended++; ended_from = hd[1]; ended_how = text[0]; ended_at = printed_len; } else if (hd[2] == V4_MSG_NACK) { @@ -129,6 +131,17 @@ static int run(void) } return 0; } +/* Let time pass until `when` says so; 0 if it never does. */ +static int run_until(int (*when)(void), unsigned cap) +{ + unsigned steps; + for (steps = 0; steps < cap; steps++) { + if (when()) return 1; + (void)v4_fabric_step(&f); (void)console_takes(); steps_run++; + if (watch) watch(); + } + return when(); +} /* The console sends text to a node: the message is put on the wire to the node the console is on. */ static int put_text(v4_cell node, const char *text) { @@ -202,6 +215,22 @@ static int triangle(void) } static v4_uheat_t clock_of(unsigned id) { return pool[id].es.anticlock; } +/* The whole of the mesh as it stands, kept and put back: so that one moment can be gone on from many times. */ +static v4_fabric_node kept_pool[3]; +static v4_place kept_places[3]; +static v4_wire_queue kept_queues[QUEUES]; +static v4_fabric kept_f; +static void keep(void) { memcpy(kept_pool, pool, sizeof pool); memcpy(kept_places, places, sizeof places); memcpy(kept_queues, queues, sizeof queues); kept_f = f; } +static void put_back(void) { memcpy(pool, kept_pool, sizeof pool); memcpy(places, kept_places, sizeof places); memcpy(queues, kept_queues, sizeof queues); f = kept_f; forget(); } + +/* what a scene waits for */ +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 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++; } + /* what a scene watches for while it runs */ static unsigned saw_room_wait, saw_full; static void watch_far_waits(void) @@ -281,11 +310,14 @@ int main(void) /* ---- no way ---- */ nacks = 0; + i = (unsigned)pool[hera].n.mem[LOST]; CHECK(is_empty(tell(99, "1 2 + .")) && ended == 0, "text for a node there is no way to is not done by anyone"); CHECK(nacks == 1 && nack_from == 10 && nack_about == 99, "the console, which sent it, is told it was refused, by the node that could not pass it on: %d from %ld about %ld", nacks, (long)nack_from, (long)nack_about); - CHECK(strcmp(tell(10, "(LOST) @ ."), "1 ") == 0, "and that node has counted it"); + 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"); /* ---- waiting ---- */ { @@ -326,8 +358,10 @@ 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"); - 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); + /* 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); } /* ---- THE FLOOD: more than the wires hold (MESH.md 7d.6, acceptance 2) ---- @@ -353,16 +387,11 @@ int main(void) lost += pool[k].n.mem[LOST]; refused += pool[k].n.mem[REFUSED]; } - lost -= 1; /* the one for node 99, earlier */ 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); - CHECK(tried > sent && sent >= got && got > 0, "there was not room for all of them: some were refused at once"); - /* Every message that got on a wire and was let go on its way is counted exactly twice: once in (LOST) - * where it was let go, and once where its NACK ended -- in (REFUSED) at the node it was for, or in - * (LOST) if it could not go either. Every SEND refused at once is counted once, in (REFUSED). So - * (LOST) + (REFUSED) - the refused at once is twice the messages let go; and those are the messages - * the nodes sent each other that were not done, and answers to them. */ - CHECK((lost + refused - (tried - sent)) % 2 == 0 && lost + refused - (tried - sent) >= 2 * (sent - got), - "every message sent was done or was counted: %ld on a wire, %ld done; (LOST) + (REFUSED) %ld, of which %ld were refused at once", sent, got, lost + refused, tried - sent); + /* 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"); CHECK(strcmp(tell(12, "7 8 * ."), "56 ") == 0 && printed_from == 12, "and the nodes go on as before"); } @@ -413,6 +442,10 @@ int main(void) c = clock_of(far); for (i = 0; i < 500; i++) (void)v4_fabric_step(&f); CHECK(asleep(far) && clock_of(far) == c, "node 12 is asleep, not going round: it owes nothing"); + /* nothing but the text goes on the wire: not what lies after it in the node's memory */ + (void)tell(12, ": S5 S\" ABCDEXYZ\" DROP 5 11 SEND ; S5"); + CHECK(coming_to(mid, 3)->used == 8 + 9 && coming_to(mid, 3)->cell[(coming_to(mid, 3)->head + 16u) % V4_WIRE_CELLS] == 'E', + "five characters sent of eight: the last word on the wire holds the fifth and nothing else: %lx", (unsigned long)coming_to(mid, 3)->cell[(coming_to(mid, 3)->head + 16u) % V4_WIRE_CELLS]); 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); } @@ -472,7 +505,24 @@ int main(void) CHECK(strcmp(tell(12, "7 8 * ."), "56 ") == 0 && printed_from == 12 && asleep(far) && asleep(hera), "told another way to the console, it goes on: \"%s\"", printed); } - /* ---- a line from the console breaks a wait for room (MESH.md 7d.4) ---- */ + /* ---- NOTHING BREAKS A WAIT FOR ROOM TO PRINT (MESH.md 7d.4, ruled 2026-10-08) ---- + * A line typed ahead while a text prints more than the wire holds does not cut the printing short. */ + CHECK(triangle(), "the same made afresh"); + { + static char want[32768]; + size_t at = 0; + for (i = 0; i < 3000; i++) at += (size_t)snprintf(want + at, sizeof want - at, "%u ", i); + snprintf(want + at, sizeof want - at, "56 "); + CHECK(told(12, ": P 3000 0 DO I . LOOP ;") && told(10, ": SLOW 600000 0 DO LOOP ;"), "node 12 has a word that prints 3000 numbers, and node 10 one that takes a while"); + forget(); + saw_room_wait = 0; watch = watch_far_waits; + CHECK(put_text(10, "SLOW") && put_text(12, "P") && put_text(12, "7 8 * .") && put_text(10, "SLOW") && put_text(10, "SLOW") && run(), + "node 12 is typed two lines, the first of which prints more than the wire holds, while node 10 is busy"); + watch = 0; + CHECK(saw_room_wait > 0 && strstr(printed, "Interrupted") == NULL, "node 12 slept for room to print with the second line waiting on its wire, and was not interrupted"); + CHECK(strcmp(printed, want) == 0 && ended == 5 && pool[far].n.mem[LOST] == 0, "the first text printed everything, and then the second line was done: %u characters of %u, %d ended", printed_len, (unsigned)strlen(want), ended); + } + /* and with a stuck node on the way, where the room never comes, text for the node is left on its wire */ CHECK(triangle(), "the same made afresh"); { CHECK(told(12, ": P 3000 0 DO I . LOOP ;") && told(12, "NO-ROUTES 2 DEFAULT-ROUTE 10 3 ROUTE"), "node 12's way to the console is by node 11 again"); @@ -480,20 +530,131 @@ int main(void) (void)tell(11, ": C BEGIN 0 UNTIL ; C"); (void)tell(12, "P"); CHECK(ended == 0 && waits_for_room(far) && pool[far].n.mem[NODE_ERROR] == 0, "node 11 is stuck, and node 12 sleeps for room to print"); - (void)tell(11, "1 DROP"); (void)tell(10, ": T12 S\" 1 DROP\" 12 SEND ; T12"); - CHECK(waits_for_room(far) && pool[far].n.mem[NODE_ERROR] == 0 && coming_to(far, 3)->used == 9, - "text for node 12 from another node does not break the wait: it is left on the wire, and node 12 sleeps on: %u cells", coming_to(far, 3)->used); (void)tell(12, "65 EMIT"); - CHECK(pool[far].n.mem[NODE_ERROR] == 21 && waits_for_room(far) && coming_to(far, 3)->used == 18, - "text for it from its console does: the text it was doing ends in error 21, Interrupted; the line that broke it stays on the wire: error %ld, %u cells", + CHECK(waits_for_room(far) && pool[far].n.mem[NODE_ERROR] == 0 && pool[far].n.mem[QUIET] == 0 && coming_to(far, 3)->used == 18, + "text for node 12, from another node and from its console, is left on the wire: its text is not ended, and it sleeps on: error %ld, %u cells", (long)pool[far].n.mem[NODE_ERROR], coming_to(far, 3)->used); - (void)tell(12, "66 EMIT"); - CHECK(pool[far].n.mem[NODE_ERROR] == 21 && waits_for_room(far) && coming_to(far, 3)->used == 27 && pool[far].n.mem[OUT_PTR] <= OUT_W + OUT_CELLS, - "its message has still to wait for the room, and is not broken a second time: MESH.md 7d.6"); CHECK(strcmp(tell(10, "1 2 + ."), "3 ") == 0, "node 10 answers meanwhile"); } + /* ---- PRINTING THROUGH A MIDDLE NODE IS NOT LOST (ruled 2026-10-08) ---- + * In the row, what node 12 prints goes by node 11 to node 10, which is busy. The wire from 11 to 10 + * fills; node 11 does not let go what it has no room to pass on: it leaves it on the wire from 12, which + * fills in its turn, and node 12 waits. */ + CHECK(row(), "a row made afresh"); + { + static char want[32768]; + size_t at = 0; + for (i = 0; i < 3000; i++) at += (size_t)snprintf(want + at, sizeof want - at, "%u ", i); + CHECK(told(12, ": P 3000 0 DO I . LOOP ;") && told(10, ": SLOW 600000 0 DO LOOP ;"), "node 12 has a word that prints 3000 numbers, and node 10 one that takes a while"); + forget(); + saw_room_wait = 0; saw_mid_hold = 0; watch = watch_mid_holds; + CHECK(put_text(12, "P") && put_text(10, "SLOW") && put_text(10, "SLOW") && put_text(10, "SLOW") && put_text(10, "SLOW") && run(), + "node 12 prints them while node 10 is busy with one line after another"); + watch = 0; + CHECK(saw_mid_hold > 0, "node 11 left output on the wire it came by, for want of room, and slept for room"); + CHECK(strcmp(printed, want) == 0, "every number arrived, in order: %u characters of %u", printed_len, (unsigned)strlen(want)); + CHECK(ended == 5 && ended_at <= printed_len && pool[far].n.mem[LINE_STATUS] == 1 && pool[hera].n.mem[LOST] == 0 && pool[mid].n.mem[LOST] == 0 && pool[far].n.mem[LOST] == 0 && pool[far].n.mem[REFUSED] == 0, + "and then how the text ended; nothing was let go anywhere: %ld %ld %ld", (long)pool[hera].n.mem[LOST], (long)pool[mid].n.mem[LOST], (long)pool[far].n.mem[LOST]); + CHECK(asleep(hera) && asleep(mid) && asleep(far) && pool[mid].n.mem[H_PORT] == -1, "all three sleep, node 11 for a message and not for room"); + + /* the middle node removed while it holds output: node 12's text ends in an error; node 10 goes on */ + CHECK(row() && told(12, ": P 3000 0 DO I . LOOP ;") && told(10, ": SLOW 600000 0 DO LOOP ;"), "the same again"); + forget(); + CHECK(put_text(12, "P") && put_text(10, "SLOW") && put_text(10, "SLOW") && put_text(10, "SLOW") && put_text(10, "SLOW") && run_until(mid_holds, 20000000), + "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(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 */ + CHECK(row() && told(12, ": P 3000 0 DO I . LOOP ;") && told(10, ": SLOW 600000 0 DO LOOP ;"), "and again"); + forget(); + CHECK(put_text(12, "P") && put_text(10, "SLOW") && put_text(10, "SLOW") && put_text(10, "SLOW") && put_text(10, "SLOW") && run_until(mid_holds, 20000000), "node 11 is holding output again"); + CHECK(v4_fabric_remove(&f, far) == &pool[2] && run(), "node 12 is removed, and the two that are left come to rest"); + CHECK(asleep(mid) && asleep(hera) && pool[mid].n.mem[H_PORT] == -1 && coming_to(hera, 2)->used == 0, "node 11 sleeps, holding nothing; nothing is left on the wire to node 10"); + CHECK(printed_len > 0 && strncmp(printed, want, printed_len) == 0 && ended >= 4, "what node 12 had printed before it went arrived, in order: %u characters", printed_len); + CHECK(strcmp(tell(11, "20 22 + ."), "42 ") == 0 && strcmp(tell(10, "1 2 + ."), "3 ") == 0, "and nodes 10 and 11 go on"); + } + + /* ---- A TAKE THAT DOES NOT TAKE (fix round 1, F1) ---- + * The wire is cut at every step from a message's coming to its being taken. If it was still on the wire + * when the wire was cut it was not taken, and nothing may be done for it: not the text before it run + * again, not a refusal counted, not a wait ended with an old answer, not a way forgotten. */ + { + unsigned k, cut_before = 0, cut_after = 0, wrong = 0; + 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++) { /* text, taken by (IDLE) */ + int there; + v4_cell lost; + put_back(); + lost = pool[far].n.mem[LOST]; + for (i = 0; i < k; i++) { (void)v4_fabric_step(&f); (void)console_takes(); } + there = far_has_one(); + (void)v4_fabric_unwire(&f, mid, 3); + if (!run() || !asleep(far) || !v4_fabric_wire(&f, mid, 3, far, 2)) { wrong += 1000; break; } + if (there) cut_before++; else { if (!cut_after && getenv("V4_MESH_STUCK")) printf(" first cut after the take: %u steps\n", k); cut_after++; } + if (strcmp(tell(12, "N @ ."), there ? "1 " : "11 ") != 0 || pool[far].n.mem[LOST] != lost + (there ? 0 : 1)) { + if (!wrong) printf(" cut %u steps after the text came, it %s on the wire: N is %s, %ld counted lost\n", k, there ? "still" : "no longer", printed, (long)(pool[far].n.mem[LOST] - lost)); + wrong++; + } + } + CHECK(wrong == 0 && cut_before > 20 && cut_after > 0, "text: cut before it was taken (%u times) nothing was done; cut after (%u), the text was done once, and its answer counted: %u wrong", cut_before, cut_after, wrong); + + cut_before = cut_after = wrong = 0; + CHECK(row() && told(12, ": N77 S\" 1 DROP\" 77 SEND ;") && put_text(12, "N77") && run_until(far_has_nack, 1000000), "a NACK has come to node 12's wire"); + keep(); + for (k = 0; k < 200; k++) { /* a NACK, taken by (IDLE) */ + int there; + 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(); } + there = far_has_nack(); + (void)v4_fabric_unwire(&f, mid, 3); + if (!run() || !asleep(far)) { wrong += 1000; break; } + if (there) cut_before++; else { if (!cut_after && getenv("V4_MESH_STUCK")) printf(" first cut after the take: %u steps\n", k); cut_after++; } + if (pool[far].n.mem[REFUSED] != refused + (there ? 0 : 1)) wrong++; + } + CHECK(wrong == 0 && cut_before > 20 && cut_after > 0, "a NACK: cut before it was taken (%u times) no refusal was counted; cut after (%u), one: %u wrong", cut_before, cut_after, wrong); + + cut_before = cut_after = wrong = 0; + CHECK(row() && told(12, ": Q S\" 1 DROP\" 11 SEND 11 AWAIT 7 + . ;") && put_text(12, "Q") && run_until(far_awaits_and_has_one, 1000000), "an answer has come to the wire of node 12, which waits for it"); + keep(); + for (k = 0; k < 200; k++) { /* an answer, taken by AWAIT */ + int there; + put_back(); + for (i = 0; i < k; i++) { (void)v4_fabric_step(&f); (void)console_takes(); } + there = far_has_one(); + (void)v4_fabric_unwire(&f, mid, 3); + if (!run() || !asleep(far)) { wrong += 1000; break; } + if (there) cut_before++; else { if (!cut_after && getenv("V4_MESH_STUCK")) printf(" first cut after the take: %u steps\n", k); cut_after++; } + /* not taken: it waits on. Taken: the wait ended with the answer, 1, and "8 " was printed -- to a wire with no one on it. */ + if (there ? !(pool[far].n.mem[AWAIT_FROM] == 11 && pool[far].n.mem[QUIET] == 0) : !(pool[far].n.mem[AWAIT_FROM] == 0 && pool[far].n.mem[QUIET] == 1)) wrong++; + } + CHECK(wrong == 0 && cut_before > 5 && cut_after > 0, "an answer: cut before it was taken (%u times) the wait went on; cut after (%u), it ended: %u wrong", cut_before, cut_after, wrong); + + cut_before = cut_after = wrong = 0; + CHECK(row() && told(12, "55 2 ROUTE 44 2 ROUTE") && told(11, "55 12 GONE") && told(12, "55 2 ROUTE") && told(11, ": G44 44 12 GONE ;") && pool[far].n.mem[ROUTE_COUNT] == 2 + && put_text(11, "G44") && run_until(far_has_one, 1000000), "a GONE from its centre has come to node 12's wire"); + keep(); + for (k = 0; k < 200; k++) { /* a GONE, taken by (IDLE): node 11 is node 12's centre */ + int there; + v4_cell ways; + put_back(); + ways = pool[far].n.mem[ROUTE_COUNT]; + for (i = 0; i < k; i++) { (void)v4_fabric_step(&f); (void)console_takes(); } + there = far_has_one(); + (void)v4_fabric_unwire(&f, mid, 3); + if (!run() || !asleep(far)) { wrong += 1000; break; } + if (there) cut_before++; else { if (!cut_after && getenv("V4_MESH_STUCK")) printf(" first cut after the take: %u steps\n", k); cut_after++; } + if (pool[far].n.mem[ROUTE_COUNT] != ways - (there ? 0 : 1)) wrong++; + } + 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); + } + /* ---- 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 @@ -508,7 +669,7 @@ int main(void) (void)tell(10, "G"); watch = 0; CHECK(saw_full > 0, "node 12 slept for room to print with the wire to node 10 holding exactly the 8 cells kept for its answer (%u, %u)", saw_room_wait, saw_full); - CHECK(strcmp(printed, "A1 ") == 0 && pool[far].n.mem[LOST] == 0 && pool[hera].n.mem[LOST] == 0, + CHECK(strcmp(printed, "A1 ") == 0 && pool[far].n.mem[LOST] == 0, "what it printed arrived, and then how the text ended, which node 10 waited for: the answer was not refused: \"%s\"", printed); CHECK(asleep(hera) && asleep(mid) && asleep(far) && pool[far].n.mem[KEEP] == -1, "all three sleep, and node 12 keeps no room"); } @@ -579,6 +740,16 @@ int main(void) (void)tell(11, "33 AWAIT ."); CHECK(ended == 0 && told(10, "44 11 GONE") && pool[mid].n.mem[AWAIT_FROM] == 33 && asleep(mid), "a GONE from its centre about another node does not end a wait"); } + /* a GONE that says it is from the centre but comes by another port is not believed (MESH.md 7b.4) */ + CHECK(triangle(), "a row made afresh, with nodes 10 and 12 wired straight to each other"); + { + v4_cell ways; + CHECK(told(11, "44 3 ROUTE") && told(12, ": FORGE 10 (ME) ! 44 11 GONE 12 (ME) ! ;"), "node 11 has a way to 44; node 12 has a word that sends a GONE saying it is from node 10"); + ways = pool[mid].n.mem[ROUTE_COUNT]; + CHECK(told(12, "FORGE") && asleep(mid) && coming_to(mid, 3)->used == 0 && pool[mid].n.mem[ROUTE_COUNT] == ways, + "it comes to node 11 by the wire from node 12, not by its centre's: it is not believed, and the way stays: %ld ways of %ld", (long)pool[mid].n.mem[ROUTE_COUNT], (long)ways); + CHECK(told(10, "44 11 GONE") && pool[mid].n.mem[ROUTE_COUNT] == ways - 1, "from node 10, by its centre's port, it is"); + } /* interrupted: text from the console */ CHECK(row(), "a row made afresh"); { @@ -626,6 +797,7 @@ int main(void) * how it ended. It is looked at once an instruction word, so it is the least that was used; that 28 * values may wait is measured in test_host_quit.c and, for passing on and refusing, in test_host_depth.c. * The return stack is empty when a text ends; (FINISH)'s wait for room goes seven deep on it. */ + CHECK(stale_out == 0, "no message of what a node printed carried anything past its last character: %u did", stale_out); printf(" between texts the nucleus used at most %u cells of the data stack and %u entries of the return stack\n", between_data, between_ret); CHECK(between_data <= 4 && between_ret <= 7, "between texts: four cells of the data stack, and seven return entries"); diff --git a/v4/tests/test_host_quit.c b/v4/tests/test_host_quit.c index 21b94780..8350d140 100644 --- a/v4/tests/test_host_quit.c +++ b/v4/tests/test_host_quit.c @@ -237,7 +237,8 @@ static int wire_do(void) if (!tx) break; if (a < 0 || a > (v4_cell)V4_WIRE_CELLS) { how = V4_WIRE_NO_ROOM; break; } if (v4_wire_room(tx, (unsigned)a) == V4_WIRE_DONE) { how = V4_WIRE_DONE; break; } - if (!wire_in.arrived) return 0; + if (!wire_in.arrived && !wire_out.freed) return 0; + wire_out.freed = 0; how = V4_WIRE_NO_ROOM; break; case 10: if (tx) how = a < 0 ? V4_WIRE_NOT_A_MESSAGE : a <= (v4_cell)V4_WIRE_CELLS ? v4_wire_room(tx, (unsigned)a) : V4_WIRE_NO_ROOM; break; diff --git a/v4/tests/test_host_unit.c b/v4/tests/test_host_unit.c index 50ff1477..33ee7e51 100644 --- a/v4/tests/test_host_unit.c +++ b/v4/tests/test_host_unit.c @@ -490,7 +490,8 @@ 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) @ ."), "0 ") == 0 && strcmp(tell(14, "(LOST) @ ."), "0 ") == 0, "nothing was let go"); + 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); /* ---- they share the store (acceptance 3) ---- */ step_limit = 30000000ul; @@ -514,7 +515,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) @ ."), "0 ") == 0 && strcmp(tell(12, "(LOST) @ ."), "0 ") == 0, "and no message was let go"); + 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(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) ----