1 | // Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file |
2 | // for details. All rights reserved. Use of this source code is governed by a |
3 | // BSD-style license that can be found in the LICENSE file. |
4 | |
5 | #if defined(DEBUG) |
6 | |
7 | #include "vm/compiler/backend/flow_graph_checker.h" |
8 | |
9 | #include "vm/compiler/backend/flow_graph.h" |
10 | #include "vm/compiler/backend/il.h" |
11 | #include "vm/compiler/backend/loops.h" |
12 | |
13 | namespace dart { |
14 | |
15 | DECLARE_FLAG(bool, trace_compiler); |
16 | |
17 | DEFINE_FLAG(int, |
18 | verify_definitions_threshold, |
19 | 250, |
20 | "Definition count threshold for extensive instruction checks" ); |
21 | |
22 | // Returns true for the "optimized out" and "null" constant. |
23 | // Such constants reside outside the IR in the sense that |
24 | // succ/pred/block links are not maintained. |
25 | static bool IsSpecialConstant(Definition* def) { |
26 | if (auto c = def->AsConstant()) { |
27 | return c->value().raw() == Symbols::OptimizedOut().raw() || |
28 | c->value().raw() == Object::ZoneHandle().raw(); |
29 | } |
30 | return false; |
31 | } |
32 | |
33 | // Returns true if block is a predecessor of succ. |
34 | static bool IsPred(BlockEntryInstr* block, BlockEntryInstr* succ) { |
35 | for (intptr_t i = 0, n = succ->PredecessorCount(); i < n; ++i) { |
36 | if (succ->PredecessorAt(i) == block) { |
37 | return true; |
38 | } |
39 | } |
40 | return false; |
41 | } |
42 | |
43 | // Returns true if block is a successor of pred. |
44 | static bool IsSucc(BlockEntryInstr* block, BlockEntryInstr* pred) { |
45 | Instruction* last = pred->last_instruction(); |
46 | for (intptr_t i = 0, n = last->SuccessorCount(); i < n; ++i) { |
47 | if (last->SuccessorAt(i) == block) { |
48 | return true; |
49 | } |
50 | } |
51 | return false; |
52 | } |
53 | |
54 | // Returns true if dom directly dominates block. |
55 | static bool IsDirectlyDominated(BlockEntryInstr* block, BlockEntryInstr* dom) { |
56 | for (intptr_t i = 0, n = dom->dominated_blocks().length(); i < n; ++i) { |
57 | if (dom->dominated_blocks()[i] == block) { |
58 | return true; |
59 | } |
60 | } |
61 | return false; |
62 | } |
63 | |
64 | // Returns true if instruction appears in use list. |
65 | static bool IsInUseList(Value* use, Instruction* instruction) { |
66 | for (; use != nullptr; use = use->next_use()) { |
67 | if (use->instruction() == instruction) { |
68 | return true; |
69 | } |
70 | } |
71 | return false; |
72 | } |
73 | |
74 | // Returns true if definition dominates instruction. Note that this |
75 | // helper is required to account for some situations that are not |
76 | // accounted for in the IR methods that compute dominance. |
77 | static bool DefDominatesUse(Definition* def, Instruction* instruction) { |
78 | if (instruction->IsPhi()) { |
79 | // A phi use is not necessarily dominated by a definition. |
80 | // Proper dominance relation on the input values of Phis is |
81 | // checked by the Phi visitor below. |
82 | return true; |
83 | } else if (def->IsMaterializeObject() || instruction->IsMaterializeObject()) { |
84 | // These instructions reside outside the IR. |
85 | return true; |
86 | } else if (auto entry = |
87 | instruction->GetBlock()->AsBlockEntryWithInitialDefs()) { |
88 | // An initial definition in the same block. |
89 | // TODO(ajcbik): use an initial def too? |
90 | for (auto idef : *entry->initial_definitions()) { |
91 | if (idef == def) { |
92 | return true; |
93 | } |
94 | } |
95 | } |
96 | // Use the standard IR method for dominance. |
97 | return instruction->IsDominatedBy(def); |
98 | } |
99 | |
100 | // Returns true if instruction forces control flow. |
101 | static bool IsControlFlow(Instruction* instruction) { |
102 | return instruction->IsBranch() || instruction->IsGoto() || |
103 | instruction->IsIndirectGoto() || instruction->IsReturn() || |
104 | instruction->IsThrow() || instruction->IsReThrow() || |
105 | instruction->IsTailCall(); |
106 | } |
107 | |
108 | // Asserts that arguments appear in environment at the right place. |
109 | static void AssertArgumentsInEnv(FlowGraph* flow_graph, Definition* call) { |
110 | Environment* env = call->env(); |
111 | if (env == nullptr) { |
112 | // Environments can be removed by EliminateEnvironments pass and |
113 | // are not present before SSA. |
114 | } else if (flow_graph->function().IsIrregexpFunction()) { |
115 | // TODO(dartbug.com/38577): cleanup regexp pipeline too.... |
116 | } else { |
117 | // Otherwise, the trailing environment entries must |
118 | // correspond directly with the arguments. |
119 | const intptr_t env_count = env->Length(); |
120 | const intptr_t arg_count = call->ArgumentCount(); |
121 | ASSERT(arg_count <= env_count); |
122 | const intptr_t env_base = env_count - arg_count; |
123 | for (intptr_t i = 0; i < arg_count; i++) { |
124 | if (call->HasPushArguments()) { |
125 | ASSERT(call->ArgumentAt(i) == env->ValueAt(env_base + i) |
126 | ->definition() |
127 | ->AsPushArgument() |
128 | ->value() |
129 | ->definition()); |
130 | } else { |
131 | // Redefintion instructions and boxing/unboxing are inserted |
132 | // without updating environment uses (FlowGraph::RenameDominatedUses, |
133 | // FlowGraph::InsertConversionsFor). |
134 | // Also, constants may belong to different blocks (e.g. function entry |
135 | // and graph entry). |
136 | Definition* arg_def = |
137 | call->ArgumentAt(i)->OriginalDefinitionIgnoreBoxingAndConstraints(); |
138 | Definition* env_def = |
139 | env->ValueAt(env_base + i) |
140 | ->definition() |
141 | ->OriginalDefinitionIgnoreBoxingAndConstraints(); |
142 | ASSERT((arg_def == env_def) || |
143 | (arg_def->IsConstant() && env_def->IsConstant() && |
144 | arg_def->AsConstant()->value().raw() == |
145 | env_def->AsConstant()->value().raw())); |
146 | } |
147 | } |
148 | } |
149 | } |
150 | |
151 | void FlowGraphChecker::VisitBlocks() { |
152 | const GrowableArray<BlockEntryInstr*>& preorder = flow_graph_->preorder(); |
153 | const GrowableArray<BlockEntryInstr*>& postorder = flow_graph_->postorder(); |
154 | const GrowableArray<BlockEntryInstr*>& rev_postorder = |
155 | flow_graph_->reverse_postorder(); |
156 | |
157 | // Make sure lengths match. |
158 | const intptr_t block_count = preorder.length(); |
159 | ASSERT(block_count == postorder.length()); |
160 | ASSERT(block_count == rev_postorder.length()); |
161 | |
162 | // Make sure postorder has true reverse. |
163 | for (intptr_t i = 0; i < block_count; ++i) { |
164 | ASSERT(postorder[i] == rev_postorder[block_count - i - 1]); |
165 | } |
166 | |
167 | // Iterate over all basic blocks. |
168 | const intptr_t max_block_id = flow_graph_->max_block_id(); |
169 | for (BlockIterator it = flow_graph_->reverse_postorder_iterator(); !it.Done(); |
170 | it.Advance()) { |
171 | BlockEntryInstr* block = it.Current(); |
172 | ASSERT(block->block_id() <= max_block_id); |
173 | // Make sure ordering is consistent. |
174 | ASSERT(block->preorder_number() <= block_count); |
175 | ASSERT(block->postorder_number() <= block_count); |
176 | ASSERT(preorder[block->preorder_number()] == block); |
177 | ASSERT(postorder[block->postorder_number()] == block); |
178 | // Make sure predecessors and successors agree. |
179 | Instruction* last = block->last_instruction(); |
180 | for (intptr_t i = 0, n = last->SuccessorCount(); i < n; ++i) { |
181 | ASSERT(IsPred(block, last->SuccessorAt(i))); |
182 | } |
183 | for (intptr_t i = 0, n = block->PredecessorCount(); i < n; ++i) { |
184 | ASSERT(IsSucc(block, block->PredecessorAt(i))); |
185 | } |
186 | // Make sure dominance relations agree. |
187 | for (intptr_t i = 0, n = block->dominated_blocks().length(); i < n; ++i) { |
188 | ASSERT(block->dominated_blocks()[i]->dominator() == block); |
189 | } |
190 | if (block->dominator() != nullptr) { |
191 | ASSERT(IsDirectlyDominated(block, block->dominator())); |
192 | } |
193 | // Visit all instructions in this block. |
194 | VisitInstructions(block); |
195 | } |
196 | } |
197 | |
198 | void FlowGraphChecker::VisitInstructions(BlockEntryInstr* block) { |
199 | // To avoid excessive runtimes, skip the instructions check if there |
200 | // are many definitions (as happens in e.g. an initialization block). |
201 | if (flow_graph_->current_ssa_temp_index() > |
202 | FLAG_verify_definitions_threshold) { |
203 | return; |
204 | } |
205 | // Give all visitors quick access. |
206 | current_block_ = block; |
207 | // Visit initial definitions. |
208 | if (auto entry = block->AsBlockEntryWithInitialDefs()) { |
209 | for (auto def : *entry->initial_definitions()) { |
210 | ASSERT(def != nullptr); |
211 | ASSERT(def->IsConstant() || def->IsParameter() || |
212 | def->IsSpecialParameter()); |
213 | // Special constants reside outside the IR. |
214 | if (IsSpecialConstant(def)) continue; |
215 | // Make sure block lookup agrees. |
216 | ASSERT(def->GetBlock() == entry); |
217 | // Initial definitions are partially linked into graph. |
218 | ASSERT(def->next() == nullptr); |
219 | ASSERT(def->previous() == entry); |
220 | // Visit the initial definition as instruction. |
221 | VisitInstruction(def); |
222 | } |
223 | } |
224 | // Visit phis in join. |
225 | if (auto entry = block->AsJoinEntry()) { |
226 | for (PhiIterator it(entry); !it.Done(); it.Advance()) { |
227 | PhiInstr* phi = it.Current(); |
228 | // Make sure block lookup agrees. |
229 | ASSERT(phi->GetBlock() == entry); |
230 | // Phis are never linked into graph. |
231 | ASSERT(phi->next() == nullptr); |
232 | ASSERT(phi->previous() == nullptr); |
233 | // Visit the phi as instruction. |
234 | VisitInstruction(phi); |
235 | } |
236 | } |
237 | // Visit regular instructions. |
238 | Instruction* last = block->last_instruction(); |
239 | ASSERT((last == block) == block->IsGraphEntry()); |
240 | Instruction* prev = block; |
241 | ASSERT(prev->previous() == nullptr); |
242 | for (ForwardInstructionIterator it(block); !it.Done(); it.Advance()) { |
243 | Instruction* instruction = it.Current(); |
244 | // Make sure block lookup agrees (scan in scan). |
245 | ASSERT(instruction->GetBlock() == block); |
246 | // Make sure linked list agrees. |
247 | ASSERT(prev->next() == instruction); |
248 | ASSERT(instruction->previous() == prev); |
249 | prev = instruction; |
250 | // Make sure control flow makes sense. |
251 | ASSERT(IsControlFlow(instruction) == (instruction == last)); |
252 | ASSERT(!instruction->IsPhi()); |
253 | // Visit the instruction. |
254 | VisitInstruction(instruction); |
255 | } |
256 | ASSERT(prev->next() == nullptr); |
257 | ASSERT(prev == last); |
258 | // Make sure loop information, when up-to-date, agrees. |
259 | if (flow_graph_->loop_hierarchy_ != nullptr) { |
260 | for (LoopInfo* loop = block->loop_info(); loop != nullptr; |
261 | loop = loop->outer()) { |
262 | ASSERT(loop->Contains(block)); |
263 | } |
264 | } |
265 | } |
266 | |
267 | void FlowGraphChecker::VisitInstruction(Instruction* instruction) { |
268 | ASSERT(!instruction->IsBlockEntry()); |
269 | |
270 | #if !defined(DART_PRECOMPILER) |
271 | // In JIT mode, any instruction which may throw must have a deopt-id, except |
272 | // tail-call because it replaces the stack frame. |
273 | ASSERT(!instruction->MayThrow() || instruction->IsTailCall() || |
274 | instruction->deopt_id() != DeoptId::kNone); |
275 | #endif // !defined(DART_PRECOMPILER) |
276 | |
277 | // Check all regular inputs. |
278 | for (intptr_t i = 0, n = instruction->InputCount(); i < n; ++i) { |
279 | VisitUseDef(instruction, instruction->InputAt(i), i, /*is_env*/ false); |
280 | } |
281 | // Check all environment inputs (including outer ones). |
282 | intptr_t i = 0; |
283 | for (Environment::DeepIterator it(instruction->env()); !it.Done(); |
284 | it.Advance()) { |
285 | VisitUseDef(instruction, it.CurrentValue(), i++, /*is_env*/ true); |
286 | } |
287 | // Visit specific instructions (definitions and anything with Visit()). |
288 | if (auto def = instruction->AsDefinition()) { |
289 | VisitDefinition(def); |
290 | } |
291 | instruction->Accept(this); |
292 | } |
293 | |
294 | void FlowGraphChecker::VisitDefinition(Definition* def) { |
295 | // Used definitions must have an SSA name, and the SSA name must |
296 | // be less than the current_ssa_temp_index. |
297 | if (def->HasSSATemp()) { |
298 | ASSERT(def->ssa_temp_index() < flow_graph_->current_ssa_temp_index()); |
299 | } else { |
300 | ASSERT(def->input_use_list() == nullptr); |
301 | } |
302 | // Check all regular uses. |
303 | Value* prev = nullptr; |
304 | for (Value* use = def->input_use_list(); use != nullptr; |
305 | use = use->next_use()) { |
306 | VisitDefUse(def, use, prev, /*is_env*/ false); |
307 | prev = use; |
308 | } |
309 | // Check all environment uses. |
310 | prev = nullptr; |
311 | for (Value* use = def->env_use_list(); use != nullptr; |
312 | use = use->next_use()) { |
313 | VisitDefUse(def, use, prev, /*is_env*/ true); |
314 | prev = use; |
315 | } |
316 | } |
317 | |
318 | void FlowGraphChecker::VisitUseDef(Instruction* instruction, |
319 | Value* use, |
320 | intptr_t index, |
321 | bool is_env) { |
322 | ASSERT(use->instruction() == instruction); |
323 | ASSERT(use->use_index() == index); |
324 | // Get definition. |
325 | Definition* def = use->definition(); |
326 | ASSERT(def != nullptr); |
327 | ASSERT(def != instruction || def->IsPhi() || def->IsMaterializeObject()); |
328 | // Make sure each input is properly defined in the graph by something |
329 | // that dominates the input (note that the proper dominance relation |
330 | // on the input values of Phis is checked by the Phi visitor below). |
331 | if (def->IsPhi()) { |
332 | ASSERT(def->GetBlock()->IsJoinEntry()); |
333 | // Phis are never linked into graph. |
334 | ASSERT(def->next() == nullptr); |
335 | ASSERT(def->previous() == nullptr); |
336 | } else if (def->IsConstant() || def->IsParameter() || |
337 | def->IsSpecialParameter()) { |
338 | // Special constants reside outside the IR. |
339 | if (IsSpecialConstant(def)) return; |
340 | // Initial definitions are partially linked into graph, but some |
341 | // constants are fully linked into graph (so no next() assert). |
342 | ASSERT(def->previous() != nullptr); |
343 | } else { |
344 | // Others are fully linked into graph. |
345 | ASSERT(def->next() != nullptr); |
346 | ASSERT(def->previous() != nullptr); |
347 | } |
348 | if (def->HasSSATemp()) { |
349 | ASSERT(DefDominatesUse(def, instruction)); |
350 | ASSERT(IsInUseList(is_env ? def->env_use_list() : def->input_use_list(), |
351 | instruction)); |
352 | } |
353 | } |
354 | |
355 | void FlowGraphChecker::VisitDefUse(Definition* def, |
356 | Value* use, |
357 | Value* prev, |
358 | bool is_env) { |
359 | ASSERT(use->definition() == def); |
360 | ASSERT(use->previous_use() == prev); |
361 | // Get using instruction. |
362 | Instruction* instruction = use->instruction(); |
363 | ASSERT(instruction != nullptr); |
364 | ASSERT(def != instruction || def->IsPhi() || def->IsMaterializeObject()); |
365 | if (is_env) { |
366 | ASSERT(instruction->env()->ValueAtUseIndex(use->use_index()) == use); |
367 | } else { |
368 | ASSERT(instruction->InputAt(use->use_index()) == use); |
369 | } |
370 | // Make sure the reaching type, if any, has an owner consistent with this use. |
371 | if (auto const type = use->reaching_type()) { |
372 | ASSERT(type->owner() == nullptr || type->owner() == def); |
373 | } |
374 | // Make sure each use appears in the graph and is properly dominated |
375 | // by the definition (note that the proper dominance relation on the |
376 | // input values of Phis is checked by the Phi visitor below). |
377 | if (instruction->IsPhi()) { |
378 | ASSERT(instruction->AsPhi()->is_alive()); |
379 | ASSERT(instruction->GetBlock()->IsJoinEntry()); |
380 | // Phis are never linked into graph. |
381 | ASSERT(instruction->next() == nullptr); |
382 | ASSERT(instruction->previous() == nullptr); |
383 | } else if (instruction->IsBlockEntry()) { |
384 | // BlockEntry instructions have environments attached to them but |
385 | // have no reliable way to verify if they are still in the graph. |
386 | ASSERT(is_env); |
387 | ASSERT(instruction->next() != nullptr); |
388 | ASSERT(DefDominatesUse(def, instruction)); |
389 | } else { |
390 | // Others are fully linked into graph. |
391 | ASSERT(IsControlFlow(instruction) || instruction->next() != nullptr); |
392 | ASSERT(instruction->previous() != nullptr); |
393 | ASSERT(!def->HasSSATemp() || DefDominatesUse(def, instruction)); |
394 | } |
395 | } |
396 | |
397 | void FlowGraphChecker::VisitConstant(ConstantInstr* constant) { |
398 | // Range check on smi. |
399 | const Object& value = constant->value(); |
400 | if (value.IsSmi()) { |
401 | const int64_t smi_value = Integer::Cast(value).AsInt64Value(); |
402 | ASSERT(compiler::target::kSmiMin <= smi_value); |
403 | ASSERT(smi_value <= compiler::target::kSmiMax); |
404 | } |
405 | // Any constant involved in SSA should appear in the entry (making it more |
406 | // likely it was inserted by the utility that avoids duplication). |
407 | // |
408 | // TODO(dartbug.com/36894) |
409 | // |
410 | // ASSERT(constant->GetBlock() == flow_graph_->graph_entry()); |
411 | } |
412 | |
413 | void FlowGraphChecker::VisitPhi(PhiInstr* phi) { |
414 | // Make sure the definition of each input value of a Phi dominates |
415 | // the corresponding incoming edge, as defined by order. |
416 | ASSERT(phi->InputCount() == current_block_->PredecessorCount()); |
417 | for (intptr_t i = 0, n = phi->InputCount(); i < n; ++i) { |
418 | Definition* def = phi->InputAt(i)->definition(); |
419 | ASSERT(def->HasSSATemp()); // phis have SSA defs |
420 | BlockEntryInstr* edge = current_block_->PredecessorAt(i); |
421 | ASSERT(DefDominatesUse(def, edge->last_instruction())); |
422 | } |
423 | } |
424 | |
425 | void FlowGraphChecker::VisitGoto(GotoInstr* jmp) { |
426 | ASSERT(jmp->SuccessorCount() == 1); |
427 | } |
428 | |
429 | void FlowGraphChecker::VisitIndirectGoto(IndirectGotoInstr* jmp) { |
430 | ASSERT(jmp->SuccessorCount() >= 1); |
431 | } |
432 | |
433 | void FlowGraphChecker::VisitBranch(BranchInstr* branch) { |
434 | ASSERT(branch->SuccessorCount() == 2); |
435 | } |
436 | |
437 | void FlowGraphChecker::VisitRedefinition(RedefinitionInstr* def) { |
438 | ASSERT(def->value()->definition() != def); |
439 | } |
440 | |
441 | void FlowGraphChecker::VisitClosureCall(ClosureCallInstr* call) { |
442 | AssertArgumentsInEnv(flow_graph_, call); |
443 | } |
444 | |
445 | void FlowGraphChecker::VisitStaticCall(StaticCallInstr* call) { |
446 | AssertArgumentsInEnv(flow_graph_, call); |
447 | } |
448 | |
449 | void FlowGraphChecker::VisitInstanceCall(InstanceCallInstr* call) { |
450 | AssertArgumentsInEnv(flow_graph_, call); |
451 | // Force-optimized functions may not have instance calls inside them because |
452 | // we do not reset ICData for these. |
453 | ASSERT(!flow_graph_->function().ForceOptimize()); |
454 | } |
455 | |
456 | void FlowGraphChecker::VisitPolymorphicInstanceCall( |
457 | PolymorphicInstanceCallInstr* call) { |
458 | AssertArgumentsInEnv(flow_graph_, call); |
459 | // Force-optimized functions may not have instance calls inside them because |
460 | // we do not reset ICData for these. |
461 | ASSERT(!flow_graph_->function().ForceOptimize()); |
462 | } |
463 | |
464 | // Main entry point of graph checker. |
465 | void FlowGraphChecker::Check(const char* pass_name) { |
466 | if (FLAG_trace_compiler) { |
467 | THR_Print("Running checker after %s\n" , pass_name); |
468 | } |
469 | ASSERT(flow_graph_ != nullptr); |
470 | VisitBlocks(); |
471 | } |
472 | |
473 | } // namespace dart |
474 | |
475 | #endif // defined(DEBUG) |
476 | |