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