[Mlir-commits] [mlir] [mlir][OpenMP] Change device declare target functions to hidden visibility (PR #207234)
Robert Imschweiler
llvmlistbot at llvm.org
Thu Jul 2 10:15:45 PDT 2026
https://github.com/ro-i created https://github.com/llvm/llvm-project/pull/207234
During OpenMP lowering, globally visible device functions are emitted. These functions might not be kernels themselves, but are designed to only be called in a kernel context. However, if they are unused, and not inlined, and reference LDS, the AMDGPU ISel emits lots of misleading warnings related to "local memory global used by non-kernel function". Fix by changing visibility from external+default to external+hidden, which allows DCE to just remove the functions.
Claude assisted with this patch.
>From 67f0c1e7ca0ae51ae84ba93f6cb50f1cd760c827 Mon Sep 17 00:00:00 2001
From: Robert Imschweiler <robert.imschweiler at amd.com>
Date: Thu, 2 Jul 2026 08:54:02 -0500
Subject: [PATCH] [mlir][OpenMP] Change device declare target functions to
hidden visibility
During OpenMP lowering, globally visible device functions are emitted.
These functions might not be kernels themselves, but are designed to
only be called in a kernel context. However, if they are unused, and not
inlined, and reference LDS, the AMDGPU ISel emits lots of misleading
warnings related to "local memory global used by non-kernel function".
Fix by changing visibility from external+default to external+hidden,
which allows DCE to just remove the functions.
Claude assisted with this patch.
---
.../OpenMP/OpenMPToLLVMIRTranslation.cpp | 15 ++++++++
...target-declare-target-func-visibility.mlir | 38 +++++++++++++++++++
.../LLVMIR/omptarget-device-shared-mem.mlir | 2 +-
.../LLVMIR/omptarget-wsloop-collapsed.mlir | 2 +-
mlir/test/Target/LLVMIR/omptarget-wsloop.mlir | 4 +-
mlir/test/Target/LLVMIR/openmp-llvm.mlir | 6 +--
6 files changed, 60 insertions(+), 7 deletions(-)
create mode 100644 mlir/test/Target/LLVMIR/omptarget-declare-target-func-visibility.mlir
diff --git a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
index eb34cabdf20f8..a287644207724 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
@@ -8819,6 +8819,21 @@ convertDeclareTargetAttr(Operation *op, mlir::omp::DeclareTargetAttr attribute,
// a deleted block.
ompBuilder->Builder.ClearInsertionPoint();
ompBuilder->Builder.SetCurrentDebugLocation(llvm::DebugLoc());
+ } else if (llvm::Function *llvmFunc =
+ moduleTranslation.lookupFunction(funcOp.getName())) {
+ // Device-side declare target functions are externally visible by
+ // default so they can be referenced from other device translation
+ // units. That also prevents the offload LTO from internalizing and
+ // deleting them when they end up unused in the final device image.
+ // Such dead functions can still reference internal LDS and trigger
+ // spurious "local memory global used by non-kernel function" backend
+ // warnings. Marking them hidden keeps the symbol usable within the
+ // device image's linkage unit while letting LTO drop it when nothing
+ // references it; symbols that must stay reachable (e.g. via an offload
+ // entry that takes their address) are kept alive by that reference.
+ if (!llvmFunc->isDeclaration() && llvmFunc->hasExternalLinkage() &&
+ llvmFunc->getVisibility() == llvm::GlobalValue::DefaultVisibility)
+ llvmFunc->setVisibility(llvm::GlobalValue::HiddenVisibility);
}
}
return success();
diff --git a/mlir/test/Target/LLVMIR/omptarget-declare-target-func-visibility.mlir b/mlir/test/Target/LLVMIR/omptarget-declare-target-func-visibility.mlir
new file mode 100644
index 0000000000000..f5f3c9f419f0b
--- /dev/null
+++ b/mlir/test/Target/LLVMIR/omptarget-declare-target-func-visibility.mlir
@@ -0,0 +1,38 @@
+// RUN: mlir-translate -mlir-to-llvmir %s | FileCheck %s
+
+// Device-side `declare target` functions are externally visible by default so
+// that they can be referenced from other device translation units. They are
+// emitted with hidden visibility so that the offload LTO can internalize and
+// delete them when they turn out to be unused in the final device image. This
+// avoids leaving dead functions behind that may, for example, reference LDS and
+// trigger spurious backend diagnostics.
+
+module attributes {llvm.target_triple = "amdgcn-amd-amdhsa", omp.is_target_device = true} {
+ // CHECK: define hidden void @device_any()
+ llvm.func @device_any() attributes {omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (to)>} {
+ llvm.return
+ }
+
+ // CHECK: define hidden void @device_nohost()
+ llvm.func @device_nohost() attributes {omp.declare_target = #omp.declaretarget<device_type = (nohost), capture_clause = (to)>} {
+ llvm.return
+ }
+
+ // A function with an explicitly requested (non-default) visibility is left
+ // untouched.
+ // CHECK: define protected void @device_protected()
+ llvm.func protected @device_protected() attributes {omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (to)>} {
+ llvm.return
+ }
+
+ // A function that is not declare target is unaffected.
+ // CHECK: define void @not_declare_target()
+ llvm.func @not_declare_target() {
+ llvm.return
+ }
+
+ // A declaration (no definition) is left untouched: there is nothing to
+ // internalize, and hiding it could over-constrain the symbol's visibility.
+ // CHECK: declare void @device_decl()
+ llvm.func @device_decl() attributes {omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (to)>}
+}
diff --git a/mlir/test/Target/LLVMIR/omptarget-device-shared-mem.mlir b/mlir/test/Target/LLVMIR/omptarget-device-shared-mem.mlir
index cdebebc3ed233..6fccc0127b347 100644
--- a/mlir/test/Target/LLVMIR/omptarget-device-shared-mem.mlir
+++ b/mlir/test/Target/LLVMIR/omptarget-device-shared-mem.mlir
@@ -1,7 +1,7 @@
// RUN: mlir-translate -mlir-to-llvmir %s | FileCheck %s
module attributes {dlti.dl_spec = #dlti.dl_spec<#dlti.dl_entry<"dlti.alloca_memory_space", 5 : ui32>>, llvm.data_layout = "e-p:64:64-p1:64:64-p2:32:32-p3:32:32-p4:64:64-p5:32:32-p6:32:32-p7:160:256:256:32-p8:128:128:128:48-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-v2048:2048-n32:64-S32-A5-G1-ni:7:8:9", llvm.target_triple = "amdgcn-amd-amdhsa", omp.is_gpu = true, omp.is_target_device = true} {
- // CHECK-LABEL: define void @device_shared_mem(
+ // CHECK-LABEL: define hidden void @device_shared_mem(
// CHECK-SAME: i32 %[[N0:.*]], i64 %[[N1:.*]])
llvm.func @device_shared_mem(%n0: i32, %n1: i64) attributes {omp.declare_target = #omp.declaretarget<device_type = (nohost), capture_clause = (to)>} {
// CHECK: %[[CAST_N0:.*]] = zext i32 %[[N0]] to i64
diff --git a/mlir/test/Target/LLVMIR/omptarget-wsloop-collapsed.mlir b/mlir/test/Target/LLVMIR/omptarget-wsloop-collapsed.mlir
index d84641ff9c99b..f120dde8131d6 100644
--- a/mlir/test/Target/LLVMIR/omptarget-wsloop-collapsed.mlir
+++ b/mlir/test/Target/LLVMIR/omptarget-wsloop-collapsed.mlir
@@ -22,7 +22,7 @@ module attributes {dlti.dl_spec = #dlti.dl_spec<#dlti.dl_entry<"dlti.alloca_memo
}
}
-// CHECK: define void @[[FUNC_COLLAPSED_WSLOOP:.*]](ptr %[[ARG0:.*]])
+// CHECK: define hidden void @[[FUNC_COLLAPSED_WSLOOP:.*]](ptr %[[ARG0:.*]])
// CHECK: call void @__kmpc_for_static_loop_4u(ptr addrspacecast (ptr addrspace(1) @[[GLOB2:[0-9]+]] to ptr),
// CHECK-SAME: ptr @[[COLLAPSED_WSLOOP_BODY_FN:.*]], ptr %[[STRUCT_ARG:.*]], i32 10000,
// CHECK-SAME: i32 %[[NUM_THREADS:.*]], i8 0)
diff --git a/mlir/test/Target/LLVMIR/omptarget-wsloop.mlir b/mlir/test/Target/LLVMIR/omptarget-wsloop.mlir
index 7be635f46111b..04458af9654c3 100644
--- a/mlir/test/Target/LLVMIR/omptarget-wsloop.mlir
+++ b/mlir/test/Target/LLVMIR/omptarget-wsloop.mlir
@@ -31,7 +31,7 @@ module attributes {dlti.dl_spec = #dlti.dl_spec<#dlti.dl_entry<"dlti.alloca_memo
}
}
-// CHECK: define void @[[FUNC0:.*]](ptr %[[ARG0:.*]])
+// CHECK: define hidden void @[[FUNC0:.*]](ptr %[[ARG0:.*]])
// CHECK: %[[STRUCTARG:.*]] = alloca { ptr }, align 8, addrspace(5)
// CHECK: %[[STRUCTARG_ASCAST:.*]] = addrspacecast ptr addrspace(5) %[[STRUCTARG]] to ptr
// CHECK: %[[GEP:.*]] = getelementptr { ptr }, ptr addrspace(5) %[[STRUCTARG]], i32 0, i32 0
@@ -45,7 +45,7 @@ module attributes {dlti.dl_spec = #dlti.dl_spec<#dlti.dl_entry<"dlti.alloca_memo
// CHECK: %[[GEP3:.*]] = getelementptr [10 x i32], ptr %[[LOADGEP]], i32 0, i32 %[[TMP2:.*]]
// CHECK: store i32 %[[VAL0:.*]], ptr %[[GEP3]], align 4
-// CHECK: define void @[[FUNC_EMPTY_WSLOOP:.*]]()
+// CHECK: define hidden void @[[FUNC_EMPTY_WSLOOP:.*]]()
// CHECK: call void @__kmpc_for_static_loop_4u(ptr addrspacecast (ptr addrspace(1) @[[GLOB2:[0-9]+]] to ptr), ptr @[[LOOP_EMPTY_BODY_FN:.*]], ptr null, i32 10, i32 %[[NUM_THREADS:.*]], i32 0, i8 0)
// CHECK: define internal void @[[LOOP_EMPTY_BODY_FN]](i32 %[[LOOP_CNT:.*]])
diff --git a/mlir/test/Target/LLVMIR/openmp-llvm.mlir b/mlir/test/Target/LLVMIR/openmp-llvm.mlir
index 790c0dfe9b12e..444918fb1a9e3 100644
--- a/mlir/test/Target/LLVMIR/openmp-llvm.mlir
+++ b/mlir/test/Target/LLVMIR/openmp-llvm.mlir
@@ -3661,7 +3661,7 @@ module attributes {omp.is_target_device = false} {
// -----
module attributes {omp.is_target_device = true} {
- // CHECK: define void @filter_nohost
+ // CHECK: define hidden void @filter_nohost
llvm.func @filter_nohost() -> ()
attributes {
omp.declare_target =
@@ -3683,7 +3683,7 @@ module attributes {omp.is_target_device = true} {
// -----
module attributes {omp.is_target_device = true} {
- // CHECK: define void @filter_nohost
+ // CHECK: define hidden void @filter_nohost
llvm.func @filter_nohost() -> ()
attributes {
omp.declare_target =
@@ -3993,7 +3993,7 @@ llvm.func @omp_groupprivate_device() attributes {
// CHECK-DAG: @nohost = internal global i32 undef
// CHECK-DAG: @[[SHARED_ANY:any.*]] = internal addrspace(3) global i32 poison
// CHECK-DAG: @[[SHARED_NOHOST:nohost.*]] = internal addrspace(3) global i32 poison
-// CHECK: define void @omp_groupprivate_device()
+// CHECK: define hidden void @omp_groupprivate_device()
// CHECK: store i32 1, ptr addrspace(3) @[[SHARED_ANY]], align 4
// CHECK: store i32 1, ptr @host, align 4
// CHECK: store i32 1, ptr addrspace(3) @[[SHARED_NOHOST]], align 4
More information about the Mlir-commits
mailing list