Scaffold value-content hashing in AliveVariableHash - #285
Draft
PhilippGrulich wants to merge 1 commit into
Draft
Conversation
Contributor
There was a problem hiding this comment.
Tracing Benchmark
Details
| Benchmark suite | Current: 22a6ec8 | Previous: f9cd28f | Ratio |
|---|---|---|---|
trace_add |
2.05949 us (± 170.178) |
2.27731 us (± 155.69) |
0.90 |
completing_trace_add |
2.05145 us (± 111.498) |
2.32405 us (± 143.855) |
0.88 |
trace_ifThenElse |
8.21317 us (± 1.06196) |
8.63915 us (± 776.354) |
0.95 |
completing_trace_ifThenElse |
4.10087 us (± 377.27) |
4.54071 us (± 401.003) |
0.90 |
trace_deeplyNestedIfElse |
23.976 us (± 3.1949) |
25.5445 us (± 1.57172) |
0.94 |
completing_trace_deeplyNestedIfElse |
11.5477 us (± 1.73786) |
12.559 us (± 987.646) |
0.92 |
trace_loop |
8.14607 us (± 1.09889) |
8.80429 us (± 1.55155) |
0.93 |
completing_trace_loop |
4.26453 us (± 387.863) |
4.79104 us (± 376.911) |
0.89 |
trace_ifInsideLoop |
15.6913 us (± 2.25973) |
16.6688 us (± 1.56827) |
0.94 |
completing_trace_ifInsideLoop |
7.60676 us (± 1.05588) |
8.40114 us (± 807.253) |
0.91 |
trace_loopDirectCall |
8.20813 us (± 861.278) |
8.6864 us (± 697.439) |
0.94 |
completing_trace_loopDirectCall |
4.50646 us (± 310.399) |
4.86476 us (± 354.791) |
0.93 |
trace_pointerLoop |
12.7142 us (± 2.35843) |
14.0902 us (± 1.18648) |
0.90 |
completing_trace_pointerLoop |
8.67457 us (± 509.607) |
10.1206 us (± 990.611) |
0.86 |
trace_staticLoop |
6.99827 us (± 777.829) |
7.21602 us (± 496.139) |
0.97 |
completing_trace_staticLoop |
7.0478 us (± 828.882) |
7.43532 us (± 991.516) |
0.95 |
trace_fibonacci |
9.27039 us (± 1.23365) |
10.0814 us (± 861.346) |
0.92 |
completing_trace_fibonacci |
5.36139 us (± 603.805) |
6.00031 us (± 426.095) |
0.89 |
trace_gcd |
7.59963 us (± 1.32303) |
7.79585 us (± 648.305) |
0.97 |
completing_trace_gcd |
3.72341 us (± 695.749) |
3.87126 us (± 272.452) |
0.96 |
trace_nestedIf10 |
34.1065 us (± 2.76513) |
38.0283 us (± 3.64798) |
0.90 |
completing_trace_nestedIf10 |
33.531 us (± 2.35585) |
37.3711 us (± 3.21573) |
0.90 |
trace_nestedIf100 |
1.32329 ms (± 80.8514) |
1.35476 ms (± 22.6997) |
0.98 |
completing_trace_nestedIf100 |
1.31998 ms (± 11.2834) |
1.35268 ms (± 26.016) |
0.98 |
trace_chainedIf10 |
90.8287 us (± 7.32508) |
97.7583 us (± 4.73759) |
0.93 |
completing_trace_chainedIf10 |
43.8857 us (± 6.14732) |
48.3258 us (± 3.59147) |
0.91 |
trace_chainedIf100 |
4.02579 ms (± 49.3023) |
4.43042 ms (± 29.6138) |
0.91 |
completing_trace_chainedIf100 |
2.10749 ms (± 22.8025) |
2.24075 ms (± 31.9951) |
0.94 |
ssa_add |
185.11 ns (± 23.1835) |
178.473 ns (± 9.97434) |
1.04 |
ssa_ifThenElse |
424.5 ns (± 27.2925) |
459.53 ns (± 43.4269) |
0.92 |
ssa_deeplyNestedIfElse |
1.05369 us (± 71.5019) |
1.16567 us (± 124.837) |
0.90 |
ssa_loop |
457.552 ns (± 36.0653) |
487.861 ns (± 38.2642) |
0.94 |
ssa_ifInsideLoop |
832.457 ns (± 52.8811) |
895.76 ns (± 72.8648) |
0.93 |
ssa_loopDirectCall |
466.115 ns (± 48.94) |
491.118 ns (± 45.7827) |
0.95 |
ssa_pointerLoop |
541.191 ns (± 37.5173) |
583.073 ns (± 41.8959) |
0.93 |
ssa_staticLoop |
380.331 ns (± 25.7262) |
422.539 ns (± 47.7647) |
0.90 |
ssa_fibonacci |
482.772 ns (± 49.3137) |
503.737 ns (± 39.1986) |
0.96 |
ssa_gcd |
439.602 ns (± 30.829) |
445.234 ns (± 36.8343) |
0.99 |
ir_add |
739.038 ns (± 84.6211) |
757.379 ns (± 38.5584) |
0.98 |
ir_ifThenElse |
1.51408 us (± 90.6361) |
1.58461 us (± 136.165) |
0.96 |
ir_deeplyNestedIfElse |
3.25893 us (± 195.653) |
3.40826 us (± 363.019) |
0.96 |
ir_loop |
1.60546 us (± 129.024) |
1.63694 us (± 149.086) |
0.98 |
ir_ifInsideLoop |
2.67112 us (± 199.596) |
2.83439 us (± 293.378) |
0.94 |
ir_loopDirectCall |
1.71925 us (± 104.71) |
1.80584 us (± 177.074) |
0.95 |
ir_pointerLoop |
1.85307 us (± 85.4157) |
1.97161 us (± 153.779) |
0.94 |
ir_staticLoop |
1.30555 us (± 96.1887) |
1.44956 us (± 119.501) |
0.90 |
ir_fibonacci |
1.6666 us (± 106.397) |
1.72022 us (± 143.401) |
0.97 |
ir_gcd |
1.43969 us (± 99.7153) |
1.4549 us (± 101.976) |
0.99 |
ir_nestedIf10 |
7.6064 us (± 892.083) |
7.70943 us (± 540.522) |
0.99 |
ir_nestedIf100 |
90.7979 us (± 3.06666) |
93.6887 us (± 16.2569) |
0.97 |
ir_chainedIf10 |
11.63 us (± 937.617) |
11.594 us (± 826.837) |
1.00 |
ir_chainedIf100 |
156.339 us (± 6.52219) |
169.138 us (± 8.3263) |
0.92 |
e2e_tiered_bc_to_mlir |
38.4447 us (± 9.73073) |
56.9167 us (± 7.69512) |
0.68 |
e2e_single_mlir |
6.24701 ms (± 169.382) |
5.52315 ms (± 41.2737) |
1.13 |
comp_mlir_add |
6.28331 ms (± 167.654) |
5.55479 ms (± 56.4997) |
1.13 |
comp_mlir_ifThenElse |
7.06156 ms (± 204.326) |
6.14317 ms (± 38.4483) |
1.15 |
comp_mlir_deeplyNestedIfElse |
5.96091 ms (± 1.14047) |
5.06889 ms (± 99.0918) |
1.18 |
comp_mlir_loop |
7.81784 ms (± 235.892) |
7.14743 ms (± 31.0585) |
1.09 |
comp_mlir_ifInsideLoop |
27.9768 ms (± 695.856) |
28.6125 ms (± 100.629) |
0.98 |
comp_mlir_loopDirectCall |
12.1588 ms (± 272.399) |
11.792 ms (± 44.0495) |
1.03 |
comp_mlir_pointerLoop |
27.1296 ms (± 324.766) |
27.7179 ms (± 76.6326) |
0.98 |
comp_mlir_staticLoop |
5.66223 ms (± 117.167) |
5.03112 ms (± 38.6111) |
1.13 |
comp_mlir_fibonacci |
10.6935 ms (± 251.432) |
10.4618 ms (± 76.5332) |
1.02 |
comp_mlir_gcd |
9.58454 ms (± 219.902) |
9.43388 ms (± 42.8866) |
1.02 |
comp_mlir_nestedIf10 |
10.709 ms (± 211.117) |
10.5281 ms (± 45.0755) |
1.02 |
comp_mlir_nestedIf100 |
25.2785 ms (± 232.134) |
25.0116 ms (± 102.345) |
1.01 |
comp_mlir_chainedIf10 |
11.2888 ms (± 373.689) |
9.56929 ms (± 46.3491) |
1.18 |
comp_mlir_chainedIf100 |
59.5513 ms (± 373.125) |
20.2061 ms (± 81.9884) |
2.95 |
exec_bc_addOne |
32.6311 ns (± 3.28518) |
36.0837 ns (± 6.3975) |
0.90 |
exec_mlir_addOne |
363.441 ns (± 8.7982) |
258.154 ns (± 2.33301) |
1.41 |
exec_cpp_addOne |
3.6099 ns (± 0.414381) |
3.54853 ns (± 0.249363) |
1.02 |
exec_interpreted_addOne |
35.133 ns (± 1.0518) |
37.1985 ns (± 2.33453) |
0.94 |
exec_mlir_add |
11.4826 ns (± 0.61655) |
10.6651 ns (± 0.946586) |
1.08 |
exec_mlir_fibonacci |
25.4618 us (± 1.60415) |
13.4703 us (± 2.01788) |
1.89 |
exec_mlir_sum |
678.024 us (± 25.216) |
527.422 us (± 21.2529) |
1.29 |
exec_cpp_add |
5.24685 ns (± 0.698784) |
4.69868 ns (± 0.745035) |
1.12 |
exec_cpp_fibonacci |
132.436 us (± 8.67999) |
96.6361 us (± 9.26507) |
1.37 |
exec_cpp_sum |
25.6651 ms (± 647.123) |
35.9426 ms (± 115.287) |
0.71 |
exec_bc_add |
36.2939 ns (± 2.8037) |
44.8635 ns (± 8.015) |
0.81 |
exec_bc_fibonacci |
552.568 us (± 13.797) |
859.028 us (± 13.0138) |
0.64 |
exec_bc_sum |
118.335 ms (± 1.20292) |
186.067 ms (± 1.36597) |
0.64 |
exec_asmjit_add |
2.97957 ns (± 0.329376) |
3.48273 ns (± 0.424618) |
0.86 |
exec_asmjit_fibonacci |
28.9186 us (± 5.23041) |
20.8625 us (± 3.3694) |
1.39 |
exec_asmjit_sum |
6.28398 ms (± 36.0384) |
4.87965 ms (± 312.598) |
1.29 |
exec_bc_add_noRegAlloc |
36.5577 ns (± 3.73759) |
44.3118 ns (± 3.89068) |
0.83 |
exec_bc_add_regAlloc |
36.9165 ns (± 4.23268) |
44.599 ns (± 5.15383) |
0.83 |
exec_bc_fibonacci_noRegAlloc |
564.465 us (± 22.7326) |
852.767 us (± 11.3762) |
0.66 |
exec_bc_fibonacci_regAlloc |
570.669 us (± 25.2508) |
875.208 us (± 25.409) |
0.65 |
exec_bc_sum_noRegAlloc |
125.193 ms (± 6.00826) |
185.974 ms (± 222.438) |
0.67 |
exec_bc_sum_regAlloc |
120.853 ms (± 2.69133) |
185.982 ms (± 165.251) |
0.65 |
tiered_compile_addOne |
39.8704 us (± 10.205) |
55.1658 us (± 10.7264) |
0.72 |
single_compile_mlir_addOne |
3.56374 ms (± 119.822) |
3.25749 ms (± 57.5499) |
1.09 |
single_compile_cpp_addOne |
21.7394 ms (± 797.618) |
24.4391 ms (± 405.29) |
0.89 |
single_compile_bc_addOne |
40.5651 us (± 10.7188) |
60.6703 us (± 11.2707) |
0.67 |
tiered_compile_sumLoop |
58.2289 us (± 13.5015) |
79.6202 us (± 15.7788) |
0.73 |
single_compile_mlir_sumLoop |
5.3712 ms (± 126.524) |
5.35632 ms (± 118.42) |
1.00 |
single_compile_cpp_sumLoop |
22.0802 ms (± 660.844) |
26.0899 ms (± 453.281) |
0.85 |
single_compile_bc_sumLoop |
55.5031 us (± 8.87159) |
78.6934 us (± 10.1775) |
0.71 |
This comment was automatically generated by workflow using github-action-benchmark.
Investigate `pathExplosion_baseline_threeCallsNoBranch` (27 RETURNs from
a chain of three 3-return-leaf calls) and stage the infrastructure that
would let AliveVariableHash mix value-content rather than ValueRef
identity:
* TraceOperation gains a `contentHash` field, populated at op-creation
time from the op kind, result type, and each input's structural
fingerprint. Constants contribute their literal bits, so two CONSTs
with the same value share a content hash regardless of source
position.
* ExecutionTrace keeps a `valueRefContentHashes` lookup so the trace
contexts can resolve a content hash by ValueRef on every
allocate/freeValRef call. Assignments forward the source's content
hash to the target; function arguments seed deterministically from
(index, type) so re-traces of the same function yield the same seed.
* AliveVariableHash::increment/decrement now take `(id, contentHash)`
and both trace contexts plumb the hash through allocate/freeValRef.
The mix function itself is still `id * HASH_MULTIPLIER` — flipping it to
return `contentHash` collapses the path-explosion fixture to its
theoretical minimum (27 -> 3 RETURNs, confirmed end-to-end) but trips a
latent SSA-propagation gap when `processControlFlowMerge` then has to
bridge inlined-callee local ValueRefs across paths. Closing that gap is
out of scope for this commit; see the plan note for the full taxonomy
of explored strategies and the open follow-ups.
Full execution suite (10003 assertions across 59 cases) and full
tracing suite (4490 assertions across 15 cases) stay green with this
scaffolding in place.
https://claude.ai/code/session_01CMziZcUBzyfHf2h2uBuv65
PhilippGrulich
force-pushed
the
claude/reduce-path-explosion-fkmWu
branch
from
May 14, 2026 17:56
4ce531c to
22a6ec8
Compare
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.
Summary
Investigation of
pathExplosion_baseline_threeCallsNoBranch(27 RETURNs from a chain of three 3-return-leaf calls) plus the full set of scaffolding needed to land the value-content-hashing strategy from the plan.The user-facing path-explosion reduction is NOT activated in this PR (mix() still returns
id * HASH_MULTIPLIER) because flipping it breaks tests in ways the bridging code can almost-but-not-quite paper over. The scaffolding is committed so that the follow-up SSA-side work has a clean substrate to plug into.What's in this PR
TraceOperation.contentHash— populated at op-creation time from the op kind, result type, and each input's structural fingerprint.ConstantLiteralinputs mix their literal bits.ExecutionTrace.valueRefContentHashes— per-ValueReflookup so the trace contexts can resolve a content hash on everyallocate/freeValRef. Assignments forward the source's content hash to the target; function arguments seed deterministically from(index, type).ExecutionTrace.bridgeMergeBlockNonLocals— utility that, given a freshly-created merge block and a current-path block, emitsASSIGN <reference-ref> <currentBlock-ref>for each non-localValueRefwhose content hash matches a value produced locally on the current path. Wired in privately; not invoked yet (see below).AliveVariableHash::increment/decrementnow take(id, contentHash). Both trace contexts plumb the hash throughallocate/freeValRef.What was prototyped on this branch and reverted
Three orthogonal switches that individually each break a different subset of tests; all kept out of this PR:
AliveVariableHash::mixto usecontentHash. CollapsespathExplosion_baseline_threeCallsNoBranchexactly to its theoretical minimum (27 → 3 RETURNs, ~6 blocks). However,pathExplosion_postCallBranch_2/_3then crash inSSACreationPhasewithWrong number of arguments in trace: expected 1, got N(N up to 8). Root cause: content-hash equivalence is not the same as semantic-role equivalence. When two CONST 1 ops live in the same block (one is the leaf-internalv == 1literal, the other is from the post-call branch'sa + 1etc.),bridgeMergeBlockNonLocalscannot tell which one represents the canonical outer-scope variable, and SSA propagation through the wrong bridge ends up promoting unrelated ValueRefs to phantom function arguments.AliveVariableHash. Motivated by the XOR-rollover canceling pair of same-content alive vars. Addition fixes that aliasing but trips the same SSA failure mode on a wider set of fixtures.traceConstant's alias-via-ASSIGN trick to every value-producing op. SidestepsprocessControlFlowMergeentirely for value ops. Preserves thepathExplosion_*shape but regresses 534/4364 tracing assertions because every other test that legitimately wanted a control-flow merge (e.g.selectFnPtrINDIRECT_CALL + RETURN merge) gets per-path duplication instead.Why land just the scaffolding
All three viable variants need a phi-aware SSA propagator (or a richer ValueRef metadata channel) that can distinguish "this ValueRef is the outer
a" from "this ValueRef happens to hold CONST 1 today." Both are out of scope for this branch — but both are unblocked by the plumbing here: a follow-up PR can flipmix()and callbridgeMergeBlockNonLocals(...)fromprocessControlFlowMergeafter SSACreationPhase learns to bridge properly.The full strategy taxonomy and the catalogue of explored variants is documented in
/root/.claude/plans/investigate-the-baseline-threecallsnobra-pure-otter.md.Test plan
nautilus-execution-tests— 10003/10003 assertions pass across 59 casesnautilus-tracing-tests— 4490/4490 assertions pass across 15 casesmix()to flip in the same PRhttps://claude.ai/code/session_01CMziZcUBzyfHf2h2uBuv65