diff --git a/src/builtins/builtins-array-gen.cc b/src/builtins/builtins-array-gen.cc index 28d2141531..30ec5dc125 100644 --- a/src/builtins/builtins-array-gen.cc +++ b/src/builtins/builtins-array-gen.cc @@ -1605,6 +1605,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)); diff --git a/src/builtins/builtins-definitions.h b/src/builtins/builtins-definitions.h index 8c35045f4b..b78163c462 100644 --- a/src/builtins/builtins-definitions.h +++ b/src/builtins/builtins-definitions.h @@ -288,6 +288,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) \ diff --git a/src/builtins/builtins.cc b/src/builtins/builtins.cc index 941a87815e..7b322351f5 100644 --- a/src/builtins/builtins.cc +++ b/src/builtins/builtins.cc @@ -197,6 +197,16 @@ Callable Builtins::CallableFor(Isolate* isolate, Name name) { BUILTIN_CODE(isolate, ArrayMapLoopLazyDeoptContinuation); return Callable(code, BuiltinDescriptor(isolate)); } + case kArrayFilterLoopEagerDeoptContinuation: { + Handle code = + BUILTIN_CODE(isolate, ArrayFilterLoopEagerDeoptContinuation); + return Callable(code, BuiltinDescriptor(isolate)); + } + case kArrayFilterLoopLazyDeoptContinuation: { + Handle code = + BUILTIN_CODE(isolate, ArrayFilterLoopLazyDeoptContinuation); + return Callable(code, BuiltinDescriptor(isolate)); + } default: UNREACHABLE(); } @@ -235,6 +245,8 @@ 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: diff --git a/src/compiler/js-call-reducer.cc b/src/compiler/js-call-reducer.cc index 6c0e57f12e..78e59ed78c 100644 --- a/src/compiler/js-call-reducer.cc +++ b/src/compiler/js-call-reducer.cc @@ -9,6 +9,7 @@ #include "src/code-stubs.h" #include "src/compilation-dependencies.h" #include "src/compiler/access-builder.h" +#include "src/compiler/allocation-builder.h" #include "src/compiler/js-graph.h" #include "src/compiler/linkage.h" #include "src/compiler/node-matchers.h" @@ -1201,6 +1202,292 @@ Reduction JSCallReducer::ReduceArrayMap(Handle function, return Replace(a); } +Reduction JSCallReducer::ReduceArrayFilter(Handle 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 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 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 initial_map( + Map::cast(native_context()->GetInitialJSArrayMap(kind))); + + 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* a; // Construct the output array. + { + AllocationBuilder ab(jsgraph(), effect, control); + ab.Allocate(initial_map->instance_size(), NOT_TENURED, Type::Array()); + ab.Store(AccessBuilder::ForMap(), initial_map); + Node* empty_fixed_array = jsgraph()->EmptyFixedArrayConstant(); + ab.Store(AccessBuilder::ForJSObjectPropertiesOrHash(), empty_fixed_array); + ab.Store(AccessBuilder::ForJSObjectElements(), empty_fixed_array); + ab.Store(AccessBuilder::ForJSArrayLength(kind), jsgraph()->ZeroConstant()); + for (int i = 0; i < initial_map->GetInObjectProperties(); ++i) { + ab.Store(AccessBuilder::ForJSObjectInObjectProperty(initial_map, i), + jsgraph()->UndefinedConstant()); + } + a = effect = ab.Finish(); + } + + Node* original_length = effect = graph()->NewNode( + simplified()->LoadField(AccessBuilder::ForJSArrayLength(kind)), receiver, + 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 checkpoint_params( + {receiver, fncallback, this_arg, a, k, original_length, to, to}); + const int stack_parameters = static_cast(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 checkpoint_params( + {receiver, fncallback, this_arg, a, k, original_length, to}); + const int stack_parameters = static_cast(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 checkpoint_params( + {receiver, fncallback, this_arg, a, k, original_length, element, to}); + const int stack_parameters = static_cast(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 checkpoint_params({receiver, fncallback, this_arg, a, k, + original_length, element, to, + callback_value}); + const int stack_parameters = static_cast(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); + Node* elements_length = etrue = graph()->NewNode( + simplified()->LoadField(AccessBuilder::ForFixedArrayLength()), elements, + etrue, 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, elements_length, 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) { @@ -1679,6 +1966,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: diff --git a/src/compiler/js-call-reducer.h b/src/compiler/js-call-reducer.h index 71314c96e4..f6403e994b 100644 --- a/src/compiler/js-call-reducer.h +++ b/src/compiler/js-call-reducer.h @@ -74,6 +74,7 @@ class JSCallReducer final : public AdvancedReducer { Reduction ReduceReflectHas(Node* node); Reduction ReduceArrayForEach(Handle function, Node* node); Reduction ReduceArrayMap(Handle function, Node* node); + Reduction ReduceArrayFilter(Handle function, Node* node); Reduction ReduceCallOrConstructWithArrayLikeOrSpread( Node* node, int arity, CallFrequency const& frequency, VectorSlotPair const& feedback); @@ -87,6 +88,12 @@ 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, diff --git a/src/deoptimizer.cc b/src/deoptimizer.cc index ffe083b6c9..38290edfbf 100644 --- a/src/deoptimizer.cc +++ b/src/deoptimizer.cc @@ -1568,6 +1568,7 @@ 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); diff --git a/test/mjsunit/filter-element-kinds.js b/test/mjsunit/filter-element-kinds.js index 54cbc3540c..7853a33b9c 100644 --- a/test/mjsunit/filter-element-kinds.js +++ b/test/mjsunit/filter-element-kinds.js @@ -67,70 +67,78 @@ function assertNotHoley(obj, name_opt) { assertEquals(false, isHoley(obj), name_opt); } -var a; -// Packed literal arrays. -obj = [1, 2, 3]; -a = obj.filter(x => false); -assertKind(elements_kind.fast_smi_only, a); -assertNotHoley(a); +// Create a new closure that inlines Array.prototype.filter(). +function create(a) { + return function() { + return a.filter(x => false); + } +} -obj = [true, true, false]; -a = obj.filter(x => false); -assertKind(elements_kind.fast, a); -assertNotHoley(a); +function runTest(test, kind, holey_predicate) { -obj = [1.0, 1.5, 3.5]; -a = obj.filter(x => false); -assertKind(elements_kind.fast_double, a); -assertNotHoley(a); + // Verify built-in implementation produces correct results. + let a = test(); + assertKind(kind, a); + holey_predicate(a); + test(); + test(); + %OptimizeFunctionOnNextCall(test); -// Holey literal arrays. -obj = [1,, 3]; obj[1] = 2; -a = obj.filter(x => false); -assertKind(elements_kind.fast_smi_only, a); -assertHoley(a); + // Now for optimized code. + a = test(); + assertKind(kind, a); + holey_predicate(a); +} -obj = [true,, false]; obj[1] = true; -a = obj.filter(x => false); -assertKind(elements_kind.fast, a); -assertHoley(a); +function chooseHoleyPredicate(a) { + return isHoley(a) ? assertHoley : assertNotHoley; +} -obj = [1.0,, 3.5]; obj[1] = 1.5; -a = obj.filter(x => false); -assertKind(elements_kind.fast_double, a); -assertHoley(a); +(function() { + let data = []; -// Packed constructed arrays. -obj = new Array(1, 2, 3); -a = obj.filter(x => false); -assertKind(elements_kind.fast_smi_only, a); -assertNotHoley(a); + // Packed literal arrays. + data.push(() => [1, 2, 3]); + data.push(() => [true, true, false]); + data.push(() => [1.0, 1.5, 3.5]); + // Holey literal arrays. + data.push(() => { let obj = [1,, 3]; obj[1] = 2; return obj; }); + data.push(() => { let obj = [true,, false]; obj[1] = true; return obj; }); + data.push(() => { let obj = [1.0,, 3.5]; obj[1] = 1.5; return obj; }); + // Packed constructed arrays. + data.push(() => new Array(1, 2, 3)); + data.push(() => new Array(true, true, false)); + data.push(() => new Array(1.0, 1.5, 3.5)); -obj = new Array(true, true, false); -a = obj.filter(x => false); -assertKind(elements_kind.fast, a); -assertNotHoley(a); + // Holey constructed arrays. + data.push(() => { + let obj = new Array(3); + obj[0] = 1; + obj[1] = 2; + obj[2] = 3; + return obj; + }); -obj = new Array(1.0, 1.5, 3.5); -a = obj.filter(x => false); -assertKind(elements_kind.fast_double, a); -assertNotHoley(a); + data.push(() => { + let obj = new Array(3); + obj[0] = true; + obj[1] = true; + obj[2] = false; + return obj; + }); -// Holey constructed arrays. -obj = new Array(3); -obj[0] = 1; obj[1] = 2; obj[2] = 3; -a = obj.filter(x => false); -assertKind(elements_kind.fast_smi_only, a); -assertHoley(a); + data.push(() => { + let obj = new Array(3); + obj[0] = 1.0; + obj[1] = 1.5; + obj[2] = 3.5; + return obj; + }); -obj = new Array(3); -obj[0] = true; obj[1] = true; obj[2] = false; -a = obj.filter(x => false); -assertKind(elements_kind.fast, a); -assertHoley(a); - -obj = new Array(3); -obj[0] = 1.0; obj[1] = 1.5; obj[2] = 3.5; -a = obj.filter(x => false); -assertKind(elements_kind.fast_double, a); -assertHoley(a); + for (datum of data) { + let a = datum(); + // runTest(create(a), getKind(a), chooseHoleyPredicate(a)); + let f = function() { return a.filter(x => false); } + runTest(f, getKind(a), chooseHoleyPredicate(a)); + } +})(); diff --git a/test/mjsunit/optimized-filter.js b/test/mjsunit/optimized-filter.js new file mode 100644 index 0000000000..b13edc3b36 --- /dev/null +++ b/test/mjsunit/optimized-filter.js @@ -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); +})(); diff --git a/test/mjsunit/regress/regress-crbug-747062.js b/test/mjsunit/regress/regress-crbug-747062.js index 4fe99d39c1..7e0e92ad7e 100644 --- a/test/mjsunit/regress/regress-crbug-747062.js +++ b/test/mjsunit/regress/regress-crbug-747062.js @@ -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); +})(); diff --git a/test/mjsunit/regress/regress-crbug-766635.js b/test/mjsunit/regress/regress-crbug-766635.js new file mode 100644 index 0000000000..ae0de0a600 --- /dev/null +++ b/test/mjsunit/regress/regress-crbug-766635.js @@ -0,0 +1,32 @@ +// 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); } diff --git a/test/mjsunit/regress/regress-crbug-776511.js b/test/mjsunit/regress/regress-crbug-776511.js new file mode 100644 index 0000000000..f757bc2cc2 --- /dev/null +++ b/test/mjsunit/regress/regress-crbug-776511.js @@ -0,0 +1,35 @@ +// 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: --enable-slow-asserts --expose-gc --allow-natives-syntax + +function __getProperties(obj) { + let properties = []; + for (let name of Object.getOwnPropertyNames(obj)) { + properties.push(name); + } + return properties; +} +function __getRandomProperty(obj, seed) { + let properties = __getProperties(obj); + return properties[seed % properties.length]; +} +(function() { + var __v_59904 = [12, 13, 14, 16, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]; + var __v_59906 = function(__v_59908) { + var __v_59909 = function(__v_59910, __v_59911) { + if (__v_59911 == 13 && __v_59908) { + __v_59904.abc = 25; + } + return true; + }; + return __v_59904.filter(__v_59909); + }; + print(__v_59906()); + __v_59904[__getRandomProperty(__v_59904, 366855)] = this, gc(); + print(__v_59906()); + %OptimizeFunctionOnNextCall(__v_59906); + var __v_59907 = __v_59906(true); + print(__v_59907); +})();