From 94541563dcaa4674d002ecb65b1cddfb2bd5ed22 Mon Sep 17 00:00:00 2001 From: River Riddle Date: Fri, 31 May 2019 09:24:48 -0700 Subject: [PATCH] Abstract the internal storage of the NamedAttributeList into a new attribute, DictionaryAttr. This attribute maintains a sorted list of NamedAttributes. This will allow for operations/functions to maintain sub dictionaries of attributes. The syntax is the same as top level attribute dictionaries: {sub_dictionary: {fn: @someFn, boolAttr: true}} -- PiperOrigin-RevId: 250898950 --- mlir/g3doc/LangRef.md | 13 ++++ mlir/include/mlir/IR/Attributes.h | 65 +++++++++++++---- mlir/include/mlir/IR/Builders.h | 1 + mlir/lib/IR/AsmPrinter.cpp | 9 +++ mlir/lib/IR/AttributeDetail.h | 77 ++++++++++++-------- mlir/lib/IR/Attributes.cpp | 143 ++++++++++++++++++++++++++++---------- mlir/lib/IR/Builders.cpp | 4 ++ mlir/lib/IR/MLIRContext.cpp | 98 +------------------------- mlir/lib/Parser/Parser.cpp | 7 ++ mlir/test/IR/parser.mlir | 7 ++ 10 files changed, 248 insertions(+), 176 deletions(-) diff --git a/mlir/g3doc/LangRef.md b/mlir/g3doc/LangRef.md index 83517c4..14ff81c 100644 --- a/mlir/g3doc/LangRef.md +++ b/mlir/g3doc/LangRef.md @@ -693,6 +693,7 @@ Attributes values are represented by the following forms: attribute-value ::= affine-map-attribute | array-attribute | bool-attribute + | dictionary-attribute | elements-attribute | integer-attribute | integer-set-attribute @@ -735,6 +736,18 @@ bool-attribute ::= bool-literal A boolean attribute is a literal attribute that represents a one-bit boolean value, true or false. +#### Dictionary Attribute + +Syntax: + +``` {.ebnf} +dictionary-attribute ::= `{` (attribute-entry (`,` attribute-entry)*)? `}` +``` + +A dictionary attribute is an attribute that represents a sorted collection of +named attribute values. The elements are sorted by name, and each name must be +unique within the collection. + #### Elements Attributes Syntax: diff --git a/mlir/include/mlir/IR/Attributes.h b/mlir/include/mlir/IR/Attributes.h index 8c2a949..3807637 100644 --- a/mlir/include/mlir/IR/Attributes.h +++ b/mlir/include/mlir/IR/Attributes.h @@ -37,6 +37,7 @@ namespace detail { struct OpaqueAttributeStorage; struct BoolAttributeStorage; +struct DictionaryAttributeStorage; struct IntegerAttributeStorage; struct FloatAttributeStorage; struct StringAttributeStorage; @@ -51,8 +52,6 @@ struct DenseFPElementsAttributeStorage; struct OpaqueElementsAttributeStorage; struct SparseElementsAttributeStorage; -class AttributeListStorage; - } // namespace detail /// Attributes are known-constant values of operations and functions. @@ -144,6 +143,7 @@ enum Kind { Unit = Attribute::FIRST_STANDARD_ATTR, Opaque, Bool, + Dictionary, Integer, Float, String, @@ -310,11 +310,11 @@ public: ArrayRef getValue() const; - size_t size() const { return getValue().size(); } - + /// Support range iteration. using iterator = llvm::ArrayRef::iterator; iterator begin() const { return getValue().begin(); } iterator end() const { return getValue().end(); } + size_t size() const { return getValue().size(); } /// Methods for support type inquiry through isa, cast, and dyn_cast. static bool kindof(unsigned kind) { @@ -322,6 +322,42 @@ public: } }; +/// NamedAttribute is used for dictionary attributes, it holds an identifier for +/// the name and a value for the attribute. The attribute pointer should always +/// be non-null. +using NamedAttribute = std::pair; + +/// Dictionary attribute is an attribute that represents a sorted collection of +/// named attribute values. The elements are sorted by name, and each name must +/// be unique within the collection. +class DictionaryAttr + : public Attribute::AttrBase { +public: + using Base::Base; + using ValueType = ArrayRef; + + static DictionaryAttr get(ArrayRef value, + MLIRContext *context); + + ArrayRef getValue() const; + + /// Return the specified attribute if present, null otherwise. + Attribute get(StringRef name) const; + Attribute get(Identifier name) const; + + /// Support range iteration. + using iterator = llvm::ArrayRef::iterator; + iterator begin() const; + iterator end() const; + size_t size() const; + + /// Methods for supporting type inquiry through isa, cast, and dyn_cast. + static bool kindof(unsigned kind) { + return kind == StandardAttributes::Dictionary; + } +}; + class AffineMapAttr : public Attribute::AttrBase { @@ -757,31 +793,30 @@ inline ::llvm::hash_code hash_value(Attribute arg) { return ::llvm::hash_value(arg.impl); } -/// NamedAttribute is used for named attribute lists, it holds an identifier for -/// the name and a value for the attribute. The attribute pointer should always -/// be non-null. -using NamedAttribute = std::pair; - /// A NamedAttributeList is used to manage a list of named attributes. This /// provides simple interfaces for adding/removing/finding attributes from -/// within a raw AttributeListStorage. +/// within a DictionaryAttr. /// -/// We assume there will be relatively few attributes on a given function +/// We assume there will be relatively few attributes on a given operation /// (maybe a dozen or so, but not hundreds or thousands) so we use linear /// searches for everything. class NamedAttributeList { public: - NamedAttributeList() : attrs(nullptr) {} + NamedAttributeList(DictionaryAttr attrs = nullptr) : attrs(attrs) {} NamedAttributeList(ArrayRef attributes); /// Return all of the attributes on this operation. - ArrayRef getAttrs() const; + ArrayRef getAttrs() const { + return attrs ? attrs.getValue() : llvm::None; + } /// Replace the held attributes with ones provided in 'newAttrs'. void setAttrs(ArrayRef attributes); /// Return the specified attribute if present, null otherwise. - Attribute get(StringRef name) const; + Attribute get(StringRef name) const { + return attrs ? attrs.get(name) : nullptr; + } Attribute get(Identifier name) const; /// If the an attribute exists with the specified name, change it to the new @@ -795,7 +830,7 @@ public: RemoveResult remove(Identifier name); private: - detail::AttributeListStorage *attrs; + DictionaryAttr attrs; }; } // end namespace mlir. diff --git a/mlir/include/mlir/IR/Builders.h b/mlir/include/mlir/IR/Builders.h index e848966..b869dcd 100644 --- a/mlir/include/mlir/IR/Builders.h +++ b/mlir/include/mlir/IR/Builders.h @@ -103,6 +103,7 @@ public: UnitAttr getUnitAttr(); BoolAttr getBoolAttr(bool value); + DictionaryAttr getDictionaryAttr(ArrayRef value); IntegerAttr getIntegerAttr(Type type, int64_t value); IntegerAttr getIntegerAttr(Type type, const APInt &value); FloatAttr getFloatAttr(Type type, double value); diff --git a/mlir/lib/IR/AsmPrinter.cpp b/mlir/lib/IR/AsmPrinter.cpp index 4c056a1..b3e2649 100644 --- a/mlir/lib/IR/AsmPrinter.cpp +++ b/mlir/lib/IR/AsmPrinter.cpp @@ -630,6 +630,15 @@ void ModulePrinter::printAttributeOptionalType(Attribute attr, case StandardAttributes::Bool: os << (attr.cast().getValue() ? "true" : "false"); break; + case StandardAttributes::Dictionary: + os << '{'; + interleaveComma(attr.cast().getValue(), + [&](NamedAttribute attr) { + os << attr.first << ": "; + printAttribute(attr.second); + }); + os << '}'; + break; case StandardAttributes::Integer: { auto intAttr = attr.cast(); // Print all integer attributes as signed unless i1. diff --git a/mlir/lib/IR/AttributeDetail.h b/mlir/lib/IR/AttributeDetail.h index d29c06c..09a7bd7 100644 --- a/mlir/lib/IR/AttributeDetail.h +++ b/mlir/lib/IR/AttributeDetail.h @@ -229,6 +229,53 @@ struct ArrayAttributeStorage : public AttributeStorage { ArrayRef value; }; +/// An attribute representing a dictionary of sorted named attributes. +struct DictionaryAttributeStorage final + : public AttributeStorage, + private llvm::TrailingObjects { + using KeyTy = ArrayRef; + + /// Given a list of NamedAttribute's, canonicalize the list (sorting + /// by name) and return the unique'd result. + static DictionaryAttributeStorage *get(ArrayRef attrs); + + /// Key equality function. + bool operator==(const KeyTy &key) const { return key == getElements(); } + + /// Construct a new storage instance. + static DictionaryAttributeStorage * + construct(AttributeStorageAllocator &allocator, const KeyTy &key) { + auto size = DictionaryAttributeStorage::totalSizeToAlloc( + key.size()); + auto rawMem = allocator.allocate(size, alignof(NamedAttribute)); + + // Initialize the storage and trailing attribute list. + auto result = ::new (rawMem) DictionaryAttributeStorage(key.size()); + std::uninitialized_copy(key.begin(), key.end(), + result->getTrailingObjects()); + return result; + } + + /// Return the elements of this dictionary attribute. + ArrayRef getElements() const { + return {getTrailingObjects(), numElements}; + } + +private: + friend class llvm::TrailingObjects; + + // This is used by the llvm::TrailingObjects base class. + size_t numTrailingObjects(OverloadToken) const { + return numElements; + } + DictionaryAttributeStorage(unsigned numElements) : numElements(numElements) {} + + /// This is the number of attributes. + const unsigned numElements; +}; + // An attribute representing a reference to an affine map. struct AffineMapAttributeStorage : public AttributeStorage { using KeyTy = AffineMap; @@ -403,36 +450,6 @@ struct SparseElementsAttributeStorage : public AttributeStorage { DenseIntElementsAttr indices; DenseElementsAttr values; }; - -/// A raw list of named attributes stored as a trailing array. -class AttributeListStorage final - : private llvm::TrailingObjects { - friend class llvm::TrailingObjects; - -public: - /// Given a list of NamedAttribute's, canonicalize the list (sorting - /// by name) and return the unique'd result. Note that the empty list is - /// represented with a null pointer. - static AttributeListStorage *get(ArrayRef attrs); - - /// Return the element constants for this aggregate constant. These are - /// known to all be constants. - ArrayRef getElements() const { - return {getTrailingObjects(), numElements}; - } - -private: - // This is used by the llvm::TrailingObjects base class. - size_t numTrailingObjects(OverloadToken) const { - return numElements; - } - AttributeListStorage() = delete; - AttributeListStorage(const AttributeListStorage &) = delete; - AttributeListStorage(unsigned numElements) : numElements(numElements) {} - - /// This is the number of attributes. - const unsigned numElements; -}; } // namespace detail } // namespace mlir diff --git a/mlir/lib/IR/Attributes.cpp b/mlir/lib/IR/Attributes.cpp index e20d7b5..225fe41 100644 --- a/mlir/lib/IR/Attributes.cpp +++ b/mlir/lib/IR/Attributes.cpp @@ -104,6 +104,98 @@ BoolAttr BoolAttr::get(bool value, MLIRContext *context) { bool BoolAttr::getValue() const { return getImpl()->value; } //===----------------------------------------------------------------------===// +// DictionaryAttr +//===----------------------------------------------------------------------===// + +/// Perform a three-way comparison between the names of the specified +/// NamedAttributes. +static int compareNamedAttributes(const NamedAttribute *lhs, + const NamedAttribute *rhs) { + return lhs->first.str().compare(rhs->first.str()); +} + +DictionaryAttr DictionaryAttr::get(ArrayRef value, + MLIRContext *context) { + assert(llvm::all_of(value, + [](const NamedAttribute &attr) { return attr.second; }) && + "value cannot have null entries"); + + // We need to sort the element list to canonicalize it, but we also don't want + // to do a ton of work in the super common case where the element list is + // already sorted. + SmallVector storage; + switch (value.size()) { + case 0: + break; + case 1: + // A single element is already sorted. + break; + case 2: + assert(value[0].first != value[1].first && + "DictionaryAttr element names must be unique"); + + // Don't invoke a general sort for two element case. + if (value[0].first.strref() > value[1].first.strref()) { + storage.push_back(value[1]); + storage.push_back(value[0]); + value = storage; + } + break; + default: + // Check to see they are sorted already. + bool isSorted = true; + for (unsigned i = 0, e = value.size() - 1; i != e; ++i) { + if (value[i].first.strref() > value[i + 1].first.strref()) { + isSorted = false; + break; + } + } + // If not, do a general sort. + if (!isSorted) { + storage.append(value.begin(), value.end()); + llvm::array_pod_sort(storage.begin(), storage.end(), + compareNamedAttributes); + value = storage; + } + + // Ensure that the attribute elements are unique. + assert(std::adjacent_find(value.begin(), value.end(), + [](NamedAttribute l, NamedAttribute r) { + return l.first == r.first; + }) == value.end() && + "DictionaryAttr element names must be unique"); + } + + return Base::get(context, StandardAttributes::Dictionary, value); +} + +ArrayRef DictionaryAttr::getValue() const { + return getImpl()->getElements(); +} + +/// Return the specified attribute if present, null otherwise. +Attribute DictionaryAttr::get(StringRef name) const { + for (auto elt : getValue()) + if (elt.first.is(name)) + return elt.second; + return nullptr; +} +Attribute DictionaryAttr::get(Identifier name) const { + for (auto elt : getValue()) + if (elt.first == name) + return elt.second; + return nullptr; +} + +DictionaryAttr::iterator DictionaryAttr::begin() const { + return getValue().begin(); +} +DictionaryAttr::iterator DictionaryAttr::end() const { + return getValue().end(); +} +size_t DictionaryAttr::size() const { return getValue().size(); } + +//===----------------------------------------------------------------------===// // IntegerAttr //===----------------------------------------------------------------------===// @@ -675,10 +767,10 @@ void DenseIntElementsAttr::getValues(SmallVectorImpl &values) const { values.assign(raw_begin(), raw_end()); } -template -static ShapedType mappingHelper( - Fn mapping, Attr& attr, ShapedType inType, Type newElementType, - llvm::SmallVectorImpl& data) { +template +static ShapedType mappingHelper(Fn mapping, Attr &attr, ShapedType inType, + Type newElementType, + llvm::SmallVectorImpl &data) { size_t bitWidth = getDenseElementBitwidth(newElementType); ShapedType newArrayType; @@ -692,7 +784,7 @@ static ShapedType mappingHelper( assert(newArrayType && "Unhandled tensor type"); data.resize(APInt::getNumWords(bitWidth * inType.getNumElements()) * - APInt::APINT_WORD_SIZE); + APInt::APINT_WORD_SIZE); uint64_t elementIdx = 0; for (auto value : attr) { @@ -709,8 +801,8 @@ DenseElementsAttr DenseIntElementsAttr::mapValues( Type newElementType, llvm::function_ref mapping) const { llvm::SmallVector elementData; - auto newArrayType = mappingHelper( - mapping, *this, getType(), newElementType, elementData); + auto newArrayType = + mappingHelper(mapping, *this, getType(), newElementType, elementData); return get(newArrayType, elementData); } @@ -746,8 +838,8 @@ DenseElementsAttr DenseFPElementsAttr::mapValues( Type newElementType, llvm::function_ref mapping) const { llvm::SmallVector elementData; - auto newArrayType = mappingHelper( - mapping, *this, getType(), newElementType, elementData); + auto newArrayType = + mappingHelper(mapping, *this, getType(), newElementType, elementData); return get(newArrayType, elementData); } @@ -862,37 +954,18 @@ NamedAttributeList::NamedAttributeList(ArrayRef attributes) { setAttrs(attributes); } -/// Return all of the attributes on this operation. -ArrayRef NamedAttributeList::getAttrs() const { - return attrs ? attrs->getElements() : llvm::None; -} - /// Replace the held attributes with ones provided in 'newAttrs'. void NamedAttributeList::setAttrs(ArrayRef attributes) { // Don't create an attribute list if there are no attributes. - if (attributes.empty()) { + if (attributes.empty()) attrs = nullptr; - return; - } - - assert(llvm::all_of(attributes, - [](const NamedAttribute &attr) { return attr.second; }) && - "attributes cannot have null entries"); - attrs = AttributeListStorage::get(attributes); + else + attrs = DictionaryAttr::get(attributes, attributes[0].second.getContext()); } /// Return the specified attribute if present, null otherwise. -Attribute NamedAttributeList::get(StringRef name) const { - for (auto elt : getAttrs()) - if (elt.first.is(name)) - return elt.second; - return nullptr; -} Attribute NamedAttributeList::get(Identifier name) const { - for (auto elt : getAttrs()) - if (elt.first == name) - return elt.second; - return nullptr; + return attrs ? attrs.get(name) : nullptr; } /// If the an attribute exists with the specified name, change it to the new @@ -906,13 +979,13 @@ void NamedAttributeList::set(Identifier name, Attribute value) { for (auto &elt : newAttrs) if (elt.first == name) { elt.second = value; - attrs = AttributeListStorage::get(newAttrs); + attrs = DictionaryAttr::get(newAttrs, value.getContext()); return; } // Otherwise, add it. newAttrs.push_back({name, value}); - attrs = AttributeListStorage::get(newAttrs); + attrs = DictionaryAttr::get(newAttrs, value.getContext()); } /// Remove the attribute with the specified name if it exists. The return @@ -931,7 +1004,7 @@ auto NamedAttributeList::remove(Identifier name) -> RemoveResult { newAttrs.reserve(origAttrs.size() - 1); newAttrs.append(origAttrs.begin(), origAttrs.begin() + i); newAttrs.append(origAttrs.begin() + i + 1, origAttrs.end()); - attrs = AttributeListStorage::get(newAttrs); + attrs = DictionaryAttr::get(newAttrs, newAttrs[0].second.getContext()); return RemoveResult::Removed; } } diff --git a/mlir/lib/IR/Builders.cpp b/mlir/lib/IR/Builders.cpp index c6e84ff..4accfb5 100644 --- a/mlir/lib/IR/Builders.cpp +++ b/mlir/lib/IR/Builders.cpp @@ -117,6 +117,10 @@ BoolAttr Builder::getBoolAttr(bool value) { return BoolAttr::get(value, context); } +DictionaryAttr Builder::getDictionaryAttr(ArrayRef value) { + return DictionaryAttr::get(value, context); +} + IntegerAttr Builder::getI64IntegerAttr(int64_t value) { return IntegerAttr::get(getIntegerType(64), APInt(64, value)); } diff --git a/mlir/lib/IR/MLIRContext.cpp b/mlir/lib/IR/MLIRContext.cpp index 1f7aca8..bf1ae96 100644 --- a/mlir/lib/IR/MLIRContext.cpp +++ b/mlir/lib/IR/MLIRContext.cpp @@ -136,8 +136,8 @@ namespace { struct BuiltinDialect : public Dialect { BuiltinDialect(MLIRContext *context) : Dialect(/*name=*/"", context) { addAttributes(); addTypes { } }; -struct AttributeListKeyInfo : DenseMapInfo { - // Array attributes are uniqued based on their elements. - using KeyTy = ArrayRef; - using DenseMapInfo::isEqual; - - static unsigned getHashValue(AttributeListStorage *key) { - return getHashValue(KeyTy(key->getElements())); - } - - static unsigned getHashValue(KeyTy key) { - return hash_combine_range(key.begin(), key.end()); - } - - static bool isEqual(const KeyTy &lhs, const AttributeListStorage *rhs) { - if (rhs == getEmptyKey() || rhs == getTombstoneKey()) - return false; - return lhs == rhs->getElements(); - } -}; - struct CallSiteLocationKeyInfo : DenseMapInfo { // Call locations are uniqued based on their held concret location // and the caller location. @@ -361,14 +341,6 @@ public: //===--------------------------------------------------------------------===// StorageUniquer attributeUniquer; - // Attribute list allocator and mutex for thread safety. - llvm::BumpPtrAllocator attributeAllocator; - llvm::sys::SmartRWMutex attributeMutex; - - using AttributeListSet = - DenseSet; - AttributeListSet attributeLists; - public: MLIRContextImpl() : filenames(locationAllocator), identifiers(identifierAllocator) {} @@ -720,72 +692,6 @@ AttributeUniquer::getInitFn(MLIRContext *ctx, const ClassID *const attrID) { }; } -/// Perform a three-way comparison between the names of the specified -/// NamedAttributes. -static int compareNamedAttributes(const NamedAttribute *lhs, - const NamedAttribute *rhs) { - return lhs->first.str().compare(rhs->first.str()); -} - -/// Given a list of NamedAttribute's, canonicalize the list (sorting -/// by name) and return the unique'd result. Note that the empty list is -/// represented with a null pointer. -AttributeListStorage * -AttributeListStorage::get(ArrayRef attrs) { - // We need to sort the element list to canonicalize it, but we also don't want - // to do a ton of work in the super common case where the element list is - // already sorted. - SmallVector storage; - switch (attrs.size()) { - case 0: - // An empty list is represented with a null pointer. - return nullptr; - case 1: - // A single element is already sorted. - break; - case 2: - // Don't invoke a general sort for two element case. - if (attrs[0].first.str() > attrs[1].first.str()) { - storage.push_back(attrs[1]); - storage.push_back(attrs[0]); - attrs = storage; - } - break; - default: - // Check to see they are sorted already. - bool isSorted = true; - for (unsigned i = 0, e = attrs.size() - 1; i != e; ++i) { - if (attrs[i].first.str() > attrs[i + 1].first.str()) { - isSorted = false; - break; - } - } - // If not, do a general sort. - if (!isSorted) { - storage.append(attrs.begin(), attrs.end()); - llvm::array_pod_sort(storage.begin(), storage.end(), - compareNamedAttributes); - attrs = storage; - } - } - - auto &impl = attrs[0].second.getContext()->getImpl(); - - // Safely get or create an attribute instance. - return safeGetOrCreate(impl.attributeLists, attrs, impl.attributeMutex, [&] { - auto byteSize = - AttributeListStorage::totalSizeToAlloc(attrs.size()); - auto rawMem = - impl.attributeAllocator.Allocate(byteSize, alignof(NamedAttribute)); - - // Placement initialize the AggregateSymbolicValue. - auto result = ::new (rawMem) AttributeListStorage(attrs.size()); - std::uninitialized_copy(attrs.begin(), attrs.end(), - result->getTrailingObjects()); - return result; - }); -} - //===----------------------------------------------------------------------===// // AffineMap uniquing //===----------------------------------------------------------------------===// diff --git a/mlir/lib/Parser/Parser.cpp b/mlir/lib/Parser/Parser.cpp index f1a6601..10f80ed 100644 --- a/mlir/lib/Parser/Parser.cpp +++ b/mlir/lib/Parser/Parser.cpp @@ -1025,6 +1025,7 @@ Attribute Parser::parseExtendedAttribute(Type type) { /// | string-literal /// | type /// | `[` (attribute-value (`,` attribute-value)*)? `]` +/// | `{` (attribute-entry (`,` attribute-entry)*)? `}` /// | function-id `:` function-type /// | (`splat` | `dense`) `<` (tensor-type | vector-type) `,` /// attribute-value `>` @@ -1153,6 +1154,12 @@ Attribute Parser::parseAttribute(Type type) { return builder.getStringAttr(val); } + case Token::l_brace: { + SmallVector elements; + if (parseAttributeDict(elements)) + return nullptr; + return builder.getDictionaryAttr(elements); + } case Token::l_square: { consumeToken(Token::l_square); SmallVector elements; diff --git a/mlir/test/IR/parser.mlir b/mlir/test/IR/parser.mlir index 253dfd5..801eb00 100644 --- a/mlir/test/IR/parser.mlir +++ b/mlir/test/IR/parser.mlir @@ -369,6 +369,13 @@ func @attributes() { // CHECK: "foo"() {set12: [#set{{[0-9]+}}, #set{{[0-9]+}}]} "foo"() {set12: [#set1, #set2]} : () -> () + // CHECK: "foo"() {dictionary: {bool: true, fn: @ifinst}} + "foo"() {dictionary: {bool: true, fn: @ifinst}} : () -> () + + // Check that the dictionary attribute elements are sorted. + // CHECK: "foo"() {dictionary: {bar: false, bool: true, fn: @ifinst}} + "foo"() {dictionary: {fn: @ifinst, bar: false, bool: true}} : () -> () + // CHECK: "foo"() {d: 1.000000e-09 : f64, func: [], i123: 7 : i64, if: "foo"} : () -> () "foo"() {if: "foo", func: [], i123: 7, d: 1.e-9} : () -> () -- 2.7.4