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