55#include "llvm/ADT/IntervalMap.h"
56#include "llvm/ADT/SmallVectorExtras.h"
57#include "llvm/ADT/TypeSwitch.h"
60#define GEN_PASS_DEF_TESTTILEALLOCATION
61#include "mlir/Dialect/ArmSME/Transforms/Passes.h.inc"
69enum class TileMask :
unsigned {
115 static constexpr std::array ZA_B_MASKS = {TileMask::kZA0B};
116 static constexpr std::array ZA_H_MASKS = {TileMask::kZA0H, TileMask::kZA1H};
117 static constexpr std::array ZA_S_MASKS = {TileMask::kZA0S, TileMask::kZA1S,
118 TileMask::kZA2S, TileMask::kZA3S};
119 static constexpr std::array ZA_D_MASKS = {
120 TileMask::kZA0D, TileMask::kZA1D, TileMask::kZA2D, TileMask::kZA3D,
121 TileMask::kZA4D, TileMask::kZA5D, TileMask::kZA6D, TileMask::kZA7D};
122 static constexpr std::array ZA_Q_MASKS = {
123 TileMask::kZA0Q, TileMask::kZA1Q, TileMask::kZA2Q, TileMask::kZA3Q,
124 TileMask::kZA4Q, TileMask::kZA5Q, TileMask::kZA6Q, TileMask::kZA7Q,
125 TileMask::kZA8Q, TileMask::kZA9Q, TileMask::kZA10Q, TileMask::kZA11Q,
126 TileMask::kZA12Q, TileMask::kZA13Q, TileMask::kZA14Q, TileMask::kZA15Q};
128 case ArmSMETileType::ZAB:
130 case ArmSMETileType::ZAH:
132 case ArmSMETileType::ZAS:
134 case ArmSMETileType::ZAD:
136 case ArmSMETileType::ZAQ:
139 llvm_unreachable(
"unknown type in getMasks");
145 FailureOr<unsigned> allocateTileId(ArmSMETileType tileType) {
146 auto masks = getMasks(tileType);
147 for (
auto [tileId, tileMask] : llvm::enumerate(masks)) {
148 if ((tilesInUse & tileMask) == TileMask::kNone) {
149 tilesInUse |= tileMask;
157 void acquireTileId(ArmSMETileType tileType,
unsigned tileId) {
158 TileMask tileMask = getMasks(tileType)[tileId];
159 assert((tilesInUse & tileMask) == TileMask::kNone &&
160 "cannot acquire allocated tile!");
161 tilesInUse |= tileMask;
165 void releaseTileId(ArmSMETileType tileType,
unsigned tileId) {
166 TileMask tileMask = getMasks(tileType)[tileId];
167 assert((tilesInUse & tileMask) == tileMask &&
168 "cannot release unallocated tile!");
169 tilesInUse ^= tileMask;
173 unsigned allocateInMemoryTileId() {
177 return nextInMemoryTileId++;
181 TileMask tilesInUse = TileMask::kNone;
202void splitCondBranches(
IRRewriter &rewriter, FunctionOpInterface function) {
204 function.walk([&](cf::CondBranchOp condBranch) {
205 if (llvm::any_of(condBranch->getOperands(), [&](
Value value) {
206 return isValidSMETileVectorType(value.getType());
208 worklist.push_back(condBranch);
214 cf::BranchOp::create(rewriter, loc, dest, args);
217 for (
auto condBranch : worklist) {
218 auto loc = condBranch.getLoc();
219 Block *block = condBranch->getBlock();
222 insertJump(loc, newTrueBranch, condBranch.getTrueDest(),
223 condBranch.getTrueDestOperands());
224 insertJump(loc, newFalseBranch, condBranch.getFalseDest(),
225 condBranch.getFalseDestOperands());
227 condBranch.getFalseDestOperandsMutable().clear();
228 condBranch.getTrueDestOperandsMutable().clear();
229 condBranch.setSuccessor(newTrueBranch, 0);
230 condBranch.setSuccessor(newFalseBranch, 1);
248 FunctionOpInterface function) {
249 for (
Block &block : function.getBlocks()) {
251 if (!isa<cf::BranchOp>(terminator))
257 CopyTileOp::create(rewriter, terminator->
getLoc(), operand.get());
272void preprocessForTileAllocation(
IRRewriter &rewriter,
273 FunctionOpInterface function) {
274 splitCondBranches(rewriter, function);
275 insertCopiesAtBranches(rewriter, function);
284 using RangeSet = llvm::IntervalMap<uint64_t, uint8_t, 16,
285 llvm::IntervalMapHalfOpenInfo<unsigned>>;
286 using Allocator = RangeSet::Allocator;
288 static constexpr uint8_t kValidLiveRange = 0xff;
290 LiveRange(Allocator &allocator)
291 : ranges(std::make_unique<RangeSet>(allocator)) {}
294 bool overlaps(LiveRange
const &otherRange)
const {
295 return llvm::IntervalMapOverlaps<RangeSet, RangeSet>(*ranges,
301 bool overlaps(uint64_t point)
const {
302 return ranges->lookup(point) == kValidLiveRange;
306 void unionWith(LiveRange
const &otherRange) {
307 for (
auto it = otherRange.ranges->begin(); it != otherRange.ranges->end();
309 ranges->insert(it.start(), it.stop(), kValidLiveRange);
310 values.set_union(otherRange.values);
314 void insert(Value value,
unsigned start,
unsigned end) {
315 values.insert(value);
317 ranges->insert(start, end, kValidLiveRange);
320 bool empty()
const {
return ranges->empty(); }
321 unsigned start()
const {
return ranges->start(); }
322 unsigned end()
const {
return ranges->stop(); }
323 bool operator<(LiveRange
const &other)
const {
324 return start() < other.start();
327 ArmSMETileType getTileType()
const {
336 std::unique_ptr<RangeSet> ranges;
339 std::optional<unsigned> tileId;
346FailureOr<DenseMap<Operation *, unsigned>>
347generateOperationNumbering(FunctionOpInterface function) {
352 for (
Block *block : blocks) {
362 return op.emitError(
"ArmSME tile allocation requires flattened control "
363 "flow; run -convert-scf-to-cf before this pass "
364 "(e.g. via convert-arm-sme-to-llvm pipeline)");
366 operationToIndexMap.try_emplace(&op,
index++);
370 return operationToIndexMap;
378 LiveRange::Allocator &liveRangeAllocator,
379 Liveness &liveness, FunctionOpInterface function) {
380 assert(!operationToIndexMap.empty() &&
"expected operation numbering");
386 auto defineOrUpdateValueLiveRange = [&](
Value value,
Operation *firstUseOrDef,
388 bool liveAtBlockEntry =
false) {
392 auto [it, _] = liveRanges.try_emplace(value, liveRangeAllocator);
393 LiveRange &valueLiveRange = it->second;
394 auto *lastUseInBlock = livenessInfo.getEndOperation(value, firstUseOrDef);
396 unsigned startOpIdx =
397 operationToIndexMap.at(firstUseOrDef) + (liveAtBlockEntry ? -1 : 0);
398 unsigned endOpIdx = operationToIndexMap.at(lastUseInBlock);
399 valueLiveRange.insert(value, startOpIdx, endOpIdx);
402 for (
Block &block : function.getBlocks()) {
406 defineOrUpdateValueLiveRange(argument, &block.
front(), *livenessInfo,
409 for (
Value liveIn : livenessInfo->
in())
410 defineOrUpdateValueLiveRange(liveIn, &block.
front(), *livenessInfo,
415 defineOrUpdateValueLiveRange(
result, &op, *livenessInfo);
423static void forEachPredecessorTileValue(
BlockArgument blockArg,
429 .Case([&](cf::BranchOp branch) {
430 Value predecessorOperand = branch.getDestOperands()[argNumber];
431 callback(predecessorOperand);
433 .Case([&](cf::CondBranchOp condBranch) {
434 if (condBranch.getFalseDest() == block) {
435 Value predecessorOperand =
436 condBranch.getFalseDestOperands()[argNumber];
437 callback(predecessorOperand);
439 if (condBranch.getTrueDest() == block) {
440 Value predecessorOperand =
441 condBranch.getTrueDestOperands()[argNumber];
442 callback(predecessorOperand);
452 for (
auto &[value, liveRange] : initialLiveRanges) {
453 liveRanges.insert({value, &liveRange});
459 auto mergeValuesIfNonOverlapping = [&](
Value a,
Value b) {
460 LiveRange *aLiveRange = liveRanges.at(a);
461 LiveRange *bLiveRange = liveRanges.at(
b);
462 if (aLiveRange != bLiveRange && !aLiveRange->overlaps(*bLiveRange)) {
463 aLiveRange->unionWith(*bLiveRange);
464 for (
Value value : bLiveRange->values)
465 liveRanges[value] = aLiveRange;
470 auto unifyDefinitionsWithOperands = [&](
Value value) {
474 for (
auto operand : armSMEOp->getOperands()) {
476 mergeValuesIfNonOverlapping(value, operand);
481 auto unifyBlockArgumentsWithPredecessors = [&](
Value value) {
482 auto blockArg = dyn_cast<BlockArgument>(value);
485 forEachPredecessorTileValue(blockArg, [&](
Value predecessorTile) {
486 mergeValuesIfNonOverlapping(blockArg, predecessorTile);
490 auto applyRule = [&](
auto rule) {
491 llvm::for_each(llvm::make_first_range(initialLiveRanges), rule);
495 applyRule(unifyBlockArgumentsWithPredecessors);
496 applyRule(unifyDefinitionsWithOperands);
500 for (
auto [_, liveRange] : liveRanges) {
501 if (!liveRange->empty())
502 uniqueLiveRanges.insert(liveRange);
506 auto coalescedLiveRanges = uniqueLiveRanges.takeVector();
507 llvm::sort(coalescedLiveRanges,
508 [](LiveRange *a, LiveRange *
b) {
return *a < *
b; });
509 return std::move(coalescedLiveRanges);
514template <
typename OverlappingRangesIterator>
516chooseSpillUsingHeuristics(OverlappingRangesIterator overlappingRanges,
517 LiveRange *newRange) {
519 auto isTrivialSpill = [&](LiveRange &allocatedRange) {
521 newRange->getTileType()) &&
522 allocatedRange.values.size() == 1 &&
526 if (isTrivialSpill(*newRange))
528 auto trivialSpill = llvm::find_if(overlappingRanges, isTrivialSpill);
529 if (trivialSpill != overlappingRanges.end())
530 return &*trivialSpill;
533 auto isSmallerTileTypeOrEndsEarlier = [](LiveRange &a, LiveRange &
b) {
537 LiveRange &latestEndingLiveRange =
538 *llvm::max_element(overlappingRanges, isSmallerTileTypeOrEndsEarlier);
539 if (!isSmallerTileTypeOrEndsEarlier(latestEndingLiveRange, *newRange))
540 return &latestEndingLiveRange;
545void allocateTilesToLiveRanges(
547 TileAllocator tileAllocator;
558 for (LiveRange *nextRange : liveRangesSortedByStartPoint) {
559 auto currentPoint = nextRange->start();
561 activeRanges.remove_if([&](LiveRange *activeRange) {
563 if (activeRange->end() <= currentPoint) {
564 tileAllocator.releaseTileId(activeRange->getTileType(),
565 *activeRange->tileId);
569 if (!activeRange->overlaps(currentPoint)) {
570 tileAllocator.releaseTileId(activeRange->getTileType(),
571 *activeRange->tileId);
572 inactiveRanges.insert(activeRange);
578 inactiveRanges.remove_if([&](LiveRange *inactiveRange) {
580 if (inactiveRange->end() <= currentPoint) {
584 if (inactiveRange->overlaps(currentPoint)) {
585 tileAllocator.acquireTileId(inactiveRange->getTileType(),
586 *inactiveRange->tileId);
587 activeRanges.insert(inactiveRange);
597 for (LiveRange *inactiveRange : inactiveRanges) {
598 if (inactiveRange->overlaps(*nextRange)) {
601 tileAllocator.acquireTileId(inactiveRange->getTileType(),
602 *inactiveRange->tileId);
603 overlappingInactiveRanges.push_back(inactiveRange);
608 auto rangeTileType = nextRange->getTileType();
609 auto tileId = tileAllocator.allocateTileId(rangeTileType);
610 if (succeeded(tileId)) {
611 nextRange->tileId = *tileId;
614 auto allOverlappingRanges = llvm::concat<LiveRange>(
615 llvm::make_pointee_range(activeRanges.getArrayRef()),
616 llvm::make_pointee_range(overlappingInactiveRanges));
618 LiveRange *rangeToSpill =
619 chooseSpillUsingHeuristics(allOverlappingRanges, nextRange);
620 if (rangeToSpill != nextRange) {
622 tileAllocator.releaseTileId(rangeToSpill->getTileType(),
623 *rangeToSpill->tileId);
625 nextRange->tileId = *tileAllocator.allocateTileId(rangeTileType);
627 if (!activeRanges.remove(rangeToSpill)) {
628 bool removed = inactiveRanges.remove(rangeToSpill);
629 assert(removed &&
"expected a range to be removed!");
633 rangeToSpill->tileId = tileAllocator.allocateInMemoryTileId();
638 activeRanges.insert(nextRange);
641 for (LiveRange *range : overlappingInactiveRanges) {
643 tileAllocator.releaseTileId(range->getTileType(), *range->tileId);
650 IntegerAttr tileIdAttr) {
652 rewriter.
modifyOpInPlace(tileOp, [&] { tileOp.setTileId(tileIdAttr); });
654 if (
auto tileOp = dyn_cast<ArmSMETileOpInterface>(user)) {
657 rewriter.
modifyOpInPlace(tileOp, [&] { tileOp.setTileId(tileIdAttr); });
663LogicalResult assignTileIdsAndResolveTrivialConflicts(
664 IRRewriter &rewriter, FunctionOpInterface function,
666 for (LiveRange
const *liveRange : allocatedLiveRanges) {
668 auto isAllocatedToSameTile = [&](
Value value) {
670 tileOp && tileOp.
getTileId() == tileIdAttr)
672 return liveRange->values.contains(value);
676 auto foldRedundantCopies = [&](
Value value) -> LogicalResult {
678 if (!copyOp || !isAllocatedToSameTile(copyOp.getTile()))
686 auto validateBlockArguments = [&](
Value value) {
687 auto blockArg = dyn_cast<BlockArgument>(value);
692 bool tileMismatch =
false;
693 forEachPredecessorTileValue(blockArg, [&](
Value predecessorTile) {
696 if (!isAllocatedToSameTile(predecessorTile)) {
698 "block argument not allocated to the same SME virtial tile as "
707 auto resolveTrivialTileConflicts = [&](
Value value) -> LogicalResult {
710 if (!tileOperand || isAllocatedToSameTile(tileOperand->get())) {
719 tileOp.emitOpError(
"tile operand allocated to different SME "
720 "virtial tile (move required)");
721 error.attachNote(tileOperand->get().getLoc())
722 <<
"tile operand is: " << tileOperand->get();
727 auto clonedOp = operandTileOp.clone();
729 [&] { clonedOp.setTileId(tileOp.getTileId()); });
730 rewriter.
insert(clonedOp);
731 if (isa<CopyTileOp>(tileOp)) {
733 clonedOp->getResult(0));
736 tileOp, [&] { tileOperand->assign(clonedOp->getResult(0)); });
741 for (
Value value : liveRange->values) {
743 assignTileIdToValue(rewriter, value, tileIdAttr);
746 if (succeeded(foldRedundantCopies(value)))
750 if (
failed(validateBlockArguments(value)))
754 if (
failed(resolveTrivialTileConflicts(value)))
764 FunctionOpInterface function) {
765 llvm::errs() <<
"SME Tile Liveness: @" << function.getName()
766 <<
"\nKey:\nS - Start\nE - End\n| - Live\n";
767 for (
auto [blockIdx, block] : llvm::enumerate(function.getBlocks())) {
768 llvm::errs() <<
"^bb" << blockIdx <<
":\n";
770 unsigned operationIndex = operationToIndexMap.at(&op);
771 for (LiveRange
const *range : liveRanges) {
773 for (
auto it = range->ranges->begin(); it != range->ranges->end();
775 if (it.start() == operationIndex)
776 liveness = (liveness ==
'E' ?
'|' :
'S');
777 else if (it.stop() == operationIndex)
778 liveness = (liveness ==
'S' ?
'|' :
'E');
779 else if (operationIndex >= it.start() && operationIndex < it.stop())
782 llvm::errs() << liveness;
784 llvm::errs() <<
' ' << op.getName() <<
'\n';
787 llvm::errs() <<
"==========\n";
790struct TestTileAllocationPass
792 using TestTileAllocationBase::TestTileAllocationBase;
793 void runOnOperation()
override {
794 FunctionOpInterface function = getOperation();
795 if (preprocessOnly) {
796 IRRewriter rewriter(function);
797 return preprocessForTileAllocation(rewriter, function);
821 LiveRange::Allocator liveRangeAllocator;
825 preprocessForTileAllocation(rewriter, function);
829 auto maybeOperationToIndexMap = generateOperationNumbering(function);
830 if (failed(maybeOperationToIndexMap))
832 auto &operationToIndexMap = *maybeOperationToIndexMap;
833 auto initialLiveRanges = gatherTileLiveRanges(
834 operationToIndexMap, liveRangeAllocator, liveness, function);
835 if (initialLiveRanges.empty())
840 auto nonEmpty = llvm::make_filter_range(
841 llvm::make_second_range(initialLiveRanges),
842 [&](LiveRange
const &liveRange) {
return !liveRange.empty(); });
843 auto initialRanges = llvm::map_to_vector(
844 nonEmpty, [](LiveRange
const &liveRange) {
return &liveRange; });
845 llvm::sort(initialRanges,
846 [](LiveRange
const *a, LiveRange
const *
b) {
return *a < *
b; });
847 llvm::errs() <<
"\n========== Initial Live Ranges:\n";
848 dumpLiveRanges(operationToIndexMap, initialRanges, function);
854 auto coalescedLiveRanges = coalesceTileLiveRanges(initialLiveRanges);
857 llvm::errs() <<
"\n========== Coalesced Live Ranges:\n";
858 dumpLiveRanges(operationToIndexMap, coalescedLiveRanges, function);
862 allocateTilesToLiveRanges(coalescedLiveRanges);
865 if (failed(assignTileIdsAndResolveTrivialConflicts(rewriter, function,
866 coalescedLiveRanges))) {
static void copy(Location loc, Value dst, Value src, Value size, OpBuilder &builder)
Copies the given number of bytes from src to dst pointers.
static bool hasArmSMETileOps(FunctionOpInterface function)
Returns true if function contains any ArmSME tile ops.
This class represents an argument of a Block.
unsigned getArgNumber() const
Returns the number of this argument.
Block * getOwner() const
Returns the block that owns this argument.
Block represents an ordered list of Operations.
iterator_range< pred_iterator > getPredecessors()
OpListType & getOperations()
Operation * getTerminator()
Get the terminator operation of this block.
BlockArgListType getArguments()
Operation * getParentOp()
Returns the closest surrounding operation that contains this block.
IntegerAttr getI32IntegerAttr(int32_t value)
This class coordinates rewriting a piece of IR outside of a pattern rewrite, providing a way to keep ...
This class represents liveness information on block level.
const ValueSetT & in() const
Returns all values that are live at the beginning of the block (unordered).
Represents an analysis for computing liveness information from a given top-level operation.
const LivenessBlockInfo * getLiveness(Block *block) const
Gets liveness info (if any) for the block.
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
void setInsertionPoint(Block *block, Block::iterator insertPoint)
Set the insertion point to the specified location.
void setInsertionPointToEnd(Block *block)
Sets the insertion point to the end of the specified block.
Operation * insert(Operation *op)
Insert the given operation at the current insertion point and return it.
This class represents an operand of an operation.
Operation * getOperation()
Inherit getOperation from OpState.
Operation is the basic unit of execution within MLIR.
Location getLoc()
The source location the operation was defined or derived from.
MutableArrayRef< OpOperand > getOpOperands()
InFlightDiagnostic emitOpError(const Twine &message={})
Emit an error with the op name prefixed, like "'dim' op " which is convenient for verifiers.
Block * splitBlock(Block *block, Block::iterator before)
Split the operations starting at "before" (inclusive) out of the given block into a new block,...
void modifyOpInPlace(Operation *root, CallableT &&callable)
This method is a utility wrapper around an in-place modification of an operation.
virtual void replaceAllUsesWith(Value from, Value to)
Find uses of from and replace them with to.
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Type getType() const
Return the type of this value.
user_range getUsers() const
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
A utility result that is used to signal how to proceed with an ongoing walk:
static WalkResult advance()
bool wasInterrupted() const
Returns true if the walk was interrupted.
static WalkResult interrupt()
mlir::IntegerAttr getTileId()
Returns the tile ID assigned to this operation.
@ LLVM_MARK_AS_BITMASK_ENUM
std::optional< ArmSMETileType > getSMETileType(VectorType)
Returns the type of SME tile this vector type corresponds to, or none if the vector type does not fit...
void eraseTriviallyDeadTileOps(IRRewriter &rewriter, FunctionOpInterface function)
Erase trivially dead tile ops from a function.
LogicalResult allocateSMETiles(FunctionOpInterface function, bool dumpRanges=false)
Allocate tile IDs to all ArmSME operations in a function.
bool isTriviallyCloneableTileOp(arm_sme::ArmSMETileOpInterface tileOp)
Returns true if tileOp is trivially cloneable.
bool isTileTypeGreaterOrEqual(ArmSMETileType typeA, ArmSMETileType typeB)
Returns true typeA is >= (in terms of bytes) than typeB.
bool isValidSMETileVectorType(VectorType vType)
Returns true if vType is a valid vector type for an SME tile or false otherwise.
static constexpr unsigned kInMemoryTileIdBase
OpOperand * getTileOpOperand(arm_sme::ArmSMETileOpInterface tileOp)
Returns the tile OpOperand for this tileOp (or null).
bool hasTileResult(arm_sme::ArmSMETileOpInterface tileOp)
Returns true if tileOp produces a tile result.
bool operator<(const Fraction &x, const Fraction &y)
Include the generated interface declarations.
llvm::MapVector< KeyT, ValueT, MapType, VectorType, N > MapVector
SetVector< Block * > getBlocksSortedByDominance(Region ®ion)
Gets a list of blocks that is sorted according to dominance.
Type getType(OpFoldResult ofr)
Returns the int type of the integer in ofr.
llvm::SetVector< T, Vector, Set, N > SetVector
llvm::TypeSwitch< T, ResultT > TypeSwitch
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
llvm::function_ref< Fn > function_ref