[llvm] [Attributor] Support SPIR-V address spaces (PR #192725)

Nick Sarnie via llvm-commits llvm-commits at lists.llvm.org
Fri Apr 17 12:51:26 PDT 2026


https://github.com/sarnex created https://github.com/llvm/llvm-project/pull/192725

Right now Attributor assumes that if the the target is a GPU is can use a single set of address space literals to determine the local address space, but that's not true in general, so add SPIR-V.

This fixes an instruction incorrectly being marked as dead and optimized out for an OpenMP SPIR-V offloading example.

>From edbcad44861a92e7c8dcb8ecba0509227eeee54e Mon Sep 17 00:00:00 2001
From: Nick Sarnie <nick.sarnie at intel.com>
Date: Fri, 17 Apr 2026 12:35:54 -0700
Subject: [PATCH] [Attributor] Support SPIR-V address spaces

Signed-off-by: Nick Sarnie <nick.sarnie at intel.com>
---
 llvm/include/llvm/Transforms/IPO/Attributor.h |  28 +++--
 llvm/lib/Transforms/IPO/Attributor.cpp        | 101 ++++++++++++++++--
 .../Transforms/IPO/AttributorAttributes.cpp   |  23 ++--
 llvm/test/Transforms/OpenMP/spirv_ctor.ll     |  53 +++++++++
 4 files changed, 173 insertions(+), 32 deletions(-)
 create mode 100644 llvm/test/Transforms/OpenMP/spirv_ctor.ll

diff --git a/llvm/include/llvm/Transforms/IPO/Attributor.h b/llvm/include/llvm/Transforms/IPO/Attributor.h
index cee9cb8e7322b..ac3e64933e896 100644
--- a/llvm/include/llvm/Transforms/IPO/Attributor.h
+++ b/llvm/include/llvm/Transforms/IPO/Attributor.h
@@ -165,17 +165,29 @@ class Function;
 namespace AA {
 using InstExclusionSetTy = SmallPtrSet<Instruction *, 4>;
 
-enum class GPUAddressSpace : unsigned {
-  Generic = 0,
-  Global = 1,
-  Shared = 3,
-  Constant = 4,
-  Local = 5,
-};
-
 /// Return true iff \p M target a GPU (and we can use GPU AS reasoning).
 LLVM_ABI bool isGPU(const Module &M);
 
+/// Check if the given address space \p AS corresponds to a GPU generic
+/// address space for the target triple in module \p M.
+LLVM_ABI bool isGPUGenericAddressSpace(const Module &M, unsigned AS);
+
+/// Check if the given address space \p AS corresponds to a GPU global
+/// address space for the target triple in module \p M.
+LLVM_ABI bool isGPUGlobalAddressSpace(const Module &M, unsigned AS);
+
+/// Check if the given address space \p AS corresponds to a GPU shared
+/// address space for the target triple in module \p M.
+LLVM_ABI bool isGPUSharedAddressSpace(const Module &M, unsigned AS);
+
+/// Check if the given address space \p AS corresponds to a GPU constant
+/// address space for the target triple in module \p M.
+LLVM_ABI bool isGPUConstantAddressSpace(const Module &M, unsigned AS);
+
+/// Check if the given address space \p AS corresponds to a GPU local/private
+/// address space for the target triple in module \p M.
+LLVM_ABI bool isGPULocalAddressSpace(const Module &M, unsigned AS);
+
 /// Flags to distinguish intra-procedural queries from *potentially*
 /// inter-procedural queries. Not that information can be valid for both and
 /// therefore both bits might be set.
diff --git a/llvm/lib/Transforms/IPO/Attributor.cpp b/llvm/lib/Transforms/IPO/Attributor.cpp
index 8079fd89c9c37..52b26c5dcadd6 100644
--- a/llvm/lib/Transforms/IPO/Attributor.cpp
+++ b/llvm/lib/Transforms/IPO/Attributor.cpp
@@ -197,11 +197,81 @@ ChangeStatus &llvm::operator&=(ChangeStatus &L, ChangeStatus R) {
 }
 ///}
 
+namespace {
+/// NVPTX/AMDGPU address space values (shared between both targets)
+enum class NVPTXAMDGPUAddressSpace : unsigned {
+  Generic = 0,
+  Global = 1,
+  Shared = 3,
+  Constant = 4,
+  Local = 5,
+};
+
+/// SPIRV address space values (StorageClass)
+enum class SPIRVAddressSpace : unsigned {
+  Local = 0,    // Function (private/local)
+  Global = 1,   // CrossWorkgroup (global)
+  Constant = 2, // UniformConstant (constant)
+  Shared = 3,   // Workgroup (shared)
+  Generic = 4,  // Generic
+};
+} // namespace
+
 bool AA::isGPU(const Module &M) {
   Triple T(M.getTargetTriple());
   return T.isGPU();
 }
 
+bool AA::isGPUGenericAddressSpace(const Module &M, unsigned AS) {
+  assert(AA::isGPU(M) && "Only callable on GPU targets");
+  Triple T(M.getTargetTriple());
+
+  if (T.isSPIRV())
+    return AS == static_cast<unsigned>(SPIRVAddressSpace::Generic);
+
+  return AS == static_cast<unsigned>(NVPTXAMDGPUAddressSpace::Generic);
+}
+
+bool AA::isGPUGlobalAddressSpace(const Module &M, unsigned AS) {
+  assert(AA::isGPU(M) && "Only callable on GPU targets");
+  Triple T(M.getTargetTriple());
+
+  if (T.isSPIRV())
+    return AS == static_cast<unsigned>(SPIRVAddressSpace::Global);
+
+  return AS == static_cast<unsigned>(NVPTXAMDGPUAddressSpace::Global);
+}
+
+bool AA::isGPUSharedAddressSpace(const Module &M, unsigned AS) {
+  assert(AA::isGPU(M) && "Only callable on GPU targets");
+  Triple T(M.getTargetTriple());
+
+  if (T.isSPIRV())
+    return AS == static_cast<unsigned>(SPIRVAddressSpace::Shared);
+
+  return AS == static_cast<unsigned>(NVPTXAMDGPUAddressSpace::Shared);
+}
+
+bool AA::isGPUConstantAddressSpace(const Module &M, unsigned AS) {
+  assert(AA::isGPU(M) && "Only callable on GPU targets");
+  Triple T(M.getTargetTriple());
+
+  if (T.isSPIRV())
+    return AS == static_cast<unsigned>(SPIRVAddressSpace::Constant);
+
+  return AS == static_cast<unsigned>(NVPTXAMDGPUAddressSpace::Constant);
+}
+
+bool AA::isGPULocalAddressSpace(const Module &M, unsigned AS) {
+  assert(AA::isGPU(M) && "Only callable on GPU targets");
+  Triple T(M.getTargetTriple());
+
+  if (T.isSPIRV())
+    return AS == static_cast<unsigned>(SPIRVAddressSpace::Local);
+
+  return AS == static_cast<unsigned>(NVPTXAMDGPUAddressSpace::Local);
+}
+
 bool AA::isNoSyncInst(Attributor &A, const Instruction &I,
                       const AbstractAttribute &QueryingAA) {
   // We are looking for volatile instructions or non-relaxed atomics.
@@ -873,17 +943,26 @@ bool AA::isAssumedThreadLocalObject(Attributor &A, Value &Obj,
   }
 
   if (A.getInfoCache().targetIsGPU()) {
-    if (Obj.getType()->getPointerAddressSpace() ==
-        (int)AA::GPUAddressSpace::Local) {
-      LLVM_DEBUG(dbgs() << "[AA] Object '" << Obj
-                        << "' is thread local; GPU local memory\n");
-      return true;
-    }
-    if (Obj.getType()->getPointerAddressSpace() ==
-        (int)AA::GPUAddressSpace::Constant) {
-      LLVM_DEBUG(dbgs() << "[AA] Object '" << Obj
-                        << "' is thread local; GPU constant memory\n");
-      return true;
+    const Module *M = nullptr;
+    if (auto *GV = dyn_cast<GlobalValue>(&Obj))
+      M = GV->getParent();
+    else if (auto *Arg = dyn_cast<Argument>(&Obj))
+      M = Arg->getParent()->getParent();
+    else if (auto *I = dyn_cast<Instruction>(&Obj))
+      M = I->getModule();
+    assert(M && "Could not find the Module");
+    if (M) {
+      unsigned ObjAS = Obj.getType()->getPointerAddressSpace();
+      if (AA::isGPULocalAddressSpace(*M, ObjAS)) {
+        LLVM_DEBUG(dbgs() << "[AA] Object '" << Obj
+                          << "' is thread local; GPU local memory\n");
+        return true;
+      }
+      if (AA::isGPUConstantAddressSpace(*M, ObjAS)) {
+        LLVM_DEBUG(dbgs() << "[AA] Object '" << Obj
+                          << "' is thread local; GPU constant memory\n");
+        return true;
+      }
     }
   }
 
diff --git a/llvm/lib/Transforms/IPO/AttributorAttributes.cpp b/llvm/lib/Transforms/IPO/AttributorAttributes.cpp
index 4282fe7623630..ac7a4bf9da2bc 100644
--- a/llvm/lib/Transforms/IPO/AttributorAttributes.cpp
+++ b/llvm/lib/Transforms/IPO/AttributorAttributes.cpp
@@ -1183,14 +1183,10 @@ struct AAPointerInfoImpl
     auto HasKernelLifetime = [&](Value *V, Module &M) {
       if (!AA::isGPU(M))
         return false;
-      switch (AA::GPUAddressSpace(V->getType()->getPointerAddressSpace())) {
-      case AA::GPUAddressSpace::Shared:
-      case AA::GPUAddressSpace::Constant:
-      case AA::GPUAddressSpace::Local:
-        return true;
-      default:
-        return false;
-      };
+      unsigned VAS = V->getType()->getPointerAddressSpace();
+      return AA::isGPUSharedAddressSpace(M, VAS) ||
+             AA::isGPUConstantAddressSpace(M, VAS) ||
+             AA::isGPULocalAddressSpace(M, VAS);
     };
 
     // The IsLiveInCalleeCB will be used by the AA::isPotentiallyReachable query
@@ -8704,11 +8700,12 @@ void AAMemoryLocationImpl::categorizePtrValue(
 
     // Filter accesses to constant (GPU) memory if we have an AS at the access
     // site or the object is known to actually have the associated AS.
-    if ((AccessAS == (unsigned)AA::GPUAddressSpace::Constant ||
-         (ObjectAS == (unsigned)AA::GPUAddressSpace::Constant &&
-          isIdentifiedObject(&Obj))) &&
-        AA::isGPU(*I.getModule()))
-      return true;
+    if (AA::isGPU(*I.getModule())) {
+      if (AA::isGPUConstantAddressSpace(*I.getModule(), AccessAS) ||
+          (AA::isGPUConstantAddressSpace(*I.getModule(), ObjectAS) &&
+           isIdentifiedObject(&Obj)))
+        return true;
+    }
 
     if (isa<UndefValue>(&Obj))
       return true;
diff --git a/llvm/test/Transforms/OpenMP/spirv_ctor.ll b/llvm/test/Transforms/OpenMP/spirv_ctor.ll
new file mode 100644
index 0000000000000..54e2bcd585804
--- /dev/null
+++ b/llvm/test/Transforms/OpenMP/spirv_ctor.ll
@@ -0,0 +1,53 @@
+; RUN: opt < %s -S -passes=openmp-opt | FileCheck %s
+
+; Check that the call to __cxx_global_var_init is not incorrectly optimized out.
+
+target triple = "spirv64-intel"
+
+%struct.S = type { i32 }
+
+$_ZN1SC2Ev = comdat any
+
+ at s = protected addrspace(1) global %struct.S zeroinitializer, align 4
+ at llvm.global_ctors = appending global [1 x { i32, ptr addrspace(9), ptr addrspace(9) }] [{ i32, ptr addrspace(9), ptr addrspace(9) } { i32 65535, ptr addrspace(9) @_GLOBAL__sub_I_ctor_dtor.cpp, ptr addrspace(9) null }]
+
+define internal spir_func void @__cxx_global_var_init() addrspace(9) {
+entry:
+  call spir_func addrspace(9) void @_ZN1SC2Ev(ptr addrspace(4) noundef align 4 dereferenceable_or_null(4) addrspacecast (ptr addrspace(1) @s to ptr addrspace(4)))
+  ret void
+}
+
+define linkonce_odr protected spir_func void @_ZN1SC2Ev(ptr addrspace(4) noundef align 4 dereferenceable_or_null(4) %this) unnamed_addr addrspace(9) comdat align 2 {
+entry:
+  %this.addr = alloca ptr addrspace(4), align 8
+  %this.addr.ascast = addrspacecast ptr %this.addr to ptr addrspace(4)
+  store ptr addrspace(4) %this, ptr addrspace(4) %this.addr.ascast, align 8
+  %this1 = load ptr addrspace(4), ptr addrspace(4) %this.addr.ascast, align 8
+  %i = getelementptr inbounds nuw %struct.S, ptr addrspace(4) %this1, i32 0, i32 0
+  store i32 7, ptr addrspace(4) %i, align 4
+  ret void
+}
+
+define weak_odr protected spir_kernel void @__omp_offloading_802_2f12bce_main_l17() {
+user_code.entry:                                  ; preds = %entry
+  %0 = load ptr addrspace(4), ptr addrspace(4) null
+  %3 = load i32, ptr addrspace(4) addrspacecast (ptr addrspace(1) @s to ptr addrspace(4)), align 4
+  store i32 %3, ptr addrspace(4) %0, align 4
+  ret void
+}
+
+; CHECK-LABEL: define internal spir_func void @_GLOBAL__sub_I_ctor_dtor.cpp()
+; CHECK-NEXT: entry:
+; CHECK-NEXT: call spir_func addrspace(9) void @__cxx_global_var_init()
+; CHECK-NEXT: ret void
+
+define internal spir_func void @_GLOBAL__sub_I_ctor_dtor.cpp() addrspace(9) {
+entry:
+  call spir_func addrspace(9) void @__cxx_global_var_init()
+  ret void
+}
+
+!llvm.module.flags = !{!2, !3}
+
+!2 = !{i32 7, !"openmp", i32 51}
+!3 = !{i32 7, !"openmp-device", i32 51}



More information about the llvm-commits mailing list