diff --git a/docs/v4.0.0/plans/2026-10-20-engine-step4-node-in-the-vms-place.md b/docs/v4.0.0/plans/2026-10-20-engine-step4-node-in-the-vms-place.md index e157950e..024a5693 100644 --- a/docs/v4.0.0/plans/2026-10-20-engine-step4-node-in-the-vms-place.md +++ b/docs/v4.0.0/plans/2026-10-20-engine-step4-node-in-the-vms-place.md @@ -55,6 +55,13 @@ minted, and the boot recognises it. Today it does not: the drive QEMU attaches b `disk/thumbdrives/zuse-thumb-ident.img`, is all zeros (bleached, `e56974e0`), and the v4 boot reads no thumbdrive and prints its prompt with no identity. See "The identity gate" below. +**RULED 2026-11-12 (Captain Bob), #189** -- v4 has no Compudynamics beyond its heat counters, and it +"blocks alpha.5": the physics comes first. **Order: Part A, Phase 1, Phase 2, then Phase 7**; Phases +3 to 5 follow after alpha.5. And **HEAT-OP**: "Add HEAT-OP. If it's a huge performance hit, we'll +revert it" -- both: `HEAT-OP[0..31]` and `ANTICLOCK` readable as registers now, a full 33-cell array +(`DECOMPOSITION.md` §7), and fed to the Stadium in Phase 1, measured, kept or reverted on the +numbers. + **Q1. What "Hera, Hermes, Artemis are kernel" means in v4.** *Recommend:* each is a function of the kernel, not a VM or node with a capsule of its own: - **Hera** — process management: the registry, births, `NODE-KILL`/`SLEEP`/`WAKE` and who may ask @@ -160,11 +167,11 @@ acceptance. Only Part A is written in tasks below; it does not depend on Q1 to Q **Interfaces:** Produces `typedef void (*v4_call_hook)(void *ctx, v4_node *n, v4_cell target);` and, in `v4_exec_state`, `v4_call_hook on_call; void *on_call_ctx;`. Called at `V4_OP_CALL` after the return address is pushed, before `n->p` moves. NULL = no hook. -- [ ] **Step 1: Failing test** `test_call_hook_sees_every_call`: a programme calling three routines, one twice; the hook records the four targets in order; with `on_call = NULL` the result is the same. -- [ ] **Step 2:** `make -C v4 test` — FAIL (no `on_call`). -- [ ] **Step 3:** Add the hook; remove `v4_heat_on_call`, `call[]`, the freeze mask and `v4_heat_call_*`, and their tests. -- [ ] **Step 4:** Suite passes at both widths and under sanitizers; `hosted-check` unchanged. -- [ ] **Step 5:** Commit `feat(v4): a hook at call, for whoever runs the node`. (No boot change: nothing sets the hook on bare metal yet. One amd64 boot to confirm POST 538/538 and the same `dict_hash`.) +- [x] **Step 1: Failing test** `test_call_hook_sees_every_call`: a programme calling three routines, one twice; the hook records the four targets in order; with `on_call = NULL` the result is the same. +- [x] **Step 2:** `make -C v4 test` — FAIL (no `on_call`). +- [x] **Step 3:** Add the hook; remove `v4_heat_on_call`, `call[]`, the freeze mask and `v4_heat_call_*`, and their tests. +- [x] **Step 4:** Suite passes at both widths and under sanitizers; `hosted-check` unchanged. +- [x] **Step 5:** Commit `feat(v4): a hook at call, for whoever runs the node`. (No boot change: nothing sets the hook on bare metal yet. One amd64 boot to confirm POST 538/538 and the same `dict_hash`.) ### Task A2: Words that are checked are called diff --git a/experiments/bare_metal/runs/doe-amd64-20261112-225951.csv b/experiments/bare_metal/runs/doe-amd64-20261112-225951.csv new file mode 100644 index 00000000..e69de29b diff --git a/logs/20261112-225951/amd64/qemu-amd64-20261112-225951.log b/logs/20261112-225951/amd64/qemu-amd64-20261112-225951.log new file mode 100644 index 00000000..bd766c42 --- /dev/null +++ b/logs/20261112-225951/amd64/qemu-amd64-20261112-225951.log @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f7ea7435484aaec97f4221899bf9833677c8da46fb8418d6d696338742193ffc +size 6630 diff --git a/logs/20261112-225951/v4-tests/README.txt b/logs/20261112-225951/v4-tests/README.txt new file mode 100644 index 00000000..3135e307 --- /dev/null +++ b/logs/20261112-225951/v4-tests/README.txt @@ -0,0 +1,5 @@ +# Task A1's v4 suite, 2026-11-12 (#189) +test_exec-before.log: test_exec.c against the engine before the hook: does not compile (no on_call). +suite-test-test32-sanitize.log: make -C v4 test test-32 sanitize hosted-check; stopped by the session's 30-minute background limit inside san-64-test_host_depth.c, every program before it 0 failures. +suite-sanitize-rest.log: the sanitizer runs from san-64-test_host_depth.c on (39 programs, 810885 checks, 0 failures); its hosted-check stopped at qemu-aarch64 not installed. +hosted-check.log: hosted-check rerun with qemu-user installed: the three boot identically, depthsweep 230 runs 0 failures. diff --git a/logs/20261112-225951/v4-tests/hosted-check.log b/logs/20261112-225951/v4-tests/hosted-check.log new file mode 100644 index 00000000..61be5a2c --- /dev/null +++ b/logs/20261112-225951/v4-tests/hosted-check.log @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ea895440477acd01c75eba316c49264cbe12147686ca2850ce7ea6fd02c3dd4e +size 9399 diff --git a/logs/20261112-225951/v4-tests/suite-sanitize-rest.log b/logs/20261112-225951/v4-tests/suite-sanitize-rest.log new file mode 100644 index 00000000..191392e5 --- /dev/null +++ b/logs/20261112-225951/v4-tests/suite-sanitize-rest.log @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0264000ede03d58a9649a6ee7920e855e3ba9f095dc690b19f2070cc4529c743 +size 59838 diff --git a/logs/20261112-225951/v4-tests/suite-test-test32-sanitize.log b/logs/20261112-225951/v4-tests/suite-test-test32-sanitize.log new file mode 100644 index 00000000..912f072f --- /dev/null +++ b/logs/20261112-225951/v4-tests/suite-test-test32-sanitize.log @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7f1a7f41d95108cb78f241d0891aeb3eb4c5679b84db3ed693a974bf7e052c32 +size 221889 diff --git a/logs/20261112-225951/v4-tests/test_exec-before.log b/logs/20261112-225951/v4-tests/test_exec-before.log new file mode 100644 index 00000000..c0392d3a --- /dev/null +++ b/logs/20261112-225951/v4-tests/test_exec-before.log @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cfebb112bdfa46c2a8134d0e1c9375e18781bee603f199e379300c8f0bb7121d +size 460 diff --git a/v4/include/v4/exec.h b/v4/include/v4/exec.h index e5f48b7f..62439f98 100644 --- a/v4/include/v4/exec.h +++ b/v4/include/v4/exec.h @@ -11,7 +11,10 @@ * * Section 1.4: "Every retired instruction increments that opcode's heat counter * and advances the anti-clock; every `call` increments the heat counter of its - * target. None of this costs an instruction." + * target. None of this costs an instruction." A call's target is counted by + * whoever runs the node, through the hook below (ENGINE.md step 3; plan + * 2026-10-20, Task A1, #189): the kernel keeps a word's accounts. The engine's + * own per-call-target table, a stand-in, is gone. * * The node owns P, A, B, the stacks and memory; heat and the anti-clock are * separate physics state (section 7 names them as registers). @@ -37,8 +40,14 @@ #include "v4/iword.h" #include "v4/node.h" +/* Called at `call` once the return address is pushed and before P moves to the + * target (so P is still the return address). NULL: no hook. */ +typedef void (*v4_call_hook)(void *ctx, v4_node *n, v4_cell target); + typedef struct { v4_uheat_t anticlock; + v4_call_hook on_call; /* reset leaves NULL */ + void *on_call_ctx; /* A node blocked at its port in the middle of an instruction word * (node.h) goes on, when it has been served, from the slot after the diff --git a/v4/include/v4/heat.h b/v4/include/v4/heat.h index 43f97b69..d77934e9 100644 --- a/v4/include/v4/heat.h +++ b/v4/include/v4/heat.h @@ -8,27 +8,21 @@ * the anti-clock; every `call` increments the heat counter of its target. None * of this costs an instruction." * - * D-6 is deferred to step 2, but the note immediately adds: "The golden model - * implements per-opcode and per-call-target heat in the meantime." The - * counters here are therefore a two-dimensional heat structure (op × target) - * on a per-call basis, with counts stored in 64-bit (because heat is - * accumulated and must not roll over at 32-bit). The "per-call-target" - * counter is indexed by the address of the called target -- on a word-addressed - * node, that is a cell value equal to the address `a` passed to `call a`. + * The per-opcode counters are here. A call's target is counted by whoever runs + * the node, through exec.h's hook at `call` (ENGINE.md step 3: "The + * per-call-target count goes"; plan 2026-10-20, Task A1, #189) -- the kernel + * keeps a word's accounts. The 4096-slot per-call-target table and its freeze + * mask, the golden model's stand-in "in the meantime" (D-6), were here until + * 2026-11-12; a target at 4096 or above was not counted, so no capsule word was. * * The anti-clock is a virtual tick driven by execution -- one tick per retired * instruction -- and the rest of §7 refers to it via `ANTICLOCK`. It is not a * wall-clock time, it is a function of the execution stream, as specified. * - * The MM names in §7: `HEAT-OP[0..31]` (R), `HEAT-CALL[...]` (R/W) with a - * freeze bit (D-6). D-4 (memory map) is deferred, so the model does not - * materialize those MM addresses yet. This header defines the backing storage - * that a later MM layer will expose; it keeps the counts separate from the node - * itself because heat is physics data, not the instruction word stream, and it - * needs to be accessible to assertions such as K conservation tests later. + * The MM names in §7: `HEAT-OP[0..31]` (R) are these counters; `HEAT-CALL[...]` + * is the kernel's word accounts (above). D-4 (memory map) is deferred. * - * Counts are 64-bit. Even for a short run a call-heavy word can heat up many - * times, and the golden model must run POST and remain monotonic and + * Counts are 64-bit. Even for a short run an opcode can retire many times, and the golden model must run POST and remain monotonic and * comparable. They are stored as `v4_uheat_t` (which is uint64_t by cell-width * parameterisation) rather than cell-sized so that a 32-bit node's heat is not * width-truncated in the model. (JUSTIFICATION.md §5 keeps heat arithmetic @@ -40,20 +34,8 @@ #include "v4/cell.h" #include "v4/opcode.h" -#define V4_HEAT_MAX_CALL_TARGETS 4096u - typedef struct { v4_uheat_t op[V4_OPCODE_COUNT]; /* HEAT-OP[0..31] */ - - /* Per-call-target heat. Indexed by target address. The table is fixed-size - * for the golden model; the actual hardware table size is deferred with D-6, - * but "in the meantime" the model must have a per-call-target structure. - * 4096 entries is small (32KB if using 64-bit per entry), enough to cover a - * typical test program's call targets without needing to allocate on-the-fly - * -- and since the node has boundaries, over-indexing is caught. */ - v4_uheat_t call[V4_HEAT_MAX_CALL_TARGETS]; - v4_ucell call_freeze_mask[V4_HEAT_MAX_CALL_TARGETS / (sizeof(v4_ucell) * 8u)]; - /* freeze bits: one per call-target entry (D-6). Stored as a bitmask. */ } v4_heat; void v4_heat_reset(v4_heat *h); @@ -61,22 +43,4 @@ void v4_heat_reset(v4_heat *h); /* Every retired instruction: +1 to the opcode's heat and +1 to ANTICLOCK. */ void v4_heat_on_retire(v4_heat *h, unsigned op, v4_uheat_t *anticlock); -/* Every call: +1 to the call-target's heat. The target is the address passed - * to call (word address). If the target is outside the table range, the model - * must not silently wrap -- a boundary-style check is the conservative choice - * here, because the table size is a model parameter. */ -void v4_heat_on_call(v4_heat *h, v4_cell target); - -/* Read/write helpers for per-call-target heat (R/W). Freeze bits are set per - * target when D-6's freeze bit is asserted; writes to a frozen target should be - * ignored in a real implementation, but the MM layer is not defined yet. The - * golden model stores freeze bits and the accessor can enforce them later. */ -v4_uheat_t v4_heat_call_get(const v4_heat *h, v4_cell target); -int v4_heat_call_set(v4_heat *h, v4_cell target, v4_uheat_t val); - -/* Freeze control (D-6). */ -void v4_heat_call_freeze(v4_heat *h, v4_cell target); -void v4_heat_call_unfreeze(v4_heat *h, v4_cell target); -int v4_heat_call_is_frozen(const v4_heat *h, v4_cell target); - #endif /* V4_HEAT_H */ diff --git a/v4/src/exec.c b/v4/src/exec.c index 4530d568..3f76d0d6 100644 --- a/v4/src/exec.c +++ b/v4/src/exec.c @@ -98,6 +98,8 @@ static int stack_faulted(v4_node *n, unsigned op) void v4_exec_reset(v4_exec_state *es) { es->anticlock = 0; + es->on_call = 0; + es->on_call_ctx = 0; es->resume = 0; es->resume_iw = 0; es->resume_slot = 0; @@ -133,7 +135,7 @@ void v4_exec_op(v4_node *n, v4_exec_state *es, v4_heat *h, case V4_OP_CALL: x = v4_iword_branch(n->p, w, slot); v4_rstack_push(rs, n->p); - v4_heat_on_call(h, x); + if (es->on_call) es->on_call(es->on_call_ctx, n, x); n->p = x; break; case V4_OP_UNEXT: diff --git a/v4/src/heat.c b/v4/src/heat.c index 3ac9e937..c1a0d8fa 100644 --- a/v4/src/heat.c +++ b/v4/src/heat.c @@ -10,8 +10,6 @@ void v4_heat_reset(v4_heat *h) * the bare-metal kernel can link it as it is. */ unsigned i; for (i = 0; i < sizeof h->op / sizeof h->op[0]; i++) h->op[i] = 0; - for (i = 0; i < sizeof h->call / sizeof h->call[0]; i++) h->call[i] = 0; - for (i = 0; i < sizeof h->call_freeze_mask / sizeof h->call_freeze_mask[0]; i++) h->call_freeze_mask[i] = 0; } void v4_heat_on_retire(v4_heat *h, unsigned op, v4_uheat_t *anticlock) @@ -21,68 +19,3 @@ void v4_heat_on_retire(v4_heat *h, unsigned op, v4_uheat_t *anticlock) } if (anticlock) (*anticlock)++; } - -static unsigned call_index(v4_cell target) -{ - /* The per-call-target heat table is the golden model's stand-in until D-6 rules which - * heat structures exist in hardware (DECOMPOSITION.md D-6). It is indexed by the call's - * target address itself, V4_HEAT_MAX_CALL_TARGETS slots. A target at or past that has - * no slot: it is not counted, and (unsigned)-1 tells the callers so -- they then record - * nothing and read 0, rather than counting it in another target's slot. ENGINE.md step 4, - * Part A1, plans to remove the table with v4_heat_on_call (corrected 2026-11-12, #181: - * this comment read as an unfinished question). */ - unsigned idx = (unsigned)(v4_ucell)target; - if (idx >= V4_HEAT_MAX_CALL_TARGETS) return (unsigned)-1; - return idx; -} - -void v4_heat_on_call(v4_heat *h, v4_cell target) -{ - unsigned idx = call_index(target); - if (idx == (unsigned)-1) return; - if (v4_heat_call_is_frozen(h, target)) return; - h->call[idx]++; -} - -v4_uheat_t v4_heat_call_get(const v4_heat *h, v4_cell target) -{ - unsigned idx = call_index(target); - if (idx == (unsigned)-1) return 0; - return h->call[idx]; -} - -int v4_heat_call_set(v4_heat *h, v4_cell target, v4_uheat_t val) -{ - unsigned idx = call_index(target); - if (idx == (unsigned)-1) return 0; - if (v4_heat_call_is_frozen(h, target)) return 0; - h->call[idx] = val; - return 1; -} - -void v4_heat_call_freeze(v4_heat *h, v4_cell target) -{ - unsigned idx = call_index(target); - if (idx == (unsigned)-1) return; - unsigned word = idx / (8u * sizeof(v4_ucell)); - unsigned bit = idx % (8u * sizeof(v4_ucell)); - h->call_freeze_mask[word] |= ((v4_ucell)1u << bit); -} - -void v4_heat_call_unfreeze(v4_heat *h, v4_cell target) -{ - unsigned idx = call_index(target); - if (idx == (unsigned)-1) return; - unsigned word = idx / (8u * sizeof(v4_ucell)); - unsigned bit = idx % (8u * sizeof(v4_ucell)); - h->call_freeze_mask[word] &= ~((v4_ucell)1u << bit); -} - -int v4_heat_call_is_frozen(const v4_heat *h, v4_cell target) -{ - unsigned idx = call_index(target); - if (idx == (unsigned)-1) return 0; - unsigned word = idx / (8u * sizeof(v4_ucell)); - unsigned bit = idx % (8u * sizeof(v4_ucell)); - return (h->call_freeze_mask[word] & ((v4_ucell)1u << bit)) != 0; -} diff --git a/v4/tests/test_exec.c b/v4/tests/test_exec.c index ab05af2a..34bcef18 100644 --- a/v4/tests/test_exec.c +++ b/v4/tests/test_exec.c @@ -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 diff --git a/v4/tests/test_heat.c b/v4/tests/test_heat.c index f5491b40..9dc54405 100644 --- a/v4/tests/test_heat.c +++ b/v4/tests/test_heat.c @@ -1,7 +1,8 @@ /* SPDX-License-Identifier: LicenseRef-Starship-1.0 * Copyright (c) 2023-2026 Robert A. James */ -/* test_heat.c -- heat and anti-clock. */ +/* test_heat.c -- heat and anti-clock. A call's target is counted through exec.h's hook + * (test_exec.c), not here: the per-call-target table is gone (#189). */ #include "v4/heat.h" #include #include @@ -30,11 +31,6 @@ static void test_reset(void) for (unsigned i = 0; i < V4_OPCODE_COUNT; i++) { CHECK(h.op[i] == 0, "op %u heat non-zero after reset", i); } - for (unsigned i = 0; i < V4_HEAT_MAX_CALL_TARGETS; i++) { - CHECK(h.call[i] == 0, "call target %u heat non-zero after reset", i); - CHECK(!v4_heat_call_is_frozen(&h, (v4_cell)i), - "call target %u frozen after reset", i); - } v4_uheat_t ac = 999; v4_heat_on_retire(&h, V4_OP_NOP, &ac); CHECK(ac == 1000, "anticlock not advanced"); @@ -57,55 +53,12 @@ static void test_retire_advances_anticlock_and_opcode(void) } } -static void test_call_advances_target(void) -{ - v4_heat h; - v4_heat_reset(&h); - v4_heat_on_call(&h, 10); - v4_heat_on_call(&h, 10); - v4_heat_on_call(&h, 5); - CHECK(v4_heat_call_get(&h, 10) == 2, "target 10"); - CHECK(v4_heat_call_get(&h, 5) == 1, "target 5"); - CHECK(v4_heat_call_get(&h, 1) == 0, "target 1"); -} - -static void test_freeze(void) -{ - v4_heat h; - v4_heat_reset(&h); - v4_heat_on_call(&h, 7); - v4_heat_call_freeze(&h, 7); - CHECK(v4_heat_call_is_frozen(&h, 7), "frozen"); - v4_heat_on_call(&h, 7); - CHECK(v4_heat_call_get(&h, 7) == 1, "still 1"); - CHECK(v4_heat_call_set(&h, 7, 100) == 0, "set returns 0 when frozen"); - CHECK(v4_heat_call_get(&h, 7) == 1, "unchanged"); - v4_heat_call_unfreeze(&h, 7); - CHECK(!v4_heat_call_is_frozen(&h, 7), "unfrozen"); - CHECK(v4_heat_call_set(&h, 7, 42) == 1, "set allowed"); - CHECK(v4_heat_call_get(&h, 7) == 42, "changed"); -} - -static void test_out_of_range_ignored(void) -{ - v4_heat h; - v4_heat_reset(&h); - v4_heat_on_call(&h, (v4_cell)V4_HEAT_MAX_CALL_TARGETS + 5); - CHECK(v4_heat_call_get(&h, (v4_cell)V4_HEAT_MAX_CALL_TARGETS + 5) == 0, - "out of range call ignored"); - CHECK(v4_heat_call_set(&h, (v4_cell)-1, 123) == 0, "set out of range"); - CHECK(v4_heat_call_is_frozen(&h, (v4_cell)-1) == 0, "frozen check"); -} - int main(void) { printf("v4 heat tests: V4_CELL_BITS=%d\n", V4_CELL_BITS); test_reset(); test_retire_advances_anticlock_and_opcode(); - test_call_advances_target(); - test_freeze(); - test_out_of_range_ignored(); printf(" %d checks, %d failures\n", checks, failures); return failures ? 1 : 0;