[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