Skip to content

Commit aee3400

Browse files
committed
[MISched][rework] Introduce and use ResourceSegments.
Re-landing the code that was reverted because of the buildbot failure in https://lab.llvm.org/buildbot#builders/9/builds/27319. Original commit message ====================== The class `ResourceSegments` is used to keep track of the intervals that represent resource usage of a list of instructions that are being scheduled by the machine scheduler. The collection is made of intervals that are closed on the left and open on the right (represented by the standard notation `[a, b)`). These collections of intervals can be extended by `add`ing new intervals accordingly while scheduling a basic block. Unit tests are added to verify the possible configurations of intervals, and the relative possibility of scheduling a new instruction in these configurations. Specifically, the methods `getFirstAvailableAtFromBottom` and `getFirstAvailableAtFromTop` are tested to make sure that both bottom-up and top-down scheduling work when tracking resource usage across the basic block with `ResourceSegments`. Note that the scheduler tracks resource usage with two methods: 1. counters (via `std::vector<unsigned> ReservedCycles;`); 2. intervals (via `std::map<unsigned, ResourceSegments> ReservedResourceSegments;`). This patch can be considered a NFC test for existing scheduling models because the tracking system that uses intervals is turned off by default (field `bit EnableIntervals = false;` in the tablegen class `SchedMachineModel`). Reviewed By: andreadb Differential Revision: https://reviews.llvm.org/D150312
1 parent 63901cb commit aee3400

File tree

9 files changed

+829
-37
lines changed

9 files changed

+829
-37
lines changed

llvm/include/llvm/CodeGen/MachineScheduler.h

Lines changed: 237 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@
9292
#include "llvm/Support/ErrorHandling.h"
9393
#include <algorithm>
9494
#include <cassert>
95+
#include <llvm/Support/raw_ostream.h>
9596
#include <memory>
9697
#include <string>
9798
#include <vector>
@@ -610,6 +611,222 @@ struct SchedRemainder {
610611
void init(ScheduleDAGMI *DAG, const TargetSchedModel *SchedModel);
611612
};
612613

614+
/// ResourceSegments are a collection of intervals closed on the
615+
/// left and opened on the right:
616+
///
617+
/// list{ [a1, b1), [a2, b2), ..., [a_N, b_N) }
618+
///
619+
/// The collection has the following properties:
620+
///
621+
/// 1. The list is ordered: a_i < b_i and b_i < a_(i+1)
622+
///
623+
/// 2. The intervals in the collection do not intersect each other.
624+
///
625+
/// A \ref ResourceSegments instance represents the cycle
626+
/// reservation history of the instance of and individual resource.
627+
class ResourceSegments {
628+
public:
629+
/// Represents an interval of discrete integer values closed on
630+
/// the left and open on the right: [a, b).
631+
typedef std::pair<int64_t, int64_t> IntervalTy;
632+
633+
/// Adds an interval [a, b) to the collection of the instance.
634+
///
635+
/// When adding [a, b[ to the collection, the operation merges the
636+
/// adjacent intervals. For example
637+
///
638+
/// 0 1 2 3 4 5 6 7 8 9 10
639+
/// [-----) [--) [--)
640+
/// + [--)
641+
/// = [-----------) [--)
642+
///
643+
/// To be able to debug duplicate resource usage, the function has
644+
/// assertion that checks that no interval should be added if it
645+
/// overlaps any of the intervals in the collection. We can
646+
/// require this because by definition a \ref ResourceSegments is
647+
/// attached only to an individual resource instance.
648+
void add(IntervalTy A, const unsigned CutOff = 10);
649+
650+
public:
651+
/// Checks whether intervals intersect.
652+
static bool intersects(IntervalTy A, IntervalTy B);
653+
654+
/// These function return the interval used by a resource in bottom and top
655+
/// scheduling.
656+
///
657+
/// Consider an instruction that uses resources X0, X1 and X2 as follows:
658+
///
659+
/// X0 X1 X1 X2 +--------+------------+------+
660+
/// |Resource|StartAtCycle|Cycles|
661+
/// +--------+------------+------+
662+
/// | X0 | 0 | 1 |
663+
/// +--------+------------+------+
664+
/// | X1 | 1 | 3 |
665+
/// +--------+------------+------+
666+
/// | X2 | 3 | 4 |
667+
/// +--------+------------+------+
668+
///
669+
/// If we can schedule the instruction at cycle C, we need to
670+
/// compute the interval of the resource as follows:
671+
///
672+
/// # TOP DOWN SCHEDULING
673+
///
674+
/// Cycles scheduling flows to the _right_, in the same direction
675+
/// of time.
676+
///
677+
/// C 1 2 3 4 5 ...
678+
/// ------|------|------|------|------|------|----->
679+
/// X0 X1 X1 X2 ---> direction of time
680+
/// X0 [C, C+1)
681+
/// X1 [C+1, C+3)
682+
/// X2 [C+3, C+4)
683+
///
684+
/// Therefore, the formula to compute the interval for a resource
685+
/// of an instruction that can be scheduled at cycle C in top-down
686+
/// scheduling is:
687+
///
688+
/// [C+StartAtCycle, C+Cycles)
689+
///
690+
///
691+
/// # BOTTOM UP SCHEDULING
692+
///
693+
/// Cycles scheduling flows to the _left_, in opposite direction
694+
/// of time.
695+
///
696+
/// In bottom up scheduling, the scheduling happens in opposite
697+
/// direction to the execution of the cycles of the
698+
/// instruction. When the instruction is scheduled at cycle `C`,
699+
/// the resources are allocated in the past relative to `C`:
700+
///
701+
/// 2 1 C -1 -2 -3 -4 -5 ...
702+
/// <-----|------|------|------|------|------|------|------|---
703+
/// X0 X1 X1 X2 ---> direction of time
704+
/// X0 (C+1, C]
705+
/// X1 (C, C-2]
706+
/// X2 (C-2, C-3]
707+
///
708+
/// Therefore, the formula to compute the interval for a resource
709+
/// of an instruction that can be scheduled at cycle C in bottom-up
710+
/// scheduling is:
711+
///
712+
/// [C-Cycle+1, C-StartAtCycle+1)
713+
///
714+
///
715+
/// NOTE: In both cases, the number of cycles booked by a
716+
/// resources is the value (Cycle - StartAtCycles).
717+
static IntervalTy getResourceIntervalBottom(unsigned C, unsigned StartAtCycle,
718+
unsigned Cycle) {
719+
return std::make_pair<long, long>((long)C - (long)Cycle + 1L,
720+
(long)C - (long)StartAtCycle + 1L);
721+
}
722+
static IntervalTy getResourceIntervalTop(unsigned C, unsigned StartAtCycle,
723+
unsigned Cycle) {
724+
return std::make_pair<long, long>((long)C + (long)StartAtCycle,
725+
(long)C + (long)Cycle);
726+
}
727+
728+
private:
729+
/// Finds the first cycle in which a resource can be allocated.
730+
///
731+
/// The function uses the \param IntervalBuider [*] to build a
732+
/// resource interval [a, b[ out of the input parameters \param
733+
/// CurrCycle, \param StartAtCycle and \param Cycle.
734+
///
735+
/// The function then loops through the intervals in the ResourceSegments
736+
/// and shifts the interval [a, b[ and the ReturnCycle to the
737+
/// right until there is no intersection between the intervals of
738+
/// the \ref ResourceSegments instance and the new shifted [a, b[. When
739+
/// this condition is met, the ReturnCycle (which
740+
/// correspond to the cycle in which the resource can be
741+
/// allocated) is returned.
742+
///
743+
/// c = CurrCycle in input
744+
/// c 1 2 3 4 5 6 7 8 9 10 ... ---> (time
745+
/// flow)
746+
/// ResourceSegments... [---) [-------) [-----------)
747+
/// c [1 3[ -> StartAtCycle=1, Cycles=3
748+
/// ++c [1 3)
749+
/// ++c [1 3)
750+
/// ++c [1 3)
751+
/// ++c [1 3)
752+
/// ++c [1 3) ---> returns c
753+
/// incremented by 5 (c+5)
754+
///
755+
///
756+
/// Notice that for bottom-up scheduling the diagram is slightly
757+
/// different because the current cycle c is always on the right
758+
/// of the interval [a, b) (see \ref
759+
/// `getResourceIntervalBottom`). This is because the cycle
760+
/// increments for bottom-up scheduling moved in the direction
761+
/// opposite to the direction of time:
762+
///
763+
/// --------> direction of time.
764+
/// XXYZZZ (resource usage)
765+
/// --------> direction of top-down execution cycles.
766+
/// <-------- direction of bottom-up execution cycles.
767+
///
768+
/// Even though bottom-up scheduling moves against the flow of
769+
/// time, the algorithm used to find the first free slot in between
770+
/// intervals is the same as for top-down scheduling.
771+
///
772+
/// [*] See \ref `getResourceIntervalTop` and
773+
/// \ref `getResourceIntervalBottom` to see how such resource intervals
774+
/// are built.
775+
unsigned
776+
getFirstAvailableAt(unsigned CurrCycle, unsigned StartAtCycle, unsigned Cycle,
777+
std::function<IntervalTy(unsigned, unsigned, unsigned)>
778+
IntervalBuilder) const;
779+
780+
public:
781+
/// getFirstAvailableAtFromBottom and getFirstAvailableAtFromTop
782+
/// should be merged in a single function in which a function that
783+
/// creates the `NewInterval` is passed as a parameter.
784+
unsigned getFirstAvailableAtFromBottom(unsigned CurrCycle,
785+
unsigned StartAtCycle,
786+
unsigned Cycle) const {
787+
return getFirstAvailableAt(CurrCycle, StartAtCycle, Cycle,
788+
getResourceIntervalBottom);
789+
}
790+
unsigned getFirstAvailableAtFromTop(unsigned CurrCycle, unsigned StartAtCycle,
791+
unsigned Cycle) const {
792+
return getFirstAvailableAt(CurrCycle, StartAtCycle, Cycle,
793+
getResourceIntervalTop);
794+
}
795+
796+
private:
797+
std::list<IntervalTy> _Intervals;
798+
/// Merge all adjacent intervals in the collection. For all pairs
799+
/// of adjacient intervals, it performs [a, b) + [b, c) -> [a, c).
800+
///
801+
/// Before performing the merge operation, the intervals are
802+
/// sorted with \ref sort_predicate.
803+
void sortAndMerge();
804+
805+
public:
806+
// constructor for empty set
807+
explicit ResourceSegments(){};
808+
bool empty() const { return _Intervals.empty(); }
809+
explicit ResourceSegments(std::list<IntervalTy> Intervals)
810+
: _Intervals(Intervals) {
811+
sortAndMerge();
812+
}
813+
814+
friend bool operator==(const ResourceSegments &c1,
815+
const ResourceSegments &c2) {
816+
return c1._Intervals == c2._Intervals;
817+
}
818+
#ifndef NDEBUG
819+
friend llvm::raw_ostream &operator<<(llvm::raw_ostream &os,
820+
const ResourceSegments &Segments) {
821+
os << "{ ";
822+
for (auto p : Segments._Intervals)
823+
os << "[" << p.first << ", " << p.second << "), ";
824+
os << "}\n";
825+
return os;
826+
}
827+
#endif
828+
};
829+
613830
/// Each Scheduling boundary is associated with ready queues. It tracks the
614831
/// current cycle in the direction of movement, and maintains the state
615832
/// of "hazards" and other interlocks at the current cycle.
@@ -675,12 +892,14 @@ class SchedBoundary {
675892
// Is the scheduled region resource limited vs. latency limited.
676893
bool IsResourceLimited;
677894

678-
// Record the highest cycle at which each resource has been reserved by a
679-
// scheduled instruction.
680-
SmallVector<unsigned, 16> ReservedCycles;
681-
682-
/// For each PIdx, stores first index into ReservedCycles that corresponds to
683-
/// it.
895+
public:
896+
private:
897+
/// Record how resources have been allocated across the cycles of
898+
/// the execution.
899+
std::map<unsigned, ResourceSegments> ReservedResourceSegments;
900+
std::vector<unsigned> ReservedCycles;
901+
/// For each PIdx, stores first index into ReservedResourceSegments that
902+
/// corresponds to it.
684903
///
685904
/// For example, consider the following 3 resources (ResourceCount =
686905
/// 3):
@@ -696,12 +915,14 @@ class SchedBoundary {
696915
/// +------------+--------+
697916
///
698917
/// In this case, the total number of resource instances is 6. The
699-
/// vector \ref ReservedCycles will have a slot for each instance. The
700-
/// vector \ref ReservedCyclesIndex will track at what index the first
918+
/// vector \ref ReservedResourceSegments will have a slot for each instance.
919+
/// The vector \ref ReservedCyclesIndex will track at what index the first
701920
/// instance of the resource is found in the vector of \ref
702-
/// ReservedCycles:
921+
/// ReservedResourceSegments:
922+
///
923+
/// Indexes of instances in
924+
/// ReservedResourceSegments
703925
///
704-
/// Indexes of instances in ReservedCycles
705926
/// 0 1 2 3 4 5
706927
/// ReservedCyclesIndex[0] = 0; [X0, X1,
707928
/// ReservedCyclesIndex[1] = 2; Y0, Y1, Y2
@@ -787,11 +1008,13 @@ class SchedBoundary {
7871008
unsigned getLatencyStallCycles(SUnit *SU);
7881009

7891010
unsigned getNextResourceCycleByInstance(unsigned InstanceIndex,
790-
unsigned Cycles);
1011+
unsigned Cycles,
1012+
unsigned StartAtCycle);
7911013

7921014
std::pair<unsigned, unsigned> getNextResourceCycle(const MCSchedClassDesc *SC,
7931015
unsigned PIdx,
794-
unsigned Cycles);
1016+
unsigned Cycles,
1017+
unsigned StartAtCycle);
7951018

7961019
bool isUnbufferedGroup(unsigned PIdx) const {
7971020
return SchedModel->getProcResource(PIdx)->SubUnitsIdxBegin &&
@@ -820,7 +1043,8 @@ class SchedBoundary {
8201043
void incExecutedResources(unsigned PIdx, unsigned Count);
8211044

8221045
unsigned countResource(const MCSchedClassDesc *SC, unsigned PIdx,
823-
unsigned Cycles, unsigned ReadyCycle);
1046+
unsigned Cycles, unsigned ReadyCycle,
1047+
unsigned StartAtCycle);
8241048

8251049
void bumpNode(SUnit *SU);
8261050

llvm/include/llvm/CodeGen/TargetSchedule.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ class TargetSchedModel {
9090
bool hasInstrSchedModelOrItineraries() const {
9191
return hasInstrSchedModel() || hasInstrItineraries();
9292
}
93-
93+
bool enableIntervals() const { return SchedModel.EnableIntervals; }
9494
/// Identify the processor corresponding to the current subtarget.
9595
unsigned getProcessorID() const { return SchedModel.getProcessorID(); }
9696

llvm/include/llvm/MC/MCSchedule.h

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,17 @@ struct MCProcResourceDesc {
5858
}
5959
};
6060

61-
/// Identify one of the processor resource kinds consumed by a particular
62-
/// scheduling class for the specified number of cycles.
61+
/// Identify one of the processor resource kinds consumed by a
62+
/// particular scheduling class for the specified number of cycles.
63+
/// TODO: consider renaming the field `StartAtCycle` and `Cycles` to
64+
/// `AcquireAtCycle` and `ReleaseAtCycle` respectively, to stress the
65+
/// fact that resource allocation is now represented as an interval,
66+
/// relatively to the issue cycle of the instruction.
6367
struct MCWriteProcResEntry {
6468
uint16_t ProcResourceIdx;
69+
/// Cycle at which the resource will be released by an instruction,
70+
/// relatively to the cycle in which the instruction is issued
71+
/// (assuming no stalls inbetween).
6572
uint16_t Cycles;
6673
/// Cycle at which the resource will be grabbed by an instruction,
6774
/// relatively to the cycle in which the instruction is issued
@@ -306,6 +313,11 @@ struct MCSchedModel {
306313

307314
bool CompleteModel;
308315

316+
// Tells the MachineScheduler whether or not to track resource usage
317+
// using intervals via ResourceSegments (see
318+
// llvm/include/llvm/CodeGen/MachineScheduler.h).
319+
bool EnableIntervals;
320+
309321
unsigned ProcID;
310322
const MCProcResourceDesc *ProcResourceTable;
311323
const MCSchedClassDesc *SchedClassTable;

llvm/include/llvm/Target/TargetSchedule.td

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,11 @@ class SchedMachineModel {
117117
list<Predicate> UnsupportedFeatures = [];
118118

119119
bit NoModel = false; // Special tag to indicate missing machine model.
120+
121+
// Tells the MachineScheduler whether or not to track resource usage
122+
// using intervals via ResourceSegments (see
123+
// llvm/include/llvm/CodeGen/MachineScheduler.h).
124+
bit EnableIntervals = false;
120125
}
121126

122127
def NoSchedModel : SchedMachineModel {

0 commit comments

Comments
 (0)