diff options
Diffstat (limited to 'src/compiler/js-call-reducer.cc')
-rw-r--r-- | src/compiler/js-call-reducer.cc | 522 |
1 files changed, 402 insertions, 120 deletions
diff --git a/src/compiler/js-call-reducer.cc b/src/compiler/js-call-reducer.cc index e48fce91..c0deb915 100644 --- a/src/compiler/js-call-reducer.cc +++ b/src/compiler/js-call-reducer.cc @@ -4,11 +4,15 @@ #include "src/compiler/js-call-reducer.h" +#include "src/code-factory.h" +#include "src/code-stubs.h" +#include "src/compilation-dependencies.h" #include "src/compiler/js-graph.h" +#include "src/compiler/linkage.h" #include "src/compiler/node-matchers.h" #include "src/compiler/simplified-operator.h" +#include "src/feedback-vector-inl.h" #include "src/objects-inl.h" -#include "src/type-feedback-vector-inl.h" namespace v8 { namespace internal { @@ -16,10 +20,14 @@ namespace compiler { Reduction JSCallReducer::Reduce(Node* node) { switch (node->opcode()) { - case IrOpcode::kJSCallConstruct: - return ReduceJSCallConstruct(node); - case IrOpcode::kJSCallFunction: - return ReduceJSCallFunction(node); + case IrOpcode::kJSConstruct: + return ReduceJSConstruct(node); + case IrOpcode::kJSConstructWithSpread: + return ReduceJSConstructWithSpread(node); + case IrOpcode::kJSCall: + return ReduceJSCall(node); + case IrOpcode::kJSCallWithSpread: + return ReduceJSCallWithSpread(node); default: break; } @@ -29,9 +37,9 @@ Reduction JSCallReducer::Reduce(Node* node) { // ES6 section 22.1.1 The Array Constructor Reduction JSCallReducer::ReduceArrayConstructor(Node* node) { - DCHECK_EQ(IrOpcode::kJSCallFunction, node->opcode()); + DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); Node* target = NodeProperties::GetValueInput(node, 0); - CallFunctionParameters const& p = CallFunctionParametersOf(node->op()); + CallParameters const& p = CallParametersOf(node->op()); // Check if we have an allocation site from the CallIC. Handle<AllocationSite> site; @@ -58,8 +66,8 @@ Reduction JSCallReducer::ReduceArrayConstructor(Node* 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()); + DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); + CallParameters const& p = CallParametersOf(node->op()); // Turn the {node} into a {JSToNumber} call. DCHECK_LE(2u, p.arity()); @@ -73,9 +81,13 @@ Reduction JSCallReducer::ReduceNumberConstructor(Node* node) { // ES6 section 19.2.3.1 Function.prototype.apply ( thisArg, argArray ) Reduction JSCallReducer::ReduceFunctionPrototypeApply(Node* node) { - DCHECK_EQ(IrOpcode::kJSCallFunction, node->opcode()); + DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); Node* target = NodeProperties::GetValueInput(node, 0); - CallFunctionParameters const& p = CallFunctionParametersOf(node->op()); + CallParameters const& p = CallParametersOf(node->op()); + // Tail calls to Function.prototype.apply are not properly supported + // down the pipeline, so we disable this optimization completely for + // tail calls (for now). + if (p.tail_call_mode() == TailCallMode::kAllow) return NoChange(); Handle<JSFunction> apply = Handle<JSFunction>::cast(HeapObjectMatcher(target).Value()); size_t arity = p.arity(); @@ -101,35 +113,65 @@ Reduction JSCallReducer::ReduceFunctionPrototypeApply(Node* node) { if (edge.from() == node) continue; return NoChange(); } + // Check if the arguments can be handled in the fast case (i.e. we don't + // have aliased sloppy arguments), and compute the {start_index} for + // rest parameters. + CreateArgumentsType const type = CreateArgumentsTypeOf(arg_array->op()); + Node* frame_state = NodeProperties::GetFrameStateInput(arg_array); + FrameStateInfo state_info = OpParameter<FrameStateInfo>(frame_state); + int formal_parameter_count; + int start_index = 0; + { + Handle<SharedFunctionInfo> shared; + if (!state_info.shared_info().ToHandle(&shared)) return NoChange(); + formal_parameter_count = shared->internal_formal_parameter_count(); + } + if (type == CreateArgumentsType::kMappedArguments) { + // Mapped arguments (sloppy mode) cannot be handled if they are aliased. + if (formal_parameter_count != 0) return NoChange(); + } else if (type == CreateArgumentsType::kRestParameter) { + start_index = formal_parameter_count; + } + // Check if are applying to inlined arguments or to the arguments of + // the outermost function. + Node* outer_state = frame_state->InputAt(kFrameStateOuterStateInput); + if (outer_state->opcode() != IrOpcode::kFrameState) { + // TODO(jarin,bmeurer): Support the NewUnmappedArgumentsElement and + // NewRestParameterElements in the EscapeAnalysis and Deoptimizer + // instead, then we don't need this hack. + // Only works with zero formal parameters because of lacking deoptimizer + // support. + if (type != CreateArgumentsType::kRestParameter && + formal_parameter_count == 0) { + // There are no other uses of the {arg_array} except in StateValues, + // so we just replace {arg_array} with a marker for the Deoptimizer + // that this refers to the arguments object. + Node* arguments = graph()->NewNode(common()->ArgumentsObjectState()); + ReplaceWithValue(arg_array, arguments); + } + + // Reduce {node} to a JSCallForwardVarargs operation, which just + // re-pushes the incoming arguments and calls the {target}. + node->RemoveInput(0); // Function.prototype.apply + node->RemoveInput(2); // arguments + NodeProperties::ChangeOp(node, javascript()->CallForwardVarargs( + start_index, p.tail_call_mode())); + return Changed(node); + } // 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. + // Add the actual parameters to the {node}, skipping the receiver, + // starting from {start_index}. Node* const parameters = frame_state->InputAt(kFrameStateParametersInput); - for (int i = start_index + 1; i < state_info.parameter_count(); ++i) { + for (int i = start_index + 1; i < parameters->InputCount(); ++i) { node->InsertInput(graph()->zone(), static_cast<int>(arity), parameters->InputAt(i)); ++arity; @@ -140,24 +182,25 @@ Reduction JSCallReducer::ReduceFunctionPrototypeApply(Node* node) { } else { return NoChange(); } - // Change {node} to the new {JSCallFunction} operator. + // Change {node} to the new {JSCall} operator. NodeProperties::ChangeOp( - node, javascript()->CallFunction(arity, p.frequency(), VectorSlotPair(), - convert_mode, p.tail_call_mode())); + node, + javascript()->Call(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); + // Try to further reduce the JSCall {node}. + Reduction const reduction = ReduceJSCall(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()); + DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); + CallParameters const& p = CallParametersOf(node->op()); Handle<JSFunction> call = Handle<JSFunction>::cast( HeapObjectMatcher(NodeProperties::GetValueInput(node, 0)).Value()); // Change context of {node} to the Function.prototype.call context, @@ -182,83 +225,276 @@ Reduction JSCallReducer::ReduceFunctionPrototypeCall(Node* node) { --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); + node, + javascript()->Call(arity, p.frequency(), VectorSlotPair(), convert_mode, + p.tail_call_mode())); + // Try to further reduce the JSCall {node}. + Reduction const reduction = ReduceJSCall(node); return reduction.Changed() ? reduction : Changed(node); } +// ES6 section 19.2.3.6 Function.prototype [ @@hasInstance ] (V) +Reduction JSCallReducer::ReduceFunctionPrototypeHasInstance(Node* node) { + DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); + Node* receiver = NodeProperties::GetValueInput(node, 1); + Node* object = (node->op()->ValueInputCount() >= 3) + ? NodeProperties::GetValueInput(node, 2) + : jsgraph()->UndefinedConstant(); + Node* context = NodeProperties::GetContextInput(node); + Node* frame_state = NodeProperties::GetFrameStateInput(node); + Node* effect = NodeProperties::GetEffectInput(node); + Node* control = NodeProperties::GetControlInput(node); + + // TODO(turbofan): If JSOrdinaryToInstance raises an exception, the + // stack trace doesn't contain the @@hasInstance call; we have the + // corresponding bug in the baseline case. Some massaging of the frame + // state would be necessary here. + + // Morph this {node} into a JSOrdinaryHasInstance node. + node->ReplaceInput(0, receiver); + node->ReplaceInput(1, object); + node->ReplaceInput(2, context); + node->ReplaceInput(3, frame_state); + node->ReplaceInput(4, effect); + node->ReplaceInput(5, control); + node->TrimInputCount(6); + NodeProperties::ChangeOp(node, javascript()->OrdinaryHasInstance()); + return 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)); +bool CanInlineApiCall(Isolate* isolate, Node* node, + Handle<FunctionTemplateInfo> function_template_info) { + DCHECK(node->opcode() == IrOpcode::kJSCall); + if (V8_UNLIKELY(FLAG_runtime_stats)) return false; + if (function_template_info->call_code()->IsUndefined(isolate)) { + return false; + } + CallParameters const& params = CallParametersOf(node->op()); + // CallApiCallbackStub expects the target in a register, so we count it out, + // and counts the receiver as an implicit argument, so we count the receiver + // out too. + int const argc = static_cast<int>(params.arity()) - 2; + if (argc > CallApiCallbackStub::kArgMax || !params.feedback().IsValid()) { + return false; } - return false; + HeapObjectMatcher receiver(NodeProperties::GetValueInput(node, 1)); + if (!receiver.HasValue()) { + return false; + } + return receiver.Value()->IsUndefined(isolate) || + (receiver.Value()->map()->IsJSObjectMap() && + !receiver.Value()->map()->is_access_check_needed()); } -// 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>(); +} // namespace + +JSCallReducer::HolderLookup JSCallReducer::LookupHolder( + Handle<JSObject> object, + Handle<FunctionTemplateInfo> function_template_info, + Handle<JSObject>* holder) { + DCHECK(object->map()->IsJSObjectMap()); + Handle<Map> object_map(object->map()); + Handle<FunctionTemplateInfo> expected_receiver_type; + if (!function_template_info->signature()->IsUndefined(isolate())) { + expected_receiver_type = + handle(FunctionTemplateInfo::cast(function_template_info->signature())); + } + if (expected_receiver_type.is_null() || + expected_receiver_type->IsTemplateFor(*object_map)) { + *holder = Handle<JSObject>::null(); + return kHolderIsReceiver; + } + while (object_map->has_hidden_prototype()) { + Handle<JSObject> prototype(JSObject::cast(object_map->prototype())); + object_map = handle(prototype->map()); + if (expected_receiver_type->IsTemplateFor(*object_map)) { + *holder = prototype; + return kHolderFound; } - dominator = NodeProperties::GetEffectInput(dominator); } + return kHolderNotFound; } -} // namespace - // ES6 section B.2.2.1.1 get Object.prototype.__proto__ Reduction JSCallReducer::ReduceObjectPrototypeGetProto(Node* node) { - DCHECK_EQ(IrOpcode::kJSCallFunction, node->opcode()); + DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); + Node* receiver = NodeProperties::GetValueInput(node, 1); + Node* effect = NodeProperties::GetEffectInput(node); // 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); + ZoneHandleSet<Map> receiver_maps; + NodeProperties::InferReceiverMapsResult result = + NodeProperties::InferReceiverMaps(receiver, effect, &receiver_maps); + if (result == NodeProperties::kReliableReceiverMaps) { + Handle<Map> candidate_map( + receiver_maps[0]->GetPrototypeChainRootMap(isolate())); + Handle<Object> candidate_prototype(candidate_map->prototype(), isolate()); + + // Check if we can constant-fold the {candidate_prototype}. + for (size_t i = 0; i < receiver_maps.size(); ++i) { + Handle<Map> const receiver_map( + receiver_maps[i]->GetPrototypeChainRootMap(isolate())); + if (receiver_map->IsJSProxyMap() || + receiver_map->has_hidden_prototype() || + receiver_map->is_access_check_needed() || + receiver_map->prototype() != *candidate_prototype) { + return NoChange(); + } } + Node* value = jsgraph()->Constant(candidate_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()); +Reduction JSCallReducer::ReduceCallApiFunction( + Node* node, Node* target, + Handle<FunctionTemplateInfo> function_template_info) { + Isolate* isolate = this->isolate(); + CHECK(!isolate->serializer_enabled()); + HeapObjectMatcher m(target); + DCHECK(m.HasValue() && m.Value()->IsJSFunction()); + if (!CanInlineApiCall(isolate, node, function_template_info)) { + return NoChange(); + } + Handle<CallHandlerInfo> call_handler_info( + handle(CallHandlerInfo::cast(function_template_info->call_code()))); + Handle<Object> data(call_handler_info->data(), isolate); + + Node* receiver_node = NodeProperties::GetValueInput(node, 1); + CallParameters const& params = CallParametersOf(node->op()); + + Handle<HeapObject> receiver = HeapObjectMatcher(receiver_node).Value(); + bool const receiver_is_undefined = receiver->IsUndefined(isolate); + if (receiver_is_undefined) { + receiver = handle(Handle<JSFunction>::cast(m.Value())->global_proxy()); + } else { + DCHECK(receiver->map()->IsJSObjectMap() && + !receiver->map()->is_access_check_needed()); + } + + Handle<JSObject> holder; + HolderLookup lookup = LookupHolder(Handle<JSObject>::cast(receiver), + function_template_info, &holder); + if (lookup == kHolderNotFound) return NoChange(); + if (receiver_is_undefined) { + receiver_node = jsgraph()->HeapConstant(receiver); + NodeProperties::ReplaceValueInput(node, receiver_node, 1); + } + Node* holder_node = + lookup == kHolderFound ? jsgraph()->HeapConstant(holder) : receiver_node; + + Zone* zone = graph()->zone(); + // Same as CanInlineApiCall: exclude the target (which goes in a register) and + // the receiver (which is implicitly counted by CallApiCallbackStub) from the + // arguments count. + int const argc = static_cast<int>(params.arity() - 2); + CallApiCallbackStub stub(isolate, argc, data->IsUndefined(isolate), false); + CallInterfaceDescriptor cid = stub.GetCallInterfaceDescriptor(); + CallDescriptor* call_descriptor = Linkage::GetStubCallDescriptor( + isolate, zone, cid, + cid.GetStackParameterCount() + argc + 1 /* implicit receiver */, + CallDescriptor::kNeedsFrameState, Operator::kNoProperties, + MachineType::AnyTagged(), 1); + ApiFunction api_function(v8::ToCData<Address>(call_handler_info->callback())); + ExternalReference function_reference( + &api_function, ExternalReference::DIRECT_API_CALL, isolate); + + // CallApiCallbackStub's register arguments: code, target, call data, holder, + // function address. + node->InsertInput(zone, 0, jsgraph()->HeapConstant(stub.GetCode())); + node->InsertInput(zone, 2, jsgraph()->Constant(data)); + node->InsertInput(zone, 3, holder_node); + node->InsertInput(zone, 4, jsgraph()->ExternalConstant(function_reference)); + NodeProperties::ChangeOp(node, common()->Call(call_descriptor)); + return Changed(node); +} + +Reduction JSCallReducer::ReduceSpreadCall(Node* node, int arity) { + DCHECK(node->opcode() == IrOpcode::kJSCallWithSpread || + node->opcode() == IrOpcode::kJSConstructWithSpread); + + // Do check to make sure we can actually avoid iteration. + if (!isolate()->initial_array_iterator_prototype_map()->is_stable()) { + return NoChange(); + } + + Node* spread = NodeProperties::GetValueInput(node, arity); + + // Check if spread is an arguments object, and {node} is the only value user + // of spread (except for value uses in frame states). + if (spread->opcode() != IrOpcode::kJSCreateArguments) return NoChange(); + for (Edge edge : spread->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 {spread}). + CreateArgumentsType type = CreateArgumentsTypeOf(spread->op()); + Node* frame_state = NodeProperties::GetFrameStateInput(spread); + 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(); + + // Only check the array iterator protector when we have a rest object. + if (!isolate()->IsArrayIteratorLookupChainIntact()) return NoChange(); + // Add a code dependency on the array iterator protector. + dependencies()->AssumePropertyCell(factory()->array_iterator_protector()); + } + + dependencies()->AssumeMapStable( + isolate()->initial_array_iterator_prototype_map()); + + node->RemoveInput(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)); + } + + if (node->opcode() == IrOpcode::kJSCallWithSpread) { + NodeProperties::ChangeOp( + node, javascript()->Call(arity + 1, 7, VectorSlotPair())); + } else { + NodeProperties::ChangeOp( + node, javascript()->Construct(arity + 2, 7, VectorSlotPair())); + } + return Changed(node); +} + +Reduction JSCallReducer::ReduceJSCall(Node* node) { + DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); + CallParameters const& p = CallParametersOf(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. + // Try to specialize JSCall {node}s with constant {target}s. HeapObjectMatcher m(target); if (m.HasValue()) { if (m.Value()->IsJSFunction()) { @@ -274,12 +510,17 @@ Reduction JSCallReducer::ReduceJSCallFunction(Node* node) { return Changed(node); } + // Don't inline cross native context. + if (function->native_context() != *native_context()) return NoChange(); + // Check for known builtin functions. switch (shared->code()->builtin_index()) { case Builtins::kFunctionPrototypeApply: return ReduceFunctionPrototypeApply(node); case Builtins::kFunctionPrototypeCall: return ReduceFunctionPrototypeCall(node); + case Builtins::kFunctionPrototypeHasInstance: + return ReduceFunctionPrototypeHasInstance(node); case Builtins::kNumberConstructor: return ReduceNumberConstructor(node); case Builtins::kObjectPrototypeGetProto: @@ -292,6 +533,12 @@ Reduction JSCallReducer::ReduceJSCallFunction(Node* node) { if (*function == function->native_context()->array_function()) { return ReduceArrayConstructor(node); } + + if (shared->IsApiFunction()) { + return ReduceCallApiFunction( + node, target, + handle(FunctionTemplateInfo::cast(shared->function_data()))); + } } else if (m.Value()->IsJSBoundFunction()) { Handle<JSBoundFunction> function = Handle<JSBoundFunction>::cast(m.Value()); @@ -300,9 +547,9 @@ Reduction JSCallReducer::ReduceJSCallFunction(Node* node) { Handle<Object> bound_this(function->bound_this(), isolate()); Handle<FixedArray> bound_arguments(function->bound_arguments(), isolate()); - CallFunctionParameters const& p = CallFunctionParametersOf(node->op()); + CallParameters const& p = CallParametersOf(node->op()); ConvertReceiverMode const convert_mode = - (bound_this->IsNull(isolate()) || bound_this->IsUndefined(isolate())) + (bound_this->IsNullOrUndefined(isolate())) ? ConvertReceiverMode::kNullOrUndefined : ConvertReceiverMode::kNotNullOrUndefined; size_t arity = p.arity(); @@ -319,11 +566,12 @@ Reduction JSCallReducer::ReduceJSCallFunction(Node* node) { 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); + NodeProperties::ChangeOp( + node, + javascript()->Call(arity, p.frequency(), VectorSlotPair(), + convert_mode, p.tail_call_mode())); + // Try to further reduce the JSCall {node}. + Reduction const reduction = ReduceJSCall(node); return reduction.Changed() ? reduction : Changed(node); } @@ -332,26 +580,36 @@ Reduction JSCallReducer::ReduceJSCallFunction(Node* node) { 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()); + if (nexus.IsUninitialized()) { + // TODO(turbofan): Tail-calling to a CallIC stub is not supported. + if (p.tail_call_mode() == TailCallMode::kAllow) return NoChange(); + + // Insert a CallIC here to collect feedback for uninitialized calls. + int const arg_count = static_cast<int>(p.arity() - 2); + Callable callable = CodeFactory::CallIC(isolate(), p.convert_mode()); + CallDescriptor::Flags flags = CallDescriptor::kNeedsFrameState; + CallDescriptor const* const desc = Linkage::GetStubCallDescriptor( + isolate(), graph()->zone(), callable.descriptor(), arg_count + 1, + flags); + Node* stub_code = jsgraph()->HeapConstant(callable.code()); + Node* stub_arity = jsgraph()->Constant(arg_count); + Node* slot_index = + jsgraph()->Constant(FeedbackVector::GetIndex(p.feedback().slot())); + Node* feedback_vector = jsgraph()->HeapConstant(p.feedback().vector()); + node->InsertInput(graph()->zone(), 0, stub_code); + node->InsertInput(graph()->zone(), 2, stub_arity); + node->InsertInput(graph()->zone(), 3, slot_index); + node->InsertInput(graph()->zone(), 4, feedback_vector); + NodeProperties::ChangeOp(node, common()->Call(desc)); return Changed(node); } + + // Not much we can do if deoptimization support is disabled. + if (!(flags() & kDeoptimizationEnabled)) return NoChange(); + Handle<Object> feedback(nexus.GetFeedback(), isolate()); if (feedback->IsAllocationSite()) { // Retrieve the Array function from the {node}. @@ -379,22 +637,30 @@ Reduction JSCallReducer::ReduceJSCallFunction(Node* node) { effect = graph()->NewNode(simplified()->CheckIf(), check, effect, control); - // Specialize the JSCallFunction node to the {target_function}. + // Specialize the JSCall 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); + // Try to further reduce the JSCall {node}. + Reduction const reduction = ReduceJSCall(node); return reduction.Changed() ? reduction : Changed(node); } } return NoChange(); } +Reduction JSCallReducer::ReduceJSCallWithSpread(Node* node) { + DCHECK_EQ(IrOpcode::kJSCallWithSpread, node->opcode()); + CallWithSpreadParameters const& p = CallWithSpreadParametersOf(node->op()); + DCHECK_LE(3u, p.arity()); + int arity = static_cast<int>(p.arity() - 1); + + return ReduceSpreadCall(node, arity); +} -Reduction JSCallReducer::ReduceJSCallConstruct(Node* node) { - DCHECK_EQ(IrOpcode::kJSCallConstruct, node->opcode()); - CallConstructParameters const& p = CallConstructParametersOf(node->op()); +Reduction JSCallReducer::ReduceJSConstruct(Node* node) { + DCHECK_EQ(IrOpcode::kJSConstruct, node->opcode()); + ConstructParameters const& p = ConstructParametersOf(node->op()); DCHECK_LE(2u, p.arity()); int const arity = static_cast<int>(p.arity() - 2); Node* target = NodeProperties::GetValueInput(node, 0); @@ -402,7 +668,7 @@ Reduction JSCallReducer::ReduceJSCallConstruct(Node* node) { Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); - // Try to specialize JSCallConstruct {node}s with constant {target}s. + // Try to specialize JSConstruct {node}s with constant {target}s. HeapObjectMatcher m(target); if (m.HasValue()) { if (m.Value()->IsJSFunction()) { @@ -412,10 +678,14 @@ Reduction JSCallReducer::ReduceJSCallConstruct(Node* node) { if (!function->IsConstructor()) { NodeProperties::ReplaceValueInputs(node, target); NodeProperties::ChangeOp( - node, javascript()->CallRuntime(Runtime::kThrowCalledNonCallable)); + node, javascript()->CallRuntime( + Runtime::kThrowConstructedNonConstructable)); return Changed(node); } + // Don't inline cross native context. + if (function->native_context() != *native_context()) return NoChange(); + // Check for the ArrayConstructor. if (*function == function->native_context()->array_function()) { // Check if we have an allocation site. @@ -487,15 +757,15 @@ Reduction JSCallReducer::ReduceJSCallConstruct(Node* node) { effect = graph()->NewNode(simplified()->CheckIf(), check, effect, control); - // Specialize the JSCallConstruct node to the {target_function}. + // Specialize the JSConstruct 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); + // Try to further reduce the JSConstruct {node}. + Reduction const reduction = ReduceJSConstruct(node); return reduction.Changed() ? reduction : Changed(node); } } @@ -503,10 +773,22 @@ Reduction JSCallReducer::ReduceJSCallConstruct(Node* node) { return NoChange(); } +Reduction JSCallReducer::ReduceJSConstructWithSpread(Node* node) { + DCHECK_EQ(IrOpcode::kJSConstructWithSpread, node->opcode()); + ConstructWithSpreadParameters const& p = + ConstructWithSpreadParametersOf(node->op()); + DCHECK_LE(3u, p.arity()); + int arity = static_cast<int>(p.arity() - 2); + + return ReduceSpreadCall(node, arity); +} + Graph* JSCallReducer::graph() const { return jsgraph()->graph(); } Isolate* JSCallReducer::isolate() const { return jsgraph()->isolate(); } +Factory* JSCallReducer::factory() const { return isolate()->factory(); } + CommonOperatorBuilder* JSCallReducer::common() const { return jsgraph()->common(); } |