[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