Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(21)

Side by Side Diff: src/compiler/js-inlining-heuristic.cc

Issue 2325943002: [turbofan] Initial support for polymorphic inlining. (Closed)
Patch Set: Addressed feedback. Created 4 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « src/compiler/js-inlining-heuristic.h ('k') | src/compiler/pipeline.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
OLDNEW
« no previous file with comments | « src/compiler/js-inlining-heuristic.h ('k') | src/compiler/pipeline.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698