MLIR  19.0.0git
TopologicalSortUtils.cpp
Go to the documentation of this file.
1 //===- TopologicalSortUtils.h - Topological sort utilities ------*- C++ -*-===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
10 #include "mlir/IR/OpDefinition.h"
11 
12 using namespace mlir;
13 
14 /// Return `true` if the given operation is ready to be scheduled.
15 static bool isOpReady(Operation *op, DenseSet<Operation *> &unscheduledOps,
16  function_ref<bool(Value, Operation *)> isOperandReady) {
17  // An operation is ready to be scheduled if all its operands are ready. An
18  // operation is ready if:
19  const auto isReady = [&](Value value) {
20  // - the user-provided callback marks it as ready,
21  if (isOperandReady && isOperandReady(value, op))
22  return true;
23  Operation *parent = value.getDefiningOp();
24  // - it is a block argument,
25  if (!parent)
26  return true;
27  // - or it is not defined by an unscheduled op (and also not nested within
28  // an unscheduled op).
29  do {
30  // Stop traversal when op under examination is reached.
31  if (parent == op)
32  return true;
33  if (unscheduledOps.contains(parent))
34  return false;
35  } while ((parent = parent->getParentOp()));
36  // No unscheduled op found.
37  return true;
38  };
39 
40  // An operation is recursively ready to be scheduled of it and its nested
41  // operations are ready.
42  WalkResult readyToSchedule = op->walk([&](Operation *nestedOp) {
43  return llvm::all_of(nestedOp->getOperands(),
44  [&](Value operand) { return isReady(operand); })
47  });
48  return !readyToSchedule.wasInterrupted();
49 }
50 
53  function_ref<bool(Value, Operation *)> isOperandReady) {
54  if (ops.empty())
55  return true;
56 
57  // The set of operations that have not yet been scheduled.
58  DenseSet<Operation *> unscheduledOps;
59  // Mark all operations as unscheduled.
60  for (Operation &op : ops)
61  unscheduledOps.insert(&op);
62 
63  Block::iterator nextScheduledOp = ops.begin();
64  Block::iterator end = ops.end();
65 
66  bool allOpsScheduled = true;
67  while (!unscheduledOps.empty()) {
68  bool scheduledAtLeastOnce = false;
69 
70  // Loop over the ops that are not sorted yet, try to find the ones "ready",
71  // i.e. the ones for which there aren't any operand produced by an op in the
72  // set, and "schedule" it (move it before the `nextScheduledOp`).
73  for (Operation &op :
74  llvm::make_early_inc_range(llvm::make_range(nextScheduledOp, end))) {
75  if (!isOpReady(&op, unscheduledOps, isOperandReady))
76  continue;
77 
78  // Schedule the operation by moving it to the start.
79  unscheduledOps.erase(&op);
80  op.moveBefore(block, nextScheduledOp);
81  scheduledAtLeastOnce = true;
82  // Move the iterator forward if we schedule the operation at the front.
83  if (&op == &*nextScheduledOp)
84  ++nextScheduledOp;
85  }
86  // If no operations were scheduled, give up and advance the iterator.
87  if (!scheduledAtLeastOnce) {
88  allOpsScheduled = false;
89  unscheduledOps.erase(&*nextScheduledOp);
90  ++nextScheduledOp;
91  }
92  }
93 
94  return allOpsScheduled;
95 }
96 
98  Block *block, function_ref<bool(Value, Operation *)> isOperandReady) {
99  if (block->empty())
100  return true;
101  if (block->back().hasTrait<OpTrait::IsTerminator>())
102  return sortTopologically(block, block->without_terminator(),
103  isOperandReady);
104  return sortTopologically(block, *block, isOperandReady);
105 }
106 
109  function_ref<bool(Value, Operation *)> isOperandReady) {
110  if (ops.empty())
111  return true;
112 
113  // The set of operations that have not yet been scheduled.
114  DenseSet<Operation *> unscheduledOps;
115 
116  // Mark all operations as unscheduled.
117  for (Operation *op : ops)
118  unscheduledOps.insert(op);
119 
120  unsigned nextScheduledOp = 0;
121 
122  bool allOpsScheduled = true;
123  while (!unscheduledOps.empty()) {
124  bool scheduledAtLeastOnce = false;
125 
126  // Loop over the ops that are not sorted yet, try to find the ones "ready",
127  // i.e. the ones for which there aren't any operand produced by an op in the
128  // set, and "schedule" it (swap it with the op at `nextScheduledOp`).
129  for (unsigned i = nextScheduledOp; i < ops.size(); ++i) {
130  if (!isOpReady(ops[i], unscheduledOps, isOperandReady))
131  continue;
132 
133  // Schedule the operation by moving it to the start.
134  unscheduledOps.erase(ops[i]);
135  std::swap(ops[i], ops[nextScheduledOp]);
136  scheduledAtLeastOnce = true;
137  ++nextScheduledOp;
138  }
139 
140  // If no operations were scheduled, just schedule the first op and continue.
141  if (!scheduledAtLeastOnce) {
142  allOpsScheduled = false;
143  unscheduledOps.erase(ops[nextScheduledOp++]);
144  }
145  }
146 
147  return allOpsScheduled;
148 }
static bool isOpReady(Operation *op, DenseSet< Operation * > &unscheduledOps, function_ref< bool(Value, Operation *)> isOperandReady)
Return true if the given operation is ready to be scheduled.
Block represents an ordered list of Operations.
Definition: Block.h:30
OpListType::iterator iterator
Definition: Block.h:137
bool empty()
Definition: Block.h:145
Operation & back()
Definition: Block.h:149
iterator_range< iterator > without_terminator()
Return an iterator range over the operation within this block excluding the terminator operation at t...
Definition: Block.h:206
This class provides the API for ops that are known to be terminators.
Definition: OpDefinition.h:764
Operation is the basic unit of execution within MLIR.
Definition: Operation.h:88
bool hasTrait()
Returns true if the operation was registered with a particular trait, e.g.
Definition: Operation.h:745
std::enable_if_t< llvm::function_traits< std::decay_t< FnT > >::num_args==1, RetT > walk(FnT &&callback)
Walk the operation by calling the callback for each nested operation (including this one),...
Definition: Operation.h:793
Operation * getParentOp()
Returns the closest surrounding operation that contains this operation or nullptr if this is a top-le...
Definition: Operation.h:234
operand_range getOperands()
Returns an iterator on the underlying Value's.
Definition: Operation.h:373
void moveBefore(Operation *existingOp)
Unlink this operation from its current block and insert it right before existingOp which may be in th...
Definition: Operation.cpp:555
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition: Value.h:96
A utility result that is used to signal how to proceed with an ongoing walk:
Definition: Visitors.h:34
static WalkResult advance()
Definition: Visitors.h:52
bool wasInterrupted() const
Returns true if the walk was interrupted.
Definition: Visitors.h:56
static WalkResult interrupt()
Definition: Visitors.h:51
Include the generated interface declarations.
bool computeTopologicalSorting(MutableArrayRef< Operation * > ops, function_ref< bool(Value, Operation *)> isOperandReady=nullptr)
Compute a topological ordering of the given ops.
bool sortTopologically(Block *block, iterator_range< Block::iterator > ops, function_ref< bool(Value, Operation *)> isOperandReady=nullptr)
Given a block, sort a range operations in said block in topological order.