feat(v4.0.0): a node looks before it writes; two neighbours no longer stop each other
MESH.md step 4, the fault found there, as ruled (section 7a): a node writes only to a neighbour that is reading, keeps what it takes in meanwhile, and loses and counts what it has no room for. Engine: two more addresses after a node's ports -- which ports have a neighbour waiting to write to it, which to read from it. Nucleus: (GATE) before every message; the messages waiting, a ring of 400 cells, dealt with when the node is idle. Of two neighbours the lower number may wait to write (ruled after it was built); NEIGHBOUR tells a node who is on each port. test_host_mesh.c, 44 checks: the case that stopped the nodes passes; six messages from each node to each at once all arrive; 800 at once, the nodes come to rest and every message arrived or was counted (287 arrived, 714 of all kinds let go). test_fabric.c: the two looks. Both widths, ASan+UBSan. POST: twelve cases handed HERE, a cell address on v4, to words that take a byte address, and so wrote into or read from the nucleus's code at cell HERE/4. Ruled: left out, marked OPEN, until HERE and the byte words are made to agree as its own step. POST is 538 cases. hosted-check on three ISAs, 538/538. Bare metal: logs/20261006-134817 (amd64), -135044 (aarch64), -135440 (riscv64). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
630d03fa4e
commit
f9c034cadd
+93
-1
@@ -128,6 +128,98 @@ header CMOVE
|
||||
NONE: drop drop (ROUTE-DEFAULT) a! @ ;
|
||||
HIT: drop drop @+ pop drop ;
|
||||
|
||||
\ 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.
|
||||
\
|
||||
\ 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.
|
||||
|
||||
\ ( mask index -- bit )
|
||||
: (BIT)
|
||||
if Z -1 + FOR 2/ NEXT 1 and ;
|
||||
Z: drop 1 and ;
|
||||
|
||||
\ ( 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
|
||||
|
||||
\ ( 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 + ! ;
|
||||
|
||||
\ ( -- 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 + ! ;
|
||||
|
||||
\ ( port to -- ) take in a message. B is at the port, by its address,
|
||||
\ and `to` is the message's first word, already read from it. 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)
|
||||
(MQ-HDR) a! !+ 5 FOR @b !+ UNEXT ! \ its seven words, and the port
|
||||
(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! @ + -MQ-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 GONE -1 + FOR @b drop UNEXT jump LOSE
|
||||
GONE: drop
|
||||
LOSE: (LOST) a! @ 1 + ! ;
|
||||
|
||||
\ ( 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! !
|
||||
L: (WRITERS) b! @b if QUIET
|
||||
(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 jump L
|
||||
|
||||
\ ( 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.
|
||||
@@ -142,7 +234,7 @@ header CMOVE
|
||||
\ lowest. quit.v4 sets those two when text for this node arrives.
|
||||
: (FLUSH-OUT)
|
||||
(OUT^) a! @ (OUT) xor if NONE drop
|
||||
(PRINT-PORT) a! @ b! (PRINT-TO) a! @ !b
|
||||
(PRINT-PORT) a! @ (GATE) (PRINT-TO) a! @ !b
|
||||
2 (HDR)
|
||||
(OUT^) a! @ (OUT) - dup !b
|
||||
3 + 2/ 2/ -1 + (OUT) a!
|
||||
|
||||
+37
-27
@@ -65,7 +65,7 @@ macro R-CLEAR RSTACK-DEPTH b! a !b endmacro
|
||||
: (FINISH) ( s -- )
|
||||
(LINE-STATUS) b! dup !b
|
||||
(FLUSH-OUT)
|
||||
(DONE-PORT) a! @ b! (MSG)+1 a! @ !b \ to whoever sent the text
|
||||
(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)
|
||||
|
||||
@@ -78,29 +78,30 @@ 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 is blocked reading its ports
|
||||
\ (docs/v4.0.0/MESH.md 4.1): it executes nothing until a neighbour writes.
|
||||
\ What arrives is a message (core.v4). Its first word is taken from any
|
||||
\ port and the rest from the port that came on. Its text goes into TIB,
|
||||
\ which holds 1024 characters, the most a message carries; a longer one is
|
||||
\ read to its end, so that what follows it is not taken for a message, and
|
||||
\ 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).
|
||||
\ ( -- ) 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).
|
||||
: (IDLE)
|
||||
(PORT)+8 b! @b (MSG) a! !+ \ to, from any port
|
||||
(PORT)+9 b! @b (PORT) + dup b! \ B: the port it came on
|
||||
5 FOR @b !+ UNEXT \ from, type, heat and TTL, ACL tag, sequence, length
|
||||
(REPLY) a! !
|
||||
(MSG)+6 a! @ -if SIZED jump BAD \ a length below zero: no telling what follows
|
||||
SIZED: dup -1025 + -if LONG drop jump FITS
|
||||
LONG: drop 3 + 2/ 2/ -1 + FOR @b drop UNEXT 0 \ too long: read it and let it go
|
||||
BAD: drop 0 (MSG)+6 a! ! 0 SPAN a! ! NODE-ERROR b! 12 !b
|
||||
FITS:
|
||||
3 + 2/ 2/ if EMPTY
|
||||
-1 + TIB 2/ 2/ a! FOR @b !+ UNEXT
|
||||
jump READ
|
||||
EMPTY: drop
|
||||
L: (MQ#) a! @ if WAIT drop jump HAVE
|
||||
WAIT: 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:
|
||||
0 (MSG)+6 a! @ TIB + C! \ a zero after the text
|
||||
(MSG)+6 a! @ SPAN a! !
|
||||
@@ -124,7 +125,7 @@ macro R-CLEAR RSTACK-DEPTH b! a !b endmacro
|
||||
\ known it is let go, and (LOST) counts it.
|
||||
: (PASS-ON)
|
||||
(MSG) a! @ (PORT-FOR) if NOWAY
|
||||
b! (MSG) a! 6 FOR @+ !b UNEXT \ its seven words
|
||||
(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)
|
||||
@@ -156,6 +157,16 @@ header ROUTE
|
||||
(ROUTE#) a! @ 1 + ! ;
|
||||
FULL: drop drop drop NODE-ERROR b! 12 !b ;
|
||||
|
||||
\ ( 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)).
|
||||
header NEIGHBOUR
|
||||
: NEIGHBOUR
|
||||
dup -if POS jump BAD
|
||||
POS: -PORTS + -if BAD drop (NEAR) + a! ! ;
|
||||
BAD: drop drop drop NODE-ERROR b! 12 !b ;
|
||||
|
||||
\ ( port -- ) the port, by its number, leads toward every node not told of
|
||||
header DEFAULT-ROUTE
|
||||
: DEFAULT-ROUTE (PORT) + (ROUTE-DEFAULT) a! ! ;
|
||||
@@ -167,12 +178,11 @@ header NO-ROUTES
|
||||
\ ( baddr u node -- ) send the node u characters of text to interpret. It
|
||||
\ is on its way when SEND returns; what it prints there goes to that node's
|
||||
\ console. With no way to the node it is error 12. The write waits for the
|
||||
\ neighbour to read: a neighbour that is itself waiting to write to this
|
||||
\ node will wait for ever, and so will this one.
|
||||
\ neighbour to read, and is not begun until it may be (core.v4, (GATE)).
|
||||
header SEND
|
||||
: SEND
|
||||
dup (PORT-FOR) if NOWAY
|
||||
b! !b \ to
|
||||
(GATE) !b \ to
|
||||
1 (HDR)
|
||||
dup !b \ length
|
||||
3 + 2/ 2/ if NONE -1 +
|
||||
|
||||
Reference in New Issue
Block a user