Skip to content

Add TwoPassTraceContext as third tracing implementation#290

Draft
PhilippGrulich wants to merge 8 commits into
mainfrom
claude/sharp-archimedes-XPE7R
Draft

Add TwoPassTraceContext as third tracing implementation#290
PhilippGrulich wants to merge 8 commits into
mainfrom
claude/sharp-archimedes-XPE7R

Conversation

@PhilippGrulich

@PhilippGrulich PhilippGrulich commented May 24, 2026

Copy link
Copy Markdown
Member

Summary

  • Adds TwoPassTraceContext, a new trace context activated via engine.traceMode = "twoPass" that provides a clean baseline for future deferred-merge experiments
  • Adds MergeDetectionPhase utility class for post-hoc merge restructuring (currently unused but available for future experimentation)
  • Registers the new context in the test infrastructure (ExecutionTest.hpp and TracingTest.cpp) so it runs alongside the existing trace modes

Design Notes

The current implementation uses inline checkTag() + processControlFlowMerge() (same semantics as LazyTraceContext) without the needsCheckpoint_ fast-path optimization. This produces identical traces to the existing contexts.

Key finding from exploration: Deferred merge detection using Tag*-only or Tag*+staticHash keys produces false positives because Tags are not unique to a single code path — different branches can produce the same backtrace at structurally similar operations. The full Snapshot (Tag + staticHash + aliveHash) is required for correct merge detection. This rules out the "cheaper merge key" approach and motivates future work on a post-trace structural matching pass.

Architecture

TwoPassTraceContext
├── traceOp(): checkTag() → onCreation (same as LazyTraceContext without fast-path)
├── traceBool(): checkTag() + SymbolicExecutionContext (identical to LazyTraceContext)
├── resume(): reset staticVars, aliveVars, paused_ between iterations
└── startTrace(): multi-function tracing loop (identical to LazyTraceContext)

MergeDetectionPhase (utility, for future use)
└── apply(): sort merge points, truncate second blocks, processControlFlowMerge()

Test plan

  • All 504/505 Engine Compiler Test assertions pass with twoPass mode (1 pre-existing ifWithTernary failure shared by all trace modes)
  • All interpreter tests pass (7/7)
  • Build passes with Clang 18
  • Code formatted with cmake format target

https://claude.ai/code/session_0132qQrKYMcQvLjjs8NA12wH

claude added 3 commits May 24, 2026 23:00
Introduces a two-tier tracing approach where snapshot computation and
tag-map lookups are only performed at "checkpoint" operations (CMP
branches, CALL, RETURN, and first-op-in-block), while all other
operations (arithmetic, constants, assignments, loads, stores) use a
fast path that skips the expensive recordSnapshot() + checkTag() calls.

Key changes:
- ExecutionTrace: add NoTag variants of addOperationWithResult and
  addAssignmentOperation that skip tag-map registration
- ExceptionBasedTraceContext/LazyTraceContext: add traceOperationFast()
  with dual fast/checkpoint lambdas, controlled by needsCheckpoint_ flag
- processControlFlowMerge: skip tag-map updates for null-snapshot ops

Status: Prototype. All interpreter tests pass (semantically correct
traces). Trace-comparison tests fail (different structure). Some
compiler tests fail (ifWithTernary) due to missed merge points when
convergence happens mid-block rather than at block boundaries.

https://claude.ai/code/session_0132qQrKYMcQvLjjs8NA12wH
Introduces a new trace context that uses inline checkTag() and
processControlFlowMerge() (same as existing contexts) but without
the LazyTraceContext's needsCheckpoint_ fast-path optimization.
This provides a clean baseline for future deferred-merge experiments.

Activated via engine option: engine.traceMode = "twoPass"

Also adds a MergeDetectionPhase utility class for future use in
deferred merge restructuring experiments.

https://claude.ai/code/session_0132qQrKYMcQvLjjs8NA12wH
@PhilippGrulich PhilippGrulich changed the title Prototype: checkpoint-only merge detection for tracing Add TwoPassTraceContext as third tracing implementation May 25, 2026
claude added 3 commits May 25, 2026 13:43
Adding twoPass to the global trace modes list caused the pre-existing
ifWithTernary failure to surface in every CI job. The TwoPassTraceContext
is still wired into LegacyCompiler and can be tested explicitly via
engine.traceMode = "twoPass".

https://claude.ai/code/session_0132qQrKYMcQvLjjs8NA12wH
The prototype commit (6d2f43d) modified ExceptionBasedTraceContext,
LazyTraceContext, and ExecutionTrace with needsCheckpoint_ fast-path
and NoTag variants. These changes broke trace reference tests across
all CI jobs by producing different merge structures.

Reverts these files to their main-branch versions. The TwoPassTraceContext
is a standalone addition that doesn't modify existing contexts.

https://claude.ai/code/session_0132qQrKYMcQvLjjs8NA12wH

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tracing Benchmark

Details
Benchmark suite Current: e8c9d0c Previous: f9cd28f Ratio
tiered_compile_addOne 49.3759 us (± 8.4582) 55.1658 us (± 10.7264) 0.90
single_compile_mlir_addOne 3.26567 ms (± 43.7988) 3.25749 ms (± 57.5499) 1.00
single_compile_cpp_addOne 24.7507 ms (± 1.42291) 24.4391 ms (± 405.29) 1.01
single_compile_bc_addOne 56.1537 us (± 9.76231) 60.6703 us (± 11.2707) 0.93
tiered_compile_sumLoop 77.4407 us (± 10.2814) 79.6202 us (± 15.7788) 0.97
single_compile_mlir_sumLoop 5.37035 ms (± 436.433) 5.35632 ms (± 118.42) 1.00
single_compile_cpp_sumLoop 25.2119 ms (± 222.384) 26.0899 ms (± 453.281) 0.97
single_compile_bc_sumLoop 75.8018 us (± 7.12729) 78.6934 us (± 10.1775) 0.96
exec_bc_addOne 35.3491 ns (± 4.98316) 36.0837 ns (± 6.3975) 0.98
exec_mlir_addOne 283.396 ns (± 2.42984) 258.154 ns (± 2.33301) 1.10
exec_cpp_addOne 3.95764 ns (± 0.686006) 3.54853 ns (± 0.249363) 1.12
exec_interpreted_addOne 42.4819 ns (± 9.60566) 37.1985 ns (± 2.33453) 1.14
ir_add 746.987 ns (± 42.9945) 757.379 ns (± 38.5584) 0.99
ir_ifThenElse 1.53632 us (± 85.237) 1.58461 us (± 136.165) 0.97
ir_deeplyNestedIfElse 3.3864 us (± 366.475) 3.40826 us (± 363.019) 0.99
ir_loop 1.68884 us (± 252.19) 1.63694 us (± 149.086) 1.03
ir_ifInsideLoop 2.80319 us (± 228.313) 2.83439 us (± 293.378) 0.99
ir_loopDirectCall 1.81276 us (± 143.492) 1.80584 us (± 177.074) 1.00
ir_pointerLoop 2.75931 us (± 834.9) 1.97161 us (± 153.779) 1.40
ir_staticLoop 1.44306 us (± 138.838) 1.44956 us (± 119.501) 1.00
ir_fibonacci 1.75446 us (± 139.103) 1.72022 us (± 143.401) 1.02
ir_gcd 1.49416 us (± 113.641) 1.4549 us (± 101.976) 1.03
ir_nestedIf10 7.72282 us (± 906.796) 7.70943 us (± 540.522) 1.00
ir_nestedIf100 86.2408 us (± 7.23312) 93.6887 us (± 16.2569) 0.92
ir_chainedIf10 11.5643 us (± 1.05563) 11.594 us (± 826.837) 1.00
ir_chainedIf100 169.089 us (± 25.5692) 169.138 us (± 8.3263) 1.00
ssa_add 183.305 ns (± 15.6324) 178.473 ns (± 9.97434) 1.03
ssa_ifThenElse 461.93 ns (± 60.3973) 459.53 ns (± 43.4269) 1.01
ssa_deeplyNestedIfElse 1.18174 us (± 145.176) 1.16567 us (± 124.837) 1.01
ssa_loop 521.829 ns (± 48.2841) 487.861 ns (± 38.2642) 1.07
ssa_ifInsideLoop 935.981 ns (± 62.5527) 895.76 ns (± 72.8648) 1.04
ssa_loopDirectCall 495.808 ns (± 42.5412) 491.118 ns (± 45.7827) 1.01
ssa_pointerLoop 608.753 ns (± 30.8363) 583.073 ns (± 41.8959) 1.04
ssa_staticLoop 396.042 ns (± 30.1721) 422.539 ns (± 47.7647) 0.94
ssa_fibonacci 531.697 ns (± 34.4653) 503.737 ns (± 39.1986) 1.06
ssa_gcd 496.838 ns (± 43.5145) 445.234 ns (± 36.8343) 1.12
e2e_tiered_bc_to_mlir 54.5016 us (± 6.45622) 56.9167 us (± 7.69512) 0.96
e2e_single_mlir 5.60496 ms (± 568.424) 5.52315 ms (± 41.2737) 1.01
comp_mlir_add 5.53034 ms (± 79.9952) 5.55479 ms (± 56.4997) 1.00
comp_mlir_ifThenElse 6.12085 ms (± 43.6689) 6.14317 ms (± 38.4483) 1.00
comp_mlir_deeplyNestedIfElse 5.04022 ms (± 158.378) 5.06889 ms (± 99.0918) 0.99
comp_mlir_loop 7.12088 ms (± 31.8853) 7.14743 ms (± 31.0585) 1.00
comp_mlir_ifInsideLoop 28.5585 ms (± 267.516) 28.6125 ms (± 100.629) 1.00
comp_mlir_loopDirectCall 11.7593 ms (± 47.5567) 11.792 ms (± 44.0495) 1.00
comp_mlir_pointerLoop 27.5973 ms (± 143.594) 27.7179 ms (± 76.6326) 1.00
comp_mlir_staticLoop 4.99837 ms (± 41.019) 5.03112 ms (± 38.6111) 0.99
comp_mlir_fibonacci 10.4271 ms (± 52.1502) 10.4618 ms (± 76.5332) 1.00
comp_mlir_gcd 9.40532 ms (± 62.1999) 9.43388 ms (± 42.8866) 1.00
comp_mlir_nestedIf10 10.4621 ms (± 53.6469) 10.5281 ms (± 45.0755) 0.99
comp_mlir_nestedIf100 24.6081 ms (± 133.002) 25.0116 ms (± 102.345) 0.98
comp_mlir_chainedIf10 9.53695 ms (± 46.6553) 9.56929 ms (± 46.3491) 1.00
comp_mlir_chainedIf100 19.9952 ms (± 168.965) 20.2061 ms (± 81.9884) 0.99
comp_cpp_add 24.5445 ms (± 242.431) 24.3267 ms (± 189.47) 1.01
comp_cpp_ifThenElse 25.0362 ms (± 247.377) 24.9638 ms (± 283.6) 1.00
comp_cpp_deeplyNestedIfElse 26.199 ms (± 444.539) 26.0088 ms (± 233.598) 1.01
comp_cpp_loop 25.22 ms (± 224.563) 25.0851 ms (± 291.998) 1.01
comp_cpp_ifInsideLoop 26.1548 ms (± 264.579) 26.2072 ms (± 775.414) 1.00
comp_cpp_loopDirectCall 25.663 ms (± 271.565) 25.3831 ms (± 531.136) 1.01
comp_cpp_pointerLoop 25.9858 ms (± 1.80081) 25.4858 ms (± 203.254) 1.02
comp_cpp_staticLoop 25.0511 ms (± 642.902) 24.7845 ms (± 176.103) 1.01
comp_cpp_fibonacci 25.2644 ms (± 258.403) 25.2385 ms (± 205.167) 1.00
comp_cpp_gcd 25.0678 ms (± 272.827) 25.0233 ms (± 436.288) 1.00
comp_cpp_nestedIf10 28.0561 ms (± 253.934) 28.0721 ms (± 363.361) 1.00
comp_cpp_nestedIf100 61.964 ms (± 1.52933) 61.7276 ms (± 707.638) 1.00
comp_cpp_chainedIf10 31.7857 ms (± 2.72235) 30.7822 ms (± 574.356) 1.03
comp_cpp_chainedIf100 93.5768 ms (± 2.34167) 91.9107 ms (± 685.096) 1.02
comp_bc_add 14.8058 us (± 2.59558) 14.7773 us (± 1.91246) 1.00
comp_bc_ifThenElse 19.4385 us (± 4.24737) 19.2429 us (± 3.53172) 1.01
comp_bc_deeplyNestedIfElse 22.7842 us (± 4.13866) 23.0339 us (± 3.44593) 0.99
comp_bc_loop 19.0311 us (± 4.38883) 18.9128 us (± 2.70016) 1.01
comp_bc_ifInsideLoop 21.5833 us (± 3.4477) 21.5194 us (± 2.28647) 1.00
comp_bc_loopDirectCall 20.5191 us (± 5.35745) 19.2878 us (± 2.03113) 1.06
comp_bc_pointerLoop 21.0134 us (± 5.17648) 20.5351 us (± 2.66896) 1.02
comp_bc_staticLoop 17.3214 us (± 3.38925) 17.2132 us (± 2.1155) 1.01
comp_bc_fibonacci 19.2063 us (± 3.61845) 19.1709 us (± 2.93634) 1.00
comp_bc_gcd 18.3378 us (± 3.16889) 18.5754 us (± 2.5724) 0.99
comp_bc_nestedIf10 35.313 us (± 5.44198) 35.6002 us (± 4.60432) 0.99
comp_bc_nestedIf100 188.002 us (± 8.54544) 198.41 us (± 10.198) 0.95
comp_bc_chainedIf10 50.8488 us (± 10.4835) 52.6073 us (± 9.11991) 0.97
comp_bc_chainedIf100 296.808 us (± 23.1299) 309.294 us (± 26.888) 0.96
comp_asmjit_add 22.3143 us (± 4.82514) 22.1735 us (± 4.82406) 1.01
comp_asmjit_ifThenElse 34.1369 us (± 6.11381) 33.4071 us (± 4.26649) 1.02
comp_asmjit_deeplyNestedIfElse 57.6219 us (± 10.2413) 56.4176 us (± 5.25514) 1.02
comp_asmjit_loop 36.5097 us (± 5.11159) 35.8631 us (± 4.35405) 1.02
comp_asmjit_ifInsideLoop 57.299 us (± 9.22632) 57.5183 us (± 8.7641) 1.00
comp_asmjit_loopDirectCall 45.9998 us (± 9.74257) 47.2559 us (± 9.00425) 0.97
comp_asmjit_pointerLoop 48.4943 us (± 8.37996) 48.2785 us (± 6.56048) 1.00
comp_asmjit_staticLoop 29.1653 us (± 5.64879) 28.6917 us (± 4.31543) 1.02
comp_asmjit_fibonacci 43.5165 us (± 7.87749) 43.5157 us (± 6.92307) 1.00
comp_asmjit_gcd 36.1862 us (± 4.92882) 35.3711 us (± 4.19816) 1.02
comp_asmjit_nestedIf10 101.111 us (± 11.4672) 103.255 us (± 10.7623) 0.98
comp_asmjit_nestedIf100 1031.3600000000001 us (± 26340.3) 1057.97 us (± 54427.5) 0.97
comp_asmjit_chainedIf10 154.114 us (± 16.4685) 153.183 us (± 14.6589) 1.01
comp_asmjit_chainedIf100 2.19993 ms (± 45.7512) 2.16653 ms (± 46.7063) 1.02
exec_mlir_add 10.3657 ns (± 0.893614) 10.6651 ns (± 0.946586) 0.97
exec_mlir_fibonacci 13.2242 us (± 1.17463) 13.4703 us (± 2.01788) 0.98
exec_mlir_sum 595.075 us (± 27.8987) 527.422 us (± 21.2529) 1.13
exec_cpp_add 4.70498 ns (± 0.987826) 4.69868 ns (± 0.745035) 1.00
exec_cpp_fibonacci 96.9705 us (± 9.64906) 96.6361 us (± 9.26507) 1.00
exec_cpp_sum 36.0489 ms (± 802.063) 35.9426 ms (± 115.287) 1.00
exec_bc_add 43.6795 ns (± 5.41778) 44.8635 ns (± 8.015) 0.97
exec_bc_fibonacci 787.302 us (± 13.8159) 859.028 us (± 13.0138) 0.92
exec_bc_sum 168.694 ms (± 590.151) 186.067 ms (± 1.36597) 0.91
exec_asmjit_add 3.45826 ns (± 0.287882) 3.48273 ns (± 0.424618) 0.99
exec_asmjit_fibonacci 20.4042 us (± 1.25159) 20.8625 us (± 3.3694) 0.98
exec_asmjit_sum 4.84832 ms (± 40.6488) 4.87965 ms (± 312.598) 0.99
exec_bc_add_noRegAlloc 43.1112 ns (± 4.85311) 44.3118 ns (± 3.89068) 0.97
exec_bc_add_regAlloc 43.3876 ns (± 5.3221) 44.599 ns (± 5.15383) 0.97
exec_bc_fibonacci_noRegAlloc 787.575 us (± 16.2729) 852.767 us (± 11.3762) 0.92
exec_bc_fibonacci_regAlloc 796.169 us (± 42.6491) 875.208 us (± 25.409) 0.91
exec_bc_sum_noRegAlloc 169.691 ms (± 3.70471) 185.974 ms (± 222.438) 0.91
exec_bc_sum_regAlloc 168.577 ms (± 2.68024) 185.982 ms (± 165.251) 0.91
trace_add 2.02407 us (± 193.769) 2.27731 us (± 155.69) 0.89
completing_trace_add 2.13372 us (± 281.7) 2.32405 us (± 143.855) 0.92
trace_ifThenElse 8.16499 us (± 1.08702) 8.63915 us (± 776.354) 0.95
completing_trace_ifThenElse 3.7725 us (± 369.394) 4.54071 us (± 401.003) 0.83
trace_deeplyNestedIfElse 23.9226 us (± 2.79848) 25.5445 us (± 1.57172) 0.94
completing_trace_deeplyNestedIfElse 9.92996 us (± 1.13172) 12.559 us (± 987.646) 0.79
trace_loop 7.89573 us (± 1.08286) 8.80429 us (± 1.55155) 0.90
completing_trace_loop 4.0924 us (± 576.34) 4.79104 us (± 376.911) 0.85
trace_ifInsideLoop 16.0887 us (± 2.7426) 16.6688 us (± 1.56827) 0.97
completing_trace_ifInsideLoop 6.76618 us (± 951.604) 8.40114 us (± 807.253) 0.81
trace_loopDirectCall 8.19934 us (± 1.19091) 8.6864 us (± 697.439) 0.94
completing_trace_loopDirectCall 4.1868 us (± 535.012) 4.86476 us (± 354.791) 0.86
trace_pointerLoop 12.4988 us (± 1.90697) 14.0902 us (± 1.18648) 0.89
completing_trace_pointerLoop 8.14105 us (± 1.06249) 10.1206 us (± 990.611) 0.80
trace_staticLoop 5.76164 us (± 560.41) 7.21602 us (± 496.139) 0.80
completing_trace_staticLoop 5.7156 us (± 669.826) 7.43532 us (± 991.516) 0.77
trace_fibonacci 9.21085 us (± 1.44412) 10.0814 us (± 861.346) 0.91
completing_trace_fibonacci 4.93946 us (± 548.392) 6.00031 us (± 426.095) 0.82
trace_gcd 7.73949 us (± 1.71352) 7.79585 us (± 648.305) 0.99
completing_trace_gcd 3.54825 us (± 466.037) 3.87126 us (± 272.452) 0.92
trace_nestedIf10 31.3547 us (± 5.35581) 38.0283 us (± 3.64798) 0.82
completing_trace_nestedIf10 32.0398 us (± 5.73964) 37.3711 us (± 3.21573) 0.86
trace_nestedIf100 1.1313 ms (± 44.3488) 1.35476 ms (± 22.6997) 0.84
completing_trace_nestedIf100 1.13514 ms (± 49.1226) 1.35268 ms (± 26.016) 0.84
trace_chainedIf10 91.1809 us (± 7.86043) 97.7583 us (± 4.73759) 0.93
completing_trace_chainedIf10 35.7613 us (± 5.67618) 48.3258 us (± 3.59147) 0.74
trace_chainedIf100 4.19821 ms (± 50.8792) 4.43042 ms (± 29.6138) 0.95
completing_trace_chainedIf100 1.51039 ms (± 25.9136) 2.24075 ms (± 31.9951) 0.67

This comment was automatically generated by workflow using github-action-benchmark.

claude added 2 commits May 25, 2026 17:19
Three optimizations measured with benchmarks:

1. AliveVariableHash: replace unordered_map<uint32_t,uint32_t> with
   flat vector<uint32_t> indexed by ValueRef. ValueRef is monotonically
   increasing from 0, so direct indexing is O(1) with no hashing.
   Impact: ~20% speedup (chainedIf10: 50.1 -> 39.7 us)

2. ExecutionTrace: merge globalTagMap and localTagMap into a single
   tagMap. Both maps always contained the same entries (addTag writes
   to both), and checkTag searched both with identical handling.
   Eliminates one find + one insert per operation.
   Impact: ~8% speedup (chainedIf10: 39.7 -> 36.8 us)

3. TypedValueRefHolder: fix move constructor from const&& to && noexcept,
   skip allocateValRef on move (steal ref, null source). Skip freeValRef
   in destructor for moved-from objects (type == Type::v sentinel).
   Impact: within noise (moves are rare on the tracing hot path)

Combined: ~27% faster tracing for control-flow-heavy functions.

https://claude.ai/code/session_0132qQrKYMcQvLjjs8NA12wH
During follow mode (replaying previously recorded operations), val<T>
constructors/destructors still call allocateValRef/freeValRef to
maintain the alive-variable hash. This is wasted work since we're just
replaying.

Cache the alive hash in the Snapshot (new aliveHash field), then:
- Set following_ flag at resume(), clear it on first RECORD operation
- Skip allocateValRef/freeValRef when following_ is true
- Restore alive hash from the CMP operation's cached snapshot at the
  follow-to-record transition in traceBool

This eliminates all AliveVariableHash overhead during follow iterations.

https://claude.ai/code/session_0132qQrKYMcQvLjjs8NA12wH
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants