Author: Thurston Dang Date: 2026-08-21T11:47:18-07:00 New Revision: 665d0a137fc2b79c1d686ad2b5843e0aeb17ef2f
URL: https://github.com/llvm/llvm-project/commit/665d0a137fc2b79c1d686ad2b5843e0aeb17ef2f DIFF: https://github.com/llvm/llvm-project/commit/665d0a137fc2b79c1d686ad2b5843e0aeb17ef2f.diff LOG: Revert "[MLIR][ODS] Parse prop-dict fields with custom parsers (#217590)" This reverts commit d829d331449316ef4e19e63ca189f4c84067264b. Added: Modified: mlir/docs/DefiningDialects/Operations.md mlir/include/mlir/IR/DialectImplementation.h mlir/include/mlir/IR/OpDefinition.h mlir/include/mlir/TableGen/Property.h mlir/lib/TableGen/Property.cpp mlir/test/IR/properties.mlir mlir/test/lib/Dialect/Test/TestOps.h mlir/test/lib/Dialect/Test/TestOps.td mlir/test/mlir-tblgen/enums-gen.td mlir/tools/mlir-tblgen/EnumsGen.cpp mlir/tools/mlir-tblgen/OpFormatGen.cpp Removed: mlir/test/IR/properties-invalid.mlir ################################################################################ diff --git a/mlir/docs/DefiningDialects/Operations.md b/mlir/docs/DefiningDialects/Operations.md index 419041a52b0da..1ef96130d836d 100644 --- a/mlir/docs/DefiningDialects/Operations.md +++ b/mlir/docs/DefiningDialects/Operations.md @@ -760,17 +760,9 @@ The available directives are as follows: * `prop-dict` - - 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. + - 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. - 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 2652114b508e3..0b4f91cd750b8 100644 --- a/mlir/include/mlir/IR/DialectImplementation.h +++ b/mlir/include/mlir/IR/DialectImplementation.h @@ -70,15 +70,6 @@ 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; @@ -140,8 +131,6 @@ 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); @@ -162,8 +151,6 @@ 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); @@ -180,51 +167,14 @@ 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<detail::HasFieldParserContainer<ContainerT>::value, - ContainerT>> { - static constexpr bool isKeyValueCompositional = false; - +struct FieldParser<ContainerT, + std::enable_if_t<llvm::is_detected<detail::has_push_back_t, + ContainerT>::value, + ContainerT>> { using ElementT = typename ContainerT::value_type; static FailureOr<ContainerT> parse(AsmParser &parser) { ContainerT elements; @@ -252,28 +202,6 @@ 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 075d39194ed97..fe2fa0a0ccd23 100644 --- a/mlir/include/mlir/IR/OpDefinition.h +++ b/mlir/include/mlir/IR/OpDefinition.h @@ -1872,16 +1872,6 @@ 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; @@ -2049,11 +2039,10 @@ class Op : public OpState, public Traits<ConcreteType>... { p, ConcreteType::getPropertiesAsAttr(ctx, properties), elidedProps); } - /// 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'. + /// 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'. template <typename T = ConcreteType> static ParseResult parseProperties(OpAsmParser &parser, OperationState &result) { @@ -2062,9 +2051,6 @@ 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 5877b54147f99..81e6d85720829 100644 --- a/mlir/include/mlir/TableGen/Property.h +++ b/mlir/include/mlir/TableGen/Property.h @@ -94,10 +94,6 @@ 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 88a64891b8b86..b003d74c7bdee 100644 --- a/mlir/lib/TableGen/Property.cpp +++ b/mlir/lib/TableGen/Property.cpp @@ -104,21 +104,6 @@ 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 deleted file mode 100644 index a6adf7ed98f8a..0000000000000 --- a/mlir/test/IR/properties-invalid.mlir +++ /dev/null @@ -1,79 +0,0 @@ -// 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 64548e41dc111..4d83038f31cdd 100644 --- a/mlir/test/IR/properties.mlir +++ b/mlir/test/IR/properties.mlir @@ -23,85 +23,6 @@ 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"() @@ -134,7 +55,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 = [2, 1], resultSegmentSizes = [2, 1]> +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>}> // CHECK: test.with_default_valued_properties na{{$}} // GENERIC: "test.with_default_valued_properties"() @@ -200,20 +121,6 @@ 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 diff ers 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 6320d39138f5f..b4cc2cd6cf569 100644 --- a/mlir/test/lib/Dialect/Test/TestOps.h +++ b/mlir/test/lib/Dialect/Test/TestOps.h @@ -43,40 +43,6 @@ #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 0bd7fe401c3c9..3cb7f6ce2a054 100644 --- a/mlir/test/lib/Dialect/Test/TestOps.td +++ b/mlir/test/lib/Dialect/Test/TestOps.td @@ -534,12 +534,6 @@ 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"; } @@ -3633,18 +3627,6 @@ 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> @@ -3660,9 +3642,6 @@ 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"> { @@ -3672,249 +3651,6 @@ 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 e64e50b2382cb..cf66ad46ad24b 100644 --- a/mlir/test/mlir-tblgen/enums-gen.td +++ b/mlir/test/mlir-tblgen/enums-gen.td @@ -45,9 +45,6 @@ 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) { @@ -61,11 +58,6 @@ 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, @@ -137,7 +129,6 @@ 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 = {}; @@ -158,12 +149,6 @@ 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 845f1ef46cf35..4b90082176dc0 100644 --- a/mlir/tools/mlir-tblgen/EnumsGen.cpp +++ b/mlir/tools/mlir-tblgen/EnumsGen.cpp @@ -129,8 +129,6 @@ 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. @@ -159,8 +157,6 @@ 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 = {{}; @@ -189,8 +185,6 @@ 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 = {{}; @@ -239,7 +233,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, separator.trim() == "," ? "false" : "true"); + casesInitList); } 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 5c97d2345051d..f4080948db10a 100644 --- a/mlir/tools/mlir-tblgen/OpFormatGen.cpp +++ b/mlir/tools/mlir-tblgen/OpFormatGen.cpp @@ -1494,223 +1494,6 @@ 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"); @@ -1744,7 +1527,6 @@ 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, _______________________________________________ llvm-branch-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits
