[Flang][MLIR][OpenMP] Create a deferred declare target marking process for Bridge.cpp (#78502)
This patch seeks to create a process that happens on module finalization for OpenMP, in which a list of operations that had declare target directives applied to them and were not generated at the time of processing the original declare target directive are re-checked to apply the appropriate declare target semantics. This works by maintaining a vector of declare target related data inside of the FIR converter, in this case the symbol and the two relevant unsigned integers representing the enumerators. This vector is added to via a new function called from Bridge.cpp, insertDeferredDeclareTargets, which happens prior to the processing of the directive (similarly to getDeclareTargetFunctionDevice currently for requires), it effectively checks if the Operation the declare target directive is applied to currently exists, if it doesn't it appends to the vector. This is a seperate function to the processing of the declare target via the overloaded genOMP as we unfortunately do not have access to the list without passing it through every call, as the AbstractConverter we pass will not allow access to it (I've seen no other cases of casting it to a FirConverter, so I opted to not do that). The list is then processed at the end of the module in the finalizeOpenMPLowering function in Bridge by calling a new function markDelayedDeclareTargetFunctions which marks the latently generated operations. In certain cases, some still will not be generated, e.g. if an interface is defined, marked as declare target, but has no definition or usage in the module then it will not be emitted to the module, so due to these cases we must silently ignore when an operation has not been found via it's symbol. The main use-case for this (although, I imagine there is others) is for processing interfaces that have been declared in a module with a declare target directive but do not have their implementation defined in the same module. For example, inside of a seperate C++ module that will be linked in. In cases where the interface is called inside of a target region it'll be marked as used on device appropriately (although, realistically a user should explicitly mark it to match the corresponding definition), however, in cases where it's used in a non-clear manner through something like a function pointer passed to an external call we require this explicit marking, which this patch adds support for (currently will cause the compiler to crash). This patch also adds documentation on the declare target process and mechanisms within the compiler currently.
This commit is contained in:
parent
3b84b6f176
commit
afb05cd646
257
flang/docs/OpenMP-declare-target.md
Normal file
257
flang/docs/OpenMP-declare-target.md
Normal file
@ -0,0 +1,257 @@
|
||||
<!--===- docs/OpenMP-declare-target.md
|
||||
|
||||
Part of the LLVM Project, under the Apache License v2.0 with LLVM
|
||||
Exceptions.
|
||||
See https://llvm.org/LICENSE.txt for license information.
|
||||
SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
|
||||
-->
|
||||
|
||||
# Introduction to Declare Target
|
||||
|
||||
In OpenMP `declare target` is a directive that can be applied to a function or
|
||||
variable (primarily global) to notate to the compiler that it should be
|
||||
generated in a particular device's environment. In essence whether something
|
||||
should be emitted for host or device, or both. An example of its usage for
|
||||
both data and functions can be seen below.
|
||||
|
||||
```Fortran
|
||||
module test_0
|
||||
integer :: sp = 0
|
||||
!$omp declare target link(sp)
|
||||
end module test_0
|
||||
|
||||
program main
|
||||
use test_0
|
||||
!$omp target map(tofrom:sp)
|
||||
sp = 1
|
||||
!$omp end target
|
||||
end program
|
||||
```
|
||||
|
||||
In the above example, we create a variable in a separate module, mark it
|
||||
as `declare target` and then map it, embedding it into the device IR and
|
||||
assigning to it.
|
||||
|
||||
|
||||
```Fortran
|
||||
function func_t_device() result(i)
|
||||
!$omp declare target to(func_t_device) device_type(nohost)
|
||||
INTEGER :: I
|
||||
I = 1
|
||||
end function func_t_device
|
||||
|
||||
program main
|
||||
!$omp target
|
||||
call func_t_device()
|
||||
!$omp end target
|
||||
end program
|
||||
```
|
||||
|
||||
In the above example, we are stating that a function is required on device
|
||||
utilising `declare target`, and that we will not be utilising it on host,
|
||||
so we are in theory free to remove or ignore it there. A user could also
|
||||
in this case, leave off the `declare target` from the function and it
|
||||
would be implicitly marked `declare target any` (for both host and device),
|
||||
as it's been utilised within a target region.
|
||||
|
||||
# Declare Target as represented in the OpenMP Dialect
|
||||
|
||||
In the OpenMP Dialect `declare target` is not represented by a specific
|
||||
`operation`. Instead, it's an OpenMP dialect specific `attribute` that can be
|
||||
applied to any operation in any dialect, which helps to simplify the
|
||||
utilisation of it. Rather than replacing or modifying existing global or
|
||||
function `operations` in a dialect, it applies to it as extra metadata that
|
||||
the lowering can use in different ways as is necessary.
|
||||
|
||||
The `attribute` is composed of multiple fields representing the clauses you
|
||||
would find on the `declare target` directive i.e. device type (`nohost`,
|
||||
`any`, `host`) or the capture clause (`link` or `to`). A small example of
|
||||
`declare target` applied to a Fortran `real` can be found below:
|
||||
|
||||
```
|
||||
fir.global internal @_QFEi {omp.declare_target =
|
||||
#omp.declaretarget<device_type = (any), capture_clause = (to)>} : f32 {
|
||||
%0 = fir.undefined f32
|
||||
fir.has_value %0 : f32
|
||||
}
|
||||
```
|
||||
|
||||
This would look similar for function style `operations`.
|
||||
|
||||
The application and access of this attribute is aided by an OpenMP Dialect
|
||||
MLIR Interface named `DeclareTargetInterface`, which can be utilised on
|
||||
operations to access the appropriate interface functions, e.g.:
|
||||
|
||||
```C++
|
||||
auto declareTargetGlobal =
|
||||
llvm::dyn_cast<mlir::omp::DeclareTargetInterface>(Op.getOperation());
|
||||
declareTargetGlobal.isDeclareTarget();
|
||||
```
|
||||
|
||||
# Declare Target Fortran OpenMP Lowering
|
||||
|
||||
The initial lowering of `declare target` to MLIR for both use-cases is done
|
||||
inside of the usual OpenMP lowering in flang/lib/Lower/OpenMP.cpp. However,
|
||||
some direct calls to `declare target` related functions from Flang's
|
||||
lowering bridge in flang/lib/Lower/Bridge.cpp are made.
|
||||
|
||||
The marking of operations with the declare target attribute happens in two
|
||||
phases, the second one optional and contingent on the first failing. The
|
||||
initial phase happens when the declare target directive and its clauses
|
||||
are initially processed, with the primary data gathering for the directive and
|
||||
clause happening in a function called `getDeclareTargetInfo`. This is then used
|
||||
to feed the `markDeclareTarget` function, which does the actual marking
|
||||
utilising the `DeclareTargetInterface`. If it encounters a variable or function
|
||||
that has been marked twice over multiple directives with two differing device
|
||||
types (e.g. `host`, `nohost`), then it will swap the device type to `any`.
|
||||
|
||||
Whenever we invoke `genFIR` on an `OpenMPDeclarativeConstruct` from the
|
||||
lowering bridge, we are also invoking another function called
|
||||
`gatherOpenMPDeferredDeclareTargets`, which gathers information relevant to the
|
||||
application of the `declare target` attribute. This information
|
||||
includes the symbol that it should be applied to, device type clause,
|
||||
and capture clause, and it is stored in a vector that is part of the lowering
|
||||
bridge's instantiation of the `AbstractConverter`. It is only stored if we
|
||||
encounter a function or variable symbol that does not have an operation
|
||||
instantiated for it yet. This cannot happen as part of the
|
||||
initial marking as we must store this data in the lowering bridge and we
|
||||
only have access to the abstract version of the converter via the OpenMP
|
||||
lowering.
|
||||
|
||||
The information produced by the first phase is used in the second phase,
|
||||
which is a form of deferred processing of the `declare target` marked
|
||||
operations that have delayed generation and cannot be proccessed in the
|
||||
first phase. The main notable case this occurs currently is when a
|
||||
Fortran function interface has been marked. This is
|
||||
done via the function
|
||||
`markOpenMPDeferredDeclareTargetFunctions`, which is called from the lowering
|
||||
bridge at the end of the lowering process allowing us to mark those where
|
||||
possible. It iterates over the data previously gathered by
|
||||
`gatherOpenMPDeferredDeclareTargets`
|
||||
checking if any of the recorded symbols have now had their corresponding
|
||||
operations instantiated and applying the declare target attribute where
|
||||
possible utilising `markDeclareTarget`. However, it must be noted that it
|
||||
is still possible for operations not to be generated for certain symbols,
|
||||
in particular the case of function interfaces that are not directly used
|
||||
or defined within the current module. This means we cannot emit errors in
|
||||
the case of left-over unmarked symbols. These must (and should) be caught
|
||||
by the initial semantic analysis.
|
||||
|
||||
NOTE: `declare target` can be applied to implicit `SAVE` attributed variables.
|
||||
However, by default Flang does not represent these as `GlobalOp`'s, which means
|
||||
we cannot tag and lower them as `declare target` normally. Instead, similarly
|
||||
to the way `threadprivate` handles these cases, we raise and initialize the
|
||||
variable as an internal `GlobalOp` and apply the attribute. This occurs in the
|
||||
flang/lib/Lower/OpenMP.cpp function `genDeclareTargetIntGlobal`.
|
||||
|
||||
# Declare Target Transformation Passes for Flang
|
||||
|
||||
There are currently two passes within Flang that are related to the processing
|
||||
of `declare target`:
|
||||
* `OMPMarkDeclareTarget` - This pass is in charge of marking functions captured
|
||||
(called from) in `target` regions or other `declare target` marked functions as
|
||||
`declare target`. It does so recursively, i.e. nested calls will also be
|
||||
implicitly marked. It currently will try to mark things as conservatively as
|
||||
possible, e.g. if captured in a `target` region it will apply `nohost`, unless
|
||||
it encounters a `host` `declare target` in which case it will apply the `any`
|
||||
device type. Functions are handled similarly, except we utilise the parent's
|
||||
device type where possible.
|
||||
* `OMPFunctionFiltering` - This is executed after the `OMPMarkDeclareTarget`
|
||||
pass, and its job is to conservatively remove host functions from
|
||||
the module where possible when compiling for the device. This helps make
|
||||
sure that most incompatible code for the host is not lowered for the
|
||||
device. Host functions with `target` regions in them need to be preserved
|
||||
(e.g. for lowering the `target region`(s) inside). Otherwise, it removes
|
||||
any function marked as a `declare target host` function and any uses will be
|
||||
replaced with `undef`'s so that the remaining host code doesn't become broken.
|
||||
Host functions with `target` regions are marked with a `declare target host`
|
||||
attribute so they will be removed after outlining the target regions contained
|
||||
inside.
|
||||
|
||||
While this infrastructure could be generally applicable to more than just Flang,
|
||||
it is only utilised in the Flang frontend, so it resides there rather than in
|
||||
the OpenMP dialect codebase.
|
||||
|
||||
# Declare Target OpenMP Dialect To LLVM-IR Lowering
|
||||
|
||||
The OpenMP dialect lowering of `declare target` is done through the
|
||||
`amendOperation` flow, as it's not an `operation` but rather an
|
||||
`attribute`. This is triggered immediately after the corresponding
|
||||
operation has been lowered to LLVM-IR. As it is applicable to
|
||||
different types of operations, we must specialise this function for
|
||||
each operation type that we may encounter. Currently, this is
|
||||
`GlobalOp`'s and `FuncOp`'s.
|
||||
|
||||
`FuncOp` processing is fairly simple. When compiling for the device,
|
||||
`host` marked functions are removed, including those that could not
|
||||
be removed earlier due to having `target` directives within. This
|
||||
leaves `any`, `device` or indeterminable functions left in the
|
||||
module to lower further. When compiling for the host, no filtering is
|
||||
done because `nohost` functions must be available as a fallback
|
||||
implementation.
|
||||
|
||||
For `GlobalOp`'s, the processing is a little more complex. We
|
||||
currently leverage the `registerTargetGlobalVariable` and
|
||||
`getAddrOfDeclareTargetVar` `OMPIRBuilder` functions shared with Clang.
|
||||
These two functions invoke each other depending on the clauses and options
|
||||
provided to the `OMPIRBuilder` (in particular, unified shared memory). Their
|
||||
main purposes are the generation of a new global device pointer with a
|
||||
"ref_" prefix on the device and enqueuing metadata generation by the
|
||||
`OMPIRBuilder` to be produced at module finalization time. This is done
|
||||
for both host and device and it links the newly generated device global
|
||||
pointer and the host pointer together across the two modules.
|
||||
|
||||
Similarly to other metadata (e.g. for `TargetOp`) that is shared across
|
||||
both host and device modules, processing of `GlobalOp`'s in the device
|
||||
needs access to the previously generated host IR file, which is done
|
||||
through another `attribute` applied to the `ModuleOp` by the compiler
|
||||
frontend. The file is loaded in and consumed by the `OMPIRBuilder` to
|
||||
populate it's `OffloadInfoManager` data structures, keeping host and
|
||||
device appropriately synchronised.
|
||||
|
||||
The second (and more important to remember) is that as we effectively replace
|
||||
the original LLVM-IR generated for the `declare target` marked `GlobalOp` we
|
||||
have some corrections we need to do for `TargetOp`'s (or other region
|
||||
operations that use them directly) which still refer to the original lowered
|
||||
global operation. This is done via `handleDeclareTargetMapVar` which is invoked
|
||||
as the final function and alteration to the lowered `target` region, it's only
|
||||
invoked for device as it's only required in the case where we have emitted the
|
||||
"ref" pointer , and it effectively replaces all uses of the originally lowered
|
||||
global symbol, with our new global ref pointer's symbol. Currently we do not
|
||||
remove or delete the old symbol, this is due to the fact that the same symbol
|
||||
can be utilised across multiple target regions, if we remove it, we risk
|
||||
breaking lowerings of target regions that will be processed at a later time.
|
||||
To appropriately delete these no longer necessary symbols we would need a
|
||||
deferred removal process at the end of the module, which is currently not in
|
||||
place. It may be possible to store this information in the OMPIRBuilder and
|
||||
then perform this cleanup process on finalization, but this is open for
|
||||
discussion and implementation still.
|
||||
|
||||
# Current Support
|
||||
|
||||
For the moment, `declare target` should work for:
|
||||
* Marking functions/subroutines and function/subroutine interfaces for
|
||||
generation on host, device or both.
|
||||
* Implicit function/subroutine capture for calls emitted in a `target` region
|
||||
or explicitly marked `declare target` function/subroutine. Note: Calls made
|
||||
via arguments passed to other functions must still be themselves marked
|
||||
`declare target`, e.g. passing a `C` function pointer and invoking it, then
|
||||
the interface and the `C` function in the other module must be marked
|
||||
`declare target`, with the same type of marking as indicated by the
|
||||
specification.
|
||||
* Marking global variables with `declare target`'s `link` clause and mapping
|
||||
the data to the device data environment utilising `declare target`. This may
|
||||
not work for all types yet, but for scalars and arrays of scalars, it
|
||||
should.
|
||||
|
||||
Doesn't work for, or needs further testing for:
|
||||
* Marking the following types with `declare target link` (needs further
|
||||
testing):
|
||||
* Descriptor based types, e.g. pointers/allocatables.
|
||||
* Derived types.
|
||||
* Members of derived types (use-case needs legality checking with OpenMP
|
||||
specification).
|
||||
* Marking global variables with `declare target`'s `to` clause. A lot of the
|
||||
lowering should exist, but it needs further testing and likely some further
|
||||
changes to fully function.
|
@ -68,6 +68,7 @@ on how to get in touch with us and to learn more about the current status.
|
||||
OpenACC
|
||||
OpenACC-descriptor-management.md
|
||||
OpenMP-4.5-grammar.md
|
||||
OpenMP-declare-target
|
||||
OpenMP-descriptor-management
|
||||
OpenMP-semantics
|
||||
OptionComparison
|
||||
|
@ -13,12 +13,19 @@
|
||||
#ifndef FORTRAN_LOWER_OPENMP_H
|
||||
#define FORTRAN_LOWER_OPENMP_H
|
||||
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
|
||||
#include <cinttypes>
|
||||
#include <utility>
|
||||
|
||||
namespace mlir {
|
||||
class Value;
|
||||
class Operation;
|
||||
class Location;
|
||||
namespace omp {
|
||||
enum class DeclareTargetDeviceType : uint32_t;
|
||||
enum class DeclareTargetCaptureClause : uint32_t;
|
||||
} // namespace omp
|
||||
} // namespace mlir
|
||||
|
||||
namespace fir {
|
||||
@ -49,6 +56,12 @@ struct Evaluation;
|
||||
struct Variable;
|
||||
} // namespace pft
|
||||
|
||||
struct OMPDeferredDeclareTargetInfo {
|
||||
mlir::omp::DeclareTargetCaptureClause declareTargetCaptureClause;
|
||||
mlir::omp::DeclareTargetDeviceType declareTargetDeviceType;
|
||||
const Fortran::semantics::Symbol &sym;
|
||||
};
|
||||
|
||||
// Generate the OpenMP terminator for Operation at Location.
|
||||
mlir::Operation *genOpenMPTerminator(fir::FirOpBuilder &, mlir::Operation *,
|
||||
mlir::Location);
|
||||
@ -86,6 +99,14 @@ bool isOpenMPDeviceDeclareTarget(Fortran::lower::AbstractConverter &,
|
||||
Fortran::semantics::SemanticsContext &,
|
||||
Fortran::lower::pft::Evaluation &,
|
||||
const parser::OpenMPDeclarativeConstruct &);
|
||||
void gatherOpenMPDeferredDeclareTargets(
|
||||
Fortran::lower::AbstractConverter &, Fortran::semantics::SemanticsContext &,
|
||||
Fortran::lower::pft::Evaluation &,
|
||||
const parser::OpenMPDeclarativeConstruct &,
|
||||
llvm::SmallVectorImpl<OMPDeferredDeclareTargetInfo> &);
|
||||
bool markOpenMPDeferredDeclareTargetFunctions(
|
||||
mlir::Operation *, llvm::SmallVectorImpl<OMPDeferredDeclareTargetInfo> &,
|
||||
AbstractConverter &);
|
||||
void genOpenMPRequires(mlir::Operation *, const Fortran::semantics::Symbol *);
|
||||
|
||||
} // namespace lower
|
||||
|
@ -2633,6 +2633,9 @@ private:
|
||||
ompDeviceCodeFound ||
|
||||
Fortran::lower::isOpenMPDeviceDeclareTarget(
|
||||
*this, bridge.getSemanticsContext(), getEval(), ompDecl);
|
||||
Fortran::lower::gatherOpenMPDeferredDeclareTargets(
|
||||
*this, bridge.getSemanticsContext(), getEval(), ompDecl,
|
||||
ompDeferredDeclareTarget);
|
||||
genOpenMPDeclarativeConstruct(
|
||||
*this, localSymbols, bridge.getSemanticsContext(), getEval(), ompDecl);
|
||||
builder->restoreInsertionPoint(insertPt);
|
||||
@ -5171,6 +5174,13 @@ private:
|
||||
/// lowering.
|
||||
void finalizeOpenMPLowering(
|
||||
const Fortran::semantics::Symbol *globalOmpRequiresSymbol) {
|
||||
if (!ompDeferredDeclareTarget.empty()) {
|
||||
bool deferredDeviceFuncFound =
|
||||
Fortran::lower::markOpenMPDeferredDeclareTargetFunctions(
|
||||
getModuleOp().getOperation(), ompDeferredDeclareTarget, *this);
|
||||
ompDeviceCodeFound = ompDeviceCodeFound || deferredDeviceFuncFound;
|
||||
}
|
||||
|
||||
// Set the module attribute related to OpenMP requires directives
|
||||
if (ompDeviceCodeFound)
|
||||
Fortran::lower::genOpenMPRequires(getModuleOp().getOperation(),
|
||||
@ -5227,6 +5237,13 @@ private:
|
||||
/// intended for device offloading has been detected
|
||||
bool ompDeviceCodeFound = false;
|
||||
|
||||
/// Keeps track of symbols defined as declare target that could not be
|
||||
/// processed at the time of lowering the declare target construct, such
|
||||
/// as certain cases where interfaces are declared but not defined within
|
||||
/// a module.
|
||||
llvm::SmallVector<Fortran::lower::OMPDeferredDeclareTargetInfo>
|
||||
ompDeferredDeclareTarget;
|
||||
|
||||
const Fortran::lower::ExprToValueMap *exprValueOverrides{nullptr};
|
||||
|
||||
/// Stack of derived type under construction to avoid infinite loops when
|
||||
|
@ -1238,6 +1238,31 @@ static mlir::omp::DeclareTargetDeviceType getDeclareTargetInfo(
|
||||
return deviceType;
|
||||
}
|
||||
|
||||
static void collectDeferredDeclareTargets(
|
||||
Fortran::lower::AbstractConverter &converter,
|
||||
Fortran::semantics::SemanticsContext &semaCtx,
|
||||
Fortran::lower::pft::Evaluation &eval,
|
||||
const Fortran::parser::OpenMPDeclareTargetConstruct &declareTargetConstruct,
|
||||
llvm::SmallVectorImpl<Fortran::lower::OMPDeferredDeclareTargetInfo>
|
||||
&deferredDeclareTarget) {
|
||||
llvm::SmallVector<DeclareTargetCapturePair> symbolAndClause;
|
||||
mlir::omp::DeclareTargetDeviceType devType = getDeclareTargetInfo(
|
||||
converter, semaCtx, eval, declareTargetConstruct, symbolAndClause);
|
||||
// Return the device type only if at least one of the targets for the
|
||||
// directive is a function or subroutine
|
||||
mlir::ModuleOp mod = converter.getFirOpBuilder().getModule();
|
||||
|
||||
for (const DeclareTargetCapturePair &symClause : symbolAndClause) {
|
||||
mlir::Operation *op = mod.lookupSymbol(converter.mangleName(
|
||||
std::get<const Fortran::semantics::Symbol &>(symClause)));
|
||||
|
||||
if (!op) {
|
||||
deferredDeclareTarget.push_back(
|
||||
{std::get<0>(symClause), devType, std::get<1>(symClause)});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static std::optional<mlir::omp::DeclareTargetDeviceType>
|
||||
getDeclareTargetFunctionDevice(
|
||||
Fortran::lower::AbstractConverter &converter,
|
||||
@ -1245,7 +1270,7 @@ getDeclareTargetFunctionDevice(
|
||||
Fortran::lower::pft::Evaluation &eval,
|
||||
const Fortran::parser::OpenMPDeclareTargetConstruct
|
||||
&declareTargetConstruct) {
|
||||
llvm::SmallVector<DeclareTargetCapturePair, 0> symbolAndClause;
|
||||
llvm::SmallVector<DeclareTargetCapturePair> symbolAndClause;
|
||||
mlir::omp::DeclareTargetDeviceType deviceType = getDeclareTargetInfo(
|
||||
converter, semaCtx, eval, declareTargetConstruct, symbolAndClause);
|
||||
|
||||
@ -1253,10 +1278,10 @@ getDeclareTargetFunctionDevice(
|
||||
// directive is a function or subroutine
|
||||
mlir::ModuleOp mod = converter.getFirOpBuilder().getModule();
|
||||
for (const DeclareTargetCapturePair &symClause : symbolAndClause) {
|
||||
mlir::Operation *op = mod.lookupSymbol(
|
||||
converter.mangleName(std::get<Fortran::semantics::Symbol>(symClause)));
|
||||
mlir::Operation *op = mod.lookupSymbol(converter.mangleName(
|
||||
std::get<const Fortran::semantics::Symbol &>(symClause)));
|
||||
|
||||
if (mlir::isa<mlir::func::FuncOp>(op))
|
||||
if (mlir::isa_and_nonnull<mlir::func::FuncOp>(op))
|
||||
return deviceType;
|
||||
}
|
||||
|
||||
@ -2007,56 +2032,56 @@ genOMP(Fortran::lower::AbstractConverter &converter,
|
||||
atomicConstruct.u);
|
||||
}
|
||||
|
||||
static void
|
||||
markDeclareTarget(mlir::Operation *op,
|
||||
Fortran::lower::AbstractConverter &converter,
|
||||
mlir::omp::DeclareTargetCaptureClause captureClause,
|
||||
mlir::omp::DeclareTargetDeviceType deviceType) {
|
||||
// TODO: Add support for program local variables with declare target applied
|
||||
auto declareTargetOp = llvm::dyn_cast<mlir::omp::DeclareTargetInterface>(op);
|
||||
if (!declareTargetOp)
|
||||
fir::emitFatalError(
|
||||
converter.getCurrentLocation(),
|
||||
"Attempt to apply declare target on unsupported operation");
|
||||
|
||||
// The function or global already has a declare target applied to it, very
|
||||
// likely through implicit capture (usage in another declare target
|
||||
// function/subroutine). It should be marked as any if it has been assigned
|
||||
// both host and nohost, else we skip, as there is no change
|
||||
if (declareTargetOp.isDeclareTarget()) {
|
||||
if (declareTargetOp.getDeclareTargetDeviceType() != deviceType)
|
||||
declareTargetOp.setDeclareTarget(mlir::omp::DeclareTargetDeviceType::any,
|
||||
captureClause);
|
||||
return;
|
||||
}
|
||||
|
||||
declareTargetOp.setDeclareTarget(deviceType, captureClause);
|
||||
}
|
||||
|
||||
static void genOMP(Fortran::lower::AbstractConverter &converter,
|
||||
Fortran::lower::SymMap &symTable,
|
||||
Fortran::semantics::SemanticsContext &semaCtx,
|
||||
Fortran::lower::pft::Evaluation &eval,
|
||||
const Fortran::parser::OpenMPDeclareTargetConstruct
|
||||
&declareTargetConstruct) {
|
||||
llvm::SmallVector<DeclareTargetCapturePair, 0> symbolAndClause;
|
||||
llvm::SmallVector<DeclareTargetCapturePair> symbolAndClause;
|
||||
mlir::ModuleOp mod = converter.getFirOpBuilder().getModule();
|
||||
mlir::omp::DeclareTargetDeviceType deviceType = getDeclareTargetInfo(
|
||||
converter, semaCtx, eval, declareTargetConstruct, symbolAndClause);
|
||||
|
||||
for (const DeclareTargetCapturePair &symClause : symbolAndClause) {
|
||||
mlir::Operation *op = mod.lookupSymbol(
|
||||
converter.mangleName(std::get<Fortran::semantics::Symbol>(symClause)));
|
||||
// There's several cases this can currently be triggered and it could be
|
||||
// one of the following:
|
||||
// 1) Invalid argument passed to a declare target that currently isn't
|
||||
// captured by a frontend semantic check
|
||||
// 2) The symbol of a valid argument is not correctly updated by one of
|
||||
// the prior passes, resulting in missing symbol information
|
||||
// 3) It's a variable internal to a module or program, that is legal by
|
||||
// Fortran OpenMP standards, but is currently unhandled as they do not
|
||||
// appear in the symbol table as they are represented as allocas
|
||||
mlir::Operation *op = mod.lookupSymbol(converter.mangleName(
|
||||
std::get<const Fortran::semantics::Symbol &>(symClause)));
|
||||
|
||||
// Some symbols are deferred until later in the module, these are handled
|
||||
// upon finalization of the module for OpenMP inside of Bridge, so we simply
|
||||
// skip for now.
|
||||
if (!op)
|
||||
TODO(converter.getCurrentLocation(),
|
||||
"Missing symbol, possible case of currently unsupported use of "
|
||||
"a program local variable in declare target or erroneous symbol "
|
||||
"information ");
|
||||
|
||||
auto declareTargetOp =
|
||||
llvm::dyn_cast<mlir::omp::DeclareTargetInterface>(op);
|
||||
if (!declareTargetOp)
|
||||
fir::emitFatalError(
|
||||
converter.getCurrentLocation(),
|
||||
"Attempt to apply declare target on unsupported operation");
|
||||
|
||||
// The function or global already has a declare target applied to it, very
|
||||
// likely through implicit capture (usage in another declare target
|
||||
// function/subroutine). It should be marked as any if it has been assigned
|
||||
// both host and nohost, else we skip, as there is no change
|
||||
if (declareTargetOp.isDeclareTarget()) {
|
||||
if (declareTargetOp.getDeclareTargetDeviceType() != deviceType)
|
||||
declareTargetOp.setDeclareTarget(
|
||||
mlir::omp::DeclareTargetDeviceType::any,
|
||||
std::get<mlir::omp::DeclareTargetCaptureClause>(symClause));
|
||||
continue;
|
||||
}
|
||||
|
||||
declareTargetOp.setDeclareTarget(
|
||||
deviceType, std::get<mlir::omp::DeclareTargetCaptureClause>(symClause));
|
||||
markDeclareTarget(
|
||||
op, converter,
|
||||
std::get<mlir::omp::DeclareTargetCaptureClause>(symClause), deviceType);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2515,6 +2540,24 @@ bool Fortran::lower::isOpenMPTargetConstruct(
|
||||
return llvm::omp::allTargetSet.test(dir);
|
||||
}
|
||||
|
||||
void Fortran::lower::gatherOpenMPDeferredDeclareTargets(
|
||||
Fortran::lower::AbstractConverter &converter,
|
||||
Fortran::semantics::SemanticsContext &semaCtx,
|
||||
Fortran::lower::pft::Evaluation &eval,
|
||||
const Fortran::parser::OpenMPDeclarativeConstruct &ompDecl,
|
||||
llvm::SmallVectorImpl<OMPDeferredDeclareTargetInfo>
|
||||
&deferredDeclareTarget) {
|
||||
std::visit(
|
||||
Fortran::common::visitors{
|
||||
[&](const Fortran::parser::OpenMPDeclareTargetConstruct &ompReq) {
|
||||
collectDeferredDeclareTargets(converter, semaCtx, eval, ompReq,
|
||||
deferredDeclareTarget);
|
||||
},
|
||||
[&](const auto &) {},
|
||||
},
|
||||
ompDecl.u);
|
||||
}
|
||||
|
||||
bool Fortran::lower::isOpenMPDeviceDeclareTarget(
|
||||
Fortran::lower::AbstractConverter &converter,
|
||||
Fortran::semantics::SemanticsContext &semaCtx,
|
||||
@ -2533,6 +2576,42 @@ bool Fortran::lower::isOpenMPDeviceDeclareTarget(
|
||||
ompDecl.u);
|
||||
}
|
||||
|
||||
// In certain cases such as subroutine or function interfaces which declare
|
||||
// but do not define or directly call the subroutine or function in the same
|
||||
// module, their lowering is delayed until after the declare target construct
|
||||
// itself is processed, so there symbol is not within the table.
|
||||
//
|
||||
// This function will also return true if we encounter any device declare
|
||||
// target cases, to satisfy checking if we require the requires attributes
|
||||
// on the module.
|
||||
bool Fortran::lower::markOpenMPDeferredDeclareTargetFunctions(
|
||||
mlir::Operation *mod,
|
||||
llvm::SmallVectorImpl<OMPDeferredDeclareTargetInfo> &deferredDeclareTargets,
|
||||
AbstractConverter &converter) {
|
||||
bool deviceCodeFound = false;
|
||||
auto modOp = llvm::cast<mlir::ModuleOp>(mod);
|
||||
for (auto declTar : deferredDeclareTargets) {
|
||||
mlir::Operation *op = modOp.lookupSymbol(converter.mangleName(declTar.sym));
|
||||
|
||||
// Due to interfaces being optionally emitted on usage in a module,
|
||||
// not finding an operation at this point cannot be a hard error, we
|
||||
// simply ignore it for now.
|
||||
// TODO: Add semantic checks for detecting cases where an erronous
|
||||
// (undefined) symbol has been supplied to a declare target clause
|
||||
if (!op)
|
||||
continue;
|
||||
|
||||
auto devType = declTar.declareTargetDeviceType;
|
||||
if (!deviceCodeFound && devType != mlir::omp::DeclareTargetDeviceType::host)
|
||||
deviceCodeFound = true;
|
||||
|
||||
markDeclareTarget(op, converter, declTar.declareTargetCaptureClause,
|
||||
devType);
|
||||
}
|
||||
|
||||
return deviceCodeFound;
|
||||
}
|
||||
|
||||
void Fortran::lower::genOpenMPRequires(
|
||||
mlir::Operation *mod, const Fortran::semantics::Symbol *symbol) {
|
||||
using MlirRequires = mlir::omp::ClauseRequires;
|
||||
|
@ -40,7 +40,7 @@ namespace omp {
|
||||
|
||||
using DeclareTargetCapturePair =
|
||||
std::pair<mlir::omp::DeclareTargetCaptureClause,
|
||||
Fortran::semantics::Symbol>;
|
||||
const Fortran::semantics::Symbol &>;
|
||||
|
||||
mlir::omp::MapInfoOp
|
||||
createMapInfoOp(fir::FirOpBuilder &builder, mlir::Location loc,
|
||||
|
60
flang/test/Lower/OpenMP/declare-target-deferred-marking.f90
Normal file
60
flang/test/Lower/OpenMP/declare-target-deferred-marking.f90
Normal file
@ -0,0 +1,60 @@
|
||||
!RUN: %flang_fc1 -emit-hlfir -fopenmp %s -o - | FileCheck %s --check-prefixes ALL,HOST
|
||||
!RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-is-device %s -o - | FileCheck %s --check-prefixes ALL
|
||||
|
||||
program main
|
||||
use, intrinsic :: iso_c_binding
|
||||
implicit none
|
||||
interface
|
||||
subroutine any_interface() bind(c,name="any_interface")
|
||||
use, intrinsic :: iso_c_binding
|
||||
implicit none
|
||||
!$omp declare target enter(any_interface) device_type(any)
|
||||
end subroutine any_interface
|
||||
|
||||
subroutine host_interface() bind(c,name="host_interface")
|
||||
use, intrinsic :: iso_c_binding
|
||||
implicit none
|
||||
!$omp declare target enter(host_interface) device_type(host)
|
||||
end subroutine host_interface
|
||||
|
||||
subroutine device_interface() bind(c,name="device_interface")
|
||||
use, intrinsic :: iso_c_binding
|
||||
implicit none
|
||||
!$omp declare target enter(device_interface) device_type(nohost)
|
||||
end subroutine device_interface
|
||||
|
||||
subroutine called_from_target_interface(f1, f2) bind(c,name="called_from_target_interface")
|
||||
use, intrinsic :: iso_c_binding
|
||||
implicit none
|
||||
type(c_funptr),value :: f1
|
||||
type(c_funptr),value :: f2
|
||||
end subroutine called_from_target_interface
|
||||
|
||||
subroutine called_from_host_interface(f1) bind(c,name="called_from_host_interface")
|
||||
use, intrinsic :: iso_c_binding
|
||||
implicit none
|
||||
type(c_funptr),value :: f1
|
||||
end subroutine called_from_host_interface
|
||||
|
||||
subroutine unused_unemitted_interface() bind(c,name="unused_unemitted_interface")
|
||||
use, intrinsic :: iso_c_binding
|
||||
implicit none
|
||||
!$omp declare target enter(unused_unemitted_interface) device_type(nohost)
|
||||
end subroutine unused_unemitted_interface
|
||||
|
||||
end interface
|
||||
|
||||
CALL called_from_host_interface(c_funloc(host_interface))
|
||||
!$omp target
|
||||
CALL called_from_target_interface(c_funloc(any_interface), c_funloc(device_interface))
|
||||
!$omp end target
|
||||
end program main
|
||||
|
||||
!HOST-LABEL: func.func {{.*}} @host_interface()
|
||||
!HOST-SAME: {{.*}}, omp.declare_target = #omp.declaretarget<device_type = (host), capture_clause = (enter)>{{.*}}
|
||||
!ALL-LABEL: func.func {{.*}} @called_from_target_interface(!fir.ref<i64>, !fir.ref<i64>)
|
||||
!ALL-SAME: {{.*}}, omp.declare_target = #omp.declaretarget<device_type = (nohost), capture_clause = (to)>{{.*}}
|
||||
!ALL-LABEL: func.func {{.*}} @any_interface()
|
||||
!ALL-SAME: {{.*}}, omp.declare_target = #omp.declaretarget<device_type = (any), capture_clause = (enter)>{{.*}}
|
||||
!ALL-LABEL: func.func {{.*}} @device_interface()
|
||||
!ALL-SAME: {{.*}}, omp.declare_target = #omp.declaretarget<device_type = (nohost), capture_clause = (enter)>{{.*}}
|
Loading…
x
Reference in New Issue
Block a user