feat(trace): tape format v2 — scope-qualified locals via S records (#539 v2)#549
Merged
Merged
Conversation
v2) Same-named bindings from different scopes merged into one A-record stream, so --step (and any tape consumer) could not tell a function- local i from the top-level one, and trajectory labels fed on the interleaved garbage. v2 adds a scope-transition record S <fn> <depth> <serial> qualifying the A records that follow. <serial> is a per-thread monotonically increasing frame-instance id (CallFrame.call_serial, stamped at all four frame-push sites), so two invocations of the same function never merge either. Emission mirrors the L-record dedup: an S is written only when the frame owning the next assignment differs from the last S — cost lands at call boundaries that actually assign. Measured (call-heavy worst case, 20k calls, n=5): +43% tape bytes, ~8% traced runtime; a straight-line module tape carries exactly one S; untraced execution is untouched (the hook stays off). Stripping S records reproduces the v1 tape byte-for-byte. TRACE_FORMAT_VERSION 1→2: v1 tapes refuse on replay and --step per the #411 version-and-reject rule. Replay skips S like A — byte-exact replay determinism is unchanged (verified same-seed). --step folds the S records into a reconstructed call chain (returning into a live serial pops to it; a new serial pops same-or-deeper frames and pushes with parent linkage). `p` shows the live chain innermost- first with {in fn} notes and shadowing — dead frames' locals no longer appear; `p name`/`t name` resolve innermost-first, so trajectory labels are per-invocation. tests/test_step.sh grows six scope checks (22 total); docs/TRACE.md documents the record kind and version history. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GdDAVfZHgoQ2UNP63FYUXm
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.
The v2 half of #539 (v3 — the
src/eigsdap.cDAP server with stepBack/reverseContinue in VS Code — remains open on that issue, and now has the scope-qualified substrate it was blocked on).The problem
A-records carry the name only, so same-named bindings from different scopes merged into one stream:
--step(and any tape consumer) could not tell a function-localifrom the top-level one, and trajectory labels fed on the interleaved garbage.The format change (
TRACE_FORMAT_VERSION1→2)A new scope-transition record qualifies the A records that follow:
<serial>is a per-thread monotonically increasing frame-instance id (CallFrame.call_serial, stamped at all four frame-push sites) — so two invocations of the same function never merge either. Emission mirrors the L-record dedup discipline: an S is written only when the frame owning the next assignment differs from the last S, so the byte cost lands at call boundaries that actually assign.--stepper the Design: trace-tape format versioning — the tape is becoming a persisted artifact #411 version-and-reject rule (no compat promise, ever).The stepper
--stepfolds S records into a reconstructed call chain (returning into a live serial pops to it; a new serial pops same-or-deeper frames and pushes with parent linkage):pshows the live chain's bindings innermost-first with{in fn}notes and shadowing — dead frames' locals no longer appear, and a shadowed outeristays hidden.p name/t nameresolve innermost-first, so trajectory labels are per-invocation ((i in work, frame #3)).Pins & docs
tests/test_step.shgrows six scope checks (22 total): S records present, distinct serials per invocation, module stream unpolluted, frame-local shadowing, per-frame trajectory resolution, shadowed-outer hidden.docs/TRACE.md: the S record row, the A record's new semantics, and the format version history.Validation
Release suite 2737/2737; ASan+UBSan
detect_leaks=12737/2737, zero LeakSanitizer reports (the run caught at->scopesteardown leak in the stepper during development — fixed at all three teardown sites). Rebased onb5db2d6(#547).🤖 Generated with Claude Code
https://claude.ai/code/session_01GdDAVfZHgoQ2UNP63FYUXm