feat(v4.0.0): the stacks are guarded (D-16); DEPTH, PICK and ROLL

Ruled 2026-10-04, revising D-2: stack overflow and underflow are errors
that are shown and return to the prompt, not silent wrap-around.

- Each stack counts what it holds.  Before every opcode the executor
  checks that the stacks hold what it takes and have room for what it
  leaves; otherwise the opcode does nothing and the node faults, as for a
  bad address, to that kind's handler.  Every fault empties both stacks.
- The fault handler is now a table of five jumps: address, data overflow,
  data underflow, return overflow, return underflow.  The host node says
  "Stack overflow", "Stack underflow", "Return stack overflow",
  "Return stack underflow", then ERROR and the prompt.
- Two registers, DSTACK-DEPTH and RSTACK-DEPTH: a fetch reads the depth,
  a store empties the stack.  QUIT, ABORT and the error exits empty the
  return stack before they call anything; ABORT empties the data stack.
- capsule/forth.v4: DEPTH, PICK and ROLL, to FORTH-79 (counting from
  one).  PICK and ROLL set the values above the one wanted aside in
  memory, and work with the stack full.
- Division by zero now takes its operands off the stack, as v3 does.
- A colon with no room for its entry abandons the line.
- tests: every opcode at every depth of both stacks; the faults, the
  registers and the three words from the prompt.

The sizes are unchanged: ten values, nine return entries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-04 17:31:18 -04:00
co-authored by Claude Opus 5.5
parent 361dcd1148
commit 0da7e32a0b
16 changed files with 542 additions and 106 deletions
+58 -26
View File
@@ -81,11 +81,16 @@ typedef struct {
unsigned input_pos; /* characters taken; input_len - input_pos are pending */
unsigned char input[V4_CONSOLE_CAP];
/* Address faults (D-14). See v4_node_fault_attach below. */
v4_cell fault_vector; /* word address of the handler, or -1: none */
v4_cell fault_addr; /* the address of the latest fault */
/* Faults (D-14, D-16). See v4_node_fault_attach below. */
v4_cell fault_vector; /* word address of the handler table, or -1: none */
v4_cell fault_addr; /* the address of the latest address fault */
unsigned fault_kind; /* which kind the latest fault was (V4_FAULT_*) */
unsigned faults; /* how many there have been */
int stopped; /* non-zero: faulted with no handler */
/* DSTACK-DEPTH and RSTACK-DEPTH (D-16). See v4_node_stack_regs_attach. */
v4_cell dstack_reg; /* its word address, or -1 */
v4_cell rstack_reg; /* its word address, or -1 */
} v4_node;
/* Zero P, A and B, empty the stacks, and clear memory. Installs every
@@ -173,31 +178,58 @@ v4_cell v4_node_fetch(v4_node *n, v4_cell addr);
/* 1 if `addr` is a word of node memory. */
int v4_node_addr_ok(v4_cell addr);
/* ADDRESS FAULTS (DECOMPOSITION.md D-14, ruled 2026-10-04: an address outside
* the node's memory is guarded, an error is shown, and the node returns to
* its prompt).
/* FAULTS (DECOMPOSITION.md D-14 and D-16, ruled 2026-10-04: an address
* outside the node's memory, and a stack pushed when full or popped when
* empty, are guarded; an error is shown; the node returns to its prompt).
*
* The executor checks every address a programme uses before using it: P when
* an instruction word or an `@p` literal is fetched or `!p` stores, A for
* `@ @+ ! !+`, B for `@b !b`. If it is outside memory there is a fault:
* - the opcode does nothing: no fetch, no store, the stacks and A and B
* untouched, nothing retired; the rest of its instruction word is not
* executed;
* - fault_addr is the address and faults is one more;
* - P becomes fault_vector, the address of the node's handler. Nothing is
* pushed: the handler does not return to the programme, it reports and
* goes back to the prompt (capsule/quit.v4, (FAULT)).
* With no handler attached (fault_vector -1) the node stops instead:
* `stopped` is set and v4_exec_step_word does nothing more until the node is
* reset or a handler is attached.
* The executor checks before every opcode (exec.h):
* - that the stacks hold what the opcode takes and have room for what it
* leaves;
* - every address a programme uses: P when an instruction word or an `@p`
* literal is fetched or `!p` stores, A for `@ @+ ! !+`, B for `@b !b`.
* If a check fails there is a fault:
* - the opcode does nothing, and the rest of its instruction word is not
* executed; A and B are untouched;
* - fault_kind says which, fault_addr is the address for an address fault,
* and faults is one more;
* - both stacks are emptied. The handler does not return to the
* programme, and what a word stopped in the middle of its work has left
* on the data stack is not something its caller can use;
* - P becomes fault_vector + fault_kind. The handler table is five words,
* one for each kind in the order below, each a jump to that kind's
* handler (capsule/quit.v4, (FAULTS)).
* With no table attached (fault_vector -1) the node stops instead: `stopped`
* is set and v4_exec_step_word does nothing more until the node is reset or
* a table is attached.
*
* v4_node_reset detaches the handler and clears the count. Attaching clears
* `stopped` and the count; `handler` must be a word of memory, or -1 to
* detach. */
void v4_node_fault_attach(v4_node *n, v4_cell handler);
* v4_node_reset detaches the table and clears the count. Attaching clears
* `stopped` and the count; all five words of the table must be in memory,
* or `table` -1 to detach. */
#define V4_FAULT_ADDRESS 0u /* an address outside memory (D-14) */
#define V4_FAULT_DATA_OVER 1u /* a push onto a full data stack */
#define V4_FAULT_DATA_UNDER 2u /* the data stack holds too little for the opcode */
#define V4_FAULT_RET_OVER 3u /* a push onto a full return stack */
#define V4_FAULT_RET_UNDER 4u /* the return stack holds too little for the opcode */
#define V4_FAULT_KINDS 5u
/* For the executor: record a fault at `addr` and redirect or stop the node,
* as above. */
void v4_node_fault(v4_node *n, v4_cell addr);
void v4_node_fault_attach(v4_node *n, v4_cell table);
/* For the executor: record a fault of `kind` (at `addr`, for an address
* fault) and redirect or stop the node, as above. */
void v4_node_fault(v4_node *n, unsigned kind, v4_cell addr);
/* DSTACK-DEPTH and RSTACK-DEPTH, the stack registers (D-16).
*
* After v4_node_stack_regs_attach(n, d, r):
* a data fetch from `d` gives how many values the data stack holds (the
* fetch's own push not counted), and from `r` how many entries the return
* stack holds;
* a store to `d` empties the data stack (the value stored is taken off
* first and ignored), and a store to `r` empties the return stack.
* Neither reads or writes n->mem. This is all a programme can see of the
* stacks: there is still no stack pointer and no address for a stack cell.
* DEPTH reads the first; QUIT and ABORT store to them. v4_node_reset
* detaches both (-1); the addresses are the caller's choice (D-4). */
void v4_node_stack_regs_attach(v4_node *n, v4_cell d, v4_cell r);
#endif /* V4_NODE_H */