Skip to content

Scaffold value-content hashing in AliveVariableHash - #285

Draft
PhilippGrulich wants to merge 1 commit into
mainfrom
claude/reduce-path-explosion-fkmWu
Draft

Scaffold value-content hashing in AliveVariableHash#285
PhilippGrulich wants to merge 1 commit into
mainfrom
claude/reduce-path-explosion-fkmWu

Conversation

@PhilippGrulich

@PhilippGrulich PhilippGrulich commented May 14, 2026

Copy link
Copy Markdown
Member

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. ConstantLiteral inputs mix their literal bits.
  • ExecutionTrace.valueRefContentHashes — per-ValueRef lookup so the trace contexts can resolve a content hash on every allocate/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, emits ASSIGN <reference-ref> <currentBlock-ref> for each non-local ValueRef whose content hash matches a value produced locally on the current path. Wired in privately; not invoked yet (see below).
  • AliveVariableHash::increment/decrement now take (id, contentHash). Both trace contexts plumb the hash through allocate/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:

  1. Flipping AliveVariableHash::mix to use contentHash. Collapses pathExplosion_baseline_threeCallsNoBranch exactly to its theoretical minimum (27 → 3 RETURNs, ~6 blocks). However, pathExplosion_postCallBranch_2 / _3 then crash in SSACreationPhase with Wrong 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-internal v == 1 literal, the other is from the post-call branch's a + 1 etc.), bridgeMergeBlockNonLocals cannot 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.
  2. Modular-addition combine in 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.
  3. Generalising traceConstant's alias-via-ASSIGN trick to every value-producing op. Sidesteps processControlFlowMerge entirely for value ops. Preserves the pathExplosion_* shape but regresses 534/4364 tracing assertions because every other test that legitimately wanted a control-flow merge (e.g. selectFnPtr INDIRECT_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 flip mix() and call bridgeMergeBlockNonLocals(...) from processControlFlowMerge after 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 cases
  • nautilus-tracing-tests — 4490/4490 assertions pass across 15 cases
  • Built Release / Clang 18; no behavioural change so cross-compiler matrix CI is the source of truth
  • (Follow-up) Re-run when phi-aware SSA propagation lands; expect path-explosion reference dumps to shrink substantially and mix() to flip in the same PR

https://claude.ai/code/session_01CMziZcUBzyfHf2h2uBuv65

@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: 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
PhilippGrulich force-pushed the claude/reduce-path-explosion-fkmWu branch from 4ce531c to 22a6ec8 Compare May 14, 2026 17:56
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