feat(v4): a hook at call, for whoever runs the node
Plan 2026-10-20 (ENGINE step 4), Task A1, for #189 -- Captain Bob, 2026-11-12: the physics blocks alpha.5; order A, 1, 2, then 7. - exec.h/exec.c: v4_call_hook, es->on_call/on_call_ctx; called at `call` once the return address is pushed, before P moves. NULL: as before. - heat.h/heat.c: the per-call-target table (4096 slots, no capsule word counted), its freeze mask and v4_heat_call_* go (ENGINE.md step 3: "The per-call-target count goes"); the kernel will keep a word's accounts. - tests: test_call_hook_sees_every_call, seen to fail first (no on_call); test_heat's per-call tests go with the table. - the plan records today's rulings (order; HEAT-OP: both, a full array). Suite (logs/20261112-225951/v4-tests): test, test-32, sanitize, 0 failures; hosted-check, the three alike. amd64 boot: POST 538/538, dict_hash 0x8c92508f748f99cf, unchanged -- nothing sets the hook yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017RzZgrf7V7Zk2iczBXDRbm
This commit is contained in:
+54
-1
@@ -207,7 +207,7 @@ static void test_branches(void)
|
||||
fresh(); rpush(7); n.mem[10] = bword(0, V4_OP_CALL, 200); n.p = 10;
|
||||
(void)v4_exec_step_word(&n, &es, &h);
|
||||
CHECK(n.p == 200 && n.rs.r == 11, "call pushes the return address");
|
||||
CHECK(v4_heat_call_get(&h, 200) == 1 && h.op[V4_OP_CALL] == 1, "call heats its target");
|
||||
CHECK(h.op[V4_OP_CALL] == 1, "call is counted as an opcode (its target: the hook, below)");
|
||||
(void)v4_rstack_pop(&n.rs);
|
||||
CHECK(n.rs.r == 7, "call pushes exactly one");
|
||||
|
||||
@@ -570,6 +570,58 @@ static void test_address_faults(void)
|
||||
"and they are not faults: only a running programme faults");
|
||||
}
|
||||
|
||||
/* ENGINE.md step 3 (plan 2026-10-20, Task A1): a hook at `call`, for whoever runs the node -- the
|
||||
* kernel keeps a word's accounts there (#189). It sees every call, in order, once the return address
|
||||
* is pushed and before P moves; with no hook the node runs exactly as before. */
|
||||
static v4_cell seen[8];
|
||||
static unsigned nseen, hook_bad;
|
||||
static void record_call(void *ctx, v4_node *nd, v4_cell target)
|
||||
{
|
||||
if (ctx != (void *)&seen || nd != &n) hook_bad++;
|
||||
if (nd->p != nd->rs.r) hook_bad++; /* P not moved yet: still the return address */
|
||||
if (nseen < 8) seen[nseen] = target;
|
||||
nseen++;
|
||||
}
|
||||
|
||||
static void call_programme(void) /* 100, 200, 100 again, 300; then a halt loop */
|
||||
{
|
||||
fresh();
|
||||
n.mem[10] = bword(0, V4_OP_CALL, 100);
|
||||
n.mem[11] = bword(0, V4_OP_CALL, 200);
|
||||
n.mem[12] = bword(0, V4_OP_CALL, 100);
|
||||
n.mem[13] = bword(0, V4_OP_CALL, 300);
|
||||
n.mem[14] = bword(0, V4_OP_JUMP, 14);
|
||||
n.mem[100] = word6(V4_OP_SEMI, NOP, NOP, NOP, NOP, NOP);
|
||||
n.mem[200] = word6(V4_OP_SEMI, NOP, NOP, NOP, NOP, NOP);
|
||||
n.mem[300] = word6(V4_OP_SEMI, NOP, NOP, NOP, NOP, NOP);
|
||||
n.p = 10;
|
||||
}
|
||||
|
||||
static void test_call_hook_sees_every_call(void)
|
||||
{
|
||||
unsigned i, retired_hooked = 0, retired_plain = 0;
|
||||
v4_cell p_hooked, p_plain;
|
||||
unsigned depth_hooked, depth_plain;
|
||||
|
||||
call_programme();
|
||||
es.on_call = record_call; es.on_call_ctx = (void *)&seen;
|
||||
nseen = 0; hook_bad = 0;
|
||||
for (i = 0; i < 9; i++) retired_hooked += v4_exec_step_word(&n, &es, &h);
|
||||
p_hooked = n.p; depth_hooked = n.rs.depth;
|
||||
CHECK(nseen == 4, "the hook sees four calls (%u)", nseen);
|
||||
CHECK(nseen >= 4 && seen[0] == 100 && seen[1] == 200 && seen[2] == 100 && seen[3] == 300,
|
||||
"the hook sees 100, 200, 100, 300, in order");
|
||||
CHECK(hook_bad == 0, "the hook is called with its context and the node, before P moves (%u wrong)", hook_bad);
|
||||
|
||||
call_programme(); /* the same with no hook */
|
||||
for (i = 0; i < 9; i++) retired_plain += v4_exec_step_word(&n, &es, &h);
|
||||
p_plain = n.p; depth_plain = n.rs.depth;
|
||||
CHECK(p_plain == p_hooked && depth_plain == depth_hooked && retired_plain == retired_hooked,
|
||||
"with no hook the node runs the same (P %ld/%ld, depth %u/%u, retired %u/%u)",
|
||||
(long)p_plain, (long)p_hooked, depth_plain, depth_hooked, retired_plain, retired_hooked);
|
||||
CHECK(es.on_call == 0, "v4_exec_reset leaves no hook");
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
printf("v4 exec tests: V4_CELL_BITS=%d\n", V4_CELL_BITS);
|
||||
@@ -581,6 +633,7 @@ int main(void)
|
||||
test_memory();
|
||||
test_return_and_ex();
|
||||
test_branches();
|
||||
test_call_hook_sees_every_call();
|
||||
test_unext();
|
||||
test_retirement();
|
||||
#if V4_CELL_BITS == 64
|
||||
|
||||
Reference in New Issue
Block a user