From b1d09af043c68aa981e6c572dd6a762ec36243dc Mon Sep 17 00:00:00 2001 From: rajames Date: Thu, 8 Oct 2026 15:59:37 -0400 Subject: [PATCH] feat(v4.0.0): messages are whole and wait on the wire -- a node keeps none but the one it is doing Step 6e, tasks 2 and 3 (docs/v4.0.0/MESH.md 7d). The nucleus puts, looks at, takes, moves and drops whole messages through the six addresses after its ports; its own ring of messages waiting, the refusals owed, (GATE) and the looking words' use are gone. SEND, an answer, a NACK and a GONE go or are refused at once; what a text prints waits for room; a text is not begun until the wire its answer goes by has room for it, and that room is kept. The lone node's host (v4/system/boot.c) keeps its two wires and does its operations; the test hosts' consoles put and take whole messages through the fabric. New: tests/test_host_depth.c, every message path of a mesh node at every depth of both stacks. Co-Authored-By: Claude Opus 5.5 --- capsules/v4/nucleus-64.f18 | Bin 56808 -> 56176 bytes v4/capsule/core.v4 | 454 +++++++++++++------------ v4/capsule/quit.v4 | 368 ++++++++++----------- v4/include/v4/boot.h | 10 +- v4/include/v4/image.h | 4 +- v4/system/boot.c | 216 +++++++++--- v4/tests/host_map.h | 97 +++--- v4/tests/test_host_depth.c | 341 +++++++++++++++++++ v4/tests/test_host_mesh.c | 657 ++++++++++++++++++++++++------------- v4/tests/test_host_quit.c | 126 +++++-- v4/tests/test_host_unit.c | 178 +++++----- v4/tools/depthsweep.py | 4 + 12 files changed, 1621 insertions(+), 834 deletions(-) create mode 100644 v4/tests/test_host_depth.c diff --git a/capsules/v4/nucleus-64.f18 b/capsules/v4/nucleus-64.f18 index d9b2d7efb9416804f5c436fcabd96cb589b6c2e6..e3657eb60f56df4f0a6666fcbb9b0f705fde7de2 100644 GIT binary patch literal 56176 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
  • Ki*MKhv-B1ef~JF4TkV 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 literal 56808 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 diff --git a/v4/capsule/core.v4 b/v4/capsule/core.v4 index e15d8f9c..a50f52c5 100644 --- a/v4/capsule/core.v4 +++ b/v4/capsule/core.v4 @@ -105,8 +105,8 @@ header CMOVE \ to, from, type, heat and TTL, ACL tag, sequence, length in characters \ The types: 1 text to be interpreted 2 what a node printed 3 how the \ text ended (one word of text: 0 QUIT, 1 completed, 2 an error). -\ The header of the message being served is in the seven cells (MSG); (REPLY) -\ is the address of the port it came on; (ME) is this node's number. +\ The header of the text being served is in the seven cells (MSG); (REPLY) +\ is the port it came on, by its number; (ME) is this node's number. \ \ Characters are kept in (OUT), 256 of them, a character to a cell, and sent \ when it is full and when the text has been done with (quit.v4). (OUT^) @@ -115,7 +115,7 @@ header CMOVE \ THE STACK. What prints may be run with the data stack all but full: EMIT \ has always needed one free cell and no more, and still does -- it keeps \ the character and A on the return stack while it works. Sending needs -\ two. +\ three. \ FINDING THE WAY (MESH.md section 7). A node knows its ports and nothing of \ the shape they are wired in. Whoever wires it tells it, for a node, which @@ -123,235 +123,269 @@ header CMOVE \ node's number and a port's address; and (ROUTE-DEFAULT), the port for any \ node that is not in the table, or 0. -\ ( node -- port | 0 ) the address of the port that leads toward the node -: (PORT-FOR) +\ ( node -- port | -1 ) the port, by its number, that leads toward the node +: (WAY) (ROUTE#) a! @ if NONE -1 + (ROUTES) a! FOR dup @+ xor if HIT drop @+ drop NEXT 0 - NONE: drop drop (ROUTE-DEFAULT) a! @ ; - HIT: drop drop @+ pop drop ; + NONE: drop drop (ROUTE-DEFAULT) a! @ if NOWAY (PORT) - ; + NOWAY: drop -1 ; + HIT: drop drop @+ pop drop (PORT) - ; -\ LOOKING BEFORE WRITING (MESH.md section 7a). A write waits until the -\ neighbour reads, and a node that is waiting to write reads nothing: two -\ neighbours that each began to write to the other would wait for ever. So -\ a node does not begin a message until it has looked, (GATE): -\ - while any neighbour is waiting to write to it, it takes that message -\ in and keeps it with the messages waiting, to deal with when it has -\ nothing else to do; -\ - then, if the neighbour it means to write to is waiting to read, it -\ writes; -\ - and if that neighbour is not, it writes all the same when the -\ neighbour's number is the higher of the two, and otherwise looks -\ again. Of two neighbours one only may wait to write to the other, and -\ it is the lower: so those that wait are waiting on ever higher -\ numbers, and the highest of them is not waiting to write; it is -\ looking, and takes in what is being written to it. -\ (WRITERS) and (READERS) are the node's two looks at its neighbours, a bit -\ for each port. (NEAR) is the number of the node on each port, told by -\ whoever wires it (NEIGHBOUR, quit.v4); a port not told of -- a device's -\ -- is written to only when what is there is waiting to read. +\ WHOLE MESSAGES (MESH.md section 7d). A message waits on a wire, in the +\ fabric, and not in a node: each wire has a queue of whole messages each +\ way. A node asks the fabric to look at one, take it, move it to another +\ wire, drop it, or put one on, by the six addresses after its ports: +\ (WIRE) the port, by its number, that what follows is about +\ (WIRE-A) (WIRE-B) what the operation needs +\ (WIRE-DO) the operation: a store here is the asking +\ (WIRE-HOW) how it went: 0 done, 1 no room, 2 no one on that port, +\ 3 no such message, 4 not a message +\ (WIRE-HAVE) which wires have a message waiting, a bit to a port +\ The operations: 1 look 2 take 3 move 4 drop 5 put 6 first 7 next +\ 8 sleep 9 sleep for room 10 room. Each is whole: all of a message +\ moves or none of it. So a fault or an error in the middle of anything +\ here leaves no message half moved; and nothing here keeps, from one +\ operation to the next, anything a later pass does not set again for +\ itself: every pass over a wire begins by naming the wire and putting its +\ mark at its first message. +\ This replaced a ring of messages waiting in the node's own memory, a +\ list of refusals owed, and messages written and read a word at a time +\ (MESH.md 7a, 7b, 7c.6). The ports themselves are still written a word at +\ a time for the two things that are not messages: a newborn node's +\ nucleus (PORT!, quit.v4) and a node's requests to its kernel. \ -\ THE MESSAGES WAITING are kept in (MQ) .. (MQ-END), one after another, -\ going round: for each its seven words, the port it came on, and its text. -\ (MQ-HEAD) is where the oldest begins, (MQ-TAIL) where the next will go, -\ (MQ#) how many cells are taken. One that there is no room for is read to -\ its end and let go, and (LOST) counts it. +\ THE STACKS. All of this runs on the stacks the node's text is using. +\ (SERVE) and what it calls keep to four cells of the data stack and four +\ entries of the return stack, its own call among them; what they need +\ from one operation to the next is in cells. (ROOM-SERVE) tries that much. -\ ( mask index -- bit ) -: (BIT) - if Z -1 + FOR 2/ NEXT 1 and ; - Z: drop 1 and ; +\ ( op -- how ) ask the fabric for the operation, and say how it went +: (DO) (WIRE-DO) b! !b (WIRE-HOW) b! @b ; -\ ( mask -- index ) the lowest port in it; the mask is not zero -: (LOW) - 0 push - L: dup 1 and if UP drop drop pop ; - UP: drop 2/ pop 1 + push jump L +\ ( port -- ) what is asked next is about that port's wire, by its number. +\ A number that is no port's is no wire: every operation then answers 2. +: (WIRE!) (WIRE) b! !b ; -\ ( w -- ) one more cell of the messages waiting. B is kept. -: (MQ!) - (MQ-TAIL) a! @ a! !+ - a (MQ-END) xor if WRAP drop a jump SET - WRAP: drop (MQ) - SET: (MQ-TAIL) a! ! - (MQ#) a! @ 1 + ! ; +\ ( cells port -- how ) has the port's wire that many cells free? 0: it +\ has; 1: it has not; 2: no one is on that port. Only this node puts +\ messages on the wires that lead from it, so room found stays until it +\ uses it. +: (ROOM?) (WIRE!) (WIRE-A) b! !b 10 jump (DO) -\ ( -- w ) the oldest cell of the messages waiting, which goes -: (MQ@) - (MQ-HEAD) a! @ a! @+ - a (MQ-END) xor if WRAP drop a jump SET - WRAP: drop (MQ) - SET: (MQ-HEAD) a! ! - (MQ#) a! @ -1 + ! ; - -\ REFUSALS (MESH.md section 7b). A message this node has no room for, or -\ no way to pass on, is let go and counted in (LOST); and its sender is -\ told, by a message of type 4, a NACK, whose one word is the node the -\ refused message was for. A node finds it has no room while it is taking -\ messages in so as to be free to write, where it cannot begin a message -\ of its own: so it notes what it owes, (OWE), in the eight pairs at -\ (OWED) -- whom it is owed to, and the node the message was for -- and -\ sends it when it next has nothing else to do, (PAY). With eight owed -\ already, one more is let go and counted. Nothing is owed for a NACK or -\ for a GONE, type 5: there is no NACK for a NACK. The last 36 cells of -\ the messages waiting are kept for those two types, so that ordinary -\ messages cannot take all the room. - -\ ( to about -- ) a refusal is owed to the node `to`, about the node `about`. -\ Owed to this node itself -- its own message came back to it and was let -\ go here -- it is put with the messages waiting, as if it had come by a -\ port, in the room kept for refusals. -: (OWE) - over (ME) a! @ xor if SELF drop - (OWED#) a! @ -8 + -if NOROOM - drop (OWED#) a! @ 2* (OWED) + a! - push !+ pop ! - (OWED#) a! @ 1 + ! ; - SELF: drop 1 (MQ#) a! @ + -MQ-ROOM + -if NOROOM - drop push (MQ!) (ME) a! @ (MQ!) 4 (MQ!) 0 (MQ!) 0 (MQ!) 0 (MQ!) 4 (MQ!) 0 (MQ!) pop (MQ!) ; - NOROOM: drop drop drop (LOST) a! @ 1 + ! ; - -\ ( from to type -- ) a message of that type, from the one node for the -\ other, has been let go here. Whoever is waiting on it is told: its -\ sender; or, for a node's answer (type 3), the node the answer was for, -\ which is the one that waits. Nothing is owed for a NACK or a GONE. -: (TELL-OF) - dup -4 + if NOT -1 + if NOT drop - -3 + if ANSWER drop jump (OWE) \ to its sender, about whom it was for - ANSWER: drop SWAP jump (OWE) \ to whom it was for, about the node that answered - NOT: drop drop drop drop ; - -\ ( port -- flag ) is there anything on that port, by its address? -: (THERE) - (PORT) - -PORTS - (READERS) b! @b SWAP jump (BIT) - -\ ( port to -- ) the seven words of a message being taken in, and the -\ port, go into (MQ-HDR). B is at the port, by its address, and `to` is -\ the message's first word, already read from it. -: (TAKE-HDR) - (MQ-HDR) a! !+ 5 FOR @b !+ UNEXT ! ; - -\ ( -- ) the message whose seven words are in (MQ-HDR) is kept with the -\ messages waiting: its text is read from the port B is at. A length -\ below zero, or above what a message carries, is kept as -1 with no text: -\ a longer one is read to its end first, so that what follows it is not -\ taken for a message. -: (TAKE-KEEP) - (MQ-HDR)+6 a! @ -if SIZED - drop -1 (MQ-HDR)+6 a! ! 0 jump COUNTED - SIZED: dup -1025 + -if LONG drop 3 + 2/ 2/ jump COUNTED - LONG: drop 3 + 2/ 2/ -1 + FOR @b drop UNEXT -1 (MQ-HDR)+6 a! ! 0 - COUNTED: \ ( n ) how many words of text follow - dup (MQ#) a! @ + \ what it would come to - (MQ-HDR)+2 a! @ -4 + if KEPT-FOR -1 + if KEPT-FOR \ a NACK or a GONE may use the room kept for them - drop -MQ-ORDINARY + jump ROOM - KEPT-FOR: drop -MQ-ROOM + - ROOM: -if FULL - drop push - (MQ-HDR) a! @ (MQ!) (MQ-HDR)+1 a! @ (MQ!) (MQ-HDR)+2 a! @ (MQ!) (MQ-HDR)+3 a! @ (MQ!) - (MQ-HDR)+4 a! @ (MQ!) (MQ-HDR)+5 a! @ (MQ!) (MQ-HDR)+6 a! @ (MQ!) (MQ-HDR)+7 a! @ (MQ!) - pop if NOTEXT -1 + FOR @b (MQ!) NEXT ; - NOTEXT: drop ; - FULL: drop if READ -1 + FOR @b drop UNEXT jump LOSE - READ: drop - LOSE: (LOST) a! @ 1 + ! - (MQ-HDR)+1 a! @ (MQ-HDR) a! @ (MQ-HDR)+2 a! @ jump (TELL-OF) - -\ ( port to -- ) take in a message and keep it -: (TAKE) (TAKE-HDR) jump (TAKE-KEEP) - -\ ( -- ) ROOM TO TAKE A MESSAGE IN. Once a message's first word has been -\ read the rest must be: whoever is writing it waits for that, and a fault -\ in the middle leaves the two out of step. Taking one in and keeping it -\ needs five cells of the data stack and four entries of the return stack. -\ So before a first word is read from within text -- where the stacks may -\ be as deep as the text has made them -- six and four are tried: if they -\ are not there the fault comes here, with nothing read, and the text -\ ends "Stack overflow" as it would have (MESH.md 7c.6). +\ ( -- ) ROOM FOR WHAT A WAIT DOES. A node that waits within text -- for +\ an answer, or for room to print -- passes on meanwhile what comes for +\ other nodes, on the stacks the text has left it: four cells of the data +\ stack and four entries of the return stack, (SERVE)'s. They are tried +\ before anything is taken off a wire or put on one: if they are not there +\ the fault comes here, and the text ends "Stack overflow" with nothing +\ half done (MESH.md 7c.7, 7d.4). +\ (ROOM-SERVE) tries just those, and is what a wait for room does each time +\ a message wakes it. (ROOM), which AWAIT does once before it waits, tries +\ six cells, as it did when a message was read a word at a time: the wait +\ ends in an error that leaves the stack as it is, and the line from the +\ console that broke it is then done on that stack, where printing needs +\ six (MESH.md 7c.7). +: (ROOM-SERVE) + 0 0 0 0 drop drop drop drop + 0 push 0 push 0 push 0 push pop drop pop drop pop drop pop drop ; : (ROOM) 0 0 0 0 0 0 drop drop drop drop drop drop 0 push 0 push 0 push 0 push pop drop pop drop pop drop pop drop ; -\ ( port -- ) wait until a message may be begun on the port, by its -\ address, taking in whatever is being written to this node meanwhile; and -\ leave B at the port. The port is kept in (GATE-PORT), not on the stack: -\ this is run with the stack as full as EMIT may be. -: (GATE) - (GATE-PORT) a! ! - 0 0 0 drop drop drop \ what follows the message's first word needs three cells: tried here, before it goes - L: (WRITERS) b! @b if QUIET - (ROOM) (LOW) (PORT) + dup b! @b (TAKE) jump L - QUIET: drop - (GATE-PORT) a! @ (PORT) - (NEAR) + a! @ if ASK - (ME) a! @ - -if GO jump ASK \ its number less this node's - GO: drop (GATE-PORT) a! @ b! ; \ the neighbour's is the higher - ASK: drop - (READERS) b! @b (GATE-PORT) a! @ (PORT) - (BIT) if NOTYET - drop (GATE-PORT) a! @ b! ; \ it is waiting to read - NOTYET: drop - (READERS) b! @b (GATE-PORT) a! @ (PORT) - -PORTS - (BIT) if NOONE \ is there anything on the port at all? - drop jump L - NOONE: drop NODE-ERROR b! 18 !b ; \ no: what waits here would wait for ever - -\ ( type -- ) words 1 to 5 of a message this node sends: from, type, and -\ the three that are carried and not used yet. Its caller has put B at the -\ port and sent word 0, whom it is to, and sends the length and the text. -\ Each write waits for the neighbour to take it. +\ ( to type -- ) the first six words of a message this node puts, in +\ (OUT-HDR): to, from, type, and the three that are carried: how many nodes +\ may pass it on, sixteen (the TTL: MESH.md 7b.3); ACL tag; sequence. : (HDR) - (ME) a! @ !b \ from - !b \ type - 16 !b 0 !b 0 !b ; \ how many nodes may pass it on (the TTL: MESH.md 7b); ACL tag; sequence + push (OUT-HDR) a! !+ (ME) b! @b !+ pop !+ 16 !+ 0 !+ 0 !+ ; -\ PAYING WHAT IS OWED does not hold this node up. A refusal is sent if the -\ neighbour it goes by is reading at this moment. If it is not, and its -\ number is the higher, the refusal is written all the same, as any message -\ would be (MESH.md 7a): that neighbour takes it in when it next looks. If -\ its number is the lower, or it has not been told of, the refusal stays -\ owed, and this node goes on with what it has to do and tries again; it -\ does not sleep while it owes one (quit.v4, (IDLE)). One there is no way -\ for, or whose way is a port with nothing on it, is let go and counted. +\ ( text port -- how ) put the message whose seven words are in (OUT-HDR) +\ and whose text is at the word address given on the wire of the port, by +\ its number. How it went is the fabric's answer: 0 it is on the wire. +\ THE ROOM KEPT FOR AN ANSWER. While this node does text, (KEEP) is the +\ port its word of how the text ended will go by, and nothing else it puts +\ on that wire may take the last 8 cells: a message that would is refused +\ here, 1, as if the wire were full. With no text being done (KEEP) is -1. +: (PUT-ON) + push (WIRE-B) b! !b pop + dup (WIRE!) + (KEEP) a! @ xor if KEPT + GO: drop (OUT-HDR) (WIRE-A) b! !b 5 jump (DO) + KEPT: drop (OUT-HDR)+6 a! @ 3 + 2/ 2/ 15 + (WIRE-A) b! !b 10 (DO) if GO ; -\ ( k -- ) the k-th refusal owed is done with: the last takes its place -: (PAID) - (OWED#) a! @ -1 + dup ! - 2* (OWED) + a! @+ @ - push push 2* (OWED) + a! pop !+ pop ! ; +\ REFUSALS (MESH.md 7b, 7d.4). A message this node cannot pass on is +\ dropped and counted in (LOST), and whoever waits on it is told, by a +\ message of type 4, a NACK, whose one word is the node the refused message +\ was for: its sender; or, for a node's answer (type 3), the node the +\ answer was for, which is the one that waits, about the node that +\ answered. The NACK is put at once. If it cannot go -- no way, no one on +\ the port, no room -- it is counted in (LOST) too, and that is the end of +\ it. Nothing is told for a NACK or for a GONE, type 5: there is no NACK +\ for a NACK. -\ ( k -- ) send the k-th refusal owed, if it can go now -: (PAY1) - dup 2* (OWED) + a! @+ @ \ k to about - over (PORT-FOR) if NOWAY \ k to about port - dup (THERE) if GONE - drop (READERS) b! @b over (PORT) - (BIT) if BUSY - drop - WRITE: b! \ k to about B: the port - SWAP !b 4 (HDR) 4 !b !b \ k to; from, type 4; four characters; about - jump (PAID) - BUSY: drop \ it is not reading: - dup (PORT) - (NEAR) + a! @ if LEAVE \ k to about port near - (ME) a! @ - -if BLIND jump LEAVE \ its number less this node's - BLIND: drop jump WRITE \ the higher: it will take it in - LEAVE: drop drop drop drop drop ; - GONE: drop - NOWAY: drop drop drop (LOST) a! @ 1 + ! jump (PAID) +\ ( -- ) the message the mark of the wire in (WIRE) is at, whose seven +\ words are in (IN), is refused. A refusal for this node itself -- its own +\ message came back to it -- is counted in (REFUSED) as one that came by a +\ 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. +: (REFUSE) + 4 (DO) 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 + ANSWER: drop (IN)+1 a! @ (ONE) a! ! (IN) a! @ \ about the node that answered; to whom the answer was for + TELL: dup (ME) a! @ xor if SELF drop + dup 4 (HDR) 4 (OUT-HDR)+6 a! ! + (WAY) (ONE) SWAP (PUT-ON) if SENT + drop (LOST) a! @ 1 + ! ; + SENT: drop ; + SELF: drop drop + (AWAIT-FROM) a! @ if COUNT (ONE) a! @ xor if MINE + COUNT: drop (REFUSED) a! @ 1 + ! ; + MINE: drop 1 (A-SELF) a! ! ; + NOT: drop ; -\ ( -- ) send every refusal this node owes that can go now -: (PAY) - (OWED#) a! @ if NONE -1 + - FOR pop dup push (PAY1) NEXT ; - NONE: 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. +\ 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 +\ 16: so 0 down to -14 are passed on and -15 is refused. Anything else is +\ nothing a node or a device sent, and is refused. +: (PASS) + (IN)+3 a! @ -if POS + 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 + SPENT: drop jump (REFUSE) + MOVED: drop ; + KEPT: drop (IN)+6 a! @ 3 + 2/ 2/ 15 + (P-WAY) a! @ (ROOM?) + (S-WIRE) a! @ (WIRE!) if GO jump SPENT + +\ ( -- ) 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 +\ is. Fetching (WIRE-HAVE) is what tells the fabric this node has seen +\ what had come: a sleep after this is woken only by what comes later +\ (MESH.md 7d.3). (S-HAVE) keeps which wires they were, for whoever then +\ 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! ! + W: (S-MASK) a! @ if END + dup 2/ ! 1 and if NEXTW + drop (S-WIRE) a! @ (WIRE!) 6 (DO) drop + M: (S-WIRE) a! @ (WIRE!) (IN) (WIRE-A) b! !b 1 (DO) if LOOKED jump NEXTW + LOOKED: drop + (IN) a! @ (ME) a! @ xor if MINE drop (PASS) jump M + MINE: drop 7 (DO) drop jump M + NEXTW: drop (S-WIRE) a! @ 1 + ! jump W + END: drop ; + +\ WHAT IS FOR THIS NODE is gone through after (SERVE), a message at a time: +\ (MINE0) begins, and each (MINE) puts the mark of a wire at the next +\ message for this node and its seven words in (IN). Whoever asked then +\ takes or drops that message -- the mark is then at the one that followed +\ -- or leaves it and goes past it, (SKIP). The wire is (S-WIRE). + +\ ( -- ) begin with the wires (SERVE) found a message on +: (MINE0) (S-HAVE) a! @ (S-MASK) a! ! -1 (S-WIRE) a! ! 0 (S-ON) a! ! ; + +\ ( -- flag ) the next message for this node; 0: there are no more +: (MINE) + L: (S-ON) a! @ if WIRE + drop (S-WIRE) a! @ (WIRE!) (IN) (WIRE-A) b! !b 1 (DO) if LOOKED + drop 0 (S-ON) a! ! jump L \ this wire has no more + LOOKED: drop (IN) a! @ (ME) a! @ xor if HIT + drop 7 (DO) drop jump L + HIT: drop -1 ; + WIRE: drop (S-WIRE) a! @ 1 + ! \ the next wire that had one + (S-MASK) a! @ if NONE + dup 2/ ! 1 and if NOT + drop (S-WIRE) a! @ (WIRE!) 6 (DO) drop 1 (S-ON) a! ! jump L + NOT: drop jump L + NONE: ; + +\ ( -- ) 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 ) 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). +: (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 + DONE: ; \ ( -- ) send what has been printed, to where (PRINT-TO) says, by the port -\ (PRINT-PORT): its length, then its characters, four to a word, the first -\ lowest. quit.v4 sets those two when text for this node arrives. +\ (PRINT-PORT): a message of type 2, its characters four to a word, the +\ first lowest. quit.v4 sets those two when text for this node arrives. +\ It waits for room on the wire, and for the 8 cells kept as well when the +\ 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 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). : (FLUSH-OUT) (OUT^) a! @ (OUT) xor if NONE drop - (PRINT-PORT) a! @ (GATE) (PRINT-TO) a! @ !b - 2 (HDR) - (OUT^) a! @ (OUT) - dup !b - 3 + 2/ 2/ -1 + (OUT) a! - FOR @+ @+ 8* + @+ 8* 8* + @+ 8* 8* 8* + !b NEXT - (OUT) (OUT^) a! ! ; + 0 0 0 drop drop drop 0 push 0 push pop drop pop drop + (OUT^) a! @ (OUT) - 3 + 2/ 2/ 7 + + (PRINT-PORT) a! @ (KEEP) a! @ xor if SAME drop jump NEED + 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 + ! ; + TELL: drop NODE-ERROR b! 18 !b ; + ROOM: drop + (PRINT-TO) a! @ 2 (HDR) + (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 + SENT: drop (OUT) (OUT^) a! ! ; NONE: drop ; \ EMIT puts the character with what has been printed. Everything the node diff --git a/v4/capsule/quit.v4 b/v4/capsule/quit.v4 index 7a4af021..a1058e47 100644 --- a/v4/capsule/quit.v4 +++ b/v4/capsule/quit.v4 @@ -5,10 +5,10 @@ \ the compiler capsule. Rests on all the files before it. \ \ A NODE IS SENT TEXT (docs/v4.0.0/MESH.md section 6). It does not read its -\ own command line and prints no prompt. A node with nothing to do waits at -\ its ports; a neighbour writes it a message; if that is text for this node -\ it is interpreted; what it printed goes back as a message, and then a -\ message saying how the text ended: +\ own command line and prints no prompt. A node with nothing to do sleeps; +\ a message comes to one of its wires; if that is text for this node it is +\ taken and interpreted; what it printed goes back as a message, and then +\ a message saying how the text ended: \ 1 it completed a console says " ok" \ 2 it ended in an error the message is already printed; " ERROR" \ 0 QUIT nothing is said @@ -33,8 +33,7 @@ \ Constants the loader supplies: \ (LINE-STATUS) word address of the variable: how the last text ended \ (MSG) (REPLY) (ME) (OUT) (OUT^) see core.v4, "what a node prints" -\ (PORT) word address of the node's port 0; (PORT)+8 is "any port" and -\ (PORT)+9 the port the last read from that came on +\ (PORT) word address of the node's port 0 \ (Q) word address of four cells of scratch, which system.v4 and \ blocks.v4 use too \ DSTACK-DEPTH RSTACK-DEPTH word addresses of the stack registers @@ -53,14 +52,27 @@ macro R-CLEAR RSTACK-DEPTH b! a !b endmacro \ for the next. (DONE) is entered with the same number and, for an error, \ stops compiling first; it is always jumped to, never called, by \ something that has emptied the return stack (or by a fault, which empties -\ it). (LINE-STATUS) keeps how the last text ended. +\ it). (LINE-STATUS) keeps how the last text ended, and is the one word of +\ the message's text. +\ THE ANSWER IS NOT REFUSED HERE: the text was not begun until the wire it +\ goes by had room for it, and that room has been kept, (KEEP), which is +\ let go only now (MESH.md 7d.4). If no one is on that port any more it +\ is counted in (LOST). +\ FOUR CELLS OF THE DATA STACK are tried first: what a node does between +\ texts -- this, passing messages on, taking the next text in -- and the +\ interpreter need that many, so text may leave 28 values on a stack of 32 +\ and no more. Text that leaves more ends here, "Stack overflow", with +\ the stack emptied, and not later, in the middle of the text after it. : (FINISH) ( s -- ) - 1 (QUIET) a! ! \ from here an error is not this text's: see (RAISED) - (LINE-STATUS) b! dup !b + 1 (QUIET) a! ! \ from here nothing is this text's doing + (LINE-STATUS) b! !b + 0 0 0 0 drop drop drop drop (FLUSH-OUT) - (DONE-PORT) a! @ (GATE) (MSG)+1 a! @ !b \ to whoever sent the text - 3 (HDR) 4 !b !b \ a message of type 3, four characters long: how it ended - jump (IDLE) + -1 (KEEP) a! ! + (MSG)+1 a! @ 3 (HDR) 4 (OUT-HDR)+6 a! ! \ to whoever sent the text: type 3, four characters + (LINE-STATUS) (DONE-PORT) a! @ (PUT-ON) if SENT + drop (LOST) a! @ 1 + ! jump (IDLE) + SENT: drop jump (IDLE) : (DONE) if GO -1 + if OK @@ -68,79 +80,70 @@ macro R-CLEAR RSTACK-DEPTH b! a !b endmacro OK: drop 1 jump (FINISH) GO: drop 0 jump (FINISH) -\ ( -- ) WAITING. A node with nothing to do deals with the oldest of the -\ messages waiting (core.v4). With none waiting it is blocked reading its -\ ports (docs/v4.0.0/MESH.md 4.1): it executes nothing until a neighbour -\ writes, and what arrives is taken in as any message is, its first word -\ from any port and the rest from the port that came on. The message's -\ text goes into TIB, which holds 1024 characters, the most a message -\ carries; one that came with a length no message has is error 12. A -\ message of type 1 for this node is text to interpret: it is done as a -\ line was, and (FINISH) reports how it ended. A message for another node -\ is passed on, (PASS-ON). +\ ( -- 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. +: (GONE?) + (CENTRE) if NOT (IN)+1 a! @ xor if YES drop 0 ; + NOT: ; + YES: drop (ONE) a! @ NO-ROUTE -1 ; + +\ ( -- ) WAITING (MESH.md 7d.4). A node with nothing to do passes on +\ what has come for other nodes, (SERVE), and then goes through what has +\ 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. +\ 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 +\ long as its type says. +\ 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. +\ 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). +\ (KEEP) and (AWAIT-FROM) are put back here every time: whatever was +\ abandoned on the way, a node with nothing to do keeps no room and waits +\ for no one. : (IDLE) L: 1 (QUIET) a! ! \ nothing here is any text's doing: see (RAISED) - (PAY) \ what refusals can be sent now, are (core.v4) - (MQ#) a! @ if WAIT drop jump HAVE - WAIT: drop (OWED#) a! @ if BLOCK \ with one still owed it does not sleep: - drop (WRITERS) b! @b if L0 \ it takes in whatever is being written to it, - (LOW) (PORT) + dup b! @b (TAKE) jump L - L0: drop jump L \ and tries again - BLOCK: drop (PORT)+8 b! @b (PORT)+9 b! @b (PORT) + dup b! \ ( to port ) B: the port it came on - SWAP (TAKE) jump L - HAVE: - (MQ@) (MSG) a! ! (MQ@) (MSG)+1 a! ! (MQ@) (MSG)+2 a! ! (MQ@) (MSG)+3 a! ! - (MQ@) (MSG)+4 a! ! (MQ@) (MSG)+5 a! ! (MQ@) (MSG)+6 a! ! (MQ@) (REPLY) a! ! - (MSG)+6 a! @ -if SIZED - drop 0 (MSG)+6 a! ! 0 SPAN a! ! NODE-ERROR b! 12 !b \ no telling what it was - SIZED: drop - TIB 2/ 2/ (MSG)+6 a! @ 3 + 2/ 2/ if EMPTY - -1 + FOR (MQ@) over a! ! 1 + NEXT - drop jump READ - EMPTY: drop drop - READ: + -1 (KEEP) a! ! 0 (AWAIT-FROM) a! ! -1 (DEFER) a! ! + (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 + 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 + GONE: drop (IN)+6 a! @ -4 + if G1 jump OTHER + G1: drop (TAKE1) (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 + 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 + ROOM: (WIRE!) 8 (WIRE-A) b! !b 9 (DO) drop jump L + START: + (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 0 (MSG)+6 a! @ TIB + C! \ a zero after the text (MSG)+6 a! @ SPAN a! ! - (MSG) a! @ (ME) a! @ xor if MINE drop jump (PASS-ON) - MINE: drop - (MSG)+2 a! @ -1 + if TEXT \ for this node, and - -3 + if NACK -1 + if GONE drop jump (IDLE) \ neither text nor these: let go - NACK: drop (REFUSED) a! @ 1 + ! jump (IDLE) \ a message of this node's was refused: counted - GONE: drop (MSG)+6 a! @ -4 + if G4 drop jump (IDLE) \ a node is gone, if its centre says so: - G4: drop (CENTRE) if NOT (MSG)+1 a! @ xor if CENTRE drop jump (IDLE) - NOT: drop jump (IDLE) - CENTRE: drop TIB 2/ 2/ a! @ NO-ROUTE jump (IDLE) \ the way to it is forgotten - TEXT: drop - (MSG)+1 a! @ (PORT-FOR) if D0 jump D1 \ the way back to whoever sent it: - D0: drop (REPLY) a! @ \ the port it came on, if no other is known - D1: (DONE-PORT) a! ! + (DONE-PORT) a! @ (KEEP) a! ! \ the room for its answer is kept from here (CONSOLE) a! @ if P0 jump P1 \ where what it prints goes: the console, - P0: drop (MSG)+1 a! @ \ or whoever sent it + P0: drop (MSG)+1 a! @ \ or whoever sent it; P1: dup (PRINT-TO) a! ! - (PORT-FOR) if Q0 jump Q1 - Q0: drop (REPLY) a! @ + (WAY) -if Q1 drop (REPLY) a! @ \ by the way there, or the wire the text came on Q1: (PRINT-PORT) a! ! jump (LINE) -\ ( -- ) A MESSAGE FOR ANOTHER NODE is passed on (MESH.md section 7): all -\ of it, as it came, to the port that leads toward that node. With no way -\ known it is let go, and (LOST) counts it. -: (PASS-ON) - (MSG)+3 a! @ if FRESH jump COUNT \ how many more nodes may pass it on; - FRESH: drop 16 \ from a device, which set none: sixteen - COUNT: -1 + if SPENT (MSG)+3 a! ! - (MSG) a! @ (PORT-FOR) if NOWAY - dup (THERE) if NOONE drop - (GATE) (MSG) a! 6 FOR @+ !b UNEXT \ its seven words - (MSG)+6 a! @ 3 + 2/ 2/ if SENT - -1 + TIB 2/ 2/ a! FOR @+ !b UNEXT \ and its text - jump (IDLE) - SENT: drop jump (IDLE) - NOONE: drop \ its way is a port with nothing on it - SPENT: \ it has been passed on as often as it may be - NOWAY: drop (LOST) a! @ 1 + ! - (MSG)+1 a! @ (MSG) a! @ (MSG)+2 a! @ (TELL-OF) jump (IDLE) \ whoever waits on it is told (core.v4) - \ ( -- ) THE TEXT, INTERPRETED. It is in TIB with a zero after it and its \ length in SPAN, and the return stack is empty. It is interpreted with one \ return entry under it, this word's call of INTERPRET; whatever is on the @@ -169,8 +172,8 @@ header ROUTE \ ( node port -- ) the node on the other end of the port, by its number; \ 0: none, or a device. Whoever wires the node tells it, and tells it again -\ when the wiring changes. It is what lets one of two neighbours, and one -\ only, wait to write to the other (core.v4, (GATE)). +\ when the wiring changes. It is how a node knows who its centre is, the +\ one node whose GONE it believes: (CENTRE). header NEIGHBOUR : NEIGHBOUR dup -if POS jump BAD @@ -197,12 +200,17 @@ header NO-ROUTE pop a! SWAP !+ ! ; \ ( about node type -- ) send the node a message of one word, `about`: -\ a NACK is type 4, a GONE type 5 (MESH.md 7b). With no way to the node -\ it is error 12. +\ a GONE is type 5 (MESH.md 7b). With no way to the node it is error 12. +\ It goes or is refused at once; refused -- no one on the port, or no room +\ on the wire -- it is counted in (LOST) (MESH.md 7d.4). : (SEND1) - push dup (PORT-FOR) if NOWAY - (GATE) !b pop (HDR) 4 !b !b ; - NOWAY: drop drop drop pop drop NODE-ERROR b! 12 !b ; + push dup (WAY) -if GO + drop drop drop pop drop NODE-ERROR b! 12 !b ; + GO: (W-PORT) a! ! SWAP (ONE) a! ! + pop (HDR) 4 (OUT-HDR)+6 a! ! + (ONE) (W-PORT) a! @ (PUT-ON) if SENT + drop (LOST) a! @ 1 + ! ; + SENT: drop ; \ ( n node -- ) tell the node that node n is gone. It acts on that only \ if this node is its centre: it forgets the way to n, and if it is waiting @@ -215,25 +223,39 @@ header NO-ROUTES : NO-ROUTES 0 (ROUTE#) a! ! 0 (ROUTE-DEFAULT) a! ! ; \ ( baddr u node port -- ) send the node u characters of text to -\ interpret, by the port given, by its address. It is on its way when this -\ returns. The write waits for the neighbour to read, and is not begun -\ until it may be (core.v4, (GATE)). +\ interpret, by the port given, by its number. The text is made ready in +\ (SEND-TEXT), four characters to a word, the message is put on the wire +\ whole, and it is on its way when this returns. IT GOES OR IS REFUSED AT +\ ONCE (MESH.md 7d.1, ruling 3): with no room on the wire it is counted in +\ (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. : (SEND-ON) - (GATE) !b \ to - 1 (HDR) - dup !b \ length + push 1 (HDR) + -if POS + BAD: drop drop pop drop NODE-ERROR b! 12 !b ; + LONG: drop jump BAD + POS: dup -1025 + -if LONG + drop dup (OUT-HDR)+6 a! ! + (SEND-TEXT) (SEND^) a! ! 3 + 2/ 2/ if NONE -1 + - FOR dup C@ over 1 + C@ 8* + over 2 + C@ 8* 8* + over 3 + C@ 8* 8* 8* + !b 4 + NEXT - drop ; - NONE: drop drop ; + 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) + if SENT -1 + if FULL drop NODE-ERROR b! 18 !b ; + FULL: drop (REFUSED) a! @ 1 + ! NODE-ERROR b! 19 !b ; + SENT: drop ; \ ( baddr u node -- ) send the node u characters of text to interpret, \ by the way this node knows to it. What it prints there goes to that \ node's console. With no way to the node it is error 12. header SEND : SEND - dup (PORT-FOR) if NOWAY jump (SEND-ON) - NOWAY: drop drop drop drop NODE-ERROR b! 12 !b ; + dup (WAY) -if GO drop drop drop drop NODE-ERROR b! 12 !b ; + GO: jump (SEND-ON) \ ( baddr u node port -- ) the same by a port this node names, by its \ number, whatever ways it knows: for a neighbour that has no number of @@ -241,7 +263,7 @@ header SEND header SEND-ON : SEND-ON dup -if POS jump BAD - POS: -PORTS + -if BAD drop (PORT) + jump (SEND-ON) + POS: -PORTS + -if BAD drop jump (SEND-ON) BAD: drop drop drop drop drop NODE-ERROR b! 12 !b ; \ ( -- n ) the number of this node's centre: the node on the port that @@ -251,111 +273,64 @@ 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! @ ; - -\ THE MESSAGES WAITING, LOOKED THROUGH for what would end a wait. Each in -\ turn is taken from the front: its eight cells into (MQ-HDR), (A-HDR). If -\ it is the answer waited for, or a NACK or a believed GONE for this node, -\ it is used up, as it would be had it come during the wait; otherwise it -\ goes to the back, (A-BACK) and (A-TEXT), so that when all have been -\ looked at they are in the order they were in. (A-LEFT) is how many -\ cells are still to be looked at. (A-FOUND) is what was found first: 0 -\ nothing, 1 the answer, its word in (A-WORD); 2 a NACK about the node -\ waited for; 3 a GONE about it; 4 text from the console, which stays -\ where it is to be done next. -: (A-HDR) (MQ-HDR) a! 7 FOR a push (MQ@) pop a! !+ NEXT ; -: (A-BACK) (MQ-HDR) a! 7 FOR @+ a push (MQ!) pop a! NEXT ; -: (A-TEXT) if NONE -1 + FOR (MQ@) (MQ!) NEXT ; NONE: drop ; -: (A-QUEUE) - 0 (A-FOUND) a! ! - (MQ#) a! @ (A-LEFT) a! ! - L: (A-LEFT) a! @ if END drop - (A-HDR) - (MQ-HDR)+6 a! @ -if SIZED drop 0 jump N - SIZED: 3 + 2/ 2/ - N: (A-LEFT) a! @ over 8 + - ! \ n: its words of text - (A-FOUND) a! @ if LOOK drop jump KEEP \ something is found: the rest only go round - LOOK: drop - (MQ-HDR) a! @ (ME) a! @ xor if MINE drop jump KEEP - MINE: drop - (MQ-HDR)+2 a! @ -1 + if TEXT -2 + if ANS -1 + if NACK -1 + if GONE drop jump KEEP - TEXT: drop (A-CONSOLE) (MQ-HDR)+1 a! @ xor if BRK drop jump KEEP - BRK: drop 4 (A-FOUND) a! ! jump KEEP - ANS: drop (MQ-HDR)+1 a! @ (AWAIT-FROM) a! @ xor if A1 drop jump KEEP - A1: drop dup -1 + if A2 drop jump KEEP - A2: drop drop (MQ@) (A-WORD) a! ! 1 (A-FOUND) a! ! jump L - NACK: drop dup -1 + if N2 drop jump KEEP - N2: drop drop (MQ@) (AWAIT-FROM) a! @ xor if N3 drop (REFUSED) a! @ 1 + ! jump L - N3: drop 2 (A-FOUND) a! ! jump L - GONE: drop dup -1 + if G2 drop jump KEEP - G2: drop (CENTRE) if NOC (MQ-HDR)+1 a! @ xor if G3 drop jump KEEP - NOC: drop jump KEEP - G3: drop drop (MQ@) dup NO-ROUTE (AWAIT-FROM) a! @ xor if G4 drop jump L - G4: drop 3 (A-FOUND) a! ! jump L - KEEP: (A-BACK) (A-TEXT) jump L - END: drop ; - -\ ( what -- how | ) the wait is over, by what (A-FOUND) says: the answer's -\ word is left; anything else is an error +\ ( 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 : (A-END) 0 (AWAIT-FROM) a! ! -1 + if ANS -1 + if REF -1 + if LEFT drop NODE-ERROR b! 21 !b ; - ANS: drop (A-WORD) a! @ ; + ANS: drop (ONE) a! @ ; REF: drop NODE-ERROR b! 19 !b ; LEFT: drop NODE-ERROR b! 20 !b ; \ ( node -- how ) WAIT FOR THE NODE'S WORD OF HOW TEXT ENDED: 0 QUIT, -\ 1 completed, 2 an error. This node is blocked reading its ports until a -\ message of type 3 comes for it from that node; every other message that -\ comes meanwhile is kept with the messages waiting. (AWAIT-FROM) holds the -\ node while it waits, and 0 otherwise. -\ THE WAIT ALSO ENDS, in an error, when the answer is not going to come -\ (MESH.md section 7b): a NACK about that node, error 19, Message refused; -\ or a GONE about it from this node's centre, error 20, Node gone. A NACK -\ about another node is counted, and a GONE about another has its way -\ forgotten, as they would be if this node were not waiting. A GONE is -\ believed only if this node's centre sent it: (CENTRE). -\ WHAT HAS COME ALREADY is looked through first, (A-QUEUE): the answer, a -\ NACK or a GONE may have been taken in before the wait began, while this -\ node was waiting to write, and is then with the messages waiting. -\ AND A LINE FROM THE CONSOLE BREAKS IT (7b.6): text for this node from its -\ console is kept, as any message is, the wait ends in error 21, -\ Interrupted, and the text is then done as usual. It is the one way to -\ end a wait on a node that is alive and never answers, short of Hera's -\ killing that node. +\ 1 completed, 2 an error. (AWAIT-FROM) holds the node while this node +\ waits, and 0 otherwise. It sleeps, executing nothing, until a message +\ comes; then it passes on what is for other nodes, (SERVE), and goes +\ through what is for itself (MESH.md 7d.4): +\ the answer -- a message of type 3 from that node -- is taken, and the +\ wait is over; +\ a NACK is taken: about that node, the wait ends in error 19, Message +\ refused; about another it is counted, as at any time; +\ a GONE is taken: believed (its centre sent it: (GONE?)) and about that +\ node, the wait ends in error 20, Node gone; +\ text from its console ends the wait in error 21, Interrupted (7b.6). +\ The text is not taken: it stays on its wire and is done when this +\ text has been finished with. It is the one way to end a wait on +\ a node that is alive and never answers, short of Hera's killing +\ that node; +\ anything else stays on its wire, in its order, for when this node has +\ nothing to do. +\ WHAT HAS COME ALREADY is on the wires and is found the first time +\ through: the node looks before it sleeps. A message it left on a wire +\ does not keep it awake. +\ The stack what it does needs is tried first, (ROOM), core.v4: text that +\ has left too little ends "Stack overflow" with nothing taken off a wire. header AWAIT : AWAIT - (AWAIT-FROM) a! ! - (A-QUEUE) - (A-FOUND) a! @ if WAIT jump (A-END) - WAIT: drop - L: (ROOM) \ before a first word is read: core.v4 - (PORT)+8 b! @b (PORT)+9 b! @b (PORT) + dup b! SWAP (TAKE-HDR) - (MQ-HDR) a! @ (ME) a! @ xor if A1 drop jump KEEP - A1: drop (MQ-HDR)+2 a! @ \ for this node: its type - -1 + if TEXT -2 + if ANSWER -1 + if NACK -1 + if GONE drop jump KEEP - TEXT: drop (A-CONSOLE) (MQ-HDR)+1 a! @ xor if BREAK drop jump KEEP \ text: from the console? - BREAK: drop (TAKE-KEEP) 4 jump (A-END) \ kept, to be done next; and the wait is over - ANSWER: drop (MQ-HDR)+1 a! @ (AWAIT-FROM) a! @ xor if A3 drop jump KEEP - A3: drop (MQ-HDR)+6 a! @ -4 + if A4 drop jump KEEP - A4: drop 0 (AWAIT-FROM) a! ! @b ; \ its one word - NACK: drop (MQ-HDR)+6 a! @ -4 + if N4 drop jump KEEP - N4: drop @b (AWAIT-FROM) a! @ xor if REFUSED \ about the node waited for? - drop (REFUSED) a! @ 1 + ! jump L \ another: counted - REFUSED: drop 2 jump (A-END) - GONE: drop (MQ-HDR)+6 a! @ -4 + if G4 drop jump KEEP - G4: drop @b \ the node that is gone - (CENTRE) if NOC (MQ-HDR)+1 a! @ xor if CENTRE \ only its centre is believed - drop drop jump L - NOC: drop drop jump L - CENTRE: drop dup NO-ROUTE - (AWAIT-FROM) a! @ xor if LEFT drop jump L - LEFT: drop 3 jump (A-END) - KEEP: (TAKE-KEEP) jump L + (AWAIT-FROM) a! ! 0 (A-SELF) a! ! + (ROOM) + 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) + M: (MINE) if SLEEP + drop (IN)+2 a! @ -1 + if TEXT -2 + if ANSWER -1 + if NACK -1 + if GONE + KEEP: drop (SKIP) jump M + TEXT: drop (A-CONSOLE) (IN)+1 a! @ xor if BREAK jump KEEP \ text: from the console? + 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) + 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? + 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 + 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 \ ( 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 @@ -427,11 +402,12 @@ header ABORT \ its own -- and end the line with ERROR. The return stack has been \ emptied; the data stack is as the word left it. \ (QUIET) is 1 while the node is doing something that is no text's doing: -\ paying a refusal, passing a message on, or sending word of how a text -\ ended. An error then has nobody to be told to -- the usual one is that -\ no one is on the port (18) -- and trying to tell it would raise it again -\ without end. So it is not told: what was being sent is let go and -\ counted, and the node goes back to waiting. +\ passing a message on, or sending what a text printed and word of how it +\ ended. An error then has nobody to be told to, and telling it would +\ begin another message for a text that is over. Nothing the nucleus does +\ there raises one as it stands -- with no one on a port it counts what it +\ could not send, and goes on -- so this is a guard: if one is raised, what +\ was being sent is let go and counted, and the node goes back to waiting. : (RAISED) (QUIET) a! @ if SPEAK drop (OUT) (OUT^) a! ! (LOST) a! @ 1 + ! jump (IDLE) diff --git a/v4/include/v4/boot.h b/v4/include/v4/boot.h index 113561aa..8194afc3 100644 --- a/v4/include/v4/boot.h +++ b/v4/include/v4/boot.h @@ -71,8 +71,8 @@ typedef struct { int v4_boot_run(const v4_boot *b); /* Hand the node a line and run it to its end, sending what it prints to - * `out`. The line is sent to the node as a message and what it prints - * comes back as messages (message.h). Returns how it ended -- V4_TEXT_QUIT, + * `out`. The line is put on the node's wire as a message, whole, and what + * it prints comes back as messages (message.h; docs/v4.0.0/MESH.md 7d.5). Returns how it ended -- V4_TEXT_QUIT, * V4_TEXT_COMPLETED or V4_TEXT_ERROR -- or one of these: */ #define V4_BOOT_LINE_TOO_LONG (-1) /* more than the node's input buffer holds; nothing was run */ #define V4_BOOT_LINE_STOPPED (-2) /* the node stopped on a fault, or never finished */ @@ -81,12 +81,12 @@ int v4_boot_run(const v4_boot *b); int v4_boot_line(const v4_boot *b, const char *text, unsigned len); /* A LINE THAT WAITS (docs/v4.0.0/MESH.md 7b.6). V4_BOOT_LINE_WAITING means - * the line has not ended: the node is waiting at its ports for an answer, + * the line has not ended: the node is asleep, waiting for an answer, * and here there is no one but the console to end that. The host reads the * next line and hands it over as usual: that breaks the wait, and what is * returned is how the line that was waiting ended -- in an error, - * Interrupted. The node has kept the line just handed over; the host then - * calls v4_boot_line with no text (0, 0) to have it done, and that returns + * Interrupted. The line just handed over is still on the node's wire; the host + * then calls v4_boot_line with no text (0, 0) to have it done, and that returns * how it ended, which may be V4_BOOT_LINE_WAITING again. */ /* The dictionary hash of a node started from `im`. */ diff --git a/v4/include/v4/image.h b/v4/include/v4/image.h index 377f8b8d..9171b38a 100644 --- a/v4/include/v4/image.h +++ b/v4/include/v4/image.h @@ -27,8 +27,8 @@ typedef struct { unsigned cell_bits, node_words, data_ring, ret_ring; /* Where the nucleus waits for a message (v4/capsule/quit.v4, (IDLE)): - * the node's P is there, blocked reading its ports, when it has nothing - * to do. */ + * a node that has taken the nucleus in begins there, and with nothing + * to do it is asleep at a wire inside it (operation 8, MESH.md 7d.3). */ v4_cell idle; v4_cell fault_table; /* (FAULTS): where a fault goes */ v4_cell port; /* word address of the node's port (node.h) */ diff --git a/v4/system/boot.c b/v4/system/boot.c index 715a5bf6..49ff118b 100644 --- a/v4/system/boot.c +++ b/v4/system/boot.c @@ -8,6 +8,7 @@ #include "v4/blocks.h" #include "v4/boot.h" #include "v4/message.h" +#include "v4/wire.h" #include "starkernel/capsule.h" #include "starkernel/capsule_generated.h" #include "starkernel/capsule_sig.h" @@ -80,10 +81,11 @@ static uint32_t word_count(const v4_node *n, const v4_image *im) /* ---- handing the node a line ---------------------------------------------- */ /* The boot is the node's neighbour on two of its ports. On port 0 it is - * the kernel: the node writes the number of a request there (ENGINE.md - * 3.3) -- for a block (blocks.h), or for one of the host's words. On port - * 1 it is the console: it writes the node a message of text and reads back - * messages of what the node printed and of how the text ended (message.h). */ + * the kernel: the node writes the number of a request there, a word + * (ENGINE.md 3.3) -- for a block (blocks.h), or for one of the host's + * words. On port 1 it is the console: it puts a message of text on the + * node's wire and takes off it the messages of what the node printed and + * of how the text ended (message.h). */ #define KERNEL_PORT 0u #define CONSOLE_PORT 1u #define CONSOLE_ID 1 /* the console's number as a sender; the node's is 0 until it is given one */ @@ -91,23 +93,165 @@ 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]) + +/* 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. */ +static int span_ok(const v4_node *n, v4_cell addr, unsigned count) +{ + if (count == 0) return 1; + if (count > V4_NODE_WORDS || addr < 0 || (v4_ucell)addr > (v4_ucell)(V4_NODE_WORDS - count)) return 0; + if (n->port >= 0 && addr <= n->port + (v4_cell)V4_PORT_HAVE && addr + (v4_cell)count > n->port) return 0; + 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. */ +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_cell a = n->wire_a, b = n->wire_b, h[V4_WIRE_HEADER], how = V4_WIRE_NO_ONE; + unsigned cells; + + switch (n->do_op) { + case 1: /* look */ + if (!rx) break; + if (rx->mark >= rx->used) how = V4_WIRE_NO_MESSAGE; + else how = span_ok(n, a, V4_WIRE_HEADER) ? v4_wire_look(rx, &n->mem[a]) : V4_WIRE_NOT_A_MESSAGE; + break; + case 2: /* take */ + if (!rx) break; + if (v4_wire_look(rx, h) != V4_WIRE_DONE) { how = V4_WIRE_NO_MESSAGE; break; } + 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; + else how = a >= 0 && a <= (v4_cell)CONSOLE_PORT ? v4_wire_move(rx, FROM_NODE((unsigned)a)) : V4_WIRE_NO_ONE; + break; + case 4: /* drop */ + if (rx) how = v4_wire_drop(rx); + break; + case 5: /* put */ + if (!tx) break; + cells = span_ok(n, a, V4_WIRE_HEADER) ? v4_wire_cells(n->mem[a + 6]) : 0; + if (cells == 0 || !span_ok(n, b, cells - V4_WIRE_HEADER)) how = V4_WIRE_NOT_A_MESSAGE; + else how = v4_wire_put(tx, &n->mem[a], cells > V4_WIRE_HEADER ? &n->mem[b] : &n->mem[0]); + break; + case 6: /* first */ + if (rx) { v4_wire_first(rx); how = V4_WIRE_DONE; } + break; + case 7: /* next */ + if (rx) how = v4_wire_next(rx); + break; + case 8: /* sleep */ + if (!boot_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; + how = V4_WIRE_NO_ROOM; + break; + case 10: /* room */ + if (!tx) break; + 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; + default: /* not an operation */ + how = V4_WIRE_NOT_A_MESSAGE; + break; + } + v4_node_done(n, how); + return 1; +} + +/* The node is told which of its wires have a message and executes one + * instruction word; if it fetched which wires have one, it has seen what + * had come. The first half of a fabric's step (fabric.c). */ +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)); + (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; + n->have_fetched = 0; + } +} + +/* Time passes for the node, in the order a fabric's step has: that, and + * then what it asked of a wire is done. Returns 0 if it is asleep at a + * wire and sleeps on. */ +static int boot_step(const v4_boot *b) +{ + boot_exec(b); + return v4_node_doing(b->n) ? boot_do(b) : 1; +} + /* Hand the node a line and run it to its end. What it prints goes to the * console; or, if `keep` is given, into `keep`, at most `cap` characters, - * and none of it is shown: *printed is then how many it printed in all. */ + * and none of it is shown: *printed is then how many it printed in all. + * The console takes every message off the node's wire each time round, so + * what the node prints never waits for room for long. */ static int run_line(const v4_boot *b, const char *text, unsigned len, char *keep, unsigned cap, unsigned *printed) { - static v4_message out, in; /* one line at a time: the boot is not re-entered */ + static v4_message out; /* one line at a time: the boot is not re-entered */ + static v4_cell in[V4_WIRE_HEADER], in_text[V4_MSG_MAX_CHARS / 4u]; v4_node *n = b->n; uint64_t steps = 0; - unsigned sent = 0, i; + unsigned i; - if (!text) out.count = 0; /* no new line: the node has one it kept (boot.h) */ - else if (!v4_message_text(&out, 0, CONSOLE_ID, V4_MSG_TEXT, text, len)) return V4_BOOT_LINE_TOO_LONG; - in.count = 0; + 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; + } for (;;) { - (void)v4_exec_step_word(n, b->es, b->h); + 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) { + 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); + if (!keep) b->out(&c, 1); + else { if (*printed < cap) keep[*printed] = c; (*printed)++; } + } + } else if (in[2] == V4_MSG_DONE) { + /* The text has ended. The node goes on to where it next asks something of a wire, with + * nothing of its own on either stack: whoever called may then look at its stacks, or + * empty them. */ + for (i = 0; i < 4096u && !v4_node_doing(n) && !n->stopped && !n->asking; i++) boot_exec(b); + return (int)in_text[0]; + } + } + if (!awake) { + if (n->do_op == 9) continue; /* waiting for room, which the console has just made */ + return V4_BOOT_LINE_WAITING; /* waiting for an answer: only the console can end that */ + } + if (n->asking && n->ask_port == KERNEL_PORT) { /* a request: the kernel's turn */ if (v4_blocks_serve(&boot_blocks, n, n->request)) { /* a block, as for any node */ v4_node_port_served(n); @@ -122,36 +266,12 @@ static int run_line(const v4_boot *b, const char *text, unsigned len, char *keep } continue; } - if (n->asking && n->ask_port == CONSOLE_PORT) { /* a word of a message from the node */ - v4_cell word = n->request; - v4_node_port_served(n); - if (v4_message_word(&in, word)) { - unsigned chars = v4_message_length(&in); - if (v4_message_type(&in) == V4_MSG_OUTPUT) { - for (i = 0; i < chars; i++) { - char c = v4_message_char(&in, i); - if (!keep) b->out(&c, 1); - else { if (*printed < cap) keep[*printed] = c; (*printed)++; } - } - } else if (v4_message_type(&in) == V4_MSG_DONE) { - return (int)in.word[V4_MSG_HEADER]; - } - in.count = 0; - } - continue; - } - if (n->asking) { /* a port with no one on it: an error on the node */ + if (n->asking) { /* a word written to a port with no one on it: an error on the node */ if (!v4_node_port_gone(n, V4_ERROR_NO_ONE)) return V4_BOOT_LINE_STOPPED; continue; } - - if (n->reading && !n->given) { - if (sent < out.count && (n->read_port == CONSOLE_PORT || n->read_port == V4_PORT_ANY)) { - v4_node_port_give(n, CONSOLE_PORT, out.word[sent++]); /* a word of the message to the node */ - continue; - } - if (n->read_port != CONSOLE_PORT && n->read_port != KERNEL_PORT && v4_node_port_gone(n, V4_ERROR_NO_ONE)) continue; /* reading one port, with no one on it */ - if (n->read_port == V4_PORT_ANY) return V4_BOOT_LINE_WAITING; /* waiting for an answer: only the console can end that */ + if (n->reading && !n->given) { /* a word read from a port: no one here writes one */ + if (n->read_port != V4_PORT_ANY && v4_node_port_gone(n, V4_ERROR_NO_ONE)) continue; return V4_BOOT_LINE_STOPPED; /* waiting for what will not come */ } if (v4_image_waiting(n, b->im)) { /* the text is reading the keyboard */ @@ -310,10 +430,14 @@ static int load_nucleus(const v4_boot *b) if (!p || cap->length % CELL_BYTES != 0) { say(b, "V4: capsule " NUCLEUS_NAME " is not whole words\n"); return 0; } words = cap->length / CELL_BYTES; - /* until it has taken every word and is waiting for a message: reading - * its ports again, but from its memory now and not from the port */ - while (at < words || !(n->reading && !n->given && n->p != b->im->port + (v4_cell)V4_PORT_ANY)) { - (void)v4_exec_step_word(n, b->es, b->h); + /* until it has taken every word and is where the nucleus waits for a + * message: asleep, no message having come */ + for (;;) { + if (!boot_step(b)) { + if (at >= words && n->do_op == 8) break; + say(b, "V4: the node did not take the nucleus in\n"); + return 0; + } if (n->stopped || n->asking || ++steps > STEP_LIMIT) { say(b, "V4: the node did not take the nucleus in\n"); return 0; @@ -322,7 +446,6 @@ static int load_nucleus(const v4_boot *b) v4_ucell word = 0; unsigned i; if (at >= words) { - if (n->p != b->im->port + (v4_cell)V4_PORT_ANY) break; /* it is waiting for a message */ say(b, "V4: the node wants more than the nucleus holds\n"); return 0; } @@ -351,10 +474,9 @@ 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; } - /* what is on its two ports, the kernel and the console, takes a word - * when it is written: the node need never wait before writing to them - * (node.h, v4_node_port_status) */ - v4_node_port_status(b->n, 0, (1u << KERNEL_PORT | 1u << CONSOLE_PORT) * (1u + (1u << V4_PORTS))); /* each of them there, and taking */ + /* 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)); 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 6278c03b..aaffb1f3 100644 --- a/v4/tests/host_map.h +++ b/v4/tests/host_map.h @@ -65,12 +65,7 @@ #define SRC (BVARS + 8) #define SRC_HOOK (BVARS + 9) #define QUIET (BUF0_W - 10) /* 1 while the node does something that is no text's doing: an error then is not told (quit.v4, (RAISED)) */ -#define A_WORD (BUF0_W - 113) /* AWAIT's look through the messages waiting: the answer's word, */ -#define A_LEFT (BUF0_W - 114) /* how many cells are still to be looked at, */ -#define A_FOUND (BUF0_W - 115) /* and what was found */ #define REFUSED (BUF0_W - 7) /* how many of this node's messages it has been told were refused */ -#define OWED_COUNT (BUF0_W - 8) /* how many refusals this node owes and has not sent yet, at most 8 */ -#define OWED (BUF0_W - 112) /* those: for each, the node it is owed to and the node the refused message was for */ #define ACL_HOOK (BUF0_W - 6) /* the xt of the access control recheck word, or 0 */ #define LINE_STATUS (BUF0_W - 9) /* how the last line ended: 0 QUIT, 1 completed, 2 an error */ #define PORT (BUF0_W - 140) /* the node's ports (node.h): V4_PORTS of them, then "any port", "which port" and the rest as far as V4_PORT_HAVE. Port 0 is where its requests go. */ @@ -79,24 +74,42 @@ #define ROUTE_DEFAULT (BUF0_W - 18) /* the port address for a node not in the table, or 0: there is none */ #define LOST (BUF0_W - 19) /* how many messages have been let go for want of a way */ #define PRINT_TO (BUF0_W - 20) /* where what the text being served prints is to go: the node, */ -#define WRITERS (PORT + (v4_cell)V4_PORTS + 2) /* which ports have a neighbour waiting to write to this node (node.h) */ -#define READERS (PORT + (v4_cell)V4_PORTS + 3) /* and which have one waiting to read from it */ -#define PRINT_PORT (BUF0_W - 33) /* and the port address that leads there */ -#define DONE_PORT (BUF0_W - 34) /* the port address that leads back to whoever sent the text being served */ -#define MQ_HEAD (BUF0_W - 35) /* the messages waiting to be dealt with: where the oldest begins, */ -#define MQ_TAIL (BUF0_W - 36) /* where the next will go, */ -#define MQ_COUNT (BUF0_W - 37) /* and how many cells they take */ +#define PRINT_PORT (BUF0_W - 33) /* and the port, by its number, that leads there */ +#define DONE_PORT (BUF0_W - 34) /* the port, by its number, that leads back to whoever sent the text being served */ +/* WHOLE MESSAGES (MESH.md 7d.4; core.v4). A node keeps no message but the one it is doing: these are what it + * builds one in and looks at one with, and where it has got to in going through its wires. */ +#define WIRE (PORT + (v4_cell)V4_PORT_WIRE) /* the six addresses after the ports (node.h): the wire an operation is about, */ +#define WIRE_A (PORT + (v4_cell)V4_PORT_A) /* what it needs, */ +#define WIRE_B (PORT + (v4_cell)V4_PORT_B) +#define WIRE_DO (PORT + (v4_cell)V4_PORT_DO) /* the operation, */ +#define WIRE_HOW (PORT + (v4_cell)V4_PORT_HOW) /* how it went, */ +#define WIRE_HAVE (PORT + (v4_cell)V4_PORT_HAVE) /* and which wires have a message */ +#define S_HAVE (BUF0_W - 35) /* which wires had a message when the node last asked, */ +#define S_MASK (BUF0_W - 36) /* those of them still to be gone through, */ +#define S_WIRE (BUF0_W - 37) /* the one being gone through, */ +#define S_ON (BUF0_W - 38) /* and whether its mark has been put at its first message */ +#define KEEP (BUF0_W - 40) /* the port the answer to the text being done goes by, on whose wire 8 cells are kept for it; -1: none */ +#define DEFER (BUF0_W - 41) /* a port whose wire had no room for the answer to text that waits to be begun; -1: none */ +#define IN_HDR (BUF0_W - 56) /* the seven words of the message a wire's mark is at */ +#define ONE (BUF0_W - 49) /* the one word of text of a NACK, a GONE or an answer, coming or going */ +#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 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 + * 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 */ -#define GATE_PORT (BUF0_W - 38) /* the port address a message is about to be begun on */ #define NEAR (BUF0_W - 64) /* for each port, the number of the node on the other end; 0: not told */ -#define MQ_HDR (BUF0_W - 56) /* a message being taken in: its seven words and the port it came on */ -#define MQ_W (OUT_W - 400) /* the messages waiting: each is its seven words, the port it came on, its text */ -#define MQ_CELLS 400 #define ROUTES (BUF0_W - 96) /* the table of ways: 16 entries of a node and the port address that leads to it */ #define ROUTE_MAX 16 #define ME (BUF0_W - 13) /* this node's number: a message is for it when its first word is this */ #define OUT_PTR (BUF0_W - 14) /* where the next character printed goes: a cell of the output buffer */ -#define REPLY (BUF0_W - 15) /* the address of the port the message being served came on */ +#define REPLY (BUF0_W - 15) /* the port, by its number, the text being served came on */ #define MSG (BUF0_W - 48) /* the header of the message being served, 7 cells */ #define OUT_W (BUF0_W - 400) /* the output buffer: 256 cells, a character to a cell */ #define OUT_CELLS 256 @@ -106,12 +119,12 @@ #define BOOT_CELLS (BVARS + 14) /* (BOOT): DP and LATEST as the loader left them, 2 cells */ #define BUF0_W (BVARS - (v4_cell)(V4_BLOCK_SLOTS * V4_BLOCK_CELLS)) /* the block window: the kernel's slots, 256 cells each (v4/blocks.h) */ /* The ports and the addresses after them, through V4_PORT_HAVE (node.h, MESH.md 7d.3), are not memory and must be where - * no variable is: after the output buffer, which ends at BUF0_W - 144, and before A_FOUND, the first variable above it. + * no variable is: after the output buffer, which ends at BUF0_W - 144, and before OUT_HDR, the first variable above it. * With 8 ports that is 18 addresses, BUF0_W - 140 to BUF0_W - 123. (They were once among the variables, and what was * stored to the variables was stored to ports.) */ -typedef char host_map_ports_fit[(PORT >= OUT_W + OUT_CELLS && PORT + (v4_cell)V4_PORT_HAVE < A_FOUND) ? 1 : -1]; -/* Nothing printed and no message waiting: a node's variables for them, as at switch-on. */ -#define HOST_MESSAGES_EMPTY(n) do { (n)->mem[OUT_PTR] = OUT_W; (n)->mem[MQ_HEAD] = MQ_W; (n)->mem[MQ_TAIL] = MQ_W; (n)->mem[MQ_COUNT] = 0; } while (0) +typedef char host_map_ports_fit[(PORT >= OUT_W + OUT_CELLS && PORT + (v4_cell)V4_PORT_HAVE < OUT_HDR) ? 1 : -1]; +/* Nothing printed: a node's variable for it, as at switch-on. */ +#define HOST_MESSAGES_EMPTY(n) do { (n)->mem[OUT_PTR] = OUT_W; } while (0) #define QVARS (BUF0_W - 4) /* (Q): quit.v4, system.v4 and blocks.v4, 4 cells */ #define WBUF (WBUF_W * 4) #define TIB (TIB_W * 4) @@ -125,8 +138,8 @@ typedef char host_map_ports_fit[(PORT >= OUT_W + OUT_CELLS && PORT + (v4_cell)V4 #ifndef DICT_END_W #define DICT_END_W ((v4_cell)13824) /* 512 cells lower than it was: the block window is four slots now */ #endif -/* the messages waiting are above the dictionary */ -typedef char host_map_queue_fits[(MQ_W >= DICT_END_W) ? 1 : -1]; +/* the texts made ready for the wire are above the dictionary */ +typedef char host_map_texts_fit[(SEND_TEXT >= DICT_END_W && SEND_TEXT + 256 <= OUT_TEXT && OUT_TEXT + 64 == OUT_W) ? 1 : -1]; /* How many slots, from slot 0, a branch may sit in on a node this size: those * whose address field reaches every word of it. */ @@ -194,27 +207,33 @@ static int host_load(v4_text *tx, v4_node *n, const char *const *files, unsigned v4_text_constant(tx, "(PRINT-PORT)", PRINT_PORT); v4_text_constant(tx, "(DONE-PORT)", DONE_PORT); v4_text_constant(tx, "(ROUTES)", ROUTES); - v4_text_constant(tx, "(WRITERS)", WRITERS); - v4_text_constant(tx, "(READERS)", READERS); v4_text_constant(tx, "-PORTS", -(v4_cell)V4_PORTS); v4_text_constant(tx, "(NEAR)", NEAR); - v4_text_constant(tx, "(GATE-PORT)", GATE_PORT); v4_text_constant(tx, "(AWAIT-FROM)", AWAIT_FROM); - v4_text_constant(tx, "(MQ)", MQ_W); - v4_text_constant(tx, "(MQ-END)", MQ_W + MQ_CELLS); - v4_text_constant(tx, "-MQ-ORDINARY", -(v4_cell)(MQ_CELLS - 7 - 36)); /* the same for a message that is not a refusal or a GONE: 36 cells are kept for those */ + v4_text_constant(tx, "(WIRE)", WIRE); + v4_text_constant(tx, "(WIRE-A)", WIRE_A); + v4_text_constant(tx, "(WIRE-B)", WIRE_B); + v4_text_constant(tx, "(WIRE-DO)", WIRE_DO); + v4_text_constant(tx, "(WIRE-HOW)", WIRE_HOW); + v4_text_constant(tx, "(WIRE-HAVE)", WIRE_HAVE); + v4_text_constant(tx, "(S-HAVE)", S_HAVE); + v4_text_constant(tx, "(S-MASK)", S_MASK); + v4_text_constant(tx, "(S-WIRE)", S_WIRE); + v4_text_constant(tx, "(S-ON)", S_ON); + v4_text_constant(tx, "(KEEP)", KEEP); + v4_text_constant(tx, "(DEFER)", DEFER); + v4_text_constant(tx, "(IN)", IN_HDR); + v4_text_constant(tx, "(ONE)", ONE); + v4_text_constant(tx, "(OUT-HDR)", OUT_HDR); + 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, "(A-SELF)", A_SELF); + v4_text_constant(tx, "(SEND^)", SEND_PTR); + v4_text_constant(tx, "(OUT-TEXT)", OUT_TEXT); + v4_text_constant(tx, "(SEND-TEXT)", SEND_TEXT); v4_text_constant(tx, "(QUIET)", QUIET); - v4_text_constant(tx, "(A-WORD)", A_WORD); - v4_text_constant(tx, "(A-LEFT)", A_LEFT); - v4_text_constant(tx, "(A-FOUND)", A_FOUND); v4_text_constant(tx, "(REFUSED)", REFUSED); - v4_text_constant(tx, "(OWED#)", OWED_COUNT); - v4_text_constant(tx, "(OWED)", OWED); - v4_text_constant(tx, "-MQ-ROOM", -(v4_cell)(MQ_CELLS - 7)); /* cells taken + a text's words + this is below zero while the message fits */ - v4_text_constant(tx, "(MQ-HEAD)", MQ_HEAD); - v4_text_constant(tx, "(MQ-TAIL)", MQ_TAIL); - v4_text_constant(tx, "(MQ#)", MQ_COUNT); - v4_text_constant(tx, "(MQ-HDR)", MQ_HDR); v4_text_constant(tx, "(OUT^)", OUT_PTR); v4_text_constant(tx, "(REPLY)", REPLY); v4_text_constant(tx, "(MSG)", MSG); diff --git a/v4/tests/test_host_depth.c b/v4/tests/test_host_depth.c new file mode 100644 index 00000000..bb662fe9 --- /dev/null +++ b/v4/tests/test_host_depth.c @@ -0,0 +1,341 @@ +/* test_host_depth.c -- every message path of a node on a mesh, at every + * depth of both stacks. docs/v4.0.0/MESH.md 7d.7, acceptance 7; 7c.6. + * + * console --1 [10] 2--2 [11] 3--2 [12] + * + * A node's message code runs on the stacks its text is using. The first + * 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. + */ +#include "v4/image.h" +#include "v4/fabric.h" +#include "v4/message.h" +#include "v4/text.h" +#include +#include +#include + +#include "host_map.h" + +static int failures = 0, checks = 0; +#define CHECK(c,...) do{checks++; if(!(c)){failures++; printf("FAIL %s:%d: ",__FILE__,__LINE__); printf(__VA_ARGS__); printf("\n");}}while(0) + +static v4_text tx; +static v4_node nucleus; /* one node with the nucleus in it: what every node here starts as */ +static v4_cell w_idle, w_fault; +static v4_fabric_node pool[3]; +static v4_place places[3]; +static v4_fabric f; +#define QUEUES 12u /* two for each wire there may be: the console's, 10-11, 11-12, 10-12, and two wires to spare */ +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 */ +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; +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 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 */ + +/* Everything the node at the console has put on the console's wire is taken, in order. How many messages. */ +static unsigned console_takes(void) +{ + static v4_cell hd[V4_WIRE_HEADER], text[V4_MSG_MAX_CHARS / 4u]; + unsigned took = 0, i; + while (v4_fabric_device_take(&f, hera, 1, hd, text, V4_MSG_MAX_CHARS / 4u) == V4_WIRE_DONE) { + took++; + if (hd[2] == V4_MSG_OUTPUT) { + 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]; + } 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) { + nacks++; nack_from = hd[1]; nack_about = text[0]; + } + } + return took; +} + +static int is_empty(const char *s) { return s[0] == 0; } + +/* 1 if the node is asleep (operation 8): it executes nothing until a message comes. */ +static int asleep(unsigned id) +{ + const v4_fabric_node *x = v4_fabric_node_at(&f, id); + return x && x->n.doing && x->n.do_op == 8; +} +static unsigned step_cap = 20000000; /* how long a line may take before it is given up on */ +static unsigned stuck; /* bit k: the node at place k is known to be going round for ever, and is not waited for */ +static void (*watch)(void); /* called after every step, if set */ +static unsigned long steps_run; + +static void show_stuck(const char *text) +{ + unsigned k, q; + if (!getenv("V4_MESH_STUCK")) return; + for (k = 0; k < 3; k++) if (v4_fabric_node_at(&f, k)) { const v4_node *n = &pool[k].n; + printf(" stuck after \"%.40s\": node %ld p=%ld doing=%d op=%ld how=%ld wire=%u asking=%d d=%u r=%u await=%ld keep=%ld lost=%ld refused=%ld fault=%u@%ld err=%ld\n", text, (long)n->mem[ME], (long)n->p, n->doing, (long)n->do_op, (long)n->how, n->wire, + n->asking, (unsigned)n->ds.depth, (unsigned)n->rs.depth, (long)n->mem[AWAIT_FROM], (long)n->mem[KEEP], (long)n->mem[LOST], (long)n->mem[REFUSED], n->fault_kind, (long)n->fault_addr, (long)n->mem[NODE_ERROR]); + for (q = 0; q < V4_PORTS; q++) if (places[k].wire[q].rx) printf(" port %u: %u cells coming, %u going\n", q, places[k].wire[q].rx->used, places[k].wire[q].tx->used); } +} +/* Let everything that follows from what has been put happen: until nothing can, or -- with a node known to be + * stuck -- until every other node has been asleep, and the console has been sent nothing, for a while. */ +static int run(void) +{ + unsigned steps = 0, quiet = 0, k; + while (steps < step_cap) { + unsigned did = v4_fabric_step(&f), took = console_takes(), awake = 0, running = 0; + steps++; steps_run++; + if (watch) watch(); + /* a step in which a node only faulted counts nothing done (exec.h): it goes on from its handler at the next */ + for (k = 0; k < 3; k++) if (v4_fabric_node_at(&f, k) && !pool[k].n.doing && !pool[k].n.asking && !pool[k].n.stopped && !(pool[k].n.reading && !pool[k].n.given)) running = 1; + if (did == 0 && took == 0 && !running) return 1; + for (k = 0; k < 3; k++) if (v4_fabric_node_at(&f, k) && !(stuck & 1u << k) && !(pool[k].n.doing && (pool[k].n.do_op == 8 || pool[k].n.do_op == 9))) awake = 1; + quiet = (stuck && !awake && took == 0) ? quiet + 1 : 0; + if (quiet >= 400000) return 1; + } + return 0; +} +/* 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) +{ + static v4_message m; + if (!v4_message_text(&m, node, CONSOLE_ID, V4_MSG_TEXT, text, (unsigned)strlen(text))) return 0; + return v4_fabric_device_put(&f, hera, 1, m.word, m.word + V4_MSG_HEADER) == V4_WIRE_DONE; +} +static void forget(void) +{ + printed_len = 0; printed[0] = 0; printed_from = -1; + ended = 0; ended_from = -1; ended_how = -1; ended_at = 0; +} +/* Send text to a node and let everything that follows from it happen. */ +static const char *tell(v4_cell node, const char *text) +{ + forget(); + if (!put_text(node, text)) return "(not put)"; + if (!run()) { show_stuck(text); return "(still running)"; } + return printed; +} + +/* A StarForth node in the fabric: born, then given the nucleus and the + * registers the nucleus expects, waiting for a message. */ +static unsigned starforth_node(unsigned k) +{ + int id = v4_fabric_add(&f, &pool[k], PORT); + v4_node *n = &pool[k].n; + memcpy(n->mem, nucleus.mem, sizeof n->mem); + v4_node_stack_regs_attach(n, DSTACK_REG, RSTACK_REG); + v4_node_error_attach(n, NODE_ERROR); + v4_node_fault_attach(n, w_fault); + n->p = w_idle; + return (unsigned)id; +} + +/* Three nodes that know nothing, in a row, the console on the first. */ +static int fresh(void) +{ + unsigned i; + int ok; + v4_fabric_init(&f, places, 3); + v4_fabric_queues(&f, queues, QUEUES); + v4_fabric_gone_error(&f, V4_ERROR_NO_ONE); + hera = starforth_node(0); + mid = starforth_node(1); + far = starforth_node(2); + ok = v4_fabric_wire_device(&f, hera, 1, &console) && v4_fabric_wire(&f, hera, 2, mid, 2) && v4_fabric_wire(&f, mid, 3, far, 2); + stuck = 0; watch = 0; nacks = 0; step_cap = 20000000; + forget(); + for (i = 0; i < 200; i++) (void)v4_fabric_step(&f); + return ok && asleep(hera) && asleep(mid) && asleep(far); +} +static int told(v4_cell node, const char *text) { return is_empty(tell(node, text)) && ended == 1 && ended_how == V4_TEXT_COMPLETED; } +/* The same, told who they are, the way to each other, and who is on each port: + * console --1 [10] 2--2 [11] 3--2 [12] */ +static int row(void) +{ + return fresh() + && told(0, "10 (ME) ! 1 (CONSOLE) ! 1 1 ROUTE 0 2 ROUTE") + && told(0, "11 (ME) ! 1 (CONSOLE) ! 2 DEFAULT-ROUTE 0 3 ROUTE") + && told(0, "12 (ME) ! 1 (CONSOLE) ! 2 DEFAULT-ROUTE") + && told(10, "0 NO-ROUTE 11 2 ROUTE 12 2 ROUTE 11 2 NEIGHBOUR") && told(11, "0 NO-ROUTE 12 3 ROUTE 10 2 NEIGHBOUR 12 3 NEIGHBOUR") && told(12, "11 2 NEIGHBOUR"); +} + +/* ---- the sweep ---- */ +static unsigned runs, bad; + +/* 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 */ +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++; } +} + +/* 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) +{ + size_t at = 0; + unsigned bit; + text[0] = 0; + for (; count >= 16; count -= 16) at += (size_t)snprintf(text + at, cap - at, "P16 "); + for (bit = 8; bit; bit >>= 1) if (count >= bit) { at += (size_t)snprintf(text + at, cap - at, "P%u ", bit); count -= bit; } +} + +/* 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[] = { + ": 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 ;", + }; + char line[64]; + unsigned k; + if (!row() || !told(11, act[path]) || !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 ;")) return 0; + if (!told(11, ": N1 ACT ;")) return 0; + for (k = 2; k <= V4_RET_DEPTH + 1u; k++) { + snprintf(line, sizeof line, ": N%u N%u ;", k, k - 1u); + if (!told(11, line)) return 0; + } + 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; + 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) +{ + 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; + unsigned left; + + 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 */ + 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; + if (!put_text(11, text) || !put_text(10, "BUSY") || !run()) why = "the nodes did not come to rest"; + watch = 0; + } else { + if (!put_text(11, text) || !run()) why = "the nodes did not come to rest"; + } + how11 = (int)pool[mid].n.mem[LINE_STATUS]; + 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"; + /* 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 && 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"; + if (!why && strcmp(tell(12, "1 2 + ."), "3 ") != 0) why = "node 12 did not answer"; + if (!why && !(asleep(hera) && asleep(mid) && asleep(far))) why = "a node is not asleep afterwards"; + checks++; + if (why) { + failures++; bad++; + printf("FAIL %s, %s %u: %s: \"%.60s\"\n", name[path], what, depth, why, printed); + show_stuck(text); + (void)made(path); /* the next begins on a row that is whole */ + } + return how11; +} + +int main(void) +{ + unsigned path, k; + char text[160]; + + printf("v4 mesh depth tests: V4_CELL_BITS=%d, V4_NODE_WORDS=%u\n", V4_CELL_BITS, (unsigned)V4_NODE_WORDS); + { + static const char *const files[] = { "core.v4", "input.v4", "dict.v4", "codegen.v4", "compile.v4", "quit.v4", "forth.v4", + "numout.v4", "words.v4", "system.v4", "qmath.v4", "blocks.v4", "log.v4", "acl.v4" }; + CHECK(host_load(&tx, &nucleus, files, 14), "the nucleus assembles"); + } + CHECK(v4_text_finish(&tx), "everything is defined: %s", v4_text_error(&tx)); + if (failures) { printf(" %d checks, %d failures\n", checks, failures); return 1; } + w_idle = v4_text_word(&tx, "(IDLE)"); + w_fault = v4_text_word(&tx, "(FAULTS)"); + nucleus.mem[DP] = DICT_W * 4; /* the variables, as at switch-on (tools/mkimage.c) */ + nucleus.mem[LATEST] = v4_text_latest(&tx); + nucleus.mem[CFP] = CFS_W; + nucleus.mem[BASE] = 10; + nucleus.mem[FENCE] = DICT_W; + nucleus.mem[BOOT_CELLS] = nucleus.mem[DP]; + nucleus.mem[BOOT_CELLS + 1] = nucleus.mem[LATEST]; + nucleus.mem[LOG_LEVEL] = 2; + nucleus.mem[CONTEXT] = LATEST; + nucleus.mem[CURRENT] = LATEST; + nucleus.mem[SRC] = TIB; + nucleus.mem[LINE_STATUS] = 1; + HOST_MESSAGES_EMPTY(&nucleus); + + for (path = 0; path < 6; path++) { + unsigned before = bad, done = 0, errors = 0; + CHECK(made(path), "a row for path %u", path); + if (failures > (int)bad) break; + /* the data stack at every depth from 20 to full */ + 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++; + } + /* 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++) { + snprintf(text, sizeof text, "N%u", k); + (void)one(path, "calls deep", k, text, 0); + } + 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); + } + /* ---- 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. ---- */ + 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"); + 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]; + fill(text, sizeof text, k); + CHECK(told(11, text) && pool[mid].n.ds.depth == k, "node 11's text leaves %u values on its stack", k); + 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"); + 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(" %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 cde5eaa7..4b0ca921 100644 --- a/v4/tests/test_host_mesh.c +++ b/v4/tests/test_host_mesh.c @@ -7,8 +7,12 @@ * Each node is the nucleus (v4/capsule/ *.v4) and nothing else. They start * alike, numbered 0 and knowing no way anywhere; everything they come to * know they are told by text sent from the console, as whoever wires a node - * tells it. The console is a device on a port and speaks messages - * (v4/include/v4/message.h). + * tells it. The console is a device on a port and speaks whole messages + * (v4/include/v4/message.h): it puts them on the wire to its node and + * takes them off the wire from it, through the fabric (MESH.md 7d.2). + * + * Each scene after the first is played on a row made afresh, row(), so + * that none depends on what an earlier one left. */ #include "v4/image.h" #include "v4/fabric.h" @@ -29,72 +33,120 @@ static v4_cell w_idle, w_fault; static v4_fabric_node pool[3]; static v4_place places[3]; static v4_fabric f; +#define QUEUES 12u /* two for each wire there may be: the console's, 10-11, 11-12, 10-12, and two wires to spare */ +static v4_wire_queue queues[QUEUES]; /* ---- the console: a device on a port ---- */ #define CONSOLE_ID 1 -static v4_message going, coming; -static unsigned going_at; -static char printed[8192]; /* what has come back since the console last sent */ +static const v4_device console = { 0, 0, 0, 0 }; /* it takes and gives no words: only whole messages */ +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; 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 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 */ -static int console_give(void *self, v4_cell *value) +/* Everything the node at the console has put on the console's wire is taken, in order. How many messages. */ +static unsigned console_takes(void) { - (void)self; - if (going_at >= going.count) return 0; - *value = going.word[going_at++]; - return 1; -} -static int console_take(void *self, v4_cell value) -{ - (void)self; - if (v4_message_word(&coming, value)) { - if (v4_message_type(&coming) == V4_MSG_OUTPUT) { - unsigned i, chars = v4_message_length(&coming); - for (i = 0; i < chars && printed_len + 1 < sizeof printed; i++) printed[printed_len++] = v4_message_char(&coming, i); + static v4_cell hd[V4_WIRE_HEADER], text[V4_MSG_MAX_CHARS / 4u]; + unsigned took = 0, i; + while (v4_fabric_device_take(&f, hera, 1, hd, text, V4_MSG_MAX_CHARS / 4u) == V4_WIRE_DONE) { + took++; + if (hd[2] == V4_MSG_OUTPUT) { + 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 = v4_message_from(&coming); - } else if (v4_message_type(&coming) == V4_MSG_DONE) { - ended++; - ended_from = v4_message_from(&coming); - ended_how = coming.word[V4_MSG_HEADER]; - } else if (v4_message_type(&coming) == V4_MSG_NACK) { - nacks++; - nack_from = v4_message_from(&coming); - nack_about = coming.word[V4_MSG_HEADER]; + printed_from = hd[1]; + } 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) { + nacks++; nack_from = hd[1]; nack_about = text[0]; } - coming.count = 0; } - return 1; + return took; } -static int console_pending(void *self) { (void)self; return going_at < going.count; } -static const v4_device console = { console_take, console_give, 0, console_pending }; static int is_empty(const char *s) { return s[0] == 0; } +/* 1 if the node is asleep (operation 8): it executes nothing until a message comes. */ +static int asleep(unsigned id) +{ + const v4_fabric_node *x = v4_fabric_node_at(&f, id); + return x && x->n.doing && x->n.do_op == 8; +} +/* 1 if it is asleep waiting for room on a wire (operation 9). */ +static int waits_for_room(unsigned id) +{ + const v4_fabric_node *x = v4_fabric_node_at(&f, id); + return x && x->n.doing && x->n.do_op == 9; +} +/* the queue of messages going from a node's port, and coming to it */ +static const v4_wire_queue *going_from(unsigned id, unsigned port) { return places[id].wire[port].tx; } +static const v4_wire_queue *coming_to(unsigned id, unsigned port) { return places[id].wire[port].rx; } + static unsigned step_cap = 20000000; /* how long a line may take before it is given up on */ +static unsigned stuck; /* bit k: the node at place k is known to be going round for ever, and is not waited for */ +static void (*watch)(void); /* called after every step, if set */ +static unsigned long steps_run; +static unsigned between_data, between_ret; /* the most of each stack any node has used while doing nothing that is a text's (QUIET), */ +static int was_quiet[3], clean[3]; /* after a text that left nothing on the data stack */ + +static void show_stuck(const char *text) +{ + unsigned k, q; + if (!getenv("V4_MESH_STUCK")) return; + for (k = 0; k < 3; k++) if (v4_fabric_node_at(&f, k)) { const v4_node *n = &pool[k].n; + printf(" stuck after \"%.40s\": node %ld p=%ld doing=%d op=%ld how=%ld wire=%u asking=%d d=%u r=%u await=%ld keep=%ld lost=%ld refused=%ld fault=%u@%ld err=%ld\n", text, (long)n->mem[ME], (long)n->p, n->doing, (long)n->do_op, (long)n->how, n->wire, + n->asking, (unsigned)n->ds.depth, (unsigned)n->rs.depth, (long)n->mem[AWAIT_FROM], (long)n->mem[KEEP], (long)n->mem[LOST], (long)n->mem[REFUSED], n->fault_kind, (long)n->fault_addr, (long)n->mem[NODE_ERROR]); + for (q = 0; q < V4_PORTS; q++) if (places[k].wire[q].rx) printf(" port %u: %u cells coming, %u going\n", q, places[k].wire[q].rx->used, places[k].wire[q].tx->used); } +} +/* Let everything that follows from what has been put happen: until nothing can, or -- with a node known to be + * stuck -- until every other node has been asleep, and the console has been sent nothing, for a while. */ +static int run(void) +{ + unsigned steps = 0, quiet = 0, k; + while (steps < step_cap) { + unsigned did = v4_fabric_step(&f), took = console_takes(), awake = 0, running = 0; + steps++; steps_run++; + if (watch) watch(); + for (k = 0; k < 3; k++) if (v4_fabric_node_at(&f, k)) { + int quiet_now = pool[k].n.mem[QUIET] == 1; + if (quiet_now && !was_quiet[k]) clean[k] = pool[k].n.ds.depth <= 1; /* its text has ended: did it leave anything? (how it ended may still be there) */ + was_quiet[k] = quiet_now; + if (!quiet_now || !clean[k]) continue; + if (pool[k].n.ds.depth > between_data) between_data = (unsigned)pool[k].n.ds.depth; + if (pool[k].n.rs.depth > between_ret) between_ret = (unsigned)pool[k].n.rs.depth; + } + /* a step in which a node only faulted counts nothing done (exec.h): it goes on from its handler at the next */ + for (k = 0; k < 3; k++) if (v4_fabric_node_at(&f, k) && !pool[k].n.doing && !pool[k].n.asking && !pool[k].n.stopped && !(pool[k].n.reading && !pool[k].n.given)) running = 1; + if (did == 0 && took == 0 && !running) return 1; + for (k = 0; k < 3; k++) if (v4_fabric_node_at(&f, k) && !(stuck & 1u << k) && !(pool[k].n.doing && (pool[k].n.do_op == 8 || pool[k].n.do_op == 9))) awake = 1; + quiet = (stuck && !awake && took == 0) ? quiet + 1 : 0; + if (quiet >= 400000) return 1; + } + return 0; +} +/* 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) +{ + static v4_message m; + if (!v4_message_text(&m, node, CONSOLE_ID, V4_MSG_TEXT, text, (unsigned)strlen(text))) return 0; + return v4_fabric_device_put(&f, hera, 1, m.word, m.word + V4_MSG_HEADER) == V4_WIRE_DONE; +} +static void forget(void) +{ + printed_len = 0; printed[0] = 0; printed_from = -1; + ended = 0; ended_from = -1; ended_how = -1; ended_at = 0; +} /* Send text to a node and let everything that follows from it happen. */ static const char *tell(v4_cell node, const char *text) { - unsigned steps = 0; - printed_len = 0; printed[0] = 0; printed_from = -1; - ended = 0; ended_from = -1; ended_how = -1; - if (!v4_message_text(&going, node, CONSOLE_ID, V4_MSG_TEXT, text, (unsigned)strlen(text))) return "(too long)"; - going_at = 0; - while (steps < step_cap && v4_fabric_step(&f) != 0) steps++; - if (steps >= step_cap) { - if (getenv("V4_MESH_STUCK")) { - unsigned k; - for (k = 0; k < 3; k++) if (v4_fabric_node_at(&f, k)) { const v4_node *n = &pool[k].n; - printf(" stuck after \"%s\": node %ld p=%ld asking=%d port=%u reading=%d rport=%u mq=%ld owed=%ld lost=%ld fault=%u@%ld\n", text, (long)n->mem[ME], (long)n->p, n->asking, n->ask_port, n->reading, n->read_port, - (long)n->mem[MQ_COUNT], (long)n->mem[OWED_COUNT], (long)n->mem[LOST], n->fault_kind, (long)n->fault_addr); } - } - return "(still running)"; - } + forget(); + if (!put_text(node, text)) return "(not put)"; + if (!run()) { show_stuck(text); return "(still running)"; } return printed; } @@ -112,15 +164,54 @@ static unsigned starforth_node(unsigned k) return (unsigned)id; } -static int waiting(unsigned id) +/* Three nodes that know nothing, in a row, the console on the first. */ +static int fresh(void) { - const v4_node *n = &v4_fabric_node_at(&f, id)->n; - return n->reading && !n->given && n->read_port == V4_PORT_ANY; + unsigned i; + int ok; + v4_fabric_init(&f, places, 3); + v4_fabric_queues(&f, queues, QUEUES); + v4_fabric_gone_error(&f, V4_ERROR_NO_ONE); + hera = starforth_node(0); + mid = starforth_node(1); + far = starforth_node(2); + ok = v4_fabric_wire_device(&f, hera, 1, &console) && v4_fabric_wire(&f, hera, 2, mid, 2) && v4_fabric_wire(&f, mid, 3, far, 2); + stuck = 0; watch = 0; nacks = 0; step_cap = 20000000; + was_quiet[0] = was_quiet[1] = was_quiet[2] = 0; clean[0] = clean[1] = clean[2] = 0; + forget(); + for (i = 0; i < 200; i++) (void)v4_fabric_step(&f); + return ok && asleep(hera) && asleep(mid) && asleep(far); +} +static int told(v4_cell node, const char *text) { return is_empty(tell(node, text)) && ended == 1 && ended_how == V4_TEXT_COMPLETED; } +/* The same, told who they are, the way to each other, and who is on each port: + * console --1 [10] 2--2 [11] 3--2 [12] */ +static int row(void) +{ + return fresh() + && told(0, "10 (ME) ! 1 (CONSOLE) ! 1 1 ROUTE 0 2 ROUTE") + && told(0, "11 (ME) ! 1 (CONSOLE) ! 2 DEFAULT-ROUTE 0 3 ROUTE") + && told(0, "12 (ME) ! 1 (CONSOLE) ! 2 DEFAULT-ROUTE") + && told(10, "0 NO-ROUTE 11 2 ROUTE 12 2 ROUTE 11 2 NEIGHBOUR") && told(11, "0 NO-ROUTE 12 3 ROUTE 10 2 NEIGHBOUR 12 3 NEIGHBOUR") && told(12, "11 2 NEIGHBOUR"); +} +/* And with the far node wired straight to the one at the console as well, port 3 to port 3; node 10 reaches + * 12 by that wire, and 12 reaches everything by it but 11. */ +static int triangle(void) +{ + return row() && v4_fabric_wire(&f, hera, 3, far, 3) + && told(10, "12 NO-ROUTE 12 3 ROUTE 12 3 NEIGHBOUR") && told(12, "NO-ROUTES 3 DEFAULT-ROUTE 11 2 ROUTE 10 3 NEIGHBOUR"); +} +static v4_uheat_t clock_of(unsigned id) { return pool[id].es.anticlock; } + +/* what a scene watches for while it runs */ +static unsigned saw_room_wait, saw_full; +static void watch_far_waits(void) +{ + if (waits_for_room(far)) { saw_room_wait++; if (going_from(far, 3) && going_from(far, 3)->used == V4_WIRE_CELLS - 8u) saw_full++; } } int main(void) { - unsigned hera, mid, far, i; + unsigned i; printf("v4 mesh tests: V4_CELL_BITS=%d, V4_NODE_WORDS=%u\n", V4_CELL_BITS, (unsigned)V4_NODE_WORDS); { @@ -146,15 +237,7 @@ int main(void) nucleus.mem[LINE_STATUS] = 1; HOST_MESSAGES_EMPTY(&nucleus); - v4_fabric_init(&f, places, 3); - hera = starforth_node(0); - mid = starforth_node(1); - far = starforth_node(2); - CHECK(v4_fabric_wire_device(&f, hera, 1, &console), "the console is wired to one node's port 1"); - CHECK(v4_fabric_wire(&f, hera, 2, mid, 2) && v4_fabric_wire(&f, mid, 3, far, 2), "and the three nodes in a row"); - going.count = 0; going_at = 0; - for (i = 0; i < 20; i++) (void)v4_fabric_step(&f); - CHECK(waiting(hera) && waiting(mid) && waiting(far), "all three wait at their ports"); + CHECK(fresh(), "the console is wired to one node's port 1, the three nodes are in a row, and all three sleep"); /* ---- they are told who they are and the way to each other, by text ---- */ CHECK(is_empty(tell(0, "10 (ME) ! 1 (CONSOLE) ! 1 1 ROUTE 0 2 ROUTE")) && ended == 1 && ended_how == V4_TEXT_COMPLETED && ended_from == 10, @@ -176,7 +259,7 @@ int main(void) "each node has its own dictionary: a word defined on one is not on another"); { const char *all = tell(12, "WORDS"); - CHECK(strlen(all) > 1000 && printed_from == 12 && ended == 1, "more than a message holds comes back in several: %u characters", (unsigned)strlen(all)); + CHECK(strlen(all) > 1000 && printed_from == 12 && ended == 1 && ended_at == printed_len, "more than a message holds comes back in several, and then how it ended: %u characters", (unsigned)strlen(all)); } { static char line[1100]; @@ -192,6 +275,9 @@ int main(void) "text one node SENDs another is done there, and what that prints goes to its console: \"%s\" from %ld", printed, (long)printed_from); CHECK(strstr(tell(10, ": NOWAY S\" 1\" 99 SEND ; NOWAY"), "Argument out of range") != NULL && ended_how == V4_TEXT_ERROR, "SEND to a node there is no way to is an error"); + CHECK(strstr(tell(10, ": LONG PAD 1025 12 SEND ; LONG"), "Argument out of range") != NULL && strstr(tell(10, ": SHORT PAD -1 12 SEND ; SHORT"), "Argument out of range") != NULL + && strcmp(tell(10, "DEPTH ."), "0 ") == 0, "and so is more text than a message carries, or less than none; nothing is left on the stack"); + CHECK(strstr(tell(10, ": P5 S\" 1\" 0 5 SEND-ON ; P5"), "No one on that port") != NULL && ended_how == V4_TEXT_ERROR, "SEND-ON by a port with no one on it is an error that says so"); /* ---- no way ---- */ nacks = 0; @@ -203,26 +289,23 @@ int main(void) /* ---- waiting ---- */ { - v4_uheat_t c0 = pool[0].es.anticlock, c1 = pool[1].es.anticlock, c2 = pool[2].es.anticlock; + v4_uheat_t c0 = clock_of(0), c1 = clock_of(1), c2 = clock_of(2); for (i = 0; i < 100; i++) (void)v4_fabric_step(&f); - CHECK(waiting(hera) && waiting(mid) && waiting(far), "with nothing to do, all three wait at their ports"); - CHECK(pool[0].es.anticlock == c0 && pool[1].es.anticlock == c1 && pool[2].es.anticlock == c2, "and execute nothing"); + CHECK(asleep(hera) && asleep(mid) && asleep(far), "with nothing to do, all three sleep"); + CHECK(clock_of(0) == c0 && clock_of(1) == c1 && clock_of(2) == c2, "and execute nothing"); } /* ---- the ways are forgotten and told again: the wiring has changed ---- */ CHECK(v4_fabric_wire(&f, hera, 3, far, 3), "the far node is wired straight to the one at the console as well"); CHECK(is_empty(tell(10, "NO-ROUTES 1 1 ROUTE 11 2 ROUTE 12 3 ROUTE")) && is_empty(tell(12, "NO-ROUTES 3 DEFAULT-ROUTE")), "and both are told"); { - v4_uheat_t before = pool[1].es.anticlock; - CHECK(strcmp(tell(12, "5 5 + ."), "10 ") == 0 && pool[1].es.anticlock == before, "text for it no longer passes through the middle node, which executes nothing"); + v4_uheat_t before = clock_of(1); + CHECK(strcmp(tell(12, "5 5 + ."), "10 ") == 0 && clock_of(1) == before, "text for it no longer passes through the middle node, which executes nothing"); } /* ---- two neighbours with something for each other at the same moment ---- - * A write blocks until the neighbour reads, and a node that is writing - * is not reading: so a node looks before it begins a message, and of - * two neighbours only the one with the lower number may wait to write - * to the other (v4/capsule/core.v4, (GATE); docs/v4.0.0/MESH.md 7a). - * Each node is told who is on each of its ports. */ + * Each puts its message on the wire to the other, whole, and neither waits for the other (MESH.md 7d.2). + * Each node is told who is on each of its ports: it is how it knows its centre. */ CHECK(is_empty(tell(10, "NO-ROUTES 1 1 ROUTE 11 2 ROUTE 12 2 ROUTE")) && is_empty(tell(12, "NO-ROUTES 2 DEFAULT-ROUTE")), "the row of three again"); CHECK(is_empty(tell(10, "11 2 NEIGHBOUR 0 3 NEIGHBOUR")) && is_empty(tell(11, "10 2 NEIGHBOUR 12 3 NEIGHBOUR")) && is_empty(tell(12, "11 2 NEIGHBOUR 0 3 NEIGHBOUR")), "each is told who is on the other end of its ports"); @@ -233,194 +316,318 @@ int main(void) * text and answers the far one: the two messages meet on one wire */ CHECK(strcmp(tell(12, ": HI S\" 72 EMIT\" 11 SEND ; HI"), "H") == 0 && printed_from == 11 && ended == 1 && ended_from == 12, "from the far end to the middle, with the answers crossing: \"%s\" from %ld", printed, (long)printed_from); - CHECK(waiting(hera) && waiting(mid) && waiting(far), "and all three are waiting again"); + CHECK(asleep(hera) && asleep(mid) && asleep(far), "and all three are asleep again"); /* every node sends every other a number of messages, all at once: each * counts what it is sent */ - CHECK(is_empty(tell(10, "VARIABLE GOT : HIT 1 GOT +! ; VARIABLE WHO VARIABLE SENT : BURST WHO ! 0 DO S\" HIT\" WHO @ SEND 1 SENT +! LOOP ;")) && - is_empty(tell(11, "VARIABLE GOT : HIT 1 GOT +! ; VARIABLE WHO VARIABLE SENT : BURST WHO ! 0 DO S\" HIT\" WHO @ SEND 1 SENT +! LOOP ;")) && - is_empty(tell(12, "VARIABLE GOT : HIT 1 GOT +! ; VARIABLE WHO VARIABLE SENT : BURST WHO ! 0 DO S\" HIT\" WHO @ SEND 1 SENT +! LOOP ;")), "each node is given a word to count with and one to send with"); +#define COUNTING "VARIABLE GOT : HIT 1 GOT +! ; VARIABLE WHO VARIABLE SENT VARIABLE TRIED : BURST WHO ! 0 DO 1 TRIED +! S\" HIT\" WHO @ SEND 1 SENT +! LOOP ;" + CHECK(is_empty(tell(10, COUNTING)) && is_empty(tell(11, COUNTING)) && is_empty(tell(12, COUNTING)), "each node is given a word to count with and one to send with"); { 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(waiting(hera) && waiting(mid) && waiting(far), "all three come to rest"); + 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); } - /* ---- more than a node has room to keep ---- - * A node that takes messages in while it waits to write keeps them, and - * when it has no room for one more it lets that one go and counts it. - * Far more are sent here than the nodes can keep. */ + /* ---- THE FLOOD: more than the wires hold (MESH.md 7d.6, acceptance 2) ---- + * 800 messages among three nodes, all at once. A SEND the wire has no room for is refused at once: the + * 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, sent = 0, v; - unsigned k; + long got = 0, lost = 0, refused = 0, sent = 0, tried = 0, v; + 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 !")) && is_empty(tell(11, "0 GOT ! 0 SENT !")) && is_empty(tell(12, "0 GOT ! 0 SENT !")), "the counts begin again"); + 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"); CHECK(strcmp(r, "(still running)") != 0, "two hundred from each to each: it ends"); - CHECK(waiting(hera) && waiting(mid) && waiting(far), "and all three come to rest, none waiting to write"); + 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; + CHECK(left == 0, "with nothing left on any wire: %u cells", left); for (k = 0; k < 3; k++) { - r = tell(who[k], "GOT @ ."); - v = strtol(r, NULL, 10); got += v; - CHECK(v > 0 && v <= 400, "node %ld took %ld of the 400 for it", (long)who[k], v); - r = tell(who[k], "(LOST) @ ."); - lost += strtol(r, NULL, 10); - r = tell(who[k], "SENT @ ."); - sent += strtol(r, NULL, 10); - CHECK(pool[k].n.mem[MQ_COUNT] == 0, "node %ld has none left waiting", (long)who[k]); + 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); + lost += pool[k].n.mem[LOST]; + refused += pool[k].n.mem[REFUSED]; } lost -= 1; /* the one for node 99, earlier */ - /* what is let go is of every kind: the text that would have set a - * node sending, the answers to text, as well as what was sent */ - printf(" of %ld messages the nodes sent each other at once %ld arrived; %ld messages of all kinds were let go for want of room\n", sent, got, lost); - CHECK(lost > 0, "there was not room for all of them"); - CHECK(got <= sent && got + lost >= sent, "every message sent either arrived or was counted: %ld sent, %ld arrived, %ld let go", sent, got, lost); + 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); CHECK(strcmp(tell(12, "7 8 * ."), "56 ") == 0 && printed_from == 12, "and the nodes go on as before"); - CHECK(pool[0].n.mem[OWED_COUNT] == 0 && pool[1].n.mem[OWED_COUNT] == 0 && pool[2].n.mem[OWED_COUNT] == 0 && waiting(hera) && waiting(mid) && waiting(far), - "every refusal a node owed has been sent, and all three are at rest"); - CHECK(pool[0].n.mem[REFUSED] + pool[1].n.mem[REFUSED] + pool[2].n.mem[REFUSED] > 0, "senders were told of what was refused: %ld %ld %ld", - (long)pool[0].n.mem[REFUSED], (long)pool[1].n.mem[REFUSED], (long)pool[2].n.mem[REFUSED]); } - /* ---- refusals and waits (docs/v4.0.0/MESH.md 7b) --------------------------- - * No message is lost without its sender being told, and no node waits - * for ever. */ - - /* no way: the refusal travels back to the node that sent the message */ + /* ---- acceptance 2 and 3: REFUSED AT ONCE, and a stuck node holds up no one ---- */ + CHECK(row(), "a row made afresh"); { - v4_cell refused = pool[2].n.mem[REFUSED], lost = pool[0].n.mem[LOST]; + v4_cell r0; + char text[64]; + CHECK(told(10, ": F 0 DO S\" HIT\" 11 SEND LOOP ;"), "node 10 is given a word that sends node 11 short texts"); + stuck = 1u << mid; + (void)tell(11, ": C BEGIN 0 UNTIL ; C"); + CHECK(ended == 0 && !asleep(mid) && coming_to(mid, 2)->used == 0, "node 11 is stuck in a loop that never ends; it took the text that stuck it"); + r0 = pool[hera].n.mem[REFUSED]; + snprintf(text, sizeof text, "%u F", (unsigned)(V4_WIRE_CELLS / 8u)); + CHECK(is_empty(tell(10, text)) && ended == 1 && ended_how == V4_TEXT_COMPLETED && pool[hera].n.mem[REFUSED] == r0 && going_from(hera, 2)->used == V4_WIRE_CELLS, + "as many as the wire holds are taken, and none is refused: the wire is full, %u cells", going_from(hera, 2)->used); + for (i = 1; i <= 5; i++) { + const char *r = tell(10, "1 F"); + CHECK(strstr(r, "Message refused") != NULL && ended == 1 && ended_how == V4_TEXT_ERROR && pool[hera].n.mem[REFUSED] == r0 + (v4_cell)i, + "one more is refused at once, and counted: the line ends, having waited for nothing: \"%s\", %ld", r, (long)(pool[hera].n.mem[REFUSED] - r0)); + CHECK(strcmp(tell(10, "1 2 + ."), "3 ") == 0, "node 10 answers"); + } + CHECK(asleep(hera) && asleep(far) && pool[hera].n.mem[LOST] == 0, "nothing waits: nodes 10 and 12 are asleep, and nothing was let go"); + + /* text for node 12, beyond the stuck one */ + { + static char line[1100]; + v4_cell lost = pool[hera].n.mem[LOST]; + memset(line, ' ', 1024); memcpy(line, "1 DROP", 6); line[1024] = 0; + nacks = 0; + (void)tell(12, line); + CHECK(nacks == 1 && nack_from == 10 && nack_about == 12 && pool[hera].n.mem[LOST] == lost + 1 && ended == 0, + "text for node 12, beyond the stuck node, is refused at node 10, whose wire to 11 is full, and the console is sent the NACK: %d from %ld about %ld", nacks, (long)nack_from, (long)nack_about); + CHECK(strcmp(tell(10, "1 2 + ."), "3 ") == 0 && asleep(hera), "node 10 answers, and sleeps"); + } + } + + /* ---- a refusal owed to a stuck node ---- */ + CHECK(triangle(), "a row made afresh, with nodes 10 and 12 wired straight to each other"); + { + v4_cell lost = pool[far].n.mem[LOST]; + v4_uheat_t c; + CHECK(told(12, "NO-ROUTES 1 3 ROUTE 10 3 ROUTE 11 2 ROUTE") && told(11, "55 3 ROUTE"), "node 12 has no way for 55; node 11 is told the way to 55 is by node 12"); + stuck = 1u << mid; + (void)tell(11, ": C1 S\" 1 DROP\" 55 SEND BEGIN 0 UNTIL ; C1"); + CHECK(ended == 0 && pool[far].n.mem[LOST] == lost + 1, "node 11 sends 55 a message by node 12 and is then stuck in a loop: node 12 has let the message go"); + CHECK(going_from(far, 2)->used == 8 && coming_to(far, 2)->used == 0, "its NACK is on the wire to node 11, which does not take it: %u cells", going_from(far, 2)->used); + 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"); + 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); + } + + /* ---- acceptance 5: PRINTING IS NOT LOST ---- */ + 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); + 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(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, by which they go to the console, is busy with one line after another"); + watch = 0; + CHECK(saw_room_wait > 0, "the wire to node 10 filled, and node 12 slept for room on it"); + CHECK(strcmp(printed, want) == 0, "every number arrived, in order: %u characters of %u", printed_len, (unsigned)strlen(want)); + CHECK(ended == 5 && pool[far].n.mem[LOST] == 0 && pool[hera].n.mem[LOST] == 0 && pool[far].n.mem[LINE_STATUS] == 1, "and then how the text ended; nothing was let go"); + CHECK(strcmp(tell(12, "7 8 * ."), "56 ") == 0 && asleep(hera) && asleep(mid) && asleep(far), "and all three go on"); + } + + /* ---- printing held by a stuck node (MESH.md 7d.6) ---- */ + CHECK(triangle(), "the same made afresh"); + { + v4_cell lost, refused; + v4_uheat_t c; + CHECK(told(12, ": P 3000 0 DO I . LOOP ;") && told(12, "NO-ROUTES 2 DEFAULT-ROUTE 10 3 ROUTE") && told(10, "77 3 ROUTE : X S\" 1 DROP\" 77 SEND ;"), + "node 12's way to the console is by node 11; node 10 is told the way to 77 is by node 12"); + stuck = 1u << mid; + (void)tell(11, ": C BEGIN 0 UNTIL ; C"); + (void)tell(12, "P"); + CHECK(ended == 0 && waits_for_room(far) && going_from(far, 2)->used > V4_WIRE_CELLS - 8u - 71u && going_from(far, 2)->used <= V4_WIRE_CELLS - 8u, + "node 12 prints until the wire to the stuck node is full but for the room kept for its answer, and sleeps for room: %u cells", going_from(far, 2)->used); + c = clock_of(far); + for (i = 0; i < 500; i++) (void)v4_fabric_step(&f); + CHECK(waits_for_room(far) && clock_of(far) == c, "executing nothing"); + lost = pool[far].n.mem[LOST]; refused = pool[hera].n.mem[REFUSED]; + /* its way for 77 is the full wire: while one more fits beside the room kept it is passed on, and + * after that it is refused and node 10 told. It is never kept. */ + for (i = 0; i < 12 && pool[far].n.mem[LOST] == lost; i++) { + unsigned before = going_from(far, 2)->used; + CHECK(is_empty(tell(10, "X")) && ended == 1 && ended_from == 10, "node 10 sends a message for node 77 by node 12"); + CHECK(coming_to(far, 3)->used == 0 && waits_for_room(far), "node 12, woken, does not keep it, and sleeps for room again"); + CHECK((going_from(far, 2)->used == before + 9u && pool[far].n.mem[LOST] == lost) || (going_from(far, 2)->used == before && pool[far].n.mem[LOST] == lost + 1), + "it is passed on, or it is refused: %u cells then, %u now", before, going_from(far, 2)->used); + } + CHECK(pool[far].n.mem[LOST] == lost + 1 && pool[hera].n.mem[REFUSED] == refused + 1 && going_from(far, 2)->used <= V4_WIRE_CELLS - 8u, + "when one more would take the room kept for the answer it is refused, and node 10 told: the 8 cells are still there (%u used)", going_from(far, 2)->used); + CHECK(strcmp(tell(10, "1 2 + ."), "3 ") == 0, "node 10 answers meanwhile"); + lost = pool[far].n.mem[LOST]; + CHECK(v4_fabric_remove(&f, mid) == &pool[1], "node 11 is removed"); + stuck = 0; + CHECK(run() && asleep(far) && pool[far].n.mem[NODE_ERROR] == 18 && pool[far].n.mem[LINE_STATUS] == 2 && pool[far].n.mem[OUT_PTR] == OUT_W, + "node 12's text ends in error 18, No one on that port, and it sleeps: error %ld, ended %ld", (long)pool[far].n.mem[NODE_ERROR], (long)pool[far].n.mem[LINE_STATUS]); + CHECK(pool[far].n.mem[LOST] == lost + 2, "the error's message and the word of how the text ended had no one to go to, and are counted: %ld", (long)(pool[far].n.mem[LOST] - lost)); + (void)tell(12, "NO-ROUTES 3 DEFAULT-ROUTE"); + 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) ---- */ + 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"); + stuck = 1u << mid; + (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", + (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"); + } + + /* ---- 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 + * that prints. Node 12 begins it -- there is room for its answer -- and its printing waits for room, since + * it may not take the 8 cells kept; when node 10 takes what is on the wire, the printing goes and then + * the answer, which node 10 is waiting for. */ + CHECK(triangle(), "the same made afresh"); + { + CHECK(told(11, "1 3 ROUTE : F1 0 DO S\" HIT\" 1 SEND LOOP ;") && told(10, ": WAIT 60000 0 DO LOOP ;") + && told(10, ": G S\" 127 F1\" 11 SEND WAIT S\" 65 EMIT\" 12 SEND WAIT WAIT 12 AWAIT . ;"), "nodes 11 and 10 are given their words"); + saw_room_wait = 0; saw_full = 0; watch = watch_far_waits; + (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, + "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"); + } + + /* ---- Review Focus 2 and 3: LEFT FOR ITSELF ---- + * Node 10 waits for node 12's answer. On the one wire, from node 11, there then come: text for node 10 + * itself; a message for the console; and node 12's answer. */ + CHECK(row(), "a row made afresh"); + { + v4_uheat_t c; + CHECK(told(12, ": LL 400000 0 DO LOOP ;") && told(11, ": S1 S\" 66 EMIT\" 10 SEND ;"), "node 12 has a word that takes a long while, node 11 one that sends node 10 text"); + forget(); + stuck = 1u << far; /* it is busy, not stuck: it is not waited for */ + CHECK(put_text(10, ": Q S\" LL\" 12 SEND 12 AWAIT . ; Q") && run() && pool[hera].n.mem[AWAIT_FROM] == 12 && asleep(hera) && ended == 0, + "node 10 sends node 12 text that takes a long while, and waits for its answer, asleep"); + CHECK(is_empty(tell(11, "S1")) && ended == 1 && ended_from == 11, "node 11 sends node 10 text, and then word of how its own text ended, for the console: that is passed on by node 10"); + CHECK(coming_to(hera, 2)->used == 9 && pool[hera].n.mem[AWAIT_FROM] == 12 && asleep(hera), "the text for node 10 itself is left on the wire, in front, and node 10 waits on, asleep: %u cells", coming_to(hera, 2)->used); + c = clock_of(hera); + for (i = 0; i < 2000; i++) (void)v4_fabric_step(&f); + CHECK(clock_of(hera) == c && asleep(hera), "with a message for itself on a wire it executes nothing until another comes: it does not go round"); + stuck = 0; + forget(); + CHECK(run() && strcmp(printed, "1 B") == 0 && ended == 1 && ended_at == 2 && pool[hera].n.mem[AWAIT_FROM] == 0, + "node 12's answer comes behind it and ends the wait; and then the text that was left is done: \"%s\"", printed); + CHECK(asleep(hera) && asleep(mid) && asleep(far) && coming_to(hera, 2)->used == 0, "all three sleep, with nothing left on the wire"); + } + + /* ---- acceptance 8: ORDER ---- */ + CHECK(triangle(), "a row made afresh, with nodes 10 and 12 wired straight to each other"); + CHECK(told(11, "12 2 ROUTE") && told(12, ": SLOW 30000 0 DO LOOP ;"), "node 11 is told the way to node 12 is by node 10; node 12 is given a word that takes a while"); + CHECK(strcmp(tell(11, ": TRIO S\" SLOW 68 EMIT\" 12 SEND S\" 69 EMIT\" 12 SEND S\" 70 EMIT\" 12 SEND ; TRIO"), "DEF") == 0, + "it sends node 12 three texts, the first slow to do: the other two wait on the wire, and they are done in the order they were sent: \"%s\"", printed); + CHECK(strcmp(tell(12, ": TWO 65 EMIT 66 EMIT NOSUCHWORD ; 67 EMIT"), "UNKNOWN WORD: 'NOSUCHWORD'\n") == 0 && ended == 1 && ended_at == printed_len, + "what a text prints, and then how it ended, reach the console in that order"); + + /* ---- AWAIT's endings (MESH.md 7b) ---- */ + CHECK(row(), "a row made afresh"); + /* refused: a NACK about the node waited for */ + { + v4_cell refused = pool[far].n.mem[REFUSED], lost = pool[hera].n.mem[LOST]; CHECK(is_empty(tell(12, ": N77 S\" 1 DROP\" 77 SEND ; N77")) && ended_how == V4_TEXT_COMPLETED, "node 12 sends text to node 77: it has a way for everything, toward 11"); - CHECK(pool[0].n.mem[LOST] == lost + 1, "node 10, two away, has no way to 77 and counts the message"); - CHECK(pool[2].n.mem[REFUSED] == refused + 1, "and node 12 is told: its count of refusals is one more"); + CHECK(pool[hera].n.mem[LOST] == lost + 1 && pool[far].n.mem[REFUSED] == refused + 1, "node 10, two away, has no way to 77 and counts the message; node 12 is told, and counts the refusal"); + CHECK(strstr(tell(12, ": W77 S\" 1 DROP\" 77 SEND 77 AWAIT . ; W77"), "Message refused") != NULL && ended == 1 && ended_how == V4_TEXT_ERROR && ended_from == 12, + "a node that sends to 77 and waits for its answer: the wait ends, with an error that says the message was refused: \"%s\"", printed); + CHECK(strcmp(tell(12, "7 8 * ."), "56 ") == 0 && pool[far].n.mem[AWAIT_FROM] == 0 && pool[far].n.mem[REFUSED] == refused + 1, "and it goes on to its next line, waiting for no one"); + /* the refusal came before the wait began: it is on the wire, and is found there */ + CHECK(strstr(tell(12, ": QF S\" 1 DROP\" 77 SEND 3000 0 DO LOOP 77 AWAIT . ; QF"), "Message refused") != NULL && ended_how == V4_TEXT_ERROR && pool[far].n.mem[AWAIT_FROM] == 0 && asleep(far), + "node 12 sends to 77, is busy a while, and then waits: the refusal is on its wire already, and the wait ends at once: \"%s\"", printed); + /* a NACK about another node does not end it, and is counted */ + refused = pool[far].n.mem[REFUSED]; + (void)tell(12, ": QO S\" 1 DROP\" 77 SEND 11 AWAIT . ; QO"); + CHECK(ended == 0 && pool[far].n.mem[AWAIT_FROM] == 11 && pool[far].n.mem[REFUSED] == refused + 1 && asleep(far), "waiting on node 11, it is told its message for 77 was refused: that is counted, and it waits on"); } - /* a sender that is waiting for the answer stops waiting */ - CHECK(strstr(tell(12, ": W77 S\" 1 DROP\" 77 SEND 77 AWAIT . ; W77"), "Message refused") != NULL && ended == 1 && ended_how == V4_TEXT_ERROR && ended_from == 12, - "a node that sends to 77 and waits for its answer: the wait ends, with an error that says the message was refused: \"%s\"", printed); - CHECK(strcmp(tell(12, "7 8 * ."), "56 ") == 0 && pool[2].n.mem[AWAIT_FROM] == 0, "and it goes on to its next line, waiting for no one"); - - /* no room: a node that is waiting keeps what it is sent, and has room for only so much */ + /* gone: a GONE from its centre, and not from another */ { - v4_cell lost = pool[1].n.mem[LOST], refused = pool[0].n.mem[REFUSED], got, not_taken; - CHECK(is_empty(tell(11, "0 GOT !")) && is_empty(tell(11, "12 AWAIT .")) && ended == 0 && pool[1].n.mem[AWAIT_FROM] == 12, "node 11 waits for an answer from 12, which is not going to send one"); - CHECK(is_empty(tell(10, ": F 49 0 DO S\" HIT\" 11 SEND LOOP ; F")) && ended == 1 && ended_from == 10, "node 10 sends it 49 messages meanwhile"); - CHECK(pool[1].n.mem[MQ_COUNT] > 300 && pool[1].n.mem[MQ_COUNT] <= MQ_CELLS - 36, "node 11 has kept what it has room for, and left the room that is kept for refusals: %ld cells", (long)pool[1].n.mem[MQ_COUNT]); - CHECK(pool[1].n.mem[OWED_COUNT] == 8, "it owes a refusal for each of the rest, as many as it can owe: %ld", (long)pool[1].n.mem[OWED_COUNT]); - CHECK(pool[0].n.mem[REFUSED] == refused, "and has sent none yet: it is still waiting"); - - /* one from its centre ends the wait */ - CHECK(is_empty(tell(10, "12 NO-ROUTE")), "node 10 forgets the way to 12"); - CHECK(strstr(tell(10, "12 11 GONE"), "Node gone") != NULL && printed_from == 11 && ended_from != -1, "node 10, its centre, tells it 12 is gone: its wait ends with an error that says so: \"%s\"", printed); - CHECK(pool[1].n.mem[AWAIT_FROM] == 0 && waiting(mid), "it is waiting for no one, and is back at its ports"); - got = strtol(tell(11, "GOT @ ."), NULL, 10); - not_taken = 49 - got; - CHECK(got >= 30 && not_taken > 8, "it then did every message it had kept: %ld of the 49", (long)got); - CHECK(pool[0].n.mem[REFUSED] == refused + 8, "and sent the eight refusals it owed: node 10 was told of %ld", (long)(pool[0].n.mem[REFUSED] - refused)); - CHECK(pool[1].n.mem[LOST] == lost + not_taken + (not_taken - 8), "it counted each message it did not take, and each refusal it could not owe: %ld for %ld not taken", - (long)(pool[1].n.mem[LOST] - lost), (long)not_taken); - /* the way to 12 is forgotten: what is sent there now is refused */ - refused = pool[1].n.mem[REFUSED]; - CHECK(is_empty(tell(11, ": P12 S\" 1 DROP\" 12 SEND ; P12")) && pool[1].n.mem[REFUSED] == refused + 1, "having forgotten the way to 12, what node 11 sends there goes toward its centre, which has no way either, and is refused"); - CHECK(is_empty(tell(10, "12 2 ROUTE")) && is_empty(tell(11, "12 3 ROUTE")) && strcmp(tell(12, "7 8 * ."), "56 ") == 0, "told the way again, they reach 12 as before"); + v4_cell ways = pool[far].n.mem[ROUTE_COUNT]; + CHECK(told(11, "44 3 ROUTE"), "node 11 is given a way"); + ways = pool[mid].n.mem[ROUTE_COUNT]; + CHECK(is_empty(tell(11, "44 AWAIT .")) && ended == 0 && pool[mid].n.mem[AWAIT_FROM] == 44, "node 11 waits for an answer from a node 44"); + CHECK(is_empty(tell(10, ": G12 44 11 GONE ;")) && told(10, "12 2 ROUTE"), "(node 10 is given a word)"); + /* node 12 is still waiting on 11: a line from the console ends that */ + CHECK(strstr(tell(12, "1 DROP"), "Interrupted") != NULL, "(node 12's wait is ended from the console)"); + CHECK(told(12, "44 11 GONE") && pool[mid].n.mem[AWAIT_FROM] == 44 && pool[mid].n.mem[ROUTE_COUNT] == ways, + "node 12 tells it that 44 is gone: node 12 is not its centre, and it goes on waiting, its ways as they were"); + CHECK(strstr(tell(10, "G12"), "Node gone") != NULL && printed_from == 11 && pool[mid].n.mem[AWAIT_FROM] == 0 && pool[mid].n.mem[ROUTE_COUNT] == ways - 1, + "node 10, its centre, tells it: the wait ends with an error that says so, and the way is forgotten: \"%s\"", printed); + CHECK(asleep(mid) && coming_to(mid, 3)->used == 0 && coming_to(mid, 2)->used == 0, "it sleeps; the GONE it did not believe was not kept"); + (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 line from the console breaks a wait (MESH.md 7b.6) */ - CHECK(is_empty(tell(10, "12 AWAIT .")) && ended == 0 && pool[0].n.mem[AWAIT_FROM] == 12 && waiting(hera), "node 10 waits for an answer from 12, which is not going to send one"); - CHECK(is_empty(tell(11, "20 22 + .")) && ended == 0 && pool[0].n.mem[AWAIT_FROM] == 12, "text from the console for node 11 comes to node 10 on its way: node 10 keeps it, and goes on waiting"); + /* interrupted: text from the console */ + CHECK(row(), "a row made afresh"); { - const char *r = tell(10, "65 EMIT"); - CHECK(strstr(r, "Interrupted\n") != NULL && pool[0].n.mem[AWAIT_FROM] == 0, "text from the console for node 10 itself ends its wait, with an error that says so: \"%s\"", r); - CHECK(strstr(r, "A") != NULL && strstr(r, "42 ") != NULL && ended == 3, "the line is then done as usual, and so is what was kept for node 11: \"%s\", %d ended", r, ended); - CHECK(strstr(r, "Interrupted\n") < strstr(r, "A"), "the wait ends before the line that ended it is done"); - } - CHECK(strcmp(tell(10, "1 2 + ."), "3 ") == 0 && waiting(hera) && waiting(mid) && waiting(far), "and all three go on as before"); - - CHECK(strstr(tell(10, "5 NO-ROUTE"), "ERROR") == NULL && strcmp(tell(11, "20 22 + ."), "42 ") == 0, "forgetting the way to a node there was no way to changes nothing"); - - - /* ---- what the review of step 6c found (MESH.md step 6c) ------------------- */ - step_cap = 3000000; - v4_cell ways11 = 0; - - /* a GONE is believed only from the node's centre: not by another port, and not from another node by the centre's port */ - CHECK(is_empty(tell(10, "12 NO-ROUTE 12 3 ROUTE 12 3 NEIGHBOUR")) && is_empty(tell(12, "NO-ROUTES 3 DEFAULT-ROUTE 11 2 ROUTE 10 3 NEIGHBOUR")), - "node 12 is reached straight from node 10 now, and has its own wire to 11"); - CHECK(is_empty(tell(11, "12 AWAIT .")) && ended == 0 && pool[1].n.mem[AWAIT_FROM] == 12, "node 11 waits for node 12's answer"); - ways11 = pool[1].n.mem[ROUTE_COUNT]; - CHECK(is_empty(tell(12, "12 11 GONE")) && ended == 1 && ended_from == 12 && pool[1].n.mem[AWAIT_FROM] == 12 && pool[1].n.mem[ROUTE_COUNT] == ways11, - "node 12 tells it, by the wire between them, that 12 is gone: that is not by its centre's port, and it goes on waiting"); - CHECK(is_empty(tell(12, "11 NO-ROUTE 12 11 GONE")) && ended == 1 && ended_from == 12 && pool[1].n.mem[AWAIT_FROM] == 12 && pool[1].n.mem[ROUTE_COUNT] == ways11, - "node 12 says it again by way of node 10: it comes by the centre's port but is not from the centre, and node 11 goes on waiting, its ways as they were"); - CHECK(strstr(tell(10, "12 11 GONE"), "Node gone") != NULL && pool[1].n.mem[AWAIT_FROM] == 0, "node 10, its centre, says it: the wait ends: \"%s\"", printed); - CHECK(is_empty(tell(11, "12 3 ROUTE")) && is_empty(tell(12, "11 2 ROUTE")), "the ways are told again"); - - /* an answer that came before the wait began is in the messages waiting, and is found there */ - CHECK(strstr(tell(12, ": QF S\" 1 DROP\" 77 SEND 3000 0 DO LOOP S\" 1 DROP\" 10 SEND 77 AWAIT . ; QF"), "Message refused") != NULL && ended_how == V4_TEXT_ERROR - && pool[2].n.mem[AWAIT_FROM] == 0 && waiting(far), - "node 12 sends to 77, is busy a while, sends another message -- taking in the refusal as it does -- and then waits for 77's answer: the wait ends at once: \"%s\"", printed); - CHECK(strcmp(tell(12, "7 8 * ."), "56 ") == 0, "and it goes on"); - - /* a waiting sender whose message found no room */ - { - CHECK(is_empty(tell(11, "0 GOT !")) && is_empty(tell(11, "12 AWAIT .")) && pool[1].n.mem[AWAIT_FROM] == 12, "node 11 waits on 12 again"); - CHECK(is_empty(tell(10, ": F40 40 0 DO S\" HIT\" 11 SEND LOOP ; F40")) && pool[1].n.mem[OWED_COUNT] == 0, "node 10 sends it as many messages as it has room for"); - (void)tell(10, ": ONE S\" HIT\" 11 SEND 12 NO-ROUTE 12 11 GONE 11 AWAIT . ; ONE"); - CHECK(strstr(printed, "Message refused") != NULL && pool[0].n.mem[AWAIT_FROM] == 0, - "it sends one more, for which there is no room, ends node 11's wait, and waits for node 11's answer: its own wait ends, the message refused: \"%s\"", printed); - CHECK(strtol(tell(11, "GOT @ ."), NULL, 10) == 40 && is_empty(tell(10, "12 3 ROUTE")) && is_empty(tell(11, "12 3 ROUTE")), "node 11 did the forty it had kept; the ways are told again"); + const char *r; + CHECK(is_empty(tell(10, "12 AWAIT .")) && ended == 0 && pool[hera].n.mem[AWAIT_FROM] == 12 && asleep(hera), "node 10 waits for an answer from 12, which is not going to send one"); + CHECK(strcmp(tell(11, "20 22 + ."), "42 ") == 0 && ended == 1 && pool[hera].n.mem[AWAIT_FROM] == 12 && asleep(hera), + "text from the console for node 11 comes to node 10 on its way: node 10 passes it on, and what comes back, and goes on waiting"); + r = tell(10, "65 EMIT"); + CHECK(strcmp(r, "Interrupted\nA") == 0 && ended == 2 && pool[hera].n.mem[AWAIT_FROM] == 0, "text from the console for node 10 itself ends its wait, with an error that says so, and is then done: \"%s\"", r); + CHECK(strcmp(tell(10, "1 2 + ."), "3 ") == 0 && asleep(hera) && asleep(mid) && asleep(far), "and all three go on as before"); + CHECK(strstr(tell(10, "5 NO-ROUTE"), "ERROR") == NULL && strcmp(tell(11, "20 22 + ."), "42 ") == 0, "forgetting the way to a node there was no way to changes nothing"); } - /* an answer that is refused on its way back is told to the node that waits for it, not to the node that answered */ + /* ---- an answer refused on its way back; a message that would go round for ever ---- */ + CHECK(triangle(), "a row made afresh, with nodes 10 and 12 wired straight to each other"); { - v4_cell r12 = pool[2].n.mem[REFUSED]; - CHECK(is_empty(tell(12, "10 2 ROUTE")), "node 12 is told the way to node 10 is by node 11"); - CHECK(is_empty(tell(11, "0 GOT !")) && is_empty(tell(11, "55 AWAIT .")) && pool[1].n.mem[AWAIT_FROM] == 55, "node 11 waits on a node there is not"); - CHECK(is_empty(tell(10, "F40")) && pool[1].n.mem[OWED_COUNT] == 0, "node 10 sends it as many messages as it has room for"); - (void)tell(10, ": RA S\" 1 DROP\" 12 SEND 3000 0 DO LOOP 55 11 GONE 12 AWAIT . ; RA"); - CHECK(strstr(printed, "Message refused") != NULL && pool[0].n.mem[AWAIT_FROM] == 0, - "node 10 sends node 12 text; its answer comes back by node 11, which has no room for it; node 10 then ends node 11's wait and waits for the answer: it is told the message was refused: \"%s\"", printed); - CHECK(pool[2].n.mem[REFUSED] == r12, "node 12, which answered, is told nothing"); - CHECK(strtol(tell(11, "GOT @ ."), NULL, 10) == 40 && is_empty(tell(12, "10 3 ROUTE")) && strcmp(tell(12, "7 8 * ."), "56 ") == 0, "node 11 did the forty it had kept; the way is told again"); - } - - /* a message that would go round for ever is refused after 16 nodes have passed it on */ - { - v4_cell refused = pool[0].n.mem[REFUSED], l10 = pool[0].n.mem[LOST], l11 = pool[1].n.mem[LOST]; - CHECK(is_empty(tell(10, "66 2 ROUTE")), "node 10 is told the way to 66 is by node 11, whose way for everything it has not been told of is back by node 10"); + v4_cell refused, l10, l11, r12; + /* a message that would go round for ever is refused after 16 nodes have passed it on */ + refused = pool[hera].n.mem[REFUSED]; l10 = pool[hera].n.mem[LOST]; l11 = pool[mid].n.mem[LOST]; + CHECK(told(10, "66 2 ROUTE"), "node 10 is told the way to 66 is by node 11, whose way for everything it has not been told of is back by node 10"); CHECK(is_empty(tell(10, ": H66 S\" 1 DROP\" 66 SEND ; H66")) && ended == 1 && ended_how == V4_TEXT_COMPLETED, "node 10 sends 66 a message: it comes to rest"); - CHECK(pool[0].n.mem[LOST] + pool[1].n.mem[LOST] == l10 + l11 + 1 && pool[0].n.mem[REFUSED] == refused + 1, - "one of the two let it go when it had been passed on 16 times, and node 10 was told it was refused"); - CHECK(is_empty(tell(10, "66 NO-ROUTE")) && waiting(hera) && waiting(mid) && waiting(far), "the way is forgotten, and all three are at rest"); + CHECK(pool[hera].n.mem[LOST] + pool[mid].n.mem[LOST] == l10 + l11 + 1 && pool[hera].n.mem[REFUSED] == refused + 1, + "one of the two let it go when it had been passed on as often as a message may be, and node 10 was told it was refused"); + nacks = 0; + CHECK(is_empty(tell(66, "1 DROP")) && nacks == 1 && nack_about == 66, "the same from the console, which sends it with no count: it too is let go, and the console told"); + CHECK(strstr(tell(10, ": W66 S\" 1 DROP\" 66 SEND 66 AWAIT . ; W66"), "Message refused") != NULL && ended_how == V4_TEXT_ERROR && pool[hera].n.mem[AWAIT_FROM] == 0, + "and waiting for 66's answer, node 10 is the one that lets its own message go when it comes back the last time: its wait ends, the message refused: \"%s\"", printed); + CHECK(told(10, "66 NO-ROUTE") && asleep(hera) && asleep(mid) && asleep(far), "the way is forgotten, and all three are at rest"); + + /* An answer that cannot be passed on: the NACK is for the node the answer was for, not the node that + * answered. Here it cannot go either -- it has the answer's own way to go -- and whoever waits is + * not told: MESH.md 7d.6. That wait is ended from the console. */ + r12 = pool[far].n.mem[REFUSED]; l11 = pool[mid].n.mem[LOST]; + CHECK(told(12, "10 2 ROUTE") && told(11, "10 4 ROUTE"), "node 12 is told the way to node 10 is by node 11, which is told it is by a port with no one on it"); + (void)tell(10, ": RA S\" 1 DROP\" 12 SEND 12 AWAIT . ; RA"); + CHECK(ended == 0 && pool[hera].n.mem[AWAIT_FROM] == 12 && pool[mid].n.mem[LOST] == l11 + 2, + "node 10 sends node 12 text and waits; the answer comes back by node 11, which cannot pass it on, nor the NACK for it: both are counted: %ld", (long)(pool[mid].n.mem[LOST] - l11)); + CHECK(pool[far].n.mem[REFUSED] == r12 && asleep(far), "node 12, which answered, is told nothing: the NACK was not for it"); + CHECK(strcmp(tell(10, "1 2 + ."), "Interrupted\n3 ") == 0 && asleep(hera), "node 10's wait is ended from the console: \"%s\"", printed); } - /* a node that owes a refusal to a node that is stuck is not stuck itself */ - CHECK(is_empty(tell(12, "NO-ROUTES 1 3 ROUTE 10 3 ROUTE 11 2 ROUTE")) && is_empty(tell(11, "55 3 ROUTE")), "node 12 has no way for 55; node 11 is told the way to 55 is by node 12"); - CHECK(strcmp(tell(11, ": C1 S\" 1 DROP\" 55 SEND BEGIN 0 UNTIL ; C1"), "(still running)") == 0, "node 11 sends 55 a message and is then stuck in a loop"); - CHECK(pool[2].n.mem[OWED_COUNT] == 1, "node 12 owes node 11 a refusal, and cannot give it: node 11 is not reading"); - (void)tell(12, "7 8 * ."); - CHECK(strcmp(printed, "56 ") == 0 && printed_from == 12, "node 12 goes on doing what it is sent: \"%s\"", printed); - - /* a node that is waiting to write to a node finds out when that node is removed */ - (void)tell(12, ": TO11 S\" 1 DROP\" 11 SEND 65 EMIT ; TO11"); - CHECK(ended == 0, "node 12 sends node 11 a message: node 11 is not reading, and node 12 waits to write to it"); - CHECK(v4_fabric_remove(&f, mid) == &pool[1], "node 11 is removed"); - (void)tell(10, "1 DROP"); - CHECK(strstr(printed, "No one on that port") != NULL && strstr(printed, "A") == NULL, "node 12's line ends in an error that says no one is there: \"%s\"", printed); - step_cap = 20000000; - CHECK(strcmp(tell(12, "7 8 * ."), "56 ") == 0 && pool[2].n.mem[OWED_COUNT] == 0 && waiting(far) && waiting(hera), - "it goes on, the refusal it owed node 11 let go: it owes nothing and is at rest"); - - /* THE LIMIT (MESH.md 7b.7): a refusal owed to a neighbour whose number is the higher is written - * without looking, as any message to it is, so a node that is stuck holds up a neighbour that owes - * it one -- until it is removed */ - step_cap = 3000000; - CHECK(is_empty(tell(12, "55 3 ROUTE")), "node 12 is told the way to 55 is by node 10, which has none"); - CHECK(strcmp(tell(12, ": C2 S\" 1 DROP\" 55 SEND BEGIN 0 UNTIL ; C2"), "(still running)") == 0 && pool[0].n.asking && pool[0].n.mem[OWED_COUNT] == 1, - "node 12 sends 55 a message and is then stuck in a loop: node 10 owes it a refusal, writes it, and is blocked"); - v4_fabric_gone_error(&f, V4_ERROR_NO_ONE); - CHECK(v4_fabric_remove(&f, far) == &pool[2], "node 12 is removed"); - step_cap = 20000000; - CHECK(strcmp(tell(10, "1 2 + ."), "3 ") == 0 && pool[0].n.mem[OWED_COUNT] == 0 && waiting(hera), "node 10 is let go, lets the refusal go, and goes on: \"%s\"", printed); + /* ---- the stacks, between texts (MESH.md 7c.6, 7d.4) ---- + * After a text that left nothing on the data stack, what a node's stacks hold while it does nothing that + * is a text's is what the nucleus uses: passing on, refusing, taking in, and sending what a text printed and + * 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. */ + 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"); printf(" %d checks, %d failures\n", checks, failures); return failures != 0; diff --git a/v4/tests/test_host_quit.c b/v4/tests/test_host_quit.c index 600c25ae..21b94780 100644 --- a/v4/tests/test_host_quit.c +++ b/v4/tests/test_host_quit.c @@ -42,6 +42,7 @@ */ #include "v4/text.h" #include "v4/message.h" +#include "v4/wire.h" #include "v4/testcode.h" #include #include @@ -63,7 +64,6 @@ static v4_heat h; static v4_text tx; static v4_cell w_key, w_key_end, w_fault, capsule_latest; static v4_cell w_idle; /* (IDLE): where the node waits for a message */ -static v4_cell w_idle_end; /* (PASS-ON): the word after it */ static char out[V4_CONSOLE_CAP + 1]; /* STORAGE (MESH.md section 8). The node asks its kernel for its blocks, and @@ -190,17 +190,81 @@ static const char *node_name(v4_cell xt) static char typed[8192]; /* typed and not yet taken */ static unsigned typed_len; static int line_open; /* a line has been sent and has not ended */ -static v4_message to_node, from_node; /* the message going in, and the one coming out */ -static unsigned to_sent; /* how many words of to_node the node has taken */ +static v4_message to_node; /* the message going in */ static char shown[V4_CONSOLE_CAP]; /* what the node has printed since the console last looked */ static unsigned shown_len; static int shown_dropped; static int text_ended; /* how the text ended, when it has */ -/* 1 if the node is where it waits for a message, with nothing given it. */ +/* THE WIRES (MESH.md 7d.5). The node is alone and has no fabric: these + * tests keep the queues of its console's wire, do what it asks of them, and + * are what is on the other end, as v4/system/boot.c is for the products. + * Every other port has no one on it. */ +static v4_wire_queue wire_in, wire_out; /* to the node from its console, and from it */ + +static int span_ok(v4_cell addr, unsigned count) +{ + if (count == 0) return 1; + if (addr < 0 || (v4_ucell)addr > (v4_ucell)(V4_NODE_WORDS - count)) return 0; + return !(addr <= PORT + (v4_cell)V4_PORT_HAVE && addr + (v4_cell)count > PORT); +} +/* Do the operation the node is blocked at. 0: it sleeps on. */ +static int wire_do(void) +{ + v4_wire_queue *rx = n.wire == CONSOLE_PORT ? &wire_in : 0, *tx = n.wire == CONSOLE_PORT ? &wire_out : 0; + v4_cell a = n.wire_a, b = n.wire_b, hd[V4_WIRE_HEADER], how = V4_WIRE_NO_ONE; + unsigned cells; + + switch (n.do_op) { + 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; } + 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 5: + if (!tx) break; + cells = span_ok(a, V4_WIRE_HEADER) ? v4_wire_cells(n.mem[a + 6]) : 0; + how = cells && span_ok(b, cells - V4_WIRE_HEADER) ? v4_wire_put(tx, &n.mem[a], cells > V4_WIRE_HEADER ? &n.mem[b] : hd) : V4_WIRE_NOT_A_MESSAGE; + break; + case 6: if (rx) { v4_wire_first(rx); how = V4_WIRE_DONE; } break; + case 7: if (rx) how = v4_wire_next(rx); break; + case 8: if (!wire_in.arrived) return 0; how = V4_WIRE_DONE; break; + case 9: + 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; + 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; + default: how = V4_WIRE_NOT_A_MESSAGE; break; + } + v4_node_done(&n, how); + return 1; +} +/* One instruction word, and then what the node asked of its wires; as the + * fabric orders a step (fabric.c). */ +static void node_step(void) +{ + v4_node_have(&n, wire_in.used > 0 ? 1u << CONSOLE_PORT : 0u); + (void)v4_exec_step_word(&n, &es, &h); + if (n.have_fetched) { wire_in.arrived = 0; n.have_fetched = 0; } + if (v4_node_doing(&n)) (void)wire_do(); +} +/* The console puts a line on the node's wire. */ +static int console_put(const v4_message *m) +{ + return v4_wire_put(&wire_in, m->word, m->word + V4_MSG_HEADER) == V4_WIRE_DONE; +} + +/* 1 if the node is asleep with nothing to do: where it waits for a message. */ static int node_idle(void) { - return n.reading && !n.given && n.read_port == V4_PORT_ANY && n.p > w_idle && n.p <= w_idle_end; /* in (IDLE), where it reads */ + return v4_node_doing(&n) && n.do_op == 8 && wire_in.used == 0 && n.mem[AWAIT_FROM] == 0; } /* Run until the text ends, or until it has been inside KEY with nothing to @@ -208,35 +272,27 @@ static int node_idle(void) * for a character, 0: neither. */ static int run_line(long max) { + static v4_cell hd[V4_WIRE_HEADER], text[V4_MSG_MAX_CHARS / 4u]; long steps = 0; unsigned idle = 0; while (steps < max && idle < 64) { - (void)v4_exec_step_word(&n, &es, &h); + node_step(); steps++; if (n.asking && n.ask_port == 0) { kernel_serve(); v4_node_port_served(&n); continue; } /* a request */ - if (n.asking && n.ask_port == CONSOLE_PORT) { /* a word of a message from the node */ - v4_cell word = n.request; - v4_node_port_served(&n); - if (v4_message_word(&from_node, word)) { - unsigned i, chars = v4_message_length(&from_node); - if (v4_message_type(&from_node) == V4_MSG_OUTPUT) { - for (i = 0; i < chars; i++) { - if (shown_len < sizeof shown) shown[shown_len++] = v4_message_char(&from_node, i); - else shown_dropped = 1; - } - } else if (v4_message_type(&from_node) == V4_MSG_DONE) { - text_ended = (int)from_node.word[V4_MSG_HEADER]; - from_node.count = 0; - last_steps = steps; - return 1; + if (n.asking) { (void)v4_node_port_gone(&n, 18); continue; } /* a port with no one on it */ + v4_wire_first(&wire_out); + while (v4_wire_take(&wire_out, hd, text, V4_MSG_MAX_CHARS / 4u) == V4_WIRE_DONE) { /* what the node has sent */ + if (hd[2] == V4_MSG_OUTPUT) { + unsigned i; + for (i = 0; i < (unsigned)hd[6]; i++) { + if (shown_len < sizeof shown) shown[shown_len++] = (char)(((v4_ucell)text[i / 4u] >> (8u * (i % 4u))) & 0xFFu); + else shown_dropped = 1; } - from_node.count = 0; + } else if (hd[2] == V4_MSG_DONE) { + text_ended = (int)text[0]; + last_steps = steps; + return 1; } - continue; - } - if (n.reading && !n.given && to_sent < to_node.count && (n.read_port == CONSOLE_PORT || n.read_port == V4_PORT_ANY)) { - v4_node_port_give(&n, CONSOLE_PORT, to_node.word[to_sent++]); /* a word of the message to the node */ - continue; } if (n.input_pos == n.input_len && n.p >= w_key && n.p < w_key_end) idle++; else idle = 0; } @@ -279,7 +335,7 @@ static void boot_with(unsigned depth) for (i = 0; i < depth; i++) v4_dstack_push(&n.ds, (v4_cell)(0x5A000000 + i)); v4_dstack_push(&n.ds, CANARY); v4_node_port_attach(&n, PORT); - v4_node_port_status(&n, 0, (1u | 1u << CONSOLE_PORT) * (1u + (1u << V4_PORTS))); /* the kernel and the console take what is written when it is written */ + v4_wire_reset(&wire_in); v4_wire_reset(&wire_out); n.mem[WORD_DEFINED] = 0; /* no one is told of entries, until a test says so */ n.mem[WORD_FORGOTTEN] = 0; known_n = 0; @@ -287,13 +343,13 @@ static void boot_with(unsigned depth) n.mem[LINE_STATUS] = 1; n.mem[ME] = 0; /* it has no number yet: a message to 0 is for it */ HOST_MESSAGES_EMPTY(&n); - n.mem[REPLY] = PORT + CONSOLE_PORT; + n.mem[REPLY] = CONSOLE_PORT; n.p = w_idle; /* waiting for a message */ typed_len = 0; line_open = 0; - to_node.count = 0; to_sent = 0; from_node.count = 0; + to_node.count = 0; shown_len = 0; shown_dropped = 0; - for (i = 0; i < 64 && !n.reading; i++) (void)v4_exec_step_word(&n, &es, &h); /* no message waits: it reads its ports, and is blocked */ + for (i = 0; i < 256 && !node_idle(); i++) node_step(); /* no message waits: it sleeps */ } static void boot(void) { boot_with(0); } /* The same with nothing at all on the data stack. */ @@ -335,7 +391,7 @@ static const char *say(const char *input) if (!nl) break; len = (unsigned)(nl - typed); if (!v4_message_text(&to_node, 0, 1, V4_MSG_TEXT, typed, len)) return "(line too long)"; - to_sent = 0; + if (!console_put(&to_node)) return "(no room on the wire)"; memmove(typed, nl + 1, typed_len - len - 1); typed_len -= len + 1; line_open = 1; @@ -376,8 +432,7 @@ static int post_line(void *self, const char *text, unsigned text_len, char *o, u (void)self; *len = 0; if (post_state_fails && text_len == 25 && memcmp(text, "DECIMAL FORTH DEFINITIONS", 25) == 0) text = "NOSUCHWORD-FOR-STATE "; - if (!v4_message_text(&to_node, 0, 1, V4_MSG_TEXT, text, text_len)) return -1; - to_sent = 0; from_node.count = 0; + if (!v4_message_text(&to_node, 0, 1, V4_MSG_TEXT, text, text_len) || !console_put(&to_node)) return -1; shown_len = 0; shown_dropped = 0; ended = run_line(post_step_limit); if (ended != 1) return -1; @@ -870,7 +925,6 @@ int main(void) printf(" capsule: %ld words\n", (long)v4_text_here(&tx) - 16); if (failures) { printf(" %d checks, %d failures\n", checks, failures); return 1; } w_idle = v4_text_word(&tx, "(IDLE)"); - w_idle_end = v4_text_word(&tx, "(PASS-ON)"); /* the word after (IDLE) in quit.v4 */ w_key = v4_text_word(&tx, "KEY"); w_key_end = v4_text_word(&tx, "CR"); /* the word after KEY in core.v4 */ w_fault = v4_text_word(&tx, "(FAULTS)"); @@ -1153,7 +1207,7 @@ int main(void) (void)say("WORDS\n"); for (at = 0; at < plain; at++) if (n.mem[OUT_W + OUT_CELLS + at] != 0) { over++; worst = k; break; } if (n.mem[OUT_PTR] > OUT_W + OUT_CELLS) { over++; worst = k; } - if (n.asking || n.doing) { over++; worst = k; } /* it stored to a port */ + if (n.asking) { over++; worst = k; } /* it stored to a port */ CHECK(strcmp(say("1 2 + .\n"), "3 ok\nok> ") == 0, "WORDS with %u values on the stack: the node goes on", k); } CHECK(over == 0, "nothing was ever put past the end of the output buffer (it was, with %u values on the stack)", worst); diff --git a/v4/tests/test_host_unit.c b/v4/tests/test_host_unit.c index 5167db89..50ff1477 100644 --- a/v4/tests/test_host_unit.c +++ b/v4/tests/test_host_unit.c @@ -63,6 +63,8 @@ static int storage_made(void) } static v4_place places[PLACES]; static v4_fabric f; +#define QUEUES 64u /* two for every wire there is at once: five kernels, the console, four to Hera, four round the square */ +static v4_wire_queue queues[QUEUES]; static v4_image im; /* only what the dictionary hash needs of it */ /* ---- the capsules ---- */ @@ -196,10 +198,10 @@ static int kernel_take(void *self, v4_cell value) /* a request */ } static const v4_device kernel = { kernel_take, kernel_give, 0, 0 }; -/* ---- the console: a device on Hera's port 1 ---- */ +/* ---- the console: a device on Hera's port 1 ---- + * It speaks whole messages: it puts them on the wire to Hera and takes them + * off the wire from her, through the fabric (MESH.md 7d.2). */ #define CONSOLE_ID 1 -static v4_message going, coming; -static unsigned going_at; static char printed[8192]; /* what has come back since the console last sent */ static unsigned printed_len; static v4_cell printed_from; @@ -207,48 +209,45 @@ static int ended; static v4_cell ended_from, ended_how; static unsigned post_passed[32]; /* by node number: POST tallies with no failures seen from it */ static unsigned post_other; /* tallies with failures */ +static const v4_device console = { 0, 0, 0, 0 }; -static int console_give(void *self, v4_cell *value) +static unsigned console_takes(void) { - (void)self; - if (going_at >= going.count) return 0; - *value = going.word[going_at++]; - return 1; -} -static int console_take(void *self, v4_cell value) -{ - (void)self; - if (v4_message_word(&coming, value)) { - if (v4_message_type(&coming) == V4_MSG_OUTPUT) { - unsigned i, chars = v4_message_length(&coming), start = printed_len; - for (i = 0; i < chars && printed_len + 1 < sizeof printed; i++) printed[printed_len++] = v4_message_char(&coming, i); + static v4_cell hd[V4_WIRE_HEADER], text[V4_MSG_MAX_CHARS / 4u]; + unsigned took = 0; + while (v4_fabric_device_take(&f, hera_place, 1, hd, text, V4_MSG_MAX_CHARS / 4u) == V4_WIRE_DONE) { + took++; + if (hd[2] == V4_MSG_OUTPUT) { + unsigned i, start = printed_len; + 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 = v4_message_from(&coming); + printed_from = hd[1]; if (strstr(printed + start, "PARITY:V4_POST")) { if (strstr(printed + start, " fail=0\n") && printed_from >= 0 && printed_from < 32) post_passed[printed_from]++; else post_other++; } - } else if (v4_message_type(&coming) == V4_MSG_DONE) { + } else if (hd[2] == V4_MSG_DONE) { ended++; - ended_from = v4_message_from(&coming); - ended_how = coming.word[V4_MSG_HEADER]; + ended_from = hd[1]; + ended_how = text[0]; } - coming.count = 0; } - return 1; + return took; } -static int console_pending(void *self) { (void)self; return going_at < going.count; } -static const v4_device console = { console_take, console_give, 0, console_pending }; -/* Is there a node that will execute at the next step? A step in which a - * node only faulted counts nothing done (exec.h), and it goes on from its - * fault handler at the next. */ +/* Is there a node, other than those known to be going round for ever, that + * will execute at the next step? A node asleep at a wire will not; a step + * in which a node only faulted counts nothing done (exec.h), and it goes on + * from its fault handler at the next. */ +static unsigned spinning; /* bit n: the node numbered n is known to be stuck in a loop, and is not waited for */ static int any_running(void) { unsigned k; for (k = 0; k < PLACES; k++) { const v4_fabric_node *x = v4_fabric_node_at(&f, k); - if (x && !places[k].asleep && !x->n.stopped && !x->n.asking && !(x->n.reading && !x->n.given)) return 1; + if (!x || places[k].asleep || x->n.stopped || x->n.asking || x->n.doing || (x->n.reading && !x->n.given)) continue; + if (x->n.mem[ME] >= 0 && x->n.mem[ME] < 32 && (spinning & 1u << x->n.mem[ME])) continue; + return 1; } return 0; } @@ -260,21 +259,31 @@ static void stuck(void) unsigned k; for (k = 0; k < PLACES; k++) { const v4_fabric_node *x = v4_fabric_node_at(&f, k); - if (x) printf(" stuck: node %ld p=%ld asking=%d port=%u reading=%d rport=%u given=%d stopped=%d mq=%ld lost=%ld\n", (long)x->n.mem[ME], (long)x->n.p, - x->n.asking, x->n.ask_port, x->n.reading, x->n.read_port, x->n.given, x->n.stopped, (long)x->n.mem[MQ_COUNT], (long)x->n.mem[LOST]); + if (x) printf(" stuck: node %ld p=%ld asking=%d port=%u reading=%d doing=%d op=%ld wire=%u stopped=%d await=%ld lost=%ld\n", (long)x->n.mem[ME], (long)x->n.p, + x->n.asking, x->n.ask_port, x->n.reading, x->n.doing, (long)x->n.do_op, x->n.wire, x->n.stopped, (long)x->n.mem[AWAIT_FROM], (long)x->n.mem[LOST]); } } -/* Send text to a node and let everything that follows from it happen. */ +/* Send text to a node and let everything that follows from it happen: until + * nothing can; or, with nodes known to be stuck, until no other node has + * executed and the console has been sent nothing for a while. */ static const char *tell(v4_cell node, const char *text) { - unsigned long steps = 0; + static v4_message m; + unsigned long steps = 0, quiet = 0; printed_len = 0; printed[0] = 0; printed_from = -1; ended = 0; ended_from = -1; ended_how = -1; - if (!v4_message_text(&going, node, CONSOLE_ID, V4_MSG_TEXT, text, (unsigned)strlen(text))) return "(too long)"; - going_at = 0; - while (steps < step_limit && (v4_fabric_step(&f) != 0 || any_running())) steps++; - if (steps >= step_limit) { stuck(); return "(still running)"; } - return printed; + if (!v4_message_text(&m, node, CONSOLE_ID, V4_MSG_TEXT, text, (unsigned)strlen(text))) return "(too long)"; + if (v4_fabric_device_put(&f, hera_place, 1, m.word, m.word + V4_MSG_HEADER) != V4_WIRE_DONE) return "(not put)"; + while (steps < step_limit) { + unsigned did = v4_fabric_step(&f), took = console_takes(); + int run = any_running(); + steps++; + if (did == 0 && took == 0 && !run) return printed; + quiet = (spinning && !run && took == 0) ? quiet + 1 : 0; + if (quiet >= 400000ul) return printed; + } + stuck(); + return "(still running)"; } static int told(v4_cell node, const char *text) { return tell(node, text)[0] == 0 && ended == 1 && ended_how == V4_TEXT_COMPLETED; } @@ -314,7 +323,7 @@ static unsigned load(v4_cell node, const char *file) while ((n = next_line(&r, line)) >= 0) { line[n] = 0; (void)tell(node, line); - if (ended != 1 || ended_how != V4_TEXT_COMPLETED) { if (!bad) { const v4_node *q = &pool[0].n; printf(" %s: \"%s\" -> \"%s\" ended=%d how=%ld; stopped=%d asking=%d port=%u reading=%d rport=%u p=%ld fault=%u@%ld\n", file, line, printed, ended, (long)ended_how, q->stopped, q->asking, q->ask_port, q->reading, q->read_port, (long)q->p, q->fault_kind, (long)q->fault_addr); } bad++; } + if (ended != 1 || ended_how != V4_TEXT_COMPLETED) { if (!bad) { const v4_node *q = &pool[0].n; printf(" %s: \"%s\" -> \"%s\" ended=%d how=%ld; stopped=%d asking=%d port=%u doing=%d op=%ld p=%ld fault=%u@%ld\n", file, line, printed, ended, (long)ended_how, q->stopped, q->asking, q->ask_port, q->doing, (long)q->do_op, (long)q->p, q->fault_kind, (long)q->fault_addr); } bad++; } } return bad; } @@ -331,10 +340,18 @@ static v4_uheat_t clock_of(v4_cell number) for (k = 0; k < PLACES; k++) if (v4_fabric_node_at(&f, k) && v4_fabric_node_at(&f, k)->n.mem[ME] == number) return v4_fabric_node_at(&f, k)->es.anticlock; return 0; } +/* 1 if the node is asleep (operation 8): it executes nothing until a message comes. */ static int waiting(v4_cell number) { const v4_node *n = node_numbered(number); - return n && n->reading && !n->given && n->read_port == V4_PORT_ANY; + return n && n->doing && n->do_op == 8; +} +/* how many cells of messages wait on the wire going from a port of the node */ +static unsigned going_from(v4_cell number, unsigned port) +{ + unsigned k; + for (k = 0; k < PLACES; k++) if (v4_fabric_node_at(&f, k) && v4_fabric_node_at(&f, k)->n.mem[ME] == number && places[k].wire[port].tx) return places[k].wire[port].tx->used; + return 0; } int main(void) @@ -381,18 +398,17 @@ int main(void) /* ---- Hera is born empty, and takes in the nucleus ---- */ v4_fabric_init(&f, places, PLACES); + v4_fabric_queues(&f, queues, QUEUES); v4_fabric_gone_error(&f, V4_ERROR_NO_ONE); - v4_fabric_interrupt_error(&f, V4_ERROR_INTERRUPTED); place = host_born(0); CHECK(place == 0, "a node is born"); 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"); - going.count = 0; going_at = 0; for (i = 0; i < 400000 && v4_fabric_step(&f) != 0; i++) { } - CHECK(given_cells == f18_count && pool[0].n.reading && pool[0].n.read_port == V4_PORT_ANY && !pool[0].n.stopped, - "it takes in the nucleus through its port and waits for a message: %u of %u cells", given_cells, f18_count); + 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); CHECK(told(0, "10 (ME) ! 1 (CONSOLE) ! 1 1 ROUTE") && ended_from == 10, "it is told it is 10, and where the console is"); { @@ -501,64 +517,78 @@ int main(void) 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) ---- */ - - /* a node is killed while its neighbour is writing to it */ - step_limit = 3000000ul; - CHECK(strcmp(tell(14, ": SPIN BEGIN 0 UNTIL ; SPIN"), "(still running)") == 0, "node 14 is stuck in a loop that never ends"); - CHECK(strcmp(tell(12, ": TO14 S\" 65 EMIT\" 14 SEND ; TO14"), "(still running)") == 0 && node_numbered(12)->asking, - "node 12, beside it, sends it text: 14 never reads, and 12 is blocked writing to it"); + /* ---- Hera kills a node by number, and tells the others it is gone (MESH.md 7b, 7d) ---- + * A node that is stuck holds up no one: what is sent to it waits on its wire until the wire is full, and + * is refused from then on; Hera can be typed to whatever any other node is doing, and her KILL of one + * stuck node is not held by another (acceptance 3 and 4). */ + step_limit = 60000000ul; + spinning = 1u << 13 | 1u << 14; + (void)tell(14, ": SPIN BEGIN 0 UNTIL ; SPIN"); + (void)tell(13, ": SPIN BEGIN 0 UNTIL ; SPIN"); + CHECK(!waiting(14) && !waiting(13) && ended == 0, "nodes 13 and 14 are each stuck in a loop that never ends"); + CHECK(tell(12, ": TO14 S\" 65 EMIT\" 14 SEND ; TO14")[0] == 0 && ended == 1 && ended_how == V4_TEXT_COMPLETED && waiting(12) && going_from(12, 3) == 9, + "node 12, beside 14, sends it text: the text waits on the wire, 14 never taking it, and node 12's line ends: \"%s\", %u cells", printed, going_from(12, 3)); + { + v4_cell refused = node_numbered(12)->mem[REFUSED]; + CHECK(strstr(tell(12, ": FILL14 200 0 DO S\" 65 EMIT\" 14 SEND LOOP ; FILL14"), "Message refused") != NULL && ended == 1 && ended_how == V4_TEXT_ERROR + && node_numbered(12)->mem[REFUSED] == refused + 1 && going_from(12, 3) > V4_WIRE_CELLS - 9u, + "it sends more: when the wire is full the next is refused at once, and told: \"%s\", %u cells on the wire", printed, going_from(12, 3)); + CHECK(strcmp(tell(12, "12 100 * ."), "1200 ") == 0 && waiting(12) && strcmp(tell(11, "11 100 * ."), "1100 ") == 0, "node 12 is not held, and goes on doing what it is sent; so does node 11"); + } + CHECK(strcmp(tell(10, "1 2 + ."), "3 ") == 0, "Hera is typed to, with two nodes stuck"); { unsigned born = born_count; const char *r = tell(10, "14 KILL 65 EMIT"); - CHECK(strstr(printed, "A") != NULL && node_numbered(14) == NULL && born_count == born, "Hera kills node 14, by its number: \"%s\"", r); + CHECK(strcmp(r, "A") == 0 && ended == 1 && ended_how == V4_TEXT_COMPLETED && node_numbered(14) == NULL && born_count == born, + "Hera kills node 14, by its number, with node 13 still stuck: the KILL ends, and the rest of her line is done: \"%s\"", r); + CHECK(going_from(10, 4) == 8 && node_numbered(13) != NULL && !waiting(13), "her word to node 13 that 14 is gone waits on the wire to it: it is stuck, and she did not wait for it: %u cells", going_from(10, 4)); } - CHECK(!node_numbered(12)->asking && !node_numbered(12)->stopped && waiting(12), "node 12 is no longer blocked: it is waiting at its ports"); - CHECK(strstr(printed, "No one on that port") != NULL, "what it was doing ended in an error, which said so: \"%s\"", printed); - step_limit = 30000000ul; - CHECK(strcmp(tell(12, "12 100 * ."), "1200 ") == 0 && strcmp(tell(13, "13 100 * ."), "1300 ") == 0 && strcmp(tell(10, "1 2 + ."), "3 ") == 0, "and everything else keeps running: \"%s\"", printed); + spinning = 1u << 13; + 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); { - v4_cell refused = node_numbered(12)->mem[REFUSED]; + 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), - "every node was told 14 is gone and forgot the way: what node 12 sends it now goes toward Hera, who has no way either, and is refused: \"%s\"", printed); + "node 12 was told 14 is gone and forgot the way: what it sends it now goes toward Hera, who has no way either, and is refused: \"%s\"", printed); + CHECK(tell(11, ": AGAIN14 S\" 65 EMIT\" 14 SEND ; AGAIN14")[0] == 0 && node_numbered(11)->mem[REFUSED] == r11 + 1 && waiting(11), "and so was node 11"); CHECK(strstr(tell(10, ": H14 S\" 1\" 14 SEND ; H14"), "Argument out of range") != NULL, "Hera herself has no way to it"); } /* a node waits on one that is stuck and is not its neighbour; Hera kills the stuck one */ - step_limit = 3000000ul; - CHECK(strcmp(tell(13, ": SPIN BEGIN 0 UNTIL ; SPIN"), "(still running)") == 0, "node 13 is stuck in a loop"); - CHECK(strcmp(tell(12, ": W13 13 AWAIT . ; W13"), "(still running)") == 0 && node_numbered(12)->mem[AWAIT_FROM] == 13 && waiting(12), - "node 12, which is not wired to it, waits for its answer"); + (void)tell(12, ": W13 13 AWAIT . ; W13"); + CHECK(ended == 0 && node_numbered(12)->mem[AWAIT_FROM] == 13 && waiting(12), "node 12, which is not wired to node 13, waits for its answer, asleep"); (void)tell(10, "13 KILL"); CHECK(node_numbered(13) == NULL && strstr(printed, "Node gone") != NULL && node_numbered(12)->mem[AWAIT_FROM] == 0 && waiting(12), "Hera kills node 13: node 12's wait ends, with an error that says the node is gone: \"%s\"", printed); - step_limit = 30000000ul; + spinning = 0; CHECK(strcmp(tell(12, "12 100 * ."), "1200 ") == 0 && strcmp(tell(11, "11 100 * ."), "1100 ") == 0, "and the others go on"); /* Hera's own wait, on a node that is stuck: a line from the console ends it */ - step_limit = 3000000ul; - CHECK(strcmp(tell(12, ": SPIN BEGIN 0 UNTIL ; SPIN"), "(still running)") == 0, "node 12 is stuck in a loop"); - CHECK(strcmp(tell(10, "12 AWAIT ."), "(still running)") == 0 && node_numbered(10)->mem[AWAIT_FROM] == 12 && waiting(10), "Hera waits for its answer, which will not come"); + spinning = 1u << 12; + (void)tell(12, ": SPIN BEGIN 0 UNTIL ; SPIN"); + (void)tell(10, "12 AWAIT ."); + CHECK(ended == 0 && !waiting(12) && node_numbered(10)->mem[AWAIT_FROM] == 12 && waiting(10), "node 12 is stuck in a loop; Hera waits for its answer, which will not come"); { const char *r = tell(10, "12 KILL 66 EMIT"); CHECK(strstr(r, "Interrupted\n") != NULL && strstr(r, "B") != NULL && strstr(r, "Interrupted\n") < strstr(r, "B") && ended == 2, "a line typed at the console ends her wait, with an error that says so, and is then done: \"%s\"", r); CHECK(node_numbered(12) == NULL && node_numbered(10)->mem[AWAIT_FROM] == 0, "the line killed the stuck node"); } - step_limit = 30000000ul; + spinning = 0; - /* Hera blocked writing to a node that is stuck: a line from the console lets her go (MESH.md 7b.7) */ - step_limit = 3000000ul; - CHECK(strcmp(tell(11, ": SPIN BEGIN 0 UNTIL ; SPIN"), "(still running)") == 0, "node 11 is stuck in a loop"); - CHECK(strcmp(tell(10, ": T11 S\" 1 DROP\" 11 SEND 67 EMIT ; T11"), "(still running)") == 0 && node_numbered(10)->asking, - "Hera sends it text: its number is the higher, so she writes without looking, and is blocked"); + /* Hera sends to a node that is stuck: her line ends, and when the wire is full she is refused; she is never held */ + spinning = 1u << 11; + (void)tell(11, ": SPIN BEGIN 0 UNTIL ; SPIN"); + CHECK(strcmp(tell(10, ": T11 S\" 1 DROP\" 11 SEND 67 EMIT ; T11"), "C") == 0 && ended == 1 && !waiting(11) && waiting(10), + "node 11 is stuck in a loop; Hera sends it text, which waits on the wire, and the rest of her line is done: \"%s\"", printed); + CHECK(strstr(tell(10, ": F11 200 0 DO S\" 1 DROP\" 11 SEND LOOP 67 EMIT ; F11"), "Message refused") != NULL && strstr(printed, "C") == NULL && ended_how == V4_TEXT_ERROR && waiting(10), + "she sends more than the wire holds: the one there is no room for is refused at once, and her line ends there: \"%s\"", printed); { const char *r = tell(10, "11 KILL 68 EMIT"); - CHECK(strstr(r, "Interrupted\n") != NULL && strstr(r, "D") != NULL && strstr(r, "C") == NULL && strstr(r, "Interrupted\n") < strstr(r, "D"), - "a line typed at the console ends the line that was blocked, with an error that says so, and is then done: \"%s\"", r); - CHECK(node_numbered(11) == NULL && !node_numbered(10)->asking && waiting(10), "the line killed the stuck node, and Hera is back at her ports"); + CHECK(strcmp(r, "D") == 0 && node_numbered(11) == NULL && waiting(10), "she is typed to, and kills the stuck node: \"%s\"", r); } + spinning = 0; CHECK(strcmp(tell(10, "1 2 + ."), "3 ") == 0, "and she goes on"); step_limit = 30000000ul; diff --git a/v4/tools/depthsweep.py b/v4/tools/depthsweep.py index 8cd1f278..f1787558 100755 --- a/v4/tools/depthsweep.py +++ b/v4/tools/depthsweep.py @@ -65,6 +65,8 @@ for k in [0, 1, shallow] + list(range(ddepth - 10, ddepth + 3)): check('SEND with no way', k, [': SD S" 1 DROP" 5 SEND ;'] + v + ['SD', '77 .'], 'Argument out of range' if sh else None) check('a write to an empty port', k, v + ['5 7 PORT!', '77 .'], 'No one on that port' if sh else None) check('an unknown word', k, v + ['NOSUCHWORD', '77 .'], 'UNKNOWN WORD' if sh else None) + check('a loop that prints 400 numbers', k, [': PR 400 0 DO I . LOOP ;'] + v + ['PR', '77 .'], ' 398 399 ok' if sh else None) + check('SEND by a way with no one on the port', k, ['5 7 ROUTE', ': SP S" 1 DROP" 5 SEND ;'] + v + ['SP', '77 .'], 'No one on that port' if sh else None) def nest(n, inner): return [': N1 %s ;' % inner] + [': N%d N%d ;' % (i, i - 1) for i in range(2, n + 1)] + ['N%d' % n] @@ -76,6 +78,8 @@ for n in [1, rshallow] + list(range(rdepth - 12, rdepth + 3)): check('WORDS, calls deep', n, nest(n, 'WORDS') + ['77 .'], '77 ok') check('a line typed during AWAIT, calls deep', n, nest(n, '5 AWAIT') + ['77 .', '88 .'], '77 ok') check('SEND with no way, calls deep', n, nest(n, 'S" 1 DROP" 5 SEND') + ['77 .'], '77 ok') + check('a loop that prints 400 numbers, calls deep', n, nest(n, '400 0 DO I . LOOP') + ['77 .'], '77 ok') + check('SEND by a way with no one on the port, calls deep', n, ['5 7 ROUTE'] + nest(n, 'S" 1 DROP" 5 SEND') + ['77 .'], '77 ok') with ThreadPoolExecutor(max_workers=8) as pool: found = [f for f in pool.map(judge, cases) if f]