Skip to content

Commit 5b95c9e

Browse files
authored
[mlir][ODS] Make prop-dict behave closer to attr-dict (#88659)
`attr-dict` currently prints any attribute (inherent or discardable) that does not occur elsewhere in the assembly format. `prop-dict` on the other hand, always prints and parses all properties (including inherent attributes stored as properties) as part of the property dictionary, regardless of whether the properties or attributes are used elsewhere. (with the exception of default-valued attributes implemented recently in #87970). This PR changes the behavior of `prop-dict` to only print and parse attributes and properties that do not occur elsewhere in the assembly format. This is achieved by 1) adding used attributes and properties to the elision list when printing and 2) using a custom version of `setPropertiesFromAttr` called `setPropertiesFromParsedAttr` that is sensitive to the assembly format and auto-generated by ODS. The current and new behavior of `prop-dict` and `attr-dict` were also documented. Happens to also fix #88506
1 parent 33e60f3 commit 5b95c9e

File tree

6 files changed

+202
-8
lines changed

6 files changed

+202
-8
lines changed

mlir/docs/DefiningDialects/Operations.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -640,13 +640,23 @@ The available directives are as follows:
640640

641641
* `attr-dict`
642642

643-
- Represents the attribute dictionary of the operation.
643+
- Represents the attribute dictionary of the operation. Any inherent
644+
- attributes that are not used elsewhere in the format are printed as
645+
- part of the attribute dictionary unless a `prop-dict` is present.
646+
- Discardable attributes are always part of the `attr-dict`.
644647

645648
* `attr-dict-with-keyword`
646649

647650
- Represents the attribute dictionary of the operation, but prefixes the
648651
dictionary with an `attributes` keyword.
649652

653+
* `prop-dict`
654+
655+
- Represents the properties of the operation converted to a dictionary.
656+
- Any property or inherent attribute that are not used elsewhere in the
657+
- format are parsed and printed as part of this dictionary.
658+
- If present, the `attr-dict` will not contain any inherent attributes.
659+
650660
* `custom` < UserDirective > ( Params )
651661

652662
- Represents a custom directive implemented by the user in C++.

mlir/include/mlir/IR/OpDefinition.h

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1993,16 +1993,42 @@ class Op : public OpState, public Traits<ConcreteType>... {
19931993
p, ConcreteType::getPropertiesAsAttr(ctx, properties), elidedProps);
19941994
}
19951995

1996-
/// Parser the properties. Unless overridden, this method will print by
1997-
/// converting the properties to an Attribute.
1996+
/// Parses 'prop-dict' for the operation. Unless overridden, the method will
1997+
/// parse the properties using the generic property dictionary using the
1998+
/// '<{ ... }>' syntax. The resulting properties are stored within the
1999+
/// property structure of 'result', accessible via 'getOrAddProperties'.
19982000
template <typename T = ConcreteType>
19992001
static ParseResult parseProperties(OpAsmParser &parser,
20002002
OperationState &result) {
20012003
if constexpr (detect_has_parse_properties<InferredProperties<T>>::value) {
20022004
return parseProperties(
20032005
parser, result.getOrAddProperties<InferredProperties<T>>());
20042006
}
2005-
return genericParseProperties(parser, result.propertiesAttr);
2007+
2008+
Attribute propertyDictionary;
2009+
if (genericParseProperties(parser, propertyDictionary))
2010+
return failure();
2011+
2012+
// The generated 'setPropertiesFromParsedAttr', like
2013+
// 'setPropertiesFromAttr', expects a 'DictionaryAttr' that is not null.
2014+
// Use an empty dictionary in the case that the whole dictionary is
2015+
// optional.
2016+
if (!propertyDictionary)
2017+
propertyDictionary = DictionaryAttr::get(result.getContext());
2018+
2019+
auto emitError = [&]() {
2020+
return mlir::emitError(result.location, "invalid properties ")
2021+
<< propertyDictionary << " for op " << result.name.getStringRef()
2022+
<< ": ";
2023+
};
2024+
2025+
// Copy the data from the dictionary attribute into the property struct of
2026+
// the operation. This method is generated by ODS by default if there are
2027+
// any occurrences of 'prop-dict' in the assembly format and should set
2028+
// any properties that aren't parsed elsewhere.
2029+
return ConcreteOpType::setPropertiesFromParsedAttr(
2030+
result.getOrAddProperties<InferredProperties<T>>(), propertyDictionary,
2031+
emitError);
20062032
}
20072033

20082034
private:

mlir/test/lib/Dialect/Test/TestDialect.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -740,6 +740,17 @@ LogicalResult OpWithResultShapePerDimInterfaceOp::reifyResultShapes(
740740
return success();
741741
}
742742

743+
LogicalResult TestOpWithPropertiesAndInferredType::inferReturnTypes(
744+
MLIRContext *context, std::optional<Location>, ValueRange operands,
745+
DictionaryAttr attributes, OpaqueProperties properties, RegionRange regions,
746+
SmallVectorImpl<Type> &inferredReturnTypes) {
747+
748+
Adaptor adaptor(operands, attributes, properties, regions);
749+
inferredReturnTypes.push_back(IntegerType::get(
750+
context, adaptor.getLhs() + adaptor.getProperties().rhs));
751+
return success();
752+
}
753+
743754
//===----------------------------------------------------------------------===//
744755
// Test SideEffect interfaces
745756
//===----------------------------------------------------------------------===//

mlir/test/lib/Dialect/Test/TestOps.td

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2852,6 +2852,23 @@ def TestOpWithProperties : TEST_Op<"with_properties"> {
28522852
);
28532853
}
28542854

2855+
def TestOpWithPropertiesAndAttr
2856+
: TEST_Op<"with_properties_and_attr"> {
2857+
let assemblyFormat = "$lhs prop-dict attr-dict";
2858+
2859+
let arguments = (ins I32Attr:$lhs, IntProperty<"int64_t">:$rhs);
2860+
}
2861+
2862+
def TestOpWithPropertiesAndInferredType
2863+
: TEST_Op<"with_properties_and_inferred_type", [
2864+
DeclareOpInterfaceMethods<InferTypeOpInterface>
2865+
]> {
2866+
let assemblyFormat = "$lhs prop-dict attr-dict";
2867+
2868+
let arguments = (ins I32Attr:$lhs, IntProperty<"int64_t">:$rhs);
2869+
let results = (outs AnyType:$result);
2870+
}
2871+
28552872
// Demonstrate how to wrap an existing C++ class named MyPropStruct.
28562873
def MyStructProperty : Property<"MyPropStruct"> {
28572874
let convertToAttribute = "$_storage.asAttribute($_ctxt)";
@@ -2871,6 +2888,15 @@ def TestOpUsingPropertyInCustom : TEST_Op<"using_property_in_custom"> {
28712888
let arguments = (ins ArrayProperty<"int64_t", 3>:$prop);
28722889
}
28732890

2891+
def TestOpUsingPropertyInCustomAndOther
2892+
: TEST_Op<"using_property_in_custom_and_other"> {
2893+
let assemblyFormat = "custom<UsingPropertyInCustom>($prop) prop-dict attr-dict";
2894+
let arguments = (ins
2895+
ArrayProperty<"int64_t", 3>:$prop,
2896+
IntProperty<"int64_t">:$other
2897+
);
2898+
}
2899+
28742900
def TestOpUsingPropertyRefInCustom : TEST_Op<"using_property_ref_in_custom"> {
28752901
let assemblyFormat = "custom<IntProperty>($first) `+` custom<SumProperty>($second, ref($first)) attr-dict";
28762902
let arguments = (ins IntProperty<"int64_t">:$first, IntProperty<"int64_t">:$second);

mlir/test/mlir-tblgen/op-format.mlir

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,17 @@ test.format_infer_variadic_type_from_non_variadic %i64, %i64 : i64
480480
// CHECK: test.format_infer_type_variadic_operands(%[[I32]], %[[I32]] : i32, i32) (%[[I64]], %[[I64]] : i64, i64)
481481
%ignored_res13:4 = test.format_infer_type_variadic_operands(%i32, %i32 : i32, i32) (%i64, %i64 : i64, i64)
482482

483+
// CHECK: test.with_properties_and_attr 16 < {rhs = 16 : i64}>
484+
test.with_properties_and_attr 16 <{rhs = 16 : i64}>
485+
486+
// CHECK: test.with_properties_and_inferred_type 16 < {rhs = 16 : i64}>
487+
%should_be_i32 = test.with_properties_and_inferred_type 16 <{rhs = 16 : i64}>
488+
// Assert through the verifier that its inferred as i32.
489+
test.format_all_types_match_var %should_be_i32, %i32 : i32
490+
491+
// CHECK: test.using_property_in_custom_and_other [1, 4, 20] < {other = 16 : i64}>
492+
test.using_property_in_custom_and_other [1, 4, 20] <{other = 16 : i64}>
493+
483494
//===----------------------------------------------------------------------===//
484495
// Check DefaultValuedStrAttr
485496
//===----------------------------------------------------------------------===//

mlir/tools/mlir-tblgen/OpFormatGen.cpp

Lines changed: 114 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -379,8 +379,11 @@ struct OperationFormat {
379379
std::vector<TypeResolution> operandTypes, resultTypes;
380380

381381
/// The set of attributes explicitly used within the format.
382-
SmallVector<const NamedAttribute *, 8> usedAttributes;
382+
llvm::SmallSetVector<const NamedAttribute *, 8> usedAttributes;
383383
llvm::StringSet<> inferredAttributes;
384+
385+
/// The set of properties explicitly used within the format.
386+
llvm::SmallSetVector<const NamedProperty *, 8> usedProperties;
384387
};
385388
} // namespace
386389

@@ -1183,6 +1186,105 @@ static void genAttrParser(AttributeVariable *attr, MethodBody &body,
11831186
}
11841187
}
11851188

1189+
// Generates the 'setPropertiesFromParsedAttr' used to set properties from a
1190+
// 'prop-dict' dictionary attr.
1191+
static void genParsedAttrPropertiesSetter(OperationFormat &fmt, Operator &op,
1192+
OpClass &opClass) {
1193+
// Not required unless 'prop-dict' is present.
1194+
if (!fmt.hasPropDict)
1195+
return;
1196+
1197+
SmallVector<MethodParameter> paramList;
1198+
paramList.emplace_back("Properties &", "prop");
1199+
paramList.emplace_back("::mlir::Attribute", "attr");
1200+
paramList.emplace_back("::llvm::function_ref<::mlir::InFlightDiagnostic()>",
1201+
"emitError");
1202+
1203+
Method *method = opClass.addStaticMethod("::mlir::LogicalResult",
1204+
"setPropertiesFromParsedAttr",
1205+
std::move(paramList));
1206+
MethodBody &body = method->body().indent();
1207+
1208+
body << R"decl(
1209+
::mlir::DictionaryAttr dict = ::llvm::dyn_cast<::mlir::DictionaryAttr>(attr);
1210+
if (!dict) {
1211+
emitError() << "expected DictionaryAttr to set properties";
1212+
return ::mlir::failure();
1213+
}
1214+
)decl";
1215+
1216+
// TODO: properties might be optional as well.
1217+
const char *propFromAttrFmt = R"decl(
1218+
auto setFromAttr = [] (auto &propStorage, ::mlir::Attribute propAttr,
1219+
::llvm::function_ref<::mlir::InFlightDiagnostic()> emitError) {{
1220+
{0};
1221+
};
1222+
auto attr = dict.get("{1}");
1223+
if (!attr) {{
1224+
emitError() << "expected key entry for {1} in DictionaryAttr to set "
1225+
"Properties.";
1226+
return ::mlir::failure();
1227+
}
1228+
if (::mlir::failed(setFromAttr(prop.{1}, attr, emitError)))
1229+
return ::mlir::failure();
1230+
)decl";
1231+
1232+
// Generate the setter for any property not parsed elsewhere.
1233+
for (const NamedProperty &namedProperty : op.getProperties()) {
1234+
if (fmt.usedProperties.contains(&namedProperty))
1235+
continue;
1236+
1237+
auto scope = body.scope("{\n", "}\n", /*indent=*/true);
1238+
1239+
StringRef name = namedProperty.name;
1240+
const Property &prop = namedProperty.prop;
1241+
FmtContext fctx;
1242+
body << formatv(propFromAttrFmt,
1243+
tgfmt(prop.getConvertFromAttributeCall(),
1244+
&fctx.addSubst("_attr", "propAttr")
1245+
.addSubst("_storage", "propStorage")
1246+
.addSubst("_diag", "emitError")),
1247+
name);
1248+
}
1249+
1250+
// Generate the setter for any attribute not parsed elsewhere.
1251+
for (const NamedAttribute &namedAttr : op.getAttributes()) {
1252+
if (fmt.usedAttributes.contains(&namedAttr))
1253+
continue;
1254+
1255+
const Attribute &attr = namedAttr.attr;
1256+
// Derived attributes do not need to be parsed.
1257+
if (attr.isDerivedAttr())
1258+
continue;
1259+
1260+
auto scope = body.scope("{\n", "}\n", /*indent=*/true);
1261+
1262+
// If the attribute has a default value or is optional, it does not need to
1263+
// be present in the parsed dictionary attribute.
1264+
bool isRequired = !attr.isOptional() && !attr.hasDefaultValue();
1265+
body << formatv(R"decl(
1266+
auto &propStorage = prop.{0};
1267+
auto attr = dict.get("{0}");
1268+
if (attr || /*isRequired=*/{1}) {{
1269+
if (!attr) {{
1270+
emitError() << "expected key entry for {0} in DictionaryAttr to set "
1271+
"Properties.";
1272+
return ::mlir::failure();
1273+
}
1274+
auto convertedAttr = ::llvm::dyn_cast<std::remove_reference_t<decltype(propStorage)>>(attr);
1275+
if (convertedAttr) {{
1276+
propStorage = convertedAttr;
1277+
} else {{
1278+
emitError() << "Invalid attribute `{0}` in property conversion: " << attr;
1279+
return ::mlir::failure();
1280+
}
1281+
}
1282+
)decl",
1283+
namedAttr.name, isRequired);
1284+
}
1285+
body << "return ::mlir::success();\n";
1286+
}
1287+
11861288
void OperationFormat::genParser(Operator &op, OpClass &opClass) {
11871289
SmallVector<MethodParameter> paramList;
11881290
paramList.emplace_back("::mlir::OpAsmParser &", "parser");
@@ -1214,6 +1316,8 @@ void OperationFormat::genParser(Operator &op, OpClass &opClass) {
12141316
genParserTypeResolution(op, body);
12151317

12161318
body << " return ::mlir::success();\n";
1319+
1320+
genParsedAttrPropertiesSetter(*this, op, opClass);
12171321
}
12181322

12191323
void OperationFormat::genElementParser(FormatElement *element, MethodBody &body,
@@ -1776,6 +1880,11 @@ const char *enumAttrBeginPrinterCode = R"(
17761880
static void genPropDictPrinter(OperationFormat &fmt, Operator &op,
17771881
MethodBody &body) {
17781882
body << " ::llvm::SmallVector<::llvm::StringRef, 2> elidedProps;\n";
1883+
for (const NamedProperty *namedProperty : fmt.usedProperties)
1884+
body << " elidedProps.push_back(\"" << namedProperty->name << "\");\n";
1885+
for (const NamedAttribute *namedAttr : fmt.usedAttributes)
1886+
body << " elidedProps.push_back(\"" << namedAttr->name << "\");\n";
1887+
17791888
// Add code to check attributes for equality with the default value
17801889
// for attributes with the elidePrintingDefaultValue bit set.
17811890
for (const NamedAttribute &namedAttr : op.getAttributes()) {
@@ -2543,7 +2652,7 @@ class OpFormatParser : public FormatParser {
25432652
llvm::DenseSet<const NamedTypeConstraint *> seenOperands;
25442653
llvm::DenseSet<const NamedRegion *> seenRegions;
25452654
llvm::DenseSet<const NamedSuccessor *> seenSuccessors;
2546-
llvm::DenseSet<const NamedProperty *> seenProperties;
2655+
llvm::SmallSetVector<const NamedProperty *, 8> seenProperties;
25472656
};
25482657
} // namespace
25492658

@@ -2589,7 +2698,8 @@ LogicalResult OpFormatParser::verify(SMLoc loc,
25892698
return failure();
25902699

25912700
// Collect the set of used attributes in the format.
2592-
fmt.usedAttributes = seenAttrs.takeVector();
2701+
fmt.usedAttributes = std::move(seenAttrs);
2702+
fmt.usedProperties = std::move(seenProperties);
25932703

25942704
// Set whether prop-dict is used in the format
25952705
fmt.hasPropDict = hasPropDict;
@@ -3042,7 +3152,7 @@ OpFormatParser::parseVariableImpl(SMLoc loc, StringRef name, Context ctx) {
30423152
return emitError(loc, "property '" + name +
30433153
"' must be bound before it is referenced");
30443154
} else {
3045-
if (!seenProperties.insert(property).second)
3155+
if (!seenProperties.insert(property))
30463156
return emitError(loc, "property '" + name + "' is already bound");
30473157
}
30483158

0 commit comments

Comments
 (0)