feat(v4.0.0): the DO loop runtimes

(DO) (?DO) (LOOP) (+LOOP) (LEAVE) I J UNLOOP and (0BRANCH), laid down
in line by hand as the compiler will, executed on the golden model at
both cell widths against C on every pair of 14 loop ends and 11 steps,
and against sequences recorded from the v3 binary.

(LOOP) goes round again while index < limit, signed, as v3 does; the
document's equality test differed whenever start >= limit.  (+LOOP) is
now specified.  J keeps the outer index in A and needs no extra return
entry.

LEAVE discards the limit and index.  v3 left them on its return stack,
which made a LEAVE in an inner loop stop the outer one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-03 20:13:24 -04:00
co-authored by Claude Opus 5.5
parent 481d484e93
commit 03d8d991ef
2 changed files with 502 additions and 14 deletions
+32 -14
View File
@@ -147,7 +147,7 @@ IF body1 ELSE body2 THEN → if L1 drop body1 jump L2
times, as on the F18. `FOR ... UNEXT` is the same but the body must fit in one instruction word.
**Register conventions.** `A` and `B` are caller-saved. A word that uses them says so. Words in this
document that clobber `A`: `@ ! +! -! 2@ 2! C@ C! D2/ M* FILL ERASE MOVE COUNT CMOVE CMOVE> BLANK -TRAILING COMPARE SEARCH SCAN SKIP DECIMAL HEX OCTAL UM* * UM/MOD Q.FROM-INT Q.TO-INT Q.* Q./ Q.EXP Q.SQRT Q.LOG Q.SIN Q.COS HOLD SIGN # #S . .R U. U.R D. D.R ? DUMP Q.PRINT TYPE SEND RECV`. Words that clobber `B`:
document that clobber `A`: `@ ! +! -! 2@ 2! C@ C! D2/ M* FILL ERASE MOVE COUNT CMOVE CMOVE> BLANK -TRAILING COMPARE SEARCH SCAN SKIP DECIMAL HEX OCTAL UM* * UM/MOD Q.FROM-INT Q.TO-INT Q.* Q./ Q.EXP Q.SQRT Q.LOG Q.SIN Q.COS +LOOP J HOLD SIGN # #S . .R U. U.R D. D.R ? DUMP Q.PRINT TYPE SEND RECV`. Words that clobber `B`:
`COMPARE SEARCH Q./ Q.EXP Q.SQRT Q.LOG Q.SIN Q.COS <# HOLD SIGN # #S #> . .R U. U.R D. D.R ? DUMP Q.PRINT EMIT CR SPACE SPACES TYPE SEND RECV`.
**Return-stack words** (`>R R> R@ 2>R 2R> 2R@ I J UNLOOP` and the loop runtimes) are always IN.
@@ -721,28 +721,46 @@ The storage service belongs to the Artemis role, now a device node.
| `IF` `ELSE` `THEN` `BEGIN` `UNTIL` `AGAIN` `WHILE` `REPEAT` `DO` `?DO` `LOOP` `+LOOP` `LEAVE` `CASE` `OF` `ENDOF` `ENDCASE` | CC | Compile-time structure words. Expansions per §2 and below. |
| `EXIT` | OP | `;` |
| `(BRANCH)` | OP | `jump` |
| `(0BRANCH)` | IN | `if L … drop` (§2) |
| `(DO)` | IN | `SWAP push push` (R: limit index) |
| `(?DO)` | IN | `2DUP = IF 2DROP jump past-loop THEN SWAP push push` |
| `(LOOP)` | IN | See below. |
| `(+LOOP)` | IN | Sign-aware boundary-crossing test; specified in the compiler capsule. |
| `(LEAVE)` | IN | `pop drop pop dup push push jump loop-test` (sets index to limit) |
| `I` | IN | `pop dup push` |
| `J` | IN | `pop pop pop dup push SWAP push SWAP push` |
| `UNLOOP` | IN | `pop pop drop drop` |
| `(0BRANCH)` | IN | `if L … drop` (§2) — executed on the golden model (2026-10-03) as `IF 1 ELSE 2 THEN`, including results recorded from the v3 binary. |
| `(DO)` | IN | `over push push drop` (R: limit index) — `SWAP push push` without the `SWAP`. The body always runs once, as v3. Executed on the golden model (2026-10-03). |
| `(?DO)` | IN | `over over xor if SKIP drop (DO) … body and loop end … jump PAST SKIP: drop drop drop PAST:` — skips the loop when index = limit, as v3. Executed on the golden model (2026-10-03). |
| `(LOOP)` | IN | See below. Adds 1 and goes round again while index < limit, signed, as v3. Executed on the golden model (2026-10-03) against C on every pair of 14 loop ends, and sequences recorded from the v3 binary. |
| `(+LOOP)` | IN | See below. `( n -- )`: adds `n` and goes round again while index < limit for `n >= 0`, while index >= limit for `n < 0`, as v3. Executed on the golden model (2026-10-03) against C on every pair of 14 loop ends with 11 steps, and sequences recorded from the v3 binary. Clobbers `A`. |
| `(LEAVE)` | IN | `pop drop pop drop jump PAST` — discards the limit and index and leaves the loop at once. In a single loop this is what v3 does. v3 left the two on its return stack (index set to limit) and jumped past the loop, so a `LEAVE` in an inner loop made the outer `LOOP` count the inner loop's leftovers and stop: `: T 3 0 DO 10 0 DO I . I 1 = IF LEAVE THEN LOOP I . LOOP ;` prints `0 1 10` in v3 and `0 1 0 0 1 1 0 1 2` here. Executed on the golden model (2026-10-03) in `LOOP` and `+LOOP`, including sequences recorded from the v3 binary. |
| `I` | IN | `pop dup push` — executed on the golden model (2026-10-03). |
| `J` | IN | `pop pop pop dup push a! push push a` — the outer index waits in `A` while the inner pair goes back, so `J` needs no return entry beyond the two loops' own (with two in-line `SWAP`s it needed one more). Executed on the golden model (2026-10-03), including a sequence recorded from the v3 binary. Clobbers `A`. |
| `UNLOOP` | IN | `pop drop pop drop` — executed on the golden model (2026-10-03) as `UNLOOP EXIT`, including a sequence recorded from the v3 binary. |
```
<less>, used by both loop ends: ( i' lim -- i' lim s ), the top bit of s set
exactly when i' < lim, signed -- i' itself when the two differ in sign, else
i' - lim (the subtraction in line):
over over xor -if SAME
drop over jump T
SAME: drop over over -
T:
(LOOP) expansion:
pop 1 + pop \ index' limit
2DUP xor \ index' limit flag (0 when equal)
if Lexit
<less> \ index' limit s
-if Lexit \ not less: done
drop push push \ R: limit index'
jump Lbody
Lexit: drop drop drop
(+LOOP) expansion: ( n -- )
dup a! pop + pop \ index' limit A: n
<less> a xor \ top bit set: go round again
-if Lexit
drop push push
jump Lbody
Lexit: drop drop drop
```
`(LOOP)` terminates when the index reaches the limit, which matches v3 for every loop where
`start < limit`.
`(LOOP)` goes round again while `index' < limit`, signed, which is v3's rule. So `0 5 DO … LOOP` runs
once, as in v3; a test for `index' = limit`, which this document had first, would run it round the whole
number circle. `(+LOOP)` goes round again exactly when `s` and `n` differ in sign: `index' < limit` for
`n >= 0` and the opposite for `n < 0`. A loop costs its word two return-stack entries while it runs.
**New in v4:** `FOR`, `NEXT`, and `UNEXT` are native (§2). Counted loops that don't need an ascending
index should use them; they cost one instruction per iteration.