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_resolver.h"
17 #include "reg_type.h"
18 #include "compiler/optimizer/code_generator/codegen.h"
19 #include "compiler/optimizer/ir/inst.h"
20 #include "compiler/optimizer/ir/graph.h"
21 #include "compiler/optimizer/ir/basicblock.h"
22 #include "compiler/optimizer/analysis/dominators_tree.h"
23 #include "optimizer/analysis/loop_analyzer.h"
24
25 namespace panda::compiler {
26 /*
27 * For each instruction set destination register if it is assigned,
28 * Pop inputs from stack and push result on stack if stack slot is assigned.
29 */
Resolve()30 void RegAllocResolver::Resolve()
31 {
32 // We use RPO order because we need to calculate Caller's roots mask before its inlined callees
33 // (see PropagateCallerMasks).
34 for (auto block : GetGraph()->GetBlocksRPO()) {
35 for (auto inst : block->AllInstsSafe()) {
36 if (inst->IsSaveState()) {
37 ResolveSaveState(inst);
38 continue;
39 }
40 ResolveInputs(inst);
41 ResolveOutput(inst);
42 if (GetGraph()->IsInstThrowable(inst)) {
43 AddCatchPhiMoves(inst);
44 }
45 }
46 }
47 }
48
AddCatchPhiMoves(Inst * inst)49 void RegAllocResolver::AddCatchPhiMoves(Inst *inst)
50 {
51 auto spill_fill_inst = GetGraph()->CreateInstSpillFill();
52 spill_fill_inst->SetSpillFillType(SpillFillType::INPUT_FILL);
53 auto handlers = GetGraph()->GetThrowableInstHandlers(inst);
54
55 for (auto catch_handler : handlers) {
56 for (auto catch_inst : catch_handler->AllInsts()) {
57 if (!catch_inst->IsCatchPhi() || catch_inst->CastToCatchPhi()->IsAcc()) {
58 continue;
59 }
60 auto catch_phi = catch_inst->CastToCatchPhi();
61 const auto &throwable_insts = catch_phi->GetThrowableInsts();
62 auto it = std::find(throwable_insts->begin(), throwable_insts->end(), inst);
63 if (it == throwable_insts->end()) {
64 continue;
65 }
66 int index = std::distance(throwable_insts->begin(), it);
67 auto catch_input = catch_phi->GetDataFlowInput(index);
68 auto input_interval = liveness_->GetInstLifeIntervals(catch_input);
69 ASSERT(input_interval->GetSibling() == nullptr);
70 auto catch_phi_interval = liveness_->GetInstLifeIntervals(catch_phi);
71 if (input_interval->GetLocation() != catch_phi_interval->GetLocation()) {
72 ConnectIntervals(spill_fill_inst, input_interval, catch_phi_interval);
73 }
74 }
75 }
76 if (!spill_fill_inst->GetSpillFills().empty()) {
77 inst->InsertBefore(spill_fill_inst);
78 }
79 }
80
ResolveInputs(Inst * inst)81 void RegAllocResolver::ResolveInputs(Inst *inst)
82 {
83 if (inst->IsPhi() || inst->IsCatchPhi() || IsPseudoUserOfMultiOutput(inst)) {
84 return;
85 }
86
87 // Life-position before instruction to analyze intervals, that were splited directly before it
88 auto pre_ins_ln = liveness_->GetInstLifeIntervals(inst)->GetBegin() - 1U;
89
90 for (size_t i = 0; i < inst->GetInputsCount(); i++) {
91 auto location = inst->GetLocation(i);
92 auto input_interval = liveness_->GetInstLifeIntervals(inst->GetDataFlowInput(i));
93
94 if (CanReadFromAccumulator(inst, i) || input_interval->NoDest() || location.IsInvalid()) {
95 continue;
96 }
97
98 // Interval with fixed register can be splited before `inst`: we don't need any extra moves in that case,
99 // since fixed register can't be overwrite
100 auto sibling = input_interval->FindSiblingAt(pre_ins_ln);
101 ASSERT(sibling != nullptr);
102 if (location.IsFixedRegister() && sibling->GetLocation() == location) {
103 continue;
104 }
105
106 // Otherwise use sibling covering `inst`
107 if (sibling->GetEnd() == pre_ins_ln) {
108 sibling = sibling->GetSibling();
109 }
110
111 // Input's location required any register: specify the allocated one
112 if (location.IsUnallocatedRegister()) {
113 ASSERT(sibling->HasReg());
114 inst->SetLocation(i, sibling->GetLocation());
115 continue;
116 }
117
118 // Finally, if input's location is not equal to the required one, add spill-fill
119 if (sibling->GetLocation() != location) {
120 AddMoveToFixedLocation(inst, sibling->GetLocation(), i);
121 }
122 }
123 }
124
AddMoveToFixedLocation(Inst * inst,Location input_location,size_t input_num)125 void RegAllocResolver::AddMoveToFixedLocation(Inst *inst, Location input_location, size_t input_num)
126 {
127 // Create or get existing SpillFillInst
128 SpillFillInst *sf_inst {};
129 if (inst->GetPrev() != nullptr && inst->GetPrev()->IsSpillFill()) {
130 sf_inst = inst->GetPrev()->CastToSpillFill();
131 } else {
132 sf_inst = GetGraph()->CreateInstSpillFill();
133 sf_inst->SetSpillFillType(SpillFillType::INPUT_FILL);
134 inst->InsertBefore(sf_inst);
135 }
136
137 // Add move from input to fixed location
138 auto type = ConvertRegType(GetGraph(), inst->GetInputType(input_num));
139 auto fixed_location = inst->GetLocation(input_num);
140 if (fixed_location.IsFixedRegister()) {
141 GetGraph()->SetRegUsage(fixed_location.GetValue(), type);
142 }
143 sf_inst->AddSpillFill(input_location, fixed_location, type);
144 }
145
GetFirstUserOrInst(Inst * inst)146 Inst *GetFirstUserOrInst(Inst *inst)
147 {
148 for (auto &user : inst->GetUsers()) {
149 if (user.GetInst()->GetOpcode() != Opcode::ReturnInlined) {
150 return user.GetInst();
151 }
152 }
153 return inst;
154 }
155
156 // For implicit null check we need to find the first null check's user to
157 // correctly capture SaveState's input locations, because implicit null checks are fired
158 // when its input is accessed by its users (for example, when LoadArray instruction is loading
159 // value from null array reference). Some life intervals may change its location (due to spilling)
160 // between NullCheck and its users, so locations captured at implicit null check could be incorrect.
161 // While implicit NullCheck may have multiple users we can use only a user dominating all other users,
162 // because null check either will be fired at it, or won't be fired at all.
GetExplicitUser(Inst * inst)163 Inst *GetExplicitUser(Inst *inst)
164 {
165 if (!inst->IsNullCheck() || !inst->CastToNullCheck()->IsImplicit() || inst->GetUsers().Empty()) {
166 return inst;
167 }
168 if (inst->HasSingleUser()) {
169 return inst->GetUsers().Front().GetInst();
170 }
171
172 Inst *user_inst {nullptr};
173 for (auto &user : inst->GetUsers()) {
174 auto curr_inst = user.GetInst();
175 if (!IsSuitableForImplicitNullCheck(curr_inst)) {
176 continue;
177 }
178 if (curr_inst->GetInput(0).GetInst() != inst) {
179 continue;
180 }
181 if (!curr_inst->CanThrow()) {
182 continue;
183 }
184 user_inst = curr_inst;
185 break;
186 }
187 #ifndef NDEBUG
188 for (auto &user : inst->GetUsers()) {
189 if (user.GetInst()->IsPhi()) {
190 continue;
191 }
192 ASSERT(user_inst != nullptr && user_inst->IsDominate(user.GetInst()));
193 }
194 #endif
195 return user_inst;
196 }
197
PropagateCallerMasks(SaveStateInst * save_state)198 void RegAllocResolver::PropagateCallerMasks(SaveStateInst *save_state)
199 {
200 save_state->CreateRootsStackMask(GetGraph()->GetAllocator());
201 auto user = GetExplicitUser(GetFirstUserOrInst(save_state));
202 // Get location of save state inputs at the save state user (note that at this point
203 // all inputs will have the same location at all users (excluding ReturnInlined that should be skipped)).
204 FillSaveStateRootsMask(save_state, user, save_state);
205 for (auto caller_inst = save_state->GetCallerInst(); caller_inst != nullptr;
206 caller_inst = caller_inst->GetSaveState()->GetCallerInst()) {
207 auto caller_ss = caller_inst->GetSaveState();
208 FillSaveStateRootsMask(caller_ss, user, save_state);
209 }
210 }
211
FillSaveStateRootsMask(SaveStateInst * save_state,Inst * user,SaveStateInst * target_ss)212 void RegAllocResolver::FillSaveStateRootsMask(SaveStateInst *save_state, Inst *user, SaveStateInst *target_ss)
213 {
214 auto dst_ln = liveness_->GetInstLifeIntervals(user)->GetBegin();
215
216 for (size_t i = 0; i < save_state->GetInputsCount(); ++i) {
217 auto input_inst = save_state->GetDataFlowInput(i);
218 if (input_inst->IsConst() || !IsTypeCollectable(input_inst->GetType())) {
219 continue;
220 }
221 auto input_interval = liveness_->GetInstLifeIntervals(input_inst);
222 auto sibling = input_interval->FindSiblingAt(dst_ln);
223 ASSERT(sibling != nullptr);
224 if (!sibling->SplitCover(dst_ln)) {
225 continue;
226 }
227 AddLocationToRoots(sibling->GetLocation(), target_ss, GetGraph());
228 #ifndef NDEBUG
229 for (auto &test_user : target_ss->GetUsers()) {
230 if (test_user.GetInst()->GetOpcode() == Opcode::ReturnInlined ||
231 test_user.GetInst()->GetId() == user->GetId()) {
232 continue;
233 }
234 auto explicit_test_user = GetExplicitUser(test_user.GetInst());
235 auto udst_ln = liveness_->GetInstLifeIntervals(explicit_test_user)->GetBegin();
236 ASSERT(sibling->GetLocation() == input_interval->FindSiblingAt(udst_ln)->GetLocation());
237 }
238 #endif
239 }
240 }
241
242 namespace {
CopySaveState(Graph * graph,SaveStateInst * inst)243 SaveStateInst *CopySaveState(Graph *graph, SaveStateInst *inst)
244 {
245 auto copy = static_cast<SaveStateInst *>(inst->Clone(graph));
246 ASSERT(copy->GetCallerInst() == inst->GetCallerInst());
247 for (size_t input_idx = 0; input_idx < inst->GetInputsCount(); input_idx++) {
248 copy->AppendInput(inst->GetInput(input_idx));
249 copy->SetVirtualRegister(input_idx, inst->GetVirtualRegister(input_idx));
250 }
251 copy->SetLinearNumber(inst->GetLinearNumber());
252 return copy;
253 }
254
HasSameLocation(LifeIntervals * interval,LifeNumber pos1,LifeNumber pos2)255 bool HasSameLocation(LifeIntervals *interval, LifeNumber pos1, LifeNumber pos2)
256 {
257 auto sibling1 = interval->FindSiblingAt(pos1);
258 auto sibling2 = interval->FindSiblingAt(pos2);
259 ASSERT(sibling1 != nullptr);
260 ASSERT(sibling2 != nullptr);
261 return sibling1->SplitCover(pos1) && sibling1->SplitCover(pos2) &&
262 sibling1->GetLocation() == sibling2->GetLocation();
263 }
264
SaveStateCopyRequired(Inst * inst,User * curr_user,User * prev_user,const LivenessAnalyzer * la)265 bool SaveStateCopyRequired(Inst *inst, User *curr_user, User *prev_user, const LivenessAnalyzer *la)
266 {
267 ASSERT(inst->IsSaveState());
268 auto curr_user_ln = la->GetInstLifeIntervals(GetExplicitUser(curr_user->GetInst()))->GetBegin();
269 auto prev_user_ln = la->GetInstLifeIntervals(GetExplicitUser(prev_user->GetInst()))->GetBegin();
270 bool need_copy = false;
271 // If current save state is part of inlined method then we have to check location for all
272 // parent save states.
273 for (auto ss = static_cast<SaveStateInst *>(inst); ss != nullptr && !need_copy;) {
274 for (size_t input_idx = 0; input_idx < ss->GetInputsCount() && !need_copy; input_idx++) {
275 auto input_interval = la->GetInstLifeIntervals(ss->GetDataFlowInput(input_idx));
276 need_copy = !HasSameLocation(input_interval, curr_user_ln, prev_user_ln);
277 }
278 auto caller = ss->GetCallerInst();
279 if (caller == nullptr) {
280 ss = nullptr;
281 } else {
282 ss = caller->GetSaveState();
283 }
284 }
285 return need_copy;
286 }
287 } // namespace
288
ResolveSaveState(Inst * inst)289 void RegAllocResolver::ResolveSaveState(Inst *inst)
290 {
291 if (GetGraph()->GetCallingConvention() == nullptr) {
292 return;
293 }
294 ASSERT(inst->IsSaveState());
295
296 bool handled_all_users = inst->HasSingleUser() || !inst->HasUsers();
297 while (!handled_all_users) {
298 size_t copy_users = 0;
299 auto user_it = inst->GetUsers().begin();
300 User *prev_user = &*user_it;
301 ++user_it;
302 bool need_copy = false;
303
304 // Find first user having different location for some of the save state inputs and use SaveState's
305 // copy for all preceding users.
306 for (; user_it != inst->GetUsers().end() && !need_copy; ++user_it, copy_users++) {
307 auto &curr_user = *user_it;
308 // ReturnInline's SaveState is required only for SaveState's inputs life range propagation,
309 // so it does not actually matter which interval will be actually used.
310 if (prev_user->GetInst()->GetOpcode() == Opcode::ReturnInlined) {
311 prev_user = &*user_it;
312 continue;
313 }
314 if (curr_user.GetInst()->GetOpcode() == Opcode::ReturnInlined) {
315 continue;
316 }
317 need_copy = SaveStateCopyRequired(inst, &curr_user, prev_user, liveness_);
318 prev_user = &*user_it;
319 }
320 if (need_copy) {
321 auto copy = CopySaveState(GetGraph(), static_cast<SaveStateInst *>(inst));
322 // Replace original SaveState with the copy for first N users (N = `copy_users` ).
323 while (copy_users > 0) {
324 auto user_inst = inst->GetUsers().Front().GetInst();
325 user_inst->ReplaceInput(inst, copy);
326 copy_users--;
327 }
328 inst->GetBasicBlock()->InsertAfter(copy, inst);
329 PropagateCallerMasks(copy);
330 handled_all_users = inst->HasSingleUser();
331 } else {
332 handled_all_users = !(user_it != inst->GetUsers().end());
333 }
334 }
335 // At this point inst either has single user or all its inputs have the same location at all users.
336 PropagateCallerMasks(static_cast<SaveStateInst *>(inst));
337 }
338
339 /*
340 * Pop output on stack from reserved register
341 */
ResolveOutput(Inst * inst)342 void RegAllocResolver::ResolveOutput(Inst *inst)
343 {
344 // Don't process LiveOut, since it is instruction with pseudo destination
345 if (inst->GetOpcode() == Opcode::LiveOut) {
346 return;
347 }
348 // Multi-output instructions' dst registers will be filled after procecssing theirs pseudo users
349 if (inst->GetLinearNumber() == INVALID_LINEAR_NUM || inst->GetDstCount() > 1) {
350 return;
351 }
352
353 if (CanStoreToAccumulator(inst)) {
354 return;
355 }
356
357 auto inst_interval = liveness_->GetInstLifeIntervals(inst);
358 if (inst_interval->NoDest()) {
359 inst->SetDstReg(INVALID_REG);
360 return;
361 }
362
363 if (inst->GetOpcode() == Opcode::Parameter) {
364 inst->CastToParameter()->GetLocationData().SetDst(inst_interval->GetLocation());
365 }
366 // Process multi-output inst
367 size_t dst_mum = inst->GetSrcRegIndex();
368 if (IsPseudoUserOfMultiOutput(inst)) {
369 inst = inst->GetInput(0).GetInst();
370 }
371 // Wrtie dst
372 auto reg_type = inst_interval->GetType();
373 if (inst_interval->HasReg()) {
374 auto reg = inst_interval->GetReg();
375 inst->SetDstReg(dst_mum, reg);
376 GetGraph()->SetRegUsage(reg, reg_type);
377 } else {
378 ASSERT(inst->IsConst() || inst->IsPhi() || inst->IsParameter());
379 }
380 }
381
ResolveCatchPhis()382 bool RegAllocResolver::ResolveCatchPhis()
383 {
384 for (auto block : GetGraph()->GetBlocksRPO()) {
385 if (!block->IsCatchBegin()) {
386 continue;
387 }
388 for (auto inst : block->AllInstsSafe()) {
389 if (!inst->IsCatchPhi()) {
390 break;
391 }
392 if (inst->CastToCatchPhi()->IsAcc()) {
393 continue;
394 }
395 // This is the case when all throwable instructions were removed from the try-block,
396 // so that catch-handler is unreachable
397 if (inst->GetInputs().Empty()) {
398 return false;
399 }
400 auto new_catch_phi = SqueezeCatchPhiInputs(inst->CastToCatchPhi());
401 if (new_catch_phi != nullptr) {
402 inst->ReplaceUsers(new_catch_phi);
403 block->RemoveInst(inst);
404 }
405 }
406 }
407 return true;
408 }
409
410 /**
411 * Try to remove catch phi's inputs:
412 * If the input's corresponding throwable instruction dominates other throwable inst, we can remove other equal catch
413 * phi's input
414 *
415 * CatchPhi(v1, v1, v1, v2, v2, v2) -> CatchPhi(v1, v2)
416 *
417 * Return nullptr if inputs count was not reduced.
418 */
SqueezeCatchPhiInputs(CatchPhiInst * catch_phi)419 Inst *RegAllocResolver::SqueezeCatchPhiInputs(CatchPhiInst *catch_phi)
420 {
421 bool inputs_are_identical = true;
422 auto first_input = catch_phi->GetInput(0).GetInst();
423 for (size_t i = 1; i < catch_phi->GetInputsCount(); ++i) {
424 if (catch_phi->GetInput(i).GetInst() != first_input) {
425 inputs_are_identical = false;
426 break;
427 }
428 }
429 if (inputs_are_identical) {
430 return first_input;
431 }
432
433 // Create a new one and fill it with the necessary inputs
434 auto new_catch_phi = GetGraph()->CreateInstCatchPhi(catch_phi->GetType(), catch_phi->GetPc());
435 ASSERT(catch_phi->GetBasicBlock()->GetFirstInst()->IsCatchPhi());
436 catch_phi->GetBasicBlock()->PrependInst(new_catch_phi);
437 for (size_t i = 0; i < catch_phi->GetInputsCount(); i++) {
438 auto input_inst = catch_phi->GetInput(i).GetInst();
439 auto current_throwable_inst = catch_phi->GetThrowableInst(i);
440 ASSERT(GetGraph()->IsInstThrowable(current_throwable_inst));
441 bool skip = false;
442 for (size_t j = 0; j < new_catch_phi->GetInputsCount(); j++) {
443 auto saved_inst = new_catch_phi->GetInput(j).GetInst();
444 if (saved_inst != input_inst) {
445 continue;
446 }
447 auto saved_throwable_inst = new_catch_phi->GetThrowableInst(j);
448 if (saved_throwable_inst->IsDominate(current_throwable_inst)) {
449 skip = true;
450 }
451 if (current_throwable_inst->IsDominate(saved_throwable_inst)) {
452 new_catch_phi->ReplaceThrowableInst(saved_throwable_inst, current_throwable_inst);
453 skip = true;
454 }
455 if (skip) {
456 break;
457 }
458 }
459 if (!skip) {
460 new_catch_phi->AppendInput(input_inst);
461 new_catch_phi->AppendThrowableInst(current_throwable_inst);
462 }
463 }
464 if (new_catch_phi->GetInputsCount() == catch_phi->GetInputsCount()) {
465 new_catch_phi->GetBasicBlock()->RemoveInst(new_catch_phi);
466 return nullptr;
467 }
468 return new_catch_phi;
469 }
470
471 } // namespace panda::compiler
472