Skip to content

Load relocation table entries as words - #975

Merged
maleadt merged 2 commits into
mainfrom
tb/table-word-loads
Oct 3, 2026
Merged

maleadt merged 2 commits into
mainfrom
tb/table-word-loads

Conversation

@maleadt

@maleadt maleadt commented Oct 3, 2026 •

Copy link
Copy Markdown
Member

Since #960, Metal kernels that compare a Julia object against a relocated one give the wrong answer when Metal's shader validation is enabled. Metal.jl's relocations test shows it (JuliaGPU/Metal.jl#997, the :mag: API validation job):

function kernel(out, sym::Symbol)
    @inbounds out[1] = sym === :foo ? Int32(1) : (sym === :bar ? Int32(2) : Int32(-1))
    return
end

With MTL_SHADER_VALIDATION=1 this returns -1 for both :foo and :bar; without validation it works. The bisect points at 9d808bc ("Lower relocation slots by substituting table addresses"): GPUCompiler 2.9 passes, 2.10 and 2.11 fail. This isn't related to LLVM.jl 10.

Why

With the :table lowering, which Metal uses, relocations are words that Metal.jl's loader writes into a device buffer. #960 replaced each relocation slot by the address of its table entry, but the load from that address kept the type Julia gave it. For a Symbol, that's a pointer. So the kernel ended up reading a host address out of device memory as a pointer:

; 2.10, AS 0 is thread memory in AIR
%foo = load ptr, ptr addrspace(1) %entry, align 8, !nonnull !0
%arg = inttoptr i64 %sym to ptr
%eq  = icmp eq ptr %foo, %arg

On Metal, the default address space is thread memory, and shader validation instruments loads of thread pointers. A host address read that way doesn't keep its bits. Comparing the values as integers afterwards doesn't help: it's the pointer-typed load itself. Before #960, the lowering loaded an i64, which is what the table actually holds:

; this PR (and 2.9)
%foo = load i64, ptr addrspace(1) %entry, align 8
%eq  = icmp eq i64 %foo, %sym

What this changes

The :table lowering now loads every table entry as a word, and converts it back with inttoptr where the code used it as a pointer. That puts the table's contract (it holds the words resolved_relocation_table returns) where the representation changes, instead of special-casing Metal. Identity comparisons then become integer comparisons. The rest of #960 stays:

  • the address PHIs and selects, and address-space inference
  • the :patch and :bake lowerings, and cglobal collection

Loads keep their alignment, volatility, atomic ordering and metadata, except for metadata that only makes sense for pointers (!nonnull, !dereferenceable, ...). The comment in src/metal.jl that called address space 0 generic now says what it is in AIR: thread memory, modeled as the flat space only for address-space inference.

We also considered fixing this in the Metal back-end, by rewriting address-space-0 loads late, or in Metal.jl. Neither knows which pointers are really host identities, so they would have to guess.

Testing

  • Metal IR tests: the merged-slot test previously required load ptr, ptr addrspace(1), i.e. the bug. It now requires word loads, and a new Symbol test checks for word loads and integer comparisons (the latter on LLVM 20+, which folds icmp of two inttoptrs).
  • Native tests: new ones run pointer-valued relocations under :bake, :patch and :table.
  • Full suite: passes on Julia 1.12 and 1.13. The new tests fail without the fix.
  • Hardware: on an M1, Metal.jl's relocations test passes with and without shader validation on 1.12 and 1.13. Metal's full suite has no new failures.

This also bumps the version to 2.11.1, which Metal.jl#997 needs to go green.

The `:table` lowering replaces each relocation slot with the address of its
entry in the relocation table, but kept the type the slot was loaded as. Slots
holding host identities (a Symbol's address, a cglobal's type tag) are mostly
loaded as pointers, so their words ended up being loaded from device memory as
pointers into the default address space. On Metal that is thread memory, and
with shader validation enabled such a loaded value does not keep its bits:
comparing a Symbol argument against a relocated Symbol failed (Metal.jl's
relocations test under `MTL_SHADER_VALIDATION=1`). The offset-based lowering
this replaced loaded integer words, and did not have that problem.

The table holds words, which is what the loader delivers, so load every slot
entry as a word and convert it back with `inttoptr` where it was loaded as a
pointer. Comparisons then fold into integer ones on LLVM 20+. The access
properties and metadata carry over, except for metadata describing the loaded
pointer. Collection and the `:patch`/`:bake` lowerings are unchanged, and the
address PHIs and selects are still moved into the table's address space.

Also correct the comment that called AS 0 generic on Metal: it is thread memory
in AIR, and only modeled as the flat space for address-space inference.
@codecov

codecov Bot commented Oct 3, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.00000% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.73%. Comparing base (a47c2e4) to head (6cb69b9).

Files with missing lines Patch % Lines
src/relocation.jl 90.00% 2 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main     #975   +/-   ##
=======================================
  Coverage   87.72%   87.73%           
=======================================
  Files          30       30           
  Lines        6078     6097   +19     
=======================================
+ Hits         5332     5349   +17     
- Misses        746      748    +2     

☔ 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 6e279d5 into main Oct 3, 2026
35 checks passed
@maleadt
maleadt deleted the tb/table-word-loads branch October 3, 2026 19:04
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