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