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 "graph_cloner.h"
17 #include "optimizer/analysis/linear_order.h"
18 #include "inst_checker_gen.h"
19 #include "graph_checker.h"
20
21 namespace panda::compiler {
22
GraphChecker(Graph * graph)23 GraphChecker::GraphChecker(Graph *graph)
24 {
25 PreCloneChecks(graph);
26 graph_ = GraphCloner(graph, GetAllocator(), GetLocalAllocator()).CloneGraph();
27 GetGraph()->GetPassManager()->SetCheckMode(true);
28 }
29
PreCloneChecks(Graph * graph)30 void GraphChecker::PreCloneChecks(Graph *graph)
31 {
32 UserInputCheck(graph);
33 }
34
UserInputCheck(Graph * graph)35 void GraphChecker::UserInputCheck(Graph *graph)
36 {
37 for (auto block : graph->GetVectorBlocks()) {
38 if (block == nullptr) {
39 continue;
40 }
41 for (auto inst : block->AllInsts()) {
42 auto u = inst->GetFirstUser();
43 ASSERT(u == nullptr || u->GetPrev() == nullptr);
44 while (u != nullptr) {
45 ASSERT(u->GetNext() == nullptr || u->GetNext()->GetPrev() == u);
46 u = u->GetNext();
47 }
48 for (auto &user : inst->GetUsers()) {
49 [[maybe_unused]] auto user_inst = user.GetInst();
50 ASSERT(user_inst->GetBasicBlock() != nullptr);
51 ASSERT_DO(CheckInstHasInput(user_inst, inst), std::cerr << "Instruction is not an input to its user\n"
52 << "input: " << *inst << std::endl
53 << "user: " << *user_inst << std::endl);
54 }
55 for (auto &input : inst->GetInputs()) {
56 [[maybe_unused]] auto input_inst = input.GetInst();
57 ASSERT(input_inst != nullptr && input_inst->GetBasicBlock() != nullptr);
58 ASSERT_DO(CheckInstHasUser(input_inst, inst), std::cerr << "Instruction is not a user to its input:\n"
59 << "user: " << *inst << std::endl
60 << "input: " << *input_inst << std::endl);
61 }
62 // Check `require_state` flag
63 auto it = std::find_if(inst->GetInputs().begin(), inst->GetInputs().end(),
64 [](Input input) { return input.GetInst()->IsSaveState(); });
65 [[maybe_unused]] bool has_save_state = (it != inst->GetInputs().end());
66 ASSERT_DO(inst->RequireState() == has_save_state,
67 std::cerr << "Incorrect 'require_state' flag in the inst: " << *inst);
68 if (inst->RequireState()) {
69 ASSERT(it->GetInst() == inst->GetSaveState());
70 }
71 }
72 }
73 }
74
Check()75 void GraphChecker::Check()
76 {
77 if (!GetGraph()->IsDynamicMethod()) {
78 InstChecker::Run(GetGraph());
79 }
80
81 #ifndef NDEBUG
82 if (GetGraph()->IsAnalysisValid<DominatorsTree>()) {
83 CheckDomTree();
84 } else {
85 GetGraph()->RunPass<DominatorsTree>();
86 }
87 if (GetGraph()->IsAnalysisValid<LoopAnalyzer>()) {
88 CheckLoopAnalysis();
89 } else {
90 GetGraph()->RunPass<LoopAnalyzer>();
91 }
92 CheckStartBlock();
93 CheckEndBlock();
94 size_t blocks_count = 0;
95 size_t blocks_id = -1;
96 for (auto block : GetGraph()->GetVectorBlocks()) {
97 ++blocks_id;
98 if (block == nullptr) {
99 continue;
100 }
101 ASSERT_PRINT(block->GetGraph() == GetGraph(), "Block linked to incorrect graph");
102 ASSERT_PRINT(block->GetId() == blocks_id, "Block ID must be equal to its ID in graph vector");
103 CheckBlock(block);
104 blocks_count++;
105 }
106 ASSERT_PRINT(blocks_count == GetGraph()->GetBlocksRPO().size(), "There is disconnected block");
107 CheckLoops();
108 // Visit graph to check instructions types
109 CheckGraph();
110 // Check that call.Inlined and Return.Inlined in correct order
111 // and check that savestate has correct link to call.inlined.
112 CheckCallReturnInlined();
113 if (NeedCheckSaveState()) {
114 // Check that objects in stack.
115 CheckSaveStateInputs();
116 // Check that between savestate and it's runtime call user have not reference insts.
117 CheckSaveStatesWithRuntimeCallUsers();
118 }
119
120 #endif // !NDEBUG
121 }
122
123 #ifndef NDEBUG
NeedCheckSaveState()124 bool GraphChecker::NeedCheckSaveState()
125 {
126 return !GetGraph()->IsBytecodeOptimizer() && GetGraph()->GetParentGraph() == nullptr &&
127 GetGraph()->IsInliningComplete() && !GetGraph()->IsSchedulerComplete();
128 }
129 #endif // !NDEBUG
130
CheckBlock(BasicBlock * block)131 void GraphChecker::CheckBlock([[maybe_unused]] BasicBlock *block)
132 {
133 #ifndef NDEBUG
134 CheckControlFlow(block);
135 CheckDataFlow(block);
136 for (auto phi_inst : block->PhiInsts()) {
137 CheckPhiInputs(phi_inst);
138 }
139 if (!GetGraph()->IsLowLevelInstructionsEnabled() && !GetGraph()->IsDynamicMethod()) {
140 CheckNoLowLevel(block);
141 }
142 if (!block->IsEndBlock() && !block->IsStartBlock()) {
143 CheckBlockEdges(*block);
144 }
145 if (block->IsTryBegin()) {
146 CheckTryBeginBlock(*block);
147 }
148 if (block->NeedsJump()) {
149 CheckJump(*block);
150 }
151 #endif // !NDEBUG
152 }
153
CheckControlFlow(BasicBlock * block)154 void GraphChecker::CheckControlFlow(BasicBlock *block)
155 {
156 auto num_succs = block->GetSuccsBlocks().size();
157 ASSERT_PRINT(block->IsEndBlock() || block->IsTryBegin() || block->IsTryEnd() ||
158 (num_succs > 0 && num_succs <= MAX_SUCCS_NUM),
159 "Non-end block and non-try-begin block should have 1 or 2 successesors");
160
161 for ([[maybe_unused]] auto pred : block->GetPredsBlocks()) {
162 ASSERT_PRINT(CheckBlockHasSuccessor(pred, block), "Block is not a successor to its predecessor");
163 }
164 for ([[maybe_unused]] auto succ : block->GetSuccsBlocks()) {
165 ASSERT_PRINT(CheckBlockHasPredecessor(succ, block), "Block is not a predecessor to its successor");
166 }
167
168 if (num_succs == MAX_SUCCS_NUM) {
169 ASSERT_PRINT(block->GetSuccessor(0) != block->GetSuccessor(1), "Wrong CFG - block with two same successors");
170 }
171
172 for ([[maybe_unused]] auto phi : block->PhiInsts()) {
173 ASSERT_DO(phi->GetInputsCount() == block->GetPredsBlocks().size(),
174 std::cerr << phi->GetInputsCount() << " " << block->GetPredsBlocks().size()
175 << "Incorrect phi's inputs count" << *phi);
176 }
177 }
178
CheckDataFlow(BasicBlock * block)179 void GraphChecker::CheckDataFlow(BasicBlock *block)
180 {
181 auto graph = block->GetGraph();
182 for (auto inst : block->AllInsts()) {
183 ASSERT_DO(inst->GetBasicBlock() == block,
184 std::cerr << "Instruction block's pointer isn't correct" << *inst << std::endl);
185 if (block != graph->GetStartBlock()) {
186 ASSERT_DO(inst->GetOpcode() != Opcode::Parameter,
187 std::cerr << "Not entry block can't contain Parameter instructions" << *inst << std::endl);
188 }
189 if (inst->GetPrev() == nullptr) {
190 ASSERT_PRINT(*block->AllInsts().begin() == inst, "First block instruction isn't correct");
191 }
192 if (inst->GetNext() == nullptr) {
193 ASSERT_PRINT(*block->AllInstsSafeReverse().begin() == inst, "Last block instruction isn't correct");
194 }
195 // Inst with reference type must have no_cse and no_hoist flags.
196 if (inst->GetType() == DataType::REFERENCE) {
197 ASSERT(inst->IsNotCseApplicable());
198 ASSERT(inst->IsNotHoistable());
199 }
200 for ([[maybe_unused]] auto &user : inst->GetUsers()) {
201 auto user_inst = user.GetInst();
202 ASSERT_DO(CheckInstHasInput(user_inst, inst), std::cerr << "Instruction is not an input to its user\n"
203 << "input: " << *inst << std::endl
204 << "user: " << *user_inst << std::endl);
205 if (!user_inst->IsPhi() && !user_inst->IsCatchPhi()) {
206 ASSERT_DO(inst->IsDominate(user_inst) ||
207 (GetGraph()->IsRegAllocApplied() &&
208 IsTryCatchDomination(inst->GetBasicBlock(), user_inst->GetBasicBlock())),
209 std::cerr << "Instruction doesn't dominate its user\n"
210 << "input: " << *inst << std::endl
211 << "user: " << *user_inst << std::endl);
212 }
213 auto arch = graph->GetArch();
214 if (DataType::Is32Bits(inst->GetType(), arch)) {
215 if (!user_inst->HasType()) {
216 continue;
217 }
218 [[maybe_unused]] auto user_input_type = user_inst->GetInputType(user.GetIndex());
219 [[maybe_unused]] bool ref_to_ptr =
220 user_input_type == DataType::POINTER && inst->GetType() == DataType::REFERENCE;
221 ASSERT_DO(DataType::Is32Bits(user_input_type, arch) || ref_to_ptr ||
222 (block->GetGraph()->IsDynamicMethod() && user_input_type == DataType::ANY),
223 std::cerr << "Undefined high-part of input instruction for its user\n"
224 << "input: " << *inst << std::endl
225 << "user: " << *user_inst << std::endl);
226 }
227 }
228 for ([[maybe_unused]] auto input : inst->GetInputs()) {
229 ASSERT_DO(CheckInstHasUser(input.GetInst(), inst), std::cerr << "Instruction is not a user to its input:\n"
230 << "input: " << *input.GetInst() << std::endl
231 << "user: " << *inst << std::endl);
232 }
233 }
234 }
235
CheckCallReturnInlined()236 void GraphChecker::CheckCallReturnInlined()
237 {
238 ArenaStack<Inst *> inlined_calles(GetLocalAllocator()->Adapter());
239 if (GetGraph()->HasEndBlock()) {
240 for (auto block : GetGraph()->GetEndBlock()->GetPredsBlocks()) {
241 if (block->IsTryEnd()) {
242 continue;
243 }
244 }
245 }
246 ASSERT(inlined_calles.empty());
247 #ifndef NDEBUG
248 // avoid check after ir_builder in inline pass
249 if (!GetGraph()->IsInliningComplete() || GetGraph()->GetParentGraph() != nullptr) {
250 return;
251 }
252 #endif
253 }
254
CheckStartBlock()255 void GraphChecker::CheckStartBlock()
256 {
257 [[maybe_unused]] Inst *has_nullptr = nullptr;
258 [[maybe_unused]] int32_t last_num = -1;
259 ASSERT(GetGraph()->GetStartBlock());
260 ASSERT_PRINT(GetGraph()->GetStartBlock()->GetPredsBlocks().empty(), "Start block can't have predecessors");
261 ASSERT_PRINT(GetGraph()->GetStartBlock()->GetSuccsBlocks().size() == 1, "Start block should have one successor");
262 for (auto inst : GetGraph()->GetStartBlock()->AllInsts()) {
263 [[maybe_unused]] Opcode opc = inst->GetOpcode();
264 ASSERT_DO(
265 opc == Opcode::Constant || opc == Opcode::Parameter || opc == Opcode::SpillFill,
266 std::cerr
267 << "Entry block can contain Constant, Parameter, NullPtr, SafePoint, NOP or SpillFill instructions"
268 << *inst << std::endl);
269 if (opc == Opcode::Parameter) {
270 auto arg_num = inst->CastToParameter()->GetArgNumber();
271 ASSERT_DO(
272 last_num < static_cast<int32_t>(arg_num),
273 std::cerr << "The argument number in the parameter must be greater than that of the previous parameter"
274 << *inst << std::endl);
275 last_num = static_cast<int32_t>(arg_num);
276 }
277 }
278 }
279
CheckEndBlock()280 void GraphChecker::CheckEndBlock()
281 {
282 if (!GetGraph()->HasEndBlock()) {
283 ASSERT_PRINT(HasOuterInfiniteLoop(), "Graph without infinite loops should have end block");
284 return;
285 }
286 ASSERT_PRINT(GetGraph()->GetEndBlock()->GetSuccsBlocks().empty(), "End block can't have successors");
287 [[maybe_unused]] auto iter = GetGraph()->GetEndBlock()->Insts();
288 ASSERT_PRINT(iter.begin() == iter.end(), "End block can't have instructions");
289 }
290
CheckGraph()291 void GraphChecker::CheckGraph()
292 {
293 size_t num_inst = GetGraph()->GetCurrentInstructionId();
294 ArenaVector<bool> inst_vec(num_inst, GetLocalAllocator()->Adapter());
295 for (auto &bb : GetGraph()->GetBlocksRPO()) {
296 for (auto inst : bb->AllInsts()) {
297 auto id = inst->GetId();
298 ASSERT_DO(id < num_inst,
299 (std::cerr << "Instruction ID must be less than graph instruction counter: " << num_inst << "\n",
300 inst->Dump(&std::cerr)));
301 ASSERT_DO(!inst_vec[id],
302 (std::cerr << "Instruction with same Id already exists:\n", inst->Dump(&std::cerr)));
303 inst_vec[id] = true;
304 ASSERT_DO(GetGraph()->IsDynamicMethod() || inst->GetType() != DataType::ANY,
305 (std::cerr << "The type ANY is supported only for dynamic languages\n", inst->Dump(&std::cerr)));
306 ASSERT_DO(inst->SupportsMode(GetGraph()->GetCompilerMode()),
307 (std::cerr << "Instruction used in wrong mode\n", inst->Dump(&std::cerr)));
308 VisitInstruction(inst);
309 }
310 }
311 }
312
CheckPhiInputs(Inst * phi_inst)313 void GraphChecker::CheckPhiInputs(Inst *phi_inst)
314 {
315 for (size_t index = 0; index < phi_inst->GetInputsCount(); ++index) {
316 [[maybe_unused]] auto pred = phi_inst->CastToPhi()->GetPhiInputBb(index);
317 [[maybe_unused]] auto input_bb = phi_inst->CastToPhi()->GetPhiInput(pred)->GetBasicBlock();
318 ASSERT_DO(input_bb->IsDominate(pred) || IsTryCatchDomination(input_bb, pred),
319 (std::cerr
320 << "Block where phi-input is located should dominate predecessor block corresponding to this input\n"
321 << "Block " << input_bb->GetId() << " should dominate " << pred->GetId() << std::endl
322 << *phi_inst));
323 }
324 }
325
CheckInstRegUsageSaved(const Inst * inst,Register reg) const326 bool GraphChecker::CheckInstRegUsageSaved(const Inst *inst, Register reg) const
327 {
328 if (reg == ACC_REG_ID) {
329 return true;
330 }
331 auto graph = inst->GetBasicBlock()->GetGraph();
332 // Empty vector regs mask means we are using dynamic general regs set.
333 if (DataType::IsFloatType(inst->GetType()) && !graph->GetUsedRegs<DataType::FLOAT64>()->empty()) {
334 return graph->GetUsedRegs<DataType::FLOAT64>()->at(reg);
335 }
336 return graph->GetUsedRegs<DataType::INT64>()->at(reg);
337 }
338
checkSpillFillMultiple(const compiler::Inst * inst)339 [[maybe_unused]] static bool checkSpillFillMultiple(const compiler::Inst *inst)
340 {
341 switch (inst->GetOpcode()) {
342 case Opcode::Parameter:
343 return false;
344 default:
345 return true;
346 }
347 }
348
CheckNoLowLevel(BasicBlock * block)349 void GraphChecker::CheckNoLowLevel(BasicBlock *block)
350 {
351 for ([[maybe_unused]] auto inst : block->Insts()) {
352 ASSERT_DO(!inst->IsLowLevel(), inst->Dump(&std::cerr));
353 }
354 }
355
MarkBlocksInLoop(Loop * loop,Marker mrk)356 void GraphChecker::MarkBlocksInLoop(Loop *loop, Marker mrk)
357 {
358 ASSERT(loop->IsIrreducible() || loop->IsRoot() || loop->GetHeader() != nullptr);
359 ASSERT(loop->IsIrreducible() || loop->IsRoot() || loop->IsTryCatchLoop() || loop->GetPreHeader() != nullptr);
360 // Mark blocks and check if marker was not set before
361 for ([[maybe_unused]] auto block : loop->GetBlocks()) {
362 ASSERT(!block->SetMarker(mrk));
363 }
364
365 for (auto inner : loop->GetInnerLoops()) {
366 MarkBlocksInLoop(inner, mrk);
367 }
368 }
369
CheckBlockHasPredecessor(BasicBlock * block,BasicBlock * predecessor)370 bool GraphChecker::CheckBlockHasPredecessor(BasicBlock *block, BasicBlock *predecessor)
371 {
372 ASSERT(block != nullptr && predecessor != nullptr);
373 for (auto pred : block->GetPredsBlocks()) {
374 if (pred == predecessor) {
375 return true;
376 }
377 }
378 return false;
379 }
380
CheckBlockHasSuccessor(BasicBlock * block,BasicBlock * successor)381 bool GraphChecker::CheckBlockHasSuccessor(BasicBlock *block, BasicBlock *successor)
382 {
383 ASSERT(block != nullptr && successor != nullptr);
384 for (auto succ : block->GetSuccsBlocks()) {
385 if (succ == successor) {
386 return true;
387 }
388 }
389 return false;
390 }
391
CheckLoopHasSafePoint(Loop * loop)392 void GraphChecker::CheckLoopHasSafePoint(Loop *loop)
393 {
394 ASSERT_DO(loop->IsTryCatchLoop() || loop->IsIrreducible(),
395 std::cerr << "Loop " << loop->GetId() << " must have safepoint\n");
396 for (auto inner : loop->GetInnerLoops()) {
397 CheckLoopHasSafePoint(inner);
398 }
399 }
400
CheckLoops()401 void GraphChecker::CheckLoops()
402 {
403 ASSERT(GetGraph()->GetAnalysis<LoopAnalyzer>().IsValid());
404 ASSERT(GetGraph()->GetRootLoop() != nullptr);
405 ASSERT(GetGraph()->GetRootLoop()->IsRoot());
406 ASSERT(GetGraph()->GetRootLoop()->GetHeader() == nullptr);
407 ASSERT(GetGraph()->GetRootLoop()->GetPreHeader() == nullptr);
408 auto root_loop = GetGraph()->GetRootLoop();
409 auto mrk = GetGraph()->NewMarker();
410 MarkBlocksInLoop(root_loop, mrk);
411
412 for ([[maybe_unused]] auto block : GetGraph()->GetBlocksRPO()) {
413 [[maybe_unused]] auto loop = block->GetLoop();
414 ASSERT(loop != nullptr);
415 ASSERT(block->IsMarked(mrk));
416 if (block->IsLoopHeader()) {
417 [[maybe_unused]] auto preds = block->GetPredsBlocks();
418 for ([[maybe_unused]] auto pred : preds) {
419 ASSERT(pred->GetLoop() != loop || loop->HasBackEdge(pred));
420 }
421
422 if (!loop->IsIrreducible()) {
423 for ([[maybe_unused]] auto back : loop->GetBackEdges()) {
424 ASSERT(std::find(preds.begin(), preds.end(), back) != preds.end());
425 }
426 }
427 } else {
428 ASSERT(!block->IsOsrEntry());
429 }
430 }
431 GetGraph()->EraseMarker(mrk);
432 if (options.IsCompilerUseSafepoint() && GetGraph()->SupportManagedCode()) {
433 for (auto inner : root_loop->GetInnerLoops()) {
434 CheckLoopHasSafePoint(inner);
435 }
436 }
437 }
438
CheckDomTree()439 void GraphChecker::CheckDomTree()
440 {
441 ASSERT(GetGraph()->GetAnalysis<DominatorsTree>().IsValid());
442 ArenaVector<BasicBlock *> dominators(GetGraph()->GetVectorBlocks().size(), GetLocalAllocator()->Adapter());
443 for (auto block : GetGraph()->GetBlocksRPO()) {
444 dominators[block->GetId()] = block->GetDominator();
445 }
446 // Rebuild dom-tree
447 GetGraph()->InvalidateAnalysis<DominatorsTree>();
448 GetGraph()->RunPass<DominatorsTree>();
449
450 for ([[maybe_unused]] auto block : GetGraph()->GetBlocksRPO()) {
451 ASSERT_DO(dominators[block->GetId()] == block->GetDominator(),
452 std::cerr << "Basic block with id " << block->GetId() << " has incorrect dominator with id "
453 << dominators[block->GetId()]->GetId() << std::endl
454 << "Correct dominator must be block with id " << block->GetDominator()->GetId() << std::endl
455 << "Note: basic blocks' ids in the original graph and in the cloned graph can be different"
456 << std::endl);
457 }
458 }
459
CheckLoopAnalysis()460 void GraphChecker::CheckLoopAnalysis()
461 {
462 // Save current loop info
463 ArenaUnorderedMap<BasicBlock *, Loop *> loops(GetLocalAllocator()->Adapter());
464 [[maybe_unused]] auto root_loop = GetGraph()->GetRootLoop();
465 for (auto block : GetGraph()->GetBlocksRPO()) {
466 if (block->IsLoopHeader()) {
467 loops.emplace(block, block->GetLoop());
468 }
469 }
470 // Build new loop info and compare with saved one
471 GetGraph()->InvalidateAnalysis<LoopAnalyzer>();
472 GetGraph()->RunPass<LoopAnalyzer>();
473 ASSERT_PRINT(*root_loop == *GetGraph()->GetRootLoop(), "Root loop is incorrect\n");
474 for (auto &[block, loop] : loops) {
475 auto expected_loop = block->GetLoop();
476 // An irreducible loop can have different heads, depending on the order of traversal
477 if (loop->IsIrreducible()) {
478 ASSERT(expected_loop->IsIrreducible());
479 continue;
480 }
481 ASSERT(block->IsLoopHeader());
482 if (loop == nullptr || expected_loop == nullptr) {
483 UNREACHABLE();
484 return;
485 }
486 ASSERT_DO(*loop == *expected_loop, std::cerr << "Loop " << loop->GetId() << " is incorrect\n");
487 }
488 }
489
490 /**
491 * Check that there is root's inner loop without exit-points
492 */
HasOuterInfiniteLoop()493 bool GraphChecker::HasOuterInfiniteLoop()
494 {
495 const auto &loops = GetGraph()->GetRootLoop()->GetInnerLoops();
496 return std::find_if(loops.begin(), loops.end(), [](const Loop *loop) { return loop->IsInfinite(); }) != loops.end();
497 }
498
CheckInstHasInput(Inst * inst,Inst * input)499 bool GraphChecker::CheckInstHasInput(Inst *inst, Inst *input)
500 {
501 ASSERT(inst != nullptr && input != nullptr);
502 ASSERT(input->GetBasicBlock() != nullptr);
503 ASSERT(input->GetBasicBlock()->GetGraph() != nullptr);
504 for (auto node : inst->GetInputs()) {
505 if (node.GetInst() == input) {
506 return true;
507 }
508 }
509 return false;
510 }
511
CheckInstHasUser(Inst * inst,Inst * user)512 bool GraphChecker::CheckInstHasUser(Inst *inst, Inst *user)
513 {
514 ASSERT(inst != nullptr && user != nullptr);
515 ASSERT(user->GetBasicBlock() != nullptr);
516 ASSERT(user->GetBasicBlock()->GetGraph() != nullptr);
517 for (auto &node : inst->GetUsers()) {
518 if (node.GetInst() == user) {
519 return true;
520 }
521 }
522 return false;
523 }
524
CheckBlockEdges(const BasicBlock & block)525 void GraphChecker::CheckBlockEdges(const BasicBlock &block)
526 {
527 [[maybe_unused]] auto last_inst_in_block = block.GetLastInst();
528 if (block.GetSuccsBlocks().size() > 1) {
529 ASSERT_PRINT(!block.IsEmpty() || block.IsTryEnd(),
530 "Block with 2 successors have no instructions or should be try-end");
531 ASSERT_PRINT(block.IsTryBegin() || block.IsTryEnd() || last_inst_in_block->IsControlFlow(),
532 "Last instruction must be control flow in block with 2 successors");
533 } else if (block.GetSuccsBlocks().size() == 1) {
534 if (block.GetSuccsBlocks()[0]->IsEndBlock()) {
535 if (block.IsEmpty()) {
536 ASSERT(block.IsTryEnd());
537 return;
538 }
539 auto last_inst = block.GetLastInst();
540 [[maybe_unused]] auto opc = last_inst->GetOpcode();
541 ASSERT_PRINT(last_inst->GetFlag(inst_flags::TERMINATOR),
542 "Last instruction in block before exit-block must be Return or Throw instruction.");
543 }
544 }
545 }
546
CheckTryBeginBlock(const BasicBlock & block)547 void GraphChecker::CheckTryBeginBlock(const BasicBlock &block)
548 {
549 ASSERT(block.IsTryBegin());
550 auto try_inst_it = std::find_if(block.AllInsts().begin(), block.AllInsts().end(),
551 [](Inst *inst) { return inst->GetOpcode() == Opcode::Try; });
552 ASSERT_PRINT(try_inst_it != block.AllInsts().end(), "Try-begin basic block should contain try-instructions");
553 [[maybe_unused]] auto try_inst = (*try_inst_it)->CastToTry();
554 for ([[maybe_unused]] auto succ_index : *try_inst->GetCatchEdgeIndexes()) {
555 ASSERT_PRINT(succ_index < block.GetSuccsBlocks().size(),
556 "Try instruction holds incorrect try-begin block successor number");
557 }
558 }
559
CheckJump(const BasicBlock & block)560 void GraphChecker::CheckJump(const BasicBlock &block)
561 {
562 ASSERT(GetGraph()->IsRegAllocApplied());
563 ASSERT(GetGraph()->IsAnalysisValid<LinearOrder>());
564 if (block.IsIfBlock()) {
565 const auto &blocks_vector = GetGraph()->GetBlocksLinearOrder();
566 auto if_block_it = std::find(blocks_vector.begin(), blocks_vector.end(), &block);
567 ASSERT(if_block_it != blocks_vector.end());
568 auto block_after_if = std::next(if_block_it);
569 if (block_after_if != blocks_vector.end()) {
570 ASSERT_PRINT(*block_after_if != (*if_block_it)->GetFalseSuccessor(),
571 "`If-block` with immediate `false`-successor shouldn't have `JumpFlag`");
572 ASSERT_PRINT(*block_after_if != (*if_block_it)->GetTrueSuccessor(),
573 "`true`-successor should be replaced with `false`-successor");
574 }
575 }
576 [[maybe_unused]] auto num_succs = block.GetSuccsBlocks().size();
577 ASSERT_PRINT(num_succs == 1 || block.IsTryBegin() || block.IsTryEnd() || block.IsIfBlock(),
578 "Basic block with Jump must have 1 successor or should be try-begin or if block");
579 }
580
581 /**
582 * Regalloc propagates catch-phi's inputs to the users and can broke user's domination. In this case:
583 * - input_block should be placed inside try block;
584 * - try-begin block should dominate user_block;
585 *
586 * [try-begin]----------\
587 * | |
588 * [input_block] |
589 * | |
590 * [try-end]----------->|
591 * |
592 * [catch-begin]
593 * |
594 * [user_block]
595 */
IsTryCatchDomination(const BasicBlock * input_block,const BasicBlock * user_block) const596 bool GraphChecker::IsTryCatchDomination(const BasicBlock *input_block, const BasicBlock *user_block) const
597 {
598 ASSERT(GetGraph()->IsRegAllocApplied());
599 if (input_block->IsTry()) {
600 auto blocks = GetGraph()->GetTryBeginBlocks();
601 auto it =
602 std::find_if(blocks.begin(), blocks.end(), [user_block](auto &bb) { return bb->IsDominate(user_block); });
603 return it != blocks.end();
604 }
605 return false;
606 }
607
IsObjectCheckDisabledForOpcode(const Inst * inst)608 bool IsObjectCheckDisabledForOpcode(const Inst *inst)
609 {
610 return inst->IsConst();
611 }
612
CheckSaveStatesWithRuntimeCallUsers()613 void GraphChecker::CheckSaveStatesWithRuntimeCallUsers()
614 {
615 #ifndef NDEBUG
616 for (auto &block : GetGraph()->GetBlocksRPO()) {
617 for (const auto &ss : block->AllInsts()) {
618 if (ss->GetOpcode() != Opcode::SaveState) {
619 continue;
620 }
621 }
622 }
623 #endif
624 }
625
PrepareUsers(Inst * inst,ArenaVector<User * > * users)626 void PrepareUsers(Inst *inst, ArenaVector<User *> *users)
627 {
628 for (auto &user : inst->GetUsers()) {
629 users->push_back(&user);
630 }
631 }
632
CheckSaveStateInputs()633 void GraphChecker::CheckSaveStateInputs()
634 {
635 #ifndef NDEBUG
636 ArenaVector<User *> users(GetLocalAllocator()->Adapter());
637 for (auto &block : GetGraph()->GetBlocksRPO()) {
638 for (const auto &inst : block->AllInsts()) {
639 if (IsObjectCheckDisabledForOpcode(inst)) {
640 continue;
641 }
642 // skip phi which all inputs is disabled
643 if (inst->GetOpcode() == Opcode::Phi) {
644 bool skip_flag = true;
645 for (const auto &input : inst->GetInputs()) {
646 skip_flag &= IsObjectCheckDisabledForOpcode(input.GetInst());
647 }
648 if (skip_flag) {
649 continue;
650 }
651 }
652
653 PrepareUsers(inst, &users);
654
655 auto object_visited = GetGraph()->NewMarker();
656 auto osr_visited = GetGraph()->NewMarker();
657 for (auto &it : users) {
658 auto user = it->GetInst();
659 // For Phi we need to check only pass between object and phi
660 if (user->IsPhi() || user->IsCatchPhi()) {
661 continue;
662 }
663 // Virtual register can be overwrite
664 if (user->IsSaveState()) {
665 continue;
666 }
667 if (inst->GetType() == DataType::REFERENCE) {
668 CheckObjectRec(inst, user, user->GetBasicBlock(), user->GetPrev(), object_visited);
669 }
670 CheckSaveStateOsrRec(inst, user, user->GetBasicBlock(), osr_visited);
671 }
672 GetGraph()->EraseMarker(object_visited);
673 GetGraph()->EraseMarker(osr_visited);
674 users.clear();
675 }
676 }
677 #endif
678 }
679
FindObjectInSaveState(const Inst * object,Inst * ss) const680 void GraphChecker::FindObjectInSaveState(const Inst *object, Inst *ss) const
681 {
682 if (object != nullptr && ss != nullptr) {
683 std::cerr << "Object not found in the SaveState: " << std::endl
684 << *object << std::endl
685 << " " << *ss << std::endl;
686 }
687 UNREACHABLE();
688 }
689
CheckObjectRec(const Inst * object,const Inst * user,const BasicBlock * block,Inst * start_from,Marker visited) const690 void GraphChecker::CheckObjectRec(const Inst *object, const Inst *user, const BasicBlock *block, Inst *start_from,
691 Marker visited) const
692 {
693 if (start_from != nullptr) {
694 auto it = InstSafeIterator<IterationType::ALL, IterationDirection::BACKWARD>(*block, start_from);
695 for (; it != block->AllInstsSafeReverse().end(); ++it) {
696 auto inst = *it;
697 if (inst == nullptr) {
698 break;
699 }
700 if (inst->SetMarker(visited)) {
701 return;
702 }
703 if (inst == object || inst == user) {
704 return;
705 }
706 }
707 }
708 for (auto pred : block->GetPredsBlocks()) {
709 // Catch-begin block has edge from try-end block, and all try-blocks should be visited from this edge.
710 // `object` can be placed inside try-block - after try-begin, so that visiting try-begin is wrong
711 if (block->IsCatchBegin() && pred->IsTryBegin()) {
712 continue;
713 }
714 CheckObjectRec(object, user, pred, pred->GetLastInst(), visited);
715 }
716 }
717
CheckSaveStateOsrRec(const Inst * inst,const Inst * user,BasicBlock * block,Marker visited)718 void GraphChecker::CheckSaveStateOsrRec(const Inst *inst, const Inst *user, BasicBlock *block, Marker visited)
719 {
720 if (block->SetMarker(visited)) {
721 return;
722 }
723 if (inst->GetBasicBlock() == block) {
724 return;
725 }
726 if (block->IsOsrEntry()) {
727 ASSERT(GetGraph()->IsOsrMode());
728 auto ss = block->GetFirstInst();
729 ASSERT(ss != nullptr);
730 [[maybe_unused]] auto it =
731 std::find_if(ss->GetInputs().begin(), ss->GetInputs().end(),
732 [inst, ss](Input input) { return ss->GetDataFlowInput(input.GetInst()) == inst; });
733 ASSERT(it != ss->GetInputs().end());
734 }
735 for (auto pred : block->GetPredsBlocks()) {
736 CheckSaveStateOsrRec(inst, user, pred, visited);
737 }
738 }
739
740 /*
741 * Visitors to check instructions types
742 */
VisitReturn(GraphVisitor * v,Inst * inst)743 void GraphChecker::VisitReturn([[maybe_unused]] GraphVisitor *v, Inst *inst)
744 {
745 [[maybe_unused]] auto op = inst->GetInputs()[0].GetInst();
746 ASSERT_DO(CheckCommonTypes(inst, op), (std::cerr << "Types of return and its input are not compatible\n return:\n",
747 inst->Dump(&std::cerr), std::cerr << "\n input:\n", op->Dump(&std::cerr)));
748 [[maybe_unused]] auto num_succs = inst->GetBasicBlock()->GetSuccsBlocks().size();
749 ASSERT_PRINT(num_succs == 1, "Basic block with Return must have 1 successor");
750 [[maybe_unused]] auto succ = inst->GetBasicBlock()->GetSuccsBlocks()[0];
751 ASSERT_DO(succ->IsEndBlock() || succ->IsTryEnd(),
752 std::cerr << "Basic block with Return must have end or try end block as successor:\n"
753 << *inst << std::endl);
754 }
755
VisitIf(GraphVisitor * v,Inst * inst)756 void GraphChecker::VisitIf([[maybe_unused]] GraphVisitor *v, Inst *inst)
757 {
758 [[maybe_unused]] auto num_succs = inst->GetBasicBlock()->GetSuccsBlocks().size();
759 ASSERT_PRINT(num_succs == MAX_SUCCS_NUM, "Basic block with If must have 2 successesors");
760
761 [[maybe_unused]] auto op1 = inst->GetInputs()[0].GetInst();
762 [[maybe_unused]] auto op2 = inst->GetInputs()[1].GetInst();
763 for (size_t i = 0; i < inst->GetInputsCount(); i++) {
764 ASSERT_DO(inst->GetInputType(i) != DataType::NO_TYPE,
765 std::cerr << "Source operand type is not set: " << *inst << std::endl);
766 }
767 ASSERT_DO(inst->GetInputType(0) == inst->GetInputType(1),
768 std::cerr << "If has different inputs type: " << *inst << std::endl);
769 if (inst->GetInputType(0) == DataType::REFERENCE) {
770 [[maybe_unused]] auto cc = inst->CastToIf()->GetCc();
771 ASSERT_DO(cc == ConditionCode::CC_NE || cc == ConditionCode::CC_EQ,
772 (std::cerr << "Reference comparison in If must be CC_NE or CC_EQ: \n", inst->Dump(&std::cerr)));
773 if (op1->IsConst()) {
774 ASSERT_DO(IsZeroConstant(op1), (std::cerr << "Constant reference input must be integer 0: \n",
775 inst->Dump(&std::cerr), op1->Dump(&std::cerr)));
776 } else {
777 ASSERT_DO(op1->GetType() == DataType::REFERENCE, (std::cerr << "If 1st operand type is not a reference\n",
778 inst->Dump(&std::cerr), op1->Dump(&std::cerr)));
779 }
780 if (op2->IsConst()) {
781 ASSERT_DO(IsZeroConstant(op2), (std::cerr << "Constant reference input must be integer 0: \n",
782 inst->Dump(&std::cerr), op2->Dump(&std::cerr)));
783 } else {
784 ASSERT_DO(op2->GetType() == DataType::REFERENCE, (std::cerr << "If 2nd operand type is not a reference\n",
785 inst->Dump(&std::cerr), op2->Dump(&std::cerr)));
786 }
787 }
788 }
789
VisitIfImm(GraphVisitor * v,Inst * inst)790 void GraphChecker::VisitIfImm([[maybe_unused]] GraphVisitor *v, Inst *inst)
791 {
792 [[maybe_unused]] auto num_succs = inst->GetBasicBlock()->GetSuccsBlocks().size();
793 ASSERT_PRINT(num_succs == MAX_SUCCS_NUM, "Basic block with IfImm must have 2 successesors");
794
795 [[maybe_unused]] auto op1 = inst->GetInput(0).GetInst();
796 [[maybe_unused]] auto op2 = inst->CastToIfImm()->GetImm();
797 ASSERT_DO(inst->GetInputType(0) != DataType::NO_TYPE,
798 std::cerr << "Source operand type is not set: " << *inst << std::endl);
799 if (inst->GetInputType(0) == DataType::REFERENCE) {
800 [[maybe_unused]] auto cc = inst->CastToIfImm()->GetCc();
801 ASSERT_DO(cc == ConditionCode::CC_NE || cc == ConditionCode::CC_EQ,
802 (std::cerr << "Reference comparison in IfImm must have CC_NE or CC_EQ: \n", inst->Dump(&std::cerr)));
803 if (op1->IsConst()) {
804 ASSERT_DO(IsZeroConstant(op1), (std::cerr << "Constant reference input must be integer 0: \n",
805 inst->Dump(&std::cerr), op1->Dump(&std::cerr)));
806 } else {
807 ASSERT_DO(op1->GetType() == DataType::REFERENCE,
808 (std::cerr << "IfImm operand type should be here a reference: \n", inst->Dump(&std::cerr),
809 op1->Dump(&std::cerr)));
810 }
811 ASSERT_DO(op2 == 0,
812 (std::cerr << "Reference can be compared only with 0 immediate: \n", inst->Dump(&std::cerr)));
813 } else {
814 ASSERT_PRINT(
815 DataType::GetCommonType(op1->GetType()) == DataType::INT64 ||
816 (static_cast<GraphChecker *>(v)->GetGraph()->IsDynamicMethod() && op1->GetType() == DataType::ANY),
817 "IfImm operand type should be here an integer");
818 }
819 }
820 } // namespace panda::compiler
821