[flang-commits] [flang] [flang][OpenMP] Mark declare target on use-associated module variables (PR #214596)
via flang-commits
flang-commits at lists.llvm.org
Thu Aug 6 16:48:33 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-flang-openmp
@llvm/pr-subscribers-flang-fir-hlfir
Author: Spencer Bryngelson (sbryngelson)
<details>
<summary>Changes</summary>
Fixes #<!-- -->214586.
A module variable marked `!$omp declare target` keeps its `OmpDeclareTarget` flag when it is use-associated into another translation unit, and the `.mod` file records the directive. But no declare target directive is parsed in that unit, so neither `markDeclareTarget` call site (`symbolAndClause` and `deferredDeclareTargets`) reaches it, and the global is emitted with no `omp.declare_target`.
`HostOpFiltering` then treats it as an ordinary host global and gives it internal linkage for the device:
```llvm
; consuming TU, --offload-device-only -O0
@<!-- -->_QMmod_aEnsz = internal addrspace(1) global i32 undef
```
so a `declare target` routine in that TU reads an uninitialized private copy instead of the definition, and `target update to(...)` has no effect on it. At -O2 the load from an `internal undef` global folds away, the dependent store is removed, and the `intent(out)` dummy becomes `readnone`. No diagnostic; the program computes wrong results. The issue has a three-file reproducer and a same-TU control that passes.
`genOpenMPSymbolProperties` already tests the flag and is reached for the use-associated variable, but its declare target branch calls `genDeclareTargetIntGlobal`, which starts with `if (!var.isGlobal())` and only materializes a `GlobalOp` for main program SAVE variables, so a module variable falls through unmarked.
This marks the declaration from the capture clause and device type recorded on the defining symbol.
Only globals **without an initializer body** are marked. A global defined in this unit is marked later by `markDeclareTarget` with the clauses as written; marking it here first would discard them, because `markDeclareTarget` leaves an already marked operation alone. That is not hypothetical -- an earlier version of this patch gated on `isDeclareTarget()` instead and regressed `Lower/OpenMP/declare-target-automap.f90`, because the main program global is not yet marked at this point.
`automap` is a modifier rather than a clause, so it is not in the symbol's clause set and cannot be recovered for a use-associated symbol; `false` is passed. That is a gap, though the current behaviour for such a variable is a silent wrong answer.
### Testing
- New `flang/test/Lower/OpenMP/declare-target-modfile.f90`, modelled on the existing `groupprivate-modfile.f90` cross-TU pattern. Verified it fails without the change and passes with it.
- `mlir/test/Dialect/OpenMP`, `mlir/test/Target/LLVMIR`, `flang/test/Lower/OpenMP`, `flang/test/Integration/OpenMP`, `flang/test/Transforms`, `flang/test/Fir`, `flang/test/Semantics/OpenMP`: 1921 tests, no regressions.
- The issue's reproducer runs correctly on gfx90a (MI250X), returning 42 instead of the host sentinel.
Developed and tested at 785fd14fa94e; the touched code is identical in main, where the patch applies cleanly.
cc @<!-- -->skatrak
---
Full diff: https://github.com/llvm/llvm-project/pull/214596.diff
2 Files Affected:
- (modified) flang/lib/Lower/OpenMP/OpenMP.cpp (+65-1)
- (added) flang/test/Lower/OpenMP/declare-target-modfile.f90 (+31)
``````````diff
diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index 3876799b3a081..bf22e998acd2f 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -6906,6 +6906,68 @@ void Fortran::lower::genOpenMPDeclarativeConstruct(
genNestedEvaluations(converter, eval);
}
+/// A declare target module variable keeps its \c OmpDeclareTarget flag when it
+/// is use-associated into another translation unit, but no declare target
+/// directive is parsed there, so neither \c markDeclareTarget call site reaches
+/// it. Attach the attribute to the declaration from what the defining symbol
+/// recorded.
+///
+/// Without this the declaration is indistinguishable from an ordinary host
+/// global and is given internal linkage for the device, replacing the
+/// definition with an undefined local copy and silently producing wrong
+/// results.
+static void
+markUseAssociatedDeclareTarget(lower::AbstractConverter &converter,
+ const lower::pft::Variable &var) {
+ if (!var.isGlobal())
+ return;
+
+ const semantics::Symbol &ultimate = var.getSymbol().GetUltimate();
+ const auto *details = ultimate.detailsIf<semantics::ObjectEntityDetails>();
+ if (!details)
+ return;
+
+ const semantics::WithOmpDeclarative::OmpClauseSet &clauses =
+ details->ompDeclTarget();
+ if (!clauses.count())
+ return;
+
+ mlir::ModuleOp mod = converter.getFirOpBuilder().getModule();
+ mlir::Operation *op = mod.lookupSymbol(converter.mangleName(ultimate));
+ if (!op)
+ return;
+
+ auto declareTargetOp = llvm::dyn_cast<mlir::omp::DeclareTargetInterface>(op);
+ if (!declareTargetOp || declareTargetOp.isDeclareTarget())
+ return;
+
+ // Only mark declarations. A global defined in this unit carries an
+ // initializer body and is marked by markDeclareTarget with the clauses as
+ // written, which happens after this point; marking it here would discard
+ // them, because markDeclareTarget leaves an already marked operation alone.
+ for (mlir::Region ®ion : op->getRegions())
+ if (!region.empty())
+ return;
+
+ // `enter` and `to` are equivalent captures and lower identically, matching
+ // what the directive path produces for the definition.
+ mlir::omp::DeclareTargetCaptureClause captureClause =
+ clauses.test(llvm::omp::Clause::OMPC_link)
+ ? mlir::omp::DeclareTargetCaptureClause::link
+ : mlir::omp::DeclareTargetCaptureClause::to;
+
+ mlir::omp::DeclareTargetDeviceType deviceType =
+ mlir::omp::DeclareTargetDeviceType::any;
+ if (const std::optional<common::OmpDeviceType> &dt =
+ details->ompDeclTargetDeviceType())
+ deviceType = toMLIRDeclareTargetDeviceType(*dt);
+
+ // automap is a modifier rather than a clause, so it is not recorded in the
+ // clause set and cannot be recovered here for a use-associated symbol.
+ declareTargetOp.setDeclareTarget(deviceType, captureClause,
+ /*automap=*/false);
+}
+
void Fortran::lower::genOpenMPSymbolProperties(
lower::AbstractConverter &converter, const lower::pft::Variable &var) {
assert(var.hasSymbol() && "Expecting Symbol");
@@ -6917,8 +6979,10 @@ void Fortran::lower::genOpenMPSymbolProperties(
if (sym.test(semantics::Symbol::Flag::OmpThreadprivate))
lower::genThreadprivateOp(converter, var);
- if (sym.test(semantics::Symbol::Flag::OmpDeclareTarget))
+ if (sym.test(semantics::Symbol::Flag::OmpDeclareTarget)) {
lower::genDeclareTargetIntGlobal(converter, var);
+ markUseAssociatedDeclareTarget(converter, var);
+ }
}
void Fortran::lower::genGroupprivateOp(lower::AbstractConverter &converter,
diff --git a/flang/test/Lower/OpenMP/declare-target-modfile.f90 b/flang/test/Lower/OpenMP/declare-target-modfile.f90
new file mode 100644
index 0000000000000..43eed5ef55c69
--- /dev/null
+++ b/flang/test/Lower/OpenMP/declare-target-modfile.f90
@@ -0,0 +1,31 @@
+! Cross-TU propagation of `declare target` on a module variable via .mod files.
+
+! RUN: rm -rf %t && split-file %s %t
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -module-dir %t %t/m.f90 -o - > /dev/null
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -J %t %t/use.f90 -o - | FileCheck %s
+
+! First RUN builds dt_mod.mod.
+! Second RUN lowers a consumer that only USE-associates the module. The
+! declaration emitted there must carry omp.declare_target, recovered from the
+! .mod file. Without it the global is treated as an ordinary host global and is
+! given internal linkage for the device, which replaces the definition with an
+! undefined local copy and silently yields wrong results.
+
+!--- m.f90
+module dt_mod
+ implicit none
+ integer :: dt_x
+ !$omp declare target(dt_x)
+end module dt_mod
+
+!--- use.f90
+subroutine use_dt_mod(out)
+ use dt_mod
+ implicit none
+ integer, intent(out) :: out
+ !$omp target map(tofrom: out)
+ out = dt_x
+ !$omp end target
+end subroutine use_dt_mod
+
+! CHECK: fir.global @_QMdt_modEdt_x {omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (to), automap = false>} : i32
``````````
</details>
https://github.com/llvm/llvm-project/pull/214596
More information about the flang-commits
mailing list