1 /**
2 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "reg_alloc_graph_coloring.h"
17 #include <cmath>
18 #include "compiler_logger.h"
19 #include "interference_graph.h"
20 #include "optimizer/analysis/dominators_tree.h"
21 #include "optimizer/code_generator/callconv.h"
22 #include "optimizer/ir/basicblock.h"
23 #include "optimizer/ir/datatype.h"
24 #include "optimizer/ir/graph.h"
25 #include "reg_type.h"
26
27 namespace panda::compiler {
RegAllocGraphColoring(Graph * graph)28 RegAllocGraphColoring::RegAllocGraphColoring(Graph *graph) : RegAllocBase(graph) {}
RegAllocGraphColoring(Graph * graph,size_t regs_count)29 RegAllocGraphColoring::RegAllocGraphColoring(Graph *graph, size_t regs_count) : RegAllocBase(graph, regs_count) {}
30
BuildIG(InterferenceGraph * ig,WorkingRanges * ranges)31 void RegAllocGraphColoring::BuildIG(InterferenceGraph *ig, WorkingRanges *ranges)
32 {
33 ig->Reserve(ranges->regular.size() + ranges->physical.size());
34 ArenaDeque<ColorNode *> active_nodes(GetGraph()->GetLocalAllocator()->Adapter());
35 ArenaVector<ColorNode *> physical_nodes(GetGraph()->GetLocalAllocator()->Adapter());
36
37 for (auto physical_interval : ranges->physical) {
38 ColorNode *node = ig->AllocNode();
39 node->Assign(physical_interval);
40 physical_nodes.push_back(node);
41 }
42
43 for (auto current_interval : ranges->regular) {
44 auto range_start = current_interval->GetBegin();
45
46 // Expire active_ranges
47 while (!active_nodes.empty() && active_nodes.front()->GetLifeIntervals()->GetEnd() <= range_start) {
48 active_nodes.pop_front();
49 }
50
51 ColorNode *node = ig->AllocNode();
52 node->Assign(current_interval);
53
54 // Interfer node
55 for (auto active_node : active_nodes) {
56 auto active_interval = active_node->GetLifeIntervals();
57 if (current_interval->IntersectsWith(active_interval)) {
58 ig->AddEdge(node->GetNumber(), active_node->GetNumber());
59 }
60 }
61
62 for (auto physical_node : physical_nodes) {
63 auto physical_interval = physical_node->GetLifeIntervals();
64 auto intersection = current_interval->GetFirstIntersectionWith(physical_interval);
65 // Current interval can intersect the physical one at the beginning of its live range
66 // only if it's a call and physical interval's range was created for it.
67 // Try to find first intersection excluding the range blocking registers during a call.
68 if (intersection == current_interval->GetBegin()) {
69 intersection = current_interval->GetFirstIntersectionWith(physical_interval, intersection + 1U);
70 }
71
72 if (intersection != INVALID_LIFE_NUMBER) {
73 ig->AddEdge(node->GetNumber(), physical_node->GetNumber());
74 node->AddCallsite(range_start);
75 }
76 }
77
78 // Add node to active_nodes sorted by End time
79 auto ranges_iter =
80 std::upper_bound(active_nodes.begin(), active_nodes.end(), node, [](const auto &lhs, const auto &rhs) {
81 return lhs->GetLifeIntervals()->GetEnd() <= rhs->GetLifeIntervals()->GetEnd();
82 });
83 active_nodes.insert(ranges_iter, node);
84 }
85 }
86
87 namespace {
FindFixedNode(const NodeVector & nodes,Location location)88 const ColorNode *FindFixedNode(const NodeVector &nodes, Location location)
89 {
90 // Find node of input
91 auto it = std::find_if(nodes.begin(), nodes.end(), [location](const auto &el) {
92 auto liveness = el.GetLifeIntervals();
93 return liveness->IsPhysical() && liveness->GetLocation() == location;
94 });
95
96 return it != nodes.end() ? &*it : nullptr;
97 }
98
FindNode(const NodeVector & nodes,const Inst * inst)99 const ColorNode *FindNode(const NodeVector &nodes, const Inst *inst)
100 {
101 // Find node of input
102 auto it = std::find_if(nodes.begin(), nodes.end(), [inst](const auto &el) {
103 auto liveness = el.GetLifeIntervals();
104 return !liveness->IsPhysical() && liveness->GetInst() == inst && liveness->GetSibling() == nullptr;
105 });
106
107 return it != nodes.end() ? &*it : nullptr;
108 }
109 } // namespace
110
111 // Find precolorings and set registers to intervals in advance
PrecolorIG(InterferenceGraph * ig,const RegisterMap & map)112 RegAllocGraphColoring::IndexVector RegAllocGraphColoring::PrecolorIG(InterferenceGraph *ig, const RegisterMap &map)
113 {
114 auto &nodes = ig->GetNodes();
115
116 // Walk nodes and propagate properties
117 IndexVector affinity_nodes;
118 for (auto &node : nodes) {
119 const auto *interv = node.GetLifeIntervals();
120
121 // Take in account preassigned registers in intervals
122 if (interv->IsPhysical() || interv->IsPreassigned()) {
123 ASSERT(node.GetCallsiteIntersectCount() == 0);
124 // Translate preassigned register from interval to color graph
125 auto color = map.CodegenToRegallocReg(interv->GetReg());
126 node.SetFixedColor(color, interv->IsPhysical());
127 AddAffinityEdgeToSibling(ig, &node, &affinity_nodes);
128 continue;
129 }
130
131 const auto *inst = interv->GetInst();
132 ASSERT(inst != nullptr);
133 if (inst->IsPhi()) {
134 // TODO (Evgeny.Erokhin): Add precoloring for the node which is input for return if callsites == 0
135 // Add inputs to affinity subgraph
136 AddAffinityEdges(ig, &node, &affinity_nodes);
137 continue;
138 }
139
140 // Add affinity edges to fixed locations
141 for (auto i = 0U; i < inst->GetInputsCount(); i++) {
142 auto location = inst->GetLocation(i);
143 if (location.IsFixedRegister()) {
144 auto input_node = FindNode(nodes, inst->GetDataFlowInput(i));
145 auto fixed_node = FindFixedNode(nodes, location);
146 // Possible when general intervals are processing, while input is fp-interval or vice versa
147 if (input_node == nullptr || fixed_node == nullptr) {
148 continue;
149 }
150 affinity_nodes.push_back(input_node->GetNumber());
151 affinity_nodes.push_back(fixed_node->GetNumber());
152 ig->AddAffinityEdge(input_node->GetNumber(), fixed_node->GetNumber());
153 }
154 }
155 }
156
157 return affinity_nodes;
158 }
159
BuildBias(InterferenceGraph * ig,const IndexVector & affinity_nodes)160 void RegAllocGraphColoring::BuildBias(InterferenceGraph *ig, const IndexVector &affinity_nodes)
161 {
162 auto &nodes = ig->GetNodes();
163
164 // Build affinity connected-components UCC(Unilaterally Connected Components) for coalescing (assign bias number to
165 // nodes of same component)
166 SmallVector<unsigned, DEFAULT_VECTOR_SIZE> walked;
167 for (auto index : affinity_nodes) {
168 auto &node = nodes[index];
169
170 // Skip already biased
171 if (node.HasBias()) {
172 continue;
173 }
174
175 // Find connected component of graph UCC by (DFS), and collect Call-sites intersections
176 walked.clear();
177 walked.push_back(node.GetNumber());
178 unsigned bias_num = ig->GetBiasCount();
179 node.SetBias(bias_num);
180 auto &bias = ig->AddBias();
181 ig->UpdateBiasData(&bias, node);
182 do {
183 // Pop back
184 unsigned cur_index = walked.back();
185 walked.resize(walked.size() - 1);
186
187 // Walk N affine nodes
188 for (auto try_index : affinity_nodes) {
189 auto &try_node = nodes[try_index];
190 if (try_node.HasBias() || !ig->HasAffinityEdge(cur_index, try_index)) {
191 continue;
192 }
193 try_node.SetBias(bias_num);
194 ig->UpdateBiasData(&bias, try_node);
195 walked.push_back(try_index);
196 }
197 } while (!walked.empty());
198 }
199
200 // TODO (Evgeny.Erokhin): Add precoloring for nodes that have Use in call argument but but callsites == 0
201 }
202
AddAffinityEdges(InterferenceGraph * ig,ColorNode * node,IndexVector * affinity_nodes)203 void RegAllocGraphColoring::AddAffinityEdges(InterferenceGraph *ig, ColorNode *node, IndexVector *affinity_nodes)
204 {
205 auto &nodes = ig->GetNodes();
206
207 // Duplicates are possible but we tolerate it
208 affinity_nodes->push_back(node->GetNumber());
209
210 // Iterate over Phi inputs
211 SmallVector<unsigned, DEFAULT_VECTOR_SIZE> affine;
212 for (const auto &input : node->GetLifeIntervals()->GetInst()->GetInputs()) {
213 // Add affinity edge
214 if (const auto *anbr = FindNode(nodes, input.GetInst())) {
215 COMPILER_LOG(DEBUG, REGALLOC) << "AfEdge: " << node->GetNumber() << " " << anbr->GetNumber() << " "
216 << *anbr->GetLifeIntervals()->GetInst();
217 ig->AddAffinityEdge(node->GetNumber(), anbr->GetNumber());
218 affinity_nodes->push_back(anbr->GetNumber());
219 }
220 }
221 }
222
AddAffinityEdgeToSibling(InterferenceGraph * ig,ColorNode * node,IndexVector * affinity_nodes)223 void RegAllocGraphColoring::AddAffinityEdgeToSibling(InterferenceGraph *ig, ColorNode *node,
224 IndexVector *affinity_nodes)
225 {
226 const auto *interv = node->GetLifeIntervals();
227 if (interv->IsPhysical() || interv->GetSibling() == nullptr) {
228 return;
229 }
230 auto node_split = FindNode(ig->GetNodes(), interv->GetInst());
231 ASSERT(node_split != nullptr);
232 COMPILER_LOG(DEBUG, REGALLOC) << "AfEdge: " << node->GetNumber() << " " << node_split->GetNumber() << " "
233 << *node_split->GetLifeIntervals()->GetInst();
234 ig->AddAffinityEdge(node->GetNumber(), node_split->GetNumber());
235 affinity_nodes->push_back(node->GetNumber());
236 affinity_nodes->push_back(node_split->GetNumber());
237 }
238
AllocateRegisters(InterferenceGraph * ig,WorkingRanges * ranges,const RegisterMap & map)239 Register RegAllocGraphColoring::AllocateRegisters(InterferenceGraph *ig, WorkingRanges *ranges, const RegisterMap &map)
240 {
241 Presplit(ranges);
242 // Build and precolor IG
243 BuildIG(ig, ranges);
244 BuildBias(ig, PrecolorIG(ig, map));
245
246 #ifdef NDEBUG
247 if (!ig->IsChordal()) {
248 COMPILER_LOG(WARNING, REGALLOC) << "Nonchordal graph: nonoptimal coloring possible";
249 }
250 #endif // NDEBUG
251
252 // Color IG
253 unsigned colors = 0;
254 if (GetGraph()->IsBytecodeOptimizer()) {
255 colors = ig->AssignColors<VIRTUAL_FRAME_SIZE>(map.GetAvailableRegsCount(), map.GetBorder());
256 } else {
257 colors = ig->AssignColors<MAX_NUM_REGS>(map.GetAvailableRegsCount(), map.GetBorder());
258 }
259
260 if (colors == 0) {
261 COMPILER_LOG(DEBUG, REGALLOC) << "SPILL REQUIRED";
262 return 0;
263 }
264 COMPILER_LOG(INFO, REGALLOC) << "IG nodes " << ig->GetNodes().size() << " colored with " << unsigned(colors);
265
266 return colors;
267 }
268
Remap(const InterferenceGraph & ig,const RegisterMap & map)269 void RegAllocGraphColoring::Remap(const InterferenceGraph &ig, const RegisterMap &map)
270 {
271 // Map allocated colors to registers
272 for (const auto &node : ig.GetNodes()) {
273 auto *interval = node.GetLifeIntervals();
274 if (!node.IsFixedColor()) {
275 // Make interval's register
276 auto color = node.GetColor();
277 ASSERT(color != INVALID_REG);
278 auto reg = map.RegallocToCodegenReg(color);
279 interval->SetReg(reg);
280 }
281 }
282 }
283
Allocate()284 bool RegAllocGraphColoring::Allocate()
285 {
286 auto *gr = GetGraph();
287
288 ReserveTempRegisters();
289 // Create intervals sequences
290 WorkingRanges general_ranges(gr->GetLocalAllocator());
291 WorkingRanges fp_ranges(gr->GetLocalAllocator());
292 InitWorkingRanges(&general_ranges, &fp_ranges);
293 COMPILER_LOG(INFO, REGALLOC) << "Ranges reg " << general_ranges.regular.size() << " fp "
294 << fp_ranges.regular.size();
295
296 // Register allocation
297 InterferenceGraph ig(gr->GetLocalAllocator());
298 RegisterMap map(gr->GetLocalAllocator());
299
300 unsigned int_colors = 0;
301 if (!general_ranges.regular.empty()) {
302 InitMap(&map, false);
303 int_colors = AllocateRegisters(&ig, &general_ranges, map);
304 if (int_colors == 0) {
305 gr->GetAnalysis<LivenessAnalyzer>().Cleanup();
306 COMPILER_LOG(DEBUG, REGALLOC) << "Integer RA failed";
307 return false;
308 }
309 Remap(ig, map);
310 }
311
312 unsigned vec_colors = 0;
313 if (!fp_ranges.regular.empty()) {
314 GetGraph()->SetHasFloatRegs();
315
316 InitMap(&map, true);
317 vec_colors = AllocateRegisters(&ig, &fp_ranges, map);
318 if (vec_colors == 0) {
319 gr->GetAnalysis<LivenessAnalyzer>().Cleanup();
320 COMPILER_LOG(DEBUG, REGALLOC) << "Vector RA failed";
321 return false;
322 }
323 Remap(ig, map);
324 }
325
326 COMPILER_LOG(DEBUG, REGALLOC) << "GC RA passed " << gr->GetRuntime()->GetMethodName(gr->GetMethod()) << " int "
327 << int_colors << " vec " << vec_colors;
328
329 return true;
330 }
331
332 namespace {
333 /*
334 * Add range in sorted order
335 */
AddRange(LifeIntervals * interval,InstructionsRanges * dest)336 void AddRange(LifeIntervals *interval, InstructionsRanges *dest)
337 {
338 auto iter = std::upper_bound(dest->begin(), dest->end(), interval,
339 [](const auto &lhs, const auto &rhs) { return lhs->GetBegin() < rhs->GetBegin(); });
340 dest->insert(iter, interval);
341 }
342 } // namespace
343
InitWorkingRanges(WorkingRanges * general_ranges,WorkingRanges * fp_ranges)344 void RegAllocGraphColoring::InitWorkingRanges(WorkingRanges *general_ranges, WorkingRanges *fp_ranges)
345 {
346 for (auto *interval : GetGraph()->GetAnalysis<LivenessAnalyzer>().GetLifeIntervals()) {
347 if (interval->GetReg() == ACC_REG_ID) {
348 continue;
349 }
350
351 if (interval->IsPreassigned() && interval->GetReg() == GetGraph()->GetZeroReg()) {
352 ASSERT(interval->GetReg() != INVALID_REG);
353 continue;
354 }
355
356 // Skip instructions without destination register
357 if (!interval->IsPhysical() && interval->NoDest()) {
358 ASSERT(interval->GetLocation().IsInvalid());
359 continue;
360 }
361
362 bool is_fp = DataType::IsFloatType(interval->GetType());
363 auto *ranges = is_fp ? fp_ranges : general_ranges;
364 if (interval->IsPhysical()) {
365 auto mask = is_fp ? GetVRegMask() : GetRegMask();
366 if (mask.IsSet(interval->GetReg())) {
367 // skip physical intervals for unavailable registers, they do not affect allocation
368 continue;
369 }
370 AddRange(interval, &ranges->physical);
371 } else {
372 AddRange(interval, &ranges->regular);
373 }
374 }
375 }
376
InitMap(RegisterMap * map,bool is_vector)377 void RegAllocGraphColoring::InitMap(RegisterMap *map, bool is_vector)
378 {
379 auto arch = GetGraph()->GetArch();
380 if (arch == Arch::NONE) {
381 ASSERT(GetGraph()->IsBytecodeOptimizer());
382 ASSERT(!is_vector);
383 map->SetMask(GetRegMask(), 0);
384 } else {
385 size_t first_callee = GetFirstCalleeReg(arch, is_vector);
386 size_t last_callee = GetLastCalleeReg(arch, is_vector);
387 map->SetCallerFirstMask(is_vector ? GetVRegMask() : GetRegMask(), first_callee, last_callee);
388 }
389 }
390
Presplit(WorkingRanges * ranges)391 void RegAllocGraphColoring::Presplit(WorkingRanges *ranges)
392 {
393 ArenaVector<LifeIntervals *> to_split(GetGraph()->GetLocalAllocator()->Adapter());
394
395 for (auto interval : ranges->regular) {
396 if (!interval->GetLocation().IsFixedRegister()) {
397 continue;
398 }
399 for (auto next : ranges->regular) {
400 if (next->GetBegin() <= interval->GetBegin()) {
401 continue;
402 }
403 if (interval->GetLocation() == next->GetLocation() && interval->IntersectsWith(next)) {
404 to_split.push_back(interval);
405 break;
406 }
407 }
408
409 if (!to_split.empty() && to_split.back() == interval) {
410 // Already added to split
411 continue;
412 }
413 for (auto physical : ranges->physical) {
414 if (interval->GetLocation() == physical->GetLocation() && interval->IntersectsWith(physical)) {
415 to_split.push_back(interval);
416 break;
417 }
418 }
419 }
420
421 for (auto interval : to_split) {
422 COMPILER_LOG(DEBUG, REGALLOC) << "Split at the beginning: " << interval->ToString();
423 auto split = interval->SplitAt(interval->GetBegin() + 1, GetGraph()->GetAllocator());
424 AddRange(split, &ranges->regular);
425 }
426 }
427 } // namespace panda::compiler