Skip to content

Erase zero-count entries in AliveVariableHash to stop unbounded memory growth#379

Merged
PhilippGrulich merged 5 commits into
mainfrom
claude/pr-299-completion-f6x5sp
Jul 9, 2026
Merged

Erase zero-count entries in AliveVariableHash to stop unbounded memory growth#379
PhilippGrulich merged 5 commits into
mainfrom
claude/pr-299-completion-f6x5sp

Conversation

@PhilippGrulich

@PhilippGrulich PhilippGrulich commented Jul 9, 2026

Copy link
Copy Markdown
Member

Problem

AliveVariableHash tracks reference counts of alive value refs and feeds an incrementally maintained XOR hash into Snapshot, which keys the tag maps used for trace deduplication. Two behaviors combined into unbounded memory growth:

  1. decrement() never removed entries. When a value ref's count dropped to 0, the entry stayed in the unordered_map. Value refs are assigned monotonically and never reused (lastValueRef is not reset in resetExecution()), so the map grew with every value ref ever seen across all symbolic-execution iterations, not with the number of currently-alive values. Path-explosion traces accumulate millions of entries in a single trace() call.
  2. reset() skipped clearing when the hash was 0 — which is exactly the state a balanced trace iteration ends in (all counts back to 0 ⇒ hash back to 0). So the dead entries survived reset().

Because both trace contexts (ExceptionBasedTraceContext, LazyTraceContext) are thread_local statics, the high-water-mark map persisted for the lifetime of the thread.

This continues #299.

Fix

  • decrement() erases the entry when the count reaches 0. This is hash-neutral: a zero count contributes (id * HASH_MULTIPLIER) * 0 = 0 to the XOR hash, identical to an absent entry — verified op-by-op in simulation and by unit test. Snapshot hashes, trace deduplication, and generated IR are bit-identical.
  • reset() checks map emptiness instead of hash != 0. Also removes a latent edge case: live entries XOR-colliding to hash 0 would previously skip the clear and leak reference counts into the next trace iteration.

The map itself stays a plain std::unordered_map with the default allocator — no custom pool. Earlier iterations of this PR explored backing the map with a bespoke free-list node pool, and later with nautilus::common::Arena (the pool already used for Block/TraceOperation elsewhere in tracing). Both were dropped: a private pool/arena only saves a malloc/free pair per traced value at the cost of real complexity, and sharing the shared per-compile trace Arena is actually unsafe — that arena is only soft-reset once per whole compileToIR() call (it backs Block/TraceOperation across every symbolic-execution iteration), while AliveVariableHash::reset() runs after every single iteration; soft-resetting it there would destroy prior iterations' trace data. The two correctness fixes above are what actually bound memory, independent of the allocator.

Testing

  • New tracing-tests suite (AliveVariableHashTest.cpp, 6 test cases) covering erasure, hash-neutrality, monotonic-id churn, count sensitivity, and reset — all pass.
  • Rebased cleanly onto latest main (no conflicting changes to the touched files).
  • Full nautilus-execution-tests (68 cases, 18k+ assertions) and nautilus-value-tests (102 cases) pass locally (Clang 18, Release, MLIR backend disabled in this sandbox).
  • Format-checked with clang-format-18 against the touched file.

https://claude.ai/code/session_01AVr15a7dpeVNdNZTLoTB14

@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: ddb6437 Previous: b8dd034 Ratio
ir_add 743.592 ns (± 70.8213) 742.281 ns (± 41.9677) 1.00
ir_ifThenElse 1.57391 us (± 229.212) 1.56339 us (± 107.218) 1.01
ir_deeplyNestedIfElse 3.37869 us (± 511.485) 3.42081 us (± 271.821) 0.99
ir_loop 1.6173 us (± 178.68) 1.6419 us (± 134.059) 0.99
ir_ifInsideLoop 2.74202 us (± 272.978) 2.8871 us (± 265.433) 0.95
ir_loopDirectCall 1.80162 us (± 220.164) 1.86279 us (± 195.477) 0.97
ir_pointerLoop 1.9327 us (± 199.959) 2.02016 us (± 263.998) 0.96
ir_staticLoop 1.46382 us (± 198.085) 1.46367 us (± 138.16) 1.00
ir_fibonacci 1.73987 us (± 191.784) 1.79825 us (± 144.221) 0.97
ir_gcd 1.45737 us (± 138.556) 1.51341 us (± 134.02) 0.96
ir_nestedIf10 7.34478 us (± 697.334) 7.78206 us (± 595.531) 0.94
ir_nestedIf100 92.6554 us (± 5.02064) 90.7018 us (± 6.7776) 1.02
ir_chainedIf10 11.1421 us (± 1.10661) 12.0022 us (± 993.771) 0.93
ir_chainedIf100 162.557 us (± 10.9081) 170.028 us (± 7.34473) 0.96
trace_add 2.33854 us (± 282.155) 2.25855 us (± 217.367) 1.04
completing_trace_add 2.51491 us (± 730.145) 2.29968 us (± 300.37) 1.09
trace_ifThenElse 8.68549 us (± 1.52527) 8.686 us (± 1.25914) 1.00
completing_trace_ifThenElse 4.52756 us (± 615.142) 4.64187 us (± 617.505) 0.98
trace_deeplyNestedIfElse 26.5981 us (± 3.56642) 25.8425 us (± 3.10961) 1.03
completing_trace_deeplyNestedIfElse 12.8579 us (± 2.00781) 12.4296 us (± 1.66459) 1.03
trace_loop 8.95612 us (± 1.68138) 8.51884 us (± 1.15269) 1.05
completing_trace_loop 4.7559 us (± 871.662) 4.65133 us (± 641.911) 1.02
trace_ifInsideLoop 17.1318 us (± 2.61733) 16.6081 us (± 1.90395) 1.03
completing_trace_ifInsideLoop 8.30379 us (± 1.1652) 8.56428 us (± 1.11759) 0.97
trace_loopDirectCall 8.89417 us (± 1.32341) 8.64894 us (± 1.21339) 1.03
completing_trace_loopDirectCall 4.93124 us (± 878.932) 4.86008 us (± 551.4) 1.01
trace_pointerLoop 14.5232 us (± 2.32757) 14.0774 us (± 2.00367) 1.03
completing_trace_pointerLoop 9.75403 us (± 1.39424) 9.99139 us (± 1.30127) 0.98
trace_staticLoop 7.96176 us (± 1.24768) 7.26277 us (± 563.927) 1.10
completing_trace_staticLoop 7.83792 us (± 1.07118) 7.19712 us (± 623.954) 1.09
trace_fibonacci 10.0608 us (± 1.27938) 9.89803 us (± 1.20681) 1.02
completing_trace_fibonacci 6.08967 us (± 795.605) 6.03325 us (± 769.842) 1.01
trace_gcd 8.1964 us (± 1.73358) 7.94175 us (± 673.823) 1.03
completing_trace_gcd 4.2782 us (± 704.904) 3.92384 us (± 442.905) 1.09
trace_nestedIf10 42.8461 us (± 6.64065) 37.3578 us (± 3.54105) 1.15
completing_trace_nestedIf10 42.9378 us (± 7.16271) 37.2769 us (± 5.75806) 1.15
trace_nestedIf100 1.77103 ms (± 57.8928) 1.33037 ms (± 30.1631) 1.33
completing_trace_nestedIf100 1.81225 ms (± 127.196) 1.33986 ms (± 36.7337) 1.35
trace_chainedIf10 102.841 us (± 9.64099) 96.784 us (± 4.57549) 1.06
completing_trace_chainedIf10 48.3751 us (± 8.41192) 47.8214 us (± 7.00374) 1.01
trace_chainedIf100 4.86702 ms (± 40.8636) 4.41093 ms (± 51.8015) 1.10
completing_trace_chainedIf100 2.1574 ms (± 46.0892) 2.25717 ms (± 37.5278) 0.96
exec_bc_addOne 42.025 ns (± 7.85179) 39.9744 ns (± 2.3718) 1.05
exec_mlir_addOne 311.554 ns (± 18.0635) 277.677 ns (± 5.23679) 1.12
exec_cpp_addOne 3.97838 ns (± 0.492221) 3.98821 ns (± 0.691522) 1.00
exec_interpreted_addOne 40.5934 ns (± 3.73445) 39.6985 ns (± 2.82815) 1.02
tiered_twotier_addOne 3.56635 ms (± 183.024) 3.3377 ms (± 103.343) 1.07
tiered_singletier_addOne 3.43938 ms (± 152.638) 3.22336 ms (± 61.1001) 1.07
single_compile_mlir_addOne 3.36434 ms (± 141.768) 3.22428 ms (± 65.8991) 1.04
single_compile_cpp_addOne 27.0951 ms (± 513.127)
single_compile_bc_addOne 76.7788 us (± 15.8572)
tiered_twotier_sumLoop 5.92331 ms (± 276.785)
tiered_singletier_sumLoop 5.73514 ms (± 264.979)
single_compile_mlir_sumLoop 5.95812 ms (± 308.345)
single_compile_cpp_sumLoop 27.6916 ms (± 533.083)
single_compile_bc_sumLoop 135.816 us (± 20.37)
exec_mlir_add 11.3372 ns (± 2.14654) 10.2599 ns (± 0.992492) 1.11
exec_mlir_fibonacci 14.424 us (± 2.45447) 13.0377 us (± 1.24569) 1.11
exec_mlir_sum 605.223 us (± 32.4769) 537.475 us (± 25.7629) 1.13
exec_cpp_add 4.84653 ns (± 0.866453) 4.80435 ns (± 1.02799) 1.01
exec_cpp_fibonacci 49.5878 us (± 7.87288) 48.5904 us (± 5.15455) 1.02
exec_cpp_sum 11.4881 ms (± 127.773) 11.3728 ms (± 64.805) 1.01
exec_bc_add 54.2186 ns (± 2.59003) 53.6301 ns (± 3.18475) 1.01
exec_bc_fibonacci 432.444 us (± 12.5113) 431.493 us (± 14.5913) 1.00
exec_bc_sum 95.9254 ms (± 3.23793) 95.5996 ms (± 3.04744) 1.00
exec_tbc_add 27.2226 ns (± 3.1341) 26.5216 ns (± 2.46214) 1.03
exec_tbc_fibonacci 142.727 us (± 7.40961) 141.662 us (± 6.09139) 1.01
exec_tbc_sum 36.2648 ms (± 149.874) 36.2433 ms (± 192.856) 1.00
exec_asmjit_add 3.59786 ns (± 0.527682) 3.56266 ns (± 0.524138) 1.01
exec_asmjit_fibonacci 14.441 us (± 2.14843) 14.671 us (± 1.91917) 0.98
exec_asmjit_sum 2.82958 ms (± 66.8128) 2.79977 ms (± 49.8118) 1.01
exec_bc_add_passesOff 56.0616 ns (± 7.44431) 54.4062 ns (± 7.52734) 1.03
exec_bc_add_passesOn 55.015 ns (± 5.58572) 54.8347 ns (± 6.78687) 1.00
exec_bc_fibonacci_passesOff 433.461 us (± 15.6606) 429.789 us (± 9.27493) 1.01
exec_bc_fibonacci_passesOn 393.822 us (± 20.3345) 388.943 us (± 11.7719) 1.01
exec_bc_sum_passesOff 95.4558 ms (± 262.82) 95.4539 ms (± 1.74597) 1.00
exec_bc_sum_passesOn 79.7419 ms (± 533.886) 79.7138 ms (± 431.334) 1.00
exec_tbc_add_passesOff 28.2565 ns (± 4.2787) 27.0077 ns (± 2.68503) 1.05
exec_tbc_add_passesOn 26.3809 ns (± 3.03377) 26.6048 ns (± 3.16677) 0.99
exec_tbc_fibonacci_passesOff 150.349 us (± 14.4575) 142.94 us (± 7.39263) 1.05
exec_tbc_fibonacci_passesOn 144.495 us (± 7.81065) 152.659 us (± 18.8212) 0.95
exec_tbc_sum_passesOff 36.249 ms (± 165.95) 36.2238 ms (± 86.0456) 1.00
exec_tbc_sum_passesOn 34.1062 ms (± 927.993) 33.9653 ms (± 74.1128) 1.00
exec_asmjit_add_passesOff 3.50959 ns (± 0.384893) 3.53068 ns (± 0.432697) 0.99
exec_asmjit_add_passesOn 3.59035 ns (± 0.668828) 3.52149 ns (± 0.390779) 1.02
exec_asmjit_fibonacci_passesOff 14.5165 us (± 1.79634) 14.1191 us (± 949.378) 1.03
exec_asmjit_fibonacci_passesOn 14.2841 us (± 1.31028) 14.4479 us (± 2.14753) 0.99
exec_asmjit_sum_passesOff 2.93295 ms (± 415.679) 2.80538 ms (± 43.6552) 1.05
exec_asmjit_sum_passesOn 2.81129 ms (± 47.6499) 2.80368 ms (± 43.6946) 1.00
exec_bc_add_noRegAlloc 55.1563 ns (± 7.32758) 54.5475 ns (± 6.3398) 1.01
exec_bc_add_regAlloc 55.9449 ns (± 8.40788) 53.8899 ns (± 5.70758) 1.04
exec_bc_fibonacci_noRegAlloc 431.4 us (± 11.6304) 430.165 us (± 9.71077) 1.00
exec_bc_fibonacci_regAlloc 430.95 us (± 10.4108) 430.074 us (± 9.20928) 1.00
exec_bc_sum_noRegAlloc 95.5771 ms (± 511.099) 95.1171 ms (± 2.93074) 1.00
exec_bc_sum_regAlloc 95.5622 ms (± 755.96) 95.1403 ms (± 1.42962) 1.00
exec_bc_add_call 56.0882 ns (± 8.849) 55.4858 ns (± 8.71539) 1.01
exec_bc_add_switch 41.976 ns (± 7.14319) 41.6231 ns (± 4.1096) 1.01
exec_bc_add_threaded 41.0034 ns (± 7.41554) 39.6267 ns (± 6.65468) 1.03
exec_bc_fibonacci_call 434.427 us (± 15.5287) 432.963 us (± 14.5845) 1.00
exec_bc_fibonacci_switch 333.46 us (± 12.9876) 330.811 us (± 8.47363) 1.01
exec_bc_fibonacci_threaded 432.392 us (± 14.0243) 434.49 us (± 9.22697) 1.00
exec_bc_sum_call 95.452 ms (± 298.591) 95.6899 ms (± 2.52453) 1.00
exec_bc_sum_switch 69.7454 ms (± 3.83578) 69.285 ms (± 2.64056) 1.01
exec_bc_sum_threaded 86.9798 ms (± 1.18949) 87.3571 ms (± 2.84576) 1.00
exec_bc_add_threaded_noReuse 40.1163 ns (± 5.75657) 38.8922 ns (± 3.41876) 1.03
exec_bc_add_threaded_reuse 35.1347 ns (± 5.97195) 34.4268 ns (± 3.46021) 1.02
exec_bc_fibonacci_threaded_noReuse 431.77 us (± 11.4618) 434.944 us (± 10.1135) 0.99
exec_bc_fibonacci_threaded_reuse 434.017 us (± 14.9735) 435.098 us (± 10.641) 1.00
exec_bc_sum_threaded_noReuse 86.987 ms (± 1.2336) 87.0089 ms (± 2.65274) 1.00
exec_bc_sum_threaded_reuse 86.6595 ms (± 876.132) 86.4087 ms (± 597.879) 1.00
exec_bc_add_threaded_noSuperinstr 40.6616 ns (± 8.92038) 39.3729 ns (± 5.94698) 1.03
exec_bc_add_threaded_superinstr 39.6767 ns (± 5.86327) 40.5814 ns (± 7.68441) 0.98
ssa_add 205.671 ns (± 20.5613) 183.366 ns (± 15.7922) 1.12
ssa_ifThenElse 473.446 ns (± 64.7643) 450.743 ns (± 49.1275) 1.05
ssa_deeplyNestedIfElse 1.21039 us (± 178.219) 1.10237 us (± 81.2423) 1.10
ssa_loop 496.835 ns (± 49.0312) 480.131 ns (± 34.2545) 1.03
ssa_ifInsideLoop 936.286 ns (± 140.575) 852.146 ns (± 51.5692) 1.10
ssa_loopDirectCall 500.277 ns (± 52.4433) 512.538 ns (± 79.3245) 0.98
ssa_pointerLoop 599.972 ns (± 57.0143) 590.898 ns (± 56.5898) 1.02
ssa_staticLoop 455.788 ns (± 70.4368) 421.109 ns (± 79.5345) 1.08
ssa_fibonacci 533.955 ns (± 76.0904) 507.209 ns (± 40.3367) 1.05
ssa_gcd 477.277 ns (± 68.5635) 452.514 ns (± 54.9062) 1.05
comp_mlir_add 6.1529 ms (± 486.695) 5.5186 ms (± 135.775) 1.11
comp_mlir_ifThenElse 7.10586 ms (± 261.177) 6.07935 ms (± 168.463) 1.17
comp_mlir_deeplyNestedIfElse 5.40274 ms (± 332.795) 5.02185 ms (± 151.574) 1.08
comp_mlir_loop 7.8332 ms (± 536.553) 7.03297 ms (± 148.394) 1.11
comp_mlir_ifInsideLoop 30.3702 ms (± 496.318) 28.2506 ms (± 259.451) 1.08
comp_mlir_loopDirectCall 12.9328 ms (± 422.073) 11.6333 ms (± 138.879) 1.11
comp_mlir_pointerLoop 29.4408 ms (± 502.316) 27.548 ms (± 289.828) 1.07
comp_mlir_staticLoop 5.58518 ms (± 372.706) 4.96993 ms (± 92.4576) 1.12
comp_mlir_fibonacci 11.7359 ms (± 399.554) 10.392 ms (± 202.824) 1.13
comp_mlir_gcd 10.3729 ms (± 516.461) 9.35415 ms (± 119.233) 1.11
comp_mlir_nestedIf10 11.4301 ms (± 466.035) 10.4034 ms (± 106.314) 1.10
comp_mlir_nestedIf100 26.5692 ms (± 270.173) 24.9788 ms (± 370.991) 1.06
comp_mlir_chainedIf10 11.0409 ms (± 409.025) 9.48514 ms (± 115.029) 1.16
comp_mlir_chainedIf100 22.0198 ms (± 277.112) 20.3187 ms (± 235.389) 1.08
comp_cpp_add 27.1522 ms (± 595.587) 24.4457 ms (± 366.637) 1.11
comp_cpp_ifThenElse 27.5663 ms (± 613.643) 25.0058 ms (± 415.217) 1.10
comp_cpp_deeplyNestedIfElse 28.8159 ms (± 592.656) 25.8982 ms (± 321.486) 1.11
comp_cpp_loop 27.6739 ms (± 482.359) 25.1046 ms (± 310.986) 1.10
comp_cpp_ifInsideLoop 28.4692 ms (± 620.005) 26.0895 ms (± 372.518) 1.09
comp_cpp_loopDirectCall 27.3116 ms (± 537.233) 25.4035 ms (± 265.361) 1.08
comp_cpp_pointerLoop 28.5044 ms (± 396.947) 25.5093 ms (± 319.433) 1.12
comp_cpp_staticLoop 27.2132 ms (± 586.855) 24.9647 ms (± 475.07) 1.09
comp_cpp_fibonacci 27.3164 ms (± 745.208) 25.224 ms (± 331.321) 1.08
comp_cpp_gcd 27.365 ms (± 669.197) 25.0461 ms (± 347.959) 1.09
comp_cpp_nestedIf10 29.7084 ms (± 446.375) 28.2036 ms (± 562.953) 1.05
comp_cpp_nestedIf100 64.5718 ms (± 874.746) 60.9706 ms (± 353.624) 1.06
comp_cpp_chainedIf10 33.4107 ms (± 1.1255) 30.3968 ms (± 375.599) 1.10
comp_cpp_chainedIf100 95.6934 ms (± 1.25808) 90.5767 ms (± 566.613) 1.06
comp_bc_add 15.3836 us (± 3.00433) 14.7506 us (± 2.67409) 1.04
comp_bc_ifThenElse 19.8618 us (± 3.39279) 19.6298 us (± 3.12543) 1.01
comp_bc_deeplyNestedIfElse 25.2049 us (± 4.31322) 24.6746 us (± 3.94954) 1.02
comp_bc_loop 20.6725 us (± 4.38233) 20.3255 us (± 3.92218) 1.02
comp_bc_ifInsideLoop 24.2373 us (± 4.66663) 23.6933 us (± 3.57835) 1.02
comp_bc_loopDirectCall 21.1873 us (± 4.30744) 20.8464 us (± 3.73989) 1.02
comp_bc_pointerLoop 22.7781 us (± 4.56727) 23.5555 us (± 5.16724) 0.97
comp_bc_staticLoop 19.34 us (± 4.24293) 18.23 us (± 3.04065) 1.06
comp_bc_fibonacci 21.1699 us (± 4.01075) 20.0865 us (± 2.48422) 1.05
comp_bc_gcd 19.8965 us (± 3.94264) 19.5617 us (± 3.82711) 1.02
comp_bc_nestedIf10 49.5387 us (± 9.70649) 48.7329 us (± 9.28946) 1.02
comp_bc_nestedIf100 303.631 us (± 17.9486) 273.033 us (± 15.1814) 1.11
comp_bc_chainedIf10 65.6445 us (± 11.0502) 61.7581 us (± 8.25296) 1.06
comp_bc_chainedIf100 495.76 us (± 21.0681) 431.657 us (± 13.5542) 1.15
comp_tbc_add 1.8902 us (± 253.935) 1.84288 us (± 253.044) 1.03
comp_tbc_ifThenElse 4.16563 us (± 531.795) 4.22194 us (± 497.789) 0.99
comp_tbc_deeplyNestedIfElse 9.51489 us (± 1.31639) 9.48021 us (± 1.03183) 1.00
comp_tbc_loop 4.26096 us (± 652.897) 4.26501 us (± 528.041) 1.00
comp_tbc_ifInsideLoop 7.34602 us (± 962.544) 7.18277 us (± 790.801) 1.02
comp_tbc_loopDirectCall 4.4831 us (± 549.12) 4.4692 us (± 537.267) 1.00
comp_tbc_pointerLoop 5.48831 us (± 681.003) 5.3662 us (± 462.091) 1.02
comp_tbc_staticLoop 4.61722 us (± 559.174) 4.53092 us (± 529.845) 1.02
comp_tbc_fibonacci 4.57803 us (± 546.538) 4.61059 us (± 479.68) 0.99
comp_tbc_gcd 3.7324 us (± 382.317) 3.77157 us (± 375.579) 0.99
comp_tbc_nestedIf10 26.2756 us (± 2.92141) 26.0004 us (± 3.0856) 1.01
comp_tbc_nestedIf100 284.023 us (± 11.1073) 268.54 us (± 9.14892) 1.06
comp_tbc_chainedIf10 34.2593 us (± 4.16016) 32.789 us (± 2.92953) 1.04
comp_tbc_chainedIf100 416.656 us (± 15.5911) 381.085 us (± 10.8024) 1.09
comp_asmjit_add 24.165 us (± 6.83404) 23.5728 us (± 4.65715) 1.03
comp_asmjit_ifThenElse 32.9127 us (± 7.30321) 32.352 us (± 6.44941) 1.02
comp_asmjit_deeplyNestedIfElse 48.2776 us (± 8.8055) 48.8905 us (± 9.68158) 0.99
comp_asmjit_loop 32.6826 us (± 6.55386) 32.528 us (± 4.19591) 1.00
comp_asmjit_ifInsideLoop 48.0765 us (± 13.2094) 46.8938 us (± 8.33724) 1.03
comp_asmjit_loopDirectCall 36.2189 us (± 5.76828) 36.8493 us (± 5.29293) 0.98
comp_asmjit_pointerLoop 37.5053 us (± 5.76464) 45.2781 us (± 9.2684) 0.83
comp_asmjit_staticLoop 29.1531 us (± 4.78006) 28.7197 us (± 4.3565) 1.02
comp_asmjit_fibonacci 33.9588 us (± 5.57575) 34.6518 us (± 5.26375) 0.98
comp_asmjit_gcd 40.554 us (± 13.3958) 33.7223 us (± 5.63561) 1.20
comp_asmjit_nestedIf10 77.1397 us (± 11.0753) 76.5731 us (± 10.1747) 1.01
comp_asmjit_nestedIf100 507.862 us (± 23.3136) 490.942 us (± 15.0466) 1.03
comp_asmjit_chainedIf10 93.6335 us (± 10.8883) 93.4498 us (± 11.0021) 1.00
comp_asmjit_chainedIf100 657.692 us (± 41.3875) 629.576 us (± 17.626) 1.04
e2e_tiered_bc_to_mlir 3550.81 us (± 181787) 3295.34 us (± 75358.2) 1.08
e2e_single_mlir 6.35252 ms (± 226.945) 5.45698 ms (± 103.069) 1.16

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

@PhilippGrulich PhilippGrulich marked this pull request as ready for review July 9, 2026 06:24
claude added 5 commits July 9, 2026 08:24
…y growth

AliveVariableHash::decrement() left entries in the counts map at count 0,
so the map grew with every value ref ever seen during tracing rather than
with the number of currently-alive variables. Because the trace contexts
are thread_local and reset() skipped clearing when the hash was 0 (exactly
the state a balanced trace ends in), these dead entries survived reset()
and accumulated across trace iterations and across traced functions for
the lifetime of the thread.

Erasing entries when their count reaches zero is hash-neutral: a zero
count contributes (id * HASH_MULTIPLIER) * 0 = 0 to the XOR hash, which is
identical to an absent entry, so snapshot hashes — and therefore trace
deduplication and generated IR — are unchanged.

Also make reset() check map emptiness instead of the hash, which removes
the latent edge case where live entries XOR-colliding to hash 0 would
leak reference counts into the next trace iteration.

Add a size() accessor and unit tests covering erasure, hash-neutrality,
and reset behavior in a new tracing-tests suite.

https://claude.ai/code/session_01XTX9MjHW7Bgq2uSrFMXhiX
Value refs are assigned monotonically and never reused, so during tracing
the map sees a stream of ids that are incremented once and decremented to
zero shortly after. Verify that this churn neither perturbs the hash nor
grows the map while a long-lived entry survives it.

https://claude.ai/code/session_01XTX9MjHW7Bgq2uSrFMXhiX
…speed

Erasing zero-count entries bounds the map to O(alive variables) but routes
one malloc/free pair per traced value through the general-purpose
allocator, which slowed tracing of large functions by up to ~1.3x. Replace
the default allocator with an internal pool that hands map nodes out of
bump-allocated 16KB chunks and recycles freed nodes via per-size free
lists (the allocator is rebound to different types - nodes and bucket
arrays - so size classes must not be mixed).

This recovers most of the regression: the tracing benchmark suite runs
within ~4% of the pre-fix baseline (down from ~17% with plain erasure)
while the pool retains a single 16KB chunk per trace context across the
entire suite. A bump-only arena would not fit this workload: without
per-node reclamation, the insert/erase churn would regrow memory with
every value ref ever seen - the leak this fix removes.

https://claude.ai/code/session_01XTX9MjHW7Bgq2uSrFMXhiX
…espoke pool

Replaces the ad-hoc free-list NodePool/PoolAllocator with nautilus's
existing bump-pointer Arena (already used for Block/TraceOperation).
Arena only supports bulk reclamation, so node-level erase() stays for
hash correctness while the actual memory reclaim happens in reset(),
which resume() already calls after every symbolic-execution iteration.
Bucket arrays keep going through the regular heap so a softReset()
can never invalidate memory the map still references.
A shared trace Arena can't be soft-reset per iteration without destroying
prior iterations' Block/TraceOperation data, and a private Arena adds
complexity for a benefit that isn't worth the risk here. Fall back to a
plain std::unordered_map with the default allocator; the two correctness
fixes (erase on decrement, reset() checks emptiness) are what actually
bound memory, independent of the allocator backing the map.
@PhilippGrulich PhilippGrulich force-pushed the claude/pr-299-completion-f6x5sp branch from 0d2b151 to ddb6437 Compare July 9, 2026 06:24
@PhilippGrulich PhilippGrulich merged commit d25f722 into main Jul 9, 2026
19 checks passed
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