CIRCT  20.0.0git
LPSchedulers.cpp
Go to the documentation of this file.
1 //===- LPSchedulers.cpp - Schedulers using external LP solvers ------------===//
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 // Implementation of linear programming-based schedulers using external solvers
10 // via OR-Tools.
11 //
12 //===----------------------------------------------------------------------===//
13 
15 
16 #include "mlir/IR/Operation.h"
17 
18 #include "ortools/linear_solver/linear_solver.h"
19 
20 using namespace circt;
21 using namespace circt::scheduling;
22 using namespace operations_research;
23 
24 LogicalResult scheduling::scheduleLP(Problem &prob, Operation *lastOp) {
25  Operation *containingOp = prob.getContainingOp();
26  if (!prob.hasOperation(lastOp))
27  return containingOp->emitError("problem does not include last operation");
28 
29  MPSolver::OptimizationProblemType problemType;
30  if (!MPSolver::ParseSolverType("GLOP_LINEAR_PROGRAMMING", &problemType) ||
31  !MPSolver::SupportsProblemType(problemType))
32  return containingOp->emitError("GLOP is unvailable");
33 
34  MPSolver solver("Problem", problemType);
35  double infinity = solver.infinity();
36 
37  // Create start time variables.
38  DenseMap<Operation *, MPVariable *> vars;
39  unsigned i = 0;
40  for (auto *op : prob.getOperations()) {
41  vars[op] = solver.MakeNumVar(0, infinity, (Twine("t_") + Twine(i)).str());
42  ++i;
43  }
44 
45  // The objective is to minimize the start time of the last operation.
46  MPObjective *objective = solver.MutableObjective();
47  objective->SetCoefficient(vars[lastOp], 1);
48  objective->SetMinimization();
49 
50  // Construct a linear constraint for each dependence.
51  for (auto *op : prob.getOperations())
52  for (auto dep : prob.getDependences(op)) {
53  Operation *src = dep.getSource();
54  Operation *dst = dep.getDestination();
55  if (src == dst)
56  return containingOp->emitError() << "dependence cycle detected";
57 
58  // t_src + t.linkedOperatorType.latency <= t_dst
59  // <=> 1 * t_src + -1 * t_dst <= -latency
60  unsigned latency = *prob.getLatency(*prob.getLinkedOperatorType(src));
61  MPConstraint *constraint =
62  solver.MakeRowConstraint(-infinity, -((double)latency));
63  constraint->SetCoefficient(vars[src], 1);
64  constraint->SetCoefficient(vars[dst], -1);
65  }
66 
67  // Invoke solver. The LP is infeasible if the scheduling problem contained
68  // dependence cycles. Otherwise, we expect the result to be optimal.
69  MPSolver::ResultStatus result = solver.Solve();
70  if (result == MPSolver::INFEASIBLE)
71  return containingOp->emitError() << "dependence cycle detected";
72  assert(result == MPSolver::OPTIMAL);
73 
74  // Retrieve start times.
75  for (auto *op : prob.getOperations())
76  prob.setStartTime(op, std::round(vars[op]->solution_value()));
77 
78  return success();
79 }
80 
81 LogicalResult scheduling::scheduleLP(CyclicProblem &prob, Operation *lastOp) {
82  Operation *containingOp = prob.getContainingOp();
83  if (!prob.hasOperation(lastOp))
84  return containingOp->emitError("problem does not include last operation");
85 
86  MPSolver::OptimizationProblemType probType;
87  if (!MPSolver::ParseSolverType("CBC_MIXED_INTEGER_PROGRAMMING", &probType) ||
88  !MPSolver::SupportsProblemType(probType))
89  return containingOp->emitError("Cbc is unavailable");
90 
91  MPSolver solver("CyclicProblem", probType);
92  double infinity = solver.infinity();
93 
94  // Create II variable.
95  MPVariable *ii = solver.MakeIntVar(1, infinity, "II");
96 
97  // Create start time variables (and collect latencies to compute upper bound
98  // for the latest start time).
99  DenseMap<Operation *, MPVariable *> t;
100  unsigned upperBound = 0;
101  unsigned i = 0;
102  for (auto *op : prob.getOperations()) {
103  t[op] = solver.MakeIntVar(0, infinity, (Twine("t_") + Twine(i)).str());
104  upperBound += *prob.getLatency(*prob.getLinkedOperatorType(op));
105  ++i;
106  }
107 
108  // The objective is to minimize the II as well as the start time of the last
109  // operation. We use a weighted sum to encode both objectives in a single
110  // linear expression.
111  MPObjective *objective = solver.MutableObjective();
112  objective->SetCoefficient(ii, upperBound);
113  objective->SetCoefficient(t[lastOp], 1);
114  objective->SetMinimization();
115 
116  // Construct a linear constraint for each dependence.
117  for (auto *op : prob.getOperations())
118  for (auto dep : prob.getDependences(op)) {
119  Operation *src = dep.getSource();
120  Operation *dst = dep.getDestination();
121 
122  // t_src + t_src.linkedOperatorType.latency <= t_dst + e.distance * II
123  // <=> 1 * t_src + -1 * t_dst + -e.distance * II <= -latency
124  unsigned latency = *prob.getLatency(*prob.getLinkedOperatorType(src));
125  MPConstraint *constraint =
126  solver.MakeRowConstraint(-infinity, -((double)latency));
127  if (src != dst) { // Handle self-arcs.
128  constraint->SetCoefficient(t[src], 1);
129  constraint->SetCoefficient(t[dst], -1);
130  }
131  constraint->SetCoefficient(ii,
132  -((double)prob.getDistance(dep).value_or(0)));
133  }
134 
135  // Invoke solver. The ILP is infeasible if the scheduling problem contained
136  // dependence graph contains cycles that do not include at least one edge with
137  // a non-zero distance. Otherwise, we expect the result to be optimal.
138  MPSolver::ResultStatus result = solver.Solve();
139  if (result == MPSolver::INFEASIBLE)
140  return containingOp->emitError() << "dependence cycle detected";
141  assert(result == MPSolver::OPTIMAL);
142 
143  // Retrieve II and start times.
144  prob.setInitiationInterval(std::round(ii->solution_value()));
145  for (auto *op : prob.getOperations())
146  prob.setStartTime(op, std::round(t[op]->solution_value()));
147 
148  return success();
149 }
assert(baseType &&"element must be base type")
This class models a cyclic scheduling problem.
Definition: Problems.h:286
void setInitiationInterval(unsigned val)
Definition: Problems.h:312
std::optional< unsigned > getDistance(Dependence dep)
The distance determines whether a dependence has to be satisfied in the same iteration (distance=0 or...
Definition: Problems.h:301
This class models the most basic scheduling problem.
Definition: Problems.h:75
std::optional< OperatorType > getLinkedOperatorType(Operation *op)
The linked operator type provides the runtime characteristics for op.
Definition: Problems.h:196
bool hasOperation(Operation *op)
Return true if op is part of this problem.
Definition: Problems.h:170
void setStartTime(Operation *op, unsigned val)
Definition: Problems.h:215
DependenceRange getDependences(Operation *op)
Return a range object to transparently iterate over op's incoming 1) implicit def-use dependences (ba...
Definition: Problems.cpp:53
const OperationSet & getOperations()
Return the set of operations.
Definition: Problems.h:172
std::optional< unsigned > getLatency(OperatorType opr)
The latency is the number of cycles opr needs to compute its result.
Definition: Problems.h:204
Operation * getContainingOp()
Return the operation containing this problem, e.g. to emit diagnostics.
Definition: Problems.h:165
LogicalResult scheduleLP(Problem &prob, Operation *lastOp)
Solve the basic problem using linear programming and an external LP solver.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition: DebugAnalysis.h:21