//===-- 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/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
information about how to create an instance of the type in the alloc region,
how to initialize the copy from the original item in the copy region, and if
needed, how to deallocate allocated memory in the dealloc region.
Examples:
* `private(x)` would be emitted as:
```mlir
omp.private {type = private} @x.privatizer : !fir.ref<i32> alloc {
^bb0(%arg0: !fir.ref<i32>):
%0 = ... allocate proper memory for the private clone ...
omp.yield(%0 : !fir.ref<i32>)
}
```
* `firstprivate(x)` would be emitted as:
```mlir
omp.private {type = firstprivate} @x.privatizer : !fir.ref<i32> alloc {
^bb0(%arg0: !fir.ref<i32>):
%0 = ... allocate proper memory for the private clone ...
omp.yield(%0 : !fir.ref<i32>)
} copy {
^bb0(%arg0: !fir.ref<i32>, %arg1: !fir.ref<i32>):
// %arg0 is the original host variable. Same as for `alloc`.
// %arg1 represents the memory allocated in `alloc`.
... 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 alloc {
^bb0(%arg0: !some.type):
%0 = ... allocate proper memory for the private clone ...
omp.yield(%0 : !fir.ref<i32>)
} dealloc {
^bb0(%arg0: !some.type):
... deallocate allocated memory ...
omp.yield
}
```
There are no restrictions on the body except for:
- The `alloc` & `dealloc` regions have a single argument.
- The `copy` region has 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.
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 MinSizedRegion<1>:$alloc_region,
AnyRegion:$copy_region,
AnyRegion:$dealloc_region);
let assemblyFormat = [{
$data_sharing_type $sym_name `:` $type
`alloc` $alloc_region
(`copy` $copy_region^)?
(`dealloc` $dealloc_region^)?
attr-dict
}];
let builders = [
OpBuilder<(ins CArg<"TypeRange">:$result,
CArg<"StringAttr">:$sym_name,
CArg<"TypeAttr">:$type)>
];
let hasVerifier = 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, $reduction_vars, type($reduction_vars), $reduction_byref,
$reduction_syms) attr-dict
}];
let hasVerifier = 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
], clauses = [
OpenMP_AllocateClause, 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, $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.
unsigned numPrivateBlockArgs();
unsigned numReductionBlockArgs();
}] # 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, $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) attr-dict
}];
let hasVerifier = 1;
}
//===----------------------------------------------------------------------===//
// Loop Nest
//===----------------------------------------------------------------------===//
def LoopNestOp : OpenMP_Op<"loop_nest", traits = [
RecursiveMemoryEffects, SameVariadicOperandSize
], clauses = [
OpenMP_LoopRelatedClause
], singleRegion = true> {
let summary = "rectangular loop nest";
let description = [{
This operation represents a collapsed rectangular loop nest. 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 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) {
%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` 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 WsloopOp : OpenMP_Op<"wsloop", traits = [
AttrSizedOperandSegments,
DeclareOpInterfaceMethods<ComposableOpInterface>,
DeclareOpInterfaceMethods<LoopWrapperInterface>,
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 and a terminator. The 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
}
omp.terminator
}
```
}] # 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, $reduction_vars, type($reduction_vars), $reduction_byref,
$reduction_syms) attr-dict
}];
let hasVerifier = 1;
}
//===----------------------------------------------------------------------===//
// Simd construct [2.9.3.1]
//===----------------------------------------------------------------------===//
def SimdOp : OpenMP_Op<"simd", traits = [
AttrSizedOperandSegments,
DeclareOpInterfaceMethods<ComposableOpInterface>,
DeclareOpInterfaceMethods<LoopWrapperInterface>,
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 and a terminator. The 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
}
omp.terminator
}
```
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, $reduction_vars, type($reduction_vars), $reduction_byref,
$reduction_syms) attr-dict
}];
let hasVerifier = 1;
}
def YieldOp : OpenMP_Op<"yield",
[Pure, ReturnLike, Terminator,
ParentOneOf<["AtomicUpdateOp", "DeclareReductionOp", "LoopNestOp",
"PrivateClauseOp"]>]> {
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>,
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 and a terminator. The 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
}
omp.terminator
}
```
}] # clausesDescription;
let builders = [
OpBuilder<(ins CArg<"const DistributeOperands &">:$clauses)>
];
let assemblyFormat = clausesAssemblyFormat # [{
custom<PrivateRegion>($region, $private_vars, type($private_vars),
$private_syms) attr-dict
}];
let hasVerifier = 1;
}
//===----------------------------------------------------------------------===//
// 2.10.1 task Construct
//===----------------------------------------------------------------------===//
def TaskOp : OpenMP_Op<"task", traits = [
AttrSizedOperandSegments, AutomaticAllocationScope,
OutlineableOpenMPOpInterface
], clauses = [
// TODO: Complete clause list (affinity, detach).
OpenMP_AllocateClause, OpenMP_DependClause, OpenMP_FinalClause,
OpenMP_IfClause, OpenMP_InReductionClause, OpenMP_MergeableClause,
OpenMP_PriorityClause, OpenMP_PrivateClause, OpenMP_UntiedClause
], 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) attr-dict
}];
let hasVerifier = 1;
}
def TaskloopOp : OpenMP_Op<"taskloop", traits = [
AttrSizedOperandSegments, AutomaticAllocationScope,
DeclareOpInterfaceMethods<ComposableOpInterface>,
DeclareOpInterfaceMethods<LoopWrapperInterface>,
RecursiveMemoryEffects, SingleBlock
], clauses = [
OpenMP_AllocateClause, OpenMP_FinalClause, OpenMP_GrainsizeClause,
OpenMP_IfClause, OpenMP_InReductionClauseSkip<extraClassDeclaration = true>,
OpenMP_MergeableClause, OpenMP_NogroupClause, OpenMP_NumTasksClause,
OpenMP_PriorityClause, OpenMP_PrivateClause,
OpenMP_ReductionClauseSkip<extraClassDeclaration = true>,
OpenMP_UntiedClause
], 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 body region can only contain a single block which must contain a single
operation and a terminator. The operation must be another compatible loop
wrapper or an `omp.loop_nest`.
```
omp.taskloop <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
}
omp.terminator
}
```
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 TaskloopOperands &">:$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, $reduction_vars,
type($reduction_vars), $reduction_byref, $reduction_syms) attr-dict
}];
let extraClassDeclaration = [{
/// Returns the reduction variables
SmallVector<Value> getAllReductionVars();
// Define BlockArgOpenMPOpInterface methods here because they are not
// inherited from the respective clauses.
unsigned numInReductionBlockArgs() { return getInReductionVars().size(); }
unsigned numReductionBlockArgs() { return getReductionVars().size(); }
void getEffects(SmallVectorImpl<MemoryEffects::EffectInstance> &effects);
}] # clausesExtraClassDeclaration;
let hasVerifier = 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<OpenMP_PointerLikeType>:$varList),
clausesArgs);
// Override inherited assembly format to include `varList`.
let assemblyFormat = "( `(` $varList^ `:` type($varList) `)` )? attr-dict";
let extraClassDeclaration = [{
/// The number of variable operands.
unsigned getNumVariableOperands() {
return getOperation()->getNumOperands();
}
/// The i-th variable operand passed.
Value getVariableOperand(unsigned i) {
return getOperand(i);
}
}] # clausesExtraClassDeclaration;
}
//===----------------------------------------------------------------------===//
// 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 extraClassDeclaration = [{
/// The number of variable operands.
unsigned getNumVariableOperands() {
return getNumOperands();
}
/// The i-th variable operand passed.
Value getVariableOperand(unsigned i) {
return getOperands()[i];
}
}];
let hasVerifier = 1;
}
def MapInfoOp : OpenMP_Op<"map.info", [AttrSizedOperandSegments]> {
let arguments = (ins OpenMP_PointerLikeType:$var_ptr,
TypeAttr:$var_type,
Optional<OpenMP_PointerLikeType>:$var_ptr_ptr,
Variadic<OpenMP_PointerLikeType>:$members,
OptionalAttr<AnyIntElementsAttr>:$members_index,
Variadic<OpenMP_MapBoundsType>:$bounds, /* rank-0 to rank-{n-1} */
OptionalAttr<UI64Attr>:$map_type,
OptionalAttr<VariableCaptureKindAttr>:$map_capture_type,
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) map_type(to) map_capture_type(ByRef)
name(index)
```
Description of arguments:
- `var_ptr`: The address of variable to copy.
- `var_type`: The type of the variable to copy.
- `var_ptr_ptr`: Used when the variable copied is a member of a class, structure
or derived type and refers to the originating struct.
- `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.
- 'map_clauses': 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.
- `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_type `)`
oilist(
`var_ptr_ptr` `(` $var_ptr_ptr `:` type($var_ptr_ptr) `)`
| `map_clauses` `(` custom<MapClause>($map_type) `)`
| `capture` `(` custom<CaptureType>($map_capture_type) `)`
| `members` `(` $members `:` custom<MembersIndex>($members_index) `:` type($members) `)`
| `bounds` `(` $bounds `)`
) `->` type($omp_ptr) attr-dict
}];
let extraClassDeclaration = [{
/// The number of variable operands.
unsigned getNumVariableOperands() {
return getNumOperands();
}
/// The i-th variable operand passed.
Value getVariableOperand(unsigned i) {
return getOperands()[i];
}
}];
}
//===---------------------------------------------------------------------===//
// 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 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 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 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 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_DependClause, OpenMP_DeviceClause,
OpenMP_HasDeviceAddrClause, 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.
}] # clausesDescription;
let builders = [
OpBuilder<(ins CArg<"const TargetOperands &">:$clauses)>
];
let extraClassDeclaration = [{
unsigned numMapBlockArgs() { return getMapVars().size(); }
}] # clausesExtraClassDeclaration;
let assemblyFormat = clausesAssemblyFormat # [{
custom<InReductionMapPrivateRegion>(
$region, $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) attr-dict
}];
let hasVerifier = 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", 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 = [
AllTypesMatch<["x", "v"]>, 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($x) `,` $element_type attr-dict";
let extraClassDeclaration = [{
/// The number of variable operands.
unsigned getNumVariableOperands() {
assert(getX() && "expected 'x' operand");
assert(getV() && "expected 'v' operand");
return 2;
}
/// The i-th variable operand passed.
Value getVariableOperand(unsigned i) {
assert(i < 2 && "invalid index position for an operand");
return i == 0 ? getX() : getV();
}
}] # clausesExtraClassDeclaration;
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 extraClassDeclaration = [{
/// The number of variable operands.
unsigned getNumVariableOperands() {
assert(getX() && "expected address operand");
assert(getExpr() && "expected value operand");
return 2;
}
/// The i-th variable operand passed.
Value getVariableOperand(unsigned i) {
assert(i < 2 && "invalid index position for an operand");
return i == 0 ? getX() : getExpr();
}
}] # clausesExtraClassDeclaration;
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), clausesArgs);
// Override region definition.
let regions = (region SizedRegion<1>:$region);
// Override clause-based assemblyFormat.
let assemblyFormat = clausesAssemblyFormat #
"$x `:` type($x) $region attr-dict";
let extraClassDeclaration = [{
/// The number of variable operands.
unsigned getNumVariableOperands() {
assert(getX() && "expected 'x' operand");
return 1;
}
/// The i-th variable operand passed.
Value getVariableOperand(unsigned i) {
assert(i == 0 && "invalid index position for an operand");
return getX();
}
}] # clausesExtraClassDeclaration;
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.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
}];
let extraClassDeclaration = [{
/// The number of variable operands.
unsigned getNumVariableOperands() {
assert(getSymAddr() && "expected one variable operand");
return 1;
}
/// The i-th variable operand passed.
Value getVariableOperand(unsigned i) {
assert(i == 0 && "invalid index position for an operand");
return getSymAddr();
}
}];
}
//===----------------------------------------------------------------------===//
// 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;
}
//===----------------------------------------------------------------------===//
// 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 three
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.
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.
}];
let arguments = (ins SymbolNameAttr:$sym_name,
TypeAttr:$type);
let regions = (region MaxSizedRegion<1>:$allocRegion,
AnyRegion:$initializerRegion,
AnyRegion:$reductionRegion,
AnyRegion:$atomicReductionRegion,
AnyRegion:$cleanupRegion);
let assemblyFormat = "$sym_name `:` $type attr-dict-with-keyword "
"( `alloc` $allocRegion^ )? "
"`init` $initializerRegion "
"`combiner` $reductionRegion "
"( `atomic` $atomicReductionRegion^ )? "
"( `cleanup` $cleanupRegion^ )? ";
let extraClassDeclaration = [{
PointerLikeType getAccumulatorType() {
if (getAtomicReductionRegion().empty())
return {};
return cast<PointerLikeType>(getAtomicReductionRegion().front().getArgument(0).getType());
}
Value getInitializerMoldArg() {
return getInitializerRegion().front().getArgument(0);
}
Value getInitializerAllocArg() {
if (getAllocRegion().empty() ||
getInitializerRegion().front().getNumArguments() != 2)
return {nullptr};
return getInitializerRegion().front().getArgument(1);
}
}];
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)>
];
}
#endif // OPENMP_OPS