CIRCT  19.0.0git
SimplexSchedulers.cpp
Go to the documentation of this file.
1 //===- SimplexSchedulers.cpp - Linear programming-based schedulers --------===//
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 with a built-in simplex
10 // solver.
11 //
12 //===----------------------------------------------------------------------===//
13 
16 
17 #include "mlir/IR/Operation.h"
18 
19 #include "llvm/ADT/DenseMap.h"
20 #include "llvm/Support/Debug.h"
21 #include "llvm/Support/Format.h"
22 
23 #include <algorithm>
24 #include <limits>
25 
26 #define DEBUG_TYPE "simplex-schedulers"
27 
28 using namespace circt;
29 using namespace circt::scheduling;
30 
31 using llvm::dbgs;
32 using llvm::format;
33 
34 namespace {
35 
36 /// This class provides a framework to model certain scheduling problems as
37 /// lexico-parametric linear programs (LP), which are then solved with an
38 /// extended version of the dual simplex algorithm.
39 ///
40 /// The approach is described in:
41 /// [1] B. D. de Dinechin, "Simplex Scheduling: More than Lifetime-Sensitive
42 /// Instruction Scheduling", PRISM 1994.22, 1994.
43 /// [2] B. D. de Dinechin, "Fast Modulo Scheduling Under the Simplex Scheduling
44 /// Framework", PRISM 1995.01, 1995.
45 ///
46 /// Resource-free scheduling problems (called "central problems" in the papers)
47 /// have an *integer* linear programming formulation with a totally unimodular
48 /// constraint matrix. Such ILPs can however be solved optimally in polynomial
49 /// time with a (non-integer) LP solver (such as the simplex algorithm), as the
50 /// LP solution is guaranteed to be integer. Note that this is the same idea as
51 /// used by SDC-based schedulers.
52 class SimplexSchedulerBase {
53 protected:
54  /// The objective is to minimize the start time of this operation.
55  Operation *lastOp;
56 
57  /// S is part of a mechanism to assign fixed values to the LP variables.
58  int parameterS;
59 
60  /// T represents the initiation interval (II). Its minimally-feasible value is
61  /// computed by the algorithm.
62  int parameterT;
63 
64  /// The simplex tableau is the algorithm's main data structure.
65  /// The dashed parts always contain the zero respectively the identity matrix,
66  /// and therefore are not stored explicitly.
67  ///
68  /// ◀───nColumns────▶
69  /// nParameters────┐
70  /// ◀─┴─▶
71  /// ┌─────┬───────────┬ ─ ─ ─ ─ ┐
72  /// ▲│. . .│. . ... . .│ 0 ▲
73  /// nObjectives││. . .│. . ... . .│ │ │
74  /// ▼│. . .│. . ... . .│ │
75  /// ├─────┼───────────┼ ─ ─ ─ ─ ┤ │
76  /// firstConstraintRow > │. . .│. . ... . .│1 │nRows
77  /// │. . .│. . ... . .│ 1 │ │
78  /// │. . .│. . ... . .│ 1 │
79  /// │. . .│. . ... . .│ 1 │ │
80  /// │. . .│. . ... . .│ 1 ▼
81  /// └─────┴───────────┴ ─ ─ ─ ─ ┘
82  /// parameter1Column ^
83  /// parameterSColumn ^
84  /// parameterTColumn ^
85  /// firstNonBasicVariableColumn ^
86  /// ─────────── ──────────
87  /// nonBasicVariables basicVariables
88  SmallVector<SmallVector<int>> tableau;
89 
90  /// During the pivot operation, one column in the elided part of the tableau
91  /// is modified; this vector temporarily catches the changes.
92  SmallVector<int> implicitBasicVariableColumnVector;
93 
94  /// The linear program models the operations' start times as variables, which
95  /// we identify here as 0, ..., |ops|-1.
96  /// Additionally, for each dependence (precisely, the inequality modeling the
97  /// precedence constraint), a slack variable is required; these are identified
98  /// as |ops|, ..., |ops|+|deps|-1.
99  ///
100  /// This vector stores the numeric IDs of non-basic variables. A variable's
101  /// index *i* in this vector corresponds to the tableau *column*
102  /// `firstNonBasicVariableColumn`+*i*.
103  SmallVector<unsigned> nonBasicVariables;
104 
105  /// This vector store the numeric IDs of basic variables. A variable's index
106  /// *i* in this vector corresponds to the tableau *row*
107  /// `firstConstraintRow`+*i*.
108  SmallVector<unsigned> basicVariables;
109 
110  /// Used to conveniently retrieve an operation's start time variable. The
111  /// alternative would be to find the op's index in the problem's list of
112  /// operations.
113  DenseMap<Operation *, unsigned> startTimeVariables;
114 
115  /// This vector keeps track of the current locations (i.e. row or column) of
116  /// a start time variable in the tableau. We encode column numbers as positive
117  /// integers, and row numbers as negative integers. We do not track the slack
118  /// variables.
119  SmallVector<int> startTimeLocations;
120 
121  /// Non-basic variables can be "frozen" to a specific value, which prevents
122  /// them from being pivoted into basis again.
123  DenseMap<unsigned, unsigned> frozenVariables;
124 
125  /// Number of rows in the tableau = |obj| + |deps|.
126  unsigned nRows;
127  /// Number of explicitly stored columns in the tableau = |params| + |ops|.
128  unsigned nColumns;
129 
130  // Number of objective rows.
131  unsigned nObjectives;
132  /// All other rows encode linear constraints.
133  unsigned &firstConstraintRow = nObjectives;
134 
135  // Number of parameters (fixed for now).
136  static constexpr unsigned nParameters = 3;
137  /// The first column corresponds to the always-one "parameter" in u = (1,S,T).
138  static constexpr unsigned parameter1Column = 0;
139  /// The second column corresponds to the variable-freezing parameter S.
140  static constexpr unsigned parameterSColumn = 1;
141  /// The third column corresponds to the parameter T, i.e. the current II.
142  static constexpr unsigned parameterTColumn = 2;
143  /// All other (explicitly stored) columns represent non-basic variables.
144  static constexpr unsigned firstNonBasicVariableColumn = nParameters;
145 
146  /// Allow subclasses to collect additional constraints that are not part of
147  /// the input problem, but should be modeled in the linear problem.
148  SmallVector<Problem::Dependence> additionalConstraints;
149 
150  virtual Problem &getProblem() = 0;
151  virtual LogicalResult checkLastOp();
152  virtual bool fillObjectiveRow(SmallVector<int> &row, unsigned obj);
153  virtual void fillConstraintRow(SmallVector<int> &row,
154  Problem::Dependence dep);
155  virtual void fillAdditionalConstraintRow(SmallVector<int> &row,
156  Problem::Dependence dep);
157  void buildTableau();
158 
159  int getParametricConstant(unsigned row);
160  SmallVector<int> getObjectiveVector(unsigned column);
161  std::optional<unsigned> findDualPivotRow();
162  std::optional<unsigned> findDualPivotColumn(unsigned pivotRow,
163  bool allowPositive = false);
164  std::optional<unsigned> findPrimalPivotColumn();
165  std::optional<unsigned> findPrimalPivotRow(unsigned pivotColumn);
166  void multiplyRow(unsigned row, int factor);
167  void addMultipleOfRow(unsigned sourceRow, int factor, unsigned targetRow);
168  void pivot(unsigned pivotRow, unsigned pivotColumn);
169  LogicalResult solveTableau();
170  LogicalResult restoreDualFeasibility();
171  bool isInBasis(unsigned startTimeVariable);
172  unsigned freeze(unsigned startTimeVariable, unsigned timeStep);
173  void translate(unsigned column, int factor1, int factorS, int factorT);
174  LogicalResult scheduleAt(unsigned startTimeVariable, unsigned timeStep);
175  void moveBy(unsigned startTimeVariable, unsigned amount);
176  unsigned getStartTime(unsigned startTimeVariable);
177 
178  void dumpTableau();
179 
180 public:
181  explicit SimplexSchedulerBase(Operation *lastOp) : lastOp(lastOp) {}
182  virtual ~SimplexSchedulerBase() = default;
183  virtual LogicalResult schedule() = 0;
184 };
185 
186 /// This class solves the basic, acyclic `Problem`.
187 class SimplexScheduler : public SimplexSchedulerBase {
188 private:
189  Problem &prob;
190 
191 protected:
192  Problem &getProblem() override { return prob; }
193 
194 public:
195  SimplexScheduler(Problem &prob, Operation *lastOp)
196  : SimplexSchedulerBase(lastOp), prob(prob) {}
197 
198  LogicalResult schedule() override;
199 };
200 
201 /// This class solves the resource-free `CyclicProblem`. The optimal initiation
202 /// interval (II) is determined as a side product of solving the parametric
203 /// problem, and corresponds to the "RecMII" (= recurrence-constrained minimum
204 /// II) usually considered as one component in the lower II bound used by modulo
205 /// schedulers.
206 class CyclicSimplexScheduler : public SimplexSchedulerBase {
207 private:
208  CyclicProblem &prob;
209 
210 protected:
211  Problem &getProblem() override { return prob; }
212  void fillConstraintRow(SmallVector<int> &row,
213  Problem::Dependence dep) override;
214 
215 public:
216  CyclicSimplexScheduler(CyclicProblem &prob, Operation *lastOp)
217  : SimplexSchedulerBase(lastOp), prob(prob) {}
218  LogicalResult schedule() override;
219 };
220 
221 // This class solves acyclic, resource-constrained `SharedOperatorsProblem` with
222 // a simplified version of the iterative heuristic presented in [2].
223 class SharedOperatorsSimplexScheduler : public SimplexSchedulerBase {
224 private:
226 
227 protected:
228  Problem &getProblem() override { return prob; }
229 
230 public:
231  SharedOperatorsSimplexScheduler(SharedOperatorsProblem &prob,
232  Operation *lastOp)
233  : SimplexSchedulerBase(lastOp), prob(prob) {}
234  LogicalResult schedule() override;
235 };
236 
237 // This class solves the `ModuloProblem` using the iterative heuristic presented
238 // in [2].
239 class ModuloSimplexScheduler : public CyclicSimplexScheduler {
240 private:
241  struct MRT {
242  ModuloSimplexScheduler &sched;
243 
245  using ReverseTableType = SmallDenseMap<Operation *, unsigned>;
248 
249  explicit MRT(ModuloSimplexScheduler &sched) : sched(sched) {}
250  LogicalResult enter(Operation *op, unsigned timeStep);
251  void release(Operation *op);
252  };
253 
254  ModuloProblem &prob;
255  SmallVector<unsigned> asapTimes, alapTimes;
256  SmallVector<Operation *> unscheduled, scheduled;
257  MRT mrt;
258 
259 protected:
260  Problem &getProblem() override { return prob; }
261  LogicalResult checkLastOp() override;
262  enum { OBJ_LATENCY = 0, OBJ_AXAP /* i.e. either ASAP or ALAP */ };
263  bool fillObjectiveRow(SmallVector<int> &row, unsigned obj) override;
264  void updateMargins();
265  void scheduleOperation(Operation *n);
266  unsigned computeResMinII();
267 
268 public:
269  ModuloSimplexScheduler(ModuloProblem &prob, Operation *lastOp)
270  : CyclicSimplexScheduler(prob, lastOp), prob(prob), mrt(*this) {}
271  LogicalResult schedule() override;
272 };
273 
274 // This class solves the `ChainingProblem` by relying on pre-computed
275 // chain-breaking constraints.
276 class ChainingSimplexScheduler : public SimplexSchedulerBase {
277 private:
278  ChainingProblem &prob;
279  float cycleTime;
280 
281 protected:
282  Problem &getProblem() override { return prob; }
283  void fillAdditionalConstraintRow(SmallVector<int> &row,
284  Problem::Dependence dep) override;
285 
286 public:
287  ChainingSimplexScheduler(ChainingProblem &prob, Operation *lastOp,
288  float cycleTime)
289  : SimplexSchedulerBase(lastOp), prob(prob), cycleTime(cycleTime) {}
290  LogicalResult schedule() override;
291 };
292 
293 // This class solves the resource-free `ChainingCyclicProblem` by relying on
294 // pre-computed chain-breaking constraints. The optimal initiation interval (II)
295 // is determined as a side product of solving the parametric problem, and
296 // corresponds to the "RecMII" (= recurrence-constrained minimum II) usually
297 // considered as one component in the lower II bound used by modulo schedulers.
298 class ChainingCyclicSimplexScheduler : public SimplexSchedulerBase {
299 private:
300  ChainingCyclicProblem &prob;
301  float cycleTime;
302 
303 protected:
304  Problem &getProblem() override { return prob; }
305  void fillConstraintRow(SmallVector<int> &row,
306  Problem::Dependence dep) override;
307  void fillAdditionalConstraintRow(SmallVector<int> &row,
308  Problem::Dependence dep) override;
309 
310 public:
311  ChainingCyclicSimplexScheduler(ChainingCyclicProblem &prob, Operation *lastOp,
312  float cycleTime)
313  : SimplexSchedulerBase(lastOp), prob(prob), cycleTime(cycleTime) {}
314  LogicalResult schedule() override;
315 };
316 
317 } // anonymous namespace
318 
319 //===----------------------------------------------------------------------===//
320 // SimplexSchedulerBase
321 //===----------------------------------------------------------------------===//
322 
323 LogicalResult SimplexSchedulerBase::checkLastOp() {
324  auto &prob = getProblem();
325  if (!prob.hasOperation(lastOp))
326  return prob.getContainingOp()->emitError(
327  "problem does not include last operation");
328  return success();
329 }
330 
331 bool SimplexSchedulerBase::fillObjectiveRow(SmallVector<int> &row,
332  unsigned obj) {
333  assert(obj == 0);
334  // Minimize start time of user-specified last operation.
335  row[startTimeLocations[startTimeVariables[lastOp]]] = 1;
336  return false;
337 }
338 
339 void SimplexSchedulerBase::fillConstraintRow(SmallVector<int> &row,
340  Problem::Dependence dep) {
341  auto &prob = getProblem();
342  Operation *src = dep.getSource();
343  Operation *dst = dep.getDestination();
344  unsigned latency = *prob.getLatency(*prob.getLinkedOperatorType(src));
345  row[parameter1Column] = -latency; // note the negation
346  if (src != dst) { // note that these coefficients just zero out in self-arcs.
347  row[startTimeLocations[startTimeVariables[src]]] = 1;
348  row[startTimeLocations[startTimeVariables[dst]]] = -1;
349  }
350 }
351 
352 void SimplexSchedulerBase::fillAdditionalConstraintRow(
353  SmallVector<int> &row, Problem::Dependence dep) {
354  // Handling is subclass-specific, so do nothing by default.
355  (void)row;
356  (void)dep;
357 }
358 
359 void SimplexSchedulerBase::buildTableau() {
360  auto &prob = getProblem();
361 
362  // The initial tableau is constructed so that operations' start time variables
363  // are out of basis, whereas all slack variables are in basis. We will number
364  // them accordingly.
365  unsigned var = 0;
366 
367  // Assign column and variable numbers to the operations' start times.
368  for (auto *op : prob.getOperations()) {
369  nonBasicVariables.push_back(var);
370  startTimeVariables[op] = var;
371  startTimeLocations.push_back(firstNonBasicVariableColumn + var);
372  ++var;
373  }
374 
375  // one column for each parameter (1,S,T), and for all operations
376  nColumns = nParameters + nonBasicVariables.size();
377 
378  // Helper to grow both the tableau and the implicit column vector.
379  auto addRow = [&]() -> SmallVector<int> & {
380  implicitBasicVariableColumnVector.push_back(0);
381  return tableau.emplace_back(nColumns, 0);
382  };
383 
384  // Set up the objective rows.
385  nObjectives = 0;
386  bool hasMoreObjectives;
387  do {
388  auto &objRowVec = addRow();
389  hasMoreObjectives = fillObjectiveRow(objRowVec, nObjectives);
390  ++nObjectives;
391  } while (hasMoreObjectives);
392 
393  // Now set up rows/constraints for the dependences.
394  for (auto *op : prob.getOperations()) {
395  for (auto &dep : prob.getDependences(op)) {
396  auto &consRowVec = addRow();
397  fillConstraintRow(consRowVec, dep);
398  basicVariables.push_back(var);
399  ++var;
400  }
401  }
402  for (auto &dep : additionalConstraints) {
403  auto &consRowVec = addRow();
404  fillAdditionalConstraintRow(consRowVec, dep);
405  basicVariables.push_back(var);
406  ++var;
407  }
408 
409  // one row per objective + one row per dependence
410  nRows = tableau.size();
411 }
412 
413 int SimplexSchedulerBase::getParametricConstant(unsigned row) {
414  auto &rowVec = tableau[row];
415  // Compute the dot-product ~B[row] * u between the constant matrix and the
416  // parameter vector.
417  return rowVec[parameter1Column] + rowVec[parameterSColumn] * parameterS +
418  rowVec[parameterTColumn] * parameterT;
419 }
420 
421 SmallVector<int> SimplexSchedulerBase::getObjectiveVector(unsigned column) {
422  SmallVector<int> objVec;
423  // Extract the column vector C^T[column] from the cost matrix.
424  for (unsigned obj = 0; obj < nObjectives; ++obj)
425  objVec.push_back(tableau[obj][column]);
426  return objVec;
427 }
428 
429 std::optional<unsigned> SimplexSchedulerBase::findDualPivotRow() {
430  // Find the first row in which the parametric constant is negative.
431  for (unsigned row = firstConstraintRow; row < nRows; ++row)
432  if (getParametricConstant(row) < 0)
433  return row;
434 
435  return std::nullopt;
436 }
437 
438 std::optional<unsigned>
439 SimplexSchedulerBase::findDualPivotColumn(unsigned pivotRow,
440  bool allowPositive) {
441  SmallVector<int> maxQuot(nObjectives, std::numeric_limits<int>::min());
442  std::optional<unsigned> pivotCol;
443 
444  // Look for non-zero entries in the constraint matrix (~A part of the
445  // tableau). If multiple candidates exist, take the one corresponding to the
446  // lexicographical maximum (over the objective rows) of the quotients:
447  // tableau[<objective row>][col] / pivotCand
448  for (unsigned col = firstNonBasicVariableColumn; col < nColumns; ++col) {
449  if (frozenVariables.count(
450  nonBasicVariables[col - firstNonBasicVariableColumn]))
451  continue;
452 
453  int pivotCand = tableau[pivotRow][col];
454  // Only negative candidates bring us closer to the optimal solution.
455  // However, when freezing variables to a certain value, we accept that the
456  // value of the objective function degrades.
457  if (pivotCand < 0 || (allowPositive && pivotCand > 0)) {
458  // The constraint matrix has only {-1, 0, 1} entries by construction.
459  assert(pivotCand * pivotCand == 1);
460 
461  SmallVector<int> quot;
462  for (unsigned obj = 0; obj < nObjectives; ++obj)
463  quot.push_back(tableau[obj][col] / pivotCand);
464 
465  if (std::lexicographical_compare(maxQuot.begin(), maxQuot.end(),
466  quot.begin(), quot.end())) {
467  maxQuot = quot;
468  pivotCol = col;
469  }
470  }
471  }
472 
473  return pivotCol;
474 }
475 
476 std::optional<unsigned> SimplexSchedulerBase::findPrimalPivotColumn() {
477  // Find the first lexico-negative column in the cost matrix.
478  SmallVector<int> zeroVec(nObjectives, 0);
479  for (unsigned col = firstNonBasicVariableColumn; col < nColumns; ++col) {
480  if (frozenVariables.count(
481  nonBasicVariables[col - firstNonBasicVariableColumn]))
482  continue;
483 
484  SmallVector<int> objVec = getObjectiveVector(col);
485  if (std::lexicographical_compare(objVec.begin(), objVec.end(),
486  zeroVec.begin(), zeroVec.end()))
487  return col;
488  }
489 
490  return std::nullopt;
491 }
492 
493 std::optional<unsigned>
494 SimplexSchedulerBase::findPrimalPivotRow(unsigned pivotColumn) {
495  int minQuot = std::numeric_limits<int>::max();
496  std::optional<unsigned> pivotRow;
497 
498  // Look for positive entries in the constraint matrix (~A part of the
499  // tableau). If multiple candidates exist, take the one corresponding to the
500  // minimum of the quotient:
501  // parametricConstant(row) / pivotCand
502  for (unsigned row = firstConstraintRow; row < nRows; ++row) {
503  int pivotCand = tableau[row][pivotColumn];
504  if (pivotCand > 0) {
505  // The constraint matrix has only {-1, 0, 1} entries by construction.
506  assert(pivotCand == 1);
507  int quot = getParametricConstant(row) / pivotCand;
508  if (quot < minQuot) {
509  minQuot = quot;
510  pivotRow = row;
511  }
512  }
513  }
514 
515  return pivotRow;
516 }
517 
518 void SimplexSchedulerBase::multiplyRow(unsigned row, int factor) {
519  assert(factor != 0);
520  for (unsigned col = 0; col < nColumns; ++col)
521  tableau[row][col] *= factor;
522  // Also multiply the corresponding entry in the temporary column vector.
523  implicitBasicVariableColumnVector[row] *= factor;
524 }
525 
526 void SimplexSchedulerBase::addMultipleOfRow(unsigned sourceRow, int factor,
527  unsigned targetRow) {
528  assert(factor != 0 && sourceRow != targetRow);
529  for (unsigned col = 0; col < nColumns; ++col)
530  tableau[targetRow][col] += tableau[sourceRow][col] * factor;
531  // Again, perform row operation on the temporary column vector as well.
532  implicitBasicVariableColumnVector[targetRow] +=
533  implicitBasicVariableColumnVector[sourceRow] * factor;
534 }
535 
536 /// The pivot operation applies elementary row operations to the tableau in
537 /// order to make the \p pivotColumn (corresponding to a non-basic variable) a
538 /// unit vector (only the \p pivotRow'th entry is 1). Then, a basis exchange is
539 /// performed: the non-basic variable is swapped with the basic variable
540 /// associated with the pivot row.
541 void SimplexSchedulerBase::pivot(unsigned pivotRow, unsigned pivotColumn) {
542  // The implicit columns are part of an identity matrix.
543  implicitBasicVariableColumnVector[pivotRow] = 1;
544 
545  int pivotElem = tableau[pivotRow][pivotColumn];
546  // The constraint matrix has only {-1, 0, 1} entries by construction.
547  assert(pivotElem * pivotElem == 1);
548  // Make `tableau[pivotRow][pivotColumn]` := 1
549  multiplyRow(pivotRow, 1 / pivotElem);
550 
551  for (unsigned row = 0; row < nRows; ++row) {
552  if (row == pivotRow)
553  continue;
554 
555  int elem = tableau[row][pivotColumn];
556  if (elem == 0)
557  continue; // nothing to do
558 
559  // Make `tableau[row][pivotColumn]` := 0.
560  addMultipleOfRow(pivotRow, -elem, row);
561  }
562 
563  // Swap the pivot column with the implicitly constructed column vector.
564  // We really only need to copy in one direction here, as the former pivot
565  // column is a unit vector, which is not stored explicitly.
566  for (unsigned row = 0; row < nRows; ++row) {
567  tableau[row][pivotColumn] = implicitBasicVariableColumnVector[row];
568  implicitBasicVariableColumnVector[row] = 0; // Reset for next pivot step.
569  }
570 
571  // Look up numeric IDs of variables involved in this pivot operation.
572  unsigned &nonBasicVar =
573  nonBasicVariables[pivotColumn - firstNonBasicVariableColumn];
574  unsigned &basicVar = basicVariables[pivotRow - firstConstraintRow];
575 
576  // Keep track of where start time variables are; ignore slack variables.
577  if (nonBasicVar < startTimeLocations.size())
578  startTimeLocations[nonBasicVar] = -pivotRow; // ...going into basis.
579  if (basicVar < startTimeLocations.size())
580  startTimeLocations[basicVar] = pivotColumn; // ...going out of basis.
581 
582  // Record the swap in the variable lists.
583  std::swap(nonBasicVar, basicVar);
584 }
585 
586 LogicalResult SimplexSchedulerBase::solveTableau() {
587  // "Solving" technically means perfoming dual pivot steps until primal
588  // feasibility is reached, i.e. the parametric constants in all constraint
589  // rows are non-negative.
590  while (auto pivotRow = findDualPivotRow()) {
591  // Look for pivot elements.
592  if (auto pivotCol = findDualPivotColumn(*pivotRow)) {
593  pivot(*pivotRow, *pivotCol);
594  continue;
595  }
596 
597  // If we did not find a pivot column, then the entire row contained only
598  // positive entries, and the problem is in principle infeasible. However, if
599  // the entry in the `parameterTColumn` is positive, we can try to make the
600  // LP feasible again by increasing the II.
601  int entry1Col = tableau[*pivotRow][parameter1Column];
602  int entryTCol = tableau[*pivotRow][parameterTColumn];
603  if (entryTCol > 0) {
604  // The negation of `entry1Col` is not in the paper. I think this is an
605  // oversight, because `entry1Col` certainly is negative (otherwise the row
606  // would not have been a valid pivot row), and without the negation, the
607  // new II would be negative.
608  assert(entry1Col < 0);
609  int newParameterT = (-entry1Col - 1) / entryTCol + 1;
610  if (newParameterT > parameterT) {
611  parameterT = newParameterT;
612  LLVM_DEBUG(dbgs() << "Increased II to " << parameterT << '\n');
613  continue;
614  }
615  }
616 
617  // Otherwise, the linear program is infeasible.
618  return failure();
619  }
620 
621  // Optimal solution found!
622  return success();
623 }
624 
625 LogicalResult SimplexSchedulerBase::restoreDualFeasibility() {
626  // Dual feasibility requires that all columns in the cost matrix are
627  // non-lexico-negative. This property may be violated after changing the order
628  // of the objective rows, and can be restored by performing primal pivot
629  // steps.
630  while (auto pivotCol = findPrimalPivotColumn()) {
631  // Look for pivot elements.
632  if (auto pivotRow = findPrimalPivotRow(*pivotCol)) {
633  pivot(*pivotRow, *pivotCol);
634  continue;
635  }
636 
637  // Otherwise, the linear program is unbounded.
638  return failure();
639  }
640 
641  // Optimal solution found!
642  return success();
643 }
644 
645 bool SimplexSchedulerBase::isInBasis(unsigned startTimeVariable) {
646  assert(startTimeVariable < startTimeLocations.size());
647  int loc = startTimeLocations[startTimeVariable];
648  if (-loc >= (int)firstConstraintRow)
649  return true;
650  if (loc >= (int)firstNonBasicVariableColumn)
651  return false;
652  llvm_unreachable("Invalid variable location");
653 }
654 
655 unsigned SimplexSchedulerBase::freeze(unsigned startTimeVariable,
656  unsigned timeStep) {
657  assert(startTimeVariable < startTimeLocations.size());
658  assert(!frozenVariables.count(startTimeVariable));
659 
660  // Mark variable.
661  frozenVariables[startTimeVariable] = timeStep;
662 
663  if (!isInBasis(startTimeVariable))
664  // That's all for non-basic variables.
665  return startTimeLocations[startTimeVariable];
666 
667  // We need to pivot this variable one out of basis.
668  unsigned pivotRow = -startTimeLocations[startTimeVariable];
669 
670  // Here, positive pivot elements can be considered as well, hence finding a
671  // suitable column should not fail.
672  auto pivotCol = findDualPivotColumn(pivotRow, /* allowPositive= */ true);
673  assert(pivotCol);
674 
675  // Perform the exchange.
676  pivot(pivotRow, *pivotCol);
677 
678  // `startTimeVariable` is now represented by `pivotCol`.
679  return *pivotCol;
680 }
681 
682 void SimplexSchedulerBase::translate(unsigned column, int factor1, int factorS,
683  int factorT) {
684  for (unsigned row = 0; row < nRows; ++row) {
685  auto &rowVec = tableau[row];
686  int elem = rowVec[column];
687  if (elem == 0)
688  continue;
689 
690  rowVec[parameter1Column] += -elem * factor1;
691  rowVec[parameterSColumn] += -elem * factorS;
692  rowVec[parameterTColumn] += -elem * factorT;
693  }
694 }
695 
696 LogicalResult SimplexSchedulerBase::scheduleAt(unsigned startTimeVariable,
697  unsigned timeStep) {
698  assert(startTimeVariable < startTimeLocations.size());
699  assert(!frozenVariables.count(startTimeVariable));
700 
701  // Freeze variable and translate its column by parameter S.
702  unsigned frozenCol = freeze(startTimeVariable, timeStep);
703  translate(frozenCol, /* factor1= */ 0, /* factorS= */ 1, /* factorT= */ 0);
704 
705  // Temporarily set S to the desired value, and attempt to solve.
706  parameterS = timeStep;
707  auto solved = solveTableau();
708  parameterS = 0;
709 
710  if (failed(solved)) {
711  // The LP is infeasible with the new constraint. We could try other values
712  // for S, but for now, we just roll back and signal failure to the driver.
713  translate(frozenCol, /* factor1= */ 0, /* factorS= */ -1, /* factorT= */ 0);
714  frozenVariables.erase(startTimeVariable);
715  auto solvedAfterRollback = solveTableau();
716  assert(succeeded(solvedAfterRollback));
717  (void)solvedAfterRollback;
718  return failure();
719  }
720 
721  // Translate S by the other parameter(s). This means setting `factor1` to
722  // `timeStep`.
723  //
724  // This translation does not change the values of the parametric constants,
725  // hence we do not need to solve the tableau again.
726  //
727  // Note: I added a negation of the factors here, which is not mentioned in the
728  // paper's text, but apparently used in the example. Without it, the intended
729  // effect, i.e. making the S-column all-zero again, is not achieved.
730  //
731  // Note 2: For cyclic problems, the paper suggested to perform a modulo
732  // decomposition: S = `factor1` + `factorT` * T, with `factor1` < T.
733  // However, this makes the value baked into the tableau dependent on
734  // `parameterT`, and it is unclear to me how to update it correctly when
735  // changing the II. I found it much more robust to fix the operations to
736  // absolute time steps, and manually shift them by the appropriate amount
737  // whenever the II is incremented (cf. adding `phiJ`, `phiN` in the modulo
738  // scheduler's `scheduleOperation` method).
739  translate(parameterSColumn, /* factor1= */ -timeStep, /* factorS= */ 1,
740  /* factorT= */ 0);
741 
742  return success();
743 }
744 
745 void SimplexSchedulerBase::moveBy(unsigned startTimeVariable, unsigned amount) {
746  assert(startTimeVariable < startTimeLocations.size());
747  assert(frozenVariables.count(startTimeVariable));
748 
749  // Bookkeeping.
750  frozenVariables[startTimeVariable] += amount;
751 
752  // Moving an already frozen variable means translating it by the desired
753  // amount, and solving the tableau to restore primal feasibility...
754  translate(startTimeLocations[startTimeVariable], /* factor1= */ amount,
755  /* factorS= */ 0, /* factorT= */ 0);
756 
757  // ... however, we typically batch-move multiple operations (otherwise, the
758  // tableau may become infeasible on intermediate steps), so actually defer
759  // solving to the caller.
760 }
761 
762 unsigned SimplexSchedulerBase::getStartTime(unsigned startTimeVariable) {
763  assert(startTimeVariable < startTimeLocations.size());
764 
765  if (!isInBasis(startTimeVariable))
766  // Non-basic variables that are not already fixed to a specific time step
767  // are 0 at the end of the simplex algorithm.
768  return frozenVariables.lookup(startTimeVariable);
769 
770  // For the variables currently in basis, we look up the solution in the
771  // tableau.
772  return getParametricConstant(-startTimeLocations[startTimeVariable]);
773 }
774 
775 void SimplexSchedulerBase::dumpTableau() {
776  for (unsigned j = 0; j < nColumns; ++j)
777  dbgs() << "====";
778  dbgs() << "==\n";
779  for (unsigned i = 0; i < nRows; ++i) {
780  if (i == firstConstraintRow) {
781  for (unsigned j = 0; j < nColumns; ++j) {
782  if (j == firstNonBasicVariableColumn)
783  dbgs() << "-+";
784  dbgs() << "----";
785  }
786  dbgs() << '\n';
787  }
788  for (unsigned j = 0; j < nColumns; ++j) {
789  if (j == firstNonBasicVariableColumn)
790  dbgs() << " |";
791  dbgs() << format(" %3d", tableau[i][j]);
792  }
793  if (i >= firstConstraintRow)
794  dbgs() << format(" |< %2d", basicVariables[i - firstConstraintRow]);
795  dbgs() << '\n';
796  }
797  for (unsigned j = 0; j < nColumns; ++j)
798  dbgs() << "====";
799  dbgs() << "==\n";
800  dbgs() << format(" %3d %3d %3d | ", 1, parameterS, parameterT);
801  for (unsigned j = firstNonBasicVariableColumn; j < nColumns; ++j)
802  dbgs() << format(" %2d^",
803  nonBasicVariables[j - firstNonBasicVariableColumn]);
804  dbgs() << '\n';
805 }
806 
807 //===----------------------------------------------------------------------===//
808 // SimplexScheduler
809 //===----------------------------------------------------------------------===//
810 
811 LogicalResult SimplexScheduler::schedule() {
812  if (failed(checkLastOp()))
813  return failure();
814 
815  parameterS = 0;
816  parameterT = 0;
817  buildTableau();
818 
819  LLVM_DEBUG(dbgs() << "Initial tableau:\n"; dumpTableau());
820 
821  if (failed(solveTableau()))
822  return prob.getContainingOp()->emitError() << "problem is infeasible";
823 
824  assert(parameterT == 0);
825  LLVM_DEBUG(
826  dbgs() << "Final tableau:\n"; dumpTableau();
827  dbgs() << "Optimal solution found with start time of last operation = "
828  << -getParametricConstant(0) << '\n');
829 
830  for (auto *op : prob.getOperations())
831  prob.setStartTime(op, getStartTime(startTimeVariables[op]));
832 
833  return success();
834 }
835 
836 //===----------------------------------------------------------------------===//
837 // CyclicSimplexScheduler
838 //===----------------------------------------------------------------------===//
839 
840 void CyclicSimplexScheduler::fillConstraintRow(SmallVector<int> &row,
841  Problem::Dependence dep) {
842  SimplexSchedulerBase::fillConstraintRow(row, dep);
843  if (auto dist = prob.getDistance(dep))
844  row[parameterTColumn] = *dist;
845 }
846 
847 LogicalResult CyclicSimplexScheduler::schedule() {
848  if (failed(checkLastOp()))
849  return failure();
850 
851  parameterS = 0;
852  parameterT = 1;
853  buildTableau();
854 
855  LLVM_DEBUG(dbgs() << "Initial tableau:\n"; dumpTableau());
856 
857  if (failed(solveTableau()))
858  return prob.getContainingOp()->emitError() << "problem is infeasible";
859 
860  LLVM_DEBUG(dbgs() << "Final tableau:\n"; dumpTableau();
861  dbgs() << "Optimal solution found with II = " << parameterT
862  << " and start time of last operation = "
863  << -getParametricConstant(0) << '\n');
864 
865  prob.setInitiationInterval(parameterT);
866  for (auto *op : prob.getOperations())
867  prob.setStartTime(op, getStartTime(startTimeVariables[op]));
868 
869  return success();
870 }
871 
872 //===----------------------------------------------------------------------===//
873 // SharedOperatorsSimplexScheduler
874 //===----------------------------------------------------------------------===//
875 
876 static bool isLimited(Operation *op, SharedOperatorsProblem &prob) {
877  return prob.getLimit(*prob.getLinkedOperatorType(op)).value_or(0) > 0;
878 }
879 
880 LogicalResult SharedOperatorsSimplexScheduler::schedule() {
881  if (failed(checkLastOp()))
882  return failure();
883 
884  parameterS = 0;
885  parameterT = 0;
886  buildTableau();
887 
888  LLVM_DEBUG(dbgs() << "Initial tableau:\n"; dumpTableau());
889 
890  if (failed(solveTableau()))
891  return prob.getContainingOp()->emitError() << "problem is infeasible";
892 
893  LLVM_DEBUG(dbgs() << "After solving resource-free problem:\n"; dumpTableau());
894 
895  // The *heuristic* part of this scheduler starts here:
896  // We will now *choose* start times for operations using a shared operator
897  // type, in a way that respects the allocation limits, and consecutively solve
898  // the LP with these added constraints. The individual LPs are still solved to
899  // optimality (meaning: the start times of the "last" operation is still
900  // optimal w.r.t. the already fixed operations), however the heuristic choice
901  // means we cannot guarantee the optimality for the overall problem.
902 
903  // Determine which operations are subject to resource constraints.
904  auto &ops = prob.getOperations();
905  SmallVector<Operation *> limitedOps;
906  for (auto *op : ops)
907  if (isLimited(op, prob))
908  limitedOps.push_back(op);
909 
910  // Build a priority list of the limited operations.
911  //
912  // We sort by the resource-free start times to produce a topological order of
913  // the operations. Better priority functions are known, but require computing
914  // additional properties, e.g. ASAP and ALAP times for mobility, or graph
915  // analysis for height. Assigning operators (=resources) in this order at
916  // least ensures that the (acyclic!) problem remains feasible throughout the
917  // process.
918  //
919  // TODO: Implement more sophisticated priority function.
920  std::stable_sort(limitedOps.begin(), limitedOps.end(),
921  [&](Operation *a, Operation *b) {
922  return getStartTime(startTimeVariables[a]) <
923  getStartTime(startTimeVariables[b]);
924  });
925 
926  // Store the number of operations using an operator type in a particular time
927  // step.
929  reservationTable;
930 
931  for (auto *op : limitedOps) {
932  auto opr = *prob.getLinkedOperatorType(op);
933  unsigned limit = prob.getLimit(opr).value_or(0);
934  assert(limit > 0);
935 
936  // Find the first time step (beginning at the current start time in the
937  // partial schedule) in which an operator instance is available.
938  unsigned startTimeVar = startTimeVariables[op];
939  unsigned candTime = getStartTime(startTimeVar);
940  while (reservationTable[opr].lookup(candTime) == limit)
941  ++candTime;
942 
943  // Fix the start time. As explained above, this cannot make the problem
944  // infeasible.
945  auto fixed = scheduleAt(startTimeVar, candTime);
946  assert(succeeded(fixed));
947  (void)fixed;
948 
949  // Record the operator use.
950  ++reservationTable[opr][candTime];
951 
952  LLVM_DEBUG(dbgs() << "After scheduling " << startTimeVar
953  << " to t=" << candTime << ":\n";
954  dumpTableau());
955  }
956 
957  assert(parameterT == 0);
958  LLVM_DEBUG(
959  dbgs() << "Final tableau:\n"; dumpTableau();
960  dbgs() << "Feasible solution found with start time of last operation = "
961  << -getParametricConstant(0) << '\n');
962 
963  for (auto *op : ops)
964  prob.setStartTime(op, getStartTime(startTimeVariables[op]));
965 
966  return success();
967 }
968 
969 //===----------------------------------------------------------------------===//
970 // ModuloSimplexScheduler
971 //===----------------------------------------------------------------------===//
972 
973 LogicalResult ModuloSimplexScheduler::checkLastOp() {
974  auto *contOp = prob.getContainingOp();
975  if (!prob.hasOperation(lastOp))
976  return contOp->emitError("problem does not include last operation");
977 
978  // Determine which operations have no outgoing *intra*-iteration dependences.
979  auto &ops = prob.getOperations();
980  DenseSet<Operation *> sinks(ops.begin(), ops.end());
981  for (auto *op : ops)
982  for (auto &dep : prob.getDependences(op))
983  if (prob.getDistance(dep).value_or(0) == 0)
984  sinks.erase(dep.getSource());
985 
986  if (!sinks.contains(lastOp))
987  return contOp->emitError("last operation is not a sink");
988  if (sinks.size() > 1)
989  return contOp->emitError("multiple sinks detected");
990 
991  return success();
992 }
993 
994 LogicalResult ModuloSimplexScheduler::MRT::enter(Operation *op,
995  unsigned timeStep) {
996  auto opr = *sched.prob.getLinkedOperatorType(op);
997  auto lim = *sched.prob.getLimit(opr);
998  assert(lim > 0);
999 
1000  auto &revTab = reverseTables[opr];
1001  assert(!revTab.count(op));
1002 
1003  unsigned slot = timeStep % sched.parameterT;
1004  auto &cell = tables[opr][slot];
1005  if (cell.size() < lim) {
1006  cell.insert(op);
1007  revTab[op] = slot;
1008  return success();
1009  }
1010  return failure();
1011 }
1012 
1013 void ModuloSimplexScheduler::MRT::release(Operation *op) {
1014  auto opr = *sched.prob.getLinkedOperatorType(op);
1015  auto &revTab = reverseTables[opr];
1016  auto it = revTab.find(op);
1017  assert(it != revTab.end());
1018  tables[opr][it->second].erase(op);
1019  revTab.erase(it);
1020 }
1021 
1022 bool ModuloSimplexScheduler::fillObjectiveRow(SmallVector<int> &row,
1023  unsigned obj) {
1024  switch (obj) {
1025  case OBJ_LATENCY:
1026  // Minimize start time of user-specified last operation.
1027  row[startTimeLocations[startTimeVariables[lastOp]]] = 1;
1028  return true;
1029  case OBJ_AXAP:
1030  // Minimize sum of start times of all-but-the-last operation.
1031  for (auto *op : getProblem().getOperations())
1032  if (op != lastOp)
1033  row[startTimeLocations[startTimeVariables[op]]] = 1;
1034  return false;
1035  default:
1036  llvm_unreachable("Unsupported objective requested");
1037  }
1038 }
1039 
1040 void ModuloSimplexScheduler::updateMargins() {
1041  // Assumption: current secondary objective is "ASAP".
1042  // Negate the objective row once to effectively maximize the sum of start
1043  // times, which yields the "ALAP" times after solving the tableau. Then,
1044  // negate it again to restore the "ASAP" objective, and store these times as
1045  // well.
1046  for (auto *axapTimes : {&alapTimes, &asapTimes}) {
1047  multiplyRow(OBJ_AXAP, -1);
1048  // This should not fail for a feasible tableau.
1049  auto dualFeasRestored = restoreDualFeasibility();
1050  auto solved = solveTableau();
1051  assert(succeeded(dualFeasRestored) && succeeded(solved));
1052  (void)dualFeasRestored, (void)solved;
1053 
1054  for (unsigned stv = 0; stv < startTimeLocations.size(); ++stv)
1055  (*axapTimes)[stv] = getStartTime(stv);
1056  }
1057 }
1058 
1059 void ModuloSimplexScheduler::scheduleOperation(Operation *n) {
1060  auto oprN = *prob.getLinkedOperatorType(n);
1061  unsigned stvN = startTimeVariables[n];
1062 
1063  // Get current state of the LP. We'll try to schedule at its current time step
1064  // in the partial solution, and the II-1 following time steps. Scheduling the
1065  // op to a later time step may increase the overall latency, however, as long
1066  // as the solution is still feasible, we prefer that over incrementing the II
1067  // to resolve resource conflicts.
1068  unsigned stN = getStartTime(stvN);
1069  unsigned ubN = stN + parameterT - 1;
1070 
1071  LLVM_DEBUG(dbgs() << "Attempting to schedule in [" << stN << ", " << ubN
1072  << "]: " << *n << '\n');
1073 
1074  for (unsigned ct = stN; ct <= ubN; ++ct)
1075  if (succeeded(mrt.enter(n, ct))) {
1076  auto fixedN = scheduleAt(stvN, ct);
1077  if (succeeded(fixedN)) {
1078  LLVM_DEBUG(dbgs() << "Success at t=" << ct << " " << *n << '\n');
1079  return;
1080  }
1081  // Problem became infeasible with `n` at `ct`, roll back the MRT
1082  // assignment. Also, no later time can be feasible, so stop the search
1083  // here.
1084  mrt.release(n);
1085  break;
1086  }
1087 
1088  // As a last resort, increase II to make room for the op. De Dinechin's
1089  // Theorem 1 lays out conditions/guidelines to transform the current partial
1090  // schedule for II to a valid one for a larger II'.
1091 
1092  LLVM_DEBUG(dbgs() << "Incrementing II to " << (parameterT + 1)
1093  << " to resolve resource conflict for " << *n << '\n');
1094 
1095  // Note that the approach below is much simpler than in the paper
1096  // because of the fully-pipelined operators. In our case, it's always
1097  // sufficient to increment the II by one.
1098 
1099  // Decompose start time.
1100  unsigned phiN = stN / parameterT;
1101  unsigned tauN = stN % parameterT;
1102 
1103  // Keep track whether the following moves free at least one operator
1104  // instance in the slot desired by the current op - then it can stay there.
1105  unsigned deltaN = 1;
1106 
1107  // We're going to revisit the current partial schedule.
1108  SmallVector<Operation *> moved;
1109  for (Operation *j : scheduled) {
1110  auto oprJ = *prob.getLinkedOperatorType(j);
1111  unsigned stvJ = startTimeVariables[j];
1112  unsigned stJ = getStartTime(stvJ);
1113  unsigned phiJ = stJ / parameterT;
1114  unsigned tauJ = stJ % parameterT;
1115  unsigned deltaJ = 0;
1116 
1117  if (oprN == oprJ) {
1118  // To actually resolve the resource conflicts, we will move operations
1119  // that are "preceded" (cf. de Dinechin's ≺ relation) one slot to the
1120  // right.
1121  if (tauN < tauJ || (tauN == tauJ && phiN > phiJ) ||
1122  (tauN == tauJ && phiN == phiJ && stvN < stvJ)) {
1123  // TODO: Replace the last condition with a proper graph analysis.
1124 
1125  deltaJ = 1;
1126  moved.push_back(j);
1127  if (tauN == tauJ)
1128  deltaN = 0;
1129  }
1130  }
1131 
1132  // Move operation.
1133  //
1134  // In order to keep the op in its current MRT slot `tauJ` after incrementing
1135  // the II, we add `phiJ`:
1136  // stJ + phiJ = (phiJ * parameterT + tauJ) + phiJ
1137  // = phiJ * (parameterT + 1) + tauJ
1138  //
1139  // Shifting an additional `deltaJ` time steps then moves the op to a
1140  // different MRT slot, in order to make room for the operation that caused
1141  // the resource conflict.
1142  moveBy(stvJ, phiJ + deltaJ);
1143  }
1144 
1145  // Finally, increment the II and solve to apply the moves.
1146  ++parameterT;
1147  auto solved = solveTableau();
1148  assert(succeeded(solved));
1149  (void)solved;
1150 
1151  // Re-enter moved operations into their new slots.
1152  for (auto *m : moved)
1153  mrt.release(m);
1154  for (auto *m : moved) {
1155  auto enteredM = mrt.enter(m, getStartTime(startTimeVariables[m]));
1156  assert(succeeded(enteredM));
1157  (void)enteredM;
1158  }
1159 
1160  // Finally, schedule the operation. Again, adding `phiN` accounts for the
1161  // implicit shift caused by incrementing the II.
1162  auto fixedN = scheduleAt(stvN, stN + phiN + deltaN);
1163  auto enteredN = mrt.enter(n, tauN + deltaN);
1164  assert(succeeded(fixedN) && succeeded(enteredN));
1165  (void)fixedN, (void)enteredN;
1166 }
1167 
1168 unsigned ModuloSimplexScheduler::computeResMinII() {
1169  unsigned resMinII = 1;
1171  for (auto *op : prob.getOperations())
1172  if (isLimited(op, prob))
1173  ++uses[*prob.getLinkedOperatorType(op)];
1174 
1175  for (auto pair : uses)
1176  resMinII = std::max(
1177  resMinII, (unsigned)ceil(pair.second / *prob.getLimit(pair.first)));
1178 
1179  return resMinII;
1180 }
1181 
1182 LogicalResult ModuloSimplexScheduler::schedule() {
1183  if (failed(checkLastOp()))
1184  return failure();
1185 
1186  parameterS = 0;
1187  parameterT = computeResMinII();
1188  LLVM_DEBUG(dbgs() << "ResMinII = " << parameterT << "\n");
1189  buildTableau();
1190  asapTimes.resize(startTimeLocations.size());
1191  alapTimes.resize(startTimeLocations.size());
1192 
1193  LLVM_DEBUG(dbgs() << "Initial tableau:\n"; dumpTableau());
1194 
1195  if (failed(solveTableau()))
1196  return prob.getContainingOp()->emitError() << "problem is infeasible";
1197 
1198  // Determine which operations are subject to resource constraints.
1199  auto &ops = prob.getOperations();
1200  for (auto *op : ops)
1201  if (isLimited(op, prob))
1202  unscheduled.push_back(op);
1203 
1204  // Main loop: Iteratively fix limited operations to time steps.
1205  while (!unscheduled.empty()) {
1206  // Update ASAP/ALAP times.
1207  updateMargins();
1208 
1209  // Heuristically (here: least amount of slack) pick the next operation to
1210  // schedule.
1211  auto *opIt =
1212  std::min_element(unscheduled.begin(), unscheduled.end(),
1213  [&](Operation *opA, Operation *opB) {
1214  auto stvA = startTimeVariables[opA];
1215  auto stvB = startTimeVariables[opB];
1216  auto slackA = alapTimes[stvA] - asapTimes[stvA];
1217  auto slackB = alapTimes[stvB] - asapTimes[stvB];
1218  return slackA < slackB;
1219  });
1220  Operation *op = *opIt;
1221  unscheduled.erase(opIt);
1222 
1223  scheduleOperation(op);
1224  scheduled.push_back(op);
1225  }
1226 
1227  LLVM_DEBUG(dbgs() << "Final tableau:\n"; dumpTableau();
1228  dbgs() << "Solution found with II = " << parameterT
1229  << " and start time of last operation = "
1230  << -getParametricConstant(0) << '\n');
1231 
1232  prob.setInitiationInterval(parameterT);
1233  for (auto *op : ops)
1234  prob.setStartTime(op, getStartTime(startTimeVariables[op]));
1235 
1236  return success();
1237 }
1238 
1239 //===----------------------------------------------------------------------===//
1240 // ChainingSimplexScheduler
1241 //===----------------------------------------------------------------------===//
1242 
1243 void ChainingSimplexScheduler::fillAdditionalConstraintRow(
1244  SmallVector<int> &row, Problem::Dependence dep) {
1245  fillConstraintRow(row, dep);
1246  // One _extra_ time step breaks the chain (note that the latency is negative
1247  // in the tableau).
1248  row[parameter1Column] -= 1;
1249 }
1250 
1251 LogicalResult ChainingSimplexScheduler::schedule() {
1252  if (failed(checkLastOp()) || failed(computeChainBreakingDependences(
1253  prob, cycleTime, additionalConstraints)))
1254  return failure();
1255 
1256  parameterS = 0;
1257  parameterT = 0;
1258  buildTableau();
1259 
1260  LLVM_DEBUG(dbgs() << "Initial tableau:\n"; dumpTableau());
1261 
1262  if (failed(solveTableau()))
1263  return prob.getContainingOp()->emitError() << "problem is infeasible";
1264 
1265  assert(parameterT == 0);
1266  LLVM_DEBUG(
1267  dbgs() << "Final tableau:\n"; dumpTableau();
1268  dbgs() << "Optimal solution found with start time of last operation = "
1269  << -getParametricConstant(0) << '\n');
1270 
1271  for (auto *op : prob.getOperations())
1272  prob.setStartTime(op, getStartTime(startTimeVariables[op]));
1273 
1274  auto filledIn = computeStartTimesInCycle(prob);
1275  assert(succeeded(filledIn)); // Problem is known to be acyclic at this point.
1276  (void)filledIn;
1277 
1278  return success();
1279 }
1280 
1281 //===----------------------------------------------------------------------===//
1282 // ChainingCyclicSimplexScheduler
1283 //===----------------------------------------------------------------------===//
1284 
1285 void ChainingCyclicSimplexScheduler::fillConstraintRow(
1286  SmallVector<int> &row, Problem::Dependence dep) {
1287  SimplexSchedulerBase::fillConstraintRow(row, dep);
1288  if (auto dist = prob.getDistance(dep))
1289  row[parameterTColumn] = *dist;
1290 }
1291 
1292 void ChainingCyclicSimplexScheduler::fillAdditionalConstraintRow(
1293  SmallVector<int> &row, Problem::Dependence dep) {
1294  fillConstraintRow(row, dep);
1295  // One _extra_ time step breaks the chain (note that the latency is negative
1296  // in the tableau).
1297  row[parameter1Column] -= 1;
1298 }
1299 
1300 LogicalResult ChainingCyclicSimplexScheduler::schedule() {
1301  if (failed(checkLastOp()) || failed(computeChainBreakingDependences(
1302  prob, cycleTime, additionalConstraints)))
1303  return failure();
1304 
1305  parameterS = 0;
1306  parameterT = 1;
1307  buildTableau();
1308 
1309  LLVM_DEBUG(dbgs() << "Initial tableau:\n"; dumpTableau());
1310 
1311  if (failed(solveTableau()))
1312  return prob.getContainingOp()->emitError() << "problem is infeasible";
1313 
1314  LLVM_DEBUG(dbgs() << "Final tableau:\n"; dumpTableau();
1315  dbgs() << "Optimal solution found with II = " << parameterT
1316  << " and start time of last operation = "
1317  << -getParametricConstant(0) << '\n');
1318 
1319  prob.setInitiationInterval(parameterT);
1320  for (auto *op : prob.getOperations())
1321  prob.setStartTime(op, getStartTime(startTimeVariables[op]));
1322 
1323  auto filledIn = computeStartTimesInCycle(prob);
1324  assert(succeeded(filledIn));
1325  (void)filledIn;
1326 
1327  return success();
1328 }
1329 
1330 //===----------------------------------------------------------------------===//
1331 // Public API
1332 //===----------------------------------------------------------------------===//
1333 
1334 LogicalResult scheduling::scheduleSimplex(Problem &prob, Operation *lastOp) {
1335  SimplexScheduler simplex(prob, lastOp);
1336  return simplex.schedule();
1337 }
1338 
1340  Operation *lastOp) {
1341  CyclicSimplexScheduler simplex(prob, lastOp);
1342  return simplex.schedule();
1343 }
1344 
1346  Operation *lastOp) {
1347  SharedOperatorsSimplexScheduler simplex(prob, lastOp);
1348  return simplex.schedule();
1349 }
1350 
1352  Operation *lastOp) {
1353  ModuloSimplexScheduler simplex(prob, lastOp);
1354  return simplex.schedule();
1355 }
1356 
1358  Operation *lastOp, float cycleTime) {
1359  ChainingSimplexScheduler simplex(prob, lastOp, cycleTime);
1360  return simplex.schedule();
1361 }
1362 
1364  Operation *lastOp, float cycleTime) {
1365  ChainingCyclicSimplexScheduler simplex(prob, lastOp, cycleTime);
1366  return simplex.schedule();
1367 }
assert(baseType &&"element must be base type")
static bool isLimited(Operation *op, SharedOperatorsProblem &prob)
This class models the accumulation of physical propagation delays on combinational paths along SSA de...
Definition: Problems.h:471
This class models the accumulation of physical propagation delays on combinational paths along SSA de...
Definition: Problems.h:340
This class models a cyclic scheduling problem.
Definition: Problems.h:292
This class models the modulo scheduling problem as the composition of the cyclic problem and the reso...
Definition: Problems.h:446
This class models the most basic scheduling problem.
Definition: Problems.h:87
std::optional< OperatorType > getLinkedOperatorType(Operation *op)
The linked operator type provides the runtime characteristics for op.
Definition: Problems.h:202
This class models a resource-constrained scheduling problem.
Definition: Problems.h:408
std::optional< unsigned > getLimit(OperatorType opr)
The limit is the maximum number of operations using opr that are allowed to start in the same time st...
Definition: Problems.h:417
A wrapper class to uniformly handle def-use and auxiliary dependence edges.
Operation * getDestination() const
Return the destination of the dependence.
Definition: Problems.cpp:449
Operation * getSource() const
Return the source of the dependence.
Definition: Problems.cpp:445
LogicalResult computeChainBreakingDependences(ChainingProblem &prob, float cycleTime, SmallVectorImpl< Problem::Dependence > &result)
Analyse combinational chains in prob's dependence graph and determine pairs of operations that must b...
LogicalResult scheduleSimplex(Problem &prob, Operation *lastOp)
Solve the basic problem using linear programming and a handwritten implementation of the simplex algo...
LogicalResult computeStartTimesInCycle(ChainingProblem &prob)
Assuming prob is scheduled and contains (integer) start times, this method fills in the start times i...
This file defines an intermediate representation for circuits acting as an abstraction for constraint...
Definition: DebugAnalysis.h:21
mlir::raw_indented_ostream & dbgs()
Definition: Utility.h:28