1 // Copyright 2015 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "src/compiler/js-call-reducer.h"
6
7 #include "src/code-factory.h"
8 #include "src/code-stubs.h"
9 #include "src/compilation-dependencies.h"
10 #include "src/compiler/js-graph.h"
11 #include "src/compiler/linkage.h"
12 #include "src/compiler/node-matchers.h"
13 #include "src/compiler/simplified-operator.h"
14 #include "src/feedback-vector-inl.h"
15 #include "src/objects-inl.h"
16
17 namespace v8 {
18 namespace internal {
19 namespace compiler {
20
Reduce(Node * node)21 Reduction JSCallReducer::Reduce(Node* node) {
22 switch (node->opcode()) {
23 case IrOpcode::kJSConstruct:
24 return ReduceJSConstruct(node);
25 case IrOpcode::kJSConstructWithSpread:
26 return ReduceJSConstructWithSpread(node);
27 case IrOpcode::kJSCall:
28 return ReduceJSCall(node);
29 case IrOpcode::kJSCallWithSpread:
30 return ReduceJSCallWithSpread(node);
31 default:
32 break;
33 }
34 return NoChange();
35 }
36
37
38 // ES6 section 22.1.1 The Array Constructor
ReduceArrayConstructor(Node * node)39 Reduction JSCallReducer::ReduceArrayConstructor(Node* node) {
40 DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
41 Node* target = NodeProperties::GetValueInput(node, 0);
42 CallParameters const& p = CallParametersOf(node->op());
43
44 // Check if we have an allocation site from the CallIC.
45 Handle<AllocationSite> site;
46 if (p.feedback().IsValid()) {
47 CallICNexus nexus(p.feedback().vector(), p.feedback().slot());
48 Handle<Object> feedback(nexus.GetFeedback(), isolate());
49 if (feedback->IsAllocationSite()) {
50 site = Handle<AllocationSite>::cast(feedback);
51 }
52 }
53
54 // Turn the {node} into a {JSCreateArray} call.
55 DCHECK_LE(2u, p.arity());
56 size_t const arity = p.arity() - 2;
57 NodeProperties::ReplaceValueInput(node, target, 0);
58 NodeProperties::ReplaceValueInput(node, target, 1);
59 // TODO(bmeurer): We might need to propagate the tail call mode to
60 // the JSCreateArray operator, because an Array call in tail call
61 // position must always properly consume the parent stack frame.
62 NodeProperties::ChangeOp(node, javascript()->CreateArray(arity, site));
63 return Changed(node);
64 }
65
66
67 // ES6 section 20.1.1 The Number Constructor
ReduceNumberConstructor(Node * node)68 Reduction JSCallReducer::ReduceNumberConstructor(Node* node) {
69 DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
70 CallParameters const& p = CallParametersOf(node->op());
71
72 // Turn the {node} into a {JSToNumber} call.
73 DCHECK_LE(2u, p.arity());
74 Node* value = (p.arity() == 2) ? jsgraph()->ZeroConstant()
75 : NodeProperties::GetValueInput(node, 2);
76 NodeProperties::ReplaceValueInputs(node, value);
77 NodeProperties::ChangeOp(node, javascript()->ToNumber());
78 return Changed(node);
79 }
80
81
82 // ES6 section 19.2.3.1 Function.prototype.apply ( thisArg, argArray )
ReduceFunctionPrototypeApply(Node * node)83 Reduction JSCallReducer::ReduceFunctionPrototypeApply(Node* node) {
84 DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
85 Node* target = NodeProperties::GetValueInput(node, 0);
86 CallParameters const& p = CallParametersOf(node->op());
87 // Tail calls to Function.prototype.apply are not properly supported
88 // down the pipeline, so we disable this optimization completely for
89 // tail calls (for now).
90 if (p.tail_call_mode() == TailCallMode::kAllow) return NoChange();
91 Handle<JSFunction> apply =
92 Handle<JSFunction>::cast(HeapObjectMatcher(target).Value());
93 size_t arity = p.arity();
94 DCHECK_LE(2u, arity);
95 ConvertReceiverMode convert_mode = ConvertReceiverMode::kAny;
96 if (arity == 2) {
97 // Neither thisArg nor argArray was provided.
98 convert_mode = ConvertReceiverMode::kNullOrUndefined;
99 node->ReplaceInput(0, node->InputAt(1));
100 node->ReplaceInput(1, jsgraph()->UndefinedConstant());
101 } else if (arity == 3) {
102 // The argArray was not provided, just remove the {target}.
103 node->RemoveInput(0);
104 --arity;
105 } else if (arity == 4) {
106 // Check if argArray is an arguments object, and {node} is the only value
107 // user of argArray (except for value uses in frame states).
108 Node* arg_array = NodeProperties::GetValueInput(node, 3);
109 if (arg_array->opcode() != IrOpcode::kJSCreateArguments) return NoChange();
110 for (Edge edge : arg_array->use_edges()) {
111 if (edge.from()->opcode() == IrOpcode::kStateValues) continue;
112 if (!NodeProperties::IsValueEdge(edge)) continue;
113 if (edge.from() == node) continue;
114 return NoChange();
115 }
116 // Check if the arguments can be handled in the fast case (i.e. we don't
117 // have aliased sloppy arguments), and compute the {start_index} for
118 // rest parameters.
119 CreateArgumentsType const type = CreateArgumentsTypeOf(arg_array->op());
120 Node* frame_state = NodeProperties::GetFrameStateInput(arg_array);
121 FrameStateInfo state_info = OpParameter<FrameStateInfo>(frame_state);
122 int formal_parameter_count;
123 int start_index = 0;
124 {
125 Handle<SharedFunctionInfo> shared;
126 if (!state_info.shared_info().ToHandle(&shared)) return NoChange();
127 formal_parameter_count = shared->internal_formal_parameter_count();
128 }
129 if (type == CreateArgumentsType::kMappedArguments) {
130 // Mapped arguments (sloppy mode) cannot be handled if they are aliased.
131 if (formal_parameter_count != 0) return NoChange();
132 } else if (type == CreateArgumentsType::kRestParameter) {
133 start_index = formal_parameter_count;
134 }
135 // Check if are applying to inlined arguments or to the arguments of
136 // the outermost function.
137 Node* outer_state = frame_state->InputAt(kFrameStateOuterStateInput);
138 if (outer_state->opcode() != IrOpcode::kFrameState) {
139 // TODO(jarin,bmeurer): Support the NewUnmappedArgumentsElement and
140 // NewRestParameterElements in the EscapeAnalysis and Deoptimizer
141 // instead, then we don't need this hack.
142 // Only works with zero formal parameters because of lacking deoptimizer
143 // support.
144 if (type != CreateArgumentsType::kRestParameter &&
145 formal_parameter_count == 0) {
146 // There are no other uses of the {arg_array} except in StateValues,
147 // so we just replace {arg_array} with a marker for the Deoptimizer
148 // that this refers to the arguments object.
149 Node* arguments = graph()->NewNode(common()->ArgumentsObjectState());
150 ReplaceWithValue(arg_array, arguments);
151 }
152
153 // Reduce {node} to a JSCallForwardVarargs operation, which just
154 // re-pushes the incoming arguments and calls the {target}.
155 node->RemoveInput(0); // Function.prototype.apply
156 node->RemoveInput(2); // arguments
157 NodeProperties::ChangeOp(node, javascript()->CallForwardVarargs(
158 start_index, p.tail_call_mode()));
159 return Changed(node);
160 }
161 // Get to the actual frame state from which to extract the arguments;
162 // we can only optimize this in case the {node} was already inlined into
163 // some other function (and same for the {arg_array}).
164 FrameStateInfo outer_info = OpParameter<FrameStateInfo>(outer_state);
165 if (outer_info.type() == FrameStateType::kArgumentsAdaptor) {
166 // Need to take the parameters from the arguments adaptor.
167 frame_state = outer_state;
168 }
169 // Remove the argArray input from the {node}.
170 node->RemoveInput(static_cast<int>(--arity));
171 // Add the actual parameters to the {node}, skipping the receiver,
172 // starting from {start_index}.
173 Node* const parameters = frame_state->InputAt(kFrameStateParametersInput);
174 for (int i = start_index + 1; i < parameters->InputCount(); ++i) {
175 node->InsertInput(graph()->zone(), static_cast<int>(arity),
176 parameters->InputAt(i));
177 ++arity;
178 }
179 // Drop the {target} from the {node}.
180 node->RemoveInput(0);
181 --arity;
182 } else {
183 return NoChange();
184 }
185 // Change {node} to the new {JSCall} operator.
186 NodeProperties::ChangeOp(
187 node,
188 javascript()->Call(arity, p.frequency(), VectorSlotPair(), convert_mode,
189 p.tail_call_mode()));
190 // Change context of {node} to the Function.prototype.apply context,
191 // to ensure any exception is thrown in the correct context.
192 NodeProperties::ReplaceContextInput(
193 node, jsgraph()->HeapConstant(handle(apply->context(), isolate())));
194 // Try to further reduce the JSCall {node}.
195 Reduction const reduction = ReduceJSCall(node);
196 return reduction.Changed() ? reduction : Changed(node);
197 }
198
199
200 // ES6 section 19.2.3.3 Function.prototype.call (thisArg, ...args)
ReduceFunctionPrototypeCall(Node * node)201 Reduction JSCallReducer::ReduceFunctionPrototypeCall(Node* node) {
202 DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
203 CallParameters const& p = CallParametersOf(node->op());
204 Handle<JSFunction> call = Handle<JSFunction>::cast(
205 HeapObjectMatcher(NodeProperties::GetValueInput(node, 0)).Value());
206 // Change context of {node} to the Function.prototype.call context,
207 // to ensure any exception is thrown in the correct context.
208 NodeProperties::ReplaceContextInput(
209 node, jsgraph()->HeapConstant(handle(call->context(), isolate())));
210 // Remove the target from {node} and use the receiver as target instead, and
211 // the thisArg becomes the new target. If thisArg was not provided, insert
212 // undefined instead.
213 size_t arity = p.arity();
214 DCHECK_LE(2u, arity);
215 ConvertReceiverMode convert_mode;
216 if (arity == 2) {
217 // The thisArg was not provided, use undefined as receiver.
218 convert_mode = ConvertReceiverMode::kNullOrUndefined;
219 node->ReplaceInput(0, node->InputAt(1));
220 node->ReplaceInput(1, jsgraph()->UndefinedConstant());
221 } else {
222 // Just remove the target, which is the first value input.
223 convert_mode = ConvertReceiverMode::kAny;
224 node->RemoveInput(0);
225 --arity;
226 }
227 NodeProperties::ChangeOp(
228 node,
229 javascript()->Call(arity, p.frequency(), VectorSlotPair(), convert_mode,
230 p.tail_call_mode()));
231 // Try to further reduce the JSCall {node}.
232 Reduction const reduction = ReduceJSCall(node);
233 return reduction.Changed() ? reduction : Changed(node);
234 }
235
236 // ES6 section 19.2.3.6 Function.prototype [ @@hasInstance ] (V)
ReduceFunctionPrototypeHasInstance(Node * node)237 Reduction JSCallReducer::ReduceFunctionPrototypeHasInstance(Node* node) {
238 DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
239 Node* receiver = NodeProperties::GetValueInput(node, 1);
240 Node* object = (node->op()->ValueInputCount() >= 3)
241 ? NodeProperties::GetValueInput(node, 2)
242 : jsgraph()->UndefinedConstant();
243 Node* context = NodeProperties::GetContextInput(node);
244 Node* frame_state = NodeProperties::GetFrameStateInput(node);
245 Node* effect = NodeProperties::GetEffectInput(node);
246 Node* control = NodeProperties::GetControlInput(node);
247
248 // TODO(turbofan): If JSOrdinaryToInstance raises an exception, the
249 // stack trace doesn't contain the @@hasInstance call; we have the
250 // corresponding bug in the baseline case. Some massaging of the frame
251 // state would be necessary here.
252
253 // Morph this {node} into a JSOrdinaryHasInstance node.
254 node->ReplaceInput(0, receiver);
255 node->ReplaceInput(1, object);
256 node->ReplaceInput(2, context);
257 node->ReplaceInput(3, frame_state);
258 node->ReplaceInput(4, effect);
259 node->ReplaceInput(5, control);
260 node->TrimInputCount(6);
261 NodeProperties::ChangeOp(node, javascript()->OrdinaryHasInstance());
262 return Changed(node);
263 }
264
265 namespace {
266
CanInlineApiCall(Isolate * isolate,Node * node,Handle<FunctionTemplateInfo> function_template_info)267 bool CanInlineApiCall(Isolate* isolate, Node* node,
268 Handle<FunctionTemplateInfo> function_template_info) {
269 DCHECK(node->opcode() == IrOpcode::kJSCall);
270 if (V8_UNLIKELY(FLAG_runtime_stats)) return false;
271 if (function_template_info->call_code()->IsUndefined(isolate)) {
272 return false;
273 }
274 CallParameters const& params = CallParametersOf(node->op());
275 // CallApiCallbackStub expects the target in a register, so we count it out,
276 // and counts the receiver as an implicit argument, so we count the receiver
277 // out too.
278 int const argc = static_cast<int>(params.arity()) - 2;
279 if (argc > CallApiCallbackStub::kArgMax || !params.feedback().IsValid()) {
280 return false;
281 }
282 HeapObjectMatcher receiver(NodeProperties::GetValueInput(node, 1));
283 if (!receiver.HasValue()) {
284 return false;
285 }
286 return receiver.Value()->IsUndefined(isolate) ||
287 (receiver.Value()->map()->IsJSObjectMap() &&
288 !receiver.Value()->map()->is_access_check_needed());
289 }
290
291 } // namespace
292
LookupHolder(Handle<JSObject> object,Handle<FunctionTemplateInfo> function_template_info,Handle<JSObject> * holder)293 JSCallReducer::HolderLookup JSCallReducer::LookupHolder(
294 Handle<JSObject> object,
295 Handle<FunctionTemplateInfo> function_template_info,
296 Handle<JSObject>* holder) {
297 DCHECK(object->map()->IsJSObjectMap());
298 Handle<Map> object_map(object->map());
299 Handle<FunctionTemplateInfo> expected_receiver_type;
300 if (!function_template_info->signature()->IsUndefined(isolate())) {
301 expected_receiver_type =
302 handle(FunctionTemplateInfo::cast(function_template_info->signature()));
303 }
304 if (expected_receiver_type.is_null() ||
305 expected_receiver_type->IsTemplateFor(*object_map)) {
306 *holder = Handle<JSObject>::null();
307 return kHolderIsReceiver;
308 }
309 while (object_map->has_hidden_prototype()) {
310 Handle<JSObject> prototype(JSObject::cast(object_map->prototype()));
311 object_map = handle(prototype->map());
312 if (expected_receiver_type->IsTemplateFor(*object_map)) {
313 *holder = prototype;
314 return kHolderFound;
315 }
316 }
317 return kHolderNotFound;
318 }
319
320 // ES6 section B.2.2.1.1 get Object.prototype.__proto__
ReduceObjectPrototypeGetProto(Node * node)321 Reduction JSCallReducer::ReduceObjectPrototypeGetProto(Node* node) {
322 DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
323 Node* receiver = NodeProperties::GetValueInput(node, 1);
324 Node* effect = NodeProperties::GetEffectInput(node);
325
326 // Try to determine the {receiver} map.
327 ZoneHandleSet<Map> receiver_maps;
328 NodeProperties::InferReceiverMapsResult result =
329 NodeProperties::InferReceiverMaps(receiver, effect, &receiver_maps);
330 if (result == NodeProperties::kReliableReceiverMaps) {
331 Handle<Map> candidate_map(
332 receiver_maps[0]->GetPrototypeChainRootMap(isolate()));
333 Handle<Object> candidate_prototype(candidate_map->prototype(), isolate());
334
335 // Check if we can constant-fold the {candidate_prototype}.
336 for (size_t i = 0; i < receiver_maps.size(); ++i) {
337 Handle<Map> const receiver_map(
338 receiver_maps[i]->GetPrototypeChainRootMap(isolate()));
339 if (receiver_map->IsJSProxyMap() ||
340 receiver_map->has_hidden_prototype() ||
341 receiver_map->is_access_check_needed() ||
342 receiver_map->prototype() != *candidate_prototype) {
343 return NoChange();
344 }
345 }
346 Node* value = jsgraph()->Constant(candidate_prototype);
347 ReplaceWithValue(node, value);
348 return Replace(value);
349 }
350
351 return NoChange();
352 }
353
ReduceCallApiFunction(Node * node,Node * target,Handle<FunctionTemplateInfo> function_template_info)354 Reduction JSCallReducer::ReduceCallApiFunction(
355 Node* node, Node* target,
356 Handle<FunctionTemplateInfo> function_template_info) {
357 Isolate* isolate = this->isolate();
358 CHECK(!isolate->serializer_enabled());
359 HeapObjectMatcher m(target);
360 DCHECK(m.HasValue() && m.Value()->IsJSFunction());
361 if (!CanInlineApiCall(isolate, node, function_template_info)) {
362 return NoChange();
363 }
364 Handle<CallHandlerInfo> call_handler_info(
365 handle(CallHandlerInfo::cast(function_template_info->call_code())));
366 Handle<Object> data(call_handler_info->data(), isolate);
367
368 Node* receiver_node = NodeProperties::GetValueInput(node, 1);
369 CallParameters const& params = CallParametersOf(node->op());
370
371 Handle<HeapObject> receiver = HeapObjectMatcher(receiver_node).Value();
372 bool const receiver_is_undefined = receiver->IsUndefined(isolate);
373 if (receiver_is_undefined) {
374 receiver = handle(Handle<JSFunction>::cast(m.Value())->global_proxy());
375 } else {
376 DCHECK(receiver->map()->IsJSObjectMap() &&
377 !receiver->map()->is_access_check_needed());
378 }
379
380 Handle<JSObject> holder;
381 HolderLookup lookup = LookupHolder(Handle<JSObject>::cast(receiver),
382 function_template_info, &holder);
383 if (lookup == kHolderNotFound) return NoChange();
384 if (receiver_is_undefined) {
385 receiver_node = jsgraph()->HeapConstant(receiver);
386 NodeProperties::ReplaceValueInput(node, receiver_node, 1);
387 }
388 Node* holder_node =
389 lookup == kHolderFound ? jsgraph()->HeapConstant(holder) : receiver_node;
390
391 Zone* zone = graph()->zone();
392 // Same as CanInlineApiCall: exclude the target (which goes in a register) and
393 // the receiver (which is implicitly counted by CallApiCallbackStub) from the
394 // arguments count.
395 int const argc = static_cast<int>(params.arity() - 2);
396 CallApiCallbackStub stub(isolate, argc, data->IsUndefined(isolate), false);
397 CallInterfaceDescriptor cid = stub.GetCallInterfaceDescriptor();
398 CallDescriptor* call_descriptor = Linkage::GetStubCallDescriptor(
399 isolate, zone, cid,
400 cid.GetStackParameterCount() + argc + 1 /* implicit receiver */,
401 CallDescriptor::kNeedsFrameState, Operator::kNoProperties,
402 MachineType::AnyTagged(), 1);
403 ApiFunction api_function(v8::ToCData<Address>(call_handler_info->callback()));
404 ExternalReference function_reference(
405 &api_function, ExternalReference::DIRECT_API_CALL, isolate);
406
407 // CallApiCallbackStub's register arguments: code, target, call data, holder,
408 // function address.
409 node->InsertInput(zone, 0, jsgraph()->HeapConstant(stub.GetCode()));
410 node->InsertInput(zone, 2, jsgraph()->Constant(data));
411 node->InsertInput(zone, 3, holder_node);
412 node->InsertInput(zone, 4, jsgraph()->ExternalConstant(function_reference));
413 NodeProperties::ChangeOp(node, common()->Call(call_descriptor));
414 return Changed(node);
415 }
416
ReduceSpreadCall(Node * node,int arity)417 Reduction JSCallReducer::ReduceSpreadCall(Node* node, int arity) {
418 DCHECK(node->opcode() == IrOpcode::kJSCallWithSpread ||
419 node->opcode() == IrOpcode::kJSConstructWithSpread);
420
421 // Do check to make sure we can actually avoid iteration.
422 if (!isolate()->initial_array_iterator_prototype_map()->is_stable()) {
423 return NoChange();
424 }
425
426 Node* spread = NodeProperties::GetValueInput(node, arity);
427
428 // Check if spread is an arguments object, and {node} is the only value user
429 // of spread (except for value uses in frame states).
430 if (spread->opcode() != IrOpcode::kJSCreateArguments) return NoChange();
431 for (Edge edge : spread->use_edges()) {
432 if (edge.from()->opcode() == IrOpcode::kStateValues) continue;
433 if (!NodeProperties::IsValueEdge(edge)) continue;
434 if (edge.from() == node) continue;
435 return NoChange();
436 }
437
438 // Get to the actual frame state from which to extract the arguments;
439 // we can only optimize this in case the {node} was already inlined into
440 // some other function (and same for the {spread}).
441 CreateArgumentsType type = CreateArgumentsTypeOf(spread->op());
442 Node* frame_state = NodeProperties::GetFrameStateInput(spread);
443 Node* outer_state = frame_state->InputAt(kFrameStateOuterStateInput);
444 if (outer_state->opcode() != IrOpcode::kFrameState) return NoChange();
445 FrameStateInfo outer_info = OpParameter<FrameStateInfo>(outer_state);
446 if (outer_info.type() == FrameStateType::kArgumentsAdaptor) {
447 // Need to take the parameters from the arguments adaptor.
448 frame_state = outer_state;
449 }
450 FrameStateInfo state_info = OpParameter<FrameStateInfo>(frame_state);
451 int start_index = 0;
452 if (type == CreateArgumentsType::kMappedArguments) {
453 // Mapped arguments (sloppy mode) cannot be handled if they are aliased.
454 Handle<SharedFunctionInfo> shared;
455 if (!state_info.shared_info().ToHandle(&shared)) return NoChange();
456 if (shared->internal_formal_parameter_count() != 0) return NoChange();
457 } else if (type == CreateArgumentsType::kRestParameter) {
458 Handle<SharedFunctionInfo> shared;
459 if (!state_info.shared_info().ToHandle(&shared)) return NoChange();
460 start_index = shared->internal_formal_parameter_count();
461
462 // Only check the array iterator protector when we have a rest object.
463 if (!isolate()->IsArrayIteratorLookupChainIntact()) return NoChange();
464 // Add a code dependency on the array iterator protector.
465 dependencies()->AssumePropertyCell(factory()->array_iterator_protector());
466 }
467
468 dependencies()->AssumeMapStable(
469 isolate()->initial_array_iterator_prototype_map());
470
471 node->RemoveInput(arity--);
472
473 // Add the actual parameters to the {node}, skipping the receiver.
474 Node* const parameters = frame_state->InputAt(kFrameStateParametersInput);
475 for (int i = start_index + 1; i < state_info.parameter_count(); ++i) {
476 node->InsertInput(graph()->zone(), static_cast<int>(++arity),
477 parameters->InputAt(i));
478 }
479
480 if (node->opcode() == IrOpcode::kJSCallWithSpread) {
481 NodeProperties::ChangeOp(
482 node, javascript()->Call(arity + 1, 7, VectorSlotPair()));
483 } else {
484 NodeProperties::ChangeOp(
485 node, javascript()->Construct(arity + 2, 7, VectorSlotPair()));
486 }
487 return Changed(node);
488 }
489
ReduceJSCall(Node * node)490 Reduction JSCallReducer::ReduceJSCall(Node* node) {
491 DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
492 CallParameters const& p = CallParametersOf(node->op());
493 Node* target = NodeProperties::GetValueInput(node, 0);
494 Node* control = NodeProperties::GetControlInput(node);
495 Node* effect = NodeProperties::GetEffectInput(node);
496
497 // Try to specialize JSCall {node}s with constant {target}s.
498 HeapObjectMatcher m(target);
499 if (m.HasValue()) {
500 if (m.Value()->IsJSFunction()) {
501 Handle<JSFunction> function = Handle<JSFunction>::cast(m.Value());
502 Handle<SharedFunctionInfo> shared(function->shared(), isolate());
503
504 // Raise a TypeError if the {target} is a "classConstructor".
505 if (IsClassConstructor(shared->kind())) {
506 NodeProperties::ReplaceValueInputs(node, target);
507 NodeProperties::ChangeOp(
508 node, javascript()->CallRuntime(
509 Runtime::kThrowConstructorNonCallableError, 1));
510 return Changed(node);
511 }
512
513 // Don't inline cross native context.
514 if (function->native_context() != *native_context()) return NoChange();
515
516 // Check for known builtin functions.
517 switch (shared->code()->builtin_index()) {
518 case Builtins::kFunctionPrototypeApply:
519 return ReduceFunctionPrototypeApply(node);
520 case Builtins::kFunctionPrototypeCall:
521 return ReduceFunctionPrototypeCall(node);
522 case Builtins::kFunctionPrototypeHasInstance:
523 return ReduceFunctionPrototypeHasInstance(node);
524 case Builtins::kNumberConstructor:
525 return ReduceNumberConstructor(node);
526 case Builtins::kObjectPrototypeGetProto:
527 return ReduceObjectPrototypeGetProto(node);
528 default:
529 break;
530 }
531
532 // Check for the Array constructor.
533 if (*function == function->native_context()->array_function()) {
534 return ReduceArrayConstructor(node);
535 }
536
537 if (shared->IsApiFunction()) {
538 return ReduceCallApiFunction(
539 node, target,
540 handle(FunctionTemplateInfo::cast(shared->function_data())));
541 }
542 } else if (m.Value()->IsJSBoundFunction()) {
543 Handle<JSBoundFunction> function =
544 Handle<JSBoundFunction>::cast(m.Value());
545 Handle<JSReceiver> bound_target_function(
546 function->bound_target_function(), isolate());
547 Handle<Object> bound_this(function->bound_this(), isolate());
548 Handle<FixedArray> bound_arguments(function->bound_arguments(),
549 isolate());
550 CallParameters const& p = CallParametersOf(node->op());
551 ConvertReceiverMode const convert_mode =
552 (bound_this->IsNullOrUndefined(isolate()))
553 ? ConvertReceiverMode::kNullOrUndefined
554 : ConvertReceiverMode::kNotNullOrUndefined;
555 size_t arity = p.arity();
556 DCHECK_LE(2u, arity);
557 // Patch {node} to use [[BoundTargetFunction]] and [[BoundThis]].
558 NodeProperties::ReplaceValueInput(
559 node, jsgraph()->Constant(bound_target_function), 0);
560 NodeProperties::ReplaceValueInput(node, jsgraph()->Constant(bound_this),
561 1);
562 // Insert the [[BoundArguments]] for {node}.
563 for (int i = 0; i < bound_arguments->length(); ++i) {
564 node->InsertInput(
565 graph()->zone(), i + 2,
566 jsgraph()->Constant(handle(bound_arguments->get(i), isolate())));
567 arity++;
568 }
569 NodeProperties::ChangeOp(
570 node,
571 javascript()->Call(arity, p.frequency(), VectorSlotPair(),
572 convert_mode, p.tail_call_mode()));
573 // Try to further reduce the JSCall {node}.
574 Reduction const reduction = ReduceJSCall(node);
575 return reduction.Changed() ? reduction : Changed(node);
576 }
577
578 // Don't mess with other {node}s that have a constant {target}.
579 // TODO(bmeurer): Also support proxies here.
580 return NoChange();
581 }
582
583 // Extract feedback from the {node} using the CallICNexus.
584 if (!p.feedback().IsValid()) return NoChange();
585 CallICNexus nexus(p.feedback().vector(), p.feedback().slot());
586 if (nexus.IsUninitialized()) {
587 // TODO(turbofan): Tail-calling to a CallIC stub is not supported.
588 if (p.tail_call_mode() == TailCallMode::kAllow) return NoChange();
589
590 // Insert a CallIC here to collect feedback for uninitialized calls.
591 int const arg_count = static_cast<int>(p.arity() - 2);
592 Callable callable = CodeFactory::CallIC(isolate(), p.convert_mode());
593 CallDescriptor::Flags flags = CallDescriptor::kNeedsFrameState;
594 CallDescriptor const* const desc = Linkage::GetStubCallDescriptor(
595 isolate(), graph()->zone(), callable.descriptor(), arg_count + 1,
596 flags);
597 Node* stub_code = jsgraph()->HeapConstant(callable.code());
598 Node* stub_arity = jsgraph()->Constant(arg_count);
599 Node* slot_index =
600 jsgraph()->Constant(FeedbackVector::GetIndex(p.feedback().slot()));
601 Node* feedback_vector = jsgraph()->HeapConstant(p.feedback().vector());
602 node->InsertInput(graph()->zone(), 0, stub_code);
603 node->InsertInput(graph()->zone(), 2, stub_arity);
604 node->InsertInput(graph()->zone(), 3, slot_index);
605 node->InsertInput(graph()->zone(), 4, feedback_vector);
606 NodeProperties::ChangeOp(node, common()->Call(desc));
607 return Changed(node);
608 }
609
610 // Not much we can do if deoptimization support is disabled.
611 if (!(flags() & kDeoptimizationEnabled)) return NoChange();
612
613 Handle<Object> feedback(nexus.GetFeedback(), isolate());
614 if (feedback->IsAllocationSite()) {
615 // Retrieve the Array function from the {node}.
616 Node* array_function = jsgraph()->HeapConstant(
617 handle(native_context()->array_function(), isolate()));
618
619 // Check that the {target} is still the {array_function}.
620 Node* check = graph()->NewNode(simplified()->ReferenceEqual(), target,
621 array_function);
622 effect = graph()->NewNode(simplified()->CheckIf(), check, effect, control);
623
624 // Turn the {node} into a {JSCreateArray} call.
625 NodeProperties::ReplaceValueInput(node, array_function, 0);
626 NodeProperties::ReplaceEffectInput(node, effect);
627 return ReduceArrayConstructor(node);
628 } else if (feedback->IsWeakCell()) {
629 Handle<WeakCell> cell = Handle<WeakCell>::cast(feedback);
630 if (cell->value()->IsJSFunction()) {
631 Node* target_function =
632 jsgraph()->Constant(handle(cell->value(), isolate()));
633
634 // Check that the {target} is still the {target_function}.
635 Node* check = graph()->NewNode(simplified()->ReferenceEqual(), target,
636 target_function);
637 effect =
638 graph()->NewNode(simplified()->CheckIf(), check, effect, control);
639
640 // Specialize the JSCall node to the {target_function}.
641 NodeProperties::ReplaceValueInput(node, target_function, 0);
642 NodeProperties::ReplaceEffectInput(node, effect);
643
644 // Try to further reduce the JSCall {node}.
645 Reduction const reduction = ReduceJSCall(node);
646 return reduction.Changed() ? reduction : Changed(node);
647 }
648 }
649 return NoChange();
650 }
651
ReduceJSCallWithSpread(Node * node)652 Reduction JSCallReducer::ReduceJSCallWithSpread(Node* node) {
653 DCHECK_EQ(IrOpcode::kJSCallWithSpread, node->opcode());
654 CallWithSpreadParameters const& p = CallWithSpreadParametersOf(node->op());
655 DCHECK_LE(3u, p.arity());
656 int arity = static_cast<int>(p.arity() - 1);
657
658 return ReduceSpreadCall(node, arity);
659 }
660
ReduceJSConstruct(Node * node)661 Reduction JSCallReducer::ReduceJSConstruct(Node* node) {
662 DCHECK_EQ(IrOpcode::kJSConstruct, node->opcode());
663 ConstructParameters const& p = ConstructParametersOf(node->op());
664 DCHECK_LE(2u, p.arity());
665 int const arity = static_cast<int>(p.arity() - 2);
666 Node* target = NodeProperties::GetValueInput(node, 0);
667 Node* new_target = NodeProperties::GetValueInput(node, arity + 1);
668 Node* effect = NodeProperties::GetEffectInput(node);
669 Node* control = NodeProperties::GetControlInput(node);
670
671 // Try to specialize JSConstruct {node}s with constant {target}s.
672 HeapObjectMatcher m(target);
673 if (m.HasValue()) {
674 if (m.Value()->IsJSFunction()) {
675 Handle<JSFunction> function = Handle<JSFunction>::cast(m.Value());
676
677 // Raise a TypeError if the {target} is not a constructor.
678 if (!function->IsConstructor()) {
679 NodeProperties::ReplaceValueInputs(node, target);
680 NodeProperties::ChangeOp(
681 node, javascript()->CallRuntime(
682 Runtime::kThrowConstructedNonConstructable));
683 return Changed(node);
684 }
685
686 // Don't inline cross native context.
687 if (function->native_context() != *native_context()) return NoChange();
688
689 // Check for the ArrayConstructor.
690 if (*function == function->native_context()->array_function()) {
691 // Check if we have an allocation site.
692 Handle<AllocationSite> site;
693 if (p.feedback().IsValid()) {
694 CallICNexus nexus(p.feedback().vector(), p.feedback().slot());
695 Handle<Object> feedback(nexus.GetFeedback(), isolate());
696 if (feedback->IsAllocationSite()) {
697 site = Handle<AllocationSite>::cast(feedback);
698 }
699 }
700
701 // Turn the {node} into a {JSCreateArray} call.
702 for (int i = arity; i > 0; --i) {
703 NodeProperties::ReplaceValueInput(
704 node, NodeProperties::GetValueInput(node, i), i + 1);
705 }
706 NodeProperties::ReplaceValueInput(node, new_target, 1);
707 NodeProperties::ChangeOp(node, javascript()->CreateArray(arity, site));
708 return Changed(node);
709 }
710 }
711
712 // Don't mess with other {node}s that have a constant {target}.
713 // TODO(bmeurer): Also support optimizing bound functions and proxies here.
714 return NoChange();
715 }
716
717 // Not much we can do if deoptimization support is disabled.
718 if (!(flags() & kDeoptimizationEnabled)) return NoChange();
719
720 if (!p.feedback().IsValid()) return NoChange();
721 CallICNexus nexus(p.feedback().vector(), p.feedback().slot());
722 Handle<Object> feedback(nexus.GetFeedback(), isolate());
723 if (feedback->IsAllocationSite()) {
724 // The feedback is an AllocationSite, which means we have called the
725 // Array function and collected transition (and pretenuring) feedback
726 // for the resulting arrays. This has to be kept in sync with the
727 // implementation of the CallConstructStub.
728 Handle<AllocationSite> site = Handle<AllocationSite>::cast(feedback);
729
730 // Retrieve the Array function from the {node}.
731 Node* array_function = jsgraph()->HeapConstant(
732 handle(native_context()->array_function(), isolate()));
733
734 // Check that the {target} is still the {array_function}.
735 Node* check = graph()->NewNode(simplified()->ReferenceEqual(), target,
736 array_function);
737 effect = graph()->NewNode(simplified()->CheckIf(), check, effect, control);
738
739 // Turn the {node} into a {JSCreateArray} call.
740 NodeProperties::ReplaceEffectInput(node, effect);
741 for (int i = arity; i > 0; --i) {
742 NodeProperties::ReplaceValueInput(
743 node, NodeProperties::GetValueInput(node, i), i + 1);
744 }
745 NodeProperties::ReplaceValueInput(node, new_target, 1);
746 NodeProperties::ChangeOp(node, javascript()->CreateArray(arity, site));
747 return Changed(node);
748 } else if (feedback->IsWeakCell()) {
749 Handle<WeakCell> cell = Handle<WeakCell>::cast(feedback);
750 if (cell->value()->IsJSFunction()) {
751 Node* target_function =
752 jsgraph()->Constant(handle(cell->value(), isolate()));
753
754 // Check that the {target} is still the {target_function}.
755 Node* check = graph()->NewNode(simplified()->ReferenceEqual(), target,
756 target_function);
757 effect =
758 graph()->NewNode(simplified()->CheckIf(), check, effect, control);
759
760 // Specialize the JSConstruct node to the {target_function}.
761 NodeProperties::ReplaceValueInput(node, target_function, 0);
762 NodeProperties::ReplaceEffectInput(node, effect);
763 if (target == new_target) {
764 NodeProperties::ReplaceValueInput(node, target_function, arity + 1);
765 }
766
767 // Try to further reduce the JSConstruct {node}.
768 Reduction const reduction = ReduceJSConstruct(node);
769 return reduction.Changed() ? reduction : Changed(node);
770 }
771 }
772
773 return NoChange();
774 }
775
ReduceJSConstructWithSpread(Node * node)776 Reduction JSCallReducer::ReduceJSConstructWithSpread(Node* node) {
777 DCHECK_EQ(IrOpcode::kJSConstructWithSpread, node->opcode());
778 ConstructWithSpreadParameters const& p =
779 ConstructWithSpreadParametersOf(node->op());
780 DCHECK_LE(3u, p.arity());
781 int arity = static_cast<int>(p.arity() - 2);
782
783 return ReduceSpreadCall(node, arity);
784 }
785
graph() const786 Graph* JSCallReducer::graph() const { return jsgraph()->graph(); }
787
isolate() const788 Isolate* JSCallReducer::isolate() const { return jsgraph()->isolate(); }
789
factory() const790 Factory* JSCallReducer::factory() const { return isolate()->factory(); }
791
common() const792 CommonOperatorBuilder* JSCallReducer::common() const {
793 return jsgraph()->common();
794 }
795
javascript() const796 JSOperatorBuilder* JSCallReducer::javascript() const {
797 return jsgraph()->javascript();
798 }
799
simplified() const800 SimplifiedOperatorBuilder* JSCallReducer::simplified() const {
801 return jsgraph()->simplified();
802 }
803
804 } // namespace compiler
805 } // namespace internal
806 } // namespace v8
807