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
@@ -316,6 +316,110 @@ static void test_soak(void)
|
||||
CHECK(v4_node_guards_intact(&n), "soak left the guards intact");
|
||||
}
|
||||
|
||||
/* D-14: an address outside node memory is a fault. The opcode does nothing,
|
||||
* the rest of its word is not executed, and P becomes the handler; with no
|
||||
* handler the node stops. */
|
||||
#define HANDLER ((v4_cell)40)
|
||||
static void test_address_faults(void)
|
||||
{
|
||||
static const v4_cell bad[] = { -1, (v4_cell)V4_NODE_WORDS, (v4_cell)V4_NODE_WORDS + 1, MINC, MAXC, (v4_cell)(V4_NODE_WORDS * 4u) };
|
||||
static const struct { unsigned op; int reg; const char *name; } m[] = {
|
||||
{ V4_OP_FETCH_A, 'a', "@" }, { V4_OP_FETCH_INC, 'a', "@+" }, { V4_OP_FETCH_B, 'b', "@b" },
|
||||
{ V4_OP_STORE_A, 'a', "!" }, { V4_OP_STORE_INC, 'a', "!+" }, { V4_OP_STORE_B, 'b', "!b" },
|
||||
};
|
||||
unsigned i, k;
|
||||
|
||||
for (i = 0; i < sizeof bad / sizeof bad[0]; i++)
|
||||
for (k = 0; k < sizeof m / sizeof m[0]; k++) {
|
||||
unsigned retired;
|
||||
fresh();
|
||||
v4_node_fault_attach(&n, HANDLER);
|
||||
dpush(11); dpush(22);
|
||||
n.a = m[k].reg == 'a' ? bad[i] : 7;
|
||||
n.b = m[k].reg == 'b' ? bad[i] : 7;
|
||||
n.mem[0] = word6(V4_OP_DUP, m[k].op, V4_OP_DROP, V4_OP_DROP, NOP, NOP);
|
||||
n.p = 0;
|
||||
retired = v4_exec_step_word(&n, &es, &h);
|
||||
CHECK(n.faults == 1 && n.fault_addr == bad[i] && !n.stopped, "%s at %lld is a fault", m[k].name, (long long)bad[i]);
|
||||
CHECK(n.p == HANDLER, "%s: P is the handler", m[k].name);
|
||||
CHECK(retired == 1, "%s: only the dup before it retired (%u)", m[k].name, retired);
|
||||
CHECK(n.ds.t == 22 && n.ds.s == 22, "%s: it neither pushed nor popped, and the drops after it did not run", m[k].name);
|
||||
CHECK(n.a == (m[k].reg == 'a' ? bad[i] : 7) && n.b == (m[k].reg == 'b' ? bad[i] : 7), "%s: A and B untouched", m[k].name);
|
||||
CHECK(v4_node_guards_intact(&n), "%s: guards intact", m[k].name);
|
||||
}
|
||||
|
||||
/* the last word of memory is not a fault, and the first is not */
|
||||
fresh(); v4_node_fault_attach(&n, HANDLER);
|
||||
n.mem[V4_NODE_WORDS - 1u] = 77; n.a = (v4_cell)(V4_NODE_WORDS - 1u); run1(V4_OP_FETCH_A);
|
||||
CHECK(n.faults == 0 && n.ds.t == 77, "the last word of memory can be fetched");
|
||||
fresh(); v4_node_fault_attach(&n, HANDLER);
|
||||
dpush(5); n.b = (v4_cell)(V4_NODE_WORDS - 1u); run1(V4_OP_STORE_B);
|
||||
CHECK(n.faults == 0 && n.mem[V4_NODE_WORDS - 1u] == 5, "and stored");
|
||||
|
||||
/* P outside memory: nothing is fetched or executed */
|
||||
for (i = 0; i < sizeof bad / sizeof bad[0]; i++) {
|
||||
fresh(); v4_node_fault_attach(&n, HANDLER);
|
||||
dpush(1);
|
||||
n.p = bad[i];
|
||||
CHECK(v4_exec_step_word(&n, &es, &h) == 0 && n.faults == 1 && n.fault_addr == bad[i] && n.p == HANDLER && n.ds.t == 1,
|
||||
"P at %lld is a fault", (long long)bad[i]);
|
||||
}
|
||||
/* @p in the last word of memory: its literal would be past the end */
|
||||
fresh(); v4_node_fault_attach(&n, HANDLER);
|
||||
dpush(1);
|
||||
n.mem[V4_NODE_WORDS - 1u] = word6(V4_OP_FETCH_P, NOP, NOP, NOP, NOP, NOP);
|
||||
n.p = (v4_cell)(V4_NODE_WORDS - 1u);
|
||||
CHECK(v4_exec_step_word(&n, &es, &h) == 0 && n.faults == 1 && n.fault_addr == (v4_cell)V4_NODE_WORDS && n.p == HANDLER && n.ds.t == 1,
|
||||
"a literal past the end of memory is a fault");
|
||||
fresh(); v4_node_fault_attach(&n, HANDLER);
|
||||
dpush(1);
|
||||
n.mem[V4_NODE_WORDS - 1u] = word6(V4_OP_STORE_P, NOP, NOP, NOP, NOP, NOP);
|
||||
n.p = (v4_cell)(V4_NODE_WORDS - 1u);
|
||||
CHECK(v4_exec_step_word(&n, &es, &h) == 0 && n.faults == 1 && n.p == HANDLER && n.ds.t == 1, "and so is !p there");
|
||||
|
||||
/* a jump out of memory faults at the next fetch */
|
||||
fresh(); v4_node_fault_attach(&n, HANDLER);
|
||||
rpush(-5);
|
||||
n.mem[0] = word6(V4_OP_SEMI, NOP, NOP, NOP, NOP, NOP);
|
||||
n.p = 0;
|
||||
CHECK(v4_exec_step_word(&n, &es, &h) == 1 && n.p == -5 && n.faults == 0, "; to an address outside memory is not yet a fault");
|
||||
CHECK(v4_exec_step_word(&n, &es, &h) == 0 && n.p == HANDLER && n.faults == 1 && n.fault_addr == -5, "the fetch there is");
|
||||
|
||||
/* the handler runs, and faults are counted */
|
||||
fresh(); v4_node_fault_attach(&n, HANDLER);
|
||||
n.mem[HANDLER] = word6(V4_OP_DUP, V4_OP_ADD, NOP, NOP, NOP, NOP);
|
||||
dpush(21); n.a = -1; run1(V4_OP_FETCH_A);
|
||||
CHECK(v4_exec_step_word(&n, &es, &h) == 6 && n.ds.t == 42, "the handler's code runs next");
|
||||
n.a = -1; run1(V4_OP_FETCH_A); n.a = -1; run1(V4_OP_STORE_A);
|
||||
CHECK(n.faults == 3, "each fault is counted");
|
||||
v4_node_fault_attach(&n, HANDLER);
|
||||
CHECK(n.faults == 0 && n.fault_vector == HANDLER, "attaching clears the count");
|
||||
|
||||
/* with no handler the node stops */
|
||||
fresh();
|
||||
CHECK(n.fault_vector == -1 && n.faults == 0 && !n.stopped, "a fresh node has no handler and has not stopped");
|
||||
dpush(9); n.a = -1;
|
||||
CHECK(run1(V4_OP_FETCH_A) == 0 && n.stopped && n.faults == 1 && n.ds.t == 9, "with no handler a fault stops the node");
|
||||
n.mem[0] = word6(V4_OP_DUP, V4_OP_ADD, NOP, NOP, NOP, NOP); n.p = 0;
|
||||
CHECK(v4_exec_step_word(&n, &es, &h) == 0 && n.p == 0 && n.ds.t == 9, "a stopped node executes nothing");
|
||||
v4_node_fault_attach(&n, HANDLER);
|
||||
CHECK(!n.stopped && v4_exec_step_word(&n, &es, &h) == 6 && n.ds.t == 18, "attaching a handler lets it run again");
|
||||
v4_node_fault_attach(&n, (v4_cell)V4_NODE_WORDS);
|
||||
CHECK(n.fault_vector == -1, "a handler address outside memory detaches");
|
||||
v4_node_reset(&n);
|
||||
CHECK(n.fault_vector == -1 && !n.stopped && n.faults == 0, "reset detaches the handler");
|
||||
|
||||
/* the C accessors never index outside memory */
|
||||
fresh();
|
||||
for (i = 0; i < sizeof bad / sizeof bad[0]; i++) {
|
||||
v4_node_store(&n, bad[i], 123);
|
||||
CHECK(v4_node_load(&n, bad[i]) == 0 && v4_node_fetch(&n, bad[i]) == 0 && !v4_node_addr_ok(bad[i]),
|
||||
"load, fetch and store at %lld touch nothing", (long long)bad[i]);
|
||||
}
|
||||
CHECK(n.faults == 0 && v4_node_guards_intact(&n) && v4_node_addr_ok(0) && v4_node_addr_ok((v4_cell)(V4_NODE_WORDS - 1u)),
|
||||
"and they are not faults: only a running programme faults");
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
printf("v4 exec tests: V4_CELL_BITS=%d\n", V4_CELL_BITS);
|
||||
@@ -332,6 +436,7 @@ int main(void)
|
||||
#if V4_CELL_BITS == 64
|
||||
test_iword_in_wide_cell();
|
||||
#endif
|
||||
test_address_faults();
|
||||
test_soak();
|
||||
|
||||
printf(" %d checks, %d failures\n", checks, failures);
|
||||
|
||||
Reference in New Issue
Block a user