Skip to content

Add explicit control-flow API (If/While/For) - #300

Draft
PhilippGrulich wants to merge 6 commits into
mainfrom
claude/nautilus-explicit-controlflow-api-41irz5
Draft

Add explicit control-flow API (If/While/For)#300
PhilippGrulich wants to merge 6 commits into
mainfrom
claude/nautilus-explicit-controlflow-api-41irz5

Conversation

@PhilippGrulich

Copy link
Copy Markdown
Member

What

Adds a lightweight, opt-in public API — nautilus/control_flow.hpp — that lets users state control flow explicitly via closure-style constructs instead of native C++ if/for over val<bool>:

#include <nautilus/control_flow.hpp>

val<int32_t> r = 1;
If(x == 42, [&]{ r = r + 1; }, [&]{ r = r + 42; });
For(val<int32_t>(0), n, [&](val<int32_t> i){ sum = sum + i; });
While([&]{ return b != 0; }, [&]{ auto t = b; b = a % b; a = t; });

It plugs into the existing engine/module — no engine API change; the constructs work inside any lambda passed to engine.registerFunction(...).

Why

Native control flow is traced by exception-based symbolic execution, which re-runs the whole function once per path — O(2^N) in branch count. The explicit constructs emit the CMP/JMP blocks directly in a single trace pass and never enqueue a symbolic path, so a function built entirely from them traces in exactly one iteration regardless of branch count.

Tracing-benchmark twins make the win concrete (Debug, cpp/bc/asmjit):

kernel implicit trace explicit trace
chainedIf10 ~51 ms ~5.8 ms
chainedIf100 ~1.34 s 5.8 ms (230×)

The explicit version is flat in branch count (chainedIf10Explicit ≈ chainedIf100Explicit), while the implicit one explodes.

How

  • ExecutionTrace gains emitCmpNoRecord / emitJmp / createMergeBlock — tag-free block emission that never touches the global tag map or the symbolic executor (so it can never trigger processControlFlowMerge).
  • TracingInterface gains explicit-CF primitives (emitExplicitCmp, openMergeBlock, switchToBlock, currentBlock, jumpTo), implemented once on the shared TraceContextBase so both exception-based and lazy tracing support them.
  • control_flow.hpp orchestrates the primitives in the tracing path and runs natively in the interpreter (non-tracing) path, mirroring select.hpp. Loops emit a real cyclic CFG (header + back-edge), so loop-carried values are reconciled by the existing SSA phase exactly like the implicit path. For lowers to While.

Mixing

Explicit constructs and implicit native control flow must not be mixed in the same traced function: a native branch triggers symbolic re-execution that would re-enter and double-emit the explicit constructs. This is detected (a second trace iteration) and rejected with a clear RuntimeException rather than miscompiling. Express all of a function's control flow with explicit constructs — they compose freely and arbitrarily nest among themselves. (A fully transparent mix would require single-pass replay against the symbolic re-execution machinery; deferred.)

Tests

ExplicitControlFlowTest.cpp covers correctness across the interpreter, every backend, and both trace modes — if-then, if-then-else, nested if, chained if, conditional-abs, counted/while/gcd/nested loops, if-in-loop, general three-clause for — plus the mixing-rejection guard. (363 assertions; full suite green with no regressions.) Because the explicit primitives throw on a second trace iteration, every passing explicit-only test is also a proof of single-pass tracing.

🤖 Generated with Claude Code

https://claude.ai/code/session_01SQ9LFon2N8u2RXdLmj1Z47


Generated by Claude Code

@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: ef68c45 Previous: d63bd8f Ratio
exec_mlir_add 11.0712 ns (± 0.967408) 10.5656 ns (± 1.08602) 1.05
exec_mlir_fibonacci 17.0254 us (± 1.85862) 13.0323 us (± 1.09201) 1.31
exec_mlir_sum 579.89 us (± 62.2236) 544.744 us (± 29.5581) 1.06
exec_cpp_add 4.64652 ns (± 0.66486) 4.75823 ns (± 1.02643) 0.98
exec_cpp_fibonacci 108.376 us (± 8.68802) 96.1834 us (± 7.67007) 1.13
exec_cpp_sum 23.6076 ms (± 290.035) 35.9165 ms (± 549.897) 0.66
exec_bc_add 45.3422 ns (± 7.3473) 43.6174 ns (± 3.93352) 1.04
exec_bc_fibonacci 700.998 us (± 12.5978) 842.262 us (± 14.6154) 0.83
exec_bc_sum 165.402 ms (± 2.56586) 179.187 ms (± 955.653) 0.92
exec_asmjit_add 3.54416 ns (± 0.265601) 3.60782 ns (± 0.497292) 0.98
exec_asmjit_fibonacci 22.2166 us (± 1.42737) 21.5143 us (± 4.60828) 1.03
exec_asmjit_sum 5.3001 ms (± 28.8712) 4.85738 ms (± 33.8584) 1.09
exec_bc_add_noRegAlloc 44.7281 ns (± 4.8541) 45.4957 ns (± 7.38134) 0.98
exec_bc_add_regAlloc 44.41 ns (± 4.80615) 43.8958 ns (± 4.00479) 1.01
exec_bc_fibonacci_noRegAlloc 708.888 us (± 39.7798) 844.035 us (± 14.8666) 0.84
exec_bc_fibonacci_regAlloc 700.92 us (± 11.1988) 842.01 us (± 14.1375) 0.83
exec_bc_sum_noRegAlloc 166.514 ms (± 13.4918) 178.948 ms (± 362.678) 0.93
exec_bc_sum_regAlloc 165.185 ms (± 1.24748) 179.176 ms (± 555.823) 0.92
ir_add 737.895 ns (± 42.0488) 795.982 ns (± 87.5329) 0.93
ir_ifThenElse 1.51726 us (± 111.785) 1.63138 us (± 141.868) 0.93
ir_deeplyNestedIfElse 3.28903 us (± 243.726) 3.34702 us (± 226.142) 0.98
ir_loop 1.56014 us (± 123.349) 1.64605 us (± 113.623) 0.95
ir_ifInsideLoop 2.71115 us (± 174.615) 2.88247 us (± 245.938) 0.94
ir_loopDirectCall 1.74363 us (± 119.54) 1.85863 us (± 162.199) 0.94
ir_pointerLoop 1.91153 us (± 118.01) 2.0172 us (± 179.225) 0.95
ir_staticLoop 1.47986 us (± 82.8024) 1.4698 us (± 172.512) 1.01
ir_fibonacci 1.68547 us (± 101.3) 1.74373 us (± 101.681) 0.97
ir_gcd 1.43211 us (± 132.305) 1.5363 us (± 98.0465) 0.93
ir_nestedIf10 7.6688 us (± 697.208) 7.53336 us (± 572.678) 1.02
ir_nestedIf100 90.6165 us (± 4.16844) 86.6056 us (± 6.84222) 1.05
ir_chainedIf10 11.8133 us (± 1.04125) 11.706 us (± 1.05245) 1.01
ir_chainedIf100 167.555 us (± 6.06906) 162.563 us (± 10.9684) 1.03
ir_loopExplicit 1.95978 us (± 118.748)
ir_gcdExplicit 1.33239 us (± 150.114)
ir_nestedIf10Explicit 10.2351 us (± 606.914)
ir_chainedIf10Explicit 11.3535 us (± 794.845)
ir_chainedIf100Explicit 151.702 us (± 7.85716)
comp_mlir_add 6.73455 ms (± 285.356) 5.59561 ms (± 178.594) 1.20
comp_mlir_ifThenElse 7.52709 ms (± 211.482) 6.2488 ms (± 412.914) 1.20
comp_mlir_deeplyNestedIfElse 6.32428 ms (± 251.624) 5.04225 ms (± 200.911) 1.25
comp_mlir_loop 8.87658 ms (± 245.137) 7.12491 ms (± 237.471) 1.25
comp_mlir_ifInsideLoop 31.7429 ms (± 295.249) 28.6722 ms (± 476.575) 1.11
comp_mlir_loopDirectCall 13.9529 ms (± 343.037) 11.808 ms (± 265.45) 1.18
comp_mlir_pointerLoop 30.6671 ms (± 409.687) 27.8439 ms (± 674.969) 1.10
comp_mlir_staticLoop 5.97853 ms (± 188.177) 4.99347 ms (± 156.303) 1.20
comp_mlir_fibonacci 12.9372 ms (± 257.21) 10.3822 ms (± 196.241) 1.25
comp_mlir_gcd 11.2887 ms (± 226.464) 9.44927 ms (± 264.935) 1.19
comp_mlir_nestedIf10 12.5487 ms (± 260.307) 10.4284 ms (± 206.842) 1.20
comp_mlir_nestedIf100 27.1037 ms (± 203.028) 24.8611 ms (± 502.505) 1.09
comp_mlir_chainedIf10 11.7744 ms (± 206.076) 9.5372 ms (± 230.498) 1.23
comp_mlir_chainedIf100 23.0814 ms (± 240.101) 20.5244 ms (± 490.216) 1.12
comp_mlir_loopExplicit 8.96576 ms (± 227.49)
comp_mlir_gcdExplicit 11.1918 ms (± 182.115)
comp_mlir_nestedIf10Explicit 12.4176 ms (± 263.98)
comp_mlir_chainedIf10Explicit 11.4817 ms (± 247.884)
comp_mlir_chainedIf100Explicit 22.5637 ms (± 241.509)
comp_cpp_add 29.8458 ms (± 502.834)
comp_cpp_ifThenElse 30.1822 ms (± 412.019)
comp_cpp_deeplyNestedIfElse 31.4409 ms (± 392.926)
comp_cpp_loop 30.6173 ms (± 414.894)
comp_cpp_ifInsideLoop 31.4386 ms (± 462.842)
comp_cpp_loopDirectCall 30.8984 ms (± 514.651)
comp_cpp_pointerLoop 30.8774 ms (± 454.299)
comp_cpp_staticLoop 30.6028 ms (± 602.088)
comp_cpp_fibonacci 30.6823 ms (± 455.166)
comp_cpp_gcd 30.9411 ms (± 404.776)
comp_cpp_nestedIf10 33.3159 ms (± 535.384)
comp_cpp_nestedIf100 66.8244 ms (± 302.102)
comp_cpp_chainedIf10 35.8279 ms (± 510.534)
comp_cpp_chainedIf100 97.8869 ms (± 1.50644)
comp_cpp_loopExplicit 31.1342 ms (± 342.155)
comp_cpp_gcdExplicit 30.5738 ms (± 313.009)
comp_cpp_nestedIf10Explicit 34.3866 ms (± 442.1)
comp_cpp_chainedIf10Explicit 35.2514 ms (± 609.732)
comp_cpp_chainedIf100Explicit 85.7684 ms (± 444.66)
comp_bc_add 13.9169 us (± 2.55371)
comp_bc_ifThenElse 15.7682 us (± 2.75612)
comp_bc_deeplyNestedIfElse 19.791 us (± 4.99383)
comp_bc_loop 15.8716 us (± 2.93568)
comp_bc_ifInsideLoop 18.3923 us (± 4.1608)
comp_bc_loopDirectCall 16.0146 us (± 3.6726)
comp_bc_pointerLoop 17.716 us (± 4.42899)
comp_bc_staticLoop 15.7268 us (± 3.1669)
comp_bc_fibonacci 16.4309 us (± 3.09246)
comp_bc_gcd 15.6309 us (± 2.96836)
comp_bc_nestedIf10 30.6249 us (± 4.79893)
comp_bc_nestedIf100 195.111 us (± 12.7317)
comp_bc_chainedIf10 43.523 us (± 9.2614)
comp_bc_chainedIf100 311.668 us (± 13.0632)
comp_bc_loopExplicit 16.8916 us (± 3.66805)
comp_bc_gcdExplicit 15.3346 us (± 2.82387)
comp_bc_nestedIf10Explicit 34.2867 us (± 5.27455)
comp_bc_chainedIf10Explicit 33.5051 us (± 4.96747)
comp_bc_chainedIf100Explicit 241.59 us (± 10.909)
comp_asmjit_add 17.5392 us (± 5.62026)
comp_asmjit_ifThenElse 26.3073 us (± 6.14298)
comp_asmjit_deeplyNestedIfElse 50.1955 us (± 16.2805)
comp_asmjit_loop 28.8873 us (± 6.86097)
comp_asmjit_ifInsideLoop 49.5814 us (± 12.4179)
comp_asmjit_loopDirectCall 32.0131 us (± 7.61984)
comp_asmjit_pointerLoop 34.8156 us (± 6.69106)
comp_asmjit_staticLoop 24.1165 us (± 6.16998)
comp_asmjit_fibonacci 31.7857 us (± 9.9986)
comp_asmjit_gcd 28.7462 us (± 7.34748)
comp_asmjit_nestedIf10 95.2504 us (± 14.7172)
comp_asmjit_nestedIf100 1.07993 ms (± 31.3352)
comp_asmjit_chainedIf10 148.139 us (± 18.7611)
comp_asmjit_chainedIf100 2.30046 ms (± 44.2273)
comp_asmjit_loopExplicit 33.6589 us (± 6.27493)
comp_asmjit_gcdExplicit 27.0775 us (± 7.78299)
comp_asmjit_nestedIf10Explicit 109.422 us (± 15.1011)
comp_asmjit_chainedIf10Explicit 122.285 us (± 15.8086)
comp_asmjit_chainedIf100Explicit 1.70584 ms (± 36.1135)
exec_bc_addOne 36.792 ns (± 5.16173) 36.9564 ns (± 5.11982) 1.00
exec_mlir_addOne 330.969 ns (± 9.37774) 282.96 ns (± 7.86376) 1.17
exec_cpp_addOne 4.16843 ns (± 0.552334) 3.98915 ns (± 0.469858) 1.04
exec_interpreted_addOne 39.7826 ns (± 2.00027) 39.6342 ns (± 2.2298) 1.00
ssa_add 167.937 ns (± 9.05868) 189.998 ns (± 20.8108) 0.88
ssa_ifThenElse 412.325 ns (± 37.6405) 498.381 ns (± 36.2383) 0.83
ssa_deeplyNestedIfElse 1.06388 us (± 82.9091) 1.2078 us (± 82.3775) 0.88
ssa_loop 434.723 ns (± 35.7133) 536.005 ns (± 34.4901) 0.81
ssa_ifInsideLoop 830.006 ns (± 89.8546) 949.222 ns (± 72.7998) 0.87
ssa_loopDirectCall 445.222 ns (± 39.6345) 545.93 ns (± 41.961) 0.82
ssa_pointerLoop 517.911 ns (± 39.4371) 635.604 ns (± 42.4872) 0.81
ssa_staticLoop 378.382 ns (± 22.0845) 511.315 ns (± 49.3922) 0.74
ssa_fibonacci 451.289 ns (± 31.8331) 565.925 ns (± 55.8251) 0.80
ssa_gcd 411.792 ns (± 35.2522) 498.53 ns (± 44.5426) 0.83
ssa_loopExplicit 532.876 ns (± 32.4148)
ssa_gcdExplicit 386.708 ns (± 20.9109)
ssa_nestedIf10Explicit 3.76737 us (± 341.651)
ssa_chainedIf10Explicit 4.22435 us (± 402.09)
ssa_chainedIf100Explicit 204.274 us (± 8.42827)
trace_add 2.35909 us (± 245.593) 2.34496 us (± 232.961) 1.01
completing_trace_add 2.35162 us (± 225.474) 2.44207 us (± 308.893) 0.96
trace_ifThenElse 8.54694 us (± 1.68798) 9.02627 us (± 1.38021) 0.95
completing_trace_ifThenElse 4.60148 us (± 649.739) 4.71302 us (± 618.171) 0.98
trace_deeplyNestedIfElse 25.6286 us (± 4.12392) 26.141 us (± 3.38505) 0.98
completing_trace_deeplyNestedIfElse 12.7623 us (± 2.05046) 13.1745 us (± 1.88355) 0.97
trace_loop 8.39181 us (± 1.59209) 8.63063 us (± 1.1372) 0.97
completing_trace_loop 4.65878 us (± 708.294) 4.67873 us (± 394.54) 1.00
trace_ifInsideLoop 16.355 us (± 4.03469) 16.9711 us (± 2.80042) 0.96
completing_trace_ifInsideLoop 8.45783 us (± 1.41354) 8.5433 us (± 1.04142) 0.99
trace_loopDirectCall 8.41977 us (± 1.68297) 8.80748 us (± 968.435) 0.96
completing_trace_loopDirectCall 5.0492 us (± 1.11871) 6.22222 us (± 2.08068) 0.81
trace_pointerLoop 13.9689 us (± 3.09042) 14.2787 us (± 2.09311) 0.98
completing_trace_pointerLoop 10.3055 us (± 1.94132) 10.4844 us (± 1.6894) 0.98
trace_staticLoop 7.42103 us (± 937.293) 7.36281 us (± 790.484) 1.01
completing_trace_staticLoop 7.44825 us (± 786.271) 7.34517 us (± 621.764) 1.01
trace_fibonacci 9.78045 us (± 1.57944) 10.1657 us (± 1.81837) 0.96
completing_trace_fibonacci 6.05948 us (± 790.3) 6.22191 us (± 551.559) 0.97
trace_gcd 7.69087 us (± 1.1662) 8.12246 us (± 1.16253) 0.95
completing_trace_gcd 3.92763 us (± 500.51) 4.14409 us (± 578.076) 0.95
trace_nestedIf10 40.3017 us (± 8.38889) 40.2189 us (± 5.18296) 1.00
completing_trace_nestedIf10 40.918 us (± 8.514) 38.9548 us (± 4.13451) 1.05
trace_nestedIf100 1.53557 ms (± 36.1092) 1.38063 ms (± 47.1346) 1.11
completing_trace_nestedIf100 1.53453 ms (± 37.2206) 1.39143 ms (± 27.2891) 1.10
trace_chainedIf10 96.5139 us (± 11.5154) 99.3271 us (± 8.40838) 0.97
completing_trace_chainedIf10 52.5404 us (± 10.7873) 51.5743 us (± 8.42379) 1.02
trace_chainedIf100 4.39674 ms (± 117.672) 4.43705 ms (± 51.9703) 0.99
completing_trace_chainedIf100 2.43518 ms (± 51.537) 2.31297 ms (± 41.3848) 1.05
trace_loopExplicit 7.66991 us (± 1.06937)
completing_trace_loopExplicit 7.71279 us (± 1.2076)
trace_gcdExplicit 3.51343 us (± 448.338)
completing_trace_gcdExplicit 3.59924 us (± 458.481)
trace_nestedIf10Explicit 44.184 us (± 10.2211)
completing_trace_nestedIf10Explicit 44.4474 us (± 10.9558)
trace_chainedIf10Explicit 21.0972 us (± 3.84807)
e2e_tiered_bc_to_mlir 49.3137 us (± 20.8453) 55.2826 us (± 11.295) 0.89
e2e_single_mlir 6.71334 ms (± 273.131) 5.51468 ms (± 173.212) 1.22
tiered_compile_addOne 49.1076 us (± 20.5234) 54.1272 us (± 7.73487) 0.91
single_compile_mlir_addOne 3.79132 ms (± 192.456) 3.22997 ms (± 116.608) 1.17
single_compile_cpp_addOne 29.774 ms (± 525.658) 24.7532 ms (± 459.823) 1.20
single_compile_bc_addOne 50.6676 us (± 20.1719) 56.0543 us (± 12.3518) 0.90
tiered_compile_sumLoop 64.931 us (± 21.858) 77.2887 us (± 16.7111) 0.84
single_compile_mlir_sumLoop 6.00627 ms (± 185.022) 5.19219 ms (± 79.0478) 1.16
single_compile_cpp_sumLoop 30.6289 ms (± 475.137) 25.472 ms (± 447.056) 1.20
single_compile_bc_sumLoop 66.2084 us (± 22.8259) 76.3645 us (± 15.2721) 0.87

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

claude added 5 commits June 18, 2026 20:44
Add a lightweight, opt-in public API (nautilus/control_flow.hpp) that lets
users state control flow explicitly via closure-style If/While/For constructs
instead of native C++ if/for over val<bool>.

Native control flow is traced by exception-based symbolic execution, which
re-runs the whole function once per path (O(2^N) in branch count). The explicit
constructs emit the CMP/JMP blocks directly in a single trace pass and never
enqueue a symbolic path, so a function built entirely from them traces in
exactly one iteration regardless of branch count. Loops emit a real cyclic CFG
(header with a back-edge), so loop-carried values are reconciled by the existing
SSA phase just like the implicit path.

Implementation:
- ExecutionTrace gains emitCmpNoRecord/emitJmp/createMergeBlock: tag-free block
  emission that never touches the global tag map or symbolic executor.
- TracingInterface gains explicit-CF primitives, implemented once on the shared
  TraceContextBase so both exception-based and lazy tracing support them.
- control_flow.hpp orchestrates the primitives in the tracing path and runs
  natively in the interpreter (non-tracing) path, mirroring select.hpp.

Mixing explicit constructs with implicit native control flow in the same traced
function is detected (a second trace iteration) and rejected with a clear error;
express all of a function's control flow with explicit constructs instead.

Tests cover correctness across the interpreter, every backend, and both trace
modes (nested ifs, while, gcd, nested/counted loops, if-in-loop, general for),
plus the mixing-rejection guard. Tracing benchmark gains explicit twins of the
path-explosion kernels: chainedIf100 traces ~230x faster explicitly and is flat
in branch count.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SQ9LFon2N8u2RXdLmj1Z47
The tracing benchmark's chainedIfExplicitN generated its explicit ifs with a
plain host for-loop, so the body operations (result = result + 1) all sat on the
same source line. Their snapshots aliased across iterations, triggering a
spurious control-flow merge that corrupted the trace and crashed
SSACreationPhase::getReturnBlock on an empty return set (the pre-existing
multi-return bug the SSA benchmark already skips chainedIf*/nestedIf* for).

Use a static_val<int32_t> loop counter so each unrolled iteration gets a distinct
snapshot hash, exactly as static_iterable loops do. The explicit control-flow
feature itself was never affected: the execution tests (spelled-out If/While/For)
pass on every backend including MLIR and both trace modes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SQ9LFon2N8u2RXdLmj1Z47
Add explicitLoopWithNativeIf / explicitWhileWithNativeIf kernels: an explicit
For/While loop whose body contains a native (implicit) if. The native branch
enqueues a symbolic path, so the function re-runs and the explicit loop's
emitters re-enter on the second iteration, where the mixing guard detects
getIterations() > 1 and throws. Asserted via REQUIRE_THROWS across every backend
and both trace modes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SQ9LFon2N8u2RXdLmj1Z47
Add larger explicit twins (chainedIf50/250/500Explicit) and the implicit
chainedIf500 to a tracing-only benchmark vector, so the front-end scaling curve
is chartable at 10/100/500 (implicit) and 10/50/100/250/500 (explicit). They are
kept out of the shared `tests` vector so the SSA/IR/backend-compilation
benchmarks don't run the 500-branch kernels.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SQ9LFon2N8u2RXdLmj1Z47
Add a "Explicit Control-flow Trace Test" case that runs the explicit If/While/For
kernels through the full trace pipeline (tracing, after_ssa, ssa_verify, ir,
after_constant_folding, after_empty_block_elim) under both tracing contexts, and
check the dumps against committed golden files under
test/data/explicit-control-flow-tests/.

The goldens confirm the explicit constructs lower to canonical CFG/SSA: branches
produce a ControlFlowMerge block with the merged value as a block argument, and
loops produce a header with a body back-edge and loop-carried values threaded as
block arguments -- identical in shape to the implicit path, and stable across
both ExceptionBasedTraceContext and LazyTraceContext.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SQ9LFon2N8u2RXdLmj1Z47
@PhilippGrulich
PhilippGrulich force-pushed the claude/nautilus-explicit-controlflow-api-41irz5 branch from a7b0ab8 to d025719 Compare June 18, 2026 20:49
…nd tests

Round of improvements to the explicit control-flow API:

- Break()/Continue(): early loop exit for explicit For/While. Tracing emits a
  jump to the loop's exit (Break) or continue target (Continue) via a per-trace
  loop-frame stack, then isolates trailing dead-path ops in a fresh unreachable
  block; the interpreter path throws a signal the loop catches, so both paths
  agree on what runs after a break/continue. For's continue target is its step
  block (C for-loop semantics: continue runs the step); While's is the header.
- Value-yielding If: a constrained overload returns the taken arm's val<T>
  (reconciled at the merge), selected when the arms return non-void. Only the
  taken arm is evaluated at runtime, unlike select().
- For(begin, end, step, body): custom (ascending) stride; the unit-stride form
  delegates to it. For loops now emit a dedicated step block directly.
- If/If-else deduplicated (no-else is If-else with an empty else arm).
- Mixing guard now keys on a per-trace sawImplicitBranch flag set in traceBool
  (both contexts), so mixing is rejected immediately and regardless of ordering
  (including a native if placed before an explicit construct), with no
  cross-function contamination.
- control_flow.hpp documents the host-loop static_val requirement (body ops on a
  shared source line otherwise alias and corrupt the trace).

Tests: Break/Continue (While/For/nested), value-If, strided For, and the
implicit-before-explicit guard ordering, all across every backend (incl. MLIR)
and both trace modes; plus an explicit-vs-implicit equivalence test that
compiles each explicit kernel and its native twin and asserts identical results.
Trace goldens regenerated and extended (14 kernels) — Break jumps to the exit
block, Continue back-edges through the step block.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SQ9LFon2N8u2RXdLmj1Z47
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