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 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 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 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 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 ) 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) 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 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 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 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 502 Graph* JSCallReducer::graph() const { return jsgraph()->graph(); } 503 504 505 Isolate* JSCallReducer::isolate() const { return jsgraph()->isolate(); } 506 507 508 CommonOperatorBuilder* JSCallReducer::common() const { 509 return jsgraph()->common(); 510 } 511 512 513 JSOperatorBuilder* JSCallReducer::javascript() const { 514 return jsgraph()->javascript(); 515 } 516 517 } // namespace compiler 518 } // namespace internal 519 } // namespace v8 520