 Chromium Code Reviews
 Chromium Code Reviews Issue 2325943002:
  [turbofan] Initial support for polymorphic inlining.  (Closed)
    
  
    Issue 2325943002:
  [turbofan] Initial support for polymorphic inlining.  (Closed) 
  | OLD | NEW | 
|---|---|
| 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 | 
| OLD | NEW |