Skip to content
MachineScheduler.cpp 93.1 KiB
Newer Older
              TryCand, Cand, Weak)) {
  // Avoid critical resource consumption and balance the schedule.
  TryCand.initResourceDelta(DAG, SchedModel);
  if (tryLess(TryCand.ResDelta.CritResources, Cand.ResDelta.CritResources,
              TryCand, Cand, ResourceReduce))
    return;
  if (tryGreater(TryCand.ResDelta.DemandedResources,
                 Cand.ResDelta.DemandedResources,
                 TryCand, Cand, ResourceDemand))
    return;

  // Avoid serializing long latency dependence chains.
  if (Cand.Policy.ReduceLatency) {
    if (Zone.isTop()) {
      if (Cand.SU->getDepth() * SchedModel->getLatencyFactor()
          > Zone.ExpectedCount) {
        if (tryLess(TryCand.SU->getDepth(), Cand.SU->getDepth(),
                    TryCand, Cand, TopDepthReduce))
          return;
      }
      if (tryGreater(TryCand.SU->getHeight(), Cand.SU->getHeight(),
                     TryCand, Cand, TopPathReduce))
        return;
    }
    else {
      if (Cand.SU->getHeight() * SchedModel->getLatencyFactor()
          > Zone.ExpectedCount) {
        if (tryLess(TryCand.SU->getHeight(), Cand.SU->getHeight(),
                    TryCand, Cand, BotHeightReduce))
          return;
      }
      if (tryGreater(TryCand.SU->getDepth(), Cand.SU->getDepth(),
                     TryCand, Cand, BotPathReduce))
        return;
    }
  }

  // Avoid increasing the max pressure of the entire region.
  if (tryLess(TryCand.RPDelta.CurrentMax.UnitIncrease,
              Cand.RPDelta.CurrentMax.UnitIncrease, TryCand, Cand, SingleMax))
    return;
  if (Cand.Reason == SingleMax)
    Cand.Reason = MultiPressure;

  // Prefer immediate defs/users of the last scheduled instruction. This is a
  // nice pressure avoidance strategy that also conserves the processor's
  // register renaming resources and keeps the machine code readable.
  if (tryGreater(Zone.NextSUs.count(TryCand.SU), Zone.NextSUs.count(Cand.SU),
                 TryCand, Cand, NextDefUse))
  // Fall through to original instruction order.
  if ((Zone.isTop() && TryCand.SU->NodeNum < Cand.SU->NodeNum)
      || (!Zone.isTop() && TryCand.SU->NodeNum > Cand.SU->NodeNum)) {
    TryCand.Reason = NodeOrder;
  }
Andrew Trick's avatar
Andrew Trick committed
/// pickNodeFromQueue helper that returns true if the LHS reg pressure effect is
/// more desirable than RHS from scheduling standpoint.
static bool compareRPDelta(const RegPressureDelta &LHS,
                           const RegPressureDelta &RHS) {
  // Compare each component of pressure in decreasing order of importance
  // without checking if any are valid. Invalid PressureElements are assumed to
  // have UnitIncrease==0, so are neutral.

  // Avoid increasing the max critical pressure in the scheduled region.
  if (LHS.Excess.UnitIncrease != RHS.Excess.UnitIncrease) {
    DEBUG(dbgs() << "  RP excess top - bot: "
          << (LHS.Excess.UnitIncrease - RHS.Excess.UnitIncrease) << '\n');
    return LHS.Excess.UnitIncrease < RHS.Excess.UnitIncrease;
  // Avoid increasing the max critical pressure in the scheduled region.
  if (LHS.CriticalMax.UnitIncrease != RHS.CriticalMax.UnitIncrease) {
    DEBUG(dbgs() << "  RP critical top - bot: "
          << (LHS.CriticalMax.UnitIncrease - RHS.CriticalMax.UnitIncrease)
          << '\n');
    return LHS.CriticalMax.UnitIncrease < RHS.CriticalMax.UnitIncrease;
  // Avoid increasing the max pressure of the entire region.
  if (LHS.CurrentMax.UnitIncrease != RHS.CurrentMax.UnitIncrease) {
    DEBUG(dbgs() << "  RP current top - bot: "
          << (LHS.CurrentMax.UnitIncrease - RHS.CurrentMax.UnitIncrease)
          << '\n');
    return LHS.CurrentMax.UnitIncrease < RHS.CurrentMax.UnitIncrease;
#ifndef NDEBUG
const char *ConvergingScheduler::getReasonStr(
  ConvergingScheduler::CandReason Reason) {
  switch (Reason) {
  case NoCand:         return "NOCAND    ";
  case PhysRegCopy:    return "PREG-COPY";
  case SingleExcess:   return "REG-EXCESS";
  case SingleCritical: return "REG-CRIT  ";
  case Cluster:        return "CLUSTER   ";
  case Weak:           return "WEAK      ";
  case SingleMax:      return "REG-MAX   ";
  case MultiPressure:  return "REG-MULTI ";
  case ResourceReduce: return "RES-REDUCE";
  case ResourceDemand: return "RES-DEMAND";
  case TopDepthReduce: return "TOP-DEPTH ";
  case TopPathReduce:  return "TOP-PATH  ";
  case BotHeightReduce:return "BOT-HEIGHT";
  case BotPathReduce:  return "BOT-PATH  ";
  case NextDefUse:     return "DEF-USE   ";
  case NodeOrder:      return "ORDER     ";
  };
  llvm_unreachable("Unknown reason!");
void ConvergingScheduler::traceCandidate(const SchedCandidate &Cand) {
  PressureElement P;
  unsigned ResIdx = 0;
  unsigned Latency = 0;
  switch (Cand.Reason) {
  default:
    break;
  case SingleExcess:
    P = Cand.RPDelta.Excess;
    break;
  case SingleCritical:
    P = Cand.RPDelta.CriticalMax;
    break;
  case SingleMax:
    P = Cand.RPDelta.CurrentMax;
    break;
  case ResourceReduce:
    ResIdx = Cand.Policy.ReduceResIdx;
    break;
  case ResourceDemand:
    ResIdx = Cand.Policy.DemandResIdx;
    break;
  case TopDepthReduce:
    Latency = Cand.SU->getDepth();
    break;
  case TopPathReduce:
    Latency = Cand.SU->getHeight();
    break;
  case BotHeightReduce:
    Latency = Cand.SU->getHeight();
    break;
  case BotPathReduce:
    Latency = Cand.SU->getDepth();
    break;
  }
  dbgs() << "  SU(" << Cand.SU->NodeNum << ") " << getReasonStr(Cand.Reason);
    dbgs() << " " << TRI->getRegPressureSetName(P.PSetID)
           << ":" << P.UnitIncrease << " ";
    dbgs() << "      ";
    dbgs() << " " << SchedModel->getProcResource(ResIdx)->Name << " ";
    dbgs() << "         ";
    dbgs() << " " << Latency << " cycles ";
    dbgs() << "          ";
  dbgs() << '\n';
/// Pick the best candidate from the top queue.
///
/// TODO: getMaxPressureDelta results can be mostly cached for each SUnit during
/// DAG building. To adjust for the current scheduling location we need to
/// maintain the number of vreg uses remaining to be top-scheduled.
void ConvergingScheduler::pickNodeFromQueue(SchedBoundary &Zone,
                                            const RegPressureTracker &RPTracker,
                                            SchedCandidate &Cand) {
  ReadyQueue &Q = Zone.Available;

  DEBUG(Q.dump());
  // getMaxPressureDelta temporarily modifies the tracker.
  RegPressureTracker &TempTracker = const_cast<RegPressureTracker&>(RPTracker);

  for (ReadyQueue::iterator I = Q.begin(), E = Q.end(); I != E; ++I) {
    SchedCandidate TryCand(Cand.Policy);
    TryCand.SU = *I;
    tryCandidate(Cand, TryCand, Zone, RPTracker, TempTracker);
    if (TryCand.Reason != NoCand) {
      // Initialize resource delta if needed in case future heuristics query it.
      if (TryCand.ResDelta == SchedResourceDelta())
        TryCand.initResourceDelta(DAG, SchedModel);
      Cand.setBest(TryCand);
      DEBUG(traceCandidate(Cand));
}

static void tracePick(const ConvergingScheduler::SchedCandidate &Cand,
                      bool IsTop) {
  DEBUG(dbgs() << "Pick " << (IsTop ? "Top " : "Bot ")
        << ConvergingScheduler::getReasonStr(Cand.Reason) << '\n');
/// Pick the best candidate node from either the top or bottom queue.
SUnit *ConvergingScheduler::pickNodeBidirectional(bool &IsTopNode) {
  // Schedule as far as possible in the direction of no choice. This is most
  // efficient, but also provides the best heuristics for CriticalPSets.
  if (SUnit *SU = Bot.pickOnlyChoice()) {
    IsTopNode = false;
    DEBUG(dbgs() << "Pick Top NOCAND\n");
  if (SUnit *SU = Top.pickOnlyChoice()) {
    IsTopNode = true;
    DEBUG(dbgs() << "Pick Bot NOCAND\n");
  CandPolicy NoPolicy;
  SchedCandidate BotCand(NoPolicy);
  SchedCandidate TopCand(NoPolicy);
  checkResourceLimits(TopCand, BotCand);

  // Prefer bottom scheduling when heuristics are silent.
  pickNodeFromQueue(Bot, DAG->getBotRPTracker(), BotCand);
  assert(BotCand.Reason != NoCand && "failed to find the first candidate");

  // If either Q has a single candidate that provides the least increase in
  // Excess pressure, we can immediately schedule from that Q.
  //
  // RegionCriticalPSets summarizes the pressure within the scheduled region and
  // affects picking from either Q. If scheduling in one direction must
  // increase pressure for one of the excess PSets, then schedule in that
  // direction first to provide more freedom in the other direction.
  if (BotCand.Reason == SingleExcess || BotCand.Reason == SingleCritical) {
    IsTopNode = false;
    tracePick(BotCand, IsTopNode);
    return BotCand.SU;
  }
  // Check if the top Q has a better candidate.
  pickNodeFromQueue(Top, DAG->getTopRPTracker(), TopCand);
  assert(TopCand.Reason != NoCand && "failed to find the first candidate");

  // If either Q has a single candidate that minimizes pressure above the
  // original region's pressure pick it.
  if (TopCand.Reason <= SingleMax || BotCand.Reason <= SingleMax) {
    if (TopCand.Reason < BotCand.Reason) {
      IsTopNode = true;
      tracePick(TopCand, IsTopNode);
      return TopCand.SU;
    }
    IsTopNode = false;
    tracePick(BotCand, IsTopNode);
    return BotCand.SU;
  // Check for a salient pressure difference and pick the best from either side.
  if (compareRPDelta(TopCand.RPDelta, BotCand.RPDelta)) {
    IsTopNode = true;
    tracePick(TopCand, IsTopNode);
    return TopCand.SU;
  // Otherwise prefer the bottom candidate, in node order if all else failed.
  if (TopCand.Reason < BotCand.Reason) {
    IsTopNode = true;
    tracePick(TopCand, IsTopNode);
    return TopCand.SU;
  tracePick(BotCand, IsTopNode);
  return BotCand.SU;
}

/// Pick the best node to balance the schedule. Implements MachineSchedStrategy.
SUnit *ConvergingScheduler::pickNode(bool &IsTopNode) {
  if (DAG->top() == DAG->bottom()) {
    assert(Top.Available.empty() && Top.Pending.empty() &&
           Bot.Available.empty() && Bot.Pending.empty() && "ReadyQ garbage");
  do {
    if (ForceTopDown) {
      SU = Top.pickOnlyChoice();
      if (!SU) {
        CandPolicy NoPolicy;
        SchedCandidate TopCand(NoPolicy);
        pickNodeFromQueue(Top, DAG->getTopRPTracker(), TopCand);
        assert(TopCand.Reason != NoCand && "failed to find the first candidate");
        SU = TopCand.SU;
      }
      IsTopNode = true;
    else if (ForceBottomUp) {
      SU = Bot.pickOnlyChoice();
      if (!SU) {
        CandPolicy NoPolicy;
        SchedCandidate BotCand(NoPolicy);
        pickNodeFromQueue(Bot, DAG->getBotRPTracker(), BotCand);
        assert(BotCand.Reason != NoCand && "failed to find the first candidate");
        SU = BotCand.SU;
      }
      IsTopNode = false;
      SU = pickNodeBidirectional(IsTopNode);
    }
  } while (SU->isScheduled);

  if (SU->isTopReady())
    Top.removeReady(SU);
  if (SU->isBottomReady())
    Bot.removeReady(SU);
  DEBUG(dbgs() << "Scheduling SU(" << SU->NodeNum << ") " << *SU->getInstr());
void ConvergingScheduler::reschedulePhysRegCopies(SUnit *SU, bool isTop) {

  MachineBasicBlock::iterator InsertPos = SU->getInstr();
  if (!isTop)
    ++InsertPos;
  SmallVectorImpl<SDep> &Deps = isTop ? SU->Preds : SU->Succs;

  // Find already scheduled copies with a single physreg dependence and move
  // them just above the scheduled instruction.
  for (SmallVectorImpl<SDep>::iterator I = Deps.begin(), E = Deps.end();
       I != E; ++I) {
    if (I->getKind() != SDep::Data || !TRI->isPhysicalRegister(I->getReg()))
      continue;
    SUnit *DepSU = I->getSUnit();
    if (isTop ? DepSU->Succs.size() > 1 : DepSU->Preds.size() > 1)
      continue;
    MachineInstr *Copy = DepSU->getInstr();
    if (!Copy->isCopy())
      continue;
    DEBUG(dbgs() << "  Rescheduling physreg copy ";
          I->getSUnit()->dump(DAG));
    DAG->moveInstruction(Copy, InsertPos);
  }
}

/// Update the scheduler's state after scheduling a node. This is the same node
/// that was just returned by pickNode(). However, ScheduleDAGMI needs to update
/// it's state based on the current cycle before MachineSchedStrategy does.
///
/// FIXME: Eventually, we may bundle physreg copies rather than rescheduling
/// them here. See comments in biasPhysRegCopy.
void ConvergingScheduler::schedNode(SUnit *SU, bool IsTopNode) {
  if (IsTopNode) {
    SU->TopReadyCycle = Top.CurrCycle;
    if (SU->hasPhysRegUses)
      reschedulePhysRegCopies(SU, true);
  else {
    SU->BotReadyCycle = Bot.CurrCycle;
    if (SU->hasPhysRegDefs)
      reschedulePhysRegCopies(SU, false);
/// Create the standard converging machine scheduler. This will be used as the
/// default scheduler if the target does not set a default.
static ScheduleDAGInstrs *createConvergingSched(MachineSchedContext *C) {
  assert((!ForceTopDown || !ForceBottomUp) &&
         "-misched-topdown incompatible with -misched-bottomup");
  ScheduleDAGMI *DAG = new ScheduleDAGMI(C, new ConvergingScheduler());
  // Register DAG post-processors.
  //
  // FIXME: extend the mutation API to allow earlier mutations to instantiate
  // data and pass it to later mutations. Have a single mutation that gathers
  // the interesting nodes in one pass.
  if (EnableCopyConstrain)
    DAG->addMutation(new CopyConstrain(DAG->TII, DAG->TRI));
  if (EnableLoadCluster)
    DAG->addMutation(new LoadClusterMutation(DAG->TII, DAG->TRI));
  if (EnableMacroFusion)
    DAG->addMutation(new MacroFusion(DAG->TII));
static MachineSchedRegistry
ConvergingSchedRegistry("converge", "Standard converging scheduler.",
                        createConvergingSched);
//===----------------------------------------------------------------------===//
// ILP Scheduler. Currently for experimental analysis of heuristics.
//===----------------------------------------------------------------------===//

namespace {
/// \brief Order nodes by the ILP metric.
struct ILPOrder {
  const SchedDFSResult *DFSResult;
  const BitVector *ScheduledTrees;
  ILPOrder(bool MaxILP): DFSResult(0), ScheduledTrees(0), MaximizeILP(MaxILP) {}

  /// \brief Apply a less-than relation on node priority.
  ///
  /// (Return true if A comes after B in the Q.)
  bool operator()(const SUnit *A, const SUnit *B) const {
    unsigned SchedTreeA = DFSResult->getSubtreeID(A);
    unsigned SchedTreeB = DFSResult->getSubtreeID(B);
    if (SchedTreeA != SchedTreeB) {
      // Unscheduled trees have lower priority.
      if (ScheduledTrees->test(SchedTreeA) != ScheduledTrees->test(SchedTreeB))
        return ScheduledTrees->test(SchedTreeB);

      // Trees with shallower connections have have lower priority.
      if (DFSResult->getSubtreeLevel(SchedTreeA)
          != DFSResult->getSubtreeLevel(SchedTreeB)) {
        return DFSResult->getSubtreeLevel(SchedTreeA)
          < DFSResult->getSubtreeLevel(SchedTreeB);
      }
    }
      return DFSResult->getILP(A) < DFSResult->getILP(B);
      return DFSResult->getILP(A) > DFSResult->getILP(B);
  }
};

/// \brief Schedule based on the ILP metric.
class ILPScheduler : public MachineSchedStrategy {
  /// In case all subtrees are eventually connected to a common root through
  /// data dependence (e.g. reduction), place an upper limit on their size.
  ///
  /// FIXME: A subtree limit is generally good, but in the situation commented
  /// above, where multiple similar subtrees feed a common root, we should
  /// only split at a point where the resulting subtrees will be balanced.
  /// (a motivating test case must be found).
  static const unsigned SubtreeLimit = 16;

  ILPOrder Cmp;

  std::vector<SUnit*> ReadyQ;
public:
  ILPScheduler(bool MaximizeILP): DAG(0), Cmp(MaximizeILP) {}
  virtual void initialize(ScheduleDAGMI *dag) {
    DAG = dag;
    Cmp.DFSResult = DAG->getDFSResult();
    Cmp.ScheduledTrees = &DAG->getScheduledTrees();
    ReadyQ.clear();
  }

  virtual void registerRoots() {
    // Restore the heap in ReadyQ with the updated DFS results.
    std::make_heap(ReadyQ.begin(), ReadyQ.end(), Cmp);
  }

  /// Implement MachineSchedStrategy interface.
  /// -----------------------------------------

  /// Callback to select the highest priority node from the ready Q.
  virtual SUnit *pickNode(bool &IsTopNode) {
    if (ReadyQ.empty()) return NULL;
    std::pop_heap(ReadyQ.begin(), ReadyQ.end(), Cmp);
    SUnit *SU = ReadyQ.back();
    ReadyQ.pop_back();
    IsTopNode = false;
    DEBUG(dbgs() << "Pick node " << "SU(" << SU->NodeNum << ") "
          << " ILP: " << DAG->getDFSResult()->getILP(SU)
          << " Tree: " << DAG->getDFSResult()->getSubtreeID(SU) << " @"
          << DAG->getDFSResult()->getSubtreeLevel(
            DAG->getDFSResult()->getSubtreeID(SU)) << '\n'
          << "Scheduling " << *SU->getInstr());
  /// \brief Scheduler callback to notify that a new subtree is scheduled.
  virtual void scheduleTree(unsigned SubtreeID) {
    std::make_heap(ReadyQ.begin(), ReadyQ.end(), Cmp);
  }

  /// Callback after a node is scheduled. Mark a newly scheduled tree, notify
  /// DFSResults, and resort the priority Q.
  virtual void schedNode(SUnit *SU, bool IsTopNode) {
    assert(!IsTopNode && "SchedDFSResult needs bottom-up");
  }

  virtual void releaseTopNode(SUnit *) { /*only called for top roots*/ }

  virtual void releaseBottomNode(SUnit *SU) {
    ReadyQ.push_back(SU);
    std::push_heap(ReadyQ.begin(), ReadyQ.end(), Cmp);
  }
};
} // namespace

static ScheduleDAGInstrs *createILPMaxScheduler(MachineSchedContext *C) {
  return new ScheduleDAGMI(C, new ILPScheduler(true));
}
static ScheduleDAGInstrs *createILPMinScheduler(MachineSchedContext *C) {
  return new ScheduleDAGMI(C, new ILPScheduler(false));
}
static MachineSchedRegistry ILPMaxRegistry(
  "ilpmax", "Schedule bottom-up for max ILP", createILPMaxScheduler);
static MachineSchedRegistry ILPMinRegistry(
  "ilpmin", "Schedule bottom-up for min ILP", createILPMinScheduler);

//===----------------------------------------------------------------------===//
// Machine Instruction Shuffler for Correctness Testing
//===----------------------------------------------------------------------===//

#ifndef NDEBUG
namespace {
/// Apply a less-than relation on the node order, which corresponds to the
/// instruction order prior to scheduling. IsReverse implements greater-than.
template<bool IsReverse>
struct SUnitOrder {
    if (IsReverse)
      return A->NodeNum > B->NodeNum;
    else
      return A->NodeNum < B->NodeNum;
/// Reorder instructions as much as possible.
class InstructionShuffler : public MachineSchedStrategy {
  bool IsAlternating;
  bool IsTopDown;

  // Using a less-than relation (SUnitOrder<false>) for the TopQ priority
  // gives nodes with a higher number higher priority causing the latest
  // instructions to be scheduled first.
  PriorityQueue<SUnit*, std::vector<SUnit*>, SUnitOrder<false> >
    TopQ;
  // When scheduling bottom-up, use greater-than as the queue priority.
  PriorityQueue<SUnit*, std::vector<SUnit*>, SUnitOrder<true> >
    BottomQ;
  InstructionShuffler(bool alternate, bool topdown)
    : IsAlternating(alternate), IsTopDown(topdown) {}
  virtual void initialize(ScheduleDAGMI *) {
    TopQ.clear();
    BottomQ.clear();
  }
  /// Implement MachineSchedStrategy interface.
  /// -----------------------------------------

  virtual SUnit *pickNode(bool &IsTopNode) {
    SUnit *SU;
    if (IsTopDown) {
      do {
        if (TopQ.empty()) return NULL;
        SU = TopQ.top();
        TopQ.pop();
      } while (SU->isScheduled);
      IsTopNode = true;
    }
    else {
      do {
        if (BottomQ.empty()) return NULL;
        SU = BottomQ.top();
        BottomQ.pop();
      } while (SU->isScheduled);
      IsTopNode = false;
    }
    if (IsAlternating)
      IsTopDown = !IsTopDown;
  virtual void schedNode(SUnit *SU, bool IsTopNode) {}

  virtual void releaseTopNode(SUnit *SU) {
    TopQ.push(SU);
  }
  virtual void releaseBottomNode(SUnit *SU) {
    BottomQ.push(SU);
static ScheduleDAGInstrs *createInstructionShuffler(MachineSchedContext *C) {
  bool Alternate = !ForceTopDown && !ForceBottomUp;
  bool TopDown = !ForceBottomUp;
  assert((TopDown || !ForceTopDown) &&
         "-misched-topdown incompatible with -misched-bottomup");
  return new ScheduleDAGMI(C, new InstructionShuffler(Alternate, TopDown));
static MachineSchedRegistry ShufflerRegistry(
  "shuffle", "Shuffle machine instructions alternating directions",
  createInstructionShuffler);
#endif // !NDEBUG

//===----------------------------------------------------------------------===//
// GraphWriter support for ScheduleDAGMI.
//===----------------------------------------------------------------------===//

#ifndef NDEBUG
namespace llvm {

template<> struct GraphTraits<
  ScheduleDAGMI*> : public GraphTraits<ScheduleDAG*> {};

template<>
struct DOTGraphTraits<ScheduleDAGMI*> : public DefaultDOTGraphTraits {

  DOTGraphTraits (bool isSimple=false) : DefaultDOTGraphTraits(isSimple) {}

  static std::string getGraphName(const ScheduleDAG *G) {
    return G->MF.getName();
  }

  static bool renderGraphFromBottomUp() {
    return true;
  }

  static bool isNodeHidden(const SUnit *Node) {
    return (Node->NumPreds > 10 || Node->NumSuccs > 10);
  }

  static bool hasNodeAddressLabel(const SUnit *Node,
                                  const ScheduleDAG *Graph) {
    return false;
  }

  /// If you want to override the dot attributes printed for a particular
  /// edge, override this method.
  static std::string getEdgeAttributes(const SUnit *Node,
                                       SUnitIterator EI,
                                       const ScheduleDAG *Graph) {
    if (EI.isArtificialDep())
      return "color=cyan,style=dashed";
    if (EI.isCtrlDep())
      return "color=blue,style=dashed";
    return "";
  }

  static std::string getNodeLabel(const SUnit *SU, const ScheduleDAG *G) {
    std::string Str;
    raw_string_ostream SS(Str);
    SS << "SU(" << SU->NodeNum << ')';
    return SS.str();
  }
  static std::string getNodeDescription(const SUnit *SU, const ScheduleDAG *G) {
    return G->getGraphNodeLabel(SU);
  }

  static std::string getNodeAttributes(const SUnit *N,
                                       const ScheduleDAG *Graph) {
    std::string Str("shape=Mrecord");
    const SchedDFSResult *DFS =
      static_cast<const ScheduleDAGMI*>(Graph)->getDFSResult();
    if (DFS) {
      Str += ",style=filled,fillcolor=\"#";
      Str += DOT::getColorString(DFS->getSubtreeID(N));
      Str += '"';
    }
    return Str;
  }
};
} // namespace llvm
#endif // NDEBUG

/// viewGraph - Pop up a ghostview window with the reachable parts of the DAG
/// rendered using 'dot'.
///
void ScheduleDAGMI::viewGraph(const Twine &Name, const Twine &Title) {
#ifndef NDEBUG
  ViewGraph(this, Name, false, Title);
#else
  errs() << "ScheduleDAGMI::viewGraph is only available in debug builds on "
         << "systems with Graphviz or gv!\n";
#endif  // NDEBUG
}

/// Out-of-line implementation with no arguments is handy for gdb.
void ScheduleDAGMI::viewGraph() {
  viewGraph(getDAGName(), "Scheduling-Units Graph for " + getDAGName());
}