diff --git a/mlir/docs/LangRef.md b/mlir/docs/LangRef.md --- a/mlir/docs/LangRef.md +++ b/mlir/docs/LangRef.md @@ -290,12 +290,14 @@ operation ::= op-result-list? (generic-operation | custom-operation) trailing-location? generic-operation ::= string-literal `(` value-use-list? `)` successor-list? - region-list? dictionary-attribute? `:` function-type + dictionary-properties? region-list? dictionary-attribute? + `:` function-type custom-operation ::= bare-id custom-operation-format op-result-list ::= op-result (`,` op-result)* `=` op-result ::= value-id (`:` integer-literal) successor-list ::= `[` successor (`,` successor)* `]` successor ::= caret-id (`:` block-arg-list)? +dictionary-propertes ::= `<` dictionary-attribute `>` region-list ::= `(` region (`,` region)* `)` dictionary-attribute ::= `{` (attribute-entry (`,` attribute-entry)*)? `}` trailing-location ::= (`loc` `(` location `)`)? @@ -312,9 +314,10 @@ The internal representation of an operation is simple: an operation is identified by a unique string (e.g. `dim`, `tf.Conv2d`, `x86.repmovsb`, `ppc.eieio`, etc), can return zero or more results, take zero or more operands, -has a dictionary of [attributes](#attributes), has zero or more successors, and -zero or more enclosed [regions](#regions). The generic printing form includes -all these elements literally, with a function type to indicate the types of the +has storage for [properties](#properties), has a dictionary of +[attributes](#attributes), has zero or more successors, and zero or more +enclosed [regions](#regions). The generic printing form includes all these +elements literally, with a function type to indicate the types of the results and operands. Example: @@ -328,8 +331,11 @@ %foo, %bar = "foo_div"() : () -> (f32, i32) // Invoke a TensorFlow function called tf.scramble with two inputs -// and an attribute "fruit". -%2 = "tf.scramble"(%result#0, %bar) {fruit = "banana"} : (f32, i32) -> f32 +// and an attribute "fruit" stored in properties. +%2 = "tf.scramble"(%result#0, %bar) <{fruit = "banana"}> : (f32, i32) -> f32 + +// Invoke an operation with some discardable attributes +%foo, %bar = "foo_div"() {some_attr = "value", other_attr = 42 : i64} : () -> (f32, i32) ``` In addition to the basic syntax above, dialects may register known operations. @@ -733,6 +739,15 @@ directly usable by any other dialect in MLIR. These types cover a range from primitive integer and floating-point types, function types, and more. +## Properties + +Properties are extra data member stored directly on an Operation class. They +provide a way to store [inherent attributes](#attributes) and other arbitrary +data. The semantics of the data is specific to a given operation, and may be +exposed through [Interfaces](Interfaces.md) accessors and other methods. +Properties can always be serialized to Attribute in order to be printed +generically. + ## Attributes Syntax: @@ -751,9 +766,10 @@ arrays, dictionaries, strings, etc.). Additionally, dialects can define their own [dialect attribute values](#dialect-attribute-values). -The top-level attribute dictionary attached to an operation has special -semantics. The attribute entries are considered to be of two different kinds -based on whether their dictionary key has a dialect prefix: +For dialects which haven't adopted properties yet, the top-level attribute +dictionary attached to an operation has special semantics. The attribute +entries are considered to be of two different kinds based on whether their +dictionary key has a dialect prefix: - *inherent attributes* are inherent to the definition of an operation's semantics. The operation itself is expected to verify the consistency of @@ -771,6 +787,10 @@ but only the top-level dictionary attribute attached to the operation is subject to the classification above. +When properties are adopted, only discardable attributes are stored in the +top-level dictionary, while inherent attributes are stored in the properties +storage. + ### Attribute Value Aliases ``` diff --git a/mlir/include/mlir/Conversion/LLVMCommon/Pattern.h b/mlir/include/mlir/Conversion/LLVMCommon/Pattern.h --- a/mlir/include/mlir/Conversion/LLVMCommon/Pattern.h +++ b/mlir/include/mlir/Conversion/LLVMCommon/Pattern.h @@ -145,6 +145,11 @@ /// Wrappers around the RewritePattern methods that pass the derived op type. void rewrite(Operation *op, ArrayRef operands, ConversionPatternRewriter &rewriter) const final { + if constexpr (SourceOp::hasProperties()) + rewrite(cast(op), + OpAdaptor(operands, op->getAttrDictionary(), + cast(op).getProperties()), + rewriter); rewrite(cast(op), OpAdaptor(operands, op->getAttrDictionary()), rewriter); } @@ -154,6 +159,11 @@ LogicalResult matchAndRewrite(Operation *op, ArrayRef operands, ConversionPatternRewriter &rewriter) const final { + if constexpr (SourceOp::hasProperties()) + return matchAndRewrite(cast(op), + OpAdaptor(operands, op->getAttrDictionary(), + cast(op).getProperties()), + rewriter); return matchAndRewrite(cast(op), OpAdaptor(operands, op->getAttrDictionary()), rewriter); diff --git a/mlir/include/mlir/IR/DialectBase.td b/mlir/include/mlir/IR/DialectBase.td --- a/mlir/include/mlir/IR/DialectBase.td +++ b/mlir/include/mlir/IR/DialectBase.td @@ -103,6 +103,9 @@ // If this dialect can be extended at runtime with new operations or types. bit isExtensible = 0; + + // Whether inherent Attributes defined in ODS will be stored as Properties. + bit usePropertiesForAttributes = 0; } #endif // DIALECTBASE_TD diff --git a/mlir/include/mlir/IR/ExtensibleDialect.h b/mlir/include/mlir/IR/ExtensibleDialect.h --- a/mlir/include/mlir/IR/ExtensibleDialect.h +++ b/mlir/include/mlir/IR/ExtensibleDialect.h @@ -26,6 +26,8 @@ #include "mlir/IR/OpDefinition.h" #include "mlir/Support/TypeID.h" #include "llvm/ADT/StringMap.h" +#include "llvm/Support/ErrorHandling.h" +#include namespace mlir { class AsmParser; @@ -462,6 +464,35 @@ return verifyRegionFn(op); } + /// Implementation for properties (unsupported right now here). + std::optional getInherentAttr(Operation *op, + StringRef name) final { + llvm::report_fatal_error("Unsupported getInherentAttr on Dynamic dialects"); + } + void setInherentAttr(Operation *op, StringAttr name, Attribute value) final { + llvm::report_fatal_error("Unsupported setInherentAttr on Dynamic dialects"); + } + void populateInherentAttrs(Operation *op, NamedAttrList &attrs) final {} + LogicalResult + verifyInherentAttrs(OperationName opName, NamedAttrList &attributes, + function_ref getDiag) final { + return success(); + } + int getOpPropertyByteSize() final { return 0; } + void initProperties(OperationName opName, OpaqueProperties storage, + OpaqueProperties init) final {} + void deleteProperties(OpaqueProperties prop) final {} + void populateDefaultProperties(OperationName opName, + OpaqueProperties properties) final {} + + LogicalResult setPropertiesFromAttr(Operation *op, Attribute attr, + InFlightDiagnostic *diag) final { + return failure(); + } + Attribute getPropertiesAsAttr(Operation *op) final { return {}; } + void copyProperties(OpaqueProperties lhs, OpaqueProperties rhs) final {} + llvm::hash_code hashProperties(OpaqueProperties prop) final { return {}; } + private: DynamicOpDefinition( StringRef name, ExtensibleDialect *dialect, diff --git a/mlir/include/mlir/IR/ODSSupport.h b/mlir/include/mlir/IR/ODSSupport.h new file mode 100644 --- /dev/null +++ b/mlir/include/mlir/IR/ODSSupport.h @@ -0,0 +1,45 @@ +//===- ODSSupport.h ---------------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file defines a number of support method for ODS generated code. +// +//===----------------------------------------------------------------------===// + +#ifndef MLIR_IR_ODSSUPPORT_H +#define MLIR_IR_ODSSUPPORT_H + +#include "mlir/IR/Attributes.h" + +namespace mlir { + +//===----------------------------------------------------------------------===// +// Support for properties +//===----------------------------------------------------------------------===// + +/// Convert an IntegerAttr attribute to an int64_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(int64_t &storage, Attribute attr, + InFlightDiagnostic *diag); + +/// Convert the provided int64_t to an IntegerAttr attribute. +Attribute convertToAttribute(MLIRContext *ctx, int64_t 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 +/// the optional diagnostic is provided an error message is also emitted. +LogicalResult convertFromAttribute(MutableArrayRef storage, + Attribute attr, InFlightDiagnostic *diag); + +/// 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 diff --git a/mlir/include/mlir/IR/OpBase.td b/mlir/include/mlir/IR/OpBase.td --- a/mlir/include/mlir/IR/OpBase.td +++ b/mlir/include/mlir/IR/OpBase.td @@ -179,6 +179,69 @@ string cppClassName = cppClassNameParam; } +// Base class for defining properties. +class Property { + // User-readable one line summary used in error reporting messages. If empty, + // a generic message will be used. + string summary = desc; + // The full description of this property. + string description = ""; + code storageType = storageTypeParam; + code interfaceType = storageTypeParam; + + // The expression to convert from the storage type to the Interface + // type. For example, an enum can be stored as an int but returned as an + // enum class. + // + // Format: + // - `$_storage` will contain the property in the storage type. + // - `$_ctx` will contain an `MLIRContext *`. + code convertFromStorage = "$_storage"; + + // The call expression to build a property storage from the interface type. + // + // Format: + // - `$_storage` will contain the property in the storage type. + // - `$_value` will contain the property in the user interface type. + code assignToStorage = "$_storage = $_value"; + + // The call expression to convert from the storage type to an attribute. + // + // Format: + // - `$_storage` is the storage type value. + // - `$_ctx` is a `MLIRContext *`. + // + // The expression must result in an Attribute. + code convertToAttribute = [{ + convertToAttribute($_ctx, $_storage) + }]; + + // The call expression to convert from an Attribute to the storage type. + // + // Format: + // - `$_storage` is the storage type value. + // - `$_attr` is the attribute. + // - `$_diag` is an optional Diagnostic pointer to emit error. + // + // The expression must return a LogicalResult + code convertFromAttribute = [{ + return convertFromAttribute($_storage, $_attr, $_diag); + }]; + + // The call expression to hash the property. + // + // Format: + // - `$_storage` is the variable to hash. + // + // The expression should define a llvm::hash_code. + code hashProperty = [{ + llvm::hash_value($_storage); + }]; + + // Default value for the property. + string defaultValue = ?; +} + // Subclass for constraints on an attribute. class AttrConstraint : Constraint; @@ -1090,6 +1153,16 @@ class DefaultValuedOptionalStrAttr : DefaultValuedOptionalAttr; +//===----------------------------------------------------------------------===// +// Primitive property kinds + +class ArrayProperty : + Property { + let interfaceType = "::llvm::ArrayRef<" # storageTypeParam # ">"; + let convertFromStorage = "$_storage"; + let assignToStorage = "::llvm::copy($_value, $_storage)"; +} + //===----------------------------------------------------------------------===// // Primitive attribute kinds diff --git a/mlir/include/mlir/IR/OpDefinition.h b/mlir/include/mlir/IR/OpDefinition.h --- a/mlir/include/mlir/IR/OpDefinition.h +++ b/mlir/include/mlir/IR/OpDefinition.h @@ -71,6 +71,21 @@ } // namespace impl +/// Structure used by default as a "marker" when no "Properties" are set on an +/// Operation. +struct EmptyProperties {}; + +/// Traits to detect whether an Operation defined a `Properties` type, otherwise +/// it'll default to `EmptyProperties`. +template +struct PropertiesSelector { + using type = EmptyProperties; +}; +template +struct PropertiesSelector> { + using type = typename Op::Properties; +}; + /// This is the concrete base class that holds the operation pointer and has /// non-generic methods that only depend on State (to avoid having them /// instantiated on template types that don't affect them. @@ -206,6 +221,13 @@ /// in generic form. static void print(Operation *op, OpAsmPrinter &p, StringRef defaultDialect); + /// Parse properties as a Attribute. + static ParseResult genericParseProperties(OpAsmParser &parser, + Attribute &result); + + /// Print the properties as a Attribute. + static void genericPrintProperties(OpAsmPrinter &p, Attribute properties); + /// Print an operation name, eliding the dialect prefix if necessary. static void printOpName(Operation *op, OpAsmPrinter &p, StringRef defaultDialect); @@ -214,6 +236,14 @@ /// so we can cast it away here. explicit OpState(Operation *state) : state(state) {} + /// For all op which don't have properties, we keep a single instance of + /// `EmptyProperties` to be used where a reference to a properties is needed: + /// this allow to bind a pointer to the reference without triggering UB. + static EmptyProperties &getEmptyProperties() { + static EmptyProperties emptyProperties; + return emptyProperties; + } + private: Operation *state; @@ -1471,13 +1501,17 @@ /// Returns true if this given Trait ID matches the IDs of any of the provided /// trait types `Traits`. template