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/common-operator.h"
8 #include "src/compiler/compiler-source-position-table.h"
9 #include "src/compiler/node-matchers.h"
10 #include "src/compiler/simplified-operator.h"
11 #include "src/objects-inl.h"
12 #include "src/optimized-compilation-info.h"
13
14 namespace v8 {
15 namespace internal {
16 namespace compiler {
17
18 #define TRACE(...) \
19 do { \
20 if (FLAG_trace_turbo_inlining) PrintF(__VA_ARGS__); \
21 } while (false)
22
23 namespace {
24
CollectFunctions(Node * node,Handle<JSFunction> * functions,int functions_size,Handle<SharedFunctionInfo> & shared)25 int CollectFunctions(Node* node, Handle<JSFunction>* functions,
26 int functions_size, Handle<SharedFunctionInfo>& shared) {
27 DCHECK_NE(0, functions_size);
28 HeapObjectMatcher m(node);
29 if (m.HasValue() && m.Value()->IsJSFunction()) {
30 functions[0] = Handle<JSFunction>::cast(m.Value());
31 return 1;
32 }
33 if (m.IsPhi()) {
34 int const value_input_count = m.node()->op()->ValueInputCount();
35 if (value_input_count > functions_size) return 0;
36 for (int n = 0; n < value_input_count; ++n) {
37 HeapObjectMatcher m(node->InputAt(n));
38 if (!m.HasValue() || !m.Value()->IsJSFunction()) return 0;
39 functions[n] = Handle<JSFunction>::cast(m.Value());
40 }
41 return value_input_count;
42 }
43 if (m.IsJSCreateClosure()) {
44 CreateClosureParameters const& p = CreateClosureParametersOf(m.op());
45 functions[0] = Handle<JSFunction>::null();
46 shared = p.shared_info();
47 return 1;
48 }
49 return 0;
50 }
51
CanInlineFunction(Handle<SharedFunctionInfo> shared)52 bool CanInlineFunction(Handle<SharedFunctionInfo> shared) {
53 // Built-in functions are handled by the JSCallReducer.
54 if (shared->HasBuiltinFunctionId()) return false;
55
56 // Only choose user code for inlining.
57 if (!shared->IsUserJavaScript()) return false;
58
59 // If there is no bytecode array, it is either not compiled or it is compiled
60 // with WebAssembly for the asm.js pipeline. In either case we don't want to
61 // inline.
62 if (!shared->HasBytecodeArray()) return false;
63
64 // Quick check on the size of the bytecode to avoid inlining large functions.
65 if (shared->GetBytecodeArray()->length() > FLAG_max_inlined_bytecode_size) {
66 return false;
67 }
68
69 return true;
70 }
71
IsSmallInlineFunction(Handle<SharedFunctionInfo> shared)72 bool IsSmallInlineFunction(Handle<SharedFunctionInfo> shared) {
73 // Forcibly inline small functions.
74 // Don't forcibly inline functions that weren't compiled yet.
75 if (shared->HasBytecodeArray() && shared->GetBytecodeArray()->length() <=
76 FLAG_max_inlined_bytecode_size_small) {
77 return true;
78 }
79 return false;
80 }
81
82 } // namespace
83
Reduce(Node * node)84 Reduction JSInliningHeuristic::Reduce(Node* node) {
85 if (!IrOpcode::IsInlineeOpcode(node->opcode())) return NoChange();
86
87 // Check if we already saw that {node} before, and if so, just skip it.
88 if (seen_.find(node->id()) != seen_.end()) return NoChange();
89 seen_.insert(node->id());
90
91 // Check if the {node} is an appropriate candidate for inlining.
92 Node* callee = node->InputAt(0);
93 Candidate candidate;
94 candidate.node = node;
95 candidate.num_functions = CollectFunctions(
96 callee, candidate.functions, kMaxCallPolymorphism, candidate.shared_info);
97 if (candidate.num_functions == 0) {
98 return NoChange();
99 } else if (candidate.num_functions > 1 && !FLAG_polymorphic_inlining) {
100 TRACE(
101 "Not considering call site #%d:%s, because polymorphic inlining "
102 "is disabled\n",
103 node->id(), node->op()->mnemonic());
104 return NoChange();
105 }
106
107 bool can_inline = false, small_inline = true;
108 candidate.total_size = 0;
109 Node* frame_state = NodeProperties::GetFrameStateInput(node);
110 FrameStateInfo const& frame_info = FrameStateInfoOf(frame_state->op());
111 Handle<SharedFunctionInfo> frame_shared_info;
112 for (int i = 0; i < candidate.num_functions; ++i) {
113 Handle<SharedFunctionInfo> shared =
114 candidate.functions[i].is_null()
115 ? candidate.shared_info
116 : handle(candidate.functions[i]->shared(), isolate());
117 candidate.can_inline_function[i] = CanInlineFunction(shared);
118 // Do not allow direct recursion i.e. f() -> f(). We still allow indirect
119 // recurion like f() -> g() -> f(). The indirect recursion is helpful in
120 // cases where f() is a small dispatch function that calls the appropriate
121 // function. In the case of direct recursion, we only have some static
122 // information for the first level of inlining and it may not be that useful
123 // to just inline one level in recursive calls. In some cases like tail
124 // recursion we may benefit from recursive inlining, if we have additional
125 // analysis that converts them to iterative implementations. Though it is
126 // not obvious if such an anlysis is needed.
127 if (frame_info.shared_info().ToHandle(&frame_shared_info) &&
128 *frame_shared_info == *shared) {
129 TRACE("Not considering call site #%d:%s, because of recursive inlining\n",
130 node->id(), node->op()->mnemonic());
131 candidate.can_inline_function[i] = false;
132 }
133 if (candidate.can_inline_function[i]) {
134 can_inline = true;
135 candidate.total_size += shared->GetBytecodeArray()->length();
136 }
137 if (!IsSmallInlineFunction(shared)) {
138 small_inline = false;
139 }
140 }
141 if (!can_inline) return NoChange();
142
143 // Gather feedback on how often this call site has been hit before.
144 if (node->opcode() == IrOpcode::kJSCall) {
145 CallParameters const p = CallParametersOf(node->op());
146 candidate.frequency = p.frequency();
147 } else {
148 ConstructParameters const p = ConstructParametersOf(node->op());
149 candidate.frequency = p.frequency();
150 }
151
152 // Handling of special inlining modes right away:
153 // - For restricted inlining: stop all handling at this point.
154 // - For stressing inlining: immediately handle all functions.
155 switch (mode_) {
156 case kRestrictedInlining:
157 return NoChange();
158 case kStressInlining:
159 return InlineCandidate(candidate, false);
160 case kGeneralInlining:
161 break;
162 }
163
164 // Don't consider a {candidate} whose frequency is below the
165 // threshold, i.e. a call site that is only hit once every N
166 // invocations of the caller.
167 if (candidate.frequency.IsKnown() &&
168 candidate.frequency.value() < FLAG_min_inlining_frequency) {
169 return NoChange();
170 }
171
172 // Forcibly inline small functions here. In the case of polymorphic inlining
173 // small_inline is set only when all functions are small.
174 if (small_inline &&
175 cumulative_count_ < FLAG_max_inlined_bytecode_size_absolute) {
176 TRACE("Inlining small function(s) at call site #%d:%s\n", node->id(),
177 node->op()->mnemonic());
178 return InlineCandidate(candidate, true);
179 }
180
181 // In the general case we remember the candidate for later.
182 candidates_.insert(candidate);
183 return NoChange();
184 }
185
Finalize()186 void JSInliningHeuristic::Finalize() {
187 if (candidates_.empty()) return; // Nothing to do without candidates.
188 if (FLAG_trace_turbo_inlining) PrintCandidates();
189
190 // We inline at most one candidate in every iteration of the fixpoint.
191 // This is to ensure that we don't consume the full inlining budget
192 // on things that aren't called very often.
193 // TODO(bmeurer): Use std::priority_queue instead of std::set here.
194 while (!candidates_.empty()) {
195 auto i = candidates_.begin();
196 Candidate candidate = *i;
197 candidates_.erase(i);
198
199 // Make sure we have some extra budget left, so that any small functions
200 // exposed by this function would be given a chance to inline.
201 double size_of_candidate =
202 candidate.total_size * FLAG_reserve_inline_budget_scale_factor;
203 int total_size = cumulative_count_ + static_cast<int>(size_of_candidate);
204 if (total_size > FLAG_max_inlined_bytecode_size_cumulative) {
205 // Try if any smaller functions are available to inline.
206 continue;
207 }
208
209 // Make sure we don't try to inline dead candidate nodes.
210 if (!candidate.node->IsDead()) {
211 Reduction const reduction = InlineCandidate(candidate, false);
212 if (reduction.Changed()) return;
213 }
214 }
215 }
216
217 namespace {
218
219 struct NodeAndIndex {
220 Node* node;
221 int index;
222 };
223
CollectStateValuesOwnedUses(Node * node,Node * state_values,NodeAndIndex * uses_buffer,size_t * use_count,size_t max_uses)224 bool CollectStateValuesOwnedUses(Node* node, Node* state_values,
225 NodeAndIndex* uses_buffer, size_t* use_count,
226 size_t max_uses) {
227 // Only accumulate states that are not shared with other users.
228 if (state_values->UseCount() > 1) return true;
229 for (int i = 0; i < state_values->InputCount(); i++) {
230 Node* input = state_values->InputAt(i);
231 if (input->opcode() == IrOpcode::kStateValues) {
232 if (!CollectStateValuesOwnedUses(node, input, uses_buffer, use_count,
233 max_uses)) {
234 return false;
235 }
236 } else if (input == node) {
237 if (*use_count >= max_uses) return false;
238 uses_buffer[*use_count] = {state_values, i};
239 (*use_count)++;
240 }
241 }
242 return true;
243 }
244
245 } // namespace
246
DuplicateStateValuesAndRename(Node * state_values,Node * from,Node * to,StateCloneMode mode)247 Node* JSInliningHeuristic::DuplicateStateValuesAndRename(Node* state_values,
248 Node* from, Node* to,
249 StateCloneMode mode) {
250 // Only rename in states that are not shared with other users. This needs to
251 // be in sync with the condition in {CollectStateValuesOwnedUses}.
252 if (state_values->UseCount() > 1) return state_values;
253 Node* copy = mode == kChangeInPlace ? state_values : nullptr;
254 for (int i = 0; i < state_values->InputCount(); i++) {
255 Node* input = state_values->InputAt(i);
256 Node* processed;
257 if (input->opcode() == IrOpcode::kStateValues) {
258 processed = DuplicateStateValuesAndRename(input, from, to, mode);
259 } else if (input == from) {
260 processed = to;
261 } else {
262 processed = input;
263 }
264 if (processed != input) {
265 if (!copy) {
266 copy = graph()->CloneNode(state_values);
267 }
268 copy->ReplaceInput(i, processed);
269 }
270 }
271 return copy ? copy : state_values;
272 }
273
274 namespace {
275
CollectFrameStateUniqueUses(Node * node,Node * frame_state,NodeAndIndex * uses_buffer,size_t * use_count,size_t max_uses)276 bool CollectFrameStateUniqueUses(Node* node, Node* frame_state,
277 NodeAndIndex* uses_buffer, size_t* use_count,
278 size_t max_uses) {
279 // Only accumulate states that are not shared with other users.
280 if (frame_state->UseCount() > 1) return true;
281 if (frame_state->InputAt(kFrameStateStackInput) == node) {
282 if (*use_count >= max_uses) return false;
283 uses_buffer[*use_count] = {frame_state, kFrameStateStackInput};
284 (*use_count)++;
285 }
286 if (!CollectStateValuesOwnedUses(node,
287 frame_state->InputAt(kFrameStateLocalsInput),
288 uses_buffer, use_count, max_uses)) {
289 return false;
290 }
291 return true;
292 }
293
294 } // namespace
295
DuplicateFrameStateAndRename(Node * frame_state,Node * from,Node * to,StateCloneMode mode)296 Node* JSInliningHeuristic::DuplicateFrameStateAndRename(Node* frame_state,
297 Node* from, Node* to,
298 StateCloneMode mode) {
299 // Only rename in states that are not shared with other users. This needs to
300 // be in sync with the condition in {DuplicateFrameStateAndRename}.
301 if (frame_state->UseCount() > 1) return frame_state;
302 Node* copy = mode == kChangeInPlace ? frame_state : nullptr;
303 if (frame_state->InputAt(kFrameStateStackInput) == from) {
304 if (!copy) {
305 copy = graph()->CloneNode(frame_state);
306 }
307 copy->ReplaceInput(kFrameStateStackInput, to);
308 }
309 Node* locals = frame_state->InputAt(kFrameStateLocalsInput);
310 Node* new_locals = DuplicateStateValuesAndRename(locals, from, to, mode);
311 if (new_locals != locals) {
312 if (!copy) {
313 copy = graph()->CloneNode(frame_state);
314 }
315 copy->ReplaceInput(kFrameStateLocalsInput, new_locals);
316 }
317 return copy ? copy : frame_state;
318 }
319
TryReuseDispatch(Node * node,Node * callee,Candidate const & candidate,Node ** if_successes,Node ** calls,Node ** inputs,int input_count)320 bool JSInliningHeuristic::TryReuseDispatch(Node* node, Node* callee,
321 Candidate const& candidate,
322 Node** if_successes, Node** calls,
323 Node** inputs, int input_count) {
324 // We will try to reuse the control flow branch created for computing
325 // the {callee} target of the call. We only reuse the branch if there
326 // is no side-effect between the call and the branch, and if the callee is
327 // only used as the target (and possibly also in the related frame states).
328
329 int const num_calls = candidate.num_functions;
330
331 DCHECK_EQ(IrOpcode::kPhi, callee->opcode());
332 DCHECK_EQ(num_calls, callee->op()->ValueInputCount());
333
334 // We are trying to match the following pattern:
335 //
336 // C1 C2
337 // . .
338 // | |
339 // Merge(merge) <-----------------+
340 // ^ ^ |
341 // V1 V2 | | E1 E2 |
342 // . . | +----+ . . |
343 // | | | | | | |
344 // Phi(callee) EffectPhi(effect_phi) |
345 // ^ ^ |
346 // | | |
347 // +----+ | |
348 // | | | |
349 // | StateValues | |
350 // | ^ | |
351 // +----+ | | |
352 // | | | | |
353 // | FrameState | |
354 // | ^ | |
355 // | | | +---+
356 // | | | | |
357 // +----+ Checkpoint(checkpoint) |
358 // | | ^ |
359 // | StateValues | +-------------+
360 // | | | |
361 // +-----+ | | |
362 // | | | | |
363 // | FrameState | |
364 // | ^ | |
365 // +-----------+ | | |
366 // Call(node)
367 // |
368 // |
369 // .
370 //
371 // The {callee} here is a phi that merges the possible call targets, {node}
372 // is the actual call that we will try to duplicate and connect to the
373 // control that comes into {merge}. There can be a {checkpoint} between
374 // the call and the calle phi.
375 //
376 // The idea is to get rid of the merge, effect phi and phi, then duplicate
377 // the call (with all the frame states and such), and connect the duplicated
378 // calls and states directly to the inputs of the ex-phi, ex-effect-phi and
379 // ex-merge. The tricky part is to make sure that there is no interference
380 // from the outside. In particular, there should not be any unaccounted uses
381 // of the phi, effect-phi and merge because we will remove them from
382 // the graph.
383 //
384 // V1 E1 C1 V2 E2 C2
385 // . . . . . .
386 // | | | | | |
387 // +----+ | | +----+ |
388 // | | | | | | |
389 // | StateValues | | | StateValues |
390 // | ^ | | | ^ |
391 // +----+ | | | +----+ | |
392 // | | | | | | | | |
393 // | FrameState | | | FrameState |
394 // | ^ | | | ^ |
395 // | | | | | | |
396 // | | | | | | |
397 // +----+ Checkpoint | +----+ Checkpoint |
398 // | | ^ | | | ^ |
399 // | StateValues | | | StateValues | |
400 // | | | | | | | |
401 // +-----+ | | | +-----+ | | |
402 // | | | | | | | | | |
403 // | FrameState | | | FrameState | |
404 // | ^ | | | ^ | |
405 // +-------------+| | | +-------------+| | |
406 // Call----+ Call----+
407 // | |
408 // +-------+ +------------+
409 // | |
410 // Merge
411 // EffectPhi
412 // Phi
413 // |
414 // ...
415
416 // If there is a control node between the callee computation
417 // and the call, bail out.
418 Node* merge = NodeProperties::GetControlInput(callee);
419 if (NodeProperties::GetControlInput(node) != merge) return false;
420
421 // If there is a non-checkpoint effect node between the callee computation
422 // and the call, bail out. We will drop any checkpoint between the call and
423 // the callee phi because the callee computation should have its own
424 // checkpoint that the call can fall back to.
425 Node* checkpoint = nullptr;
426 Node* effect = NodeProperties::GetEffectInput(node);
427 if (effect->opcode() == IrOpcode::kCheckpoint) {
428 checkpoint = effect;
429 if (NodeProperties::GetControlInput(checkpoint) != merge) return false;
430 effect = NodeProperties::GetEffectInput(effect);
431 }
432 if (effect->opcode() != IrOpcode::kEffectPhi) return false;
433 if (NodeProperties::GetControlInput(effect) != merge) return false;
434 Node* effect_phi = effect;
435
436 // The effect phi, the callee, the call and the checkpoint must be the only
437 // users of the merge.
438 for (Node* merge_use : merge->uses()) {
439 if (merge_use != effect_phi && merge_use != callee && merge_use != node &&
440 merge_use != checkpoint) {
441 return false;
442 }
443 }
444
445 // The effect phi must be only used by the checkpoint or the call.
446 for (Node* effect_phi_use : effect_phi->uses()) {
447 if (effect_phi_use != node && effect_phi_use != checkpoint) return false;
448 }
449
450 // We must replace the callee phi with the appropriate constant in
451 // the entire subgraph reachable by inputs from the call (terminating
452 // at phis and merges). Since we do not want to walk (and later duplicate)
453 // the subgraph here, we limit the possible uses to this set:
454 //
455 // 1. In the call (as a target).
456 // 2. The checkpoint between the call and the callee computation merge.
457 // 3. The lazy deoptimization frame state.
458 //
459 // This corresponds to the most common pattern, where the function is
460 // called with only local variables or constants as arguments.
461 //
462 // To check the uses, we first collect all the occurrences of callee in 1, 2
463 // and 3, and then we check that all uses of callee are in the collected
464 // occurrences. If there is an unaccounted use, we do not try to rewire
465 // the control flow.
466 //
467 // Note: With CFG, this would be much easier and more robust - we would just
468 // duplicate all the nodes between the merge and the call, replacing all
469 // occurrences of the {callee} phi with the appropriate constant.
470
471 // First compute the set of uses that are only reachable from 2 and 3.
472 const size_t kMaxUses = 8;
473 NodeAndIndex replaceable_uses[kMaxUses];
474 size_t replaceable_uses_count = 0;
475
476 // Collect the uses to check case 2.
477 Node* checkpoint_state = nullptr;
478 if (checkpoint) {
479 checkpoint_state = checkpoint->InputAt(0);
480 if (!CollectFrameStateUniqueUses(callee, checkpoint_state, replaceable_uses,
481 &replaceable_uses_count, kMaxUses)) {
482 return false;
483 }
484 }
485
486 // Collect the uses to check case 3.
487 Node* frame_state = NodeProperties::GetFrameStateInput(node);
488 if (!CollectFrameStateUniqueUses(callee, frame_state, replaceable_uses,
489 &replaceable_uses_count, kMaxUses)) {
490 return false;
491 }
492
493 // Bail out if there is a use of {callee} that is not reachable from 1, 2
494 // and 3.
495 for (Edge edge : callee->use_edges()) {
496 // Case 1 (use by the call as a target).
497 if (edge.from() == node && edge.index() == 0) continue;
498 // Case 2 and 3 - used in checkpoint and/or lazy deopt frame states.
499 bool found = false;
500 for (size_t i = 0; i < replaceable_uses_count; i++) {
501 if (replaceable_uses[i].node == edge.from() &&
502 replaceable_uses[i].index == edge.index()) {
503 found = true;
504 break;
505 }
506 }
507 if (!found) return false;
508 }
509
510 // Clone the call and the framestate, including the uniquely reachable
511 // state values, making sure that we replace the phi with the constant.
512 for (int i = 0; i < num_calls; ++i) {
513 // Clone the calls for each branch.
514 // We need to specialize the calls to the correct target, effect, and
515 // control. We also need to duplicate the checkpoint and the lazy
516 // frame state, and change all the uses of the callee to the constant
517 // callee.
518 Node* target = callee->InputAt(i);
519 Node* effect = effect_phi->InputAt(i);
520 Node* control = merge->InputAt(i);
521
522 if (checkpoint) {
523 // Duplicate the checkpoint.
524 Node* new_checkpoint_state = DuplicateFrameStateAndRename(
525 checkpoint_state, callee, target,
526 (i == num_calls - 1) ? kChangeInPlace : kCloneState);
527 effect = graph()->NewNode(checkpoint->op(), new_checkpoint_state, effect,
528 control);
529 }
530
531 // Duplicate the call.
532 Node* new_lazy_frame_state = DuplicateFrameStateAndRename(
533 frame_state, callee, target,
534 (i == num_calls - 1) ? kChangeInPlace : kCloneState);
535 inputs[0] = target;
536 inputs[input_count - 3] = new_lazy_frame_state;
537 inputs[input_count - 2] = effect;
538 inputs[input_count - 1] = control;
539 calls[i] = if_successes[i] =
540 graph()->NewNode(node->op(), input_count, inputs);
541 }
542
543 // Mark the control inputs dead, so that we can kill the merge.
544 node->ReplaceInput(input_count - 1, jsgraph()->Dead());
545 callee->ReplaceInput(num_calls, jsgraph()->Dead());
546 effect_phi->ReplaceInput(num_calls, jsgraph()->Dead());
547 if (checkpoint) {
548 checkpoint->ReplaceInput(2, jsgraph()->Dead());
549 }
550
551 merge->Kill();
552 return true;
553 }
554
CreateOrReuseDispatch(Node * node,Node * callee,Candidate const & candidate,Node ** if_successes,Node ** calls,Node ** inputs,int input_count)555 void JSInliningHeuristic::CreateOrReuseDispatch(Node* node, Node* callee,
556 Candidate const& candidate,
557 Node** if_successes,
558 Node** calls, Node** inputs,
559 int input_count) {
560 SourcePositionTable::Scope position(
561 source_positions_, source_positions_->GetSourcePosition(node));
562 if (TryReuseDispatch(node, callee, candidate, if_successes, calls, inputs,
563 input_count)) {
564 return;
565 }
566
567 Node* fallthrough_control = NodeProperties::GetControlInput(node);
568 int const num_calls = candidate.num_functions;
569
570 // Create the appropriate control flow to dispatch to the cloned calls.
571 for (int i = 0; i < num_calls; ++i) {
572 // TODO(2206): Make comparison be based on underlying SharedFunctionInfo
573 // instead of the target JSFunction reference directly.
574 Node* target = jsgraph()->HeapConstant(candidate.functions[i]);
575 if (i != (num_calls - 1)) {
576 Node* check =
577 graph()->NewNode(simplified()->ReferenceEqual(), callee, target);
578 Node* branch =
579 graph()->NewNode(common()->Branch(), check, fallthrough_control);
580 fallthrough_control = graph()->NewNode(common()->IfFalse(), branch);
581 if_successes[i] = graph()->NewNode(common()->IfTrue(), branch);
582 } else {
583 if_successes[i] = fallthrough_control;
584 }
585
586 // Clone the calls for each branch.
587 // The first input to the call is the actual target (which we specialize
588 // to the known {target}); the last input is the control dependency.
589 // We also specialize the new.target of JSConstruct {node}s if it refers
590 // to the same node as the {node}'s target input, so that we can later
591 // properly inline the JSCreate operations.
592 if (node->opcode() == IrOpcode::kJSConstruct && inputs[0] == inputs[1]) {
593 inputs[1] = target;
594 }
595 inputs[0] = target;
596 inputs[input_count - 1] = if_successes[i];
597 calls[i] = if_successes[i] =
598 graph()->NewNode(node->op(), input_count, inputs);
599 }
600 }
601
InlineCandidate(Candidate const & candidate,bool small_function)602 Reduction JSInliningHeuristic::InlineCandidate(Candidate const& candidate,
603 bool small_function) {
604 int const num_calls = candidate.num_functions;
605 Node* const node = candidate.node;
606 if (num_calls == 1) {
607 Handle<SharedFunctionInfo> shared =
608 candidate.functions[0].is_null()
609 ? candidate.shared_info
610 : handle(candidate.functions[0]->shared(), isolate());
611 Reduction const reduction = inliner_.ReduceJSCall(node);
612 if (reduction.Changed()) {
613 cumulative_count_ += shared->GetBytecodeArray()->length();
614 }
615 return reduction;
616 }
617
618 // Expand the JSCall/JSConstruct node to a subgraph first if
619 // we have multiple known target functions.
620 DCHECK_LT(1, num_calls);
621 Node* calls[kMaxCallPolymorphism + 1];
622 Node* if_successes[kMaxCallPolymorphism];
623 Node* callee = NodeProperties::GetValueInput(node, 0);
624
625 // Setup the inputs for the cloned call nodes.
626 int const input_count = node->InputCount();
627 Node** inputs = graph()->zone()->NewArray<Node*>(input_count);
628 for (int i = 0; i < input_count; ++i) {
629 inputs[i] = node->InputAt(i);
630 }
631
632 // Create the appropriate control flow to dispatch to the cloned calls.
633 CreateOrReuseDispatch(node, callee, candidate, if_successes, calls, inputs,
634 input_count);
635
636 // Check if we have an exception projection for the call {node}.
637 Node* if_exception = nullptr;
638 if (NodeProperties::IsExceptionalCall(node, &if_exception)) {
639 Node* if_exceptions[kMaxCallPolymorphism + 1];
640 for (int i = 0; i < num_calls; ++i) {
641 if_successes[i] = graph()->NewNode(common()->IfSuccess(), calls[i]);
642 if_exceptions[i] =
643 graph()->NewNode(common()->IfException(), calls[i], calls[i]);
644 }
645
646 // Morph the {if_exception} projection into a join.
647 Node* exception_control =
648 graph()->NewNode(common()->Merge(num_calls), num_calls, if_exceptions);
649 if_exceptions[num_calls] = exception_control;
650 Node* exception_effect = graph()->NewNode(common()->EffectPhi(num_calls),
651 num_calls + 1, if_exceptions);
652 Node* exception_value = graph()->NewNode(
653 common()->Phi(MachineRepresentation::kTagged, num_calls), num_calls + 1,
654 if_exceptions);
655 ReplaceWithValue(if_exception, exception_value, exception_effect,
656 exception_control);
657 }
658
659 // Morph the original call site into a join of the dispatched call sites.
660 Node* control =
661 graph()->NewNode(common()->Merge(num_calls), num_calls, if_successes);
662 calls[num_calls] = control;
663 Node* effect =
664 graph()->NewNode(common()->EffectPhi(num_calls), num_calls + 1, calls);
665 Node* value =
666 graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, num_calls),
667 num_calls + 1, calls);
668 ReplaceWithValue(node, value, effect, control);
669
670 // Inline the individual, cloned call sites.
671 for (int i = 0; i < num_calls; ++i) {
672 Handle<JSFunction> function = candidate.functions[i];
673 Node* node = calls[i];
674 if (small_function ||
675 (candidate.can_inline_function[i] &&
676 cumulative_count_ < FLAG_max_inlined_bytecode_size_cumulative)) {
677 Reduction const reduction = inliner_.ReduceJSCall(node);
678 if (reduction.Changed()) {
679 // Killing the call node is not strictly necessary, but it is safer to
680 // make sure we do not resurrect the node.
681 node->Kill();
682 cumulative_count_ += function->shared()->GetBytecodeArray()->length();
683 }
684 }
685 }
686
687 return Replace(value);
688 }
689
operator ()(const Candidate & left,const Candidate & right) const690 bool JSInliningHeuristic::CandidateCompare::operator()(
691 const Candidate& left, const Candidate& right) const {
692 if (right.frequency.IsUnknown()) {
693 if (left.frequency.IsUnknown()) {
694 // If left and right are both unknown then the ordering is indeterminate,
695 // which breaks strict weak ordering requirements, so we fall back to the
696 // node id as a tie breaker.
697 return left.node->id() > right.node->id();
698 }
699 return true;
700 } else if (left.frequency.IsUnknown()) {
701 return false;
702 } else if (left.frequency.value() > right.frequency.value()) {
703 return true;
704 } else if (left.frequency.value() < right.frequency.value()) {
705 return false;
706 } else {
707 return left.node->id() > right.node->id();
708 }
709 }
710
PrintCandidates()711 void JSInliningHeuristic::PrintCandidates() {
712 StdoutStream os;
713 os << "Candidates for inlining (size=" << candidates_.size() << "):\n";
714 for (const Candidate& candidate : candidates_) {
715 os << " #" << candidate.node->id() << ":"
716 << candidate.node->op()->mnemonic()
717 << ", frequency: " << candidate.frequency << std::endl;
718 for (int i = 0; i < candidate.num_functions; ++i) {
719 Handle<SharedFunctionInfo> shared =
720 candidate.functions[i].is_null()
721 ? candidate.shared_info
722 : handle(candidate.functions[i]->shared(), isolate());
723 PrintF(" - size:%d, name: %s\n", shared->GetBytecodeArray()->length(),
724 shared->DebugName()->ToCString().get());
725 }
726 }
727 }
728
graph() const729 Graph* JSInliningHeuristic::graph() const { return jsgraph()->graph(); }
730
common() const731 CommonOperatorBuilder* JSInliningHeuristic::common() const {
732 return jsgraph()->common();
733 }
734
simplified() const735 SimplifiedOperatorBuilder* JSInliningHeuristic::simplified() const {
736 return jsgraph()->simplified();
737 }
738
739 #undef TRACE
740
741 } // namespace compiler
742 } // namespace internal
743 } // namespace v8
744