Chromium Code Reviews| Index: src/compiler/js-inlining-heuristic.cc |
| diff --git a/src/compiler/js-inlining-heuristic.cc b/src/compiler/js-inlining-heuristic.cc |
| index 6808c92e901e5be5e9e755b22ce59d1209cdc838..e741813845923a4f9688a169a986c9103bfc49e4 100644 |
| --- a/src/compiler/js-inlining-heuristic.cc |
| +++ b/src/compiler/js-inlining-heuristic.cc |
| @@ -5,81 +5,128 @@ |
| #include "src/compiler/js-inlining-heuristic.h" |
| #include "src/compilation-info.h" |
| +#include "src/compiler/common-operator.h" |
| #include "src/compiler/node-matchers.h" |
| +#include "src/compiler/simplified-operator.h" |
| #include "src/objects-inl.h" |
| namespace v8 { |
| namespace internal { |
| namespace compiler { |
| -Reduction JSInliningHeuristic::Reduce(Node* node) { |
| - if (!IrOpcode::IsInlineeOpcode(node->opcode())) return NoChange(); |
| +#define TRACE(...) \ |
| + do { \ |
| + if (FLAG_trace_turbo_inlining) PrintF(__VA_ARGS__); \ |
| + } while (false) |
| - // Check if we already saw that {node} before, and if so, just skip it. |
| - if (seen_.find(node->id()) != seen_.end()) return NoChange(); |
| - seen_.insert(node->id()); |
| +namespace { |
| - Node* callee = node->InputAt(0); |
| - HeapObjectMatcher match(callee); |
| - if (!match.HasValue() || !match.Value()->IsJSFunction()) return NoChange(); |
| - Handle<JSFunction> function = Handle<JSFunction>::cast(match.Value()); |
| - |
| - // Functions marked with %SetForceInlineFlag are immediately inlined. |
| - if (function->shared()->force_inline()) { |
| - return inliner_.ReduceJSCall(node, function); |
| +int CollectFunctions(Node* node, Handle<JSFunction>* functions, |
| + int functions_size) { |
| + DCHECK_NE(0u, functions_size); |
| + HeapObjectMatcher m(node); |
| + if (m.HasValue() && m.Value()->IsJSFunction()) { |
| + functions[0] = Handle<JSFunction>::cast(m.Value()); |
| + return 1; |
| } |
| - |
| - // Handling of special inlining modes right away: |
| - // - For restricted inlining: stop all handling at this point. |
| - // - For stressing inlining: immediately handle all functions. |
| - switch (mode_) { |
| - case kRestrictedInlining: |
| - return NoChange(); |
| - case kStressInlining: |
| - return inliner_.ReduceJSCall(node, function); |
| - case kGeneralInlining: |
| - break; |
| + if (m.IsPhi()) { |
| + int const value_input_count = m.node()->op()->ValueInputCount(); |
| + if (value_input_count > functions_size) return 0; |
| + for (int n = 0; n < value_input_count; ++n) { |
| + HeapObjectMatcher m(node->InputAt(n)); |
| + if (!m.HasValue() || !m.Value()->IsJSFunction()) return 0; |
| + functions[n] = Handle<JSFunction>::cast(m.Value()); |
| + } |
| + return value_input_count; |
| } |
| + return 0; |
| +} |
| - // --------------------------------------------------------------------------- |
| - // Everything below this line is part of the inlining heuristic. |
| - // --------------------------------------------------------------------------- |
| - |
| +bool CanInlineFunction(Handle<JSFunction> function) { |
| // Built-in functions are handled by the JSBuiltinReducer. |
| - if (function->shared()->HasBuiltinFunctionId()) return NoChange(); |
| + if (function->shared()->HasBuiltinFunctionId()) return false; |
| // Don't inline builtins. |
| - if (function->shared()->IsBuiltin()) return NoChange(); |
| + if (function->shared()->IsBuiltin()) return false; |
| // Quick check on source code length to avoid parsing large candidate. |
| if (function->shared()->SourceSize() > FLAG_max_inlined_source_size) { |
| - return NoChange(); |
| + return false; |
| } |
| // Quick check on the size of the AST to avoid parsing large candidate. |
| if (function->shared()->ast_node_count() > FLAG_max_inlined_nodes) { |
| + return false; |
| + } |
| + |
| + // Avoid inlining across the boundary of asm.js code. |
| + if (function->shared()->asm_function()) return false; |
| + return true; |
| +} |
| + |
| +} // namespace |
| + |
| +Reduction JSInliningHeuristic::Reduce(Node* node) { |
| + if (!IrOpcode::IsInlineeOpcode(node->opcode())) return NoChange(); |
| + |
| + // Check if we already saw that {node} before, and if so, just skip it. |
| + if (seen_.find(node->id()) != seen_.end()) return NoChange(); |
| + seen_.insert(node->id()); |
| + |
| + // Check if the {node} is an appropriate candidate for inlining. |
| + Node* callee = node->InputAt(0); |
| + Candidate candidate; |
| + candidate.node = node; |
| + candidate.num_functions = |
| + CollectFunctions(callee, candidate.functions, kMaxCallPolymorphism); |
| + if (candidate.num_functions == 0) { |
| + return NoChange(); |
| + } else if (candidate.num_functions > 1 && !FLAG_polymorphic_inlining) { |
| + TRACE( |
| + "Not considering call site #%d:%s, because polymorphic inlining " |
| + "is disabled\n", |
| + node->id(), node->op()->mnemonic()); |
| return NoChange(); |
| } |
| - // Avoid inlining within or across the boundary of asm.js code. |
| - if (info_->shared_info()->asm_function()) return NoChange(); |
| - if (function->shared()->asm_function()) return NoChange(); |
| + // Functions marked with %SetForceInlineFlag are immediately inlined. |
| + bool can_inline = false, force_inline = true; |
| + for (int i = 0; i < candidate.num_functions; ++i) { |
| + Handle<JSFunction> function = candidate.functions[i]; |
| + if (!function->shared()->force_inline()) { |
| + force_inline = false; |
| + } |
| + if (CanInlineFunction(function)) { |
| + can_inline = true; |
| + } |
| + } |
| + if (force_inline) return InlineCandidate(candidate); |
| + if (!can_inline) return NoChange(); |
| // Stop inlining once the maximum allowed level is reached. |
| int level = 0; |
| for (Node* frame_state = NodeProperties::GetFrameStateInput(node); |
| frame_state->opcode() == IrOpcode::kFrameState; |
| frame_state = NodeProperties::GetFrameStateInput(frame_state)) { |
| - if (++level > FLAG_max_inlining_levels) return NoChange(); |
| + FrameStateInfo const& frame_info = OpParameter<FrameStateInfo>(frame_state); |
| + if (frame_info.type() == FrameStateType::kJavaScriptFunction) { |
| + if (++level > FLAG_max_inlining_levels) { |
| + TRACE( |
| + "Not considering call site #%d:%s, because inlining depth " |
| + "%d exceeds maximum allowed level %d\n", |
| + node->id(), node->op()->mnemonic(), level, |
| + FLAG_max_inlining_levels); |
| + return NoChange(); |
| + } |
| + } |
| } |
| // Gather feedback on how often this call site has been hit before. |
| - int calls = -1; // Same default as CallICNexus::ExtractCallCount. |
| if (node->opcode() == IrOpcode::kJSCallFunction) { |
| CallFunctionParameters p = CallFunctionParametersOf(node->op()); |
| if (p.feedback().IsValid()) { |
| CallICNexus nexus(p.feedback().vector(), p.feedback().slot()); |
| - calls = nexus.ExtractCallCount(); |
| + candidate.calls = nexus.ExtractCallCount(); |
| } |
| } else { |
| DCHECK_EQ(IrOpcode::kJSCallConstruct, node->opcode()); |
| @@ -87,24 +134,30 @@ Reduction JSInliningHeuristic::Reduce(Node* node) { |
| if (p.feedback().IsValid()) { |
| int const extra_index = |
| p.feedback().vector()->GetIndex(p.feedback().slot()) + 1; |
| - Handle<Object> feedback_extra(p.feedback().vector()->get(extra_index), |
| - function->GetIsolate()); |
| + Object* feedback_extra = p.feedback().vector()->get(extra_index); |
| if (feedback_extra->IsSmi()) { |
| - calls = Handle<Smi>::cast(feedback_extra)->value(); |
| + candidate.calls = Smi::cast(feedback_extra)->value(); |
| } |
| } |
| } |
| - // --------------------------------------------------------------------------- |
| - // Everything above this line is part of the inlining heuristic. |
| - // --------------------------------------------------------------------------- |
| + // Handling of special inlining modes right away: |
| + // - For restricted inlining: stop all handling at this point. |
| + // - For stressing inlining: immediately handle all functions. |
| + switch (mode_) { |
| + case kRestrictedInlining: |
| + return NoChange(); |
| + case kStressInlining: |
| + return InlineCandidate(candidate); |
| + case kGeneralInlining: |
| + break; |
| + } |
| // In the general case we remember the candidate for later. |
| - candidates_.insert({function, node, calls}); |
| + candidates_.insert(candidate); |
| return NoChange(); |
| } |
| - |
| void JSInliningHeuristic::Finalize() { |
| if (candidates_.empty()) return; // Nothing to do without candidates. |
| if (FLAG_trace_turbo_inlining) PrintCandidates(); |
| @@ -120,15 +173,110 @@ void JSInliningHeuristic::Finalize() { |
| candidates_.erase(i); |
| // Make sure we don't try to inline dead candidate nodes. |
| if (!candidate.node->IsDead()) { |
| - Reduction r = inliner_.ReduceJSCall(candidate.node, candidate.function); |
| - if (r.Changed()) { |
| - cumulative_count_ += candidate.function->shared()->ast_node_count(); |
| - return; |
| - } |
| + Reduction const reduction = InlineCandidate(candidate); |
| + if (reduction.Changed()) return; |
| } |
| } |
| } |
| +Reduction JSInliningHeuristic::InlineCandidate(Candidate const& candidate) { |
| + int const num_calls = candidate.num_functions; |
| + Node* const node = candidate.node; |
| + if (num_calls == 1) { |
| + Handle<JSFunction> function = candidate.functions[0]; |
| + Reduction const reduction = inliner_.ReduceJSCall(node, function); |
| + if (reduction.Changed()) { |
| + cumulative_count_ += function->shared()->ast_node_count(); |
| + } |
| + return reduction; |
| + } |
| + |
| + // Expand the JSCallFunction/JSCallConstruct node to a subgraph first if |
| + // we have multiple known target functions. |
| + DCHECK_LT(1, num_calls); |
| + Node* calls[kMaxCallPolymorphism + 1]; |
| + Node* if_successes[kMaxCallPolymorphism]; |
| + Node* callee = NodeProperties::GetValueInput(node, 0); |
| + Node* control = NodeProperties::GetControlInput(node); |
| + |
| + // Setup the inputs for the cloned call nodes. |
| + int const input_count = node->InputCount(); |
| + Node** inputs = graph()->zone()->NewArray<Node*>(input_count); |
| + for (int i = 0; i < input_count; ++i) { |
| + inputs[i] = node->InputAt(i); |
| + } |
| + |
| + // Create the appropriate control flow to dispatch to the cloned calls. |
| + for (int i = 0; i < num_calls; ++i) { |
| + Node* target = jsgraph()->HeapConstant(candidate.functions[i]); |
| + if (i != (num_calls - 1)) { |
| + Node* check = |
| + graph()->NewNode(simplified()->ReferenceEqual(), callee, target); |
| + Node* branch = graph()->NewNode(common()->Branch(), check, control); |
| + control = graph()->NewNode(common()->IfFalse(), branch); |
| + if_successes[i] = graph()->NewNode(common()->IfTrue(), branch); |
| + } else { |
| + if_successes[i] = control; |
| + } |
| + |
| + // The first input to the call is the actual target (which we specialize |
| + // to the known {target}); the last input is the control dependency. |
| + inputs[0] = target; |
| + inputs[input_count - 1] = if_successes[i]; |
| + calls[i] = graph()->NewNode(node->op(), input_count, inputs); |
| + if_successes[i] = graph()->NewNode(common()->IfSuccess(), calls[i]); |
| + } |
| + |
| + // Check if we have an exception projection for the call {node}. |
| + Node* if_exception = nullptr; |
| + for (Edge const edge : node->use_edges()) { |
| + if (NodeProperties::IsControlEdge(edge) && |
| + edge.from()->opcode() == IrOpcode::kIfException) { |
| + if_exception = edge.from(); |
| + break; |
| + } |
| + } |
| + if (if_exception != nullptr) { |
| + // Morph the {if_exception} projection into a join. |
| + Node* if_exceptions[kMaxCallPolymorphism + 1]; |
| + for (int i = 0; i < num_calls; ++i) { |
| + if_exceptions[i] = |
| + graph()->NewNode(common()->IfException(), calls[i], calls[i]); |
| + } |
| + Node* control = |
| + 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
|
| + if_exceptions[num_calls] = control; |
| + Node* effect = graph()->NewNode(common()->EffectPhi(num_calls), |
| + num_calls + 1, if_exceptions); |
| + Node* value = graph()->NewNode( |
| + common()->Phi(MachineRepresentation::kTagged, num_calls), num_calls + 1, |
| + if_exceptions); |
| + ReplaceWithValue(if_exception, value, effect, control); |
| + } |
| + |
| + // Morph the call site into the dispatched call sites. |
| + control = |
| + graph()->NewNode(common()->Merge(num_calls), num_calls, if_successes); |
| + calls[num_calls] = control; |
| + Node* effect = |
| + graph()->NewNode(common()->EffectPhi(num_calls), num_calls + 1, calls); |
| + Node* value = |
| + graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, num_calls), |
| + num_calls + 1, calls); |
| + ReplaceWithValue(node, value, effect, control); |
| + |
| + // Inline the individual, cloned call sites. |
| + for (int i = 0; i < num_calls; ++i) { |
| + Handle<JSFunction> function = candidate.functions[i]; |
| + Node* node = calls[i]; |
| + Reduction const reduction = inliner_.ReduceJSCall(node, function); |
| + if (reduction.Changed()) { |
| + cumulative_count_ += function->shared()->ast_node_count(); |
| + } |
| + } |
| + |
| + return Replace(value); |
| +} |
| bool JSInliningHeuristic::CandidateCompare::operator()( |
| const Candidate& left, const Candidate& right) const { |
| @@ -138,18 +286,31 @@ bool JSInliningHeuristic::CandidateCompare::operator()( |
| return left.node < right.node; |
| } |
| - |
| void JSInliningHeuristic::PrintCandidates() { |
| PrintF("Candidates for inlining (size=%zu):\n", candidates_.size()); |
| for (const Candidate& candidate : candidates_) { |
| - PrintF(" id:%d, calls:%d, size[source]:%d, size[ast]:%d / %s\n", |
| - candidate.node->id(), candidate.calls, |
| - candidate.function->shared()->SourceSize(), |
| - candidate.function->shared()->ast_node_count(), |
| - candidate.function->shared()->DebugName()->ToCString().get()); |
| + PrintF(" #%d:%s, calls:%d\n", candidate.node->id(), |
| + candidate.node->op()->mnemonic(), candidate.calls); |
| + for (int i = 0; i < candidate.num_functions; ++i) { |
| + Handle<JSFunction> function = candidate.functions[i]; |
| + PrintF(" - size[source]:%d, size[ast]:%d, name: %s\n", |
| + function->shared()->SourceSize(), |
| + function->shared()->ast_node_count(), |
| + function->shared()->DebugName()->ToCString().get()); |
| + } |
| } |
| } |
| +Graph* JSInliningHeuristic::graph() const { return jsgraph()->graph(); } |
| + |
| +CommonOperatorBuilder* JSInliningHeuristic::common() const { |
| + return jsgraph()->common(); |
| +} |
| + |
| +SimplifiedOperatorBuilder* JSInliningHeuristic::simplified() const { |
| + return jsgraph()->simplified(); |
| +} |
| + |
| } // namespace compiler |
| } // namespace internal |
| } // namespace v8 |