// 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/compiler/simplified-operator.h" #include "src/objects-inl.h" #include "src/type-feedback-vector-inl.h" namespace v8 { namespace internal { namespace compiler { 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); // 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::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}). CreateArgumentsType type = CreateArgumentsTypeOf(arg_array->op()); Node* frame_state = NodeProperties::GetFrameStateInput(arg_array); 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); int start_index = 0; if (type == CreateArgumentsType::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(); } else if (type == CreateArgumentsType::kRestParameter) { Handle<SharedFunctionInfo> shared; if (!state_info.shared_info().ToHandle(&shared)) return NoChange(); start_index = shared->internal_formal_parameter_count(); } // 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 = 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.frequency(), VectorSlotPair(), 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.frequency(), VectorSlotPair(), convert_mode, p.tail_call_mode())); // Try to further reduce the JSCallFunction {node}. Reduction const reduction = ReduceJSCallFunction(node); return reduction.Changed() ? reduction : Changed(node); } namespace { // TODO(turbofan): Shall we move this to the NodeProperties? Or some (untyped) // alias analyzer? bool IsSame(Node* a, Node* b) { if (a == b) { return true; } else if (a->opcode() == IrOpcode::kCheckHeapObject) { return IsSame(a->InputAt(0), b); } else if (b->opcode() == IrOpcode::kCheckHeapObject) { return IsSame(a, b->InputAt(0)); } return false; } // TODO(turbofan): Share with similar functionality in JSInliningHeuristic // and JSNativeContextSpecialization, i.e. move to NodeProperties helper?! MaybeHandle<Map> InferReceiverMap(Node* node) { Node* receiver = NodeProperties::GetValueInput(node, 1); Node* effect = NodeProperties::GetEffectInput(node); // Check if the {node} is dominated by a CheckMaps with a single map // for the {receiver}, and if so use that map for the lowering below. for (Node* dominator = effect;;) { if (dominator->opcode() == IrOpcode::kCheckMaps && IsSame(dominator->InputAt(0), receiver)) { if (dominator->op()->ValueInputCount() == 2) { HeapObjectMatcher m(dominator->InputAt(1)); if (m.HasValue()) return Handle<Map>::cast(m.Value()); } return MaybeHandle<Map>(); } if (dominator->op()->EffectInputCount() != 1) { // Didn't find any appropriate CheckMaps node. return MaybeHandle<Map>(); } dominator = NodeProperties::GetEffectInput(dominator); } } } // namespace // ES6 section B.2.2.1.1 get Object.prototype.__proto__ Reduction JSCallReducer::ReduceObjectPrototypeGetProto(Node* node) { DCHECK_EQ(IrOpcode::kJSCallFunction, node->opcode()); // Try to determine the {receiver} map. Handle<Map> receiver_map; if (InferReceiverMap(node).ToHandle(&receiver_map)) { // Check if we can constant-fold the {receiver} map. if (!receiver_map->IsJSProxyMap() && !receiver_map->has_hidden_prototype() && !receiver_map->is_access_check_needed()) { Handle<Object> receiver_prototype(receiver_map->prototype(), isolate()); Node* value = jsgraph()->Constant(receiver_prototype); ReplaceWithValue(node, value); return Replace(value); } } return NoChange(); } Reduction JSCallReducer::ReduceJSCallFunction(Node* node) { DCHECK_EQ(IrOpcode::kJSCallFunction, node->opcode()); CallFunctionParameters const& p = CallFunctionParametersOf(node->op()); Node* target = NodeProperties::GetValueInput(node, 0); 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::ReplaceValueInputs(node, target); NodeProperties::ChangeOp( node, javascript()->CallRuntime( Runtime::kThrowConstructorNonCallableError, 1)); return Changed(node); } // Check for known builtin functions. switch (shared->code()->builtin_index()) { case Builtins::kFunctionPrototypeApply: return ReduceFunctionPrototypeApply(node); case Builtins::kFunctionPrototypeCall: return ReduceFunctionPrototypeCall(node); case Builtins::kNumberConstructor: return ReduceNumberConstructor(node); case Builtins::kObjectPrototypeGetProto: return ReduceObjectPrototypeGetProto(node); default: break; } // Check for the Array constructor. if (*function == function->native_context()->array_function()) { return ReduceArrayConstructor(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(isolate()) || bound_this->IsUndefined(isolate())) ? 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.frequency(), VectorSlotPair(), 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()); if (nexus.IsUninitialized() && (flags() & kBailoutOnUninitialized)) { Node* frame_state = NodeProperties::FindFrameStateBefore(node); Node* deoptimize = graph()->NewNode( common()->Deoptimize( DeoptimizeKind::kSoft, DeoptimizeReason::kInsufficientTypeFeedbackForCall), frame_state, effect, control); // TODO(bmeurer): This should be on the AdvancedReducer somehow. NodeProperties::MergeControlToEnd(graph(), common(), deoptimize); Revisit(graph()->end()); node->TrimInputCount(0); NodeProperties::ChangeOp(node, common()->Dead()); return Changed(node); } Handle<Object> feedback(nexus.GetFeedback(), isolate()); if (feedback->IsAllocationSite()) { // Retrieve the Array function from the {node}. Node* array_function = jsgraph()->HeapConstant( handle(native_context()->array_function(), isolate())); // Check that the {target} is still the {array_function}. Node* check = graph()->NewNode(simplified()->ReferenceEqual(), target, array_function); effect = graph()->NewNode(simplified()->CheckIf(), check, effect, control); // Turn the {node} into a {JSCreateArray} call. NodeProperties::ReplaceValueInput(node, array_function, 0); NodeProperties::ReplaceEffectInput(node, effect); 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 = graph()->NewNode(simplified()->ReferenceEqual(), target, target_function); effect = graph()->NewNode(simplified()->CheckIf(), check, effect, control); // Specialize the JSCallFunction node to the {target_function}. NodeProperties::ReplaceValueInput(node, target_function, 0); NodeProperties::ReplaceEffectInput(node, effect); // 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* 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()) { NodeProperties::ReplaceValueInputs(node, target); NodeProperties::ChangeOp( node, javascript()->CallRuntime(Runtime::kThrowCalledNonCallable)); 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()) { 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. 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(); if (!p.feedback().IsValid()) return NoChange(); CallICNexus nexus(p.feedback().vector(), p.feedback().slot()); Handle<Object> feedback(nexus.GetFeedback(), 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 = jsgraph()->HeapConstant( handle(native_context()->array_function(), isolate())); // Check that the {target} is still the {array_function}. Node* check = graph()->NewNode(simplified()->ReferenceEqual(), target, array_function); effect = graph()->NewNode(simplified()->CheckIf(), check, effect, control); // Turn the {node} into a {JSCreateArray} call. NodeProperties::ReplaceEffectInput(node, effect); 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 = graph()->NewNode(simplified()->ReferenceEqual(), target, target_function); effect = graph()->NewNode(simplified()->CheckIf(), check, effect, control); // Specialize the JSCallConstruct node to the {target_function}. NodeProperties::ReplaceValueInput(node, target_function, 0); NodeProperties::ReplaceEffectInput(node, effect); 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(); } 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