Flutter Engine
The Flutter Engine
inliner.cc
Go to the documentation of this file.
1// Copyright (c) 2013, 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
6
22#include "vm/flags.h"
23#include "vm/kernel.h"
24#include "vm/log.h"
25#include "vm/longjump.h"
26#include "vm/object.h"
27#include "vm/object_store.h"
28
29namespace dart {
30
32 deoptimization_counter_inlining_threshold,
33 12,
34 "How many times we allow deoptimization before we stop inlining.");
35DEFINE_FLAG(bool, trace_inlining, false, "Trace inlining");
36DEFINE_FLAG(charp, inlining_filter, nullptr, "Inline only in named function");
37
38// Flags for inlining heuristics.
40 inline_getters_setters_smaller_than,
41 10,
42 "Always inline getters and setters that have fewer instructions");
44 inlining_depth_threshold,
45 6,
46 "Inline function calls up to threshold nesting depth");
48 int,
49 inlining_size_threshold,
50 25,
51 "Always inline functions that have threshold or fewer instructions");
53 inlining_callee_call_sites_threshold,
54 1,
55 "Always inline functions containing threshold or fewer calls.");
57 inlining_callee_size_threshold,
58 160,
59 "Do not inline callees larger than threshold");
61 inlining_small_leaf_size_threshold,
62 50,
63 "Do not inline leaf callees larger than threshold");
65 inlining_caller_size_threshold,
66 50000,
67 "Stop inlining once caller reaches the threshold.");
69 inlining_hotness,
70 10,
71 "Inline only hotter calls, in percents (0 .. 100); "
72 "default 10%: calls above-equal 10% of max-count are inlined.");
74 inlining_recursion_depth_threshold,
75 1,
76 "Inline recursive function calls up to threshold recursion depth.");
78 max_inlined_per_depth,
79 500,
80 "Max. number of inlined calls per depth");
81DEFINE_FLAG(bool, print_inlining_tree, false, "Print inlining tree");
82
83DECLARE_FLAG(int, max_deoptimization_counter_threshold);
84DECLARE_FLAG(bool, print_flow_graph);
85DECLARE_FLAG(bool, print_flow_graph_optimized);
86
87// Quick access to the current zone.
88#define Z (zone())
89
90#define TRACE_INLINING(statement) \
91 do { \
92 if (trace_inlining()) statement; \
93 } while (false)
94
95#define PRINT_INLINING_TREE(comment, caller, target, instance_call) \
96 do { \
97 if (FLAG_print_inlining_tree) { \
98 inlined_info_.Add(InlinedInfo(caller, target, inlining_depth_, \
99 instance_call, comment)); \
100 } \
101 } while (false)
102
103// Test if a call is recursive by looking in the deoptimization environment.
105 Environment* env = call->env();
106 while (env != nullptr) {
107 if (function.ptr() == env->function().ptr()) {
108 return true;
109 }
110 env = env->outer();
111 }
112 return false;
113}
114
115// Pair of an argument name and its value.
120};
121
122// Ensures we only inline callee graphs which are safe. There are certain
123// instructions which cannot be inlined and we ensure here that we don't do
124// that.
126 public:
127 static void Validate(FlowGraph* callee_graph) {
128#ifdef DEBUG
129 for (BlockIterator block_it = callee_graph->reverse_postorder_iterator();
130 !block_it.Done(); block_it.Advance()) {
131 BlockEntryInstr* entry = block_it.Current();
132
133 for (ForwardInstructionIterator it(entry); !it.Done(); it.Advance()) {
134 Instruction* current = it.Current();
135 if (current->IsBranch()) {
136 current = current->AsBranch()->comparison();
137 }
138 // The following instructions are not safe to inline, since they make
139 // assumptions about the frame layout.
140 ASSERT(!current->IsTailCall());
141 ASSERT(!current->IsLoadIndexedUnsafe());
142 ASSERT(!current->IsStoreIndexedUnsafe());
143 }
144 }
145#endif // DEBUG
146 }
147};
148
149// Helper to collect information about a callee graph when considering it for
150// inlining.
152 public:
153 GraphInfoCollector() : call_site_count_(0), instruction_count_(0) {}
154
155 void Collect(const FlowGraph& graph) {
156 call_site_count_ = 0;
157 instruction_count_ = 0;
158 for (BlockIterator block_it = graph.postorder_iterator(); !block_it.Done();
159 block_it.Advance()) {
160 // Skip any blocks from the prologue to make them not count towards the
161 // inlining instruction budget.
162 const intptr_t block_id = block_it.Current()->block_id();
163 if (graph.prologue_info().Contains(block_id)) {
164 continue;
165 }
166
167 for (ForwardInstructionIterator it(block_it.Current()); !it.Done();
168 it.Advance()) {
169 Instruction* current = it.Current();
170 // Don't count instructions that won't generate any code.
171 if (current->IsRedefinition()) {
172 continue;
173 }
174 // UnboxedConstant is often folded into the indexing
175 // instructions (similar to Constant instructions which
176 // belong to initial definitions and not counted here).
177 if (current->IsUnboxedConstant()) {
178 continue;
179 }
180 ++instruction_count_;
181 // Count inputs of certain instructions as if separate MoveArgument
182 // instructions are used for inputs. This is done in order to
183 // preserve inlining behavior and avoid code size growth after
184 // MoveArgument insertion was moved to the end of the
185 // compilation pipeline.
186 if (current->IsAllocateObject()) {
187 instruction_count_ += current->InputCount();
188 } else if (current->ArgumentCount() > 0) {
189 ASSERT(!current->HasMoveArguments());
190 instruction_count_ += current->ArgumentCount();
191 }
192 if (current->IsInstanceCall() || current->IsStaticCall() ||
193 current->IsClosureCall()) {
194 ++call_site_count_;
195 continue;
196 }
197 if (current->IsPolymorphicInstanceCall()) {
199 current->AsPolymorphicInstanceCall();
200 // These checks make sure that the number of call-sites counted does
201 // not change relative to the time when the current set of inlining
202 // parameters was fixed.
203 // TODO(fschneider): Determine new heuristic parameters that avoid
204 // these checks entirely.
205 if (!call->IsSureToCallSingleRecognizedTarget() &&
206 (call->token_kind() != Token::kEQ)) {
207 ++call_site_count_;
208 }
209 }
210 }
211 }
212 }
213
214 intptr_t call_site_count() const { return call_site_count_; }
215 intptr_t instruction_count() const { return instruction_count_; }
216
217 private:
218 intptr_t call_site_count_;
219 intptr_t instruction_count_;
220};
221
222// Structure for collecting inline data needed to print inlining tree.
228 const char* bailout_reason;
229 InlinedInfo(const Function* caller_function,
230 const Function* inlined_function,
231 const intptr_t depth,
232 const Definition* call,
233 const char* reason)
234 : caller(caller_function),
235 inlined(inlined_function),
236 inlined_depth(depth),
238 bailout_reason(reason) {}
239};
240
241// Heuristic that maps the loop nesting depth to a static estimate of number
242// of times code at that depth is executed (code at each higher nesting
243// depth is assumed to execute 10x more often up to depth 3).
244static intptr_t AotCallCountApproximation(intptr_t nesting_depth) {
245 switch (nesting_depth) {
246 case 0:
247 // The value 1 makes most sense, but it may give a high ratio to call
248 // sites outside loops. Therefore, such call sites are subject to
249 // subsequent stricter heuristic to limit code size increase.
250 return 1;
251 case 1:
252 return 10;
253 case 2:
254 return 10 * 10;
255 default:
256 return 10 * 10 * 10;
257 }
258}
259
260// A collection of call sites to consider for inlining.
261class CallSites : public ValueObject {
262 public:
263 template <typename CallType>
264 struct CallInfo {
266 CallType* call;
267 intptr_t call_depth;
269 intptr_t call_count;
270 double ratio = 0.0;
271
273 CallType* call,
274 intptr_t call_depth,
275 intptr_t nesting_depth)
277 call(call),
280 if (CompilerState::Current().is_aot()) {
282 } else {
283 call_count = call->CallCount();
284 }
285 }
286
287 const Function& caller() const { return caller_graph->function(); }
288 };
289
290 explicit CallSites(intptr_t threshold,
292 : inlining_depth_threshold_(threshold),
293 static_calls_(),
294 closure_calls_(),
295 instance_calls_(),
296 calls_(calls) {}
297
299 const {
300 return instance_calls_;
301 }
302
304 return static_calls_;
305 }
306
308 return closure_calls_;
309 }
310
311 bool HasCalls() const {
312 return !(static_calls_.is_empty() && closure_calls_.is_empty() &&
313 instance_calls_.is_empty());
314 }
315
316 intptr_t NumCalls() const {
317 return instance_calls_.length() + static_calls_.length() +
318 closure_calls_.length();
319 }
320
321 void Clear() {
322 static_calls_.Clear();
323 closure_calls_.Clear();
324 instance_calls_.Clear();
325 }
326
327 template <typename CallType>
328 static intptr_t ComputeMaxCallCount(
329 const GrowableArray<CallInfo<CallType>>& calls,
330 intptr_t start_index) {
331 intptr_t max_count = 0;
332 for (intptr_t i = start_index; i < calls.length(); ++i) {
333 const auto count = calls[i].call_count;
334 if (count > max_count) {
335 max_count = count;
336 }
337 }
338 return max_count;
339 }
340
341 template <typename CallType>
343 intptr_t start_index,
344 intptr_t max_count) {
345 for (intptr_t i = start_index; i < calls.length(); ++i) {
346 calls[i].ratio = static_cast<double>(calls[i].call_count) / max_count;
347 }
348 }
349
350 // Computes the ratio for each call site in a method, defined as the
351 // number of times a call site is executed over the maximum number of
352 // times any call site is executed in the method. JIT uses actual call
353 // counts whereas AOT uses a static estimate based on nesting depth.
354 void ComputeCallSiteRatio(intptr_t static_calls_start_ix,
355 intptr_t instance_calls_start_ix,
356 intptr_t calls_start_ix) {
357 intptr_t max_count = 0;
358 max_count = Utils::Maximum(
359 max_count,
360 ComputeMaxCallCount(instance_calls_, instance_calls_start_ix));
361 max_count = Utils::Maximum(
362 max_count, ComputeMaxCallCount(static_calls_, static_calls_start_ix));
363 max_count =
364 Utils::Maximum(max_count, ComputeMaxCallCount(*calls_, calls_start_ix));
365
366 if (max_count == 0) {
367 return;
368 }
369
370 ComputeCallRatio(instance_calls_, instance_calls_start_ix, max_count);
371 ComputeCallRatio(static_calls_, static_calls_start_ix, max_count);
372 ComputeCallRatio(*calls_, calls_start_ix, max_count);
373 }
374
376 FlowGraph* graph,
377 intptr_t depth,
378 GrowableArray<InlinedInfo>* inlined_info) {
379 const Function* caller = &graph->function();
381 for (BlockIterator block_it = graph->postorder_iterator(); !block_it.Done();
382 block_it.Advance()) {
383 for (ForwardInstructionIterator it(block_it.Current()); !it.Done();
384 it.Advance()) {
385 Instruction* current = it.Current();
386 Definition* call = nullptr;
387 if (current->IsPolymorphicInstanceCall()) {
388 PolymorphicInstanceCallInstr* instance_call =
389 current->AsPolymorphicInstanceCall();
390 target = instance_call->targets().FirstTarget().ptr();
391 call = instance_call;
392 } else if (current->IsStaticCall()) {
393 StaticCallInstr* static_call = current->AsStaticCall();
394 target = static_call->function().ptr();
395 call = static_call;
396 } else if (current->IsClosureCall()) {
397 // TODO(srdjan): Add data for closure calls.
398 }
399 if (call != nullptr) {
400 inlined_info->Add(
401 InlinedInfo(caller, &target, depth + 1, call, "Too deep"));
402 }
403 }
404 }
405 }
406
407 template <typename CallType>
409 intptr_t j = 0;
410 for (intptr_t i = 0; i < arr->length(); i++) {
411 if ((*arr)[i].call->previous() != nullptr) {
412 if (i != j) {
413 (*arr)[j] = (*arr)[i];
414 }
415 j++;
416 }
417 }
418 arr->TruncateTo(j);
419 }
420
421 // Attempt to devirtualize collected call-sites by applying Canonicalization
422 // rules.
424 GrowableArray<Definition*> worklist(calls_->length());
425 BitVector processed(graph->zone(), graph->current_ssa_temp_index());
426
427 auto add_to_worklist = [&](Definition* defn) {
428 ASSERT(defn->HasSSATemp());
429 const auto ssa_index = defn->ssa_temp_index();
430 if (ssa_index < processed.length() && !processed.Contains(ssa_index)) {
431 processed.Add(ssa_index);
432 worklist.Add(defn);
433 return true;
434 }
435 return false;
436 };
437
438 auto add_transitive_dependencies_to_worklist = [&](intptr_t from_index) {
439 // Caveat: worklist is growing as we are iterating over it. This loop
440 // goes up to |worklist.length()| and thus is going to visit all newly
441 // added definitions and add their dependencies to the worklist
442 // transitively.
443 for (intptr_t i = from_index; i < worklist.length(); i++) {
444 auto defn = worklist[i];
445 for (auto input : defn->inputs()) {
446 add_to_worklist(input);
447 }
448 // For instructions with arguments we don't expect push arguments to
449 // be inserted yet.
450 ASSERT(defn->ArgumentCount() == 0 || !defn->HasMoveArguments());
451 }
452 };
453
454 // Step 1: add all calls to worklist and then transitively add all
455 // their dependencies (values that flow into inputs). Calls will
456 // form the prefix of the worklist followed by their inputs.
457 for (auto& call_info : *calls_) {
458 // Call might not have an SSA temp assigned because its result is
459 // not used. We still want to add such call to worklist but we
460 // should not try to update the bitvector.
461 if (call_info.call->HasSSATemp()) {
462 add_to_worklist(call_info.call);
463 } else {
464 worklist.Add(call_info.call);
465 }
466 }
467 RELEASE_ASSERT(worklist.length() == calls_->length());
468 add_transitive_dependencies_to_worklist(0);
469
470 // Step 2: canonicalize each definition from the worklist. We process
471 // worklist backwards which means we will usually canonicalize inputs before
472 // we canonicalize the instruction that uses them.
473 // Note: worklist is not topologically sorted, so we might end up
474 // processing some uses before the defs.
475 bool changed = false;
476 intptr_t last_unhandled_call_index = calls_->length() - 1;
477 while (!worklist.is_empty()) {
478 auto defn = worklist.RemoveLast();
479
480 // Once we reach the prefix of the worklist we know that we are processing
481 // calls we are interested in.
482 CallInfo<InstanceCallInstr>* call_info = nullptr;
483 if (worklist.length() == last_unhandled_call_index) {
484 call_info = &(*calls_)[last_unhandled_call_index];
485 RELEASE_ASSERT(call_info->call == defn);
486 last_unhandled_call_index--;
487 }
488
489 // Can't apply canonicalization rule to this definition.
490 if (defn->HasUnmatchedInputRepresentations() &&
491 defn->SpeculativeModeOfInputs() == Instruction::kGuardInputs) {
492 continue;
493 }
494
495 auto replacement = defn->Canonicalize(graph);
496 if (replacement != defn) {
497 changed = true;
498 if (replacement != nullptr) {
499 defn->ReplaceUsesWith(replacement);
500 if (replacement->ssa_temp_index() == -1) {
501 graph->EnsureSSATempIndex(defn, replacement);
502 }
503
504 // Add the replacement with all of its dependencies to the worklist.
505 if (add_to_worklist(replacement)) {
506 add_transitive_dependencies_to_worklist(worklist.length() - 1);
507 }
508
509 // We have devirtualized |InstanceCall| into |StaticCall| check
510 // inlining heuristics and add the |StaticCall| into |static_calls_|
511 // if heuristics suggest inlining.
512 //
513 // Note: currently |InstanceCallInstr::Canonicalize| can only return
514 // a newly constructed |StaticCallInstr|, so the check below is
515 // redundant (it will always succeed). Nevertheless we add it to
516 // catch situations in the future when canonicalization rule is
517 // strengthened.
518 const bool newly_inserted =
519 replacement->ssa_temp_index() >= processed.length();
520 if (call_info != nullptr && replacement->IsStaticCall() &&
521 newly_inserted) {
522 HandleDevirtualization(call_info,
523 replacement->Cast<StaticCallInstr>());
524 }
525 }
526 if (auto phi = defn->AsPhi()) {
527 phi->UnuseAllInputs();
528 phi->block()->RemovePhi(phi);
529 } else {
530 defn->RemoveFromGraph();
531 }
532 }
533 }
534
535 if (changed) {
536 PruneRemovedCallsIn(&instance_calls_);
537 PruneRemovedCallsIn(&static_calls_);
538 PruneRemovedCallsIn(&closure_calls_);
539 PruneRemovedCallsIn(calls_);
540 }
541 }
542
544 intptr_t depth,
545 GrowableArray<InlinedInfo>* inlined_info) {
547 ASSERT(graph != nullptr);
548 if (depth > inlining_depth_threshold_) {
549 if (FLAG_print_inlining_tree) {
550 RecordAllNotInlinedFunction(graph, depth, inlined_info);
551 }
552 return;
553 }
554
555 // At the maximum inlining depth, only profitable methods
556 // are further considered for inlining.
557 const bool inline_only_profitable_methods =
558 (depth >= inlining_depth_threshold_);
559
560 // In AOT, compute loop hierarchy.
561 const bool is_aot = CompilerState::Current().is_aot();
562 if (is_aot) {
563 graph->GetLoopHierarchy();
564 }
565
566 const intptr_t instance_calls_start_ix = instance_calls_.length();
567 const intptr_t static_calls_start_ix = static_calls_.length();
568 const intptr_t calls_start_ix = calls_->length();
569 for (BlockIterator block_it = graph->postorder_iterator(); !block_it.Done();
570 block_it.Advance()) {
571 BlockEntryInstr* entry = block_it.Current();
572 const intptr_t nesting_depth = entry->NestingDepth();
573 for (auto current : entry->instructions()) {
574 if (auto instance_call = current->AsPolymorphicInstanceCall()) {
575 if (!inline_only_profitable_methods ||
576 instance_call->IsSureToCallSingleRecognizedTarget() ||
577 instance_call->HasOnlyDispatcherOrImplicitAccessorTargets()) {
578 // Consider instance call for further inlining. Note that it will
579 // still be subject to all the inlining heuristics.
580 instance_calls_.Add({graph, instance_call, depth, nesting_depth});
581 } else {
582 // No longer consider the instance call because inlining is too
583 // deep and the method is not deemed profitable by other criteria.
584 if (FLAG_print_inlining_tree) {
585 const Function* caller = &graph->function();
586 const Function* target = &instance_call->targets().FirstTarget();
587 inlined_info->Add(InlinedInfo(caller, target, depth + 1,
588 instance_call, "Too deep"));
589 }
590 }
591 } else if (auto call = current->AsInstanceCall()) {
592 calls_->Add({graph, call, depth, nesting_depth});
593 } else if (auto static_call = current->AsStaticCall()) {
594 HandleStaticCall(static_call, inline_only_profitable_methods, graph,
595 depth, nesting_depth, inlined_info);
596 } else if (auto closure_call = current->AsClosureCall()) {
597 if (!inline_only_profitable_methods) {
598 // Consider closure for further inlining. Note that it will
599 // still be subject to all the inlining heuristics.
600 closure_calls_.Add({graph, closure_call, depth, nesting_depth});
601 } else {
602 // No longer consider the closure because inlining is too deep.
603 }
604 }
605 }
606 }
607 ComputeCallSiteRatio(static_calls_start_ix, instance_calls_start_ix,
608 calls_start_ix);
609 }
610
611 private:
612 bool HandleStaticCall(StaticCallInstr* static_call,
613 bool inline_only_profitable_methods,
614 FlowGraph* graph,
615 intptr_t depth,
616 intptr_t nesting_depth,
617 GrowableArray<InlinedInfo>* inlined_info) {
618 const Function& function = static_call->function();
619 if (!inline_only_profitable_methods || function.IsRecognized() ||
620 function.IsDispatcherOrImplicitAccessor() ||
621 function.IsMethodExtractor() ||
622 (function.is_const() && function.IsGenerativeConstructor())) {
623 // Consider static call for further inlining. Note that it will
624 // still be subject to all the inlining heuristics.
625 static_calls_.Add({graph, static_call, depth, nesting_depth});
626 return true;
627 } else if (inlined_info != nullptr) {
628 // No longer consider the static call because inlining is too
629 // deep and the method is not deemed profitable by other criteria.
630 if (FLAG_print_inlining_tree) {
631 const Function* caller = &graph->function();
632 const Function* target = &static_call->function();
633 inlined_info->Add(
634 InlinedInfo(caller, target, depth + 1, static_call, "Too deep"));
635 }
636 }
637 return false;
638 }
639
640 bool HandleDevirtualization(CallInfo<InstanceCallInstr>* call_info,
641 StaticCallInstr* static_call) {
642 // Found devirtualized call and associated information.
643 const bool inline_only_profitable_methods =
644 (call_info->call_depth >= inlining_depth_threshold_);
645 if (HandleStaticCall(static_call, inline_only_profitable_methods,
646 call_info->caller_graph, call_info->call_depth,
647 call_info->nesting_depth,
648 /*inlined_info=*/nullptr)) {
649 static_calls_.Last().ratio = call_info->ratio;
650 return true;
651 }
652 return false;
653 }
654
655 intptr_t inlining_depth_threshold_;
656 GrowableArray<CallInfo<StaticCallInstr>> static_calls_;
657 GrowableArray<CallInfo<ClosureCallInstr>> closure_calls_;
658 GrowableArray<CallInfo<PolymorphicInstanceCallInstr>> instance_calls_;
659 GrowableArray<CallInfo<InstanceCallInstr>>* calls_;
660
661 DISALLOW_COPY_AND_ASSIGN(CallSites);
662};
663
664// Determines if inlining this graph yields a small leaf node, or a sequence of
665// static calls that is no larger than the call site it will replace.
666static bool IsSmallLeafOrReduction(int inlining_depth,
667 intptr_t call_site_instructions,
668 FlowGraph* graph) {
669 intptr_t instruction_count = 0;
670 intptr_t call_count = 0;
671 for (BlockIterator block_it = graph->postorder_iterator(); !block_it.Done();
672 block_it.Advance()) {
673 BlockEntryInstr* entry = block_it.Current();
674 for (ForwardInstructionIterator it(entry); !it.Done(); it.Advance()) {
675 Instruction* current = it.Current();
676 if (current->IsDartReturn()) continue;
677 ASSERT(!current->IsNativeReturn());
678 ++instruction_count;
679 if (current->IsInstanceCall() || current->IsPolymorphicInstanceCall() ||
680 current->IsClosureCall()) {
681 return false;
682 }
683 if (current->IsStaticCall()) {
684 const Function& function = current->AsStaticCall()->function();
685 const intptr_t inl_size = function.optimized_instruction_count();
686 const bool always_inline =
688 // Accept a static call that is always inlined in some way and add the
689 // cached size to the total instruction count. A reasonable guess is
690 // made if the count has not been collected yet (listed methods are
691 // never very large).
692 if (always_inline || function.IsRecognized()) {
693 if (!always_inline) {
694 const intptr_t kAvgListedMethodSize = 20;
695 instruction_count +=
696 (inl_size == 0 ? kAvgListedMethodSize : inl_size);
697 }
698 } else {
699 ++call_count;
700 instruction_count += current->AsStaticCall()->ArgumentCount();
701 instruction_count += 1; // pop the call frame.
702 }
703 continue;
704 }
705 }
706 }
707 if (call_count > 0) {
708 return instruction_count <= call_site_instructions;
709 }
710 return instruction_count <= FLAG_inlining_small_leaf_size_threshold;
711}
712
716 intptr_t first_arg_index, // 1 if type args are passed.
718 const Function& caller)
719 : call(call),
723 callee_graph(nullptr),
724 parameter_stubs(nullptr),
725 exit_collector(nullptr),
726 caller(caller) {}
727
730 const intptr_t first_arg_index;
736};
737
738class CallSiteInliner;
739
741 public:
744 const Function& caller_function);
745
746 bool Inline();
747
748 private:
749 bool CheckInlinedDuplicate(const Function& target);
750 bool CheckNonInlinedDuplicate(const Function& target);
751
752 bool TryInliningPoly(const TargetInfo& target);
753
754 TargetEntryInstr* BuildDecisionGraph();
755
756 IsolateGroup* isolate_group() const;
757 Zone* zone() const;
758 intptr_t AllocateBlockId() const;
759 inline bool trace_inlining() const;
760
761 CallSiteInliner* const owner_;
762 PolymorphicInstanceCallInstr* const call_;
763 const intptr_t num_variants_;
764 const CallTargets& variants_;
765
766 CallTargets inlined_variants_;
767 // The non_inlined_variants_ can be used in a long-lived instruction object,
768 // so they are not embedded into the shorter-lived PolymorphicInliner object.
769 CallTargets* non_inlined_variants_;
770 GrowableArray<BlockEntryInstr*> inlined_entries_;
771 InlineExitCollector* exit_collector_;
772
773 const Function& caller_function_;
774};
775
777 if (auto instance_call = call->AsInstanceCallBase()) {
778 return (instance_call->entry_kind() == Code::EntryKind::kUnchecked) &&
779 instance_call->is_call_on_this();
780 }
781 return false;
782}
783
784// Helper which returns true if callee potentially has a more specific
785// parameter type and thus a redefinition needs to be inserted.
787 BitVector* is_generic_covariant_impl,
788 const FunctionType& interface_target_signature,
789 const FunctionType& callee_signature,
790 intptr_t first_arg_index,
791 intptr_t arg_index) {
792 if (arg_index > first_arg_index && is_generic_covariant_impl != nullptr &&
793 is_generic_covariant_impl->Contains(arg_index - first_arg_index)) {
794 const intptr_t param_index = arg_index - first_arg_index;
795 const intptr_t num_named_params =
796 callee_signature.NumOptionalNamedParameters();
797 const intptr_t num_params = callee_signature.NumParameters();
798 if (num_named_params == 0 &&
799 param_index >= interface_target_signature.NumParameters()) {
800 // An optional positional parameter which was added in the callee but
801 // not present in the interface target.
802 return false;
803 }
804
805 // Check if this argument corresponds to a named parameter. In this case
806 // we need to find correct index based on the name.
807 intptr_t interface_target_param_index = param_index;
808 if (num_named_params > 0 &&
809 (num_params - num_named_params) <= param_index) {
810 // This is a named parameter.
811 const String& name =
812 String::Handle(callee_signature.ParameterNameAt(param_index));
813 interface_target_param_index = -1;
814 for (intptr_t i = interface_target_signature.NumParameters() -
815 interface_target_signature.NumOptionalNamedParameters(),
816 n = interface_target_signature.NumParameters();
817 i < n; i++) {
818 if (interface_target_signature.ParameterNameAt(i) == name.ptr()) {
819 interface_target_param_index = i;
820 break;
821 }
822 }
823
824 // This is a named parameter which was added in the callee.
825 if (interface_target_param_index == -1) {
826 return false;
827 }
828 }
829 const AbstractType& callee_parameter_type =
830 AbstractType::Handle(callee_signature.ParameterTypeAt(param_index));
831 const AbstractType& interface_target_parameter_type =
832 AbstractType::Handle(interface_target_signature.ParameterTypeAt(
833 interface_target_param_index));
834 if (interface_target_parameter_type.ptr() != callee_parameter_type.ptr()) {
835 // This a conservative approximation.
836 return true;
837 }
838 }
839 return false;
840}
841
842static void ReplaceParameterStubs(Zone* zone,
843 FlowGraph* caller_graph,
844 InlinedCallData* call_data,
845 const TargetInfo* target_info) {
846 const bool is_polymorphic = call_data->call->IsPolymorphicInstanceCall();
847 const bool no_checks =
849 ASSERT(is_polymorphic == (target_info != nullptr));
850 FlowGraph* callee_graph = call_data->callee_graph;
851 auto callee_entry = callee_graph->graph_entry()->normal_entry();
852 const Function& callee = callee_graph->function();
853
854 FunctionType& interface_target_signature = FunctionType::Handle();
855 FunctionType& callee_signature = FunctionType::Handle(callee.signature());
856
857 // If we are inlining a call on this and we are going to skip parameter checks
858 // then a situation can arise when parameter type in the callee has a narrower
859 // type than what interface target specifies, e.g.
860 //
861 // class A<T> {
862 // void f(T v);
863 // void g(T v) { f(v); }
864 // }
865 // class B extends A<X> { void f(X v) { ... } }
866 //
867 // Consider when B.f is inlined into a callsite in A.g (e.g. due to
868 // polymorphic inlining). v is known to be X within the body of B.f, but not
869 // guaranteed to be X outside of it. Thus we must ensure that all operations
870 // with v that depend on its type being X are pinned to stay within the
871 // inlined body.
872 //
873 // We achieve that by inserting redefinitions for parameters which potentially
874 // have narrower types in callee compared to those in the interface target of
875 // the call.
876 BitVector* is_generic_covariant_impl = nullptr;
877 if (no_checks && callee.IsRegularFunction()) {
878 const Function& interface_target =
879 call_data->call->AsInstanceCallBase()->interface_target();
880
881 callee_signature = callee.signature();
882 interface_target_signature = interface_target.signature();
883
884 // If signatures match then there is nothing to do.
885 if (interface_target.signature() != callee.signature()) {
886 const intptr_t num_params = callee.NumParameters();
887 BitVector is_covariant(zone, num_params);
888 is_generic_covariant_impl = new (zone) BitVector(zone, num_params);
889
890 kernel::ReadParameterCovariance(callee_graph->function(), &is_covariant,
891 is_generic_covariant_impl);
892 }
893 }
894
895 // Replace each stub with the actual argument or the caller's constant.
896 // Nulls denote optional parameters for which no actual was given.
897 const intptr_t first_arg_index = call_data->first_arg_index;
898
899 // When first_arg_index > 0, the stub and actual argument processed in the
900 // first loop iteration represent a passed-in type argument vector.
901 GrowableArray<Value*>* arguments = call_data->arguments;
902 intptr_t first_arg_stub_index = 0;
903 if (arguments->length() != call_data->parameter_stubs->length()) {
904 ASSERT(arguments->length() == call_data->parameter_stubs->length() - 1);
905 ASSERT(first_arg_index == 0);
906 // The first parameter stub accepts an optional type argument vector, but
907 // none was provided in arguments.
908 first_arg_stub_index = 1;
909 }
910 for (intptr_t i = 0; i < arguments->length(); ++i) {
911 Value* actual = (*arguments)[i];
912 Definition* defn = nullptr;
913
914 // Replace the receiver argument with a redefinition to prevent code from
915 // the inlined body from being hoisted above the inlined entry.
916 const bool is_polymorphic_receiver =
917 (is_polymorphic && (i == first_arg_index));
918
919 if (actual == nullptr) {
920 ASSERT(!is_polymorphic_receiver);
921 continue;
922 }
923
924 if (is_polymorphic_receiver ||
926 is_generic_covariant_impl, interface_target_signature,
927 callee_signature, first_arg_index, i)) {
928 RedefinitionInstr* redefinition =
929 new (zone) RedefinitionInstr(actual->Copy(zone));
930 caller_graph->AllocateSSAIndex(redefinition);
931 if (is_polymorphic_receiver && target_info->IsSingleCid()) {
932 redefinition->UpdateType(CompileType::FromCid(target_info->cid_start));
933 }
934 redefinition->InsertAfter(callee_entry);
935 defn = redefinition;
936 // Since the redefinition does not dominate the callee entry, replace
937 // uses of the receiver argument in this entry with the redefined value.
938 callee_entry->ReplaceInEnvironment(
939 call_data->parameter_stubs->At(first_arg_stub_index + i),
940 actual->definition());
941 } else {
942 defn = actual->definition();
943 }
944
945 call_data->parameter_stubs->At(first_arg_stub_index + i)
946 ->ReplaceUsesWith(defn);
947 }
948
949 // Replace remaining constants with uses by constants in the caller's
950 // initial definitions.
951 auto defns = callee_graph->graph_entry()->initial_definitions();
952 for (intptr_t i = 0; i < defns->length(); ++i) {
953 ConstantInstr* constant = (*defns)[i]->AsConstant();
954 if (constant != nullptr && constant->HasUses()) {
955 constant->ReplaceUsesWith(caller_graph->GetConstant(
956 constant->value(), constant->representation()));
957 }
958 }
959
960 defns = callee_graph->graph_entry()->normal_entry()->initial_definitions();
961 for (intptr_t i = 0; i < defns->length(); ++i) {
962 auto defn = (*defns)[i];
963 if (!defn->HasUses()) continue;
964
965 if (auto constant = defn->AsConstant()) {
966 constant->ReplaceUsesWith(caller_graph->GetConstant(
967 constant->value(), constant->representation()));
968 }
969
970 if (auto param = defn->AsParameter()) {
971 if (param->location().Equals(Location::RegisterLocation(ARGS_DESC_REG))) {
972 param->ReplaceUsesWith(
973 caller_graph->GetConstant(call_data->arguments_descriptor));
974 }
975 }
976 }
977}
978
980 public:
981 explicit CallSiteInliner(FlowGraphInliner* inliner, intptr_t threshold)
982 : inliner_(inliner),
983 caller_graph_(inliner->flow_graph()),
984 inlined_(false),
985 initial_size_(inliner->flow_graph()->InstructionCount()),
986 inlined_size_(0),
987 inlined_recursive_call_(false),
988 inlining_depth_(1),
989 inlining_recursion_depth_(0),
990 inlining_depth_threshold_(threshold),
991 collected_call_sites_(nullptr),
992 inlining_call_sites_(nullptr),
993 function_cache_(),
994 inlined_info_() {}
995
996 FlowGraph* caller_graph() const { return caller_graph_; }
997
998 Thread* thread() const { return caller_graph_->thread(); }
999 Zone* zone() const { return caller_graph_->zone(); }
1000
1001 bool trace_inlining() const { return inliner_->trace_inlining(); }
1002
1003 int inlining_depth() { return inlining_depth_; }
1004
1006 InliningDecision(bool b, const char* r) : value(b), reason(r) {}
1007 bool value;
1008 const char* reason;
1009 static InliningDecision Yes(const char* reason) {
1010 return InliningDecision(true, reason);
1011 }
1012 static InliningDecision No(const char* reason) {
1013 return InliningDecision(false, reason);
1014 }
1015 };
1016
1017 // Inlining heuristics based on Cooper et al. 2008.
1019 intptr_t instr_count,
1020 intptr_t call_site_count) {
1021 // Pragma or size heuristics.
1022 if (inliner_->AlwaysInline(callee)) {
1023 return InliningDecision::Yes("AlwaysInline");
1024 } else if (inlined_size_ > FLAG_inlining_caller_size_threshold) {
1025 // Prevent caller methods becoming humongous and thus slow to compile.
1026 return InliningDecision::No("--inlining-caller-size-threshold");
1027 } else if (instr_count > FLAG_inlining_callee_size_threshold) {
1028 // Prevent inlining of callee methods that exceed certain size.
1029 return InliningDecision::No("--inlining-callee-size-threshold");
1030 }
1031 // Inlining depth.
1032 const int callee_inlining_depth = callee.inlining_depth();
1033 if (callee_inlining_depth > 0 &&
1034 ((callee_inlining_depth + inlining_depth_) >
1035 FLAG_inlining_depth_threshold)) {
1036 return InliningDecision::No("--inlining-depth-threshold");
1037 }
1038 // Situation instr_count == 0 denotes no counts have been computed yet.
1039 // In that case, we say ok to the early heuristic and come back with the
1040 // late heuristic.
1041 if (instr_count == 0) {
1042 return InliningDecision::Yes("need to count first");
1043 } else if (instr_count <= FLAG_inlining_size_threshold) {
1044 return InliningDecision::Yes("--inlining-size-threshold");
1045 } else if (call_site_count <= FLAG_inlining_callee_call_sites_threshold) {
1046 return InliningDecision::Yes("--inlining-callee-call-sites-threshold");
1047 }
1048 return InliningDecision::No("default");
1049 }
1050
1052 // If inlining depth is less than one abort.
1053 if (inlining_depth_threshold_ < 1) return;
1054 if (caller_graph_->function().deoptimization_counter() >=
1055 FLAG_deoptimization_counter_inlining_threshold) {
1056 return;
1057 }
1058 // Create two call site collections to swap between.
1060 CallSites sites1(inlining_depth_threshold_, &calls);
1061 CallSites sites2(inlining_depth_threshold_, &calls);
1062 CallSites* call_sites_temp = nullptr;
1063 collected_call_sites_ = &sites1;
1064 inlining_call_sites_ = &sites2;
1065 // Collect initial call sites.
1066 collected_call_sites_->FindCallSites(caller_graph_, inlining_depth_,
1067 &inlined_info_);
1068 while (collected_call_sites_->HasCalls()) {
1070 THR_Print(" Depth %" Pd " ----------\n", inlining_depth_));
1071 if (FLAG_print_inlining_tree) {
1072 THR_Print("**Depth % " Pd " calls to inline %" Pd " (threshold % " Pd
1073 ")\n",
1074 inlining_depth_, collected_call_sites_->NumCalls(),
1075 static_cast<intptr_t>(FLAG_max_inlined_per_depth));
1076 }
1077 if (collected_call_sites_->NumCalls() > FLAG_max_inlined_per_depth) {
1078 break;
1079 }
1080 // Swap collected and inlining arrays and clear the new collecting array.
1081 call_sites_temp = collected_call_sites_;
1082 collected_call_sites_ = inlining_call_sites_;
1083 inlining_call_sites_ = call_sites_temp;
1084 collected_call_sites_->Clear();
1085 // Inline call sites at the current depth.
1086 bool inlined_instance = InlineInstanceCalls();
1087 bool inlined_statics = InlineStaticCalls();
1088 bool inlined_closures = InlineClosureCalls();
1089 if (inlined_instance || inlined_statics || inlined_closures) {
1090 collected_call_sites_->TryDevirtualize(caller_graph());
1091 // Increment the inlining depths. Checked before subsequent inlining.
1092 ++inlining_depth_;
1093 if (inlined_recursive_call_) {
1094 ++inlining_recursion_depth_;
1095 inlined_recursive_call_ = false;
1096 }
1098 }
1099 }
1100
1101 collected_call_sites_ = nullptr;
1102 inlining_call_sites_ = nullptr;
1103 }
1104
1105 bool inlined() const { return inlined_; }
1106
1107 double GrowthFactor() const {
1108 return static_cast<double>(inlined_size_) /
1109 static_cast<double>(initial_size_);
1110 }
1111
1112 // Helper to create a parameter stub from an actual argument.
1114 Value* argument,
1115 FlowGraph* graph) {
1116 ConstantInstr* constant = argument->definition()->AsConstant();
1117 if (constant != nullptr) {
1118 return graph->GetConstant(constant->value());
1119 }
1120 ParameterInstr* param = new (Z) ParameterInstr(
1121 graph->graph_entry(),
1122 /*env_index=*/i, /*param_index=*/i, Location(), kNoRepresentation);
1123 if (i >= 0) {
1124 // Compute initial parameter type using static and inferred types
1125 // and combine it with an argument type from the caller.
1126 param->UpdateType(
1127 *CompileType::ComputeRefinedType(param->Type(), argument->Type()));
1128 } else {
1129 // Parameter stub for function type arguments.
1130 // It doesn't correspond to a real parameter, so don't try to
1131 // query its static/inferred type.
1132 param->UpdateType(*argument->Type());
1133 }
1134 return param;
1135 }
1136
1138 const Array& argument_names,
1139 InlinedCallData* call_data,
1140 bool stricter_heuristic) {
1141 Timer timer;
1142 if (thread()->compiler_timings() != nullptr) {
1143 timer.Start();
1144 }
1145 const bool success = TryInliningImpl(function, argument_names, call_data,
1146 stricter_heuristic);
1147 if (thread()->compiler_timings() != nullptr) {
1148 timer.Stop();
1150 timer);
1151 }
1152 return success;
1153 }
1154
1156 const Array& argument_names,
1157 InlinedCallData* call_data,
1158 bool stricter_heuristic) {
1159 if (trace_inlining()) {
1160 String& name = String::Handle(function.QualifiedUserVisibleName());
1161 THR_Print(" => %s (deopt count %d)\n", name.ToCString(),
1162 function.deoptimization_counter());
1163 }
1164
1165 // Abort if the inlinable bit on the function is low.
1166 if (!function.CanBeInlined()) {
1168 " Bailout: not inlinable due to !function.CanBeInlined()\n"));
1169 PRINT_INLINING_TREE("Not inlinable", &call_data->caller, &function,
1170 call_data->call);
1171 return false;
1172 }
1173
1175 TRACE_INLINING(THR_Print(" Bailout: vm:never-inline pragma\n"));
1176 PRINT_INLINING_TREE("vm:never-inline", &call_data->caller, &function,
1177 call_data->call);
1178 return false;
1179 }
1180
1181 // Don't inline any intrinsified functions in precompiled mode
1182 // to reduce code size and make sure we use the intrinsic code.
1183 if (CompilerState::Current().is_aot() && function.is_intrinsic() &&
1184 !inliner_->AlwaysInline(function)) {
1185 TRACE_INLINING(THR_Print(" Bailout: intrinsic\n"));
1186 PRINT_INLINING_TREE("intrinsic", &call_data->caller, &function,
1187 call_data->call);
1188 return false;
1189 }
1190
1191 // Do not rely on function type feedback or presence of code to determine
1192 // if a function was compiled.
1193 if (!CompilerState::Current().is_aot() && !function.WasCompiled()) {
1194 TRACE_INLINING(THR_Print(" Bailout: not compiled yet\n"));
1195 PRINT_INLINING_TREE("Not compiled", &call_data->caller, &function,
1196 call_data->call);
1197 return false;
1198 }
1199
1200 // Type feedback may have been cleared for this function (ClearICDataArray),
1201 // but we need it for inlining.
1202 if (!CompilerState::Current().is_aot() && !function.ForceOptimize() &&
1203 function.ic_data_array() == Array::null()) {
1204 TRACE_INLINING(THR_Print(" Bailout: type feedback cleared\n"));
1205 PRINT_INLINING_TREE("No ICData", &call_data->caller, &function,
1206 call_data->call);
1207 return false;
1208 }
1209
1210 // Abort if this function has deoptimized too much.
1211 if (function.deoptimization_counter() >=
1212 FLAG_max_deoptimization_counter_threshold) {
1213 function.set_is_inlinable(false);
1214 TRACE_INLINING(THR_Print(" Bailout: deoptimization threshold\n"));
1215 PRINT_INLINING_TREE("Deoptimization threshold exceeded",
1216 &call_data->caller, &function, call_data->call);
1217 return false;
1218 }
1219
1220 // Apply early heuristics. For a specialized case
1221 // (constants_arg_counts > 0), don't use a previously
1222 // estimate of the call site and instruction counts.
1223 // Note that at this point, optional constant parameters
1224 // are not counted yet, which makes this decision approximate.
1225 GrowableArray<Value*>* arguments = call_data->arguments;
1226 const intptr_t constant_arg_count = CountConstants(*arguments);
1227 const intptr_t instruction_count =
1228 constant_arg_count == 0 ? function.optimized_instruction_count() : 0;
1229 const intptr_t call_site_count =
1230 constant_arg_count == 0 ? function.optimized_call_site_count() : 0;
1231 InliningDecision decision =
1232 ShouldWeInline(function, instruction_count, call_site_count);
1233 if (!decision.value) {
1235 THR_Print(" Bailout: early heuristics (%s) with "
1236 "code size: %" Pd ", "
1237 "call sites: %" Pd ", "
1238 "inlining depth of callee: %d, "
1239 "const args: %" Pd "\n",
1240 decision.reason, instruction_count, call_site_count,
1241 function.inlining_depth(), constant_arg_count));
1242 PRINT_INLINING_TREE("Early heuristic", &call_data->caller, &function,
1243 call_data->call);
1244 return false;
1245 }
1246
1247 if ((function.HasOptionalPositionalParameters() ||
1248 function.HasOptionalNamedParameters()) &&
1249 !function.AreValidArguments(function.NumTypeParameters(),
1250 arguments->length(), argument_names,
1251 nullptr)) {
1252 TRACE_INLINING(THR_Print(" Bailout: optional arg mismatch\n"));
1253 PRINT_INLINING_TREE("Optional arg mismatch", &call_data->caller,
1254 &function, call_data->call);
1255 return false;
1256 }
1257
1258 // Abort if this is a recursive occurrence.
1259 Definition* call = call_data->call;
1260 // Added 'volatile' works around a possible GCC 4.9 compiler bug.
1261 volatile bool is_recursive_call = IsCallRecursive(function, call);
1262 if (is_recursive_call &&
1263 inlining_recursion_depth_ >= FLAG_inlining_recursion_depth_threshold) {
1264 TRACE_INLINING(THR_Print(" Bailout: recursive function\n"));
1265 PRINT_INLINING_TREE("Recursive function", &call_data->caller, &function,
1266 call_data->call);
1267 return false;
1268 }
1269
1271 {
1272 // Save and clear deopt id.
1273 DeoptIdScope deopt_id_scope(thread(), 0);
1274
1275 // Install bailout jump.
1276 LongJumpScope jump;
1277 if (setjmp(*jump.Set()) == 0) {
1278 // Load IC data for the callee.
1279 ZoneGrowableArray<const ICData*>* ic_data_array =
1281 const bool clone_ic_data = Compiler::IsBackgroundCompilation();
1282 ASSERT(CompilerState::Current().is_aot() || function.ForceOptimize() ||
1283 function.ic_data_array() != Array::null());
1284 function.RestoreICDataMap(ic_data_array, clone_ic_data);
1285
1286 // Parse the callee function.
1287 bool in_cache;
1288 ParsedFunction* parsed_function =
1289 GetParsedFunction(function, &in_cache);
1290
1291 // Build the callee graph.
1292 InlineExitCollector* exit_collector =
1293 new (Z) InlineExitCollector(caller_graph_, call);
1294 FlowGraph* callee_graph;
1296 if (StaticCallInstr* instr = call_data->call->AsStaticCall()) {
1297 entry_kind = instr->entry_kind();
1298 } else if (InstanceCallInstr* instr =
1299 call_data->call->AsInstanceCall()) {
1300 entry_kind = instr->entry_kind();
1301 } else if (PolymorphicInstanceCallInstr* instr =
1302 call_data->call->AsPolymorphicInstanceCall()) {
1303 entry_kind = instr->entry_kind();
1304 } else if (call_data->call->IsClosureCall()) {
1305 // Closure functions only have one entry point.
1306 }
1307 // context_level_array=nullptr below means we are not building var desc.
1309 parsed_function, ic_data_array, /*context_level_array=*/nullptr,
1310 exit_collector,
1311 /*optimized=*/true, Compiler::kNoOSRDeoptId,
1312 caller_graph_->max_block_id() + 1,
1313 entry_kind == Code::EntryKind::kUnchecked);
1314 {
1315 COMPILER_TIMINGS_TIMER_SCOPE(thread(), BuildGraph);
1316 callee_graph = builder.BuildGraph();
1317 // Make sure SSA temp indices in the callee graph
1318 // do not intersect with SSA temp indices in the caller.
1319 ASSERT(callee_graph->current_ssa_temp_index() == 0);
1320 callee_graph->set_current_ssa_temp_index(
1321 caller_graph_->current_ssa_temp_index());
1322#if defined(DEBUG)
1323 // The inlining IDs of instructions in the callee graph are unset
1324 // until we call SetInliningID later.
1325 GrowableArray<const Function*> callee_inline_id_to_function;
1326 callee_inline_id_to_function.Add(&function);
1327 FlowGraphChecker(callee_graph, callee_inline_id_to_function)
1328 .Check("Builder (callee)");
1329#endif
1330 CalleeGraphValidator::Validate(callee_graph);
1331 }
1332
1333 {
1334 COMPILER_TIMINGS_TIMER_SCOPE(thread(), PopulateWithICData);
1335
1336#if defined(DART_PRECOMPILER) && !defined(TARGET_ARCH_IA32)
1337 if (CompilerState::Current().is_aot()) {
1338 callee_graph->PopulateWithICData(parsed_function->function());
1339 }
1340#endif
1341
1342 // If we inline a function which is intrinsified without a
1343 // fall-through to IR code, we will not have any ICData attached, so
1344 // we do it manually here.
1345 if (!CompilerState::Current().is_aot() && function.is_intrinsic()) {
1346 callee_graph->PopulateWithICData(parsed_function->function());
1347 }
1348 }
1349
1350 // The parameter stubs are a copy of the actual arguments providing
1351 // concrete information about the values, for example constant values,
1352 // without linking between the caller and callee graphs.
1353 // TODO(zerny): Put more information in the stubs, eg, type information.
1354 const intptr_t first_actual_param_index = call_data->first_arg_index;
1355 const intptr_t inlined_type_args_param = function.IsGeneric() ? 1 : 0;
1356 const intptr_t num_inlined_params =
1357 inlined_type_args_param + function.NumParameters();
1358 ZoneGrowableArray<Definition*>* param_stubs =
1359 new (Z) ZoneGrowableArray<Definition*>(num_inlined_params);
1360
1361 // Create a ConstantInstr as Definition for the type arguments, if any.
1362 if (first_actual_param_index > 0) {
1363 // A type argument vector is explicitly passed.
1364 param_stubs->Add(
1365 CreateParameterStub(-1, (*arguments)[0], callee_graph));
1366 } else if (inlined_type_args_param > 0) {
1367 // No type argument vector is passed to the generic function,
1368 // pass a null vector, which is the same as a vector of dynamic types.
1369 param_stubs->Add(callee_graph->GetConstant(Object::ZoneHandle()));
1370 }
1371 // Create a parameter stub for each fixed positional parameter.
1372 for (intptr_t i = 0; i < function.num_fixed_parameters(); ++i) {
1373 param_stubs->Add(CreateParameterStub(
1374 i, (*arguments)[first_actual_param_index + i], callee_graph));
1375 }
1376
1377 // If the callee has optional parameters, rebuild the argument and stub
1378 // arrays so that actual arguments are in one-to-one with the formal
1379 // parameters.
1380 if (function.HasOptionalParameters()) {
1381 TRACE_INLINING(THR_Print(" adjusting for optional parameters\n"));
1382 if (!AdjustForOptionalParameters(
1383 *parsed_function, first_actual_param_index, argument_names,
1384 arguments, param_stubs, callee_graph)) {
1385 function.set_is_inlinable(false);
1386 TRACE_INLINING(THR_Print(" Bailout: optional arg mismatch\n"));
1387 PRINT_INLINING_TREE("Optional arg mismatch", &call_data->caller,
1388 &function, call_data->call);
1389 return false;
1390 }
1391 }
1392
1393 // After treating optional parameters the actual/formal count must
1394 // match.
1395 ASSERT(arguments->length() ==
1396 first_actual_param_index + function.NumParameters());
1397
1398 // Update try-index of the callee graph.
1399 BlockEntryInstr* call_block = call_data->call->GetBlock();
1400 if (call_block->InsideTryBlock()) {
1401 intptr_t try_index = call_block->try_index();
1402 for (BlockIterator it = callee_graph->reverse_postorder_iterator();
1403 !it.Done(); it.Advance()) {
1404 BlockEntryInstr* block = it.Current();
1405 block->set_try_index(try_index);
1406 }
1407 }
1408
1410
1411 {
1412 // Compute SSA on the callee graph, catching bailouts.
1413 COMPILER_TIMINGS_TIMER_SCOPE(thread(), ComputeSSA);
1414 callee_graph->ComputeSSA(param_stubs);
1415#if defined(DEBUG)
1416 // The inlining IDs of instructions in the callee graph are unset
1417 // until we call SetInliningID later.
1418 GrowableArray<const Function*> callee_inline_id_to_function;
1419 callee_inline_id_to_function.Add(&function);
1420 FlowGraphChecker(callee_graph, callee_inline_id_to_function)
1421 .Check("SSA (callee)");
1422#endif
1423 }
1424
1426 (FLAG_print_flow_graph || FLAG_print_flow_graph_optimized)) {
1427 THR_Print("Callee graph for inlining %s (unoptimized)\n",
1428 function.ToFullyQualifiedCString());
1429 FlowGraphPrinter printer(*callee_graph);
1430 printer.PrintBlocks();
1431 }
1432
1433 {
1434 // TODO(fschneider): Improve suppression of speculative inlining.
1435 // Deopt-ids overlap between caller and callee.
1436 if (CompilerState::Current().is_aot()) {
1437#if defined(DART_PRECOMPILER) && !defined(TARGET_ARCH_IA32)
1438 AotCallSpecializer call_specializer(inliner_->precompiler_,
1439 callee_graph,
1440 inliner_->speculative_policy_);
1441
1442 CompilerPassState state(Thread::Current(), callee_graph,
1443 inliner_->speculative_policy_);
1444 state.call_specializer = &call_specializer;
1446#else
1447 UNREACHABLE();
1448#endif // defined(DART_PRECOMPILER) && !defined(TARGET_ARCH_IA32)
1449 } else {
1450 JitCallSpecializer call_specializer(callee_graph,
1451 inliner_->speculative_policy_);
1452
1453 CompilerPassState state(Thread::Current(), callee_graph,
1454 inliner_->speculative_policy_);
1455 state.call_specializer = &call_specializer;
1457 }
1458 }
1459
1461 (FLAG_print_flow_graph || FLAG_print_flow_graph_optimized)) {
1462 THR_Print("Callee graph for inlining %s (optimized)\n",
1463 function.ToFullyQualifiedCString());
1464 FlowGraphPrinter printer(*callee_graph);
1465 printer.PrintBlocks();
1466 }
1467
1468 // Collect information about the call site and caller graph. At this
1469 // point, optional constant parameters are counted too, making the
1470 // specialized vs. non-specialized decision accurate.
1471 intptr_t constants_count = 0;
1472 for (intptr_t i = 0, n = param_stubs->length(); i < n; ++i) {
1473 if ((*param_stubs)[i]->IsConstant()) ++constants_count;
1474 }
1475 intptr_t instruction_count = 0;
1476 intptr_t call_site_count = 0;
1477 FlowGraphInliner::CollectGraphInfo(callee_graph, constants_count,
1478 /*force*/ false, &instruction_count,
1479 &call_site_count);
1480
1481 // Use heuristics do decide if this call should be inlined.
1482 {
1483 COMPILER_TIMINGS_TIMER_SCOPE(thread(), MakeInliningDecision);
1484 InliningDecision decision =
1485 ShouldWeInline(function, instruction_count, call_site_count);
1486 if (!decision.value) {
1487 // If size is larger than all thresholds, don't consider it again.
1488
1489 // TODO(dartbug.com/49665): Make compiler smart enough so it itself
1490 // can identify highly-specialized functions that should always
1491 // be considered for inlining, without relying on a pragma.
1492 if ((instruction_count > FLAG_inlining_size_threshold) &&
1493 (call_site_count > FLAG_inlining_callee_call_sites_threshold)) {
1494 // Will keep trying to inline the function if it can be
1495 // specialized based on argument types.
1497 function)) {
1498 function.set_is_inlinable(false);
1499 TRACE_INLINING(THR_Print(" Mark not inlinable\n"));
1500 }
1501 }
1503 THR_Print(" Bailout: heuristics (%s) with "
1504 "code size: %" Pd ", "
1505 "call sites: %" Pd ", "
1506 "inlining depth of callee: %d, "
1507 "const args: %" Pd "\n",
1508 decision.reason, instruction_count, call_site_count,
1509 function.inlining_depth(), constants_count));
1510 PRINT_INLINING_TREE("Heuristic fail", &call_data->caller, &function,
1511 call_data->call);
1512 return false;
1513 }
1514
1515 // If requested, a stricter heuristic is applied to this inlining.
1516 // This heuristic always scans the method (rather than possibly
1517 // reusing cached results) to make sure all specializations are
1518 // accounted for.
1519 // TODO(ajcbik): with the now better bookkeeping, explore removing
1520 // this
1521 if (stricter_heuristic) {
1522 intptr_t call_site_instructions = 0;
1523 if (auto static_call = call->AsStaticCall()) {
1524 // Push all the arguments, do the call, drop arguments.
1525 call_site_instructions = static_call->ArgumentCount() + 1 + 1;
1526 }
1527 if (!IsSmallLeafOrReduction(inlining_depth_, call_site_instructions,
1528 callee_graph)) {
1530 THR_Print(" Bailout: heuristics (no small leaf)\n"));
1531 PRINT_INLINING_TREE("Heuristic fail (no small leaf)",
1532 &call_data->caller, &function,
1533 call_data->call);
1534 return false;
1535 }
1536 }
1537 }
1538
1539 // Inline dispatcher methods regardless of the current depth.
1540 {
1541 const intptr_t depth =
1542 function.IsDispatcherOrImplicitAccessor() ? 0 : inlining_depth_;
1543 collected_call_sites_->FindCallSites(callee_graph, depth,
1544 &inlined_info_);
1545 }
1546
1547 // Add the function to the cache.
1548 if (!in_cache) {
1549 function_cache_.Add(parsed_function);
1550 }
1551
1552 // Build succeeded so we restore the bailout jump.
1553 inlined_ = true;
1554 inlined_size_ += instruction_count;
1555 if (is_recursive_call) {
1556 inlined_recursive_call_ = true;
1557 }
1558
1559 call_data->callee_graph = callee_graph;
1560 call_data->parameter_stubs = param_stubs;
1561 call_data->exit_collector = exit_collector;
1562
1563 // When inlined, we add the guarded fields of the callee to the caller's
1564 // list of guarded fields.
1565 const FieldSet* callee_guarded_fields =
1566 callee_graph->parsed_function().guarded_fields();
1567 FieldSet::Iterator it = callee_guarded_fields->GetIterator();
1568 while (const Field** field = it.Next()) {
1570 }
1571
1572 {
1573 COMPILER_TIMINGS_TIMER_SCOPE(thread(), SetInliningId);
1575 callee_graph, inliner_->NextInlineId(callee_graph->function(),
1576 call_data->call->source()));
1577 }
1578 TRACE_INLINING(THR_Print(" Success\n"));
1580 " with reason %s, code size %" Pd ", call sites: %" Pd "\n",
1581 decision.reason, instruction_count, call_site_count));
1582 PRINT_INLINING_TREE(nullptr, &call_data->caller, &function, call);
1583 return true;
1584 } else {
1586
1587 if (error.IsLanguageError() &&
1588 (LanguageError::Cast(error).kind() == Report::kBailout)) {
1589 if (error.ptr() == Object::background_compilation_error().ptr()) {
1590 // Fall through to exit the compilation, and retry it later.
1591 } else {
1593 THR_Print(" Bailout: %s\n", error.ToErrorCString()));
1594 PRINT_INLINING_TREE("Bailout", &call_data->caller, &function, call);
1595 return false;
1596 }
1597 } else {
1598 // Fall through to exit long jump scope.
1599 }
1600 }
1601 }
1602
1603 // Propagate a compile-time error. In precompilation we attempt to
1604 // inline functions that have never been compiled before; when JITing we
1605 // should only see language errors in unoptimized compilation.
1606 // Otherwise, there can be an out-of-memory error (unhandled exception).
1607 // In background compilation we may abort compilation as the state
1608 // changes while compiling. Propagate that 'error' and retry compilation
1609 // later.
1610 ASSERT(CompilerState::Current().is_aot() ||
1611 (error.ptr() == Object::out_of_memory_error().ptr()) ||
1612 Compiler::IsBackgroundCompilation() || error.IsUnhandledException());
1614 UNREACHABLE();
1615 return false;
1616 }
1617
1618 void PrintInlinedInfo(const Function& top) {
1619 if (inlined_info_.length() > 0) {
1620 THR_Print("Inlining into: '%s'\n growth: %f (%" Pd " -> %" Pd ")\n",
1621 top.ToFullyQualifiedCString(), GrowthFactor(), initial_size_,
1622 inlined_size_);
1623 PrintInlinedInfoFor(top, 1);
1624 }
1625 }
1626
1627 private:
1629
1630 static bool Contains(const GrowableArray<intptr_t>& a, intptr_t deopt_id) {
1631 for (intptr_t i = 0; i < a.length(); i++) {
1632 if (a[i] == deopt_id) return true;
1633 }
1634 return false;
1635 }
1636
1637 void PrintInlinedInfoFor(const Function& caller, intptr_t depth) {
1638 // Prevent duplicate printing as inlined_info aggregates all inlining.
1639 GrowableArray<intptr_t> call_instructions_printed;
1640 // Print those that were inlined.
1641 for (intptr_t i = 0; i < inlined_info_.length(); i++) {
1642 const InlinedInfo& info = inlined_info_[i];
1643 if (info.bailout_reason != nullptr) {
1644 continue;
1645 }
1646 if ((info.inlined_depth == depth) &&
1647 (info.caller->ptr() == caller.ptr()) &&
1648 !Contains(call_instructions_printed, info.call_instr->GetDeoptId())) {
1649 for (int t = 0; t < depth; t++) {
1650 THR_Print(" ");
1651 }
1652 THR_Print("%" Pd " %s\n", info.call_instr->GetDeoptId(),
1653 info.inlined->ToQualifiedCString());
1654 PrintInlinedInfoFor(*info.inlined, depth + 1);
1655 call_instructions_printed.Add(info.call_instr->GetDeoptId());
1656 }
1657 }
1658 call_instructions_printed.Clear();
1659 // Print those that were not inlined.
1660 for (intptr_t i = 0; i < inlined_info_.length(); i++) {
1661 const InlinedInfo& info = inlined_info_[i];
1662 if (info.bailout_reason == nullptr) {
1663 continue;
1664 }
1665 if ((info.inlined_depth == depth) &&
1666 (info.caller->ptr() == caller.ptr()) &&
1667 !Contains(call_instructions_printed, info.call_instr->GetDeoptId())) {
1668 for (int t = 0; t < depth; t++) {
1669 THR_Print(" ");
1670 }
1671 THR_Print("NO %" Pd " %s - %s\n", info.call_instr->GetDeoptId(),
1672 info.inlined->ToQualifiedCString(), info.bailout_reason);
1673 call_instructions_printed.Add(info.call_instr->GetDeoptId());
1674 }
1675 }
1676 }
1677
1678 void InlineCall(InlinedCallData* call_data) {
1679 COMPILER_TIMINGS_TIMER_SCOPE(thread(), InlineCall);
1680 FlowGraph* callee_graph = call_data->callee_graph;
1681 auto callee_function_entry = callee_graph->graph_entry()->normal_entry();
1682
1683 // Plug result in the caller graph.
1684 InlineExitCollector* exit_collector = call_data->exit_collector;
1685 exit_collector->PrepareGraphs(callee_graph);
1686 ReplaceParameterStubs(zone(), caller_graph_, call_data, nullptr);
1687
1688 // Inlined force-optimized idempotent functions get deopt-id and
1689 // environment from the call, so when deoptimized, the call is repeated.
1690 if (callee_graph->function().ForceOptimize()) {
1691 // We should only reach here if `Function::CanBeInlined()` returned true,
1692 // which only happens if the force-optimized function is idempotent.
1693 ASSERT(CompilerState::Current().is_aot() ||
1694 callee_graph->function().IsIdempotent());
1695 for (BlockIterator block_it = callee_graph->postorder_iterator();
1696 !block_it.Done(); block_it.Advance()) {
1697 for (ForwardInstructionIterator it(block_it.Current()); !it.Done();
1698 it.Advance()) {
1699 Instruction* current = it.Current();
1700 if (current->env() != nullptr) {
1701 call_data->call->env()->DeepCopyTo(zone(), current);
1702 current->CopyDeoptIdFrom(*call_data->call);
1703 current->env()->MarkAsLazyDeoptToBeforeDeoptId();
1704 }
1705 }
1706 }
1707 }
1708 exit_collector->ReplaceCall(callee_function_entry);
1709
1710 ASSERT(!call_data->call->HasMoveArguments());
1711 }
1712
1713 static intptr_t CountConstants(const GrowableArray<Value*>& arguments) {
1714 intptr_t count = 0;
1715 for (intptr_t i = 0; i < arguments.length(); i++) {
1716 if (arguments[i]->BindsToConstant()) count++;
1717 }
1718 return count;
1719 }
1720
1721 // Parse a function reusing the cache if possible.
1722 ParsedFunction* GetParsedFunction(const Function& function, bool* in_cache) {
1723 // TODO(zerny): Use a hash map for the cache.
1724 for (intptr_t i = 0; i < function_cache_.length(); ++i) {
1725 ParsedFunction* parsed_function = function_cache_[i];
1726 if (parsed_function->function().ptr() == function.ptr()) {
1727 *in_cache = true;
1728 return parsed_function;
1729 }
1730 }
1731 *in_cache = false;
1732 ParsedFunction* parsed_function =
1733 new (Z) ParsedFunction(thread(), function);
1734 return parsed_function;
1735 }
1736
1737 bool InlineStaticCalls() {
1738 bool inlined = false;
1739 const auto& call_info = inlining_call_sites_->static_calls();
1740 TRACE_INLINING(THR_Print(" Static Calls (%" Pd ")\n", call_info.length()));
1741 for (intptr_t call_idx = 0; call_idx < call_info.length(); ++call_idx) {
1742 StaticCallInstr* call = call_info[call_idx].call;
1743 const Function& target = call->function();
1744 if (!inliner_->AlwaysInline(target) &&
1745 (call_info[call_idx].ratio * 100) < FLAG_inlining_hotness) {
1746 if (trace_inlining()) {
1747 String& name = String::Handle(target.QualifiedUserVisibleName());
1748 THR_Print(" => %s (deopt count %d)\n Bailout: cold %f\n",
1749 name.ToCString(), target.deoptimization_counter(),
1750 call_info[call_idx].ratio);
1751 }
1752 PRINT_INLINING_TREE("Too cold", &call_info[call_idx].caller(),
1753 &call->function(), call);
1754 continue;
1755 }
1756
1757 GrowableArray<Value*> arguments(call->ArgumentCount());
1758 for (int i = 0; i < call->ArgumentCount(); ++i) {
1759 arguments.Add(call->ArgumentValueAt(i));
1760 }
1761 InlinedCallData call_data(
1762 call, Array::ZoneHandle(Z, call->GetArgumentsDescriptor()),
1763 call->FirstArgIndex(), &arguments, call_info[call_idx].caller());
1764
1765 // Under AOT, calls outside loops may pass our regular heuristics due
1766 // to a relatively high ratio. So, unless we are optimizing solely for
1767 // speed, such call sites are subject to subsequent stricter heuristic
1768 // to limit code size increase.
1769 bool stricter_heuristic = CompilerState::Current().is_aot() &&
1770 FLAG_optimization_level <= 2 &&
1771 !inliner_->AlwaysInline(target) &&
1772 call_info[call_idx].nesting_depth == 0;
1773 if (TryInlining(call->function(), call->argument_names(), &call_data,
1774 stricter_heuristic)) {
1775 InlineCall(&call_data);
1776 inlined = true;
1777 }
1778 }
1779 return inlined;
1780 }
1781
1782 bool InlineClosureCalls() {
1783 // Under this flag, tear off testing closure calls appear before the
1784 // StackOverflowInstr, which breaks assertions in our compiler when inlined.
1785 // TODO(sjindel): move testing closure calls after first check
1786 if (FLAG_enable_testing_pragmas) return false; // keep all closures
1787 bool inlined = false;
1788 const auto& call_info = inlining_call_sites_->closure_calls();
1790 THR_Print(" Closure Calls (%" Pd ")\n", call_info.length()));
1791 for (intptr_t call_idx = 0; call_idx < call_info.length(); ++call_idx) {
1792 ClosureCallInstr* call = call_info[call_idx].call;
1793 // Find the closure of the callee.
1794 ASSERT(call->ArgumentCount() > 0);
1795 Function& target = Function::ZoneHandle(call->target_function().ptr());
1796 if (target.IsNull()) {
1797 Definition* receiver =
1798 call->Receiver()->definition()->OriginalDefinition();
1799 if (const auto* alloc = receiver->AsAllocateClosure()) {
1800 target = alloc->known_function().ptr();
1801 } else if (ConstantInstr* constant = receiver->AsConstant()) {
1802 if (constant->value().IsClosure()) {
1803 target = Closure::Cast(constant->value()).function();
1804 }
1805 }
1806 }
1807
1808 if (target.IsNull()) {
1809 TRACE_INLINING(THR_Print(" Bailout: unknown target\n"));
1810 continue;
1811 }
1812
1813 if (call->ArgumentCount() > target.NumParameters() ||
1814 call->ArgumentCount() < target.num_fixed_parameters()) {
1815 TRACE_INLINING(THR_Print(" Bailout: wrong parameter count\n"));
1816 continue;
1817 }
1818
1819 GrowableArray<Value*> arguments(call->ArgumentCount());
1820 for (int i = 0; i < call->ArgumentCount(); ++i) {
1821 arguments.Add(call->ArgumentValueAt(i));
1822 }
1823 const Array& arguments_descriptor =
1824 Array::ZoneHandle(Z, call->GetArgumentsDescriptor());
1825 InlinedCallData call_data(call, arguments_descriptor,
1826 call->FirstArgIndex(), &arguments,
1827 call_info[call_idx].caller());
1828 if (TryInlining(target, call->argument_names(), &call_data, false)) {
1829 InlineCall(&call_data);
1830 inlined = true;
1831 }
1832 }
1833 return inlined;
1834 }
1835
1836 bool InlineInstanceCalls() {
1837 bool inlined = false;
1838 const auto& call_info = inlining_call_sites_->instance_calls();
1839 TRACE_INLINING(THR_Print(" Polymorphic Instance Calls (%" Pd ")\n",
1840 call_info.length()));
1841 for (intptr_t call_idx = 0; call_idx < call_info.length(); ++call_idx) {
1842 PolymorphicInstanceCallInstr* call = call_info[call_idx].call;
1843 // PolymorphicInliner introduces deoptimization paths.
1844 if (!call->complete() && !FLAG_polymorphic_with_deopt) {
1845 TRACE_INLINING(THR_Print(" => %s\n Bailout: call with checks\n",
1846 call->function_name().ToCString()));
1847 continue;
1848 }
1849 const Function& cl = call_info[call_idx].caller();
1850 PolymorphicInliner inliner(this, call, cl);
1851 if (inliner.Inline()) inlined = true;
1852 }
1853 return inlined;
1854 }
1855
1856 bool AdjustForOptionalParameters(const ParsedFunction& parsed_function,
1857 intptr_t first_arg_index,
1858 const Array& argument_names,
1859 GrowableArray<Value*>* arguments,
1860 ZoneGrowableArray<Definition*>* param_stubs,
1861 FlowGraph* callee_graph) {
1862 const Function& function = parsed_function.function();
1863 // The language and this code does not support both optional positional
1864 // and optional named parameters for the same function.
1865 ASSERT(!function.HasOptionalPositionalParameters() ||
1866 !function.HasOptionalNamedParameters());
1867
1868 intptr_t arg_count = arguments->length();
1869 intptr_t param_count = function.NumParameters();
1870 intptr_t fixed_param_count = function.num_fixed_parameters();
1871 intptr_t argument_names_count =
1872 (argument_names.IsNull()) ? 0 : argument_names.Length();
1873 ASSERT(fixed_param_count <= arg_count - first_arg_index);
1874 ASSERT(arg_count - first_arg_index <= param_count);
1875
1876 if (function.HasOptionalPositionalParameters()) {
1877 // Arguments mismatch: Caller supplied unsupported named argument.
1878 ASSERT(argument_names_count == 0);
1879 // Create a stub for each optional positional parameters with an actual.
1880 for (intptr_t i = first_arg_index + fixed_param_count; i < arg_count;
1881 ++i) {
1882 param_stubs->Add(CreateParameterStub(i, (*arguments)[i], callee_graph));
1883 }
1884 ASSERT(function.NumOptionalPositionalParameters() ==
1885 (param_count - fixed_param_count));
1886 // For each optional positional parameter without an actual, add its
1887 // default value.
1888 for (intptr_t i = arg_count - first_arg_index; i < param_count; ++i) {
1889 const Instance& object =
1890 parsed_function.DefaultParameterValueAt(i - fixed_param_count);
1891 ConstantInstr* constant = callee_graph->GetConstant(object);
1892 arguments->Add(nullptr);
1893 param_stubs->Add(constant);
1894 }
1895 return true;
1896 }
1897
1898 ASSERT(function.HasOptionalNamedParameters());
1899
1900 const intptr_t positional_args =
1901 arg_count - first_arg_index - argument_names_count;
1902 // Arguments mismatch: Caller supplied unsupported positional argument.
1903 ASSERT(positional_args == fixed_param_count);
1904
1905 // Fast path when no optional named parameters are given.
1906 if (argument_names_count == 0) {
1907 for (intptr_t i = 0; i < param_count - fixed_param_count; ++i) {
1908 const Instance& object = parsed_function.DefaultParameterValueAt(i);
1909 ConstantInstr* constant = callee_graph->GetConstant(object);
1910 arguments->Add(nullptr);
1911 param_stubs->Add(constant);
1912 }
1913 return true;
1914 }
1915
1916 // Otherwise, build a collection of name/argument pairs.
1917 GrowableArray<NamedArgument> named_args(argument_names_count);
1918 for (intptr_t i = 0; i < argument_names.Length(); ++i) {
1919 String& arg_name = String::Handle(caller_graph_->zone());
1920 arg_name ^= argument_names.At(i);
1921 named_args.Add(NamedArgument(
1922 &arg_name, (*arguments)[first_arg_index + fixed_param_count + i]));
1923 }
1924
1925 // Truncate the arguments array to just type args and fixed parameters.
1926 arguments->TruncateTo(first_arg_index + fixed_param_count);
1927
1928 // For each optional named parameter, add the actual argument or its
1929 // default if no argument is passed.
1930 intptr_t match_count = 0;
1931 for (intptr_t i = fixed_param_count; i < param_count; ++i) {
1932 String& param_name = String::Handle(function.ParameterNameAt(i));
1933 // Search for and add the named argument.
1934 Value* arg = nullptr;
1935 for (intptr_t j = 0; j < named_args.length(); ++j) {
1936 if (param_name.Equals(*named_args[j].name)) {
1937 arg = named_args[j].value;
1938 match_count++;
1939 break;
1940 }
1941 }
1942 arguments->Add(arg);
1943 // Create a stub for the argument or use the parameter's default value.
1944 if (arg != nullptr) {
1945 param_stubs->Add(CreateParameterStub(i, arg, callee_graph));
1946 } else {
1947 const Instance& object =
1948 parsed_function.DefaultParameterValueAt(i - fixed_param_count);
1949 ConstantInstr* constant = callee_graph->GetConstant(object);
1950 param_stubs->Add(constant);
1951 }
1952 }
1953 return argument_names_count == match_count;
1954 }
1955
1956 FlowGraphInliner* inliner_;
1957 FlowGraph* caller_graph_;
1958 bool inlined_;
1959 const intptr_t initial_size_;
1960 intptr_t inlined_size_;
1961 bool inlined_recursive_call_;
1962 intptr_t inlining_depth_;
1963 intptr_t inlining_recursion_depth_;
1964 intptr_t inlining_depth_threshold_;
1965 CallSites* collected_call_sites_;
1966 CallSites* inlining_call_sites_;
1967 GrowableArray<ParsedFunction*> function_cache_;
1968 GrowableArray<InlinedInfo> inlined_info_;
1969
1971};
1972
1975 const Function& caller_function)
1976 : owner_(owner),
1977 call_(call),
1978 num_variants_(call->NumberOfChecks()),
1979 variants_(call->targets_),
1980 inlined_variants_(zone()),
1981 non_inlined_variants_(new(zone()) CallTargets(zone())),
1982 inlined_entries_(num_variants_),
1983 exit_collector_(new(Z) InlineExitCollector(owner->caller_graph(), call)),
1984 caller_function_(caller_function) {}
1985
1986IsolateGroup* PolymorphicInliner::isolate_group() const {
1987 return owner_->caller_graph()->isolate_group();
1988}
1989
1990Zone* PolymorphicInliner::zone() const {
1991 return owner_->caller_graph()->zone();
1992}
1993
1994intptr_t PolymorphicInliner::AllocateBlockId() const {
1995 return owner_->caller_graph()->allocate_block_id();
1996}
1997
1998// Inlined bodies are shared if two different class ids have the same
1999// inlined target. This sharing is represented by using three different
2000// types of entries in the inlined_entries_ array:
2001//
2002// * GraphEntry: the inlined body is not shared.
2003//
2004// * TargetEntry: the inlined body is shared and this is the first variant.
2005//
2006// * JoinEntry: the inlined body is shared and this is a subsequent variant.
2007bool PolymorphicInliner::CheckInlinedDuplicate(const Function& target) {
2008 for (intptr_t i = 0; i < inlined_variants_.length(); ++i) {
2009 if ((target.ptr() == inlined_variants_.TargetAt(i)->target->ptr()) &&
2010 !target.is_polymorphic_target()) {
2011 // The call target is shared with a previous inlined variant. Share
2012 // the graph. This requires a join block at the entry, and edge-split
2013 // form requires a target for each branch.
2014 //
2015 // Represent the sharing by recording a fresh target for the first
2016 // variant and the shared join for all later variants.
2017 if (inlined_entries_[i]->IsGraphEntry()) {
2018 // Convert the old target entry to a new join entry.
2019 auto old_entry = inlined_entries_[i]->AsGraphEntry()->normal_entry();
2020 BlockEntryInstr* old_target = old_entry;
2021
2022 // Unuse all inputs in the old graph entry since it is not part of
2023 // the graph anymore. A new target be created instead.
2024 inlined_entries_[i]->AsGraphEntry()->UnuseAllInputs();
2025
2026 JoinEntryInstr* new_join =
2027 BranchSimplifier::ToJoinEntry(zone(), old_target);
2028 old_target->ReplaceAsPredecessorWith(new_join);
2029 for (intptr_t j = 0; j < old_target->dominated_blocks().length(); ++j) {
2030 BlockEntryInstr* block = old_target->dominated_blocks()[j];
2031 new_join->AddDominatedBlock(block);
2032 }
2033 // Since we are reusing the same inlined body across multiple cids,
2034 // reset the type information on the redefinition of the receiver
2035 // in case it was originally given a concrete type.
2036 ASSERT(new_join->next()->IsRedefinition());
2037 new_join->next()->AsRedefinition()->UpdateType(CompileType::Dynamic());
2038 // Create a new target with the join as unconditional successor.
2039 TargetEntryInstr* new_target = new TargetEntryInstr(
2040 AllocateBlockId(), old_target->try_index(), DeoptId::kNone);
2041 new_target->InheritDeoptTarget(zone(), new_join);
2042 GotoInstr* new_goto = new (Z) GotoInstr(new_join, DeoptId::kNone);
2043 new_goto->InheritDeoptTarget(zone(), new_join);
2044 new_target->LinkTo(new_goto);
2045 new_target->set_last_instruction(new_goto);
2046 new_join->predecessors_.Add(new_target);
2047
2048 // Record the new target for the first variant.
2049 inlined_entries_[i] = new_target;
2050 }
2051 ASSERT(inlined_entries_[i]->IsTargetEntry());
2052 // Record the shared join for this variant.
2053 BlockEntryInstr* join =
2054 inlined_entries_[i]->last_instruction()->SuccessorAt(0);
2055 ASSERT(join->IsJoinEntry());
2056 inlined_entries_.Add(join);
2057 return true;
2058 }
2059 }
2060
2061 return false;
2062}
2063
2064bool PolymorphicInliner::CheckNonInlinedDuplicate(const Function& target) {
2065 for (intptr_t i = 0; i < non_inlined_variants_->length(); ++i) {
2066 if (target.ptr() == non_inlined_variants_->TargetAt(i)->target->ptr()) {
2067 return true;
2068 }
2069 }
2070
2071 return false;
2072}
2073
2074bool PolymorphicInliner::TryInliningPoly(const TargetInfo& target_info) {
2075 GrowableArray<Value*> arguments(call_->ArgumentCount());
2076 for (int i = 0; i < call_->ArgumentCount(); ++i) {
2077 arguments.Add(call_->ArgumentValueAt(i));
2078 }
2079 const Array& arguments_descriptor =
2081 InlinedCallData call_data(call_, arguments_descriptor, call_->FirstArgIndex(),
2082 &arguments, caller_function_);
2083 Function& target = Function::ZoneHandle(zone(), target_info.target->ptr());
2084 if (!owner_->TryInlining(target, call_->argument_names(), &call_data,
2085 false)) {
2086 return false;
2087 }
2088
2089 FlowGraph* callee_graph = call_data.callee_graph;
2090 call_data.exit_collector->PrepareGraphs(callee_graph);
2091 inlined_entries_.Add(callee_graph->graph_entry());
2092 exit_collector_->Union(call_data.exit_collector);
2093
2094 ReplaceParameterStubs(zone(), owner_->caller_graph(), &call_data,
2095 &target_info);
2096 return true;
2097}
2098
2100 for (intptr_t i = second->InputCount() - 1; i >= 0; --i) {
2101 Value* input = second->InputAt(i);
2102 input->definition()->AddInputUse(input);
2103 }
2104 first->LinkTo(second);
2105 return second;
2106}
2107
2108// Build a DAG to dispatch to the inlined function bodies. Load the class
2109// id of the receiver and make explicit comparisons for each inlined body,
2110// in frequency order. If all variants are inlined, the entry to the last
2111// inlined body is guarded by a CheckClassId instruction which can deopt.
2112// If not all variants are inlined, we add a PolymorphicInstanceCall
2113// instruction to handle the non-inlined variants.
2114TargetEntryInstr* PolymorphicInliner::BuildDecisionGraph() {
2115 COMPILER_TIMINGS_TIMER_SCOPE(owner_->thread(), BuildDecisionGraph);
2116 const intptr_t try_idx = call_->GetBlock()->try_index();
2117
2118 // Start with a fresh target entry.
2119 TargetEntryInstr* entry = new (Z) TargetEntryInstr(
2120 AllocateBlockId(), try_idx, CompilerState::Current().GetNextDeoptId());
2121 entry->InheritDeoptTarget(zone(), call_);
2122
2123 // This function uses a cursor (a pointer to the 'current' instruction) to
2124 // build the graph. The next instruction will be inserted after the
2125 // cursor.
2126 BlockEntryInstr* current_block = entry;
2127 Instruction* cursor = entry;
2128
2129 Definition* receiver = call_->Receiver()->definition();
2130 // There are at least two variants including non-inlined ones, so we have
2131 // at least one branch on the class id.
2132 // Redefinition and CheckClassId assume kTagged.
2133 Representation cid_representation =
2134 call_->complete() ? kUnboxedUword : kTagged;
2135 LoadClassIdInstr* load_cid =
2136 new (Z) LoadClassIdInstr(new (Z) Value(receiver), cid_representation);
2137 owner_->caller_graph()->AllocateSSAIndex(load_cid);
2138 cursor = AppendInstruction(cursor, load_cid);
2139 for (intptr_t i = 0; i < inlined_variants_.length(); ++i) {
2140 const CidRange& variant = inlined_variants_[i];
2141 bool is_last_test = (i == inlined_variants_.length() - 1);
2142 // 1. Guard the body with a class id check. We don't need any check if
2143 // it's the last test and global analysis has told us that the call is
2144 // complete.
2145 if (is_last_test && non_inlined_variants_->is_empty()) {
2146 // If it is the last variant use a check class id instruction which can
2147 // deoptimize, followed unconditionally by the body. Omit the check if
2148 // we know that we have covered all possible classes.
2149 if (!call_->complete()) {
2150 RedefinitionInstr* cid_redefinition =
2151 new RedefinitionInstr(new (Z) Value(load_cid));
2152 owner_->caller_graph()->AllocateSSAIndex(cid_redefinition);
2153 cursor = AppendInstruction(cursor, cid_redefinition);
2154 CheckClassIdInstr* check_class_id = new (Z) CheckClassIdInstr(
2155 new (Z) Value(cid_redefinition), variant, call_->deopt_id());
2156 check_class_id->InheritDeoptTarget(zone(), call_);
2157 cursor = AppendInstruction(cursor, check_class_id);
2158 }
2159
2160 // The next instruction is the first instruction of the inlined body.
2161 // Handle the two possible cases (unshared and shared subsequent
2162 // predecessors) separately.
2163 BlockEntryInstr* callee_entry = inlined_entries_[i];
2164 if (callee_entry->IsGraphEntry()) {
2165 // Unshared. Graft the normal entry on after the check class
2166 // instruction.
2167 auto target = callee_entry->AsGraphEntry()->normal_entry();
2168 ASSERT(cursor != nullptr);
2169 cursor->LinkTo(target->next());
2170 target->ReplaceAsPredecessorWith(current_block);
2171 // Unuse all inputs of the graph entry and the normal entry. They are
2172 // not in the graph anymore.
2173 callee_entry->UnuseAllInputs();
2174 target->UnuseAllInputs();
2175 // All blocks that were dominated by the normal entry are now
2176 // dominated by the current block.
2177 for (intptr_t j = 0; j < target->dominated_blocks().length(); ++j) {
2178 BlockEntryInstr* block = target->dominated_blocks()[j];
2179 current_block->AddDominatedBlock(block);
2180 }
2181 } else if (callee_entry->IsJoinEntry()) {
2182 // Shared inlined body and this is a subsequent entry. We have
2183 // already constructed a join and set its dominator. Add a jump to
2184 // the join.
2185 JoinEntryInstr* join = callee_entry->AsJoinEntry();
2186 ASSERT(join->dominator() != nullptr);
2187 GotoInstr* goto_join = new GotoInstr(join, DeoptId::kNone);
2188 goto_join->InheritDeoptTarget(zone(), join);
2189 cursor->LinkTo(goto_join);
2190 current_block->set_last_instruction(goto_join);
2191 } else {
2192 // There is no possibility of a TargetEntry (the first entry to a
2193 // shared inlined body) because this is the last inlined entry.
2194 UNREACHABLE();
2195 }
2196 cursor = nullptr;
2197 } else {
2198 // For all variants except the last, use a branch on the loaded class
2199 // id.
2200 BlockEntryInstr* cid_test_entry_block = current_block;
2201 ComparisonInstr* compare;
2202 if (variant.cid_start == variant.cid_end) {
2203 ConstantInstr* cid_constant = owner_->caller_graph()->GetConstant(
2204 Smi::ZoneHandle(Smi::New(variant.cid_end)), cid_representation);
2205 compare = new EqualityCompareInstr(
2206 call_->source(), Token::kEQ, new Value(load_cid),
2207 new Value(cid_constant),
2208 cid_representation == kTagged ? kSmiCid : kIntegerCid,
2210 } else {
2211 compare = new TestRangeInstr(call_->source(), new Value(load_cid),
2212 variant.cid_start, variant.cid_end,
2213 cid_representation);
2214 }
2215 BranchInstr* branch = new BranchInstr(compare, DeoptId::kNone);
2216
2217 branch->InheritDeoptTarget(zone(), call_);
2218 AppendInstruction(cursor, branch);
2219 cursor = nullptr;
2220 current_block->set_last_instruction(branch);
2221
2222 // 2. Handle a match by linking to the inlined body. There are three
2223 // cases (unshared, shared first predecessor, and shared subsequent
2224 // predecessors).
2225 BlockEntryInstr* callee_entry = inlined_entries_[i];
2226 TargetEntryInstr* true_target = nullptr;
2227 if (callee_entry->IsGraphEntry()) {
2228 // Unshared.
2229 auto graph_entry = callee_entry->AsGraphEntry();
2230 auto function_entry = graph_entry->normal_entry();
2231
2232 true_target = BranchSimplifier::ToTargetEntry(zone(), function_entry);
2233 function_entry->ReplaceAsPredecessorWith(true_target);
2234 for (intptr_t j = 0; j < function_entry->dominated_blocks().length();
2235 ++j) {
2236 BlockEntryInstr* block = function_entry->dominated_blocks()[j];
2237 true_target->AddDominatedBlock(block);
2238 }
2239
2240 // Unuse all inputs of the graph entry. It is not in the graph anymore.
2241 graph_entry->UnuseAllInputs();
2242 } else if (callee_entry->IsTargetEntry()) {
2243 ASSERT(!callee_entry->IsFunctionEntry());
2244 // Shared inlined body and this is the first entry. We have already
2245 // constructed a join and this target jumps to it.
2246 true_target = callee_entry->AsTargetEntry();
2247 BlockEntryInstr* join = true_target->last_instruction()->SuccessorAt(0);
2248 current_block->AddDominatedBlock(join);
2249 } else {
2250 // Shared inlined body and this is a subsequent entry. We have
2251 // already constructed a join. We need a fresh target that jumps to
2252 // the join.
2253 JoinEntryInstr* join = callee_entry->AsJoinEntry();
2254 ASSERT(join != nullptr);
2255 ASSERT(join->dominator() != nullptr);
2256 true_target =
2257 new TargetEntryInstr(AllocateBlockId(), try_idx, DeoptId::kNone);
2258 true_target->InheritDeoptTarget(zone(), join);
2259 GotoInstr* goto_join = new GotoInstr(join, DeoptId::kNone);
2260 goto_join->InheritDeoptTarget(zone(), join);
2261 true_target->LinkTo(goto_join);
2262 true_target->set_last_instruction(goto_join);
2263 }
2264 *branch->true_successor_address() = true_target;
2265 current_block->AddDominatedBlock(true_target);
2266
2267 // 3. Prepare to handle a match failure on the next iteration or the
2268 // fall-through code below for non-inlined variants.
2269
2270 TargetEntryInstr* false_target =
2271 new TargetEntryInstr(AllocateBlockId(), try_idx, DeoptId::kNone);
2272 false_target->InheritDeoptTarget(zone(), call_);
2273 *branch->false_successor_address() = false_target;
2274 cid_test_entry_block->AddDominatedBlock(false_target);
2275
2276 cursor = current_block = false_target;
2277 }
2278 }
2279
2280 ASSERT(!call_->HasMoveArguments());
2281
2282 // Handle any non-inlined variants.
2283 if (!non_inlined_variants_->is_empty()) {
2284 PolymorphicInstanceCallInstr* fallback_call =
2285 PolymorphicInstanceCallInstr::FromCall(Z, call_, *non_inlined_variants_,
2286 call_->complete());
2287 owner_->caller_graph()->AllocateSSAIndex(fallback_call);
2288 fallback_call->InheritDeoptTarget(zone(), call_);
2289 fallback_call->set_total_call_count(call_->CallCount());
2290 DartReturnInstr* fallback_return = new DartReturnInstr(
2291 call_->source(), new Value(fallback_call), DeoptId::kNone);
2292 fallback_return->InheritDeoptTargetAfter(owner_->caller_graph(), call_,
2293 fallback_call);
2294 AppendInstruction(AppendInstruction(cursor, fallback_call),
2295 fallback_return);
2296 exit_collector_->AddExit(fallback_return);
2297 cursor = nullptr;
2298 }
2299 return entry;
2300}
2301
2302static void TracePolyInlining(const CallTargets& targets,
2303 intptr_t idx,
2304 intptr_t total,
2305 const char* message) {
2306 String& name =
2308 int percent = total == 0 ? 0 : (100 * targets.TargetAt(idx)->count) / total;
2309 THR_Print("%s cid %" Pd "-%" Pd ": %" Pd "/%" Pd " %d%% %s\n",
2310 name.ToCString(), targets[idx].cid_start, targets[idx].cid_end,
2311 targets.TargetAt(idx)->count, total, percent, message);
2312}
2313
2314bool PolymorphicInliner::trace_inlining() const {
2315 return owner_->trace_inlining();
2316}
2317
2319 ASSERT(&variants_ == &call_->targets_);
2320
2321 intptr_t total = call_->total_call_count();
2322 for (intptr_t var_idx = 0; var_idx < variants_.length(); ++var_idx) {
2323 TargetInfo* info = variants_.TargetAt(var_idx);
2324 if (variants_.length() > FLAG_max_polymorphic_checks) {
2325 non_inlined_variants_->Add(info);
2326 continue;
2327 }
2328
2329 const Function& target = *variants_.TargetAt(var_idx)->target;
2330 const intptr_t count = variants_.TargetAt(var_idx)->count;
2331
2332 // We we almost inlined all the cases then try a little harder to inline
2333 // the last two, because it's a big win if we inline all of them (compiler
2334 // can see all side effects).
2335 const bool try_harder = (var_idx >= variants_.length() - 2) &&
2336 non_inlined_variants_->length() == 0;
2337
2338 intptr_t size = target.optimized_instruction_count();
2339 bool small = (size != 0 && size < FLAG_inlining_size_threshold);
2340
2341 // If it's less than 3% of the dispatches, we won't even consider
2342 // checking for the class ID and branching to another already-inlined
2343 // version.
2344 if (!try_harder && count < (total >> 5)) {
2346 TracePolyInlining(variants_, var_idx, total, "way too infrequent"));
2347 non_inlined_variants_->Add(info);
2348 continue;
2349 }
2350
2351 // First check if this is the same target as an earlier inlined variant.
2352 if (CheckInlinedDuplicate(target)) {
2353 TRACE_INLINING(TracePolyInlining(variants_, var_idx, total,
2354 "duplicate already inlined"));
2355 inlined_variants_.Add(info);
2356 continue;
2357 }
2358
2359 // If it's less than 12% of the dispatches and it's not already inlined, we
2360 // don't consider inlining. For very small functions we are willing to
2361 // consider inlining for 6% of the cases.
2362 if (!try_harder && count < (total >> (small ? 4 : 3))) {
2364 TracePolyInlining(variants_, var_idx, total, "too infrequent"));
2365 non_inlined_variants_->Add(&variants_[var_idx]);
2366 continue;
2367 }
2368
2369 // Also check if this is the same target as an earlier non-inlined
2370 // variant. If so and since inlining decisions are costly, do not try
2371 // to inline this variant.
2372 if (CheckNonInlinedDuplicate(target)) {
2374 TracePolyInlining(variants_, var_idx, total, "already not inlined"));
2375 non_inlined_variants_->Add(&variants_[var_idx]);
2376 continue;
2377 }
2378
2379 // Make an inlining decision.
2380 if (TryInliningPoly(*info)) {
2381 TRACE_INLINING(TracePolyInlining(variants_, var_idx, total, "inlined"));
2382 inlined_variants_.Add(&variants_[var_idx]);
2383 } else {
2385 TracePolyInlining(variants_, var_idx, total, "not inlined"));
2386 non_inlined_variants_->Add(&variants_[var_idx]);
2387 }
2388 }
2389
2390 // If there are no inlined variants, leave the call in place.
2391 if (inlined_variants_.is_empty()) return false;
2392
2393 // Now build a decision tree (a DAG because of shared inline variants) and
2394 // inline it at the call site.
2395 TargetEntryInstr* entry = BuildDecisionGraph();
2396 exit_collector_->ReplaceCall(entry);
2397 return true;
2398}
2399
2401 FlowGraph* flow_graph,
2402 GrowableArray<const Function*>* inline_id_to_function,
2403 GrowableArray<TokenPosition>* inline_id_to_token_pos,
2404 GrowableArray<intptr_t>* caller_inline_id,
2405 SpeculativeInliningPolicy* speculative_policy,
2406 Precompiler* precompiler)
2407 : flow_graph_(flow_graph),
2408 inline_id_to_function_(inline_id_to_function),
2409 inline_id_to_token_pos_(inline_id_to_token_pos),
2410 caller_inline_id_(caller_inline_id),
2411 trace_inlining_(FLAG_trace_inlining && flow_graph->should_print()),
2412 speculative_policy_(speculative_policy),
2413 precompiler_(precompiler) {}
2414
2416 intptr_t constants_count,
2417 bool force,
2418 intptr_t* instruction_count,
2419 intptr_t* call_site_count) {
2422 // For OSR, don't even bother.
2424 *instruction_count = 0;
2425 *call_site_count = 0;
2426 return;
2427 }
2428 // Specialized case: always recompute, never cache.
2429 if (constants_count > 0) {
2430 ASSERT(!force);
2432 info.Collect(*flow_graph);
2433 *instruction_count = info.instruction_count();
2434 *call_site_count = info.call_site_count();
2435 return;
2436 }
2437 // Non-specialized case: unless forced, only recompute on a cache miss.
2438 ASSERT(constants_count == 0);
2439 if (force || (function.optimized_instruction_count() == 0)) {
2441 info.Collect(*flow_graph);
2442 function.SetOptimizedInstructionCountClamped(info.instruction_count());
2443 function.SetOptimizedCallSiteCountClamped(info.call_site_count());
2444 }
2445 *instruction_count = function.optimized_instruction_count();
2446 *call_site_count = function.optimized_call_site_count();
2447}
2448
2450 intptr_t inlining_id) {
2452 flow_graph->set_inlining_id(inlining_id);
2453 // We only need to set the inlining ID on instructions that may possibly
2454 // have token positions, so no need to set it on blocks or internal
2455 // definitions.
2456 for (BlockIterator block_it = flow_graph->postorder_iterator();
2457 !block_it.Done(); block_it.Advance()) {
2458 for (ForwardInstructionIterator it(block_it.Current()); !it.Done();
2459 it.Advance()) {
2460 Instruction* current = it.Current();
2461 current->set_inlining_id(inlining_id);
2462 }
2463 }
2464}
2465
2466// Use function name to determine if inlineable operator.
2467// Add names as necessary.
2469 return (function.name() == Symbols::IndexToken().ptr()) ||
2470 (function.name() == Symbols::AssignIndexToken().ptr()) ||
2471 (function.name() == Symbols::Plus().ptr()) ||
2472 (function.name() == Symbols::Minus().ptr());
2473}
2474
2476 if (!function.has_pragma()) {
2477 return false;
2478 }
2479 Thread* thread = dart::Thread::Current();
2480 COMPILER_TIMINGS_TIMER_SCOPE(thread, CheckForPragma);
2482 return Library::FindPragma(thread, /*only_core=*/false, function,
2483 Symbols::vm_prefer_inline(),
2484 /*multiple=*/false, &options);
2485}
2486
2488 if (!function.has_pragma()) {
2489 return false;
2490 }
2491 Thread* thread = dart::Thread::Current();
2492 COMPILER_TIMINGS_TIMER_SCOPE(thread, CheckForPragma);
2494 return Library::FindPragma(thread, /*only_core=*/false, function,
2495 Symbols::vm_never_inline(),
2496 /*multiple=*/false, &options);
2497}
2498
2500 const Function& function) {
2501 if (!function.has_pragma()) {
2502 return false;
2503 }
2504 Thread* thread = dart::Thread::Current();
2505 COMPILER_TIMINGS_TIMER_SCOPE(thread, CheckForPragma);
2507 return Library::FindPragma(thread, /*only_core=*/false, function,
2508 Symbols::vm_always_consider_inlining(),
2509 /*multiple=*/false, &options);
2510}
2511
2515 THR_Print("vm:prefer-inline pragma for %s\n", function.ToCString()));
2516 return true;
2517 }
2518
2519 COMPILER_TIMINGS_TIMER_SCOPE(dart::Thread::Current(), MakeInliningDecision);
2520 // We don't want to inline DIFs for recognized methods because we would rather
2521 // replace them with inline FG before inlining introduces any superfluous
2522 // AssertAssignable instructions.
2523 if (function.IsDispatcherOrImplicitAccessor() &&
2524 !(function.kind() == UntaggedFunction::kDynamicInvocationForwarder &&
2525 function.IsRecognized())) {
2526 // Smaller or same size as the call.
2527 return true;
2528 }
2529
2530 if (function.is_const()) {
2531 // Inlined const fields are smaller than a call.
2532 return true;
2533 }
2534
2535 if (function.IsMethodExtractor()) {
2536 // Tear-off closure allocation has about the same size as the call.
2537 return true;
2538 }
2539
2540 if (function.IsGetterFunction() || function.IsSetterFunction() ||
2542 (function.kind() == UntaggedFunction::kConstructor)) {
2543 const intptr_t count = function.optimized_instruction_count();
2544 if ((count != 0) && (count < FLAG_inline_getters_setters_smaller_than)) {
2545 return true;
2546 }
2547 }
2548 return false;
2549}
2550
2552 // Collect some early graph information assuming it is non-specialized
2553 // so that the cached approximation may be used later for an early
2554 // bailout from inlining.
2555 intptr_t instruction_count = 0;
2556 intptr_t call_site_count = 0;
2558 /*constants_count*/ 0,
2559 /*force*/ false, &instruction_count,
2560 &call_site_count);
2561
2562 const Function& top = flow_graph_->function();
2563 if ((FLAG_inlining_filter != nullptr) &&
2564 (strstr(top.ToFullyQualifiedCString(), FLAG_inlining_filter) ==
2565 nullptr)) {
2566 return 0;
2567 }
2568
2569 if (trace_inlining()) {
2571 THR_Print("Inlining calls in %s\n", name.ToCString());
2572 }
2573
2575 (FLAG_print_flow_graph || FLAG_print_flow_graph_optimized)) {
2576 THR_Print("Before Inlining of %s\n",
2577 flow_graph_->function().ToFullyQualifiedCString());
2578 FlowGraphPrinter printer(*flow_graph_);
2579 printer.PrintBlocks();
2580 }
2581
2582 intptr_t inlining_depth_threshold = FLAG_inlining_depth_threshold;
2583
2584 CallSiteInliner inliner(this, inlining_depth_threshold);
2585 inliner.InlineCalls();
2586 if (FLAG_print_inlining_tree) {
2587 inliner.PrintInlinedInfo(top);
2588 }
2589
2590 if (inliner.inlined()) {
2591 flow_graph_->DiscoverBlocks();
2592 if (trace_inlining()) {
2593 THR_Print("Inlining growth factor: %f\n", inliner.GrowthFactor());
2595 (FLAG_print_flow_graph || FLAG_print_flow_graph_optimized)) {
2596 THR_Print("After Inlining of %s\n",
2597 flow_graph_->function().ToFullyQualifiedCString());
2598 FlowGraphPrinter printer(*flow_graph_);
2599 printer.PrintBlocks();
2600 }
2601 }
2602 }
2603 return inliner.inlining_depth();
2604}
2605
2607 const InstructionSource& source) {
2608 const intptr_t id = inline_id_to_function_->length();
2609 // TODO(johnmccutchan): Do not allow IsNoSource once all nodes have proper
2610 // source positions.
2611 ASSERT(source.token_pos.IsReal() || source.token_pos.IsSynthetic() ||
2612 source.token_pos.IsNoSource());
2613 RELEASE_ASSERT(!function.IsNull());
2614 ASSERT(FunctionType::Handle(function.signature()).IsFinalized());
2615 inline_id_to_function_->Add(&function);
2616 inline_id_to_token_pos_->Add(source.token_pos);
2617 caller_inline_id_->Add(source.inlining_id);
2618 // We always have one less token position than functions.
2619 ASSERT(inline_id_to_token_pos_->length() ==
2620 (inline_id_to_function_->length() - 1));
2621 return id;
2622}
2623
2624} // namespace dart
const char * options
static void info(const char *fmt,...) SK_PRINTF_LIKE(1
Definition: DM.cpp:213
int count
Definition: FontMgrTest.cpp:50
#define UNREACHABLE()
Definition: assert.h:248
#define RELEASE_ASSERT(cond)
Definition: assert.h:327
Iterator GetIterator() const
Definition: hash_map.h:87
void Add(const T &value)
intptr_t length() const
void Add(intptr_t i)
Definition: bit_vector.h:63
intptr_t length() const
Definition: bit_vector.h:117
bool Contains(intptr_t i) const
Definition: bit_vector.h:91
InstructionsIterable instructions()
Definition: il.h:1774
intptr_t NestingDepth() const
Definition: il.cc:1838
intptr_t try_index() const
Definition: il.h:1730
bool InsideTryBlock() const
Definition: il.h:1734
void set_try_index(intptr_t index)
Definition: il.h:1731
GrowableArray< Definition * > * initial_definitions()
Definition: il.h:1917
bool Done() const
Definition: flow_graph.h:46
static void AssignEdgeWeights(FlowGraph *flow_graph)
static TargetEntryInstr * ToTargetEntry(Zone *zone, BlockEntryInstr *target)
static JoinEntryInstr * ToJoinEntry(Zone *zone, BlockEntryInstr *target)
InliningDecision ShouldWeInline(const Function &callee, intptr_t instr_count, intptr_t call_site_count)
Definition: inliner.cc:1018
Zone * zone() const
Definition: inliner.cc:999
bool TryInlining(const Function &function, const Array &argument_names, InlinedCallData *call_data, bool stricter_heuristic)
Definition: inliner.cc:1137
friend class PolymorphicInliner
Definition: inliner.cc:1628
FlowGraph * caller_graph() const
Definition: inliner.cc:996
Definition * CreateParameterStub(intptr_t i, Value *argument, FlowGraph *graph)
Definition: inliner.cc:1113
Thread * thread() const
Definition: inliner.cc:998
double GrowthFactor() const
Definition: inliner.cc:1107
bool trace_inlining() const
Definition: inliner.cc:1001
bool TryInliningImpl(const Function &function, const Array &argument_names, InlinedCallData *call_data, bool stricter_heuristic)
Definition: inliner.cc:1155
void PrintInlinedInfo(const Function &top)
Definition: inliner.cc:1618
bool inlined() const
Definition: inliner.cc:1105
CallSiteInliner(FlowGraphInliner *inliner, intptr_t threshold)
Definition: inliner.cc:981
void FindCallSites(FlowGraph *graph, intptr_t depth, GrowableArray< InlinedInfo > *inlined_info)
Definition: inliner.cc:543
static void ComputeCallRatio(GrowableArray< CallInfo< CallType > > &calls, intptr_t start_index, intptr_t max_count)
Definition: inliner.cc:342
void Clear()
Definition: inliner.cc:321
intptr_t NumCalls() const
Definition: inliner.cc:316
static intptr_t ComputeMaxCallCount(const GrowableArray< CallInfo< CallType > > &calls, intptr_t start_index)
Definition: inliner.cc:328
const GrowableArray< CallInfo< ClosureCallInstr > > & closure_calls() const
Definition: inliner.cc:307
bool HasCalls() const
Definition: inliner.cc:311
const GrowableArray< CallInfo< StaticCallInstr > > & static_calls() const
Definition: inliner.cc:303
void ComputeCallSiteRatio(intptr_t static_calls_start_ix, intptr_t instance_calls_start_ix, intptr_t calls_start_ix)
Definition: inliner.cc:354
void TryDevirtualize(FlowGraph *graph)
Definition: inliner.cc:423
static void RecordAllNotInlinedFunction(FlowGraph *graph, intptr_t depth, GrowableArray< InlinedInfo > *inlined_info)
Definition: inliner.cc:375
CallSites(intptr_t threshold, GrowableArray< CallInfo< InstanceCallInstr > > *calls)
Definition: inliner.cc:290
const GrowableArray< CallInfo< PolymorphicInstanceCallInstr > > & instance_calls() const
Definition: inliner.cc:298
static void PruneRemovedCallsIn(GrowableArray< CallInfo< CallType > > *arr)
Definition: inliner.cc:408
TargetInfo * TargetAt(int i) const
Definition: il.h:796
const Function & FirstTarget() const
Definition: il.cc:5513
static void Validate(FlowGraph *callee_graph)
Definition: inliner.cc:127
intptr_t length() const
Definition: il.h:758
void Add(CidRange *target)
Definition: il.h:752
bool is_empty() const
Definition: il.h:762
static CompileType * ComputeRefinedType(CompileType *old_type, CompileType *new_type)
static CompileType FromCid(intptr_t cid)
static CompileType Dynamic()
static void RunInliningPipeline(PipelineMode mode, CompilerPassState *state)
intptr_t GetNextDeoptId()
bool is_aot() const
static CompilerState & Current()
const Function * function() const
void RecordInliningStatsByOutcome(bool success, const Timer &timer)
static bool IsBackgroundCompilation()
Definition: compiler.cc:298
static constexpr intptr_t kNoOSRDeoptId
Definition: compiler.h:73
const Object & value() const
Definition: il.h:4230
bool HasUses() const
Definition: il.h:2569
void ReplaceUsesWith(Definition *other)
Definition: il.cc:1493
void AddInputUse(Value *value)
Definition: il.h:2585
static constexpr intptr_t kNone
Definition: deopt_id.h:27
bool AlwaysInline(const Function &function)
Definition: inliner.cc:2512
FlowGraphInliner(FlowGraph *flow_graph, GrowableArray< const Function * > *inline_id_to_function, GrowableArray< TokenPosition > *inline_id_to_token_pos, GrowableArray< intptr_t > *caller_inline_id, SpeculativeInliningPolicy *speculative_policy, Precompiler *precompiler)
Definition: inliner.cc:2400
intptr_t NextInlineId(const Function &function, const InstructionSource &source)
Definition: inliner.cc:2606
bool trace_inlining() const
Definition: inliner.h:132
static bool FunctionHasAlwaysConsiderInliningPragma(const Function &function)
Definition: inliner.cc:2499
static void SetInliningId(FlowGraph *flow_graph, intptr_t inlining_id)
Definition: inliner.cc:2449
static bool FunctionHasNeverInlinePragma(const Function &function)
Definition: inliner.cc:2487
static void CollectGraphInfo(FlowGraph *flow_graph, intptr_t num_constant_args, bool force, intptr_t *instruction_count, intptr_t *call_site_count)
Definition: inliner.cc:2415
FlowGraph * flow_graph() const
Definition: inliner.h:128
static bool FunctionHasPreferInlinePragma(const Function &function)
Definition: inliner.cc:2475
GraphEntryInstr * graph_entry() const
Definition: flow_graph.h:268
ConstantInstr * GetConstant(const Object &object, Representation representation=kTagged)
Definition: flow_graph.cc:187
IsolateGroup * isolate_group() const
Definition: flow_graph.h:262
PrologueInfo prologue_info() const
Definition: flow_graph.h:427
void EnsureSSATempIndex(Definition *defn, Definition *replacement)
Definition: flow_graph.cc:90
bool IsCompiledForOsr() const
Definition: flow_graph.h:460
Zone * zone() const
Definition: flow_graph.h:261
intptr_t current_ssa_temp_index() const
Definition: flow_graph.h:243
intptr_t inlining_id() const
Definition: flow_graph.h:464
Thread * thread() const
Definition: flow_graph.h:260
void set_current_ssa_temp_index(intptr_t index)
Definition: flow_graph.h:244
BlockIterator postorder_iterator() const
Definition: flow_graph.h:222
intptr_t max_block_id() const
Definition: flow_graph.h:264
void DiscoverBlocks()
Definition: flow_graph.cc:346
void set_inlining_id(intptr_t value)
Definition: flow_graph.h:465
const LoopHierarchy & GetLoopHierarchy()
Definition: flow_graph.h:430
const Function & function() const
Definition: flow_graph.h:130
const ParsedFunction & parsed_function() const
Definition: flow_graph.h:129
void PopulateWithICData(const Function &function)
Definition: flow_graph.cc:2528
void AllocateSSAIndex(Definition *def)
Definition: flow_graph.h:274
BlockIterator reverse_postorder_iterator() const
Definition: flow_graph.h:219
void ComputeSSA(ZoneGrowableArray< Definition * > *inlining_parameters)
Definition: flow_graph.cc:926
intptr_t allocate_block_id()
Definition: flow_graph.h:266
intptr_t NumOptionalNamedParameters() const
Definition: object.h:9641
AbstractTypePtr ParameterTypeAt(intptr_t index) const
Definition: object.cc:8585
StringPtr ParameterNameAt(intptr_t index) const
Definition: object.cc:8645
intptr_t NumParameters() const
Definition: object.h:9648
bool IsRegularFunction() const
Definition: object.h:3280
bool IsIdempotent() const
Definition: object.cc:9042
const char * ToFullyQualifiedCString() const
Definition: object.cc:9762
StringPtr QualifiedUserVisibleName() const
Definition: object.cc:11023
intptr_t NumParameters() const
Definition: object.cc:8877
FunctionEntryInstr * normal_entry() const
Definition: il.h:2001
void Collect(const FlowGraph &graph)
Definition: inliner.cc:155
intptr_t instruction_count() const
Definition: inliner.cc:215
intptr_t call_site_count() const
Definition: inliner.cc:214
void Union(const InlineExitCollector *other)
void AddExit(DartReturnInstr *exit)
void ReplaceCall(BlockEntryInstr *callee_entry)
virtual intptr_t InputCount() const =0
void LinkTo(Instruction *next)
Definition: il.h:1108
virtual Value * InputAt(intptr_t i) const =0
virtual BlockEntryInstr * GetBlock()
Definition: il.cc:1352
@ kGuardInputs
Definition: il.h:972
@ kNotSpeculative
Definition: il.h:975
virtual intptr_t ArgumentCount() const
Definition: il.h:1041
virtual Representation representation() const
Definition: il.h:1260
void ReplaceInEnvironment(Definition *current, Definition *replacement)
Definition: il.cc:1289
InstructionSource source() const
Definition: il.h:1008
Value * ArgumentValueAt(intptr_t index) const
Definition: il.h:3435
intptr_t deopt_id() const
Definition: il.h:993
void InsertAfter(Instruction *prev)
Definition: il.cc:1325
bool HasMoveArguments() const
Definition: il.h:1059
virtual void set_inlining_id(intptr_t value)
Definition: il.h:1312
static bool FindPragma(Thread *T, bool only_core, const Object &object, const String &pragma_name, bool multiple=false, Object *options=nullptr)
Definition: object.cc:4151
static Location RegisterLocation(Register reg)
Definition: locations.h:398
DART_NORETURN void Jump(int value, const Error &error)
Definition: longjump.cc:22
jmp_buf * Set()
Definition: longjump.cc:16
static ObjectPtr null()
Definition: object.h:433
ObjectPtr ptr() const
Definition: object.h:332
static Object & Handle()
Definition: object.h:407
static Object & ZoneHandle()
Definition: object.h:419
const Function & function() const
Definition: parser.h:73
void AddToGuardedFields(const Field *field) const
Definition: parser.cc:88
const FieldSet * guarded_fields() const
Definition: parser.h:191
PolymorphicInliner(CallSiteInliner *owner, PolymorphicInstanceCallInstr *call, const Function &caller_function)
Definition: inliner.cc:1973
virtual intptr_t CallCount() const
Definition: il.cc:5549
const CallTargets & targets() const
Definition: il.h:4953
static PolymorphicInstanceCallInstr * FromCall(Zone *zone, InstanceCallBaseInstr *call, const CallTargets &targets, bool complete)
Definition: il.h:4924
@ kBailout
Definition: report.h:26
static SmiPtr New(intptr_t value)
Definition: object.h:10006
const Function & function() const
Definition: il.h:5623
static const String & Plus()
Definition: symbols.h:617
static const String & Minus()
Definition: symbols.h:618
const Array & argument_names() const
Definition: il.h:4615
intptr_t ArgumentCount() const
Definition: il.h:4586
intptr_t FirstArgIndex() const
Definition: il.h:4576
Value * Receiver() const
Definition: il.h:4577
ArrayPtr GetArgumentsDescriptor() const
Definition: il.h:4617
LongJumpScope * long_jump_base() const
Definition: thread_state.h:47
static Thread * Current()
Definition: thread.h:362
DART_WARN_UNUSED_RESULT ErrorPtr StealStickyError()
Definition: thread.cc:245
void CheckForSafepoint()
Definition: thread.h:1104
CompilerTimings * compiler_timings() const
Definition: thread.h:617
void Stop()
Definition: timer.h:117
void Start()
Definition: timer.h:111
static constexpr T Maximum(T x, T y)
Definition: utils.h:41
Definition: il.h:75
Value * Copy(Zone *zone)
Definition: il.h:134
Definition * definition() const
Definition: il.h:103
CompileType * Type()
#define COMPILER_TIMINGS_TIMER_SCOPE(thread, timer_id)
#define THR_Print(format,...)
Definition: log.h:20
#define ASSERT(E)
SkBitmap source
Definition: examples.cpp:28
static bool b
struct MyStruct a[10]
AtkStateType state
const uint8_t uint32_t uint32_t GError ** error
uint32_t * target
constexpr bool FLAG_support_il_printer
Definition: flag_list.h:48
const char * charp
Definition: flags.h:12
Dart_NativeFunction function
Definition: fuchsia.cc:51
#define Z
Definition: inliner.cc:88
#define PRINT_INLINING_TREE(comment, caller, target, instance_call)
Definition: inliner.cc:95
#define TRACE_INLINING(statement)
Definition: inliner.cc:90
size_t length
Win32Message message
bool Contains(const Container &container, const Value &value)
void ReadParameterCovariance(const Function &function, BitVector *is_covariant, BitVector *is_generic_covariant_impl)
Definition: kernel.cc:599
Definition: dart_vm.cc:33
const char *const name
static bool IsSmallLeafOrReduction(int inlining_depth, intptr_t call_site_instructions, FlowGraph *graph)
Definition: inliner.cc:666
static constexpr Representation kUnboxedUword
Definition: locations.h:171
static bool IsInlineableOperator(const Function &function)
Definition: inliner.cc:2468
Representation
Definition: locations.h:66
static bool IsAThisCallThroughAnUncheckedEntryPoint(Definition *call)
Definition: inliner.cc:776
const Register ARGS_DESC_REG
static bool IsCallRecursive(const Function &function, Definition *call)
Definition: inliner.cc:104
static void ReplaceParameterStubs(Zone *zone, FlowGraph *caller_graph, InlinedCallData *call_data, const TargetInfo *target_info)
Definition: inliner.cc:842
static intptr_t AotCallCountApproximation(intptr_t nesting_depth)
Definition: inliner.cc:244
DEFINE_FLAG(bool, print_cluster_information, false, "Print information about clusters written to snapshot")
static bool IsConstant(Definition *def, int64_t *val)
Definition: loops.cc:123
static bool CalleeParameterTypeMightBeMoreSpecific(BitVector *is_generic_covariant_impl, const FunctionType &interface_target_signature, const FunctionType &callee_signature, intptr_t first_arg_index, intptr_t arg_index)
Definition: inliner.cc:786
static Instruction * AppendInstruction(Instruction *first, Instruction *second)
Definition: inliner.cc:2099
static void TracePolyInlining(const CallTargets &targets, intptr_t idx, intptr_t total, const char *message)
Definition: inliner.cc:2302
DECLARE_FLAG(bool, show_invisible_frames)
def call(args)
Definition: dom.py:159
Definition: __init__.py:1
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network JSON encoded network policy per domain This overrides the DisallowInsecureConnections switch Embedder can specify whether to allow or disallow insecure connections at a domain level old gen heap size
Definition: switches.h:259
#define Pd
Definition: globals.h:408
#define DISALLOW_COPY_AND_ASSIGN(TypeName)
Definition: globals.h:581
int compare(const void *untyped_lhs, const void *untyped_rhs)
Definition: skdiff.h:161
static SkString join(const CommandLineFlags::StringArray &)
Definition: skpbench.cpp:741
InliningDecision(bool b, const char *r)
Definition: inliner.cc:1006
static InliningDecision No(const char *reason)
Definition: inliner.cc:1012
static InliningDecision Yes(const char *reason)
Definition: inliner.cc:1009
CallInfo(FlowGraph *caller_graph, CallType *call, intptr_t call_depth, intptr_t nesting_depth)
Definition: inliner.cc:272
FlowGraph * caller_graph
Definition: inliner.cc:265
const Function & caller() const
Definition: inliner.cc:287
intptr_t cid_start
Definition: il.h:220
bool IsSingleCid() const
Definition: il.h:207
const Array & arguments_descriptor
Definition: inliner.cc:729
Definition * call
Definition: inliner.cc:728
GrowableArray< Value * > * arguments
Definition: inliner.cc:731
const Function & caller
Definition: inliner.cc:735
InlineExitCollector * exit_collector
Definition: inliner.cc:734
const intptr_t first_arg_index
Definition: inliner.cc:730
InlinedCallData(Definition *call, const Array &arguments_descriptor, intptr_t first_arg_index, GrowableArray< Value * > *arguments, const Function &caller)
Definition: inliner.cc:714
ZoneGrowableArray< Definition * > * parameter_stubs
Definition: inliner.cc:733
FlowGraph * callee_graph
Definition: inliner.cc:732
intptr_t inlined_depth
Definition: inliner.cc:226
const Function * caller
Definition: inliner.cc:224
const char * bailout_reason
Definition: inliner.cc:228
InlinedInfo(const Function *caller_function, const Function *inlined_function, const intptr_t depth, const Definition *call, const char *reason)
Definition: inliner.cc:229
const Definition * call_instr
Definition: inliner.cc:227
const Function * inlined
Definition: inliner.cc:225
NamedArgument(String *name, Value *value)
Definition: inliner.cc:119
bool Contains(intptr_t block_id) const
Definition: flow_graph.h:108
const Function * target
Definition: il.h:727
intptr_t count
Definition: il.h:728
const uintptr_t id