Chromium Code Reviews| 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) { |
|
petermarshall
2017/06/20 12:21:57
Maybe you could pull this condition into a boolean
Benedikt Meurer
2017/06/20 12:34:41
I thought about it, but decided against it, b/c
(
|
| + 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) { |