OLD | NEW |
---|---|
1 // Copyright 2015 the V8 project authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "src/compiler/js-inlining-heuristic.h" | 5 #include "src/compiler/js-inlining-heuristic.h" |
6 | 6 |
7 #include "src/compilation-info.h" | 7 #include "src/compilation-info.h" |
8 #include "src/compiler/common-operator.h" | |
8 #include "src/compiler/node-matchers.h" | 9 #include "src/compiler/node-matchers.h" |
10 #include "src/compiler/simplified-operator.h" | |
9 #include "src/objects-inl.h" | 11 #include "src/objects-inl.h" |
10 | 12 |
11 namespace v8 { | 13 namespace v8 { |
12 namespace internal { | 14 namespace internal { |
13 namespace compiler { | 15 namespace compiler { |
14 | 16 |
17 #define TRACE(...) \ | |
18 do { \ | |
19 if (FLAG_trace_turbo_inlining) PrintF(__VA_ARGS__); \ | |
20 } while (false) | |
21 | |
22 namespace { | |
23 | |
24 int CollectFunctions(Node* node, Handle<JSFunction>* functions, | |
25 int functions_size) { | |
26 DCHECK_NE(0u, functions_size); | |
27 HeapObjectMatcher m(node); | |
28 if (m.HasValue() && m.Value()->IsJSFunction()) { | |
29 functions[0] = Handle<JSFunction>::cast(m.Value()); | |
30 return 1; | |
31 } | |
32 if (m.IsPhi()) { | |
33 int const value_input_count = m.node()->op()->ValueInputCount(); | |
34 if (value_input_count > functions_size) return 0; | |
35 for (int n = 0; n < value_input_count; ++n) { | |
36 HeapObjectMatcher m(node->InputAt(n)); | |
37 if (!m.HasValue() || !m.Value()->IsJSFunction()) return 0; | |
38 functions[n] = Handle<JSFunction>::cast(m.Value()); | |
39 } | |
40 return value_input_count; | |
41 } | |
42 return 0; | |
43 } | |
44 | |
45 bool CanInlineFunction(Handle<JSFunction> function) { | |
46 // Built-in functions are handled by the JSBuiltinReducer. | |
47 if (function->shared()->HasBuiltinFunctionId()) return false; | |
48 | |
49 // Don't inline builtins. | |
50 if (function->shared()->IsBuiltin()) return false; | |
51 | |
52 // Quick check on source code length to avoid parsing large candidate. | |
53 if (function->shared()->SourceSize() > FLAG_max_inlined_source_size) { | |
54 return false; | |
55 } | |
56 | |
57 // Quick check on the size of the AST to avoid parsing large candidate. | |
58 if (function->shared()->ast_node_count() > FLAG_max_inlined_nodes) { | |
59 return false; | |
60 } | |
61 | |
62 // Avoid inlining across the boundary of asm.js code. | |
63 if (function->shared()->asm_function()) return false; | |
64 return true; | |
65 } | |
66 | |
67 } // namespace | |
68 | |
15 Reduction JSInliningHeuristic::Reduce(Node* node) { | 69 Reduction JSInliningHeuristic::Reduce(Node* node) { |
16 if (!IrOpcode::IsInlineeOpcode(node->opcode())) return NoChange(); | 70 if (!IrOpcode::IsInlineeOpcode(node->opcode())) return NoChange(); |
17 | 71 |
18 // Check if we already saw that {node} before, and if so, just skip it. | 72 // Check if we already saw that {node} before, and if so, just skip it. |
19 if (seen_.find(node->id()) != seen_.end()) return NoChange(); | 73 if (seen_.find(node->id()) != seen_.end()) return NoChange(); |
20 seen_.insert(node->id()); | 74 seen_.insert(node->id()); |
21 | 75 |
76 // Check if the {node} is an appropriate candidate for inlining. | |
22 Node* callee = node->InputAt(0); | 77 Node* callee = node->InputAt(0); |
23 HeapObjectMatcher match(callee); | 78 Candidate candidate; |
24 if (!match.HasValue() || !match.Value()->IsJSFunction()) return NoChange(); | 79 candidate.node = node; |
25 Handle<JSFunction> function = Handle<JSFunction>::cast(match.Value()); | 80 candidate.num_functions = |
26 | 81 CollectFunctions(callee, candidate.functions, kMaxCallPolymorphism); |
27 // Functions marked with %SetForceInlineFlag are immediately inlined. | 82 if (candidate.num_functions == 0) { |
28 if (function->shared()->force_inline()) { | 83 return NoChange(); |
29 return inliner_.ReduceJSCall(node, function); | 84 } else if (candidate.num_functions > 1 && !FLAG_polymorphic_inlining) { |
30 } | 85 TRACE( |
31 | 86 "Not considering call site #%d:%s, because polymorphic inlining " |
32 // Handling of special inlining modes right away: | 87 "is disabled\n", |
33 // - For restricted inlining: stop all handling at this point. | 88 node->id(), node->op()->mnemonic()); |
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(); | 89 return NoChange(); |
57 } | 90 } |
58 | 91 |
59 // Quick check on the size of the AST to avoid parsing large candidate. | 92 // Functions marked with %SetForceInlineFlag are immediately inlined. |
60 if (function->shared()->ast_node_count() > FLAG_max_inlined_nodes) { | 93 bool can_inline = false, force_inline = true; |
61 return NoChange(); | 94 for (int i = 0; i < candidate.num_functions; ++i) { |
95 Handle<JSFunction> function = candidate.functions[i]; | |
96 if (!function->shared()->force_inline()) { | |
97 force_inline = false; | |
98 } | |
99 if (CanInlineFunction(function)) { | |
100 can_inline = true; | |
101 } | |
62 } | 102 } |
63 | 103 if (force_inline) return InlineCandidate(candidate); |
64 // Avoid inlining within or across the boundary of asm.js code. | 104 if (!can_inline) return NoChange(); |
65 if (info_->shared_info()->asm_function()) return NoChange(); | |
66 if (function->shared()->asm_function()) return NoChange(); | |
67 | 105 |
68 // Stop inlining once the maximum allowed level is reached. | 106 // Stop inlining once the maximum allowed level is reached. |
69 int level = 0; | 107 int level = 0; |
70 for (Node* frame_state = NodeProperties::GetFrameStateInput(node); | 108 for (Node* frame_state = NodeProperties::GetFrameStateInput(node); |
71 frame_state->opcode() == IrOpcode::kFrameState; | 109 frame_state->opcode() == IrOpcode::kFrameState; |
72 frame_state = NodeProperties::GetFrameStateInput(frame_state)) { | 110 frame_state = NodeProperties::GetFrameStateInput(frame_state)) { |
73 if (++level > FLAG_max_inlining_levels) return NoChange(); | 111 FrameStateInfo const& frame_info = OpParameter<FrameStateInfo>(frame_state); |
112 if (frame_info.type() == FrameStateType::kJavaScriptFunction) { | |
113 if (++level > FLAG_max_inlining_levels) { | |
114 TRACE( | |
115 "Not considering call site #%d:%s, because inlining depth " | |
116 "%d exceeds maximum allowed level %d\n", | |
117 node->id(), node->op()->mnemonic(), level, | |
118 FLAG_max_inlining_levels); | |
119 return NoChange(); | |
120 } | |
121 } | |
74 } | 122 } |
75 | 123 |
76 // Gather feedback on how often this call site has been hit before. | 124 // Gather feedback on how often this call site has been hit before. |
77 int calls = -1; // Same default as CallICNexus::ExtractCallCount. | |
78 if (node->opcode() == IrOpcode::kJSCallFunction) { | 125 if (node->opcode() == IrOpcode::kJSCallFunction) { |
79 CallFunctionParameters p = CallFunctionParametersOf(node->op()); | 126 CallFunctionParameters p = CallFunctionParametersOf(node->op()); |
80 if (p.feedback().IsValid()) { | 127 if (p.feedback().IsValid()) { |
81 CallICNexus nexus(p.feedback().vector(), p.feedback().slot()); | 128 CallICNexus nexus(p.feedback().vector(), p.feedback().slot()); |
82 calls = nexus.ExtractCallCount(); | 129 candidate.calls = nexus.ExtractCallCount(); |
83 } | 130 } |
84 } else { | 131 } else { |
85 DCHECK_EQ(IrOpcode::kJSCallConstruct, node->opcode()); | 132 DCHECK_EQ(IrOpcode::kJSCallConstruct, node->opcode()); |
86 CallConstructParameters p = CallConstructParametersOf(node->op()); | 133 CallConstructParameters p = CallConstructParametersOf(node->op()); |
87 if (p.feedback().IsValid()) { | 134 if (p.feedback().IsValid()) { |
88 int const extra_index = | 135 int const extra_index = |
89 p.feedback().vector()->GetIndex(p.feedback().slot()) + 1; | 136 p.feedback().vector()->GetIndex(p.feedback().slot()) + 1; |
90 Handle<Object> feedback_extra(p.feedback().vector()->get(extra_index), | 137 Object* feedback_extra = p.feedback().vector()->get(extra_index); |
91 function->GetIsolate()); | |
92 if (feedback_extra->IsSmi()) { | 138 if (feedback_extra->IsSmi()) { |
93 calls = Handle<Smi>::cast(feedback_extra)->value(); | 139 candidate.calls = Smi::cast(feedback_extra)->value(); |
94 } | 140 } |
95 } | 141 } |
96 } | 142 } |
97 | 143 |
98 // --------------------------------------------------------------------------- | 144 // Handling of special inlining modes right away: |
99 // Everything above this line is part of the inlining heuristic. | 145 // - For restricted inlining: stop all handling at this point. |
100 // --------------------------------------------------------------------------- | 146 // - For stressing inlining: immediately handle all functions. |
147 switch (mode_) { | |
148 case kRestrictedInlining: | |
149 return NoChange(); | |
150 case kStressInlining: | |
151 return InlineCandidate(candidate); | |
152 case kGeneralInlining: | |
153 break; | |
154 } | |
101 | 155 |
102 // In the general case we remember the candidate for later. | 156 // In the general case we remember the candidate for later. |
103 candidates_.insert({function, node, calls}); | 157 candidates_.insert(candidate); |
104 return NoChange(); | 158 return NoChange(); |
105 } | 159 } |
106 | 160 |
107 | |
108 void JSInliningHeuristic::Finalize() { | 161 void JSInliningHeuristic::Finalize() { |
109 if (candidates_.empty()) return; // Nothing to do without candidates. | 162 if (candidates_.empty()) return; // Nothing to do without candidates. |
110 if (FLAG_trace_turbo_inlining) PrintCandidates(); | 163 if (FLAG_trace_turbo_inlining) PrintCandidates(); |
111 | 164 |
112 // We inline at most one candidate in every iteration of the fixpoint. | 165 // We inline at most one candidate in every iteration of the fixpoint. |
113 // This is to ensure that we don't consume the full inlining budget | 166 // This is to ensure that we don't consume the full inlining budget |
114 // on things that aren't called very often. | 167 // on things that aren't called very often. |
115 // TODO(bmeurer): Use std::priority_queue instead of std::set here. | 168 // TODO(bmeurer): Use std::priority_queue instead of std::set here. |
116 while (!candidates_.empty()) { | 169 while (!candidates_.empty()) { |
117 if (cumulative_count_ > FLAG_max_inlined_nodes_cumulative) return; | 170 if (cumulative_count_ > FLAG_max_inlined_nodes_cumulative) return; |
118 auto i = candidates_.begin(); | 171 auto i = candidates_.begin(); |
119 Candidate candidate = *i; | 172 Candidate candidate = *i; |
120 candidates_.erase(i); | 173 candidates_.erase(i); |
121 // Make sure we don't try to inline dead candidate nodes. | 174 // Make sure we don't try to inline dead candidate nodes. |
122 if (!candidate.node->IsDead()) { | 175 if (!candidate.node->IsDead()) { |
123 Reduction r = inliner_.ReduceJSCall(candidate.node, candidate.function); | 176 Reduction const reduction = InlineCandidate(candidate); |
124 if (r.Changed()) { | 177 if (reduction.Changed()) return; |
125 cumulative_count_ += candidate.function->shared()->ast_node_count(); | |
126 return; | |
127 } | |
128 } | 178 } |
129 } | 179 } |
130 } | 180 } |
131 | 181 |
182 Reduction JSInliningHeuristic::InlineCandidate(Candidate const& candidate) { | |
183 int const num_calls = candidate.num_functions; | |
184 Node* const node = candidate.node; | |
185 if (num_calls == 1) { | |
186 Handle<JSFunction> function = candidate.functions[0]; | |
187 Reduction const reduction = inliner_.ReduceJSCall(node, function); | |
188 if (reduction.Changed()) { | |
189 cumulative_count_ += function->shared()->ast_node_count(); | |
190 } | |
191 return reduction; | |
192 } | |
193 | |
194 // Expand the JSCallFunction/JSCallConstruct node to a subgraph first if | |
195 // we have multiple known target functions. | |
196 DCHECK_LT(1, num_calls); | |
197 Node* calls[kMaxCallPolymorphism + 1]; | |
198 Node* if_successes[kMaxCallPolymorphism]; | |
199 Node* callee = NodeProperties::GetValueInput(node, 0); | |
200 Node* control = NodeProperties::GetControlInput(node); | |
201 | |
202 // Setup the inputs for the cloned call nodes. | |
203 int const input_count = node->InputCount(); | |
204 Node** inputs = graph()->zone()->NewArray<Node*>(input_count); | |
205 for (int i = 0; i < input_count; ++i) { | |
206 inputs[i] = node->InputAt(i); | |
207 } | |
208 | |
209 // Create the appropriate control flow to dispatch to the cloned calls. | |
210 for (int i = 0; i < num_calls; ++i) { | |
211 Node* target = jsgraph()->HeapConstant(candidate.functions[i]); | |
212 if (i != (num_calls - 1)) { | |
213 Node* check = | |
214 graph()->NewNode(simplified()->ReferenceEqual(), callee, target); | |
215 Node* branch = graph()->NewNode(common()->Branch(), check, control); | |
216 control = graph()->NewNode(common()->IfFalse(), branch); | |
217 if_successes[i] = graph()->NewNode(common()->IfTrue(), branch); | |
218 } else { | |
219 if_successes[i] = control; | |
220 } | |
221 | |
222 // The first input to the call is the actual target (which we specialize | |
223 // to the known {target}); the last input is the control dependency. | |
224 inputs[0] = target; | |
225 inputs[input_count - 1] = if_successes[i]; | |
226 calls[i] = graph()->NewNode(node->op(), input_count, inputs); | |
227 if_successes[i] = graph()->NewNode(common()->IfSuccess(), calls[i]); | |
228 } | |
229 | |
230 // Check if we have an exception projection for the call {node}. | |
231 Node* if_exception = nullptr; | |
232 for (Edge const edge : node->use_edges()) { | |
233 if (NodeProperties::IsControlEdge(edge) && | |
234 edge.from()->opcode() == IrOpcode::kIfException) { | |
235 if_exception = edge.from(); | |
236 break; | |
237 } | |
238 } | |
239 if (if_exception != nullptr) { | |
240 // Morph the {if_exception} projection into a join. | |
241 Node* if_exceptions[kMaxCallPolymorphism + 1]; | |
242 for (int i = 0; i < num_calls; ++i) { | |
243 if_exceptions[i] = | |
244 graph()->NewNode(common()->IfException(), calls[i], calls[i]); | |
245 } | |
246 Node* control = | |
247 graph()->NewNode(common()->Merge(num_calls), num_calls, if_exceptions); | |
brucedawson
2016/09/12 18:08:00
Probably not a bug, but this declaration of 'contr
| |
248 if_exceptions[num_calls] = control; | |
249 Node* effect = graph()->NewNode(common()->EffectPhi(num_calls), | |
250 num_calls + 1, if_exceptions); | |
251 Node* value = graph()->NewNode( | |
252 common()->Phi(MachineRepresentation::kTagged, num_calls), num_calls + 1, | |
253 if_exceptions); | |
254 ReplaceWithValue(if_exception, value, effect, control); | |
255 } | |
256 | |
257 // Morph the call site into the dispatched call sites. | |
258 control = | |
259 graph()->NewNode(common()->Merge(num_calls), num_calls, if_successes); | |
260 calls[num_calls] = control; | |
261 Node* effect = | |
262 graph()->NewNode(common()->EffectPhi(num_calls), num_calls + 1, calls); | |
263 Node* value = | |
264 graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, num_calls), | |
265 num_calls + 1, calls); | |
266 ReplaceWithValue(node, value, effect, control); | |
267 | |
268 // Inline the individual, cloned call sites. | |
269 for (int i = 0; i < num_calls; ++i) { | |
270 Handle<JSFunction> function = candidate.functions[i]; | |
271 Node* node = calls[i]; | |
272 Reduction const reduction = inliner_.ReduceJSCall(node, function); | |
273 if (reduction.Changed()) { | |
274 cumulative_count_ += function->shared()->ast_node_count(); | |
275 } | |
276 } | |
277 | |
278 return Replace(value); | |
279 } | |
132 | 280 |
133 bool JSInliningHeuristic::CandidateCompare::operator()( | 281 bool JSInliningHeuristic::CandidateCompare::operator()( |
134 const Candidate& left, const Candidate& right) const { | 282 const Candidate& left, const Candidate& right) const { |
135 if (left.calls != right.calls) { | 283 if (left.calls != right.calls) { |
136 return left.calls > right.calls; | 284 return left.calls > right.calls; |
137 } | 285 } |
138 return left.node < right.node; | 286 return left.node < right.node; |
139 } | 287 } |
140 | 288 |
141 | |
142 void JSInliningHeuristic::PrintCandidates() { | 289 void JSInliningHeuristic::PrintCandidates() { |
143 PrintF("Candidates for inlining (size=%zu):\n", candidates_.size()); | 290 PrintF("Candidates for inlining (size=%zu):\n", candidates_.size()); |
144 for (const Candidate& candidate : candidates_) { | 291 for (const Candidate& candidate : candidates_) { |
145 PrintF(" id:%d, calls:%d, size[source]:%d, size[ast]:%d / %s\n", | 292 PrintF(" #%d:%s, calls:%d\n", candidate.node->id(), |
146 candidate.node->id(), candidate.calls, | 293 candidate.node->op()->mnemonic(), candidate.calls); |
147 candidate.function->shared()->SourceSize(), | 294 for (int i = 0; i < candidate.num_functions; ++i) { |
148 candidate.function->shared()->ast_node_count(), | 295 Handle<JSFunction> function = candidate.functions[i]; |
149 candidate.function->shared()->DebugName()->ToCString().get()); | 296 PrintF(" - size[source]:%d, size[ast]:%d, name: %s\n", |
297 function->shared()->SourceSize(), | |
298 function->shared()->ast_node_count(), | |
299 function->shared()->DebugName()->ToCString().get()); | |
300 } | |
150 } | 301 } |
151 } | 302 } |
152 | 303 |
304 Graph* JSInliningHeuristic::graph() const { return jsgraph()->graph(); } | |
305 | |
306 CommonOperatorBuilder* JSInliningHeuristic::common() const { | |
307 return jsgraph()->common(); | |
308 } | |
309 | |
310 SimplifiedOperatorBuilder* JSInliningHeuristic::simplified() const { | |
311 return jsgraph()->simplified(); | |
312 } | |
313 | |
153 } // namespace compiler | 314 } // namespace compiler |
154 } // namespace internal | 315 } // namespace internal |
155 } // namespace v8 | 316 } // namespace v8 |
OLD | NEW |