diff options
Diffstat (limited to 'deps/v8/src/compiler/js-call-reducer.cc')
-rw-r--r-- | deps/v8/src/compiler/js-call-reducer.cc | 557 |
1 files changed, 557 insertions, 0 deletions
diff --git a/deps/v8/src/compiler/js-call-reducer.cc b/deps/v8/src/compiler/js-call-reducer.cc new file mode 100644 index 0000000000..a15d6fd6fd --- /dev/null +++ b/deps/v8/src/compiler/js-call-reducer.cc @@ -0,0 +1,557 @@ +// Copyright 2015 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "src/compiler/js-call-reducer.h" + +#include "src/compiler/js-graph.h" +#include "src/compiler/node-matchers.h" +#include "src/objects-inl.h" +#include "src/type-feedback-vector-inl.h" + +namespace v8 { +namespace internal { +namespace compiler { + +namespace { + +VectorSlotPair CallCountFeedback(VectorSlotPair p) { + // Extract call count from {p}. + if (!p.IsValid()) return VectorSlotPair(); + CallICNexus n(p.vector(), p.slot()); + int const call_count = n.ExtractCallCount(); + if (call_count <= 0) return VectorSlotPair(); + + // Create megamorphic CallIC feedback with the given {call_count}. + StaticFeedbackVectorSpec spec; + FeedbackVectorSlot slot = spec.AddCallICSlot(); + Handle<TypeFeedbackMetadata> metadata = + TypeFeedbackMetadata::New(n.GetIsolate(), &spec); + Handle<TypeFeedbackVector> vector = + TypeFeedbackVector::New(n.GetIsolate(), metadata); + CallICNexus nexus(vector, slot); + nexus.ConfigureMegamorphic(call_count); + return VectorSlotPair(vector, slot); +} + +} // namespace + + +Reduction JSCallReducer::Reduce(Node* node) { + switch (node->opcode()) { + case IrOpcode::kJSCallConstruct: + return ReduceJSCallConstruct(node); + case IrOpcode::kJSCallFunction: + return ReduceJSCallFunction(node); + default: + break; + } + return NoChange(); +} + + +// ES6 section 22.1.1 The Array Constructor +Reduction JSCallReducer::ReduceArrayConstructor(Node* node) { + DCHECK_EQ(IrOpcode::kJSCallFunction, node->opcode()); + Node* target = NodeProperties::GetValueInput(node, 0); + CallFunctionParameters const& p = CallFunctionParametersOf(node->op()); + + // Check if we have an allocation site from the CallIC. + Handle<AllocationSite> site; + if (p.feedback().IsValid()) { + CallICNexus nexus(p.feedback().vector(), p.feedback().slot()); + Handle<Object> feedback(nexus.GetFeedback(), isolate()); + if (feedback->IsAllocationSite()) { + site = Handle<AllocationSite>::cast(feedback); + } + } + + // Turn the {node} into a {JSCreateArray} call. + DCHECK_LE(2u, p.arity()); + size_t const arity = p.arity() - 2; + NodeProperties::ReplaceValueInput(node, target, 0); + NodeProperties::ReplaceValueInput(node, target, 1); + NodeProperties::RemoveFrameStateInput(node, 1); + // TODO(bmeurer): We might need to propagate the tail call mode to + // the JSCreateArray operator, because an Array call in tail call + // position must always properly consume the parent stack frame. + NodeProperties::ChangeOp(node, javascript()->CreateArray(arity, site)); + return Changed(node); +} + + +// ES6 section 20.1.1 The Number Constructor +Reduction JSCallReducer::ReduceNumberConstructor(Node* node) { + DCHECK_EQ(IrOpcode::kJSCallFunction, node->opcode()); + CallFunctionParameters const& p = CallFunctionParametersOf(node->op()); + + // Turn the {node} into a {JSToNumber} call. + DCHECK_LE(2u, p.arity()); + Node* value = (p.arity() == 2) ? jsgraph()->ZeroConstant() + : NodeProperties::GetValueInput(node, 2); + NodeProperties::RemoveFrameStateInput(node, 1); + NodeProperties::ReplaceValueInputs(node, value); + NodeProperties::ChangeOp(node, javascript()->ToNumber()); + return Changed(node); +} + + +// ES6 section 19.2.3.1 Function.prototype.apply ( thisArg, argArray ) +Reduction JSCallReducer::ReduceFunctionPrototypeApply(Node* node) { + DCHECK_EQ(IrOpcode::kJSCallFunction, node->opcode()); + Node* target = NodeProperties::GetValueInput(node, 0); + CallFunctionParameters const& p = CallFunctionParametersOf(node->op()); + Handle<JSFunction> apply = + Handle<JSFunction>::cast(HeapObjectMatcher(target).Value()); + size_t arity = p.arity(); + DCHECK_LE(2u, arity); + ConvertReceiverMode convert_mode = ConvertReceiverMode::kAny; + if (arity == 2) { + // Neither thisArg nor argArray was provided. + convert_mode = ConvertReceiverMode::kNullOrUndefined; + node->ReplaceInput(0, node->InputAt(1)); + node->ReplaceInput(1, jsgraph()->UndefinedConstant()); + } else if (arity == 3) { + // The argArray was not provided, just remove the {target}. + node->RemoveInput(0); + --arity; + } else if (arity == 4) { + // Check if argArray is an arguments object, and {node} is the only value + // user of argArray (except for value uses in frame states). + Node* arg_array = NodeProperties::GetValueInput(node, 3); + if (arg_array->opcode() != IrOpcode::kJSCreateArguments) return NoChange(); + for (Edge edge : arg_array->use_edges()) { + if (edge.from()->opcode() == IrOpcode::kStateValues) continue; + if (!NodeProperties::IsValueEdge(edge)) continue; + if (edge.from() == node) continue; + return NoChange(); + } + // Get to the actual frame state from which to extract the arguments; + // we can only optimize this in case the {node} was already inlined into + // some other function (and same for the {arg_array}). + CreateArgumentsParameters const& p = + CreateArgumentsParametersOf(arg_array->op()); + Node* frame_state = NodeProperties::GetFrameStateInput(arg_array, 0); + Node* outer_state = frame_state->InputAt(kFrameStateOuterStateInput); + if (outer_state->opcode() != IrOpcode::kFrameState) return NoChange(); + FrameStateInfo outer_info = OpParameter<FrameStateInfo>(outer_state); + if (outer_info.type() == FrameStateType::kArgumentsAdaptor) { + // Need to take the parameters from the arguments adaptor. + frame_state = outer_state; + } + FrameStateInfo state_info = OpParameter<FrameStateInfo>(frame_state); + if (p.type() == CreateArgumentsParameters::kMappedArguments) { + // Mapped arguments (sloppy mode) cannot be handled if they are aliased. + Handle<SharedFunctionInfo> shared; + if (!state_info.shared_info().ToHandle(&shared)) return NoChange(); + if (shared->internal_formal_parameter_count() != 0) return NoChange(); + } + // Remove the argArray input from the {node}. + node->RemoveInput(static_cast<int>(--arity)); + // Add the actual parameters to the {node}, skipping the receiver. + Node* const parameters = frame_state->InputAt(kFrameStateParametersInput); + for (int i = p.start_index() + 1; i < state_info.parameter_count(); ++i) { + node->InsertInput(graph()->zone(), static_cast<int>(arity), + parameters->InputAt(i)); + ++arity; + } + // Drop the {target} from the {node}. + node->RemoveInput(0); + --arity; + } else { + return NoChange(); + } + // Change {node} to the new {JSCallFunction} operator. + NodeProperties::ChangeOp( + node, javascript()->CallFunction(arity, p.language_mode(), + CallCountFeedback(p.feedback()), + convert_mode, p.tail_call_mode())); + // Change context of {node} to the Function.prototype.apply context, + // to ensure any exception is thrown in the correct context. + NodeProperties::ReplaceContextInput( + node, jsgraph()->HeapConstant(handle(apply->context(), isolate()))); + // Try to further reduce the JSCallFunction {node}. + Reduction const reduction = ReduceJSCallFunction(node); + return reduction.Changed() ? reduction : Changed(node); +} + + +// ES6 section 19.2.3.3 Function.prototype.call (thisArg, ...args) +Reduction JSCallReducer::ReduceFunctionPrototypeCall(Node* node) { + DCHECK_EQ(IrOpcode::kJSCallFunction, node->opcode()); + CallFunctionParameters const& p = CallFunctionParametersOf(node->op()); + Handle<JSFunction> call = Handle<JSFunction>::cast( + HeapObjectMatcher(NodeProperties::GetValueInput(node, 0)).Value()); + // Change context of {node} to the Function.prototype.call context, + // to ensure any exception is thrown in the correct context. + NodeProperties::ReplaceContextInput( + node, jsgraph()->HeapConstant(handle(call->context(), isolate()))); + // Remove the target from {node} and use the receiver as target instead, and + // the thisArg becomes the new target. If thisArg was not provided, insert + // undefined instead. + size_t arity = p.arity(); + DCHECK_LE(2u, arity); + ConvertReceiverMode convert_mode; + if (arity == 2) { + // The thisArg was not provided, use undefined as receiver. + convert_mode = ConvertReceiverMode::kNullOrUndefined; + node->ReplaceInput(0, node->InputAt(1)); + node->ReplaceInput(1, jsgraph()->UndefinedConstant()); + } else { + // Just remove the target, which is the first value input. + convert_mode = ConvertReceiverMode::kAny; + node->RemoveInput(0); + --arity; + } + NodeProperties::ChangeOp( + node, javascript()->CallFunction(arity, p.language_mode(), + CallCountFeedback(p.feedback()), + 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* context = NodeProperties::GetContextInput(node); + 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<JSFunction> function = Handle<JSFunction>::cast(m.Value()); + Handle<SharedFunctionInfo> shared(function->shared(), isolate()); + + // Raise a TypeError if the {target} is a "classConstructor". + if (IsClassConstructor(shared->kind())) { + NodeProperties::RemoveFrameStateInput(node, 0); + NodeProperties::ReplaceValueInputs(node, target); + NodeProperties::ChangeOp( + node, javascript()->CallRuntime( + Runtime::kThrowConstructorNonCallableError, 1)); + 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; + } + } + + // Check for the Array constructor. + if (*function == function->native_context()->array_function()) { + return ReduceArrayConstructor(node); + } + + // Check for the Number constructor. + if (*function == function->native_context()->number_function()) { + return ReduceNumberConstructor(node); + } + } else if (m.Value()->IsJSBoundFunction()) { + Handle<JSBoundFunction> function = + Handle<JSBoundFunction>::cast(m.Value()); + Handle<JSReceiver> bound_target_function( + function->bound_target_function(), isolate()); + Handle<Object> bound_this(function->bound_this(), isolate()); + Handle<FixedArray> bound_arguments(function->bound_arguments(), + isolate()); + CallFunctionParameters const& p = CallFunctionParametersOf(node->op()); + ConvertReceiverMode const convert_mode = + (bound_this->IsNull() || bound_this->IsUndefined()) + ? ConvertReceiverMode::kNullOrUndefined + : ConvertReceiverMode::kNotNullOrUndefined; + size_t arity = p.arity(); + DCHECK_LE(2u, arity); + // Patch {node} to use [[BoundTargetFunction]] and [[BoundThis]]. + NodeProperties::ReplaceValueInput( + node, jsgraph()->Constant(bound_target_function), 0); + NodeProperties::ReplaceValueInput(node, jsgraph()->Constant(bound_this), + 1); + // Insert the [[BoundArguments]] for {node}. + for (int i = 0; i < bound_arguments->length(); ++i) { + node->InsertInput( + graph()->zone(), i + 2, + jsgraph()->Constant(handle(bound_arguments->get(i), isolate()))); + arity++; + } + NodeProperties::ChangeOp( + node, javascript()->CallFunction(arity, p.language_mode(), + CallCountFeedback(p.feedback()), + convert_mode, p.tail_call_mode())); + // Try to further reduce the JSCallFunction {node}. + Reduction const reduction = ReduceJSCallFunction(node); + return reduction.Changed() ? reduction : Changed(node); + } + + // Don't mess with other {node}s that have a constant {target}. + // TODO(bmeurer): Also support 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->IsAllocationSite()) { + // 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* native_context = effect = graph()->NewNode( + javascript()->LoadContext(0, Context::NATIVE_CONTEXT_INDEX, true), + context, 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(DeoptimizeKind::kEager), + 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::ReplaceValueInput(node, array_function, 0); + NodeProperties::ReplaceEffectInput(node, effect); + NodeProperties::ReplaceControlInput(node, control); + return ReduceArrayConstructor(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(DeoptimizeKind::kEager), + 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::ReplaceEffectInput(node, effect); + NodeProperties::ReplaceControlInput(node, control); + + // Try to further reduce the JSCallFunction {node}. + Reduction const reduction = ReduceJSCallFunction(node); + return reduction.Changed() ? reduction : Changed(node); + } + } + return NoChange(); +} + + +Reduction JSCallReducer::ReduceJSCallConstruct(Node* node) { + DCHECK_EQ(IrOpcode::kJSCallConstruct, node->opcode()); + CallConstructParameters const& p = CallConstructParametersOf(node->op()); + DCHECK_LE(2u, p.arity()); + 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); + if (m.HasValue()) { + if (m.Value()->IsJSFunction()) { + Handle<JSFunction> function = Handle<JSFunction>::cast(m.Value()); + + // 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, + javascript()->CallRuntime(Runtime::kThrowCalledNonCallable, 1)); + return Changed(node); + } + + // Check for the ArrayConstructor. + if (*function == function->native_context()->array_function()) { + // Check if we have an allocation site. + Handle<AllocationSite> site; + if (p.feedback().IsValid()) { + Handle<Object> feedback( + p.feedback().vector()->Get(p.feedback().slot()), isolate()); + if (feedback->IsAllocationSite()) { + site = Handle<AllocationSite>::cast(feedback); + } + } + + // 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); + } + NodeProperties::ReplaceValueInput(node, new_target, 1); + NodeProperties::ChangeOp(node, javascript()->CreateArray(arity, site)); + return Changed(node); + } + } + + // 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(); + + // 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* native_context = effect = graph()->NewNode( + javascript()->LoadContext(0, Context::NATIVE_CONTEXT_INDEX, true), + context, 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(DeoptimizeKind::kEager), + 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(DeoptimizeKind::kEager), + 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(); +} + + +MaybeHandle<Context> JSCallReducer::GetNativeContext(Node* node) { + Node* const context = NodeProperties::GetContextInput(node); + return NodeProperties::GetSpecializationNativeContext(context, + native_context()); +} + + +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(); +} + +} // namespace compiler +} // namespace internal +} // namespace v8 |