CIRCT  19.0.0git
SchedulingAnalysis.cpp
Go to the documentation of this file.
1 //===- SchedulingAnalysis.cpp - scheduling analyses -----------------------===//
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 //
9 // This file implements methods that perform analysis involving scheduling.
10 //
11 //===----------------------------------------------------------------------===//
12 
16 #include "mlir/Dialect/Affine/IR/AffineOps.h"
17 #include "mlir/Dialect/Affine/LoopUtils.h"
18 #include "mlir/Dialect/Func/IR/FuncOps.h"
19 #include "mlir/Dialect/MemRef/IR/MemRef.h"
20 #include "mlir/Dialect/SCF/IR/SCF.h"
21 #include "mlir/IR/BuiltinOps.h"
22 #include "mlir/Pass/AnalysisManager.h"
23 #include <limits>
24 
25 using namespace mlir;
26 using namespace mlir::affine;
27 using namespace circt::scheduling;
28 
29 /// CyclicSchedulingAnalysis constructs a CyclicProblem for each AffineForOp by
30 /// performing a memory dependence analysis and inserting dependences into the
31 /// problem. The client should retrieve the partially complete problem to add
32 /// and associate operator types.
34  Operation *op, AnalysisManager &am) {
35  auto funcOp = cast<func::FuncOp>(op);
36 
37  MemoryDependenceAnalysis &memoryAnalysis =
38  am.getAnalysis<MemoryDependenceAnalysis>();
39 
40  // Only consider innermost loops of perfectly nested AffineForOps.
41  for (auto root : funcOp.getOps<AffineForOp>()) {
42  SmallVector<AffineForOp> nestedLoops;
43  getPerfectlyNestedLoops(nestedLoops, root);
44  analyzeForOp(nestedLoops.back(), memoryAnalysis);
45  }
46 }
47 
49  AffineForOp forOp, MemoryDependenceAnalysis memoryAnalysis) {
50  // Create a cyclic scheduling problem.
51  CyclicProblem problem = CyclicProblem::get(forOp);
52 
53  // Insert memory dependences into the problem.
54  forOp.getBody()->walk([&](Operation *op) {
55  // Insert every operation into the problem.
56  problem.insertOperation(op);
57 
58  ArrayRef<MemoryDependence> dependences = memoryAnalysis.getDependences(op);
59  if (dependences.empty())
60  return;
61 
62  for (MemoryDependence memoryDep : dependences) {
63  // Don't insert a dependence into the problem if there is no dependence.
64  if (!hasDependence(memoryDep.dependenceType))
65  continue;
66 
67  // Insert a dependence into the problem.
68  Problem::Dependence dep(memoryDep.source, op);
69  auto depInserted = problem.insertDependence(dep);
70  assert(succeeded(depInserted));
71  (void)depInserted;
72 
73  // Use the lower bound of the innermost loop for this dependence. This
74  // assumes outer loops execute sequentially, i.e. one iteration of the
75  // inner loop completes before the next iteration is initiated. With
76  // proper analysis and lowerings, this can be relaxed.
77  unsigned distance = *memoryDep.dependenceComponents.back().lb;
78  if (distance > 0)
79  problem.setDistance(dep, distance);
80  }
81  });
82 
83  // Insert conditional dependences into the problem.
84  forOp.getBody()->walk([&](Operation *op) {
85  Block *thenBlock = nullptr;
86  Block *elseBlock = nullptr;
87  if (auto ifOp = dyn_cast<scf::IfOp>(op)) {
88  thenBlock = ifOp.thenBlock();
89  elseBlock = ifOp.elseBlock();
90  } else if (auto ifOp = dyn_cast<AffineIfOp>(op)) {
91  thenBlock = ifOp.getThenBlock();
92  if (ifOp.hasElse())
93  elseBlock = ifOp.getElseBlock();
94  } else {
95  return WalkResult::advance();
96  }
97 
98  // No special handling required for control-only `if`s.
99  if (op->getNumResults() == 0)
100  return WalkResult::skip();
101 
102  // Model the implicit value flow from the `yield` to the `if`'s result(s).
103  Problem::Dependence depThen(thenBlock->getTerminator(), op);
104  auto depInserted = problem.insertDependence(depThen);
105  assert(succeeded(depInserted));
106  (void)depInserted;
107 
108  if (elseBlock) {
109  Problem::Dependence depElse(elseBlock->getTerminator(), op);
110  depInserted = problem.insertDependence(depElse);
111  assert(succeeded(depInserted));
112  (void)depInserted;
113  }
114 
115  return WalkResult::advance();
116  });
117 
118  // Set the anchor for scheduling. Insert dependences from all stores to the
119  // terminator to ensure the problem schedules them before the terminator.
120  auto *anchor = forOp.getBody()->getTerminator();
121  forOp.getBody()->walk([&](Operation *op) {
122  if (!isa<AffineStoreOp, memref::StoreOp>(op))
123  return;
124  Problem::Dependence dep(op, anchor);
125  auto depInserted = problem.insertDependence(dep);
126  assert(succeeded(depInserted));
127  (void)depInserted;
128  });
129 
130  // Handle explicitly computed loop-carried values, i.e. excluding the
131  // induction variable. Insert inter-iteration dependences from the definers of
132  // "iter_args" to their users.
133  if (unsigned nIterArgs = anchor->getNumOperands(); nIterArgs > 0) {
134  auto iterArgs = forOp.getRegionIterArgs();
135  for (unsigned i = 0; i < nIterArgs; ++i) {
136  Operation *iterArgDefiner = anchor->getOperand(i).getDefiningOp();
137  // If it's not an operation, we don't need to model the dependence.
138  if (!iterArgDefiner)
139  continue;
140 
141  for (Operation *iterArgUser : iterArgs[i].getUsers()) {
142  Problem::Dependence dep(iterArgDefiner, iterArgUser);
143  auto depInserted = problem.insertDependence(dep);
144  assert(succeeded(depInserted));
145  (void)depInserted;
146 
147  // Values always flow between subsequent iterations.
148  problem.setDistance(dep, 1);
149  }
150  }
151  }
152 
153  // Store the partially complete problem.
154  problems.insert(std::pair<Operation *, CyclicProblem>(forOp, problem));
155 }
156 
159  auto problem = problems.find(forOp);
160  assert(problem != problems.end() && "expected problem to exist");
161  return problem->second;
162 }
assert(baseType &&"element must be base type")
This class models a cyclic scheduling problem.
Definition: Problems.h:292
void setDistance(Dependence dep, unsigned val)
Definition: Problems.h:305
void insertOperation(Operation *op)
Include op in this scheduling problem.
Definition: Problems.h:152
LogicalResult insertDependence(Dependence dep)
Include dep in the scheduling problem.
Definition: Problems.cpp:26
A wrapper class to uniformly handle def-use and auxiliary dependence edges.
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:54
CyclicSchedulingAnalysis(Operation *funcOp, mlir::AnalysisManager &am)
CyclicSchedulingAnalysis constructs a CyclicProblem for each AffineForOp by performing a memory depen...
void analyzeForOp(mlir::affine::AffineForOp forOp, MemoryDependenceAnalysis memoryAnalysis)
scheduling::CyclicProblem & getProblem(mlir::affine::AffineForOp forOp)
MemoryDependenceAnalysis traverses any AffineForOps in the FuncOp body and checks for affine memory a...
ArrayRef< MemoryDependence > getDependences(Operation *)
Returns the dependences, if any, that the given Operation depends on.
MemoryDependence captures a dependence from one memory operation to another.