[flang][openmp] Support importing module declare target globals
Project: llvm/llvm-project | Language: C++ | Concepts: api-design, error-handling, testing-strategy, refactoring | Difficulty: mid
Link: https://github.com/llvm/llvm-project/pull/213930
The problem
When a Fortran module is imported that contains OpenMP declare target directives for global variables, the attributes specifying device type and capture clause were not being correctly attached to those globals in the importing translation unit. This led to incomplete or incorrect OpenMP lowering for imported symbols, especially for device code generation.
Why it's non-trivial
A naive fix might try to attach attributes during normal symbol lowering, but imported module symbols are not always handled in the same pass as locally defined symbols. The attributes must be attached after all relevant global operations have been created, and only for those that are imported and marked with declare target. Additionally, the logic must distinguish between globals and non-globals, and handle the various possible declare target clauses (e.g., link, to, enter) and device types.
The approach
The patch introduces a new function to attach OpenMP declare target attributes to imported globals, and hooks it into the lowering process. Key changes:
// flang/include/flang/Lower/OpenMP.h
// New function declaration to attach attributes to imported globals.
void attachOpenMPDeclareTargetAttributes(lower::AbstractConverter &converter,
const lower::pft::Variable &var);
This function will be called for each variable that is global and marked as OmpDeclareTarget.
// flang/lib/Lower/OpenMP/OpenMP.cpp
void Fortran::lower::genOpenMPSymbolProperties(
lower::AbstractConverter &converter, const lower::pft::Variable &var) {
...
if (sym.test(semantics::Symbol::Flag::OmpDeclareTarget)) {
if (var.isGlobal())
lower::attachOpenMPDeclareTargetAttributes(converter, var);
else
lower::genDeclareTargetIntGlobal(converter, var);
}
}
This change ensures that for global variables with the OmpDeclareTarget flag, the new attribute attachment function is called instead of the old logic, which only created globals for non-global variables.
// flang/lib/Lower/OpenMP/OpenMP.cpp
void Fortran::lower::attachOpenMPDeclareTargetAttributes(
lower::AbstractConverter &converter, const lower::pft::Variable &var) {
auto module = converter.getModuleOp();
mlir::Operation *globalOp =
module.lookupSymbol(converter.mangleName(var.getSymbol()));
auto ultimateSymbol = var.getSymbol().GetUltimate();
if (globalOp && ultimateSymbol.IsFromModFile()) {
auto declareTargetOp =
llvm::dyn_cast<mlir::omp::DeclareTargetInterface>(globalOp);
Fortran::common::visit(
[&](const auto &details) {
if constexpr (std::is_base_of_v<semantics::WithOmpDeclarative,
std::decay_t<decltype(details)>>) {
mlir::omp::DeclareTargetCaptureClause clause;
mlir::omp::DeclareTargetDeviceType deviceType =
toMLIRDeclareTargetDeviceType(
details.ompDeclTargetDeviceType().value_or(
Fortran::common::OmpDeviceType::Any));
const llvm::omp::ClauseSet clauses = details.ompDeclTarget();
if (clauses.test(llvm::omp::Clause::OMPC_link))
clause = mlir::omp::DeclareTargetCaptureClause::link;
else if (clauses.test(llvm::omp::Clause::OMPC_to))
clause = mlir::omp::DeclareTargetCaptureClause::to;
else
clause = mlir::omp::DeclareTargetCaptureClause::enter;
declareTargetOp.setDeclareTarget(deviceType, clause, false);
}
},
ultimateSymbol.details());
}
}
This function checks if the global operation corresponds to an imported symbol from a module file, determines the correct clause and device type, and attaches the appropriate OpenMP attributes.
! flang/test/Lower/OpenMP/declare_target_module.f90
!CHECK-DAG: fir.global @_QMdeclare_target_moduleEglobal_arr {alignment = 64 : i64, omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (enter), automap = false>} : !fir.array<10xi32>
!CHECK-DAG: fir.global @_QMdeclare_target_moduleEglobal_real {omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (link), automap = false>} : f32
!CHECK-DAG: fir.global @_QMdeclare_target_moduleEglobal_integer {omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (to), automap = false>} : i32
!CHECK-DAG: fir.global @_QMdeclare_target_moduleEglobal_device_integer {omp.declare_target = #omp.declaretarget<device_type = (nohost), capture_clause = (enter), automap = false>} : i32
The new test ensures that the correct OpenMP attributes are attached to each imported global, as verified by the FileCheck patterns.
From the review thread
No notable review discussion on this PR.
Takeaways
- When handling imported symbols, attribute attachment may need to be deferred until after all relevant IR operations have been created.
- Attribute propagation for imported entities should check both the symbol origin (e.g., from module file) and the relevant semantic flags.
- Comprehensive testing, including both the source module and the importing unit, is necessary to ensure correct attribute propagation across module boundaries.