@@ -41,3 +41,199 @@ mapping information is named `MapInfoOp` / `omp.map.info`. The same rules are
41
41
followed if multiple operations are created for different variants of the same
42
42
directive, e.g. ` atomic ` becomes ` Atomic{Read,Write,Update,Capture}Op ` /
43
43
` omp.atomic.{read,write,update,capture} ` .
44
+
45
+ ## Clause-Based Operation Definition
46
+
47
+ One main feature of the OpenMP specification is that, even though the set of
48
+ clauses that could be applied to a given directive is independent from other
49
+ directives, these clauses can generally apply to multiple directives. Since
50
+ clauses usually define which arguments the corresponding MLIR operation takes,
51
+ it is possible (and preferred) to define OpenMP dialect operations based on the
52
+ list of clauses taken by the corresponding directive. This makes it simpler to
53
+ keep their representation consistent across operations and minimizes redundancy
54
+ in the dialect.
55
+
56
+ To achieve this, the base ` OpenMP_Clause ` tablegen class has been created. It is
57
+ intended to be used to create clause definitions that can be then attached to
58
+ multiple ` OpenMP_Op ` definitions, resulting in the latter inheriting by default
59
+ all properties defined by clauses attached, similarly to the trait mechanism.
60
+ This mechanism is implemented in
61
+ [ OpenMPOpBase.td] ( https://github.com/llvm/llvm-project/blob/main/mlir/include/mlir/Dialect/OpenMP/OpenMPOpBase.td ) .
62
+
63
+ ### Adding a Clause
64
+
65
+ OpenMP clause definitions are located in
66
+ [ OpenMPClauses.td] ( https://github.com/llvm/llvm-project/blob/main/mlir/include/mlir/Dialect/OpenMP/OpenMPClauses.td ) .
67
+ For each clause, an ` OpenMP_Clause ` subclass and a definition based on it must
68
+ be created. The subclass must take a ` bit ` template argument for each of the
69
+ properties it can populate on associated ` OpenMP_Op ` s. These must be forwarded
70
+ to the base class. The definition must be an instantiation of the base class
71
+ where all these template arguments are set to ` false ` . The definition's name
72
+ must be ` OpenMP_<Name>Clause ` , whereas its base class' must be
73
+ ` OpenMP_<Name>ClauseSkip ` . Following this pattern makes it possible to
74
+ optionally skip the inheritance of some properties when defining operations:
75
+ [ more info] ( #overriding-clause-inherited-properties ) .
76
+
77
+ Clauses can define the following properties:
78
+ - ` list<Traits> traits ` : To be used when having a certain clause always
79
+ implies some op trait, like the ` map ` clause and the ` MapClauseOwningInterface ` .
80
+ - ` dag(ins) arguments ` : Mandatory property holding values and attributes
81
+ used to represent the clause. Argument names use snake_case and should contain
82
+ the clause name to avoid name clashes between clauses. Variadic arguments
83
+ (non-attributes) must contain the "_ vars" suffix.
84
+ - ` string {req,opt}AssemblyFormat ` : Optional formatting strings to produce
85
+ custom human-friendly printers and parsers for arguments associated with the
86
+ clause. It will be combined with assembly formats for other clauses as explained
87
+ [ below] ( #adding-an-operation ) .
88
+ - ` string description ` : Optional description text to describe the clause and
89
+ its representation.
90
+ - ` string extraClassDeclaration ` : Optional C++ declarations to be added to
91
+ operation classes including the clause.
92
+
93
+ For example:
94
+
95
+ ``` tablegen
96
+ class OpenMP_ExampleClauseSkip<
97
+ bit traits = false, bit arguments = false, bit assemblyFormat = false,
98
+ bit description = false, bit extraClassDeclaration = false
99
+ > : OpenMP_Clause<traits, arguments, assemblyFormat, description,
100
+ extraClassDeclaration> {
101
+ let arguments = (ins
102
+ Optional<AnyType>:$example_var
103
+ );
104
+
105
+ let optAssemblyFormat = [{
106
+ `example` `(` $example_var `:` type($example_var) `)`
107
+ }];
108
+
109
+ let description = [{
110
+ The `example_var` argument defines the variable to which the EXAMPLE clause
111
+ applies.
112
+ }];
113
+ }
114
+
115
+ def OpenMP_ExampleClause : OpenMP_ExampleClauseSkip<>;
116
+ ```
117
+
118
+ ### Adding an Operation
119
+
120
+ Operations in the OpenMP dialect, located in
121
+ [ OpenMPOps.td] ( https://github.com/llvm/llvm-project/blob/main/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td ) ,
122
+ can be defined like any other regular operation by just specifying a ` mnemonic `
123
+ and optional list of ` traits ` when inheriting from ` OpenMP_Op ` , and then
124
+ defining the expected ` description ` , ` arguments ` , etc. properties inside of its
125
+ body. However, in most cases, basing the operation definition on its list of
126
+ accepted clauses is significantly simpler because some of the properties can
127
+ just be inherited from these clauses.
128
+
129
+ In general, the way to achieve this is to specify, in addition to the ` mnemonic `
130
+ and optional list of ` traits ` , a list of ` clauses ` where all the applicable
131
+ ` OpenMP_<Name>Clause ` definitions are added. Then, the only properties that
132
+ would have to be defined in the operation's body are the ` summary ` and
133
+ ` description ` . For the latter, only the operation itself would have to be
134
+ defined, and the description for its clause-inherited arguments is appended
135
+ through the inherited ` clausesDescription ` property.
136
+
137
+ If the operation is intended to have a single region, this is better achieved by
138
+ setting the ` singleRegion=true ` template argument of ` OpenMP_Op ` rather manually
139
+ populating the ` regions ` property of the operation, because that way the default
140
+ ` assemblyFormat ` is also updated correspondingly.
141
+
142
+ For example:
143
+
144
+ ``` tablegen
145
+ def ExampleOp : OpenMP_Op<"example", traits = [
146
+ AttrSizedOperandSegments, ...
147
+ ], clauses = [
148
+ OpenMP_AlignedClause, OpenMP_IfClause, OpenMP_LinearClause, ...
149
+ ], singleRegion = true> {
150
+ let summary = "example construct";
151
+ let description = [{
152
+ The example construct represents...
153
+ }] # clausesDescription;
154
+ }
155
+ ```
156
+
157
+ This is possible because the ` arguments ` , ` assemblyFormat ` and
158
+ ` extraClassDeclaration ` properties of the operation are by default
159
+ populated by concatenating the corresponding properties of the clauses on the
160
+ list. In the case of the ` assemblyFormat ` , this involves combining the
161
+ ` reqAssemblyFormat ` and the ` optAssemblyFormat ` properties. The
162
+ ` reqAssemblyFormat ` of all clauses is concatenated first and separated using
163
+ spaces, whereas the ` optAssemblyFormat ` is wrapped in an ` oilist() ` and
164
+ interleaved with "|" instead of spaces. The resulting ` assemblyFormat ` contains
165
+ the required assembly format strings, followed by the optional assembly format
166
+ strings, optionally the ` $region ` and the ` attr-dict ` .
167
+
168
+ ### Overriding Clause-Inherited Properties
169
+
170
+ Although the clause-based definition of operations can greatly reduce work, it's
171
+ also somewhat restrictive, since there may be some situations where only part of
172
+ the operation definition can be automated in that manner. For a fine-grained
173
+ control over properties inherited from each clause two features are available:
174
+
175
+ - Inhibition of properties. By using ` OpenMP_<Name>ClauseSkip ` tablegen
176
+ classes, the list of properties copied from the clause to the operation can be
177
+ selected. For example, ` OpenMP_IfClauseSkip<assemblyFormat = true> ` would result
178
+ in every property defined for the ` OpenMP_IfClause ` except for the
179
+ ` assemblyFormat ` being used to initially populate the properties of the
180
+ operation.
181
+ - Augmentation of properties. There are times when there is a need to add to
182
+ a clause-populated operation property. Instead of overriding the property in the
183
+ definition of the operation and having to manually replicate what would
184
+ otherwise be automatically populated before adding to it, some internal
185
+ properties are defined to hold this default value: ` clausesArgs ` ,
186
+ ` clausesAssemblyFormat ` , ` clauses{Req,Opt}AssemblyFormat ` and
187
+ ` clausesExtraClassDeclaration ` .
188
+
189
+ In the following example, assuming both the ` OpenMP_InReductionClause ` and the
190
+ ` OpenMP_ReductionClause ` define a ` getReductionVars ` extra class declaration,
191
+ we skip the conflicting ` extraClassDeclaration ` s inherited by both clauses and
192
+ provide another implementation, without having to also re-define other
193
+ declarations inherited from the ` OpenMP_AllocateClause ` :
194
+
195
+ ``` tablegen
196
+ def ExampleOp : OpenMP_Op<"example", traits = [
197
+ AttrSizedOperandSegments, ...
198
+ ], clauses = [
199
+ OpenMP_AllocateClause,
200
+ OpenMP_InReductionClauseSkip<extraClassDeclaration = true>,
201
+ OpenMP_ReductionClauseSkip<extraClassDeclaration = true>
202
+ ], singleRegion = true> {
203
+ let summary = "example construct";
204
+ let description = [{
205
+ This operation represents...
206
+ }] # clausesDescription;
207
+
208
+ // Override the clause-populated extraClassDeclaration and add the default
209
+ // back via appending clausesExtraClassDeclaration to it. This has the effect
210
+ // of adding one declaration. Since this property is skipped for the
211
+ // InReduction and Reduction clauses, clausesExtraClassDeclaration won't
212
+ // incorporate the definition of this property for these clauses.
213
+ let extraClassDeclaration = [{
214
+ SmallVector<Value> getReductionVars() {
215
+ // Concatenate inReductionVars and reductionVars and return the result...
216
+ }
217
+ }] # clausesExtraClassDeclaration;
218
+ }
219
+ ```
220
+
221
+ These features are intended for complex edge cases, but an effort should be made
222
+ to avoid having to use them, since they may introduce inconsistencies and
223
+ complexity to the dialect.
224
+
225
+ ### Tablegen Verification Pass
226
+
227
+ As a result of the implicit way in which fundamental properties of MLIR
228
+ operations are populated following this approach, and the ability to override
229
+ them, forgetting to append clause-inherited values might result in hard to debug
230
+ tablegen errors.
231
+
232
+ For this reason, the ` -verify-openmp-ops ` tablegen pseudo-backend was created.
233
+ It runs before any other tablegen backends are triggered for the
234
+ [ OpenMPOps.td] ( https://github.com/llvm/llvm-project/blob/main/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td )
235
+ file and warns any time a property defined for a clause is not found in the
236
+ corresponding operation, except if it is explicitly skipped as described
237
+ [ above] ( #overriding-clause-inherited-properties ) . This way, in case of a later
238
+ tablegen failure while processing OpenMP dialect operations, earlier messages
239
+ triggered by that pass can point to a likely solution.
0 commit comments