@@ -41,3 +41,199 @@ mapping information is named `MapInfoOp` / `omp.map.info`. The same rules are
4141followed if multiple operations are created for different variants of the same
4242directive, e.g. ` atomic ` becomes ` Atomic{Read,Write,Update,Capture}Op ` /
4343` 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