[flang-commits] [flang] [mlir] [flang][OpenACC] Emit module PARAMETERs as initialized linkonce_odr globals (PR #220666)

via flang-commits flang-commits at lists.llvm.org
Wed Sep 2 10:49:07 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-mlir-openacc

Author: nvptm

<details>
<summary>Changes</summary>

Emit an initialized linkonce_odr global from the module-file value in each compilation unit that needs a module PARAMETER (follow USE association so the initializer is not lost). Separately compiled uses under OpenACC default(present) then keep the constant on the device instead of treating a bodyless external as data that must already be present.

Keep external linkage when the PARAMETER has an explicit !$acc declare or a CUDA data attribute, which need a single shared device symbol rather than a per-unit copy.

---
Full diff: https://github.com/llvm/llvm-project/pull/220666.diff


7 Files Affected:

- (modified) flang/lib/Lower/ConvertVariable.cpp (+17-2) 
- (modified) flang/test/Fir/OpenACC/acc-declare-gpu-module-insertion.fir (+57-11) 
- (modified) flang/test/Integration/function-sections.f90 (+2-1) 
- (modified) flang/test/Lower/dense-attributed-array.f90 (+1-1) 
- (added) flang/test/Lower/module-parameter-linkonce-odr.f90 (+83) 
- (added) flang/test/Transforms/OpenACC/acc-implicit-declare-module-parameter.fir (+46) 
- (modified) mlir/lib/Dialect/OpenACC/Transforms/ACCDeclareGPUModuleInsertion.cpp (+9-9) 


``````````diff
diff --git a/flang/lib/Lower/ConvertVariable.cpp b/flang/lib/Lower/ConvertVariable.cpp
index 8438b39c990a9..ac78c0ea24a2d 100644
--- a/flang/lib/Lower/ConvertVariable.cpp
+++ b/flang/lib/Lower/ConvertVariable.cpp
@@ -509,8 +509,9 @@ fir::GlobalOp Fortran::lower::defineGlobal(
   if (global && globalIsInitialized(global))
     return global;
 
+  // Follow USE association so module PARAMETERs keep their initializer.
   const auto *oeDetails =
-      sym.detailsIf<Fortran::semantics::ObjectEntityDetails>();
+      sym.GetUltimate().detailsIf<Fortran::semantics::ObjectEntityDetails>();
 
   // If this is an array, check to see if we can use a dense attribute
   // with a tensor mlir type. This optimization currently only supports
@@ -662,8 +663,22 @@ getLinkageAttribute(Fortran::lower::AbstractConverter &converter,
       (!converter.getLoweringOptions().getSkipExternalRttiDefinition() ||
        Fortran::semantics::IsFromBuiltinModule(var.getSymbol())))
     return builder.createLinkOnceODRLinkage();
-  if (var.isModuleOrSubmoduleVariable())
+  if (var.isModuleOrSubmoduleVariable()) {
+    // Named module PARAMETERs: emit linkonce_odr from the module-file value.
+    // Keep strong linkage when a unique GPU symbol must survive:
+    // - !$acc declare (device registration / USE-side acc.declare)
+    // - CUDA data attributes (e.g. constant)
+    if (var.hasSymbol() &&
+        Fortran::semantics::IsNamedConstant(var.getSymbol().GetUltimate())) {
+      const Fortran::semantics::Symbol &ultimate =
+          var.getSymbol().GetUltimate();
+      if (ultimate.test(Fortran::semantics::Symbol::Flag::AccDeclare) ||
+          Fortran::semantics::GetCUDADataAttr(&ultimate))
+        return {}; // external linkage
+      return builder.createLinkOnceODRLinkage();
+    }
     return {}; // external linkage
+  }
   // Otherwise, the variable is owned by a procedure and must not be visible in
   // other compilation units.
   return builder.createInternalLinkage();
diff --git a/flang/test/Fir/OpenACC/acc-declare-gpu-module-insertion.fir b/flang/test/Fir/OpenACC/acc-declare-gpu-module-insertion.fir
index 5f7edd6970e8d..525f8d765ef77 100644
--- a/flang/test/Fir/OpenACC/acc-declare-gpu-module-insertion.fir
+++ b/flang/test/Fir/OpenACC/acc-declare-gpu-module-insertion.fir
@@ -1,7 +1,8 @@
-// RUN: fir-opt %s --pass-pipeline="builtin.module(acc-declare-gpu-module-insertion{cuda-unified=true},acc-declare-gpu-module-insertion{cuda-unified=true})" | FileCheck %s
+// RUN: fir-opt %s -split-input-file --pass-pipeline="builtin.module(acc-declare-gpu-module-insertion{cuda-unified=true},acc-declare-gpu-module-insertion{cuda-unified=true})" | FileCheck %s --check-prefixes=CHECK,UNIFIED
+// RUN: fir-opt %s -split-input-file --pass-pipeline="builtin.module(acc-declare-gpu-module-insertion,acc-declare-gpu-module-insertion)" | FileCheck %s --check-prefixes=CHECK,NOUNIF
 
-// Globals registered as host variables must be declarations in device code.
-// device_resident globals remain device definitions.
+// Globals registered as host variables must be declarations in device code
+// when cuda-unified is set. device_resident globals remain device definitions.
 module attributes {fir.defaultkind = "a1c4d8i4l4r4", fir.kindmap = "", gpu.container_module} {
   fir.global internal @shared {acc.declare = #acc.declare<dataClause = acc_create>} : !fir.array<7xf32> {
     %0 = fir.zero_bits !fir.array<7xf32>
@@ -28,14 +29,59 @@ module attributes {fir.defaultkind = "a1c4d8i4l4r4", fir.kindmap = "", gpu.conta
   }
 }
 
+// UNIFIED-LABEL: gpu.module @acc_gpu_module {
+// UNIFIED: fir.global @precloned {acc.declare = #acc.declare<dataClause = acc_create>} : !fir.array<1xf32>
+// UNIFIED-NOT: fir.has_value
+// UNIFIED: fir.global @shared {acc.declare = #acc.declare<dataClause = acc_create>} : !fir.array<7xf32>
+// UNIFIED-NOT: fir.has_value
+// UNIFIED: fir.global @initialized_shared {acc.declare = #acc.declare<dataClause = acc_copyin>} : !fir.array<1xf32>
+// UNIFIED-NOT: fir.has_value
+// UNIFIED: fir.global @resident {{.*}} : !fir.array<7xf32> {
+// UNIFIED: fir.has_value
+// UNIFIED: fir.global @constant_shared {acc.declare = #acc.declare<dataClause = acc_copyin>} constant : i32 {
+// UNIFIED: fir.has_value
+
+// NOUNIF-LABEL: gpu.module @acc_gpu_module {
+// NOUNIF: fir.global @precloned {acc.declare = #acc.declare<dataClause = acc_create>} : !fir.array<1xf32> {
+// NOUNIF: fir.has_value
+// NOUNIF: fir.global internal @shared {acc.declare = #acc.declare<dataClause = acc_create>} : !fir.array<7xf32> {
+// NOUNIF: fir.has_value
+// NOUNIF: fir.global @initialized_shared(dense<1.000000e+00> : vector<1xf32>) {acc.declare = #acc.declare<dataClause = acc_copyin>} : !fir.array<1xf32>
+// NOUNIF: fir.global @resident {{.*}} : !fir.array<7xf32> {
+// NOUNIF: fir.has_value
+// NOUNIF: fir.global @constant_shared {acc.declare = #acc.declare<dataClause = acc_copyin>} constant : i32 {
+// NOUNIF: fir.has_value
+
+// -----
+
+// Device copy of a host linkonce_odr constant keeps that linkage.
+module attributes {fir.defaultkind = "a1c4d8i4l4r4", fir.kindmap = "", gpu.container_module} {
+  fir.global linkonce_odr @param_n {acc.declare = #acc.declare<dataClause = acc_copyin>} constant : i32 {
+    %0 = arith.constant 30 : i32
+    fir.has_value %0 : i32
+  }
+}
+
 // CHECK-LABEL: gpu.module @acc_gpu_module {
-// CHECK: fir.global @precloned {acc.declare = #acc.declare<dataClause = acc_create>} : !fir.array<1xf32>
-// CHECK-NOT: fir.has_value
-// CHECK: fir.global @shared {acc.declare = #acc.declare<dataClause = acc_create>} : !fir.array<7xf32>
-// CHECK-NOT: fir.has_value
-// CHECK: fir.global @initialized_shared {acc.declare = #acc.declare<dataClause = acc_copyin>} : !fir.array<1xf32>
-// CHECK-NOT: fir.has_value
-// CHECK: fir.global @resident {{.*}} : !fir.array<7xf32> {
+// CHECK: fir.global linkonce_odr @param_n {acc.declare = #acc.declare<dataClause = acc_copyin>} constant : i32 {
 // CHECK: fir.has_value
-// CHECK: fir.global @constant_shared {acc.declare = #acc.declare<dataClause = acc_copyin>} constant : i32 {
+
+// -----
+
+// Already present in the GPU module with linkonce; reuse as-is.
+module attributes {fir.defaultkind = "a1c4d8i4l4r4", fir.kindmap = "", gpu.container_module} {
+  fir.global linkonce @_QQclXlit {acc.declare = #acc.declare<dataClause = acc_copyin>} constant : !fir.char<1,3> {
+    %0 = fir.string_lit "abc"(3) : !fir.char<1,3>
+    fir.has_value %0 : !fir.char<1,3>
+  }
+  gpu.module @acc_gpu_module {
+    fir.global linkonce @_QQclXlit constant : !fir.char<1,3> {
+      %0 = fir.string_lit "abc"(3) : !fir.char<1,3>
+      fir.has_value %0 : !fir.char<1,3>
+    }
+  }
+}
+
+// CHECK-LABEL: gpu.module @acc_gpu_module {
+// CHECK: fir.global linkonce @_QQclXlit {acc.declare = #acc.declare<dataClause = acc_copyin>} constant : !fir.char<1,3> {
 // CHECK: fir.has_value
diff --git a/flang/test/Integration/function-sections.f90 b/flang/test/Integration/function-sections.f90
index ab8bfc986e13f..d0be7d1c62706 100644
--- a/flang/test/Integration/function-sections.f90
+++ b/flang/test/Integration/function-sections.f90
@@ -44,5 +44,6 @@ program test
 ! DATA-PLAIN: .data
 ! DATA-PLAIN-NOT: .section{{.*}}.data.
 
+! Module PARAMETERs are linkonce_odr, so they always get a comdat section.
 ! RODATA-SECT: .section{{.*}}.rodata._QMparam_modECn
-! RODATA-PLAIN-NOT: .section{{.*}}.rodata._QMparam_modECn
+! RODATA-PLAIN: .section{{.*}}.rodata._QMparam_modECn{{.*}}comdat
diff --git a/flang/test/Lower/dense-attributed-array.f90 b/flang/test/Lower/dense-attributed-array.f90
index 7de146484c11f..7e36dbe0b4c82 100644
--- a/flang/test/Lower/dense-attributed-array.f90
+++ b/flang/test/Lower/dense-attributed-array.f90
@@ -19,5 +19,5 @@ subroutine ss
 !CHECK:  %[[c0:.*]] = arith.constant 53 : i32
 !CHECK:  hlfir.assign %[[c0]] to %[[d0]]#0 : i32, !fir.ref<i32>
 !CHECK:  return
-!CHECK: fir.global @_QMmmECqq(dense<[51, 52, 53]> : tensor<3xi32>) {alignment = 64 : i64} constant : !fir.array<3xi32>
+!CHECK: fir.global linkonce_odr @_QMmmECqq(dense<[51, 52, 53]> : tensor<3xi32>) {alignment = 64 : i64} constant : !fir.array<3xi32>
 !CHECK: }
diff --git a/flang/test/Lower/module-parameter-linkonce-odr.f90 b/flang/test/Lower/module-parameter-linkonce-odr.f90
new file mode 100644
index 0000000000000..5ff76dad421d2
--- /dev/null
+++ b/flang/test/Lower/module-parameter-linkonce-odr.f90
@@ -0,0 +1,83 @@
+! Module PARAMETERs used from another TU must be initialized linkonce_odr.
+! PARAMETERs with !$acc declare or a CUDA data attribute keep strong linkage.
+!
+! RUN: split-file %s %t
+! RUN: bbc -emit-hlfir %t/mod_params.f90 -o %t/mod_params.mlir --module=%t
+! RUN: bbc -emit-hlfir %t/kernel.f90 -o - -I %t | FileCheck %s --check-prefix=LINKONCE
+! RUN: bbc -fopenacc -emit-hlfir %t/mod_decl.f90 -o - --module=%t | FileCheck %s --check-prefix=DECL-DEF
+! RUN: bbc -fopenacc -emit-hlfir %t/use_decl.f90 -o - -I %t | FileCheck %s --check-prefix=DECL-USE
+! RUN: bbc -emit-hlfir %t/use_decl.f90 -o - -I %t | FileCheck %s --check-prefix=DECL-USE-NOACC
+! RUN: bbc -fcuda -emit-hlfir %t/mod_cuda.f90 -o - --module=%t | FileCheck %s --check-prefix=CUDA-CONST
+! RUN: bbc -fcuda -emit-hlfir %t/use_cuda.f90 -o - -I %t | FileCheck %s --check-prefix=CUDA-USE
+! RUN: bbc -emit-hlfir %t/use_cuda.f90 -o - -I %t | FileCheck %s --check-prefix=CUDA-USE-NOFCUDA
+
+//--- mod_params.f90
+module mod_params
+  implicit none
+  real(8), parameter :: arr_val(4) = [1.0d0, 2.0d0, 3.0d0, 4.0d0]
+end module
+
+//--- kernel.f90
+module kernel_mod
+  use mod_params, only: arr_val
+  implicit none
+contains
+  subroutine do_kernel(x)
+    real(8), intent(inout) :: x
+    x = x * arr_val(1)
+  end subroutine
+end module
+
+! LINKONCE: fir.global linkonce_odr @_QMmod_paramsECarr_val({{.*}}) {{.*}}constant : !fir.array<4xf64>
+! LINKONCE-NOT: fir.global @_QMmod_paramsECarr_val {{.*}}constant
+
+//--- mod_decl.f90
+module mod_decl
+  implicit none
+  real, parameter :: p = 1.5
+  !$acc declare create(p)
+end module
+
+//--- use_decl.f90
+subroutine use_decl()
+  use mod_decl
+  implicit none
+  real :: x
+  x = p
+end subroutine
+
+! DECL-DEF: fir.global @_QMmod_declECp{{.*}}constant
+! DECL-DEF-NOT: fir.global linkonce_odr @_QMmod_declECp
+
+! DECL-USE: fir.global @_QMmod_declECp {acc.declare = #acc.declare<dataClause = acc_create>
+! DECL-USE-NOT: fir.global linkonce_odr @_QMmod_declECp
+
+! Without -fopenacc the .mod does not restore AccDeclare, so this is a plain
+! PARAMETER and gets linkonce_odr.
+! DECL-USE-NOACC: fir.global linkonce_odr @_QMmod_declECp
+
+//--- mod_cuda.f90
+module mod_cuda
+  integer, parameter :: host_vals(2) = [11, 12]
+  integer, constant, parameter :: const_vals(2) = [-4, -8]
+end module
+
+//--- use_cuda.f90
+subroutine use_cuda()
+  use mod_cuda
+  implicit none
+  integer :: x
+  x = host_vals(1) + const_vals(1)
+end subroutine
+
+! Plain PARAMETER still gets linkonce_odr; CUDA constant PARAMETER stays strong.
+! CUDA-CONST-DAG: fir.global linkonce_odr @_QMmod_cudaEChost_vals
+! CUDA-CONST-DAG: fir.global @_QMmod_cudaECconst_vals{{.*}}data_attr = #cuf.cuda<constant>
+
+! CUDA data attributes are stored in the .mod, so the consumer keeps external
+! linkage for const_vals with or without -fcuda.
+! CUDA-USE-DAG: fir.global linkonce_odr @_QMmod_cudaEChost_vals
+! CUDA-USE-DAG: fir.global @_QMmod_cudaECconst_vals{{.*}}data_attr = #cuf.cuda<constant>
+! CUDA-USE-NOFCUDA-DAG: fir.global linkonce_odr @_QMmod_cudaEChost_vals
+! CUDA-USE-NOFCUDA-DAG: fir.global @_QMmod_cudaECconst_vals{{.*}}data_attr = #cuf.cuda<constant>
+! CUDA-USE-NOFCUDA-NOT: fir.global linkonce_odr @_QMmod_cudaECconst_vals
diff --git a/flang/test/Transforms/OpenACC/acc-implicit-declare-module-parameter.fir b/flang/test/Transforms/OpenACC/acc-implicit-declare-module-parameter.fir
new file mode 100644
index 0000000000000..b14c38e798216
--- /dev/null
+++ b/flang/test/Transforms/OpenACC/acc-implicit-declare-module-parameter.fir
@@ -0,0 +1,46 @@
+// An initialized linkonce_odr module PARAMETER (the lowering used for a
+// separately compiled USE) must be implicitly declared copyin when its
+// address is taken inside a compute region, and DEFAULT(PRESENT) must not
+// emit acc.present for it. A bodyless external constant is still hoisted.
+//
+// RUN: fir-opt %s --pass-pipeline="builtin.module(acc-initialize-fir-analyses,acc-implicit-declare,acc-implicit-data)" -split-input-file | FileCheck %s
+
+// -----
+
+fir.global linkonce_odr @_QMmod_paramsECarr_val(dense<[1.000000e+00, 2.000000e+00, 3.000000e+00, 4.000000e+00]> : tensor<4xf64>) {alignment = 64 : i64} constant : !fir.array<4xf64>
+
+func.func @_QMkernel_modPdo_kernel(%arg0: !fir.ref<!fir.array<4xf64>> {fir.bindc_name = "x"}) {
+  acc.parallel {
+    %addr = fir.address_of(@_QMmod_paramsECarr_val) : !fir.ref<!fir.array<4xf64>>
+    %0 = fir.load %addr : !fir.ref<!fir.array<4xf64>>
+    acc.yield
+  } defaultAttr(present)
+  return
+}
+
+// CHECK: fir.global linkonce_odr @_QMmod_paramsECarr_val({{.*}}) {{{.*}}acc.declare = #acc.declare<dataClause = acc_copyin>{{.*}}} {{.*}}constant : !fir.array<4xf64>
+// CHECK-LABEL: func.func @_QMkernel_modPdo_kernel
+// CHECK: acc.parallel
+// CHECK: fir.address_of(@_QMmod_paramsECarr_val)
+// CHECK-NOT: acc.present{{.*}}@_QMmod_paramsECarr_val
+// CHECK-NOT: acc.present{{.*}}name("arr_val")
+
+// -----
+
+fir.global @_QMmod_paramsECarr_val_ext constant : !fir.array<4xf64>
+
+func.func @_QMkernel_modPdo_kernel_ext(%arg0: !fir.ref<!fir.array<4xf64>> {fir.bindc_name = "x"}) {
+  acc.parallel {
+    %addr = fir.address_of(@_QMmod_paramsECarr_val_ext) : !fir.ref<!fir.array<4xf64>>
+    %0 = fir.load %addr : !fir.ref<!fir.array<4xf64>>
+    acc.yield
+  } defaultAttr(present)
+  return
+}
+
+// CHECK: fir.global @_QMmod_paramsECarr_val_ext constant : !fir.array<4xf64>
+// CHECK-NOT: acc.declare
+// CHECK-LABEL: func.func @_QMkernel_modPdo_kernel_ext
+// CHECK: fir.address_of(@_QMmod_paramsECarr_val_ext)
+// CHECK: acc.present{{.*}}{acc.from_default}
+// CHECK: acc.parallel
diff --git a/mlir/lib/Dialect/OpenACC/Transforms/ACCDeclareGPUModuleInsertion.cpp b/mlir/lib/Dialect/OpenACC/Transforms/ACCDeclareGPUModuleInsertion.cpp
index e5b93e053c87d..b43796912f3f5 100644
--- a/mlir/lib/Dialect/OpenACC/Transforms/ACCDeclareGPUModuleInsertion.cpp
+++ b/mlir/lib/Dialect/OpenACC/Transforms/ACCDeclareGPUModuleInsertion.cpp
@@ -56,6 +56,7 @@
 #include "mlir/IR/Operation.h"
 #include "mlir/IR/OperationSupport.h"
 #include "mlir/IR/SymbolTable.h"
+#include "llvm/ADT/StringRef.h"
 
 namespace mlir {
 namespace acc {
@@ -134,16 +135,15 @@ class ACCDeclareGPUModuleInsertion
         };
         if (!isEquivalent(existing, deviceGlobal)) {
           // Earlier GPU lowering can create a global in the GPU module before
-          // this pass. In unified memory, convert an equivalent pre-existing
-          // global to the declaration form expected for an OpenACC global.
-          if (makeUnifiedDeclaration) {
-            Operation *normalizedExisting = existing->clone();
+          // this pass. Normalize the pre-existing global to the form expected
+          // for an OpenACC device copy, then reuse if equivalent.
+          Operation *normalizedExisting = existing->clone();
+          if (makeUnifiedDeclaration)
             makeDeviceGlobalDeclaration(*normalizedExisting);
-            bool canReuse = isEquivalent(normalizedExisting, deviceGlobal);
-            normalizedExisting->destroy();
-            if (canReuse)
-              makeDeviceGlobalDeclaration(*existing);
-          }
+          bool canReuse = isEquivalent(normalizedExisting, deviceGlobal);
+          normalizedExisting->destroy();
+          if (canReuse && makeUnifiedDeclaration)
+            makeDeviceGlobalDeclaration(*existing);
         }
         if (!isEquivalent(existing, deviceGlobal)) {
           deviceGlobal->destroy();

``````````

</details>


https://github.com/llvm/llvm-project/pull/220666


More information about the flang-commits mailing list