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
13namespace v8 {
14namespace internal {
15namespace compiler {
16
17Reduction 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
31Reduction 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
60Reduction 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 )
75Reduction 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)
158Reduction 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
192namespace {
193
194// TODO(turbofan): Shall we move this to the NodeProperties? Or some (untyped)
195// alias analyzer?
196bool 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?!
209MaybeHandle<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__
234Reduction 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
254Reduction 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
395Reduction 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
506Graph* JSCallReducer::graph() const { return jsgraph()->graph(); }
507
508Isolate* JSCallReducer::isolate() const { return jsgraph()->isolate(); }
509
510CommonOperatorBuilder* JSCallReducer::common() const {
511  return jsgraph()->common();
512}
513
514JSOperatorBuilder* JSCallReducer::javascript() const {
515  return jsgraph()->javascript();
516}
517
518SimplifiedOperatorBuilder* JSCallReducer::simplified() const {
519  return jsgraph()->simplified();
520}
521
522}  // namespace compiler
523}  // namespace internal
524}  // namespace v8
525