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:
co-authored by
Claude Opus 5.5
parent
361dcd1148
commit
0da7e32a0b
+58
-26
@@ -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 */
|
||||
|
||||
Reference in New Issue
Block a user