[llvm] [CodeGen] Add complete-schedule search strategy API (PR #220644)
Kevin Choi via llvm-commits
llvm-commits at lists.llvm.org
Wed Sep 30 10:54:17 PDT 2026
https://github.com/choikwa updated https://github.com/llvm/llvm-project/pull/220644
>From c14a26782ff76cbcb4b48172653c833c317f32d5 Mon Sep 17 00:00:00 2001
From: choikwa <code.kchoi at gmail.com>
Date: Tue, 1 Sep 2026 22:12:08 +0000
Subject: [PATCH 1/5] [CodeGen] Add complete-schedule search strategy API
Add target-independent support for schedulers that explore complete legal instruction orders instead of choosing one ready node at a time. MachineSchedSearchRegion exposes stable region-local ordinals, strong dependency legality, a topological fallback, and legal relocation ranges. MachineSchedCompleteScheduleOptimizer receives an explicit founder schedule and may return a replacement, leaving target-specific search, scoring, feature extraction, and model inference outside generic CodeGen.
Provide two integration paths for the optimizer. MachineSchedCompleteScheduleReplayer adapts a complete result to the existing incremental MachineSchedStrategy loop, while ScheduleDAGMI can invoke the same optimizer after an ordinary scheduler has materialized its actual founder. Results are validated before use; declined, invalid, and incomplete-cutoff cases preserve the founder. Schedule application reuses LLVM instruction movement and LiveIntervals maintenance, with ScheduleDAGMILive repairing liveness flags after a post-schedule rewrite. These entry points allow targets to plug in SA, learned search, replay, or trajectory collection without placing those policies in the base scheduler.
---
.../include/llvm/CodeGen/MachineSchedSearch.h | 127 ++++++++
llvm/include/llvm/CodeGen/MachineScheduler.h | 49 ++++
llvm/lib/CodeGen/CMakeLists.txt | 1 +
llvm/lib/CodeGen/MachineSchedSearch.cpp | 245 ++++++++++++++++
llvm/lib/CodeGen/MachineScheduler.cpp | 5 +
llvm/unittests/CodeGen/CMakeLists.txt | 1 +
.../CodeGen/MachineSchedSearchTest.cpp | 277 ++++++++++++++++++
7 files changed, 705 insertions(+)
create mode 100644 llvm/include/llvm/CodeGen/MachineSchedSearch.h
create mode 100644 llvm/lib/CodeGen/MachineSchedSearch.cpp
create mode 100644 llvm/unittests/CodeGen/MachineSchedSearchTest.cpp
diff --git a/llvm/include/llvm/CodeGen/MachineSchedSearch.h b/llvm/include/llvm/CodeGen/MachineSchedSearch.h
new file mode 100644
index 0000000000000..6476b185f99dc
--- /dev/null
+++ b/llvm/include/llvm/CodeGen/MachineSchedSearch.h
@@ -0,0 +1,127 @@
+//===- MachineSchedSearch.h - Complete schedule search ----------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file provides common support for exploring complete instruction orders.
+// A complete-schedule optimizer can either replace the normal scheduling
+// strategy through a replay adapter or refine the schedule materialized by an
+// existing strategy through ScheduleDAGMI's post-scheduling hook.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CODEGEN_MACHINESCHEDSEARCH_H
+#define LLVM_CODEGEN_MACHINESCHEDSEARCH_H
+
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/CodeGen/MachineScheduler.h"
+#include "llvm/Support/Compiler.h"
+
+namespace llvm {
+
+/// A read-only, region-local view of a fully constructed machine scheduling
+/// DAG for complete-schedule exploration.
+///
+/// Nodes are identified by stable ordinals in [0, size()). Candidate schedules
+/// are complete permutations of those ordinals. Only strong dependencies are
+/// legality constraints; weak edges remain scheduling preferences.
+///
+/// This view is intended for search-based schedulers that evaluate multiple
+/// complete schedules without mutating the MachineFunction. It is valid only
+/// while the underlying SUnit storage remains alive and unchanged.
+class LLVM_ABI MachineSchedSearchRegion {
+public:
+ struct MoveRange {
+ /// First legal final position after removing and reinserting the node.
+ unsigned Begin;
+ /// Last legal final position after removing and reinserting the node.
+ unsigned End;
+ };
+
+private:
+ const ScheduleDAGMI *DAG = nullptr;
+ ArrayRef<SUnit> Nodes;
+ SmallVector<SmallVector<unsigned, 4>, 0> Predecessors;
+ SmallVector<SmallVector<unsigned, 4>, 0> Successors;
+
+public:
+ explicit MachineSchedSearchRegion(ArrayRef<SUnit> Nodes);
+ explicit MachineSchedSearchRegion(ScheduleDAGMI &DAG);
+
+ /// Return the underlying scheduler DAG, or nullptr when the view was
+ /// constructed directly from SUnit storage.
+ const ScheduleDAGMI *getDAG() const { return DAG; }
+ unsigned size() const { return Predecessors.size(); }
+ const SUnit &getSUnit(unsigned Node) const;
+
+ ArrayRef<unsigned> predecessors(unsigned Node) const {
+ return Predecessors[Node];
+ }
+ ArrayRef<unsigned> successors(unsigned Node) const {
+ return Successors[Node];
+ }
+
+ /// Return the order in which nodes appeared when the DAG was built.
+ SmallVector<unsigned, 0> getInitialOrder() const;
+
+ /// Return a stable topological order, preferring lower node ordinals when
+ /// more than one node is ready.
+ SmallVector<unsigned, 0> getTopologicalOrder() const;
+
+ /// Return whether \p Order is a complete permutation that preserves every
+ /// strong dependency in the scheduling DAG.
+ bool isLegalOrder(ArrayRef<unsigned> Order) const;
+
+ /// Return the inclusive range of final positions to which \p Node may be
+ /// relocated while all other nodes retain their relative order. Returns
+ /// false if the input order is not legal or the node ordinal is invalid.
+ bool getLegalMoveRange(ArrayRef<unsigned> Order, unsigned Node,
+ MoveRange &Range) const;
+};
+
+/// Adapter for schedulers that compute a complete schedule before LLVM begins
+/// applying scheduling decisions.
+///
+/// An owned MachineSchedCompleteScheduleOptimizer receives the incoming legal
+/// order as its founder and may return a replacement. The selected order is
+/// validated before use and replayed top-down through ScheduleDAGMI's
+/// incremental MachineSchedStrategy interface. When used with
+/// ScheduleDAGMILive, instruction movement, LiveIntervals, and
+/// register-pressure accounting therefore remain owned by the existing
+/// scheduler.
+///
+/// This is not a common base for all search-based schedulers. Strategies that
+/// choose each node from the current ready set should implement
+/// MachineSchedStrategy directly or derive from another incremental strategy.
+///
+/// If the optimizer declines to provide an order or returns an invalid one, the
+/// existing order is preserved when legal. Otherwise, a stable topological
+/// order is used.
+class LLVM_ABI MachineSchedCompleteScheduleReplayer
+ : public MachineSchedStrategy {
+ std::unique_ptr<MachineSchedCompleteScheduleOptimizer> Optimizer;
+ SmallVector<SUnit *, 0> CompleteSchedule;
+ unsigned NextNodeToReplay = 0;
+ bool UsedOptimizedSchedule = false;
+
+public:
+ explicit MachineSchedCompleteScheduleReplayer(
+ std::unique_ptr<MachineSchedCompleteScheduleOptimizer> Optimizer);
+ ~MachineSchedCompleteScheduleReplayer() override;
+
+ void initialize(ScheduleDAGMI *DAG) override;
+ SUnit *pickNode(bool &IsTopNode) override;
+ void schedNode(SUnit *, bool) override {}
+ void releaseTopNode(SUnit *) override {}
+ void releaseBottomNode(SUnit *) override {}
+
+ bool usedOptimizedSchedule() const { return UsedOptimizedSchedule; }
+};
+
+} // namespace llvm
+
+#endif // LLVM_CODEGEN_MACHINESCHEDSEARCH_H
diff --git a/llvm/include/llvm/CodeGen/MachineScheduler.h b/llvm/include/llvm/CodeGen/MachineScheduler.h
index 5280a2376c34a..cac8b2f9e9afc 100644
--- a/llvm/include/llvm/CodeGen/MachineScheduler.h
+++ b/llvm/include/llvm/CodeGen/MachineScheduler.h
@@ -133,6 +133,7 @@ class LiveIntervals;
class MachineFunction;
class MachineInstr;
class MachineLoopInfo;
+class MachineSchedSearchRegion;
class RegisterClassInfo;
class SchedDFSResult;
class TargetInstrInfo;
@@ -305,6 +306,31 @@ class LLVM_ABI MachineSchedStrategy {
virtual void releaseBottomNode(SUnit *SU) = 0;
};
+/// Interface for transforming one complete legal schedule into another.
+///
+/// Founder is explicit so the same optimizer can run either before scheduling,
+/// with the incoming order as founder, or after an existing strategy has
+/// materialized a target-selected founder.
+class LLVM_ABI MachineSchedCompleteScheduleOptimizer {
+public:
+ virtual ~MachineSchedCompleteScheduleOptimizer();
+
+ /// Optimize Founder and write a complete region-local SUnit permutation to
+ /// Result. Return false to preserve Founder. The caller validates generic
+ /// permutation and strong-dependency legality before applying Result.
+ virtual bool optimizeCompleteSchedule(const MachineSchedSearchRegion &Region,
+ ArrayRef<unsigned> Founder,
+ SmallVectorImpl<unsigned> &Result) = 0;
+
+ /// Perform any additional client-specific validation that is not represented
+ /// by strong DAG dependencies. This is called only for a complete,
+ /// dependency-preserving result returned by optimizeCompleteSchedule().
+ virtual bool validateCompleteSchedule(const MachineSchedSearchRegion &Region,
+ ArrayRef<unsigned> Result) {
+ return true;
+ }
+};
+
/// ScheduleDAGMI is an implementation of ScheduleDAGInstrs that simply
/// schedules machine instructions according to the given MachineSchedStrategy
/// without much extra book-keeping. This is the common functionality between
@@ -316,6 +342,10 @@ class LLVM_ABI ScheduleDAGMI : public ScheduleDAGInstrs {
MachineBlockFrequencyInfo *MBFI;
std::unique_ptr<MachineSchedStrategy> SchedImpl;
+ /// Optional optimizer invoked after SchedImpl has materialized a complete
+ /// schedule for a region.
+ std::unique_ptr<MachineSchedCompleteScheduleOptimizer> PostSchedOptimizer;
+
/// Ordered list of DAG postprocessing steps.
std::vector<std::unique_ptr<ScheduleDAGMutation>> Mutations;
@@ -363,6 +393,11 @@ class LLVM_ABI ScheduleDAGMI : public ScheduleDAGInstrs {
Mutations.push_back(std::move(Mutation));
}
+ /// Install an optimizer that may replace the complete schedule produced by
+ /// SchedImpl. ScheduleDAGMI takes ownership of the optimizer.
+ void setPostScheduleOptimizer(
+ std::unique_ptr<MachineSchedCompleteScheduleOptimizer> Optimizer);
+
MachineBasicBlock::iterator top() const { return CurrentTop; }
MachineBasicBlock::iterator bottom() const { return CurrentBottom; }
@@ -395,6 +430,18 @@ class LLVM_ABI ScheduleDAGMI : public ScheduleDAGInstrs {
/// instances of ScheduleDAGMI to perform custom DAG postprocessing.
void postProcessDAG();
+ /// Invoke PostSchedOptimizer on the materialized schedule, if present.
+ /// This runs only after every SUnit has been scheduled. It does not rebuild
+ /// ready queues or the register-pressure state used while selecting nodes.
+ void runPostScheduleOptimizer();
+
+ /// Apply a complete region-local SUnit order to the instruction stream.
+ /// The order must already have been validated against the current DAG.
+ /// Target DAGs may override this to update target metadata derived from the
+ /// final instruction order, but should delegate instruction movement and
+ /// generic liveness maintenance to the base implementation.
+ virtual void applyCompleteSchedule(ArrayRef<unsigned> Order);
+
/// Release ExitSU predecessors and setup scheduler queues.
void initQueues(ArrayRef<SUnit*> TopRoots, ArrayRef<SUnit*> BotRoots);
@@ -426,6 +473,8 @@ class LLVM_ABI ScheduleDAGMI : public ScheduleDAGInstrs {
/// machine instructions while updating LiveIntervals and tracking regpressure.
class LLVM_ABI ScheduleDAGMILive : public ScheduleDAGMI {
protected:
+ void applyCompleteSchedule(ArrayRef<unsigned> Order) override;
+
RegisterClassInfo *RegClassInfo;
/// Information about DAG subtrees. If DFSResult is NULL, then SchedulerTrees
diff --git a/llvm/lib/CodeGen/CMakeLists.txt b/llvm/lib/CodeGen/CMakeLists.txt
index 99dfb4bb09df7..1af30b8c2033f 100644
--- a/llvm/lib/CodeGen/CMakeLists.txt
+++ b/llvm/lib/CodeGen/CMakeLists.txt
@@ -158,6 +158,7 @@ add_llvm_component_library(LLVMCodeGen
MachineRegionInfo.cpp
MachineRegisterInfo.cpp
MachineScheduler.cpp
+ MachineSchedSearch.cpp
MachineSink.cpp
MachineSizeOpts.cpp
MachineSSAContext.cpp
diff --git a/llvm/lib/CodeGen/MachineSchedSearch.cpp b/llvm/lib/CodeGen/MachineSchedSearch.cpp
new file mode 100644
index 0000000000000..fa5ef43f1715f
--- /dev/null
+++ b/llvm/lib/CodeGen/MachineSchedSearch.cpp
@@ -0,0 +1,245 @@
+//===- MachineSchedSearch.cpp - Complete schedule search ------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/CodeGen/MachineSchedSearch.h"
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/STLExtras.h"
+#include <algorithm>
+#include <functional>
+#include <numeric>
+#include <queue>
+#include <vector>
+
+using namespace llvm;
+
+MachineSchedCompleteScheduleOptimizer::
+ ~MachineSchedCompleteScheduleOptimizer() = default;
+
+MachineSchedSearchRegion::MachineSchedSearchRegion(ArrayRef<SUnit> Nodes)
+ : Nodes(Nodes), Predecessors(Nodes.size()), Successors(Nodes.size()) {
+ DenseMap<const SUnit *, unsigned> Ordinals;
+ for (auto [Ordinal, SU] : enumerate(Nodes))
+ Ordinals[&SU] = Ordinal;
+
+ for (auto [Ordinal, SU] : enumerate(Nodes)) {
+ for (const SDep &Pred : SU.Preds) {
+ const SUnit *PredSU = Pred.getSUnit();
+ if (Pred.isWeak() || PredSU->isBoundaryNode())
+ continue;
+ auto PredOrdinal = Ordinals.find(PredSU);
+ assert(PredOrdinal != Ordinals.end() &&
+ "predecessor must belong to the scheduling region");
+ if (PredOrdinal == Ordinals.end())
+ continue;
+ Predecessors[Ordinal].push_back(PredOrdinal->second);
+ Successors[PredOrdinal->second].push_back(Ordinal);
+ }
+ }
+}
+
+MachineSchedSearchRegion::MachineSchedSearchRegion(ScheduleDAGMI &DAG)
+ : MachineSchedSearchRegion(DAG.SUnits) {
+ this->DAG = &DAG;
+}
+
+const SUnit &MachineSchedSearchRegion::getSUnit(unsigned Node) const {
+ assert(Node < size() && "invalid scheduling node ordinal");
+ return Nodes[Node];
+}
+
+SmallVector<unsigned, 0> MachineSchedSearchRegion::getInitialOrder() const {
+ SmallVector<unsigned, 0> Order(size());
+ std::iota(Order.begin(), Order.end(), 0);
+ return Order;
+}
+
+SmallVector<unsigned, 0> MachineSchedSearchRegion::getTopologicalOrder() const {
+ SmallVector<unsigned, 0> RemainingPredecessors;
+ RemainingPredecessors.reserve(size());
+ for (unsigned Node = 0; Node != size(); ++Node)
+ RemainingPredecessors.push_back(predecessors(Node).size());
+
+ std::priority_queue<unsigned, std::vector<unsigned>, std::greater<unsigned>>
+ Ready;
+ for (unsigned Node = 0; Node != size(); ++Node)
+ if (RemainingPredecessors[Node] == 0)
+ Ready.push(Node);
+
+ SmallVector<unsigned, 0> Order;
+ Order.reserve(size());
+ while (!Ready.empty()) {
+ unsigned Node = Ready.top();
+ Ready.pop();
+ Order.push_back(Node);
+ for (unsigned Succ : successors(Node)) {
+ assert(RemainingPredecessors[Succ] != 0 &&
+ "inconsistent machine scheduling DAG");
+ --RemainingPredecessors[Succ];
+ if (RemainingPredecessors[Succ] == 0)
+ Ready.push(Succ);
+ }
+ }
+ assert(Order.size() == size() &&
+ "machine scheduling DAG contains a strong dependency cycle");
+ return Order;
+}
+
+bool MachineSchedSearchRegion::isLegalOrder(ArrayRef<unsigned> Order) const {
+ if (Order.size() != size())
+ return false;
+
+ SmallVector<unsigned, 0> Position(size(), size());
+ for (auto [Pos, Node] : enumerate(Order)) {
+ if (Node >= size() || Position[Node] != size())
+ return false;
+ Position[Node] = Pos;
+ }
+
+ for (unsigned Node = 0; Node != size(); ++Node)
+ for (unsigned Pred : predecessors(Node))
+ if (Position[Pred] >= Position[Node])
+ return false;
+ return true;
+}
+
+bool MachineSchedSearchRegion::getLegalMoveRange(ArrayRef<unsigned> Order,
+ unsigned Node,
+ MoveRange &Range) const {
+ if (Node >= size() || !isLegalOrder(Order))
+ return false;
+
+ SmallVector<unsigned, 0> Position(size());
+ for (auto [Pos, Ordinal] : enumerate(Order))
+ Position[Ordinal] = Pos;
+
+ Range.Begin = 0;
+ Range.End = size() - 1;
+ for (unsigned Pred : predecessors(Node))
+ Range.Begin = std::max(Range.Begin, Position[Pred] + 1);
+ for (unsigned Succ : successors(Node))
+ Range.End = std::min(Range.End, Position[Succ] - 1);
+ return true;
+}
+
+MachineSchedCompleteScheduleReplayer::MachineSchedCompleteScheduleReplayer(
+ std::unique_ptr<MachineSchedCompleteScheduleOptimizer> Optimizer)
+ : Optimizer(std::move(Optimizer)) {
+ assert(this->Optimizer && "complete-schedule optimizer must be provided");
+}
+
+MachineSchedCompleteScheduleReplayer::~MachineSchedCompleteScheduleReplayer() =
+ default;
+
+void MachineSchedCompleteScheduleReplayer::initialize(ScheduleDAGMI *DAG) {
+ MachineSchedSearchRegion Region(*DAG);
+ SmallVector<unsigned, 0> Founder = Region.getInitialOrder();
+ if (!Region.isLegalOrder(Founder))
+ Founder = Region.getTopologicalOrder();
+
+ SmallVector<unsigned, 0> Order;
+ UsedOptimizedSchedule =
+ Optimizer->optimizeCompleteSchedule(Region, Founder, Order) &&
+ Region.isLegalOrder(Order) &&
+ Optimizer->validateCompleteSchedule(Region, Order);
+ if (!UsedOptimizedSchedule) {
+ Order = std::move(Founder);
+ }
+
+ CompleteSchedule.clear();
+ CompleteSchedule.reserve(Order.size());
+ for (unsigned Node : Order)
+ CompleteSchedule.push_back(&DAG->SUnits[Node]);
+ NextNodeToReplay = 0;
+}
+
+SUnit *MachineSchedCompleteScheduleReplayer::pickNode(bool &IsTopNode) {
+ if (NextNodeToReplay == CompleteSchedule.size())
+ return nullptr;
+ SUnit *SU = CompleteSchedule[NextNodeToReplay++];
+ assert(SU->isTopReady() &&
+ "validated complete schedule contains an unavailable node");
+ IsTopNode = true;
+ return SU;
+}
+
+void ScheduleDAGMI::setPostScheduleOptimizer(
+ std::unique_ptr<MachineSchedCompleteScheduleOptimizer> Optimizer) {
+ PostSchedOptimizer = std::move(Optimizer);
+}
+
+void ScheduleDAGMI::runPostScheduleOptimizer() {
+ if (!PostSchedOptimizer || SUnits.empty())
+ return;
+
+ // A debug scheduling cutoff makes CurrentTop meet CurrentBottom without
+ // necessarily scheduling the whole region. There is no complete founder to
+ // optimize in that case.
+ if (!llvm::all_of(SUnits, [](const SUnit &SU) { return SU.isScheduled; }))
+ return;
+
+ MachineSchedSearchRegion Region(*this);
+ DenseMap<const MachineInstr *, unsigned> Ordinals;
+ for (auto [Ordinal, SU] : enumerate(SUnits))
+ Ordinals[SU.getInstr()] = Ordinal;
+
+ SmallVector<unsigned, 0> Founder;
+ Founder.reserve(SUnits.size());
+ for (auto I = RegionBegin; I != RegionEnd; ++I) {
+ auto Ordinal = Ordinals.find(&*I);
+ if (Ordinal != Ordinals.end())
+ Founder.push_back(Ordinal->second);
+ }
+ if (!Region.isLegalOrder(Founder))
+ return;
+
+ SmallVector<unsigned, 0> Result;
+ if (!PostSchedOptimizer->optimizeCompleteSchedule(Region, Founder, Result) ||
+ !Region.isLegalOrder(Result) ||
+ !PostSchedOptimizer->validateCompleteSchedule(Region, Result) ||
+ Result == Founder)
+ return;
+
+ applyCompleteSchedule(Result);
+}
+
+void ScheduleDAGMI::applyCompleteSchedule(ArrayRef<unsigned> Order) {
+ MachineBasicBlock::iterator InsertPos = RegionBegin;
+ for (unsigned Node : Order) {
+ MachineInstr *MI = SUnits[Node].getInstr();
+ while (InsertPos != RegionEnd && InsertPos->isDebugInstr())
+ ++InsertPos;
+ if (InsertPos != RegionEnd && &*InsertPos == MI) {
+ ++InsertPos;
+ continue;
+ }
+ moveInstruction(MI, InsertPos);
+ InsertPos = std::next(MI->getIterator());
+ }
+}
+
+void ScheduleDAGMILive::applyCompleteSchedule(ArrayRef<unsigned> Order) {
+ ScheduleDAGMI::applyCompleteSchedule(Order);
+ if (!ShouldTrackPressure)
+ return;
+
+ for (unsigned Node : Order) {
+ MachineInstr *MI = SUnits[Node].getInstr();
+ // A prior ordering may have introduced read-undef flags that are no longer
+ // valid. Recompute them together with the other liveness flags below.
+ for (MachineOperand &Op : MI->all_defs())
+ Op.setIsUndef(false);
+
+ RegisterOperands RegOpers;
+ RegOpers.collect(*MI, *TRI, MRI, ShouldTrackLaneMasks,
+ /*IgnoreDead=*/false);
+ if (ShouldTrackLaneMasks)
+ RegOpers.adjustLaneLiveness(*LIS, MRI, *MI);
+ else
+ RegOpers.detectDeadDefs(*MI, *LIS);
+ }
+}
diff --git a/llvm/lib/CodeGen/MachineScheduler.cpp b/llvm/lib/CodeGen/MachineScheduler.cpp
index 814ea3f8eeb05..fea955e4a4342 100644
--- a/llvm/lib/CodeGen/MachineScheduler.cpp
+++ b/llvm/lib/CodeGen/MachineScheduler.cpp
@@ -32,6 +32,7 @@
#include "llvm/CodeGen/MachineOperand.h"
#include "llvm/CodeGen/MachinePassRegistry.h"
#include "llvm/CodeGen/MachineRegisterInfo.h"
+#include "llvm/CodeGen/MachineSchedSearch.h"
#include "llvm/CodeGen/RegisterClassInfo.h"
#include "llvm/CodeGen/RegisterPressure.h"
#include "llvm/CodeGen/ScheduleDAG.h"
@@ -1118,6 +1119,8 @@ void ScheduleDAGMI::schedule() {
}
assert(CurrentTop == CurrentBottom && "Nonempty unscheduled zone.");
+ runPostScheduleOptimizer();
+
placeDebugValues();
LLVM_DEBUG({
@@ -1738,6 +1741,8 @@ void ScheduleDAGMILive::schedule() {
}
assert(CurrentTop == CurrentBottom && "Nonempty unscheduled zone.");
+ runPostScheduleOptimizer();
+
placeDebugValues();
LLVM_DEBUG({
diff --git a/llvm/unittests/CodeGen/CMakeLists.txt b/llvm/unittests/CodeGen/CMakeLists.txt
index deaa3dbcfcc5b..eb632f15d21d0 100644
--- a/llvm/unittests/CodeGen/CMakeLists.txt
+++ b/llvm/unittests/CodeGen/CMakeLists.txt
@@ -39,6 +39,7 @@ add_llvm_unittest(CodeGenTests
MachineInstrTest.cpp
MachineModuleInfoTest.cpp
MachineOperandTest.cpp
+ MachineSchedSearchTest.cpp
MIR2VecTest.cpp
RegAllocBasicTest.cpp
RegAllocScoreTest.cpp
diff --git a/llvm/unittests/CodeGen/MachineSchedSearchTest.cpp b/llvm/unittests/CodeGen/MachineSchedSearchTest.cpp
new file mode 100644
index 0000000000000..9ab9a4732798d
--- /dev/null
+++ b/llvm/unittests/CodeGen/MachineSchedSearchTest.cpp
@@ -0,0 +1,277 @@
+#include "llvm/CodeGen/MachineSchedSearch.h"
+#include "llvm/CodeGen/CodeGenTargetMachineImpl.h"
+#include "llvm/CodeGen/MachineFunction.h"
+#include "llvm/CodeGen/MachineModuleInfo.h"
+#include "llvm/CodeGen/TargetFrameLowering.h"
+#include "llvm/CodeGen/TargetInstrInfo.h"
+#include "llvm/CodeGen/TargetLowering.h"
+#include "llvm/CodeGen/TargetSubtargetInfo.h"
+#include "llvm/IR/Module.h"
+#include "llvm/MC/MCAsmInfo.h"
+#include "llvm/MC/TargetRegistry.h"
+#include "llvm/Target/TargetOptions.h"
+#include "llvm/TargetParser/Triple.h"
+#include "gtest/gtest.h"
+#include <array>
+
+using namespace llvm;
+
+namespace {
+
+// Include helper functions to construct a target-independent MachineFunction.
+#include "MFCommon.inc"
+
+void initializeTestDAG(std::array<SUnit, 5> &Nodes) {
+ for (unsigned I = 0; I != Nodes.size(); ++I)
+ Nodes[I].NodeNum = I;
+
+ // 0 --\
+ // 2 -> 3 4
+ // 1 --/
+ Nodes[2].addPred(SDep(&Nodes[0], SDep::Artificial));
+ Nodes[2].addPred(SDep(&Nodes[1], SDep::Artificial));
+ Nodes[3].addPred(SDep(&Nodes[2], SDep::Artificial));
+}
+
+class TestCompleteScheduleOptimizer final
+ : public MachineSchedCompleteScheduleOptimizer {
+ SmallVector<unsigned> Result;
+ SmallVector<unsigned> *SeenFounder;
+ bool AcceptResult;
+ bool AcceptValidation;
+
+public:
+ TestCompleteScheduleOptimizer(ArrayRef<unsigned> Result,
+ bool AcceptResult = true,
+ bool AcceptValidation = true,
+ SmallVector<unsigned> *SeenFounder = nullptr)
+ : Result(Result), SeenFounder(SeenFounder), AcceptResult(AcceptResult),
+ AcceptValidation(AcceptValidation) {}
+
+ bool optimizeCompleteSchedule(const MachineSchedSearchRegion &,
+ ArrayRef<unsigned> Founder,
+ SmallVectorImpl<unsigned> &Order) override {
+ if (SeenFounder)
+ SeenFounder->assign(Founder.begin(), Founder.end());
+ Order.assign(Result.begin(), Result.end());
+ return AcceptResult;
+ }
+
+ bool validateCompleteSchedule(const MachineSchedSearchRegion &,
+ ArrayRef<unsigned>) override {
+ return AcceptValidation;
+ }
+};
+
+class TestScheduleDAGMI final : public ScheduleDAGMI {
+public:
+ TestScheduleDAGMI(MachineSchedContext *Context,
+ std::unique_ptr<MachineSchedStrategy> Strategy)
+ : ScheduleDAGMI(Context, std::move(Strategy),
+ /*RemoveKillFlags=*/false) {}
+
+ using ScheduleDAGMI::runPostScheduleOptimizer;
+};
+
+SmallVector<unsigned> initializeAndPick(ArrayRef<unsigned> Result,
+ bool AcceptResult = true,
+ bool AcceptValidation = true) {
+ LLVMContext Context;
+ Module M("MachineSchedSearchTest", Context);
+ std::unique_ptr<MachineFunction> MF = createMachineFunction(Context, M);
+ MachineSchedContext SchedContext;
+ SchedContext.MF = MF.get();
+
+ auto Optimizer = std::make_unique<TestCompleteScheduleOptimizer>(
+ Result, AcceptResult, AcceptValidation);
+ auto Strategy = std::make_unique<MachineSchedCompleteScheduleReplayer>(
+ std::move(Optimizer));
+ MachineSchedCompleteScheduleReplayer *StrategyPtr = Strategy.get();
+ ScheduleDAGMI DAG(&SchedContext, std::move(Strategy),
+ /*RemoveKillFlags=*/false);
+ DAG.SUnits.resize(3);
+ for (unsigned I = 0; I != DAG.SUnits.size(); ++I)
+ DAG.SUnits[I].NodeNum = I;
+
+ StrategyPtr->initialize(&DAG);
+ SmallVector<unsigned> Picked;
+ bool IsTopNode = false;
+ while (SUnit *SU = StrategyPtr->pickNode(IsTopNode)) {
+ EXPECT_TRUE(IsTopNode);
+ Picked.push_back(SU->NodeNum);
+ }
+ return Picked;
+}
+
+SmallVector<unsigned>
+runPostOptimization(ArrayRef<unsigned> Result, bool AcceptResult = true,
+ bool AcceptValidation = true,
+ bool MaterializeCompleteFounder = true,
+ SmallVector<unsigned> *SeenFounder = nullptr,
+ ArrayRef<unsigned> MaterializedFounder = {}) {
+ LLVMContext Context;
+ Module M("MachineSchedCompleteScheduleOptimizerTest", Context);
+ std::unique_ptr<MachineFunction> MF = createMachineFunction(Context, M);
+ MachineBasicBlock *MBB = MF->CreateMachineBasicBlock();
+ MF->push_back(MBB);
+
+ MCInstrDesc Desc = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ std::array<MachineInstr *, 3> Instructions;
+ for (MachineInstr *&MI : Instructions)
+ MI = MF->CreateMachineInstr(Desc, DebugLoc());
+ if (MaterializedFounder.empty()) {
+ for (MachineInstr *MI : Instructions)
+ MBB->push_back(MI);
+ } else {
+ for (unsigned Node : MaterializedFounder)
+ MBB->push_back(Instructions[Node]);
+ }
+
+ MachineSchedContext SchedContext;
+ SchedContext.MF = MF.get();
+ auto ReplayOptimizer = std::make_unique<TestCompleteScheduleOptimizer>(
+ SmallVector<unsigned, 3>{0, 1, 2});
+ auto Strategy = std::make_unique<MachineSchedCompleteScheduleReplayer>(
+ std::move(ReplayOptimizer));
+ TestScheduleDAGMI DAG(&SchedContext, std::move(Strategy));
+ DAG.startBlock(MBB);
+ DAG.enterRegion(MBB, MBB->begin(), MBB->end(), Instructions.size());
+ for (auto [Ordinal, MI] : enumerate(Instructions)) {
+ DAG.SUnits.emplace_back(MI, Ordinal);
+ DAG.SUnits.back().isScheduled = MaterializeCompleteFounder;
+ }
+
+ DAG.setPostScheduleOptimizer(std::make_unique<TestCompleteScheduleOptimizer>(
+ Result, AcceptResult, AcceptValidation, SeenFounder));
+ DAG.runPostScheduleOptimizer();
+
+ SmallVector<unsigned> Order;
+ for (MachineInstr &MI : *MBB) {
+ auto I = llvm::find(Instructions, &MI);
+ if (I == Instructions.end()) {
+ ADD_FAILURE() << "instruction not found in original region";
+ return {};
+ }
+ Order.push_back(std::distance(Instructions.begin(), I));
+ }
+ return Order;
+}
+
+} // namespace
+
+TEST(MachineSchedSearchRegion, ValidatesCompleteOrders) {
+ std::array<SUnit, 5> Nodes;
+ initializeTestDAG(Nodes);
+ MachineSchedSearchRegion Region(Nodes);
+
+ EXPECT_TRUE(Region.isLegalOrder({0, 1, 2, 3, 4}));
+ EXPECT_TRUE(Region.isLegalOrder({4, 1, 0, 2, 3}));
+
+ EXPECT_FALSE(Region.isLegalOrder({0, 1, 2, 3}));
+ EXPECT_FALSE(Region.isLegalOrder({0, 1, 2, 3, 5}));
+ EXPECT_FALSE(Region.isLegalOrder({0, 1, 2, 3, 3}));
+ EXPECT_FALSE(Region.isLegalOrder({2, 0, 1, 3, 4}));
+ EXPECT_FALSE(Region.isLegalOrder({0, 1, 3, 2, 4}));
+}
+
+TEST(MachineSchedSearchRegion, WeakEdgesArePreferences) {
+ std::array<SUnit, 2> Nodes;
+ for (unsigned I = 0; I != Nodes.size(); ++I)
+ Nodes[I].NodeNum = I;
+ Nodes[1].addPred(SDep(&Nodes[0], SDep::Weak));
+
+ MachineSchedSearchRegion Region(Nodes);
+ EXPECT_TRUE(Region.isLegalOrder({0, 1}));
+ EXPECT_TRUE(Region.isLegalOrder({1, 0}));
+}
+
+TEST(MachineSchedSearchRegion, UsesViewLocalOrdinals) {
+ std::array<SUnit, 2> Nodes;
+ Nodes[0].NodeNum = 17;
+ Nodes[1].NodeNum = 9;
+ Nodes[1].addPred(SDep(&Nodes[0], SDep::Artificial));
+
+ MachineSchedSearchRegion Region(Nodes);
+ EXPECT_TRUE(Region.isLegalOrder({0, 1}));
+ EXPECT_FALSE(Region.isLegalOrder({1, 0}));
+ EXPECT_EQ(&Region.getSUnit(0), &Nodes[0]);
+ EXPECT_EQ(&Region.getSUnit(1), &Nodes[1]);
+}
+
+TEST(MachineSchedSearchRegion, BuildsStableTopologicalFallback) {
+ std::array<SUnit, 3> Nodes;
+ for (unsigned I = 0; I != Nodes.size(); ++I)
+ Nodes[I].NodeNum = I;
+ Nodes[0].addPred(SDep(&Nodes[1], SDep::Artificial));
+
+ MachineSchedSearchRegion Region(Nodes);
+ EXPECT_FALSE(Region.isLegalOrder(Region.getInitialOrder()));
+ EXPECT_EQ(Region.getTopologicalOrder(), (SmallVector<unsigned, 3>{1, 0, 2}));
+}
+
+TEST(MachineSchedSearchRegion, ComputesLegalRelocationRange) {
+ std::array<SUnit, 5> Nodes;
+ initializeTestDAG(Nodes);
+ MachineSchedSearchRegion Region(Nodes);
+ const unsigned Order[] = {0, 1, 2, 3, 4};
+ MachineSchedSearchRegion::MoveRange Range;
+
+ ASSERT_TRUE(Region.getLegalMoveRange(Order, 0, Range));
+ EXPECT_EQ(Range.Begin, 0u);
+ EXPECT_EQ(Range.End, 1u);
+
+ ASSERT_TRUE(Region.getLegalMoveRange(Order, 2, Range));
+ EXPECT_EQ(Range.Begin, 2u);
+ EXPECT_EQ(Range.End, 2u);
+
+ ASSERT_TRUE(Region.getLegalMoveRange(Order, 4, Range));
+ EXPECT_EQ(Range.Begin, 0u);
+ EXPECT_EQ(Range.End, 4u);
+
+ EXPECT_FALSE(Region.getLegalMoveRange({2, 0, 1, 3, 4}, 0, Range));
+ EXPECT_FALSE(Region.getLegalMoveRange(Order, 5, Range));
+}
+
+TEST(MachineSchedCompleteScheduleReplayer, ReplaysValidatedCompleteOrder) {
+ EXPECT_EQ(initializeAndPick({2, 0, 1}), (SmallVector<unsigned, 3>{2, 0, 1}));
+}
+
+TEST(MachineSchedCompleteScheduleReplayer, PreservesExistingOrderOnFailure) {
+ EXPECT_EQ(initializeAndPick({2, 2, 1}), (SmallVector<unsigned, 3>{0, 1, 2}));
+ EXPECT_EQ(initializeAndPick({2, 0, 1}, /*AcceptResult=*/false),
+ (SmallVector<unsigned, 3>{0, 1, 2}));
+ EXPECT_EQ(initializeAndPick({2, 0, 1}, /*AcceptResult=*/true,
+ /*AcceptValidation=*/false),
+ (SmallVector<unsigned, 3>{0, 1, 2}));
+}
+
+TEST(MachineSchedCompleteScheduleOptimizer, OptimizesMaterializedFounder) {
+ SmallVector<unsigned> SeenFounder;
+ EXPECT_EQ(runPostOptimization({2, 0, 1}, /*AcceptResult=*/true,
+ /*AcceptValidation=*/true,
+ /*MaterializeCompleteFounder=*/true,
+ &SeenFounder,
+ /*MaterializedFounder=*/{1, 2, 0}),
+ (SmallVector<unsigned, 3>{2, 0, 1}));
+ EXPECT_EQ(SeenFounder, (SmallVector<unsigned, 3>{1, 2, 0}));
+}
+
+TEST(MachineSchedCompleteScheduleOptimizer, PreservesFounderOnFailure) {
+ EXPECT_EQ(runPostOptimization({2, 2, 1}),
+ (SmallVector<unsigned, 3>{0, 1, 2}));
+ EXPECT_EQ(runPostOptimization({2, 0, 1}, /*AcceptResult=*/false),
+ (SmallVector<unsigned, 3>{0, 1, 2}));
+ EXPECT_EQ(runPostOptimization({2, 0, 1}, /*AcceptResult=*/true,
+ /*AcceptValidation=*/false),
+ (SmallVector<unsigned, 3>{0, 1, 2}));
+}
+
+TEST(MachineSchedCompleteScheduleOptimizer, SkipsIncompleteFounder) {
+ SmallVector<unsigned> SeenFounder;
+ EXPECT_EQ(runPostOptimization({2, 0, 1}, /*AcceptResult=*/true,
+ /*AcceptValidation=*/true,
+ /*MaterializeCompleteFounder=*/false,
+ &SeenFounder),
+ (SmallVector<unsigned, 3>{0, 1, 2}));
+ EXPECT_TRUE(SeenFounder.empty());
+}
>From 0979aa0a1e9b6aa1884e323d197906bcde97614d Mon Sep 17 00:00:00 2001
From: choikwa <code.kchoi at gmail.com>
Date: Wed, 2 Sep 2026 19:50:33 +0000
Subject: [PATCH 2/5] address review
---
llvm/include/llvm/CodeGen/MachineScheduler.h | 2 +-
llvm/lib/CodeGen/MachineSchedSearch.cpp | 5 ++---
2 files changed, 3 insertions(+), 4 deletions(-)
diff --git a/llvm/include/llvm/CodeGen/MachineScheduler.h b/llvm/include/llvm/CodeGen/MachineScheduler.h
index cac8b2f9e9afc..7a49a4c2c8489 100644
--- a/llvm/include/llvm/CodeGen/MachineScheduler.h
+++ b/llvm/include/llvm/CodeGen/MachineScheduler.h
@@ -311,7 +311,7 @@ class LLVM_ABI MachineSchedStrategy {
/// Founder is explicit so the same optimizer can run either before scheduling,
/// with the incoming order as founder, or after an existing strategy has
/// materialized a target-selected founder.
-class LLVM_ABI MachineSchedCompleteScheduleOptimizer {
+struct LLVM_ABI MachineSchedCompleteScheduleOptimizer {
public:
virtual ~MachineSchedCompleteScheduleOptimizer();
diff --git a/llvm/lib/CodeGen/MachineSchedSearch.cpp b/llvm/lib/CodeGen/MachineSchedSearch.cpp
index fa5ef43f1715f..f127c2045771b 100644
--- a/llvm/lib/CodeGen/MachineSchedSearch.cpp
+++ b/llvm/lib/CodeGen/MachineSchedSearch.cpp
@@ -9,9 +9,9 @@
#include "llvm/CodeGen/MachineSchedSearch.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/Sequence.h"
#include <algorithm>
#include <functional>
-#include <numeric>
#include <queue>
#include <vector>
@@ -53,8 +53,7 @@ const SUnit &MachineSchedSearchRegion::getSUnit(unsigned Node) const {
}
SmallVector<unsigned, 0> MachineSchedSearchRegion::getInitialOrder() const {
- SmallVector<unsigned, 0> Order(size());
- std::iota(Order.begin(), Order.end(), 0);
+ SmallVector<unsigned, 0> Order = to_vector<0>(seq<unsigned>(size()));;
return Order;
}
>From d0b5a36a8ff1e27713450f6e964d337230e61210 Mon Sep 17 00:00:00 2001
From: choikwa <code.kchoi at gmail.com>
Date: Wed, 2 Sep 2026 19:56:42 +0000
Subject: [PATCH 3/5] NFC, add Founder definition in comment
---
llvm/include/llvm/CodeGen/MachineScheduler.h | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/llvm/include/llvm/CodeGen/MachineScheduler.h b/llvm/include/llvm/CodeGen/MachineScheduler.h
index 7a49a4c2c8489..d01989e2750e0 100644
--- a/llvm/include/llvm/CodeGen/MachineScheduler.h
+++ b/llvm/include/llvm/CodeGen/MachineScheduler.h
@@ -308,9 +308,9 @@ class LLVM_ABI MachineSchedStrategy {
/// Interface for transforming one complete legal schedule into another.
///
-/// Founder is explicit so the same optimizer can run either before scheduling,
-/// with the incoming order as founder, or after an existing strategy has
-/// materialized a target-selected founder.
+/// The incoming complete schedule is denoted as Founder. It can be either
+/// the initial serialized nodenum schedule before scheduler runs or the
+/// completed schedule after scheduler ran.
struct LLVM_ABI MachineSchedCompleteScheduleOptimizer {
public:
virtual ~MachineSchedCompleteScheduleOptimizer();
>From f4cb7db7fb0a77d52f078566cb1149ba473d2c46 Mon Sep 17 00:00:00 2001
From: choikwa <code.kchoi at gmail.com>
Date: Wed, 2 Sep 2026 19:58:17 +0000
Subject: [PATCH 4/5] remove extra ;
---
llvm/lib/CodeGen/MachineSchedSearch.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/llvm/lib/CodeGen/MachineSchedSearch.cpp b/llvm/lib/CodeGen/MachineSchedSearch.cpp
index f127c2045771b..70c0636026e3a 100644
--- a/llvm/lib/CodeGen/MachineSchedSearch.cpp
+++ b/llvm/lib/CodeGen/MachineSchedSearch.cpp
@@ -53,7 +53,7 @@ const SUnit &MachineSchedSearchRegion::getSUnit(unsigned Node) const {
}
SmallVector<unsigned, 0> MachineSchedSearchRegion::getInitialOrder() const {
- SmallVector<unsigned, 0> Order = to_vector<0>(seq<unsigned>(size()));;
+ SmallVector<unsigned, 0> Order = to_vector<0>(seq<unsigned>(size()));
return Order;
}
>From f424fbcb9c419ea2a7918ab3a7c1b80e5b830863 Mon Sep 17 00:00:00 2001
From: choikwa <code.kchoi at gmail.com>
Date: Wed, 30 Sep 2026 17:53:24 +0000
Subject: [PATCH 5/5] Address review
---
.../include/llvm/CodeGen/MachineSchedSearch.h | 25 ++++++-------------
llvm/lib/CodeGen/MachineSchedSearch.cpp | 8 +++---
2 files changed, 11 insertions(+), 22 deletions(-)
diff --git a/llvm/include/llvm/CodeGen/MachineSchedSearch.h b/llvm/include/llvm/CodeGen/MachineSchedSearch.h
index 6476b185f99dc..a7e45817451c5 100644
--- a/llvm/include/llvm/CodeGen/MachineSchedSearch.h
+++ b/llvm/include/llvm/CodeGen/MachineSchedSearch.h
@@ -74,6 +74,7 @@ class LLVM_ABI MachineSchedSearchRegion {
/// Return whether \p Order is a complete permutation that preserves every
/// strong dependency in the scheduling DAG.
+ /// TODO: Optimize legality check. ie. can only check a delta
bool isLegalOrder(ArrayRef<unsigned> Order) const;
/// Return the inclusive range of final positions to which \p Node may be
@@ -83,24 +84,12 @@ class LLVM_ABI MachineSchedSearchRegion {
MoveRange &Range) const;
};
-/// Adapter for schedulers that compute a complete schedule before LLVM begins
-/// applying scheduling decisions.
-///
-/// An owned MachineSchedCompleteScheduleOptimizer receives the incoming legal
-/// order as its founder and may return a replacement. The selected order is
-/// validated before use and replayed top-down through ScheduleDAGMI's
-/// incremental MachineSchedStrategy interface. When used with
-/// ScheduleDAGMILive, instruction movement, LiveIntervals, and
-/// register-pressure accounting therefore remain owned by the existing
-/// scheduler.
-///
-/// This is not a common base for all search-based schedulers. Strategies that
-/// choose each node from the current ready set should implement
-/// MachineSchedStrategy directly or derive from another incremental strategy.
-///
-/// If the optimizer declines to provide an order or returns an invalid one, the
-/// existing order is preserved when legal. Otherwise, a stable topological
-/// order is used.
+/// Replay a complete schedule through ScheduleDAGMI's MachineSchedStrategy
+/// interface. The MachineSchedCompleteScheduleOptimizer may replace the initial
+/// legal order. After validating the replacement, pickNode() returns its nodes
+/// sequentially from the top, letting ScheduleDAGMI apply each scheduling
+/// decision. If the optimizer declines or its order is invalid, the initial
+/// order is replayed.
class LLVM_ABI MachineSchedCompleteScheduleReplayer
: public MachineSchedStrategy {
std::unique_ptr<MachineSchedCompleteScheduleOptimizer> Optimizer;
diff --git a/llvm/lib/CodeGen/MachineSchedSearch.cpp b/llvm/lib/CodeGen/MachineSchedSearch.cpp
index 70c0636026e3a..add395bc9f599 100644
--- a/llvm/lib/CodeGen/MachineSchedSearch.cpp
+++ b/llvm/lib/CodeGen/MachineSchedSearch.cpp
@@ -32,8 +32,6 @@ MachineSchedSearchRegion::MachineSchedSearchRegion(ArrayRef<SUnit> Nodes)
if (Pred.isWeak() || PredSU->isBoundaryNode())
continue;
auto PredOrdinal = Ordinals.find(PredSU);
- assert(PredOrdinal != Ordinals.end() &&
- "predecessor must belong to the scheduling region");
if (PredOrdinal == Ordinals.end())
continue;
Predecessors[Ordinal].push_back(PredOrdinal->second);
@@ -99,10 +97,12 @@ bool MachineSchedSearchRegion::isLegalOrder(ArrayRef<unsigned> Order) const {
Position[Node] = Pos;
}
- for (unsigned Node = 0; Node != size(); ++Node)
- for (unsigned Pred : predecessors(Node))
+ for (unsigned Node = 0; Node != size(); ++Node) {
+ for (unsigned Pred : predecessors(Node)) {
if (Position[Pred] >= Position[Node])
return false;
+ }
+ }
return true;
}
More information about the llvm-commits
mailing list