Implement Uday's suggestion to unique attribute lists across instructions,

reducing the memory impact on Operation to one word instead of 3 from an
std::vector.

Implement Jacques' suggestion to merge OpImpl::Storage into OpImpl::Base.

PiperOrigin-RevId: 203426518
This commit is contained in:
Chris Lattner 2018-07-05 21:20:59 -07:00 committed by jpienaar
parent 1928e20a56
commit 9e0e01b47a
10 changed files with 251 additions and 49 deletions

View File

@ -19,6 +19,7 @@
#define MLIR_IR_IDENTIFIER_H
#include "mlir/Support/LLVM.h"
#include "llvm/ADT/DenseMapInfo.h"
#include "llvm/ADT/StringRef.h"
namespace mlir {
@ -61,6 +62,16 @@ public:
void print(raw_ostream &os) const;
void dump() const;
static Identifier getEmptyKey() {
return Identifier(
(const char *)llvm::DenseMapInfo<const void *>::getEmptyKey());
}
static Identifier getTombstoneKey() {
return Identifier(
(const char *)llvm::DenseMapInfo<const void *>::getTombstoneKey());
}
private:
/// These are the bytes of the string, which is a nul terminated string.
const char *pointer;
@ -85,6 +96,28 @@ inline bool operator!=(Identifier lhs, StringRef rhs) { return !lhs.is(rhs); }
inline bool operator==(StringRef lhs, Identifier rhs) { return rhs.is(lhs); }
inline bool operator!=(StringRef lhs, Identifier rhs) { return !rhs.is(lhs); }
// Make identifiers hashable.
inline llvm::hash_code hash_value(Identifier arg) {
return llvm::hash_value(arg.str());
}
} // end namespace mlir
namespace llvm {
// Identifiers hash just like pointers, there is no need to hash the bytes.
template <> struct DenseMapInfo<mlir::Identifier> {
static mlir::Identifier getEmptyKey() {
return mlir::Identifier::getEmptyKey();
}
static mlir::Identifier getTombstoneKey() {
return mlir::Identifier::getTombstoneKey();
}
static unsigned getHashValue(mlir::Identifier Val) {
return DenseMapInfo<const void *>::getHashValue(Val.data());
}
static bool isEqual(mlir::Identifier LHS, mlir::Identifier RHS) {
return LHS == RHS;
}
};
} // end namespace llvm
#endif

View File

@ -87,8 +87,9 @@ class OperationInst
: public Operation, public Instruction,
public llvm::ilist_node_with_parent<OperationInst, BasicBlock> {
public:
explicit OperationInst(Identifier name, ArrayRef<NamedAttribute> attrs = {})
: Operation(name, attrs), Instruction(Kind::Operation) {}
explicit OperationInst(Identifier name, ArrayRef<NamedAttribute> attrs,
MLIRContext *context)
: Operation(name, attrs, context), Instruction(Kind::Operation) {}
~OperationInst() {}
/// Unlink this instruction from its BasicBlock and delete it.

View File

@ -24,6 +24,7 @@
namespace mlir {
class Attribute;
class AttributeListStorage;
template <typename OpType> class ConstOpPointer;
template <typename OpType> class OpPointer;
@ -52,20 +53,18 @@ public:
// (maybe a dozen or so, but not hundreds or thousands) so we use linear
// searches for everything.
ArrayRef<NamedAttribute> getAttrs() const {
return attrs;
}
ArrayRef<NamedAttribute> getAttrs() const;
/// Return the specified attribute if present, null otherwise.
Attribute *getAttr(Identifier name) const {
for (auto elt : attrs)
for (auto elt : getAttrs())
if (elt.first == name)
return elt.second;
return nullptr;
}
Attribute *getAttr(StringRef name) const {
for (auto elt : attrs)
for (auto elt : getAttrs())
if (elt.first.is(name))
return elt.second;
return nullptr;
@ -83,7 +82,7 @@ public:
/// If the an attribute exists with the specified name, change it to the new
/// value. Otherwise, add a new attribute with the specified name/value.
void setAttr(Identifier name, Attribute *value);
void setAttr(Identifier name, Attribute *value, MLIRContext *context);
enum class RemoveResult {
Removed, NotFound
@ -91,7 +90,7 @@ public:
/// Remove the attribute with the specified name if it exists. The return
/// value indicates whether the attribute was present or not.
RemoveResult removeAttr(Identifier name);
RemoveResult removeAttr(Identifier name, MLIRContext *context);
/// The getAs methods perform a dynamic cast from an Operation (like
/// OperationInst and OperationStmt) to a typed Op like DimOp. This returns
@ -112,14 +111,15 @@ public:
}
protected:
Operation(Identifier name, ArrayRef<NamedAttribute> attrs);
Operation(Identifier name, ArrayRef<NamedAttribute> attrs,
MLIRContext *context);
~Operation();
private:
Operation(const Operation&) = delete;
void operator=(const Operation&) = delete;
Identifier name;
std::vector<NamedAttribute> attrs;
AttributeListStorage *attrs;
};
} // end namespace mlir

View File

@ -72,9 +72,11 @@ public:
namespace OpImpl {
/// Every op should subclass this class to provide the basic storage of the
/// Operation*.
class Storage {
/// This provides public APIs that all operations should have. The template
/// argument 'ConcreteType' should be the concrete type by CRTP and the others
/// are base classes by the policy pattern.
template <typename ConcreteType, typename... Traits>
class Base : public Traits... {
public:
/// Return the operation that this refers to.
const Operation *getOperation() const { return state; }
@ -88,31 +90,24 @@ public:
/// If the an attribute exists with the specified name, change it to the new
/// value. Otherwise, add a new attribute with the specified name/value.
void setAttr(Identifier name, Attribute *value) {
state->setAttr(name, value);
void setAttr(Identifier name, Attribute *value, MLIRContext *context) {
state->setAttr(name, value, context);
}
protected:
/// Mutability management is handled by the OpWrapper/OpConstWrapper classes,
/// so we can cast it away here.
explicit Storage(const Operation *state)
: state(const_cast<Operation *>(state)) {}
private:
Operation *state;
};
/// This provides public APIs that all operations should have. The template
/// argument 'ConcreteType' should be the concrete type by CRTP and the others
/// are base classes by the policy pattern.
template <typename ConcreteType, typename... Traits>
class Base : public Traits... {
public:
/// This is the hook used by the AsmPrinter to emit this to the .mlir file.
/// Op implementations should provide a print method.
static void printAssembly(const Operation *op, raw_ostream &os) {
op->getAs<ConcreteType>()->print(os);
}
protected:
/// Mutability management is handled by the OpWrapper/OpConstWrapper classes,
/// so we can cast it away here.
explicit Base(const Operation *state)
: state(const_cast<Operation *>(state)) {}
private:
Operation *state;
};
/// This class provides the API for ops that are known to have exactly one

View File

@ -37,8 +37,7 @@ class OperationSet;
/// %2 = addf %0, %1 : f32
///
class AddFOp
: public OpImpl::Storage,
public OpImpl::Base<AddFOp, OpImpl::TwoOperands, OpImpl::OneResult> {
: public OpImpl::Base<AddFOp, OpImpl::TwoOperands, OpImpl::OneResult> {
public:
/// Methods for support type inquiry through isa, cast, and dyn_cast.
static StringRef getOperationName() { return "addf"; }
@ -47,7 +46,7 @@ public:
private:
friend class Operation;
explicit AddFOp(const Operation *state) : Storage(state) {}
explicit AddFOp(const Operation *state) : Base(state) {}
};
/// The "dim" builtin takes a memref or tensor operand and returns an
@ -57,8 +56,7 @@ private:
/// %1 = dim %0, 2 : tensor<?x?x?xf32>
///
class DimOp
: public OpImpl::Storage,
public OpImpl::Base<DimOp, OpImpl::OneOperand, OpImpl::OneResult> {
: public OpImpl::Base<DimOp, OpImpl::OneOperand, OpImpl::OneResult> {
public:
/// This returns the dimension number that the 'dim' is inspecting.
unsigned getIndex() const {
@ -72,7 +70,7 @@ public:
private:
friend class Operation;
explicit DimOp(const Operation *state) : Storage(state) {}
explicit DimOp(const Operation *state) : Base(state) {}
};
/// Install the standard operations in the specified operation set.

View File

@ -0,0 +1,58 @@
//===- AttributeListStorage.h - Attr representation for ops -----*- C++ -*-===//
//
// Copyright 2019 The MLIR Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// =============================================================================
#ifndef ATTRIBUTELISTSTORAGE_H
#define ATTRIBUTELISTSTORAGE_H
#include "mlir/IR/Operation.h"
#include "llvm/Support/TrailingObjects.h"
namespace mlir {
class AttributeListStorage final
: private llvm::TrailingObjects<AttributeListStorage, NamedAttribute> {
friend class llvm::TrailingObjects<AttributeListStorage, NamedAttribute>;
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<NamedAttribute> attrs,
MLIRContext *context);
/// Return the element constants for this aggregate constant. These are
/// known to all be constants.
ArrayRef<NamedAttribute> getElements() const {
return {getTrailingObjects<NamedAttribute>(), numElements};
}
private:
// This is used by the llvm::TrailingObjects base class.
size_t numTrailingObjects(OverloadToken<NamedAttribute>) const {
return numElements;
}
AttributeListStorage() = delete;
AttributeListStorage(const AttributeListStorage &) = delete;
AttributeListStorage(unsigned numElements) : numElements(numElements) {}
/// This is the number of attributes.
const unsigned numElements;
};
} // end namespace mlir
#endif

View File

@ -16,6 +16,7 @@
// =============================================================================
#include "mlir/IR/MLIRContext.h"
#include "AttributeListStorage.h"
#include "mlir/IR/AffineExpr.h"
#include "mlir/IR/AffineMap.h"
#include "mlir/IR/Attributes.h"
@ -24,6 +25,7 @@
#include "mlir/IR/StandardOps.h"
#include "mlir/IR/Types.h"
#include "mlir/Support/STLExtras.h"
#include "third_party/llvm/llvm/include/llvm/ADT/STLExtras.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/Support/Allocator.h"
@ -125,6 +127,24 @@ struct ArrayAttrKeyInfo : DenseMapInfo<ArrayAttr*> {
return lhs == rhs->getValue();
}
};
struct AttributeListKeyInfo : DenseMapInfo<AttributeListStorage *> {
// Array attributes are uniqued based on their elements.
using KeyTy = ArrayRef<NamedAttribute>;
using DenseMapInfo<AttributeListStorage *>::getHashValue;
using DenseMapInfo<AttributeListStorage *>::isEqual;
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();
}
};
} // end anonymous namespace.
@ -181,6 +201,9 @@ public:
StringMap<StringAttr*> stringAttrs;
using ArrayAttrSet = DenseSet<ArrayAttr*, ArrayAttrKeyInfo>;
ArrayAttrSet arrayAttrs;
using AttributeListSet =
DenseSet<AttributeListStorage *, AttributeListKeyInfo>;
AttributeListSet attributeLists;
public:
MLIRContextImpl() : identifiers(allocator) {
@ -448,6 +471,77 @@ ArrayAttr *ArrayAttr::get(ArrayRef<Attribute*> value, MLIRContext *context) {
return *existing.first = result;
}
/// 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<NamedAttribute> attrs,
MLIRContext *context) {
// 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<NamedAttribute, 8> 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;
}
}
// Ok, now that we've canonicalized our attributes, unique them.
auto &impl = context->getImpl();
// Look to see if we already have this.
auto existing = impl.attributeLists.insert_as(nullptr, attrs);
// If we already have it, return that value.
if (!existing.second)
return *existing.first;
// Otherwise, allocate a new AttributeListStorage, unique it and return it.
auto byteSize =
AttributeListStorage::totalSizeToAlloc<NamedAttribute>(attrs.size());
auto rawMem = impl.allocator.Allocate(byteSize, alignof(NamedAttribute));
// Placement initialize the AggregateSymbolicValue.
auto result = ::new (rawMem) AttributeListStorage(attrs.size());
std::uninitialized_copy(attrs.begin(), attrs.end(),
result->getTrailingObjects<NamedAttribute>());
return *existing.first = result;
}
//===----------------------------------------------------------------------===//
// AffineMap and AffineExpr uniquing
//===----------------------------------------------------------------------===//

View File

@ -16,10 +16,14 @@
// =============================================================================
#include "mlir/IR/Operation.h"
#include "AttributeListStorage.h"
using namespace mlir;
Operation::Operation(Identifier name, ArrayRef<NamedAttribute> attrs)
: name(name), attrs(attrs.begin(), attrs.end()) {
Operation::Operation(Identifier name, ArrayRef<NamedAttribute> attrs,
MLIRContext *context)
: name(name) {
this->attrs = AttributeListStorage::get(attrs, context);
#ifndef NDEBUG
for (auto elt : attrs)
assert(elt.second != nullptr && "Attributes cannot have null entries");
@ -29,27 +33,46 @@ Operation::Operation(Identifier name, ArrayRef<NamedAttribute> attrs)
Operation::~Operation() {
}
ArrayRef<NamedAttribute> Operation::getAttrs() const {
if (!attrs)
return {};
return attrs->getElements();
}
/// If an attribute exists with the specified name, change it to the new
/// value. Otherwise, add a new attribute with the specified name/value.
void Operation::setAttr(Identifier name, Attribute *value) {
void Operation::setAttr(Identifier name, Attribute *value,
MLIRContext *context) {
assert(value && "attributes may never be null");
auto origAttrs = getAttrs();
SmallVector<NamedAttribute, 8> newAttrs(origAttrs.begin(), origAttrs.end());
// If we already have this attribute, replace it.
for (auto &elt : attrs)
for (auto &elt : newAttrs)
if (elt.first == name) {
elt.second = value;
attrs = AttributeListStorage::get(newAttrs, context);
return;
}
// Otherwise, add it.
attrs.push_back({name, value});
newAttrs.push_back({name, value});
attrs = AttributeListStorage::get(newAttrs, context);
}
/// Remove the attribute with the specified name if it exists. The return
/// value indicates whether the attribute was present or not.
auto Operation::removeAttr(Identifier name) -> RemoveResult {
for (unsigned i = 0, e = attrs.size(); i != e; ++i) {
if (attrs[i].first == name) {
attrs.erase(attrs.begin()+i);
auto Operation::removeAttr(Identifier name, MLIRContext *context)
-> RemoveResult {
auto origAttrs = getAttrs();
for (unsigned i = 0, e = origAttrs.size(); i != e; ++i) {
if (origAttrs[i].first == name) {
SmallVector<NamedAttribute, 8> newAttrs;
newAttrs.reserve(origAttrs.size() - 1);
newAttrs.append(origAttrs.begin(), origAttrs.begin() + i);
newAttrs.append(origAttrs.begin() + i + 1, origAttrs.end());
attrs = AttributeListStorage::get(newAttrs, context);
return RemoveResult::Removed;
}
}

View File

@ -1296,7 +1296,7 @@ parseCFGOperation(CFGFunctionParserState &functionState) {
}
auto nameId = Identifier::get(name, context);
return new OperationInst(nameId, attributes);
return new OperationInst(nameId, attributes, context);
}

View File

@ -89,7 +89,7 @@ bb42: ; CHECK: bb0:
; CHECK: "foo"(){a: 1, b: -423, c: [true, false]}
"foo"(){a: 1, b: -423, c: [true, false] }
; CHECK: "foo"(){if: "foo", cfgfunc: [], i123: 7}
; CHECK: "foo"(){cfgfunc: [], i123: 7, if: "foo"}
"foo"(){if: "foo", cfgfunc: [], i123: 7}
return