[Mlir-commits] [mlir] [MLIR][ODS] Parse prop-dict fields with custom parsers (PR #217590)
Mehdi Amini
llvmlistbot at llvm.org
Thu Aug 20 03:52:12 PDT 2026
https://github.com/joker-eph created https://github.com/llvm/llvm-project/pull/217590
Teach generated operation parsers to accept a key-value spelling for prop-dict and dispatch known fields through their ODS parsers. Keep the DictionaryAttr spelling as a compatibility path and use attribute conversion when a property has no usable FieldParser.
See #155475
Assisted-by: Codex
>From 92800dd7a7728c30a88f99b3d33a664fa437404b Mon Sep 17 00:00:00 2001
From: Mehdi Amini <joker.eph at gmail.com>
Date: Wed, 19 Aug 2026 07:08:17 -0700
Subject: [PATCH] [MLIR][ODS] Parse prop-dict fields with custom parsers
Teach generated operation parsers to accept a key-value spelling for
prop-dict and dispatch known fields through their ODS parsers. Keep the
DictionaryAttr spelling as a compatibility path and use attribute conversion
when a property has no usable FieldParser.
Assisted-by: Codex
---
mlir/docs/DefiningDialects/Operations.md | 14 +-
mlir/include/mlir/IR/DialectImplementation.h | 80 +++++-
mlir/include/mlir/IR/OpDefinition.h | 22 +-
mlir/include/mlir/TableGen/Property.h | 4 +
mlir/lib/TableGen/Property.cpp | 15 ++
mlir/test/IR/properties-invalid.mlir | 79 ++++++
mlir/test/IR/properties.mlir | 95 ++++++-
mlir/test/lib/Dialect/Test/TestOps.h | 34 +++
mlir/test/lib/Dialect/Test/TestOps.td | 264 +++++++++++++++++++
mlir/test/mlir-tblgen/enums-gen.td | 15 ++
mlir/tools/mlir-tblgen/EnumsGen.cpp | 8 +-
mlir/tools/mlir-tblgen/OpFormatGen.cpp | 218 +++++++++++++++
12 files changed, 835 insertions(+), 13 deletions(-)
create mode 100644 mlir/test/IR/properties-invalid.mlir
diff --git a/mlir/docs/DefiningDialects/Operations.md b/mlir/docs/DefiningDialects/Operations.md
index 1ef96130d836d..419041a52b0da 100644
--- a/mlir/docs/DefiningDialects/Operations.md
+++ b/mlir/docs/DefiningDialects/Operations.md
@@ -760,9 +760,17 @@ The available directives are as follows:
* `prop-dict`
- - Represents the properties of the operation converted to a dictionary.
- - Any property or inherent attribute that are not used elsewhere in the
- format are parsed and printed as part of this dictionary.
+ - Represents the properties of the operation. The generated parser
+ accepts a `<key = value, ...>` list. Explicit property parsers and
+ inherent-attribute parsers must consume exactly one value and leave the
+ comma separating it from the next entry unconsumed. Properties relying
+ on the default parser use attribute conversion instead when no
+ `FieldParser` specialization is available or when the selected
+ specialization declares `isKeyValueCompositional = false`.
+ - The legacy `<{key = attribute, ...}>` dictionary spelling is also
+ accepted when parsing and is used by the generated printer.
+ - Any property or inherent attribute that is not used elsewhere in the
+ format is parsed and printed as part of this list.
- If present, the `attr-dict` will not contain any inherent attributes.
* `custom < UserDirective > ( Params )`
diff --git a/mlir/include/mlir/IR/DialectImplementation.h b/mlir/include/mlir/IR/DialectImplementation.h
index 0b4f91cd750b8..2652114b508e3 100644
--- a/mlir/include/mlir/IR/DialectImplementation.h
+++ b/mlir/include/mlir/IR/DialectImplementation.h
@@ -70,6 +70,15 @@ class DialectAsmParser : public AsmParser {
/// Provide a template class that can be specialized by users to dispatch to
/// parsers. Auto-generated parsers generate calls to `FieldParser<T>::parse`,
/// where `T` is the parameter storage type, to parse custom types.
+///
+/// A parser is key-value compositional only if it consumes exactly one value
+/// and leaves the comma separating the next key unconsumed. For example, an
+/// undelimited array parser for `values = 1, 2, next = 9` cannot distinguish
+/// its element commas from the comma before `next` and may try to parse `next`
+/// as another element. Marking it non-compositional lets a keyed property list
+/// use a self-delimiting attribute such as `array<i64: 1, 2>` instead.
+/// Specializations with this behavior, or that may succeed without consuming a
+/// token, should define `isKeyValueCompositional` as false.
template <typename T, typename = T>
struct FieldParser;
@@ -131,6 +140,8 @@ struct FieldParser<
std::optional<AttributeT>,
std::enable_if_t<std::is_base_of<Attribute, AttributeT>::value,
std::optional<AttributeT>>> {
+ static constexpr bool isKeyValueCompositional = false;
+
static FailureOr<std::optional<AttributeT>> parse(AsmParser &parser) {
if constexpr (HasStaticDialectName<AttributeT>::value) {
parser.getContext()->getOrLoadDialect(AttributeT::dialectName);
@@ -151,6 +162,8 @@ template <typename IntT>
struct FieldParser<
std::optional<IntT>,
std::enable_if_t<std::is_integral<IntT>::value, std::optional<IntT>>> {
+ static constexpr bool isKeyValueCompositional = false;
+
static FailureOr<std::optional<IntT>> parse(AsmParser &parser) {
IntT value;
OptionalParseResult result = parser.parseOptionalInteger(value);
@@ -167,14 +180,51 @@ namespace detail {
template <typename T>
using has_push_back_t = decltype(std::declval<T>().push_back(
std::declval<typename T::value_type &&>()));
+
+template <typename StorageType, typename = void>
+struct HasFieldParser : std::false_type {};
+
+template <typename StorageType>
+struct HasFieldParser<StorageType,
+ std::void_t<decltype(sizeof(FieldParser<StorageType>)),
+ decltype(FieldParser<StorageType>::parse(
+ std::declval<OpAsmParser &>()))>>
+ : std::true_type {};
+
+template <typename ContainerT, typename = void>
+struct HasFieldParserContainer : std::false_type {};
+
+template <typename ContainerT>
+struct HasFieldParserContainer<ContainerT,
+ std::void_t<has_push_back_t<ContainerT>>>
+ : HasFieldParser<typename ContainerT::value_type> {};
+
+template <typename Parser, typename = void>
+struct IsKeyValueCompositional : std::true_type {};
+
+template <typename Parser>
+struct IsKeyValueCompositional<
+ Parser, std::void_t<decltype(Parser::isKeyValueCompositional)>>
+ : std::bool_constant<Parser::isKeyValueCompositional> {};
+
+/// Whether the selected FieldParser consumes exactly one value in a keyed
+/// property list. Parser specializations may set isKeyValueCompositional to
+/// false if they can succeed without consuming a token or consume an
+/// undelimited comma-separated list.
+template <typename StorageType>
+struct HasKeyValueFieldParser
+ : std::conjunction<HasFieldParser<StorageType>,
+ IsKeyValueCompositional<FieldParser<StorageType>>> {};
} // namespace detail
/// Parse any container that supports back insertion as a list.
template <typename ContainerT>
-struct FieldParser<ContainerT,
- std::enable_if_t<llvm::is_detected<detail::has_push_back_t,
- ContainerT>::value,
- ContainerT>> {
+struct FieldParser<
+ ContainerT,
+ std::enable_if_t<detail::HasFieldParserContainer<ContainerT>::value,
+ ContainerT>> {
+ static constexpr bool isKeyValueCompositional = false;
+
using ElementT = typename ContainerT::value_type;
static FailureOr<ContainerT> parse(AsmParser &parser) {
ContainerT elements;
@@ -202,6 +252,28 @@ struct FieldParser<AffineMap> {
}
};
+namespace detail {
+/// Parse a property with its FieldParser when one is available, otherwise
+/// fall back to the property's attribute conversion.
+template <typename StorageType, typename ConvertFromAttribute>
+ParseResult
+parsePropertyWithFallback(OpAsmParser &parser, StorageType &storage,
+ ConvertFromAttribute convertFromAttribute) {
+ if constexpr (HasKeyValueFieldParser<StorageType>::value) {
+ auto value = FieldParser<StorageType>::parse(parser);
+ if (failed(value))
+ return failure();
+ storage = std::move(*value);
+ return success();
+ } else {
+ Attribute attr;
+ if (parser.parseAttribute(attr))
+ return failure();
+ return convertFromAttribute(storage, attr);
+ }
+}
+} // namespace detail
+
} // namespace mlir
#endif // MLIR_IR_DIALECTIMPLEMENTATION_H
diff --git a/mlir/include/mlir/IR/OpDefinition.h b/mlir/include/mlir/IR/OpDefinition.h
index fe2fa0a0ccd23..075d39194ed97 100644
--- a/mlir/include/mlir/IR/OpDefinition.h
+++ b/mlir/include/mlir/IR/OpDefinition.h
@@ -1872,6 +1872,16 @@ class Op : public OpState, public Traits<ConcreteType>... {
using detect_has_parse_properties =
llvm::is_detected<has_parse_properties, T>;
+ /// Trait to check if T provides a generated parser for the key-value
+ /// spelling of `prop-dict`.
+ template <typename T, typename... Args>
+ using has_parse_properties_from_key_value_list =
+ decltype(T::parsePropertiesFromKeyValueList(
+ std::declval<OpAsmParser &>(), std::declval<OperationState &>()));
+ template <typename T>
+ using detect_has_parse_properties_from_key_value_list =
+ llvm::is_detected<has_parse_properties_from_key_value_list, T>;
+
/// Trait to check if T provides a 'ConcreteEntity' type alias.
template <typename T>
using has_concrete_entity_t = typename T::ConcreteEntity;
@@ -2039,10 +2049,11 @@ class Op : public OpState, public Traits<ConcreteType>... {
p, ConcreteType::getPropertiesAsAttr(ctx, properties), elidedProps);
}
- /// Parses 'prop-dict' for the operation. Unless overridden, the method will
- /// parse the properties using the generic property dictionary using the
- /// '<{ ... }>' syntax. The resulting properties are stored within the
- /// property structure of 'result', accessible via 'getOrAddProperties'.
+ /// Parses 'prop-dict' for the operation. Generated parsers accept a keyed
+ /// list whose values use their custom assembly parsers, as well as the
+ /// legacy generic '<{ ... }>' dictionary syntax. The resulting properties
+ /// are stored within the property structure of 'result', accessible via
+ /// 'getOrAddProperties'.
template <typename T = ConcreteType>
static ParseResult parseProperties(OpAsmParser &parser,
OperationState &result) {
@@ -2051,6 +2062,9 @@ class Op : public OpState, public Traits<ConcreteType>... {
parser, result.getOrAddProperties<InferredProperties<T>>());
}
+ if constexpr (detect_has_parse_properties_from_key_value_list<T>::value)
+ return T::parsePropertiesFromKeyValueList(parser, result);
+
Attribute propertyDictionary;
if (genericParseProperties(parser, propertyDictionary))
return failure();
diff --git a/mlir/include/mlir/TableGen/Property.h b/mlir/include/mlir/TableGen/Property.h
index 81e6d85720829..5877b54147f99 100644
--- a/mlir/include/mlir/TableGen/Property.h
+++ b/mlir/include/mlir/TableGen/Property.h
@@ -94,6 +94,10 @@ class Property : public PropConstraint {
// Returns the method call which parses this property from textual MLIR.
StringRef getParserCall() const { return parserCall; }
+ // Returns true if this property uses the parser inherited from the base
+ // Property class.
+ bool usesDefaultParser() const;
+
// Returns true if this property has defined an optional parser.
bool hasOptionalParser() const { return !optionalParserCall.empty(); }
diff --git a/mlir/lib/TableGen/Property.cpp b/mlir/lib/TableGen/Property.cpp
index b003d74c7bdee..88a64891b8b86 100644
--- a/mlir/lib/TableGen/Property.cpp
+++ b/mlir/lib/TableGen/Property.cpp
@@ -104,6 +104,21 @@ Pred Property::getPredicate() const {
return Pred(maybePred->getValue());
}
+bool Property::usesDefaultParser() const {
+ const Record *propertyClass = def->getRecords().getClass("Property");
+ if (const auto *baseInit =
+ llvm::dyn_cast<DefInit>(def->getValueInit("baseProperty"))) {
+ Property baseProperty(baseInit);
+ if (getParserCall() == baseProperty.getParserCall())
+ return baseProperty.usesDefaultParser();
+ }
+ // RecordVal retains the source location of the initializer that supplied a
+ // field. An inherited parser therefore points at Property::parser, while an
+ // explicit `let parser` points at the override without inspecting its text.
+ return def->getValue("parser")->getLoc().getPointer() ==
+ propertyClass->getValue("parser")->getLoc().getPointer();
+}
+
Property Property::getBaseProperty() const {
if (const auto *defInit =
llvm::dyn_cast<llvm::DefInit>(def->getValueInit("baseProperty"))) {
diff --git a/mlir/test/IR/properties-invalid.mlir b/mlir/test/IR/properties-invalid.mlir
new file mode 100644
index 0000000000000..a6adf7ed98f8a
--- /dev/null
+++ b/mlir/test/IR/properties-invalid.mlir
@@ -0,0 +1,79 @@
+// RUN: mlir-opt %s -split-input-file -verify-diagnostics
+
+// expected-error @below {{properties dictionary is missing required property: prop}}
+test.with_custom_prop_dict <attr = 1>
+
+// -----
+
+// expected-error @below {{properties dictionary is missing required attribute: attr}}
+test.with_custom_prop_dict <prop = 2>
+
+// -----
+
+// expected-error @below {{duplicate or unknown property in properties dictionary: prop}}
+test.with_custom_prop_dict <attr = 1, prop = 2, prop = 3>
+
+// -----
+
+// expected-error @below {{duplicate or unknown property in properties dictionary: unknown}}
+test.with_custom_prop_dict <attr = 1, prop = 2, unknown = 3>
+
+// -----
+
+// A required property dictionary cannot be omitted entirely.
+// expected-error @below {{properties dictionary is missing required property: prop}}
+test.with_custom_prop_dict
+
+// -----
+
+// expected-error @below {{expected integer value}}
+test.with_custom_prop_dict <attr = 1, prop = bad>
+
+// -----
+
+// expected-error @below {{invalid value for property prop}}
+test.with_wrapped_properties <prop = 1 : i64>
+
+// -----
+
+// A required keyed value may not succeed without consuming a token.
+// expected-error @below {{expected attribute value}}
+test.with_key_value_parser_boundaries <values = array<i64: 1>, maybe = >
+
+// -----
+
+%c0 = arith.constant 0 : i64
+// A segment-size property inferred later in the parser must not be accepted
+// and then silently overwritten.
+// expected-error @below {{unknown property in properties dictionary: operandSegmentSizes}}
+test.variadic_segment_prop %c0 : %c0 : i64 : i64 <operandSegmentSizes = [1, 1]> end
+
+// -----
+
+%c0 = arith.constant 0 : i64
+// expected-error @below {{unknown property in properties dictionary: resultSegmentSizes}}
+test.variadic_segment_prop %c0 : %c0 : i64 : i64 <resultSegmentSizes = [1, 1]> end
+
+// -----
+
+%c0 = arith.constant 0 : i64
+// expected-error @below {{properties dictionary is missing required property: operandSegmentSizes}}
+test.variadic_segment_prop_bulk_type(%c0, %c0, %c0) : (i64, i64, i64) -> (i64, i64, i64) <resultSegmentSizes = [2, 1]>
+
+// -----
+
+%c0 = arith.constant 0 : i64
+// expected-error @below {{properties dictionary is missing required property: resultSegmentSizes}}
+test.variadic_segment_prop_bulk_type(%c0, %c0, %c0) : (i64, i64, i64) -> (i64, i64, i64) <operandSegmentSizes = [2, 1]>
+
+// -----
+
+%c0 = arith.constant 0 : i64
+// expected-error @below {{expected 2 entries for operandSegmentSizes}}
+test.variadic_segment_prop_bulk_type(%c0, %c0, %c0) : (i64, i64, i64) -> (i64, i64, i64) <operandSegmentSizes = [3], resultSegmentSizes = [2, 1]>
+
+// -----
+
+%c0 = arith.constant 0 : i64
+// expected-error @below {{expected 2 entries for resultSegmentSizes}}
+test.variadic_segment_prop_bulk_type(%c0, %c0, %c0) : (i64, i64, i64) -> (i64, i64, i64) <operandSegmentSizes = [2, 1], resultSegmentSizes = [3]>
diff --git a/mlir/test/IR/properties.mlir b/mlir/test/IR/properties.mlir
index 4d83038f31cdd..64548e41dc111 100644
--- a/mlir/test/IR/properties.mlir
+++ b/mlir/test/IR/properties.mlir
@@ -23,6 +23,85 @@ test.with_wrapped_properties <{prop = "content for properties"}>
// GENERIC: "test.empty_properties"()
test.empty_properties
+// An explicitly empty key-value list is also accepted.
+// CHECK: test.empty_properties
+// GENERIC: "test.empty_properties"()
+test.empty_properties <>
+
+// The key-value spelling uses the custom parsers for both attributes and
+// properties. Until the custom printer is enabled, it round-trips to the
+// generic DictionaryAttr spelling.
+// CHECK: test.with_custom_prop_dict <{attr = 1 : i32, prop = 2 : i64}>
+// GENERIC: "test.with_custom_prop_dict"()
+// GENERIC-SAME: <{attr = 1 : i32, defaulted = 42 : i64, prop = 2 : i64, unit = false}>
+test.with_custom_prop_dict <attr = 1, prop = 2>
+
+// The generic DictionaryAttr spelling remains accepted for compatibility.
+// CHECK: test.with_custom_prop_dict <{attr = 3 : i32, prop = 4 : i64}>
+// GENERIC: "test.with_custom_prop_dict"()
+// GENERIC-SAME: <{attr = 3 : i32, defaulted = 42 : i64, prop = 4 : i64, unit = false}>
+test.with_custom_prop_dict <{attr = 3 : i32, prop = 4 : i64}>
+
+// Entries are order-independent, and optional/default-valued entries use
+// their custom parsers when present.
+// CHECK: test.with_custom_prop_dict <{attr = 5 : i32, defaulted = 43 : i64, optional = "set", prop = 6 : i64}>
+// GENERIC: "test.with_custom_prop_dict"()
+// GENERIC-SAME: <{attr = 5 : i32, defaulted = 43 : i64, optional = "set", prop = 6 : i64, unit = false}>
+test.with_custom_prop_dict <optional = "set", defaulted = 43, prop = 6, attr = 5>
+
+// A field name that is also the start of an attribute must not be consumed by
+// the legacy DictionaryAttr compatibility probe.
+// CHECK: test.with_custom_prop_dict <{attr = 7 : i32, prop = 8 : i64, unit}>
+// GENERIC: "test.with_custom_prop_dict"()
+// GENERIC-SAME: <{attr = 7 : i32, defaulted = 42 : i64, prop = 8 : i64, unit}>
+test.with_custom_prop_dict <unit = unit, attr = 7, prop = 8>
+
+// Properties bound elsewhere in the assembly format are excluded from the
+// key-value list.
+// CHECK: test.with_properties_and_attr 7 <{rhs = 8 : i64}>
+// GENERIC: "test.with_properties_and_attr"()
+// GENERIC-SAME: <{lhs = 7 : i32, rhs = 8 : i64}>
+test.with_properties_and_attr 7 <rhs = 8>
+
+// A property without a usable custom parser falls back to its attribute
+// conversion for this compatibility spelling.
+// CHECK: test.with_wrapped_properties <{prop = "custom spelling"}>
+// GENERIC: "test.with_wrapped_properties"()
+// GENERIC-SAME: <{prop = "custom spelling"}>
+test.with_wrapped_properties <prop = "custom spelling">
+
+// Forwarding property wrappers preserve whether their base uses the default
+// FieldParser, so a wrapped custom storage type still uses attribute fallback.
+// CHECK: test.with_default_wrapped_properties
+// GENERIC: "test.with_default_wrapped_properties"()
+// GENERIC-SAME: <{prop = "wrapped default spelling"}>
+test.with_default_wrapped_properties <prop = "wrapped default spelling">
+
+// A container FieldParser is unavailable when its element parser is
+// unavailable, so the complete property also falls back to conversion.
+// CHECK: test.with_wrapped_array_properties
+// GENERIC: "test.with_wrapped_array_properties"()
+// GENERIC-SAME: <{prop = ["first", "second"]}>
+test.with_wrapped_array_properties <prop = ["first", "second"]>
+
+// Default optional and container FieldParsers do not delimit exactly one
+// property value, so they use attribute conversion in a key-value list. The
+// following scalar key also checks that the container does not consume the
+// outer comma.
+// CHECK: test.with_key_value_parser_boundaries
+// CHECK-SAME: <{maybe = [], maybeEnum = [], next = 9 : i64, specializedMaybe = [7 : i16], specializedValues = array<i32: 3, 4>, values = array<i64: 1, 2>}>
+// GENERIC: "test.with_key_value_parser_boundaries"()
+// GENERIC-SAME: <{maybe = [], maybeEnum = [], next = 9 : i64, specializedMaybe = [7 : i16], specializedValues = array<i32: 3, 4>, values = array<i64: 1, 2>}>
+test.with_key_value_parser_boundaries <specializedValues = [3, 4], specializedMaybe = some<7>, values = array<i64: 1, 2>, maybe = [], maybeEnum = [], next = 9>
+
+// A comma-separated bit-enum FieldParser is not compositional with the outer
+// list, so prop-dict uses its attribute conversion before parsing another key.
+// CHECK: test.op_with_bit_enum_prop_dict
+// CHECK-SAME: <{flags = 3 : i32, next = 9 : i64}>
+// GENERIC: "test.op_with_bit_enum_prop_dict"()
+// GENERIC-SAME: <{flags = 3 : i32, next = 9 : i64}>
+test.op_with_bit_enum_prop_dict <flags = 3 : i32, next = 9>
+
// CHECK: test.using_property_in_custom
// CHECK-SAME: [1, 4, 20]{{$}}
// GENERIC: "test.using_property_in_custom"()
@@ -55,7 +134,7 @@ test.variadic_segment_prop %ci64, %ci64 : %ci64 : i64, i64 : i64 end
// key 'operandSegmentSizes' in dictionary attribute".
// CHECK: test.variadic_segment_prop_bulk_type(%[[CI64]], %[[CI64]], %[[CI64]]) : (i64, i64, i64) -> (i64, i64, i64) <{operandSegmentSizes = array<i32: 2, 1>, resultSegmentSizes = array<i32: 2, 1>}>
// GENERIC: "test.variadic_segment_prop_bulk_type"(%[[CI64]], %[[CI64]], %[[CI64]]) <{operandSegmentSizes = array<i32: 2, 1>, resultSegmentSizes = array<i32: 2, 1>}> : (i64, i64, i64) -> (i64, i64, i64)
-test.variadic_segment_prop_bulk_type(%ci64, %ci64, %ci64) : (i64, i64, i64) -> (i64, i64, i64) <{operandSegmentSizes = array<i32: 2, 1>, resultSegmentSizes = array<i32: 2, 1>}>
+test.variadic_segment_prop_bulk_type(%ci64, %ci64, %ci64) : (i64, i64, i64) -> (i64, i64, i64) <operandSegmentSizes = [2, 1], resultSegmentSizes = [2, 1]>
// CHECK: test.with_default_valued_properties na{{$}}
// GENERIC: "test.with_default_valued_properties"()
@@ -121,6 +200,20 @@ test.op_with_property_predicates <{
non_empty_constrained = [1],
unconstrained = 0 : i64}>
+// Keyed parsing composes optional and aggregate property parsers with a
+// following outer dictionary entry.
+// CHECK: test.op_with_property_predicates
+// CHECK-SAME: array = [3, 4]
+// CHECK-SAME: optional = [2]
+test.op_with_property_predicates <
+ scalar = 1,
+ optional = 2,
+ more_constrained = 1,
+ array = [3, 4],
+ non_empty_unconstrained = [1],
+ non_empty_constrained = [1],
+ unconstrained = 0>
+
// Tests that DefaultValuedProp is printed when value differs from default.
// CHECK: test.op_with_property_predicates
// CHECK-SAME: defaulted = 3
diff --git a/mlir/test/lib/Dialect/Test/TestOps.h b/mlir/test/lib/Dialect/Test/TestOps.h
index b4cc2cd6cf569..6320d39138f5f 100644
--- a/mlir/test/lib/Dialect/Test/TestOps.h
+++ b/mlir/test/lib/Dialect/Test/TestOps.h
@@ -43,6 +43,40 @@
#include "mlir/Interfaces/ViewLikeInterface.h"
#include "llvm/ADT/SetVector.h"
#include "llvm/ADT/SmallVector.h"
+#include <optional>
+
+namespace mlir {
+// Self-delimiting full specializations remain usable in a keyed prop-dict even
+// when their storage type is optional or container-like.
+template <>
+struct FieldParser<llvm::SmallVector<int32_t>> {
+ static FailureOr<llvm::SmallVector<int32_t>> parse(AsmParser &parser) {
+ llvm::SmallVector<int32_t> values;
+ if (parser.parseCommaSeparatedList(AsmParser::Delimiter::Square, [&]() {
+ int32_t value;
+ if (parser.parseInteger(value))
+ return failure();
+ values.push_back(value);
+ return success();
+ }))
+ return failure();
+ return values;
+ }
+};
+
+template <>
+struct FieldParser<std::optional<int16_t>> {
+ static FailureOr<std::optional<int16_t>> parse(AsmParser &parser) {
+ if (succeeded(parser.parseOptionalKeyword("none")))
+ return std::optional<int16_t>{};
+ int16_t value;
+ if (parser.parseKeyword("some") || parser.parseLess() ||
+ parser.parseInteger(value) || parser.parseGreater())
+ return failure();
+ return std::optional<int16_t>{value};
+ }
+};
+} // namespace mlir
namespace test {
class TestDialect;
diff --git a/mlir/test/lib/Dialect/Test/TestOps.td b/mlir/test/lib/Dialect/Test/TestOps.td
index 3cb7f6ce2a054..0bd7fe401c3c9 100644
--- a/mlir/test/lib/Dialect/Test/TestOps.td
+++ b/mlir/test/lib/Dialect/Test/TestOps.td
@@ -534,6 +534,12 @@ def OpWithTestBitEnum : TEST_Op<"op_with_bit_enum_prop"> {
let assemblyFormat = "$value1 ($value2^)? attr-dict `:` `(``)`";
}
+def OpWithTestBitEnumPropDict
+ : TEST_Op<"op_with_bit_enum_prop_dict"> {
+ let arguments = (ins TestBitEnumProp:$flags, I64Prop:$next);
+ let assemblyFormat = "prop-dict attr-dict";
+}
+
def TestBitEnumPropNamed : NamedEnumProp<TestBitEnum, "bit_enum"> {
let defaultValue = TestBitEnum.cppType # "::Read";
}
@@ -3627,6 +3633,18 @@ def TestOpWithPropertiesAndAttr
let arguments = (ins I32Attr:$lhs, IntProp<"int64_t">:$rhs);
}
+def TestOpWithCustomPropDict : TEST_Op<"with_custom_prop_dict"> {
+ let assemblyFormat = "prop-dict attr-dict";
+
+ let arguments = (ins
+ I32Attr:$attr,
+ I64Prop:$prop,
+ DefaultValuedProp<I64Prop, "42">:$defaulted,
+ OptionalAttr<StrAttr>:$optional,
+ UnitProp:$unit
+ );
+}
+
def TestOpWithPropertiesAndInferredType
: TEST_Op<"with_properties_and_inferred_type", [
DeclareOpInterfaceMethods<InferTypeOpInterface>
@@ -3642,6 +3660,9 @@ def MyStructProperty : Property<"MyPropStruct"> {
let convertToAttribute = "return $_storage.asAttribute($_ctxt);";
let convertFromAttribute = "return MyPropStruct::setFromAttr($_storage, $_attr, $_diag);";
let hashProperty = "$_storage.hash();";
+ // An optional parser does not imply that the default required FieldParser is
+ // available. `prop-dict` must still use attribute conversion as its fallback.
+ let optionalParser = "return std::nullopt;";
}
def TestOpWithWrappedProperties : TEST_Op<"with_wrapped_properties"> {
@@ -3651,6 +3672,249 @@ def TestOpWithWrappedProperties : TEST_Op<"with_wrapped_properties"> {
);
}
+def TestOpWithDefaultWrappedProperties
+ : TEST_Op<"with_default_wrapped_properties"> {
+ let assemblyFormat = "prop-dict attr-dict";
+ let arguments = (ins
+ DefaultValuedProp<MyStructProperty, "MyPropStruct{}">:$prop
+ );
+}
+
+def MyStructArrayProperty
+ : Property<"::llvm::SmallVector<MyPropStruct>"> {
+ let hashProperty =
+ "::llvm::hash_combine_range($_storage.begin(), $_storage.end())";
+ let readFromMlirBytecode = [{
+ uint64_t size;
+ if (::mlir::failed($_reader.readVarInt(size)))
+ return ::mlir::failure();
+ $_storage.clear();
+ while (size--) {
+ MyPropStruct value;
+ if (::mlir::failed(readFromMlirBytecode($_reader, value)))
+ return ::mlir::failure();
+ $_storage.push_back(std::move(value));
+ }
+ }];
+ let writeToMlirBytecode = [{
+ $_writer.writeVarInt($_storage.size());
+ for (MyPropStruct &value : $_storage)
+ writeToMlirBytecode($_writer, value);
+ }];
+ let convertToAttribute = [{
+ ::llvm::SmallVector<::mlir::Attribute> attrs;
+ for (const MyPropStruct &value : $_storage)
+ attrs.push_back(value.asAttribute($_ctxt));
+ return ::mlir::ArrayAttr::get($_ctxt, attrs);
+ }];
+ let convertFromAttribute = [{
+ auto array = ::llvm::dyn_cast<::mlir::ArrayAttr>($_attr);
+ if (!array)
+ return ::mlir::failure();
+ $_storage.clear();
+ for (::mlir::Attribute attr : array) {
+ MyPropStruct value;
+ if (::mlir::failed(MyPropStruct::setFromAttr(value, attr, $_diag)))
+ return ::mlir::failure();
+ $_storage.push_back(std::move(value));
+ }
+ return ::mlir::success();
+ }];
+}
+
+def TestOpWithWrappedArrayProperties
+ : TEST_Op<"with_wrapped_array_properties"> {
+ let assemblyFormat = "prop-dict attr-dict";
+ let arguments = (ins MyStructArrayProperty:$prop);
+}
+
+// The default FieldParser specializations for optional integers and generic
+// containers do not consume exactly one value in an outer comma-separated
+// list. The generated prop-dict parser must use attribute conversion for
+// these storage types instead.
+def KeyValueListProperty : Property<"::llvm::SmallVector<int64_t>"> {
+ let convertToAttribute =
+ "return ::mlir::DenseI64ArrayAttr::get($_ctxt, $_storage);";
+ let convertFromAttribute = [{
+ auto array = ::llvm::dyn_cast<::mlir::DenseI64ArrayAttr>($_attr);
+ if (!array)
+ return $_diag() << "expected a dense i64 array";
+ auto values = array.asArrayRef();
+ $_storage.assign(values.begin(), values.end());
+ return ::mlir::success();
+ }];
+ let hashProperty =
+ "::llvm::hash_combine_range($_storage.begin(), $_storage.end())";
+ let readFromMlirBytecode = readMlirBytecodeUsingConvertFromAttribute;
+ let writeToMlirBytecode = writeMlirBytecodeWithConvertToAttribute;
+}
+
+def KeyValueOptionalProperty : Property<"std::optional<int64_t>"> {
+ let convertToAttribute = [{
+ if (!$_storage)
+ return ::mlir::ArrayAttr::get($_ctxt, {});
+ auto value = ::mlir::IntegerAttr::get(
+ ::mlir::IntegerType::get($_ctxt, 64), *$_storage);
+ return ::mlir::ArrayAttr::get($_ctxt, {value});
+ }];
+ let convertFromAttribute = [{
+ auto array = ::llvm::dyn_cast<::mlir::ArrayAttr>($_attr);
+ if (!array || array.size() > 1)
+ return $_diag() << "expected a zero- or one-element array";
+ if (array.empty()) {
+ $_storage = std::nullopt;
+ return ::mlir::success();
+ }
+ auto value = ::llvm::dyn_cast<::mlir::IntegerAttr>(array[0]);
+ if (!value)
+ return $_diag() << "expected an integer element";
+ $_storage = value.getInt();
+ return ::mlir::success();
+ }];
+ let hashProperty = "::llvm::hash_value($_storage.value_or(0))";
+ let readFromMlirBytecode = [{
+ bool isPresent;
+ if (::mlir::failed($_reader.readBool(isPresent)))
+ return ::mlir::failure();
+ if (!isPresent) {
+ $_storage = std::nullopt;
+ return ::mlir::success();
+ }
+ int64_t value;
+ if (::mlir::failed($_reader.readSignedVarInt(value)))
+ return ::mlir::failure();
+ $_storage = value;
+ }];
+ let writeToMlirBytecode = [{
+ $_writer.writeOwnedBool($_storage.has_value());
+ if ($_storage)
+ $_writer.writeSignedVarInt(*$_storage);
+ }];
+}
+
+def KeyValueOptionalEnumProperty
+ : Property<"std::optional<test::TestEnum>"> {
+ let convertToAttribute = [{
+ if (!$_storage)
+ return ::mlir::ArrayAttr::get($_ctxt, {});
+ auto value = ::mlir::IntegerAttr::get(
+ ::mlir::IntegerType::get($_ctxt, 32),
+ static_cast<uint32_t>(*$_storage));
+ return ::mlir::ArrayAttr::get($_ctxt, {value});
+ }];
+ let convertFromAttribute = [{
+ auto array = ::llvm::dyn_cast<::mlir::ArrayAttr>($_attr);
+ if (!array || array.size() > 1)
+ return $_diag() << "expected a zero- or one-element array";
+ if (array.empty()) {
+ $_storage = std::nullopt;
+ return ::mlir::success();
+ }
+ auto value = ::llvm::dyn_cast<::mlir::IntegerAttr>(array[0]);
+ if (!value)
+ return $_diag() << "expected an integer element";
+ $_storage = static_cast<test::TestEnum>(value.getInt());
+ return ::mlir::success();
+ }];
+ let hashProperty = [{
+ ::llvm::hash_value(static_cast<uint32_t>(
+ $_storage.value_or(test::TestEnum::First)))
+ }];
+ let readFromMlirBytecode = [{
+ bool isPresent;
+ if (::mlir::failed($_reader.readBool(isPresent)))
+ return ::mlir::failure();
+ if (!isPresent) {
+ $_storage = std::nullopt;
+ return ::mlir::success();
+ }
+ uint64_t value;
+ if (::mlir::failed($_reader.readVarInt(value)))
+ return ::mlir::failure();
+ $_storage = static_cast<test::TestEnum>(value);
+ }];
+ let writeToMlirBytecode = [{
+ $_writer.writeOwnedBool($_storage.has_value());
+ if ($_storage)
+ $_writer.writeVarInt(static_cast<uint64_t>(*$_storage));
+ }];
+}
+
+def KeyValueSpecializedListProperty
+ : Property<"::llvm::SmallVector<int32_t>"> {
+ let convertToAttribute =
+ "return ::mlir::DenseI32ArrayAttr::get($_ctxt, $_storage);";
+ let convertFromAttribute = [{
+ auto array = ::llvm::dyn_cast<::mlir::DenseI32ArrayAttr>($_attr);
+ if (!array)
+ return $_diag() << "expected a dense i32 array";
+ auto values = array.asArrayRef();
+ $_storage.assign(values.begin(), values.end());
+ return ::mlir::success();
+ }];
+ let hashProperty =
+ "::llvm::hash_combine_range($_storage.begin(), $_storage.end())";
+ let readFromMlirBytecode = readMlirBytecodeUsingConvertFromAttribute;
+ let writeToMlirBytecode = writeMlirBytecodeWithConvertToAttribute;
+}
+
+def KeyValueSpecializedOptionalProperty
+ : Property<"std::optional<int16_t>"> {
+ let convertToAttribute = [{
+ if (!$_storage)
+ return ::mlir::ArrayAttr::get($_ctxt, {});
+ auto value = ::mlir::IntegerAttr::get(
+ ::mlir::IntegerType::get($_ctxt, 16), *$_storage);
+ return ::mlir::ArrayAttr::get($_ctxt, {value});
+ }];
+ let convertFromAttribute = [{
+ auto array = ::llvm::dyn_cast<::mlir::ArrayAttr>($_attr);
+ if (!array || array.size() > 1)
+ return $_diag() << "expected a zero- or one-element array";
+ if (array.empty()) {
+ $_storage = std::nullopt;
+ return ::mlir::success();
+ }
+ auto value = ::llvm::dyn_cast<::mlir::IntegerAttr>(array[0]);
+ if (!value)
+ return $_diag() << "expected an integer element";
+ $_storage = static_cast<int16_t>(value.getInt());
+ return ::mlir::success();
+ }];
+ let hashProperty = "::llvm::hash_value($_storage.value_or(0))";
+ let readFromMlirBytecode = [{
+ bool isPresent;
+ if (::mlir::failed($_reader.readBool(isPresent)))
+ return ::mlir::failure();
+ if (!isPresent) {
+ $_storage = std::nullopt;
+ return ::mlir::success();
+ }
+ int64_t value;
+ if (::mlir::failed($_reader.readSignedVarInt(value)))
+ return ::mlir::failure();
+ $_storage = static_cast<int16_t>(value);
+ }];
+ let writeToMlirBytecode = [{
+ $_writer.writeOwnedBool($_storage.has_value());
+ if ($_storage)
+ $_writer.writeSignedVarInt(*$_storage);
+ }];
+}
+
+def TestOpWithKeyValueParserBoundaries
+ : TEST_Op<"with_key_value_parser_boundaries"> {
+ let assemblyFormat = "prop-dict attr-dict";
+ let arguments = (ins
+ KeyValueListProperty:$values,
+ KeyValueOptionalProperty:$maybe,
+ KeyValueOptionalEnumProperty:$maybeEnum,
+ KeyValueSpecializedListProperty:$specializedValues,
+ KeyValueSpecializedOptionalProperty:$specializedMaybe,
+ I64Prop:$next
+ );
+}
+
// Same as above, but without a custom `hashProperty` field, checking
// that ADL is correctly working.
def MyStructProperty2 : Property<"MyPropStruct"> {
diff --git a/mlir/test/mlir-tblgen/enums-gen.td b/mlir/test/mlir-tblgen/enums-gen.td
index cf66ad46ad24b..e64e50b2382cb 100644
--- a/mlir/test/mlir-tblgen/enums-gen.td
+++ b/mlir/test/mlir-tblgen/enums-gen.td
@@ -45,6 +45,9 @@ def MyBitEnum: I32BitEnumAttr<"MyBitEnum", "An example bit enum",
// DECL: return parser.emitError(loc, "expected one of [none, tagged, Bit1, Bit2, Bit3, BitGroup] for An example bit enum, got: ") << enumKeyword;
// DECL: }
+// DECL: struct FieldParser<std::optional<::MyBitEnum>, std::optional<::MyBitEnum>> {
+// DECL: static constexpr bool isKeyValueCompositional = false;
+
// DECL: inline ::llvm::raw_ostream &operator<<(::llvm::raw_ostream &p, ::MyBitEnum value) {
// DECL: auto valueStr = stringifyEnum(value);
// DECL: switch (value) {
@@ -58,6 +61,11 @@ def MyBitEnum: I32BitEnumAttr<"MyBitEnum", "An example bit enum",
// DECL: return p << '"' << valueStr << '"';
// DECL: return p << valueStr;
+// DECL: struct FieldParser<::MyCommaSeparatedBitEnum, ::MyCommaSeparatedBitEnum> {
+// DECL: static constexpr bool isKeyValueCompositional = false;
+// DECL: struct FieldParser<std::optional<::MyCommaSeparatedBitEnum>, std::optional<::MyCommaSeparatedBitEnum>> {
+// DECL: static constexpr bool isKeyValueCompositional = false;
+
// DECL: enum class MyI8Enum : uint8_t {
// DECL: a = 254,
// DECL: b = 255,
@@ -129,6 +137,7 @@ def MyNonQuotedPrintBitEnum
[None, Bit0, Bit1, Bit2, Bit3, BitGroup]>;
// DECL: struct FieldParser<::MyNonQuotedPrintBitEnum, ::MyNonQuotedPrintBitEnum> {
+// DECL: static constexpr bool isKeyValueCompositional = true;
// DECL: template <typename ParserT>
// DECL: static FailureOr<::MyNonQuotedPrintBitEnum> parse(ParserT &parser) {
// DECL: ::MyNonQuotedPrintBitEnum flags = {};
@@ -149,6 +158,12 @@ def MyNonQuotedPrintBitEnum
// DECL: return flags;
// DECL: }
+def MyCommaSeparatedBitEnum
+ : I32BitEnum<"MyCommaSeparatedBitEnum", "Comma-separated bit enum",
+ [None, Bit0, Bit1]> {
+ let separator = ", ";
+}
+
// DECL: inline ::llvm::raw_ostream &operator<<(::llvm::raw_ostream &p, ::MyNonQuotedPrintBitEnum value) {
// DECL: auto valueStr = stringifyEnum(value);
// DECL-NEXT: return p << valueStr;
diff --git a/mlir/tools/mlir-tblgen/EnumsGen.cpp b/mlir/tools/mlir-tblgen/EnumsGen.cpp
index 4b90082176dc0..845f1ef46cf35 100644
--- a/mlir/tools/mlir-tblgen/EnumsGen.cpp
+++ b/mlir/tools/mlir-tblgen/EnumsGen.cpp
@@ -129,6 +129,8 @@ struct FieldParser<{0}, {0}> {{
/// let parameters = (ins OptionalParameter<"std::optional<TheEnumName>">:$value);
template<>
struct FieldParser<std::optional<{0}>, std::optional<{0}>> {{
+ static constexpr bool isKeyValueCompositional = false;
+
template <typename ParserT>
static FailureOr<std::optional<{0}>> parse(ParserT &parser) {{
// Parse the keyword/string containing the enum.
@@ -157,6 +159,8 @@ inline ::llvm::raw_ostream &operator<<(::llvm::raw_ostream &p, {0} value) {{
template<>
struct FieldParser<{0}, {0}> {{
+ static constexpr bool isKeyValueCompositional = {7};
+
template <typename ParserT>
static FailureOr<{0}> parse(ParserT &parser) {{
{0} flags = {{};
@@ -185,6 +189,8 @@ inline ::llvm::raw_ostream &operator<<(::llvm::raw_ostream &p, {0} value) {{
/// let parameters = (ins OptionalParameter<"std::optional<TheEnumName>">:$value);
template<>
struct FieldParser<std::optional<{0}>, std::optional<{0}>> {{
+ static constexpr bool isKeyValueCompositional = false;
+
template <typename ParserT>
static FailureOr<std::optional<{0}>> parse(ParserT &parser) {{
{0} flags = {{};
@@ -233,7 +239,7 @@ inline ::llvm::raw_ostream &operator<<(::llvm::raw_ostream &p, {0} value) {{
.Default("error, enum separator must be '|' or ','");
os << formatv(parsedAndPrinterStartUnquotedBitEnum, qualName, cppNamespace,
enumInfo.getSummary(), casesList, separator, parseSeparatorFn,
- casesInitList);
+ casesInitList, separator.trim() == "," ? "false" : "true");
} else {
os << formatv(parsedAndPrinterStart, qualName, cppNamespace,
enumInfo.getSummary(), casesList, casesInitList);
diff --git a/mlir/tools/mlir-tblgen/OpFormatGen.cpp b/mlir/tools/mlir-tblgen/OpFormatGen.cpp
index 470d27cfc6060..815c51c91dddd 100644
--- a/mlir/tools/mlir-tblgen/OpFormatGen.cpp
+++ b/mlir/tools/mlir-tblgen/OpFormatGen.cpp
@@ -1494,6 +1494,223 @@ return ::mlir::success();
)decl";
}
+/// Generate the parser for the key-value spelling of `prop-dict`. The generic
+/// DictionaryAttr spelling remains supported as a compatibility path.
+static void genKeyValuePropDictParser(OperationFormat &fmt, Operator &op,
+ OpClass &opClass) {
+ if (!fmt.hasPropDict || !fmt.useProperties)
+ return;
+
+ SmallVector<MethodParameter> paramList;
+ paramList.emplace_back("::mlir::OpAsmParser &", "parser");
+ paramList.emplace_back("::mlir::OperationState &", "result");
+
+ Method *method = opClass.addStaticMethod("::mlir::ParseResult",
+ "parsePropertiesFromKeyValueList",
+ std::move(paramList));
+ MethodBody &body = method->body().indent();
+
+ body << R"decl(
+auto &prop = result.getOrAddProperties<Properties>();
+(void)prop;
+)decl";
+
+ bool parseOperandSegmentSizes =
+ op.getTrait("::mlir::OpTrait::AttrSizedOperandSegments") &&
+ fmt.allOperands;
+ bool parseResultSegmentSizes =
+ op.getTrait("::mlir::OpTrait::AttrSizedResultSegments") &&
+ fmt.allResultTypes;
+
+ if (parseOperandSegmentSizes)
+ body << "bool seen_operandSegmentSizes = false;\n";
+ if (parseResultSegmentSizes)
+ body << "bool seen_resultSegmentSizes = false;\n";
+
+ auto shouldParseProperty = [&](const NamedProperty &property) {
+ return !fmt.usedProperties.contains(&property) &&
+ !fmt.inferredAttributes.contains(property.name);
+ };
+ auto shouldParseAttribute = [&](const NamedAttribute &attribute) {
+ return !attribute.attr.isDerivedAttr() &&
+ !fmt.usedAttributes.contains(&attribute) &&
+ !fmt.inferredAttributes.contains(attribute.name);
+ };
+
+ for (const NamedProperty &property : op.getProperties())
+ if (shouldParseProperty(property))
+ body << "bool seen_" << property.name << " = false;\n";
+ for (const NamedAttribute &attribute : op.getAttributes())
+ if (shouldParseAttribute(attribute))
+ body << "bool seen_" << attribute.name << " = false;\n";
+
+ body << R"decl(
+if (succeeded(parser.parseOptionalLess())) {
+ ::llvm::SMLoc dictionaryLoc = parser.getCurrentLocation();
+ ::mlir::NamedAttrList propertyAttributes;
+ if (parser.parseOptionalAttrDict(propertyAttributes))
+ return ::mlir::failure();
+ if (dictionaryLoc != parser.getCurrentLocation()) {
+ if (parser.parseGreater())
+ return ::mlir::failure();
+ auto propertyDictionary =
+ ::mlir::DictionaryAttr::get(parser.getContext(), propertyAttributes);
+ return setPropertiesFromParsedAttr(prop, propertyDictionary, [&]() {
+ return parser.emitError(dictionaryLoc)
+ << "invalid properties " << propertyDictionary << ": ";
+ });
+ }
+
+ bool reachedEnd = succeeded(parser.parseOptionalGreater());
+ while (!reachedEnd) {
+ ::llvm::SMLoc keyLoc = parser.getCurrentLocation();
+ ::llvm::StringRef key;
+ if (parser.parseKeyword(&key) || parser.parseEqual())
+ return ::mlir::failure();
+)decl";
+
+ bool isFirst = true;
+ FmtContext attrTypeCtx;
+ attrTypeCtx.withBuilder("parser.getBuilder()");
+
+ auto genSegmentSizesParser = [&](StringRef name) {
+ body << (isFirst ? " if" : " else if") << " (!seen_" << name
+ << " && key == \"" << name << "\") {\n"
+ << " seen_" << name << " = true;\n"
+ << R"decl(
+ ::llvm::SmallVector<int32_t> parsedSegmentSizes;
+ if (parser.parseCommaSeparatedList(
+ ::mlir::AsmParser::Delimiter::Square, [&]() {
+ int32_t size;
+ if (parser.parseInteger(size))
+ return ::mlir::failure();
+ parsedSegmentSizes.push_back(size);
+ return ::mlir::success();
+ }))
+ return ::mlir::failure();
+)decl"
+ << " if (parsedSegmentSizes.size() != prop." << name
+ << ".size())\n"
+ << " return parser.emitError(keyLoc, \"expected "
+ << (name == "operandSegmentSizes" ? op.getNumOperands()
+ : op.getNumResults())
+ << " entries for " << name << "\");\n"
+ << " ::llvm::copy(parsedSegmentSizes, prop." << name
+ << ".begin());\n"
+ << " }\n";
+ isFirst = false;
+ };
+
+ if (parseOperandSegmentSizes)
+ genSegmentSizesParser("operandSegmentSizes");
+ if (parseResultSegmentSizes)
+ genSegmentSizesParser("resultSegmentSizes");
+
+ for (const NamedProperty &property : op.getProperties()) {
+ if (!shouldParseProperty(property))
+ continue;
+ body << (isFirst ? " if" : " else if") << " (!seen_" << property.name
+ << " && key == \"" << property.name << "\") {\n"
+ << " seen_" << property.name << " = true;\n";
+ if (!property.prop.usesDefaultParser()) {
+ PropertyVariable propertyVariable(&property);
+ genPropertyParser(&propertyVariable, body.indent(), fmt.opCppClassName);
+ } else {
+ FmtContext fctx;
+ fctx.addSubst("_attr", "propertyAttr");
+ fctx.addSubst("_storage", "propStorage");
+ fctx.addSubst("_diag", "emitError");
+ body.indent() << R"decl(
+auto parseResult = ::mlir::detail::parsePropertyWithFallback(
+ parser, prop.)decl"
+ << property.name << R"decl(,
+ [&](auto &propStorage,
+ ::mlir::Attribute propertyAttr) -> ::mlir::LogicalResult {
+ auto emitError = [&]() {
+ return parser.emitError(parser.getCurrentLocation())
+ << "invalid value for property " << key << ": ";
+ };
+)decl";
+ body << tgfmt(property.prop.getConvertFromAttributeCall(), &fctx)
+ << ";\n";
+ body << "});\n"
+ << "if (failed(parseResult))\n"
+ << " return ::mlir::failure();\n";
+ body.unindent();
+ }
+ body.unindent() << " }\n";
+ isFirst = false;
+ }
+ for (const NamedAttribute &attribute : op.getAttributes()) {
+ if (!shouldParseAttribute(attribute))
+ continue;
+ body << (isFirst ? " if" : " else if") << " (!seen_" << attribute.name
+ << " && key == \"" << attribute.name << "\") {\n"
+ << " seen_" << attribute.name << " = true;\n"
+ << " " << attribute.attr.getStorageType() << " " << attribute.name
+ << "Attr;\n";
+ AttributeVariable attributeVariable(&attribute);
+ genAttrParser(&attributeVariable, body.indent(), attrTypeCtx,
+ /*parseAsOptional=*/false, /*useProperties=*/true,
+ fmt.opCppClassName);
+ body.unindent() << " }\n";
+ isFirst = false;
+ }
+
+ if (isFirst) {
+ body << R"decl(
+ return parser.emitError(keyLoc,
+ "unknown property in properties dictionary: ")
+ << key;
+)decl";
+ } else {
+ body << R"decl(
+ else {
+ return parser.emitError(
+ keyLoc,
+ "duplicate or unknown property in properties dictionary: ")
+ << key;
+ }
+)decl";
+ }
+
+ body << R"decl(
+ reachedEnd = succeeded(parser.parseOptionalGreater());
+ if (!reachedEnd && parser.parseComma())
+ return ::mlir::failure();
+ }
+}
+)decl";
+
+ if (parseOperandSegmentSizes)
+ body << "if (!seen_operandSegmentSizes)\n"
+ " return ::mlir::emitError(result.location, \"properties "
+ "dictionary is missing required property: "
+ "operandSegmentSizes\");\n";
+ if (parseResultSegmentSizes)
+ body << "if (!seen_resultSegmentSizes)\n"
+ " return ::mlir::emitError(result.location, \"properties "
+ "dictionary is missing required property: "
+ "resultSegmentSizes\");\n";
+
+ for (const NamedProperty &property : op.getProperties()) {
+ if (shouldParseProperty(property) && !property.prop.hasDefaultValue())
+ body << "if (!seen_" << property.name
+ << ")\n return ::mlir::emitError(result.location, "
+ "\"properties dictionary is missing required property: "
+ << property.name << "\");\n";
+ }
+ for (const NamedAttribute &attribute : op.getAttributes()) {
+ if (shouldParseAttribute(attribute) && !attribute.attr.isOptional() &&
+ !attribute.attr.hasDefaultValue())
+ body << "if (!seen_" << attribute.name
+ << ")\n return ::mlir::emitError(result.location, "
+ "\"properties dictionary is missing required attribute: "
+ << attribute.name << "\");\n";
+ }
+ body << "return ::mlir::success();\n";
+}
+
void OperationFormat::genParser(Operator &op, OpClass &opClass) {
SmallVector<MethodParameter> paramList;
paramList.emplace_back("::mlir::OpAsmParser &", "parser");
@@ -1527,6 +1744,7 @@ void OperationFormat::genParser(Operator &op, OpClass &opClass) {
body << " return ::mlir::success();\n";
genParsedAttrPropertiesSetter(*this, op, opClass);
+ genKeyValuePropDictParser(*this, op, opClass);
}
void OperationFormat::genElementParser(FormatElement *element, MethodBody &body,
More information about the Mlir-commits
mailing list