[Turbofan] Array.prototype.filter inlining.
Support inlining of Array.prototype.filter in TurboFan. Bug: v8:1956 Change-Id: Iba4d683aaa86c6104e8a1cf4d0f549a0c516576a Reviewed-on: https://chromium-review.googlesource.com/657021 Commit-Queue: Michael Stanton <mvstanton@chromium.org> Reviewed-by: Michael Starzinger <mstarzinger@chromium.org> Cr-Commit-Position: refs/heads/master@{#48040}
This commit is contained in:
parent
7742e534a8
commit
37aa13fe3b
@ -1565,6 +1565,66 @@ 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));
|
||||
|
@ -287,6 +287,10 @@ 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) \
|
||||
|
@ -210,6 +210,16 @@ 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();
|
||||
}
|
||||
|
@ -650,18 +650,14 @@ 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_throw = check_fail = graph()->NewNode(
|
||||
javascript()->CallRuntime(Runtime::kThrowCalledNonCallable), fncallback,
|
||||
context, check_frame_state, effect, check_fail);
|
||||
control = graph()->NewNode(common()->IfTrue(), check_branch);
|
||||
Node* check_fail = nullptr;
|
||||
Node* check_throw = nullptr;
|
||||
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);
|
||||
@ -693,32 +689,9 @@ Reduction JSCallReducer::ReduceArrayForEach(Handle<JSFunction> function,
|
||||
|
||||
// Make sure the map hasn't changed during the iteration
|
||||
Node* orig_map = jsgraph()->HeapConstant(receiver_map);
|
||||
Node* array_map = effect =
|
||||
graph()->NewNode(simplified()->LoadField(AccessBuilder::ForMap()),
|
||||
receiver, effect, control);
|
||||
Node* check_map =
|
||||
graph()->NewNode(simplified()->ReferenceEqual(), array_map, orig_map);
|
||||
effect =
|
||||
graph()->NewNode(simplified()->CheckIf(), check_map, effect, control);
|
||||
WireInMapCheck(orig_map, receiver, &effect, control);
|
||||
|
||||
// Make sure that the access is still in bounds, since the callback could have
|
||||
// changed the array's size.
|
||||
Node* length = 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 = graph()->NewNode(
|
||||
simplified()->LoadField(AccessBuilder::ForJSObjectElements()), receiver,
|
||||
effect, control);
|
||||
|
||||
Node* element = graph()->NewNode(
|
||||
simplified()->LoadElement(AccessBuilder::ForFixedArrayElement()),
|
||||
elements, k, effect, control);
|
||||
Node* element = SafeLoadElement(receiver, control, &effect, &k);
|
||||
|
||||
Node* next_k =
|
||||
graph()->NewNode(simplified()->NumberAdd(), k, jsgraph()->Constant(1));
|
||||
@ -752,23 +725,8 @@ Reduction JSCallReducer::ReduceArrayForEach(Handle<JSFunction> function,
|
||||
// Rewire potential exception edges.
|
||||
Node* on_exception = nullptr;
|
||||
if (NodeProperties::IsExceptionalCall(node, &on_exception)) {
|
||||
// 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);
|
||||
RewirePostCallbackExceptionEdges(check_throw, on_exception, effect,
|
||||
&check_fail, &control);
|
||||
}
|
||||
|
||||
if (IsHoleyElementsKind(kind)) {
|
||||
@ -791,9 +749,10 @@ Reduction JSCallReducer::ReduceArrayForEach(Handle<JSFunction> function,
|
||||
control = if_false;
|
||||
effect = eloop;
|
||||
|
||||
// The above %ThrowCalledNonCallable 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.
|
||||
// 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);
|
||||
@ -855,15 +814,7 @@ Reduction JSCallReducer::ReduceArrayMap(Handle<JSFunction> function,
|
||||
Node* orig_map = jsgraph()->HeapConstant(receiver_map);
|
||||
|
||||
// Make sure the map hasn't changed before we construct the output array.
|
||||
{
|
||||
Node* array_map = effect =
|
||||
graph()->NewNode(simplified()->LoadField(AccessBuilder::ForMap()),
|
||||
receiver, effect, control);
|
||||
Node* check_map =
|
||||
graph()->NewNode(simplified()->ReferenceEqual(), array_map, orig_map);
|
||||
effect =
|
||||
graph()->NewNode(simplified()->CheckIf(), check_map, effect, control);
|
||||
}
|
||||
WireInMapCheck(orig_map, receiver, &effect, control);
|
||||
|
||||
Node* original_length = graph()->NewNode(
|
||||
simplified()->LoadField(AccessBuilder::ForJSArrayLength(PACKED_ELEMENTS)),
|
||||
@ -883,18 +834,14 @@ 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_throw = check_fail = graph()->NewNode(
|
||||
javascript()->CallRuntime(Runtime::kThrowCalledNonCallable), fncallback,
|
||||
context, check_frame_state, effect, check_fail);
|
||||
control = graph()->NewNode(common()->IfTrue(), check_branch);
|
||||
Node* check_fail = nullptr;
|
||||
Node* check_throw = nullptr;
|
||||
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);
|
||||
@ -925,32 +872,9 @@ Reduction JSCallReducer::ReduceArrayMap(Handle<JSFunction> function,
|
||||
graph()->NewNode(common()->Checkpoint(), frame_state, effect, control);
|
||||
|
||||
// Make sure the map hasn't changed during the iteration
|
||||
Node* array_map = effect =
|
||||
graph()->NewNode(simplified()->LoadField(AccessBuilder::ForMap()),
|
||||
receiver, effect, control);
|
||||
Node* check_map =
|
||||
graph()->NewNode(simplified()->ReferenceEqual(), array_map, orig_map);
|
||||
effect =
|
||||
graph()->NewNode(simplified()->CheckIf(), check_map, effect, control);
|
||||
WireInMapCheck(orig_map, receiver, &effect, control);
|
||||
|
||||
// Make sure that the access is still in bounds, since the callback could have
|
||||
// changed the array's size.
|
||||
Node* length = 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 = graph()->NewNode(
|
||||
simplified()->LoadField(AccessBuilder::ForJSObjectElements()), receiver,
|
||||
effect, control);
|
||||
|
||||
Node* element = graph()->NewNode(
|
||||
simplified()->LoadElement(AccessBuilder::ForFixedArrayElement()),
|
||||
elements, k, effect, control);
|
||||
Node* element = SafeLoadElement(receiver, control, &effect, &k);
|
||||
|
||||
Node* next_k =
|
||||
graph()->NewNode(simplified()->NumberAdd(), k, jsgraph()->OneConstant());
|
||||
@ -969,23 +893,8 @@ Reduction JSCallReducer::ReduceArrayMap(Handle<JSFunction> function,
|
||||
// Rewire potential exception edges.
|
||||
Node* on_exception = nullptr;
|
||||
if (NodeProperties::IsExceptionalCall(node, &on_exception)) {
|
||||
// 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);
|
||||
RewirePostCallbackExceptionEdges(check_throw, on_exception, effect,
|
||||
&check_fail, &control);
|
||||
}
|
||||
|
||||
Handle<Map> double_map(Map::cast(
|
||||
@ -1005,9 +914,10 @@ Reduction JSCallReducer::ReduceArrayMap(Handle<JSFunction> function,
|
||||
control = if_false;
|
||||
effect = eloop;
|
||||
|
||||
// The above %ThrowCalledNonCallable 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.
|
||||
// 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);
|
||||
@ -1016,6 +926,344 @@ 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();
|
||||
}
|
||||
if (receiver_maps.size() != 1) return NoChange();
|
||||
Handle<Map> receiver_map(receiver_maps[0]);
|
||||
ElementsKind kind = receiver_map->elements_kind();
|
||||
|
||||
// TODO(mvstanton): Handle holey Smi and Object fast elements kinds and double
|
||||
// packed.
|
||||
if (!IsFastPackedElementsKind(kind) || IsDoubleElementsKind(kind) ||
|
||||
!CanInlineArrayIteratingBuiltin(receiver_map)) {
|
||||
return NoChange();
|
||||
}
|
||||
|
||||
// We want the input to be a generic Array.
|
||||
const int map_index = Context::ArrayMapIndex(kind);
|
||||
Handle<JSFunction> handle_constructor(
|
||||
JSFunction::cast(
|
||||
Map::cast(native_context()->get(map_index))->GetConstructor()),
|
||||
isolate());
|
||||
Node* array_constructor = jsgraph()->HeapConstant(handle_constructor);
|
||||
if (receiver_map->prototype() !=
|
||||
native_context()->get(Context::INITIAL_ARRAY_PROTOTYPE_INDEX)) {
|
||||
return NoChange();
|
||||
}
|
||||
|
||||
// And ensure that any changes to the Array species constructor cause deopt.
|
||||
if (!isolate()->IsArraySpeciesLookupChainIntact()) return NoChange();
|
||||
|
||||
dependencies()->AssumePropertyCell(factory()->species_protector());
|
||||
|
||||
Node* k = jsgraph()->ZeroConstant();
|
||||
Node* to = jsgraph()->ZeroConstant();
|
||||
Node* orig_map = jsgraph()->HeapConstant(receiver_map);
|
||||
|
||||
// Make sure the map hasn't changed before we construct the output array.
|
||||
WireInMapCheck(orig_map, receiver, &effect, control);
|
||||
|
||||
Node* original_length = graph()->NewNode(
|
||||
simplified()->LoadField(AccessBuilder::ForJSArrayLength(PACKED_ELEMENTS)),
|
||||
receiver, effect, control);
|
||||
|
||||
// Create the initially empty output array.
|
||||
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.
|
||||
WireInMapCheck(orig_map, receiver, &effect, control);
|
||||
|
||||
Node* element = SafeLoadElement(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, context, &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* context,
|
||||
Node** control, Node** effect,
|
||||
Node* a, Node* to, Node* element,
|
||||
Node* callback_value) {
|
||||
Node* boolean_result = graph()->NewNode(
|
||||
javascript()->ToBoolean(ToBooleanHint::kAny), callback_value, context);
|
||||
|
||||
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.
|
||||
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,
|
||||
to, to, etrue, if_true);
|
||||
|
||||
// Update the length of {a}.
|
||||
Node* new_length_a = graph()->NewNode(simplified()->NumberAdd(), 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, 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::WireInMapCheck(Node* orig_map, Node* receiver,
|
||||
Node** effect, Node* control) {
|
||||
Node* array_map = *effect =
|
||||
graph()->NewNode(simplified()->LoadField(AccessBuilder::ForMap()),
|
||||
receiver, *effect, control);
|
||||
Node* check_map =
|
||||
graph()->NewNode(simplified()->ReferenceEqual(), array_map, orig_map);
|
||||
*effect =
|
||||
graph()->NewNode(simplified()->CheckIf(), check_map, *effect, control);
|
||||
}
|
||||
|
||||
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::kThrowCalledNonCallable), 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(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 = 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 = graph()->NewNode(
|
||||
simplified()->LoadField(AccessBuilder::ForJSObjectElements()), receiver,
|
||||
*effect, control);
|
||||
|
||||
Node* element = graph()->NewNode(
|
||||
simplified()->LoadElement(AccessBuilder::ForFixedArrayElement()),
|
||||
elements, *k, *effect, control);
|
||||
return element;
|
||||
}
|
||||
|
||||
Reduction JSCallReducer::ReduceCallApiFunction(
|
||||
Node* node, Handle<FunctionTemplateInfo> function_template_info) {
|
||||
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
|
||||
@ -1376,6 +1624,8 @@ 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:
|
||||
|
@ -71,6 +71,7 @@ class JSCallReducer final : public AdvancedReducer {
|
||||
Reduction ReduceReflectGetPrototypeOf(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);
|
||||
@ -84,6 +85,33 @@ 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* context,
|
||||
Node** control, Node** effect, Node* a,
|
||||
Node* to, Node* element, Node* callback_value);
|
||||
|
||||
// If {fncallback} is not callable, throw via %ThrowCalledNonCallable.
|
||||
// {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 %ThrowCalledNonCallable 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);
|
||||
|
||||
// Creates a map check for {receiver} against {orig_map}. Updates {effect}.
|
||||
void WireInMapCheck(Node* orig_map, Node* receiver, Node** effect,
|
||||
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(Node* receiver, Node* control, Node** effect, Node** k);
|
||||
|
||||
Graph* graph() const;
|
||||
JSGraph* jsgraph() const { return jsgraph_; }
|
||||
Isolate* isolate() const;
|
||||
|
440
test/mjsunit/optimized-filter.js
Normal file
440
test/mjsunit/optimized-filter.js
Normal file
@ -0,0 +1,440 @@
|
||||
// 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);
|
||||
})();
|
@ -35,3 +35,19 @@
|
||||
%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);
|
||||
})();
|
||||
|
Loading…
Reference in New Issue
Block a user