diff --git a/mlir/include/mlir/IR/Constraints.td b/mlir/include/mlir/IR/Constraints.td index 13223aa8abcda..62bc84c387063 100644 --- a/mlir/include/mlir/IR/Constraints.td +++ b/mlir/include/mlir/IR/Constraints.td @@ -93,6 +93,12 @@ class Or children> : CombinedPred; // A predicate that holds if its child does not. class Neg : CombinedPred; +// 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 0becf7d009835..df630ada78d35 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 { // User-readable one line summary used in error reporting messages. If empty, @@ -63,6 +65,13 @@ class Property { 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 { 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 : Property { - // 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($_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 { + 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 { + 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 { 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 { + 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, string desc = ""> : - Property<"::llvm::SmallVector<" # elem.storageType # ">", desc> { - let summary = "array of " # elem.summary; +class ArrayProperty, 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, string desc = ""> : return ::mlir::ArrayAttr::get($_ctxt, elems); }]; + let predicate = !if(!eq(elem.predicate, TruePred), + TruePred, + Concat<"::llvm::all_of($_self, ", _makeStorageWrapperPred.ret, ")">); + defvar theParserBegin = [{ auto& storage = $_storage; auto parseElemFn = [&]() -> ::mlir::ParseResult { @@ -463,8 +549,8 @@ class ArrayProperty, string desc = ""> : }]); } -class IntArrayProperty : - ArrayProperty> { +class IntArrayProperty : + ArrayProperty { // 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 : }]; } -/// 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 { - 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 /// interfaced with as an std::optional.. /// The syntax is `none` (or empty string if elided) for an absent value or @@ -575,6 +637,11 @@ class OptionalProperty 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 702e6756e6a95..386159191ef1f 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 f71dd0bd35f86..e5cfc074d09f4 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 b86b87df91c60..13851167ddaf7 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(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 0000000000000..3a4e2972948a4 --- /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 d24d52f356d88..384139d9c32c0 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:$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($prop) attr-dict"; - let arguments = (ins IntArrayProperty<"int64_t">:$prop); + let arguments = (ins IntArrayProperty:$prop); } def TestOpUsingPropertyInCustomAndOther : TEST_Op<"using_property_in_custom_and_other"> { let assemblyFormat = "custom($prop) prop-dict attr-dict"; let arguments = (ins - IntArrayProperty<"int64_t">:$prop, - IntProperty<"int64_t">:$other + IntArrayProperty:$prop, + I64Property:$other ); } @@ -3253,6 +3253,30 @@ def TestOpWithArrayProperties : TEST_Op<"with_array_properties"> { ); } +def NonNegativeI64Property : ConfinedProperty= 0">, "non-negative int64_t">; + +class NonEmptyArray : ConfinedProperty + , Neg>, + "non-empty array of " # p.summary>; + +def OpWithPropertyPredicates : TEST_Op<"op_with_property_predicates"> { + let arguments = (ins + NonNegativeI64Property:$scalar, + OptionalProperty:$optional, + DefaultValuedProperty:$defaulted, + ConfinedProperty, "between 0 and 5">:$more_constrained, + ArrayProperty:$array, + NonEmptyArray:$non_empty_unconstrained, + NonEmptyArray:$non_empty_constrained, + // Test applying predicates when the fromStorage() on the optional<> isn't trivial. + OptionalProperty>:$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 0000000000000..fa06e14fb1998 --- /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 traits = []> : + Op; + +def NonNegativeI64Property : ConfinedProperty= 0">, "non-negative int64_t">; + +class NonEmptyArray : ConfinedProperty + , Neg>, + "non-empty array of " # p.summary>; + +def OpWithPredicates : NS_Op<"op_with_predicates"> { + let arguments = (ins + NonNegativeI64Property:$scalar, + OptionalProperty:$optional, + DefaultValuedProperty:$defaulted, + ConfinedProperty, "between 0 and 5">:$moreConstrained, + ArrayProperty:$array, + NonEmptyArray:$non_empty_unconstrained, + NonEmptyArray:$non_empty_constrained, + // Test applying predicates when the fromStorage() on the optional<> isn't trivial. + OptionalProperty>:$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 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 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 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 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> 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 918583c9f4ed4..bf32ce42cf6fb 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 { let defaultValue = "::llvm::ArrayRef{}"; let storageTypeValueOverride = "::llvm::SmallVector{}"; } diff --git a/mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp b/mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp index 30a2d4da573dc..a970cbc5caceb 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 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");