Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(79)

Unified Diff: src/compiler/js-call-reducer.cc

Issue 2950773002: [turbofan] Introduce new JSCallWithArrayLike operator. (Closed)
Patch Set: REBASE Created 3 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « src/compiler/js-call-reducer.h ('k') | src/compiler/js-generic-lowering.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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) {
« no previous file with comments | « src/compiler/js-call-reducer.h ('k') | src/compiler/js-generic-lowering.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698