fix(nvvm): keep address-space-qualified pointers out of the generic space#404
Open
nh13 wants to merge 1 commit into
Open
fix(nvvm): keep address-space-qualified pointers out of the generic space#404nh13 wants to merge 1 commit into
nh13 wants to merge 1 commit into
Conversation
…pace
Every access to an `#[address_space(shared)]` (or `constant`) static was
emitted as a *generic* access behind an `addrspacecast`, because `load`,
`volatile_load`, `gep`, `inbounds_gep` and `check_store` all cast their
pointer operand through `type_ptr_to`, which always yields an addrspace 0
pointer.
Correctness then rests entirely on libNVVM's `InferAddressSpaces` pass
folding the cast back into the access. It usually does, which is why this
mostly works. When it does not, the access is emitted as a generic `ld`/`st`
against the raw shared window offset -- an out-of-bounds access at runtime,
with no error or warning at compile time, and only on the warps whose data
reaches the affected branch.
Cast the pointer operand in the address space it already points into
instead. For the kernel added in this commit the emitted NVVM IR goes from
%22 = getelementptr inbounds i32,
i32* addrspacecast (i32 addrspace(3)* ... @sh ... to i32*), i64 %18
store i32 %1, i32* %22, align 4
to
%22 = getelementptr inbounds i32, i32 addrspace(3)* ... @sh ..., i64 %18
store i32 %1, i32 addrspace(3)* %22, align 4
`raw_eq`'s integer-compare fast path had the same defect and is fixed with
it. Casts that are *semantically* required -- a pointer passed to a callee
whose parameter is generic, or to `memcmp` -- are left alone; those still go
through `bitcast`, which addrspacecasts to generic as before.
Add a `dis` compiletest pinning the shared-memory codegen shape: the kernel
must use `st.shared`/`ld.shared` on the raw symbol offset and needs no
`cvta.shared`.
Refs Rust-GPU#402
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs #402.
The defect
Every access to an
#[address_space(shared)](orconstant) static is emitted as a generic access behind anaddrspacecast.load,volatile_load,gep,inbounds_gepandcheck_storeall cast their pointer operand throughtype_ptr_to, which always yields an addrspace 0 pointer, andbitcast/pointercastinsert anaddrspacecast (3 -> 0)to make that type-check.Correctness then rests entirely on libNVVM's
InferAddressSpacespass folding the cast back into the access. It usually does -- which is why static shared memory mostly works today, and why a small reproducer does not reveal anything. When the fold misses, the access is emitted as a genericld/stagainst the raw shared window offset. That is an out-of-bounds access at runtime, with no error or warning at compile time.That is exactly the failure in #402: 106 of 108 accesses in the reporting kernel became
ld.shared/st.shared, and 2 -- both to the same symbol, both inside a data-dependent branch -- stayed generic, socompute-sanitizerreported an invalid 4-byte write to0x26c, the raw shared offset of that symbol's slot.Here is the emitted NVVM IR for the kernel added in this PR, before this change:
Both the GEP and the access are in the generic space, and the only thing standing between that and a bad access is a libNVVM optimization pass.
The fix
Cast the pointer operand in the address space it already points into, via a new
pointercast_preserving_addrspacehelper. Same kernel, after:The access is now
sharedby construction rather than by inference.raw_eq's integer-compare fast path (intrinsic.rs) had the same defect -- it bitcast both operands to a generic pointer before loading -- and is fixed with it.Casts that are semantically required are deliberately left alone: a pointer passed to a callee whose parameter type is a generic pointer, and the
memcmpbranch ofraw_eq, still go throughbitcast, which addrspacecasts to generic exactly as before. Those are the cases where a generic pointer is genuinely what the callee expects.On testing, and what I could not show
I was not able to construct a small kernel whose PTX differs before and after. I tried a lane-0 store plus post-
__syncwarpbroadcast read, aselectbetween two shared statics (a shapeInferAddressSpaceshandles poorly), and forcing-Copt-level=0; in every case libNVVM recovered the shared space both before and after, so the PTX is identical. This matches the reporter's own experience in #402 -- their synthetic reproducer did not trigger it either, and the trigger appears to depend on surrounding context such as function size, inlining, or register pressure.So this PR should be judged on the IR shape, which is verifiable and shown above, rather than on a PTX-level regression test. The included
distest pins the shared-memory codegen shape --st.shared/ld.sharedon the raw symbol offset, nocvta.shared-- which guards against regression but would not have caught the original bug.Verification
In
ghcr.io/rust-gpu/rust-cuda-ubuntu24-cuda12:compute_61,compute_75,compute_90(64 passed, 3 ignored).cargo fmt --all -- --checkandrustfmt --check tests/compiletests/ui/**/*.rsclean.cargo clippy -p rustc_codegen_nvvmclean withRUSTFLAGS=-Dwarnings.atomic_opcarried aFIXMEstating that it assumes all pointers are generic. Emitted PTX forfetch_add/loadon a sharedAtomicU32is byte-identical before and after: thellvm.nvvm.isspacep.*predicates declare generic parameters, socheck_callcasts for those calls while the atomic itself stays on the shared pointer, givingatom.shared.add.u32. TheFIXMEis updated to say what is actually left to do -- skip the runtime predicate dance when the address space is statically known.🤖 Generated with Claude Code