[Mlir-commits] [mlir] d8399d5 - [mlir] Add predicates to tablegen-defined properties (#120176)

llvmlistbot at llvm.org llvmlistbot at llvm.org
Wed Dec 18 14:14:02 PST 2024


Author: Krzysztof Drewniak
Date: 2024-12-18T16:13:58-06:00
New Revision: d8399d5dd6a5a7025621eddd97fc0fa1f494bad8

URL: https://github.com/llvm/llvm-project/commit/d8399d5dd6a5a7025621eddd97fc0fa1f494bad8
DIFF: https://github.com/llvm/llvm-project/commit/d8399d5dd6a5a7025621eddd97fc0fa1f494bad8.diff

LOG: [mlir] Add predicates to tablegen-defined properties (#120176)

Give the properties from tablegen a `predicate` field that holds the
predicate that the property needs to satisfy, if one exists, and hook
that field up to verifier generation.

Added: 
    mlir/test/IR/test-op-property-predicates.mlir
    mlir/test/mlir-tblgen/op-properties-predicates.td

Modified: 
    mlir/include/mlir/IR/Constraints.td
    mlir/include/mlir/IR/Properties.td
    mlir/include/mlir/TableGen/Property.h
    mlir/lib/TableGen/Predicate.cpp
    mlir/lib/TableGen/Property.cpp
    mlir/test/lib/Dialect/Test/TestOps.td
    mlir/test/mlir-tblgen/op-properties.td
    mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp

Removed: 
    


################################################################################
diff  --git a/mlir/include/mlir/IR/Constraints.td b/mlir/include/mlir/IR/Constraints.td
index 13223aa8abcdaa..62bc84c3870636 100644
--- a/mlir/include/mlir/IR/Constraints.td
+++ b/mlir/include/mlir/IR/Constraints.td
@@ -93,6 +93,12 @@ class Or<list<Pred> children> : CombinedPred<PredCombinerOr, children>;
 // A predicate that holds if its child does not.
 class Neg<Pred child> : CombinedPred<PredCombinerNot, [child]>;
 
+// A predicate that is always true.
+defvar TruePred = And<[]>;
+
+// A predicate that is always false.
+defvar False = Or<[]>;
+
 // A predicate that substitutes "pat" with "repl" in predicate calls of the
 // leaves of the predicate tree (i.e., not CombinedPred).
 //

diff  --git a/mlir/include/mlir/IR/Properties.td b/mlir/include/mlir/IR/Properties.td
index 0becf7d0098356..df630ada78d358 100644
--- a/mlir/include/mlir/IR/Properties.td
+++ b/mlir/include/mlir/IR/Properties.td
@@ -13,6 +13,8 @@
 #ifndef PROPERTIES
 #define PROPERTIES
 
+include "mlir/IR/Constraints.td"
+
 // Base class for defining properties.
 class Property<string storageTypeParam = "", string desc = ""> {
   // User-readable one line summary used in error reporting messages. If empty,
@@ -63,6 +65,13 @@ class Property<string storageTypeParam = "", string desc = ""> {
     return convertFromAttribute($_storage, $_attr, $_diag);
   }];
 
+  // The verification predicate for this property. Defaults to the true predicate,
+  // since properties are always their expected type.
+  // Within the predicate, `$_self` is an instance of the **interface**
+  // type of the property. Setting this field to ? will also result in a
+  // true predicate but is not recommended, as it breaks composability.
+  Pred predicate = TruePred;
+
   // The call expression to hash the property.
   //
   // Format:
@@ -150,8 +159,8 @@ class Property<string storageTypeParam = "", string desc = ""> {
       return ::mlir::failure();
   }];
 
-  // Base definition for the property. (Will be) used for `OptionalProperty` and
-  // such cases, analogously to `baseAttr`.
+  // Base definition for the property. Used to look through `OptionalProperty`
+  // for some format generation, as with the `baseAttr` field on attributes.
   Property baseProperty = ?;
 
   // Default value for the property within its storage. This should be an expression
@@ -224,8 +233,7 @@ def I64Property : IntProperty<"int64_t">;
 
 class EnumProperty<string storageTypeParam, string desc = "", string default = ""> :
     Property<storageTypeParam, desc> {
-  // TODO: take advantage of EnumAttrInfo and the like to make this share nice
-  // parsing code with EnumAttr.
+  // TODO:  implement predicate for enum validity.
   let writeToMlirBytecode = [{
     $_writer.writeVarInt(static_cast<uint64_t>($_storage));
   }];
@@ -330,6 +338,59 @@ def UnitProperty : Property<"bool", "unit property"> {
   }];
 }
 
+//===----------------------------------------------------------------------===//
+// Property field overwrites
+
+/// Class for giving a property a default value.
+/// This doesn't change anything about the property other than giving it a default
+/// which can be used by ODS to elide printing.
+class DefaultValuedProperty<Property p, string default = "", string storageDefault = ""> : Property<p.storageType, p.summary> {
+  let defaultValue = default;
+  let storageTypeValueOverride = storageDefault;
+  let baseProperty = p;
+  // Keep up to date with `Property` above.
+  let summary = p.summary;
+  let description = p.description;
+  let storageType = p.storageType;
+  let interfaceType = p.interfaceType;
+  let convertFromStorage = p.convertFromStorage;
+  let assignToStorage = p.assignToStorage;
+  let convertToAttribute = p.convertToAttribute;
+  let convertFromAttribute = p.convertFromAttribute;
+  let predicate = p.predicate;
+  let hashProperty = p.hashProperty;
+  let parser = p.parser;
+  let optionalParser = p.optionalParser;
+  let printer = p.printer;
+  let readFromMlirBytecode = p.readFromMlirBytecode;
+  let writeToMlirBytecode = p.writeToMlirBytecode;
+}
+
+/// Apply the predicate `pred` to the property `p`, ANDing it with any
+/// predicates it may already have. If `newSummary` is provided, replace the
+/// summary of `p` with `newSummary`.
+class ConfinedProperty<Property p, Pred pred, string newSummary = "">
+    : Property<p.storageType, !if(!empty(newSummary), p.summary, newSummary)> {
+  let predicate = !if(!ne(p.predicate, TruePred), And<[p.predicate, pred]>, pred);
+  let baseProperty = p;
+  // Keep up to date with `Property` above.
+  let description = p.description;
+  let storageType = p.storageType;
+  let interfaceType = p.interfaceType;
+  let convertFromStorage = p.convertFromStorage;
+  let assignToStorage = p.assignToStorage;
+  let convertToAttribute = p.convertToAttribute;
+  let convertFromAttribute = p.convertFromAttribute;
+  let hashProperty = p.hashProperty;
+  let parser = p.parser;
+  let optionalParser = p.optionalParser;
+  let printer = p.printer;
+  let readFromMlirBytecode = p.readFromMlirBytecode;
+  let writeToMlirBytecode = p.writeToMlirBytecode;
+  let defaultValue = p.defaultValue;
+  let storageTypeValueOverride = p.storageTypeValueOverride;
+}
+
 //===----------------------------------------------------------------------===//
 // Primitive property combinators
 
@@ -342,14 +403,35 @@ class _makePropStorage<Property prop, string name> {
         true : "") # ";";
 }
 
+/// Construct a `Pred`icate `ret` that wraps the predicate of the underlying
+/// property `childProp` with:
+///
+///   [](childProp.storageType& s) {
+///     return [](childProp.interfaceType i) {
+///       return leafSubst(childProp.predicate, "$_self" to "i");
+///     }(childProp.convertFromStorage(s))
+///   }
+///
+/// and then appends `prefix` and `suffix`.
+class _makeStorageWrapperPred<Property wrappedProp> {
+  Pred ret =
+    Concat<
+      "[](" # "const " # wrappedProp.storageType
+        # "& baseStore) -> bool { return []("
+        # wrappedProp.interfaceType # " baseIface) -> bool { return (",
+      SubstLeaves<"$_self", "baseIface", wrappedProp.predicate>,
+      "); }(" # !subst("$_storage", "baseStore", wrappedProp.convertFromStorage)
+        # "); }">;
+}
+
 /// The generic class for arrays of some other property, which is stored as a
 /// `SmallVector` of that property. This uses an `ArrayAttr` as its attribute form
 /// though subclasses can override this, as is the case with IntArrayAttr below.
 /// Those wishing to use a non-default number of SmallVector elements should
 /// subclass `ArrayProperty`.
-class ArrayProperty<Property elem = Property<>, string desc = ""> :
-  Property<"::llvm::SmallVector<" # elem.storageType # ">", desc> {
-  let summary = "array of " # elem.summary;
+class ArrayProperty<Property elem = Property<>, string newSummary = ""> :
+  Property<"::llvm::SmallVector<" # elem.storageType # ">",
+    !if(!empty(newSummary), "array of " # elem.summary, newSummary)> {
   let interfaceType = "::llvm::ArrayRef<" # elem.storageType # ">";
   let convertFromStorage = "::llvm::ArrayRef<" # elem.storageType # ">{$_storage}";
   let assignToStorage = "$_storage.assign($_value.begin(), $_value.end())";
@@ -382,6 +464,10 @@ class ArrayProperty<Property elem = Property<>, string desc = ""> :
     return ::mlir::ArrayAttr::get($_ctxt, elems);
   }];
 
+  let predicate = !if(!eq(elem.predicate, TruePred),
+    TruePred,
+    Concat<"::llvm::all_of($_self, ", _makeStorageWrapperPred<elem>.ret, ")">);
+
   defvar theParserBegin = [{
     auto& storage = $_storage;
     auto parseElemFn = [&]() -> ::mlir::ParseResult {
@@ -463,8 +549,8 @@ class ArrayProperty<Property elem = Property<>, string desc = ""> :
     }]);
 }
 
-class IntArrayProperty<string storageTypeParam = "", string desc = ""> :
-    ArrayProperty<IntProperty<storageTypeParam, desc>> {
+class IntArrayProperty<Property elem, string newSummary=""> :
+    ArrayProperty<elem, newSummary> {
   // Bring back the trivial conversions we don't get in the general case.
   let convertFromAttribute = [{
     return convertFromAttribute($_storage, $_attr, $_diag);
@@ -474,30 +560,6 @@ class IntArrayProperty<string storageTypeParam = "", string desc = ""> :
   }];
 }
 
-/// Class for giving a property a default value.
-/// This doesn't change anything about the property other than giving it a default
-/// which can be used by ODS to elide printing.
-class DefaultValuedProperty<Property p, string default = "", string storageDefault = ""> : Property<p.storageType, p.summary> {
-  let defaultValue = default;
-  let storageTypeValueOverride = storageDefault;
-  let baseProperty = p;
-  // Keep up to date with `Property` above.
-  let summary = p.summary;
-  let description = p.description;
-  let storageType = p.storageType;
-  let interfaceType = p.interfaceType;
-  let convertFromStorage = p.convertFromStorage;
-  let assignToStorage = p.assignToStorage;
-  let convertToAttribute = p.convertToAttribute;
-  let convertFromAttribute = p.convertFromAttribute;
-  let hashProperty = p.hashProperty;
-  let parser = p.parser;
-  let optionalParser = p.optionalParser;
-  let printer = p.printer;
-  let readFromMlirBytecode = p.readFromMlirBytecode;
-  let writeToMlirBytecode = p.writeToMlirBytecode;
-}
-
 /// An optional property, stored as an std::optional<p.storageType>
 /// interfaced with as an std::optional<p.interfaceType>..
 /// The syntax is `none` (or empty string if elided) for an absent value or
@@ -575,6 +637,11 @@ class OptionalProperty<Property p, bit canDelegateParsing = 1>
     return ::mlir::ArrayAttr::get($_ctxt, {attr});
   }];
 
+  let predicate = !if(!ne(p.predicate, TruePred),
+    Or<[CPred<"!$_self.has_value()">,
+        SubstLeaves<"$_self", "(*($_self))", p.predicate>]>,
+    TruePred);
+
   defvar delegatedParserBegin = [{
     if (::mlir::succeeded($_parser.parseOptionalKeyword("none"))) {
       $_storage = std::nullopt;

diff  --git a/mlir/include/mlir/TableGen/Property.h b/mlir/include/mlir/TableGen/Property.h
index 702e6756e6a95c..386159191ef1f4 100644
--- a/mlir/include/mlir/TableGen/Property.h
+++ b/mlir/include/mlir/TableGen/Property.h
@@ -27,6 +27,7 @@ namespace mlir {
 namespace tblgen {
 class Dialect;
 class Type;
+class Pred;
 
 // Wrapper class providing helper methods for accessing MLIR Property defined
 // in TableGen. This class should closely reflect what is defined as class
@@ -74,6 +75,10 @@ class Property {
     return convertFromAttributeCall;
   }
 
+  // Return the property's predicate. Properties that didn't come from
+  // tablegen (the hardcoded ones) have the null predicate.
+  Pred getPredicate() const;
+
   // Returns the method call which parses this property from textual MLIR.
   StringRef getParserCall() const { return parserCall; }
 

diff  --git a/mlir/lib/TableGen/Predicate.cpp b/mlir/lib/TableGen/Predicate.cpp
index f71dd0bd35f86c..e5cfc074d09f4e 100644
--- a/mlir/lib/TableGen/Predicate.cpp
+++ b/mlir/lib/TableGen/Predicate.cpp
@@ -235,6 +235,16 @@ propagateGroundTruth(PredNode *node,
     return node;
   }
 
+  if (node->kind == PredCombinerKind::And && node->children.empty()) {
+    node->kind = PredCombinerKind::True;
+    return node;
+  }
+
+  if (node->kind == PredCombinerKind::Or && node->children.empty()) {
+    node->kind = PredCombinerKind::False;
+    return node;
+  }
+
   // Otherwise, look at child nodes.
 
   // Move child nodes into some local variable so that they can be optimized

diff  --git a/mlir/lib/TableGen/Property.cpp b/mlir/lib/TableGen/Property.cpp
index b86b87df91c607..13851167ddaf75 100644
--- a/mlir/lib/TableGen/Property.cpp
+++ b/mlir/lib/TableGen/Property.cpp
@@ -14,6 +14,7 @@
 #include "mlir/TableGen/Property.h"
 #include "mlir/TableGen/Format.h"
 #include "mlir/TableGen/Operator.h"
+#include "mlir/TableGen/Predicate.h"
 #include "llvm/TableGen/Record.h"
 
 using namespace mlir;
@@ -68,8 +69,8 @@ Property::Property(StringRef summary, StringRef description,
                    StringRef writeToMlirBytecodeCall,
                    StringRef hashPropertyCall, StringRef defaultValue,
                    StringRef storageTypeValueOverride)
-    : summary(summary), description(description), storageType(storageType),
-      interfaceType(interfaceType),
+    : def(nullptr), summary(summary), description(description),
+      storageType(storageType), interfaceType(interfaceType),
       convertFromStorageCall(convertFromStorageCall),
       assignToStorageCall(assignToStorageCall),
       convertToAttributeCall(convertToAttributeCall),
@@ -91,6 +92,15 @@ StringRef Property::getPropertyDefName() const {
   return def->getName();
 }
 
+Pred Property::getPredicate() const {
+  if (!def)
+    return Pred();
+  const llvm::RecordVal *maybePred = def->getValue("predicate");
+  if (!maybePred || !maybePred->getValue())
+    return Pred();
+  return Pred(maybePred->getValue());
+}
+
 Property Property::getBaseProperty() const {
   if (const auto *defInit =
           llvm::dyn_cast<llvm::DefInit>(def->getValueInit("baseProperty"))) {

diff  --git a/mlir/test/IR/test-op-property-predicates.mlir b/mlir/test/IR/test-op-property-predicates.mlir
new file mode 100644
index 00000000000000..3a4e2972948a43
--- /dev/null
+++ b/mlir/test/IR/test-op-property-predicates.mlir
@@ -0,0 +1,148 @@
+// RUN: mlir-opt -split-input-file -verify-diagnostics %s
+
+test.op_with_property_predicates <{
+  scalar = 1 : i64,
+  optional = [2 : i64],
+  defaulted = 3 : i64,
+  more_constrained = 4 : i64,
+  array = [],
+  non_empty_unconstrained = [5: i64],
+  non_empty_constrained = [6 : i64],
+  non_empty_optional = [[7 : i64]],
+  unconstrained = 8 : i64}>
+
+// -----
+
+test.op_with_property_predicates <{
+  scalar = 1 : i64,
+  more_constrained = 4 : i64,
+  array = [],
+  non_empty_unconstrained = [5: i64],
+  non_empty_constrained = [6 : i64],
+  unconstrained = 8 : i64}>
+
+// -----
+
+// expected-error @+1 {{'test.op_with_property_predicates' op property 'scalar' failed to satisfy constraint: non-negative int64_t}}
+test.op_with_property_predicates <{
+  scalar = -1 : i64,
+  optional = [2 : i64],
+  defaulted = 3 : i64,
+  more_constrained = 4 : i64,
+  array = [],
+  non_empty_unconstrained = [5: i64],
+  non_empty_constrained = [6 : i64],
+  non_empty_optional = [[7 : i64]],
+  unconstrained = 8 : i64}>
+
+// -----
+
+// expected-error @+1 {{'test.op_with_property_predicates' op property 'optional' failed to satisfy constraint: optional non-negative int64_t}}
+test.op_with_property_predicates <{
+  scalar = 1 : i64,
+  optional = [-1 : i64],
+  defaulted = 3 : i64,
+  more_constrained = 4 : i64,
+  array = [],
+  non_empty_unconstrained = [5: i64],
+  non_empty_constrained = [6 : i64],
+  non_empty_optional = [[7 : i64]],
+  unconstrained = 8 : i64}>
+
+// -----
+
+// expected-error @+1 {{'test.op_with_property_predicates' op property 'defaulted' failed to satisfy constraint: non-negative int64_t}}
+test.op_with_property_predicates <{
+  scalar = 1 : i64,
+  optional = [2 : i64],
+  defaulted = -1 : i64,
+  more_constrained = 4 : i64,
+  array = [],
+  non_empty_unconstrained = [5: i64],
+  non_empty_constrained = [6 : i64],
+  non_empty_optional = [[7 : i64]],
+  unconstrained = 8 : i64}>
+
+// -----
+
+// expected-error @+1 {{'test.op_with_property_predicates' op property 'more_constrained' failed to satisfy constraint: between 0 and 5}}
+test.op_with_property_predicates <{
+  scalar = 1 : i64,
+  optional = [2 : i64],
+  defaulted = 3 : i64,
+  more_constrained = 100 : i64,
+  array = [],
+  non_empty_unconstrained = [5: i64],
+  non_empty_constrained = [6 : i64],
+  non_empty_optional = [[7 : i64]],
+  unconstrained = 8 : i64}>
+
+// -----
+
+// expected-error @+1 {{'test.op_with_property_predicates' op property 'array' failed to satisfy constraint: array of non-negative int64_t}}
+test.op_with_property_predicates <{
+  scalar = 1 : i64,
+  optional = [2 : i64],
+  defaulted = 3 : i64,
+  more_constrained = 4 : i64,
+  array = [-1 : i64],
+  non_empty_unconstrained = [5: i64],
+  non_empty_constrained = [6 : i64],
+  non_empty_optional = [[7 : i64]],
+  unconstrained = 8 : i64}>
+
+// -----
+
+// expected-error @+1 {{'test.op_with_property_predicates' op property 'non_empty_unconstrained' failed to satisfy constraint: non-empty array of int64_t}}
+test.op_with_property_predicates <{
+  scalar = 1 : i64,
+  optional = [2 : i64],
+  defaulted = 3 : i64,
+  more_constrained = 4 : i64,
+  array = [],
+  non_empty_unconstrained = [],
+  non_empty_constrained = [6 : i64],
+  non_empty_optional = [[7 : i64]],
+  unconstrained = 8 : i64}>
+
+// -----
+
+// expected-error @+1 {{'test.op_with_property_predicates' op property 'non_empty_constrained' failed to satisfy constraint: non-empty array of non-negative int64_t}}
+test.op_with_property_predicates <{
+  scalar = 1 : i64,
+  optional = [2 : i64],
+  defaulted = 3 : i64,
+  more_constrained = 4 : i64,
+  array = [],
+  non_empty_unconstrained = [5: i64],
+  non_empty_constrained = [],
+  non_empty_optional = [[7 : i64]],
+  unconstrained = 8 : i64}>
+
+// -----
+
+// expected-error @+1 {{'test.op_with_property_predicates' op property 'non_empty_constrained' failed to satisfy constraint: non-empty array of non-negative int64_t}}
+test.op_with_property_predicates <{
+  scalar = 1 : i64,
+  optional = [2 : i64],
+  defaulted = 3 : i64,
+  more_constrained = 4 : i64,
+  array = [],
+  non_empty_unconstrained = [5: i64],
+  non_empty_constrained = [-1 : i64],
+  non_empty_optional = [[7 : i64]],
+  unconstrained = 8 : i64}>
+
+// -----
+
+// expected-error @+1 {{'test.op_with_property_predicates' op property 'non_empty_optional' failed to satisfy constraint: optional non-empty array of non-negative int64_t}}
+test.op_with_property_predicates <{
+  scalar = 1 : i64,
+  optional = [2 : i64],
+  defaulted = 3 : i64,
+  more_constrained = 4 : i64,
+  array = [],
+  non_empty_unconstrained = [5: i64],
+  non_empty_constrained = [6 : i64],
+  non_empty_optional = [[]],
+  unconstrained = 8 : i64}>

diff  --git a/mlir/test/lib/Dialect/Test/TestOps.td b/mlir/test/lib/Dialect/Test/TestOps.td
index d24d52f356d88f..384139d9c32c02 100644
--- a/mlir/test/lib/Dialect/Test/TestOps.td
+++ b/mlir/test/lib/Dialect/Test/TestOps.td
@@ -2998,7 +2998,7 @@ def TestOpWithProperties : TEST_Op<"with_properties"> {
     StrAttr:$b, // Attributes can directly be used here.
     StringProperty:$c,
     BoolProperty:$flag,
-    IntArrayProperty<"int64_t">:$array // example of an array
+    IntArrayProperty<I64Property>:$array // example of an array
   );
 }
 
@@ -3040,15 +3040,15 @@ def TestOpWithEmptyProperties : TEST_Op<"empty_properties"> {
 
 def TestOpUsingPropertyInCustom : TEST_Op<"using_property_in_custom"> {
   let assemblyFormat = "custom<UsingPropertyInCustom>($prop) attr-dict";
-  let arguments = (ins IntArrayProperty<"int64_t">:$prop);
+  let arguments = (ins IntArrayProperty<I64Property>:$prop);
 }
 
 def TestOpUsingPropertyInCustomAndOther
   : TEST_Op<"using_property_in_custom_and_other"> {
   let assemblyFormat = "custom<UsingPropertyInCustom>($prop) prop-dict attr-dict";
   let arguments = (ins
-    IntArrayProperty<"int64_t">:$prop,
-    IntProperty<"int64_t">:$other
+    IntArrayProperty<I64Property>:$prop,
+    I64Property:$other
   );
 }
 
@@ -3253,6 +3253,30 @@ def TestOpWithArrayProperties : TEST_Op<"with_array_properties"> {
   );
 }
 
+def NonNegativeI64Property : ConfinedProperty<I64Property,
+  CPred<"$_self >= 0">, "non-negative int64_t">;
+
+class NonEmptyArray<Property p> : ConfinedProperty
+    <ArrayProperty<p>, Neg<CPred<"$_self.empty()">>,
+    "non-empty array of " # p.summary>;
+
+def OpWithPropertyPredicates : TEST_Op<"op_with_property_predicates"> {
+  let arguments = (ins
+    NonNegativeI64Property:$scalar,
+    OptionalProperty<NonNegativeI64Property>:$optional,
+    DefaultValuedProperty<NonNegativeI64Property, "0">:$defaulted,
+    ConfinedProperty<NonNegativeI64Property,
+      CPred<"$_self <= 5">, "between 0 and 5">:$more_constrained,
+    ArrayProperty<NonNegativeI64Property>:$array,
+    NonEmptyArray<I64Property>:$non_empty_unconstrained,
+    NonEmptyArray<NonNegativeI64Property>:$non_empty_constrained,
+    // Test applying predicates when the fromStorage() on the optional<> isn't trivial.
+    OptionalProperty<NonEmptyArray<NonNegativeI64Property>>:$non_empty_optional,
+    I64Property:$unconstrained
+  );
+  let assemblyFormat = "attr-dict prop-dict";
+}
+
 //===----------------------------------------------------------------------===//
 // Test Dataflow
 //===----------------------------------------------------------------------===//

diff  --git a/mlir/test/mlir-tblgen/op-properties-predicates.td b/mlir/test/mlir-tblgen/op-properties-predicates.td
new file mode 100644
index 00000000000000..fa06e14fb19986
--- /dev/null
+++ b/mlir/test/mlir-tblgen/op-properties-predicates.td
@@ -0,0 +1,73 @@
+// RUN: mlir-tblgen -gen-op-defs -I %S/../../include %s | FileCheck %s
+
+include "mlir/IR/Constraints.td"
+include "mlir/IR/EnumAttr.td"
+include "mlir/IR/OpBase.td"
+include "mlir/IR/Properties.td"
+
+def Test_Dialect : Dialect {
+  let name = "test";
+  let cppNamespace = "foobar";
+}
+class NS_Op<string mnemonic, list<Trait> traits = []> :
+    Op<Test_Dialect, mnemonic, traits>;
+
+def NonNegativeI64Property : ConfinedProperty<I64Property,
+  CPred<"$_self >= 0">, "non-negative int64_t">;
+
+class NonEmptyArray<Property p> : ConfinedProperty
+    <ArrayProperty<p>, Neg<CPred<"$_self.empty()">>,
+    "non-empty array of " # p.summary>;
+
+def OpWithPredicates : NS_Op<"op_with_predicates"> {
+  let arguments = (ins
+    NonNegativeI64Property:$scalar,
+    OptionalProperty<NonNegativeI64Property>:$optional,
+    DefaultValuedProperty<NonNegativeI64Property, "0">:$defaulted,
+    ConfinedProperty<NonNegativeI64Property,
+      CPred<"$_self <= 5">, "between 0 and 5">:$moreConstrained,
+    ArrayProperty<NonNegativeI64Property>:$array,
+    NonEmptyArray<I64Property>:$non_empty_unconstrained,
+    NonEmptyArray<NonNegativeI64Property>:$non_empty_constrained,
+    // Test applying predicates when the fromStorage() on the optional<> isn't trivial.
+    OptionalProperty<NonEmptyArray<NonNegativeI64Property>>:$non_empty_optional,
+    I64Property:$unconstrained
+  );
+}
+
+// CHECK-LABEL: OpWithPredicates::verifyInvariantsImpl()
+// Note: for test readibility, we capture [[maybe_unused]] into the variable maybe_unused
+// CHECK: [[maybe_unused:\[\[maybe_unused\]\]]] int64_t tblgen_scalar = this->getScalar();
+// CHECK: if (!((tblgen_scalar >= 0)))
+// CHECK-NEXT:  return emitOpError("property 'scalar' failed to satisfy constraint: non-negative int64_t");
+
+// CHECK: [[maybe_unused]] std::optional<int64_t> tblgen_optional = this->getOptional();
+// CHECK: if (!(((!tblgen_optional.has_value())) || (((*(tblgen_optional)) >= 0))))
+// CHECK-NEXT:   return emitOpError("property 'optional' failed to satisfy constraint: optional non-negative int64_t");
+
+// CHECK: [[maybe_unused]] int64_t tblgen_defaulted = this->getDefaulted();
+// CHECK: if (!((tblgen_defaulted >= 0)))
+// CHECK-NEXT:  return emitOpError("property 'defaulted' failed to satisfy constraint: non-negative int64_t");
+
+// CHECK: [[maybe_unused]] int64_t tblgen_moreConstrained = this->getMoreConstrained();
+// CHECK: if (!(((tblgen_moreConstrained >= 0)) && ((tblgen_moreConstrained <= 5))))
+// CHECK-NEXT:  return emitOpError("property 'moreConstrained' failed to satisfy constraint: between 0 and 5");
+
+// CHECK: [[maybe_unused]] ::llvm::ArrayRef<int64_t> tblgen_array = this->getArray();
+// CHECK: if (!(::llvm::all_of(tblgen_array, [](const int64_t& baseStore) -> bool { return [](int64_t baseIface) -> bool { return ((baseIface >= 0)); }(baseStore); })))
+// CHECK-NEXT: return emitOpError("property 'array' failed to satisfy constraint: array of non-negative int64_t");
+
+// CHECK: [[maybe_unused]] ::llvm::ArrayRef<int64_t> tblgen_nonEmptyUnconstrained = this->getNonEmptyUnconstrained();
+// CHECK: if (!(!((tblgen_nonEmptyUnconstrained.empty()))))
+// CHECK-NEXT: return emitOpError("property 'non_empty_unconstrained' failed to satisfy constraint: non-empty array of int64_t");
+
+// CHECK: [[maybe_unused]] ::llvm::ArrayRef<int64_t> tblgen_nonEmptyConstrained = this->getNonEmptyConstrained();
+// CHECK: if (!((::llvm::all_of(tblgen_nonEmptyConstrained, [](const int64_t& baseStore) -> bool { return [](int64_t baseIface) -> bool { return ((baseIface >= 0)); }(baseStore); })) && (!((tblgen_nonEmptyConstrained.empty())))))
+// CHECK-NEXT:   return emitOpError("property 'non_empty_constrained' failed to satisfy constraint: non-empty array of non-negative int64_t");
+
+// CHECK: [[maybe_unused]] std::optional<::llvm::ArrayRef<int64_t>> tblgen_nonEmptyOptional = this->getNonEmptyOptional();
+// CHECK: (!(((!tblgen_nonEmptyOptional.has_value())) || ((::llvm::all_of((*(tblgen_nonEmptyOptional)), [](const int64_t& baseStore) -> bool { return [](int64_t baseIface) -> bool { return ((baseIface >= 0)); }(baseStore); })) && (!(((*(tblgen_nonEmptyOptional)).empty()))))))
+// CHECK-NEXT:   return emitOpError("property 'non_empty_optional' failed to satisfy constraint: optional non-empty array of non-negative int64_t");
+
+// CHECK-NOT: int64_t tblgen_unconstrained
+// CHECK: return ::mlir::success();

diff  --git a/mlir/test/mlir-tblgen/op-properties.td b/mlir/test/mlir-tblgen/op-properties.td
index 918583c9f4ed4b..bf32ce42cf6fbf 100644
--- a/mlir/test/mlir-tblgen/op-properties.td
+++ b/mlir/test/mlir-tblgen/op-properties.td
@@ -20,7 +20,7 @@ def OpWithAttr : NS_Op<"op_with_attr">{
 // Test required and optional properties
 // ---
 
-def DefaultI64Array : IntArrayProperty<"int64_t"> {
+def DefaultI64Array : IntArrayProperty<I64Property> {
   let defaultValue = "::llvm::ArrayRef<int64_t>{}";
   let storageTypeValueOverride = "::llvm::SmallVector<int64_t>{}";
 }

diff  --git a/mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp b/mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp
index 30a2d4da573dc5..a970cbc5cacebe 100644
--- a/mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp
+++ b/mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp
@@ -1033,6 +1033,61 @@ while (true) {{
       emitVerifier(namedAttr.attr, namedAttr.name, getVarName(namedAttr.name));
 }
 
+static void genPropertyVerifier(const OpOrAdaptorHelper &emitHelper,
+                                FmtContext &ctx, MethodBody &body) {
+
+  // Code to get a reference to a property into a variable to avoid multiple
+  // evaluations while verifying a property.
+  // {0}: Property variable name.
+  // {1}: Property name, with the first letter capitalized, to find the getter.
+  // {2}: Property interface type.
+  const char *const fetchProperty = R"(
+  [[maybe_unused]] {2} {0} = this->get{1}();
+)";
+
+  // Code to verify that the predicate of a property holds. Embeds the
+  // condition inline.
+  // {0}: Property condition code, with tgfmt() applied.
+  // {1}: Emit error prefix.
+  // {2}: Property name.
+  // {3}: Property description.
+  const char *const verifyProperty = R"(
+  if (!({0}))
+    return {1}"property '{2}' failed to satisfy constraint: {3}");
+)";
+
+  // Prefix variables with `tblgen_` to avoid hiding the attribute accessor.
+  const auto getVarName = [&](const NamedProperty &prop) {
+    std::string varName =
+        convertToCamelFromSnakeCase(prop.name, /*capitalizeFirst=*/false);
+    return (tblgenNamePrefix + Twine(varName)).str();
+  };
+
+  for (const NamedProperty &prop : emitHelper.getOp().getProperties()) {
+    Pred predicate = prop.prop.getPredicate();
+    // Null predicate, nothing to verify.
+    if (predicate == Pred())
+      continue;
+
+    std::string rawCondition = predicate.getCondition();
+    if (rawCondition == "true")
+      continue;
+    bool needsOp = StringRef(rawCondition).contains("$_op");
+    if (needsOp && !emitHelper.isEmittingForOp())
+      continue;
+
+    auto scope = body.scope("{\n", "}\n", /*indent=*/true);
+    std::string varName = getVarName(prop);
+    std::string getterName =
+        convertToCamelFromSnakeCase(prop.name, /*capitalizeFirst=*/true);
+    body << formatv(fetchProperty, varName, getterName,
+                    prop.prop.getInterfaceType());
+    body << formatv(verifyProperty, tgfmt(rawCondition, &ctx.withSelf(varName)),
+                    emitHelper.emitErrorPrefix(), prop.name,
+                    prop.prop.getSummary());
+  }
+}
+
 /// Include declarations specified on NativeTrait
 static std::string formatExtraDeclarations(const Operator &op) {
   SmallVector<StringRef> extraDeclarations;
@@ -3726,6 +3781,7 @@ void OpEmitter::genVerifier() {
   bool useProperties = emitHelper.hasProperties();
 
   populateSubstitutions(emitHelper, verifyCtx);
+  genPropertyVerifier(emitHelper, verifyCtx, implBody);
   genAttributeVerifier(emitHelper, verifyCtx, implBody, staticVerifierEmitter,
                        useProperties);
   genOperandResultVerifier(implBody, op.getOperands(), "operand");


        


More information about the Mlir-commits mailing list