[turbofan] Move JSCallFunction specialization to JSCallReducer.

This is the first part to refactoring the JSNativeContextSpecialization
class, which has grown way too big recently.

Also don't collect cross context feedback for the CallIC in general.
Neither TurboFan nor Crankshaft can make any use of cross context
JSFunction feedback that is collected by the CallIC, so there's no
point in gathering that feedback at all (it just complicates the
checking that is necessary in the compilers). What we should do
instead at some point (when Crankshaft becomes less important) is
to collect the SharedFunctionInfo as feedback for those cases.

R=yangguo@chromium.org
BUG=v8:4470
LOG=n

Review URL: https://codereview.chromium.org/1451273002

Cr-Commit-Position: refs/heads/master@{#32022}
This commit is contained in:
bmeurer 2015-11-16 23:14:46 -08:00 committed by Commit bot
parent a240df84a3
commit e5edd66d07
15 changed files with 187 additions and 79 deletions

View File

@ -2559,6 +2559,14 @@ void CallICStub::Generate(MacroAssembler* masm) {
__ cmp(r1, r4); __ cmp(r1, r4);
__ b(eq, &miss); __ b(eq, &miss);
// Make sure the function belongs to the same native context (which implies
// the same global object).
__ ldr(r4, FieldMemOperand(r1, JSFunction::kContextOffset));
__ ldr(r4, ContextOperand(r4, Context::GLOBAL_OBJECT_INDEX));
__ ldr(ip, GlobalObjectOperand());
__ cmp(r4, ip);
__ b(ne, &miss);
// Update stats. // Update stats.
__ ldr(r4, FieldMemOperand(r2, with_types_offset)); __ ldr(r4, FieldMemOperand(r2, with_types_offset));
__ add(r4, r4, Operand(Smi::FromInt(1))); __ add(r4, r4, Operand(Smi::FromInt(1)));

View File

@ -2945,6 +2945,14 @@ void CallICStub::Generate(MacroAssembler* masm) {
__ Cmp(function, x5); __ Cmp(function, x5);
__ B(eq, &miss); __ B(eq, &miss);
// Make sure the function belongs to the same native context (which implies
// the same global object).
__ Ldr(x4, FieldMemOperand(function, JSFunction::kContextOffset));
__ Ldr(x4, ContextMemOperand(x4, Context::GLOBAL_OBJECT_INDEX));
__ Ldr(x4, GlobalObjectMemOperand());
__ Cmp(x4, x5);
__ B(ne, &miss);
// Update stats. // Update stats.
__ Ldr(x4, FieldMemOperand(feedback_vector, with_types_offset)); __ Ldr(x4, FieldMemOperand(feedback_vector, with_types_offset));
__ Adds(x4, x4, Operand(Smi::FromInt(1))); __ Adds(x4, x4, Operand(Smi::FromInt(1)));

View File

@ -6,6 +6,7 @@
#include "src/compiler/js-graph.h" #include "src/compiler/js-graph.h"
#include "src/compiler/node-matchers.h" #include "src/compiler/node-matchers.h"
#include "src/compiler/simplified-operator.h"
#include "src/objects-inl.h" #include "src/objects-inl.h"
#include "src/type-feedback-vector-inl.h" #include "src/type-feedback-vector-inl.h"
@ -38,23 +39,12 @@ VectorSlotPair CallCountFeedback(VectorSlotPair p) {
Reduction JSCallReducer::Reduce(Node* node) { Reduction JSCallReducer::Reduce(Node* node) {
if (node->opcode() == IrOpcode::kJSCallFunction) { switch (node->opcode()) {
HeapObjectMatcher m(node->InputAt(0)); case IrOpcode::kJSCallFunction:
if (m.HasValue() && m.Value()->IsJSFunction()) { return ReduceJSCallFunction(node);
Handle<SharedFunctionInfo> shared(
Handle<JSFunction>::cast(m.Value())->shared(), isolate());
if (shared->HasBuiltinFunctionId()) {
switch (shared->builtin_function_id()) {
case kFunctionApply:
return ReduceFunctionPrototypeApply(node);
case kFunctionCall:
return ReduceFunctionPrototypeCall(node);
default: default:
break; break;
} }
}
}
}
return NoChange(); return NoChange();
} }
@ -133,7 +123,9 @@ Reduction JSCallReducer::ReduceFunctionPrototypeApply(Node* node) {
// to ensure any exception is thrown in the correct context. // to ensure any exception is thrown in the correct context.
NodeProperties::ReplaceContextInput( NodeProperties::ReplaceContextInput(
node, jsgraph()->HeapConstant(handle(apply->context(), isolate()))); node, jsgraph()->HeapConstant(handle(apply->context(), isolate())));
return Changed(node); // Try to further reduce the JSCallFunction {node}.
Reduction const reduction = ReduceJSCallFunction(node);
return reduction.Changed() ? reduction : Changed(node);
} }
@ -168,7 +160,88 @@ Reduction JSCallReducer::ReduceFunctionPrototypeCall(Node* node) {
node, javascript()->CallFunction(arity, p.language_mode(), node, javascript()->CallFunction(arity, p.language_mode(),
CallCountFeedback(p.feedback()), CallCountFeedback(p.feedback()),
convert_mode, p.tail_call_mode())); convert_mode, p.tail_call_mode()));
// Try to further reduce the JSCallFunction {node}.
Reduction const reduction = ReduceJSCallFunction(node);
return reduction.Changed() ? reduction : Changed(node);
}
Reduction JSCallReducer::ReduceJSCallFunction(Node* node) {
DCHECK_EQ(IrOpcode::kJSCallFunction, node->opcode());
CallFunctionParameters const& p = CallFunctionParametersOf(node->op());
Node* target = NodeProperties::GetValueInput(node, 0);
Node* frame_state = NodeProperties::GetFrameStateInput(node, 1);
Node* control = NodeProperties::GetControlInput(node);
Node* effect = NodeProperties::GetEffectInput(node);
// Try to specialize JSCallFunction {node}s with constant {target}s.
HeapObjectMatcher m(target);
if (m.HasValue()) {
if (m.Value()->IsJSFunction()) {
Handle<SharedFunctionInfo> shared(
Handle<JSFunction>::cast(m.Value())->shared(), isolate());
// Raise a TypeError if the {target} is a "classConstructor".
if (IsClassConstructor(shared->kind())) {
NodeProperties::RemoveFrameStateInput(node, 0);
NodeProperties::RemoveValueInputs(node);
NodeProperties::ChangeOp(
node, javascript()->CallRuntime(
Runtime::kThrowConstructorNonCallableError, 0));
return Changed(node); return Changed(node);
}
// Check for known builtin functions.
if (shared->HasBuiltinFunctionId()) {
switch (shared->builtin_function_id()) {
case kFunctionApply:
return ReduceFunctionPrototypeApply(node);
case kFunctionCall:
return ReduceFunctionPrototypeCall(node);
default:
break;
}
}
}
// Don't mess with other {node}s that have a constant {target}.
// TODO(bmeurer): Also support optimizing bound functions and proxies here.
return NoChange();
}
// Not much we can do if deoptimization support is disabled.
if (!(flags() & kDeoptimizationEnabled)) return NoChange();
// Extract feedback from the {node} using the CallICNexus.
if (!p.feedback().IsValid()) return NoChange();
CallICNexus nexus(p.feedback().vector(), p.feedback().slot());
Handle<Object> feedback(nexus.GetFeedback(), isolate());
if (feedback->IsWeakCell()) {
Handle<WeakCell> cell = Handle<WeakCell>::cast(feedback);
if (cell->value()->IsJSFunction()) {
// Check that the {target} is still the {target_function}.
Node* target_function = jsgraph()->HeapConstant(
handle(JSFunction::cast(cell->value()), isolate()));
Node* check = graph()->NewNode(simplified()->ReferenceEqual(Type::Any()),
target, target_function);
Node* branch =
graph()->NewNode(common()->Branch(BranchHint::kTrue), check, control);
Node* if_false = graph()->NewNode(common()->IfFalse(), branch);
Node* deoptimize = graph()->NewNode(common()->Deoptimize(), frame_state,
effect, if_false);
// TODO(bmeurer): This should be on the AdvancedReducer somehow.
NodeProperties::MergeControlToEnd(graph(), common(), deoptimize);
control = graph()->NewNode(common()->IfTrue(), branch);
// Specialize the JSCallFunction node to the {target_function}.
NodeProperties::ReplaceValueInput(node, target_function, 0);
NodeProperties::ReplaceControlInput(node, control);
// Try to further reduce the JSCallFunction {node}.
Reduction const reduction = ReduceJSCallFunction(node);
return reduction.Changed() ? reduction : Changed(node);
}
}
return NoChange();
} }
@ -178,10 +251,20 @@ Graph* JSCallReducer::graph() const { return jsgraph()->graph(); }
Isolate* JSCallReducer::isolate() const { return jsgraph()->isolate(); } Isolate* JSCallReducer::isolate() const { return jsgraph()->isolate(); }
CommonOperatorBuilder* JSCallReducer::common() const {
return jsgraph()->common();
}
JSOperatorBuilder* JSCallReducer::javascript() const { JSOperatorBuilder* JSCallReducer::javascript() const {
return jsgraph()->javascript(); return jsgraph()->javascript();
} }
SimplifiedOperatorBuilder* JSCallReducer::simplified() const {
return jsgraph()->simplified();
}
} // namespace compiler } // namespace compiler
} // namespace internal } // namespace internal
} // namespace v8 } // namespace v8

View File

@ -5,6 +5,7 @@
#ifndef V8_COMPILER_JS_CALL_REDUCER_H_ #ifndef V8_COMPILER_JS_CALL_REDUCER_H_
#define V8_COMPILER_JS_CALL_REDUCER_H_ #define V8_COMPILER_JS_CALL_REDUCER_H_
#include "src/base/flags.h"
#include "src/compiler/graph-reducer.h" #include "src/compiler/graph-reducer.h"
namespace v8 { namespace v8 {
@ -12,30 +13,47 @@ namespace internal {
namespace compiler { namespace compiler {
// Forward declarations. // Forward declarations.
class CommonOperatorBuilder;
class JSGraph; class JSGraph;
class JSOperatorBuilder; class JSOperatorBuilder;
class SimplifiedOperatorBuilder;
// Performs strength reduction on {JSCallFunction} nodes, which might allow // Performs strength reduction on {JSCallFunction} nodes, which might allow
// inlining or other optimizations to be performed afterwards. // inlining or other optimizations to be performed afterwards.
class JSCallReducer final : public Reducer { class JSCallReducer final : public Reducer {
public: public:
explicit JSCallReducer(JSGraph* jsgraph) : jsgraph_(jsgraph) {} // Flags that control the mode of operation.
enum Flag {
kNoFlags = 0u,
kDeoptimizationEnabled = 1u << 0,
};
typedef base::Flags<Flag> Flags;
JSCallReducer(JSGraph* jsgraph, Flags flags)
: jsgraph_(jsgraph), flags_(flags) {}
Reduction Reduce(Node* node) final; Reduction Reduce(Node* node) final;
private: private:
Reduction ReduceFunctionPrototypeApply(Node* node); Reduction ReduceFunctionPrototypeApply(Node* node);
Reduction ReduceFunctionPrototypeCall(Node* node); Reduction ReduceFunctionPrototypeCall(Node* node);
Reduction ReduceJSCallFunction(Node* node);
Graph* graph() const; Graph* graph() const;
Flags flags() const { return flags_; }
JSGraph* jsgraph() const { return jsgraph_; } JSGraph* jsgraph() const { return jsgraph_; }
Isolate* isolate() const; Isolate* isolate() const;
CommonOperatorBuilder* common() const;
JSOperatorBuilder* javascript() const; JSOperatorBuilder* javascript() const;
SimplifiedOperatorBuilder* simplified() const;
JSGraph* const jsgraph_; JSGraph* const jsgraph_;
Flags const flags_;
}; };
DEFINE_OPERATORS_FOR_FLAGS(JSCallReducer::Flags)
} // namespace compiler } // namespace compiler
} // namespace internal } // namespace internal
} // namespace v8 } // namespace v8

View File

@ -38,8 +38,6 @@ JSNativeContextSpecialization::JSNativeContextSpecialization(
Reduction JSNativeContextSpecialization::Reduce(Node* node) { Reduction JSNativeContextSpecialization::Reduce(Node* node) {
switch (node->opcode()) { switch (node->opcode()) {
case IrOpcode::kJSCallFunction:
return ReduceJSCallFunction(node);
case IrOpcode::kJSLoadNamed: case IrOpcode::kJSLoadNamed:
return ReduceJSLoadNamed(node); return ReduceJSLoadNamed(node);
case IrOpcode::kJSStoreNamed: case IrOpcode::kJSStoreNamed:
@ -55,56 +53,6 @@ Reduction JSNativeContextSpecialization::Reduce(Node* node) {
} }
Reduction JSNativeContextSpecialization::ReduceJSCallFunction(Node* node) {
DCHECK_EQ(IrOpcode::kJSCallFunction, node->opcode());
CallFunctionParameters const& p = CallFunctionParametersOf(node->op());
Node* target = NodeProperties::GetValueInput(node, 0);
Node* frame_state = NodeProperties::GetFrameStateInput(node, 1);
Node* control = NodeProperties::GetControlInput(node);
Node* effect = NodeProperties::GetEffectInput(node);
// Not much we can do if deoptimization support is disabled.
if (!(flags() & kDeoptimizationEnabled)) return NoChange();
// Don't mess with JSCallFunction nodes that have a constant {target}.
if (HeapObjectMatcher(target).HasValue()) return NoChange();
if (!p.feedback().IsValid()) return NoChange();
CallICNexus nexus(p.feedback().vector(), p.feedback().slot());
Handle<Object> feedback(nexus.GetFeedback(), isolate());
if (feedback->IsWeakCell()) {
Handle<WeakCell> cell = Handle<WeakCell>::cast(feedback);
if (cell->value()->IsJSFunction()) {
// Avoid cross-context leaks, meaning don't embed references to functions
// in other native contexts.
Handle<JSFunction> function(JSFunction::cast(cell->value()), isolate());
if (function->context()->native_context() != *native_context()) {
return NoChange();
}
// Check that the {target} is still the {target_function}.
Node* target_function = jsgraph()->HeapConstant(function);
Node* check = graph()->NewNode(simplified()->ReferenceEqual(Type::Any()),
target, target_function);
Node* branch =
graph()->NewNode(common()->Branch(BranchHint::kTrue), check, control);
Node* if_false = graph()->NewNode(common()->IfFalse(), branch);
Node* deoptimize = graph()->NewNode(common()->Deoptimize(), frame_state,
effect, if_false);
// TODO(bmeurer): This should be on the AdvancedReducer somehow.
NodeProperties::MergeControlToEnd(graph(), common(), deoptimize);
control = graph()->NewNode(common()->IfTrue(), branch);
// Specialize the JSCallFunction node to the {target_function}.
NodeProperties::ReplaceValueInput(node, target_function, 0);
NodeProperties::ReplaceControlInput(node, control);
return Changed(node);
}
// TODO(bmeurer): Also support optimizing bound functions and proxies here.
}
return NoChange();
}
Reduction JSNativeContextSpecialization::ReduceNamedAccess( Reduction JSNativeContextSpecialization::ReduceNamedAccess(
Node* node, Node* value, MapHandleList const& receiver_maps, Node* node, Node* value, MapHandleList const& receiver_maps,
Handle<Name> name, AccessMode access_mode, LanguageMode language_mode, Handle<Name> name, AccessMode access_mode, LanguageMode language_mode,

View File

@ -50,7 +50,6 @@ class JSNativeContextSpecialization final : public AdvancedReducer {
Reduction Reduce(Node* node) final; Reduction Reduce(Node* node) final;
private: private:
Reduction ReduceJSCallFunction(Node* node);
Reduction ReduceJSLoadNamed(Node* node); Reduction ReduceJSLoadNamed(Node* node);
Reduction ReduceJSStoreNamed(Node* node); Reduction ReduceJSStoreNamed(Node* node);
Reduction ReduceJSLoadProperty(Node* node); Reduction ReduceJSLoadProperty(Node* node);

View File

@ -177,6 +177,15 @@ void NodeProperties::RemoveNonValueInputs(Node* node) {
} }
// static
void NodeProperties::RemoveValueInputs(Node* node) {
int value_input_count = node->op()->ValueInputCount();
while (--value_input_count >= 0) {
node->RemoveInput(value_input_count);
}
}
void NodeProperties::MergeControlToEnd(Graph* graph, void NodeProperties::MergeControlToEnd(Graph* graph,
CommonOperatorBuilder* common, CommonOperatorBuilder* common,
Node* node) { Node* node) {

View File

@ -86,6 +86,7 @@ class NodeProperties final {
static void ReplaceFrameStateInput(Node* node, int index, Node* frame_state); static void ReplaceFrameStateInput(Node* node, int index, Node* frame_state);
static void RemoveFrameStateInput(Node* node, int index); static void RemoveFrameStateInput(Node* node, int index);
static void RemoveNonValueInputs(Node* node); static void RemoveNonValueInputs(Node* node);
static void RemoveValueInputs(Node* node);
// Merge the control node {node} into the end of the graph, introducing a // Merge the control node {node} into the end of the graph, introducing a
// merge node or expanding an existing merge node if necessary. // merge node or expanding an existing merge node if necessary.

View File

@ -538,7 +538,10 @@ struct InliningPhase {
data->common()); data->common());
CommonOperatorReducer common_reducer(&graph_reducer, data->graph(), CommonOperatorReducer common_reducer(&graph_reducer, data->graph(),
data->common(), data->machine()); data->common(), data->machine());
JSCallReducer call_reducer(data->jsgraph()); JSCallReducer call_reducer(data->jsgraph(),
data->info()->is_deoptimization_enabled()
? JSCallReducer::kDeoptimizationEnabled
: JSCallReducer::kNoFlags);
JSContextSpecialization context_specialization( JSContextSpecialization context_specialization(
&graph_reducer, data->jsgraph(), &graph_reducer, data->jsgraph(),
data->info()->is_function_context_specializing() data->info()->is_function_context_specializing()

View File

@ -2212,6 +2212,13 @@ void CallICStub::Generate(MacroAssembler* masm) {
__ cmp(edi, ecx); __ cmp(edi, ecx);
__ j(equal, &miss); __ j(equal, &miss);
// Make sure the function belongs to the same native context (which implies
// the same global object).
__ mov(ecx, FieldOperand(edi, JSFunction::kContextOffset));
__ mov(ecx, ContextOperand(ecx, Context::GLOBAL_OBJECT_INDEX));
__ cmp(ecx, GlobalObjectOperand());
__ j(not_equal, &miss);
// Update stats. // Update stats.
__ add(FieldOperand(ebx, with_types_offset), Immediate(Smi::FromInt(1))); __ add(FieldOperand(ebx, with_types_offset), Immediate(Smi::FromInt(1)));

View File

@ -2310,6 +2310,12 @@ void CallIC::HandleMiss(Handle<Object> function) {
if (array_function.is_identical_to(js_function)) { if (array_function.is_identical_to(js_function)) {
// Alter the slot. // Alter the slot.
nexus->ConfigureMonomorphicArray(); nexus->ConfigureMonomorphicArray();
} else if (js_function->context()->native_context() !=
*isolate()->native_context()) {
// Don't collect cross-native context feedback for the CallIC.
// TODO(bmeurer): We should collect the SharedFunctionInfo as
// feedback in this case instead.
nexus->ConfigureMegamorphic();
} else { } else {
nexus->ConfigureMonomorphic(js_function); nexus->ConfigureMonomorphic(js_function);
} }

View File

@ -2682,6 +2682,13 @@ void CallICStub::Generate(MacroAssembler* masm) {
__ LoadGlobalFunction(Context::ARRAY_FUNCTION_INDEX, t0); __ LoadGlobalFunction(Context::ARRAY_FUNCTION_INDEX, t0);
__ Branch(&miss, eq, a1, Operand(t0)); __ Branch(&miss, eq, a1, Operand(t0));
// Make sure the function belongs to the same native context (which implies
// the same global object).
__ lw(t0, FieldMemOperand(a1, JSFunction::kContextOffset));
__ lw(t0, ContextOperand(t0, Context::GLOBAL_OBJECT_INDEX));
__ lw(t1, GlobalObjectOperand());
__ Branch(&miss, ne, t0, Operand(t1));
// Update stats. // Update stats.
__ lw(t0, FieldMemOperand(a2, with_types_offset)); __ lw(t0, FieldMemOperand(a2, with_types_offset));
__ Addu(t0, t0, Operand(Smi::FromInt(1))); __ Addu(t0, t0, Operand(Smi::FromInt(1)));

View File

@ -2758,6 +2758,13 @@ void CallICStub::Generate(MacroAssembler* masm) {
__ LoadGlobalFunction(Context::ARRAY_FUNCTION_INDEX, a4); __ LoadGlobalFunction(Context::ARRAY_FUNCTION_INDEX, a4);
__ Branch(&miss, eq, a1, Operand(a4)); __ Branch(&miss, eq, a1, Operand(a4));
// Make sure the function belongs to the same native context (which implies
// the same global object).
__ ld(t0, FieldMemOperand(a1, JSFunction::kContextOffset));
__ ld(t0, ContextOperand(t0, Context::GLOBAL_OBJECT_INDEX));
__ ld(t1, GlobalObjectOperand());
__ Branch(&miss, ne, t0, Operand(t1));
// Update stats. // Update stats.
__ ld(a4, FieldMemOperand(a2, with_types_offset)); __ ld(a4, FieldMemOperand(a2, with_types_offset));
__ Daddu(a4, a4, Operand(Smi::FromInt(1))); __ Daddu(a4, a4, Operand(Smi::FromInt(1)));

View File

@ -2051,6 +2051,13 @@ void CallICStub::Generate(MacroAssembler* masm) {
__ cmpp(rdi, rcx); __ cmpp(rdi, rcx);
__ j(equal, &miss); __ j(equal, &miss);
// Make sure the function belongs to the same native context (which implies
// the same global object).
__ movp(rcx, FieldOperand(rdi, JSFunction::kContextOffset));
__ movp(rcx, ContextOperand(rcx, Context::GLOBAL_OBJECT_INDEX));
__ cmpp(rcx, GlobalObjectOperand());
__ j(not_equal, &miss);
// Update stats. // Update stats.
__ SmiAddConstant(FieldOperand(rbx, with_types_offset), Smi::FromInt(1)); __ SmiAddConstant(FieldOperand(rbx, with_types_offset), Smi::FromInt(1));

View File

@ -3674,19 +3674,16 @@ TEST(IncrementalMarkingPreservesMonomorphicCallIC) {
v8::Local<v8::Value> fun1, fun2; v8::Local<v8::Value> fun1, fun2;
v8::Local<v8::Context> ctx = CcTest::isolate()->GetCurrentContext(); v8::Local<v8::Context> ctx = CcTest::isolate()->GetCurrentContext();
{ {
LocalContext env;
CompileRun("function fun() {};"); CompileRun("function fun() {};");
fun1 = env->Global()->Get(env.local(), v8_str("fun")).ToLocalChecked(); fun1 = CcTest::global()->Get(ctx, v8_str("fun")).ToLocalChecked();
} }
{ {
LocalContext env;
CompileRun("function fun() {};"); CompileRun("function fun() {};");
fun2 = env->Global()->Get(env.local(), v8_str("fun")).ToLocalChecked(); fun2 = CcTest::global()->Get(ctx, v8_str("fun")).ToLocalChecked();
} }
// Prepare function f that contains type feedback for closures // Prepare function f that contains type feedback for the two closures.
// originating from two different native contexts.
CHECK(CcTest::global()->Set(ctx, v8_str("fun1"), fun1).FromJust()); CHECK(CcTest::global()->Set(ctx, v8_str("fun1"), fun1).FromJust());
CHECK(CcTest::global()->Set(ctx, v8_str("fun2"), fun2).FromJust()); CHECK(CcTest::global()->Set(ctx, v8_str("fun2"), fun2).FromJust());
CompileRun("function f(a, b) { a(); b(); } f(fun1, fun2);"); CompileRun("function f(a, b) { a(); b(); } f(fun1, fun2);");