| //===-- 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/IR/EnumAttr.td" |
| include "mlir/IR/OpBase.td" |
| include "mlir/Interfaces/SideEffectInterfaces.td" |
| include "mlir/Interfaces/ControlFlowInterfaces.td" |
| include "mlir/IR/SymbolInterfaces.td" |
| include "mlir/Dialect/LLVMIR/LLVMOpBase.td" |
| include "mlir/Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.td" |
| include "mlir/Dialect/OpenMP/OpenMPOpsInterfaces.td" |
| include "mlir/Dialect/OpenMP/OpenMPTypeInterfaces.td" |
| |
| def OpenMP_Dialect : Dialect { |
| let name = "omp"; |
| let cppNamespace = "::mlir::omp"; |
| let dependentDialects = ["::mlir::LLVM::LLVMDialect, ::mlir::func::FuncDialect"]; |
| let useDefaultAttributePrinterParser = 1; |
| let useDefaultTypePrinterParser = 1; |
| } |
| |
| // OmpCommon requires definition of OpenACC_Dialect. |
| include "mlir/Dialect/OpenMP/OmpCommon.td" |
| |
| //===----------------------------------------------------------------------===// |
| // OpenMP Attributes |
| //===----------------------------------------------------------------------===// |
| |
| class OpenMP_Attr<string name, string attrMnemonic, |
| list<Trait> traits = [], |
| string baseCppClass = "::mlir::Attribute"> |
| : AttrDef<OpenMP_Dialect, name, traits, baseCppClass> { |
| let mnemonic = attrMnemonic; |
| } |
| |
| def VersionAttr : OpenMP_Attr<"Version", "version"> { |
| let parameters = (ins |
| "uint32_t":$version |
| ); |
| |
| let assemblyFormat = "`<` struct(params) `>`"; |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // Runtime library flag's attribute that holds information for lowering to LLVM |
| //===----------------------------------------------------------------------===// |
| |
| def FlagsAttr : OpenMP_Attr<"Flags", "flags"> { |
| let parameters = (ins |
| DefaultValuedParameter<"uint32_t", "0">:$debug_kind, |
| DefaultValuedParameter<"bool", "false">:$assume_teams_oversubscription, |
| DefaultValuedParameter<"bool", "false">:$assume_threads_oversubscription, |
| DefaultValuedParameter<"bool", "false">:$assume_no_thread_state, |
| DefaultValuedParameter<"bool", "false">:$assume_no_nested_parallelism, |
| DefaultValuedParameter<"bool", "false">:$no_gpu_lib, |
| DefaultValuedParameter<"uint32_t", "50">:$openmp_device_version |
| ); |
| |
| let assemblyFormat = "`<` struct(params) `>`"; |
| } |
| |
| |
| class OpenMP_Op<string mnemonic, list<Trait> traits = []> : |
| Op<OpenMP_Dialect, mnemonic, traits>; |
| |
| // Type which can be constraint accepting standard integers and indices. |
| def IntLikeType : AnyTypeOf<[AnyInteger, Index]>; |
| |
| def OpenMP_PointerLikeType : TypeAlias<OpenMP_PointerLikeTypeInterface, |
| "OpenMP-compatible variable type">; |
| |
| class OpenMP_Type<string name, string typeMnemonic> : TypeDef<OpenMP_Dialect, name> { |
| let mnemonic = typeMnemonic; |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // 2.12.7 Declare Target Directive |
| //===----------------------------------------------------------------------===// |
| |
| def DeviceTypeAny : I32EnumAttrCase<"any", 0>; |
| def DeviceTypeHost : I32EnumAttrCase<"host", 1>; |
| def DeviceTypeNoHost : I32EnumAttrCase<"nohost", 2>; |
| |
| def DeclareTargetDeviceType : I32EnumAttr< |
| "DeclareTargetDeviceType", |
| "device_type clause", |
| [DeviceTypeAny, DeviceTypeHost, DeviceTypeNoHost]> { |
| let genSpecializedAttr = 0; |
| let cppNamespace = "::mlir::omp"; |
| } |
| |
| def DeclareTargetDeviceTypeAttr : EnumAttr<OpenMP_Dialect, DeclareTargetDeviceType, |
| "device_type"> { |
| let assemblyFormat = "`(` $value `)`"; |
| } |
| |
| def CaptureClauseTo : I32EnumAttrCase<"to", 0>; |
| def CaptureClauseLink : I32EnumAttrCase<"link", 1>; |
| def CaptureClauseEnter : I32EnumAttrCase<"enter", 2>; |
| |
| def DeclareTargetCaptureClause : I32EnumAttr< |
| "DeclareTargetCaptureClause", |
| "capture clause", |
| [CaptureClauseTo, CaptureClauseLink, CaptureClauseEnter]> { |
| let genSpecializedAttr = 0; |
| let cppNamespace = "::mlir::omp"; |
| } |
| |
| def DeclareTargetCaptureClauseAttr : EnumAttr<OpenMP_Dialect, DeclareTargetCaptureClause, |
| "capture_clause"> { |
| let assemblyFormat = "`(` $value `)`"; |
| } |
| |
| def DeclareTargetAttr : OpenMP_Attr<"DeclareTarget", "declaretarget"> { |
| let parameters = (ins |
| OptionalParameter<"DeclareTargetDeviceTypeAttr">:$device_type, |
| OptionalParameter<"DeclareTargetCaptureClauseAttr">:$capture_clause |
| ); |
| |
| let assemblyFormat = "`<` struct(params) `>`"; |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // 2.19.4 Data-Sharing Attribute Clauses |
| //===----------------------------------------------------------------------===// |
| |
| def DataSharingTypePrivate : I32EnumAttrCase<"Private", 0, "private">; |
| def DataSharingTypeFirstPrivate : I32EnumAttrCase<"FirstPrivate", 1, "firstprivate">; |
| |
| def DataSharingClauseType : I32EnumAttr< |
| "DataSharingClauseType", |
| "Type of a data-sharing clause", |
| [DataSharingTypePrivate, DataSharingTypeFirstPrivate]> { |
| let genSpecializedAttr = 0; |
| let cppNamespace = "::mlir::omp"; |
| } |
| |
| def DataSharingClauseTypeAttr : EnumAttr< |
| OpenMP_Dialect, DataSharingClauseType, "data_sharing_type"> { |
| let assemblyFormat = "`{` `type` `=` $value `}`"; |
| } |
| |
| def PrivateClauseOp : OpenMP_Op<"private", [IsolatedFromAbove]> { |
| 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", [ |
| AutomaticAllocationScope, AttrSizedOperandSegments, |
| DeclareOpInterfaceMethods<LoopWrapperInterface>, |
| DeclareOpInterfaceMethods<OutlineableOpenMPOpInterface>, |
| RecursiveMemoryEffects, ReductionClauseInterface]> { |
| 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_var 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. |
| |
| The optional $num_threads_var parameter specifies the number of threads which |
| should be used to execute the parallel region. |
| |
| The $allocators_vars and $allocate_vars parameters are a variadic list of values |
| that specify the memory allocator to be used to obtain storage for private values. |
| |
| Reductions can be performed in a parallel construct by specifying reduction |
| accumulator variables in `reduction_vars` and symbols referring to reduction |
| declarations in the `reductions` attribute. Each reduction is identified |
| by the accumulator it uses and accumulators must not be repeated in the same |
| reduction. The `omp.reduction` operation accepts the accumulator and a |
| partial value which is considered to be produced by the thread for the |
| given reduction. If multiple values are produced for the same accumulator, |
| i.e. there are multiple `omp.reduction`s, the last value is taken. The |
| reduction declaration specifies how to combine the values from each thread |
| into the final value, which is available in the accumulator after all the |
| threads complete. |
| |
| The optional $proc_bind_val attribute controls the thread affinity for the execution |
| of the parallel region. |
| |
| The optional byref attribute controls whether reduction arguments are passed by |
| reference or by value. |
| }]; |
| |
| let arguments = (ins Optional<I1>:$if_expr_var, |
| Optional<IntLikeType>:$num_threads_var, |
| Variadic<AnyType>:$allocate_vars, |
| Variadic<AnyType>:$allocators_vars, |
| Variadic<OpenMP_PointerLikeType>:$reduction_vars, |
| OptionalAttr<SymbolRefArrayAttr>:$reductions, |
| OptionalAttr<ProcBindKindAttr>:$proc_bind_val, |
| Variadic<AnyType>:$private_vars, |
| OptionalAttr<SymbolRefArrayAttr>:$privatizers, |
| UnitAttr:$byref); |
| |
| let regions = (region AnyRegion:$region); |
| |
| let builders = [ |
| OpBuilder<(ins CArg<"ArrayRef<NamedAttribute>", "{}">:$attributes)>, |
| OpBuilder<(ins CArg<"const ParallelClauseOps &">:$clauses)> |
| ]; |
| let extraClassDeclaration = [{ |
| /// Returns the number of reduction variables. |
| unsigned getNumReductionVars() { return getReductionVars().size(); } |
| }]; |
| let assemblyFormat = [{ |
| oilist( |
| `if` `(` $if_expr_var `:` type($if_expr_var) `)` |
| | `num_threads` `(` $num_threads_var `:` type($num_threads_var) `)` |
| | `allocate` `(` |
| custom<AllocateAndAllocator>( |
| $allocate_vars, type($allocate_vars), |
| $allocators_vars, type($allocators_vars) |
| ) `)` |
| | `proc_bind` `(` custom<ClauseAttr>($proc_bind_val) `)` |
| | `byref` $byref |
| ) custom<ParallelRegion>($region, $reduction_vars, type($reduction_vars), |
| $reductions, $private_vars, type($private_vars), |
| $privatizers) 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", [ |
| AttrSizedOperandSegments, RecursiveMemoryEffects, |
| ReductionClauseInterface]> { |
| 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. |
| |
| The optional $num_teams_upper and $num_teams_lower specify the limit on the |
| number of teams to be created. If only the upper bound is specified, it acts |
| as if the lower bound was set to the same value. It is not supported to set |
| $num_teams_lower if $num_teams_upper is not specified. They define a closed |
| range, where both the lower and upper bounds are included. |
| |
| If the $if_expr is present and it evaluates to `false`, the number of teams |
| created is one. |
| |
| The optional $thread_limit specifies the limit on the number of threads. |
| |
| The $allocators_vars and $allocate_vars parameters are a variadic list of |
| values that specify the memory allocator to be used to obtain storage for |
| private values. |
| }]; |
| |
| let arguments = (ins Optional<AnyInteger>:$num_teams_lower, |
| Optional<AnyInteger>:$num_teams_upper, |
| Optional<I1>:$if_expr, |
| Optional<AnyInteger>:$thread_limit, |
| Variadic<AnyType>:$allocate_vars, |
| Variadic<AnyType>:$allocators_vars, |
| Variadic<OpenMP_PointerLikeType>:$reduction_vars, |
| OptionalAttr<SymbolRefArrayAttr>:$reductions); |
| |
| let regions = (region AnyRegion:$region); |
| |
| let builders = [ |
| OpBuilder<(ins CArg<"const TeamsClauseOps &">:$clauses)> |
| ]; |
| |
| let assemblyFormat = [{ |
| oilist( |
| `num_teams` `(` ( $num_teams_lower^ `:` type($num_teams_lower) )? `to` |
| $num_teams_upper `:` type($num_teams_upper) `)` |
| | `if` `(` $if_expr `)` |
| | `thread_limit` `(` $thread_limit `:` type($thread_limit) `)` |
| | `reduction` `(` |
| custom<ReductionVarList>( |
| $reduction_vars, type($reduction_vars), $reductions |
| ) `)` |
| | `allocate` `(` |
| custom<AllocateAndAllocator>( |
| $allocate_vars, type($allocate_vars), |
| $allocators_vars, type($allocators_vars) |
| ) `)` |
| ) $region attr-dict |
| }]; |
| |
| let hasVerifier = 1; |
| } |
| |
| def OMP_ScheduleModNone : I32EnumAttrCase<"none", 0>; |
| def OMP_ScheduleModMonotonic : I32EnumAttrCase<"monotonic", 1>; |
| def OMP_ScheduleModNonmonotonic : I32EnumAttrCase<"nonmonotonic", 2>; |
| // FIXME: remove this value for the modifier because this is handled using a |
| // separate attribute |
| def OMP_ScheduleModSIMD : I32EnumAttrCase<"simd", 3>; |
| |
| def ScheduleModifier |
| : I32EnumAttr<"ScheduleModifier", "OpenMP Schedule Modifier", |
| [OMP_ScheduleModNone, OMP_ScheduleModMonotonic, |
| OMP_ScheduleModNonmonotonic, OMP_ScheduleModSIMD]> { |
| let genSpecializedAttr = 0; |
| let cppNamespace = "::mlir::omp"; |
| } |
| def ScheduleModifierAttr : EnumAttr<OpenMP_Dialect, ScheduleModifier, |
| "sched_mod">; |
| |
| //===----------------------------------------------------------------------===// |
| // 2.8.1 Sections Construct |
| //===----------------------------------------------------------------------===// |
| |
| def SectionOp : OpenMP_Op<"section", [HasParent<"SectionsOp">]> { |
| 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. |
| }]; |
| let regions = (region AnyRegion:$region); |
| let assemblyFormat = "$region attr-dict"; |
| } |
| |
| def SectionsOp : OpenMP_Op<"sections", [AttrSizedOperandSegments, |
| ReductionClauseInterface]> { |
| 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. |
| |
| Reductions can be performed in a sections construct by specifying reduction |
| accumulator variables in `reduction_vars` and symbols referring to reduction |
| declarations in the `reductions` attribute. Each reduction is identified |
| by the accumulator it uses and accumulators must not be repeated in the same |
| reduction. The `omp.reduction` operation accepts the accumulator and a |
| partial value which is considered to be produced by the section for the |
| given reduction. If multiple values are produced for the same accumulator, |
| i.e. there are multiple `omp.reduction`s, the last value is taken. The |
| reduction declaration specifies how to combine the values from each section |
| into the final value, which is available in the accumulator after all the |
| sections complete. |
| |
| The $allocators_vars and $allocate_vars parameters are a variadic list of values |
| that specify the memory allocator to be used to obtain storage for private values. |
| |
| The `nowait` attribute, when present, signifies that there should be no |
| implicit barrier at the end of the construct. |
| }]; |
| let arguments = (ins Variadic<OpenMP_PointerLikeType>:$reduction_vars, |
| OptionalAttr<SymbolRefArrayAttr>:$reductions, |
| Variadic<AnyType>:$allocate_vars, |
| Variadic<AnyType>:$allocators_vars, |
| UnitAttr:$nowait); |
| |
| let regions = (region SizedRegion<1>:$region); |
| |
| let builders = [ |
| OpBuilder<(ins CArg<"const SectionsClauseOps &">:$clauses)> |
| ]; |
| |
| let assemblyFormat = [{ |
| oilist( `reduction` `(` |
| custom<ReductionVarList>( |
| $reduction_vars, type($reduction_vars), $reductions |
| ) `)` |
| | `allocate` `(` |
| custom<AllocateAndAllocator>( |
| $allocate_vars, type($allocate_vars), |
| $allocators_vars, type($allocators_vars) |
| ) `)` |
| | `nowait` $nowait |
| ) $region attr-dict |
| }]; |
| |
| let hasVerifier = 1; |
| let hasRegionVerifier = 1; |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // 2.8.2 Single Construct |
| //===----------------------------------------------------------------------===// |
| |
| def SingleOp : OpenMP_Op<"single", [AttrSizedOperandSegments]> { |
| 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 unless a nowait clause is specified. |
| |
| If copyprivate variables and functions are specified, then each thread |
| variable is updated with the variable value of the thread that executed |
| the single region, using the specified copy functions. |
| }]; |
| |
| let arguments = (ins Variadic<AnyType>:$allocate_vars, |
| Variadic<AnyType>:$allocators_vars, |
| Variadic<OpenMP_PointerLikeType>:$copyprivate_vars, |
| OptionalAttr<SymbolRefArrayAttr>:$copyprivate_funcs, |
| UnitAttr:$nowait); |
| |
| let regions = (region AnyRegion:$region); |
| |
| let builders = [ |
| OpBuilder<(ins CArg<"const SingleClauseOps &">:$clauses)> |
| ]; |
| |
| let assemblyFormat = [{ |
| oilist(`allocate` `(` |
| custom<AllocateAndAllocator>( |
| $allocate_vars, type($allocate_vars), |
| $allocators_vars, type($allocators_vars) |
| ) `)` |
| |`nowait` $nowait |
| |`copyprivate` `(` |
| custom<CopyPrivateVarList>( |
| $copyprivate_vars, type($copyprivate_vars), $copyprivate_funcs |
| ) `)` |
| ) $region attr-dict |
| }]; |
| let hasVerifier = 1; |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // Loop Nest |
| //===----------------------------------------------------------------------===// |
| |
| def LoopNestOp : OpenMP_Op<"loop_nest", [SameVariadicOperandSize, |
| AllTypesMatch<["lowerBound", "upperBound", "step"]>, |
| RecursiveMemoryEffects]> { |
| 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 `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 `lowerBound`, `upperBound` and `step` |
| 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 arguments = (ins Variadic<IntLikeType>:$lowerBound, |
| Variadic<IntLikeType>:$upperBound, |
| Variadic<IntLikeType>:$step, |
| UnitAttr:$inclusive); |
| |
| let builders = [ |
| OpBuilder<(ins CArg<"const LoopNestClauseOps &">:$clauses)> |
| ]; |
| |
| let regions = (region AnyRegion:$region); |
| |
| let extraClassDeclaration = [{ |
| /// Returns the number of loops in the loop nest. |
| unsigned getNumLoops() { return getLowerBound().size(); } |
| |
| /// 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); |
| }]; |
| |
| let hasCustomAssemblyFormat = 1; |
| let hasVerifier = 1; |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // 2.9.2 Workshare Loop Construct |
| //===----------------------------------------------------------------------===// |
| |
| def WsloopOp : OpenMP_Op<"wsloop", [AttrSizedOperandSegments, |
| DeclareOpInterfaceMethods<LoopWrapperInterface>, |
| RecursiveMemoryEffects, ReductionClauseInterface, |
| SingleBlockImplicitTerminator<"TerminatorOp">]> { |
| 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 |
| } |
| ``` |
| |
| The `linear_step_vars` operand additionally specifies the step for each |
| associated linear operand. Note that the `linear_vars` and |
| `linear_step_vars` variadic lists should contain the same number of |
| elements. |
| |
| Reductions can be performed in a worksharing-loop by specifying reduction |
| accumulator variables in `reduction_vars` and symbols referring to reduction |
| declarations in the `reductions` attribute. Each reduction is identified |
| by the accumulator it uses and accumulators must not be repeated in the same |
| reduction. A private variable corresponding to the accumulator is used in |
| place of the accumulator inside the body of the worksharing-loop. The |
| reduction declaration specifies how to combine the values from each |
| iteration into the final value, which is available in the accumulator after |
| the loop completes. |
| |
| The optional `schedule_val` attribute specifies the loop schedule for this |
| loop, determining how the loop is distributed across the parallel threads. |
| The optional `schedule_chunk_var` associated with this determines further |
| controls this distribution. |
| |
| Collapsed loops are represented by the worksharing-loop having a list of |
| indices, bounds and steps where the size of the list is equal to the |
| collapse value. |
| |
| The `nowait` attribute, when present, signifies that there should be no |
| implicit barrier at the end of the loop. |
| |
| The optional `ordered_val` attribute specifies how many loops are associated |
| with the worksharing-loop construct. The value of zero refers to the ordered |
| clause specified without parameter. |
| |
| The optional `order` attribute specifies which order the iterations of the |
| associate loops are executed in. Currently the only option for this |
| attribute is "concurrent". |
| |
| The optional `byref` attribute indicates that reduction arguments should be |
| passed by reference. |
| }]; |
| |
| let arguments = (ins Variadic<AnyType>:$linear_vars, |
| Variadic<I32>:$linear_step_vars, |
| Variadic<OpenMP_PointerLikeType>:$reduction_vars, |
| OptionalAttr<SymbolRefArrayAttr>:$reductions, |
| OptionalAttr<ScheduleKindAttr>:$schedule_val, |
| Optional<AnyType>:$schedule_chunk_var, |
| OptionalAttr<ScheduleModifierAttr>:$schedule_modifier, |
| UnitAttr:$simd_modifier, |
| UnitAttr:$nowait, |
| UnitAttr:$byref, |
| ConfinedAttr<OptionalAttr<I64Attr>, [IntMinValue<0>]>:$ordered_val, |
| OptionalAttr<OrderKindAttr>:$order_val); |
| |
| let builders = [ |
| OpBuilder<(ins CArg<"ArrayRef<NamedAttribute>", "{}">:$attributes)>, |
| OpBuilder<(ins CArg<"const WsloopClauseOps &">:$clauses)> |
| ]; |
| |
| let regions = (region AnyRegion:$region); |
| |
| let extraClassDeclaration = [{ |
| /// Returns the number of reduction variables. |
| unsigned getNumReductionVars() { return getReductionVars().size(); } |
| }]; |
| let hasCustomAssemblyFormat = 1; |
| let assemblyFormat = [{ |
| oilist(`linear` `(` |
| custom<LinearClause>($linear_vars, type($linear_vars), |
| $linear_step_vars) `)` |
| |`schedule` `(` |
| custom<ScheduleClause>( |
| $schedule_val, $schedule_modifier, $simd_modifier, |
| $schedule_chunk_var, type($schedule_chunk_var)) `)` |
| |`nowait` $nowait |
| |`byref` $byref |
| |`ordered` `(` $ordered_val `)` |
| |`order` `(` custom<ClauseAttr>($order_val) `)` |
| ) custom<Wsloop>($region, $reduction_vars, type($reduction_vars), |
| $reductions) attr-dict |
| }]; |
| let hasVerifier = 1; |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // Simd construct [2.9.3.1] |
| //===----------------------------------------------------------------------===// |
| |
| def SimdOp : OpenMP_Op<"simd", [AttrSizedOperandSegments, |
| DeclareOpInterfaceMethods<LoopWrapperInterface>, |
| RecursiveMemoryEffects, |
| SingleBlockImplicitTerminator<"TerminatorOp">]> { |
| 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`. |
| |
| The `alignment_values` attribute additionally specifies alignment of each |
| corresponding aligned operand. Note that `$aligned_vars` and |
| `alignment_values` should contain the same number of elements. |
| |
| 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. |
| |
| The optional `nontemporal` attribute specifies variables which have low |
| temporal locality across the iterations where they are accessed. |
| |
| The optional `order` attribute specifies which order the iterations of the |
| associate loops are executed in. Currently the only option for this |
| attribute is "concurrent". |
| |
| When a simdlen clause is present, the preferred number of iterations to be |
| executed concurrently is the value provided to the simdlen clause. |
| |
| The safelen clause specifies that no two concurrent iterations within a |
| SIMD chunk can have a distance in the logical iteration space that is |
| greater than or equal to the value given in the clause. |
| ``` |
| 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 |
| } |
| ``` |
| }]; |
| |
| // TODO: Add other clauses |
| let arguments = (ins Variadic<OpenMP_PointerLikeType>:$aligned_vars, |
| OptionalAttr<I64ArrayAttr>:$alignment_values, |
| Optional<I1>:$if_expr, |
| Variadic<OpenMP_PointerLikeType>:$nontemporal_vars, |
| OptionalAttr<OrderKindAttr>:$order_val, |
| ConfinedAttr<OptionalAttr<I64Attr>, [IntPositive]>:$simdlen, |
| ConfinedAttr<OptionalAttr<I64Attr>, [IntPositive]>:$safelen |
| ); |
| |
| let regions = (region AnyRegion:$region); |
| |
| let builders = [ |
| OpBuilder<(ins CArg<"const SimdClauseOps &">:$clauses)> |
| ]; |
| |
| let assemblyFormat = [{ |
| oilist(`aligned` `(` |
| custom<AlignedClause>($aligned_vars, type($aligned_vars), |
| $alignment_values) `)` |
| |`if` `(` $if_expr `)` |
| |`nontemporal` `(` $nontemporal_vars `:` type($nontemporal_vars) `)` |
| |`order` `(` custom<ClauseAttr>($order_val) `)` |
| |`simdlen` `(` $simdlen `)` |
| |`safelen` `(` $safelen `)` |
| ) $region attr-dict |
| }]; |
| |
| let hasCustomAssemblyFormat = 1; |
| 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", [AttrSizedOperandSegments, |
| DeclareOpInterfaceMethods<LoopWrapperInterface>, |
| RecursiveMemoryEffects, |
| SingleBlockImplicitTerminator<"TerminatorOp">]> { |
| 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`. |
| |
| The `dist_schedule_static` attribute specifies the schedule for this |
| loop, determining how the loop is distributed across the parallel threads. |
| The optional `schedule_chunk` associated with this determines further |
| controls this distribution. |
| |
| ```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 |
| } |
| ``` |
| // TODO: private_var, firstprivate_var, lastprivate_var, collapse |
| }]; |
| let arguments = (ins |
| UnitAttr:$dist_schedule_static, |
| Optional<IntLikeType>:$chunk_size, |
| Variadic<AnyType>:$allocate_vars, |
| Variadic<AnyType>:$allocators_vars, |
| OptionalAttr<OrderKindAttr>:$order_val); |
| |
| let regions = (region AnyRegion:$region); |
| |
| let builders = [ |
| OpBuilder<(ins CArg<"const DistributeClauseOps &">:$clauses)> |
| ]; |
| |
| let assemblyFormat = [{ |
| oilist(`dist_schedule_static` $dist_schedule_static |
| |`chunk_size` `(` $chunk_size `:` type($chunk_size) `)` |
| |`order` `(` custom<ClauseAttr>($order_val) `)` |
| |`allocate` `(` |
| custom<AllocateAndAllocator>( |
| $allocate_vars, type($allocate_vars), |
| $allocators_vars, type($allocators_vars) |
| ) `)` |
| ) $region attr-dict |
| }]; |
| |
| let hasVerifier = 1; |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // 2.10.1 task Construct |
| //===----------------------------------------------------------------------===// |
| |
| def ClauseTaskDependIn : I32EnumAttrCase<"taskdependin", 0>; |
| def ClauseTaskDependOut : I32EnumAttrCase<"taskdependout", 1>; |
| def ClauseTaskDependInOut : I32EnumAttrCase<"taskdependinout", 2>; |
| |
| def ClauseTaskDepend : I32EnumAttr< |
| "ClauseTaskDepend", |
| "depend clause in a target or task construct", |
| [ClauseTaskDependIn, ClauseTaskDependOut, ClauseTaskDependInOut]> { |
| let genSpecializedAttr = 0; |
| let cppNamespace = "::mlir::omp"; |
| } |
| def ClauseTaskDependAttr : |
| EnumAttr<OpenMP_Dialect, ClauseTaskDepend, "clause_task_depend"> { |
| let assemblyFormat = "`(` $value `)`"; |
| } |
| def TaskDependArrayAttr : |
| TypedArrayAttrBase<ClauseTaskDependAttr, "clause_task_depend array attr"> { |
| let constBuilderCall = ?; |
| } |
| |
| def TaskOp : OpenMP_Op<"task", [AttrSizedOperandSegments, |
| OutlineableOpenMPOpInterface, AutomaticAllocationScope, |
| ReductionClauseInterface]> { |
| 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. |
| |
| When a `final` clause is present on a task construct and the `final_expr` |
| evaluates to `true`, the generated task will be a "final task". All task |
| constructs encountered during execution of a final task will generate final |
| and included tasks. |
| |
| If the `untied` clause is present on a task construct, any thread in the |
| team can resume the task region after a suspension. The `untied` clause is |
| ignored if a `final` clause is present on the same task construct and the |
| `final_expr` evaluates to `true`, or if a task is an included task. |
| |
| When the `mergeable` clause is present on a task construct, the generated |
| task is a "mergeable task". |
| |
| The `in_reduction` clause specifies that this particular task (among all the |
| tasks in current taskgroup, if any) participates in a reduction. |
| |
| The `priority` clause is a hint for the priority of the generated task. |
| The `priority` is a non-negative integer expression that provides a hint for |
| task execution order. Among all tasks ready to be executed, higher priority |
| tasks (those with a higher numerical value in the priority clause |
| expression) are recommended to execute before lower priority ones. The |
| default priority-value when no priority clause is specified should be |
| assumed to be zero (the lowest priority). |
| |
| The `depends` and `depend_vars` arguments are variadic lists of values |
| that specify the dependencies of this particular task in relation to |
| other tasks. |
| |
| The `allocators_vars` and `allocate_vars` arguments are a variadic list of |
| values that specify the memory allocator to be used to obtain storage for |
| private values. |
| |
| }]; |
| |
| // TODO: depend, affinity and detach clauses |
| let arguments = (ins Optional<I1>:$if_expr, |
| Optional<I1>:$final_expr, |
| UnitAttr:$untied, |
| UnitAttr:$mergeable, |
| Variadic<OpenMP_PointerLikeType>:$in_reduction_vars, |
| OptionalAttr<SymbolRefArrayAttr>:$in_reductions, |
| Optional<I32>:$priority, |
| OptionalAttr<TaskDependArrayAttr>:$depends, |
| Variadic<OpenMP_PointerLikeType>:$depend_vars, |
| Variadic<AnyType>:$allocate_vars, |
| Variadic<AnyType>:$allocators_vars); |
| let regions = (region AnyRegion:$region); |
| let builders = [ |
| OpBuilder<(ins CArg<"const TaskClauseOps &">:$clauses)> |
| ]; |
| let assemblyFormat = [{ |
| oilist(`if` `(` $if_expr `)` |
| |`final` `(` $final_expr `)` |
| |`untied` $untied |
| |`mergeable` $mergeable |
| |`in_reduction` `(` |
| custom<ReductionVarList>( |
| $in_reduction_vars, type($in_reduction_vars), $in_reductions |
| ) `)` |
| |`priority` `(` $priority `)` |
| |`allocate` `(` |
| custom<AllocateAndAllocator>( |
| $allocate_vars, type($allocate_vars), |
| $allocators_vars, type($allocators_vars) |
| ) `)` |
| |`depend` `(` |
| custom<DependVarList>( |
| $depend_vars, type($depend_vars), $depends |
| ) `)` |
| ) $region attr-dict |
| }]; |
| let extraClassDeclaration = [{ |
| /// Returns the reduction variables |
| SmallVector<Value> getReductionVars() { |
| return SmallVector<Value>(getInReductionVars().begin(), |
| getInReductionVars().end()); |
| } |
| }]; |
| let hasVerifier = 1; |
| } |
| |
| def TaskloopOp : OpenMP_Op<"taskloop", [AttrSizedOperandSegments, |
| AutomaticAllocationScope, |
| DeclareOpInterfaceMethods<LoopWrapperInterface>, |
| RecursiveMemoryEffects, ReductionClauseInterface, |
| SingleBlockImplicitTerminator<"TerminatorOp">]> { |
| 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. |
| |
| When a `final` clause is present on a taskloop construct and the `final` |
| clause expression evaluates to `true`, the generated tasks will be final |
| tasks. The use of a variable in a `final` clause expression of a taskloop |
| construct causes an implicit reference to the variable in all enclosing |
| constructs. |
| |
| If the `untied` clause is specified, all tasks generated by the taskloop |
| construct are untied tasks. |
| |
| When the `mergeable` clause is present on a taskloop construct, each |
| generated task is a mergeable task. |
| |
| Reductions can be performed in a loop by specifying reduction accumulator |
| variables in `reduction_vars` or `in_reduction_vars` and symbols referring |
| to reduction declarations in the `reductions` or `in_reductions` attribute. |
| Each reduction is identified by the accumulator it uses and accumulators |
| must not be repeated in the same reduction. The `omp.reduction` operation |
| accepts the accumulator and a partial value which is considered to be |
| produced by the current loop iteration for the given reduction. If multiple |
| values are produced for the same accumulator, i.e. there are multiple |
| `omp.reduction`s, the last value is taken. The reduction declaration |
| specifies how to combine the values from each iteration into the final |
| value, which is available in the accumulator after the loop completes. |
| |
| 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. |
| |
| 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. |
| |
| When a `priority` clause is present on a taskloop construct, the generated |
| tasks use the `priority-value` as if it was specified for each individual |
| task. If the `priority` clause is not specified, tasks generated by the |
| taskloop construct have the default task priority (zero). |
| |
| The `allocators_vars` and `allocate_vars` arguments are a variadic list of |
| values that specify the memory allocator to be used to obtain storage for |
| private values. |
| |
| If a `grainsize` clause is present on the taskloop construct, the number of |
| logical loop iterations assigned to each generated task is greater than or |
| equal to the minimum of the value of the grain-size expression and the |
| number of logical loop iterations, but less than two times the value of the |
| grain-size expression. |
| |
| If `num_tasks` is specified, the taskloop construct creates as many tasks as |
| the minimum of the num-tasks expression and the number of logical loop |
| iterations. Each task must have at least one logical loop iteration. |
| |
| By default, the taskloop construct executes as if it was enclosed in a |
| taskgroup construct with no statements or directives outside of the taskloop |
| construct. Thus, the taskloop construct creates an implicit taskgroup |
| region. If the `nogroup` clause is present, no implicit taskgroup region is |
| created. |
| }]; |
| |
| let arguments = (ins Optional<I1>:$if_expr, |
| Optional<I1>:$final_expr, |
| UnitAttr:$untied, |
| UnitAttr:$mergeable, |
| Variadic<OpenMP_PointerLikeType>:$in_reduction_vars, |
| OptionalAttr<SymbolRefArrayAttr>:$in_reductions, |
| Variadic<OpenMP_PointerLikeType>:$reduction_vars, |
| OptionalAttr<SymbolRefArrayAttr>:$reductions, |
| Optional<IntLikeType>:$priority, |
| Variadic<AnyType>:$allocate_vars, |
| Variadic<AnyType>:$allocators_vars, |
| Optional<IntLikeType>: $grain_size, |
| Optional<IntLikeType>: $num_tasks, |
| UnitAttr: $nogroup); |
| |
| let regions = (region AnyRegion:$region); |
| |
| let builders = [ |
| OpBuilder<(ins CArg<"const TaskloopClauseOps &">:$clauses)> |
| ]; |
| |
| let assemblyFormat = [{ |
| oilist(`if` `(` $if_expr `)` |
| |`final` `(` $final_expr `)` |
| |`untied` $untied |
| |`mergeable` $mergeable |
| |`in_reduction` `(` |
| custom<ReductionVarList>( |
| $in_reduction_vars, type($in_reduction_vars), $in_reductions |
| ) `)` |
| |`reduction` `(` |
| custom<ReductionVarList>( |
| $reduction_vars, type($reduction_vars), $reductions |
| ) `)` |
| |`priority` `(` $priority `:` type($priority) `)` |
| |`allocate` `(` |
| custom<AllocateAndAllocator>( |
| $allocate_vars, type($allocate_vars), |
| $allocators_vars, type($allocators_vars) |
| ) `)` |
| |`grain_size` `(` $grain_size `:` type($grain_size) `)` |
| |`num_tasks` `(` $num_tasks `:` type($num_tasks) `)` |
| |`nogroup` $nogroup |
| ) $region attr-dict |
| }]; |
| |
| let extraClassDeclaration = [{ |
| /// Returns the reduction variables |
| SmallVector<Value> getAllReductionVars(); |
| void getEffects(SmallVectorImpl<MemoryEffects::EffectInstance> &effects); |
| }]; |
| |
| let hasVerifier = 1; |
| } |
| |
| def TaskgroupOp : OpenMP_Op<"taskgroup", [AttrSizedOperandSegments, |
| ReductionClauseInterface, |
| AutomaticAllocationScope]> { |
| 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. |
| |
| The `task_reduction` clause specifies a reduction among tasks. For each list |
| item, the number of copies is unspecified. Any copies associated with the |
| reduction are initialized before they are accessed by the tasks |
| participating in the reduction. After the end of the region, the original |
| list item contains the result of the reduction. |
| |
| The `allocators_vars` and `allocate_vars` arguments are a variadic list of |
| values that specify the memory allocator to be used to obtain storage for |
| private values. |
| }]; |
| |
| let arguments = (ins Variadic<OpenMP_PointerLikeType>:$task_reduction_vars, |
| OptionalAttr<SymbolRefArrayAttr>:$task_reductions, |
| Variadic<AnyType>:$allocate_vars, |
| Variadic<AnyType>:$allocators_vars); |
| |
| let regions = (region AnyRegion:$region); |
| |
| let builders = [ |
| OpBuilder<(ins CArg<"const TaskgroupClauseOps &">:$clauses)> |
| ]; |
| |
| let assemblyFormat = [{ |
| oilist(`task_reduction` `(` |
| custom<ReductionVarList>( |
| $task_reduction_vars, type($task_reduction_vars), $task_reductions |
| ) `)` |
| |`allocate` `(` |
| custom<AllocateAndAllocator>( |
| $allocate_vars, type($allocate_vars), |
| $allocators_vars, type($allocators_vars) |
| ) `)` |
| ) $region attr-dict |
| }]; |
| |
| let extraClassDeclaration = [{ |
| /// Returns the reduction variables |
| operand_range getAllReductionVars() { return getTaskReductionVars(); } |
| }]; |
| |
| 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"> { |
| 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. |
| }]; |
| |
| let arguments = (ins Variadic<OpenMP_PointerLikeType>:$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); |
| } |
| }]; |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // Map related constructs |
| //===----------------------------------------------------------------------===// |
| |
| def CaptureThis : I32EnumAttrCase<"This", 0>; |
| def CaptureByRef : I32EnumAttrCase<"ByRef", 1>; |
| def CaptureByCopy : I32EnumAttrCase<"ByCopy", 2>; |
| def CaptureVLAType : I32EnumAttrCase<"VLAType", 3>; |
| |
| def VariableCaptureKind : I32EnumAttr< |
| "VariableCaptureKind", |
| "variable capture kind", |
| [CaptureThis, CaptureByRef, CaptureByCopy, CaptureVLAType]> { |
| let genSpecializedAttr = 0; |
| let cppNamespace = "::mlir::omp"; |
| } |
| |
| def VariableCaptureKindAttr : EnumAttr<OpenMP_Dialect, VariableCaptureKind, |
| "variable_capture_kind"> { |
| let assemblyFormat = "`(` $value `)`"; |
| } |
| |
| def MapBoundsType : OpenMP_Type<"MapBounds", "map_bounds_ty"> { |
| let summary = "Type for representing omp map clause bounds information"; |
| } |
| |
| 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 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<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", [AttrSizedOperandSegments, |
| MapClauseOwningOpInterface]>{ |
| 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. |
| |
| The optional $device parameter specifies the device number for the target |
| region. |
| |
| The optional $use_device_ptr specifies the device pointers to the |
| corresponding list items in the device data environment. |
| |
| The optional $use_device_addr specifies the address of the objects in the |
| device data enviornment. |
| |
| The $map_operands specifies the locator-list operands of the map clause. |
| |
| The $map_types specifies the types and modifiers for the map clause. |
| |
| TODO: depend clause and map_type_modifier values iterator and mapper. |
| }]; |
| |
| let arguments = (ins Optional<I1>:$if_expr, |
| Optional<AnyInteger>:$device, |
| Variadic<OpenMP_PointerLikeType>:$use_device_ptr, |
| Variadic<OpenMP_PointerLikeType>:$use_device_addr, |
| Variadic<AnyType>:$map_operands); |
| |
| let regions = (region AnyRegion:$region); |
| |
| let builders = [ |
| OpBuilder<(ins CArg<"const TargetDataClauseOps &">:$clauses)> |
| ]; |
| |
| let assemblyFormat = [{ |
| oilist(`if` `(` $if_expr `:` type($if_expr) `)` |
| | `device` `(` $device `:` type($device) `)` |
| | `map_entries` `(` $map_operands `:` type($map_operands) `)` |
| | `use_device_ptr` `(` $use_device_ptr `:` type($use_device_ptr) `)` |
| | `use_device_addr` `(` $use_device_addr `:` type($use_device_addr) `)`) |
| $region attr-dict |
| }]; |
| |
| let hasVerifier = 1; |
| } |
| |
| //===---------------------------------------------------------------------===// |
| // 2.14.3 target enter data Construct |
| //===---------------------------------------------------------------------===// |
| |
| def TargetEnterDataOp: OpenMP_Op<"target_enter_data", |
| [AttrSizedOperandSegments, |
| MapClauseOwningOpInterface]>{ |
| 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. |
| |
| The optional $device parameter specifies the device number for the |
| target region. |
| |
| The optional $nowait eliminates the implicit barrier so the parent task |
| can make progress even if the target task is not yet completed. |
| |
| The $map_operands specifies the locator-list operands of the map clause. |
| |
| The $map_types specifies the types and modifiers for the map clause. |
| |
| The `depends` and `depend_vars` arguments are variadic lists of values |
| that specify the dependencies of this particular target task in relation to |
| other tasks. |
| |
| TODO: map_type_modifier values iterator and mapper. |
| }]; |
| |
| let arguments = (ins Optional<I1>:$if_expr, |
| Optional<AnyInteger>:$device, |
| OptionalAttr<TaskDependArrayAttr>:$depends, |
| Variadic<OpenMP_PointerLikeType>:$depend_vars, |
| UnitAttr:$nowait, |
| Variadic<AnyType>:$map_operands); |
| |
| let builders = [ |
| OpBuilder<(ins CArg<"const TargetEnterExitUpdateDataClauseOps &">:$clauses)> |
| ]; |
| |
| let assemblyFormat = [{ |
| oilist(`if` `(` $if_expr `:` type($if_expr) `)` |
| | `device` `(` $device `:` type($device) `)` |
| | `nowait` $nowait |
| | `map_entries` `(` $map_operands `:` type($map_operands) `)` |
| | `depend` `(` custom<DependVarList>($depend_vars, type($depend_vars), $depends) `)` |
| ) attr-dict |
| }]; |
| |
| let hasVerifier = 1; |
| } |
| |
| //===---------------------------------------------------------------------===// |
| // 2.14.4 target exit data Construct |
| //===---------------------------------------------------------------------===// |
| |
| def TargetExitDataOp: OpenMP_Op<"target_exit_data", |
| [AttrSizedOperandSegments, |
| MapClauseOwningOpInterface]>{ |
| 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. |
| |
| The optional $device parameter specifies the device number for the |
| target region. |
| |
| The optional $nowait eliminates the implicit barrier so the parent |
| task can make progress even if the target task is not yet completed. |
| |
| The $map_operands specifies the locator-list operands of the map clause. |
| |
| The $map_types specifies the types and modifiers for the map clause. |
| |
| The `depends` and `depend_vars` arguments are variadic lists of values |
| that specify the dependencies of this particular target task in relation to |
| other tasks. |
| |
| TODO: map_type_modifier values iterator and mapper. |
| }]; |
| |
| let arguments = (ins Optional<I1>:$if_expr, |
| Optional<AnyInteger>:$device, |
| OptionalAttr<TaskDependArrayAttr>:$depends, |
| Variadic<OpenMP_PointerLikeType>:$depend_vars, |
| UnitAttr:$nowait, |
| Variadic<AnyType>:$map_operands); |
| |
| let builders = [ |
| OpBuilder<(ins CArg<"const TargetEnterExitUpdateDataClauseOps &">:$clauses)> |
| ]; |
| |
| let assemblyFormat = [{ |
| oilist(`if` `(` $if_expr `:` type($if_expr) `)` |
| | `device` `(` $device `:` type($device) `)` |
| | `nowait` $nowait |
| | `map_entries` `(` $map_operands `:` type($map_operands) `)` |
| | `depend` `(` custom<DependVarList>($depend_vars, type($depend_vars), $depends) `)` |
| ) attr-dict |
| }]; |
| |
| let hasVerifier = 1; |
| } |
| |
| //===---------------------------------------------------------------------===// |
| // 2.14.6 target update Construct |
| //===---------------------------------------------------------------------===// |
| |
| def TargetUpdateOp: OpenMP_Op<"target_update", [AttrSizedOperandSegments, |
| MapClauseOwningOpInterface]>{ |
| 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. |
| |
| The optional $device parameter specifies the device number for the |
| target region. |
| |
| The optional $nowait eliminates the implicit barrier so the parent |
| task can make progress even if the target task is not yet completed. |
| |
| 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. |
| |
| The `depends` and `depend_vars` arguments are variadic lists of values |
| that specify the dependencies of this particular target task in relation to |
| other tasks. |
| |
| }]; |
| |
| let arguments = (ins Optional<I1>:$if_expr, |
| Optional<AnyInteger>:$device, |
| OptionalAttr<TaskDependArrayAttr>:$depends, |
| Variadic<OpenMP_PointerLikeType>:$depend_vars, |
| UnitAttr:$nowait, |
| Variadic<OpenMP_PointerLikeType>:$map_operands); |
| |
| let builders = [ |
| OpBuilder<(ins CArg<"const TargetEnterExitUpdateDataClauseOps &">:$clauses)> |
| ]; |
| |
| let assemblyFormat = [{ |
| oilist(`if` `(` $if_expr `:` type($if_expr) `)` |
| | `device` `(` $device `:` type($device) `)` |
| | `nowait` $nowait |
| | `motion_entries` `(` $map_operands `:` type($map_operands) `)` |
| | `depend` `(` custom<DependVarList>($depend_vars, type($depend_vars), $depends) `)` |
| ) attr-dict |
| }]; |
| |
| let hasVerifier = 1; |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // 2.14.5 target construct |
| //===----------------------------------------------------------------------===// |
| |
| def TargetOp : OpenMP_Op<"target", [IsolatedFromAbove, MapClauseOwningOpInterface, |
| OutlineableOpenMPOpInterface, AttrSizedOperandSegments]> { |
| 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 optional $device parameter specifies the device number for the target region. |
| |
| The optional $thread_limit specifies the limit on the number of threads |
| |
| The optional $nowait eliminates the implicit barrier so the parent task can make progress |
| even if the target task is not yet completed. |
| |
| The `depends` and `depend_vars` arguments are variadic lists of values |
| that specify the dependencies of this particular target task in relation to |
| other tasks. |
| |
| The optional $is_device_ptr indicates list items are device pointers. |
| |
| The optional $has_device_addr indicates that list items already have device |
| addresses, so they may be directly accessed from the target device. This |
| includes array sections. |
| |
| The optional $map_operands maps data from the task’s environment to the |
| device environment. |
| |
| TODO: defaultmap, in_reduction |
| |
| }]; |
| |
| let arguments = (ins Optional<I1>:$if_expr, |
| Optional<AnyInteger>:$device, |
| Optional<AnyInteger>:$thread_limit, |
| OptionalAttr<TaskDependArrayAttr>:$depends, |
| Variadic<OpenMP_PointerLikeType>:$depend_vars, |
| UnitAttr:$nowait, |
| Variadic<OpenMP_PointerLikeType>:$is_device_ptr, |
| Variadic<OpenMP_PointerLikeType>:$has_device_addr, |
| Variadic<AnyType>:$map_operands, |
| Variadic<AnyType>:$private_vars, |
| OptionalAttr<SymbolRefArrayAttr>:$privatizers); |
| |
| let regions = (region AnyRegion:$region); |
| |
| let builders = [ |
| OpBuilder<(ins CArg<"const TargetClauseOps &">:$clauses)> |
| ]; |
| |
| let assemblyFormat = [{ |
| oilist( `if` `(` $if_expr `)` |
| | `device` `(` $device `:` type($device) `)` |
| | `thread_limit` `(` $thread_limit `:` type($thread_limit) `)` |
| | `nowait` $nowait |
| | `is_device_ptr` `(` $is_device_ptr `:` type($is_device_ptr) `)` |
| | `has_device_addr` `(` $has_device_addr `:` type($has_device_addr) `)` |
| | `map_entries` `(` custom<MapEntries>($map_operands, type($map_operands)) `)` |
| | `private` `(` custom<PrivateList>($private_vars, type($private_vars), $privatizers) `)` |
| | `depend` `(` custom<DependVarList>($depend_vars, type($depend_vars), $depends) `)` |
| ) $region attr-dict |
| }]; |
| |
| let hasVerifier = 1; |
| } |
| |
| |
| //===----------------------------------------------------------------------===// |
| // 2.16 master Construct |
| //===----------------------------------------------------------------------===// |
| def MasterOp : OpenMP_Op<"master"> { |
| let summary = "master construct"; |
| let description = [{ |
| The master construct specifies a structured block that is executed by |
| the master thread of the team. |
| }]; |
| |
| let regions = (region AnyRegion:$region); |
| |
| let assemblyFormat = "$region attr-dict"; |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // 2.17.1 critical Construct |
| //===----------------------------------------------------------------------===// |
| def CriticalDeclareOp : OpenMP_Op<"critical.declare", [Symbol]> { |
| let summary = "declares a named critical section."; |
| |
| let description = [{ |
| Declares a named critical section. |
| |
| The name can be used in critical constructs in the dialect. |
| }]; |
| |
| let arguments = (ins SymbolNameAttr:$sym_name, |
| DefaultValuedAttr<I64Attr, "0">:$hint_val); |
| |
| let builders = [ |
| OpBuilder<(ins CArg<"const CriticalClauseOps &">:$clauses)> |
| ]; |
| |
| let assemblyFormat = [{ |
| $sym_name oilist(`hint` `(` custom<SynchronizationHint>($hint_val) `)`) |
| attr-dict |
| }]; |
| let hasVerifier = 1; |
| } |
| |
| |
| def CriticalOp : OpenMP_Op<"critical", |
| [DeclareOpInterfaceMethods<SymbolUserOpInterface>]> { |
| 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. |
| }]; |
| |
| let arguments = (ins OptionalAttr<FlatSymbolRefAttr>:$name); |
| |
| let regions = (region AnyRegion:$region); |
| |
| 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 ClauseDependSource : I32EnumAttrCase<"dependsource", 0>; |
| def ClauseDependSink : I32EnumAttrCase<"dependsink", 1>; |
| |
| def ClauseDepend : I32EnumAttr< |
| "ClauseDepend", |
| "depend clause", |
| [ClauseDependSource, ClauseDependSink]> { |
| let genSpecializedAttr = 0; |
| let cppNamespace = "::mlir::omp"; |
| } |
| def ClauseDependAttr : EnumAttr<OpenMP_Dialect, ClauseDepend, "clause_depend"> { |
| let assemblyFormat = "`(` $value `)`"; |
| } |
| |
| def OrderedOp : OpenMP_Op<"ordered"> { |
| let summary = "ordered construct without region"; |
| let description = [{ |
| The ordered construct without region is a stand-alone directive that |
| specifies cross-iteration dependences in a doacross loop nest. |
| |
| The `depend_type_val` attribute refers to either the DEPEND(SOURCE) clause |
| or the DEPEND(SINK: vec) clause. |
| |
| The `num_loops_val` attribute specifies the number of loops in the doacross |
| nest. |
| |
| The `depend_vec_vars` is a variadic list of operands that specifies the index |
| of the loop iterator in the doacross nest for the DEPEND(SOURCE) clause or |
| the index of the element of "vec" for the DEPEND(SINK: vec) clause. It |
| contains the operands in multiple "vec" when multiple DEPEND(SINK: vec) |
| clauses exist in one ORDERED directive. |
| }]; |
| |
| let arguments = (ins OptionalAttr<ClauseDependAttr>:$depend_type_val, |
| ConfinedAttr<OptionalAttr<I64Attr>, [IntMinValue<0>]>:$num_loops_val, |
| Variadic<AnyType>:$depend_vec_vars); |
| |
| let builders = [ |
| OpBuilder<(ins CArg<"const OrderedOpClauseOps &">:$clauses)> |
| ]; |
| |
| let assemblyFormat = [{ |
| ( `depend_type` `` $depend_type_val^ )? |
| ( `depend_vec` `(` $depend_vec_vars^ `:` type($depend_vec_vars) `)` )? |
| attr-dict |
| }]; |
| let hasVerifier = 1; |
| } |
| |
| def OrderedRegionOp : OpenMP_Op<"ordered.region"> { |
| 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. |
| |
| The `simd` attribute corresponds to the SIMD clause specified. If it is not |
| present, it behaves as if the THREADS clause is specified or no clause is |
| specified. |
| }]; |
| |
| let arguments = (ins UnitAttr:$simd); |
| |
| let regions = (region AnyRegion:$region); |
| |
| let builders = [ |
| OpBuilder<(ins CArg<"const OrderedRegionClauseOps &">:$clauses)> |
| ]; |
| |
| let assemblyFormat = [{ ( `simd` $simd^ )? $region attr-dict}]; |
| let hasVerifier = 1; |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // 2.17.5 taskwait Construct |
| //===----------------------------------------------------------------------===// |
| |
| def TaskwaitOp : OpenMP_Op<"taskwait"> { |
| let summary = "taskwait construct"; |
| let description = [{ |
| The taskwait construct specifies a wait on the completion of child tasks |
| of the current task. |
| }]; |
| |
| let builders = [ |
| OpBuilder<(ins CArg<"const TaskwaitClauseOps &">:$clauses)> |
| ]; |
| |
| let assemblyFormat = "attr-dict"; |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // 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", [AllTypesMatch<["x", "v"]>, |
| AtomicReadOpInterface]> { |
| |
| 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. |
| |
| `hint` is the value of hint (as specified in the hint clause). It is a |
| compile time constant. As the name suggests, this is just a hint for |
| optimization. |
| |
| `memory_order` indicates the memory ordering behavior of the construct. It |
| can be one of `seq_cst`, `acquire` or `relaxed`. |
| }]; |
| |
| let arguments = (ins OpenMP_PointerLikeType:$x, |
| OpenMP_PointerLikeType:$v, |
| TypeAttr:$element_type, |
| DefaultValuedOptionalAttr<I64Attr, "0">:$hint_val, |
| OptionalAttr<MemoryOrderKindAttr>:$memory_order_val); |
| let assemblyFormat = [{ |
| $v `=` $x |
| oilist( `memory_order` `(` custom<ClauseAttr>($memory_order_val) `)` |
| | `hint` `(` custom<SynchronizationHint>($hint_val) `)`) |
| `:` type($x) `,` $element_type attr-dict |
| }]; |
| let hasVerifier = 1; |
| 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(); |
| } |
| }]; |
| } |
| |
| def AtomicWriteOp : OpenMP_Op<"atomic.write", [AtomicWriteOpInterface]> { |
| |
| 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). |
| |
| `hint` is the value of hint (as specified in the hint clause). It is a |
| compile time constant. As the name suggests, this is just a hint for |
| optimization. |
| |
| `memory_order` indicates the memory ordering behavior of the construct. It |
| can be one of `seq_cst`, `release` or `relaxed`. |
| }]; |
| |
| let arguments = (ins OpenMP_PointerLikeType:$x, |
| AnyType:$expr, |
| DefaultValuedOptionalAttr<I64Attr, "0">:$hint_val, |
| OptionalAttr<MemoryOrderKindAttr>:$memory_order_val); |
| let assemblyFormat = [{ |
| $x `=` $expr |
| oilist( `hint` `(` custom<SynchronizationHint>($hint_val) `)` |
| | `memory_order` `(` custom<ClauseAttr>($memory_order_val) `)`) |
| `:` type($x) `,` type($expr) |
| attr-dict |
| }]; |
| let hasVerifier = 1; |
| 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(); |
| } |
| }]; |
| } |
| |
| def AtomicUpdateOp : OpenMP_Op<"atomic.update", |
| [SingleBlockImplicitTerminator<"YieldOp">, |
| RecursiveMemoryEffects, |
| AtomicUpdateOpInterface]> { |
| |
| 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. |
| |
| `hint` is the value of hint (as used in the hint clause). It is a compile |
| time constant. As the name suggests, this is just a hint for optimization. |
| |
| `memory_order` indicates the memory ordering behavior of the construct. It |
| can be one of `seq_cst`, `release` or `relaxed`. |
| |
| 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. |
| }]; |
| |
| let arguments = (ins Arg<OpenMP_PointerLikeType, |
| "Address of variable to be updated", |
| [MemRead, MemWrite]>:$x, |
| DefaultValuedOptionalAttr<I64Attr, "0">:$hint_val, |
| OptionalAttr<MemoryOrderKindAttr>:$memory_order_val); |
| let regions = (region SizedRegion<1>:$region); |
| let assemblyFormat = [{ |
| oilist( `memory_order` `(` custom<ClauseAttr>($memory_order_val) `)` |
| | `hint` `(` custom<SynchronizationHint>($hint_val) `)`) |
| $x `:` type($x) $region attr-dict |
| }]; |
| let hasVerifier = 1; |
| let hasRegionVerifier = 1; |
| let hasCanonicalizeMethod = 1; |
| 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(); |
| } |
| }]; |
| } |
| |
| def AtomicCaptureOp : OpenMP_Op<"atomic.capture", |
| [SingleBlockImplicitTerminator<"TerminatorOp">, |
| RecursiveMemoryEffects, AtomicCaptureOpInterface]> { |
| let summary = "performs an atomic capture"; |
| let description = [{ |
| This operation performs an atomic capture. |
| |
| `hint` is the value of hint (as used in the hint clause). It is a compile |
| time constant. As the name suggests, this is just a hint for optimization. |
| |
| `memory_order` indicates the memory ordering behavior of the construct. It |
| can be one of `seq_cst`, `acq_rel`, `release`, `acquire` or `relaxed`. |
| |
| 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 |
| } |
| ``` |
| |
| }]; |
| |
| let arguments = (ins DefaultValuedOptionalAttr<I64Attr, "0">:$hint_val, |
| OptionalAttr<MemoryOrderKindAttr>:$memory_order_val); |
| let regions = (region SizedRegion<1>:$region); |
| let assemblyFormat = [{ |
| oilist(`memory_order` `(` custom<ClauseAttr>($memory_order_val) `)` |
| |`hint` `(` custom<SynchronizationHint>($hint_val) `)`) |
| $region attr-dict |
| }]; |
| let hasRegionVerifier = 1; |
| let hasVerifier = 1; |
| 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(); |
| }]; |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // [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"> { |
| let summary = "cancel directive"; |
| let description = [{ |
| The cancel construct activates cancellation of the innermost enclosing |
| region of the type specified. |
| }]; |
| let arguments = (ins CancellationConstructTypeAttr:$cancellation_construct_type_val, |
| Optional<I1>:$if_expr); |
| let assemblyFormat = [{ `cancellation_construct_type` `(` |
| custom<ClauseAttr>($cancellation_construct_type_val) `)` |
| ( `if` `(` $if_expr^ `)` )? attr-dict}]; |
| let hasVerifier = 1; |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // 2.18.2 Cancellation Point Construct |
| //===----------------------------------------------------------------------===// |
| def CancellationPointOp : OpenMP_Op<"cancellation_point"> { |
| 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. |
| }]; |
| let arguments = (ins CancellationConstructTypeAttr:$cancellation_construct_type_val); |
| let assemblyFormat = [{ `cancellation_construct_type` `(` |
| custom<ClauseAttr>($cancellation_construct_type_val) `)` |
| attr-dict}]; |
| let hasVerifier = 1; |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // 2.19.5.7 declare reduction Directive |
| //===----------------------------------------------------------------------===// |
| |
| def DeclareReductionOp : OpenMP_Op<"declare_reduction", [Symbol, |
| IsolatedFromAbove]> { |
| let summary = "declares a reduction kind"; |
| |
| let description = [{ |
| Declares an OpenMP reduction kind. This requires two mandatory and two |
| optional regions. |
| |
| 1. 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. It is |
| expected to `omp.yield` the new value on all control flow paths. |
| 2. 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. |
| 3. 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. |
| 4. 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 AnyRegion:$initializerRegion, |
| AnyRegion:$reductionRegion, |
| AnyRegion:$atomicReductionRegion, |
| AnyRegion:$cleanupRegion); |
| |
| let assemblyFormat = "$sym_name `:` $type attr-dict-with-keyword " |
| "`init` $initializerRegion " |
| "`combiner` $reductionRegion " |
| "custom<AtomicReductionRegion>($atomicReductionRegion) " |
| "custom<CleanupReductionRegion>($cleanupRegion)"; |
| |
| let extraClassDeclaration = [{ |
| PointerLikeType getAccumulatorType() { |
| if (getAtomicReductionRegion().empty()) |
| return {}; |
| |
| return cast<PointerLikeType>(getAtomicReductionRegion().front().getArgument(0).getType()); |
| } |
| }]; |
| let hasRegionVerifier = 1; |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // 2.19.5.4 reduction clause |
| //===----------------------------------------------------------------------===// |
| |
| def ReductionOp : OpenMP_Op<"reduction"> { |
| let summary = "reduction construct"; |
| let description = [{ |
| Indicates the value that is produced by the current reduction-participating |
| entity for a reduction requested in some ancestor. The reduction is |
| identified by the accumulator, but the value of the accumulator may not be |
| updated immediately. |
| }]; |
| |
| let arguments= (ins AnyType:$operand, OpenMP_PointerLikeType:$accumulator); |
| let assemblyFormat = [{ |
| $operand `,` $accumulator attr-dict `:` type($operand) `,` type($accumulator) |
| }]; |
| let hasVerifier = 1; |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // 8.2 requires directive |
| //===----------------------------------------------------------------------===// |
| |
| // atomic_default_mem_order clause values not defined here because they can be |
| // represented by the OMPC_MemoryOrder enumeration instead. |
| def ClauseRequiresNone : I32BitEnumAttrCaseNone<"none">; |
| def ClauseRequiresReverseOffload : I32BitEnumAttrCaseBit<"reverse_offload", 0>; |
| def ClauseRequiresUnifiedAddress : I32BitEnumAttrCaseBit<"unified_address", 1>; |
| def ClauseRequiresUnifiedSharedMemory |
| : I32BitEnumAttrCaseBit<"unified_shared_memory", 2>; |
| def ClauseRequiresDynamicAllocators |
| : I32BitEnumAttrCaseBit<"dynamic_allocators", 3>; |
| |
| def ClauseRequires : I32BitEnumAttr< |
| "ClauseRequires", |
| "requires clauses", |
| [ |
| ClauseRequiresNone, |
| ClauseRequiresReverseOffload, |
| ClauseRequiresUnifiedAddress, |
| ClauseRequiresUnifiedSharedMemory, |
| ClauseRequiresDynamicAllocators |
| ]> { |
| let genSpecializedAttr = 0; |
| let cppNamespace = "::mlir::omp"; |
| } |
| def ClauseRequiresAttr : |
| EnumAttr<OpenMP_Dialect, ClauseRequires, "clause_requires"> { |
| } |
| |
| #endif // OPENMP_OPS |