[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