[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);
__ 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.
__ ldr(r4, FieldMemOperand(r2, with_types_offset));
__ add(r4, r4, Operand(Smi::FromInt(1)));

View File

@ -2945,6 +2945,14 @@ void CallICStub::Generate(MacroAssembler* masm) {
__ Cmp(function, x5);
__ 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.
__ Ldr(x4, FieldMemOperand(feedback_vector, with_types_offset));
__ Adds(x4, x4, Operand(Smi::FromInt(1)));

View File

@ -6,6 +6,7 @@
#include "src/compiler/js-graph.h"
#include "src/compiler/node-matchers.h"
#include "src/compiler/simplified-operator.h"
#include "src/objects-inl.h"
#include "src/type-feedback-vector-inl.h"
@ -38,22 +39,11 @@ VectorSlotPair CallCountFeedback(VectorSlotPair p) {
Reduction JSCallReducer::Reduce(Node* node) {
if (node->opcode() == IrOpcode::kJSCallFunction) {
HeapObjectMatcher m(node->InputAt(0));
if (m.HasValue() && m.Value()->IsJSFunction()) {
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:
break;
}
}
}
switch (node->opcode()) {
case IrOpcode::kJSCallFunction:
return ReduceJSCallFunction(node);
default:
break;
}
return NoChange();
}
@ -133,7 +123,9 @@ Reduction JSCallReducer::ReduceFunctionPrototypeApply(Node* node) {
// to ensure any exception is thrown in the correct context.
NodeProperties::ReplaceContextInput(
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(),
CallCountFeedback(p.feedback()),
convert_mode, p.tail_call_mode()));
return Changed(node);
// 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);
}
// 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(); }
CommonOperatorBuilder* JSCallReducer::common() const {
return jsgraph()->common();
}
JSOperatorBuilder* JSCallReducer::javascript() const {
return jsgraph()->javascript();
}
SimplifiedOperatorBuilder* JSCallReducer::simplified() const {
return jsgraph()->simplified();
}
} // namespace compiler
} // namespace internal
} // namespace v8

View File

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

View File

@ -38,8 +38,6 @@ JSNativeContextSpecialization::JSNativeContextSpecialization(
Reduction JSNativeContextSpecialization::Reduce(Node* node) {
switch (node->opcode()) {
case IrOpcode::kJSCallFunction:
return ReduceJSCallFunction(node);
case IrOpcode::kJSLoadNamed:
return ReduceJSLoadNamed(node);
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(
Node* node, Node* value, MapHandleList const& receiver_maps,
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;
private:
Reduction ReduceJSCallFunction(Node* node);
Reduction ReduceJSLoadNamed(Node* node);
Reduction ReduceJSStoreNamed(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,
CommonOperatorBuilder* common,
Node* node) {

View File

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

View File

@ -538,7 +538,10 @@ struct InliningPhase {
data->common());
CommonOperatorReducer common_reducer(&graph_reducer, data->graph(),
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(
&graph_reducer, data->jsgraph(),
data->info()->is_function_context_specializing()

View File

@ -2212,6 +2212,13 @@ void CallICStub::Generate(MacroAssembler* masm) {
__ cmp(edi, ecx);
__ 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.
__ 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)) {
// Alter the slot.
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 {
nexus->ConfigureMonomorphic(js_function);
}

View File

@ -2682,6 +2682,13 @@ void CallICStub::Generate(MacroAssembler* masm) {
__ LoadGlobalFunction(Context::ARRAY_FUNCTION_INDEX, 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.
__ lw(t0, FieldMemOperand(a2, with_types_offset));
__ Addu(t0, t0, Operand(Smi::FromInt(1)));

View File

@ -2758,6 +2758,13 @@ void CallICStub::Generate(MacroAssembler* masm) {
__ LoadGlobalFunction(Context::ARRAY_FUNCTION_INDEX, 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.
__ ld(a4, FieldMemOperand(a2, with_types_offset));
__ Daddu(a4, a4, Operand(Smi::FromInt(1)));

View File

@ -2051,6 +2051,13 @@ void CallICStub::Generate(MacroAssembler* masm) {
__ cmpp(rdi, rcx);
__ 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.
__ 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::Context> ctx = CcTest::isolate()->GetCurrentContext();
{
LocalContext env;
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() {};");
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
// originating from two different native contexts.
// Prepare function f that contains type feedback for the two closures.
CHECK(CcTest::global()->Set(ctx, v8_str("fun1"), fun1).FromJust());
CHECK(CcTest::global()->Set(ctx, v8_str("fun2"), fun2).FromJust());
CompileRun("function f(a, b) { a(); b(); } f(fun1, fun2);");