[turbofan] Enable inlining based on SharedFunctionInfo.
This adapts the inlining logic to allow for inlining based solely on a statically known underlying SharedFunctionInfo instead of a concrete closure of the call target. In cases where the closure is known, its bound context is constant promoted just as before. In the new cases where only the SFI for an entire class of closures is known, we use the dynamic SSA-value of the bound context. R=bmeurer@chromium.org BUG=v8:2206 Review-Url: https://codereview.chromium.org/2626783003 Cr-Commit-Position: refs/heads/master@{#42968}
This commit is contained in:
parent
ec922ef6c7
commit
b628aba090
@ -22,7 +22,7 @@ namespace compiler {
|
||||
namespace {
|
||||
|
||||
int CollectFunctions(Node* node, Handle<JSFunction>* functions,
|
||||
int functions_size) {
|
||||
int functions_size, Handle<SharedFunctionInfo>& shared) {
|
||||
DCHECK_NE(0, functions_size);
|
||||
HeapObjectMatcher m(node);
|
||||
if (m.HasValue() && m.Value()->IsJSFunction()) {
|
||||
@ -39,23 +39,29 @@ int CollectFunctions(Node* node, Handle<JSFunction>* functions,
|
||||
}
|
||||
return value_input_count;
|
||||
}
|
||||
if (m.IsJSCreateClosure()) {
|
||||
CreateClosureParameters const& p = CreateClosureParametersOf(m.op());
|
||||
functions[0] = Handle<JSFunction>::null();
|
||||
shared = p.shared_info();
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool CanInlineFunction(Handle<JSFunction> function) {
|
||||
bool CanInlineFunction(Handle<SharedFunctionInfo> shared) {
|
||||
// Built-in functions are handled by the JSBuiltinReducer.
|
||||
if (function->shared()->HasBuiltinFunctionId()) return false;
|
||||
if (shared->HasBuiltinFunctionId()) return false;
|
||||
|
||||
// Only choose user code for inlining.
|
||||
if (!function->shared()->IsUserJavaScript()) return false;
|
||||
if (!shared->IsUserJavaScript()) 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) {
|
||||
if (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;
|
||||
if (shared->asm_function()) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -72,8 +78,8 @@ Reduction JSInliningHeuristic::Reduce(Node* node) {
|
||||
Node* callee = node->InputAt(0);
|
||||
Candidate candidate;
|
||||
candidate.node = node;
|
||||
candidate.num_functions =
|
||||
CollectFunctions(callee, candidate.functions, kMaxCallPolymorphism);
|
||||
candidate.num_functions = CollectFunctions(
|
||||
callee, candidate.functions, kMaxCallPolymorphism, candidate.shared_info);
|
||||
if (candidate.num_functions == 0) {
|
||||
return NoChange();
|
||||
} else if (candidate.num_functions > 1 && !FLAG_polymorphic_inlining) {
|
||||
@ -87,11 +93,14 @@ Reduction JSInliningHeuristic::Reduce(Node* node) {
|
||||
// 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()) {
|
||||
Handle<SharedFunctionInfo> shared =
|
||||
candidate.functions[i].is_null()
|
||||
? candidate.shared_info
|
||||
: handle(candidate.functions[i]->shared());
|
||||
if (!shared->force_inline()) {
|
||||
force_inline = false;
|
||||
}
|
||||
if (CanInlineFunction(function)) {
|
||||
if (CanInlineFunction(shared)) {
|
||||
can_inline = true;
|
||||
}
|
||||
}
|
||||
@ -167,10 +176,13 @@ 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);
|
||||
Handle<SharedFunctionInfo> shared =
|
||||
candidate.functions[0].is_null()
|
||||
? candidate.shared_info
|
||||
: handle(candidate.functions[0]->shared());
|
||||
Reduction const reduction = inliner_.ReduceJSCall(node);
|
||||
if (reduction.Changed()) {
|
||||
cumulative_count_ += function->shared()->ast_node_count();
|
||||
cumulative_count_ += shared->ast_node_count();
|
||||
}
|
||||
return reduction;
|
||||
}
|
||||
@ -192,6 +204,8 @@ Reduction JSInliningHeuristic::InlineCandidate(Candidate const& candidate) {
|
||||
|
||||
// Create the appropriate control flow to dispatch to the cloned calls.
|
||||
for (int i = 0; i < num_calls; ++i) {
|
||||
// TODO(2206): Make comparison be based on underlying SharedFunctionInfo
|
||||
// instead of the target JSFunction reference directly.
|
||||
Node* target = jsgraph()->HeapConstant(candidate.functions[i]);
|
||||
if (i != (num_calls - 1)) {
|
||||
Node* check =
|
||||
@ -255,7 +269,7 @@ Reduction JSInliningHeuristic::InlineCandidate(Candidate const& candidate) {
|
||||
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);
|
||||
Reduction const reduction = inliner_.ReduceJSCall(node);
|
||||
if (reduction.Changed()) {
|
||||
cumulative_count_ += function->shared()->ast_node_count();
|
||||
}
|
||||
@ -281,9 +295,12 @@ void JSInliningHeuristic::PrintCandidates() {
|
||||
PrintF(" #%d:%s, frequency:%g\n", candidate.node->id(),
|
||||
candidate.node->op()->mnemonic(), candidate.frequency);
|
||||
for (int i = 0; i < candidate.num_functions; ++i) {
|
||||
Handle<JSFunction> function = candidate.functions[i];
|
||||
PrintF(" - size:%d, name: %s\n", function->shared()->ast_node_count(),
|
||||
function->shared()->DebugName()->ToCString().get());
|
||||
Handle<SharedFunctionInfo> shared =
|
||||
candidate.functions[i].is_null()
|
||||
? candidate.shared_info
|
||||
: handle(candidate.functions[i]->shared());
|
||||
PrintF(" - size:%d, name: %s\n", shared->ast_node_count(),
|
||||
shared->DebugName()->ToCString().get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -37,6 +37,11 @@ class JSInliningHeuristic final : public AdvancedReducer {
|
||||
|
||||
struct Candidate {
|
||||
Handle<JSFunction> functions[kMaxCallPolymorphism];
|
||||
// TODO(2206): For now polymorphic inlining is treated orthogonally to
|
||||
// inlining based on SharedFunctionInfo. This should be unified and the
|
||||
// above array should be switched to SharedFunctionInfo instead. Currently
|
||||
// we use {num_functions == 1 && functions[0].is_null()} as an indicator.
|
||||
Handle<SharedFunctionInfo> shared_info;
|
||||
int num_functions;
|
||||
Node* node = nullptr; // The call site at which to inline.
|
||||
float frequency = 0.0f; // Relative frequency of this call site.
|
||||
|
@ -347,29 +347,120 @@ bool IsNonConstructible(Handle<SharedFunctionInfo> shared_info) {
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
Reduction JSInliner::Reduce(Node* node) {
|
||||
if (!IrOpcode::IsInlineeOpcode(node->opcode())) return NoChange();
|
||||
// Determines whether the call target of the given call {node} is statically
|
||||
// known and can be used as an inlining candidate. The {SharedFunctionInfo} of
|
||||
// the call target is provided (the exact closure might be unknown).
|
||||
bool JSInliner::DetermineCallTarget(
|
||||
Node* node, Handle<SharedFunctionInfo>& shared_info_out) {
|
||||
DCHECK(IrOpcode::IsInlineeOpcode(node->opcode()));
|
||||
HeapObjectMatcher match(node->InputAt(0));
|
||||
|
||||
// This reducer can handle both normal function calls as well a constructor
|
||||
// calls whenever the target is a constant function object, as follows:
|
||||
// - JSCall(target:constant, receiver, args...)
|
||||
// - JSConstruct(target:constant, args..., new.target)
|
||||
HeapObjectMatcher match(node->InputAt(0));
|
||||
if (!match.HasValue() || !match.Value()->IsJSFunction()) return NoChange();
|
||||
Handle<JSFunction> function = Handle<JSFunction>::cast(match.Value());
|
||||
if (match.HasValue() && match.Value()->IsJSFunction()) {
|
||||
Handle<JSFunction> function = Handle<JSFunction>::cast(match.Value());
|
||||
|
||||
return ReduceJSCall(node, function);
|
||||
// Disallow cross native-context inlining for now. This means that all parts
|
||||
// of the resulting code will operate on the same global object. This also
|
||||
// prevents cross context leaks, where we could inline functions from a
|
||||
// different context and hold on to that context (and closure) from the code
|
||||
// object.
|
||||
// TODO(turbofan): We might want to revisit this restriction later when we
|
||||
// have a need for this, and we know how to model different native contexts
|
||||
// in the same graph in a compositional way.
|
||||
if (function->context()->native_context() !=
|
||||
info_->context()->native_context()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
shared_info_out = handle(function->shared());
|
||||
return true;
|
||||
}
|
||||
|
||||
// This reducer can also handle calls where the target is statically known to
|
||||
// be the result of a closure instantiation operation, as follows:
|
||||
// - JSCall(JSCreateClosure[shared](context), receiver, args...)
|
||||
// - JSConstruct(JSCreateClosure[shared](context), args..., new.target)
|
||||
if (match.IsJSCreateClosure()) {
|
||||
CreateClosureParameters const& p = CreateClosureParametersOf(match.op());
|
||||
|
||||
// Disallow inlining in case the instantiation site was never run and hence
|
||||
// the vector cell does not contain a valid feedback vector for the call
|
||||
// target.
|
||||
// TODO(turbofan): We might consider to eagerly create the feedback vector
|
||||
// in such a case (in {DetermineCallContext} below) eventually.
|
||||
FeedbackVectorSlot slot = p.feedback().slot();
|
||||
Handle<Cell> cell(Cell::cast(p.feedback().vector()->Get(slot)));
|
||||
if (!cell->value()->IsTypeFeedbackVector()) return false;
|
||||
|
||||
shared_info_out = p.shared_info();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Reduction JSInliner::ReduceJSCall(Node* node, Handle<JSFunction> function) {
|
||||
// Determines statically known information about the call target (assuming that
|
||||
// the call target is known according to {DetermineCallTarget} above). The
|
||||
// following static information is provided:
|
||||
// - context : The context (as SSA value) bound by the call target.
|
||||
// - feedback_vector : The target is guaranteed to use this feedback vector.
|
||||
void JSInliner::DetermineCallContext(
|
||||
Node* node, Node*& context_out,
|
||||
Handle<TypeFeedbackVector>& feedback_vector_out) {
|
||||
DCHECK(IrOpcode::IsInlineeOpcode(node->opcode()));
|
||||
HeapObjectMatcher match(node->InputAt(0));
|
||||
|
||||
if (match.HasValue() && match.Value()->IsJSFunction()) {
|
||||
Handle<JSFunction> function = Handle<JSFunction>::cast(match.Value());
|
||||
|
||||
// If the target function was never invoked, its literals array might not
|
||||
// contain a feedback vector. We ensure at this point that it is created.
|
||||
JSFunction::EnsureLiterals(function);
|
||||
|
||||
// The inlinee specializes to the context from the JSFunction object.
|
||||
context_out = jsgraph()->Constant(handle(function->context()));
|
||||
feedback_vector_out = handle(function->feedback_vector());
|
||||
return;
|
||||
}
|
||||
|
||||
if (match.IsJSCreateClosure()) {
|
||||
CreateClosureParameters const& p = CreateClosureParametersOf(match.op());
|
||||
|
||||
// Load the feedback vector of the target by looking up its vector cell at
|
||||
// the instantiation site (we only decide to inline if it's populated).
|
||||
FeedbackVectorSlot slot = p.feedback().slot();
|
||||
Handle<Cell> cell(Cell::cast(p.feedback().vector()->Get(slot)));
|
||||
DCHECK(cell->value()->IsTypeFeedbackVector());
|
||||
|
||||
// The inlinee uses the locally provided context at instantiation.
|
||||
context_out = NodeProperties::GetContextInput(match.node());
|
||||
feedback_vector_out = handle(TypeFeedbackVector::cast(cell->value()));
|
||||
return;
|
||||
}
|
||||
|
||||
// Must succeed.
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
Reduction JSInliner::Reduce(Node* node) {
|
||||
if (!IrOpcode::IsInlineeOpcode(node->opcode())) return NoChange();
|
||||
return ReduceJSCall(node);
|
||||
}
|
||||
|
||||
Reduction JSInliner::ReduceJSCall(Node* node) {
|
||||
DCHECK(IrOpcode::IsInlineeOpcode(node->opcode()));
|
||||
Handle<SharedFunctionInfo> shared_info;
|
||||
JSCallAccessor call(node);
|
||||
Handle<SharedFunctionInfo> shared_info(function->shared());
|
||||
|
||||
// Determine the call target.
|
||||
if (!DetermineCallTarget(node, shared_info)) return NoChange();
|
||||
|
||||
// Inlining is only supported in the bytecode pipeline.
|
||||
if (!info_->is_optimizing_from_bytecode()) {
|
||||
TRACE("Inlining %s into %s is not supported in the deprecated pipeline\n",
|
||||
TRACE("Not inlining %s into %s due to use of the deprecated pipeline\n",
|
||||
shared_info->DebugName()->ToCString().get(),
|
||||
info_->shared_info()->DebugName()->ToCString().get());
|
||||
return NoChange();
|
||||
@ -410,22 +501,6 @@ Reduction JSInliner::ReduceJSCall(Node* node, Handle<JSFunction> function) {
|
||||
return NoChange();
|
||||
}
|
||||
|
||||
// Disallow cross native-context inlining for now. This means that all parts
|
||||
// of the resulting code will operate on the same global object.
|
||||
// This also prevents cross context leaks for asm.js code, where we could
|
||||
// inline functions from a different context and hold on to that context (and
|
||||
// closure) from the code object.
|
||||
// TODO(turbofan): We might want to revisit this restriction later when we
|
||||
// have a need for this, and we know how to model different native contexts
|
||||
// in the same graph in a compositional way.
|
||||
if (function->context()->native_context() !=
|
||||
info_->context()->native_context()) {
|
||||
TRACE("Not inlining %s into %s because of different native contexts\n",
|
||||
shared_info->DebugName()->ToCString().get(),
|
||||
info_->shared_info()->DebugName()->ToCString().get());
|
||||
return NoChange();
|
||||
}
|
||||
|
||||
// TODO(turbofan): TranslatedState::GetAdaptedArguments() currently relies on
|
||||
// not inlining recursive functions. We might want to relax that at some
|
||||
// point.
|
||||
@ -504,8 +579,10 @@ Reduction JSInliner::ReduceJSCall(Node* node, Handle<JSFunction> function) {
|
||||
shared_info->DebugName()->ToCString().get(),
|
||||
info_->shared_info()->DebugName()->ToCString().get());
|
||||
|
||||
// If function was lazily compiled, its literals array may not yet be set up.
|
||||
JSFunction::EnsureLiterals(function);
|
||||
// Determine the targets feedback vector and its context.
|
||||
Node* context;
|
||||
Handle<TypeFeedbackVector> feedback_vector;
|
||||
DetermineCallContext(node, context, feedback_vector);
|
||||
|
||||
// Create the subgraph for the inlinee.
|
||||
Node* start;
|
||||
@ -514,9 +591,8 @@ Reduction JSInliner::ReduceJSCall(Node* node, Handle<JSFunction> function) {
|
||||
// Run the BytecodeGraphBuilder to create the subgraph.
|
||||
Graph::SubgraphScope scope(graph());
|
||||
BytecodeGraphBuilder graph_builder(
|
||||
&zone, shared_info, handle(function->feedback_vector()),
|
||||
BailoutId::None(), jsgraph(), call.frequency(), source_positions_,
|
||||
inlining_id);
|
||||
&zone, shared_info, feedback_vector, BailoutId::None(), jsgraph(),
|
||||
call.frequency(), source_positions_, inlining_id);
|
||||
graph_builder.CreateGraph(false);
|
||||
|
||||
// Extract the inlinee start/end nodes.
|
||||
@ -593,12 +669,6 @@ Reduction JSInliner::ReduceJSCall(Node* node, Handle<JSFunction> function) {
|
||||
FrameStateType::kConstructStub, info.shared_info());
|
||||
}
|
||||
|
||||
// The inlinee specializes to the context from the JSFunction object.
|
||||
// TODO(turbofan): We might want to load the context from the JSFunction at
|
||||
// runtime in case we only know the SharedFunctionInfo once we have dynamic
|
||||
// type feedback in the compiler.
|
||||
Node* context = jsgraph()->Constant(handle(function->context()));
|
||||
|
||||
// Insert a JSConvertReceiver node for sloppy callees. Note that the context
|
||||
// passed into this node has to be the callees context (loaded above).
|
||||
if (node->opcode() == IrOpcode::kJSCall &&
|
||||
|
@ -36,7 +36,7 @@ class JSInliner final : public AdvancedReducer {
|
||||
|
||||
// Can be used by inlining heuristics or by testing code directly, without
|
||||
// using the above generic reducer interface of the inlining machinery.
|
||||
Reduction ReduceJSCall(Node* node, Handle<JSFunction> function);
|
||||
Reduction ReduceJSCall(Node* node);
|
||||
|
||||
private:
|
||||
CommonOperatorBuilder* common() const;
|
||||
@ -50,6 +50,11 @@ class JSInliner final : public AdvancedReducer {
|
||||
JSGraph* const jsgraph_;
|
||||
SourcePositionTable* const source_positions_;
|
||||
|
||||
bool DetermineCallTarget(Node* node,
|
||||
Handle<SharedFunctionInfo>& shared_info_out);
|
||||
void DetermineCallContext(Node* node, Node*& context_out,
|
||||
Handle<TypeFeedbackVector>& feedback_vector_out);
|
||||
|
||||
Node* CreateArtificialFrameState(Node* node, Node* outer_frame_state,
|
||||
int parameter_count,
|
||||
FrameStateType frame_state_type,
|
||||
|
@ -7078,9 +7078,6 @@ class SharedFunctionInfo: public HeapObject {
|
||||
// the entry itself is left in the map in order to proceed sharing literals.
|
||||
void EvictFromOptimizedCodeMap(Code* optimized_code, const char* reason);
|
||||
|
||||
static Handle<TypeFeedbackVector> FindOrCreateVector(
|
||||
Handle<SharedFunctionInfo> shared, Handle<Context> native_context);
|
||||
|
||||
// Add or update entry in the optimized code map for context-dependent code.
|
||||
static void AddToOptimizedCodeMap(Handle<SharedFunctionInfo> shared,
|
||||
Handle<Context> native_context,
|
||||
|
Loading…
Reference in New Issue
Block a user