[Mlir-commits] [mlir] [mlir][OpenMP] Change device declare target functions to hidden visibility (PR #207234)
llvmlistbot at llvm.org
llvmlistbot at llvm.org
Thu Jul 2 10:16:36 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-flang-openmp
Author: Robert Imschweiler (ro-i)
<details>
<summary>Changes</summary>
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.
---
Full diff: https://github.com/llvm/llvm-project/pull/207234.diff
6 Files Affected:
- (modified) mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp (+15)
- (added) mlir/test/Target/LLVMIR/omptarget-declare-target-func-visibility.mlir (+38)
- (modified) mlir/test/Target/LLVMIR/omptarget-device-shared-mem.mlir (+1-1)
- (modified) mlir/test/Target/LLVMIR/omptarget-wsloop-collapsed.mlir (+1-1)
- (modified) mlir/test/Target/LLVMIR/omptarget-wsloop.mlir (+2-2)
- (modified) mlir/test/Target/LLVMIR/openmp-llvm.mlir (+3-3)
``````````diff
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
``````````
</details>
https://github.com/llvm/llvm-project/pull/207234
More information about the Mlir-commits
mailing list