[llvm-branch-commits] [mlir] [MLIR][OpenMP][Docs] NFC: Document clause-based op representation (PR #107234)

Sergio Afonso via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Fri Sep 13 08:09:13 PDT 2024


https://github.com/skatrak updated https://github.com/llvm/llvm-project/pull/107234

>From 47e8403d4adaba03696862ac3ea353fc0feb37d3 Mon Sep 17 00:00:00 2001
From: Sergio Afonso <safonsof at amd.com>
Date: Tue, 3 Sep 2024 17:09:57 +0100
Subject: [PATCH 1/2] [MLIR][OpenMP] NFC: Document clause-based op
 representation

This patch documents the clause-based op represetation discussed in
[this RFC](https://discourse.llvm.org/t/rfc-clause-based-representation-of-openmp-dialect-operations/79053).
---
 mlir/docs/Dialects/OpenMPDialect/_index.md | 192 +++++++++++++++++++++
 1 file changed, 192 insertions(+)

diff --git a/mlir/docs/Dialects/OpenMPDialect/_index.md b/mlir/docs/Dialects/OpenMPDialect/_index.md
index 43e82a871db066..8abdc731675fc0 100644
--- a/mlir/docs/Dialects/OpenMPDialect/_index.md
+++ b/mlir/docs/Dialects/OpenMPDialect/_index.md
@@ -39,3 +39,195 @@ mapping information is named `MapInfoOp` / `omp.map.info`. The same rules are
 followed if multiple operations are created for different variants of the same
 directive, e.g. `atomic` becomes `Atomic{Read,Write,Update,Capture}Op` /
 `omp.atomic.{read,write,update,capture}`.
+
+## Clause-Based Operation Definition
+
+One main feature of the OpenMP specification is that, even though the set of
+clauses that could be applied to a given directive is independent from other
+directives, these clauses can generally apply to multiple directives. Since
+clauses usually define which arguments the corresponding MLIR operation takes,
+it is possible (and preferred) to define OpenMP dialect operations based on the
+list of clauses taken by the corresponding directive. This makes it simpler to
+keep their representation consistent across operations and minimizes redundancy
+in the dialect.
+
+To achieve this, the base `OpenMP_Clause` tablegen class has been created. It is
+intended to be used to create clause definitions that can be then attached to
+multiple `OpenMP_Op` definitions, resulting in the latter inheriting by default
+all properties defined by clauses attached, similarly to the trait mechanism.
+This mechanism is implemented in
+[OpenMPOpBase.td](https://github.com/llvm/llvm-project/blob/main/mlir/include/mlir/Dialect/OpenMP/OpenMPOpBase.td).
+
+### Adding a Clause
+
+OpenMP clause definitions are located in
+[OpenMPClauses.td](https://github.com/llvm/llvm-project/blob/main/mlir/include/mlir/Dialect/OpenMP/OpenMPClauses.td).
+For each clause, an `OpenMP_Clause` subclass and a definition based on it must
+be created. The subclass must take a `bit` template argument for each of the
+properties it may define, listed below, which is forwarded to the base class.
+The definition must be an instantiation of the base class where all these
+template arguments are set to `false`. The definition's name must be
+`OpenMP_<Name>Clause`, whereas its base class' must be
+`OpenMP_<Name>ClauseSkip`. Following this pattern makes it possible to
+optionally skip the inheritance of some properties when defining operations:
+[more info](#overriding-clause-inherited-properties).
+
+Clauses can define the following properties:
+  - `list<Traits> traits`: To be used when having a certain clause always
+implies some op trait, like the `map` clause and the `MapClauseOwningInterface`.
+  - `dag(ins) arguments`: Mandatory property holding values and attributes
+used to represent the clause. Argument names use snake_case and should contain
+the clause name to avoid name clashes between clauses. Variadic arguments
+(non-attributes) must contain the "_vars" suffix.
+  - `string assemblyFormat`: Optional formatting string to produce custom
+human-friendly printers and parsers for arguments associated with the clause.
+It will be combined with assembly formats for other clauses depending on the
+`isRequired` template argument passed to the parent `OpenMP_Clause` class, as
+explained [below](#adding-an-operation). 
+  - `string description`: Optional description text to describe the clause and
+its representation.
+  - `string extraClassDeclaration`: Optional C++ declarations to be added to
+operation classes including the clause.
+
+For example:
+
+```tablegen
+class OpenMP_ExampleClauseSkip<
+    bit traits = false, bit arguments = false, bit assemblyFormat = false,
+    bit description = false, bit extraClassDeclaration = false
+  > : OpenMP_Clause</*isRequired=*/false, traits, arguments, assemblyFormat,
+                    description, extraClassDeclaration> {
+  let arguments = (ins
+    Optional<AnyType>:$example_var
+  );
+
+  let assemblyFormat = [{
+    `example` `(` $example_var `:` type($example_var) `)`
+  }];
+
+  let description = [{
+    The `example_var` argument defines the variable to which the EXAMPLE clause
+    applies.
+  }];
+}
+
+def OpenMP_ExampleClause : OpenMP_ExampleClauseSkip<>;
+```
+
+### Adding an Operation
+
+Operations in the OpenMP dialect, located in
+[OpenMPOps.td](https://github.com/llvm/llvm-project/blob/main/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td),
+can be defined like any other regular operation by just specifying a `mnemonic`
+and optional list of `traits` when inheriting from `OpenMP_Op`, and then
+defining the expected `description`, `arguments`, etc. properties inside of its
+body. However, in most cases, basing the operation definition on its list of
+accepted clauses is significantly simpler because some of the properties can
+just be inherited from these clauses.
+
+In general, the way to achieve this is to specify, in addition to the `mnemonic`
+and optional list of `traits`, a list of `clauses` where all the applicable
+`OpenMP_<Name>Clause` definitions are added. Then, the only properties that
+would have to be defined in the operation's body are the `summary` and
+`description`. For the latter, only the operation itself would have to be
+defined, and the description for its clause-inherited arguments is appended
+through the inherited `clausesDescription` property.
+
+If the operation is intended to have a single region, this is better achieved by
+setting the `singleRegion=true` template argument of `OpenMP_Op` rather manually
+populating the `regions` property of the operation, because that way the default
+`assemblyFormat` is also updated correspondingly.
+
+For example:
+
+```tablegen
+def ExampleOp : OpenMP_Op<"example", traits = [
+    AttrSizedOperandSegments, ...
+  ], clauses = [
+    OpenMP_AlignedClause, OpenMP_IfClause, OpenMP_LinearClause, ...
+  ], singleRegion = true> {
+  let summary = "example construct";
+  let description = [{
+    The example construct represents...
+  }] # clausesDescription;
+}
+```
+
+This is possible because the `arguments`, `assemblyFormat` and
+`extraClassDeclaration` properties of the operation are by default
+populated by concatenating the corresponding properties of the clauses on the
+list. In the case of the `assemblyFormat`, this also involves splitting the
+format strings for required clauses from the ones for optional clauses. The
+latter are wrapped in an `oilist()` and interleaved with "|" instead of spaces.
+
+### Overriding Clause-Inherited Properties
+
+Although the clause-based definition of operations can greatly reduce work, it's
+also somewhat restrictive, since there may be some situations where only part of
+the operation definition can be automated in that manner. For a fine-grained
+control over properties inherited from each clause two features are available:
+
+  - Inhibition of properties. By using `OpenMP_<Name>ClauseSkip` tablegen
+classes, the list of properties copied from the clause to the operation can be
+selected. For example, `OpenMP_IfClauseSkip<assemblyFormat = true>` would result
+in every property defined for the `OpenMP_IfClause` except for the
+`assemblyFormat` being used to initially populate the properties of the
+operation.
+  - Augmentation of properties. There are times when there is a need to add to
+a clause-populated operation property. Instead of overriding the property in the
+definition of the operation and having to manually replicate what would
+otherwise be automatically populated before adding to it, some internal
+properties are defined to hold this default value: `clausesArgs`,
+`clausesAssemblyFormat` and `clausesExtraClassDeclaration`.
+
+In the following example, assuming both the `OpenMP_InReductionClause` and the
+`OpenMP_ReductionClause` define a `getReductionVars` extra class declaration,
+we skip the conflicting `extraClassDeclaration`s inherited by both clauses and
+provide another implementation, without having to also re-define other
+declarations inherited from the `OpenMP_AllocateClause`:
+
+```tablegen
+def ExampleOp : OpenMP_Op<"example", traits = [
+    AttrSizedOperandSegments, ...
+  ], clauses = [
+    OpenMP_AllocateClause,
+    OpenMP_InReductionClauseSkip<extraClassDeclaration = true>,
+    OpenMP_ReductionClauseSkip<extraClassDeclaration = true>
+  ], singleRegion = true> {
+  let summary = "example construct";
+  let description = [{
+    This operation represents...
+  }] # clausesDescription;
+
+  // Override the clause-populated extraClassDeclaration and add the default
+  // back via appending clausesExtraClassDeclaration to it. This has the effect
+  // of adding one declaration. Since this property is skipped for the
+  // InReduction and Reduction clauses, clausesExtraClassDeclaration won't
+  // incorporate the definition of this property for these clauses.
+  let extraClassDeclaration = [{
+    SmallVector<Value> getReductionVars() {
+      // Concatenate inReductionVars and reductionVars and return the result...
+    }
+  }] # clausesExtraClassDeclaration;
+}
+```
+
+These features are intended for complex edge cases, but an effort should be made
+to avoid having to use them, since they may introduce inconsistencies and
+complexity to the dialect.
+
+### Tablegen Verification Pass
+
+As a result of the implicit way in which fundamental properties of MLIR
+operations are populated following this approach, and the ability to override
+them, forgetting to append clause-inherited values might result in hard to debug
+tablegen errors.
+
+For this reason, the `-verify-openmp-ops` tablegen pseudo-backend was created.
+It runs before any other tablegen backends are triggered for the
+[OpenMPOps.td](https://github.com/llvm/llvm-project/blob/main/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td)
+file and warns any time a property defined for a clause is not found in the
+corresponding operation, except if it is explicitly skipped as described
+[above](#overriding-clause-inherited-properties). This way, in case of a later
+tablegen failure while processing OpenMP dialect operations, earlier messages
+triggered by that pass can point to a likely solution.

>From 3e8a41fddddbdc8bfa1575c0ea82f19d9e09c17f Mon Sep 17 00:00:00 2001
From: Sergio Afonso <safonsof at amd.com>
Date: Fri, 13 Sep 2024 16:08:59 +0100
Subject: [PATCH 2/2] Update documentation after changes to OpenMP_Clause

---
 mlir/docs/Dialects/OpenMPDialect/_index.md | 36 ++++++++++++----------
 1 file changed, 20 insertions(+), 16 deletions(-)

diff --git a/mlir/docs/Dialects/OpenMPDialect/_index.md b/mlir/docs/Dialects/OpenMPDialect/_index.md
index 8abdc731675fc0..b9c53fcb9fa7be 100644
--- a/mlir/docs/Dialects/OpenMPDialect/_index.md
+++ b/mlir/docs/Dialects/OpenMPDialect/_index.md
@@ -64,10 +64,10 @@ OpenMP clause definitions are located in
 [OpenMPClauses.td](https://github.com/llvm/llvm-project/blob/main/mlir/include/mlir/Dialect/OpenMP/OpenMPClauses.td).
 For each clause, an `OpenMP_Clause` subclass and a definition based on it must
 be created. The subclass must take a `bit` template argument for each of the
-properties it may define, listed below, which is forwarded to the base class.
-The definition must be an instantiation of the base class where all these
-template arguments are set to `false`. The definition's name must be
-`OpenMP_<Name>Clause`, whereas its base class' must be
+properties it can populate on associated `OpenMP_Op`s. These must be forwarded
+to the base class. The definition must be an instantiation of the base class
+where all these template arguments are set to `false`. The definition's name
+must be `OpenMP_<Name>Clause`, whereas its base class' must be
 `OpenMP_<Name>ClauseSkip`. Following this pattern makes it possible to
 optionally skip the inheritance of some properties when defining operations:
 [more info](#overriding-clause-inherited-properties).
@@ -79,11 +79,10 @@ implies some op trait, like the `map` clause and the `MapClauseOwningInterface`.
 used to represent the clause. Argument names use snake_case and should contain
 the clause name to avoid name clashes between clauses. Variadic arguments
 (non-attributes) must contain the "_vars" suffix.
-  - `string assemblyFormat`: Optional formatting string to produce custom
-human-friendly printers and parsers for arguments associated with the clause.
-It will be combined with assembly formats for other clauses depending on the
-`isRequired` template argument passed to the parent `OpenMP_Clause` class, as
-explained [below](#adding-an-operation). 
+  - `string {req,opt}AssemblyFormat`: Optional formatting strings to produce
+custom human-friendly printers and parsers for arguments associated with the
+clause. It will be combined with assembly formats for other clauses as explained
+[below](#adding-an-operation).
   - `string description`: Optional description text to describe the clause and
 its representation.
   - `string extraClassDeclaration`: Optional C++ declarations to be added to
@@ -95,13 +94,13 @@ For example:
 class OpenMP_ExampleClauseSkip<
     bit traits = false, bit arguments = false, bit assemblyFormat = false,
     bit description = false, bit extraClassDeclaration = false
-  > : OpenMP_Clause</*isRequired=*/false, traits, arguments, assemblyFormat,
-                    description, extraClassDeclaration> {
+  > : OpenMP_Clause<traits, arguments, assemblyFormat, description,
+                    extraClassDeclaration> {
   let arguments = (ins
     Optional<AnyType>:$example_var
   );
 
-  let assemblyFormat = [{
+  let optAssemblyFormat = [{
     `example` `(` $example_var `:` type($example_var) `)`
   }];
 
@@ -156,9 +155,13 @@ def ExampleOp : OpenMP_Op<"example", traits = [
 This is possible because the `arguments`, `assemblyFormat` and
 `extraClassDeclaration` properties of the operation are by default
 populated by concatenating the corresponding properties of the clauses on the
-list. In the case of the `assemblyFormat`, this also involves splitting the
-format strings for required clauses from the ones for optional clauses. The
-latter are wrapped in an `oilist()` and interleaved with "|" instead of spaces.
+list. In the case of the `assemblyFormat`, this involves combining the
+`reqAssemblyFormat` and the `optAssemblyFormat` properties. The
+`reqAssemblyFormat` of all clauses is concatenated first and separated using
+spaces, whereas the `optAssemblyFormat` is wrapped in an `oilist()` and
+interleaved with "|" instead of spaces. The resulting `assemblyFormat` contains
+the required assembly format strings, followed by the optional assembly format
+strings, optionally the `$region` and the `attr-dict`.
 
 ### Overriding Clause-Inherited Properties
 
@@ -178,7 +181,8 @@ a clause-populated operation property. Instead of overriding the property in the
 definition of the operation and having to manually replicate what would
 otherwise be automatically populated before adding to it, some internal
 properties are defined to hold this default value: `clausesArgs`,
-`clausesAssemblyFormat` and `clausesExtraClassDeclaration`.
+`clausesAssemblyFormat`, `clauses{Req,Opt}AssemblyFormat` and
+`clausesExtraClassDeclaration`.
 
 In the following example, assuming both the `OpenMP_InReductionClause` and the
 `OpenMP_ReductionClause` define a `getReductionVars` extra class declaration,



More information about the llvm-branch-commits mailing list