CIRCT 20.0.0git
Loading...
Searching...
No Matches
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
25using namespace mlir;
26using namespace mlir::affine;
27using 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(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:286
void setDistance(Dependence dep, unsigned val)
Definition Problems.h:304
void insertOperation(Operation *op)
Include op in this scheduling problem.
Definition Problems.h:146
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.
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.