Repository navigation
Emit LLVM nonnull / align / dereferenceable for proven Rust references #1098
Description
Motivation
Part of #413.
After #1062 preserves Rust pointer/reference kind through dialect MIR and #1089
adds conservatively proven noalias / readonly parameter attributes, the
compiler has the information needed to emit additional LLVM validity attributes
for Rust references.
Rust references carry stronger validity guarantees than raw pointers. In
particular, for sized pointees the compiler can conservatively preserve:
- non-nullness
- pointee alignment
- a dereferenceable byte count when the reference is known to remain valid for
the required extent
These facts should be derived while the original rustc Ty, layout, and trait
information are still available rather than inferred later from LLVM pointer
shape.
Proposed semantics
Use a conservative subset of rustc's reference parameter policy:
| Rust parameter | Condition | Additional LLVM attributes |
|---|---|---|
&T |
T: Sized |
nonnull, align N when N > 1 |
&mut T |
T: Sized |
nonnull, align N when N > 1 |
&T |
T: Sized + Freeze, size_of::<T>() > 0 |
dereferenceable(size_of::<T>()) |
&mut T |
T: Sized + Unpin, size_of::<T>() > 0 |
dereferenceable(size_of::<T>()) |
| unsized reference | — | no validity attributes in this change |
| raw pointer | — | no reference-derived validity attributes |
align 1 is omitted as redundant.
Zero-sized pointees never produce dereferenceable(0). An over-aligned ZST may
therefore carry nonnull align N without dereferenceable.
Implementation outline
- During MIR import, inspect the original rustc parameter type while type,
layout,Freeze, andUnpininformation is available. - Record only proven per-source-argument validity facts on
MirFuncOp. - Verify that reference-derived facts are attached only to source arguments
represented as Rust references. - Preserve the facts through MIR lowering.
- Remap source arguments to physical LLVM pointer parameters after ABI
flattening. - Make
llvm-exportmechanically serialize the already-proven attributes.
The downstream layers must not infer Rust validity semantics from pointer
mutability, address space, LLVM pointer shape, or DisjointSlice.
Conservative cases
This change intentionally does not emit validity attributes for:
- raw pointers
- erased/compiler-generated pointers
- unsized references
DisjointSlicebased solely on its lowered representation
For shared references containing UnsafeCell, nonnull and alignment remain
valid for sized pointees, but dereferenceable is not emitted unless the
required Freeze condition is proven.
For non-Unpin mutable references, nonnull and alignment remain valid for
sized pointees, but dereferenceable is not emitted.
Acceptance criteria
- sized
&Tand&mut Temitnonnull - pointee alignment greater than one emits LLVM
align N &TwithFreezeand non-zero pointee size emits
dereferenceable(size_of::<T>())&mut TwithUnpinand non-zero pointee size emits
dereferenceable(size_of::<T>())&UnsafeCell<T>does not acquiredereferenceable&mut !Unpindoes not acquiredereferenceable- over-aligned ZST references emit
nonnull align Nwithout
dereferenceable(0) - unsized references receive no validity attributes from this policy
- raw pointers receive no reference-derived validity attributes
- invalid attribute values/combinations are rejected
- legacy typed-pointer LLVM export continues to work
- existing CUDA examples continue to build
- workspace Clippy passes with warnings denied
Out of scope
- validity attributes for DST / slice references
dereferenceable_or_null- recursive provenance analysis
- deriving validity attributes from
DisjointSlice - deriving validity attributes from raw pointers
- general LLVM-level pointer inference
Activity
nihalpasham commentedon Aug 24, 2026
Keeping this open only as part of the same post-#413 design as #1088.
nonnullandalignmay be straightforward, butdereferenceableis an optimizer promise with more conditions than the table in this issue.UnsafeCell, pinned types, argument position, optimization mode, and erased reference information.No implementation should start until #1088 and #1098 share one auditable per-argument proof model that meets the current acceptance criteria.
nihalpasham commentedon Sep 3, 2026
This is the half of the #1088 design that can start now. Same rule: every fact comes through
rustc_public. NoTyCtxtreads, noDebug-stringparsing.What is buildable today, and what waits:
&T,&mut T, and the data pointer of&[T]/&mut [T];nonnullyes,alignonly when > 1, neverdereferenceable(0);DisjointSlice, slicelen, by-value structs,extern "C"kernels: nothing.mir.funcper source argument, from kind + layout;NVVMpath only ifnvvmVerifyProgramaccepts.nonnull/alignalone will not move PTX much; the measured win (Preserve Rust reference kind and exclusivity through MIR import and lowering #413) arrives withnoalias/readonlyonce the upstream change lands. This half puts the plumbing in place so that becomes a bit copy..llfor the parameter lines:&f32,&mut f32,&[f32], an over-aligned ZST, plus the bare controls above.@uurl - want to take this half? An approach note here first would be great.
nihalpasham commentedon Sep 3, 2026
Filed the upstream ask for typed
ArgAttributesin rustc_public: rust-lang/rustc_public#132.uurl commentedon Sep 3, 2026
Yes @nihalpasham , I’ll take the nonnull / align half. I’ll post an approach note here first, scoped to facts available through rustc_public only: reference kind + pointee layout, a typed per-source-argument attribute on mir.func, slice-data-pointer remapping in mir-lower, and LLVM export on kernel entry parameters with the bare controls you listed.
I’ll leave dereferenceable, noalias, and readonly out of scope until typed ArgAttributes are available through rustc_public, and track rust-lang/rustc_public#132 as that dependency.
uurl commentedon Sep 4, 2026
Thanks — here is the approach I plan to use for the currently unblocked
nonnull/alignhalf.Proof boundary
I’ll treat
rustc_publicas the only semantic source of truth.No
TyCtxtreads,rustc_internalconversion, auto-trait re-derivation, orDebugparsing.The importer-side facts will come from:
RigidTy::Reffor reference classification;Ty::layout()for exact pointee alignment.For a thin
&T/&mut T, the pointee layout directly supplies the alignment.For
&[T]/&mut [T],[T]itself is unsized, so the slice data-pointer alignment will be derived from the typed elementTlayout. If the required typed layout is unavailable, the importer will fail closed and emit no validity fact rather than infer one.MIR representation
I plan to add one domain-specific typed MIR attribute representing the proven kernel-parameter validity facts, conceptually:
The fact is recorded by source argument index on
mir.func.I will not reuse the string/integer marker scheme from the closed #1099 path.
Only facts established by the importer will be transported downstream; neither
mir-lowernorllvm-exportwill infer reference semantics.Scope
This change will apply only to Rust CUDA kernel entry parameters.
Positive cases:
&T→nonnull, plusalign NwhenN > 1&mut T→ same&[T]/&mut [T]→ attributes on the flattened data pointer onlynonnull;align Nonly whenN > 1Bare controls:
DisjointSliceextern "C"kernel parametersNo attribute will be recovered from pointer mutability, address space, an erased pointer kind, or lowered LLVM pointer shape.
Lowering
mir-lowerwill consume the typed per-source-argument fact only for a kernel entry function and map it through the existing kernel ABI lowering.For slices:
No general-purpose function-parameter attribute inference is planned.
LLVM export
llvm-exportwill mechanically serialize the facts already carried by the lowered function parameter:No
dereferenceable,noalias, orreadonlywill be emitted in this change.The NVVM path will also be checked through the existing verifier/toolkit validation.
Validation
I plan to add:
importer tests proving the classification is sourced only from typed
rustc_publicinformation;MIR attribute verifier coverage, including malformed/out-of-range combinations;
source-argument → physical-parameter lowering tests, especially slice data pointer vs. length;
LLVM exporter tests for
nonnullandalign;an end-to-end example that inspects its generated
.llparameter lines for:&f32&mut f32&[f32]&mut [f32]nvvmVerifyProgram/ normal CUDA example validation.Explicitly deferred
These remain blocked on typed
ArgAttributesthroughrustc_public/ rust-lang/rustc_public#132:noaliasreadonlydereferenceableI won’t re-derive
Freeze,Unpin,NoFree, or equivalent semantics locally.uurl commentedon Sep 4, 2026
One soundness edge came up while validating the implementation before opening the PR.
DeviceBuffercurrently represents a zero-byte allocation withCUdeviceptr == 0, and the typed#[cuda_module]slice launch paths forward that pointer into the(data, len)kernel ABI. That conflicts with emitting LLVMnonnullon the data pointer of&[T]/&mut [T]for an empty slice. The same case exists for ZST element buffers, whose byte extent is zero even whenlen > 0.I don't want to paper over that at the exporter boundary.
My proposed fix is to normalize zero-byte Rust-slice launch pointers at the typed host marshalling boundary: when
len == 0orsize_of::<T>() == 0, pass a non-null dangling value aligned toalign_of::<T>(); otherwise preserve the actualCUdeviceptr. I would apply this consistently to the sync and async slice launch helpers and add host-side tests for empty slices, over-aligned ZSTs, and the normal non-empty path.This does not add dereferenceability or alias promises, and
DisjointSliceremains bare.If that matches the intended slice contract for this half, I'll include the launch-side normalization with the
nonnull/alignplumbing.nihalpasham commentedon Sep 6, 2026
Looks good to me.
nonnull/alignscoping matches the split from Emit LLVMnoalias/readonlyfor Rust references with proven semantics #1088: rustc_public-only, typedMIRattribute, kernel entry params only,dereferenceable/noalias/readonlydeferred to rustc_public#132.ptr: 0is intentional there (Drop-skip), and cuda-host's slice launch forwards it as-is today.A PR now would be great.
nihalpasham commentedon Sep 15, 2026
#1244 is ready after maintainer repair and partially addresses this issue.
nonnull/ alignment facts now reach kernel pointer parameters.Keeping #1098 open for
dereferenceableand the remaining typed-attribute work; this PR claims no measured speedup.cuda-oxide: update to nightly-2026-10-11 (#1482)
nihalpasham commentedon Oct 11, 2026
Related upstream report: LLVM #230960 tracks lost volatile reads with
noalias readonlykernel parameters.#1483 still adds rustc-proven,
no_free-gated sized dereferenceability on top of the existing nonnull/alignment work from #1244. It defers explicitreadonlybecause of the upstream bug. The validity-attribute work can proceed under that narrower contract; this comment does not close the issue.cuda-oxide: preserve rustc reference attributes on kernels (#1483)