programs-react, web: apply instruction contexts as postconditions in the tracer - #289
Merged
Conversation
…struction's postcondition Instruction contexts are postconditions: instruction i's context — its semantic facts and its pointers — describes the machine state after i executes. A trace step observes the state before its instruction runs, so the step about to execute instruction i is observing the postcondition of instruction i-1. The tracer was applying instruction i's context against instruction i's pre-execution state, so every variable read stale at its producing instruction (memory-homed locals masked this by reading zero) and the value assigned by the last executed instruction was never shown. Add effectiveContextForStep(program, pcToInstruction, trace, i): prepend the program-level context and index the resulting sequence by trace position — instruction i-1's context at step i, with the program-level context as the base case for the first step. Pointer resolution still runs against the state observed at step i; only the context selection shifts. Wire the shared helper into the two tracer surfaces (programs-react TraceContext and the web TraceDrawer) for the variables and call-info panels, so both stop resolving current-instruction facts a step early and stop diverging from one another. Call-stack reconstruction (buildCallStack) and its argument resolution carry the same off-by-one but are left to a follow-up: the frame-timing shift is entangled with the close-after / invoke- inclusive / TCO / inline tuning, and the argument values are already correct because their pointers resolve on a no-op JUMPDEST.
Contributor
|
buildCallStack scanned each step's own instruction, so a frame was pushed while parked on the caller JUMP — one step before the call-info banner (already on postcondition timing) named the invoke. Walk the effective-context sequence instead: at step i the frame list reflects the contexts of the instructions executed at steps 0..i-1, with the program-level context as the base case, selected per step via effectiveContextForStep so the frame list and the banner can never disagree. A frame's stepIndex becomes the step whose observed state its argument pointers describe — the callee entry's postcondition — which is where both surfaces already resolve them. JUMPDEST is a no-op, so the resolved argument values are unchanged. The frame discipline is untouched: close-after, the caller-JUMP / callee-JUMPDEST invoke dedup, TCO back-edge frame reuse, and inline virtual frames all survive with their step indices shifted by one. A step with no context contributes no events and no inline membership, so the defensive guard still tears down a stale virtual frame there (previously only for instructions present in the map). Tests: pin the frame/banner agreement at every step of a real call and the program-level base case; re-time the existing buildCallStack suite; add a TraceProvider integration test that resolves an argument through the shifted frame. Claude-Session: https://claude.ai/code/session_01JeiUMsA1rcjAbvraYpvg1F
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Instruction contexts are postconditions: instruction i's context — its semantic facts and its pointers — describes the machine state after i executes. A trace step observes the machine state before its instruction runs, so a debugger paused at the step about to execute instruction i is observing the postcondition of instruction i-1.
The tracer was instead applying instruction i's context against instruction i's own pre-execution state. Consequences at O0 with memory-homed locals emitted:
0x000…0("not yet assigned") rather than obvious garbage, which is why it went unnoticed.STOP) carries no context of its own under the old scheme.Fix
Add
effectiveContextForStep(program, pcToInstruction, trace, i): prepend the program-level context and index the resulting sequence by trace position — instruction i-1's context at step i, with the program-level context as the base case for the first step. Pointer resolution still runs against the state observed at step i; only the context selection shifts.The helper is context-source-agnostic (it takes a
contextAtPcaccessor), so both tracer surfaces share it:TraceContext— the variables and call-info panels.TraceDrawer— the same two panels; the two surfaces previously duplicated the resolver and now agree.currentInstruction(opcode/pc display, source highlighting, transform tags) still tracks the instruction about to execute — only the state-resolved semantic facts move.Call stack
Call-stack reconstruction moves to the same timing.
buildCallStacknow walks the effective-context sequence viaeffectiveContextForStep(program-level context as the base case, then instruction i-1's context at step i), so a frame is pushed on the step the call-info banner first names its invoke and the two can never disagree. The frame discipline itself is untouched — close-after, the caller-JUMP/callee-JUMPDESTinvoke dedupe, TCO back-edge frame reuse, and inline virtual frames all behave as before, one step later. A frame'sstepIndexis now the step whose observed state its argument pointers describe (the callee entry's postcondition), which is where both surfaces resolve them; sinceJUMPDESTis a no-op the resolved argument values are unchanged. A step with no context contributes no events and no inline membership, so the defensive membership guard still tears down a stale virtual frame there.Tests
effectiveContextForStepunit tests: program-level base case, the previous-instruction selection, and the missing-context edges.buildCallStack: new tests pin the frame/banner agreement at every step of a real call (including the frame's root step and argument pointers) and the program-level base case; the existing suite is re-timed with its discipline assertions intact.TraceProviderintegration tests (@testing-library/react): stepping shows instruction i-1's variables, the program-level context at the first step, the invoke surfacing only after its instruction executes, the frame and banner appearing together, and an argument resolving to the expected value through the shifted frame.Full suite green;
docusaurus build(web in-context typecheck) passes.https://claude.ai/code/session_01JeiUMsA1rcjAbvraYpvg1F