feat(v4.0.0): address faults -- an address outside memory is guarded (D-14)

Ruled 2026-10-04: guarded, an error shown, back to the prompt.

- The executor checks every address a programme uses (P, A, B) before
  using it.  Outside memory the opcode does nothing, the rest of its word
  is not executed, and P becomes the node's fault handler; a node with no
  handler stops.  v4_node_load/store never index outside memory.
- capsule/quit.v4: (FAULT), the host node's handler, prints
  "Address out of range", ends an open definition, prints ERROR and
  returns to the prompt.
- tests: every memory opcode and P in test_exec.c; from the prompt, from
  inside nested words and loops, in test_host_quit.c.

This closes the hole node.h described: a wild address used to index the
model's own memory gigabytes out of bounds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-04 12:04:59 -04:00
co-authored by Claude Opus 5.5
parent b5d644d498
commit b3d2d56d11
11 changed files with 277 additions and 74 deletions
+23 -1
View File
@@ -35,6 +35,14 @@ static int ends_word(unsigned op)
return v4_op_is_branch(op) || op == V4_OP_SEMI || op == V4_OP_EX;
}
/* D-14: an address outside memory is a fault, and the opcode does nothing. */
static int faulted(v4_node *n, v4_cell addr)
{
if (v4_node_addr_ok(addr)) return 0;
v4_node_fault(n, addr);
return 1;
}
void v4_exec_reset(v4_exec_state *es)
{
es->anticlock = 0;
@@ -87,31 +95,39 @@ void v4_exec_op(v4_node *n, v4_exec_state *es, v4_heat *h,
break;
case V4_OP_FETCH_P:
if (faulted(n, n->p)) return;
v4_dstack_push(ds, v4_node_load(n, n->p));
n->p = add_wrap(n->p, 1);
break;
case V4_OP_FETCH_INC:
if (faulted(n, n->a)) return;
v4_dstack_push(ds, v4_node_fetch(n, n->a));
n->a = add_wrap(n->a, 1);
break;
case V4_OP_FETCH_B:
if (faulted(n, n->b)) return;
v4_dstack_push(ds, v4_node_fetch(n, n->b));
break;
case V4_OP_FETCH_A:
if (faulted(n, n->a)) return;
v4_dstack_push(ds, v4_node_fetch(n, n->a));
break;
case V4_OP_STORE_P:
if (faulted(n, n->p)) return;
v4_node_store(n, n->p, v4_dstack_pop(ds));
n->p = add_wrap(n->p, 1);
break;
case V4_OP_STORE_INC:
if (faulted(n, n->a)) return;
v4_node_store(n, n->a, v4_dstack_pop(ds));
n->a = add_wrap(n->a, 1);
break;
case V4_OP_STORE_B:
if (faulted(n, n->b)) return;
v4_node_store(n, n->b, v4_dstack_pop(ds));
break;
case V4_OP_STORE_A:
if (faulted(n, n->a)) return;
v4_node_store(n, n->a, v4_dstack_pop(ds));
break;
@@ -181,18 +197,24 @@ void v4_exec_op(v4_node *n, v4_exec_state *es, v4_heat *h,
unsigned v4_exec_step_word(v4_node *n, v4_exec_state *es, v4_heat *h)
{
v4_iword iw = fetch_iword(n, n->p);
v4_iword iw;
unsigned executed = 0;
unsigned slot = 0;
/* D-14: a node that faulted with no handler has stopped; and P itself
* may be what is outside memory. */
if (n->stopped || faulted(n, n->p)) return 0;
iw = fetch_iword(n, n->p);
n->p = add_wrap(n->p, 1);
while (slot < V4_SLOT_COUNT) {
unsigned op = v4_iword_op(iw, slot);
/* Decided before the opcode runs, because running it changes R. */
int again = (op == V4_OP_UNEXT) && (n->rs.r != 0);
unsigned faults = n->faults;
v4_exec_op(n, es, h, op, iw, slot);
if (n->faults != faults) break; /* P is the handler now, or the node has stopped */
executed++;
if (again) {