Skip to content

Commit f606423

Browse files
authored
Merge pull request #185 from Xilinx/jrickert.cleanup_minimize_live_tensors
Replace recursive functions in XTenMinimizeLiveTensors with iterative versions
2 parents ea00296 + 4c00009 commit f606423

1 file changed

Lines changed: 134 additions & 92 deletions

File tree

lib/Transform/XTenMinimizeLiveTensors.cpp

Lines changed: 134 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,11 @@
2424
#include <mlir/Support/IndentedOstream.h>
2525
#include <mlir/Support/LogicalResult.h>
2626

27+
#include <deque>
28+
#include <map>
2729
#include <memory>
28-
#include <set>
30+
#include <optional>
31+
#include <utility>
2932

3033
#define DEBUG_TYPE "xten-minimize-live"
3134

@@ -83,7 +86,7 @@ struct OpInfo {
8386
mlir::LogicalResult verifyStrAttr(mlir::Operation *op, llvm::StringRef attrKey,
8487
llvm::StringRef attrValue) {
8588
if (!op->hasAttr(attrKey) ||
86-
!(op->getAttr(attrKey).cast<StringAttr>().str() == attrValue)) {
89+
!(cast<StringAttr>(op->getAttr(attrKey)).str() == attrValue)) {
8790
return failure();
8891
}
8992
return success();
@@ -223,7 +226,7 @@ size_t getSize(Value val) {
223226
if (elementType.isIntOrFloat())
224227
return (elementType.getIntOrFloatBitWidth() * type.getNumElements()) / 8;
225228

226-
if (auto complexType = elementType.dyn_cast<ComplexType>()) {
229+
if (auto complexType = dyn_cast<ComplexType>(elementType)) {
227230
elementType = complexType.getElementType();
228231
return (elementType.getIntOrFloatBitWidth() * type.getNumElements() * 2) /
229232
8;
@@ -270,22 +273,28 @@ class XTenMinimizeLiveTensorsPass
270273
XTenMinimizeLiveTensorsPass(const XTenMinimizeLiveTensorsPass &pass) =
271274
default;
272275

273-
// Recursively collect the OpInfo of all FM producers.
274-
[[nodiscard]] LogicalResult
275-
collectOperandInfo(OpInfo const &opInfo) { // NOLINT(misc-no-recursion)
276+
// Collect the OpInfo of all FM producers.
277+
LogicalResult collectOperandInfo(OpInfo const &opInfo) {
276278
// Visit all FM operands to collect their OpInfo.
277-
for (auto operand : opInfo.operands) {
279+
280+
std::deque<std::pair<Value, const OpInfo &>> worklist;
281+
for (Value v : opInfo.operands)
282+
worklist.emplace_back(v, opInfo);
283+
284+
while (!worklist.empty()) {
285+
const auto [operand, currentOpInfo] = worklist.front();
286+
worklist.pop_front();
278287
Operation *defOp = operand.getDefiningOp();
279288
if (defOp == nullptr) {
280289
// Use currFn as stand-in for BlockArguments.
281-
assert(operand.isa<BlockArgument>());
290+
assert(isa<BlockArgument>(operand));
282291
defOp = currFn;
283292
}
284293

285294
// If OpInfo is already created, so we only need to note this consumer.
286-
auto prevInfoIt = opToInfo.find(defOp);
295+
const auto prevInfoIt = opToInfo.find(defOp);
287296
if (prevInfoIt != opToInfo.end()) {
288-
prevInfoIt->second.consumers.push_back(opInfo.op);
297+
prevInfoIt->second.consumers.push_back(currentOpInfo.op);
289298
continue;
290299
}
291300

@@ -306,16 +315,15 @@ class XTenMinimizeLiveTensorsPass
306315
.operands = *fmOperands,
307316
.results = fmResults,
308317
.sharesResultMemory = sharesResultMemory,
309-
.consumers = {opInfo.op}};
310-
auto [opFwdIt, succeeded] = opToInfo.emplace(defOp, std::move(info));
311-
setOpSizes(opFwdIt->second);
312-
313-
// Recursively collect details of the operands of this operand.
314-
LogicalResult result = collectOperandInfo(opFwdIt->second);
315-
if (result.failed()) {
316-
signalPassFailure();
317-
return LogicalResult::failure();
318-
}
318+
.consumers = {currentOpInfo.op},
319+
.orderedProducers = {}};
320+
setOpSizes(info);
321+
const auto [opFwdIt, succeeded] =
322+
opToInfo.emplace(defOp, std::move(info));
323+
assert(succeeded && "unexpected duplicate op in opToInfo");
324+
325+
for (auto v : llvm::reverse(opFwdIt->second.operands))
326+
worklist.emplace_front(v, opFwdIt->second);
319327
}
320328
return LogicalResult::success();
321329
}
@@ -341,100 +349,128 @@ class XTenMinimizeLiveTensorsPass
341349
});
342350
}
343351

344-
/// Recursively determine branch running sizes.
352+
/// Determine branch running sizes.
345353
///
346354
/// \p opInfo points to the info for the op being analyzed.
347355
/// \p brInfo incoming branch info, to be updated by this op.
348356
/// \p completed contains the BranchRunning info for any fully
349357
/// analyzed operations.
350-
void determineBranchRunning(OpInfo *opInfo, // NOLINT(misc-no-recursion)
351-
BranchRunning &brInfo,
358+
void determineBranchRunning(OpInfo *opInfo, BranchRunning brInfo,
352359
std::map<Operation *, BranchRunning> &completed) {
353-
// Analyze simple fallthrough operations.
354-
while (opInfo->operands.size() < 2 && opInfo->consumers.size() < 2) {
355-
// Avoid recomputing BranchRunning for operations we already visited.
356-
auto opIt = completed.find(opInfo->op);
357-
if (opIt != completed.end())
358-
return;
359-
360-
brInfo.maxRunning = std::max(brInfo.maxRunning, opInfo->sizes.results);
361-
brInfo.lastResults = opInfo->sizes.results;
362-
opInfo->orderedProducers = {brInfo.lastOp};
363-
brInfo.lastOp = opInfo->op;
364-
completed.insert({opInfo->op, brInfo});
365-
366-
if (opInfo->consumers.empty())
367-
return; // Nothing more to compute.
368-
opInfo = &opToInfo.at(opInfo->consumers.front());
369-
}
370360

371-
// Avoid recomputing BranchRunning for operations we already visited.
372-
auto opIt = completed.find(opInfo->op);
373-
if (opIt != completed.end())
374-
return;
361+
std::deque<std::pair<OpInfo *, BranchRunning>> worklist;
362+
363+
// This lambda is used so that we can use returns from inner loops.
364+
// An alternative would be the use of gotos
365+
const auto determineBranchRunningImpl =
366+
[this, &worklist](OpInfo *opInfo, BranchRunning &brInfo,
367+
std::map<Operation *, BranchRunning> &completed) {
368+
// Analyze simple fallthrough operations.
369+
while (opInfo->operands.size() < 2 && opInfo->consumers.size() < 2) {
370+
// Avoid recomputing BranchRunning for operations we already
371+
// visited.
372+
auto opIt = completed.find(opInfo->op);
373+
if (opIt != completed.end())
374+
return;
375+
376+
brInfo.maxRunning =
377+
std::max(brInfo.maxRunning, opInfo->sizes.results);
378+
brInfo.lastResults = opInfo->sizes.results;
379+
opInfo->orderedProducers = {brInfo.lastOp};
380+
brInfo.lastOp = opInfo->op;
381+
completed.insert({opInfo->op, brInfo});
382+
383+
if (opInfo->consumers.empty())
384+
return; // Nothing more to compute.
385+
opInfo = &opToInfo.at(opInfo->consumers.front());
386+
}
375387

376-
// At a joining point - collect this branch and proceed iff all incoming
377-
// branches have been collected.
378-
SmallVector<BranchRunning> branches;
379-
for (Value val : opInfo->operands) {
380-
Operation *op = val.getDefiningOp();
381-
if (op == nullptr)
382-
op = currFn; // BlockArgument stand-in.
383-
384-
auto opIt = completed.find(op);
385-
if (opIt == completed.end())
386-
return; // Another producer needs to complete first.
387-
branches.push_back(opIt->second);
388-
}
388+
// Avoid recomputing BranchRunning for operations we already visited.
389+
auto opIt = completed.find(opInfo->op);
390+
if (opIt != completed.end())
391+
return;
392+
393+
// At a joining point - collect this branch and proceed iff all
394+
// incoming branches have been collected.
395+
SmallVector<BranchRunning> branches;
396+
for (Value val : opInfo->operands) {
397+
Operation *op = val.getDefiningOp();
398+
if (op == nullptr)
399+
op = currFn; // BlockArgument stand-in.
400+
401+
auto opIt = completed.find(op);
402+
if (opIt == completed.end())
403+
return; // Another producer needs to complete first.
404+
branches.push_back(opIt->second);
405+
}
389406

390-
// Concat producers should be ordered according to their operands
391-
// (op0 before op1 before op2, etc.), so no need to sort them out.
392-
if (!isConcatSubgraph(opInfo->op)) {
393-
std::sort(branches.begin(), branches.end(),
407+
// Concat producers should be ordered according to their operands
408+
// (op0 before op1 before op2, etc.), so no need to sort them out.
409+
if (!isConcatSubgraph(opInfo->op)) {
410+
std::sort(
411+
branches.begin(), branches.end(),
394412
[](BranchRunning &aBranch, BranchRunning &bBranch) -> bool {
395413
return (aBranch.maxRunning - aBranch.lastResults) >
396414
(bBranch.maxRunning - bBranch.lastResults);
397415
});
398-
} else if (hasAllGAPInputs(opInfo->op)) {
399-
// For GAP concats we want right-to-left ordering of the inputs
400-
std::reverse(branches.begin(), branches.end());
401-
}
416+
} else if (hasAllGAPInputs(opInfo->op)) {
417+
// For GAP concats we want right-to-left ordering of the inputs
418+
std::reverse(branches.begin(), branches.end());
419+
}
402420

403-
opInfo->orderedProducers.clear();
404-
for (BranchRunning const &branch : branches)
405-
opInfo->orderedProducers.push_back(branch.lastOp);
406-
407-
// Complete the brInfo for this operation.
408-
size_t maxRunning = opInfo->sizes.running;
409-
for (BranchRunning const &branch : branches)
410-
maxRunning = std::max(maxRunning, branch.maxRunning);
411-
brInfo.maxRunning = maxRunning;
412-
brInfo.lastResults = opInfo->sizes.results;
413-
brInfo.lastOp = opInfo->op;
414-
completed.insert({opInfo->op, brInfo});
415-
416-
// Continue to all consumers.
417-
for (Operation *consumer : opInfo->consumers) {
418-
BranchRunning nextRunning{.maxRunning = maxRunning,
419-
.lastResults = opInfo->sizes.results,
420-
.lastOp = opInfo->op};
421-
determineBranchRunning(&opToInfo.at(consumer), nextRunning, completed);
421+
opInfo->orderedProducers.clear();
422+
for (BranchRunning const &branch : branches)
423+
opInfo->orderedProducers.push_back(branch.lastOp);
424+
425+
// Complete the brInfo for this operation.
426+
size_t maxRunning = opInfo->sizes.running;
427+
for (BranchRunning const &branch : branches)
428+
maxRunning = std::max(maxRunning, branch.maxRunning);
429+
brInfo.maxRunning = maxRunning;
430+
brInfo.lastResults = opInfo->sizes.results;
431+
brInfo.lastOp = opInfo->op;
432+
completed.insert({opInfo->op, brInfo});
433+
assert(opInfo->orderedProducers.size() == opInfo->operands.size());
434+
435+
// Continue to all consumers.
436+
for (Operation *consumer : llvm::reverse(opInfo->consumers)) {
437+
BranchRunning nextRunning{.maxRunning = maxRunning,
438+
.lastResults = opInfo->sizes.results,
439+
.lastOp = opInfo->op};
440+
worklist.emplace_front(&opToInfo.at(consumer), nextRunning);
441+
}
442+
};
443+
444+
worklist.emplace_back(opInfo, brInfo);
445+
while (!worklist.empty()) {
446+
auto [opInfo, brInfo] = worklist.front();
447+
worklist.pop_front();
448+
determineBranchRunningImpl(opInfo, brInfo, completed);
422449
}
423-
assert(opInfo->orderedProducers.size() == opInfo->operands.size());
424450
}
425451

426452
/// Move the operators to the desired lexical order.
427-
void moveToOrder(OpInfo const &fwd) { // NOLINT(misc-no-recursion)
428-
for (Operation *op : fwd.orderedProducers) {
429-
if (op == currFn)
453+
void moveToOrder(OpInfo const &fwd) {
454+
std::deque<std::pair<Operation *, Operation *>>
455+
worklist; // first is the producer,second is the operation it should be
456+
// moved before
457+
for (Operation *producer : fwd.orderedProducers) {
458+
worklist.emplace_back(producer, fwd.op);
459+
}
460+
while (!worklist.empty()) {
461+
auto [producer, beforeOp] = worklist.front();
462+
worklist.pop_front();
463+
if (producer == currFn)
430464
continue; // BlockArguments cannot be moved.
431-
OpInfo &visitFwd = opToInfo.at(op);
465+
OpInfo &visitFwd = opToInfo.at(producer);
432466
if (visitFwd.ordered)
433467
continue;
434468

435-
op->moveBefore(fwd.op);
469+
producer->moveBefore(beforeOp);
436470
visitFwd.ordered = true;
437-
moveToOrder(visitFwd);
471+
for (Operation *nextProducer : llvm::reverse(visitFwd.orderedProducers)) {
472+
worklist.emplace_front(nextProducer, producer);
473+
}
438474
}
439475
}
440476

@@ -455,7 +491,11 @@ class XTenMinimizeLiveTensorsPass
455491
assert(isa<func::ReturnOp>(returnStmt) &&
456492
"A function must terminate with a return stmt");
457493
SmallVector<Value> const retVal = returnStmt->getOperands();
458-
OpInfo fwdInfo = {.op = returnStmt, .operands = retVal, .results = {}};
494+
OpInfo fwdInfo = {.op = returnStmt,
495+
.operands = retVal,
496+
.results = {},
497+
.consumers = {},
498+
.orderedProducers = {}};
459499
auto [opFwdIt, succeeded] =
460500
opToInfo.emplace(returnStmt, std::move(fwdInfo));
461501
OpInfo const &retFwd = opFwdIt->second;
@@ -505,6 +545,8 @@ class XTenMinimizeLiveTensorsPass
505545

506546
private:
507547
/// The analysis results for each operation.
548+
/// Be careful when trying to replace with a different map type, as code in
549+
/// this pass relies on stable iterators/references into the map.
508550
std::map<Operation *, OpInfo> opToInfo;
509551
/// The function being analyzed - needed in places to represent
510552
/// BlockArguments.

0 commit comments

Comments
 (0)