1 | //===- GCNMinRegStrategy.cpp ----------------------------------------------===// |
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 | /// \file |
10 | /// This file defines and implements the class GCNMinRegScheduler, which |
11 | /// implements an experimental, simple scheduler whose main goal is to learn |
12 | /// ways about consuming less possible registers for a region. |
13 | /// |
14 | //===----------------------------------------------------------------------===// |
15 | |
16 | #include "llvm/CodeGen/ScheduleDAG.h" |
17 | using namespace llvm; |
18 | |
19 | #define DEBUG_TYPE "machine-scheduler" |
20 | |
21 | namespace { |
22 | |
23 | class GCNMinRegScheduler { |
24 | struct Candidate : ilist_node<Candidate> { |
25 | const SUnit *SU; |
26 | int Priority; |
27 | |
28 | Candidate(const SUnit *SU_, int Priority_ = 0) |
29 | : SU(SU_), Priority(Priority_) {} |
30 | }; |
31 | |
32 | SpecificBumpPtrAllocator<Candidate> Alloc; |
33 | using Queue = simple_ilist<Candidate>; |
34 | Queue RQ; // Ready queue |
35 | |
36 | std::vector<unsigned> NumPreds; |
37 | |
38 | bool isScheduled(const SUnit *SU) const { |
39 | assert(!SU->isBoundaryNode()); |
40 | return NumPreds[SU->NodeNum] == std::numeric_limits<unsigned>::max(); |
41 | } |
42 | |
43 | void setIsScheduled(const SUnit *SU) { |
44 | assert(!SU->isBoundaryNode()); |
45 | NumPreds[SU->NodeNum] = std::numeric_limits<unsigned>::max(); |
46 | } |
47 | |
48 | unsigned getNumPreds(const SUnit *SU) const { |
49 | assert(!SU->isBoundaryNode()); |
50 | assert(NumPreds[SU->NodeNum] != std::numeric_limits<unsigned>::max()); |
51 | return NumPreds[SU->NodeNum]; |
52 | } |
53 | |
54 | unsigned decNumPreds(const SUnit *SU) { |
55 | assert(!SU->isBoundaryNode()); |
56 | assert(NumPreds[SU->NodeNum] != std::numeric_limits<unsigned>::max()); |
57 | return --NumPreds[SU->NodeNum]; |
58 | } |
59 | |
60 | void initNumPreds(const decltype(ScheduleDAG::SUnits) &SUnits); |
61 | |
62 | int getReadySuccessors(const SUnit *SU) const; |
63 | int getNotReadySuccessors(const SUnit *SU) const; |
64 | |
65 | template <typename Calc> |
66 | unsigned findMax(unsigned Num, Calc C); |
67 | |
68 | Candidate* pickCandidate(); |
69 | |
70 | void bumpPredsPriority(const SUnit *SchedSU, int Priority); |
71 | void releaseSuccessors(const SUnit* SU, int Priority); |
72 | |
73 | public: |
74 | std::vector<const SUnit*> schedule(ArrayRef<const SUnit*> TopRoots, |
75 | const ScheduleDAG &DAG); |
76 | }; |
77 | |
78 | } // end anonymous namespace |
79 | |
80 | void GCNMinRegScheduler::initNumPreds(const decltype(ScheduleDAG::SUnits) &SUnits) { |
81 | NumPreds.resize(new_size: SUnits.size()); |
82 | for (unsigned I = 0; I < SUnits.size(); ++I) |
83 | NumPreds[I] = SUnits[I].NumPredsLeft; |
84 | } |
85 | |
86 | int GCNMinRegScheduler::getReadySuccessors(const SUnit *SU) const { |
87 | unsigned NumSchedSuccs = 0; |
88 | for (auto SDep : SU->Succs) { |
89 | bool wouldBeScheduled = true; |
90 | for (auto PDep : SDep.getSUnit()->Preds) { |
91 | auto PSU = PDep.getSUnit(); |
92 | assert(!PSU->isBoundaryNode()); |
93 | if (PSU != SU && !isScheduled(SU: PSU)) { |
94 | wouldBeScheduled = false; |
95 | break; |
96 | } |
97 | } |
98 | NumSchedSuccs += wouldBeScheduled ? 1 : 0; |
99 | } |
100 | return NumSchedSuccs; |
101 | } |
102 | |
103 | int GCNMinRegScheduler::getNotReadySuccessors(const SUnit *SU) const { |
104 | return SU->Succs.size() - getReadySuccessors(SU); |
105 | } |
106 | |
107 | template <typename Calc> |
108 | unsigned GCNMinRegScheduler::findMax(unsigned Num, Calc C) { |
109 | assert(!RQ.empty() && Num <= RQ.size()); |
110 | |
111 | using T = decltype(C(*RQ.begin())) ; |
112 | |
113 | T Max = std::numeric_limits<T>::min(); |
114 | unsigned NumMax = 0; |
115 | for (auto I = RQ.begin(); Num; --Num) { |
116 | T Cur = C(*I); |
117 | if (Cur >= Max) { |
118 | if (Cur > Max) { |
119 | Max = Cur; |
120 | NumMax = 1; |
121 | } else |
122 | ++NumMax; |
123 | auto &Cand = *I++; |
124 | RQ.remove(N&: Cand); |
125 | RQ.push_front(Node&: Cand); |
126 | continue; |
127 | } |
128 | ++I; |
129 | } |
130 | return NumMax; |
131 | } |
132 | |
133 | GCNMinRegScheduler::Candidate* GCNMinRegScheduler::pickCandidate() { |
134 | do { |
135 | unsigned Num = RQ.size(); |
136 | if (Num == 1) break; |
137 | |
138 | LLVM_DEBUG(dbgs() << "\nSelecting max priority candidates among " << Num |
139 | << '\n'); |
140 | Num = findMax(Num, C: [=](const Candidate &C) { return C.Priority; }); |
141 | if (Num == 1) break; |
142 | |
143 | LLVM_DEBUG(dbgs() << "\nSelecting min non-ready producing candidate among " |
144 | << Num << '\n'); |
145 | Num = findMax(Num, C: [=](const Candidate &C) { |
146 | auto SU = C.SU; |
147 | int Res = getNotReadySuccessors(SU); |
148 | LLVM_DEBUG(dbgs() << "SU(" << SU->NodeNum << ") would left non-ready " |
149 | << Res << " successors, metric = " << -Res << '\n'); |
150 | return -Res; |
151 | }); |
152 | if (Num == 1) break; |
153 | |
154 | LLVM_DEBUG(dbgs() << "\nSelecting most producing candidate among " << Num |
155 | << '\n'); |
156 | Num = findMax(Num, C: [=](const Candidate &C) { |
157 | auto SU = C.SU; |
158 | auto Res = getReadySuccessors(SU); |
159 | LLVM_DEBUG(dbgs() << "SU(" << SU->NodeNum << ") would make ready " << Res |
160 | << " successors, metric = " << Res << '\n'); |
161 | return Res; |
162 | }); |
163 | if (Num == 1) break; |
164 | |
165 | Num = Num ? Num : RQ.size(); |
166 | LLVM_DEBUG( |
167 | dbgs() |
168 | << "\nCan't find best candidate, selecting in program order among " |
169 | << Num << '\n'); |
170 | Num = findMax(Num, C: [=](const Candidate &C) { return -(int64_t)C.SU->NodeNum; }); |
171 | assert(Num == 1); |
172 | } while (false); |
173 | |
174 | return &RQ.front(); |
175 | } |
176 | |
177 | void GCNMinRegScheduler::bumpPredsPriority(const SUnit *SchedSU, int Priority) { |
178 | SmallPtrSet<const SUnit*, 32> Set; |
179 | for (const auto &S : SchedSU->Succs) { |
180 | if (S.getSUnit()->isBoundaryNode() || isScheduled(SU: S.getSUnit()) || |
181 | S.getKind() != SDep::Data) |
182 | continue; |
183 | for (const auto &P : S.getSUnit()->Preds) { |
184 | auto PSU = P.getSUnit(); |
185 | assert(!PSU->isBoundaryNode()); |
186 | if (PSU != SchedSU && !isScheduled(SU: PSU)) { |
187 | Set.insert(Ptr: PSU); |
188 | } |
189 | } |
190 | } |
191 | SmallVector<const SUnit*, 32> Worklist(Set.begin(), Set.end()); |
192 | while (!Worklist.empty()) { |
193 | auto SU = Worklist.pop_back_val(); |
194 | assert(!SU->isBoundaryNode()); |
195 | for (const auto &P : SU->Preds) { |
196 | if (!P.getSUnit()->isBoundaryNode() && !isScheduled(SU: P.getSUnit()) && |
197 | Set.insert(Ptr: P.getSUnit()).second) |
198 | Worklist.push_back(Elt: P.getSUnit()); |
199 | } |
200 | } |
201 | LLVM_DEBUG(dbgs() << "Make the predecessors of SU(" << SchedSU->NodeNum |
202 | << ")'s non-ready successors of " << Priority |
203 | << " priority in ready queue: " ); |
204 | for (auto &C : RQ) { |
205 | if (Set.count(Ptr: C.SU)) { |
206 | C.Priority = Priority; |
207 | LLVM_DEBUG(dbgs() << " SU(" << C.SU->NodeNum << ')'); |
208 | } |
209 | } |
210 | LLVM_DEBUG(dbgs() << '\n'); |
211 | } |
212 | |
213 | void GCNMinRegScheduler::releaseSuccessors(const SUnit* SU, int Priority) { |
214 | for (const auto &S : SU->Succs) { |
215 | auto SuccSU = S.getSUnit(); |
216 | if (S.isWeak()) |
217 | continue; |
218 | assert(SuccSU->isBoundaryNode() || getNumPreds(SuccSU) > 0); |
219 | if (!SuccSU->isBoundaryNode() && decNumPreds(SU: SuccSU) == 0) |
220 | RQ.push_front(Node&: *new (Alloc.Allocate()) Candidate(SuccSU, Priority)); |
221 | } |
222 | } |
223 | |
224 | std::vector<const SUnit*> |
225 | GCNMinRegScheduler::schedule(ArrayRef<const SUnit*> TopRoots, |
226 | const ScheduleDAG &DAG) { |
227 | const auto &SUnits = DAG.SUnits; |
228 | std::vector<const SUnit*> Schedule; |
229 | Schedule.reserve(n: SUnits.size()); |
230 | |
231 | initNumPreds(SUnits); |
232 | |
233 | int StepNo = 0; |
234 | |
235 | for (const auto *SU : TopRoots) { |
236 | RQ.push_back(Node&: *new (Alloc.Allocate()) Candidate(SU, StepNo)); |
237 | } |
238 | releaseSuccessors(SU: &DAG.EntrySU, Priority: StepNo); |
239 | |
240 | while (!RQ.empty()) { |
241 | LLVM_DEBUG(dbgs() << "\n=== Picking candidate, Step = " << StepNo |
242 | << "\n" |
243 | "Ready queue:" ; |
244 | for (auto &C |
245 | : RQ) dbgs() |
246 | << ' ' << C.SU->NodeNum << "(P" << C.Priority << ')'; |
247 | dbgs() << '\n';); |
248 | |
249 | auto C = pickCandidate(); |
250 | assert(C); |
251 | RQ.remove(N&: *C); |
252 | auto SU = C->SU; |
253 | LLVM_DEBUG(dbgs() << "Selected " ; DAG.dumpNode(*SU)); |
254 | |
255 | releaseSuccessors(SU, Priority: StepNo); |
256 | Schedule.push_back(x: SU); |
257 | setIsScheduled(SU); |
258 | |
259 | if (getReadySuccessors(SU) == 0) |
260 | bumpPredsPriority(SchedSU: SU, Priority: StepNo); |
261 | |
262 | ++StepNo; |
263 | } |
264 | assert(SUnits.size() == Schedule.size()); |
265 | |
266 | return Schedule; |
267 | } |
268 | |
269 | namespace llvm { |
270 | |
271 | std::vector<const SUnit*> makeMinRegSchedule(ArrayRef<const SUnit*> TopRoots, |
272 | const ScheduleDAG &DAG) { |
273 | GCNMinRegScheduler S; |
274 | return S.schedule(TopRoots, DAG); |
275 | } |
276 | |
277 | } // end namespace llvm |
278 | |