Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
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.
104static bool IsCallRecursive(const Function& function, Definition* call) {
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.
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),
237 call_instr(call),
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
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;
1295 Code::EntryKind entry_kind = Code::EntryKind::kNormal;
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
1970 DISALLOW_COPY_AND_ASSIGN(CallSiteInliner);
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,
2209 DeoptId::kNone, false, Instruction::kNotSpeculative);
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);
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)) {
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
static bool compare(const SkBitmap &ref, const SkIRect &iref, const SkBitmap &test, const SkIRect &itest)
Definition BlurTest.cpp:100
const char * options
static void info(const char *fmt,...) SK_PRINTF_LIKE(1
Definition DM.cpp:213
int count
#define UNREACHABLE()
Definition assert.h:248
#define RELEASE_ASSERT(cond)
Definition assert.h:327
#define Z
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:1768
intptr_t NestingDepth() const
Definition il.cc:1829
intptr_t try_index() const
Definition il.h:1724
bool InsideTryBlock() const
Definition il.h:1728
void set_try_index(intptr_t index)
Definition il.h:1725
GrowableArray< Definition * > * initial_definitions()
Definition il.h:1911
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
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:790
const Function & FirstTarget() const
Definition il.cc:5515
static void Validate(FlowGraph *callee_graph)
Definition inliner.cc:127
intptr_t length() const
Definition il.h:752
void Add(CidRange *target)
Definition il.h:746
bool is_empty() const
Definition il.h:756
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()
static CompilerState & Current()
const Function * function() const
void RecordInliningStatsByOutcome(bool success, const Timer &timer)
static bool IsBackgroundCompilation()
Definition compiler.cc:299
static constexpr intptr_t kNoOSRDeoptId
Definition compiler.h:73
const Object & value() const
Definition il.h:4212
void AddInputUse(Value *value)
Definition il.h:2567
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)
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:95
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()
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)
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)
intptr_t allocate_block_id()
Definition flow_graph.h:266
intptr_t NumOptionalNamedParameters() const
Definition object.h:9621
AbstractTypePtr ParameterTypeAt(intptr_t index) const
Definition object.cc:8643
StringPtr ParameterNameAt(intptr_t index) const
Definition object.cc:8703
intptr_t NumParameters() const
Definition object.h:9628
bool IsRegularFunction() const
Definition object.h:3260
bool IsIdempotent() const
Definition object.cc:9100
const char * ToFullyQualifiedCString() const
Definition object.cc:9820
StringPtr QualifiedUserVisibleName() const
Definition object.cc:11081
intptr_t NumParameters() const
Definition object.cc:8935
FunctionEntryInstr * normal_entry() const
Definition il.h:1986
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:1102
virtual Value * InputAt(intptr_t i) const =0
virtual BlockEntryInstr * GetBlock()
Definition il.cc:1350
virtual intptr_t ArgumentCount() const
Definition il.h:1035
InstructionSource source() const
Definition il.h:1002
Value * ArgumentValueAt(intptr_t index) const
Definition il.h:3417
intptr_t deopt_id() const
Definition il.h:987
bool HasMoveArguments() const
Definition il.h:1053
virtual void set_inlining_id(intptr_t value)
Definition il.h:1306
static bool FindPragma(Thread *T, bool only_core, const Object &object, const String &pragma_name, bool multiple=false, Object *options=nullptr)
Definition object.cc:4201
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:5551
const CallTargets & targets() const
Definition il.h:4935
static PolymorphicInstanceCallInstr * FromCall(Zone *zone, InstanceCallBaseInstr *call, const CallTargets &targets, bool complete)
Definition il.h:4906
static SmiPtr New(intptr_t value)
Definition object.h:9985
const Function & function() const
Definition il.h:5574
static const String & Plus()
Definition symbols.h:616
static const String & Minus()
Definition symbols.h:617
const Array & argument_names() const
Definition il.h:4597
intptr_t ArgumentCount() const
Definition il.h:4568
intptr_t FirstArgIndex() const
Definition il.h:4558
Value * Receiver() const
Definition il.h:4559
ArrayPtr GetArgumentsDescriptor() const
Definition il.h:4599
LongJumpScope * long_jump_base() const
static Thread * Current()
Definition thread.h:361
DART_WARN_UNUSED_RESULT ErrorPtr StealStickyError()
Definition thread.cc:243
void CheckForSafepoint()
Definition thread.h:1091
CompilerTimings * compiler_timings() const
Definition thread.h:612
void Stop()
Definition timer.h:117
void Start()
Definition timer.h:111
static constexpr T Maximum(T x, T y)
Definition utils.h:26
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
#define DECLARE_FLAG(type, name)
Definition flags.h:14
#define DEFINE_FLAG(type, name, default_value, comment)
Definition flags.h:16
Dart_NativeFunction function
Definition fuchsia.cc:51
#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
const char *const name
static constexpr const char * kNone
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
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
call(args)
Definition dom.py:159
Definition __init__.py:1
SINT Vec< 2 *N, T > join(const Vec< N, T > &lo, const Vec< N, T > &hi)
Definition SkVx.h:242
#define Pd
Definition globals.h:408
#define DISALLOW_COPY_AND_ASSIGN(TypeName)
Definition globals.h:581
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
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:721
intptr_t count
Definition il.h:722
const uintptr_t id