[flang-commits] [flang] 5c9830c - [flang][OpenACC] Emit module PARAMETERs as initialized linkonce_odr globals (#220666)
via flang-commits
flang-commits at lists.llvm.org
Thu Sep 3 08:58:47 PDT 2026
Author: nvptm
Date: 2026-09-03T08:58:42-07:00
New Revision: 5c9830c0a5a7c1bbae3b6e82232530be101723aa
URL: https://github.com/llvm/llvm-project/commit/5c9830c0a5a7c1bbae3b6e82232530be101723aa
DIFF: https://github.com/llvm/llvm-project/commit/5c9830c0a5a7c1bbae3b6e82232530be101723aa.diff
LOG: [flang][OpenACC] Emit module PARAMETERs as initialized linkonce_odr globals (#220666)
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.
Added:
flang/test/Lower/module-parameter-linkonce-odr.f90
flang/test/Transforms/OpenACC/acc-implicit-declare-module-parameter.fir
Modified:
flang/lib/Lower/ConvertVariable.cpp
flang/test/Fir/OpenACC/acc-declare-gpu-module-insertion.fir
flang/test/Integration/function-sections.f90
flang/test/Lower/dense-attributed-array.f90
Removed:
################################################################################
diff --git a/flang/lib/Lower/ConvertVariable.cpp b/flang/lib/Lower/ConvertVariable.cpp
index 8438b39c990a9..5d9fdf48bd067 100644
--- a/flang/lib/Lower/ConvertVariable.cpp
+++ b/flang/lib/Lower/ConvertVariable.cpp
@@ -509,8 +509,15 @@ fir::GlobalOp Fortran::lower::defineGlobal(
if (global && globalIsInitialized(global))
return global;
+ // Follow USE association only for named constants so the module-file
+ // initializer is available in the using TU. Other globals keep the
+ // local symbol: copying a non-PARAMETER initializer across TUs with
+ // external linkage would be a duplicate definition.
+ const Fortran::semantics::Symbol &objSym =
+ Fortran::semantics::IsNamedConstant(sym.GetUltimate()) ? sym.GetUltimate()
+ : sym;
const auto *oeDetails =
- sym.detailsIf<Fortran::semantics::ObjectEntityDetails>();
+ objSym.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 +669,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..c0314ff1a6607
--- /dev/null
+++ b/flang/test/Lower/module-parameter-linkonce-odr.f90
@@ -0,0 +1,90 @@
+! Module PARAMETERs used from another TU must be initialized linkonce_odr.
+! PARAMETERs with !$acc declare or a CUDA data attribute keep strong linkage:
+! the defining TU has the initializer; the using TU emits a declaration.
+!
+! 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
+
+! Defining TU has an initialized definition; using TU is a declaration.
+! DECL-DEF: fir.global @_QMmod_declECp {acc.declare = #acc.declare<dataClause = acc_create>} constant : f32 {
+! DECL-DEF: fir.has_value
+! 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 @_QMmod_declECp(
+! DECL-USE-NOT: fir.has_value
+! 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
+! with an initializer in the defining TU and a declaration in the consumer.
+! 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. '{' after the name is a
+! declaration (no dense initializer); DAG because emission order is not a contract.
+! 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
More information about the flang-commits
mailing list