Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(124)

Unified Diff: src/compiler/js-inlining-heuristic.cc

Issue 2325943002: [turbofan] Initial support for polymorphic inlining. (Closed)
Patch Set: Addressed feedback. Created 4 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « src/compiler/js-inlining-heuristic.h ('k') | src/compiler/pipeline.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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
« no previous file with comments | « src/compiler/js-inlining-heuristic.h ('k') | src/compiler/pipeline.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698