[turbofan] Allow to consume feedback on CallConstruct.

Add an eager deoptimization location for JSCallConstruct and adapt the
JSCallReducer to consume target feedback for construction sites (only
applies to explicit new F(...args) not the super constructor calls).
Also recognize the new Array(...args) constructs with only target
feedback.

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

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

Cr-Commit-Position: refs/heads/master@{#32177}
This commit is contained in:
bmeurer 2015-11-23 05:33:48 -08:00 committed by Commit bot
parent 0da1a0c068
commit a61a6f9998
4 changed files with 108 additions and 7 deletions

View File

@ -2514,8 +2514,9 @@ void AstGraphBuilder::VisitCallSuper(Call* expr) {
// Create node to perform the super call.
const Operator* call =
javascript()->CallConstruct(args->length() + 2, VectorSlotPair());
FrameStateBeforeAndAfter states(this, super->new_target_var()->id());
Node* value = ProcessArguments(call, args->length() + 2);
PrepareFrameState(value, expr->id(), ast_context()->GetStateCombine());
states.AddToNode(value, expr->ReturnId(), OutputFrameStateCombine::Push());
ast_context()->ProduceValue(value);
}
@ -2527,6 +2528,11 @@ void AstGraphBuilder::VisitCallNew(CallNew* expr) {
ZoneList<Expression*>* args = expr->arguments();
VisitForValues(args);
// The baseline compiler doesn't push the new.target, so we need to record
// the frame state before the push.
FrameStateBeforeAndAfter states(
this, args->is_empty() ? expr->expression()->id() : args->last()->id());
// The new target is the same as the callee.
environment()->Push(environment()->Peek(args->length()));
@ -2535,7 +2541,7 @@ void AstGraphBuilder::VisitCallNew(CallNew* expr) {
const Operator* call =
javascript()->CallConstruct(args->length() + 2, feedback);
Node* value = ProcessArguments(call, args->length() + 2);
PrepareFrameState(value, expr->id(), ast_context()->GetStateCombine());
states.AddToNode(value, expr->ReturnId(), OutputFrameStateCombine::Push());
ast_context()->ProduceValue(value);
}

View File

@ -329,6 +329,10 @@ Reduction JSCallReducer::ReduceJSCallConstruct(Node* node) {
int const arity = static_cast<int>(p.arity() - 2);
Node* target = NodeProperties::GetValueInput(node, 0);
Node* new_target = NodeProperties::GetValueInput(node, arity + 1);
Node* context = NodeProperties::GetContextInput(node);
Node* frame_state = NodeProperties::GetFrameStateInput(node, 1);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
// Try to specialize JSCallConstruct {node}s with constant {target}s.
HeapObjectMatcher m(target);
@ -338,6 +342,11 @@ Reduction JSCallReducer::ReduceJSCallConstruct(Node* node) {
// Raise a TypeError if the {target} is not a constructor.
if (!function->IsConstructor()) {
// Drop the lazy bailout location and use the eager bailout point for
// the runtime function (actually as lazy bailout point). It doesn't
// really matter which bailout location we use since we never really
// go back after throwing the exception.
NodeProperties::RemoveFrameStateInput(node, 0);
NodeProperties::ReplaceValueInputs(node, target);
NodeProperties::ChangeOp(
node,
@ -358,6 +367,7 @@ Reduction JSCallReducer::ReduceJSCallConstruct(Node* node) {
}
// Turn the {node} into a {JSCreateArray} call.
NodeProperties::RemoveFrameStateInput(node, 1);
for (int i = arity; i > 0; --i) {
NodeProperties::ReplaceValueInput(
node, NodeProperties::GetValueInput(node, i), i + 1);
@ -373,6 +383,94 @@ Reduction JSCallReducer::ReduceJSCallConstruct(Node* node) {
return NoChange();
}
// Not much we can do if deoptimization support is disabled.
if (!(flags() & kDeoptimizationEnabled)) return NoChange();
// TODO(mvstanton): Use ConstructICNexus here, once available.
Handle<Object> feedback;
if (!p.feedback().IsValid()) return NoChange();
feedback = handle(p.feedback().vector()->Get(p.feedback().slot()), isolate());
if (feedback->IsAllocationSite()) {
// The feedback is an AllocationSite, which means we have called the
// Array function and collected transition (and pretenuring) feedback
// for the resulting arrays. This has to be kept in sync with the
// implementation of the CallConstructStub.
Handle<AllocationSite> site = Handle<AllocationSite>::cast(feedback);
// Retrieve the Array function from the {node}.
Node* array_function;
Handle<Context> native_context;
if (GetNativeContext(node).ToHandle(&native_context)) {
array_function = jsgraph()->HeapConstant(
handle(native_context->array_function(), isolate()));
} else {
Node* global_object = effect = graph()->NewNode(
javascript()->LoadContext(0, Context::GLOBAL_OBJECT_INDEX, true),
context, context, effect);
Node* native_context = effect = graph()->NewNode(
javascript()->LoadNativeContext(), global_object, context, effect);
array_function = effect = graph()->NewNode(
javascript()->LoadContext(0, Context::ARRAY_FUNCTION_INDEX, true),
native_context, native_context, effect);
}
// Check that the {target} is still the {array_function}.
Node* check = effect =
graph()->NewNode(javascript()->StrictEqual(), target, array_function,
context, effect, control);
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);
// Turn the {node} into a {JSCreateArray} call.
NodeProperties::ReplaceEffectInput(node, effect);
NodeProperties::ReplaceControlInput(node, control);
NodeProperties::RemoveFrameStateInput(node, 1);
for (int i = arity; i > 0; --i) {
NodeProperties::ReplaceValueInput(
node, NodeProperties::GetValueInput(node, i), i + 1);
}
NodeProperties::ReplaceValueInput(node, new_target, 1);
NodeProperties::ChangeOp(node, javascript()->CreateArray(arity, site));
return Changed(node);
} else if (feedback->IsWeakCell()) {
Handle<WeakCell> cell = Handle<WeakCell>::cast(feedback);
if (cell->value()->IsJSFunction()) {
Node* target_function =
jsgraph()->Constant(handle(cell->value(), isolate()));
// Check that the {target} is still the {target_function}.
Node* check = effect =
graph()->NewNode(javascript()->StrictEqual(), target, target_function,
context, effect, control);
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 JSCallConstruct node to the {target_function}.
NodeProperties::ReplaceValueInput(node, target_function, 0);
NodeProperties::ReplaceEffectInput(node, effect);
NodeProperties::ReplaceControlInput(node, control);
if (target == new_target) {
NodeProperties::ReplaceValueInput(node, target_function, arity + 1);
}
// Try to further reduce the JSCallConstruct {node}.
Reduction const reduction = ReduceJSCallConstruct(node);
return reduction.Changed() ? reduction : Changed(node);
}
}
return NoChange();
}

View File

@ -55,7 +55,6 @@ class JSCallAccessor {
}
Node* frame_state_before() {
DCHECK_EQ(IrOpcode::kJSCallFunction, call_->opcode());
return NodeProperties::GetFrameStateInput(call_, 1);
}

View File

@ -35,13 +35,11 @@ int OperatorProperties::GetFrameStateInputCount(const Operator* op) {
return 0;
// We record the frame state immediately before and immediately after every
// function call.
// construct/function call.
case IrOpcode::kJSCallConstruct:
case IrOpcode::kJSCallFunction:
return 2;
// Construct calls
case IrOpcode::kJSCallConstruct:
// Compare operations
case IrOpcode::kJSEqual:
case IrOpcode::kJSNotEqual: