Skip to content

Commit 3dfa861

Browse files
committed
[mlir][IR] Refactor the internal implementation of Value
The current implementation of Value involves a pointer int pair with several different kinds of owners, i.e. BlockArgumentImpl*, Operation *, TrailingOpResult*. This design arose from the desire to save memory overhead for operations that have a very small number of results (generally 0-2). There are, unfortunately, many problematic aspects of the current implementation that make Values difficult to work with or just inefficient. Operation result types are stored as a separate array on the Operation. This is very inefficient for many reasons: we use TupleType for multiple results, which can lead to huge amounts of memory usage if multi-result operations change types frequently(they do). It also means that simple methods like Value::getType/Value::setType now require complex logic to get to the desired type. Value only has one pointer bit free, severely limiting the ability to use it in things like PointerUnion/PointerIntPair. Given that we store the kind of a Value along with the "owner" pointer, we only leave one bit free for users of Value. This creates situations where we end up nesting PointerUnions to be able to use Value in one. As noted above, most of the methods in Value need to branch on at least 3 different cases which is both inefficient, possibly error prone, and verbose. The current storage of results also creates problems for utilities like ValueRange/TypeRange, which want to efficiently store base pointers to ranges (of which Operation* isn't really useful as one). This revision greatly simplifies the implementation of Value by the introduction of a new ValueImpl class. This class contains all of the state shared between all of the various derived value classes; i.e. the use list, the type, and the kind. This shared implementation class provides several large benefits: * Most of the methods on value are now branchless, and often one-liners. * The "kind" of the value is now stored in ValueImpl instead of Value This frees up all of Value's pointer bits, allowing for users to take full advantage of PointerUnion/PointerIntPair/etc. It also allows for storing more operation results as "inline", 6 now instead of 2, freeing up 1 word per new inline result. * Operation result types are now stored in the result, instead of a side array This drops the size of zero-result operations by 1 word. It also removes the memory crushing use of TupleType for operations results (which could lead up to hundreds of megabytes of "dead" TupleTypes in the context). This also allowed restructured ValueRange, making it simpler and one word smaller. This revision does come with two conceptual downsides: * Operation::getResultTypes no longer returns an ArrayRef<Type> This conceptually makes some usages slower, as the iterator increment is slightly more complex. * OpResult::getOwner is slightly more expensive, as it now requires a little bit of arithmetic From profiling, neither of the conceptual downsides have resulted in any perceivable hit to performance. Given the advantages of the new design, most compiles are slightly faster. Differential Revision: https://reviews.llvm.org/D97804
1 parent 5d91698 commit 3dfa861

File tree

16 files changed

+389
-475
lines changed

16 files changed

+389
-475
lines changed

mlir/include/mlir/Conversion/StandardToLLVM/ConvertStandardToLLVM.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ class LLVMTypeConverter : public TypeConverter {
7777
/// supported LLVM IR type. In particular, if more than one value is
7878
/// returned, create an LLVM IR structure type with elements that correspond
7979
/// to each of the MLIR types converted with `convertType`.
80-
Type packFunctionResults(ArrayRef<Type> types);
80+
Type packFunctionResults(TypeRange types);
8181

8282
/// Convert a type in the context of the default or bare pointer calling
8383
/// convention. Calling convention sensitive types, such as MemRefType and

mlir/include/mlir/IR/Operation.h

Lines changed: 32 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -272,18 +272,21 @@ class alignas(8) Operation final
272272
//===--------------------------------------------------------------------===//
273273

274274
/// Return the number of results held by this operation.
275-
unsigned getNumResults();
275+
unsigned getNumResults() { return numResults; }
276276

277277
/// Get the 'idx'th result of this operation.
278-
OpResult getResult(unsigned idx) { return OpResult(this, idx); }
278+
OpResult getResult(unsigned idx) { return OpResult(getOpResultImpl(idx)); }
279279

280280
/// Support result iteration.
281281
using result_range = ResultRange;
282282
using result_iterator = result_range::iterator;
283283

284284
result_iterator result_begin() { return getResults().begin(); }
285285
result_iterator result_end() { return getResults().end(); }
286-
result_range getResults() { return result_range(this); }
286+
result_range getResults() {
287+
return numResults == 0 ? result_range(nullptr, 0)
288+
: result_range(getInlineOpResult(0), numResults);
289+
}
287290

288291
result_range getOpResults() { return getResults(); }
289292
OpResult getOpResult(unsigned idx) { return getResult(idx); }
@@ -293,7 +296,7 @@ class alignas(8) Operation final
293296
using result_type_range = result_range::type_range;
294297
result_type_iterator result_type_begin() { return getResultTypes().begin(); }
295298
result_type_iterator result_type_end() { return getResultTypes().end(); }
296-
result_type_range getResultTypes();
299+
result_type_range getResultTypes() { return getResults().getTypes(); }
297300

298301
//===--------------------------------------------------------------------===//
299302
// Attributes
@@ -620,7 +623,7 @@ class alignas(8) Operation final
620623
bool hasValidOrder() { return orderIndex != kInvalidOrderIdx; }
621624

622625
private:
623-
Operation(Location location, OperationName name, TypeRange resultTypes,
626+
Operation(Location location, OperationName name, unsigned numResults,
624627
unsigned numSuccessors, unsigned numRegions,
625628
DictionaryAttr attributes, bool hasOperandStorage);
626629

@@ -630,17 +633,17 @@ class alignas(8) Operation final
630633

631634
/// Returns the additional size necessary for allocating the given objects
632635
/// before an Operation in-memory.
633-
static size_t prefixAllocSize(unsigned numTrailingResults,
636+
static size_t prefixAllocSize(unsigned numOutOfLineResults,
634637
unsigned numInlineResults) {
635-
return sizeof(detail::TrailingOpResult) * numTrailingResults +
636-
sizeof(detail::InLineOpResult) * numInlineResults;
638+
return sizeof(detail::OutOfLineOpResult) * numOutOfLineResults +
639+
sizeof(detail::InlineOpResult) * numInlineResults;
637640
}
638641
/// Returns the additional size allocated before this Operation in-memory.
639642
size_t prefixAllocSize() {
640643
unsigned numResults = getNumResults();
641-
unsigned numTrailingResults = OpResult::getNumTrailing(numResults);
644+
unsigned numOutOfLineResults = OpResult::getNumTrailing(numResults);
642645
unsigned numInlineResults = OpResult::getNumInline(numResults);
643-
return prefixAllocSize(numTrailingResults, numInlineResults);
646+
return prefixAllocSize(numOutOfLineResults, numInlineResults);
644647
}
645648

646649
/// Returns the operand storage object.
@@ -649,20 +652,29 @@ class alignas(8) Operation final
649652
return *getTrailingObjects<detail::OperandStorage>();
650653
}
651654

652-
/// Returns a pointer to the use list for the given trailing result.
653-
detail::TrailingOpResult *getTrailingResult(unsigned resultNumber) {
654-
// Trailing results are stored in reverse order after(before in memory) the
655-
// inline results.
656-
return reinterpret_cast<detail::TrailingOpResult *>(
657-
getInlineResult(OpResult::getMaxInlineResults() - 1)) -
655+
/// Returns a pointer to the use list for the given out-of-line result.
656+
detail::OutOfLineOpResult *getOutOfLineOpResult(unsigned resultNumber) {
657+
// Out-of-line results are stored in reverse order after (before in memory)
658+
// the inline results.
659+
return reinterpret_cast<detail::OutOfLineOpResult *>(getInlineOpResult(
660+
detail::OpResultImpl::getMaxInlineResults() - 1)) -
658661
++resultNumber;
659662
}
660663

661664
/// Returns a pointer to the use list for the given inline result.
662-
detail::InLineOpResult *getInlineResult(unsigned resultNumber) {
665+
detail::InlineOpResult *getInlineOpResult(unsigned resultNumber) {
663666
// Inline results are stored in reverse order before the operation in
664667
// memory.
665-
return reinterpret_cast<detail::InLineOpResult *>(this) - ++resultNumber;
668+
return reinterpret_cast<detail::InlineOpResult *>(this) - ++resultNumber;
669+
}
670+
671+
/// Returns a pointer to the use list for the given result, which may be
672+
/// either inline or out-of-line.
673+
detail::OpResultImpl *getOpResultImpl(unsigned resultNumber) {
674+
unsigned maxInlineResults = detail::OpResultImpl::getMaxInlineResults();
675+
if (resultNumber < maxInlineResults)
676+
return getInlineOpResult(resultNumber);
677+
return getOutOfLineOpResult(resultNumber - maxInlineResults);
666678
}
667679

668680
/// Provide a 'getParent' method for ilist_node_with_parent methods.
@@ -683,24 +695,15 @@ class alignas(8) Operation final
683695
/// O(1) local dominance checks between operations.
684696
mutable unsigned orderIndex = 0;
685697

698+
const unsigned numResults;
686699
const unsigned numSuccs;
687-
const unsigned numRegions : 30;
700+
const unsigned numRegions : 31;
688701

689702
/// This bit signals whether this operation has an operand storage or not. The
690703
/// operand storage may be elided for operations that are known to never have
691704
/// operands.
692705
bool hasOperandStorage : 1;
693706

694-
/// This holds the result types of the operation. There are three different
695-
/// states recorded here:
696-
/// - 0 results : The type below is null.
697-
/// - 1 result : The single result type is held here.
698-
/// - N results : The type here is a tuple holding the result types.
699-
/// Note: We steal a bit for 'hasSingleResult' from somewhere else so that we
700-
/// can use 'resultType` in an ArrayRef<Type>.
701-
bool hasSingleResult : 1;
702-
Type resultType;
703-
704707
/// This holds the name of the operation.
705708
OperationName name;
706709

mlir/include/mlir/IR/OperationSupport.h

Lines changed: 26 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -555,36 +555,6 @@ class OperandStorage final
555555
};
556556
} // end namespace detail
557557

558-
//===----------------------------------------------------------------------===//
559-
// ResultStorage
560-
//===----------------------------------------------------------------------===//
561-
562-
namespace detail {
563-
/// This class provides the implementation for an in-line operation result. This
564-
/// is an operation result whose number can be stored inline inside of the bits
565-
/// of an Operation*.
566-
struct alignas(8) InLineOpResult : public IRObjectWithUseList<OpOperand> {};
567-
/// This class provides the implementation for an out-of-line operation result.
568-
/// This is an operation result whose number cannot be stored inline inside of
569-
/// the bits of an Operation*.
570-
struct alignas(8) TrailingOpResult : public IRObjectWithUseList<OpOperand> {
571-
TrailingOpResult(uint64_t trailingResultNumber)
572-
: trailingResultNumber(trailingResultNumber) {}
573-
574-
/// Returns the parent operation of this trailing result.
575-
Operation *getOwner();
576-
577-
/// Return the proper result number of this op result.
578-
unsigned getResultNumber() {
579-
return trailingResultNumber + OpResult::getMaxInlineResults();
580-
}
581-
582-
/// The trailing result number, or the offset from the beginning of the
583-
/// trailing array.
584-
uint64_t trailingResultNumber;
585-
};
586-
} // end namespace detail
587-
588558
//===----------------------------------------------------------------------===//
589559
// OpPrintingFlags
590560
//===----------------------------------------------------------------------===//
@@ -757,63 +727,52 @@ class MutableOperandRange {
757727

758728
/// This class implements the result iterators for the Operation class.
759729
class ResultRange final
760-
: public llvm::indexed_accessor_range<ResultRange, Operation *, OpResult,
761-
OpResult, OpResult> {
730+
: public llvm::detail::indexed_accessor_range_base<
731+
ResultRange, detail::OpResultImpl *, OpResult, OpResult, OpResult> {
762732
public:
763-
using indexed_accessor_range<ResultRange, Operation *, OpResult, OpResult,
764-
OpResult>::indexed_accessor_range;
765-
ResultRange(Operation *op);
733+
using RangeBaseT::RangeBaseT;
766734

767735
/// Returns the types of the values within this range.
768-
using type_iterator = ArrayRef<Type>::iterator;
769-
using type_range = ArrayRef<Type>;
770-
type_range getTypes() const;
736+
using type_iterator = ValueTypeIterator<iterator>;
737+
using type_range = ValueTypeRange<ResultRange>;
738+
type_range getTypes() const { return {begin(), end()}; }
771739
auto getType() const { return getTypes(); }
772740

773741
private:
774-
/// See `llvm::indexed_accessor_range` for details.
775-
static OpResult dereference(Operation *op, ptrdiff_t index);
742+
/// See `llvm::detail::indexed_accessor_range_base` for details.
743+
static detail::OpResultImpl *offset_base(detail::OpResultImpl *object,
744+
ptrdiff_t index) {
745+
return object->getNextResultAtOffset(index);
746+
}
747+
/// See `llvm::detail::indexed_accessor_range_base` for details.
748+
static OpResult dereference_iterator(detail::OpResultImpl *object,
749+
ptrdiff_t index) {
750+
return offset_base(object, index);
751+
}
776752

777-
/// Allow access to `dereference_iterator`.
778-
friend llvm::indexed_accessor_range<ResultRange, Operation *, OpResult,
779-
OpResult, OpResult>;
753+
/// Allow access to `offset_base` and `dereference_iterator`.
754+
friend RangeBaseT;
780755
};
781756

782757
//===----------------------------------------------------------------------===//
783758
// ValueRange
784759

785-
namespace detail {
786-
/// The type representing the owner of a ValueRange. This is either a list of
787-
/// values, operands, or an Operation+start index for results.
788-
struct ValueRangeOwner {
789-
ValueRangeOwner(const Value *owner) : ptr(owner), startIndex(0) {}
790-
ValueRangeOwner(OpOperand *owner) : ptr(owner), startIndex(0) {}
791-
ValueRangeOwner(Operation *owner, unsigned startIndex)
792-
: ptr(owner), startIndex(startIndex) {}
793-
bool operator==(const ValueRangeOwner &rhs) const { return ptr == rhs.ptr; }
794-
795-
/// The owner pointer of the range. The owner has represents three distinct
796-
/// states:
797-
/// const Value *: The owner is the base to a contiguous array of Value.
798-
/// OpOperand * : The owner is the base to a contiguous array of operands.
799-
/// void* : This owner is an Operation*. It is marked as void* here
800-
/// because the definition of Operation is not visible here.
801-
PointerUnion<const Value *, OpOperand *, void *> ptr;
802-
803-
/// Ths start index into the range. This is only used for Operation* owners.
804-
unsigned startIndex;
805-
};
806-
} // end namespace detail
807-
808760
/// This class provides an abstraction over the different types of ranges over
809761
/// Values. In many cases, this prevents the need to explicitly materialize a
810762
/// SmallVector/std::vector. This class should be used in places that are not
811763
/// suitable for a more derived type (e.g. ArrayRef) or a template range
812764
/// parameter.
813765
class ValueRange final
814766
: public llvm::detail::indexed_accessor_range_base<
815-
ValueRange, detail::ValueRangeOwner, Value, Value, Value> {
767+
ValueRange,
768+
PointerUnion<const Value *, OpOperand *, detail::OpResultImpl *>,
769+
Value, Value, Value> {
816770
public:
771+
/// The type representing the owner of a ValueRange. This is either a list of
772+
/// values, operands, or results.
773+
using OwnerT =
774+
PointerUnion<const Value *, OpOperand *, detail::OpResultImpl *>;
775+
817776
using RangeBaseT::RangeBaseT;
818777

819778
template <typename Arg,
@@ -841,8 +800,6 @@ class ValueRange final
841800
auto getType() const { return getTypes(); }
842801

843802
private:
844-
using OwnerT = detail::ValueRangeOwner;
845-
846803
/// See `llvm::detail::indexed_accessor_range_base` for details.
847804
static OwnerT offset_base(const OwnerT &owner, ptrdiff_t index);
848805
/// See `llvm::detail::indexed_accessor_range_base` for details.

mlir/include/mlir/IR/TypeRange.h

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,11 @@ class ValueTypeRange;
3434
/// a SmallVector/std::vector. This class should be used in places that are not
3535
/// suitable for a more derived type (e.g. ArrayRef) or a template range
3636
/// parameter.
37-
class TypeRange
38-
: public llvm::detail::indexed_accessor_range_base<
39-
TypeRange,
40-
llvm::PointerUnion<const Value *, const Type *, OpOperand *>, Type,
41-
Type, Type> {
37+
class TypeRange : public llvm::detail::indexed_accessor_range_base<
38+
TypeRange,
39+
llvm::PointerUnion<const Value *, const Type *,
40+
OpOperand *, detail::OpResultImpl *>,
41+
Type, Type, Type> {
4242
public:
4343
using RangeBaseT::RangeBaseT;
4444
TypeRange(ArrayRef<Type> types = llvm::None);
@@ -64,7 +64,9 @@ class TypeRange
6464
/// * A pointer to the first element of an array of values.
6565
/// * A pointer to the first element of an array of types.
6666
/// * A pointer to the first element of an array of operands.
67-
using OwnerT = llvm::PointerUnion<const Value *, const Type *, OpOperand *>;
67+
/// * A pointer to the first element of an array of results.
68+
using OwnerT = llvm::PointerUnion<const Value *, const Type *, OpOperand *,
69+
detail::OpResultImpl *>;
6870

6971
/// See `llvm::detail::indexed_accessor_range_base` for details.
7072
static OwnerT offset_base(OwnerT object, ptrdiff_t index);
@@ -111,6 +113,18 @@ class ValueTypeRange final
111113
template <typename Container>
112114
ValueTypeRange(Container &&c) : ValueTypeRange(c.begin(), c.end()) {}
113115

116+
/// Return the type at the given index.
117+
Type operator[](size_t index) const {
118+
assert(index < size() && "invalid index into type range");
119+
return *(this->begin() + index);
120+
}
121+
122+
/// Return the size of this range.
123+
size_t size() const { return llvm::size(*this); }
124+
125+
/// Return first type in the range.
126+
Type front() { return (*this)[0]; }
127+
114128
/// Compare this range with another.
115129
template <typename OtherT>
116130
bool operator==(const OtherT &other) const {

mlir/include/mlir/IR/TypeUtilities.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,7 @@ LogicalResult verifyCompatibleShape(Type type1, Type type2);
5757

5858
/// Returns success if the given two arrays have the same number of elements and
5959
/// each pair wise entries have compatible shape.
60-
LogicalResult verifyCompatibleShapes(ArrayRef<Type> types1,
61-
ArrayRef<Type> types2);
60+
LogicalResult verifyCompatibleShapes(TypeRange types1, TypeRange types2);
6261

6362
//===----------------------------------------------------------------------===//
6463
// Utility Iterators

0 commit comments

Comments
 (0)