Skip to content

Metal: read byref kernel arguments in place - #1013

Merged
maleadt merged 1 commit into
tb/metal-pointer-wordsfrom
tb/metal-byref-args
Oct 14, 2026
Merged

maleadt merged 1 commit into
tb/metal-pointer-wordsfrom
tb/metal-byref-args

Conversation

@maleadt

@maleadt maleadt commented Oct 14, 2026

Copy link
Copy Markdown
Member

add_parameter_address_spaces! copies every byref kernel argument from device memory into a stack slot, because the kernel body expects AS 0 pointers. Optimization usually removes that copy. When the argument is indexed dynamically, the copy survives, and Apple's back-end miscompiles some of these kernels. In JuliaGPU/Metal.jl#1018, a closure capturing two arrays and a 4-element tuple (or SVector) indexed in a loop, and called at two sites, left 511 of 512 outputs unwritten:

expected: 1000.0, got (first 4): Float32[-1.0, -1.0, -1.0, -1.0]
wrong entries: 511 of 512

With MTL_SHADER_VALIDATION, or without any one of the dynamic indexing, the bounds checks or the second call site, the same kernel is correct.

Arguments whose only uses are plain loads, directly or through GEPs and bitcasts, are now passed as an addrspacecast of the device pointer. The InferAddressSpacesPass in finish_ir! turns those into device-memory loads, so no copy is made. This also matches what MSL does with buffer arguments. Arguments with other uses keep the copy. An example is a tuple passed to a non-inlined function that is also called with a stack tuple: its address space can't be inferred, and a device pointer left in AS 0 would be truncated like the pointers in #1012. Volatile and atomic loads also keep the copy.

This builds on #1012: a Julia object reference inside a byref argument is now loaded from device memory, which that PR makes safe.

Tested on Julia 1.10, 1.11 and 1.12 with the GPUCompiler Metal tests; the new IR test fails without the change. The Metal.jl reproducer is fixed and tested in JuliaGPU/Metal.jl#1012.

Byref kernel arguments arrive in device memory, and were copied to a stack
slot for the body, which expects AS 0 pointers. Optimization normally
removes that copy, but not when the argument is indexed dynamically, and
the AGX back-end miscompiles some kernels where it survives: a closure
capturing arrays and a dynamically indexed `SVector` wrote nothing for
most threads (JuliaGPU/Metal.jl#1018).

Pass arguments that are only loaded from as casts of the device pointer
instead, which address-space inference turns into device-memory loads.
Arguments with other uses, e.g. passed to a call that also receives stack
memory, are still copied.
@codecov

codecov Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 89.54%. Comparing base (8a07ca6) to head (434ec2f).

Additional details and impacted files
@@                    Coverage Diff                     @@
##           tb/metal-pointer-words    #1013      +/-   ##
==========================================================
+ Coverage                   89.52%   89.54%   +0.01%     
==========================================================
  Files                          31       31              
  Lines                        6924     6935      +11     
==========================================================
+ Hits                         6199     6210      +11     
  Misses                        725      725              

☔ 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.

@maleadt
maleadt merged commit e176f41 into tb/metal-pointer-words Oct 14, 2026
36 checks passed
@maleadt
maleadt deleted the tb/metal-byref-args branch October 14, 2026 14:49
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.

1 participant