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/compiler/js-graph.h"
8 #include "src/compiler/node-matchers.h"
9 #include "src/compiler/simplified-operator.h"
10 #include "src/objects-inl.h"
11 #include "src/type-feedback-vector-inl.h"
12 
13 namespace v8 {
14 namespace internal {
15 namespace compiler {
16 
Reduce(Node * node)17 Reduction JSCallReducer::Reduce(Node* node) {
18   switch (node->opcode()) {
19     case IrOpcode::kJSCallConstruct:
20       return ReduceJSCallConstruct(node);
21     case IrOpcode::kJSCallFunction:
22       return ReduceJSCallFunction(node);
23     default:
24       break;
25   }
26   return NoChange();
27 }
28 
29 
30 // ES6 section 22.1.1 The Array Constructor
ReduceArrayConstructor(Node * node)31 Reduction JSCallReducer::ReduceArrayConstructor(Node* node) {
32   DCHECK_EQ(IrOpcode::kJSCallFunction, node->opcode());
33   Node* target = NodeProperties::GetValueInput(node, 0);
34   CallFunctionParameters const& p = CallFunctionParametersOf(node->op());
35 
36   // Check if we have an allocation site from the CallIC.
37   Handle<AllocationSite> site;
38   if (p.feedback().IsValid()) {
39     CallICNexus nexus(p.feedback().vector(), p.feedback().slot());
40     Handle<Object> feedback(nexus.GetFeedback(), isolate());
41     if (feedback->IsAllocationSite()) {
42       site = Handle<AllocationSite>::cast(feedback);
43     }
44   }
45 
46   // Turn the {node} into a {JSCreateArray} call.
47   DCHECK_LE(2u, p.arity());
48   size_t const arity = p.arity() - 2;
49   NodeProperties::ReplaceValueInput(node, target, 0);
50   NodeProperties::ReplaceValueInput(node, target, 1);
51   // TODO(bmeurer): We might need to propagate the tail call mode to
52   // the JSCreateArray operator, because an Array call in tail call
53   // position must always properly consume the parent stack frame.
54   NodeProperties::ChangeOp(node, javascript()->CreateArray(arity, site));
55   return Changed(node);
56 }
57 
58 
59 // ES6 section 20.1.1 The Number Constructor
ReduceNumberConstructor(Node * node)60 Reduction JSCallReducer::ReduceNumberConstructor(Node* node) {
61   DCHECK_EQ(IrOpcode::kJSCallFunction, node->opcode());
62   CallFunctionParameters const& p = CallFunctionParametersOf(node->op());
63 
64   // Turn the {node} into a {JSToNumber} call.
65   DCHECK_LE(2u, p.arity());
66   Node* value = (p.arity() == 2) ? jsgraph()->ZeroConstant()
67                                  : NodeProperties::GetValueInput(node, 2);
68   NodeProperties::ReplaceValueInputs(node, value);
69   NodeProperties::ChangeOp(node, javascript()->ToNumber());
70   return Changed(node);
71 }
72 
73 
74 // ES6 section 19.2.3.1 Function.prototype.apply ( thisArg, argArray )
ReduceFunctionPrototypeApply(Node * node)75 Reduction JSCallReducer::ReduceFunctionPrototypeApply(Node* node) {
76   DCHECK_EQ(IrOpcode::kJSCallFunction, node->opcode());
77   Node* target = NodeProperties::GetValueInput(node, 0);
78   CallFunctionParameters const& p = CallFunctionParametersOf(node->op());
79   Handle<JSFunction> apply =
80       Handle<JSFunction>::cast(HeapObjectMatcher(target).Value());
81   size_t arity = p.arity();
82   DCHECK_LE(2u, arity);
83   ConvertReceiverMode convert_mode = ConvertReceiverMode::kAny;
84   if (arity == 2) {
85     // Neither thisArg nor argArray was provided.
86     convert_mode = ConvertReceiverMode::kNullOrUndefined;
87     node->ReplaceInput(0, node->InputAt(1));
88     node->ReplaceInput(1, jsgraph()->UndefinedConstant());
89   } else if (arity == 3) {
90     // The argArray was not provided, just remove the {target}.
91     node->RemoveInput(0);
92     --arity;
93   } else if (arity == 4) {
94     // Check if argArray is an arguments object, and {node} is the only value
95     // user of argArray (except for value uses in frame states).
96     Node* arg_array = NodeProperties::GetValueInput(node, 3);
97     if (arg_array->opcode() != IrOpcode::kJSCreateArguments) return NoChange();
98     for (Edge edge : arg_array->use_edges()) {
99       if (edge.from()->opcode() == IrOpcode::kStateValues) continue;
100       if (!NodeProperties::IsValueEdge(edge)) continue;
101       if (edge.from() == node) continue;
102       return NoChange();
103     }
104     // Get to the actual frame state from which to extract the arguments;
105     // we can only optimize this in case the {node} was already inlined into
106     // some other function (and same for the {arg_array}).
107     CreateArgumentsType type = CreateArgumentsTypeOf(arg_array->op());
108     Node* frame_state = NodeProperties::GetFrameStateInput(arg_array);
109     Node* outer_state = frame_state->InputAt(kFrameStateOuterStateInput);
110     if (outer_state->opcode() != IrOpcode::kFrameState) return NoChange();
111     FrameStateInfo outer_info = OpParameter<FrameStateInfo>(outer_state);
112     if (outer_info.type() == FrameStateType::kArgumentsAdaptor) {
113       // Need to take the parameters from the arguments adaptor.
114       frame_state = outer_state;
115     }
116     FrameStateInfo state_info = OpParameter<FrameStateInfo>(frame_state);
117     int start_index = 0;
118     if (type == CreateArgumentsType::kMappedArguments) {
119       // Mapped arguments (sloppy mode) cannot be handled if they are aliased.
120       Handle<SharedFunctionInfo> shared;
121       if (!state_info.shared_info().ToHandle(&shared)) return NoChange();
122       if (shared->internal_formal_parameter_count() != 0) return NoChange();
123     } else if (type == CreateArgumentsType::kRestParameter) {
124       Handle<SharedFunctionInfo> shared;
125       if (!state_info.shared_info().ToHandle(&shared)) return NoChange();
126       start_index = shared->internal_formal_parameter_count();
127     }
128     // Remove the argArray input from the {node}.
129     node->RemoveInput(static_cast<int>(--arity));
130     // Add the actual parameters to the {node}, skipping the receiver.
131     Node* const parameters = frame_state->InputAt(kFrameStateParametersInput);
132     for (int i = start_index + 1; i < state_info.parameter_count(); ++i) {
133       node->InsertInput(graph()->zone(), static_cast<int>(arity),
134                         parameters->InputAt(i));
135       ++arity;
136     }
137     // Drop the {target} from the {node}.
138     node->RemoveInput(0);
139     --arity;
140   } else {
141     return NoChange();
142   }
143   // Change {node} to the new {JSCallFunction} operator.
144   NodeProperties::ChangeOp(
145       node, javascript()->CallFunction(arity, p.frequency(), VectorSlotPair(),
146                                        convert_mode, p.tail_call_mode()));
147   // Change context of {node} to the Function.prototype.apply context,
148   // to ensure any exception is thrown in the correct context.
149   NodeProperties::ReplaceContextInput(
150       node, jsgraph()->HeapConstant(handle(apply->context(), isolate())));
151   // Try to further reduce the JSCallFunction {node}.
152   Reduction const reduction = ReduceJSCallFunction(node);
153   return reduction.Changed() ? reduction : Changed(node);
154 }
155 
156 
157 // ES6 section 19.2.3.3 Function.prototype.call (thisArg, ...args)
ReduceFunctionPrototypeCall(Node * node)158 Reduction JSCallReducer::ReduceFunctionPrototypeCall(Node* node) {
159   DCHECK_EQ(IrOpcode::kJSCallFunction, node->opcode());
160   CallFunctionParameters const& p = CallFunctionParametersOf(node->op());
161   Handle<JSFunction> call = Handle<JSFunction>::cast(
162       HeapObjectMatcher(NodeProperties::GetValueInput(node, 0)).Value());
163   // Change context of {node} to the Function.prototype.call context,
164   // to ensure any exception is thrown in the correct context.
165   NodeProperties::ReplaceContextInput(
166       node, jsgraph()->HeapConstant(handle(call->context(), isolate())));
167   // Remove the target from {node} and use the receiver as target instead, and
168   // the thisArg becomes the new target.  If thisArg was not provided, insert
169   // undefined instead.
170   size_t arity = p.arity();
171   DCHECK_LE(2u, arity);
172   ConvertReceiverMode convert_mode;
173   if (arity == 2) {
174     // The thisArg was not provided, use undefined as receiver.
175     convert_mode = ConvertReceiverMode::kNullOrUndefined;
176     node->ReplaceInput(0, node->InputAt(1));
177     node->ReplaceInput(1, jsgraph()->UndefinedConstant());
178   } else {
179     // Just remove the target, which is the first value input.
180     convert_mode = ConvertReceiverMode::kAny;
181     node->RemoveInput(0);
182     --arity;
183   }
184   NodeProperties::ChangeOp(
185       node, javascript()->CallFunction(arity, p.frequency(), VectorSlotPair(),
186                                        convert_mode, p.tail_call_mode()));
187   // Try to further reduce the JSCallFunction {node}.
188   Reduction const reduction = ReduceJSCallFunction(node);
189   return reduction.Changed() ? reduction : Changed(node);
190 }
191 
192 namespace {
193 
194 // TODO(turbofan): Shall we move this to the NodeProperties? Or some (untyped)
195 // alias analyzer?
IsSame(Node * a,Node * b)196 bool IsSame(Node* a, Node* b) {
197   if (a == b) {
198     return true;
199   } else if (a->opcode() == IrOpcode::kCheckHeapObject) {
200     return IsSame(a->InputAt(0), b);
201   } else if (b->opcode() == IrOpcode::kCheckHeapObject) {
202     return IsSame(a, b->InputAt(0));
203   }
204   return false;
205 }
206 
207 // TODO(turbofan): Share with similar functionality in JSInliningHeuristic
208 // and JSNativeContextSpecialization, i.e. move to NodeProperties helper?!
InferReceiverMap(Node * node)209 MaybeHandle<Map> InferReceiverMap(Node* node) {
210   Node* receiver = NodeProperties::GetValueInput(node, 1);
211   Node* effect = NodeProperties::GetEffectInput(node);
212   // Check if the {node} is dominated by a CheckMaps with a single map
213   // for the {receiver}, and if so use that map for the lowering below.
214   for (Node* dominator = effect;;) {
215     if (dominator->opcode() == IrOpcode::kCheckMaps &&
216         IsSame(dominator->InputAt(0), receiver)) {
217       if (dominator->op()->ValueInputCount() == 2) {
218         HeapObjectMatcher m(dominator->InputAt(1));
219         if (m.HasValue()) return Handle<Map>::cast(m.Value());
220       }
221       return MaybeHandle<Map>();
222     }
223     if (dominator->op()->EffectInputCount() != 1) {
224       // Didn't find any appropriate CheckMaps node.
225       return MaybeHandle<Map>();
226     }
227     dominator = NodeProperties::GetEffectInput(dominator);
228   }
229 }
230 
231 }  // namespace
232 
233 // ES6 section B.2.2.1.1 get Object.prototype.__proto__
ReduceObjectPrototypeGetProto(Node * node)234 Reduction JSCallReducer::ReduceObjectPrototypeGetProto(Node* node) {
235   DCHECK_EQ(IrOpcode::kJSCallFunction, node->opcode());
236 
237   // Try to determine the {receiver} map.
238   Handle<Map> receiver_map;
239   if (InferReceiverMap(node).ToHandle(&receiver_map)) {
240     // Check if we can constant-fold the {receiver} map.
241     if (!receiver_map->IsJSProxyMap() &&
242         !receiver_map->has_hidden_prototype() &&
243         !receiver_map->is_access_check_needed()) {
244       Handle<Object> receiver_prototype(receiver_map->prototype(), isolate());
245       Node* value = jsgraph()->Constant(receiver_prototype);
246       ReplaceWithValue(node, value);
247       return Replace(value);
248     }
249   }
250 
251   return NoChange();
252 }
253 
ReduceJSCallFunction(Node * node)254 Reduction JSCallReducer::ReduceJSCallFunction(Node* node) {
255   DCHECK_EQ(IrOpcode::kJSCallFunction, node->opcode());
256   CallFunctionParameters const& p = CallFunctionParametersOf(node->op());
257   Node* target = NodeProperties::GetValueInput(node, 0);
258   Node* control = NodeProperties::GetControlInput(node);
259   Node* effect = NodeProperties::GetEffectInput(node);
260 
261   // Try to specialize JSCallFunction {node}s with constant {target}s.
262   HeapObjectMatcher m(target);
263   if (m.HasValue()) {
264     if (m.Value()->IsJSFunction()) {
265       Handle<JSFunction> function = Handle<JSFunction>::cast(m.Value());
266       Handle<SharedFunctionInfo> shared(function->shared(), isolate());
267 
268       // Raise a TypeError if the {target} is a "classConstructor".
269       if (IsClassConstructor(shared->kind())) {
270         NodeProperties::ReplaceValueInputs(node, target);
271         NodeProperties::ChangeOp(
272             node, javascript()->CallRuntime(
273                       Runtime::kThrowConstructorNonCallableError, 1));
274         return Changed(node);
275       }
276 
277       // Check for known builtin functions.
278       switch (shared->code()->builtin_index()) {
279         case Builtins::kFunctionPrototypeApply:
280           return ReduceFunctionPrototypeApply(node);
281         case Builtins::kFunctionPrototypeCall:
282           return ReduceFunctionPrototypeCall(node);
283         case Builtins::kNumberConstructor:
284           return ReduceNumberConstructor(node);
285         case Builtins::kObjectPrototypeGetProto:
286           return ReduceObjectPrototypeGetProto(node);
287         default:
288           break;
289       }
290 
291       // Check for the Array constructor.
292       if (*function == function->native_context()->array_function()) {
293         return ReduceArrayConstructor(node);
294       }
295     } else if (m.Value()->IsJSBoundFunction()) {
296       Handle<JSBoundFunction> function =
297           Handle<JSBoundFunction>::cast(m.Value());
298       Handle<JSReceiver> bound_target_function(
299           function->bound_target_function(), isolate());
300       Handle<Object> bound_this(function->bound_this(), isolate());
301       Handle<FixedArray> bound_arguments(function->bound_arguments(),
302                                          isolate());
303       CallFunctionParameters const& p = CallFunctionParametersOf(node->op());
304       ConvertReceiverMode const convert_mode =
305           (bound_this->IsNull(isolate()) || bound_this->IsUndefined(isolate()))
306               ? ConvertReceiverMode::kNullOrUndefined
307               : ConvertReceiverMode::kNotNullOrUndefined;
308       size_t arity = p.arity();
309       DCHECK_LE(2u, arity);
310       // Patch {node} to use [[BoundTargetFunction]] and [[BoundThis]].
311       NodeProperties::ReplaceValueInput(
312           node, jsgraph()->Constant(bound_target_function), 0);
313       NodeProperties::ReplaceValueInput(node, jsgraph()->Constant(bound_this),
314                                         1);
315       // Insert the [[BoundArguments]] for {node}.
316       for (int i = 0; i < bound_arguments->length(); ++i) {
317         node->InsertInput(
318             graph()->zone(), i + 2,
319             jsgraph()->Constant(handle(bound_arguments->get(i), isolate())));
320         arity++;
321       }
322       NodeProperties::ChangeOp(node, javascript()->CallFunction(
323                                          arity, p.frequency(), VectorSlotPair(),
324                                          convert_mode, p.tail_call_mode()));
325       // Try to further reduce the JSCallFunction {node}.
326       Reduction const reduction = ReduceJSCallFunction(node);
327       return reduction.Changed() ? reduction : Changed(node);
328     }
329 
330     // Don't mess with other {node}s that have a constant {target}.
331     // TODO(bmeurer): Also support proxies here.
332     return NoChange();
333   }
334 
335   // Not much we can do if deoptimization support is disabled.
336   if (!(flags() & kDeoptimizationEnabled)) return NoChange();
337 
338   // Extract feedback from the {node} using the CallICNexus.
339   if (!p.feedback().IsValid()) return NoChange();
340   CallICNexus nexus(p.feedback().vector(), p.feedback().slot());
341   if (nexus.IsUninitialized() && (flags() & kBailoutOnUninitialized)) {
342     Node* frame_state = NodeProperties::FindFrameStateBefore(node);
343     Node* deoptimize = graph()->NewNode(
344         common()->Deoptimize(
345             DeoptimizeKind::kSoft,
346             DeoptimizeReason::kInsufficientTypeFeedbackForCall),
347         frame_state, effect, control);
348     // TODO(bmeurer): This should be on the AdvancedReducer somehow.
349     NodeProperties::MergeControlToEnd(graph(), common(), deoptimize);
350     Revisit(graph()->end());
351     node->TrimInputCount(0);
352     NodeProperties::ChangeOp(node, common()->Dead());
353     return Changed(node);
354   }
355   Handle<Object> feedback(nexus.GetFeedback(), isolate());
356   if (feedback->IsAllocationSite()) {
357     // Retrieve the Array function from the {node}.
358     Node* array_function = jsgraph()->HeapConstant(
359         handle(native_context()->array_function(), isolate()));
360 
361     // Check that the {target} is still the {array_function}.
362     Node* check = graph()->NewNode(simplified()->ReferenceEqual(), target,
363                                    array_function);
364     effect = graph()->NewNode(simplified()->CheckIf(), check, effect, control);
365 
366     // Turn the {node} into a {JSCreateArray} call.
367     NodeProperties::ReplaceValueInput(node, array_function, 0);
368     NodeProperties::ReplaceEffectInput(node, effect);
369     return ReduceArrayConstructor(node);
370   } else if (feedback->IsWeakCell()) {
371     Handle<WeakCell> cell = Handle<WeakCell>::cast(feedback);
372     if (cell->value()->IsJSFunction()) {
373       Node* target_function =
374           jsgraph()->Constant(handle(cell->value(), isolate()));
375 
376       // Check that the {target} is still the {target_function}.
377       Node* check = graph()->NewNode(simplified()->ReferenceEqual(), target,
378                                      target_function);
379       effect =
380           graph()->NewNode(simplified()->CheckIf(), check, effect, control);
381 
382       // Specialize the JSCallFunction node to the {target_function}.
383       NodeProperties::ReplaceValueInput(node, target_function, 0);
384       NodeProperties::ReplaceEffectInput(node, effect);
385 
386       // Try to further reduce the JSCallFunction {node}.
387       Reduction const reduction = ReduceJSCallFunction(node);
388       return reduction.Changed() ? reduction : Changed(node);
389     }
390   }
391   return NoChange();
392 }
393 
394 
ReduceJSCallConstruct(Node * node)395 Reduction JSCallReducer::ReduceJSCallConstruct(Node* node) {
396   DCHECK_EQ(IrOpcode::kJSCallConstruct, node->opcode());
397   CallConstructParameters const& p = CallConstructParametersOf(node->op());
398   DCHECK_LE(2u, p.arity());
399   int const arity = static_cast<int>(p.arity() - 2);
400   Node* target = NodeProperties::GetValueInput(node, 0);
401   Node* new_target = NodeProperties::GetValueInput(node, arity + 1);
402   Node* effect = NodeProperties::GetEffectInput(node);
403   Node* control = NodeProperties::GetControlInput(node);
404 
405   // Try to specialize JSCallConstruct {node}s with constant {target}s.
406   HeapObjectMatcher m(target);
407   if (m.HasValue()) {
408     if (m.Value()->IsJSFunction()) {
409       Handle<JSFunction> function = Handle<JSFunction>::cast(m.Value());
410 
411       // Raise a TypeError if the {target} is not a constructor.
412       if (!function->IsConstructor()) {
413         NodeProperties::ReplaceValueInputs(node, target);
414         NodeProperties::ChangeOp(
415             node, javascript()->CallRuntime(Runtime::kThrowCalledNonCallable));
416         return Changed(node);
417       }
418 
419       // Check for the ArrayConstructor.
420       if (*function == function->native_context()->array_function()) {
421         // Check if we have an allocation site.
422         Handle<AllocationSite> site;
423         if (p.feedback().IsValid()) {
424           CallICNexus nexus(p.feedback().vector(), p.feedback().slot());
425           Handle<Object> feedback(nexus.GetFeedback(), isolate());
426           if (feedback->IsAllocationSite()) {
427             site = Handle<AllocationSite>::cast(feedback);
428           }
429         }
430 
431         // Turn the {node} into a {JSCreateArray} call.
432         for (int i = arity; i > 0; --i) {
433           NodeProperties::ReplaceValueInput(
434               node, NodeProperties::GetValueInput(node, i), i + 1);
435         }
436         NodeProperties::ReplaceValueInput(node, new_target, 1);
437         NodeProperties::ChangeOp(node, javascript()->CreateArray(arity, site));
438         return Changed(node);
439       }
440     }
441 
442     // Don't mess with other {node}s that have a constant {target}.
443     // TODO(bmeurer): Also support optimizing bound functions and proxies here.
444     return NoChange();
445   }
446 
447   // Not much we can do if deoptimization support is disabled.
448   if (!(flags() & kDeoptimizationEnabled)) return NoChange();
449 
450   if (!p.feedback().IsValid()) return NoChange();
451   CallICNexus nexus(p.feedback().vector(), p.feedback().slot());
452   Handle<Object> feedback(nexus.GetFeedback(), isolate());
453   if (feedback->IsAllocationSite()) {
454     // The feedback is an AllocationSite, which means we have called the
455     // Array function and collected transition (and pretenuring) feedback
456     // for the resulting arrays.  This has to be kept in sync with the
457     // implementation of the CallConstructStub.
458     Handle<AllocationSite> site = Handle<AllocationSite>::cast(feedback);
459 
460     // Retrieve the Array function from the {node}.
461     Node* array_function = jsgraph()->HeapConstant(
462         handle(native_context()->array_function(), isolate()));
463 
464     // Check that the {target} is still the {array_function}.
465     Node* check = graph()->NewNode(simplified()->ReferenceEqual(), target,
466                                    array_function);
467     effect = graph()->NewNode(simplified()->CheckIf(), check, effect, control);
468 
469     // Turn the {node} into a {JSCreateArray} call.
470     NodeProperties::ReplaceEffectInput(node, effect);
471     for (int i = arity; i > 0; --i) {
472       NodeProperties::ReplaceValueInput(
473           node, NodeProperties::GetValueInput(node, i), i + 1);
474     }
475     NodeProperties::ReplaceValueInput(node, new_target, 1);
476     NodeProperties::ChangeOp(node, javascript()->CreateArray(arity, site));
477     return Changed(node);
478   } else if (feedback->IsWeakCell()) {
479     Handle<WeakCell> cell = Handle<WeakCell>::cast(feedback);
480     if (cell->value()->IsJSFunction()) {
481       Node* target_function =
482           jsgraph()->Constant(handle(cell->value(), isolate()));
483 
484       // Check that the {target} is still the {target_function}.
485       Node* check = graph()->NewNode(simplified()->ReferenceEqual(), target,
486                                      target_function);
487       effect =
488           graph()->NewNode(simplified()->CheckIf(), check, effect, control);
489 
490       // Specialize the JSCallConstruct node to the {target_function}.
491       NodeProperties::ReplaceValueInput(node, target_function, 0);
492       NodeProperties::ReplaceEffectInput(node, effect);
493       if (target == new_target) {
494         NodeProperties::ReplaceValueInput(node, target_function, arity + 1);
495       }
496 
497       // Try to further reduce the JSCallConstruct {node}.
498       Reduction const reduction = ReduceJSCallConstruct(node);
499       return reduction.Changed() ? reduction : Changed(node);
500     }
501   }
502 
503   return NoChange();
504 }
505 
graph() const506 Graph* JSCallReducer::graph() const { return jsgraph()->graph(); }
507 
isolate() const508 Isolate* JSCallReducer::isolate() const { return jsgraph()->isolate(); }
509 
common() const510 CommonOperatorBuilder* JSCallReducer::common() const {
511   return jsgraph()->common();
512 }
513 
javascript() const514 JSOperatorBuilder* JSCallReducer::javascript() const {
515   return jsgraph()->javascript();
516 }
517 
simplified() const518 SimplifiedOperatorBuilder* JSCallReducer::simplified() const {
519   return jsgraph()->simplified();
520 }
521 
522 }  // namespace compiler
523 }  // namespace internal
524 }  // namespace v8
525