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:
co-authored by
Claude Opus 5.5
parent
b5d644d498
commit
b3d2d56d11
+46
-31
@@ -80,6 +80,12 @@ typedef struct {
|
||||
unsigned input_len; /* characters fed */
|
||||
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 */
|
||||
unsigned faults; /* how many there have been */
|
||||
int stopped; /* non-zero: faulted with no handler */
|
||||
} v4_node;
|
||||
|
||||
/* Zero P, A and B, empty the stacks, and clear memory. Installs every
|
||||
@@ -97,37 +103,16 @@ int v4_node_guards_intact(const v4_node *n);
|
||||
* and a narrower parameter would put a conversion between the register and the
|
||||
* access on every one of the eight memory opcodes.
|
||||
*
|
||||
* PRECONDITION: 0 <= addr < V4_NODE_WORDS.
|
||||
* An address outside 0 .. V4_NODE_WORDS-1 is never used to index memory
|
||||
* (D-14, ruled 2026-10-04): v4_node_load gives 0 for it and v4_node_store
|
||||
* does nothing. That is all these two do. They are what C code uses -- the
|
||||
* assemblers, the tests -- and they do not count a fault; the executor does,
|
||||
* for the addresses a running programme uses (see v4_node_fault_attach).
|
||||
*
|
||||
* Out-of-range addressing is not defined by DECOMPOSITION.md. D-1 fixes word
|
||||
* addressing and says nothing about an address outside the node's memory; a
|
||||
* F18 has no such case because its address space is the memory. Whether a
|
||||
* conforming model should wrap, saturate, or fault is a real open question and
|
||||
* is raised as a gap rather than decided here, because each answer changes the
|
||||
* ISA's observable behaviour and inventing one silently would bake a guess into
|
||||
* every later test.
|
||||
*
|
||||
* WHAT THE BOUNDARY HERE DOES AND DOES NOT COVER, since it is easy to
|
||||
* overclaim and the distinction decides whether out-of-range addresses are
|
||||
* actually protected:
|
||||
*
|
||||
* - It DOES cover a linear index error. An access to mem[-1] or
|
||||
* mem[V4_NODE_WORDS] lands in the 5% band, because the band is immediately
|
||||
* adjacent (tests/test_node.c asserts the adjacency with offsetof rather
|
||||
* than assuming it). That access is inside the struct, so AddressSanitizer
|
||||
* stays silent about it and v4_node_guards_intact() is what reports it.
|
||||
*
|
||||
* - It does NOT cover an out-of-range *word address*. A v4_cell address of
|
||||
* -1 casts to unsigned 0xFFFFFFFF, which is 16 GB past mem at 32-bit cells
|
||||
* -- far outside the struct and outside the band. So a P, A or B that runs
|
||||
* off the end of memory is caught by neither the band nor, in a production
|
||||
* build without sanitizers, by anything else here. It is a precondition
|
||||
* violation and the range check that would catch it is the open question
|
||||
* above, not the band.
|
||||
*
|
||||
* The band is worth its 10.2% for the first case regardless. The second case
|
||||
* is a hole, and it is closed by ruling on out-of-range addressing, not by
|
||||
* widening the band. */
|
||||
* The 5% band on each side of mem is a different protection: it is where a
|
||||
* linear index error in the model's own C -- mem[-1], mem[V4_NODE_WORDS] --
|
||||
* would land, inside the struct where AddressSanitizer cannot see it, and
|
||||
* v4_node_guards_intact() reports it. */
|
||||
v4_cell v4_node_load(const v4_node *n, v4_cell addr);
|
||||
void v4_node_store(v4_node *n, v4_cell addr, v4_cell value);
|
||||
|
||||
@@ -182,7 +167,37 @@ void v4_node_console_input_attach(v4_node *n, v4_cell rx, v4_cell status);
|
||||
unsigned v4_node_console_feed(v4_node *n, const void *chars, unsigned len);
|
||||
|
||||
/* A data fetch: what @, @+ and @b read at `addr`. The two receive registers
|
||||
* above when attached, memory otherwise. Same precondition as v4_node_load. */
|
||||
* above when attached, memory otherwise; 0 outside memory, as v4_node_load. */
|
||||
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).
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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);
|
||||
|
||||
/* 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);
|
||||
|
||||
#endif /* V4_NODE_H */
|
||||
|
||||
Reference in New Issue
Block a user