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