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:
@@ -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
|
||||
|
||||
|
||||
Binary file not shown.
@@ -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.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+10
-1
@@ -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
|
||||
|
||||
+9
-45
@@ -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 */
|
||||
|
||||
+3
-1
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
+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
|
||||
|
||||
+2
-49
@@ -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 <stdint.h>
|
||||
#include <stdio.h>
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user