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