Revert "[Turbofan] Array.prototype.filter inlining."

This reverts commit 9fd029ef25.

Due to a ClusterFuzz issue.

TBR=mstarzinger@chromium.org

Bug: chromium:776511
Change-Id: I0f30e2e8de97f3c437a1756c82e645828358ad91
Reviewed-on: https://chromium-review.googlesource.com/730006
Reviewed-by: Michael Stanton <mvstanton@chromium.org>
Commit-Queue: Michael Stanton <mvstanton@chromium.org>
Cr-Commit-Position: refs/heads/master@{#48775}
This commit is contained in:
Mike Stanton 2017-10-20 09:59:24 +02:00 committed by Commit Bot
parent 14917b6531
commit f0aa474e9a
9 changed files with 94 additions and 952 deletions

View File

@ -1555,66 +1555,6 @@ TF_BUILTIN(ArrayFilterLoopContinuation, ArrayBuiltinCodeStubAssembler) {
&ArrayBuiltinCodeStubAssembler::NullPostLoopAction);
}
TF_BUILTIN(ArrayFilterLoopEagerDeoptContinuation,
ArrayBuiltinCodeStubAssembler) {
Node* context = Parameter(Descriptor::kContext);
Node* receiver = Parameter(Descriptor::kReceiver);
Node* callbackfn = Parameter(Descriptor::kCallbackFn);
Node* this_arg = Parameter(Descriptor::kThisArg);
Node* array = Parameter(Descriptor::kArray);
Node* initial_k = Parameter(Descriptor::kInitialK);
Node* len = Parameter(Descriptor::kLength);
Node* to = Parameter(Descriptor::kTo);
Callable stub(
Builtins::CallableFor(isolate(), Builtins::kArrayFilterLoopContinuation));
Return(CallStub(stub, context, receiver, callbackfn, this_arg, array,
receiver, initial_k, len, to));
}
TF_BUILTIN(ArrayFilterLoopLazyDeoptContinuation,
ArrayBuiltinCodeStubAssembler) {
Node* context = Parameter(Descriptor::kContext);
Node* receiver = Parameter(Descriptor::kReceiver);
Node* callbackfn = Parameter(Descriptor::kCallbackFn);
Node* this_arg = Parameter(Descriptor::kThisArg);
Node* array = Parameter(Descriptor::kArray);
Node* initial_k = Parameter(Descriptor::kInitialK);
Node* len = Parameter(Descriptor::kLength);
Node* value_k = Parameter(Descriptor::kValueK);
Node* result = Parameter(Descriptor::kResult);
VARIABLE(to, MachineRepresentation::kTagged, Parameter(Descriptor::kTo));
// This custom lazy deopt point is right after the callback. filter() needs
// to pick up at the next step, which is setting the callback result in
// the output array. After incrementing k and to, we can glide into the loop
// continuation builtin.
Label true_continue(this, &to), false_continue(this);
// iii. If selected is true, then...
BranchIfToBooleanIsTrue(result, &true_continue, &false_continue);
BIND(&true_continue);
{
// 1. Perform ? CreateDataPropertyOrThrow(A, ToString(to), kValue).
CallRuntime(Runtime::kCreateDataProperty, context, array, to.value(),
value_k);
// 2. Increase to by 1.
to.Bind(NumberInc(to.value()));
Goto(&false_continue);
}
BIND(&false_continue);
// Increment k.
initial_k = NumberInc(initial_k);
Callable stub(
Builtins::CallableFor(isolate(), Builtins::kArrayFilterLoopContinuation));
Return(CallStub(stub, context, receiver, callbackfn, this_arg, array,
receiver, initial_k, len, to.value()));
}
TF_BUILTIN(ArrayFilter, ArrayBuiltinCodeStubAssembler) {
Node* argc =
ChangeInt32ToIntPtr(Parameter(BuiltinDescriptor::kArgumentsCount));

View File

@ -287,10 +287,6 @@ namespace internal {
TFS(ArrayFilterLoopContinuation, kReceiver, kCallbackFn, kThisArg, kArray, \
kObject, kInitialK, kLength, kTo) \
TFJ(ArrayFilter, SharedFunctionInfo::kDontAdaptArgumentsSentinel) \
TFJ(ArrayFilterLoopEagerDeoptContinuation, 6, kCallbackFn, kThisArg, kArray, \
kInitialK, kLength, kTo) \
TFJ(ArrayFilterLoopLazyDeoptContinuation, 8, kCallbackFn, kThisArg, kArray, \
kInitialK, kLength, kValueK, kTo, kResult) \
/* ES6 #sec-array.prototype.foreach */ \
TFS(ArrayMapLoopContinuation, kReceiver, kCallbackFn, kThisArg, kArray, \
kObject, kInitialK, kLength, kTo) \

View File

@ -197,16 +197,6 @@ Callable Builtins::CallableFor(Isolate* isolate, Name name) {
BUILTIN_CODE(isolate, ArrayMapLoopLazyDeoptContinuation);
return Callable(code, BuiltinDescriptor(isolate));
}
case kArrayFilterLoopEagerDeoptContinuation: {
Handle<Code> code =
BUILTIN_CODE(isolate, ArrayFilterLoopEagerDeoptContinuation);
return Callable(code, BuiltinDescriptor(isolate));
}
case kArrayFilterLoopLazyDeoptContinuation: {
Handle<Code> code =
BUILTIN_CODE(isolate, ArrayFilterLoopLazyDeoptContinuation);
return Callable(code, BuiltinDescriptor(isolate));
}
default:
UNREACHABLE();
}
@ -245,8 +235,6 @@ bool Builtins::IsLazy(int index) {
case kArrayForEachLoopLazyDeoptContinuation: // https://crbug.com/v8/6786.
case kArrayMapLoopEagerDeoptContinuation: // https://crbug.com/v8/6786.
case kArrayMapLoopLazyDeoptContinuation: // https://crbug.com/v8/6786.
case kArrayFilterLoopEagerDeoptContinuation: // https://crbug.com/v8/6786.
case kArrayFilterLoopLazyDeoptContinuation: // https://crbug.com/v8/6786.
case kCheckOptimizationMarker:
case kCompileLazy:
case kDeserializeLazy:

View File

@ -833,14 +833,19 @@ Reduction JSCallReducer::ReduceArrayForEach(Handle<JSFunction> function,
// Check whether the given callback function is callable. Note that this has
// to happen outside the loop to make sure we also throw on empty arrays.
Node* check = graph()->NewNode(simplified()->ObjectIsCallable(), fncallback);
Node* check_branch =
graph()->NewNode(common()->Branch(BranchHint::kTrue), check, control);
Node* check_fail = graph()->NewNode(common()->IfFalse(), check_branch);
Node* check_frame_state = CreateJavaScriptBuiltinContinuationFrameState(
jsgraph(), function, Builtins::kArrayForEachLoopLazyDeoptContinuation,
node->InputAt(0), context, &checkpoint_params[0], stack_parameters,
outer_frame_state, ContinuationFrameStateMode::LAZY);
Node* check_fail = nullptr;
Node* check_throw = nullptr;
WireInCallbackIsCallableCheck(fncallback, context, check_frame_state, effect,
&control, &check_fail, &check_throw);
Node* check_throw = check_fail = graph()->NewNode(
javascript()->CallRuntime(Runtime::kThrowTypeError, 2),
jsgraph()->Constant(MessageTemplate::kCalledNonCallable), fncallback,
context, check_frame_state, effect, check_fail);
control = graph()->NewNode(common()->IfTrue(), check_branch);
// Start the loop.
Node* loop = control = graph()->NewNode(common()->Loop(2), control, control);
@ -875,7 +880,24 @@ Reduction JSCallReducer::ReduceArrayForEach(Handle<JSFunction> function,
simplified()->CheckMaps(CheckMapsFlag::kNone, receiver_maps), receiver,
effect, control);
Node* element = SafeLoadElement(kind, receiver, control, &effect, &k);
// Make sure that the access is still in bounds, since the callback could have
// changed the array's size.
Node* length = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSArrayLength(PACKED_ELEMENTS)),
receiver, effect, control);
k = effect =
graph()->NewNode(simplified()->CheckBounds(), k, length, effect, control);
// Reload the elements pointer before calling the callback, since the previous
// callback might have resized the array causing the elements buffer to be
// re-allocated.
Node* elements = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSObjectElements()), receiver,
effect, control);
Node* element = effect = graph()->NewNode(
simplified()->LoadElement(AccessBuilder::ForFixedArrayElement(kind)),
elements, k, effect, control);
Node* next_k =
graph()->NewNode(simplified()->NumberAdd(), k, jsgraph()->Constant(1));
@ -915,8 +937,23 @@ Reduction JSCallReducer::ReduceArrayForEach(Handle<JSFunction> function,
// Rewire potential exception edges.
Node* on_exception = nullptr;
if (NodeProperties::IsExceptionalCall(node, &on_exception)) {
RewirePostCallbackExceptionEdges(check_throw, on_exception, effect,
&check_fail, &control);
// Create appropriate {IfException} and {IfSuccess} nodes.
Node* if_exception0 =
graph()->NewNode(common()->IfException(), check_throw, check_fail);
check_fail = graph()->NewNode(common()->IfSuccess(), check_fail);
Node* if_exception1 =
graph()->NewNode(common()->IfException(), effect, control);
control = graph()->NewNode(common()->IfSuccess(), control);
// 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(on_exception, phi, ephi, merge);
}
if (IsHoleyElementsKind(kind)) {
@ -939,10 +976,9 @@ Reduction JSCallReducer::ReduceArrayForEach(Handle<JSFunction> function,
control = if_false;
effect = eloop;
// Wire up the branch for the case when IsCallable fails for the callback.
// Since {check_throw} is an unconditional throw, it's impossible to
// return a successful completion. Therefore, we simply connect the successful
// completion to the graph end.
// The above %ThrowTypeError runtime call is an unconditional throw, making
// it impossible to return a successful completion in this case. We simply
// connect the successful completion to the graph end.
Node* terminate =
graph()->NewNode(common()->Throw(), check_throw, check_fail);
NodeProperties::MergeControlToEnd(graph(), common(), terminate);
@ -1033,14 +1069,19 @@ Reduction JSCallReducer::ReduceArrayMap(Handle<JSFunction> function,
// Check whether the given callback function is callable. Note that this has
// to happen outside the loop to make sure we also throw on empty arrays.
Node* check = graph()->NewNode(simplified()->ObjectIsCallable(), fncallback);
Node* check_branch =
graph()->NewNode(common()->Branch(BranchHint::kTrue), check, control);
Node* check_fail = graph()->NewNode(common()->IfFalse(), check_branch);
Node* check_frame_state = CreateJavaScriptBuiltinContinuationFrameState(
jsgraph(), function, Builtins::kArrayMapLoopLazyDeoptContinuation,
node->InputAt(0), context, &checkpoint_params[0], stack_parameters,
outer_frame_state, ContinuationFrameStateMode::LAZY);
Node* check_fail = nullptr;
Node* check_throw = nullptr;
WireInCallbackIsCallableCheck(fncallback, context, check_frame_state, effect,
&control, &check_fail, &check_throw);
Node* check_throw = check_fail = graph()->NewNode(
javascript()->CallRuntime(Runtime::kThrowTypeError, 2),
jsgraph()->Constant(MessageTemplate::kCalledNonCallable), fncallback,
context, check_frame_state, effect, check_fail);
control = graph()->NewNode(common()->IfTrue(), check_branch);
// Start the loop.
Node* loop = control = graph()->NewNode(common()->Loop(2), control, control);
@ -1075,7 +1116,24 @@ Reduction JSCallReducer::ReduceArrayMap(Handle<JSFunction> function,
simplified()->CheckMaps(CheckMapsFlag::kNone, receiver_maps), receiver,
effect, control);
Node* element = SafeLoadElement(kind, receiver, control, &effect, &k);
// Make sure that the access is still in bounds, since the callback could have
// changed the array's size.
Node* length = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSArrayLength(kind)), receiver,
effect, control);
k = effect =
graph()->NewNode(simplified()->CheckBounds(), k, length, effect, control);
// Reload the elements pointer before calling the callback, since the previous
// callback might have resized the array causing the elements buffer to be
// re-allocated.
Node* elements = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSObjectElements()), receiver,
effect, control);
Node* element = effect = graph()->NewNode(
simplified()->LoadElement(AccessBuilder::ForFixedArrayElement(kind)),
elements, k, effect, control);
Node* next_k =
graph()->NewNode(simplified()->NumberAdd(), k, jsgraph()->OneConstant());
@ -1094,8 +1152,23 @@ Reduction JSCallReducer::ReduceArrayMap(Handle<JSFunction> function,
// Rewire potential exception edges.
Node* on_exception = nullptr;
if (NodeProperties::IsExceptionalCall(node, &on_exception)) {
RewirePostCallbackExceptionEdges(check_throw, on_exception, effect,
&check_fail, &control);
// Create appropriate {IfException} and {IfSuccess} nodes.
Node* if_exception0 =
graph()->NewNode(common()->IfException(), check_throw, check_fail);
check_fail = graph()->NewNode(common()->IfSuccess(), check_fail);
Node* if_exception1 =
graph()->NewNode(common()->IfException(), effect, control);
control = graph()->NewNode(common()->IfSuccess(), control);
// 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(on_exception, phi, ephi, merge);
}
Handle<Map> double_map(Map::cast(
@ -1115,10 +1188,9 @@ Reduction JSCallReducer::ReduceArrayMap(Handle<JSFunction> function,
control = if_false;
effect = eloop;
// Wire up the branch for the case when IsCallable fails for the callback.
// Since {check_throw} is an unconditional throw, it's impossible to
// return a successful completion. Therefore, we simply connect the successful
// completion to the graph end.
// The above %ThrowTypeError runtime call is an unconditional throw, making
// it impossible to return a successful completion in this case. We simply
// connect the successful completion to the graph end.
Node* terminate =
graph()->NewNode(common()->Throw(), check_throw, check_fail);
NodeProperties::MergeControlToEnd(graph(), common(), terminate);
@ -1127,344 +1199,6 @@ Reduction JSCallReducer::ReduceArrayMap(Handle<JSFunction> function,
return Replace(a);
}
Reduction JSCallReducer::ReduceArrayFilter(Handle<JSFunction> function,
Node* node) {
if (!FLAG_turbo_inline_array_builtins) return NoChange();
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
Node* outer_frame_state = NodeProperties::GetFrameStateInput(node);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
Node* context = NodeProperties::GetContextInput(node);
CallParameters const& p = CallParametersOf(node->op());
// Try to determine the {receiver} map.
Node* receiver = NodeProperties::GetValueInput(node, 1);
Node* fncallback = node->op()->ValueInputCount() > 2
? NodeProperties::GetValueInput(node, 2)
: jsgraph()->UndefinedConstant();
Node* this_arg = node->op()->ValueInputCount() > 3
? NodeProperties::GetValueInput(node, 3)
: jsgraph()->UndefinedConstant();
ZoneHandleSet<Map> receiver_maps;
NodeProperties::InferReceiverMapsResult result =
NodeProperties::InferReceiverMaps(receiver, effect, &receiver_maps);
if (result != NodeProperties::kReliableReceiverMaps) {
return NoChange();
}
// And ensure that any changes to the Array species constructor cause deopt.
if (!isolate()->IsArraySpeciesLookupChainIntact()) return NoChange();
if (receiver_maps.size() == 0) return NoChange();
const ElementsKind kind = receiver_maps[0]->elements_kind();
// TODO(danno): Handle holey and double elements kinds.
if (!IsFastPackedElementsKind(kind) || IsDoubleElementsKind(kind)) {
return NoChange();
}
for (Handle<Map> receiver_map : receiver_maps) {
if (!CanInlineArrayIteratingBuiltin(receiver_map)) {
return NoChange();
}
// We can handle different maps, as long as their elements kind are the
// same.
if (receiver_map->elements_kind() != kind) {
return NoChange();
}
}
dependencies()->AssumePropertyCell(factory()->species_protector());
Handle<JSFunction> handle_constructor(
JSFunction::cast(
native_context()->GetInitialJSArrayMap(kind)->GetConstructor()),
isolate());
Node* array_constructor = jsgraph()->HeapConstant(handle_constructor);
Node* k = jsgraph()->ZeroConstant();
Node* to = jsgraph()->ZeroConstant();
// Make sure the map hasn't changed before we construct the output array.
effect = graph()->NewNode(
simplified()->CheckMaps(CheckMapsFlag::kNone, receiver_maps), receiver,
effect, control);
Node* original_length = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSArrayLength(kind)), receiver,
effect, control);
// This array should be PACKED_SMI_ELEMENTS because of the zero length.
// Even though {JSCreateArray} is not marked as {kNoThrow}, we can elide the
// exceptional projections because it cannot throw with the given parameters.
Node* a = control = effect = graph()->NewNode(
javascript()->CreateArray(1, Handle<AllocationSite>::null()),
array_constructor, array_constructor, jsgraph()->ZeroConstant(), context,
outer_frame_state, effect, control);
// Check whether the given callback function is callable. Note that this has
// to happen outside the loop to make sure we also throw on empty arrays.
Node* check_fail = nullptr;
Node* check_throw = nullptr;
{
// This frame state doesn't ever call the deopt continuation, it's only
// necessary to specifiy a continuation in order to handle the exceptional
// case. We don't have all the values available to completely fill out
// checkpoint_params yet, but that's okay because it'll never be called.
// Therefore, "to" is mentioned twice, once standing in for the k_value
// value.
std::vector<Node*> checkpoint_params(
{receiver, fncallback, this_arg, a, k, original_length, to, to});
const int stack_parameters = static_cast<int>(checkpoint_params.size());
Node* check_frame_state = CreateJavaScriptBuiltinContinuationFrameState(
jsgraph(), function, Builtins::kArrayFilterLoopLazyDeoptContinuation,
node->InputAt(0), context, &checkpoint_params[0], stack_parameters,
outer_frame_state, ContinuationFrameStateMode::LAZY);
WireInCallbackIsCallableCheck(fncallback, context, check_frame_state,
effect, &control, &check_fail, &check_throw);
}
// Start the loop.
Node* loop = control = graph()->NewNode(common()->Loop(2), control, control);
Node* eloop = effect =
graph()->NewNode(common()->EffectPhi(2), effect, effect, loop);
Node* vloop = k = graph()->NewNode(
common()->Phi(MachineRepresentation::kTagged, 2), k, k, loop);
Node* v_to_loop = to = graph()->NewNode(
common()->Phi(MachineRepresentation::kTaggedSigned, 2), to, to, loop);
control = loop;
effect = eloop;
Node* continue_test =
graph()->NewNode(simplified()->NumberLessThan(), k, original_length);
Node* continue_branch = graph()->NewNode(common()->Branch(BranchHint::kTrue),
continue_test, control);
Node* if_true = graph()->NewNode(common()->IfTrue(), continue_branch);
Node* if_false = graph()->NewNode(common()->IfFalse(), continue_branch);
control = if_true;
{
std::vector<Node*> checkpoint_params(
{receiver, fncallback, this_arg, a, k, original_length, to});
const int stack_parameters = static_cast<int>(checkpoint_params.size());
Node* frame_state = CreateJavaScriptBuiltinContinuationFrameState(
jsgraph(), function, Builtins::kArrayFilterLoopEagerDeoptContinuation,
node->InputAt(0), context, &checkpoint_params[0], stack_parameters,
outer_frame_state, ContinuationFrameStateMode::EAGER);
effect =
graph()->NewNode(common()->Checkpoint(), frame_state, effect, control);
}
// Make sure the map hasn't changed during the iteration.
effect = graph()->NewNode(
simplified()->CheckMaps(CheckMapsFlag::kNone, receiver_maps), receiver,
effect, control);
Node* element = SafeLoadElement(kind, receiver, control, &effect, &k);
Node* next_k =
graph()->NewNode(simplified()->NumberAdd(), k, jsgraph()->OneConstant());
Node* callback_value = nullptr;
{
// This frame state is dealt with by hand in
// Builtins::kArrayFilterLoopLazyDeoptContinuation.
std::vector<Node*> checkpoint_params(
{receiver, fncallback, this_arg, a, k, original_length, element, to});
const int stack_parameters = static_cast<int>(checkpoint_params.size());
Node* frame_state = CreateJavaScriptBuiltinContinuationFrameState(
jsgraph(), function, Builtins::kArrayFilterLoopLazyDeoptContinuation,
node->InputAt(0), context, &checkpoint_params[0], stack_parameters,
outer_frame_state, ContinuationFrameStateMode::LAZY);
callback_value = control = effect = graph()->NewNode(
javascript()->Call(5, p.frequency()), fncallback, this_arg, element, k,
receiver, context, frame_state, effect, control);
}
// Rewire potential exception edges.
Node* on_exception = nullptr;
if (NodeProperties::IsExceptionalCall(node, &on_exception)) {
RewirePostCallbackExceptionEdges(check_throw, on_exception, effect,
&check_fail, &control);
}
// We need an eager frame state for right after the callback function
// returned, just in case an attempt to grow the output array fails.
//
// Note that we are intentionally reusing the
// Builtins::kArrayFilterLoopLazyDeoptContinuation as an *eager* entry
// point in this case. This is safe, because re-evaluating a [ToBoolean]
// coercion is safe.
{
std::vector<Node*> checkpoint_params({receiver, fncallback, this_arg, a, k,
original_length, element, to,
callback_value});
const int stack_parameters = static_cast<int>(checkpoint_params.size());
Node* frame_state = CreateJavaScriptBuiltinContinuationFrameState(
jsgraph(), function, Builtins::kArrayFilterLoopLazyDeoptContinuation,
node->InputAt(0), context, &checkpoint_params[0], stack_parameters,
outer_frame_state, ContinuationFrameStateMode::EAGER);
effect =
graph()->NewNode(common()->Checkpoint(), frame_state, effect, control);
}
// We have to coerce callback_value to boolean, and only store the element in
// a if it's true. The checkpoint above protects against the case that
// growing {a} fails.
to = DoFilterPostCallbackWork(kind, &control, &effect, a, to, element,
callback_value);
k = next_k;
loop->ReplaceInput(1, control);
vloop->ReplaceInput(1, k);
v_to_loop->ReplaceInput(1, to);
eloop->ReplaceInput(1, effect);
control = if_false;
effect = eloop;
// Wire up the branch for the case when IsCallable fails for the callback.
// Since {check_throw} is an unconditional throw, it's impossible to
// return a successful completion. Therefore, we simply connect the successful
// completion to the graph end.
Node* terminate =
graph()->NewNode(common()->Throw(), check_throw, check_fail);
NodeProperties::MergeControlToEnd(graph(), common(), terminate);
ReplaceWithValue(node, a, effect, control);
return Replace(a);
}
Node* JSCallReducer::DoFilterPostCallbackWork(ElementsKind kind, Node** control,
Node** effect, Node* a, Node* to,
Node* element,
Node* callback_value) {
Node* boolean_result = graph()->NewNode(
simplified()->ToBoolean(ToBooleanHint::kAny), callback_value);
Node* check_boolean_result =
graph()->NewNode(simplified()->ReferenceEqual(), boolean_result,
jsgraph()->TrueConstant());
Node* boolean_branch = graph()->NewNode(common()->Branch(BranchHint::kTrue),
check_boolean_result, *control);
Node* if_true = graph()->NewNode(common()->IfTrue(), boolean_branch);
Node* etrue = *effect;
Node* vtrue;
{
// Load the elements backing store of the {receiver}.
Node* elements = etrue = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSObjectElements()), a, etrue,
if_true);
// We know that {to} is in Unsigned31 range here, being smaller than
// {original_length} at all times.
Node* checked_to =
graph()->NewNode(common()->TypeGuard(Type::Unsigned31()), to, if_true);
GrowFastElementsMode mode = GrowFastElementsMode::kSmiOrObjectElements;
// TODO(mvstanton): Make sure{flags} is correct when we handle{a} as a
// double output array.
DCHECK(!IsDoubleElementsKind(kind));
elements = etrue =
graph()->NewNode(simplified()->MaybeGrowFastElements(mode), a, elements,
checked_to, checked_to, etrue, if_true);
// Update the length of {a}.
Node* new_length_a = graph()->NewNode(simplified()->NumberAdd(), checked_to,
jsgraph()->OneConstant());
etrue = graph()->NewNode(
simplified()->StoreField(AccessBuilder::ForJSArrayLength(kind)), a,
new_length_a, etrue, if_true);
// Append the value to the {elements}.
etrue = graph()->NewNode(
simplified()->StoreElement(AccessBuilder::ForFixedArrayElement(kind)),
elements, checked_to, element, etrue, if_true);
vtrue = new_length_a;
}
Node* if_false = graph()->NewNode(common()->IfFalse(), boolean_branch);
Node* efalse = *effect;
Node* vfalse = to;
*control = graph()->NewNode(common()->Merge(2), if_true, if_false);
*effect = graph()->NewNode(common()->EffectPhi(2), etrue, efalse, *control);
to = graph()->NewNode(common()->Phi(MachineRepresentation::kTaggedSigned, 2),
vtrue, vfalse, *control);
return to;
}
void JSCallReducer::WireInCallbackIsCallableCheck(
Node* fncallback, Node* context, Node* check_frame_state, Node* effect,
Node** control, Node** check_fail, Node** check_throw) {
Node* check = graph()->NewNode(simplified()->ObjectIsCallable(), fncallback);
Node* check_branch =
graph()->NewNode(common()->Branch(BranchHint::kTrue), check, *control);
*check_fail = graph()->NewNode(common()->IfFalse(), check_branch);
*check_throw = *check_fail = graph()->NewNode(
javascript()->CallRuntime(Runtime::kThrowTypeError, 2),
jsgraph()->Constant(MessageTemplate::kCalledNonCallable), fncallback,
context, check_frame_state, effect, *check_fail);
*control = graph()->NewNode(common()->IfTrue(), check_branch);
}
void JSCallReducer::RewirePostCallbackExceptionEdges(Node* check_throw,
Node* on_exception,
Node* effect,
Node** check_fail,
Node** control) {
// Create appropriate {IfException} and {IfSuccess} nodes.
Node* if_exception0 =
graph()->NewNode(common()->IfException(), check_throw, *check_fail);
*check_fail = graph()->NewNode(common()->IfSuccess(), *check_fail);
Node* if_exception1 =
graph()->NewNode(common()->IfException(), effect, *control);
*control = graph()->NewNode(common()->IfSuccess(), *control);
// 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(on_exception, phi, ephi, merge);
}
Node* JSCallReducer::SafeLoadElement(ElementsKind kind, Node* receiver,
Node* control, Node** effect, Node** k) {
// Make sure that the access is still in bounds, since the callback could have
// changed the array's size.
Node* length = *effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSArrayLength(kind)), receiver,
*effect, control);
*k = *effect = graph()->NewNode(simplified()->CheckBounds(), *k, length,
*effect, control);
// Reload the elements pointer before calling the callback, since the previous
// callback might have resized the array causing the elements buffer to be
// re-allocated.
Node* elements = *effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSObjectElements()), receiver,
*effect, control);
Node* element = *effect = graph()->NewNode(
simplified()->LoadElement(AccessBuilder::ForFixedArrayElement(kind)),
elements, *k, *effect, control);
return element;
}
Reduction JSCallReducer::ReduceCallApiFunction(
Node* node, Handle<FunctionTemplateInfo> function_template_info) {
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
@ -1881,8 +1615,6 @@ Reduction JSCallReducer::ReduceJSCall(Node* node) {
return ReduceArrayForEach(function, node);
case Builtins::kArrayMap:
return ReduceArrayMap(function, node);
case Builtins::kArrayFilter:
return ReduceArrayFilter(function, node);
case Builtins::kReturnReceiver:
return ReduceReturnReceiver(node);
default:

View File

@ -73,7 +73,6 @@ class JSCallReducer final : public AdvancedReducer {
Reduction ReduceReflectHas(Node* node);
Reduction ReduceArrayForEach(Handle<JSFunction> function, Node* node);
Reduction ReduceArrayMap(Handle<JSFunction> function, Node* node);
Reduction ReduceArrayFilter(Handle<JSFunction> function, Node* node);
Reduction ReduceCallOrConstructWithArrayLikeOrSpread(
Node* node, int arity, CallFrequency const& frequency,
VectorSlotPair const& feedback);
@ -87,30 +86,6 @@ class JSCallReducer final : public AdvancedReducer {
Reduction ReduceSoftDeoptimize(Node* node, DeoptimizeReason reason);
// Returns the updated {to} node, and updates control and effect along the
// way.
Node* DoFilterPostCallbackWork(ElementsKind kind, Node** control,
Node** effect, Node* a, Node* to,
Node* element, Node* callback_value);
// If {fncallback} is not callable, throw a TypeError.
// {control} is altered, and new nodes {check_fail} and {check_throw} are
// returned. {check_fail} is the control branch where IsCallable failed,
// and {check_throw} is the call to throw a TypeError in that
// branch.
void WireInCallbackIsCallableCheck(Node* fncallback, Node* context,
Node* check_frame_state, Node* effect,
Node** control, Node** check_fail,
Node** check_throw);
void RewirePostCallbackExceptionEdges(Node* check_throw, Node* on_exception,
Node* effect, Node** check_fail,
Node** control);
// Load receiver[k], first bounding k by receiver array length.
// k is thusly changed, and the effect is changed as well.
Node* SafeLoadElement(ElementsKind kind, Node* receiver, Node* control,
Node** effect, Node** k);
Graph* graph() const;
JSGraph* jsgraph() const { return jsgraph_; }
Isolate* isolate() const;

View File

@ -1569,7 +1569,6 @@ void Deoptimizer::DoComputeBuiltinContinuation(
BailoutId bailout_id = translated_frame->node_id();
Builtins::Name builtin_name = Builtins::GetBuiltinFromBailoutId(bailout_id);
DCHECK(!Builtins::IsLazy(builtin_name));
Code* builtin = isolate()->builtins()->builtin(builtin_name);
Callable continuation_callable =
Builtins::CallableFor(isolate(), builtin_name);

View File

@ -1,440 +0,0 @@
// Copyright 2017 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --allow-natives-syntax --expose-gc --turbo-inline-array-builtins
// Flags: --opt --no-always-opt
// Unknown field access leads to soft-deopt unrelated to filter, should still
// lead to correct result.
(function() {
var a = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25];
var result = 0;
var eagerDeoptInCalled = function(deopt) {
var callback = function(v,i,o) {
if (i == 13 && deopt) {
a.abc = 25;
}
// Ensure that the output array is smaller by shaving off the first
// item.
if (i === 0) return false;
result += v;
return true;
}
return a.filter(callback);
}
eagerDeoptInCalled();
eagerDeoptInCalled();
%OptimizeFunctionOnNextCall(eagerDeoptInCalled);
eagerDeoptInCalled();
var deopt_result = eagerDeoptInCalled(true);
assertEquals(a.slice(1), deopt_result);
eagerDeoptInCalled();
assertEquals(1620, result);
})();
// Length change detected during loop, must cause properly handled eager deopt.
(function() {
var eagerDeoptInCalled = function(deopt) {
var a = [1,2,3,4,5,6,7,8,9,10];
var callback = function(v,i,o) {
a.length = (i == 5 && deopt) ? 8 : 10;
return i == 0 ? false : true;
}
return a.filter(callback);
}
var like_a = [1,2,3,4,5,6,7,8,9,10];
assertEquals(like_a.slice(1), eagerDeoptInCalled());
eagerDeoptInCalled();
%OptimizeFunctionOnNextCall(eagerDeoptInCalled);
assertEquals(like_a.slice(1), eagerDeoptInCalled());
assertEquals(like_a.slice(1).slice(0, 7), eagerDeoptInCalled(true));
eagerDeoptInCalled();
})();
// Lazy deopt from a callback that changes the input array. Ensure that
// the value stored in the output array is from the original read.
(function() {
var a = [1, 2, 3, 4, 5];
var lazyChanger = function(deopt) {
var callback = function(v,i,o) {
if (i === 2 && deopt) {
a[2] = 100;
%DeoptimizeNow();
}
return true;
}
return a.filter(callback);
}
assertEquals(a, lazyChanger());
lazyChanger();
%OptimizeFunctionOnNextCall(lazyChanger);
var deopt_result = lazyChanger(true);
assertEquals([1, 2, 3, 4, 5], deopt_result);
assertEquals([1, 2, 100, 4, 5], lazyChanger());
})();
// Lazy deopt from a callback that returns false at the deopt point.
// Ensure the non-selection is respected in the output array.
(function() {
var a = [1, 2, 3, 4, 5];
var lazyDeselection = function(deopt) {
var callback = function(v,i,o) {
if (i === 2 && deopt) {
%DeoptimizeNow();
return false;
}
return true;
}
return a.filter(callback);
}
assertEquals(a, lazyDeselection());
lazyDeselection();
%OptimizeFunctionOnNextCall(lazyDeselection);
var deopt_result = lazyDeselection(true);
assertEquals([1, 2, 4, 5], deopt_result);
assertEquals([1, 2, 3, 4, 5], lazyDeselection());
})();
// Escape analyzed array
(function() {
var result = 0;
var eagerDeoptInCalled = function(deopt) {
var a_noescape = [0,1,2,3,4,5];
var callback = function(v,i,o) {
result += v;
if (i == 13 && deopt) {
a_noescape.length = 25;
}
return true;
}
a_noescape.filter(callback);
}
eagerDeoptInCalled();
eagerDeoptInCalled();
%OptimizeFunctionOnNextCall(eagerDeoptInCalled);
eagerDeoptInCalled();
eagerDeoptInCalled(true);
eagerDeoptInCalled();
assertEquals(75, result);
})();
// Escape analyzed array where callback function isn't inlined, forcing a lazy
// deopt with GC that relies on the stashed-away return result fro the lazy
// deopt being properly stored in a place on the stack that gets GC'ed.
(function() {
var result = 0;
var lazyDeopt = function(deopt) {
var b = [1,2,3];
var callback = function(v,i,o) {
result += i;
if (i == 1 && deopt) {
%DeoptimizeFunction(lazyDeopt);
}
gc(); gc();
return true;
};
%NeverOptimizeFunction(callback);
b.filter(callback);
}
lazyDeopt();
lazyDeopt();
%OptimizeFunctionOnNextCall(lazyDeopt);
lazyDeopt();
lazyDeopt(true);
lazyDeopt();
})();
// Lazy deopt from runtime call from inlined callback function.
(function() {
var a = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25];
var result = 0;
var lazyDeopt = function(deopt) {
var callback = function(v,i,o) {
result += i;
if (i == 13 && deopt) {
%DeoptimizeNow();
}
return true;
}
a.filter(callback);
}
lazyDeopt();
lazyDeopt();
%OptimizeFunctionOnNextCall(lazyDeopt);
lazyDeopt();
lazyDeopt(true);
lazyDeopt();
assertEquals(1500, result);
})();
// Lazy deopt from runtime call from non-inline callback function.
(function() {
var a = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25];
var result = 0;
var lazyDeopt = function(deopt) {
var callback = function(v,i,o) {
result += i;
if (i == 13 && deopt) {
%DeoptimizeNow();
}
return true;
};
%NeverOptimizeFunction(callback);
a.filter(callback);
}
lazyDeopt();
lazyDeopt();
%OptimizeFunctionOnNextCall(lazyDeopt);
lazyDeopt();
lazyDeopt(true);
lazyDeopt();
assertEquals(1500, result);
})();
(function() {
var a = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25];
var result = 0;
var lazyDeopt = function(deopt) {
var callback = function(v,i,o) {
result += i;
if (i == 13 && deopt) {
%DeoptimizeNow();
gc();
gc();
gc();
}
return true;
}
a.filter(callback);
}
lazyDeopt();
lazyDeopt();
%OptimizeFunctionOnNextCall(lazyDeopt);
lazyDeopt();
lazyDeopt(true);
lazyDeopt();
assertEquals(1500, result);
})();
// Call to a.filter is done inside a try-catch block and the callback function
// being called actually throws.
(function() {
var a = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25];
var caught = false;
var result = 0;
var lazyDeopt = function(deopt) {
var callback = function(v,i,o) {
result += i;
if (i == 1 && deopt) {
throw("a");
}
return true;
}
try {
a.filter(callback);
} catch (e) {
caught = true;
}
}
lazyDeopt();
lazyDeopt();
%OptimizeFunctionOnNextCall(lazyDeopt);
lazyDeopt();
assertDoesNotThrow(() => lazyDeopt(true));
assertTrue(caught);
lazyDeopt();
})();
// Call to a.filter is done inside a try-catch block and the callback function
// being called actually throws, but the callback is not inlined.
(function() {
var a = [1,2,3,4,5,6,7,8,9,10];
var caught = false;
var result = 0;
var lazyDeopt = function(deopt) {
var callback = function(v,i,o) {
result += i;
if (i == 1 && deopt) {
throw("a");
}
return true;
};
%NeverOptimizeFunction(callback);
try {
a.filter(callback);
} catch (e) {
caught = true;
}
}
lazyDeopt();
lazyDeopt();
%OptimizeFunctionOnNextCall(lazyDeopt);
lazyDeopt();
assertDoesNotThrow(() => lazyDeopt(true));
assertTrue(caught);
lazyDeopt();
})();
// Call to a.filter is done inside a try-catch block and the callback function
// being called throws into a deoptimized caller function.
(function TestThrowIntoDeoptimizedOuter() {
var a = [1,2,3,4];
var lazyDeopt = function(deopt) {
var callback = function(v,i,o) {
if (i == 1 && deopt) {
%DeoptimizeFunction(lazyDeopt);
throw "some exception";
}
return true;
};
%NeverOptimizeFunction(callback);
var result = 0;
try {
result = a.filter(callback);
} catch (e) {
assertEquals("some exception", e)
result = "nope";
}
return result;
}
assertEquals([1,2,3,4], lazyDeopt(false));
assertEquals([1,2,3,4], lazyDeopt(false));
assertEquals("nope", lazyDeopt(true));
assertEquals("nope", lazyDeopt(true));
%OptimizeFunctionOnNextCall(lazyDeopt);
assertEquals([1,2,3,4], lazyDeopt(false));
assertEquals("nope", lazyDeopt(true));
})();
// An error generated inside the callback includes filter in it's
// stack trace.
(function() {
var re = /Array\.filter/;
var lazyDeopt = function(deopt) {
var b = [1,2,3];
var result = 0;
var callback = function(v,i,o) {
result += v;
if (i == 1) {
var e = new Error();
assertTrue(re.exec(e.stack) !== null);
}
return true;
};
var o = [1,2,3];
b.filter(callback);
}
lazyDeopt();
lazyDeopt();
%OptimizeFunctionOnNextCall(lazyDeopt);
lazyDeopt();
})();
// An error generated inside a non-inlined callback function also
// includes filter in it's stack trace.
(function() {
var re = /Array\.filter/;
var lazyDeopt = function(deopt) {
var b = [1,2,3];
var result = 0;
var callback = function(v,i,o) {
result += v;
if (i == 1) {
var e = new Error();
assertTrue(re.exec(e.stack) !== null);
}
return true;
};
%NeverOptimizeFunction(callback);
var o = [1,2,3];
b.filter(callback);
}
lazyDeopt();
lazyDeopt();
%OptimizeFunctionOnNextCall(lazyDeopt);
lazyDeopt();
})();
// An error generated inside a recently deoptimized callback function
// includes filter in it's stack trace.
(function() {
var re = /Array\.filter/;
var lazyDeopt = function(deopt) {
var b = [1,2,3];
var result = 0;
var callback = function(v,i,o) {
result += v;
if (i == 1) {
%DeoptimizeNow();
} else if (i == 2) {
var e = new Error();
assertTrue(re.exec(e.stack) !== null);
}
return true;
};
var o = [1,2,3];
b.filter(callback);
}
lazyDeopt();
lazyDeopt();
%OptimizeFunctionOnNextCall(lazyDeopt);
lazyDeopt();
})();
// Verify that various exception edges are handled appropriately.
// The thrown Error object should always indicate it was created from
// a filter call stack.
(function() {
var re = /Array\.filter/;
var a = [1,2,3];
var result = 0;
var lazyDeopt = function() {
var callback = function(v,i,o) {
result += i;
if (i == 1) {
%DeoptimizeFunction(lazyDeopt);
throw new Error();
}
return true;
};
a.filter(callback);
}
assertThrows(() => lazyDeopt());
assertThrows(() => lazyDeopt());
try {
lazyDeopt();
} catch (e) {
assertTrue(re.exec(e.stack) !== null);
}
%OptimizeFunctionOnNextCall(lazyDeopt);
try {
lazyDeopt();
} catch (e) {
assertTrue(re.exec(e.stack) !== null);
}
})();
// Messing with the Array species constructor causes deoptimization.
(function() {
var result = 0;
var a = [1,2,3];
var species_breakage = function() {
var callback = function(v,i,o) {
result += v;
return true;
}
a.filter(callback);
}
species_breakage();
species_breakage();
%OptimizeFunctionOnNextCall(species_breakage);
species_breakage();
a.constructor = {};
a.constructor[Symbol.species] = function() {};
species_breakage();
assertUnoptimized(species_breakage);
assertEquals(24, result);
})();

View File

@ -35,19 +35,3 @@
%OptimizeFunctionOnNextCall(foo);
assertInstanceof(foo(), TypeError);
})();
(function TestNonCallableFilter() {
function foo() { [].filter(undefined); }
assertThrows(foo, TypeError);
assertThrows(foo, TypeError);
%OptimizeFunctionOnNextCall(foo);
assertThrows(foo, TypeError);
})();
(function TestNonCallableFilterCaught() {
function foo() { try { [].filter(undefined) } catch(e) { return e } }
assertInstanceof(foo(), TypeError);
assertInstanceof(foo(), TypeError);
%OptimizeFunctionOnNextCall(foo);
assertInstanceof(foo(), TypeError);
})();

View File

@ -1,32 +0,0 @@
// Copyright 2017 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --allow-natives-syntax
function classOf() {; }
function PrettyPrint(value) { return ""; }
function fail() { }
function deepEquals(a, b) { if (a === b) { if (a === 0)1 / b; return true; } if (typeof a != typeof b) return false; if (typeof a == "number") return isNaN(); if (typeof a !== "object" && typeof a !== "function") return false; var objectClass = classOf(); if (b) return false; if (objectClass === "RegExp") {; } if (objectClass === "Function") return false; if (objectClass === "Array") { var elementCount = 0; if (a.length != b.length) { return false; } for (var i = 0; i < a.length; i++) { if (a[i][i]) return false; } return true; } if (objectClass == "String" || objectClass == "Number" || objectClass == "Boolean" || objectClass == "Date") { if (a.valueOf()) return false; }; }
assertSame = function assertSame() { if (found === expected) { if (1 / found) return; } else if ((expected !== expected) && (found !== found)) { return; }; }; assertEquals = function assertEquals(expected, found, name_opt) { if (!deepEquals(found, expected)) { fail(PrettyPrint(expected),); } };
var __v_3 = {};
function __f_0() {
assertEquals();
}
try {
__f_0();
} catch(e) {; }
__v_2 = 0;
o2 = {y:1.5};
o2.y = 0;
o3 = o2.y;
function __f_1() {
for (var __v_1 = 0; __v_1 < 10; __v_1++) {
__v_2 += __v_3.x + o3.foo;
[ 3].filter(__f_9);
}
}
__f_1();
%OptimizeFunctionOnNextCall(__f_1);
__f_1();
function __f_9(){ "use __f_9"; assertEquals( this); }