[llvm] [AMDGPU] Add object linking support for LDS and named barrier lowering in the middle end (PR #191645)
via llvm-commits
llvm-commits at lists.llvm.org
Sat Apr 11 10:05:07 PDT 2026
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-backend-amdgpu
Author: Shilei Tian (shiltian)
<details>
<summary>Changes</summary>
This is the first patch in a series introducing object linking support for
AMDGPU.
This PR adds the -amdgpu-enable-object-linking flag to enable object linking in
the backend. It also updates the AMDGPULowerModuleLDSPass and
AMDGPULowerExecSync passes to support lowering LDS and named barrier globals
when object linking is enabled.
---
Patch is 30.25 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/191645.diff
12 Files Affected:
- (modified) llvm/lib/Target/AMDGPU/AMDGPULowerExecSync.cpp (+37)
- (modified) llvm/lib/Target/AMDGPU/AMDGPULowerModuleLDSPass.cpp (+168-2)
- (modified) llvm/lib/Target/AMDGPU/AMDGPUTargetMachine.cpp (+7)
- (modified) llvm/lib/Target/AMDGPU/AMDGPUTargetMachine.h (+1)
- (added) llvm/test/CodeGen/AMDGPU/lds-link-time-named-barrier.ll (+35)
- (added) llvm/test/CodeGen/AMDGPU/lower-module-lds-link-time-classify.ll (+73)
- (added) llvm/test/CodeGen/AMDGPU/lower-module-lds-link-time-internal-multi-user.ll (+50)
- (added) llvm/test/CodeGen/AMDGPU/lower-module-lds-link-time-kernel-direct-lds.ll (+40)
- (added) llvm/test/CodeGen/AMDGPU/lower-module-lds-link-time-multi-kernel.ll (+62)
- (added) llvm/test/CodeGen/AMDGPU/lower-module-lds-link-time-multi-lds-per-func.ll (+52)
- (added) llvm/test/CodeGen/AMDGPU/lower-module-lds-link-time-transitive.ll (+50)
- (added) llvm/test/CodeGen/AMDGPU/lower-module-lds-link-time.ll (+49)
``````````diff
diff --git a/llvm/lib/Target/AMDGPU/AMDGPULowerExecSync.cpp b/llvm/lib/Target/AMDGPU/AMDGPULowerExecSync.cpp
index c26e97360efef..7d8226900bec1 100644
--- a/llvm/lib/Target/AMDGPU/AMDGPULowerExecSync.cpp
+++ b/llvm/lib/Target/AMDGPU/AMDGPULowerExecSync.cpp
@@ -178,7 +178,44 @@ static bool lowerExecSyncGlobalVariables(
return Changed;
}
+// With object linking, barrier ID assignment is deferred to the linker.
+// Externalize named barrier globals and emit self-contained metadata so the
+// AsmPrinter can generate the callgraph entries the linker needs.
+static bool handleNamedBarriersForObjectLinking(Module &M) {
+ DenseMap<GlobalVariable *, DenseSet<Function *>> BarrierToFuncs;
+ for (auto &GV : M.globals()) {
+ if (!isNamedBarrier(GV) || GV.use_empty())
+ continue;
+ for (User *U : GV.users()) {
+ if (auto *I = dyn_cast<Instruction>(U))
+ BarrierToFuncs[&GV].insert(I->getFunction());
+ }
+ }
+ if (BarrierToFuncs.empty())
+ return false;
+
+ LLVMContext &Ctx = M.getContext();
+ NamedMDNode *BarMD = M.getOrInsertNamedMetadata("amdgpu.named_barrier.uses");
+
+ for (auto &[V, Funcs] : BarrierToFuncs) {
+ V->setInitializer(nullptr);
+ V->setLinkage(GlobalValue::ExternalLinkage);
+ if (!V->getName().starts_with("__amdgpu_named_barrier"))
+ V->setName("__amdgpu_named_barrier." + V->getName());
+
+ SmallVector<Metadata *, 4> Ops;
+ Ops.push_back(ValueAsMetadata::get(V));
+ for (Function *F : Funcs)
+ Ops.push_back(ValueAsMetadata::get(F));
+ BarMD->addOperand(MDNode::get(Ctx, Ops));
+ }
+ return true;
+}
+
static bool runLowerExecSyncGlobals(Module &M) {
+ if (AMDGPUTargetMachine::EnableObjectLinking)
+ return handleNamedBarriersForObjectLinking(M);
+
CallGraph CG = CallGraph(M);
bool Changed = false;
Changed |= eliminateConstantExprUsesOfLDSFromAllInstructions(M);
diff --git a/llvm/lib/Target/AMDGPU/AMDGPULowerModuleLDSPass.cpp b/llvm/lib/Target/AMDGPU/AMDGPULowerModuleLDSPass.cpp
index c3614cf3e16b3..10d57391ff464 100644
--- a/llvm/lib/Target/AMDGPU/AMDGPULowerModuleLDSPass.cpp
+++ b/llvm/lib/Target/AMDGPU/AMDGPULowerModuleLDSPass.cpp
@@ -185,6 +185,7 @@
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SetOperations.h"
+#include "llvm/ADT/SmallString.h"
#include "llvm/Analysis/CallGraph.h"
#include "llvm/Analysis/ScopedNoAliasAA.h"
#include "llvm/CodeGen/TargetPassConfig.h"
@@ -844,8 +845,8 @@ class AMDGPULowerModuleLDS {
auto *emptyCharArray = ArrayType::get(Type::getInt8Ty(Ctx), 0);
GlobalVariable *N = new GlobalVariable(
M, emptyCharArray, false, GlobalValue::ExternalLinkage, nullptr,
- Twine("llvm.amdgcn." + func->getName() + ".dynlds"), nullptr, GlobalValue::NotThreadLocal, AMDGPUAS::LOCAL_ADDRESS,
- false);
+ Twine("llvm.amdgcn." + func->getName() + ".dynlds"), nullptr,
+ GlobalValue::NotThreadLocal, AMDGPUAS::LOCAL_ADDRESS, false);
N->setAlignment(MaxDynamicAlignment);
assert(AMDGPU::isDynamicLDS(*N));
@@ -910,7 +911,172 @@ class AMDGPULowerModuleLDS {
return KernelToCreatedDynamicLDS;
}
+ // Per-TU mode for link-time LDS resolution. Instead of computing a global
+ // layout, create per-function LDS struct declarations so the linker can
+ // assign offsets across TUs.
+ bool runOnModuleLinkTime(Module &M) {
+ bool Changed = superAlignLDSGlobals(M);
+ Changed |= eliminateConstantExprUsesOfLDSFromAllInstructions(M);
+
+ CallGraph CG(M);
+ FunctionVariableMap KernelLDSUses, FunctionLDSUses;
+ getUsesOfLDSByFunction(CG, M, KernelLDSUses, FunctionLDSUses);
+
+ if (KernelLDSUses.empty() && FunctionLDSUses.empty())
+ return Changed;
+
+ FunctionVariableMap AllLDSUses;
+ for (auto &[F, Vars] : KernelLDSUses)
+ AllLDSUses[F].insert(Vars.begin(), Vars.end());
+ for (auto &[F, Vars] : FunctionLDSUses)
+ AllLDSUses[F].insert(Vars.begin(), Vars.end());
+
+ // Named barriers are handled by AMDGPULowerExecSync; filter them out.
+ for (auto &[F, Vars] : AllLDSUses) {
+ SmallVector<GlobalVariable *> Barriers;
+ for (GlobalVariable *V : Vars)
+ if (AMDGPU::isNamedBarrier(*V))
+ Barriers.push_back(V);
+ for (GlobalVariable *V : Barriers)
+ Vars.erase(V);
+ }
+
+ // Build reverse map: LDS variable -> functions that use it.
+ DenseMap<GlobalVariable *, SmallVector<Function *, 4>> VarToFuncs;
+ for (auto &[F, Vars] : AllLDSUses)
+ for (auto *V : Vars)
+ VarToFuncs[V].push_back(F);
+
+ // A variable is function-scope iff it has local linkage and exactly one
+ // user function. Everything else is global-scope and must remain as a
+ // standalone external declaration so the linker can assign a single shared
+ // offset.
+ DenseSet<GlobalVariable *> GlobalScopeVars;
+ DenseSet<GlobalVariable *> InternalMultiUserVars;
+ for (auto &[V, Funcs] : VarToFuncs) {
+ if (!V->hasLocalLinkage() || Funcs.size() > 1) {
+ GlobalScopeVars.insert(V);
+ if (V->hasLocalLinkage())
+ InternalMultiUserVars.insert(V);
+ }
+ }
+
+ // Wrap function-scope LDS into per-function structs (unchanged logic,
+ // but global-scope variables are excluded from the set).
+ SmallVector<std::pair<Function *, GlobalVariable *>, 4> FuncToLdsStruct;
+ DenseSet<GlobalVariable *> AllReplacedVars;
+ for (auto &KV : AllLDSUses) {
+ Function *F = KV.first;
+ DenseSet<GlobalVariable *> FuncScopeVars;
+ for (auto *V : KV.second)
+ if (!GlobalScopeVars.count(V))
+ FuncScopeVars.insert(V);
+
+ if (FuncScopeVars.empty())
+ continue;
+
+ std::string StructName = ("__amdgpu_lds." + F->getName()).str();
+ LDSVariableReplacement Replacement =
+ createLDSVariableReplacement(M, StructName, FuncScopeVars);
+
+ GlobalVariable *SGV = Replacement.SGV;
+ SGV->setLinkage(GlobalValue::ExternalLinkage);
+ SGV->setInitializer(nullptr);
+ FuncToLdsStruct.push_back({F, SGV});
+
+ replaceLDSVariablesWithStruct(
+ M, FuncScopeVars, Replacement, [F](const Use &U) {
+ auto *I = dyn_cast<Instruction>(U.getUser());
+ return I && I->getFunction() == F;
+ });
+
+ AllReplacedVars.insert(FuncScopeVars.begin(), FuncScopeVars.end());
+ }
+
+ // Internal-linkage LDS variables used by multiple functions would collide
+ // across TUs if promoted individually to external linkage (same name in
+ // different TUs). Pack them into a single per-module struct with a
+ // module-unique name so the linker treats them as one allocation unit.
+ if (!InternalMultiUserVars.empty()) {
+ SmallString<256> ModSuffix(M.getSourceFileName());
+ std::replace_if(
+ ModSuffix.begin(), ModSuffix.end(),
+ [](char Ch) { return !isAlnum(Ch); }, '_');
+ std::string StructName = ("__amdgpu_lds.__internal." + ModSuffix).str();
+
+ LDSVariableReplacement Replacement =
+ createLDSVariableReplacement(M, StructName, InternalMultiUserVars);
+
+ GlobalVariable *SGV = Replacement.SGV;
+ SGV->setLinkage(GlobalValue::ExternalLinkage);
+ SGV->setInitializer(nullptr);
+
+ replaceLDSVariablesWithStruct(
+ M, InternalMultiUserVars, Replacement,
+ [](const Use &U) { return isa<Instruction>(U.getUser()); });
+
+ DenseSet<Function *> FuncsUsingInternalVars;
+ for (GlobalVariable *V : InternalMultiUserVars) {
+ for (Function *F : VarToFuncs[V])
+ FuncsUsingInternalVars.insert(F);
+ }
+ for (Function *F : FuncsUsingInternalVars)
+ FuncToLdsStruct.push_back({F, SGV});
+
+ AllReplacedVars.insert(InternalMultiUserVars.begin(),
+ InternalMultiUserVars.end());
+ }
+
+ // Convert global-scope LDS to external declarations. Their uses remain
+ // intact and ISel generates R_AMDGPU_ABS32_LO relocations for them.
+ for (GlobalVariable *V : GlobalScopeVars) {
+ V->setInitializer(nullptr);
+ V->setLinkage(GlobalValue::ExternalLinkage);
+ }
+
+ // Emit amdgpu.lds.uses metadata for struct and global-scope LDS.
+ {
+ LLVMContext &Ctx = M.getContext();
+ NamedMDNode *LdsMD = M.getOrInsertNamedMetadata("amdgpu.lds.uses");
+
+ for (auto &[F, SGV] : FuncToLdsStruct)
+ LdsMD->addOperand(MDNode::get(
+ Ctx, {ValueAsMetadata::get(F), ValueAsMetadata::get(SGV)}));
+
+ for (auto &[V, Funcs] : VarToFuncs) {
+ if (GlobalScopeVars.count(V) && !InternalMultiUserVars.count(V)) {
+ for (Function *F : Funcs) {
+ LdsMD->addOperand(MDNode::get(
+ Ctx, {ValueAsMetadata::get(F), ValueAsMetadata::get(V)}));
+ }
+ }
+ }
+ }
+
+ for (Function &F : M) {
+ if (isKernel(F) && !F.isDeclaration())
+ F.addFnAttr("amdgpu-link-time-lds");
+ }
+
+ DenseSet<GlobalVariable *> AllLDSVarsForCleanup = AllReplacedVars;
+ AllLDSVarsForCleanup.insert(GlobalScopeVars.begin(), GlobalScopeVars.end());
+ removeLocalVarsFromUsedLists(M, AllLDSVarsForCleanup);
+ for (GlobalVariable *GV : AllReplacedVars) {
+ GV->removeDeadConstantUsers();
+ if (GV->use_empty())
+ GV->eraseFromParent();
+ }
+
+ return true;
+ }
+
bool runOnModule(Module &M) {
+ if (AMDGPUTargetMachine::EnableObjectLinking)
+ return runOnModuleLinkTime(M);
+ return runOnModuleNormal(M);
+ }
+
+ bool runOnModuleNormal(Module &M) {
CallGraph CG = CallGraph(M);
bool Changed = superAlignLDSGlobals(M);
diff --git a/llvm/lib/Target/AMDGPU/AMDGPUTargetMachine.cpp b/llvm/lib/Target/AMDGPU/AMDGPUTargetMachine.cpp
index daa9f933fce59..9e5751023f52d 100644
--- a/llvm/lib/Target/AMDGPU/AMDGPUTargetMachine.cpp
+++ b/llvm/lib/Target/AMDGPU/AMDGPUTargetMachine.cpp
@@ -546,6 +546,12 @@ static cl::opt<bool>
"and asan instrument resulting IR."),
cl::init(true), cl::Hidden);
+static cl::opt<bool, true> EnableObjectLinking(
+ "amdgpu-enable-object-linking",
+ cl::desc("Enable object linking for cross-TU LDS and ABI support"),
+ cl::location(AMDGPUTargetMachine::EnableObjectLinking), cl::init(false),
+ cl::Hidden);
+
static cl::opt<bool, true> EnableLowerModuleLDS(
"amdgpu-enable-lower-module-lds", cl::desc("Enable lower module lds pass"),
cl::location(AMDGPUTargetMachine::EnableLowerModuleLDS), cl::init(true),
@@ -877,6 +883,7 @@ AMDGPUTargetMachine::AMDGPUTargetMachine(const Target &T, const Triple &TT,
}
bool AMDGPUTargetMachine::EnableFunctionCalls = false;
+bool AMDGPUTargetMachine::EnableObjectLinking = false;
bool AMDGPUTargetMachine::EnableLowerModuleLDS = true;
AMDGPUTargetMachine::~AMDGPUTargetMachine() = default;
diff --git a/llvm/lib/Target/AMDGPU/AMDGPUTargetMachine.h b/llvm/lib/Target/AMDGPU/AMDGPUTargetMachine.h
index a9e24acec045e..006a22bb19421 100644
--- a/llvm/lib/Target/AMDGPU/AMDGPUTargetMachine.h
+++ b/llvm/lib/Target/AMDGPU/AMDGPUTargetMachine.h
@@ -40,6 +40,7 @@ class AMDGPUTargetMachine : public CodeGenTargetMachineImpl {
public:
static bool EnableFunctionCalls;
+ static bool EnableObjectLinking;
static bool EnableLowerModuleLDS;
AMDGPUTargetMachine(const Target &T, const Triple &TT, StringRef CPU,
diff --git a/llvm/test/CodeGen/AMDGPU/lds-link-time-named-barrier.ll b/llvm/test/CodeGen/AMDGPU/lds-link-time-named-barrier.ll
new file mode 100644
index 0000000000000..b6c86089c4832
--- /dev/null
+++ b/llvm/test/CodeGen/AMDGPU/lds-link-time-named-barrier.ll
@@ -0,0 +1,35 @@
+; RUN: opt -S -mtriple=amdgcn-amd-amdhsa -amdgpu-enable-object-linking -passes=amdgpu-lower-exec-sync,amdgpu-lower-module-lds < %s | FileCheck %s
+
+; Verify that with object linking enabled:
+; 1. AMDGPULowerExecSync externalizes named barriers and emits
+; amdgpu.named_barrier.uses metadata with (barrier, func...) format
+; 2. AMDGPULowerModuleLDS does not handle named barriers at all
+; 3. amdgpu.lds.uses does NOT contain barrier entries
+
+ at bar = internal addrspace(3) global target("amdgcn.named.barrier", 0) poison
+ at lds = internal addrspace(3) global [4 x i32] poison, align 4
+
+; Named barrier becomes an external declaration with __amdgpu_named_barrier prefix.
+; CHECK: @__amdgpu_named_barrier.bar = external dso_local addrspace(3) global target("amdgcn.named.barrier", 0)
+; CHECK-NOT: !absolute_symbol
+; Regular LDS is packed into the per-function struct (external, for linker).
+; CHECK: @__amdgpu_lds.kernel = external dso_local addrspace(3) global %__amdgpu_lds.kernel.t, align 16
+
+define amdgpu_kernel void @kernel(i32 %idx) {
+; CHECK-LABEL: define amdgpu_kernel void @kernel(
+; CHECK: call void @llvm.amdgcn.s.barrier.signal.var(ptr addrspace(3) @__amdgpu_named_barrier.bar, i32 3)
+; CHECK: call void @llvm.amdgcn.s.barrier.join(ptr addrspace(3) @__amdgpu_named_barrier.bar)
+ call void @llvm.amdgcn.s.barrier.signal.var(ptr addrspace(3) @bar, i32 3)
+ call void @llvm.amdgcn.s.barrier.join(ptr addrspace(3) @bar)
+ call void @llvm.amdgcn.s.barrier.wait(i16 1)
+ %gep = getelementptr [4 x i32], ptr addrspace(3) @lds, i32 0, i32 %idx
+ store i32 42, ptr addrspace(3) %gep, align 4
+ ret void
+}
+
+; Named barrier metadata: (barrier_sym, func1, ...) -- emitted by ExecSync.
+; CHECK-DAG: !amdgpu.named_barrier.uses = !{[[BAR_MD:![0-9]+]]}
+; CHECK-DAG: [[BAR_MD]] = !{ptr addrspace(3) @__amdgpu_named_barrier.bar, ptr @kernel}
+; LDS metadata must have exactly one entry (the LDS struct), no barrier entries.
+; CHECK-DAG: !amdgpu.lds.uses = !{[[LDS_MD:![0-9]+]]}
+; CHECK-DAG: [[LDS_MD]] = !{ptr @kernel, ptr addrspace(3) @__amdgpu_lds.kernel}
diff --git a/llvm/test/CodeGen/AMDGPU/lower-module-lds-link-time-classify.ll b/llvm/test/CodeGen/AMDGPU/lower-module-lds-link-time-classify.ll
new file mode 100644
index 0000000000000..bec74968599e9
--- /dev/null
+++ b/llvm/test/CodeGen/AMDGPU/lower-module-lds-link-time-classify.ll
@@ -0,0 +1,73 @@
+; RUN: opt -S -mtriple=amdgcn-amd-amdhsa -passes=amdgpu-lower-module-lds -amdgpu-enable-object-linking < %s | FileCheck %s
+
+; Test the three-way classification of LDS variables:
+; 1. Global-scope (external linkage): standalone external declaration
+; 2. Kernel-scope (internal linkage, single kernel user): wrapped in per-kernel struct
+; 3. Callee-scope (internal linkage, single callee user): wrapped in per-callee struct
+;
+; Also tests that a global-scope variable used by multiple functions produces
+; one metadata entry per (function, variable) pair.
+
+; Global-scope: external linkage, used by both func and my_kernel.
+ at lds_global = addrspace(3) global [64 x i32] poison, align 16
+
+; Callee-scope: internal linkage, used only by func.
+ at lds_func_priv = internal addrspace(3) global [32 x float] poison, align 4
+
+; Kernel-scope: internal linkage, used only by my_kernel.
+ at lds_kernel_priv = internal addrspace(3) global [16 x i64] poison, align 8
+
+declare void @extern_func()
+
+; Global-scope: remains as external declaration.
+; CHECK-DAG: @lds_global = external addrspace(3) global [64 x i32]
+
+; Callee-scope: wrapped into per-function struct.
+; CHECK-DAG: @__amdgpu_lds.func = external {{(dso_local )?}}addrspace(3) global %__amdgpu_lds.func.t
+
+; Kernel-scope: wrapped into per-kernel struct.
+; CHECK-DAG: @__amdgpu_lds.my_kernel = external {{(dso_local )?}}addrspace(3) global %__amdgpu_lds.my_kernel.t
+
+; Original internal-linkage variables should be removed.
+; CHECK-NOT: @lds_func_priv
+; CHECK-NOT: @lds_kernel_priv
+
+; func: uses lds_global directly, uses lds_func_priv via struct GEP.
+; CHECK-LABEL: define void @func()
+; CHECK: getelementptr {{.*}} ptr addrspace(3) @lds_global
+; CHECK: getelementptr {{.*}} ptr addrspace(3) @__amdgpu_lds.func
+
+; my_kernel: uses lds_global directly, uses lds_kernel_priv via struct GEP.
+; CHECK-LABEL: define amdgpu_kernel void @my_kernel()
+; CHECK: getelementptr {{.*}} ptr addrspace(3) @lds_global
+; CHECK: getelementptr {{.*}} ptr addrspace(3) @__amdgpu_lds.my_kernel
+
+; Metadata:
+; CHECK: !amdgpu.lds.uses = !{{{![0-9]+, ![0-9]+, ![0-9]+, ![0-9]+}}}
+; Function-scope entries (one per struct).
+; CHECK-DAG: !{ptr @my_kernel, ptr addrspace(3) @__amdgpu_lds.my_kernel}
+; CHECK-DAG: !{ptr @func, ptr addrspace(3) @__amdgpu_lds.func}
+; Global-scope entries (one per using function).
+; CHECK-DAG: !{ptr @my_kernel, ptr addrspace(3) @lds_global}
+; CHECK-DAG: !{ptr @func, ptr addrspace(3) @lds_global}
+
+; Module should be marked with the link-time LDS module flag.
+; CHECK: !{i32 1, !"amdgpu-link-time-lds", i32 1}
+
+define void @func() {
+ %gep1 = getelementptr [64 x i32], ptr addrspace(3) @lds_global, i32 0, i32 0
+ store i32 1, ptr addrspace(3) %gep1
+ %gep2 = getelementptr [32 x float], ptr addrspace(3) @lds_func_priv, i32 0, i32 0
+ store float 2.0, ptr addrspace(3) %gep2
+ call void @extern_func()
+ ret void
+}
+
+define amdgpu_kernel void @my_kernel() {
+ %gep1 = getelementptr [64 x i32], ptr addrspace(3) @lds_global, i32 0, i32 0
+ store i32 3, ptr addrspace(3) %gep1
+ %gep2 = getelementptr [16 x i64], ptr addrspace(3) @lds_kernel_priv, i32 0, i32 0
+ store i64 4, ptr addrspace(3) %gep2
+ call void @func()
+ ret void
+}
diff --git a/llvm/test/CodeGen/AMDGPU/lower-module-lds-link-time-internal-multi-user.ll b/llvm/test/CodeGen/AMDGPU/lower-module-lds-link-time-internal-multi-user.ll
new file mode 100644
index 0000000000000..ba8cf81ca6d6f
--- /dev/null
+++ b/llvm/test/CodeGen/AMDGPU/lower-module-lds-link-time-internal-multi-user.ll
@@ -0,0 +1,50 @@
+; RUN: opt -S -mtriple=amdgcn-amd-amdhsa -passes=amdgpu-lower-module-lds -amdgpu-enable-object-linking < %s | FileCheck %s
+
+source_filename = "source_a.hip"
+
+; Internal-linkage LDS variables used by multiple kernels must be packed into a
+; per-module struct with a module-unique name, rather than promoted individually
+; to external linkage, to avoid cross-TU name collisions.
+
+ at a = internal addrspace(3) global [32 x i32] poison, align 16
+ at b = internal addrspace(3) global [16 x float] poison, align 4
+
+; Per-module struct containing both internal multi-user variables.
+; CHECK: @__amdgpu_lds.__internal.source_a_hip = external {{(dso_local )?}}addrspace(3) global %__amdgpu_lds.__internal.source_a_hip.t, align 16
+
+; Original internal-linkage variables should be removed.
+; CHECK-NOT: @a =
+; CHECK-NOT: @b =
+
+; Both kernels reference the struct.
+; CHECK-LABEL: define amdgpu_kernel void @kernel1()
+; CHECK: @__amdgpu_lds.__internal.source_a_hip
+; CHECK: @__amdgpu_lds.__internal.source_a_hip
+
+; CHECK-LABEL: define amdgpu_kernel void @kernel2()
+; CHECK: @__amdgpu_lds.__internal.source_a_hip
+; CHECK: @__amdgpu_lds.__internal.source_a_hip
+
+; Metadata: struct entries for both kernels.
+; CHECK: !amdgpu.lds.uses = !{{{![0-9]+, ![0-9]+}}}
+; CHECK-DAG: !{ptr @kernel1, ptr addrspace(3) @__amdgpu_lds.__internal.source_a_hip}
+; CHECK-DAG: !{ptr @kernel2, ptr addrspace(3) @__amdgpu_lds.__internal.source_a_hip}
+
+; Module should be marked with the link-time LDS module flag.
+; CHECK: !{i32 1, !"amdgpu-link-time-lds", i32 1}
+
+define amdgpu_kernel void @kernel1() {
+ %gep_a = getelementptr [32 x i32], ptr addrspace(3) @a, i32 0, i32 0
+ store i32 1, ptr addrspace(3) %gep_a
+ %gep_b = getelementptr [16 x float], ptr addrspace(3) @b, i32 0, i32 0
+ store float 2.0, ptr addrspace(3) %gep_b
+ ret void
+}
+
+define amdgpu_kernel void @kernel2() {
+ %gep_a = getelementptr [32 x i32], ptr addrspace(3) @a, i32 0, i32 0
+ store i32 3, ptr addrspace(3) %gep_a
+ %gep_b = getelementptr [16 x float], ptr addrspace(3) @b, i32 0, i32 0
+ store float 4.0, ptr addrspace(3) %gep_b
+ ret void
+}
diff --git a/llvm/test/CodeGen/AMDGPU/lower-module-lds-link-time-kernel-direct-lds.ll b/llvm/test/CodeGen/AMDGPU/lower-module-lds-link-time-kernel-direct-lds.ll
new file mode 100644
index 0000000000000..776e953c0fc3b
--- /dev/null
+++ b/llvm/test/CodeGen/AMDGPU/lower-module-lds-link-time-kernel-direct-lds.ll
@@ -0,0 +1,40 @@
+; RUN: opt -S -mtriple=amdgcn-amd-amdhsa -passes=amdgpu-lower-module-lds -amdgpu-enable-object-linking < %s | FileCheck %s
+
+; Only the kernel itself directly uses LDS (no device function uses LDS).
+; The LDS variable has external linkage -> global-scope -> standalone declaration.
+
+ at lds_var = addrspace(3) global [32 x float] poison, align 4
+
+declare void @extern_func()
+
+; Global-...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/191645
More information about the llvm-commits
mailing list