trace: descriptor-assembled chunks arm their own history recording (#831) - #858
Conversation
) g_trace_hist was set only by the bytecode compiler's source scan, so a chunk assembled by vm_run_bytecode/sandbox_run containing the history-reading opcodes recorded nothing and its own prev/at reads answered null — unless the HOST program's text happened to contain a temporal query, a relationship no bytecode producer can reason about. Reproduces back to v0.34.0 (the non-regression half split from #830). chunk_arm_temporal (src/chunk.c) replays the compiler's scan over the verified code stream, stepping with the same op_verify_operands table and recursing into nested function chunks: per-name arming for OP_INTERROGATE_NAMED kind 6 (prev) and every OP_INTERROGATE_NAMED_AT, observer-state capture for the where/why/how-at kinds, and the wildcard on a GET_NAME of state_at. Called from both descriptor sites after chunk_verify accepts, so a chunk with no temporal opcode arms nothing and temporal-free producers keep recording off. Suite [70f] (test_temporal_producers_unarmed.eigs) is [70e]'s missing half: a host with NO temporal query anywhere — all 5 checks answer null without the fix. Closes #831 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
544a4ee to
a41806f
Compare
There was a problem hiding this comment.
🟢 Ready to approve
The change is narrowly scoped, reuses the verifier’s operand layout for safe bytecode walking, adds targeted regression coverage (including nested-chunk recursion), and is consistent with the compiler’s established arming rules.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Pull request overview
Fixes a long-standing gap where bytecode descriptors executed via vm_run_bytecode / sandbox_run could contain temporal-read opcodes (prev / … at …) yet still record no history (and therefore return null) unless the host program’s compiled source happened to contain its own temporal query. This aligns descriptor-assembled execution with the compiler’s temporal scan behavior so non-compiler producers (e.g., ouroboros codegen, vendored generators) get correct temporal semantics.
Changes:
- Add
chunk_arm_temporal()to replay the compiler’s temporal scan over verified assembled bytecode (including recursion into nested function chunks). - Invoke
chunk_arm_temporal()from bothvm_run_bytecodeandsandbox_runso descriptors can enable/arm history recording based on their own content. - Add a new test suite
[70f]to cover the “host contains no temporal query” case, including nested-function andstate_atwildcard behavior.
File summaries
| File | Description |
|---|---|
| tests/test_temporal_producers_unarmed.eigs | New regression test ensuring descriptors arm/enable history recording without any host-level temporal query. |
| tests/run_all_tests.sh | Registers the new [70f] suite entry for CI/suite execution. |
| src/vm.h | Exposes chunk_arm_temporal() alongside the chunk verifier API. |
| src/chunk.c | Implements chunk_arm_temporal() by walking verified bytecode and arming names / observer-history capture; recurses into nested chunks. |
| src/builtins.c | Calls chunk_arm_temporal() in vm_run_bytecode and sandbox_run after descriptor verification/build. |
| CHANGELOG.md | Documents the fix and its impact/behavior (including pay-for-what-you-use intent and new suite coverage). |
Review details
- Files reviewed: 6/6 changed files
- Comments generated: 0
- Review effort level: Lite
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
There was a problem hiding this comment.
🟢 Ready to approve
The change correctly arms temporal recording for verified descriptor chunks (including nested functions), adds targeted regression coverage, and cleanly limits overhead to chunks that actually use temporal opcodes.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
- Files reviewed: 6/6 changed files
- Comments generated: 0 new
- Review effort level: Lite
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
Closes #831
g_trace_histis set only by the bytecode compiler's source scan, so a descriptor assembled byvm_run_bytecode/sandbox_runcontainingprev/atopcodes recorded nothing and its own temporal reads answerednull— unless the host program's text happened to contain a temporal query of its own, a relationship no bytecode producer (ouroboros codegen, iLambdaAi's vendored copy) can reason about. Not a regression: reproduces on v0.34.0; this is the older half split out of #830.Fix
chunk_arm_temporal(src/chunk.c) — the assembler's twin of the compiler's temporal scan, per the sketch in #831. It steps the verified code stream reusing theop_verify_operandswidth table (call it only afterchunk_verifyaccepts) and recurses into nested function chunks:OP_INTERROGATE_NAMEDkind 6 (prev) → arm that nameOP_INTERROGATE_NAMED_ATany kind → arm that name; kinds 3–5 (where/why/how) also set observer-state captureOP_GET_NAMEofstate_at→ wildcard, exactly as a source-level reference doesPay-for-what-you-use: a chunk with no temporal opcode arms nothing, so temporal-free producers keep recording off (the cost constraint that kept this out of #830). Called from both descriptor sites:
vm_run_bytecodeandsandbox_run(wherestate_atis allowlisted, so sandboxed generated code legitimately reaches it).Coverage
New suite
[70f]—tests/test_temporal_producers_unarmed.eigs, [70e]'s missing half: a host program with no temporal query anywhere in compiled source, so every answer exists only if the assembler armed the names. Checks: prev, what-at both sides of the line boundary, prev inside a nested function chunk, and a state_at reference (last, since it widens to the wildcard for the process).Verified fail-without-fix: all 5 checks answer
nullwith the fix stashed.Gates
detect_leaks=1: 3742/3742, leak tally 0make jit-smoke,make freestanding-checkgreen🤖 Generated with Claude Code