[llvm] IR: Add nonnull flag to addrspacecast (PR #217903)

via llvm-commits llvm-commits at lists.llvm.org
Fri Aug 21 10:02:05 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-ir

Author: Matt Arsenault (arsenm)

<details>
<summary>Changes</summary>

Introduce the nonnull flag on the addrspacecast instruction,
asserting that the source pointer is not the null value of its
source address space. If the source is the source-address-space null
value, the result is poison.

The LangRef does not yet acknowdlege the existence of non-0 null
pointers, or null pointers for address spaces other than default,
but will "soon".

This gives the target a way to omit the runtime null check to map
between the null values. Currently AMDGPU works around the lack of
this flag by introducing the hacky llvm.amdgcn.addrspacecast.nonnull
intrinsic in a late target codegen pass.

Co-authored-by: Claude (Claude-Opus-4.8) <noreply@<!-- -->anthropic.com>

---
Full diff: https://github.com/llvm/llvm-project/pull/217903.diff


13 Files Affected:

- (modified) llvm/docs/LangRef.md (+6-1) 
- (modified) llvm/include/llvm/Bitcode/LLVMBitCodes.h (+3) 
- (modified) llvm/include/llvm/IR/IRBuilder.h (+10-3) 
- (modified) llvm/include/llvm/IR/Instructions.h (+10) 
- (modified) llvm/lib/AsmParser/LLParser.cpp (+8-1) 
- (modified) llvm/lib/Bitcode/Reader/BitcodeReader.cpp (+3) 
- (modified) llvm/lib/Bitcode/Writer/BitcodeWriter.cpp (+3) 
- (modified) llvm/lib/IR/AsmWriter.cpp (+3) 
- (modified) llvm/test/Assembler/flags.ll (+12) 
- (modified) llvm/test/Bitcode/compatibility.ll (+3-1) 
- (modified) llvm/test/Bitcode/flags.ll (+5-1) 
- (modified) llvm/test/Transforms/InferAddressSpaces/AMDGPU/infer-addrspacecast.ll (+19) 
- (modified) llvm/test/Transforms/InstCombine/addrspacecast.ll (+11) 


``````````diff
diff --git a/llvm/docs/LangRef.md b/llvm/docs/LangRef.md
index 82eebd26c0054..90b1dd94e9e1c 100644
--- a/llvm/docs/LangRef.md
+++ b/llvm/docs/LangRef.md
@@ -13263,7 +13263,8 @@ If `value` is of the {ref}`byte type <t_byte>`:
 ##### Syntax:
 
 ```
-<result> = addrspacecast <pty> <ptrval> to <pty2>       ; yields pty2
+<result> = addrspacecast <pty> <ptrval> to <pty2>          ; yields pty2
+<result> = addrspacecast nonnull <pty> <ptrval> to <pty2>  ; yields pty2
 ```
 
 ##### Overview:
@@ -13300,6 +13301,10 @@ should yield the original bit pattern).
 Which address space casts are supported depends on the target. Unsupported
 address space casts return {ref}`poison <poisonvalues>`.
 
+The optional `nonnull` flag asserts that `ptrval` is not the null value of
+its source address space; if it is, the result is
+{ref}`poison <poisonvalues>`.
+
 ##### Example:
 
 ```llvm
diff --git a/llvm/include/llvm/Bitcode/LLVMBitCodes.h b/llvm/include/llvm/Bitcode/LLVMBitCodes.h
index 226be19624db0..8771ab0103604 100644
--- a/llvm/include/llvm/Bitcode/LLVMBitCodes.h
+++ b/llvm/include/llvm/Bitcode/LLVMBitCodes.h
@@ -572,6 +572,9 @@ enum PossiblyExactOperatorOptionalFlags { PEO_EXACT = 0 };
 /// PossiblyDisjointInst's SubclassOptionalData contents.
 enum PossiblyDisjointInstOptionalFlags { PDI_DISJOINT = 0 };
 
+/// Flags for serializing AddrSpaceCastInst's SubclassOptionalData contents.
+enum AddrSpaceCastInstOptionalFlags { ASCI_NON_NULL = 0 };
+
 /// Mark to distinguish metadata from value in an operator bundle.
 enum MetadataOperandBundleValueMarker { OB_METADATA = 0x80000000 };
 
diff --git a/llvm/include/llvm/IR/IRBuilder.h b/llvm/include/llvm/IR/IRBuilder.h
index f2621bbe298df..ddf6740b363d7 100644
--- a/llvm/include/llvm/IR/IRBuilder.h
+++ b/llvm/include/llvm/IR/IRBuilder.h
@@ -2245,9 +2245,16 @@ class IRBuilderBase {
     return CreateCast(Instruction::BitCast, V, DestTy, Name);
   }
 
-  Value *CreateAddrSpaceCast(Value *V, Type *DestTy,
-                             const Twine &Name = "") {
-    return CreateCast(Instruction::AddrSpaceCast, V, DestTy, Name);
+  Value *CreateAddrSpaceCast(Value *V, Type *DestTy, const Twine &Name = "",
+                             bool IsNonNull = false) {
+    if (V->getType() == DestTy)
+      return V;
+    if (Value *Folded = Folder.FoldCast(Instruction::AddrSpaceCast, V, DestTy))
+      return Folded;
+    Instruction *I = Insert(new AddrSpaceCastInst(V, DestTy), Name);
+    if (IsNonNull)
+      cast<AddrSpaceCastInst>(I)->setNonNull();
+    return I;
   }
 
   Value *CreateZExtOrBitCast(Value *V, Type *DestTy, const Twine &Name = "") {
diff --git a/llvm/include/llvm/IR/Instructions.h b/llvm/include/llvm/IR/Instructions.h
index 0475798b9463d..ae874d78aba47 100644
--- a/llvm/include/llvm/IR/Instructions.h
+++ b/llvm/include/llvm/IR/Instructions.h
@@ -5190,6 +5190,8 @@ class AddrSpaceCastInst : public CastInst {
   LLVM_ABI AddrSpaceCastInst *cloneImpl() const;
 
 public:
+  enum { NonNull = (1 << 0) };
+
   /// Constructor with insert-before-instruction semantics
   LLVM_ABI AddrSpaceCastInst(
       Value *S,                  ///< The value to be casted
@@ -5207,6 +5209,14 @@ class AddrSpaceCastInst : public CastInst {
     return isa<Instruction>(V) && classof(cast<Instruction>(V));
   }
 
+  void setNonNull(bool B = true) {
+    SubclassOptionalData = (SubclassOptionalData & ~NonNull) | (B * NonNull);
+  }
+
+  /// Test whether the source is known not to be the null value of its
+  /// address space.
+  bool hasNonNull() const { return (SubclassOptionalData & NonNull) != 0; }
+
   /// Gets the pointer operand.
   Value *getPointerOperand() {
     return getOperand(0);
diff --git a/llvm/lib/AsmParser/LLParser.cpp b/llvm/lib/AsmParser/LLParser.cpp
index 93a79a7035e6f..2ecd4a686f1cf 100644
--- a/llvm/lib/AsmParser/LLParser.cpp
+++ b/llvm/lib/AsmParser/LLParser.cpp
@@ -7769,9 +7769,16 @@ int LLParser::parseInstruction(Instruction *&Inst, BasicBlock *BB,
       cast<TruncInst>(Inst)->setHasNoSignedWrap(true);
     return false;
   }
+  case lltok::kw_addrspacecast: {
+    bool NonNull = EatIfPresent(lltok::kw_nonnull);
+    if (parseCast(Inst, PFS, KeywordVal))
+      return true;
+    if (NonNull)
+      cast<AddrSpaceCastInst>(Inst)->setNonNull();
+    return false;
+  }
   case lltok::kw_sext:
   case lltok::kw_bitcast:
-  case lltok::kw_addrspacecast:
   case lltok::kw_fptoui:
   case lltok::kw_fptosi:
   case lltok::kw_inttoptr:
diff --git a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
index bf8f6d12ce5a8..1d7fbd3d8b0ff 100644
--- a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
+++ b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
@@ -5368,6 +5368,9 @@ Error BitcodeReader::parseFunctionBody(Function *F) {
             cast<TruncInst>(I)->setHasNoUnsignedWrap(true);
           if (Record[OpNum] & (1 << bitc::TIO_NO_SIGNED_WRAP))
             cast<TruncInst>(I)->setHasNoSignedWrap(true);
+        } else if (Opc == Instruction::AddrSpaceCast) {
+          if (Record[OpNum] & (1 << bitc::ASCI_NON_NULL))
+            cast<AddrSpaceCastInst>(I)->setNonNull(true);
         }
         if (isa<FPMathOperator>(I)) {
           uint64_t Flags = Record[OpNum];
diff --git a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
index 3b4065c0c7895..0ab5495a90e4d 100644
--- a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
+++ b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
@@ -1857,6 +1857,9 @@ static uint64_t getOptimizationFlags(const Value *V) {
   } else if (const auto *ICmp = dyn_cast<ICmpInst>(V)) {
     if (ICmp->hasSameSign())
       Flags |= 1 << bitc::ICMP_SAME_SIGN;
+  } else if (const auto *ASC = dyn_cast<AddrSpaceCastInst>(V)) {
+    if (ASC->hasNonNull())
+      Flags |= 1 << bitc::ASCI_NON_NULL;
   }
 
   return Flags;
diff --git a/llvm/lib/IR/AsmWriter.cpp b/llvm/lib/IR/AsmWriter.cpp
index c3202eea12c28..346246a66e2df 100644
--- a/llvm/lib/IR/AsmWriter.cpp
+++ b/llvm/lib/IR/AsmWriter.cpp
@@ -1534,6 +1534,9 @@ static void writeOptimizationInfo(raw_ostream &Out, const User *U) {
   } else if (const auto *ICmp = dyn_cast<ICmpInst>(U)) {
     if (ICmp->hasSameSign())
       Out << " samesign";
+  } else if (const auto *ASC = dyn_cast<AddrSpaceCastInst>(U)) {
+    if (ASC->hasNonNull())
+      Out << " nonnull";
   }
 }
 
diff --git a/llvm/test/Assembler/flags.ll b/llvm/test/Assembler/flags.ll
index b685277f4ee04..45011a06b4b1c 100644
--- a/llvm/test/Assembler/flags.ll
+++ b/llvm/test/Assembler/flags.ll
@@ -293,6 +293,18 @@ define i1 @test_icmp_samesign(i32 %a, i32 %b) {
   ret i1 %res
 }
 
+define ptr @test_addrspacecast_nonnull(ptr addrspace(1) %a) {
+; CHECK: %res = addrspacecast nonnull ptr addrspace(1) %a to ptr
+  %res = addrspacecast nonnull ptr addrspace(1) %a to ptr
+  ret ptr %res
+}
+
+define <2 x ptr> @test_addrspacecast_nonnull_vector(<2 x ptr addrspace(1)> %a) {
+; CHECK: %res = addrspacecast nonnull <2 x ptr addrspace(1)> %a to <2 x ptr>
+  %res = addrspacecast nonnull <2 x ptr addrspace(1)> %a to <2 x ptr>
+  ret <2 x ptr> %res
+}
+
 define <2 x i1> @test_icmp_samesign2(<2 x i32> %a, <2 x i32> %b) {
   ; CHECK: %res = icmp samesign ult <2 x i32> %a, %b
   %res = icmp samesign ult <2 x i32> %a, %b
diff --git a/llvm/test/Bitcode/compatibility.ll b/llvm/test/Bitcode/compatibility.ll
index 768fa6d6ec735..d8784b4d8a2f2 100644
--- a/llvm/test/Bitcode/compatibility.ll
+++ b/llvm/test/Bitcode/compatibility.ll
@@ -1712,7 +1712,7 @@ define void @instructions.memops(ptr %base) {
 }
 
 ; Instructions -- Conversion Operations
-define void @instructions.conversions() {
+define void @instructions.conversions(ptr %pop) {
   trunc i32 -1 to i1
   ; CHECK: trunc i32 -1 to i1
   zext i32 -1 to i64
@@ -1747,6 +1747,8 @@ define void @instructions.conversions() {
   ; CHECK: bitcast i32 0 to i32
   addrspacecast ptr null to ptr addrspace(1)
   ; CHECK: addrspacecast ptr null to ptr addrspace(1)
+  addrspacecast nonnull ptr %pop to ptr addrspace(1)
+  ; CHECK: addrspacecast nonnull ptr %pop to ptr addrspace(1)
 
   ret void
 }
diff --git a/llvm/test/Bitcode/flags.ll b/llvm/test/Bitcode/flags.ll
index 99988c9ba3d3d..b20f0566bddef 100644
--- a/llvm/test/Bitcode/flags.ll
+++ b/llvm/test/Bitcode/flags.ll
@@ -7,7 +7,7 @@
 ; Make sure the flags are serialized/deserialized properly for both
 ; forward and backward references.
 
-define void @foo() nounwind {
+define void @foo(ptr addrspace(1) %p, <2 x ptr addrspace(1)> %pv) nounwind {
 entry:
   br label %first
 
@@ -32,6 +32,9 @@ second:                                           ; preds = %first
   %tv = trunc <2 x i32> %aa to <2 x i16>
   %ii = icmp samesign ult i32 %a, %z
   %iv = icmp samesign ult <2 x i32> %aa, %aa
+  %an = addrspacecast nonnull ptr addrspace(1) %p to ptr
+  %ap = addrspacecast ptr addrspace(1) %p to ptr
+  %anv = addrspacecast nonnull <2 x ptr addrspace(1)> %pv to <2 x ptr>
   unreachable
 
 first:                                                    ; preds = %entry
@@ -57,5 +60,6 @@ first:                                                    ; preds = %entry
   %ttv = trunc <2 x i32> %aa to <2 x i16>
   %icm = icmp samesign ult i32 %a, %zz
   %icv = icmp samesign ult <2 x i32> %aa, %aa
+  %ann = addrspacecast nonnull ptr addrspace(1) %p to ptr
   br label %second
 }
diff --git a/llvm/test/Transforms/InferAddressSpaces/AMDGPU/infer-addrspacecast.ll b/llvm/test/Transforms/InferAddressSpaces/AMDGPU/infer-addrspacecast.ll
index 4375eebd8e16b..b723d171bada3 100644
--- a/llvm/test/Transforms/InferAddressSpaces/AMDGPU/infer-addrspacecast.ll
+++ b/llvm/test/Transforms/InferAddressSpaces/AMDGPU/infer-addrspacecast.ll
@@ -53,3 +53,22 @@ define void @multiuse_addrspacecast_gep_addrspacecast(ptr addrspace(3) %ptr) {
   store i32 8, ptr addrspace(3) %asc1, align 8
   ret void
 }
+
+; nonnull is dropped when the cast is rebuilt on the sunk select operand.
+; CHECK-LABEL: @rebuilt_addrspacecast_drops_nonnull(
+; CHECK: %sel = select i1 %c, ptr addrspace(3) %p, ptr addrspace(3) %q
+; CHECK-NEXT: %1 = addrspacecast ptr addrspace(3) %sel to ptr
+; CHECK-NOT: nonnull
+; CHECK-NEXT: call void @use_flat(ptr %1)
+; CHECK-NEXT: %v = load i32, ptr addrspace(3) %sel, align 4
+; CHECK-NEXT: ret i32 %v
+define i32 @rebuilt_addrspacecast_drops_nonnull(i1 %c, ptr addrspace(3) %p, ptr addrspace(3) %q) {
+  %pf = addrspacecast nonnull ptr addrspace(3) %p to ptr
+  %qf = addrspacecast nonnull ptr addrspace(3) %q to ptr
+  %sel = select i1 %c, ptr %pf, ptr %qf
+  call void @use_flat(ptr %sel)
+  %v = load i32, ptr %sel
+  ret i32 %v
+}
+
+declare void @use_flat(ptr)
diff --git a/llvm/test/Transforms/InstCombine/addrspacecast.ll b/llvm/test/Transforms/InstCombine/addrspacecast.ll
index ff37149bdab7b..3761a91ca87c8 100644
--- a/llvm/test/Transforms/InstCombine/addrspacecast.ll
+++ b/llvm/test/Transforms/InstCombine/addrspacecast.ll
@@ -241,3 +241,14 @@ define void @constant_fold_gep_inttoptr() #0 {
   store i32 7, ptr addrspace(4) %cast
   ret void
 }
+
+; nonnull is dropped when a cast-of-cast pair collapses to a single cast.
+define ptr addrspace(1) @drop_nonnull_on_collapsed_castcast(ptr addrspace(3) %p) {
+; CHECK-LABEL: @drop_nonnull_on_collapsed_castcast(
+; CHECK-NEXT:    [[B:%.*]] = addrspacecast ptr addrspace(3) [[P:%.*]] to ptr addrspace(1)
+; CHECK-NEXT:    ret ptr addrspace(1) [[B]]
+;
+  %a = addrspacecast ptr addrspace(3) %p to ptr
+  %b = addrspacecast nonnull ptr %a to ptr addrspace(1)
+  ret ptr addrspace(1) %b
+}

``````````

</details>


https://github.com/llvm/llvm-project/pull/217903


More information about the llvm-commits mailing list