Skip to content

fix(mir): preserve wrapping pointer-offset semantics - #612

Merged
nihalpasham merged 1 commit into
NVIDIA:mainfrom
niklebedenko:upstreaming/wrapping-pointer-offsets
Aug 1, 2026
Merged

nihalpasham merged 1 commit into
NVIDIA:mainfrom
niklebedenko:upstreaming/wrapping-pointer-offsets

Conversation

@niklebedenko

Copy link
Copy Markdown
Contributor

Closes #611.

fix(mir): preserve wrapping pointer-offset semantics

Summary

Safe raw-pointer wrapping methods previously failed device compilation because
rustc's arith_offset intrinsic was unsupported. This change carries the
source pointer-arithmetic contract through MIR and LLVM lowering: ordinary
offsets remain getelementptr inbounds, while wrapping offsets become plain
getelementptr, preserving Rust's permitted address wrapping without adding
LLVM poison semantics.

Before: wrapping_offset -> unsupported arith_offset
After:  wrapping_offset -> mir.ptr_offset(non-inbounds) -> getelementptr

What changed

Pointer-arithmetic contract

  • Adds an inbounds semantic marker to MirPtrOffsetOp. Absence defaults to
    true, preserving every existing producer's behavior.
  • Imports both rustc spellings of arith_offset through one focused importer
    path and stamps those operations as non-inbounds.
  • Propagates the marker to the LLVM GEP operation and makes textual export emit
    inbounds conditionally.
  • Continues to obtain element scaling from the MIR pointer's authoritative
    pointee type rather than reconstructing it from the call name or result.

Coverage and related paths

  • Dialect, lowering, and exporter tests assert both sides of the contract:
    default ordinary offsets are in-bounds and explicit wrapping offsets are not.
  • Extends the existing ptr_offset_from example so one GPU run checks positive
    and negative wrapping offsets, aggregate scaling, const and mutable pointers,
    zero-sized pointees, and isize::MAX address wrapping.
  • The extreme and out-of-allocation cases are compared as addresses only. They
    are never dereferenced.
  • Merged PR mir-importer: support pointer distance intrinsics #206 added pointer-distance intrinsics; those paths remain
    unchanged. Open PR fix(mir): keep small runtime-indexed arrays in SSA #398's DeferredGep and small-array scalarization changes
    are deliberately absent from this branch.

Known separate limitations

  • A wrapped pointer may be dereferenced only when the resulting pointer is
    valid for that access under Rust's normal rules.
  • This does not change exposed-provenance reconstruction or pointer/integer
    cast semantics.
  • The compatibility default on operations without the marker is in-bounds.
    New wrapping producers must set the marker explicitly.
  • This change adds no array scalarization, speculative memory access, or
    optimization-pipeline policy.

Testing

  • Upstream red at bead880c with only the regression present:
    scripts/smoketest.sh --only '^ptr_offset_from$' --no-color failed
    (exit=101) because std::intrinsics::arith_offset was unsupported.
  • Prepared branch:
    scripts/smoketest.sh --only '^ptr_offset_from$' --no-color passed on an
    RTX 3080 (sm_86), 1/1 example.
  • Focused regressions passed:
    test_mir_ptr_offset_verify,
    gep_inbounds_marker_controls_exported_pointer_semantics, and
    convert_wrapping_ptr_offset_lowers_to_non_inbounds_gep.
  • cargo test -p dialect-mir -p llvm-export -p mir-importer -p mir-lower --all-targets passed, including 891 importer tests, 146 mir-lower unit
    tests, and 76 mir-lower integration tests.
  • cargo clippy -p dialect-mir -p llvm-export -p mir-importer -p mir-lower --all-targets -- -D warnings passed.
  • The standalone example passes cargo fmt --check and
    cargo clippy --all-targets -- -D warnings; repository formatting and
    git diff --check pass.

Checklist

  • The branch is based on current upstream main, or its dependency is explicit.
  • The regression fails on upstream main and passes on this branch.
  • Relevant sibling paths and edge cases are covered or called out above.
  • Formatting, focused tests, and relevant broader checks pass.
  • Commits include DCO signoff and new files have required headers.

@nihalpasham nihalpasham added bug Something isn't working codegen Device code-generation pipeline (Rust MIR to IR to PTX) IR-lowering Lowering between dialects (dialect-mir to LLVM dialect) llvm-export Textual LLVM/NVVM IR exporter (llvm-export crate) labels Aug 1, 2026
@nihalpasham

Copy link
Copy Markdown
Collaborator

LGTM as-is.

Gist:

  • Two kinds of pointer arithmetic.
    • ptr.add(n) promises the result stays inside the same allocation;
    • ptr.wrapping_add(n) makes no such promise, and it's safe code precisely because you're allowed to compute a wild out-of-bounds address as long as you never dereference it.
  • The second kind didn't compile on device at all: rustc lowers it through an arith_offset intrinsic our importer didn't recognize.
  • The simplest one-line fix would have been a miscompile. Both forms become LLVM's getelementptr (pointer-plus-offset), but the ordinary one carries an inbounds tag:
    • a promise to LLVM that the result is in bounds, which LLVM optimizes against. Break the promise and the value is poison, meaning LLVM may fold any comparison or branch that touches it to whatever it likes.
    • Wrapping offsets exist to go out of bounds, so stamping them inbounds would hand LLVM permission to miscompile correct programs.

Fix: mir.ptr_offset gains a marker where absent means inbounds, so every existing producer keeps its optimization contract untouched; the new arith_offset arm sets the marker; and the exporter emits a plain getelementptr for wrapping offsets while everything else keeps inbounds. That's how rustc's own LLVM backend maps these.

Verified on hardware:

  • ptr_offset_from passes on GPU including the isize::MAX wrap slot and both ZST slots.
  • The .ll shows both shapes: plain GEP for wrapping, inbounds retained everywhere else.
  • All four IR-layer crates' test suites pass.

Merging;

closes #611.

@nihalpasham
nihalpasham merged commit 4ed5fa0 into NVIDIA:main Aug 1, 2026
33 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working codegen Device code-generation pipeline (Rust MIR to IR to PTX) IR-lowering Lowering between dialects (dialect-mir to LLVM dialect) llvm-export Textual LLVM/NVVM IR exporter (llvm-export crate)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Wrapping pointer offsets cannot be represented through MIR lowering

2 participants