feat(v4.0.0): the host node's stacks are 32 deep (D-17)

With the stacks counted and guarded (D-16) their size is a parameter of
the node.  The host node, which runs the interpreter and the compiler
under the user's programme, gets 32 values and 32 return entries; a mesh
node keeps the F18's 10 and 9.  Nothing else about the mechanism or any
word's definition changes.

- stack.h: V4_DATA_RING and V4_RET_RING are build parameters; the
  Makefile sets them for the host-node tests.
- tests/test_host_quit.c no longer assumes a size: it fills the stacks to
  whatever they are, and takes every exit with the return stack full.
- Measured on the host node now: 28 values on a line, 29 waiting between
  lines, words 31 deep from the prompt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-04 18:29:10 -04:00
co-authored by Claude Opus 5.5
parent 0da7e32a0b
commit ebffa6082d
8 changed files with 133 additions and 58 deletions
+15 -12
View File
@@ -177,7 +177,8 @@ definition below depends on one, it says so.
| **D-13** | Pictured-output hold buffer: size and errors (ruled 2026-10-03). | **63 characters, as v3; on error set `NODE-ERROR` and drop the character.** The buffer holds 63 characters at either cell width. `HOLD` of a value outside 0–255, or into a full buffer, stores nothing and sets `NODE-ERROR` (§7), which is v3's behaviour (it set its error flag and dropped the character). A full double in base 2 therefore does not fit, as in v3. |
| **D-14** | An address outside the node's memory (ruled 2026-10-04). | **Guarded: an address fault.** Every address a running programme uses is checked before it is used — `P` when an instruction word or an `@p` literal is fetched or `!p` stores, `A` for `@ @+ ! !+`, `B` for `@b !b`. Outside `0 … memory size − 1` the opcode does nothing (no fetch, no store, `A` and `B` untouched), the rest of its instruction word is not executed, both stacks are emptied (D-16), and `P` becomes the node's **fault handler** for that kind of fault. The handler does not return to the programme. On the host node the handler is `(FAULT)` (`v4/capsule/quit.v4`): it prints `Address out of range`, ends any definition that was open, prints ` ERROR` and returns to the prompt, from however deep the fault was. A node with no handler stops. v3 printed ` ERROR` alone. What a mesh node's handler does — it has no console — comes with the mesh (step 2). Executed on the golden model (2026-10-04): `tests/test_exec.c` for every memory opcode and for `P`, `tests/test_host_quit.c` from the prompt. |
| **D-15** | Division by zero in `/` `MOD` `/MOD` `*/` `*/MOD` `M/MOD` (ruled 2026-10-04). | **Guarded: the word reports it and the line ends.** Each of these words tests its divisor before anything else. If it is zero the word prints v3's message, its own name and `: Division by zero`, takes its operands off the stack as v3 does, ends any definition that was open, prints ` ERROR` and returns to the prompt, from however deep; its caller is not returned to. `M/MOD` says so too, where v3 printed ` ERROR` alone. Source in `v4/capsule/forth.v4`; executed on the golden model's host node (2026-10-04), including transcripts of the v3 binary. `Q./` keeps D-11 (saturate and flag). `UM/MOD` and `SM/REM` are internal and unguarded: their callers have checked. What a mesh node, with no console, does on a zero divisor comes with the mesh (step 2); §4's definitions, which the mesh-node tests execute, still leave it unspecified. |
| **D-16** | Stack overflow and underflow; `DEPTH`, `PICK`, `ROLL` (ruled 2026-10-04; revises D-2). | **Guarded: a stack fault.** Each stack counts what it holds. Before every opcode the node checks that the stacks hold what the opcode takes — including a `T` or `S` it only reads — and have room for what it leaves. If not, the opcode does nothing and the node faults exactly as for a bad address (D-14), to that kind's handler: `Stack overflow`, `Stack underflow`, `Return stack overflow`, `Return stack underflow`, then ` ERROR` and the prompt. **Every fault, D-14's included, empties both stacks**: the handler does not return, and what a word stopped part-way has left on the data stack is of no use to its caller. (v3 keeps what the failing word had not taken, and names the word: `DROP: Stack underflow`.) The fault handler is a table of five words, one per kind, each a jump (`(FAULTS)` in `v4/capsule/quit.v4`). Two registers (§7) are all a programme sees of the stacks: `DSTACK-DEPTH` and `RSTACK-DEPTH` read as the depth, and a store to one empties that stack. There is still no stack pointer and no address for a stack cell, so `SP@` and `SP!` stay retired; `.S` waits for number output to reach the capsule. The sizes are unchanged, 10 and 9: one more value, or one more level of call, is now an error message where it used to be silent corruption. Executed on the golden model (2026-10-04): `tests/test_exec.c` for every opcode at every depth of both stacks, `tests/test_host_quit.c` from the prompt. |
| **D-16** | Stack overflow and underflow; `DEPTH`, `PICK`, `ROLL` (ruled 2026-10-04; revises D-2). | **Guarded: a stack fault.** Each stack counts what it holds. Before every opcode the node checks that the stacks hold what the opcode takes — including a `T` or `S` it only reads — and have room for what it leaves. If not, the opcode does nothing and the node faults exactly as for a bad address (D-14), to that kind's handler: `Stack overflow`, `Stack underflow`, `Return stack overflow`, `Return stack underflow`, then ` ERROR` and the prompt. **Every fault, D-14's included, empties both stacks**: the handler does not return, and what a word stopped part-way has left on the data stack is of no use to its caller. (v3 keeps what the failing word had not taken, and names the word: `DROP: Stack underflow`.) The fault handler is a table of five words, one per kind, each a jump (`(FAULTS)` in `v4/capsule/quit.v4`). Two registers (§7) are all a programme sees of the stacks: `DSTACK-DEPTH` and `RSTACK-DEPTH` read as the depth, and a store to one empties that stack. There is still no stack pointer and no address for a stack cell, so `SP@` and `SP!` stay retired; `.S` waits for number output to reach the capsule. The sizes were unchanged by this ruling, 10 and 9 (D-17 then deepens the host node's): one more value, or one more level of call, is now an error message where it used to be silent corruption. Executed on the golden model (2026-10-04): `tests/test_exec.c` for every opcode at every depth of both stacks, `tests/test_host_quit.c` from the prompt. |
| **D-17** | Stack sizes on the host node (2026-10-04, following D-16). | **32 values and 32 return entries on the host node; a mesh node keeps the F18's 10 and 9.** Once the stacks are counted (D-16) their size is a parameter of the node, like its memory, and the host node is the one that runs the interpreter and the compiler underneath the user's programme: at 10 and 9 the prompt left a programme about six values, and `/` could be used only four words deep. The mechanism is the same at both sizes — top registers over a ring — and so is every word's definition. v3's stacks are deeper still. In the golden model the sizes are `V4_DATA_RING` and `V4_RET_RING` (`stack.h`), set for the host-node tests in `v4/Makefile`. |
**Consequences of D-2 that every definition must respect.** The data stack holds 10 items and the
return stack 9, and every `call`, `FOR`, `DO` loop frame and `push` uses return-stack slots. Nesting
@@ -314,7 +315,7 @@ Section numbers match the v3 primitive reference.
| `ROT` | CAP | §4. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Leaves its caller 6 data cells and 4 return entries; in line, with `SWAP` in line, it calls nothing. |
| `-ROT` | CAP | `SWAP push SWAP pop`, `SWAP` in line (`ROT ROT` in one pass). Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Leaves its caller 6 data cells and 5 return entries. |
| `DEPTH` | CAP | FORTH-79: how many values were on the stack before `DEPTH`. `DSTACK-DEPTH b! @b` (D-16). Source in `v4/capsule/forth.v4`; executed on the golden model's host node (2026-10-04), including a transcript of the v3 binary. |
| `PICK` | CAP | FORTH-79 (D-7): `( n -- x )`, a copy of the n-th value counting from one; `1 PICK` is `DUP`, `2 PICK` is `OVER`. The stack has no addresses, so the `n - 1` values above the one wanted are set aside in memory and put back. `n < 1` prints `PICK: Invalid index`; `n` greater than the depth is a stack underflow fault. It works with the stack full (nine values and `n`). v3's `PICK` counts from zero. Source in `v4/capsule/forth.v4`; executed on the golden model's host node (2026-10-04). |
| `PICK` | CAP | FORTH-79 (D-7): `( n -- x )`, a copy of the n-th value counting from one; `1 PICK` is `DUP`, `2 PICK` is `OVER`. The stack has no addresses, so the `n - 1` values above the one wanted are set aside in memory and put back. `n < 1` prints `PICK: Invalid index`; `n` greater than the depth is a stack underflow fault. It works with the stack full (every cell but one holding a value, and `n`). v3's `PICK` counts from zero. Source in `v4/capsule/forth.v4`; executed on the golden model's host node (2026-10-04). |
| `ROLL` | CAP | FORTH-79 (D-7): `( n -- )`, the n-th value counting from one is taken out and put on top; `3 ROLL` is `ROT`, `1 ROLL` does nothing. Built as `PICK` is, with the same errors. v3's `ROLL` counts from zero. Source in `v4/capsule/forth.v4`; executed on the golden model's host node (2026-10-04). |
### 5.2 Return stack
@@ -383,7 +384,7 @@ Section numbers match the v3 primitive reference.
| `+` | OP | `+` |
| `-` | CAP | `NEGATE +`, `NEGATE` in line — executed on the golden model (2026-10-03), including a transcript of the v3 binary. |
| `*` | CAP | `UM* drop` — executed on the golden model (2026-10-03) against C's wrapping product. Leaves its caller 6 data cells and 5 return entries. |
| `/` | CAP | `/MOD NIP`, as `push S>D pop SM/REM push drop pop` (`/MOD`'s body and `NIP` in line, so it runs no deeper than `/MOD`). Truncates toward zero, as v3. Executed on the golden model (2026-10-03) against C wherever the quotient fits a cell. Leaves its caller 5 data cells and 2 return entries. Division by zero is guarded on the host node (D-15). **On the host node** (`v4/capsule/forth.v4`) `SM/REM` has `UM/MOD` written into it and keeps its two signs in memory, not on the return stack, so `/`, `MOD` and `/MOD` work from four words deep at the prompt, and `*/` and `*/MOD`, with `M*` written in, from two and three; the answers are the same, checked against C on every pair of the edge values. |
| `/` | CAP | `/MOD NIP`, as `push S>D pop SM/REM push drop pop` (`/MOD`'s body and `NIP` in line, so it runs no deeper than `/MOD`). Truncates toward zero, as v3. Executed on the golden model (2026-10-03) against C wherever the quotient fits a cell. Leaves its caller 5 data cells and 2 return entries. Division by zero is guarded on the host node (D-15). **On the host node** (`v4/capsule/forth.v4`) `SM/REM` has `UM/MOD` written into it and keeps its two signs in memory, not on the return stack, so that at nine return entries `/`, `MOD` and `/MOD` worked from four words deep at the prompt, and `*/` and `*/MOD`, with `M*` written in, from two and three (with D-17's 32 entries there is room to spare); the answers are the same, checked against C on every pair of the edge values. |
| `MOD` `/MOD` `*/` `*/MOD` (§4 versions) | RET | Shadowed duplicates. Only the §6 versions survive. |
| `1+` `1-` `2+` `2-` | IN | `1 +`, `-1 +`, `2 +`, `-2 +` — executed on the golden model (2026-10-03), including a transcript of the v3 binary. |
| `2*` | OP | `2*` |
@@ -753,21 +754,23 @@ call would bury what it works on. A **compile-only** word may not be executed by
`v4/capsule/forth.v4` holds the first words of the vocabulary flagged this way (`DUP`, `+`, `>R`, `I`,
`LEAVE`, `@`, the in-line variables and so on).
**The short stacks (D-2, D-16) and the compiler.** The interpreter and compiler run on the same ten-cell and
nine-entry stacks as the user's words, with the user's values beneath them. Two consequences are
built into the capsule:
**The stacks and the compiler.** The interpreter and compiler run on the same stacks as the user's
words, with the user's values beneath them. The capsule was written for stacks of ten cells and nine
entries (D-2), and is as sparing as that needed; the host node's are now 32 and 32 (D-17). The two
habits built into the capsule stay, and the measurements below are for the host node as it is now:
- Every word of the capsule keeps what it works on in memory and has at most three cells of its own on
the data stack. Measured on the golden model: a line may have **6 values on the stack** while the
interpreter reads its next word, 6 may wait there while the next line is typed, and defining and
running a word leaves 4 cells under it untouched.
the data stack. Measured on the golden model: a line may have **28 values on the stack** while the
interpreter reads its next word, 29 may wait there while the next line is typed and interpreted
(the prompt and the interpreter take no more than four cells), and defining and running a word
leaves 26 cells under it untouched. (At ten cells these were 6, 6 and 4.)
- The capsule's words call each other as little as they can: byte access shifts without a loop,
opcodes are shifted into the word being built instead of placed by a counted shift, and the long
jobs (laying a loop's end, making a data word) are single words reached by a jump. Measured on the
golden model: the prompt has one return entry under each line, its call of `INTERPRET`, so words
run from the prompt may call each other **8 deep**; a line that runs a word which calls nothing
leaves 5 of the nine entries untouched; every kind of line leaves at least 3 spare while it is
compiled, and a defining word (`CREATE … DOES>`) works from the prompt, from a word, and from a
run from the prompt may call each other **31 deep** (8 deep at nine entries), and one more is
reported as a return stack overflow; a line that runs a word which calls nothing uses four
entries; and a defining word (`CREATE … DOES>`) works from the prompt, from a word, and from a
word that calls that. A `DO` loop takes two return
entries. `*` is therefore not `UM* drop` on the host node but N steps of `+*` with
only the count on the return stack; it gives the same low cell (what `+*` loses at the top of `T`