Skip to content

Metal: give every loop a single exit block - #1079

Merged
maleadt merged 1 commit into
mainfrom
tb/metal-loop-exits
Nov 5, 2026
Merged

maleadt merged 1 commit into
mainfrom
tb/metal-loop-exits

Conversation

@maleadt

@maleadt maleadt commented Nov 5, 2026

Copy link
Copy Markdown
Member

Fixes JuliaGPU/Metal.jl#1127.

lower_unreachable_control_flow! turns every exception into a branch to the kernel's return, so each bounds check becomes an exit of every loop around it. Apple's back-end has to structurize control flow, and with enough such exits it hangs in MTLCompilerService or silently skips code. On an M1 (macOS 27, Metal.jl 1.12.0):

  • #1127's mwe.jl compiles for K=14 and hangs for K=15 and 16;
  • mwe_wrong.jl skips the final store for K=4 and 6, and hangs for K=8;
  • a kernel with 14 unrolled in-bounds loads throws a spurious BoundsError;
  • AcceleratedKernels' columns scan through a strided view hangs under --check-bounds=yes on Julia 1.10, which is what turned AcceleratedKernels.jl#199's Metal CI red.

LLVM's AMDGPU pipeline runs FixIrreducible and UnifyLoopExits before StructurizeCFG for the same reason. This runs them at the end of lower_air!. It has to come after the last SimplifyCFG, which would merge the exit block back into separate exits. Two LLVM bugs need working around:

With the change, on the same M1:

  • mwe.jl K=15/16 and mwe_wrong.jl K=4/6/8 compile and give correct results;
  • exceptions are still reported, and only the threads that fail skip the rest of the kernel;
  • the AcceleratedKernels tests that hung pass.

Run time of bounds-checked loop kernels changes by up to 1.5%: a grid-stride sum takes 1639–1650 µs instead of 1635–1637 µs, and 8 unrolled checked loads per step take 1388 µs instead of 1367 µs (median of 50, three runs). Compile time is unchanged.

Metal.jl's test suite passes, except relocations, which fails the same way without this change.

@maleadt maleadt added the metal Stuff about the Apple metal back-end. label Nov 5, 2026
@codecov

codecov Bot commented Nov 5, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.90%. Comparing base (38a5625) to head (548f540).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@             Coverage Diff             @@
##             main    #1079       +/-   ##
===========================================
+ Coverage   74.85%   91.90%   +17.04%     
===========================================
  Files          31       31               
  Lines        7490     7731      +241     
===========================================
+ Hits         5607     7105     +1498     
+ Misses       1883      626     -1257     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

`lower_unreachable_control_flow!` turns every exception into a branch to the
kernel's return, which makes each bounds check an exit of the loops around
it. Apple's back-end, which has to structurize control flow, hangs on loops
with many such exits, or silently skips code after them (Metal.jl#1127).

Run FixIrreducible and UnifyLoopExits after the last SimplifyCFG, as LLVM
does before structurizing for AMDGPU. Before LLVM 24 these passes only
handle branches, so switches are lowered first, and LLVM 20-22 crash on
loops without exits, so functions with blocks that can't return are left
as they are.
@maleadt
maleadt force-pushed the tb/metal-loop-exits branch from b023e64 to 548f540 Compare November 5, 2026 13:28
@maleadt
maleadt merged commit c6a69fc into main Nov 5, 2026
29 of 30 checks passed
@maleadt
maleadt deleted the tb/metal-loop-exits branch November 5, 2026 13:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

metal Stuff about the Apple metal back-end.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Hang in MTLCompilerService

1 participant