Skip to content

[mlir] Add predicates to tablegen-defined properties #120176

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Dec 18, 2024

Conversation

krzysz00
Copy link
Contributor

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.

@llvmbot
Copy link
Member

llvmbot commented Dec 17, 2024

@llvm/pr-subscribers-mlir

Author: Krzysztof Drewniak (krzysz00)

Changes

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.


Patch is 26.65 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/120176.diff

8 Files Affected:

  • (modified) mlir/include/mlir/IR/Properties.td (+96-33)
  • (modified) mlir/include/mlir/TableGen/Property.h (+5)
  • (modified) mlir/lib/TableGen/Property.cpp (+12-2)
  • (added) mlir/test/IR/test-op-property-predicates.mlir (+148)
  • (modified) mlir/test/lib/Dialect/Test/TestOps.td (+28-4)
  • (added) mlir/test/mlir-tblgen/op-properties-predicates.td (+72)
  • (modified) mlir/test/mlir-tblgen/op-properties.td (+1-1)
  • (modified) mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp (+55)
diff --git a/mlir/include/mlir/IR/Properties.td b/mlir/include/mlir/IR/Properties.td
index 0becf7d0098356..63ba0fda2ac979 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,12 @@ class Property<string storageTypeParam = "", string desc = ""> {
     return convertFromAttribute($_storage, $_attr, $_diag);
   }];
 
+  // The verification predicate for this property. Defaults to And<[]>,
+  // which is trivially true, since properties are always their expected type.
+  // Within the predicate, `$_self` is an instance of the **interface**
+  // type of the property.
+  Pred predicate = ?;
+
   // The call expression to hash the property.
   //
   // Format:
@@ -150,8 +158,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 +232,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 +337,56 @@ 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;
+}
+
+class ConfinedProperty<Property p, Pred pred, string newSummary = "">
+    : Property<p.storageType, !if(!empty(newSummary), p.summary, newSummary)> {
+  let predicate = !if(!initialized(p.predicate), 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 +399,37 @@ 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 _makeChildWrapperPred<string prefix, Property wrappedProp, string suffix> {
+  Pred ret =
+    !if(!initialized(wrappedProp.predicate),
+      Concat<
+        prefix # "[]("
+          # "const " # wrappedProp.storageType # "& baseStore) -> bool { return []("
+          # wrappedProp.interfaceType # " baseIface) -> bool { return (",
+        SubstLeaves<"$_self", "baseIface", wrappedProp.predicate>,
+        "); }(" # !subst("$_storage", "baseStore", wrappedProp.convertFromStorage)
+          # "); }" # suffix
+      >, ?);
+}
+
 /// 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 +462,8 @@ class ArrayProperty<Property elem = Property<>, string desc = ""> :
     return ::mlir::ArrayAttr::get($_ctxt, elems);
   }];
 
+  let predicate = _makeChildWrapperPred<"::llvm::all_of($_self, ", elem, ")">.ret;
+
   defvar theParserBegin = [{
     auto& storage = $_storage;
     auto parseElemFn = [&]() -> ::mlir::ParseResult {
@@ -463,8 +545,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 +556,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 +633,11 @@ class OptionalProperty<Property p, bit canDelegateParsing = 1>
     return ::mlir::ArrayAttr::get($_ctxt, {attr});
   }];
 
+  let predicate = !if(!initialized(p.predicate),
+    Or<[CPred<"!$_self.has_value()">,
+        SubstLeaves<"$_self", "(*($_self))", p.predicate>]>,
+    ?);
+
   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/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..4dba420bc8f825
--- /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 satiisfy 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 satiisfy 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 satiisfy 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 satiisfy 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 satiisfy 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 satiisfy 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 satiisfy 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 satiisfy 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 satiisfy 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..0a036e9f0cae51
--- /dev/null
+++ b/mlir/test/mlir-tblgen/op-properties-predicates.td
@@ -0,0 +1,72 @@
+// 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,...
[truncated]

@llvmbot
Copy link
Member

llvmbot commented Dec 17, 2024

@llvm/pr-subscribers-mlir-ods

Author: Krzysztof Drewniak (krzysz00)

Changes

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.


Patch is 26.65 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/120176.diff

8 Files Affected:

  • (modified) mlir/include/mlir/IR/Properties.td (+96-33)
  • (modified) mlir/include/mlir/TableGen/Property.h (+5)
  • (modified) mlir/lib/TableGen/Property.cpp (+12-2)
  • (added) mlir/test/IR/test-op-property-predicates.mlir (+148)
  • (modified) mlir/test/lib/Dialect/Test/TestOps.td (+28-4)
  • (added) mlir/test/mlir-tblgen/op-properties-predicates.td (+72)
  • (modified) mlir/test/mlir-tblgen/op-properties.td (+1-1)
  • (modified) mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp (+55)
diff --git a/mlir/include/mlir/IR/Properties.td b/mlir/include/mlir/IR/Properties.td
index 0becf7d0098356..63ba0fda2ac979 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,12 @@ class Property<string storageTypeParam = "", string desc = ""> {
     return convertFromAttribute($_storage, $_attr, $_diag);
   }];
 
+  // The verification predicate for this property. Defaults to And<[]>,
+  // which is trivially true, since properties are always their expected type.
+  // Within the predicate, `$_self` is an instance of the **interface**
+  // type of the property.
+  Pred predicate = ?;
+
   // The call expression to hash the property.
   //
   // Format:
@@ -150,8 +158,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 +232,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 +337,56 @@ 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;
+}
+
+class ConfinedProperty<Property p, Pred pred, string newSummary = "">
+    : Property<p.storageType, !if(!empty(newSummary), p.summary, newSummary)> {
+  let predicate = !if(!initialized(p.predicate), 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 +399,37 @@ 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 _makeChildWrapperPred<string prefix, Property wrappedProp, string suffix> {
+  Pred ret =
+    !if(!initialized(wrappedProp.predicate),
+      Concat<
+        prefix # "[]("
+          # "const " # wrappedProp.storageType # "& baseStore) -> bool { return []("
+          # wrappedProp.interfaceType # " baseIface) -> bool { return (",
+        SubstLeaves<"$_self", "baseIface", wrappedProp.predicate>,
+        "); }(" # !subst("$_storage", "baseStore", wrappedProp.convertFromStorage)
+          # "); }" # suffix
+      >, ?);
+}
+
 /// 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 +462,8 @@ class ArrayProperty<Property elem = Property<>, string desc = ""> :
     return ::mlir::ArrayAttr::get($_ctxt, elems);
   }];
 
+  let predicate = _makeChildWrapperPred<"::llvm::all_of($_self, ", elem, ")">.ret;
+
   defvar theParserBegin = [{
     auto& storage = $_storage;
     auto parseElemFn = [&]() -> ::mlir::ParseResult {
@@ -463,8 +545,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 +556,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 +633,11 @@ class OptionalProperty<Property p, bit canDelegateParsing = 1>
     return ::mlir::ArrayAttr::get($_ctxt, {attr});
   }];
 
+  let predicate = !if(!initialized(p.predicate),
+    Or<[CPred<"!$_self.has_value()">,
+        SubstLeaves<"$_self", "(*($_self))", p.predicate>]>,
+    ?);
+
   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/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..4dba420bc8f825
--- /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 satiisfy 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 satiisfy 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 satiisfy 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 satiisfy 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 satiisfy 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 satiisfy 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 satiisfy 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 satiisfy 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 satiisfy 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..0a036e9f0cae51
--- /dev/null
+++ b/mlir/test/mlir-tblgen/op-properties-predicates.td
@@ -0,0 +1,72 @@
+// 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,...
[truncated]

@llvmbot
Copy link
Member

llvmbot commented Dec 17, 2024

@llvm/pr-subscribers-mlir-core

Author: Krzysztof Drewniak (krzysz00)

Changes

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.


Patch is 26.65 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/120176.diff

8 Files Affected:

  • (modified) mlir/include/mlir/IR/Properties.td (+96-33)
  • (modified) mlir/include/mlir/TableGen/Property.h (+5)
  • (modified) mlir/lib/TableGen/Property.cpp (+12-2)
  • (added) mlir/test/IR/test-op-property-predicates.mlir (+148)
  • (modified) mlir/test/lib/Dialect/Test/TestOps.td (+28-4)
  • (added) mlir/test/mlir-tblgen/op-properties-predicates.td (+72)
  • (modified) mlir/test/mlir-tblgen/op-properties.td (+1-1)
  • (modified) mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp (+55)
diff --git a/mlir/include/mlir/IR/Properties.td b/mlir/include/mlir/IR/Properties.td
index 0becf7d0098356..63ba0fda2ac979 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,12 @@ class Property<string storageTypeParam = "", string desc = ""> {
     return convertFromAttribute($_storage, $_attr, $_diag);
   }];
 
+  // The verification predicate for this property. Defaults to And<[]>,
+  // which is trivially true, since properties are always their expected type.
+  // Within the predicate, `$_self` is an instance of the **interface**
+  // type of the property.
+  Pred predicate = ?;
+
   // The call expression to hash the property.
   //
   // Format:
@@ -150,8 +158,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 +232,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 +337,56 @@ 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;
+}
+
+class ConfinedProperty<Property p, Pred pred, string newSummary = "">
+    : Property<p.storageType, !if(!empty(newSummary), p.summary, newSummary)> {
+  let predicate = !if(!initialized(p.predicate), 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 +399,37 @@ 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 _makeChildWrapperPred<string prefix, Property wrappedProp, string suffix> {
+  Pred ret =
+    !if(!initialized(wrappedProp.predicate),
+      Concat<
+        prefix # "[]("
+          # "const " # wrappedProp.storageType # "& baseStore) -> bool { return []("
+          # wrappedProp.interfaceType # " baseIface) -> bool { return (",
+        SubstLeaves<"$_self", "baseIface", wrappedProp.predicate>,
+        "); }(" # !subst("$_storage", "baseStore", wrappedProp.convertFromStorage)
+          # "); }" # suffix
+      >, ?);
+}
+
 /// 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 +462,8 @@ class ArrayProperty<Property elem = Property<>, string desc = ""> :
     return ::mlir::ArrayAttr::get($_ctxt, elems);
   }];
 
+  let predicate = _makeChildWrapperPred<"::llvm::all_of($_self, ", elem, ")">.ret;
+
   defvar theParserBegin = [{
     auto& storage = $_storage;
     auto parseElemFn = [&]() -> ::mlir::ParseResult {
@@ -463,8 +545,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 +556,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 +633,11 @@ class OptionalProperty<Property p, bit canDelegateParsing = 1>
     return ::mlir::ArrayAttr::get($_ctxt, {attr});
   }];
 
+  let predicate = !if(!initialized(p.predicate),
+    Or<[CPred<"!$_self.has_value()">,
+        SubstLeaves<"$_self", "(*($_self))", p.predicate>]>,
+    ?);
+
   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/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..4dba420bc8f825
--- /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 satiisfy 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 satiisfy 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 satiisfy 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 satiisfy 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 satiisfy 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 satiisfy 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 satiisfy 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 satiisfy 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 satiisfy 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..0a036e9f0cae51
--- /dev/null
+++ b/mlir/test/mlir-tblgen/op-properties-predicates.td
@@ -0,0 +1,72 @@
+// 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,...
[truncated]

Copy link
Member

@zero9178 zero9178 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great to me, thank you! I added a few comments but they're all implementation details, no conceptual complaints from my side.

Also adding Jeff as a reviewer given the disucssion at #94732

@zero9178 zero9178 requested a review from Mogball December 17, 2024 12:08
Base automatically changed from users/krzysz00/initialized-predicate-tblgen to main December 18, 2024 02:34
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.
@krzysz00 krzysz00 force-pushed the users/krzysz00/predicates-on-properties branch from e8b571f to 3238de5 Compare December 18, 2024 03:41
Copy link
Member

@zero9178 zero9178 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, perfect thank you!

@krzysz00 krzysz00 merged commit d8399d5 into main Dec 18, 2024
8 checks passed
@krzysz00 krzysz00 deleted the users/krzysz00/predicates-on-properties branch December 18, 2024 22:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
mlir:core MLIR Core Infrastructure mlir:ods mlir
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants