2617 lines
102 KiB
TableGen
2617 lines
102 KiB
TableGen
//===-- OpenMPOps.td - OpenMP dialect operation definitions *- tablegen -*-===//
|
|
//
|
|
// 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This file defines the basic operations for the OpenMP dialect.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
|
#ifndef OPENMP_OPS
|
|
#define OPENMP_OPS
|
|
|
|
include "mlir/Dialect/LLVMIR/LLVMOpBase.td"
|
|
include "mlir/Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.td"
|
|
include "mlir/Dialect/OpenACCMPCommon/Interfaces/OpenACCMPOpsInterfaces.td"
|
|
include "mlir/Dialect/OpenMP/OpenMPClauses.td"
|
|
include "mlir/Dialect/OpenMP/OpenMPOpBase.td"
|
|
include "mlir/Interfaces/ControlFlowInterfaces.td"
|
|
include "mlir/Interfaces/SideEffectInterfaces.td"
|
|
include "mlir/IR/EnumAttr.td"
|
|
include "mlir/IR/OpAsmInterface.td"
|
|
include "mlir/IR/OpBase.td"
|
|
include "mlir/IR/SymbolInterfaces.td"
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// 2.19.4 Data-Sharing Attribute Clauses
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
def PrivateClauseOp : OpenMP_Op<"private", [IsolatedFromAbove, RecipeInterface]> {
|
|
let summary = "Provides declaration of [first]private logic.";
|
|
let description = [{
|
|
This operation provides a declaration of how to implement the
|
|
[first]privatization of a variable. The dialect users should provide
|
|
which type should be allocated for this variable. The allocated (usually by
|
|
alloca) variable is passed to the initialization region which does everything
|
|
else (e.g. initialization of Fortran runtime descriptors). Information about
|
|
how to initialize the copy from the original item should be given in the
|
|
copy region, and if needed, how to deallocate memory (allocated by the
|
|
initialization region) in the dealloc region.
|
|
|
|
Examples:
|
|
|
|
* `private(x)` would not need any regions because no initialization is
|
|
required by the standard for i32 variables and this is not firstprivate.
|
|
```mlir
|
|
omp.private {type = private} @x.privatizer : i32
|
|
```
|
|
|
|
* `firstprivate(x)` would be emitted as:
|
|
```mlir
|
|
omp.private {type = firstprivate} @x.privatizer : i32 copy {
|
|
^bb0(%arg0: !fir.ref<i32>, %arg1: !fir.ref<i32>):
|
|
// %arg0 is the original host variable.
|
|
// %arg1 represents the memory allocated for this private variable.
|
|
... copy from host to the privatized clone ....
|
|
omp.yield(%arg1 : !fir.ref<i32>)
|
|
}
|
|
```
|
|
|
|
* `private(x)` for "allocatables" would be emitted as:
|
|
```mlir
|
|
omp.private {type = private} @x.privatizer : !some.type init {
|
|
^bb0(%arg0: !some.pointer<!some.type>, %arg1: !some.pointer<!some.type>):
|
|
// initialize %arg1, using %arg0 as a mold for allocations.
|
|
// For example if %arg0 is a heap allocated array with a runtime determined
|
|
// length and !some.type is a runtime type descriptor, the init region
|
|
// will read the array length from %arg0, and heap allocate an array of the
|
|
// right length and initialize %arg1 to contain the array allocation and
|
|
// length.
|
|
omp.yield(%arg1 : !some.pointer<!some.type>)
|
|
} dealloc {
|
|
^bb0(%arg0: !some.pointer<!some.type>):
|
|
// ... deallocate memory allocated by the init region...
|
|
// In the example above, this will free the heap allocated array data.
|
|
omp.yield
|
|
}
|
|
```
|
|
|
|
There are no restrictions on the body except for:
|
|
- The `dealloc` regions has a single argument.
|
|
- The `init` & `copy` regions have 2 arguments.
|
|
- All three regions are terminated by `omp.yield` ops.
|
|
The above restrictions and other obvious restrictions (e.g. verifying the
|
|
type of yielded values) are verified by the custom op verifier. The actual
|
|
contents of the blocks inside all regions are not verified.
|
|
|
|
Instances of this op would then be used by ops that model directives that
|
|
accept data-sharing attribute clauses.
|
|
|
|
The `sym_name` attribute provides a symbol by which the privatizer op can be
|
|
referenced by other dialect ops.
|
|
|
|
The `type` attribute is the type of the value being privatized. This type
|
|
will be implicitly allocated in MLIR->LLVMIR conversion and passed as the
|
|
second argument to the init region. Therefore the type of arguments to
|
|
the regions should be a type which represents a pointer to `type`.
|
|
|
|
The `data_sharing_type` attribute specifies whether privatizer corresponds
|
|
to a `private` or a `firstprivate` clause.
|
|
}];
|
|
|
|
let arguments = (ins SymbolNameAttr:$sym_name,
|
|
TypeAttrOf<AnyType>:$type,
|
|
DataSharingClauseTypeAttr:$data_sharing_type);
|
|
|
|
let regions = (region AnyRegion:$init_region,
|
|
AnyRegion:$copy_region,
|
|
AnyRegion:$dealloc_region);
|
|
|
|
let assemblyFormat = [{
|
|
$data_sharing_type $sym_name `:` $type
|
|
(`init` $init_region^)?
|
|
(`copy` $copy_region^)?
|
|
(`dealloc` $dealloc_region^)?
|
|
attr-dict
|
|
}];
|
|
|
|
let builders = [
|
|
OpBuilder<(ins CArg<"TypeRange">:$result,
|
|
CArg<"StringAttr">:$sym_name,
|
|
CArg<"TypeAttr">:$type)>
|
|
];
|
|
|
|
let extraClassDeclaration = [{
|
|
BlockArgument getInitMoldArg() {
|
|
auto ®ion = getInitRegion();
|
|
return region.empty() ? nullptr : region.getArgument(0);
|
|
}
|
|
BlockArgument getInitPrivateArg() {
|
|
auto ®ion = getInitRegion();
|
|
return region.empty() ? nullptr : region.getArgument(1);
|
|
}
|
|
BlockArgument getCopyMoldArg() {
|
|
auto ®ion = getCopyRegion();
|
|
return region.empty() ? nullptr : region.getArgument(0);
|
|
}
|
|
BlockArgument getCopyPrivateArg() {
|
|
auto ®ion = getCopyRegion();
|
|
return region.empty() ? nullptr : region.getArgument(1);
|
|
}
|
|
BlockArgument getDeallocMoldArg() {
|
|
auto ®ion = getDeallocRegion();
|
|
return region.empty() ? nullptr : region.getArgument(0);
|
|
}
|
|
|
|
/// Returns true if the init region might read from the mold argument
|
|
bool initReadsFromMold() {
|
|
BlockArgument moldArg = getInitMoldArg();
|
|
return moldArg && !moldArg.use_empty();
|
|
}
|
|
|
|
/// Returns true if any region of this privatizer might read from the mold
|
|
/// argument
|
|
bool readsFromMold() {
|
|
return initReadsFromMold() || !getCopyRegion().empty();
|
|
}
|
|
|
|
/// needsMap returns true if the value being privatized should additionally
|
|
/// be mapped to the target region using a MapInfoOp. This is most common
|
|
/// when an allocatable is privatized. In such cases, the descriptor is used
|
|
/// in privatization and needs to be mapped on to the device. The use of
|
|
/// firstprivate also creates the need to map the host variable to the device.
|
|
bool needsMap() {
|
|
return readsFromMold();
|
|
}
|
|
|
|
/// Get the type for arguments to nested regions. This should
|
|
/// generally be either the same as getType() or some pointer
|
|
/// type (pointing to the type allocated by this op).
|
|
/// This method will return Type{nullptr} if there are no nested
|
|
/// regions.
|
|
Type getArgType() {
|
|
for (Region *region : getRegions())
|
|
for (Type ty : region->getArgumentTypes())
|
|
return ty;
|
|
return nullptr;
|
|
}
|
|
}];
|
|
|
|
let hasRegionVerifier = 1;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// 2.6 parallel Construct
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
def ParallelOp : OpenMP_Op<"parallel", traits = [
|
|
AttrSizedOperandSegments, AutomaticAllocationScope,
|
|
DeclareOpInterfaceMethods<ComposableOpInterface>,
|
|
DeclareOpInterfaceMethods<OutlineableOpenMPOpInterface>,
|
|
RecursiveMemoryEffects
|
|
], clauses = [
|
|
OpenMP_AllocateClause, OpenMP_IfClause, OpenMP_NumThreadsClause,
|
|
OpenMP_PrivateClause, OpenMP_ProcBindClause, OpenMP_ReductionClause
|
|
], singleRegion = true> {
|
|
let summary = "parallel construct";
|
|
let description = [{
|
|
The parallel construct includes a region of code which is to be executed
|
|
by a team of threads.
|
|
|
|
The optional `if_expr` parameter specifies a boolean result of a conditional
|
|
check. If this value is 1 or is not provided then the parallel region runs
|
|
as normal, if it is 0 then the parallel region is executed with one thread.
|
|
}] # clausesDescription;
|
|
|
|
let builders = [
|
|
OpBuilder<(ins CArg<"ArrayRef<NamedAttribute>", "{}">:$attributes)>,
|
|
OpBuilder<(ins CArg<"const ParallelOperands &">:$clauses)>
|
|
];
|
|
|
|
let assemblyFormat = clausesAssemblyFormat # [{
|
|
custom<PrivateReductionRegion>($region, $private_vars, type($private_vars),
|
|
$private_syms, $private_needs_barrier, $reduction_mod, $reduction_vars,
|
|
type($reduction_vars), $reduction_byref, $reduction_syms) attr-dict
|
|
}];
|
|
|
|
let hasVerifier = 1;
|
|
let hasRegionVerifier = 1;
|
|
}
|
|
|
|
def TerminatorOp : OpenMP_Op<"terminator", [Terminator, Pure]> {
|
|
let summary = "terminator for OpenMP regions";
|
|
let description = [{
|
|
A terminator operation for regions that appear in the body of OpenMP
|
|
operation. These regions are not expected to return any value so the
|
|
terminator takes no operands. The terminator op returns control to the
|
|
enclosing op.
|
|
}];
|
|
|
|
let assemblyFormat = "attr-dict";
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// 2.7 teams Construct
|
|
//===----------------------------------------------------------------------===//
|
|
def TeamsOp : OpenMP_Op<"teams", traits = [
|
|
AttrSizedOperandSegments, RecursiveMemoryEffects, OutlineableOpenMPOpInterface
|
|
], clauses = [
|
|
OpenMP_AllocateClause, OpenMP_DynGroupprivateClause, OpenMP_IfClause,
|
|
OpenMP_NumTeamsClause, OpenMP_PrivateClause, OpenMP_ReductionClause,
|
|
OpenMP_ThreadLimitClause
|
|
], singleRegion = true> {
|
|
let summary = "teams construct";
|
|
let description = [{
|
|
The teams construct defines a region of code that triggers the creation of a
|
|
league of teams. Once created, the number of teams remains constant for the
|
|
duration of its code region.
|
|
|
|
If the `if_expr` is present and it evaluates to `false`, the number of teams
|
|
created is one.
|
|
}] # clausesDescription;
|
|
|
|
let builders = [
|
|
OpBuilder<(ins CArg<"const TeamsOperands &">:$clauses)>
|
|
];
|
|
|
|
let assemblyFormat = clausesAssemblyFormat # [{
|
|
custom<PrivateReductionRegion>($region, $private_vars, type($private_vars),
|
|
$private_syms, $private_needs_barrier, $reduction_mod, $reduction_vars,
|
|
type($reduction_vars), $reduction_byref, $reduction_syms) attr-dict
|
|
}];
|
|
|
|
let hasVerifier = 1;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// 2.8.1 Sections Construct
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
def SectionOp : OpenMP_Op<"section", traits = [
|
|
BlockArgOpenMPOpInterface, HasParent<"SectionsOp">
|
|
], singleRegion = true> {
|
|
let summary = "section directive";
|
|
let description = [{
|
|
A section operation encloses a region which represents one section in a
|
|
sections construct. A section op should always be surrounded by an
|
|
`omp.sections` operation. The section operation may have block args
|
|
which corespond to the block arguments of the surrounding `omp.sections`
|
|
operation. This is done to reflect situations where these block arguments
|
|
represent variables private to each section.
|
|
}];
|
|
let extraClassDeclaration = [{
|
|
// Override BlockArgOpenMPOpInterface methods based on the parent
|
|
// omp.sections operation. Only forward-declare here because SectionsOp is
|
|
// not completely defined at this point.
|
|
OperandRange getPrivateVars();
|
|
OperandRange getReductionVars();
|
|
}] # clausesExtraClassDeclaration;
|
|
let assemblyFormat = "$region attr-dict";
|
|
}
|
|
|
|
def SectionsOp : OpenMP_Op<"sections", traits = [
|
|
AttrSizedOperandSegments
|
|
], clauses = [
|
|
OpenMP_AllocateClause, OpenMP_NowaitClause, OpenMP_PrivateClause,
|
|
OpenMP_ReductionClause
|
|
], singleRegion = true> {
|
|
let summary = "sections construct";
|
|
let description = [{
|
|
The sections construct is a non-iterative worksharing construct that
|
|
contains `omp.section` operations. The `omp.section` operations are to be
|
|
distributed among and executed by the threads in a team. Each `omp.section`
|
|
is executed once by one of the threads in the team in the context of its
|
|
implicit task.
|
|
Block arguments for reduction variables should be mirrored in enclosed
|
|
`omp.section` operations.
|
|
}] # clausesDescription;
|
|
|
|
// Override region definition.
|
|
let regions = (region SizedRegion<1>:$region);
|
|
|
|
let builders = [
|
|
OpBuilder<(ins CArg<"const SectionsOperands &">:$clauses)>
|
|
];
|
|
|
|
let assemblyFormat = clausesAssemblyFormat # [{
|
|
custom<PrivateReductionRegion>($region, $private_vars, type($private_vars),
|
|
$private_syms, $private_needs_barrier, $reduction_mod, $reduction_vars,
|
|
type($reduction_vars), $reduction_byref, $reduction_syms) attr-dict
|
|
}];
|
|
|
|
let hasVerifier = 1;
|
|
let hasRegionVerifier = 1;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// 2.8.2 Single Construct
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
def SingleOp : OpenMP_Op<"single", traits = [
|
|
AttrSizedOperandSegments
|
|
], clauses = [
|
|
OpenMP_AllocateClause, OpenMP_CopyprivateClause, OpenMP_NowaitClause,
|
|
OpenMP_PrivateClause
|
|
], singleRegion = true> {
|
|
let summary = "single directive";
|
|
let description = [{
|
|
The single construct specifies that the associated structured block is
|
|
executed by only one of the threads in the team (not necessarily the
|
|
master thread), in the context of its implicit task. The other threads
|
|
in the team, which do not execute the block, wait at an implicit barrier
|
|
at the end of the single construct.
|
|
}] # clausesDescription;
|
|
|
|
let builders = [
|
|
OpBuilder<(ins CArg<"const SingleOperands &">:$clauses)>
|
|
];
|
|
|
|
let assemblyFormat = clausesAssemblyFormat # [{
|
|
custom<PrivateRegion>($region, $private_vars, type($private_vars),
|
|
$private_syms, $private_needs_barrier) attr-dict
|
|
}];
|
|
|
|
let hasVerifier = 1;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Scope Construct (OpenMP 5.1)
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
def ScopeOp
|
|
: OpenMP_Op<"scope", traits = [AttrSizedOperandSegments],
|
|
clauses = [OpenMP_AllocateClause, OpenMP_NowaitClause,
|
|
OpenMP_PrivateClause, OpenMP_ReductionClause],
|
|
singleRegion = true> {
|
|
let summary = "scope directive";
|
|
let description = [{
|
|
The scope construct defines a structured block that is executed by all
|
|
threads in the current team, providing a lexical scope for
|
|
privatization. An implicit barrier occurs at the end of the region
|
|
unless the `nowait` clause is present.
|
|
|
|
Introduced in OpenMP 5.1.
|
|
}]#clausesDescription;
|
|
|
|
let builders = [OpBuilder<(ins CArg<"const ScopeOperands &">:$clauses)>];
|
|
|
|
let assemblyFormat = clausesAssemblyFormat#[{
|
|
custom<PrivateReductionRegion>($region, $private_vars, type($private_vars),
|
|
$private_syms, $private_needs_barrier, $reduction_mod, $reduction_vars,
|
|
type($reduction_vars), $reduction_byref, $reduction_syms) attr-dict
|
|
}];
|
|
|
|
let hasVerifier = 1;
|
|
}
|
|
|
|
//===---------------------------------------------------------------------===//
|
|
// OpenMP Canonical Loop Info Creation
|
|
//===---------------------------------------------------------------------===//
|
|
|
|
def NewCliOp : OpenMP_Op<"new_cli",
|
|
[DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>]> {
|
|
let summary = "Create a new Canonical Loop Info value.";
|
|
let description = [{
|
|
Create a new CLI that can be passed as an argument to a CanonicalLoopOp
|
|
and to loop transformation operations to handle dependencies between
|
|
loop transformation operations.
|
|
}];
|
|
|
|
let arguments = (ins );
|
|
let results = (outs CanonicalLoopInfoType:$result);
|
|
let assemblyFormat = [{
|
|
attr-dict
|
|
}];
|
|
|
|
let builders = [
|
|
OpBuilder<(ins )>,
|
|
];
|
|
|
|
let hasVerifier = 1;
|
|
}
|
|
|
|
//===---------------------------------------------------------------------===//
|
|
// OpenMP Canonical Loop Operation
|
|
//===---------------------------------------------------------------------===//
|
|
def CanonicalLoopOp : OpenMPTransform_Op<"canonical_loop",
|
|
[DeclareOpInterfaceMethods<OpAsmOpInterface, [ "getAsmBlockNames", "getAsmBlockArgumentNames"]>]> {
|
|
let summary = "OpenMP Canonical Loop Operation";
|
|
let description = [{
|
|
All loops that conform to OpenMP's definition of a canonical loop can be
|
|
simplified to a CanonicalLoopOp. In particular, there are no loop-carried
|
|
variables and the number of iterations it will execute is known before the
|
|
operation. This allows e.g. to determine the number of threads and chunks
|
|
the iterations space is split into before executing any iteration. More
|
|
restrictions may apply in cases such as (collapsed) loop nests, doacross
|
|
loops, etc.
|
|
|
|
In contrast to other loop operations such as `scf.for`, the number of
|
|
iterations is determined by only a single variable, the trip-count. The
|
|
induction variable value is the logical iteration number of that iteration,
|
|
which OpenMP defines to be between 0 and the trip-count (exclusive).
|
|
Loop representation having lower-bound, upper-bound, and step-size operands,
|
|
require passes to do more work than necessary, including handling special
|
|
cases such as upper-bound smaller than lower-bound, upper-bound equal to
|
|
the integer type's maximal value, negative step size, etc. This complexity
|
|
is better only handled once by the front-end and can apply its semantics
|
|
for such cases while still being able to represent any kind of loop, which
|
|
kind of the point of a mid-end intermediate representation. User-defined
|
|
types such as random-access iterators in C++ could not directly be
|
|
represented anyway.
|
|
|
|
The induction variable is always of the same type as the tripcount argument.
|
|
Since it can never be negative, tripcount is always interpreted as an
|
|
unsigned integer. It is the caller's responsibility to ensure the tripcount
|
|
is not negative when its interpretation is signed, i.e.
|
|
`%tripcount = max(0,%tripcount)`.
|
|
|
|
An optional argument to a omp.canonical_loop that can be passed in
|
|
is a CanonicalLoopInfo value that can be used to refer to the canonical
|
|
loop to apply transformations -- such as tiling, unrolling, or
|
|
work-sharing -- to the loop, similar to the transform dialect but
|
|
with OpenMP-specific semantics. Because it is optional, it has to be the
|
|
last of the operands, but appears first in the pretty format printing.
|
|
|
|
The pretty assembly format is inspired by python syntax, where `range(n)`
|
|
returns an iterator that runs from $0$ to $n-1$. The pretty assembly syntax
|
|
is one of:
|
|
|
|
omp.canonical_loop(%cli) %iv : !type in range(%tripcount)
|
|
omp.canonical_loop %iv : !type in range(%tripcount)
|
|
|
|
A CanonicalLoopOp is lowered to LLVM-IR using
|
|
`OpenMPIRBuilder::createCanonicalLoop`.
|
|
|
|
#### Examples
|
|
|
|
Translation from lower-bound, upper-bound, step-size to trip-count.
|
|
```c
|
|
for (int i = 3; i < 42; i+=2) {
|
|
B[i] = A[i];
|
|
}
|
|
```
|
|
|
|
```mlir
|
|
%lb = arith.constant 3 : i32
|
|
%ub = arith.constant 42 : i32
|
|
%step = arith.constant 2 : i32
|
|
%range = arith.sub %ub, %lb : i32
|
|
%tripcount = arith.div %range, %step : i32
|
|
omp.canonical_loop %iv : i32 in range(%tripcount) {
|
|
%offset = arith.mul %iv, %step : i32
|
|
%i = arith.add %offset, %lb : i32
|
|
%a = load %arrA[%i] : memref<?xf32>
|
|
store %a, %arrB[%i] : memref<?xf32>
|
|
}
|
|
```
|
|
|
|
Nested canonical loop with transformation of the inner loop.
|
|
```mlir
|
|
%outer = omp.new_cli : !omp.cli
|
|
%inner = omp.new_cli : !omp.cli
|
|
omp.canonical_loop(%outer) %iv1 : i32 in range(%tc1) {
|
|
omp.canonical_loop(%inner) %iv2 : i32 in range(%tc2) {
|
|
%a = load %arrA[%iv1, %iv2] : memref<?x?xf32>
|
|
store %a, %arrB[%iv1, %iv2] : memref<?x?xf32>
|
|
}
|
|
}
|
|
omp.unroll_full(%inner)
|
|
```
|
|
}];
|
|
|
|
|
|
let arguments = (ins IntLikeType:$tripCount,
|
|
Optional<CanonicalLoopInfoType>:$cli);
|
|
let regions = (region AnyRegion:$region);
|
|
|
|
let extraClassDeclaration = [{
|
|
::mlir::Value getInductionVar();
|
|
}];
|
|
|
|
let builders = [
|
|
OpBuilder<(ins "::mlir::Value":$tripCount)>,
|
|
OpBuilder<(ins "::mlir::Value":$tripCount, "::mlir::Value":$cli)>,
|
|
];
|
|
|
|
let hasCustomAssemblyFormat = 1;
|
|
let hasVerifier = 1;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// OpenMP unroll_heuristic operation
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
def UnrollHeuristicOp : OpenMPTransform_Op<"unroll_heuristic", []> {
|
|
let summary = "OpenMP heuristic unroll operation";
|
|
let description = [{
|
|
Represents a `#pragma omp unroll` construct introduced in OpenMP 5.1.
|
|
|
|
The operation has one applyee and no generatees. The applyee is unrolled
|
|
according to implementation-defined heuristics. Implementations may choose
|
|
to not unroll the loop, partially unroll by a chosen factor, or fully
|
|
unroll it. Even if the implementation chooses to partially unroll the
|
|
applyee, the resulting unrolled loop is not accessible as a generatee. Use
|
|
omp.unroll_partial if a generatee is required.
|
|
|
|
The lowering is implemented using `OpenMPIRBuilder::unrollLoopHeuristic`,
|
|
which just attaches `llvm.loop.unroll.enable` metadata to the loop so the
|
|
unrolling is carried-out by LLVM's LoopUnroll pass. That is, unrolling only
|
|
actually performed in optimized builds.
|
|
|
|
Assembly formats:
|
|
omp.unroll_heuristic(%cli)
|
|
omp.unroll_heuristic(%cli) -> ()
|
|
}];
|
|
|
|
let arguments = (ins CanonicalLoopInfoType:$applyee);
|
|
|
|
let builders = [
|
|
OpBuilder<(ins "::mlir::Value":$cli)>,
|
|
];
|
|
|
|
let hasCustomAssemblyFormat = 1;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// OpenMP tile operation
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
def TileOp : OpenMPTransformBase_Op<"tile",
|
|
clauses = [OpenMP_SizesClause]> {
|
|
let summary = "OpenMP tile operation";
|
|
let description = [{
|
|
Represents the OpenMP tile directive introduced in OpenMP 5.1.
|
|
|
|
The construct partitions the logical iteration space of the affected loops
|
|
into equally-sized tiles, then creates two sets of nested loops. The outer
|
|
loops, called the grid loops, iterate over all tiles. The inner loops,
|
|
called the intratile loops, iterate over the logical iterations of a tile.
|
|
The sizes clause determines the size of a tile.
|
|
|
|
Currently, the affected loops must be rectangular (the tripcount of the
|
|
inner loop must not depend on any iv of an surrounding affected loop) and
|
|
perfectly nested (except for the innermost affected loop, no operations
|
|
other than the nested loop and the terminator in the loop body).
|
|
}] # clausesDescription;
|
|
|
|
let hasVerifier = 1;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// OpenMP fuse operation
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
def FuseOp
|
|
: OpenMPTransformBase_Op<"fuse", clauses = [OpenMP_LooprangeClause]> {
|
|
let summary = "OpenMP fuse operation";
|
|
let description = [{
|
|
Represents the OpenMP fuse directive introduced in OpenMP 6.0.
|
|
|
|
The construct takes a loop sequence and merges the loops specifed by the
|
|
`looprange` clause and generates a loop sequence with the loops before the
|
|
`first` attribute untouched, the generated fused loop, and the loops after
|
|
the the `first` + `count` attributes untouched mantaining the orignal
|
|
order. If the `looprange` clause is not present all the loops in the
|
|
sequence are fused generating a single loop.
|
|
Each logical iteration of the fused loop executes a logical iteration of
|
|
each affected loop. The fused loop has the number of logical iterations
|
|
equal to the affected loop with most logical iterations.
|
|
|
|
The `first` and `count` attributes of the `looprange` clause are constant
|
|
and known beforehand if present.
|
|
}]#clausesDescription;
|
|
|
|
let hasVerifier = 1;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// 2.8.3 Workshare Construct
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
def WorkshareOp : OpenMP_Op<"workshare", traits = [
|
|
RecursiveMemoryEffects,
|
|
], clauses = [
|
|
OpenMP_NowaitClause,
|
|
], singleRegion = true> {
|
|
let summary = "workshare directive";
|
|
let description = [{
|
|
The workshare construct divides the execution of the enclosed structured
|
|
block into separate units of work, and causes the threads of the team to
|
|
share the work such that each unit is executed only once by one thread, in
|
|
the context of its implicit task
|
|
|
|
This operation is used for the intermediate representation of the workshare
|
|
block before the work gets divided between the threads. See the flang
|
|
LowerWorkshare pass for details.
|
|
}] # clausesDescription;
|
|
|
|
let builders = [
|
|
OpBuilder<(ins CArg<"const WorkshareOperands &">:$clauses)>
|
|
];
|
|
}
|
|
|
|
def WorkshareLoopWrapperOp : OpenMP_Op<"workshare.loop_wrapper", traits = [
|
|
DeclareOpInterfaceMethods<LoopWrapperInterface>, HasAncestor<"WorkshareOp">,
|
|
NoTerminator, RecursiveMemoryEffects, SingleBlock
|
|
], singleRegion = true> {
|
|
let summary = "contains loop nests to be parallelized by workshare";
|
|
let description = [{
|
|
This operation wraps a loop nest that is marked for dividing into units of
|
|
work by an encompassing omp.workshare operation.
|
|
}];
|
|
|
|
let builders = [
|
|
OpBuilder<(ins), [{ build($_builder, $_state, {}); }]>
|
|
];
|
|
let assemblyFormat = "$region attr-dict";
|
|
let hasRegionVerifier = 1;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Loop Nest
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
def LoopNestOp : OpenMP_Op<"loop_nest", traits = [
|
|
RecursiveMemoryEffects, SameVariadicOperandSize
|
|
], clauses = [
|
|
OpenMP_CollapseClause,
|
|
OpenMP_LoopRelatedClause,
|
|
OpenMP_TileSizesClause
|
|
], singleRegion = true> {
|
|
let summary = "rectangular loop nest";
|
|
let description = [{
|
|
This operation represents a rectangular loop nest which may be collapsed
|
|
and/or tiled. For each rectangular loop of the nest represented by an
|
|
instance of this operation, lower and upper bounds, as well as a step
|
|
variable, must be defined. The collapse clause specifies how many loops
|
|
that should be collapsed (1 if no collapse is done) after any tiling is
|
|
performed. The tiling sizes is represented by the tile sizes clause.
|
|
|
|
The lower and upper bounds specify a half-open range: the range includes the
|
|
lower bound but does not include the upper bound. If the `loop_inclusive`
|
|
attribute is specified then the upper bound is also included.
|
|
|
|
The body region can contain any number of blocks. The region is terminated
|
|
by an `omp.yield` instruction without operands. The induction variables,
|
|
represented as entry block arguments to the loop nest operation's single
|
|
region, match the types of the `loop_lower_bounds`, `loop_upper_bounds` and
|
|
`loop_steps` arguments.
|
|
|
|
```mlir
|
|
omp.loop_nest (%i1, %i2) : i32 = (%c0, %c0) to (%c10, %c10) step (%c1, %c1) collapse(2) tiles(5,5) {
|
|
%a = load %arrA[%i1, %i2] : memref<?x?xf32>
|
|
%b = load %arrB[%i1, %i2] : memref<?x?xf32>
|
|
%sum = arith.addf %a, %b : f32
|
|
store %sum, %arrC[%i1, %i2] : memref<?x?xf32>
|
|
omp.yield
|
|
}
|
|
```
|
|
|
|
This is a temporary simplified definition of a loop based on existing OpenMP
|
|
loop operations intended to serve as a stopgap solution until the long-term
|
|
representation of canonical loops is defined. Specifically, this operation
|
|
is intended to serve as a unique source for loop information during the
|
|
transition to making `omp.distribute`, `omp.simd`, `omp.taskloop.wrapper` and
|
|
`omp.wsloop` wrapper operations. It is not intended to help with the
|
|
addition of support for loop transformations, non-rectangular loops and
|
|
non-perfectly nested loops.
|
|
}];
|
|
|
|
let builders = [
|
|
OpBuilder<(ins CArg<"const LoopNestOperands &">:$clauses)>
|
|
];
|
|
|
|
let extraClassDeclaration = [{
|
|
/// Returns the induction variables of the loop nest.
|
|
ArrayRef<BlockArgument> getIVs() { return getRegion().getArguments(); }
|
|
|
|
/// Fills a list of wrapper operations around this loop nest. Wrappers
|
|
/// in the resulting vector will be sorted from innermost to outermost.
|
|
void gatherWrappers(SmallVectorImpl<LoopWrapperInterface> &wrappers);
|
|
}] # clausesExtraClassDeclaration;
|
|
|
|
// Disable inherited clause-based declarative assembly format and instead
|
|
// enable using the custom parser-printer implemented in C++.
|
|
let assemblyFormat = ?;
|
|
let hasCustomAssemblyFormat = 1;
|
|
let hasVerifier = 1;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// 2.9.2 Workshare Loop Construct
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
def LoopOp : OpenMP_Op<"loop", traits = [
|
|
AttrSizedOperandSegments, DeclareOpInterfaceMethods<LoopWrapperInterface>,
|
|
NoTerminator, SingleBlock
|
|
], clauses = [
|
|
OpenMP_BindClause, OpenMP_PrivateClause, OpenMP_OrderClause,
|
|
OpenMP_ReductionClause
|
|
], singleRegion = true> {
|
|
let summary = "loop construct";
|
|
let description = [{
|
|
A loop construct specifies that the logical iterations of the associated loops
|
|
may execute concurrently and permits the encountering threads to execute the
|
|
loop accordingly. A loop construct can have 3 different types of binding:
|
|
1. teams: in which case the binding region is the innermost enclosing `teams`
|
|
region.
|
|
2. parallel: in which case the binding region is the innermost enclosing `parallel`
|
|
region.
|
|
3. thread: in which case the binding region is not defined.
|
|
|
|
The body region can only contain a single block which must contain a single
|
|
operation, this operation must be an `omp.loop_nest`.
|
|
|
|
```
|
|
omp.loop <clauses> {
|
|
omp.loop_nest (%i1, %i2) : index = (%c0, %c0) to (%c10, %c10) step (%c1, %c1) {
|
|
%a = load %arrA[%i1, %i2] : memref<?x?xf32>
|
|
%b = load %arrB[%i1, %i2] : memref<?x?xf32>
|
|
%sum = arith.addf %a, %b : f32
|
|
store %sum, %arrC[%i1, %i2] : memref<?x?xf32>
|
|
omp.yield
|
|
}
|
|
}
|
|
```
|
|
}] # clausesDescription;
|
|
|
|
let assemblyFormat = clausesAssemblyFormat # [{
|
|
custom<PrivateReductionRegion>($region, $private_vars, type($private_vars),
|
|
$private_syms, $private_needs_barrier, $reduction_mod, $reduction_vars,
|
|
type($reduction_vars), $reduction_byref, $reduction_syms) attr-dict
|
|
}];
|
|
|
|
let builders = [
|
|
OpBuilder<(ins CArg<"const LoopOperands &">:$clauses)>
|
|
];
|
|
|
|
let hasVerifier = 1;
|
|
let hasRegionVerifier = 1;
|
|
}
|
|
|
|
def WsloopOp : OpenMP_Op<"wsloop", traits = [
|
|
AttrSizedOperandSegments, DeclareOpInterfaceMethods<ComposableOpInterface>,
|
|
DeclareOpInterfaceMethods<LoopWrapperInterface>, NoTerminator,
|
|
RecursiveMemoryEffects, SingleBlock
|
|
], clauses = [
|
|
OpenMP_AllocateClause, OpenMP_LinearClause, OpenMP_NowaitClause,
|
|
OpenMP_OrderClause, OpenMP_OrderedClause, OpenMP_PrivateClause,
|
|
OpenMP_ReductionClause, OpenMP_ScheduleClause
|
|
], singleRegion = true> {
|
|
let summary = "worksharing-loop construct";
|
|
let description = [{
|
|
The worksharing-loop construct specifies that the iterations of the loop(s)
|
|
will be executed in parallel by threads in the current context. These
|
|
iterations are spread across threads that already exist in the enclosing
|
|
parallel region.
|
|
|
|
The body region can only contain a single block which must contain a single
|
|
operation. This operation must be another compatible loop wrapper or an
|
|
`omp.loop_nest`.
|
|
|
|
```
|
|
omp.wsloop <clauses> {
|
|
omp.loop_nest (%i1, %i2) : index = (%c0, %c0) to (%c10, %c10) step (%c1, %c1) {
|
|
%a = load %arrA[%i1, %i2] : memref<?x?xf32>
|
|
%b = load %arrB[%i1, %i2] : memref<?x?xf32>
|
|
%sum = arith.addf %a, %b : f32
|
|
store %sum, %arrC[%i1, %i2] : memref<?x?xf32>
|
|
omp.yield
|
|
}
|
|
}
|
|
```
|
|
}] # clausesDescription;
|
|
|
|
let builders = [
|
|
OpBuilder<(ins CArg<"ArrayRef<NamedAttribute>", "{}">:$attributes)>,
|
|
OpBuilder<(ins CArg<"const WsloopOperands &">:$clauses)>
|
|
];
|
|
|
|
let assemblyFormat = clausesAssemblyFormat # [{
|
|
custom<PrivateReductionRegion>($region, $private_vars, type($private_vars),
|
|
$private_syms, $private_needs_barrier, $reduction_mod, $reduction_vars,
|
|
type($reduction_vars), $reduction_byref, $reduction_syms) attr-dict
|
|
}];
|
|
|
|
let hasVerifier = 1;
|
|
let hasRegionVerifier = 1;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Simd construct [2.9.3.1]
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
def SimdOp : OpenMP_Op<"simd", traits = [
|
|
AttrSizedOperandSegments, DeclareOpInterfaceMethods<ComposableOpInterface>,
|
|
DeclareOpInterfaceMethods<LoopWrapperInterface>, NoTerminator,
|
|
RecursiveMemoryEffects, SingleBlock
|
|
], clauses = [
|
|
OpenMP_AlignedClause, OpenMP_IfClause, OpenMP_LinearClause,
|
|
OpenMP_NontemporalClause, OpenMP_OrderClause, OpenMP_PrivateClause,
|
|
OpenMP_ReductionClause, OpenMP_SafelenClause, OpenMP_SimdlenClause
|
|
], singleRegion = true> {
|
|
let summary = "simd construct";
|
|
let description = [{
|
|
The simd construct can be applied to a loop to indicate that the loop can be
|
|
transformed into a SIMD loop (that is, multiple iterations of the loop can
|
|
be executed concurrently using SIMD instructions).
|
|
|
|
The body region can only contain a single block which must contain a single
|
|
operation. This operation must be another compatible loop wrapper or an
|
|
`omp.loop_nest`.
|
|
|
|
```
|
|
omp.simd <clauses> {
|
|
omp.loop_nest (%i1, %i2) : index = (%c0, %c0) to (%c10, %c10) step (%c1, %c1) {
|
|
%a = load %arrA[%i1, %i2] : memref<?x?xf32>
|
|
%b = load %arrB[%i1, %i2] : memref<?x?xf32>
|
|
%sum = arith.addf %a, %b : f32
|
|
store %sum, %arrC[%i1, %i2] : memref<?x?xf32>
|
|
omp.yield
|
|
}
|
|
}
|
|
```
|
|
|
|
When an if clause is present and evaluates to false, the preferred number of
|
|
iterations to be executed concurrently is one, regardless of whether
|
|
a simdlen clause is specified.
|
|
}] # clausesDescription;
|
|
|
|
let builders = [
|
|
OpBuilder<(ins CArg<"const SimdOperands &">:$clauses)>
|
|
];
|
|
|
|
let assemblyFormat = clausesAssemblyFormat # [{
|
|
custom<PrivateReductionRegion>($region, $private_vars, type($private_vars),
|
|
$private_syms, $private_needs_barrier, $reduction_mod, $reduction_vars,
|
|
type($reduction_vars), $reduction_byref, $reduction_syms) attr-dict
|
|
}];
|
|
|
|
let hasVerifier = 1;
|
|
let hasRegionVerifier = 1;
|
|
}
|
|
|
|
|
|
def YieldOp
|
|
: OpenMP_Op<"yield", [Pure, ReturnLike, Terminator,
|
|
ParentOneOf<["AtomicUpdateOp", "AtomicCompareOp",
|
|
"DeclareReductionOp", "LoopNestOp",
|
|
"PrivateClauseOp", "IteratorOp"]>]> {
|
|
let summary = "loop yield and termination operation";
|
|
let description = [{
|
|
"omp.yield" yields SSA values from the OpenMP dialect op region and
|
|
terminates the region. The semantics of how the values are yielded is
|
|
defined by the parent operation.
|
|
}];
|
|
|
|
let arguments = (ins Variadic<AnyType>:$results);
|
|
|
|
let builders = [
|
|
OpBuilder<(ins), [{ build($_builder, $_state, {}); }]>
|
|
];
|
|
|
|
let assemblyFormat = "( `(` $results^ `:` type($results) `)` )? attr-dict";
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Distribute construct [2.9.4.1]
|
|
//===----------------------------------------------------------------------===//
|
|
def DistributeOp : OpenMP_Op<"distribute", traits = [
|
|
AttrSizedOperandSegments, DeclareOpInterfaceMethods<ComposableOpInterface>,
|
|
DeclareOpInterfaceMethods<LoopWrapperInterface>, NoTerminator,
|
|
RecursiveMemoryEffects, SingleBlock
|
|
], clauses = [
|
|
OpenMP_AllocateClause, OpenMP_DistScheduleClause, OpenMP_OrderClause,
|
|
OpenMP_PrivateClause
|
|
], singleRegion = true> {
|
|
let summary = "distribute construct";
|
|
let description = [{
|
|
The distribute construct specifies that the iterations of one or more loops
|
|
(optionally specified using collapse clause) will be executed by the
|
|
initial teams in the context of their implicit tasks. The loops that the
|
|
distribute op is associated with starts with the outermost loop enclosed by
|
|
the distribute op region and going down the loop nest toward the innermost
|
|
loop. The iterations are distributed across the initial threads of all
|
|
initial teams that execute the teams region to which the distribute region
|
|
binds.
|
|
|
|
The distribute loop construct specifies that the iterations of the loop(s)
|
|
will be executed in parallel by threads in the current context. These
|
|
iterations are spread across threads that already exist in the enclosing
|
|
region.
|
|
|
|
The body region can only contain a single block which must contain a single
|
|
operation. This operation must be another compatible loop wrapper or an
|
|
`omp.loop_nest`.
|
|
|
|
```mlir
|
|
omp.distribute <clauses> {
|
|
omp.loop_nest (%i1, %i2) : index = (%c0, %c0) to (%c10, %c10) step (%c1, %c1) {
|
|
%a = load %arrA[%i1, %i2] : memref<?x?xf32>
|
|
%b = load %arrB[%i1, %i2] : memref<?x?xf32>
|
|
%sum = arith.addf %a, %b : f32
|
|
store %sum, %arrC[%i1, %i2] : memref<?x?xf32>
|
|
omp.yield
|
|
}
|
|
}
|
|
```
|
|
}] # clausesDescription;
|
|
|
|
let builders = [
|
|
OpBuilder<(ins CArg<"const DistributeOperands &">:$clauses)>
|
|
];
|
|
|
|
let assemblyFormat = clausesAssemblyFormat # [{
|
|
custom<PrivateRegion>($region, $private_vars, type($private_vars),
|
|
$private_syms, $private_needs_barrier) attr-dict
|
|
}];
|
|
|
|
let hasVerifier = 1;
|
|
let hasRegionVerifier = 1;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// 2.10.1 task Construct
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
def TaskOp
|
|
: OpenMP_Op<"task",
|
|
traits = [AttrSizedOperandSegments, AutomaticAllocationScope,
|
|
OutlineableOpenMPOpInterface],
|
|
clauses = [
|
|
// TODO: Complete clause list (detach).
|
|
OpenMP_AffinityClause, OpenMP_AllocateClause,
|
|
OpenMP_DependClause, OpenMP_FinalClause, OpenMP_IfClause,
|
|
OpenMP_InReductionClause, OpenMP_MergeableClause,
|
|
OpenMP_PriorityClause, OpenMP_PrivateClause,
|
|
OpenMP_UntiedClause, OpenMP_DetachClause],
|
|
singleRegion = true> {
|
|
let summary = "task construct";
|
|
let description = [{
|
|
The task construct defines an explicit task.
|
|
|
|
For definitions of "undeferred task", "included task", "final task" and
|
|
"mergeable task", please check OpenMP Specification.
|
|
|
|
When an `if` clause is present on a task construct, and the value of
|
|
`if_expr` evaluates to `false`, an "undeferred task" is generated, and the
|
|
encountering thread must suspend the current task region, for which
|
|
execution cannot be resumed until execution of the structured block that is
|
|
associated with the generated task is completed.
|
|
|
|
The `in_reduction` clause specifies that this particular task (among all the
|
|
tasks in current taskgroup, if any) participates in a reduction.
|
|
`in_reduction_byref` indicates whether each reduction variable should
|
|
be passed by value or by reference.
|
|
}] # clausesDescription;
|
|
|
|
let builders = [
|
|
OpBuilder<(ins CArg<"const TaskOperands &">:$clauses)>
|
|
];
|
|
|
|
let assemblyFormat = clausesAssemblyFormat # [{
|
|
custom<InReductionPrivateRegion>(
|
|
$region, $in_reduction_vars, type($in_reduction_vars),
|
|
$in_reduction_byref, $in_reduction_syms, $private_vars,
|
|
type($private_vars), $private_syms, $private_needs_barrier) attr-dict
|
|
}];
|
|
|
|
let hasVerifier = 1;
|
|
}
|
|
|
|
// SingleBlock restriction is just to ease implementation. It would be okay to
|
|
// relax this.
|
|
def TaskloopContextOp : OpenMP_Op<"taskloop.context", traits = [
|
|
AttrSizedOperandSegments, AutomaticAllocationScope,
|
|
RecursiveMemoryEffects, SingleBlock,
|
|
DeclareOpInterfaceMethods<OutlineableOpenMPOpInterface>
|
|
], clauses = [
|
|
OpenMP_AllocateClause, OpenMP_FinalClause, OpenMP_GrainsizeClause,
|
|
OpenMP_IfClause, OpenMP_InReductionClause, OpenMP_MergeableClause,
|
|
OpenMP_NogroupClause, OpenMP_NumTasksClause, OpenMP_PriorityClause,
|
|
OpenMP_PrivateClause, OpenMP_ReductionClause, OpenMP_UntiedClause
|
|
], singleRegion = true> {
|
|
let summary = "OutlinableOpenMPOpInterface wrapper for taskloop construct";
|
|
let description = [{
|
|
The taskloop construct specifies that the iterations of one or more
|
|
associated loops will be executed in parallel using explicit tasks. The
|
|
iterations are distributed across tasks generated by the construct and
|
|
scheduled to be executed. The representation of this construct is split
|
|
between omp.taskloop.context and omp.taskloop.wrapper.
|
|
|
|
The taskloop construct must be a loop wrapper to support composite
|
|
constructs such as taskloop simd. Loop wrappers do not allow intervening
|
|
operations between the wrapper and wrapped loop(wrapper). But unlike other
|
|
loop wrappers, the body of taskloop is also outlined. This outlining means
|
|
that we need a way to represent the correct location of allocas for
|
|
temporaries created inside of the loop body. In order to achieve this,
|
|
the outlining part is represented in this operation: `omp.taskloop.context`,
|
|
and the loop wrapping is represented in `omp.taskloop.wrapper`. For example:
|
|
```
|
|
omp.taskloop.context <clauses> {
|
|
// task-local stack allocations can go here
|
|
omp.taskloop.wrapper {
|
|
omp.loop_nest (%i1, %i2) : index = (%c0, %c0) to (%c10, %c10) step (%c1, %c1) {
|
|
%a = load %arrA[%i1, %i2] : memref<?x?xf32>
|
|
%b = load %arrB[%i1, %i2] : memref<?x?xf32>
|
|
%sum = arith.addf %a, %b : f32
|
|
store %sum, %arrC[%i1, %i2] : memref<?x?xf32>
|
|
omp.yield
|
|
}
|
|
}
|
|
omp.terminator
|
|
}
|
|
```
|
|
|
|
The wrapped `omp.loop_nest` lower bounds, upper bounds and steps must be
|
|
either defined outside of the `omp.taskloop.context` region or produced by
|
|
pure, regionless operations inside the region that do not depend on block
|
|
arguments.
|
|
|
|
For definitions of "undeferred task", "included task", "final task" and
|
|
"mergeable task", please check OpenMP Specification.
|
|
|
|
When an `if` clause is present on a taskloop construct, and if the `if`
|
|
clause expression evaluates to `false`, undeferred tasks are generated. The
|
|
use of a variable in an `if` clause expression of a taskloop construct
|
|
causes an implicit reference to the variable in all enclosing constructs.
|
|
}] # clausesDescription # [{
|
|
If an `in_reduction` clause is present on the taskloop construct, the
|
|
behavior is as if each generated task was defined by a task construct on
|
|
which an `in_reduction` clause with the same reduction operator and list
|
|
items is present. Thus, the generated tasks are participants of a reduction
|
|
previously defined by a reduction scoping clause. In this case, accumulator
|
|
variables are specified in `in_reduction_vars`, symbols referring to
|
|
reduction declarations in `in_reduction_syms` and `in_reduction_byref`
|
|
indicate for each reduction variable whether it should be passed by value or
|
|
by reference.
|
|
|
|
If a `reduction` clause is present on the taskloop construct, the behavior
|
|
is as if a `task_reduction` clause with the same reduction operator and list
|
|
items was applied to the implicit taskgroup construct enclosing the taskloop
|
|
construct. The taskloop construct executes as if each generated task was
|
|
defined by a task construct on which an `in_reduction` clause with the same
|
|
reduction operator and list items is present. Thus, the generated tasks are
|
|
participants of the reduction defined by the `task_reduction` clause that
|
|
was applied to the implicit taskgroup construct.
|
|
}];
|
|
|
|
let builders = [
|
|
OpBuilder<(ins CArg<"const TaskloopContextOperands &">:$clauses)>
|
|
];
|
|
|
|
let assemblyFormat = clausesAssemblyFormat # [{
|
|
custom<InReductionPrivateReductionRegion>(
|
|
$region, $in_reduction_vars, type($in_reduction_vars),
|
|
$in_reduction_byref, $in_reduction_syms, $private_vars,
|
|
type($private_vars), $private_syms, $private_needs_barrier,
|
|
$reduction_mod, $reduction_vars, type($reduction_vars),
|
|
$reduction_byref, $reduction_syms) attr-dict
|
|
}];
|
|
|
|
let extraClassDeclaration = [{
|
|
TaskloopWrapperOp getLoopOp();
|
|
}] # clausesExtraClassDeclaration;
|
|
|
|
let hasVerifier = 1;
|
|
let hasRegionVerifier = 1;
|
|
}
|
|
|
|
def TaskloopWrapperOp : OpenMP_Op<"taskloop.wrapper", traits = [
|
|
DeclareOpInterfaceMethods<ComposableOpInterface>,
|
|
DeclareOpInterfaceMethods<LoopWrapperInterface>, NoTerminator,
|
|
RecursiveMemoryEffects, SingleBlock
|
|
], clauses = [
|
|
// See taskloop.context, which generates the runtime calls and outlined
|
|
// task function.
|
|
], singleRegion = true> {
|
|
let summary = "taskloop construct";
|
|
let description = [{
|
|
The taskloop construct specifies that the iterations of one or more
|
|
associated loops will be executed in parallel using explicit tasks. The
|
|
iterations are distributed across tasks generated by the construct and
|
|
scheduled to be executed. The representation of this construct is split
|
|
between omp.taskloop.context and omp.taskloop.wrapper.
|
|
|
|
This operation is intended to act as the loop wrapper portion of the
|
|
taskloop op definition. See the description of omp.taskloop.context for more
|
|
details.
|
|
|
|
The body region can only contain a single block which must contain a single
|
|
operation. This operation must be another compatible loop wrapper or an
|
|
`omp.loop_nest`.
|
|
|
|
```
|
|
omp.taskloop.context <clauses> {
|
|
omp.taskloop.wrapper {
|
|
omp.loop_nest (%i1, %i2) : index = (%c0, %c0) to (%c10, %c10) step (%c1, %c1) {
|
|
%a = load %arrA[%i1, %i2] : memref<?x?xf32>
|
|
%b = load %arrB[%i1, %i2] : memref<?x?xf32>
|
|
%sum = arith.addf %a, %b : f32
|
|
store %sum, %arrC[%i1, %i2] : memref<?x?xf32>
|
|
omp.yield
|
|
}
|
|
}
|
|
omp.terminator
|
|
}
|
|
```
|
|
}] # clausesDescription;
|
|
|
|
let builders = [
|
|
OpBuilder<(ins CArg<"const TaskloopWrapperOperands &">:$clauses)>
|
|
];
|
|
|
|
let assemblyFormat = "$region attr-dict";
|
|
|
|
let extraClassDeclaration = [{
|
|
TaskloopContextOp getTaskloopContext();
|
|
}] # clausesExtraClassDeclaration;
|
|
|
|
let hasVerifier = 1;
|
|
let hasRegionVerifier = 1;
|
|
}
|
|
|
|
def TaskgroupOp : OpenMP_Op<"taskgroup", traits = [
|
|
AttrSizedOperandSegments, AutomaticAllocationScope
|
|
], clauses = [
|
|
OpenMP_AllocateClause, OpenMP_TaskReductionClause
|
|
], singleRegion = true> {
|
|
let summary = "taskgroup construct";
|
|
let description = [{
|
|
The taskgroup construct specifies a wait on completion of child tasks of the
|
|
current task and their descendent tasks.
|
|
|
|
When a thread encounters a taskgroup construct, it starts executing the
|
|
region. All child tasks generated in the taskgroup region and all of their
|
|
descendants that bind to the same parallel region as the taskgroup region
|
|
are part of the taskgroup set associated with the taskgroup region. There is
|
|
an implicit task scheduling point at the end of the taskgroup region. The
|
|
current task is suspended at the task scheduling point until all tasks in
|
|
the taskgroup set complete execution.
|
|
}] # clausesDescription;
|
|
|
|
let builders = [
|
|
OpBuilder<(ins CArg<"const TaskgroupOperands &">:$clauses)>
|
|
];
|
|
|
|
let assemblyFormat = clausesAssemblyFormat # [{
|
|
custom<TaskReductionRegion>(
|
|
$region, $task_reduction_vars, type($task_reduction_vars),
|
|
$task_reduction_byref, $task_reduction_syms) attr-dict
|
|
}];
|
|
|
|
let hasVerifier = 1;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// 2.10.4 taskyield Construct
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
def TaskyieldOp : OpenMP_Op<"taskyield"> {
|
|
let summary = "taskyield construct";
|
|
let description = [{
|
|
The taskyield construct specifies that the current task can be suspended
|
|
in favor of execution of a different task.
|
|
}];
|
|
|
|
let assemblyFormat = "attr-dict";
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// 2.13.7 flush Construct
|
|
//===----------------------------------------------------------------------===//
|
|
def FlushOp : OpenMP_Op<"flush", clauses = [
|
|
// TODO: Complete clause list (memory_order).
|
|
]> {
|
|
let summary = "flush construct";
|
|
let description = [{
|
|
The flush construct executes the OpenMP flush operation. This operation
|
|
makes a thread's temporary view of memory consistent with memory and
|
|
enforces an order on the memory operations of the variables explicitly
|
|
specified or implied.
|
|
}] # clausesDescription;
|
|
|
|
let arguments = !con((ins Variadic<AnyType>:$varList),
|
|
clausesArgs);
|
|
|
|
// Override inherited assembly format to include `varList`.
|
|
let assemblyFormat = "( `(` $varList^ `:` type($varList) `)` )? attr-dict";
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Map related constructs
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
def MapBoundsOp : OpenMP_Op<"map.bounds",
|
|
[AttrSizedOperandSegments, NoMemoryEffect]> {
|
|
let summary = "Represents normalized bounds information for map clauses.";
|
|
|
|
let description = [{
|
|
This operation is a variation on the OpenACC dialects DataBoundsOp. Within
|
|
the OpenMP dialect it stores the bounds/range of data to be mapped to a
|
|
device specified by map clauses on target directives. Within
|
|
the OpenMP dialect, the MapBoundsOp is associated with MapInfoOp,
|
|
helping to store bounds information for the mapped variable.
|
|
|
|
It is used to support OpenMP array sectioning, Fortran pointer and
|
|
allocatable mapping and pointer/allocatable member of derived types.
|
|
In all cases the MapBoundsOp holds information on the section of
|
|
data to be mapped. Such as the upper bound and lower bound of the
|
|
section of data to be mapped. This information is currently
|
|
utilised by the LLVM-IR lowering to help generate instructions to
|
|
copy data to and from the device when processing target operations.
|
|
|
|
The example below copys a section of a 10-element array; all except the
|
|
first element, utilising OpenMP array sectioning syntax where array
|
|
subscripts are provided to specify the bounds to be mapped to device.
|
|
To simplify the examples, the constants are used directly, in reality
|
|
they will be MLIR SSA values.
|
|
|
|
C++:
|
|
```
|
|
int array[10];
|
|
#pragma target map(array[1:9])
|
|
```
|
|
=>
|
|
```mlir
|
|
omp.map.bounds lower_bound(1) upper_bound(9) extent(9) start_idx(0)
|
|
```
|
|
|
|
Fortran:
|
|
```
|
|
integer :: array(1:10)
|
|
!$target map(array(2:10))
|
|
```
|
|
=>
|
|
```mlir
|
|
omp.map.bounds lower_bound(1) upper_bound(9) extent(9) start_idx(1)
|
|
```
|
|
|
|
For Fortran pointers and allocatables (as well as those that are
|
|
members of derived types) the bounds information is provided by
|
|
the Fortran compiler and runtime through descriptor information.
|
|
|
|
A basic pointer example can be found below (constants again
|
|
provided for simplicity, where in reality SSA values will be
|
|
used, in this case that point to data yielded by Fortran's
|
|
descriptors):
|
|
|
|
Fortran:
|
|
```
|
|
integer, pointer :: ptr(:)
|
|
allocate(ptr(10))
|
|
!$target map(ptr)
|
|
```
|
|
=>
|
|
```mlir
|
|
omp.map.bounds lower_bound(0) upper_bound(9) extent(10) start_idx(1)
|
|
```
|
|
|
|
This operation records the bounds information in a normalized fashion
|
|
(zero-based). This works well with the `PointerLikeType`
|
|
requirement in data clauses - since a `lower_bound` of 0 means looking
|
|
at data at the zero offset from pointer.
|
|
|
|
This operation must have an `upper_bound` or `extent` (or both are allowed -
|
|
but not checked for consistency). When the source language's arrays are
|
|
not zero-based, the `start_idx` must specify the zero-position index.
|
|
}];
|
|
|
|
let arguments = (ins Optional<IntLikeType>:$lower_bound,
|
|
Optional<IntLikeType>:$upper_bound,
|
|
Optional<IntLikeType>:$extent,
|
|
Optional<IntLikeType>:$stride,
|
|
DefaultValuedAttr<BoolAttr, "false">:$stride_in_bytes,
|
|
Optional<IntLikeType>:$start_idx);
|
|
let results = (outs OpenMP_MapBoundsType:$result);
|
|
|
|
let assemblyFormat = [{
|
|
oilist(
|
|
`lower_bound` `(` $lower_bound `:` type($lower_bound) `)`
|
|
| `upper_bound` `(` $upper_bound `:` type($upper_bound) `)`
|
|
| `extent` `(` $extent `:` type($extent) `)`
|
|
| `stride` `(` $stride `:` type($stride) `)`
|
|
| `start_idx` `(` $start_idx `:` type($start_idx) `)`
|
|
) attr-dict
|
|
}];
|
|
|
|
let hasVerifier = 1;
|
|
}
|
|
|
|
def MapInfoOp : OpenMP_Op<"map.info", [AttrSizedOperandSegments]> {
|
|
let arguments = (ins OpenMP_PointerLikeType:$var_ptr,
|
|
TypeAttr:$var_ptr_type,
|
|
ClauseMapFlagsAttr:$map_type,
|
|
VariableCaptureKindAttr:$map_capture_type,
|
|
Optional<OpenMP_PointerLikeType>:$var_ptr_ptr,
|
|
OptionalAttr<TypeAttr>:$var_ptr_ptr_type,
|
|
Variadic<OpenMP_PointerLikeType>:$members,
|
|
OptionalAttr<IndexListArrayAttr>:$members_index,
|
|
Variadic<OpenMP_MapBoundsType>:$bounds, /* rank-0 to rank-{n-1} */
|
|
OptionalAttr<FlatSymbolRefAttr>:$mapper_id,
|
|
OptionalAttr<StrAttr>:$name,
|
|
DefaultValuedAttr<BoolAttr, "false">:$partial_map);
|
|
let results = (outs OpenMP_PointerLikeType:$omp_ptr);
|
|
|
|
let description = [{
|
|
The MapInfoOp captures information relating to individual OpenMP map clauses
|
|
that are applied to certain OpenMP directives such as Target and Target Data.
|
|
|
|
For example, the map type modifier; such as from, tofrom and to, the variable
|
|
being captured or the bounds of an array section being mapped.
|
|
|
|
It can be used to capture both implicit and explicit map information, where
|
|
explicit is an argument directly specified to an OpenMP map clause or implicit
|
|
where a variable is utilised in a target region but is defined externally to
|
|
the target region.
|
|
|
|
This map information is later used to aid the lowering of the target operations
|
|
they are attached to providing argument input and output context for kernels
|
|
generated or the target data mapping environment.
|
|
|
|
Example (Fortran):
|
|
|
|
```
|
|
integer :: index
|
|
!$target map(to: index)
|
|
```
|
|
=>
|
|
```mlir
|
|
omp.map.info var_ptr(%index_ssa : ref<i32>, i32) map_type(to) map_capture_type(ByRef)
|
|
name(index)
|
|
```
|
|
|
|
Description of arguments:
|
|
- `var_ptr`: The address of variable to copy.
|
|
- `var_ptr_type`: The type of the variable (`var_ptr`) to copy.
|
|
- 'map_type': OpenMP map type for this map capture, for example: from, to and
|
|
always. It's a bitfield composed of the OpenMP runtime flags stored in
|
|
OpenMPOffloadMappingFlags.
|
|
- 'map_capture_type': Capture type for the variable e.g. this, byref, byvalue, byvla
|
|
this can affect how the variable is lowered.
|
|
- `var_ptr_ptr`: Used when the variable being copied is a fortran, C or C++ pointer,
|
|
with this field referring to the base address of the pointer. If the `var_ptr_ptr`
|
|
field is present, a corresponding `var_ptr_ptr_type` must also be present.
|
|
- `var_ptr_ptr_type`: Used when the variable being copied is a fortran, C or
|
|
C++ pointer, with this field referring to the underlying type of the pointed
|
|
to data. If the `var_ptr_ptr_type` field is present, a corresponding `var_ptr_ptr`
|
|
must also be present.
|
|
- `members`: Used to indicate mapped child members for the current MapInfoOp,
|
|
represented as other MapInfoOp's, utilised in cases where a parent structure
|
|
type and members of the structure type are being mapped at the same time.
|
|
For example: map(to: parent, parent->member, parent->member2[:10])
|
|
- `members_index`: Used to indicate the ordering of members within the containing
|
|
parent (generally a record type such as a structure, class or derived type),
|
|
e.g. struct {int x, float y, double z}, x would be 0, y would be 1, and z
|
|
would be 2. This aids the mapping.
|
|
- `bounds`: Used when copying slices of array's, pointers or pointer members of
|
|
objects (e.g. derived types or classes), indicates the bounds to be copied
|
|
of the variable. When it's an array slice it is in rank order where rank 0
|
|
is the inner-most dimension.
|
|
- 'mapper_id': OpenMP mapper map type modifier for this map capture. It's used to
|
|
specify a user defined mapper to be used for mapping.
|
|
- `name`: Holds the name of variable as specified in user clause (including bounds).
|
|
- `partial_map`: The record type being mapped will not be mapped in its entirety,
|
|
it may be used however, in a mapping to bind it's mapped components together.
|
|
}];
|
|
|
|
let assemblyFormat = [{
|
|
`var_ptr` `(` $var_ptr `:` type($var_ptr) `,` $var_ptr_type `)`
|
|
`map_clauses` `(` custom<MapClause>($map_type) `)`
|
|
`capture` `(` custom<CaptureType>($map_capture_type) `)`
|
|
oilist(
|
|
`var_ptr_ptr` `(` $var_ptr_ptr `:` type($var_ptr_ptr) `,` $var_ptr_ptr_type`)`
|
|
| `mapper` `(` $mapper_id `)`
|
|
| `members` `(` $members `:` custom<MembersIndex>($members_index) `:` type($members) `)`
|
|
| `bounds` `(` $bounds `)`
|
|
) `->` type($omp_ptr) attr-dict
|
|
}];
|
|
|
|
let hasVerifier = 1;
|
|
}
|
|
|
|
//===---------------------------------------------------------------------===//
|
|
// 2.14.2 target data Construct
|
|
//===---------------------------------------------------------------------===//
|
|
|
|
def TargetDataOp: OpenMP_Op<"target_data", traits = [
|
|
AttrSizedOperandSegments
|
|
], clauses = [
|
|
OpenMP_DeviceClause, OpenMP_IfClause, OpenMP_MapClause,
|
|
OpenMP_UseDeviceAddrClause, OpenMP_UseDevicePtrClause
|
|
], singleRegion = true> {
|
|
let summary = "target data construct";
|
|
let description = [{
|
|
Map variables to a device data environment for the extent of the region.
|
|
|
|
The omp target data directive maps variables to a device data
|
|
environment, and defines the lexical scope of the data environment
|
|
that is created. The omp target data directive can reduce data copies
|
|
to and from the offloading device when multiple target regions are using
|
|
the same data.
|
|
|
|
The optional `if_expr` parameter specifies a boolean result of a conditional
|
|
check. If this value is 1 or is not provided then the target region runs on
|
|
a device, if it is 0 then the target region is executed on the host device.
|
|
}] # clausesDescription;
|
|
|
|
let builders = [
|
|
OpBuilder<(ins CArg<"const TargetDataOperands &">:$clauses)>
|
|
];
|
|
|
|
let extraClassDeclaration = [{
|
|
// Override BlockArgOpenMPOpInterface method because `map` clauses have no
|
|
// associated entry block arguments in this operation.
|
|
unsigned numMapBlockArgs() {
|
|
return 0;
|
|
}
|
|
}] # clausesExtraClassDeclaration;
|
|
|
|
let assemblyFormat = clausesAssemblyFormat # [{
|
|
custom<UseDeviceAddrUseDevicePtrRegion>(
|
|
$region, $use_device_addr_vars, type($use_device_addr_vars),
|
|
$use_device_ptr_vars, type($use_device_ptr_vars)) attr-dict
|
|
}];
|
|
|
|
let hasVerifier = 1;
|
|
}
|
|
|
|
//===---------------------------------------------------------------------===//
|
|
// 2.14.3 target enter data Construct
|
|
//===---------------------------------------------------------------------===//
|
|
|
|
def TargetEnterDataOp: OpenMP_Op<"target_enter_data", traits = [
|
|
AttrSizedOperandSegments
|
|
], clauses = [
|
|
OpenMP_DependClause, OpenMP_DeviceClause, OpenMP_IfClause, OpenMP_MapClause,
|
|
OpenMP_NowaitClause
|
|
]> {
|
|
let summary = "target enter data construct";
|
|
let description = [{
|
|
The target enter data directive specifies that variables are mapped to
|
|
a device data environment. The target enter data directive is a
|
|
stand-alone directive.
|
|
|
|
The optional `if_expr` parameter specifies a boolean result of a conditional
|
|
check. If this value is 1 or is not provided then the target region runs on
|
|
a device, if it is 0 then the target region is executed on the host device.
|
|
}] # clausesDescription;
|
|
|
|
let builders = [
|
|
OpBuilder<(ins CArg<"const TargetEnterExitUpdateDataOperands &">:$clauses)>
|
|
];
|
|
|
|
let extraClassDeclaration = [{
|
|
// Override BlockArgOpenMPOpInterface method because `map` clauses have no
|
|
// associated entry block arguments in this operation.
|
|
unsigned numMapBlockArgs() {
|
|
return 0;
|
|
}
|
|
}] # clausesExtraClassDeclaration;
|
|
|
|
let hasVerifier = 1;
|
|
}
|
|
|
|
//===---------------------------------------------------------------------===//
|
|
// 2.14.4 target exit data Construct
|
|
//===---------------------------------------------------------------------===//
|
|
|
|
def TargetExitDataOp: OpenMP_Op<"target_exit_data", traits = [
|
|
AttrSizedOperandSegments
|
|
], clauses = [
|
|
OpenMP_DependClause, OpenMP_DeviceClause, OpenMP_IfClause, OpenMP_MapClause,
|
|
OpenMP_NowaitClause
|
|
]> {
|
|
let summary = "target exit data construct";
|
|
let description = [{
|
|
The target exit data directive specifies that variables are mapped to a
|
|
device data environment. The target exit data directive is
|
|
a stand-alone directive.
|
|
|
|
The optional `if_expr` parameter specifies a boolean result of a conditional
|
|
check. If this value is 1 or is not provided then the target region runs on
|
|
a device, if it is 0 then the target region is executed on the host device.
|
|
}] # clausesDescription;
|
|
|
|
let builders = [
|
|
OpBuilder<(ins CArg<"const TargetEnterExitUpdateDataOperands &">:$clauses)>
|
|
];
|
|
|
|
let extraClassDeclaration = [{
|
|
// Override BlockArgOpenMPOpInterface method because `map` clauses have no
|
|
// associated entry block arguments in this operation.
|
|
unsigned numMapBlockArgs() {
|
|
return 0;
|
|
}
|
|
}] # clausesExtraClassDeclaration;
|
|
|
|
let hasVerifier = 1;
|
|
}
|
|
|
|
//===---------------------------------------------------------------------===//
|
|
// 2.14.6 target update Construct
|
|
//===---------------------------------------------------------------------===//
|
|
|
|
def TargetUpdateOp: OpenMP_Op<"target_update", traits = [
|
|
AttrSizedOperandSegments
|
|
], clauses = [
|
|
OpenMP_DependClause, OpenMP_DeviceClause, OpenMP_IfClause, OpenMP_MapClause,
|
|
OpenMP_NowaitClause
|
|
]> {
|
|
let summary = "target update construct";
|
|
let description = [{
|
|
The target update directive makes the corresponding list items in the device
|
|
data environment consistent with their original list items, according to the
|
|
specified motion clauses. The target update construct is a stand-alone
|
|
directive.
|
|
|
|
The optional `if_expr` parameter specifies a boolean result of a conditional
|
|
check. If this value is 1 or is not provided then the target region runs on
|
|
a device, if it is 0 then the target region is executed on the host device.
|
|
|
|
We use `MapInfoOp` to model the motion clauses and their modifiers. Even
|
|
though the spec differentiates between map-types & map-type-modifiers vs.
|
|
motion-clauses & motion-modifiers, the motion clauses and their modifiers
|
|
are a subset of map types and their modifiers. The subset relation is
|
|
handled in during verification to make sure the restrictions for target
|
|
update are respected.
|
|
}] # clausesDescription;
|
|
|
|
let builders = [
|
|
OpBuilder<(ins CArg<"const TargetEnterExitUpdateDataOperands &">:$clauses)>
|
|
];
|
|
|
|
let extraClassDeclaration = [{
|
|
// Override BlockArgOpenMPOpInterface method because `map` clauses have no
|
|
// associated entry block arguments in this operation.
|
|
unsigned numMapBlockArgs() {
|
|
return 0;
|
|
}
|
|
}] # clausesExtraClassDeclaration;
|
|
|
|
let hasVerifier = 1;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// 2.14.5 target construct
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
def TargetOp : OpenMP_Op<"target", traits = [
|
|
AttrSizedOperandSegments, BlockArgOpenMPOpInterface, IsolatedFromAbove,
|
|
OutlineableOpenMPOpInterface
|
|
], clauses = [
|
|
// TODO: Complete clause list (defaultmap, uses_allocators).
|
|
OpenMP_AllocateClause, OpenMP_BareClause, OpenMP_DependClause,
|
|
OpenMP_DeviceClause, OpenMP_DynGroupprivateClause,
|
|
OpenMP_HasDeviceAddrClause, OpenMP_HostEvalClause,
|
|
OpenMP_IfClause, OpenMP_InReductionClause, OpenMP_IsDevicePtrClause,
|
|
OpenMP_MapClauseSkip<assemblyFormat = true>, OpenMP_NowaitClause,
|
|
OpenMP_PrivateClause, OpenMP_ThreadLimitClause
|
|
], singleRegion = true> {
|
|
let summary = "target construct";
|
|
let description = [{
|
|
The target construct includes a region of code which is to be executed
|
|
on a device.
|
|
|
|
The optional `if_expr` parameter specifies a boolean result of a conditional
|
|
check. If this value is 1 or is not provided then the target region runs on
|
|
a device, if it is 0 then the target region is executed on the host device.
|
|
|
|
The `private_maps` attribute connects `private` operands to their corresponding
|
|
`map` operands. For `private` operands that require a map, the value of the
|
|
corresponding element in the attribute is the index of the `map` operand
|
|
(relative to other `map` operands not the whole operands of the operation). For
|
|
`private` opernads that do not require a map, this value is -1 (which is omitted
|
|
from the assembly foramt printing).
|
|
}] # clausesDescription;
|
|
|
|
let arguments = !con(clausesArgs,
|
|
(ins OptionalAttr<DenseI64ArrayAttr>:$private_maps));
|
|
|
|
let builders = [
|
|
OpBuilder<(ins CArg<"const TargetOperands &">:$clauses)>
|
|
];
|
|
|
|
let extraClassDeclaration = [{
|
|
mlir::Value getMappedValueForPrivateVar(unsigned privVarIdx) {
|
|
std::optional<DenseI64ArrayAttr> privateMapIdices = getPrivateMapsAttr();
|
|
|
|
if (!privateMapIdices.has_value())
|
|
return {};
|
|
|
|
int64_t mapInfoOpIdx = (*privateMapIdices)[privVarIdx];
|
|
|
|
if (mapInfoOpIdx == -1)
|
|
return {};
|
|
|
|
return getMapVars()[mapInfoOpIdx];
|
|
}
|
|
|
|
/// Returns the innermost OpenMP dialect operation captured by this target
|
|
/// construct. For an operation to be detected as captured, it must be
|
|
/// inside a (possibly multi-level) nest of OpenMP dialect operation's
|
|
/// regions where none of these levels contain other operations considered
|
|
/// not-allowed for these purposes (i.e. only terminator operations are
|
|
/// allowed from the OpenMP dialect, and other dialect's operations are
|
|
/// allowed as long as they don't have a memory write effect).
|
|
///
|
|
/// If there are omp.loop_nest operations in the sequence of nested
|
|
/// operations, the top level one will be the one captured.
|
|
Operation *getInnermostCapturedOmpOp();
|
|
|
|
/// Infers the kernel type (Bare, Generic or SPMD) based on the contents of
|
|
/// the target region.
|
|
///
|
|
/// \param capturedOp result of a still valid (no modifications made to any
|
|
/// nested operations) previous call to `getInnermostCapturedOmpOp()`.
|
|
/// \param hostEvalTripCount output argument to store whether this kernel
|
|
/// wraps a loop whose bounds must be evaluated on the host prior to
|
|
/// launching it.
|
|
static ::mlir::omp::TargetExecMode
|
|
getKernelExecFlags(Operation *capturedOp,
|
|
bool *hostEvalTripCount = nullptr);
|
|
}] # clausesExtraClassDeclaration;
|
|
|
|
let assemblyFormat = clausesAssemblyFormat # [{
|
|
( `map_iterated` `(` $map_iterated^ `:` type($map_iterated) `)` )?
|
|
custom<TargetOpRegion>(
|
|
$region, $has_device_addr_vars, type($has_device_addr_vars),
|
|
$host_eval_vars, type($host_eval_vars), $in_reduction_vars,
|
|
type($in_reduction_vars), $in_reduction_byref, $in_reduction_syms,
|
|
$map_vars, type($map_vars), $private_vars, type($private_vars),
|
|
$private_syms, $private_needs_barrier, $private_maps) attr-dict
|
|
}];
|
|
|
|
let hasVerifier = 1;
|
|
let hasRegionVerifier = 1;
|
|
}
|
|
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// 2.16 master Construct
|
|
//===----------------------------------------------------------------------===//
|
|
def MasterOp : OpenMP_Op<"master", singleRegion = true> {
|
|
let summary = "master construct";
|
|
let description = [{
|
|
The master construct specifies a structured block that is executed by
|
|
the master thread of the team.
|
|
}];
|
|
|
|
let assemblyFormat = "$region attr-dict";
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// 2.17.1 critical Construct
|
|
//===----------------------------------------------------------------------===//
|
|
def CriticalDeclareOp : OpenMP_Op<"critical.declare", clauses = [
|
|
OpenMP_CriticalNameClause, OpenMP_HintClause
|
|
]> {
|
|
let summary = "declares a named critical section.";
|
|
let description = [{
|
|
Declares a named critical section.
|
|
}] # clausesDescription;
|
|
|
|
let builders = [
|
|
OpBuilder<(ins CArg<"const CriticalDeclareOperands &">:$clauses)>
|
|
];
|
|
|
|
let hasVerifier = 1;
|
|
}
|
|
|
|
|
|
def CriticalOp : OpenMP_Op<"critical", [
|
|
DeclareOpInterfaceMethods<SymbolUserOpInterface>
|
|
], singleRegion = 1> {
|
|
let summary = "critical construct";
|
|
let description = [{
|
|
The critical construct imposes a restriction on the associated structured
|
|
block (region) to be executed by only a single thread at a time.
|
|
|
|
The optional `name` argument of critical constructs is used to identify
|
|
them. Unnamed critical constructs behave as though an identical name was
|
|
specified.
|
|
}];
|
|
|
|
let arguments = (ins OptionalAttr<FlatSymbolRefAttr>:$name);
|
|
|
|
let assemblyFormat = [{
|
|
(`(` $name^ `)`)? $region attr-dict
|
|
}];
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// 2.17.2 barrier Construct
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
def BarrierOp : OpenMP_Op<"barrier"> {
|
|
let summary = "barrier construct";
|
|
let description = [{
|
|
The barrier construct specifies an explicit barrier at the point at which
|
|
the construct appears.
|
|
}];
|
|
|
|
let assemblyFormat = "attr-dict";
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// [5.1] 2.19.9 ordered Construct
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
def OrderedOp : OpenMP_Op<"ordered", clauses = [OpenMP_DoacrossClause]> {
|
|
let summary = "ordered construct without region";
|
|
let description = [{
|
|
The ordered construct without region is a stand-alone directive that
|
|
specifies cross-iteration dependencies in a doacross loop nest.
|
|
}] # clausesDescription;
|
|
|
|
let builders = [
|
|
OpBuilder<(ins CArg<"const OrderedOperands &">:$clauses)>
|
|
];
|
|
|
|
let hasVerifier = 1;
|
|
}
|
|
|
|
def OrderedRegionOp : OpenMP_Op<"ordered.region", clauses = [
|
|
OpenMP_ParallelizationLevelClause
|
|
], singleRegion = true> {
|
|
let summary = "ordered construct with region";
|
|
let description = [{
|
|
The ordered construct with region specifies a structured block in a
|
|
worksharing-loop, SIMD, or worksharing-loop SIMD region that is executed in
|
|
the order of the loop iterations.
|
|
}] # clausesDescription;
|
|
|
|
let builders = [
|
|
OpBuilder<(ins CArg<"const OrderedRegionOperands &">:$clauses)>
|
|
];
|
|
|
|
let hasVerifier = 1;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// 2.17.5 taskwait Construct
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
def TaskwaitOp
|
|
: OpenMP_Op<"taskwait", traits = [AttrSizedOperandSegments],
|
|
clauses = [OpenMP_DependClause, OpenMP_NowaitClause]> {
|
|
let summary = "taskwait construct";
|
|
let description = [{
|
|
The taskwait construct specifies a wait on the completion of child tasks
|
|
of the current task.
|
|
}] # clausesDescription;
|
|
|
|
let builders = [
|
|
OpBuilder<(ins CArg<"const TaskwaitOperands &">:$clauses)>
|
|
];
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// 2.17.7 atomic construct
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
// In the OpenMP Specification, atomic construct has an `atomic-clause` which
|
|
// can take the values `read`, `write`, `update` and `capture`. These four
|
|
// kinds of atomic constructs are fundamentally independent and are handled
|
|
// separately while lowering. Having four separate operations (one for each
|
|
// value of the clause) here decomposes handling of this construct into a
|
|
// two-step process.
|
|
|
|
def AtomicReadOp : OpenMP_Op<"atomic.read", traits = [
|
|
AtomicReadOpInterface
|
|
], clauses = [
|
|
OpenMP_HintClause, OpenMP_MemoryOrderClause
|
|
]> {
|
|
let summary = "performs an atomic read";
|
|
let description = [{
|
|
This operation performs an atomic read.
|
|
|
|
The operand `x` is the address from where the value is atomically read.
|
|
The operand `v` is the address where the value is stored after reading.
|
|
}] # clausesDescription;
|
|
|
|
let arguments = !con((ins OpenMP_PointerLikeType:$x,
|
|
OpenMP_PointerLikeType:$v,
|
|
TypeAttr:$element_type), clausesArgs);
|
|
|
|
// Override clause-based assemblyFormat.
|
|
let assemblyFormat = "$v `=` $x" # clausesReqAssemblyFormat # " oilist(" #
|
|
clausesOptAssemblyFormat #
|
|
") `:` type($v) `,` type($x) `,` $element_type attr-dict";
|
|
|
|
let hasVerifier = 1;
|
|
}
|
|
|
|
def AtomicWriteOp : OpenMP_Op<"atomic.write", traits = [
|
|
AtomicWriteOpInterface
|
|
], clauses = [
|
|
OpenMP_HintClause, OpenMP_MemoryOrderClause
|
|
]> {
|
|
let summary = "performs an atomic write";
|
|
let description = [{
|
|
This operation performs an atomic write.
|
|
|
|
The operand `x` is the address to where the `expr` is atomically
|
|
written w.r.t. multiple threads. The evaluation of `expr` need not be
|
|
atomic w.r.t. the write to address. In general, the type(x) must
|
|
dereference to type(expr).
|
|
}] # clausesDescription;
|
|
|
|
let arguments = !con((ins OpenMP_PointerLikeType:$x,
|
|
AnyType:$expr), clausesArgs);
|
|
|
|
// Override clause-based assemblyFormat.
|
|
let assemblyFormat = "$x `=` $expr" # clausesReqAssemblyFormat # " oilist(" #
|
|
clausesOptAssemblyFormat # ") `:` type($x) `,` type($expr) attr-dict";
|
|
|
|
let hasVerifier = 1;
|
|
}
|
|
|
|
def AtomicUpdateOp : OpenMP_Op<"atomic.update", traits = [
|
|
AtomicUpdateOpInterface, RecursiveMemoryEffects,
|
|
SingleBlockImplicitTerminator<"YieldOp">
|
|
], clauses = [
|
|
OpenMP_HintClause, OpenMP_MemoryOrderClause
|
|
], singleRegion = 1> {
|
|
let summary = "performs an atomic update";
|
|
let description = [{
|
|
This operation performs an atomic update.
|
|
|
|
The operand `x` is exactly the same as the operand `x` in the OpenMP
|
|
Standard (OpenMP 5.0, section 2.17.7). It is the address of the variable
|
|
that is being updated. `x` is atomically read/written.
|
|
|
|
The region describes how to update the value of `x`. It takes the value at
|
|
`x` as an input and must yield the updated value. Only the update to `x` is
|
|
atomic. Generally the region must have only one instruction, but can
|
|
potentially have more than one instructions too. The update is sematically
|
|
similar to a compare-exchange loop based atomic update.
|
|
|
|
The syntax of atomic update operation is different from atomic read and
|
|
atomic write operations. This is because only the host dialect knows how to
|
|
appropriately update a value. For example, while generating LLVM IR, if
|
|
there are no special `atomicrmw` instructions for the operation-type
|
|
combination in atomic update, a compare-exchange loop is generated, where
|
|
the core update operation is directly translated like regular operations by
|
|
the host dialect. The front-end must handle semantic checks for allowed
|
|
operations.
|
|
}] # clausesDescription;
|
|
|
|
let arguments = !con(
|
|
(ins Arg<OpenMP_PointerLikeType,
|
|
"Address of variable to be updated", [MemRead, MemWrite]>:$x,
|
|
OptionalAttr<AtomicControlAttr>:$atomic_control),
|
|
clausesArgs);
|
|
|
|
// Override region definition.
|
|
let regions = (region SizedRegion<1>:$region);
|
|
|
|
// Override clause-based assemblyFormat.
|
|
let assemblyFormat = clausesAssemblyFormat #
|
|
"$x `:` type($x) $region attr-dict";
|
|
|
|
let hasVerifier = 1;
|
|
let hasRegionVerifier = 1;
|
|
let hasCanonicalizeMethod = 1;
|
|
}
|
|
|
|
def AtomicCaptureOp : OpenMP_Op<"atomic.capture", traits = [
|
|
AtomicCaptureOpInterface, RecursiveMemoryEffects,
|
|
SingleBlockImplicitTerminator<"TerminatorOp">
|
|
], clauses = [
|
|
OpenMP_HintClause, OpenMP_MemoryOrderClause
|
|
], singleRegion = 1> {
|
|
let summary = "performs an atomic capture";
|
|
let description = [{
|
|
This operation performs an atomic capture.
|
|
|
|
The region has the following allowed forms:
|
|
```
|
|
omp.atomic.capture {
|
|
omp.atomic.update ...
|
|
omp.atomic.read ...
|
|
omp.terminator
|
|
}
|
|
|
|
omp.atomic.capture {
|
|
omp.atomic.read ...
|
|
omp.atomic.update ...
|
|
omp.terminator
|
|
}
|
|
|
|
omp.atomic.capture {
|
|
omp.atomic.read ...
|
|
omp.atomic.write ...
|
|
omp.terminator
|
|
}
|
|
```
|
|
}] # clausesDescription;
|
|
|
|
// Override region definition.
|
|
let regions = (region SizedRegion<1>:$region);
|
|
|
|
let extraClassDeclaration = [{
|
|
/// Returns the `atomic.read` operation inside the region, if any.
|
|
/// Otherwise, it returns nullptr.
|
|
AtomicReadOp getAtomicReadOp();
|
|
|
|
/// Returns the `atomic.write` operation inside the region, if any.
|
|
/// Otherwise, it returns nullptr.
|
|
AtomicWriteOp getAtomicWriteOp();
|
|
|
|
/// Returns the `atomic.update` operation inside the region, if any.
|
|
/// Otherwise, it returns nullptr.
|
|
AtomicUpdateOp getAtomicUpdateOp();
|
|
}] # clausesExtraClassDeclaration;
|
|
|
|
let hasRegionVerifier = 1;
|
|
let hasVerifier = 1;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// [5.1] 2.17.7 atomic Directive - compare clause
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
def AtomicCompareOp : OpenMP_Op<"atomic.compare", traits = [
|
|
AtomicCompareOpInterface, RecursiveMemoryEffects,
|
|
SingleBlockImplicitTerminator<"YieldOp">
|
|
], clauses = [
|
|
OpenMP_HintClause, OpenMP_MemoryOrderClause
|
|
], singleRegion = 1> {
|
|
let summary = "performs an atomic compare";
|
|
let description = [{
|
|
This operation performs an atomic compare-and-swap.
|
|
|
|
The `atomic compare` construct implements atomic conditional update
|
|
semantics. The operand `x` is the address of the variable that is being
|
|
compared and potentially updated. The region describes the comparison
|
|
and update logic.
|
|
|
|
The region has the following structure:
|
|
```
|
|
omp.atomic.compare {
|
|
if (x == d) x = e
|
|
omp.yield
|
|
}
|
|
```
|
|
}] # clausesDescription;
|
|
|
|
let arguments = !con(
|
|
(ins Arg<OpenMP_PointerLikeType,
|
|
"Address of variable to be compared/updated", [MemRead, MemWrite]>:$x,
|
|
UnitAttr:$weak),
|
|
clausesArgs);
|
|
|
|
// Override region definition.
|
|
let regions = (region SizedRegion<1>:$region);
|
|
|
|
// Override clause-based assemblyFormat.
|
|
let assemblyFormat = clausesAssemblyFormat #
|
|
"$x `:` type($x) $region attr-dict";
|
|
|
|
let hasVerifier = 1;
|
|
let hasRegionVerifier = 1;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// [5.1] 2.21.2 threadprivate Directive
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
def ThreadprivateOp : OpenMP_Op<"threadprivate",
|
|
[AllTypesMatch<["sym_addr", "tls_addr"]>]> {
|
|
let summary = "threadprivate directive";
|
|
let description = [{
|
|
The threadprivate directive specifies that variables are replicated, with
|
|
each thread having its own copy.
|
|
|
|
The current implementation uses the OpenMP runtime to provide thread-local
|
|
storage (TLS). Using the TLS feature of the LLVM IR will be supported in
|
|
future.
|
|
|
|
This operation takes in the address of a symbol that represents the original
|
|
variable and returns the address of its TLS. All occurrences of
|
|
threadprivate variables in a parallel region should use the TLS returned by
|
|
this operation.
|
|
|
|
The `sym_addr` refers to the address of the symbol, which is a pointer to
|
|
the original variable.
|
|
}];
|
|
|
|
let arguments = (ins OpenMP_PointerLikeType:$sym_addr);
|
|
let results = (outs OpenMP_PointerLikeType:$tls_addr);
|
|
let assemblyFormat = [{
|
|
$sym_addr `:` type($sym_addr) `->` type($tls_addr) attr-dict
|
|
}];
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// 2.18.1 Cancel Construct
|
|
//===----------------------------------------------------------------------===//
|
|
def CancelOp : OpenMP_Op<"cancel", clauses = [
|
|
OpenMP_CancelDirectiveNameClause, OpenMP_IfClause
|
|
]> {
|
|
let summary = "cancel directive";
|
|
let description = [{
|
|
The cancel construct activates cancellation of the innermost enclosing
|
|
region of the type specified.
|
|
}] # clausesDescription;
|
|
|
|
let builders = [
|
|
OpBuilder<(ins CArg<"const CancelOperands &">:$clauses)>
|
|
];
|
|
|
|
let hasVerifier = 1;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// 2.18.2 Cancellation Point Construct
|
|
//===----------------------------------------------------------------------===//
|
|
def CancellationPointOp : OpenMP_Op<"cancellation_point", clauses = [
|
|
OpenMP_CancelDirectiveNameClause
|
|
]> {
|
|
let summary = "cancellation point directive";
|
|
let description = [{
|
|
The cancellation point construct introduces a user-defined cancellation
|
|
point at which implicit or explicit tasks check if cancellation of the
|
|
innermost enclosing region of the type specified has been activated.
|
|
}] # clausesDescription;
|
|
|
|
let builders = [
|
|
OpBuilder<(ins CArg<"const CancellationPointOperands &">:$clauses)>
|
|
];
|
|
|
|
let hasVerifier = 1;
|
|
}
|
|
|
|
def ScanOp : OpenMP_Op<"scan", [
|
|
AttrSizedOperandSegments, MemoryEffects<[MemWrite]>
|
|
], clauses = [
|
|
OpenMP_InclusiveClause, OpenMP_ExclusiveClause]> {
|
|
let summary = "scan directive";
|
|
let description = [{
|
|
The scan directive allows to specify scan reductions. It should be
|
|
enclosed within a parent directive along with which a reduction clause
|
|
with `inscan` modifier must be specified. The scan directive allows to
|
|
split code blocks into input phase and scan phase in the region
|
|
enclosed by the parent.
|
|
}] # clausesDescription;
|
|
|
|
let builders = [
|
|
OpBuilder<(ins CArg<"const ScanOperands &">:$clauses)>
|
|
];
|
|
|
|
let hasVerifier = 1;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// 2.19.7.3 Declare Mapper Directive
|
|
//===----------------------------------------------------------------------===//
|
|
def DeclareMapperOp : OpenMP_Op<"declare_mapper", [
|
|
IsolatedFromAbove,
|
|
RecipeInterface,
|
|
SingleBlock,
|
|
Symbol
|
|
]> {
|
|
let summary = "declare mapper directive";
|
|
let description = [{
|
|
The declare mapper directive declares a user-defined mapper for a given
|
|
type, and defines a mapper-identifier that can be used in a map clause.
|
|
}] # clausesDescription;
|
|
|
|
let arguments = (ins SymbolNameAttr:$sym_name,
|
|
TypeAttr:$type);
|
|
|
|
let regions = (region AnyRegion:$body);
|
|
|
|
let assemblyFormat = "$sym_name `:` $type $body attr-dict";
|
|
|
|
let extraClassDeclaration = [{
|
|
/// Get DeclareMapperInfoOp.
|
|
DeclareMapperInfoOp getDeclareMapperInfo(){
|
|
return cast<DeclareMapperInfoOp>(getRegion().getBlocks().front().getTerminator());
|
|
}
|
|
|
|
/// Get SymVal block argument
|
|
BlockArgument getSymVal(){
|
|
return getRegion().getArgument(0);
|
|
}
|
|
}];
|
|
|
|
let hasRegionVerifier = 1;
|
|
}
|
|
|
|
def DeclareMapperInfoOp : OpenMP_Op<"declare_mapper.info",
|
|
[AttrSizedOperandSegments,
|
|
HasParent<"DeclareMapperOp">, Terminator],
|
|
clauses = [OpenMP_MapClause]> {
|
|
let summary = "declare mapper info";
|
|
let description = [{
|
|
This Op is used to capture the map information related to it's
|
|
parent DeclareMapperOp.
|
|
}] # clausesDescription;
|
|
|
|
let builders = [OpBuilder<(
|
|
ins CArg<"const DeclareMapperInfoOperands &">:$clauses)>];
|
|
|
|
let extraClassDeclaration = [{
|
|
// Override BlockArgOpenMPOpInterface method because `map` clauses have no
|
|
// associated entry block arguments in this operation.
|
|
unsigned numMapBlockArgs() {
|
|
return 0;
|
|
}
|
|
}] # clausesExtraClassDeclaration;
|
|
|
|
let hasVerifier = 1;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// 2.19.5.7 declare reduction Directive
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
def DeclareReductionOp : OpenMP_Op<"declare_reduction", [IsolatedFromAbove,
|
|
RecipeInterface,
|
|
Symbol]> {
|
|
let summary = "declares a reduction kind";
|
|
let description = [{
|
|
Declares an OpenMP reduction kind. This requires two mandatory and four
|
|
optional regions.
|
|
|
|
1. The optional alloc region specifies how to allocate the thread-local
|
|
reduction value. This region should not contain control flow and all
|
|
IR should be suitable for inlining straight into an entry block. In
|
|
the common case this is expected to contain only allocas. It is
|
|
expected to `omp.yield` the allocated value on all control paths.
|
|
If allocation is conditional (e.g. only allocate if the mold is
|
|
allocated), this should be done in the initilizer region and this
|
|
region not included. The alloc region is not used for by-value
|
|
reductions (where allocation is implicit).
|
|
2. The initializer region specifies how to initialize the thread-local
|
|
reduction value. This is usually the neutral element of the reduction.
|
|
For convenience, the region has an argument that contains the value
|
|
of the reduction accumulator at the start of the reduction. If an alloc
|
|
region is specified, there is a second block argument containing the
|
|
address of the allocated memory. The initializer region is expected to
|
|
`omp.yield` the new value on all control flow paths.
|
|
3. The reduction region specifies how to combine two values into one, i.e.
|
|
the reduction operator. It accepts the two values as arguments and is
|
|
expected to `omp.yield` the combined value on all control flow paths.
|
|
4. The atomic reduction region is optional and specifies how two values
|
|
can be combined atomically given local accumulator variables. It is
|
|
expected to store the combined value in the first accumulator variable.
|
|
5. The cleanup region is optional and specifies how to clean up any memory
|
|
allocated by the initializer region. The region has an argument that
|
|
contains the value of the thread-local reduction accumulator. This will
|
|
be executed after the reduction has completed.
|
|
6. The DataPtrPtr region specifies how to access the base address of a
|
|
descriptor. This is used, in particular, for GPU reductions in order
|
|
know where partial reduction results are stored in remote lanes.
|
|
|
|
Note that the MLIR type system does not allow for type-polymorphic
|
|
reductions. Separate reduction declarations should be created for different
|
|
element and accumulator types.
|
|
|
|
For initializer and reduction regions, the operand to `omp.yield` must
|
|
match the parent operation's results.
|
|
|
|
* `$byref_element_type`: For by-ref reductions, we want to keep track of the
|
|
boxed/allocated type. For example, for a `real, allocatable` variable,
|
|
`real` should be stored in this attribute.
|
|
|
|
}];
|
|
|
|
let arguments = (ins SymbolNameAttr:$sym_name,
|
|
TypeAttr:$type,
|
|
OptionalAttr<TypeAttr>:$byref_element_type
|
|
);
|
|
|
|
let regions = (region MaxSizedRegion<1>:$allocRegion,
|
|
AnyRegion:$initializerRegion,
|
|
AnyRegion:$reductionRegion,
|
|
AnyRegion:$atomicReductionRegion,
|
|
AnyRegion:$cleanupRegion,
|
|
MaxSizedRegion<1>:$dataPtrPtrRegion);
|
|
|
|
let assemblyFormat = "$sym_name `:` $type attr-dict-with-keyword "
|
|
"( `alloc` $allocRegion^ )? "
|
|
"`init` $initializerRegion "
|
|
"`combiner` $reductionRegion "
|
|
"( `atomic` $atomicReductionRegion^ )? "
|
|
"( `cleanup` $cleanupRegion^ )? "
|
|
"( `data_ptr_ptr` $dataPtrPtrRegion^ )? ";
|
|
|
|
let extraClassDeclaration = [{
|
|
BlockArgument getAllocMoldArg() {
|
|
auto ®ion = getAllocRegion();
|
|
return region.empty() ? nullptr : region.getArgument(0);
|
|
}
|
|
BlockArgument getInitializerMoldArg() {
|
|
return getInitializerRegion().getArgument(0);
|
|
}
|
|
BlockArgument getInitializerAllocArg() {
|
|
return getAllocRegion().empty() ?
|
|
nullptr : getInitializerRegion().getArgument(1);
|
|
}
|
|
BlockArgument getReductionLhsArg() {
|
|
return getReductionRegion().getArgument(0);
|
|
}
|
|
BlockArgument getReductionRhsArg() {
|
|
return getReductionRegion().getArgument(1);
|
|
}
|
|
BlockArgument getAtomicReductionLhsArg() {
|
|
auto ®ion = getAtomicReductionRegion();
|
|
return region.empty() ? nullptr : region.getArgument(0);
|
|
}
|
|
BlockArgument getAtomicReductionRhsArg() {
|
|
auto ®ion = getAtomicReductionRegion();
|
|
return region.empty() ? nullptr : region.getArgument(1);
|
|
}
|
|
BlockArgument getCleanupAllocArg() {
|
|
auto ®ion = getCleanupRegion();
|
|
return region.empty() ? nullptr : region.getArgument(0);
|
|
}
|
|
BlockArgument getDataPtrPtrRegionArg() {
|
|
auto ®ion = getDataPtrPtrRegion();
|
|
return region.empty() ? nullptr : region.getArgument(0);
|
|
}
|
|
|
|
PointerLikeType getAccumulatorType() {
|
|
if (getAtomicReductionRegion().empty())
|
|
return {};
|
|
|
|
return cast<PointerLikeType>(getAtomicReductionLhsArg().getType());
|
|
}
|
|
}];
|
|
let hasRegionVerifier = 1;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// [Spec 5.2] 10.5 masked Construct
|
|
//===----------------------------------------------------------------------===//
|
|
def MaskedOp : OpenMP_Op<"masked", clauses = [
|
|
OpenMP_FilterClause
|
|
], singleRegion = 1> {
|
|
let summary = "masked construct";
|
|
let description = [{
|
|
Masked construct allows to specify a structured block to be executed by a subset of
|
|
threads of the current team.
|
|
}] # clausesDescription;
|
|
|
|
let builders = [
|
|
OpBuilder<(ins CArg<"const MaskedOperands &">:$clauses)>
|
|
];
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// [Spec 5.2] 6.5 allocate Directive
|
|
//===----------------------------------------------------------------------===//
|
|
def AllocateDirOp : OpenMP_Op<"allocate_dir", [AttrSizedOperandSegments], clauses = [
|
|
OpenMP_AlignClause, OpenMP_AllocatorClause
|
|
]> {
|
|
let summary = "allocate directive";
|
|
let description = [{
|
|
The storage for each list item that appears in the allocate directive is
|
|
provided an allocation through the memory allocator.
|
|
}] # clausesDescription;
|
|
|
|
let arguments = !con((ins Variadic<AnyType>:$varList),
|
|
clausesArgs);
|
|
|
|
// Override inherited assembly format to include `varList`.
|
|
let assemblyFormat = " `(` $varList `:` type($varList) `)` oilist(" #
|
|
clausesOptAssemblyFormat #
|
|
") attr-dict ";
|
|
|
|
let hasVerifier = 1;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// AllocateFreeOp
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
def AllocateFreeOp : OpenMP_Op<"allocate_free", [AttrSizedOperandSegments],
|
|
clauses = [OpenMP_AllocatorClause]> {
|
|
let summary = "free-op paired with allocate directive";
|
|
let description = [{
|
|
At the end of the scope each list item allocated using allocate directive
|
|
should be deallocated(using this free operation).
|
|
}] # clausesDescription;
|
|
|
|
let arguments = !con((ins Variadic<AnyType>:$varList),
|
|
clausesArgs);
|
|
|
|
let assemblyFormat = " `(` $varList `:` type($varList) `)` oilist(" #
|
|
clausesOptAssemblyFormat #
|
|
") attr-dict ";
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// TargetAllocMemOp
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
def TargetAllocMemOp : OpenMP_Op<"target_allocmem", traits = [
|
|
AttrSizedOperandSegments
|
|
], clauses = [
|
|
OpenMP_HeapAllocClause
|
|
]> {
|
|
let summary = "allocate storage on an openmp device for an object of a given type";
|
|
|
|
let description = [{
|
|
Allocates memory on the specified OpenMP device for an object of the given
|
|
type. Returns an integer value representing the device pointer to the
|
|
allocated memory. The memory is uninitialized after allocation. Operations
|
|
must be paired with `omp.target_freemem` to avoid memory leaks.
|
|
|
|
```mlir
|
|
// Allocate a static 3x3 integer vector on device 0
|
|
%device_0 = arith.constant 0 : i32
|
|
%ptr_static = omp.target_allocmem %device_0 : i32, vector<3x3xi32>
|
|
// ... use %ptr_static ...
|
|
omp.target_freemem %device_0, %ptr_static : i32, i64
|
|
|
|
// Allocate a dynamic 2D Fortran array (fir.array) on device 1
|
|
%device_1 = arith.constant 1 : i32
|
|
%rows = arith.constant 10 : index
|
|
%cols = arith.constant 20 : index
|
|
%ptr_dynamic = omp.target_allocmem %device_1 : i32, !fir.array<?x?xf32>, %rows, %cols : index, index
|
|
// ... use %ptr_dynamic ...
|
|
omp.target_freemem %device_1, %ptr_dynamic : i32, i64
|
|
```
|
|
|
|
The `device` is an integer ID of the OpenMP device where the memory will be
|
|
allocated.
|
|
}] # clausesDescription;
|
|
|
|
let arguments = !con((ins Arg<AnyInteger>:$device), clausesArgs);
|
|
let results = (outs I64);
|
|
|
|
// Override inherited assembly format to include `device`.
|
|
let assemblyFormat = " $device `:` type($device) `,` "
|
|
# clausesReqAssemblyFormat # " attr-dict";
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// TargetFreeMemOp
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
def TargetFreeMemOp : OpenMP_Op<"target_freemem",
|
|
[MemoryEffects<[MemFree]>]> {
|
|
let summary = "free memory on an openmp device";
|
|
|
|
let description = [{
|
|
Deallocates memory on the specified OpenMP device that was previously
|
|
allocated by an `omp.target_allocmem` operation. After this operation, the
|
|
deallocated memory is in an undefined state and should not be accessed.
|
|
It is crucial to ensure that all accesses to the memory region are completed
|
|
before `omp.target_freemem` is called to avoid undefined behavior.
|
|
|
|
* `$device`: The integer ID of the OpenMP device from which the memory will be freed.
|
|
* `$heapref`: The integer value representing the device pointer to the memory
|
|
to be deallocated, which was previously returned by `omp.target_allocmem`.
|
|
|
|
```mlir
|
|
// Example of allocating and freeing memory on an OpenMP device
|
|
%device_id = arith.constant 0 : i32
|
|
%allocated_ptr = omp.target_allocmem %device_id : i32, vector<3x3xi32>
|
|
// ... operations using %allocated_ptr on the device ...
|
|
omp.target_freemem %device_id, %allocated_ptr : i32, i64
|
|
```
|
|
}];
|
|
|
|
let arguments = (ins
|
|
Arg<AnyInteger, "", [MemFree]>:$device,
|
|
Arg<I64, "", [MemFree]>:$heapref
|
|
);
|
|
let assemblyFormat = "$device `,` $heapref attr-dict `:` type($device) `,` qualified(type($heapref))";
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// AllocSharedMemOp
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
def AllocSharedMemOp : OpenMP_Op<"alloc_shared_mem", traits = [
|
|
MemoryEffects<[MemAlloc<DefaultResource>]>
|
|
], clauses = [
|
|
OpenMP_MemAllocationSizeClause
|
|
]> {
|
|
let summary = "allocate storage on shared memory for objects of a given type";
|
|
|
|
let description = [{
|
|
Allocates memory shared across threads of a team for an object of the given
|
|
type. Returns a pointer representing the allocated memory. The memory is
|
|
uninitialized after allocation. Operations must be paired with
|
|
`omp.free_shared` to avoid memory leaks.
|
|
|
|
```mlir
|
|
// Allocate an i32 vector with %size elements and aligned to 8 bytes.
|
|
%ptr_shared = omp.alloc_shared_mem %size x i32 : (i64) align(8) -> !llvm.ptr
|
|
// ...
|
|
omp.free_shared_mem [%size x i32 : (i64) align(8)] %ptr_shared : !llvm.ptr
|
|
```
|
|
}] # clausesDescription;
|
|
|
|
let results = (outs OpenMP_PointerLikeType);
|
|
let assemblyFormat = clausesReqAssemblyFormat # " oilist(" #
|
|
clausesOptAssemblyFormat # ") `->` type(results) attr-dict";
|
|
|
|
let extraClassDeclaration = [{
|
|
mlir::Type getAllocatedType() { return getMemElemTypeAttr().getValue(); }
|
|
}];
|
|
let hasVerifier = 1;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// FreeSharedMemOp
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
def FreeSharedMemOp : OpenMP_Op<"free_shared_mem", traits = [
|
|
MemoryEffects<[MemFree]>
|
|
], clauses = [
|
|
OpenMP_MemAllocationSizeClause
|
|
]> {
|
|
let summary = "free shared memory";
|
|
|
|
let description = [{
|
|
Deallocates shared memory that was previously allocated by an
|
|
`omp.alloc_shared_mem` operation. After this operation, the deallocated
|
|
memory is in an undefined state and should not be accessed.
|
|
|
|
```mlir
|
|
// Example of allocating and freeing shared memory.
|
|
%ptr_shared = omp.alloc_shared_mem %size x i32 : (i64) -> !llvm.ptr
|
|
// ...
|
|
omp.free_shared_mem [%size x i32 : (i64)] %ptr_shared : !llvm.ptr
|
|
```
|
|
|
|
The `heapref` operand represents the pointer to shared memory to be
|
|
deallocated, previously returned by `omp.alloc_shared_mem`.
|
|
}] # clausesDescription;
|
|
|
|
let arguments = !con(clausesArgs, (ins
|
|
Arg<OpenMP_PointerLikeType, "", [MemFree]>:$heapref
|
|
));
|
|
let assemblyFormat = "` ` `[`" # clausesReqAssemblyFormat # " oilist(" #
|
|
clausesOptAssemblyFormat # ") `]` $heapref `:` type($heapref) attr-dict";
|
|
let hasVerifier = 1;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// workdistribute Construct
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
def WorkdistributeOp : OpenMP_Op<"workdistribute"> {
|
|
let summary = "workdistribute directive";
|
|
let description = [{
|
|
workdistribute divides execution of the enclosed structured block into
|
|
separate units of work, each executed only once by each
|
|
initial thread in the league.
|
|
```
|
|
!$omp target teams
|
|
!$omp workdistribute
|
|
y = a * x + y
|
|
!$omp end workdistribute
|
|
!$omp end target teams
|
|
```
|
|
}];
|
|
let regions = (region AnyRegion:$region);
|
|
let hasVerifier = 1;
|
|
let assemblyFormat = "$region attr-dict";
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// declare simd Construct
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
def DeclareSimdOp
|
|
: OpenMP_Op<"declare_simd", traits = [AttrSizedOperandSegments],
|
|
clauses = [OpenMP_AlignedClause, OpenMP_InbranchClause,
|
|
OpenMP_LinearClause, OpenMP_NotinbranchClause,
|
|
OpenMP_SimdlenClause, OpenMP_UniformClause]> {
|
|
let summary = "declare simd directive";
|
|
let description = [{
|
|
"omp.declare_simd" models the OpenMP `declare simd` directive.
|
|
|
|
This is a declarative operation (no region) intended to appear inside
|
|
a function body. It attaches clauses of declare simd to the enclosing
|
|
function.
|
|
|
|
Example:
|
|
```mlir
|
|
func.func @add(%a: memref<16xi32>) {
|
|
omp.declare_simd simdlen(8) aligned(%a : memref<16xi32> -> 64 : i64)
|
|
...
|
|
}
|
|
```
|
|
}] # clausesDescription;
|
|
|
|
let builders = [OpBuilder<(
|
|
ins CArg<"const DeclareSimdOperands &">:$clauses)>];
|
|
|
|
let hasVerifier = 1;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Affinity Entry Op
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
def AffinityEntryOp : OpenMP_Op<"affinity_entry", [Pure]> {
|
|
let summary = "OpenMP affinity clause entry value";
|
|
|
|
let arguments = (ins OpenMP_PointerLikeType:$addr, IntLikeType:$len);
|
|
|
|
let results = (outs OpenMP_AffinityEntryType:$entry);
|
|
|
|
let assemblyFormat = [{
|
|
$addr `,` $len `:` `(` type($addr) `,` type($len) `)` `->`
|
|
qualified(type($entry)) attr-dict
|
|
}];
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Iterator Op
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
def IteratorOp : OpenMP_Op<"iterator", [AttrSizedOperandSegments, SingleBlock],
|
|
clauses = [OpenMP_LoopRelatedClause]> {
|
|
let summary = "OpenMP iterator modifier";
|
|
let description = [{
|
|
The result of `omp.iterator` is an abstract handle of type
|
|
`!omp.iterated<T>`, representing the list of yielded values. This handle
|
|
can be directly consumed by OpenMP clauses that accept iterator modifiers,
|
|
such as `affinity`, `map`, `to`, `from`, or `depend`.
|
|
|
|
Example:
|
|
%it = omp.iterator(%i, %j) =
|
|
(%lb_i to %ub_i step %st_i, %lb_j to %ub_j step %st_j) {
|
|
%addr = ...
|
|
omp.yield(%addr)
|
|
} -> !omp.iterated<ptr>
|
|
}];
|
|
|
|
let regions = (region SizedRegion<1>:$region);
|
|
let results = (outs OpenMP_IteratedType:$iterated);
|
|
|
|
let assemblyFormat = [{
|
|
`(` custom<IteratorHeader>($region,
|
|
$loop_lower_bounds, $loop_upper_bounds, $loop_steps,
|
|
type($loop_lower_bounds), type($loop_upper_bounds), type($loop_steps))
|
|
`->` qualified(type($iterated)) attr-dict
|
|
}];
|
|
|
|
let hasVerifier = 1;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// [6.0] groupprivate Directive
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
def GroupprivateOp : OpenMP_Op<"groupprivate",
|
|
[Pure, DeclareOpInterfaceMethods<SymbolUserOpInterface>]> {
|
|
let summary = "groupprivate directive";
|
|
let description = [{
|
|
The groupprivate directive specifies that variables are replicated, with
|
|
each group having its own copy.
|
|
|
|
This operation takes a symbol reference to a global variable and returns
|
|
the address of its groupprivate copy. The referenced symbol must exist and
|
|
must not be a function.
|
|
|
|
The optional `device_type` attribute specifies where the groupprivate
|
|
storage should be allocated (host, nohost, or any).
|
|
}];
|
|
|
|
let arguments = (ins
|
|
FlatSymbolRefAttr:$sym_name,
|
|
OptionalAttr<DeclareTargetDeviceTypeAttr>:$device_type
|
|
);
|
|
let results = (outs OpenMP_PointerLikeType:$gp_addr);
|
|
let assemblyFormat = [{
|
|
$sym_name (`device_type` $device_type^)? `:` type($gp_addr) attr-dict
|
|
}];
|
|
}
|
|
|
|
#endif // OPENMP_OPS
|