Index: src/compiler/js-call-reducer.cc |
diff --git a/src/compiler/js-call-reducer.cc b/src/compiler/js-call-reducer.cc |
index aca46624abec012eb2c27c968f1ee1b7b0fb96eb..feff55dd6264c33920d4e4535d4c08d6c8c08730 100644 |
--- a/src/compiler/js-call-reducer.cc |
+++ b/src/compiler/js-call-reducer.cc |
@@ -28,6 +28,8 @@ Reduction JSCallReducer::Reduce(Node* node) { |
return ReduceJSConstructWithSpread(node); |
case IrOpcode::kJSCall: |
return ReduceJSCall(node); |
+ case IrOpcode::kJSCallWithArrayLike: |
+ return ReduceJSCallWithArrayLike(node); |
case IrOpcode::kJSCallWithSpread: |
return ReduceJSCallWithSpread(node); |
default: |
@@ -95,18 +97,52 @@ Reduction JSCallReducer::ReduceNumberConstructor(Node* node) { |
return Changed(node); |
} |
+namespace { |
+ |
+bool CanBeNullOrUndefined(Node* node) { |
+ switch (node->opcode()) { |
+ case IrOpcode::kJSCreate: |
+ case IrOpcode::kJSCreateArguments: |
+ case IrOpcode::kJSCreateArray: |
+ case IrOpcode::kJSCreateClosure: |
+ case IrOpcode::kJSCreateIterResultObject: |
+ case IrOpcode::kJSCreateKeyValueArray: |
+ case IrOpcode::kJSCreateLiteralArray: |
+ case IrOpcode::kJSCreateLiteralObject: |
+ case IrOpcode::kJSCreateLiteralRegExp: |
+ case IrOpcode::kJSConstruct: |
+ case IrOpcode::kJSConstructForwardVarargs: |
+ case IrOpcode::kJSConstructWithSpread: |
+ case IrOpcode::kJSConvertReceiver: |
+ case IrOpcode::kJSToBoolean: |
+ case IrOpcode::kJSToInteger: |
+ case IrOpcode::kJSToLength: |
+ case IrOpcode::kJSToName: |
+ case IrOpcode::kJSToNumber: |
+ case IrOpcode::kJSToObject: |
+ case IrOpcode::kJSToString: |
+ case IrOpcode::kJSToPrimitiveToString: |
+ return false; |
+ case IrOpcode::kHeapConstant: { |
+ Handle<HeapObject> value = HeapObjectMatcher(node).Value(); |
+ Isolate* const isolate = value->GetIsolate(); |
+ return value->IsNull(isolate) || value->IsUndefined(isolate); |
+ } |
+ default: |
+ return true; |
+ } |
+} |
+ |
+} // namespace |
// ES6 section 19.2.3.1 Function.prototype.apply ( thisArg, argArray ) |
Reduction JSCallReducer::ReduceFunctionPrototypeApply(Node* node) { |
DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); |
- Node* target = NodeProperties::GetValueInput(node, 0); |
CallParameters const& p = CallParametersOf(node->op()); |
// Tail calls to Function.prototype.apply are not properly supported |
// down the pipeline, so we disable this optimization completely for |
// tail calls (for now). |
if (p.tail_call_mode() == TailCallMode::kAllow) return NoChange(); |
- Handle<JSFunction> apply = |
- Handle<JSFunction>::cast(HeapObjectMatcher(target).Value()); |
size_t arity = p.arity(); |
DCHECK_LE(2u, arity); |
ConvertReceiverMode convert_mode = ConvertReceiverMode::kAny; |
@@ -119,97 +155,102 @@ Reduction JSCallReducer::ReduceFunctionPrototypeApply(Node* node) { |
// The argArray was not provided, just remove the {target}. |
node->RemoveInput(0); |
--arity; |
- } else if (arity == 4) { |
- // Check if argArray is an arguments object, and {node} is the only value |
- // user of argArray (except for value uses in frame states). |
- Node* arg_array = NodeProperties::GetValueInput(node, 3); |
- if (arg_array->opcode() != IrOpcode::kJSCreateArguments) return NoChange(); |
- for (Edge edge : arg_array->use_edges()) { |
- Node* const user = edge.from(); |
- if (user == node) continue; |
- // Ignore uses as frame state's locals or parameters. |
- if (user->opcode() == IrOpcode::kStateValues) continue; |
- // Ignore uses as frame state's accumulator. |
- if (user->opcode() == IrOpcode::kFrameState && |
- user->InputAt(2) == arg_array) { |
- continue; |
- } |
- if (!NodeProperties::IsValueEdge(edge)) continue; |
- return NoChange(); |
- } |
- // Check if the arguments can be handled in the fast case (i.e. we don't |
- // have aliased sloppy arguments), and compute the {start_index} for |
- // rest parameters. |
- CreateArgumentsType const type = CreateArgumentsTypeOf(arg_array->op()); |
- Node* frame_state = NodeProperties::GetFrameStateInput(arg_array); |
- FrameStateInfo state_info = OpParameter<FrameStateInfo>(frame_state); |
- int start_index = 0; |
- // Determine the formal parameter count; |
- Handle<SharedFunctionInfo> shared; |
- if (!state_info.shared_info().ToHandle(&shared)) return NoChange(); |
- int formal_parameter_count = shared->internal_formal_parameter_count(); |
- if (type == CreateArgumentsType::kMappedArguments) { |
- // Mapped arguments (sloppy mode) that are aliased can only be handled |
- // here if there's no side-effect between the {node} and the {arg_array}. |
- // TODO(turbofan): Further relax this constraint. |
- if (formal_parameter_count != 0) { |
- Node* effect = NodeProperties::GetEffectInput(node); |
- while (effect != arg_array) { |
- if (effect->op()->EffectInputCount() != 1 || |
- !(effect->op()->properties() & Operator::kNoWrite)) { |
- return NoChange(); |
- } |
- effect = NodeProperties::GetEffectInput(effect); |
- } |
+ } else { |
+ Node* target = NodeProperties::GetValueInput(node, 1); |
+ Node* this_argument = NodeProperties::GetValueInput(node, 2); |
+ Node* arguments_list = NodeProperties::GetValueInput(node, 3); |
+ Node* context = NodeProperties::GetContextInput(node); |
+ Node* frame_state = NodeProperties::GetFrameStateInput(node); |
+ Node* effect = NodeProperties::GetEffectInput(node); |
+ Node* control = NodeProperties::GetControlInput(node); |
+ |
+ // If {arguments_list} cannot be null or undefined, we don't need |
+ // to expand this {node} to control-flow. |
+ if (!CanBeNullOrUndefined(arguments_list)) { |
+ // Massage the value inputs appropriately. |
+ node->ReplaceInput(0, target); |
+ node->ReplaceInput(1, this_argument); |
+ node->ReplaceInput(2, arguments_list); |
+ while (arity-- > 3) node->RemoveInput(3); |
+ |
+ // Morph the {node} to a {JSCallWithArrayLike}. |
+ NodeProperties::ChangeOp(node, |
+ javascript()->CallWithArrayLike(p.frequency())); |
+ Reduction const reduction = ReduceJSCallWithArrayLike(node); |
+ return reduction.Changed() ? reduction : Changed(node); |
+ } else { |
+ // Check whether {arguments_list} is null. |
+ Node* check_null = |
+ graph()->NewNode(simplified()->ReferenceEqual(), arguments_list, |
+ jsgraph()->NullConstant()); |
+ control = graph()->NewNode(common()->Branch(BranchHint::kFalse), |
+ check_null, control); |
+ Node* if_null = graph()->NewNode(common()->IfTrue(), control); |
+ control = graph()->NewNode(common()->IfFalse(), control); |
+ |
+ // Check whether {arguments_list} is undefined. |
+ Node* check_undefined = |
+ graph()->NewNode(simplified()->ReferenceEqual(), arguments_list, |
+ jsgraph()->UndefinedConstant()); |
+ control = graph()->NewNode(common()->Branch(BranchHint::kFalse), |
+ check_undefined, control); |
+ Node* if_undefined = graph()->NewNode(common()->IfTrue(), control); |
+ control = graph()->NewNode(common()->IfFalse(), control); |
+ |
+ // Lower to {JSCallWithArrayLike} if {arguments_list} is neither null |
+ // nor undefined. |
+ Node* effect0 = effect; |
+ Node* control0 = control; |
+ Node* value0 = effect0 = control0 = graph()->NewNode( |
+ javascript()->CallWithArrayLike(p.frequency()), target, this_argument, |
+ arguments_list, context, frame_state, effect0, control0); |
+ |
+ // Lower to {JSCall} if {arguments_list} is either null or undefined. |
+ Node* effect1 = effect; |
+ Node* control1 = |
+ graph()->NewNode(common()->Merge(2), if_null, if_undefined); |
+ Node* value1 = effect1 = control1 = |
+ graph()->NewNode(javascript()->Call(2), target, this_argument, |
+ context, frame_state, effect1, control1); |
+ |
+ // Rewire potential exception edges. |
+ Node* if_exception = nullptr; |
+ if (NodeProperties::IsExceptionalCall(node, &if_exception)) { |
+ // Create appropriate {IfException} and {IfSuccess} nodes. |
+ Node* if_exception0 = |
+ graph()->NewNode(common()->IfException(), control0, effect0); |
+ control0 = graph()->NewNode(common()->IfSuccess(), control0); |
+ Node* if_exception1 = |
+ graph()->NewNode(common()->IfException(), control1, effect1); |
+ control1 = graph()->NewNode(common()->IfSuccess(), control1); |
+ |
+ // Join the exception edges. |
+ Node* merge = |
+ graph()->NewNode(common()->Merge(2), if_exception0, if_exception1); |
+ Node* ephi = graph()->NewNode(common()->EffectPhi(2), if_exception0, |
+ if_exception1, merge); |
+ Node* phi = |
+ graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), |
+ if_exception0, if_exception1, merge); |
+ ReplaceWithValue(if_exception, phi, ephi, merge); |
} |
- } else if (type == CreateArgumentsType::kRestParameter) { |
- start_index = formal_parameter_count; |
- } |
- // Check if are applying to inlined arguments or to the arguments of |
- // the outermost function. |
- Node* outer_state = frame_state->InputAt(kFrameStateOuterStateInput); |
- if (outer_state->opcode() != IrOpcode::kFrameState) { |
- // Reduce {node} to a JSCallForwardVarargs operation, which just |
- // re-pushes the incoming arguments and calls the {target}. |
- node->RemoveInput(0); // Function.prototype.apply |
- node->RemoveInput(2); // arguments |
- NodeProperties::ChangeOp(node, javascript()->CallForwardVarargs( |
- 2, start_index, p.tail_call_mode())); |
- return Changed(node); |
- } |
- // Get to the actual frame state from which to extract the arguments; |
- // we can only optimize this in case the {node} was already inlined into |
- // some other function (and same for the {arg_array}). |
- FrameStateInfo outer_info = OpParameter<FrameStateInfo>(outer_state); |
- if (outer_info.type() == FrameStateType::kArgumentsAdaptor) { |
- // Need to take the parameters from the arguments adaptor. |
- frame_state = outer_state; |
- } |
- // Remove the argArray input from the {node}. |
- node->RemoveInput(static_cast<int>(--arity)); |
- // Add the actual parameters to the {node}, skipping the receiver, |
- // starting from {start_index}. |
- Node* const parameters = frame_state->InputAt(kFrameStateParametersInput); |
- for (int i = start_index + 1; i < parameters->InputCount(); ++i) { |
- node->InsertInput(graph()->zone(), static_cast<int>(arity), |
- parameters->InputAt(i)); |
- ++arity; |
+ |
+ // Join control paths. |
+ control = graph()->NewNode(common()->Merge(2), control0, control1); |
+ effect = |
+ graph()->NewNode(common()->EffectPhi(2), effect0, effect1, control); |
+ Node* value = |
+ graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), |
+ value0, value1, control); |
+ ReplaceWithValue(node, value, effect, control); |
+ return Replace(value); |
} |
- // Drop the {target} from the {node}. |
- node->RemoveInput(0); |
- --arity; |
- } else { |
- return NoChange(); |
} |
// Change {node} to the new {JSCall} operator. |
NodeProperties::ChangeOp( |
node, |
javascript()->Call(arity, p.frequency(), VectorSlotPair(), convert_mode, |
p.tail_call_mode())); |
- // Change context of {node} to the Function.prototype.apply context, |
- // to ensure any exception is thrown in the correct context. |
- NodeProperties::ReplaceContextInput( |
- node, jsgraph()->HeapConstant(handle(apply->context(), isolate()))); |
// Try to further reduce the JSCall {node}. |
Reduction const reduction = ReduceJSCall(node); |
return reduction.Changed() ? reduction : Changed(node); |
@@ -374,6 +415,27 @@ Reduction JSCallReducer::ReduceObjectPrototypeIsPrototypeOf(Node* node) { |
return Changed(node); |
} |
+// ES6 section 26.1.1 Reflect.apply ( target, thisArgument, argumentsList ) |
+Reduction JSCallReducer::ReduceReflectApply(Node* node) { |
+ DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); |
+ CallParameters const& p = CallParametersOf(node->op()); |
+ int arity = static_cast<int>(p.arity() - 2); |
+ DCHECK_LE(0, arity); |
+ // Massage value inputs appropriately. |
+ node->RemoveInput(0); |
+ node->RemoveInput(0); |
+ while (arity < 3) { |
+ node->InsertInput(graph()->zone(), arity++, jsgraph()->UndefinedConstant()); |
+ } |
+ while (arity-- > 3) { |
+ node->RemoveInput(arity); |
+ } |
+ NodeProperties::ChangeOp(node, |
+ javascript()->CallWithArrayLike(p.frequency())); |
+ Reduction const reduction = ReduceJSCallWithArrayLike(node); |
+ return reduction.Changed() ? reduction : Changed(node); |
+} |
+ |
// ES6 section 26.1.7 Reflect.getPrototypeOf ( target ) |
Reduction JSCallReducer::ReduceReflectGetPrototypeOf(Node* node) { |
DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); |
@@ -598,27 +660,33 @@ Reduction JSCallReducer::ReduceCallApiFunction( |
return Changed(node); |
} |
-Reduction JSCallReducer::ReduceSpreadCall(Node* node, int arity) { |
- DCHECK(node->opcode() == IrOpcode::kJSCallWithSpread || |
+Reduction JSCallReducer::ReduceCallOrConstructWithArrayLikeOrSpread( |
+ Node* node, int arity, CallFrequency const& frequency) { |
+ DCHECK(node->opcode() == IrOpcode::kJSCallWithArrayLike || |
+ node->opcode() == IrOpcode::kJSCallWithSpread || |
node->opcode() == IrOpcode::kJSConstructWithSpread); |
- // Do check to make sure we can actually avoid iteration. |
- if (!isolate()->initial_array_iterator_prototype_map()->is_stable()) { |
+ // In case of a call/construct with spread, we need to |
+ // ensure that it's safe to avoid the actual iteration. |
+ if ((node->opcode() == IrOpcode::kJSCallWithSpread || |
+ node->opcode() == IrOpcode::kJSConstructWithSpread) && |
+ !isolate()->initial_array_iterator_prototype_map()->is_stable()) { |
return NoChange(); |
} |
- Node* spread = NodeProperties::GetValueInput(node, arity); |
- |
- // Check if spread is an arguments object, and {node} is the only value user |
- // of spread (except for value uses in frame states). |
- if (spread->opcode() != IrOpcode::kJSCreateArguments) return NoChange(); |
- for (Edge edge : spread->use_edges()) { |
+ // Check if {arguments_list} is an arguments object, and {node} is the only |
+ // value user of {arguments_list} (except for value uses in frame states). |
+ Node* arguments_list = NodeProperties::GetValueInput(node, arity); |
+ if (arguments_list->opcode() != IrOpcode::kJSCreateArguments) |
+ return NoChange(); |
+ for (Edge edge : arguments_list->use_edges()) { |
Node* const user = edge.from(); |
if (user == node) continue; |
// Ignore uses as frame state's locals or parameters. |
if (user->opcode() == IrOpcode::kStateValues) continue; |
// Ignore uses as frame state's accumulator. |
- if (user->opcode() == IrOpcode::kFrameState && user->InputAt(2) == spread) { |
+ if (user->opcode() == IrOpcode::kFrameState && |
+ user->InputAt(2) == arguments_list) { |
continue; |
} |
if (!NodeProperties::IsValueEdge(edge)) continue; |
@@ -627,9 +695,9 @@ Reduction JSCallReducer::ReduceSpreadCall(Node* node, int arity) { |
// Get to the actual frame state from which to extract the arguments; |
// we can only optimize this in case the {node} was already inlined into |
- // some other function (and same for the {spread}). |
- CreateArgumentsType const type = CreateArgumentsTypeOf(spread->op()); |
- Node* frame_state = NodeProperties::GetFrameStateInput(spread); |
+ // some other function (and same for the {arguments_list}). |
+ CreateArgumentsType const type = CreateArgumentsTypeOf(arguments_list->op()); |
+ Node* frame_state = NodeProperties::GetFrameStateInput(arguments_list); |
FrameStateInfo state_info = OpParameter<FrameStateInfo>(frame_state); |
int start_index = 0; |
// Determine the formal parameter count; |
@@ -642,7 +710,7 @@ Reduction JSCallReducer::ReduceSpreadCall(Node* node, int arity) { |
// TODO(turbofan): Further relax this constraint. |
if (formal_parameter_count != 0) { |
Node* effect = NodeProperties::GetEffectInput(node); |
- while (effect != spread) { |
+ while (effect != arguments_list) { |
if (effect->op()->EffectInputCount() != 1 || |
!(effect->op()->properties() & Operator::kNoWrite)) { |
return NoChange(); |
@@ -653,24 +721,34 @@ Reduction JSCallReducer::ReduceSpreadCall(Node* node, int arity) { |
} else if (type == CreateArgumentsType::kRestParameter) { |
start_index = formal_parameter_count; |
- // Only check the array iterator protector when we have a rest object. |
- if (!isolate()->IsArrayIteratorLookupChainIntact()) return NoChange(); |
+ // For spread calls/constructs with rest parameters we need to ensure that |
+ // the array iterator protector is intact, which guards that the rest |
+ // parameter iteration is not observable. |
+ if (node->opcode() == IrOpcode::kJSCallWithSpread || |
+ node->opcode() == IrOpcode::kJSConstructWithSpread) { |
+ if (!isolate()->IsArrayIteratorLookupChainIntact()) return NoChange(); |
+ dependencies()->AssumePropertyCell(factory()->array_iterator_protector()); |
+ } |
} |
- // Install appropriate code dependencies. |
- dependencies()->AssumeMapStable( |
- isolate()->initial_array_iterator_prototype_map()); |
- if (type == CreateArgumentsType::kRestParameter) { |
- dependencies()->AssumePropertyCell(factory()->array_iterator_protector()); |
+ // For call/construct with spread, we need to also install a code |
+ // dependency on the initial %ArrayIteratorPrototype% map here to |
+ // ensure that no one messes with the next method. |
+ if (node->opcode() == IrOpcode::kJSCallWithSpread || |
+ node->opcode() == IrOpcode::kJSConstructWithSpread) { |
+ dependencies()->AssumeMapStable( |
+ isolate()->initial_array_iterator_prototype_map()); |
} |
- // Remove the spread input from the {node}. |
+ |
+ // Remove the {arguments_list} input from the {node}. |
node->RemoveInput(arity--); |
// Check if are spreading to inlined arguments or to the arguments of |
// the outermost function. |
Node* outer_state = frame_state->InputAt(kFrameStateOuterStateInput); |
if (outer_state->opcode() != IrOpcode::kFrameState) { |
Operator const* op = |
- (node->opcode() == IrOpcode::kJSCallWithSpread) |
+ (node->opcode() == IrOpcode::kJSCallWithArrayLike || |
+ node->opcode() == IrOpcode::kJSCallWithSpread) |
? javascript()->CallForwardVarargs(arity + 1, start_index, |
TailCallMode::kDisallow) |
: javascript()->ConstructForwardVarargs(arity + 2, start_index); |
@@ -692,16 +770,16 @@ Reduction JSCallReducer::ReduceSpreadCall(Node* node, int arity) { |
parameters->InputAt(i)); |
} |
- // TODO(turbofan): Collect call counts on spread call/construct and thread it |
- // through here. |
- if (node->opcode() == IrOpcode::kJSCallWithSpread) { |
- NodeProperties::ChangeOp(node, javascript()->Call(arity + 1)); |
- Reduction const r = ReduceJSCall(node); |
- return r.Changed() ? r : Changed(node); |
+ if (node->opcode() == IrOpcode::kJSCallWithArrayLike || |
+ node->opcode() == IrOpcode::kJSCallWithSpread) { |
+ NodeProperties::ChangeOp(node, javascript()->Call(arity + 1, frequency)); |
+ Reduction const reduction = ReduceJSCall(node); |
+ return reduction.Changed() ? reduction : Changed(node); |
} else { |
- NodeProperties::ChangeOp(node, javascript()->Construct(arity + 2)); |
- Reduction const r = ReduceJSConstruct(node); |
- return r.Changed() ? r : Changed(node); |
+ NodeProperties::ChangeOp(node, |
+ javascript()->Construct(arity + 2, frequency)); |
+ Reduction const reduction = ReduceJSConstruct(node); |
+ return reduction.Changed() ? reduction : Changed(node); |
} |
} |
@@ -775,6 +853,8 @@ Reduction JSCallReducer::ReduceJSCall(Node* node) { |
return ReduceObjectPrototypeGetProto(node); |
case Builtins::kObjectPrototypeIsPrototypeOf: |
return ReduceObjectPrototypeIsPrototypeOf(node); |
+ case Builtins::kReflectApply: |
+ return ReduceReflectApply(node); |
case Builtins::kReflectGetPrototypeOf: |
return ReduceReflectGetPrototypeOf(node); |
case Builtins::kArrayForEach: |
@@ -905,13 +985,22 @@ Reduction JSCallReducer::ReduceJSCall(Node* node) { |
return NoChange(); |
} |
+Reduction JSCallReducer::ReduceJSCallWithArrayLike(Node* node) { |
+ DCHECK_EQ(IrOpcode::kJSCallWithArrayLike, node->opcode()); |
+ CallFrequency frequency = CallFrequencyOf(node->op()); |
+ return ReduceCallOrConstructWithArrayLikeOrSpread(node, 2, frequency); |
+} |
+ |
Reduction JSCallReducer::ReduceJSCallWithSpread(Node* node) { |
DCHECK_EQ(IrOpcode::kJSCallWithSpread, node->opcode()); |
SpreadWithArityParameter const& p = SpreadWithArityParameterOf(node->op()); |
DCHECK_LE(3u, p.arity()); |
int arity = static_cast<int>(p.arity() - 1); |
- return ReduceSpreadCall(node, arity); |
+ // TODO(turbofan): Collect call counts on spread call/construct and thread it |
+ // through here. |
+ CallFrequency frequency; |
+ return ReduceCallOrConstructWithArrayLikeOrSpread(node, arity, frequency); |
} |
Reduction JSCallReducer::ReduceJSConstruct(Node* node) { |
@@ -1035,7 +1124,10 @@ Reduction JSCallReducer::ReduceJSConstructWithSpread(Node* node) { |
DCHECK_LE(3u, p.arity()); |
int arity = static_cast<int>(p.arity() - 2); |
- return ReduceSpreadCall(node, arity); |
+ // TODO(turbofan): Collect call counts on spread call/construct and thread it |
+ // through here. |
+ CallFrequency frequency; |
+ return ReduceCallOrConstructWithArrayLikeOrSpread(node, arity, frequency); |
} |
Reduction JSCallReducer::ReduceReturnReceiver(Node* node) { |