julia
b4135368 - compiler: compose inner effect splits into enclosing kernel shadows

Commit
29 days ago
compiler: compose inner effect splits into enclosing kernel shadows Nanosoldier found large regressions (up to 53x on sum(skipmissing(...)), ~7x on logical-indexing sums, 6.6x on plain reductions) on the first benchmark run of the effect-precondition branch. Root causes and fixes: - Splits applied at per-element/iteration-protocol granularity expand into a guard diamond with an outlined checked call per element, which defeats vectorization - and, worse, the fallback invoke made `scan_nothrow_split_ir` reject any enclosing kernel, so mis-placed inner splits permanently blocked the correct kernel-level placement. The inliner now marks each split's guard branch with a new `IR_FLAG_SPLIT_GUARD` statement flag (which persists into cached optimized IR), and the nothrow-shadow synthesis recognizes the marked diamonds and composes them: the enclosing kernel's shadow keeps the guard and turns the fallback region into `return false`, while the assume variant folds the guard to the (equivalent) unchecked arm. Both arms of a split compute the same value, so a failing inner guard merely sends the enclosing kernel to its own checked fallback. - The shadow's load-taint analysis now exempts element loads whose memory eltype differs from every deleted store's memory eltype (`Memory{T}` and `Memory{S}` are distinct objects when `T !== S`), which lets kernels like `_unsafe_getindex!` - stores to the freshly allocated destination, guard conditions derived from mask chunk loads - synthesize their precondition. - `simd_index` gets its `@inbounds` back: the index is in bounds by construction of the `@simd` macro expansion, and the checked range access added per-iteration conditions to every `@simd` kernel's shadow that LLVM's IRCE cannot fold into endpoint comparisons (single-condition affine exit loops fold; multi-condition ones keep the check as an O(n) loop costing more than the guarded kernel). - The `SkipMissing` `mapreduce_impl` is restructured to the kernel-split pattern (`_mapreduce_impl_skipmissing_base` with plain checked element accesses) instead of per-element splits inside its loops. With these, `sum(::Vector)` and `sum(skipmissing(...))` benchmark at parity with master and `sum(A[mask])` recovers from 6x to 1.65x; the residual is the inner `iterate` shadow surviving as a second mask walk because its bound checks, while implied by dominating branches, are not folded - a follow-up (dominance-implication folding in shadows, or the planned shadow-as-CodeInstance restructuring) closes that. Deterministic codegen regression tests pin the structure of the three benchmark families: no bounds-error throws, vectorized fast loops, no `middle.split` (the signature of an unfolded check loop), and zero remaining inner fallback invokes after composition. This commit was written with the assistance of generative AI. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Author
Committer
Parents
Loading