[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:
parent
a240df84a3
commit
e5edd66d07
@ -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)));
|
||||
|
@ -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)));
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -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.
|
||||
|
@ -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()
|
||||
|
@ -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)));
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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)));
|
||||
|
@ -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)));
|
||||
|
@ -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));
|
||||
|
||||
|
@ -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);");
|
||||
|
Loading…
Reference in New Issue
Block a user