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.h" 8#include "src/compiler/node-matchers.h" 9#include "src/objects-inl.h" 10 11namespace v8 { 12namespace internal { 13namespace compiler { 14 15Reduction JSInliningHeuristic::Reduce(Node* node) { 16 if (!IrOpcode::IsInlineeOpcode(node->opcode())) return NoChange(); 17 18 // Check if we already saw that {node} before, and if so, just skip it. 19 if (seen_.find(node->id()) != seen_.end()) return NoChange(); 20 seen_.insert(node->id()); 21 22 Node* callee = node->InputAt(0); 23 HeapObjectMatcher match(callee); 24 if (!match.HasValue() || !match.Value()->IsJSFunction()) return NoChange(); 25 Handle<JSFunction> function = Handle<JSFunction>::cast(match.Value()); 26 27 // Functions marked with %SetForceInlineFlag are immediately inlined. 28 if (function->shared()->force_inline()) { 29 return inliner_.ReduceJSCall(node, function); 30 } 31 32 // Handling of special inlining modes right away: 33 // - For restricted inlining: stop all handling at this point. 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(); 57 } 58 59 // Quick check on the size of the AST to avoid parsing large candidate. 60 if (function->shared()->ast_node_count() > FLAG_max_inlined_nodes) { 61 return NoChange(); 62 } 63 64 // Avoid inlining within or across the boundary of asm.js code. 65 if (info_->shared_info()->asm_function()) return NoChange(); 66 if (function->shared()->asm_function()) return NoChange(); 67 68 // Stop inlinining once the maximum allowed level is reached. 69 int level = 0; 70 for (Node* frame_state = NodeProperties::GetFrameStateInput(node, 0); 71 frame_state->opcode() == IrOpcode::kFrameState; 72 frame_state = NodeProperties::GetFrameStateInput(frame_state, 0)) { 73 if (++level > FLAG_max_inlining_levels) return NoChange(); 74 } 75 76 // 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) { 79 CallFunctionParameters p = CallFunctionParametersOf(node->op()); 80 if (p.feedback().IsValid()) { 81 CallICNexus nexus(p.feedback().vector(), p.feedback().slot()); 82 calls = nexus.ExtractCallCount(); 83 } 84 } else { 85 DCHECK_EQ(IrOpcode::kJSCallConstruct, node->opcode()); 86 CallConstructParameters p = CallConstructParametersOf(node->op()); 87 if (p.feedback().IsValid()) { 88 int const extra_index = 89 p.feedback().vector()->GetIndex(p.feedback().slot()) + 1; 90 Handle<Object> feedback_extra(p.feedback().vector()->get(extra_index), 91 function->GetIsolate()); 92 if (feedback_extra->IsSmi()) { 93 calls = Handle<Smi>::cast(feedback_extra)->value(); 94 } 95 } 96 } 97 98 // --------------------------------------------------------------------------- 99 // Everything above this line is part of the inlining heuristic. 100 // --------------------------------------------------------------------------- 101 102 // In the general case we remember the candidate for later. 103 candidates_.insert({function, node, calls}); 104 return NoChange(); 105} 106 107 108void JSInliningHeuristic::Finalize() { 109 if (candidates_.empty()) return; // Nothing to do without candidates. 110 if (FLAG_trace_turbo_inlining) PrintCandidates(); 111 112 // 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 114 // on things that aren't called very often. 115 // TODO(bmeurer): Use std::priority_queue instead of std::set here. 116 while (!candidates_.empty()) { 117 if (cumulative_count_ > FLAG_max_inlined_nodes_cumulative) return; 118 auto i = candidates_.begin(); 119 Candidate candidate = *i; 120 candidates_.erase(i); 121 // Make sure we don't try to inline dead candidate nodes. 122 if (!candidate.node->IsDead()) { 123 Reduction r = inliner_.ReduceJSCall(candidate.node, candidate.function); 124 if (r.Changed()) { 125 cumulative_count_ += candidate.function->shared()->ast_node_count(); 126 return; 127 } 128 } 129 } 130} 131 132 133bool JSInliningHeuristic::CandidateCompare::operator()( 134 const Candidate& left, const Candidate& right) const { 135 if (left.calls != right.calls) { 136 return left.calls > right.calls; 137 } 138 return left.node < right.node; 139} 140 141 142void JSInliningHeuristic::PrintCandidates() { 143 PrintF("Candidates for inlining (size=%zu):\n", candidates_.size()); 144 for (const Candidate& candidate : candidates_) { 145 PrintF(" id:%d, calls:%d, size[source]:%d, size[ast]:%d / %s\n", 146 candidate.node->id(), candidate.calls, 147 candidate.function->shared()->SourceSize(), 148 candidate.function->shared()->ast_node_count(), 149 candidate.function->shared()->DebugName()->ToCString().get()); 150 } 151} 152 153} // namespace compiler 154} // namespace internal 155} // namespace v8 156