the JIT parks a statement after a terminator in its own dead block; the dim-expr folding fixture halves by a multiply - #3964
Conversation
… nested block's return / break / continue, or the constant-condition if the compiler unfolds even unoptimized, left the tail after the ret in the same basic block (LLVM "Terminator found in the middle of a basic block", surfaced by test_step_smoothstep's HOST_FAST_MATH skip arm under options optimize = false); tests/jit_tests/terminated_block_tail.das pins every shape; the dim-expr folding fixture halves its vector by a multiply, since a -ffast-math host divides by a runtime splat through rcpps one ULP off; codegen version 0x76, emitter pin re-hashed Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
There was a problem hiding this comment.
🟢 Approval recommended
The JIT fix is localized, follows an existing dead-block pattern already used in the emitter, and is backed by targeted regression tests plus the necessary codegen cache invalidation.
Pull request overview
This PR fixes an LLVM IR validity bug in the dasLLVM JIT emitter where code generation could append instructions after a terminator inside the same basic block (triggering LLVM verifier failures). It also hardens fast-math test stability by adjusting a dim-expr folding fixture to avoid an imprecise vector-division lowering under -ffast-math.
Changes:
- Park the LLVM IR builder in a fresh “dead” basic block when a block already terminates, so subsequently visited (unreachable) statements don’t land after a terminator in the same block.
- Add a JIT regression test covering multiple “statement-after-terminator” shapes under
options optimize = false. - Update the dim-expr folding fixture to use
* 0.5instead of/ 2.0, and bump JIT codegen version + emitter hash.
File summaries
| File | Description |
|---|---|
| tests/lint/_fixture_dim_expr_folding.das | Makes the “half vector” fixture stable under fast-math by using an exact multiply-by-0.5 form. |
| tests/jit_tests/terminated_block_tail.das | Adds coverage for unreachable tail statements after return/break/continue and skip+return shapes. |
| modules/dasLLVM/daslib/llvm_jit.das | Adjusts block-expression visitation to relocate emission to a dead block when the current block already has a terminator. |
| modules/dasLLVM/daslib/llvm_jit_run.das | Bumps LLVM_JIT_CODEGEN_VERSION and updates the emitter hash to invalidate cached artifacts. |
Review details
- Files reviewed: 4/4 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
🟡 Changes recommended
The PR description claims several additional fixes/files that do not appear to be included in the presented changes, so the PR metadata must be aligned with the actual diff (or the missing commits added) before approval.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
- Files reviewed: 4/4 changed files
- Comments generated: 1
- Review effort level: Lite
| if (current_block_terminates()) { | ||
| //! a statement after a terminator - a nested block's return / break / continue, or the | ||
| //! constant-condition if the compiler unfolds even unoptimized - lands in its own dead block | ||
| LLVMPositionBuilderAtEnd(g_builder, append_basic_block("dead_at_line_{int(expr.at.line)}_")) | ||
| } |
Why. The JIT rejected any function that has a statement after a terminator inside one block list: a nested block's
return/break/continue, or the constant-conditionifthe compiler unfolds even unoptimized. The tail was appended after theretin the same basic block and LLVM's verifier failed the function ("Terminator found in the middle of a basic block"). The new fast-math nightly lane hit it first, throughtest_step_smoothstep.das'sif (HOST_FAST_MATH) { skip; return }arm underoptions optimize = false. The same lane showedtests/lint/test_dim_expr_folding.dasasserting an exactfloat4 / 2.0fold that a gcc-ffast-mathhost answers one ULP off.What changes.
preVisitExprBlockExpressionparks the builder in a fresh dead basic block when the current block already terminates, so the tail becomes unreachable IR instead of an invalid block.tests/jit_tests/terminated_block_tail.daspins every shape (bare-block return, constant-if return, break and continue inside a nested block, void bare-block return, the skip-then-return shape) underoptions optimize = false.* 0.5instead of/ 2.0: a-ffast-mathhost lowers a vector division by a runtime splat torcppsplus one Newton step, while a multiply by 0.5 is exact on every host.LLVM_JIT_CODEGEN_VERSION0x76, emitter pin re-hashed.Observable behavior.
options optimize = false+ a terminated nested block + a following statement: JIT panicFailed to get IR of '<fn> implementation'-> compiles and runs, the tail never executes.test_step_smoothstep.das(1 error) andtest_dim_expr_folding.das(1 failed) -> green.Where to look. The one new branch in
llvm_jit.daspreVisitExprBlockExpression; the dead block is the same devicevisitExprIfThenElsealready uses for a fully-terminating if/else.Validation, claims, ledger
Validation
-DDAS_FAST_MATH=ONRelease build (zen4, g++ 12,HOST_FAST_MATHtrue):VEC_HALFanswered0.49999997, 0.99999994, 1.4999999, 1.9999999in interpreter and JIT; thercppslowering confirmed from the assembly of_mm_div_ps(a, _mm_set1_ps(b))under-O2 -ffast-math. The terminator bug reproduces on any host with-jit.test_dim_expr_folding.das,test_step_smoothstep.dasand the new test green under both tiers, the smoothstep vector arm skipping as designed.tests/jit_testssweep under-jit(389 passed, 1 skipped), the dasLLVM module-owned suite under-jit(84 passed), make-prjit-smoke, lint and format on the changed files. The TDD audit's negative controls: the new test fails at every pinned shape with the emitter change reverted.tests/jit_testsis gated off under--use-aot; the fixture change is exercised by the AOT'dtest_dim_expr_folding.dason the nightly).Claims - stated, not tested
-ffast-mathhost's vector division by a runtime splat is one ULP off in the interpreter tier. Measured on zen4, pinned by no test after this change; whether it is ledgered as an accepted host property is a separate ruling.Not done
tests/module_cache/test_default_cache_path.daschildren (empty output, rc 0, every nightly since 2026-09-04), the Windows watchdog string-heap bound, the MinGW isolated-mode sharing violation, the tsantest_trayQuit exit code.