From a61a6f999884781481a237297da8c5f56d92d67f Mon Sep 17 00:00:00 2001 From: bmeurer Date: Mon, 23 Nov 2015 05:33:48 -0800 Subject: [PATCH] [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} --- src/compiler/ast-graph-builder.cc | 10 ++- src/compiler/js-call-reducer.cc | 98 +++++++++++++++++++++++++++++ src/compiler/js-inlining.cc | 1 - src/compiler/operator-properties.cc | 6 +- 4 files changed, 108 insertions(+), 7 deletions(-) diff --git a/src/compiler/ast-graph-builder.cc b/src/compiler/ast-graph-builder.cc index 0afd13d3f5..0128e4622d 100644 --- a/src/compiler/ast-graph-builder.cc +++ b/src/compiler/ast-graph-builder.cc @@ -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* 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); } diff --git a/src/compiler/js-call-reducer.cc b/src/compiler/js-call-reducer.cc index f6a818e54c..98a9b6e694 100644 --- a/src/compiler/js-call-reducer.cc +++ b/src/compiler/js-call-reducer.cc @@ -329,6 +329,10 @@ Reduction JSCallReducer::ReduceJSCallConstruct(Node* node) { int const arity = static_cast(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 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 site = Handle::cast(feedback); + + // Retrieve the Array function from the {node}. + Node* array_function; + Handle 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 cell = Handle::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(); } diff --git a/src/compiler/js-inlining.cc b/src/compiler/js-inlining.cc index c2cdf5f6dd..63f7d1f470 100644 --- a/src/compiler/js-inlining.cc +++ b/src/compiler/js-inlining.cc @@ -55,7 +55,6 @@ class JSCallAccessor { } Node* frame_state_before() { - DCHECK_EQ(IrOpcode::kJSCallFunction, call_->opcode()); return NodeProperties::GetFrameStateInput(call_, 1); } diff --git a/src/compiler/operator-properties.cc b/src/compiler/operator-properties.cc index e066481c21..d0ad330fcc 100644 --- a/src/compiler/operator-properties.cc +++ b/src/compiler/operator-properties.cc @@ -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: