Skip to content

Commit

Permalink
Preserve use-list orders in mlir bytecode
Browse files Browse the repository at this point in the history
This patch implements a mechanism to read/write use-list orders from/to the mlir bytecode format. When producing bytecode, use-list orders are appended to each value of the IR. When reading bytecode, use-lists orders are loaded in memory and used at the end of parsing to sort the existing use-list chains.

Reviewed By: mehdi_amini

Differential Revision: https://reviews.llvm.org/D149755
  • Loading branch information
Matteo Franciolini authored and joker-eph committed May 21, 2023
1 parent f81ccb5 commit 6127819
Show file tree
Hide file tree
Showing 14 changed files with 770 additions and 10 deletions.
34 changes: 34 additions & 0 deletions mlir/docs/BytecodeFormat.md
Expand Up @@ -339,11 +339,20 @@ op {
numSuccessors: varint?,
successors: varint[],
numUseListOrders: varint?,
useListOrders: uselist[],
regionEncoding: varint?, // (numRegions << 1) | (isIsolatedFromAbove)
// regions are stored in a section if isIsolatedFromAbove
regions: (region | region_section)[]
}
uselist {
indexInRange: varint?,
useListEncoding: varint, // (numIndices << 1) | (isIndexPairEncoding)
indices: varint[]
}
```

The encoding of an operation is important because this is generally the most
Expand Down Expand Up @@ -377,6 +386,26 @@ definition of that value from the start of the first ancestor isolated region.
If the operation has successors, the number of successors and the indexes of the
successor blocks within the parent region are encoded.

##### Use-list orders

The reference use-list order is assumed to be the reverse of the global
enumeration of all the op operands that one would obtain with a pre-order walk
of the IR. This order is naturally obtained by building blocks of operations
op-by-op. However, some transformations may shuffle the use-lists with respect
to this reference ordering. If any of the results of the operation have a
use-list order that is not sorted with respect to the reference use-list order,
an encoding is emitted such that it is possible to reconstruct such order after
parsing the bytecode. The encoding represents an index map from the reference
operand order to the current use-list order. A bit flag is used to detect if
this encoding is of type index-pair or not. When the bit flag is set to zero,
the element at `i` represent the position of the use `i` of the reference list
into the current use-list. When the bit flag is set to `1`, the encoding
represent index pairs `(i, j)`, which indicate that the use at position `i` of
the reference list is mapped to position `j` in the current use-list. When only
less than half of the elements in the current use-list are shuffled with respect
to the reference use-list, the index-pair encoding is used to reduce the
bytecode memory requirements.

##### Regions

If the operation has regions, the number of regions and if the regions are
Expand Down Expand Up @@ -410,6 +439,8 @@ block {
block_arguments {
numArgs: varint?,
args: block_argument[]
numUseListOrders: varint?,
useListOrders: uselist[],
}
block_argument {
Expand All @@ -421,3 +452,6 @@ block_argument {
A block is encoded with an array of operations and block arguments. The first
field is an encoding that combines the number of operations in the block, with a
flag indicating if the block has arguments.

Use-list orders are attached to block arguments similarly to how they are
attached to operation results.
Expand Up @@ -11,8 +11,8 @@
//
//===----------------------------------------------------------------------===//

#ifndef LIB_MLIR_BYTECODE_ENCODING_H
#define LIB_MLIR_BYTECODE_ENCODING_H
#ifndef MLIR_BYTECODE_ENCODING_H
#define MLIR_BYTECODE_ENCODING_H

#include <cstdint>

Expand All @@ -27,7 +27,7 @@ enum {
kMinSupportedVersion = 0,

/// The current bytecode version.
kVersion = 2,
kVersion = 3,

/// An arbitrary value used to fill alignment padding.
kAlignmentByte = 0xCB,
Expand Down Expand Up @@ -87,10 +87,27 @@ enum : uint8_t {
kHasOperands = 0b00000100,
kHasSuccessors = 0b00001000,
kHasInlineRegions = 0b00010000,
kHasUseListOrders = 0b00100000,
// clang-format on
};
} // namespace OpEncodingMask

/// Get the unique ID of a value use. We encode the unique ID combining an owner
/// number and the argument number such as if ownerID(op1) < ownerID(op2), then
/// useID(op1) < useID(op2). If uses have the same owner, then argNumber(op1) <
/// argNumber(op2) implies useID(op1) < useID(op2).
template <typename OperandT>
static inline uint64_t getUseID(OperandT &val, unsigned ownerID) {
uint32_t operandNumberID;
if constexpr (std::is_same_v<OpOperand, OperandT>)
operandNumberID = val.getOperandNumber();
else if constexpr (std::is_same_v<BlockArgument, OperandT>)
operandNumberID = val.getArgNumber();
else
llvm_unreachable("unexpected operand type");
return (static_cast<uint64_t>(ownerID) << 32) | operandNumberID;
}

} // namespace bytecode
} // namespace mlir

Expand Down
45 changes: 45 additions & 0 deletions mlir/include/mlir/IR/UseDefLists.h
Expand Up @@ -44,6 +44,21 @@ class IROperandBase {
/// of the SSA machinery.
IROperandBase *getNextOperandUsingThisValue() { return nextUse; }

/// Initialize the use-def chain by setting the back address to self and
/// nextUse to nullptr.
void initChainWithUse(IROperandBase **self) {
assert(this == *self);
back = self;
nextUse = nullptr;
}

/// Link the current node to next.
void linkTo(IROperandBase *next) {
nextUse = next;
if (nextUse)
nextUse->back = &nextUse;
}

protected:
IROperandBase(Operation *owner) : owner(owner) {}
IROperandBase(IROperandBase &&other) : owner(other.owner) {
Expand Down Expand Up @@ -192,6 +207,30 @@ class IRObjectWithUseList {
use_begin()->set(newValue);
}

/// Shuffle the use-list chain according to the provided indices vector, which
/// need to represent a valid shuffle. That is, a vector of unique integers in
/// range [0, numUses - 1]. Users of this function need to guarantee the
/// validity of the indices vector.
void shuffleUseList(ArrayRef<unsigned> indices) {
assert((size_t)std::distance(getUses().begin(), getUses().end()) ==
indices.size() &&
"indices vector expected to have a number of elements equal to the "
"number of uses");
SmallVector<detail::IROperandBase *> shuffled(indices.size());
detail::IROperandBase *ptr = firstUse;
for (size_t idx = 0; idx < indices.size();
idx++, ptr = ptr->getNextOperandUsingThisValue())
shuffled[indices[idx]] = ptr;

initFirstUse(shuffled.front());
auto *current = firstUse;
for (auto &next : llvm::drop_begin(shuffled)) {
current->linkTo(next);
current = next;
}
current->linkTo(nullptr);
}

//===--------------------------------------------------------------------===//
// Uses
//===--------------------------------------------------------------------===//
Expand Down Expand Up @@ -234,6 +273,12 @@ class IRObjectWithUseList {
OperandType *getFirstUse() const { return (OperandType *)firstUse; }

private:
/// Set use as the first use of the chain.
void initFirstUse(detail::IROperandBase *use) {
firstUse = use;
firstUse->initChainWithUse(&firstUse);
}

detail::IROperandBase *firstUse = nullptr;

/// Allow access to `firstUse`.
Expand Down
5 changes: 5 additions & 0 deletions mlir/include/mlir/IR/Value.h
Expand Up @@ -187,6 +187,11 @@ class Value {
/// Returns true if the value is used outside of the given block.
bool isUsedOutsideOfBlock(Block *block);

/// Shuffle the use list order according to the provided indices. It is
/// responsibility of the caller to make sure that the indices map the current
/// use-list chain to another valid use-list chain.
void shuffleUseList(ArrayRef<unsigned> indices);

//===--------------------------------------------------------------------===//
// Uses

Expand Down

0 comments on commit 6127819

Please sign in to comment.