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 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 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 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 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 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 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 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 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 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 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 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 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 690 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 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 729 Graph* JSInliningHeuristic::graph() const { return jsgraph()->graph(); } 730 731 CommonOperatorBuilder* JSInliningHeuristic::common() const { 732 return jsgraph()->common(); 733 } 734 735 SimplifiedOperatorBuilder* JSInliningHeuristic::simplified() const { 736 return jsgraph()->simplified(); 737 } 738 739 #undef TRACE 740 741 } // namespace compiler 742 } // namespace internal 743 } // namespace v8 744