diff --git a/mlir/docs/DefiningDialects/Operations.md b/mlir/docs/DefiningDialects/Operations.md index 01fadef5c3dbe..c252d9caf98eb 100644 --- a/mlir/docs/DefiningDialects/Operations.md +++ b/mlir/docs/DefiningDialects/Operations.md @@ -101,6 +101,9 @@ their semantics via a special [TableGen backend][TableGenBackend]: * The `AttrConstraint` class hierarchy: They are used to specify the constraints over attributes. A notable subclass hierarchy is `Attr`, which stands for constraints for attributes whose values are of common types. +* The `Property` class hierarchy: They are used to specify non-attribute-backed + properties that are inherent to operations. This will be expanded to a + `PropertyConstraint` class or something similar in the future. An operation is defined by specializing the `Op` class with concrete contents for all the fields it requires. For example, `tf.AvgPool` is defined as @@ -172,9 +175,9 @@ understanding the operation. ### Operation arguments -There are two kinds of arguments: operands and attributes. Operands are runtime -values produced by other ops; while attributes are compile-time known constant -values, including two categories: +There are three kinds of arguments: operands, attributes, and properties. +Operands are runtime values produced by other ops; while attributes and properties +are compile-time known constant values, including two categories: 1. Natural attributes: these attributes affect the behavior of the operations (e.g., padding for convolution); @@ -187,8 +190,11 @@ values, including two categories: even though they are not materialized, it should be possible to store as an attribute. -Both operands and attributes are specified inside the `dag`-typed `arguments`, -led by `ins`: +Properties are similar to attributes, except that they are not stored within +the MLIR context but are stored inline with the operation. + +Operands, attributes, and properties are specified inside the `dag`-typed +`arguments`, led by `ins`: ```tablegen let arguments = (ins @@ -196,13 +202,15 @@ let arguments = (ins ... :$, ... + :$, ); ``` Here `` is a TableGen `def` from the `TypeConstraint` class hierarchy. Similarly, `` is a TableGen `def` from the -`AttrConstraint` class hierarchy. See [Constraints](#constraints) for more -information. +`AttrConstraint` class hierarchy and `` is a subclass +of `Property` (though a `PropertyConstraint` hierarchy is planned). +See [Constraints](#constraints) for more information. There is no requirements on the relative order of operands and attributes; they can mix freely. The relative order of operands themselves matters. From each @@ -324,6 +332,18 @@ Right now, the following primitive constraints are supported: TODO: Design and implement more primitive constraints +#### Optional and default-valued properties + +To declare a property with a default value, use `DefaultValuedProperty<..., "...">`. +If the property's storage data type is different from its interface type, +for example, in the case of array properties (which are stored as `SmallVector`s +but use `ArrayRef` as an interface type), add the storage-type equivalent +of the default value as the third argument. + +To declare an optional property, use `OptionalProperty<...>`. +This wraps the underlying property in an `std::optional` and gives it a +default value of `std::nullopt`. + #### Combining constraints `AllAttrOf` is provided to allow combination of multiple constraints which @@ -429,6 +449,8 @@ def MyOp : ... { I32Attr:$i32_attr, F32Attr:$f32_attr, ... + I32Property:$i32_prop, + ... ); let results = (outs @@ -453,7 +475,8 @@ static void build(OpBuilder &odsBuilder, OperationState &odsState, static void build(OpBuilder &odsBuilder, OperationState &odsState, Type i32_result, Type f32_result, ..., Value i32_operand, Value f32_operand, ..., - IntegerAttr i32_attr, FloatAttr f32_attr, ...); + IntegerAttr i32_attr, FloatAttr f32_attr, ..., + int32_t i32_prop); // Each result-type/operand/attribute has a separate parameter. The parameters // for attributes are raw values unwrapped with mlir::Attribute instances. @@ -462,13 +485,15 @@ static void build(OpBuilder &odsBuilder, OperationState &odsState, static void build(OpBuilder &odsBuilder, OperationState &odsState, Type i32_result, Type f32_result, ..., Value i32_operand, Value f32_operand, ..., - APInt i32_attr, StringRef f32_attr, ...); + APInt i32_attr, StringRef f32_attr, ..., + int32_t i32_prop, ...); // Each operand/attribute has a separate parameter but result type is aggregate. static void build(OpBuilder &odsBuilder, OperationState &odsState, TypeRange resultTypes, Value i32_operand, Value f32_operand, ..., - IntegerAttr i32_attr, FloatAttr f32_attr, ...); + IntegerAttr i32_attr, FloatAttr f32_attr, ..., + int32_t i32_prop, ...); // All operands/attributes have aggregate parameters. // Generated if return type can be inferred. @@ -921,8 +946,10 @@ optional-group: `(` then-elements `)` (`:` `(` else-elements `)`)? `?` The elements of an optional group have the following requirements: * The first element of `then-elements` must either be a attribute, literal, - operand, or region. + operand, property, or region. - This is because the first element must be optionally parsable. + - If a property is used, it must have an `optionalParser` defined and have a + default value. * Exactly one argument variable or type directive within either `then-elements` or `else-elements` must be marked as the anchor of the group. @@ -984,6 +1011,8 @@ foo.op is_read_only foo.op ``` +The same logic applies to a `UnitProperty`. + ##### Optional "else" Group Optional groups also have support for an "else" group of elements. These are @@ -1026,6 +1055,8 @@ to: 1. All operand and result types must appear within the format using the various `type` directives, either individually or with the `operands` or `results` directives. +1. Unless all non-attribute properties appear in the format, the `prop-dict` + directive must be present. 1. The `attr-dict` directive must always be present. 1. Must not contain overlapping information; e.g. multiple instances of 'attr-dict', types, operands, etc. diff --git a/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td b/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td index 06656c791c594..e40000829989d 100644 --- a/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td +++ b/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td @@ -59,22 +59,9 @@ class LLVM_IntArithmeticOpWithOverflowFlag traits = []> : LLVM_ArithmeticOpBase], traits)> { - dag iofArg = (ins EnumProperty<"IntegerOverflowFlags">:$overflowFlags); + dag iofArg = (ins EnumProperty<"IntegerOverflowFlags", "", "IntegerOverflowFlags::none">:$overflowFlags); let arguments = !con(commonArgs, iofArg); - let builders = [ - OpBuilder<(ins "Type":$type, "Value":$lhs, "Value":$rhs, - "IntegerOverflowFlags":$overflowFlags), [{ - $_state.getOrAddProperties().overflowFlags = overflowFlags; - build($_builder, $_state, type, lhs, rhs); - }]>, - OpBuilder<(ins "Value":$lhs, "Value":$rhs, - "IntegerOverflowFlags":$overflowFlags), [{ - $_state.getOrAddProperties().overflowFlags = overflowFlags; - build($_builder, $_state, lhs, rhs); - }]> - ]; - string mlirBuilder = [{ auto op = $_builder.create<$_qualCppClassName>($_location, $lhs, $rhs); moduleImport.setIntegerOverflowFlags(inst, op); diff --git a/mlir/include/mlir/IR/ODSSupport.h b/mlir/include/mlir/IR/ODSSupport.h index 70e3f986431e2..25d6f3da6a861 100644 --- a/mlir/include/mlir/IR/ODSSupport.h +++ b/mlir/include/mlir/IR/ODSSupport.h @@ -33,6 +33,37 @@ convertFromAttribute(int64_t &storage, Attribute attr, /// Convert the provided int64_t to an IntegerAttr attribute. Attribute convertToAttribute(MLIRContext *ctx, int64_t storage); +/// Convert an IntegerAttr attribute to an int32_t, or return an error if the +/// attribute isn't an IntegerAttr. If the optional diagnostic is provided an +/// error message is also emitted. +LogicalResult +convertFromAttribute(int32_t &storage, Attribute attr, + function_ref emitError); + +/// Convert the provided int32_t to an IntegerAttr attribute. +Attribute convertToAttribute(MLIRContext *ctx, int32_t storage); + +/// Extract the string from `attr` into `storage`. If `attr` is not a +/// `StringAttr`, return failure and emit an error into the diagnostic from +/// `emitError`. +LogicalResult +convertFromAttribute(std::string &storage, Attribute attr, + function_ref emitError); + +/// Convert the given string into a StringAttr. Note that this takes a reference +/// to the storage of a string property, which is an std::string. +Attribute convertToAttribute(MLIRContext *ctx, const std::string &storage); + +/// Extract the boolean from `attr` into `storage`. If `attr` is not a +/// `BoolAttr`, return failure and emit an error into the diagnostic from +/// `emitError`. +LogicalResult +convertFromAttribute(bool &storage, Attribute attr, + function_ref emitError); + +/// Convert the given string into a BooleanAttr. +Attribute convertToAttribute(MLIRContext *ctx, bool storage); + /// Convert a DenseI64ArrayAttr to the provided storage. It is expected that the /// storage has the same size as the array. An error is returned if the /// attribute isn't a DenseI64ArrayAttr or it does not have the same size. If @@ -49,9 +80,24 @@ LogicalResult convertFromAttribute(MutableArrayRef storage, Attribute attr, function_ref emitError); +/// Convert a DenseI64ArrayAttr to the provided storage, which will be +/// cleared before writing. An error is returned and emitted to the optional +/// `emitError` function if the attribute isn't a DenseI64ArrayAttr. +LogicalResult +convertFromAttribute(SmallVectorImpl &storage, Attribute attr, + function_ref emitError); + +/// Convert a DenseI32ArrayAttr to the provided storage, which will be +/// cleared before writing. It is expected that the storage has the same size as +/// the array. An error is returned and emitted to the optional `emitError` +/// function if the attribute isn't a DenseI32ArrayAttr. +LogicalResult +convertFromAttribute(SmallVectorImpl &storage, Attribute attr, + function_ref emitError); + /// Convert the provided ArrayRef to a DenseI64ArrayAttr attribute. Attribute convertToAttribute(MLIRContext *ctx, ArrayRef storage); } // namespace mlir -#endif // MLIR_IR_ODSSUPPORT_H \ No newline at end of file +#endif // MLIR_IR_ODSSUPPORT_H diff --git a/mlir/include/mlir/IR/Properties.td b/mlir/include/mlir/IR/Properties.td index 0babdbbfa05bc..0becf7d009835 100644 --- a/mlir/include/mlir/IR/Properties.td +++ b/mlir/include/mlir/IR/Properties.td @@ -29,7 +29,6 @@ class Property { // // Format: // - `$_storage` will contain the property in the storage type. - // - `$_ctxt` will contain an `MLIRContext *`. code convertFromStorage = "$_storage"; // The call expression to build a property storage from the interface type. @@ -40,24 +39,26 @@ class Property { code assignToStorage = "$_storage = $_value"; // The call expression to convert from the storage type to an attribute. + // The resulting attribute must be non-null in non-error cases. // // Format: // - `$_storage` is the storage type value. // - `$_ctxt` is a `MLIRContext *`. // - // The expression must result in an Attribute. + // The expression must return an `Attribute` and will be used as a function body. code convertToAttribute = [{ - convertToAttribute($_ctxt, $_storage) + return convertToAttribute($_ctxt, $_storage); }]; // The call expression to convert from an Attribute to the storage type. // // Format: - // - `$_storage` is the storage type value. + // - `$_storage` is a reference to a value of the storage type. // - `$_attr` is the attribute. // - `$_diag` is a callback to get a Diagnostic to emit error. // - // The expression must return a LogicalResult + // The expression must return a LogicalResult and will be used as a function body + // or in other similar contexts. code convertFromAttribute = [{ return convertFromAttribute($_storage, $_attr, $_diag); }]; @@ -68,18 +69,68 @@ class Property { // - `$_storage` is the variable to hash. // // The expression should define a llvm::hash_code. - code hashProperty = [{ - llvm::hash_value($_storage); + // If unspecified, defaults to `llvm::hash_value($_storage)`. + // The default is not specified in tablegen because many combinators, like + // ArrayProperty, can fall back to more efficient implementations of + // `hashProperty` when their underlying elements have trivial hashing. + code hashProperty = ""; + + // The body of the parser for a value of this property. + // Format: + // - `$_parser` is the OpAsmParser. + // - `$_storage` is the location into which the value is to be placed if it is + // present. + // - `$_ctxt` is a `MLIRContext *` + // + // This defines the body of a function (typically a lambda) that returns a + // ParseResult. There is an implicit `return success()` at the end of the parser + // code. + // + // When this code executes, `$_storage` will be initialized to the property's + // default value (if any, accounting for the storage type override). + code parser = [{ + auto value = ::mlir::FieldParser<}] # storageType # [{>::parse($_parser); + if (::mlir::failed(value)) + return ::mlir::failure(); + $_storage = std::move(*value); }]; + // The body of the parser for a value of this property as the anchor of an optional + // group. This should parse the property if possible and do nothing if a value of + // the relevant type is not next in the parse stream. + // You are not required to define this parser if it cannot be meaningfully + // implemented. + // This has the same context and substitutions as `parser` except that it is + // required to return an OptionalParseResult. + // + // If the optional parser doesn't parse anything, it should not set + // $_storage, since the parser doesn't know if the default value has been + // overwritten. + code optionalParser = ""; + + // The printer for a value of this property. + // Format: + // - `$_storage` is the storage data. + // - `$_printer` is the OpAsmPrinter instance. + // - `$_ctxt` is a `MLIRContext *` + // + // This may be called in an expression context, so variable declarations must + // be placed within a new scope. + // + // The printer for a property should always print a non-empty value - default value + // printing elision happens outside the context of this printing expression. + code printer = "$_printer << $_storage"; + // The call expression to emit the storage type to bytecode. // // Format: // - `$_storage` is the storage type value. // - `$_writer` is a `DialectBytecodeWriter`. // - `$_ctxt` is a `MLIRContext *`. + // + // This will become the body af a function returning void. code writeToMlirBytecode = [{ - writeToMlirBytecode($_writer, $_storage) + writeToMlirBytecode($_writer, $_storage); }]; // The call expression to read the storage type from bytecode. @@ -88,13 +139,31 @@ class Property { // - `$_storage` is the storage type value. // - `$_reader` is a `DialectBytecodeReader`. // - `$_ctxt` is a `MLIRContext *`. + // + // This will become the body of a function returning LogicalResult. + // There is an implicit `return success()` at the end of this function. + // + // When this code executes, `$_storage` will be initialized to the property's + // default value (if any, accounting for the storage type override). code readFromMlirBytecode = [{ if (::mlir::failed(readFromMlirBytecode($_reader, $_storage))) return ::mlir::failure(); }]; - // Default value for the property. - string defaultValue = ?; + // Base definition for the property. (Will be) used for `OptionalProperty` and + // such cases, analogously to `baseAttr`. + Property baseProperty = ?; + + // Default value for the property within its storage. This should be an expression + // of type `interfaceType` and should be comparable with other types of that + // interface typ with `==`. The empty string means there is no default value. + string defaultValue = ""; + + // If set, the default value the storage of the property should be initilized to. + // This is only needed when the storage and interface types of the property + // are distinct (ex. SmallVector for storage vs. ArrayRef for interfacing), as it + // will fall back to `defaultValue` when unspecified. + string storageTypeValueOverride = ""; } /// Implementation of the Property class's `readFromMlirBytecode` field using @@ -133,12 +202,16 @@ defvar writeMlirBytecodeWithConvertToAttribute = [{ // Primitive property kinds // Any kind of integer stored as properties. -class IntProperty : +class IntProperty : Property { - code writeToMlirBytecode = [{ + let summary = !if(!empty(desc), storageTypeParam, desc); + let optionalParser = [{ + return $_parser.parseOptionalInteger($_storage); + }]; + let writeToMlirBytecode = [{ $_writer.writeVarInt($_storage); }]; - code readFromMlirBytecode = [{ + let readFromMlirBytecode = [{ uint64_t val; if (failed($_reader.readVarInt(val))) return ::mlir::failure(); @@ -146,24 +219,472 @@ class IntProperty : }]; } -class ArrayProperty : - Property { - let interfaceType = "::llvm::ArrayRef<" # storageTypeParam # ">"; - let convertFromStorage = "$_storage"; - let assignToStorage = "::llvm::copy($_value, $_storage)"; -} +def I32Property : IntProperty<"int32_t">; +def I64Property : IntProperty<"int64_t">; -class EnumProperty : +class EnumProperty : Property { - code writeToMlirBytecode = [{ + // TODO: take advantage of EnumAttrInfo and the like to make this share nice + // parsing code with EnumAttr. + let writeToMlirBytecode = [{ $_writer.writeVarInt(static_cast($_storage)); }]; - code readFromMlirBytecode = [{ + let readFromMlirBytecode = [{ uint64_t val; if (failed($_reader.readVarInt(val))) return ::mlir::failure(); $_storage = static_cast<}] # storageTypeParam # [{>(val); }]; + let defaultValue = default; } +def StringProperty : Property<"std::string", "string"> { + let interfaceType = "::llvm::StringRef"; + let convertFromStorage = "::llvm::StringRef{$_storage}"; + let assignToStorage = "$_storage = $_value.str()"; + let optionalParser = [{ + if (::mlir::failed($_parser.parseOptionalString(&$_storage))) + return std::nullopt; + }]; + let printer = "$_printer.printString($_storage)"; + let readFromMlirBytecode = [{ + StringRef val; + if (::mlir::failed($_reader.readString(val))) + return ::mlir::failure(); + $_storage = val.str(); + }]; + let writeToMlirBytecode = [{ + $_writer.writeOwnedString($_storage); + }]; +} + +def BoolProperty : IntProperty<"bool", "boolean"> { + let printer = [{ $_printer << ($_storage ? "true" : "false") }]; + let readFromMlirBytecode = [{ + return $_reader.readBool($_storage); + }]; + let writeToMlirBytecode = [{ + $_writer.writeOwnedBool($_storage); + }]; +} + +def UnitProperty : Property<"bool", "unit property"> { + let summary = "unit property"; + let description = [{ + A property whose presence or abscence is used as a flag. + + This is stored as a boolean that defaults to false, and is named UnitProperty + by analogy with UnitAttr, which has the more comprehensive rationale and + explains the less typical syntax. + + Note that this attribute does have a syntax for the false case to allow for its + use in contexts where default values shouldn't be elided. + }]; + let defaultValue = "false"; + + let convertToAttribute = [{ + if ($_storage) + return ::mlir::UnitAttr::get($_ctxt); + else + return ::mlir::BoolAttr::get($_ctxt, false); + }]; + let convertFromAttribute = [{ + if (::llvm::isa<::mlir::UnitAttr>($_attr)) { + $_storage = true; + return ::mlir::success(); + } + if (auto boolAttr = ::llvm::dyn_cast<::mlir::BoolAttr>($_attr)) { + $_storage = boolAttr.getValue(); + return ::mlir::success(); + } + return ::mlir::failure(); + }]; + + let parser = [{ + ::llvm::StringRef keyword; + if (::mlir::failed($_parser.parseOptionalKeyword(&keyword, + {"unit", "unit_absent"}))) + return $_parser.emitError($_parser.getCurrentLocation(), + "expected 'unit' or 'unit_absent'"); + $_storage = (keyword == "unit"); + }]; + + let optionalParser = [{ + ::llvm::StringRef keyword; + if (::mlir::failed($_parser.parseOptionalKeyword(&keyword, + {"unit", "unit_absent"}))) + return std::nullopt; + $_storage = (keyword == "unit"); + }]; + + let printer = [{ + $_printer << ($_storage ? "unit" : "unit_absent") + }]; + + let writeToMlirBytecode = [{ + $_writer.writeOwnedBool($_storage); + }]; + let readFromMlirBytecode = [{ + if (::mlir::failed($_reader.readBool($_storage))) + return ::mlir::failure(); + }]; +} + +//===----------------------------------------------------------------------===// +// Primitive property combinators + +/// Create a variable named `name` of `prop`'s storage type that is initialized +/// to the correct default value, if there is one. +class _makePropStorage { + code ret = prop.storageType # " " # name + # !cond(!not(!empty(prop.storageTypeValueOverride)) : " = " # prop.storageTypeValueOverride, + !not(!empty(prop.defaultValue)) : " = " # prop.defaultValue, + true : "") # ";"; +} + +/// The generic class for arrays of some other property, which is stored as a +/// `SmallVector` of that property. This uses an `ArrayAttr` as its attribute form +/// though subclasses can override this, as is the case with IntArrayAttr below. +/// Those wishing to use a non-default number of SmallVector elements should +/// subclass `ArrayProperty`. +class ArrayProperty, string desc = ""> : + Property<"::llvm::SmallVector<" # elem.storageType # ">", desc> { + let summary = "array of " # elem.summary; + let interfaceType = "::llvm::ArrayRef<" # elem.storageType # ">"; + let convertFromStorage = "::llvm::ArrayRef<" # elem.storageType # ">{$_storage}"; + let assignToStorage = "$_storage.assign($_value.begin(), $_value.end())"; + + let convertFromAttribute = [{ + auto arrayAttr = ::llvm::dyn_cast_if_present<::mlir::ArrayAttr>($_attr); + if (!arrayAttr) + return $_diag() << "expected array attribute"; + for (::mlir::Attribute elemAttr : arrayAttr) { + }] # _makePropStorage.ret # [{ + auto elemRes = [&](Attribute propAttr, }] # elem.storageType # [{& propStorage) -> ::mlir::LogicalResult { + }] # !subst("$_attr", "propAttr", + !subst("$_storage", "propStorage", elem.convertFromAttribute)) # [{ + }(elemAttr, elemVal); + if (::mlir::failed(elemRes)) + return ::mlir::failure(); + $_storage.push_back(std::move(elemVal)); + } + return ::mlir::success(); + }]; + + let convertToAttribute = [{ + SmallVector elems; + for (const auto& elemVal : $_storage) { + auto elemAttr = [&](const }] # elem.storageType #[{& propStorage) -> ::mlir::Attribute { + }] # !subst("$_storage", "propStorage", elem.convertToAttribute) # [{ + }(elemVal); + elems.push_back(elemAttr); + } + return ::mlir::ArrayAttr::get($_ctxt, elems); + }]; + + defvar theParserBegin = [{ + auto& storage = $_storage; + auto parseElemFn = [&]() -> ::mlir::ParseResult { + }] # _makePropStorage.ret # [{ + auto elemParse = [&](}] # elem.storageType # [{& propStorage) -> ::mlir::ParseResult { + }] # !subst("$_storage", "propStorage", elem.parser) # [{ + return ::mlir::success(); + }(elemVal); + if (::mlir::failed(elemParse)) + return ::mlir::failure(); + storage.push_back(std::move(elemVal)); + return ::mlir::success(); + }; + }]; + let parser = theParserBegin # [{ + return $_parser.parseCommaSeparatedList( + ::mlir::OpAsmParser::Delimiter::Square, parseElemFn); + }]; + // Hack around the lack of a peek method + let optionalParser = theParserBegin # [{ + auto oldLoc = $_parser.getCurrentLocation(); + auto parseResult = $_parser.parseCommaSeparatedList( + ::mlir::OpAsmParser::Delimiter::OptionalSquare, parseElemFn); + if (::mlir::failed(parseResult)) + return ::mlir::failure(); + auto newLoc = $_parser.getCurrentLocation(); + if (oldLoc == newLoc) + return std::nullopt; + return ::mlir::success(); + }]; + + let printer = [{ [&](){ + $_printer << "["; + auto elemPrinter = [&](const }] # elem.storageType # [{& elemVal) { + }] # !subst("$_storage", "elemVal", elem.printer) #[{; + }; + ::llvm::interleaveComma($_storage, $_printer, elemPrinter); + $_printer << "]"; + }()}]; + + let readFromMlirBytecode = [{ + uint64_t length; + if (::mlir::failed($_reader.readVarInt(length))) + return ::mlir::failure(); + $_storage.reserve(length); + for (uint64_t i = 0; i < length; ++i) { + }]# _makePropStorage.ret # [{ + auto elemRead = [&](}] # elem.storageType # [{& propStorage) -> ::mlir::LogicalResult { + }] # !subst("$_storage", "propStorage", elem.readFromMlirBytecode) # [{; + return ::mlir::success(); + }(elemVal); + if (::mlir::failed(elemRead)) + return ::mlir::failure(); + $_storage.push_back(std::move(elemVal)); + } + }]; + + let writeToMlirBytecode = [{ + $_writer.writeVarInt($_storage.size()); + for (const auto& elemVal : $_storage) { + [&]() { + }] # !subst("$_storage", "elemVal", elem.writeToMlirBytecode) #[{; + }(); + } + }]; + + // There's no hash_value for SmallVector, so we construct the ArrayRef ourselves. + // In the non-trivial case, we define a mapped range to get internal hash + // codes. + let hashProperty = !if(!empty(elem.hashProperty), + [{::llvm::hash_value(::llvm::ArrayRef<}] # elem.storageType # [{>{$_storage})}], + [{[&]() -> ::llvm::hash_code { + auto getElemHash = [](const auto& propStorage) -> ::llvm::hash_code { + return }] # !subst("$_storage", "propStorage", elem.hashProperty) # [{; + }; + auto mapped = ::llvm::map_range($_storage, getElemHash); + return ::llvm::hash_combine_range(mapped.begin(), mapped.end()); + }() + }]); +} + +class IntArrayProperty : + ArrayProperty> { + // Bring back the trivial conversions we don't get in the general case. + let convertFromAttribute = [{ + return convertFromAttribute($_storage, $_attr, $_diag); + }]; + let convertToAttribute = [{ + return convertToAttribute($_ctxt, $_storage); + }]; +} + +/// Class for giving a property a default value. +/// This doesn't change anything about the property other than giving it a default +/// which can be used by ODS to elide printing. +class DefaultValuedProperty : Property { + let defaultValue = default; + let storageTypeValueOverride = storageDefault; + let baseProperty = p; + // Keep up to date with `Property` above. + let summary = p.summary; + let description = p.description; + let storageType = p.storageType; + let interfaceType = p.interfaceType; + let convertFromStorage = p.convertFromStorage; + let assignToStorage = p.assignToStorage; + let convertToAttribute = p.convertToAttribute; + let convertFromAttribute = p.convertFromAttribute; + let hashProperty = p.hashProperty; + let parser = p.parser; + let optionalParser = p.optionalParser; + let printer = p.printer; + let readFromMlirBytecode = p.readFromMlirBytecode; + let writeToMlirBytecode = p.writeToMlirBytecode; +} + +/// An optional property, stored as an std::optional +/// interfaced with as an std::optional.. +/// The syntax is `none` (or empty string if elided) for an absent value or +/// `some<[underlying property]>` when a value is set. +/// +/// As a special exception, if the underlying property has an optional parser and +/// no default value (ex. an integer property), the printer will skip the `some` +/// bracketing and delegate to the optional parser. In that case, the syntax is the +/// syntax of the underlying property, or the keyword `none` in the rare cases that +/// it is needed. This behavior can be disabled by setting `canDelegateParsing` to 0. +class OptionalProperty + : Property<"std::optional<" # p.storageType # ">", "optional " # p.summary> { + + // In the cases where the underlying attribute is plain old data that's passed by + // value, the conversion code is trivial. + defvar hasTrivialStorage = !and(!eq(p.convertFromStorage, "$_storage"), + !eq(p.assignToStorage, "$_storage = $_value"), + !eq(p.storageType, p.interfaceType)); + + defvar delegatesParsing = !and(!empty(p.defaultValue), + !not(!empty(p.optionalParser)), canDelegateParsing); + + let interfaceType = "std::optional<" # p.interfaceType # ">"; + let defaultValue = "std::nullopt"; + + let convertFromStorage = !if(hasTrivialStorage, + p.convertFromStorage, + [{($_storage.has_value() ? std::optional<}] # p.interfaceType # ">{" + # !subst("$_storage", "(*($_storage))", p.convertFromStorage) + # [{} : std::nullopt)}]); + let assignToStorage = !if(hasTrivialStorage, + p.assignToStorage, + [{[&]() { + if (!$_value.has_value()) { + $_storage = std::nullopt; + return; + } + }] # _makePropStorage.ret # [{ + [&](}] # p.storageType # [{& propStorage) { + }] # !subst("$_storage", "propStorage", + !subst("$_value", "(*($_value))", p.assignToStorage)) # [{; + }(presentVal); + $_storage = std::move(presentVal); + }()}]); + + let convertFromAttribute = [{ + auto arrayAttr = ::llvm::dyn_cast<::mlir::ArrayAttr>($_attr); + if (!arrayAttr) + return $_diag() << "expected optional properties to materialize as arrays"; + if (arrayAttr.size() > 1) + return $_diag() << "expected optional properties to become 0- or 1-element arrays"; + if (arrayAttr.empty()) { + $_storage = std::nullopt; + return ::mlir::success(); + } + ::mlir::Attribute presentAttr = arrayAttr[0]; + }] # _makePropStorage.ret # [{ + auto presentRes = [&](Attribute propAttr, }] # p.storageType # [{& propStorage) -> ::mlir::LogicalResult { + }] # !subst("$_storage", "propStorage", + !subst("$_attr", "propAttr", p.convertFromAttribute)) # [{ + }(presentAttr, presentVal); + if (::mlir::failed(presentRes)) + return ::mlir::failure(); + $_storage = std::move(presentVal); + return ::mlir::success(); + }]; + + let convertToAttribute = [{ + if (!$_storage.has_value()) { + return ::mlir::ArrayAttr::get($_ctxt, {}); + } + auto attr = [&]() -> ::mlir::Attribute { + }] # !subst("$_storage", "(*($_storage))", p.convertToAttribute) # [{ + }(); + return ::mlir::ArrayAttr::get($_ctxt, {attr}); + }]; + + defvar delegatedParserBegin = [{ + if (::mlir::succeeded($_parser.parseOptionalKeyword("none"))) { + $_storage = std::nullopt; + return ::mlir::success(); + } + }] #_makePropStorage.ret # [{ + auto delegParseResult = [&](}] # p.storageType # [{& propStorage) -> ::mlir::OptionalParseResult { + }] # !subst("$_storage", "propStorage", p.optionalParser) # [{ + return ::mlir::success(); + }(presentVal); + if (!delegParseResult.has_value()) { + }]; + + defvar delegatedParserEnd = [{ + } + if (delegParseResult.has_value() && ::mlir::failed(*delegParseResult)) + return ::mlir::failure(); + $_storage = std::move(presentVal); + return ::mlir::success(); + }]; + // If we're being explicitly called for our parser, we're expecting to have been + // printede into a context where the default value isn't elided. Therefore, + // not-present from the underlying parser is a failure. + defvar delegatedParser = delegatedParserBegin # [{ + return ::mlir::failure(); + }] # delegatedParserEnd; + defvar delegatedOptionalParser = delegatedParserBegin # [{ + return std::nullopt; + }] # delegatedParserEnd; + + defvar generalParserBegin = [{ + ::llvm::StringRef keyword; + if (::mlir::failed($_parser.parseOptionalKeyword(&keyword, {"none", "some"}))) { + }]; + defvar generalParserEnd = [{ + } + if (keyword == "none") { + $_storage = std::nullopt; + return ::mlir::success(); + } + if (::mlir::failed($_parser.parseLess())) + return ::mlir::failure(); + }] # _makePropStorage.ret # [{ + auto presentParse = [&](}] # p.storageType # [{& propStorage) -> ::mlir::ParseResult { + }] # !subst("$_storage", "propStorage", p.parser) # [{ + return ::mlir::success(); + }(presentVal); + if (presentParse || $_parser.parseGreater()) + return ::mlir::failure(); + $_storage = std::move(presentVal); + }]; + defvar generalParser = generalParserBegin # [{ + return $_parser.emitError($_parser.getCurrentLocation(), "expected 'none' or 'some'"); + }] # generalParserEnd; + defvar generalOptionalParser = generalParserBegin # [{ + return std::nullopt; + }] # generalParserEnd; + + let parser = !if(delegatesParsing, delegatedParser, generalParser); + let optionalParser = !if(delegatesParsing, + delegatedOptionalParser, generalOptionalParser); + + defvar delegatedPrinter = [{ + [&]() { + if (!$_storage.has_value()) { + $_printer << "none"; + return; + } + }] # !subst("$_storage", "(*($_storage))", p.printer) # [{; + }()}]; + defvar generalPrinter = [{ + [&]() { + if (!$_storage.has_value()) { + $_printer << "none"; + return; + } + $_printer << "some<"; + }] # !subst("$_storage", "(*($_storage))", p.printer) # [{; + $_printer << ">"; + }()}]; + let printer = !if(delegatesParsing, delegatedPrinter, generalPrinter); + + let readFromMlirBytecode = [{ + bool isPresent = false; + if (::mlir::failed($_reader.readBool(isPresent))) + return ::mlir::failure(); + if (!isPresent) { + $_storage = std::nullopt; + return ::mlir::success(); + } + }] # _makePropStorage.ret # [{ + auto presentResult = [&](}] # p.storageType # [{& propStorage) -> ::mlir::LogicalResult { + }] # !subst("$_storage", "propStorage", p.readFromMlirBytecode) # [{; + return ::mlir::success(); + }(presentVal); + if (::mlir::failed(presentResult)) + return ::mlir::failure(); + $_storage = std::move(presentVal); + }]; + let writeToMlirBytecode = [{ + $_writer.writeOwnedBool($_storage.has_value()); + if (!$_storage.has_value()) + return; + }] # !subst("$_storage", "(*($_storage))", p.writeToMlirBytecode); + + let hashProperty = !if(!empty(p.hashProperty), p.hashProperty, + [{ ::llvm::hash_value($_storage.has_value() ? std::optional<::llvm::hash_code>{}] # + !subst("$_storage", "(*($_storage))", p.hashProperty) #[{} : std::nullopt) }]); + assert !or(!not(delegatesParsing), !eq(defaultValue, "std::nullopt")), + "For delegated parsing to be used, the default value must be nullopt. " # + "To use a non-trivial default, set the canDelegateParsing argument to 0"; +} #endif // PROPERTIES diff --git a/mlir/include/mlir/TableGen/Operator.h b/mlir/include/mlir/TableGen/Operator.h index cc5853c044e97..768291a3a7267 100644 --- a/mlir/include/mlir/TableGen/Operator.h +++ b/mlir/include/mlir/TableGen/Operator.h @@ -384,7 +384,7 @@ class Operator { SmallVector attributes; /// The properties of the op. - SmallVector properties; + SmallVector properties; /// The arguments of the op (operands and native attributes). SmallVector arguments; diff --git a/mlir/include/mlir/TableGen/Property.h b/mlir/include/mlir/TableGen/Property.h index d0d6f4940c7c0..702e6756e6a95 100644 --- a/mlir/include/mlir/TableGen/Property.h +++ b/mlir/include/mlir/TableGen/Property.h @@ -35,12 +35,20 @@ class Property { public: explicit Property(const llvm::Record *record); explicit Property(const llvm::DefInit *init); - Property(StringRef storageType, StringRef interfaceType, - StringRef convertFromStorageCall, StringRef assignToStorageCall, - StringRef convertToAttributeCall, StringRef convertFromAttributeCall, + Property(StringRef summary, StringRef description, StringRef storageType, + StringRef interfaceType, StringRef convertFromStorageCall, + StringRef assignToStorageCall, StringRef convertToAttributeCall, + StringRef convertFromAttributeCall, StringRef parserCall, + StringRef optionalParserCall, StringRef printerCall, StringRef readFromMlirBytecodeCall, StringRef writeToMlirBytecodeCall, StringRef hashPropertyCall, - StringRef defaultValue); + StringRef defaultValue, StringRef storageTypeValueOverride); + + // Returns the summary (for error messages) of this property's type. + StringRef getSummary() const { return summary; } + + // Returns the description of this property. + StringRef getDescription() const { return description; } // Returns the storage type. StringRef getStorageType() const { return storageType; } @@ -66,6 +74,19 @@ class Property { return convertFromAttributeCall; } + // Returns the method call which parses this property from textual MLIR. + StringRef getParserCall() const { return parserCall; } + + // Returns true if this property has defined an optional parser. + bool hasOptionalParser() const { return !optionalParserCall.empty(); } + + // Returns the method call which optionally parses this property from textual + // MLIR. + StringRef getOptionalParserCall() const { return optionalParserCall; } + + // Returns the method call which prints this property to textual MLIR. + StringRef getPrinterCall() const { return printerCall; } + // Returns the method call which reads this property from // bytecode and assign it to the storage. StringRef getReadFromMlirBytecodeCall() const { @@ -87,6 +108,24 @@ class Property { // Returns the default value for this Property. StringRef getDefaultValue() const { return defaultValue; } + // Returns whether this Property has a default storage-type value that is + // distinct from its default interface-type value. + bool hasStorageTypeValueOverride() const { + return !storageTypeValueOverride.empty(); + } + + StringRef getStorageTypeValueOverride() const { + return storageTypeValueOverride; + } + + // Returns this property's TableGen def-name. + StringRef getPropertyDefName() const; + + // Returns the base-level property that this Property constraint is based on + // or the Property itself otherwise. (Note: there are currently no + // property constraints, this function is added for future-proofing) + Property getBaseProperty() const; + // Returns the TableGen definition this Property was constructed from. const llvm::Record &getDef() const { return *def; } @@ -95,16 +134,22 @@ class Property { const llvm::Record *def; // Elements describing a Property, in general fetched from the record. + StringRef summary; + StringRef description; StringRef storageType; StringRef interfaceType; StringRef convertFromStorageCall; StringRef assignToStorageCall; StringRef convertToAttributeCall; StringRef convertFromAttributeCall; + StringRef parserCall; + StringRef optionalParserCall; + StringRef printerCall; StringRef readFromMlirBytecodeCall; StringRef writeToMlirBytecodeCall; StringRef hashPropertyCall; StringRef defaultValue; + StringRef storageTypeValueOverride; }; // A struct wrapping an op property and its name together diff --git a/mlir/lib/IR/ODSSupport.cpp b/mlir/lib/IR/ODSSupport.cpp index 6e968d62e61c7..d56c75ede9849 100644 --- a/mlir/lib/IR/ODSSupport.cpp +++ b/mlir/lib/IR/ODSSupport.cpp @@ -33,6 +33,50 @@ Attribute mlir::convertToAttribute(MLIRContext *ctx, int64_t storage) { return IntegerAttr::get(IntegerType::get(ctx, 64), storage); } +LogicalResult +mlir::convertFromAttribute(int32_t &storage, Attribute attr, + function_ref emitError) { + auto valueAttr = dyn_cast(attr); + if (!valueAttr) { + emitError() << "expected IntegerAttr for key `value`"; + return failure(); + } + storage = valueAttr.getValue().getSExtValue(); + return success(); +} +Attribute mlir::convertToAttribute(MLIRContext *ctx, int32_t storage) { + return IntegerAttr::get(IntegerType::get(ctx, 32), storage); +} + +LogicalResult +mlir::convertFromAttribute(std::string &storage, Attribute attr, + function_ref emitError) { + auto valueAttr = dyn_cast(attr); + if (!valueAttr) + return emitError() + << "expected string property to come from string attribute"; + storage = valueAttr.getValue().str(); + return success(); +} +Attribute mlir::convertToAttribute(MLIRContext *ctx, + const std::string &storage) { + return StringAttr::get(ctx, storage); +} + +LogicalResult +mlir::convertFromAttribute(bool &storage, Attribute attr, + function_ref emitError) { + auto valueAttr = dyn_cast(attr); + if (!valueAttr) + return emitError() + << "expected string property to come from string attribute"; + storage = valueAttr.getValue(); + return success(); +} +Attribute mlir::convertToAttribute(MLIRContext *ctx, bool storage) { + return BoolAttr::get(ctx, storage); +} + template LogicalResult convertDenseArrayFromAttr(MutableArrayRef storage, Attribute attr, @@ -64,6 +108,33 @@ mlir::convertFromAttribute(MutableArrayRef storage, Attribute attr, "DenseI32ArrayAttr"); } +template +LogicalResult +convertDenseArrayFromAttr(SmallVectorImpl &storage, Attribute attr, + function_ref emitError, + StringRef denseArrayTyStr) { + auto valueAttr = dyn_cast(attr); + if (!valueAttr) { + emitError() << "expected " << denseArrayTyStr << " for key `value`"; + return failure(); + } + storage.resize_for_overwrite(valueAttr.size()); + llvm::copy(valueAttr.asArrayRef(), storage.begin()); + return success(); +} +LogicalResult +mlir::convertFromAttribute(SmallVectorImpl &storage, Attribute attr, + function_ref emitError) { + return convertDenseArrayFromAttr(storage, attr, emitError, + "DenseI64ArrayAttr"); +} +LogicalResult +mlir::convertFromAttribute(SmallVectorImpl &storage, Attribute attr, + function_ref emitError) { + return convertDenseArrayFromAttr(storage, attr, emitError, + "DenseI32ArrayAttr"); +} + Attribute mlir::convertToAttribute(MLIRContext *ctx, ArrayRef storage) { return DenseI64ArrayAttr::get(ctx, storage); diff --git a/mlir/lib/TableGen/Property.cpp b/mlir/lib/TableGen/Property.cpp index e61d2fd2480fd..b86b87df91c60 100644 --- a/mlir/lib/TableGen/Property.cpp +++ b/mlir/lib/TableGen/Property.cpp @@ -33,16 +33,23 @@ static StringRef getValueAsString(const Init *init) { } Property::Property(const Record *def) - : Property(getValueAsString(def->getValueInit("storageType")), - getValueAsString(def->getValueInit("interfaceType")), - getValueAsString(def->getValueInit("convertFromStorage")), - getValueAsString(def->getValueInit("assignToStorage")), - getValueAsString(def->getValueInit("convertToAttribute")), - getValueAsString(def->getValueInit("convertFromAttribute")), - getValueAsString(def->getValueInit("readFromMlirBytecode")), - getValueAsString(def->getValueInit("writeToMlirBytecode")), - getValueAsString(def->getValueInit("hashProperty")), - getValueAsString(def->getValueInit("defaultValue"))) { + : Property( + getValueAsString(def->getValueInit("summary")), + getValueAsString(def->getValueInit("description")), + getValueAsString(def->getValueInit("storageType")), + getValueAsString(def->getValueInit("interfaceType")), + getValueAsString(def->getValueInit("convertFromStorage")), + getValueAsString(def->getValueInit("assignToStorage")), + getValueAsString(def->getValueInit("convertToAttribute")), + getValueAsString(def->getValueInit("convertFromAttribute")), + getValueAsString(def->getValueInit("parser")), + getValueAsString(def->getValueInit("optionalParser")), + getValueAsString(def->getValueInit("printer")), + getValueAsString(def->getValueInit("readFromMlirBytecode")), + getValueAsString(def->getValueInit("writeToMlirBytecode")), + getValueAsString(def->getValueInit("hashProperty")), + getValueAsString(def->getValueInit("defaultValue")), + getValueAsString(def->getValueInit("storageTypeValueOverride"))) { this->def = def; assert((def->isSubClassOf("Property") || def->isSubClassOf("Attr")) && "must be subclass of TableGen 'Property' class"); @@ -50,22 +57,44 @@ Property::Property(const Record *def) Property::Property(const DefInit *init) : Property(init->getDef()) {} -Property::Property(StringRef storageType, StringRef interfaceType, +Property::Property(StringRef summary, StringRef description, + StringRef storageType, StringRef interfaceType, StringRef convertFromStorageCall, StringRef assignToStorageCall, StringRef convertToAttributeCall, - StringRef convertFromAttributeCall, + StringRef convertFromAttributeCall, StringRef parserCall, + StringRef optionalParserCall, StringRef printerCall, StringRef readFromMlirBytecodeCall, StringRef writeToMlirBytecodeCall, - StringRef hashPropertyCall, StringRef defaultValue) - : storageType(storageType), interfaceType(interfaceType), + StringRef hashPropertyCall, StringRef defaultValue, + StringRef storageTypeValueOverride) + : summary(summary), description(description), storageType(storageType), + interfaceType(interfaceType), convertFromStorageCall(convertFromStorageCall), assignToStorageCall(assignToStorageCall), convertToAttributeCall(convertToAttributeCall), convertFromAttributeCall(convertFromAttributeCall), + parserCall(parserCall), optionalParserCall(optionalParserCall), + printerCall(printerCall), readFromMlirBytecodeCall(readFromMlirBytecodeCall), writeToMlirBytecodeCall(writeToMlirBytecodeCall), - hashPropertyCall(hashPropertyCall), defaultValue(defaultValue) { + hashPropertyCall(hashPropertyCall), defaultValue(defaultValue), + storageTypeValueOverride(storageTypeValueOverride) { if (storageType.empty()) storageType = "Property"; } + +StringRef Property::getPropertyDefName() const { + if (def->isAnonymous()) { + return getBaseProperty().def->getName(); + } + return def->getName(); +} + +Property Property::getBaseProperty() const { + if (const auto *defInit = + llvm::dyn_cast(def->getValueInit("baseProperty"))) { + return Property(defInit).getBaseProperty(); + } + return *this; +} diff --git a/mlir/test/IR/properties.mlir b/mlir/test/IR/properties.mlir index 01ea856b03168..418b81dcbb034 100644 --- a/mlir/test/IR/properties.mlir +++ b/mlir/test/IR/properties.mlir @@ -2,10 +2,10 @@ // # RUN: mlir-opt %s -mlir-print-op-generic -split-input-file | mlir-opt -mlir-print-op-generic | FileCheck %s --check-prefix=GENERIC // CHECK: test.with_properties -// CHECK-SAME: <{a = 32 : i64, array = array, b = "foo"}>{{$}} +// CHECK-SAME: a = 32, b = "foo", c = "bar", flag = true, array = [1, 2, 3, 4]{{$}} // GENERIC: "test.with_properties"() -// GENERIC-SAME: <{a = 32 : i64, array = array, b = "foo"}> : () -> () -test.with_properties <{a = 32 : i64, array = array, b = "foo"}> +// GENERIC-SAME: <{a = 32 : i64, array = array, b = "foo", c = "bar", flag = true}> : () -> () +test.with_properties a = 32, b = "foo", c = "bar", flag = true, array = [1, 2, 3, 4] // CHECK: test.with_nice_properties // CHECK-SAME: "foo bar" is -3{{$}} @@ -34,18 +34,48 @@ test.using_property_in_custom [1, 4, 20] // GENERIC-SAME: }> test.using_property_ref_in_custom 1 + 4 = 5 -// CHECK: test.with_default_valued_properties {{$}} +// CHECK: test.with_default_valued_properties na{{$}} // GENERIC: "test.with_default_valued_properties"() -// GENERIC-SAME: <{a = 0 : i32}> -test.with_default_valued_properties <{a = 0 : i32}> +// GENERIC-SAME: <{a = 0 : i32, b = "", c = -1 : i32, unit = false}> : () -> () +test.with_default_valued_properties 0 "" -1 unit_absent + +// CHECK: test.with_default_valued_properties 1 "foo" 0 unit{{$}} +// GENERIC: "test.with_default_valued_properties"() +// GENERIC-SAME: <{a = 1 : i32, b = "foo", c = 0 : i32, unit}> : () -> () +test.with_default_valued_properties 1 "foo" 0 unit // CHECK: test.with_optional_properties -// CHECK-SAME: <{b = 0 : i32}> +// CHECK-SAME: simple = 0 +// GENERIC: "test.with_optional_properties"() +// GENERIC-SAME: <{hasDefault = [], hasUnit = false, longSyntax = [], maybeUnit = [], nested = [], nonTrivialStorage = [], simple = [0]}> : () -> () +test.with_optional_properties simple = 0 + +// CHECK: test.with_optional_properties{{$}} // GENERIC: "test.with_optional_properties"() -// GENERIC-SAME: <{b = 0 : i32}> -test.with_optional_properties <{b = 0 : i32}> +// GENERIC-SAME: simple = [] +test.with_optional_properties -// CHECK: test.with_optional_properties {{$}} +// CHECK: test.with_optional_properties +// CHECK-SAME: anAttr = 0 simple = 1 nonTrivialStorage = "foo" hasDefault = some<0> nested = some<1> longSyntax = some<"bar"> hasUnit maybeUnit = some // GENERIC: "test.with_optional_properties"() -// GENERIC-SAME: : () -> () +// GENERIC-SAME: <{anAttr = 0 : i32, hasDefault = [0], hasUnit, longSyntax = ["bar"], maybeUnit = [unit], nested = {{\[}}[1]], nonTrivialStorage = ["foo"], simple = [1]}> : () -> () test.with_optional_properties + anAttr = 0 + simple = 1 + nonTrivialStorage = "foo" + hasDefault = some<0> + nested = some<1> + longSyntax = some<"bar"> + hasUnit + maybeUnit = some + +// CHECK: test.with_optional_properties +// CHECK-SAME: nested = some +// GENERIC: "test.with_optional_properties"() +// GENERIC-SAME: nested = {{\[}}[]] +test.with_optional_properties nested = some + +// CHECK: test.with_array_properties +// CHECK-SAME: ints = [1, 2] strings = ["a", "b"] nested = {{\[}}[1, 2], [3, 4]] opt = [-1, -2] explicitOptions = [none, 0] explicitUnits = [unit, unit_absent] +// GENERIC: "test.with_array_properties"() +test.with_array_properties ints = [1, 2] strings = ["a", "b"] nested = [[1, 2], [3, 4]] opt = [-1, -2] explicitOptions = [none, 0] explicitUnits = [unit, unit_absent] [] thats_has_default diff --git a/mlir/test/IR/traits.mlir b/mlir/test/IR/traits.mlir index 1e046706379cd..49cfd7e496746 100644 --- a/mlir/test/IR/traits.mlir +++ b/mlir/test/IR/traits.mlir @@ -502,6 +502,25 @@ func.func @succeededOilistTrivial() { // ----- +// CHECK-LABEL: @succeededOilistTrivialProperties +func.func @succeededOilistTrivialProperties() { + // CHECK: test.oilist_with_keywords_only_properties keyword + test.oilist_with_keywords_only_properties keyword + // CHECK: test.oilist_with_keywords_only_properties otherKeyword + test.oilist_with_keywords_only_properties otherKeyword + // CHECK: test.oilist_with_keywords_only_properties keyword otherKeyword + test.oilist_with_keywords_only_properties keyword otherKeyword + // CHECK: test.oilist_with_keywords_only_properties keyword otherKeyword + test.oilist_with_keywords_only_properties otherKeyword keyword + // CHECK: test.oilist_with_keywords_only_properties thirdKeyword + test.oilist_with_keywords_only_properties thirdKeyword + // CHECK: test.oilist_with_keywords_only_properties keyword thirdKeyword + test.oilist_with_keywords_only_properties keyword thirdKeyword + return +} + +// ----- + // CHECK-LABEL: @succeededOilistSimple func.func @succeededOilistSimple(%arg0 : i32, %arg1 : i32, %arg2 : i32) { // CHECK: test.oilist_with_simple_args keyword %{{.*}} : i32 diff --git a/mlir/test/Transforms/test-legalizer.mlir b/mlir/test/Transforms/test-legalizer.mlir index 65c947198e06e..a52c0e636f0cd 100644 --- a/mlir/test/Transforms/test-legalizer.mlir +++ b/mlir/test/Transforms/test-legalizer.mlir @@ -408,10 +408,10 @@ func.func @test_move_op_before_rollback() { // CHECK-LABEL: func @test_properties_rollback() func.func @test_properties_rollback() { - // CHECK: test.with_properties <{a = 32 : i64, + // CHECK: test.with_properties a = 32, // expected-remark @below{{op 'test.with_properties' is not legalizable}} test.with_properties - <{a = 32 : i64, array = array, b = "foo"}> + a = 32, b = "foo", c = "bar", flag = true, array = [1, 2, 3, 4] {modify_inplace} "test.return"() : () -> () } diff --git a/mlir/test/lib/Dialect/Test/TestFormatUtils.cpp b/mlir/test/lib/Dialect/Test/TestFormatUtils.cpp index 6e75dd3932281..9ed1b3a47be36 100644 --- a/mlir/test/lib/Dialect/Test/TestFormatUtils.cpp +++ b/mlir/test/lib/Dialect/Test/TestFormatUtils.cpp @@ -297,11 +297,17 @@ void test::printSwitchCases(OpAsmPrinter &p, Operation *op, // CustomUsingPropertyInCustom //===----------------------------------------------------------------------===// -bool test::parseUsingPropertyInCustom(OpAsmParser &parser, int64_t value[3]) { - return parser.parseLSquare() || parser.parseInteger(value[0]) || - parser.parseComma() || parser.parseInteger(value[1]) || - parser.parseComma() || parser.parseInteger(value[2]) || - parser.parseRSquare(); +bool test::parseUsingPropertyInCustom(OpAsmParser &parser, + SmallVector &value) { + auto elemParser = [&]() { + int64_t v = 0; + if (failed(parser.parseInteger(v))) + return failure(); + value.push_back(v); + return success(); + }; + return failed(parser.parseCommaSeparatedList(OpAsmParser::Delimiter::Square, + elemParser)); } void test::printUsingPropertyInCustom(OpAsmPrinter &printer, Operation *op, diff --git a/mlir/test/lib/Dialect/Test/TestFormatUtils.h b/mlir/test/lib/Dialect/Test/TestFormatUtils.h index 7e9cd834278e3..6d4df7d82ffa5 100644 --- a/mlir/test/lib/Dialect/Test/TestFormatUtils.h +++ b/mlir/test/lib/Dialect/Test/TestFormatUtils.h @@ -160,7 +160,8 @@ void printSwitchCases(mlir::OpAsmPrinter &p, mlir::Operation *op, // CustomUsingPropertyInCustom //===----------------------------------------------------------------------===// -bool parseUsingPropertyInCustom(mlir::OpAsmParser &parser, int64_t value[3]); +bool parseUsingPropertyInCustom(mlir::OpAsmParser &parser, + llvm::SmallVector &value); void printUsingPropertyInCustom(mlir::OpAsmPrinter &printer, mlir::Operation *op, diff --git a/mlir/test/lib/Dialect/Test/TestOps.td b/mlir/test/lib/Dialect/Test/TestOps.td index 9450764fcb1d5..2d97a02b8076a 100644 --- a/mlir/test/lib/Dialect/Test/TestOps.td +++ b/mlir/test/lib/Dialect/Test/TestOps.td @@ -2947,11 +2947,18 @@ def TestVersionedOpC : TEST_Op<"versionedC"> { // Op with a properties struct defined inline. def TestOpWithProperties : TEST_Op<"with_properties"> { - let assemblyFormat = "prop-dict attr-dict"; + let assemblyFormat = [{ + `a` `=` $a `,` + `b` `=` $b `,` + `c` `=` $c `,` + `flag` `=` $flag `,` + `array` `=` $array attr-dict}]; let arguments = (ins - IntProperty<"int64_t">:$a, + I64Property:$a, StrAttr:$b, // Attributes can directly be used here. - ArrayProperty<"int64_t", 4>:$array // example of an array + StringProperty:$c, + BoolProperty:$flag, + IntArrayProperty<"int64_t">:$array // example of an array ); } @@ -2974,7 +2981,7 @@ def TestOpWithPropertiesAndInferredType // Demonstrate how to wrap an existing C++ class named MyPropStruct. def MyStructProperty : Property<"MyPropStruct"> { - let convertToAttribute = "$_storage.asAttribute($_ctxt)"; + let convertToAttribute = "return $_storage.asAttribute($_ctxt);"; let convertFromAttribute = "return MyPropStruct::setFromAttr($_storage, $_attr, $_diag);"; let hashProperty = "$_storage.hash();"; } @@ -2988,14 +2995,14 @@ def TestOpWithWrappedProperties : TEST_Op<"with_wrapped_properties"> { def TestOpUsingPropertyInCustom : TEST_Op<"using_property_in_custom"> { let assemblyFormat = "custom($prop) attr-dict"; - let arguments = (ins ArrayProperty<"int64_t", 3>:$prop); + let arguments = (ins IntArrayProperty<"int64_t">:$prop); } def TestOpUsingPropertyInCustomAndOther : TEST_Op<"using_property_in_custom_and_other"> { let assemblyFormat = "custom($prop) prop-dict attr-dict"; let arguments = (ins - ArrayProperty<"int64_t", 3>:$prop, + IntArrayProperty<"int64_t">:$prop, IntProperty<"int64_t">:$other ); } @@ -3021,7 +3028,7 @@ def TestOpUsingIntPropertyWithWorseBytecode def PropertiesWithCustomPrint : Property<"PropertiesWithCustomPrint"> { let convertToAttribute = [{ - getPropertiesAsAttribute($_ctxt, $_storage) + return getPropertiesAsAttribute($_ctxt, $_storage); }]; let convertFromAttribute = [{ return setPropertiesFromAttribute($_storage, $_attr, $_diag); @@ -3085,7 +3092,7 @@ def TestOpWithNiceProperties : TEST_Op<"with_nice_properties"> { def VersionedProperties : Property<"VersionedProperties"> { let convertToAttribute = [{ - getPropertiesAsAttribute($_ctxt, $_storage) + return getPropertiesAsAttribute($_ctxt, $_storage); }]; let convertFromAttribute = [{ return setPropertiesFromAttribute($_storage, $_attr, $_diag); @@ -3131,13 +3138,65 @@ def TestOpWithVersionedProperties : TEST_Op<"with_versioned_properties"> { } def TestOpWithDefaultValuedProperties : TEST_Op<"with_default_valued_properties"> { - let assemblyFormat = "prop-dict attr-dict"; - let arguments = (ins DefaultValuedAttr:$a); + let assemblyFormat = [{ + ($a^) : (`na`)? + ($b^)? + ($c^)? + ($unit^)? + attr-dict + }]; + let arguments = (ins DefaultValuedAttr:$a, + DefaultValuedProperty:$b, + DefaultValuedProperty, "-1">:$c, + UnitProperty:$unit); } def TestOpWithOptionalProperties : TEST_Op<"with_optional_properties"> { - let assemblyFormat = "prop-dict attr-dict"; - let arguments = (ins OptionalAttr:$a, OptionalAttr:$b); + let assemblyFormat = [{ + (`anAttr` `=` $anAttr^)? + (`simple` `=` $simple^)? + (`nonTrivialStorage` `=` $nonTrivialStorage^)? + (`hasDefault` `=` $hasDefault^)? + (`nested` `=` $nested^)? + (`longSyntax` `=` $longSyntax^)? + (`hasUnit` $hasUnit^)? + (`maybeUnit` `=` $maybeUnit^)? + attr-dict + }]; + let arguments = (ins + OptionalAttr:$anAttr, + OptionalProperty:$simple, + OptionalProperty:$nonTrivialStorage, + // Confirm that properties with default values now default to nullopt and have + // the long syntax. + OptionalProperty>:$hasDefault, + OptionalProperty>:$nested, + OptionalProperty:$longSyntax, + UnitProperty:$hasUnit, + OptionalProperty:$maybeUnit); +} + +def TestOpWithArrayProperties : TEST_Op<"with_array_properties"> { + let assemblyFormat = [{ + `ints` `=` $ints + `strings` `=` $strings + `nested` `=` $nested + `opt` `=` $opt + `explicitOptions` `=` $explicitOptions + `explicitUnits` `=` $explicitUnits + ($hasDefault^ `thats_has_default`)? + attr-dict + }]; + let arguments = (ins + ArrayProperty:$ints, + ArrayProperty:$strings, + ArrayProperty>:$nested, + OptionalProperty>:$opt, + ArrayProperty>:$explicitOptions, + ArrayProperty:$explicitUnits, + DefaultValuedProperty, + "::llvm::ArrayRef{}", "::llvm::SmallVector{}">:$hasDefault + ); } //===----------------------------------------------------------------------===// diff --git a/mlir/test/lib/Dialect/Test/TestOpsSyntax.td b/mlir/test/lib/Dialect/Test/TestOpsSyntax.td index 3129085058fd9..795b9da955632 100644 --- a/mlir/test/lib/Dialect/Test/TestOpsSyntax.td +++ b/mlir/test/lib/Dialect/Test/TestOpsSyntax.td @@ -86,6 +86,17 @@ def OIListTrivial : TEST_Op<"oilist_with_keywords_only"> { }]; } +// Ops related to OIList primitive +def OIListTrivialProperties : TEST_Op<"oilist_with_keywords_only_properties"> { + let arguments = (ins UnitProperty:$keyword, UnitProperty:$otherKeyword, + UnitProperty:$diffNameUnitPropertyKeyword); + let assemblyFormat = [{ + oilist( `keyword` $keyword + | `otherKeyword` $otherKeyword + | `thirdKeyword` $diffNameUnitPropertyKeyword) attr-dict + }]; +} + def OIListSimple : TEST_Op<"oilist_with_simple_args", [AttrSizedOperandSegments]> { let arguments = (ins Optional:$arg0, Optional:$arg1, @@ -392,6 +403,17 @@ def FormatOptionalUnitAttrNoElide let assemblyFormat = "($is_optional^)? attr-dict"; } +def FormatOptionalUnitProperty : TEST_Op<"format_optional_unit_property"> { + let arguments = (ins UnitProperty:$is_optional); + let assemblyFormat = "(`is_optional` $is_optional^)? attr-dict"; +} + +def FormatOptionalUnitPropertyNoElide + : TEST_Op<"format_optional_unit_property_no_elide"> { + let arguments = (ins UnitProperty:$is_optional); + let assemblyFormat = "($is_optional^)? attr-dict"; +} + def FormatOptionalEnumAttr : TEST_Op<"format_optional_enum_attr"> { let arguments = (ins OptionalAttr:$attr); let assemblyFormat = "($attr^)? attr-dict"; diff --git a/mlir/test/mlir-tblgen/op-format.mlir b/mlir/test/mlir-tblgen/op-format.mlir index 46d272649caed..03288ae8bd3d7 100644 --- a/mlir/test/mlir-tblgen/op-format.mlir +++ b/mlir/test/mlir-tblgen/op-format.mlir @@ -195,6 +195,16 @@ test.format_optional_unit_attribute // CHECK: test.format_optional_unit_attribute_no_elide unit test.format_optional_unit_attribute_no_elide unit +// CHECK: test.format_optional_unit_property is_optional +test.format_optional_unit_property is_optional + +// CHECK: test.format_optional_unit_property +// CHECK-NOT: is_optional +test.format_optional_unit_property + +// CHECK: test.format_optional_unit_property_no_elide unit +test.format_optional_unit_property_no_elide unit + // CHECK: test.format_optional_enum_attr case5 test.format_optional_enum_attr case5 diff --git a/mlir/test/mlir-tblgen/op-format.td b/mlir/test/mlir-tblgen/op-format.td index 4a19ffb3dfcc6..8af4341952f04 100644 --- a/mlir/test/mlir-tblgen/op-format.td +++ b/mlir/test/mlir-tblgen/op-format.td @@ -73,7 +73,7 @@ def OptionalGroupA : TestFormat_Op<[{ // CHECK-NEXT: result.addAttribute("a", parser.getBuilder().getUnitAttr()) // CHECK: parser.parseKeyword("bar") // CHECK-LABEL: OptionalGroupB::print -// CHECK: if (!getAAttr()) +// CHECK: if (!(getAAttr() && getAAttr() != ((false) ? ::mlir::OpBuilder((*this)->getContext()).getUnitAttr() : nullptr))) // CHECK-NEXT: odsPrinter << ' ' << "foo" // CHECK-NEXT: else // CHECK-NEXT: odsPrinter << ' ' << "bar" @@ -84,7 +84,7 @@ def OptionalGroupB : TestFormat_Op<[{ // Optional group anchored on a default-valued attribute: // CHECK-LABEL: OptionalGroupC::parse -// CHECK: if (getAAttr() && getAAttr() != ::mlir::OpBuilder((*this)->getContext()).getStringAttr("default")) { +// CHECK: if (getAAttr() != ::mlir::OpBuilder((*this)->getContext()).getStringAttr("default")) { // CHECK-NEXT: odsPrinter << ' '; // CHECK-NEXT: odsPrinter.printAttributeWithoutType(getAAttr()); // CHECK-NEXT: } diff --git a/mlir/test/mlir-tblgen/op-properties.td b/mlir/test/mlir-tblgen/op-properties.td index 7b0ee6b2a1bd8..918583c9f4ed4 100644 --- a/mlir/test/mlir-tblgen/op-properties.td +++ b/mlir/test/mlir-tblgen/op-properties.td @@ -1,8 +1,10 @@ -// RUN: mlir-tblgen -gen-op-decls -I %S/../../include %s | FileCheck %s +// RUN: mlir-tblgen -gen-op-decls -I %S/../../include %s | FileCheck %s --check-prefix=DECL +// RUN: mlir-tblgen -gen-op-defs -I %S/../../include %s | FileCheck %s --check-prefix=DEFS include "mlir/IR/AttrTypeBase.td" include "mlir/IR/EnumAttr.td" include "mlir/IR/OpBase.td" +include "mlir/IR/Properties.td" def Test_Dialect : Dialect { let name = "test"; @@ -15,7 +17,115 @@ def OpWithAttr : NS_Op<"op_with_attr">{ let arguments = (ins AnyAttr:$attr, OptionalAttr:$optional); } -// CHECK: void setAttrAttr(::mlir::Attribute attr) -// CHECK-NEXT: getProperties().attr = attr -// CHECK: void setOptionalAttr(::mlir::Attribute attr) -// CHECK-NEXT: getProperties().optional = attr +// Test required and optional properties +// --- + +def DefaultI64Array : IntArrayProperty<"int64_t"> { + let defaultValue = "::llvm::ArrayRef{}"; + let storageTypeValueOverride = "::llvm::SmallVector{}"; +} + +def OpWithProps : NS_Op<"op_with_props"> { + let arguments = (ins + BoolProperty:$flag, + StringProperty:$string, + ArrayProperty:$strings, + DefaultValuedProperty:$default_int, + OptionalProperty:$optional, + DefaultI64Array:$intArray + ); +} + +/// Check that optional arguments to builders only go at the end. +def OpWithSomeOptionalProperties : NS_Op<"op_with_some_optional_props"> { + let arguments = (ins + OptionalProperty:$mustSpecify, + I64Property:$required, + OptionalProperty:$canOmit, + DefaultValuedProperty:$canOmit2 + ); +} + +/// Check that the ambiguous attribute protection correctly stops optional properties +/// from getting default argument values in builders. +def OpWithOptionalPropsAndAttrs : + NS_Op<"with_some_optional_props_and_atts"> { + let arguments = (ins + OptionalProperty:$mustSpecify, + OptionalAttr:$ambiguous, + OptionalAttr:$canOmit, + OptionalProperty:$canOmitProp + ); +} + +// DECL: void setAttrAttr(::mlir::Attribute attr) +// DECL-NEXT: getProperties().attr = attr +// DECL: void setOptionalAttr(::mlir::Attribute attr) +// DECL-NEXT: getProperties().optional = attr + +// ----- + +// DECL-LABEL: class OpWithOptionalPropsAndAttrs : +// DECL: static void build( +// DECL-SAME: ::mlir::OpBuilder &odsBuilder, +// DECL-SAME: ::mlir::OperationState &odsState, +// DECL-SAME: /*optional*/std::optional mustSpecify, +// DECL-SAME: /*optional*/::mlir::BoolAttr ambiguous, +// DECL-SAME: /*optional*/::mlir::IntegerAttr canOmit, +// DECL-SAME: /*optional*/std::optional canOmitProp = std::nullopt); + +// ----- + +// COM: Ensure the struct is set up how we expect +// DECL-LABEL: class OpWithPropsGenericAdaptorBase +// DECL: using flagTy = bool; +// DECL-NEXT: flagTy flag; +// DECL-NEXT: bool getFlag() +// DECL-NEXT: propStorage = this->flag +// DECL-NEXT: return propStorage; +// DECL: void setFlag(bool propValue) +// DECL-NEXT: propStorage = this->flag; +// DECL-NEXT: propStorage = propValue; +// DECL: using stringTy = std::string; +// DECL: llvm::StringRef getString() +// DECL: auto &propStorage = this->string; +// DECL-NEXT: return ::llvm::StringRef{propStorage}; +// DECL: using stringsTy = ::llvm::SmallVector +// DECL: ::llvm::ArrayRef getStrings() +// DECL: using default_intTy = int32_t; +// DECL: default_intTy default_int = 0; +// DECL: intArrayTy intArray = ::llvm::SmallVector{}; +// DECL: ::llvm::ArrayRef getIntArray() +// DECL: return ::llvm::ArrayRef{propStorage} +// DECL: void setIntArray(::llvm::ArrayRef propValue) +// DECL: propStorage.assign +// DECL-LABEL: class OpWithProps : +// DECL: setString(::llvm::StringRef newString) +// DECL-NEXT: getProperties().setString(newString) + +// DECL: static void build( +// DECL-SAME: ::mlir::OpBuilder &odsBuilder, +// DECL-SAME: ::mlir::OperationState &odsState, +// DECL-SAME: bool flag, +// DECL-SAME: ::llvm::StringRef string, +// DECL-SAME: ::llvm::ArrayRef strings, +// DECL-SAME: /*optional*/int32_t default_int = 0, +// DECL-SAME: /*optional*/std::optional optional = std::nullopt, +// DECL-SAME: /*optional*/::llvm::ArrayRef intArray = ::llvm::ArrayRef{}); + +// DEFS-LABEL: OpWithProps::computePropertiesHash +// DEFS: hash_intArray +// DEFS-NEXT: return ::llvm::hash_value(::llvm::ArrayRef{propStorage}) +// DEFS: ::llvm::hash_value(prop.optional) +// DEFS: hash_intArray(prop.intArray) + +// ----- + +// DECL-LABEL: class OpWithSomeOptionalProperties : +// DECL: static void build( +// DECL-SAME: ::mlir::OpBuilder &odsBuilder, +// DECL-SAME: ::mlir::OperationState &odsState, +// DECL-SAME: /*optional*/std::optional mustSpecify, +// DECL-SAME: int64_t required, +// DECL-SAME: /*optional*/std::optional<::llvm::StringRef> canOmit = std::nullopt, +// DECL-SAME: /*optional*/int64_t canOmit2 = -1); diff --git a/mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp b/mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp index 0fc750c7bbc88..a2ceefb34db45 100644 --- a/mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp +++ b/mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp @@ -155,6 +155,36 @@ static const char *const valueRangeReturnCode = R"( std::next({0}, valueRange.first + valueRange.second)}; )"; +/// Parse operand/result segment_size property. +/// {0}: Number of elements in the segment array +static const char *const parseTextualSegmentSizeFormat = R"( + size_t i = 0; + auto parseElem = [&]() -> ::mlir::ParseResult { + if (i >= {0}) + return $_parser.emitError($_parser.getCurrentLocation(), + "expected `]` after {0} segment sizes"); + if (failed($_parser.parseInteger($_storage[i]))) + return ::mlir::failure(); + i += 1; + return ::mlir::success(); + }; + if (failed($_parser.parseCommaSeparatedList( + ::mlir::AsmParser::Delimeter::Square, parseElem))) + return failure(); + if (i < {0}) + return $_parser.emitError($_parser.getCurrentLocation(), + "expected {0} segment sizes, found only ") << i; + return success(); +)"; + +static const char *const printTextualSegmentSize = R"( + [&]() { + $_printer << '['; + ::llvm::interleaveComma($_storage, $_printer); + $_printer << ']'; + }() +)"; + /// Read operand/result segment_size from bytecode. static const char *const readBytecodeSegmentSizeNative = R"( if ($_reader.getBytecodeVersion() >= /*kNativePropertiesODSSegmentSize=*/6) @@ -422,8 +452,10 @@ class OpOrAdaptorHelper { // Property std::optional operandSegmentsSize; std::string operandSegmentsSizeStorage; + std::string operandSegmentsSizeParser; std::optional resultSegmentsSize; std::string resultSegmentsSizeStorage; + std::string resultSegmentsSizeParser; // Indices to store the position in the emission order of the operand/result // segment sizes attribute if emitted as part of the properties for legacy @@ -448,31 +480,40 @@ void OpOrAdaptorHelper::computeAttrMetadata() { {namedAttr.name, AttributeMetadata{namedAttr.name, !isOptional, attr}}); } - auto makeProperty = [&](StringRef storageType) { + auto makeProperty = [&](StringRef storageType, StringRef parserCall) { return Property( + /*summary=*/"", + /*description=*/"", /*storageType=*/storageType, /*interfaceType=*/"::llvm::ArrayRef", /*convertFromStorageCall=*/"$_storage", /*assignToStorageCall=*/ "::llvm::copy($_value, $_storage.begin())", /*convertToAttributeCall=*/ - "::mlir::DenseI32ArrayAttr::get($_ctxt, $_storage)", + "return ::mlir::DenseI32ArrayAttr::get($_ctxt, $_storage);", /*convertFromAttributeCall=*/ "return convertFromAttribute($_storage, $_attr, $_diag);", + /*parserCall=*/parserCall, + /*optionalParserCall=*/"", + /*printerCall=*/printTextualSegmentSize, /*readFromMlirBytecodeCall=*/readBytecodeSegmentSizeNative, /*writeToMlirBytecodeCall=*/writeBytecodeSegmentSizeNative, /*hashPropertyCall=*/ "::llvm::hash_combine_range(std::begin($_storage), " "std::end($_storage));", - /*StringRef defaultValue=*/""); + /*StringRef defaultValue=*/"", + /*storageTypeValueOverride=*/""); }; // Include key attributes from several traits as implicitly registered. if (op.getTrait("::mlir::OpTrait::AttrSizedOperandSegments")) { if (op.getDialect().usePropertiesForAttributes()) { operandSegmentsSizeStorage = llvm::formatv("std::array", op.getNumOperands()); - operandSegmentsSize = {"operandSegmentSizes", - makeProperty(operandSegmentsSizeStorage)}; + operandSegmentsSizeParser = + llvm::formatv(parseTextualSegmentSizeFormat, op.getNumOperands()); + operandSegmentsSize = { + "operandSegmentSizes", + makeProperty(operandSegmentsSizeStorage, operandSegmentsSizeParser)}; } else { attrMetadata.insert( {operandSegmentAttrName, AttributeMetadata{operandSegmentAttrName, @@ -484,8 +525,11 @@ void OpOrAdaptorHelper::computeAttrMetadata() { if (op.getDialect().usePropertiesForAttributes()) { resultSegmentsSizeStorage = llvm::formatv("std::array", op.getNumResults()); - resultSegmentsSize = {"resultSegmentSizes", - makeProperty(resultSegmentsSizeStorage)}; + resultSegmentsSizeParser = + llvm::formatv(parseTextualSegmentSizeFormat, op.getNumResults()); + resultSegmentsSize = { + "resultSegmentSizes", + makeProperty(resultSegmentsSizeStorage, resultSegmentsSizeParser)}; } else { attrMetadata.insert( {resultSegmentAttrName, @@ -572,6 +616,12 @@ class OpEmitter { void genPropertiesSupportForBytecode(ArrayRef attrOrProperties); + // Generates getters for the properties. + void genPropGetters(); + + // Generates seters for the properties. + void genPropSetters(); + // Generates getters for the attributes. void genAttrGetters(); @@ -1041,6 +1091,8 @@ OpEmitter::OpEmitter(const Operator &op, genNamedRegionGetters(); genNamedSuccessorGetters(); genPropertiesSupport(); + genPropGetters(); + genPropSetters(); genAttrGetters(); genAttrSetters(); genOptionalAttrRemovers(); @@ -1198,6 +1250,16 @@ void OpEmitter::genAttrNameGetters() { } } +// Emit the getter for a named property. +// It is templated to be shared between the Op and the adaptor class. +template +static void emitPropGetter(OpClassOrAdaptor &opClass, const Operator &op, + StringRef name, const Property &prop) { + auto *method = opClass.addInlineMethod(prop.getInterfaceType(), name); + ERROR_IF_PRUNED(method, name, op); + method->body() << formatv(" return getProperties().{0}();", name); +} + // Emit the getter for an attribute with the return type specified. // It is templated to be shared between the Op and the adaptor class. template @@ -1313,7 +1375,7 @@ void OpEmitter::genPropertiesSupport() { )decl"; const char *propFromAttrFmt = R"decl( auto setFromAttr = [] (auto &propStorage, ::mlir::Attribute propAttr, - ::llvm::function_ref<::mlir::InFlightDiagnostic()> emitError) {{ + ::llvm::function_ref<::mlir::InFlightDiagnostic()> emitError) -> ::mlir::LogicalResult {{ {0} }; {2}; @@ -1358,7 +1420,10 @@ void OpEmitter::genPropertiesSupport() { .addSubst("_storage", propertyStorage) .addSubst("_diag", propertyDiag)), name, getAttr); - if (prop.hasDefaultValue()) { + if (prop.hasStorageTypeValueOverride()) { + setPropMethod << formatv(attrGetDefaultFmt, name, + prop.getStorageTypeValueOverride()); + } else if (prop.hasDefaultValue()) { setPropMethod << formatv(attrGetDefaultFmt, name, prop.getDefaultValue()); } else { @@ -1409,8 +1474,10 @@ void OpEmitter::genPropertiesSupport() { const char *propToAttrFmt = R"decl( { const auto &propStorage = prop.{0}; - attrs.push_back(odsBuilder.getNamedAttr("{0}", - {1})); + auto attr = [&]() -> ::mlir::Attribute {{ + {1} + }(); + attrs.push_back(odsBuilder.getNamedAttr("{0}", attr)); } )decl"; for (const auto &attrOrProp : attrOrProperties) { @@ -1458,9 +1525,12 @@ void OpEmitter::genPropertiesSupport() { StringRef name = namedProperty->name; auto &prop = namedProperty->prop; FmtContext fctx; - hashMethod << formatv(propHashFmt, name, - tgfmt(prop.getHashPropertyCall(), - &fctx.addSubst("_storage", propertyStorage))); + if (!prop.getHashPropertyCall().empty()) { + hashMethod << formatv( + propHashFmt, name, + tgfmt(prop.getHashPropertyCall(), + &fctx.addSubst("_storage", propertyStorage))); + } } } hashMethod << " return llvm::hash_combine("; @@ -1468,8 +1538,13 @@ void OpEmitter::genPropertiesSupport() { attrOrProperties, hashMethod, [&](const ConstArgument &attrOrProp) { if (const auto *namedProperty = llvm::dyn_cast_if_present(attrOrProp)) { - hashMethod << "\n hash_" << namedProperty->name << "(prop." - << namedProperty->name << ")"; + if (!namedProperty->prop.getHashPropertyCall().empty()) { + hashMethod << "\n hash_" << namedProperty->name << "(prop." + << namedProperty->name << ")"; + } else { + hashMethod << "\n ::llvm::hash_value(prop." + << namedProperty->name << ")"; + } return; } const auto *namedAttr = @@ -1524,8 +1599,9 @@ void OpEmitter::genPropertiesSupport() { "\"{0}\") return ", resultSegmentAttrName); } - getInherentAttrMethod << tgfmt(prop.getConvertToAttributeCall(), &fctx) - << ";\n"; + getInherentAttrMethod << "[&]() -> ::mlir::Attribute { " + << tgfmt(prop.getConvertToAttributeCall(), &fctx) + << " }();\n"; if (name == operandSegmentAttrName) { setInherentAttrMethod @@ -1549,13 +1625,15 @@ void OpEmitter::genPropertiesSupport() { )decl", name); if (name == operandSegmentAttrName) { - populateInherentAttrsMethod - << formatv(" attrs.append(\"{0}\", {1});\n", operandSegmentAttrName, - tgfmt(prop.getConvertToAttributeCall(), &fctx)); + populateInherentAttrsMethod << formatv( + " attrs.append(\"{0}\", [&]() -> ::mlir::Attribute { {1} }());\n", + operandSegmentAttrName, + tgfmt(prop.getConvertToAttributeCall(), &fctx)); } else { - populateInherentAttrsMethod - << formatv(" attrs.append(\"{0}\", {1});\n", resultSegmentAttrName, - tgfmt(prop.getConvertToAttributeCall(), &fctx)); + populateInherentAttrsMethod << formatv( + " attrs.append(\"{0}\", [&]() -> ::mlir::Attribute { {1} }());\n", + resultSegmentAttrName, + tgfmt(prop.getConvertToAttributeCall(), &fctx)); } } getInherentAttrMethod << " return std::nullopt;\n"; @@ -1701,6 +1779,26 @@ void OpEmitter::genPropertiesSupportForBytecode( readPropertiesMethod << " return ::mlir::success();"; } +void OpEmitter::genPropGetters() { + for (const NamedProperty &prop : op.getProperties()) { + std::string name = op.getGetterName(prop.name); + emitPropGetter(opClass, op, name, prop.prop); + } +} + +void OpEmitter::genPropSetters() { + for (const NamedProperty &prop : op.getProperties()) { + std::string name = op.getSetterName(prop.name); + std::string argName = "new" + convertToCamelFromSnakeCase( + prop.name, /*capitalizeFirst=*/true); + auto *method = opClass.addInlineMethod( + "void", name, MethodParameter(prop.prop.getInterfaceType(), argName)); + if (!method) + return; + method->body() << formatv(" getProperties().{0}({1});", name, argName); + } +} + void OpEmitter::genAttrGetters() { FmtContext fctx; fctx.withBuilder("::mlir::Builder((*this)->getContext())"); @@ -2957,6 +3055,12 @@ void OpEmitter::buildParamList(SmallVectorImpl ¶mList, } // Add parameters for all arguments (operands and attributes). + // Track "attr-like" (property and attribute) optional values separate from + // attributes themselves so that the disambiguation code can look at the first + // attribute specifically when determining where to trim the optional-value + // list to avoid ambiguity while preserving the ability of all-property ops to + // use default parameters. + int defaultValuedAttrLikeStartIndex = op.getNumArgs(); int defaultValuedAttrStartIndex = op.getNumArgs(); // Successors and variadic regions go at the end of the parameter list, so no // default arguments are possible. @@ -2967,6 +3071,15 @@ void OpEmitter::buildParamList(SmallVectorImpl ¶mList, for (int i = op.getNumArgs() - 1; i >= 0; --i) { auto *namedAttr = llvm::dyn_cast_if_present(op.getArg(i)); + auto *namedProperty = + llvm::dyn_cast_if_present(op.getArg(i)); + if (namedProperty) { + Property prop = namedProperty->prop; + if (!prop.hasDefaultValue()) + break; + defaultValuedAttrLikeStartIndex = i; + continue; + } if (!namedAttr) break; @@ -2986,6 +3099,7 @@ void OpEmitter::buildParamList(SmallVectorImpl ¶mList, if (retType == "::llvm::APInt" || retType == "::llvm::APFloat") break; + defaultValuedAttrLikeStartIndex = i; defaultValuedAttrStartIndex = i; } } @@ -3001,8 +3115,10 @@ void OpEmitter::buildParamList(SmallVectorImpl ¶mList, if ((attrParamKind == AttrParamKind::WrappedAttr && canUseUnwrappedRawValue(attr)) || (attrParamKind == AttrParamKind::UnwrappedValue && - !canUseUnwrappedRawValue(attr))) + !canUseUnwrappedRawValue(attr))) { ++defaultValuedAttrStartIndex; + defaultValuedAttrLikeStartIndex = defaultValuedAttrStartIndex; + } } /// Collect any inferred attributes. @@ -3029,8 +3145,16 @@ void OpEmitter::buildParamList(SmallVectorImpl ¶mList, operand->isOptional()); continue; } - if (llvm::isa_and_present(arg)) { - // TODO + if (auto *propArg = llvm::dyn_cast_if_present(arg)) { + const Property &prop = propArg->prop; + StringRef type = prop.getInterfaceType(); + std::string defaultValue; + if (prop.hasDefaultValue() && i >= defaultValuedAttrLikeStartIndex) { + defaultValue = prop.getDefaultValue(); + } + bool isOptional = prop.hasDefaultValue(); + paramList.emplace_back(type, propArg->name, StringRef(defaultValue), + isOptional); continue; } const NamedAttribute &namedAttr = *arg.get(); @@ -3157,6 +3281,15 @@ void OpEmitter::genCodeForAddingArgAndRegionForBuilder( } } + // Push all properties to the result. + for (const auto &namedProp : op.getProperties()) { + // Use the setter from the Properties struct since the conversion from the + // interface type (used in the builder argument) to the storage type (used + // in the state) is not necessarily trivial. + std::string setterName = op.getSetterName(namedProp.name); + body << formatv(" {0}.getOrAddProperties().{1}({2});\n", + builderOpState, setterName, namedProp.name); + } // Push all attributes to the result. for (const auto &namedAttr : op.getAttributes()) { auto &attr = namedAttr.attr; @@ -3996,17 +4129,19 @@ OpOperandAdaptorEmitter::OpOperandAdaptorEmitter( // Generate the data member using the storage type. os << " using " << name << "Ty = " << prop.getStorageType() << ";\n" << " " << name << "Ty " << name; - if (prop.hasDefaultValue()) + if (prop.hasStorageTypeValueOverride()) + os << " = " << prop.getStorageTypeValueOverride(); + else if (prop.hasDefaultValue()) os << " = " << prop.getDefaultValue(); comparatorOs << " rhs." << name << " == this->" << name << " &&\n"; // Emit accessors using the interface type. const char *accessorFmt = R"decl(; - {0} get{1}() { + {0} get{1}() const { auto &propStorage = this->{2}; return {3}; } - void set{1}(const {0} &propValue) { + void set{1}({0} propValue) { auto &propStorage = this->{2}; {4}; } @@ -4274,6 +4409,11 @@ OpOperandAdaptorEmitter::OpOperandAdaptorEmitter( ERROR_IF_PRUNED(m, "Adaptor::getAttributes", op); m->body() << " return odsAttrs;"; } + for (auto &namedProp : op.getProperties()) { + std::string name = op.getGetterName(namedProp.name); + emitPropGetter(genericAdaptorBase, op, name, namedProp.prop); + } + for (auto &namedAttr : op.getAttributes()) { const auto &name = namedAttr.name; const auto &attr = namedAttr.attr; diff --git a/mlir/tools/mlir-tblgen/OpFormatGen.cpp b/mlir/tools/mlir-tblgen/OpFormatGen.cpp index a97d8760842a9..27ad79a5c1efe 100644 --- a/mlir/tools/mlir-tblgen/OpFormatGen.cpp +++ b/mlir/tools/mlir-tblgen/OpFormatGen.cpp @@ -45,7 +45,7 @@ class OpVariableElement : public VariableElementBase { OpVariableElement(const VarT *var) : var(var) {} /// Get the variable. - const VarT *getVar() { return var; } + const VarT *getVar() const { return var; } protected: /// The op variable, e.g. a type or attribute constraint. @@ -64,11 +64,6 @@ struct AttributeVariable return attrType ? attrType->getBuilderCall() : std::nullopt; } - /// Return if this attribute refers to a UnitAttr. - bool isUnitAttr() const { - return var->attr.getBaseAttr().getAttrDefName() == "UnitAttr"; - } - /// Indicate if this attribute is printed "qualified" (that is it is /// prefixed with the `#dialect.mnemonic`). bool shouldBeQualified() { return shouldBeQualifiedFlag; } @@ -98,6 +93,42 @@ using SuccessorVariable = /// This class represents a variable that refers to a property argument. using PropertyVariable = OpVariableElement; + +/// LLVM RTTI helper for attribute-like variables, that is, attributes or +/// properties. This allows for common handling of attributes and properties in +/// parts of the code that are oblivious to whether something is stored as an +/// attribute or a property. +struct AttributeLikeVariable : public VariableElement { + enum { AttributeLike = 1 << 0 }; + + static bool classof(const VariableElement *ve) { + return ve->getKind() == VariableElement::Attribute || + ve->getKind() == VariableElement::Property; + } + + static bool classof(const FormatElement *fe) { + return isa(fe) && classof(cast(fe)); + } + + /// Returns true if the variable is a UnitAttr or a UnitProperty. + bool isUnit() const { + if (const auto *attr = dyn_cast(this)) + return attr->getVar()->attr.getBaseAttr().getAttrDefName() == "UnitAttr"; + if (const auto *prop = dyn_cast(this)) { + return prop->getVar()->prop.getBaseProperty().getPropertyDefName() == + "UnitProperty"; + } + llvm_unreachable("Type that wasn't listed in classof()"); + } + + StringRef getName() const { + if (const auto *attr = dyn_cast(this)) + return attr->getVar()->name; + if (const auto *prop = dyn_cast(this)) + return prop->getVar()->name; + llvm_unreachable("Type that wasn't listed in classof()"); + } +}; } // namespace //===----------------------------------------------------------------------===// @@ -214,11 +245,11 @@ class OIListElement : public DirectiveElementBase { /// If the parsing element is a single UnitAttr element, then it returns the /// attribute variable. Otherwise, returns nullptr. - AttributeVariable * - getUnitAttrParsingElement(ArrayRef pelement) { + AttributeLikeVariable * + getUnitVariableParsingElement(ArrayRef pelement) { if (pelement.size() == 1) { - auto *attrElem = dyn_cast(pelement[0]); - if (attrElem && attrElem->isUnitAttr()) + auto *attrElem = dyn_cast(pelement[0]); + if (attrElem && attrElem->isUnit()) return attrElem; } return nullptr; @@ -488,6 +519,36 @@ const char *const enumAttrParserCode = R"( } )"; +/// The code snippet used to generate a parser call for a property. +/// {0}: The name of the property +/// {1}: The C++ class name of the operation +/// {2}: The property's parser code with appropriate substitutions performed +/// {3}: The description of the expected property for the error message. +const char *const propertyParserCode = R"( + auto {0}PropLoc = parser.getCurrentLocation(); + auto {0}PropParseResult = [&](auto& propStorage) -> ::mlir::ParseResult {{ + {2} + return ::mlir::success(); + }(result.getOrAddProperties<{1}::Properties>().{0}); + if (failed({0}PropParseResult)) {{ + return parser.emitError({0}PropLoc, "invalid value for property {0}, expected {3}"); + } +)"; + +/// The code snippet used to generate a parser call for a property. +/// {0}: The name of the property +/// {1}: The C++ class name of the operation +/// {2}: The property's parser code with appropriate substitutions performed +const char *const optionalPropertyParserCode = R"( + auto {0}PropParseResult = [&](auto& propStorage) -> ::mlir::OptionalParseResult {{ + {2} + return ::mlir::success(); + }(result.getOrAddProperties<{1}::Properties>().{0}); + if ({0}PropParseResult.has_value() && failed(*{0}PropParseResult)) {{ + return ::mlir::failure(); + } +)"; + /// The code snippet used to generate a parser call for an operand. /// /// {0}: The name of the operand. @@ -796,9 +857,9 @@ static void genElementParserStorage(FormatElement *element, const Operator &op, // If the anchor is a unit attribute, it won't be parsed directly so elide // it. - auto *anchor = dyn_cast(optional->getAnchor()); + auto *anchor = dyn_cast(optional->getAnchor()); FormatElement *elidedAnchorElement = nullptr; - if (anchor && anchor != elements.front() && anchor->isUnitAttr()) + if (anchor && anchor != elements.front() && anchor->isUnit()) elidedAnchorElement = anchor; for (FormatElement *childElement : elements) if (childElement != elidedAnchorElement) @@ -808,7 +869,7 @@ static void genElementParserStorage(FormatElement *element, const Operator &op, } else if (auto *oilist = dyn_cast(element)) { for (ArrayRef pelement : oilist->getParsingElements()) { - if (!oilist->getUnitAttrParsingElement(pelement)) + if (!oilist->getUnitVariableParsingElement(pelement)) for (FormatElement *element : pelement) genElementParserStorage(element, op, body); } @@ -1049,7 +1110,6 @@ static void genCustomDirectiveParser(CustomDirective *dir, MethodBody &body, body << llvm::formatv(" result.addAttribute(\"{0}\", {0}Attr);\n", var->name); } - } else if (auto *operand = dyn_cast(param)) { const NamedTypeConstraint *var = operand->getVar(); if (var->isOptional()) { @@ -1137,6 +1197,29 @@ static void genEnumAttrParser(const NamedAttribute *var, MethodBody &body, validCaseKeywordsStr, errorMessage, attrAssignment); } +// Generate the parser for a property. +static void genPropertyParser(PropertyVariable *propVar, MethodBody &body, + StringRef opCppClassName, + bool requireParse = true) { + StringRef name = propVar->getVar()->name; + const Property &prop = propVar->getVar()->prop; + bool parseOptionally = + prop.hasDefaultValue() && !requireParse && prop.hasOptionalParser(); + FmtContext fmtContext; + fmtContext.addSubst("_parser", "parser"); + fmtContext.addSubst("_ctxt", "parser.getContext()"); + fmtContext.addSubst("_storage", "propStorage"); + + if (parseOptionally) { + body << formatv(optionalPropertyParserCode, name, opCppClassName, + tgfmt(prop.getOptionalParserCall(), &fmtContext)); + } else { + body << formatv(propertyParserCode, name, opCppClassName, + tgfmt(prop.getParserCall(), &fmtContext), + prop.getSummary()); + } +} + // Generate the parser for an attribute. static void genAttrParser(AttributeVariable *attr, MethodBody &body, FmtContext &attrTypeCtx, bool parseAsOptional, @@ -1213,14 +1296,16 @@ if (!dict) { } )decl"; - // TODO: properties might be optional as well. + // {0}: fromAttribute call + // {1}: property name + // {2}: isRequired const char *propFromAttrFmt = R"decl( auto setFromAttr = [] (auto &propStorage, ::mlir::Attribute propAttr, - ::llvm::function_ref<::mlir::InFlightDiagnostic()> emitError) {{ + ::llvm::function_ref<::mlir::InFlightDiagnostic()> emitError) -> ::mlir::LogicalResult {{ {0}; }; auto attr = dict.get("{1}"); -if (!attr) {{ +if (!attr && {2}) {{ emitError() << "expected key entry for {1} in DictionaryAttr to set " "Properties."; return ::mlir::failure(); @@ -1238,13 +1323,14 @@ if (::mlir::failed(setFromAttr(prop.{1}, attr, emitError))) StringRef name = namedProperty.name; const Property &prop = namedProperty.prop; + bool isRequired = !prop.hasDefaultValue(); FmtContext fctx; body << formatv(propFromAttrFmt, tgfmt(prop.getConvertFromAttributeCall(), &fctx.addSubst("_attr", "propAttr") .addSubst("_storage", "propStorage") .addSubst("_diag", "emitError")), - name); + name, isRequired); } // Generate the setter for any attribute not parsed elsewhere. @@ -1331,20 +1417,24 @@ void OperationFormat::genElementParser(FormatElement *element, MethodBody &body, // If the anchor is a unit attribute, we don't need to print it. When // parsing, we will add this attribute if this group is present. FormatElement *elidedAnchorElement = nullptr; - auto *anchorAttr = dyn_cast(optional->getAnchor()); - if (anchorAttr && anchorAttr != firstElement && - anchorAttr->isUnitAttr()) { - elidedAnchorElement = anchorAttr; + auto *anchorVar = dyn_cast(optional->getAnchor()); + if (anchorVar && anchorVar != firstElement && anchorVar->isUnit()) { + elidedAnchorElement = anchorVar; if (!thenGroup == optional->isInverted()) { - // Add the anchor unit attribute to the operation state. - if (useProperties) { + // Add the anchor unit attribute or property to the operation state + // or set the property to true. + if (isa(anchorVar)) { + body << formatv( + " result.getOrAddProperties<{1}::Properties>().{0} = true;", + anchorVar->getName(), opCppClassName); + } else if (useProperties) { body << formatv( " result.getOrAddProperties<{1}::Properties>().{0} = " "parser.getBuilder().getUnitAttr();", - anchorAttr->getVar()->name, opCppClassName); + anchorVar->getName(), opCppClassName); } else { - body << " result.addAttribute(\"" << anchorAttr->getVar()->name + body << " result.addAttribute(\"" << anchorVar->getName() << "\", parser.getBuilder().getUnitAttr());\n"; } } @@ -1368,6 +1458,12 @@ void OperationFormat::genElementParser(FormatElement *element, MethodBody &body, genAttrParser(attrVar, body, attrTypeCtx, /*parseAsOptional=*/true, useProperties, opCppClassName); body << " if (" << attrVar->getVar()->name << "Attr) {\n"; + } else if (auto *propVar = dyn_cast(firstElement)) { + genPropertyParser(propVar, body, opCppClassName, /*requireParse=*/false); + body << llvm::formatv("if ({0}PropParseResult.has_value() && " + "succeeded(*{0}PropParseResult)) ", + propVar->getVar()->name) + << " {\n"; } else if (auto *literal = dyn_cast(firstElement)) { body << " if (::mlir::succeeded(parser.parseOptional"; genLiteralParser(literal->getSpelling(), body); @@ -1430,15 +1526,19 @@ void OperationFormat::genElementParser(FormatElement *element, MethodBody &body, body << ")) {\n"; StringRef lelementName = lelement->getSpelling(); body << formatv(oilistParserCode, lelementName); - if (AttributeVariable *unitAttrElem = - oilist->getUnitAttrParsingElement(pelement)) { - if (useProperties) { + if (AttributeLikeVariable *unitVarElem = + oilist->getUnitVariableParsingElement(pelement)) { + if (isa(unitVarElem)) { + body << formatv( + " result.getOrAddProperties<{1}::Properties>().{0} = true;", + unitVarElem->getName(), opCppClassName); + } else if (useProperties) { body << formatv( " result.getOrAddProperties<{1}::Properties>().{0} = " "parser.getBuilder().getUnitAttr();", - unitAttrElem->getVar()->name, opCppClassName); + unitVarElem->getName(), opCppClassName); } else { - body << " result.addAttribute(\"" << unitAttrElem->getVar()->name + body << " result.addAttribute(\"" << unitVarElem->getName() << "\", UnitAttr::get(parser.getContext()));\n"; } } else { @@ -1468,6 +1568,8 @@ void OperationFormat::genElementParser(FormatElement *element, MethodBody &body, (genCtx == GenContext::Normal && attr->getVar()->attr.isOptional()); genAttrParser(attr, body, attrTypeCtx, parseAsOptional, useProperties, opCppClassName); + } else if (auto *prop = dyn_cast(element)) { + genPropertyParser(prop, body, opCppClassName); } else if (auto *operand = dyn_cast(element)) { ArgumentLengthKind lengthKind = getArgumentLengthKind(operand->getVar()); @@ -1876,6 +1978,38 @@ const char *enumAttrBeginPrinterCode = R"( auto caseValueStr = {1}(caseValue); )"; +/// Generate a check that an optional or default-valued attribute or property +/// has a non-default value. For these purposes, the default value of an +/// optional attribute is its presence, even if the attribute itself has a +/// default value. +static void genNonDefaultValueCheck(MethodBody &body, const Operator &op, + AttributeVariable &attrElement) { + Attribute attr = attrElement.getVar()->attr; + std::string getter = op.getGetterName(attrElement.getVar()->name); + bool optionalAndDefault = attr.isOptional() && attr.hasDefaultValue(); + if (optionalAndDefault) + body << "("; + if (attr.isOptional()) + body << getter << "Attr()"; + if (optionalAndDefault) + body << " && "; + if (attr.hasDefaultValue()) { + FmtContext fctx; + fctx.withBuilder("::mlir::OpBuilder((*this)->getContext())"); + body << getter << "Attr() != " + << tgfmt(attr.getConstBuilderTemplate(), &fctx, + attr.getDefaultValue()); + } + if (optionalAndDefault) + body << ")"; +} + +static void genNonDefaultValueCheck(MethodBody &body, const Operator &op, + PropertyVariable &propElement) { + body << op.getGetterName(propElement.getVar()->name) + << "() != " << propElement.getVar()->prop.getDefaultValue(); +} + /// Generate the printer for the 'prop-dict' directive. static void genPropDictPrinter(OperationFormat &fmt, Operator &op, MethodBody &body) { @@ -1904,6 +2038,15 @@ static void genPropDictPrinter(OperationFormat &fmt, Operator &op, body << " }\n"; } } + // Similarly, elide default-valued properties. + for (const NamedProperty &prop : op.getProperties()) { + if (prop.prop.hasDefaultValue()) { + body << " if (" << op.getGetterName(prop.name) + << "() == " << prop.prop.getDefaultValue() << ") {"; + body << " elidedProps.push_back(\"" << prop.name << "\");\n"; + body << " }\n"; + } + } body << " _odsPrinter << \" \";\n" << " printProperties(this->getContext(), _odsPrinter, " @@ -2031,7 +2174,6 @@ static void genCustomDirectiveParameterPrinter(FormatElement *element, } else if (auto *property = dyn_cast(element)) { FmtContext ctx; - ctx.addSubst("_ctxt", "getContext()"); const NamedProperty *namedProperty = property->getVar(); ctx.addSubst("_storage", "getProperties()." + namedProperty->name); body << tgfmt(namedProperty->prop.getConvertFromStorageCall(), &ctx); @@ -2154,16 +2296,6 @@ static void genEnumAttrPrinter(const NamedAttribute *var, const Operator &op, " }\n"; } -/// Generate a check that a DefaultValuedAttr has a value that is non-default. -static void genNonDefaultValueCheck(MethodBody &body, const Operator &op, - AttributeVariable &attrElement) { - FmtContext fctx; - Attribute attr = attrElement.getVar()->attr; - fctx.withBuilder("::mlir::OpBuilder((*this)->getContext())"); - body << " && " << op.getGetterName(attrElement.getVar()->name) << "Attr() != " - << tgfmt(attr.getConstBuilderTemplate(), &fctx, attr.getDefaultValue()); -} - /// Generate the check for the anchor of an optional group. static void genOptionalGroupPrinterAnchor(FormatElement *anchor, const Operator &op, @@ -2190,17 +2322,12 @@ static void genOptionalGroupPrinterAnchor(FormatElement *anchor, genOptionalGroupPrinterAnchor(element->getInputs(), op, body); }) .Case([&](AttributeVariable *element) { - Attribute attr = element->getVar()->attr; - body << op.getGetterName(element->getVar()->name) << "Attr()"; - if (attr.isOptional()) - return; // done - if (attr.hasDefaultValue()) { - // Consider a default-valued attribute as present if it's not the - // default value. - genNonDefaultValueCheck(body, op, *element); - return; - } - llvm_unreachable("attribute must be optional or default-valued"); + // Consider a default-valued attribute as present if it's not the + // default value and an optional one present if it is set. + genNonDefaultValueCheck(body, op, *element); + }) + .Case([&](PropertyVariable *element) { + genNonDefaultValueCheck(body, op, *element); }) .Case([&](CustomDirective *ele) { body << '('; @@ -2276,10 +2403,10 @@ void OperationFormat::genElementPrinter(FormatElement *element, ArrayRef thenElements = optional->getThenElements(); ArrayRef elseElements = optional->getElseElements(); FormatElement *elidedAnchorElement = nullptr; - auto *anchorAttr = dyn_cast(anchor); + auto *anchorAttr = dyn_cast(anchor); if (anchorAttr && anchorAttr != thenElements.front() && (elseElements.empty() || anchorAttr != elseElements.front()) && - anchorAttr->isUnitAttr()) { + anchorAttr->isUnit()) { elidedAnchorElement = anchorAttr; } auto genElementPrinters = [&](ArrayRef elements) { @@ -2319,13 +2446,13 @@ void OperationFormat::genElementPrinter(FormatElement *element, for (VariableElement *var : vars) { TypeSwitch(var) .Case([&](AttributeVariable *attrEle) { - body << " || (" << op.getGetterName(attrEle->getVar()->name) - << "Attr()"; - Attribute attr = attrEle->getVar()->attr; - if (attr.hasDefaultValue()) { - // Don't print default-valued attributes. - genNonDefaultValueCheck(body, op, *attrEle); - } + body << " || ("; + genNonDefaultValueCheck(body, op, *attrEle); + body << ")"; + }) + .Case([&](PropertyVariable *propEle) { + body << " || ("; + genNonDefaultValueCheck(body, op, *propEle); body << ")"; }) .Case([&](OperandVariable *ele) { @@ -2352,7 +2479,7 @@ void OperationFormat::genElementPrinter(FormatElement *element, body << ") {\n"; genLiteralPrinter(lelement->getSpelling(), body, shouldEmitSpace, lastWasPunctuation); - if (oilist->getUnitAttrParsingElement(pelement) == nullptr) { + if (oilist->getUnitVariableParsingElement(pelement) == nullptr) { for (FormatElement *element : pelement) genElementPrinter(element, body, op, shouldEmitSpace, lastWasPunctuation); @@ -2369,7 +2496,7 @@ void OperationFormat::genElementPrinter(FormatElement *element, return; } - // Emit the attribute dictionary. + // Emit the property dictionary. if (isa(element)) { genPropDictPrinter(*this, op, body); lastWasPunctuation = false; @@ -2408,6 +2535,13 @@ void OperationFormat::genElementPrinter(FormatElement *element, else body << "_odsPrinter.printStrippedAttrOrType(" << op.getGetterName(var->name) << "Attr());\n"; + } else if (auto *property = dyn_cast(element)) { + const NamedProperty *var = property->getVar(); + FmtContext fmtContext; + fmtContext.addSubst("_printer", "_odsPrinter"); + fmtContext.addSubst("_ctxt", "getContext()"); + fmtContext.addSubst("_storage", "getProperties()." + var->name); + body << tgfmt(var->prop.getPrinterCall(), &fmtContext) << ";\n"; } else if (auto *operand = dyn_cast(element)) { if (operand->getVar()->isVariadicOfVariadic()) { body << " ::llvm::interleaveComma(" @@ -2737,6 +2871,10 @@ static bool isOptionallyParsed(FormatElement *el) { Attribute attr = attrVar->getVar()->attr; return attr.isOptional() || attr.hasDefaultValue(); } + if (auto *propVar = dyn_cast(el)) { + const Property &prop = propVar->getVar()->prop; + return prop.hasDefaultValue() && prop.hasOptionalParser(); + } if (auto *operandVar = dyn_cast(el)) { const NamedTypeConstraint *operand = operandVar->getVar(); return operand->isOptional() || operand->isVariadic() || @@ -3141,10 +3279,9 @@ OpFormatParser::parseVariableImpl(SMLoc loc, StringRef name, Context ctx) { } if (const NamedProperty *property = findArg(op.getProperties(), name)) { - if (ctx != CustomDirectiveContext && ctx != RefDirectiveContext) + if (ctx == TypeDirectiveContext) return emitError( - loc, "properties currently only supported in `custom` directive"); - + loc, "properties cannot be used as children to a `type` directive"); if (ctx == RefDirectiveContext) { if (!seenProperties.count(property)) return emitError(loc, "property '" + name + @@ -3428,6 +3565,15 @@ LogicalResult OpFormatParser::verifyOIListParsingElement(FormatElement *element, "an oilist parsing group"); return success(); }) + // Only optional properties can be within an oilist parsing group. + .Case([&](PropertyVariable *propEle) { + if (!propEle->getVar()->prop.hasDefaultValue()) + return emitError( + loc, + "only default-valued or optional properties can be used in " + "an olist parsing group"); + return success(); + }) // Only optional-like(i.e. variadic) operands can be within an // oilist parsing group. .Case([&](OperandVariable *ele) { @@ -3557,6 +3703,16 @@ LogicalResult OpFormatParser::verifyOptionalGroupElement(SMLoc loc, "can be used to anchor an optional group"); return success(); }) + // All properties can be within the optional group, but only optional + // properties can be the anchor. + .Case([&](PropertyVariable *propEle) { + Property prop = propEle->getVar()->prop; + if (isAnchor && !(prop.hasDefaultValue() && prop.hasOptionalParser())) + return emitError(loc, "only properties with default values " + "that can be optionally parsed " + "can be used to anchor an optional group"); + return success(); + }) // Only optional-like(i.e. variadic) operands can be within an optional // group. .Case([&](OperandVariable *ele) {