1 // Copyright 2015 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "src/compiler/js-inlining-heuristic.h"
6 
7 #include "src/compiler.h"
8 #include "src/compiler/node-matchers.h"
9 #include "src/objects-inl.h"
10 
11 namespace v8 {
12 namespace internal {
13 namespace compiler {
14 
Reduce(Node * node)15 Reduction JSInliningHeuristic::Reduce(Node* node) {
16   if (!IrOpcode::IsInlineeOpcode(node->opcode())) return NoChange();
17 
18   // Check if we already saw that {node} before, and if so, just skip it.
19   if (seen_.find(node->id()) != seen_.end()) return NoChange();
20   seen_.insert(node->id());
21 
22   Node* callee = node->InputAt(0);
23   HeapObjectMatcher match(callee);
24   if (!match.HasValue() || !match.Value()->IsJSFunction()) return NoChange();
25   Handle<JSFunction> function = Handle<JSFunction>::cast(match.Value());
26 
27   // Functions marked with %SetForceInlineFlag are immediately inlined.
28   if (function->shared()->force_inline()) {
29     return inliner_.ReduceJSCall(node, function);
30   }
31 
32   // Handling of special inlining modes right away:
33   //  - For restricted inlining: stop all handling at this point.
34   //  - For stressing inlining: immediately handle all functions.
35   switch (mode_) {
36     case kRestrictedInlining:
37       return NoChange();
38     case kStressInlining:
39       return inliner_.ReduceJSCall(node, function);
40     case kGeneralInlining:
41       break;
42   }
43 
44   // ---------------------------------------------------------------------------
45   // Everything below this line is part of the inlining heuristic.
46   // ---------------------------------------------------------------------------
47 
48   // Built-in functions are handled by the JSBuiltinReducer.
49   if (function->shared()->HasBuiltinFunctionId()) return NoChange();
50 
51   // Don't inline builtins.
52   if (function->shared()->IsBuiltin()) return NoChange();
53 
54   // Quick check on source code length to avoid parsing large candidate.
55   if (function->shared()->SourceSize() > FLAG_max_inlined_source_size) {
56     return NoChange();
57   }
58 
59   // Quick check on the size of the AST to avoid parsing large candidate.
60   if (function->shared()->ast_node_count() > FLAG_max_inlined_nodes) {
61     return NoChange();
62   }
63 
64   // Avoid inlining within or across the boundary of asm.js code.
65   if (info_->shared_info()->asm_function()) return NoChange();
66   if (function->shared()->asm_function()) return NoChange();
67 
68   // Stop inlinining once the maximum allowed level is reached.
69   int level = 0;
70   for (Node* frame_state = NodeProperties::GetFrameStateInput(node, 0);
71        frame_state->opcode() == IrOpcode::kFrameState;
72        frame_state = NodeProperties::GetFrameStateInput(frame_state, 0)) {
73     if (++level > FLAG_max_inlining_levels) return NoChange();
74   }
75 
76   // Gather feedback on how often this call site has been hit before.
77   int calls = -1;  // Same default as CallICNexus::ExtractCallCount.
78   // TODO(turbofan): We also want call counts for constructor calls.
79   if (node->opcode() == IrOpcode::kJSCallFunction) {
80     CallFunctionParameters p = CallFunctionParametersOf(node->op());
81     if (p.feedback().IsValid()) {
82       CallICNexus nexus(p.feedback().vector(), p.feedback().slot());
83       calls = nexus.ExtractCallCount();
84     }
85   }
86 
87   // ---------------------------------------------------------------------------
88   // Everything above this line is part of the inlining heuristic.
89   // ---------------------------------------------------------------------------
90 
91   // In the general case we remember the candidate for later.
92   candidates_.insert({function, node, calls});
93   return NoChange();
94 }
95 
96 
Finalize()97 void JSInliningHeuristic::Finalize() {
98   if (candidates_.empty()) return;  // Nothing to do without candidates.
99   if (FLAG_trace_turbo_inlining) PrintCandidates();
100 
101   // We inline at most one candidate in every iteration of the fixpoint.
102   // This is to ensure that we don't consume the full inlining budget
103   // on things that aren't called very often.
104   // TODO(bmeurer): Use std::priority_queue instead of std::set here.
105   while (!candidates_.empty()) {
106     if (cumulative_count_ > FLAG_max_inlined_nodes_cumulative) return;
107     auto i = candidates_.begin();
108     Candidate candidate = *i;
109     candidates_.erase(i);
110     // Make sure we don't try to inline dead candidate nodes.
111     if (!candidate.node->IsDead()) {
112       Reduction r = inliner_.ReduceJSCall(candidate.node, candidate.function);
113       if (r.Changed()) {
114         cumulative_count_ += candidate.function->shared()->ast_node_count();
115         return;
116       }
117     }
118   }
119 }
120 
121 
operator ()(const Candidate & left,const Candidate & right) const122 bool JSInliningHeuristic::CandidateCompare::operator()(
123     const Candidate& left, const Candidate& right) const {
124   return left.node != right.node && left.calls >= right.calls;
125 }
126 
127 
PrintCandidates()128 void JSInliningHeuristic::PrintCandidates() {
129   PrintF("Candidates for inlining (size=%zu):\n", candidates_.size());
130   for (const Candidate& candidate : candidates_) {
131     PrintF("  id:%d, calls:%d, size[source]:%d, size[ast]:%d / %s\n",
132            candidate.node->id(), candidate.calls,
133            candidate.function->shared()->SourceSize(),
134            candidate.function->shared()->ast_node_count(),
135            candidate.function->shared()->DebugName()->ToCString().get());
136   }
137 }
138 
139 }  // namespace compiler
140 }  // namespace internal
141 }  // namespace v8
142