diff --git a/deps/v8/src/compiler/graph.h b/deps/v8/src/compiler/graph.h index 1e861c7b151e1b..60af4789bcd33f 100644 --- a/deps/v8/src/compiler/graph.h +++ b/deps/v8/src/compiler/graph.h @@ -104,6 +104,17 @@ class V8_EXPORT_PRIVATE Graph final : public NON_EXPORTED_BASE(ZoneObject) { Node* nodes[] = {n1, n2, n3, n4, n5, n6, n7, n8, n9}; return NewNode(op, arraysize(nodes), nodes); } + Node* NewNode(const Operator* op, Node* n1, Node* n2, Node* n3, Node* n4, + Node* n5, Node* n6, Node* n7, Node* n8, Node* n9, Node* n10) { + Node* nodes[] = {n1, n2, n3, n4, n5, n6, n7, n8, n9, n10}; + return NewNode(op, arraysize(nodes), nodes); + } + Node* NewNode(const Operator* op, Node* n1, Node* n2, Node* n3, Node* n4, + Node* n5, Node* n6, Node* n7, Node* n8, Node* n9, Node* n10, + Node* n11) { + Node* nodes[] = {n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11}; + return NewNode(op, arraysize(nodes), nodes); + } // Clone the {node}, and assign a new node id to the copy. Node* CloneNode(const Node* node); diff --git a/deps/v8/src/compiler/js-builtin-reducer.cc b/deps/v8/src/compiler/js-builtin-reducer.cc index a1c83ce1b639b7..bea8f18b63953f 100644 --- a/deps/v8/src/compiler/js-builtin-reducer.cc +++ b/deps/v8/src/compiler/js-builtin-reducer.cc @@ -5,6 +5,7 @@ #include "src/compiler/js-builtin-reducer.h" #include "src/base/bits.h" +#include "src/builtins/builtins-utils.h" #include "src/code-factory.h" #include "src/compilation-dependencies.h" #include "src/compiler/access-builder.h" @@ -1005,6 +1006,179 @@ Reduction JSBuiltinReducer::ReduceArrayPush(Node* node) { return NoChange(); } +// ES6 section 22.1.3.22 Array.prototype.shift ( ) +Reduction JSBuiltinReducer::ReduceArrayShift(Node* node) { + Node* target = NodeProperties::GetValueInput(node, 0); + Node* receiver = NodeProperties::GetValueInput(node, 1); + Node* context = NodeProperties::GetContextInput(node); + Node* frame_state = NodeProperties::GetFrameStateInput(node); + Node* effect = NodeProperties::GetEffectInput(node); + Node* control = NodeProperties::GetControlInput(node); + + // TODO(turbofan): Extend this to also handle fast holey double elements + // once we got the hole NaN mess sorted out in TurboFan/V8. + Handle receiver_map; + if (GetMapWitness(node).ToHandle(&receiver_map) && + CanInlineArrayResizeOperation(receiver_map) && + receiver_map->elements_kind() != FAST_HOLEY_DOUBLE_ELEMENTS) { + // Install code dependencies on the {receiver} prototype maps and the + // global array protector cell. + dependencies()->AssumePropertyCell(factory()->array_protector()); + dependencies()->AssumePrototypeMapsStable(receiver_map); + + // Load length of the {receiver}. + Node* length = effect = graph()->NewNode( + simplified()->LoadField( + AccessBuilder::ForJSArrayLength(receiver_map->elements_kind())), + receiver, effect, control); + + // Return undefined if {receiver} has no elements. + Node* check0 = graph()->NewNode(simplified()->NumberEqual(), length, + jsgraph()->ZeroConstant()); + Node* branch0 = + graph()->NewNode(common()->Branch(BranchHint::kFalse), check0, control); + + Node* if_true0 = graph()->NewNode(common()->IfTrue(), branch0); + Node* etrue0 = effect; + Node* vtrue0 = jsgraph()->UndefinedConstant(); + + Node* if_false0 = graph()->NewNode(common()->IfFalse(), branch0); + Node* efalse0 = effect; + Node* vfalse0; + { + // Check if we should take the fast-path. + Node* check1 = + graph()->NewNode(simplified()->NumberLessThanOrEqual(), length, + jsgraph()->Constant(JSArray::kMaxCopyElements)); + Node* branch1 = graph()->NewNode(common()->Branch(BranchHint::kTrue), + check1, if_false0); + + Node* if_true1 = graph()->NewNode(common()->IfTrue(), branch1); + Node* etrue1 = efalse0; + Node* vtrue1; + { + Node* elements = etrue1 = graph()->NewNode( + simplified()->LoadField(AccessBuilder::ForJSObjectElements()), + receiver, etrue1, if_true1); + + // Load the first element here, which we return below. + vtrue1 = etrue1 = graph()->NewNode( + simplified()->LoadElement(AccessBuilder::ForFixedArrayElement( + receiver_map->elements_kind())), + elements, jsgraph()->ZeroConstant(), etrue1, if_true1); + + // Ensure that we aren't shifting a copy-on-write backing store. + if (IsFastSmiOrObjectElementsKind(receiver_map->elements_kind())) { + elements = etrue1 = + graph()->NewNode(simplified()->EnsureWritableFastElements(), + receiver, elements, etrue1, if_true1); + } + + // Shift the remaining {elements} by one towards the start. + Node* loop = graph()->NewNode(common()->Loop(2), if_true1, if_true1); + Node* eloop = + graph()->NewNode(common()->EffectPhi(2), etrue1, etrue1, loop); + Node* index = graph()->NewNode( + common()->Phi(MachineRepresentation::kTagged, 2), + jsgraph()->OneConstant(), + jsgraph()->Constant(JSArray::kMaxCopyElements - 1), loop); + + { + Node* check2 = + graph()->NewNode(simplified()->NumberLessThan(), index, length); + Node* branch2 = graph()->NewNode(common()->Branch(), check2, loop); + + if_true1 = graph()->NewNode(common()->IfFalse(), branch2); + etrue1 = eloop; + + Node* control = graph()->NewNode(common()->IfTrue(), branch2); + Node* effect = etrue1; + + ElementAccess const access = AccessBuilder::ForFixedArrayElement( + receiver_map->elements_kind()); + Node* value = effect = + graph()->NewNode(simplified()->LoadElement(access), elements, + index, effect, control); + effect = graph()->NewNode( + simplified()->StoreElement(access), elements, + graph()->NewNode(simplified()->NumberSubtract(), index, + jsgraph()->OneConstant()), + value, effect, control); + + loop->ReplaceInput(1, control); + eloop->ReplaceInput(1, effect); + index->ReplaceInput(1, + graph()->NewNode(simplified()->NumberAdd(), index, + jsgraph()->OneConstant())); + } + + // Compute the new {length}. + length = graph()->NewNode(simplified()->NumberSubtract(), length, + jsgraph()->OneConstant()); + + // Store the new {length} to the {receiver}. + etrue1 = graph()->NewNode( + simplified()->StoreField( + AccessBuilder::ForJSArrayLength(receiver_map->elements_kind())), + receiver, length, etrue1, if_true1); + + // Store a hole to the element we just removed from the {receiver}. + etrue1 = graph()->NewNode( + simplified()->StoreElement(AccessBuilder::ForFixedArrayElement( + GetHoleyElementsKind(receiver_map->elements_kind()))), + elements, length, jsgraph()->TheHoleConstant(), etrue1, if_true1); + } + + Node* if_false1 = graph()->NewNode(common()->IfFalse(), branch1); + Node* efalse1 = efalse0; + Node* vfalse1; + { + // Call the generic C++ implementation. + const int builtin_index = Builtins::kArrayShift; + CallDescriptor const* const desc = Linkage::GetCEntryStubCallDescriptor( + graph()->zone(), 1, BuiltinArguments::kNumExtraArgsWithReceiver, + Builtins::name(builtin_index), node->op()->properties(), + CallDescriptor::kNeedsFrameState); + Node* stub_code = jsgraph()->CEntryStubConstant(1, kDontSaveFPRegs, + kArgvOnStack, true); + Address builtin_entry = Builtins::CppEntryOf(builtin_index); + Node* entry = jsgraph()->ExternalConstant( + ExternalReference(builtin_entry, isolate())); + Node* argc = + jsgraph()->Constant(BuiltinArguments::kNumExtraArgsWithReceiver); + if_false1 = efalse1 = vfalse1 = + graph()->NewNode(common()->Call(desc), stub_code, receiver, argc, + target, jsgraph()->UndefinedConstant(), entry, + argc, context, frame_state, efalse1, if_false1); + } + + if_false0 = graph()->NewNode(common()->Merge(2), if_true1, if_false1); + efalse0 = + graph()->NewNode(common()->EffectPhi(2), etrue1, efalse1, if_false0); + vfalse0 = + graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), + vtrue1, vfalse1, if_false0); + } + + control = graph()->NewNode(common()->Merge(2), if_true0, if_false0); + effect = graph()->NewNode(common()->EffectPhi(2), etrue0, efalse0, control); + Node* value = + graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), + vtrue0, vfalse0, control); + + // Convert the hole to undefined. Do this last, so that we can optimize + // conversion operator via some smart strength reduction in many cases. + if (IsFastHoleyElementsKind(receiver_map->elements_kind())) { + value = + graph()->NewNode(simplified()->ConvertTaggedHoleToUndefined(), value); + } + + ReplaceWithValue(node, value, effect, control); + return Replace(value); + } + return NoChange(); +} + namespace { bool HasInstanceTypeWitness(Node* receiver, Node* effect, @@ -2125,6 +2299,8 @@ Reduction JSBuiltinReducer::Reduce(Node* node) { return ReduceArrayPop(node); case kArrayPush: return ReduceArrayPush(node); + case kArrayShift: + return ReduceArrayShift(node); case kDateNow: return ReduceDateNow(node); case kDateGetTime: diff --git a/deps/v8/src/compiler/js-builtin-reducer.h b/deps/v8/src/compiler/js-builtin-reducer.h index e792ad3c3afeb7..736ece34e4f5db 100644 --- a/deps/v8/src/compiler/js-builtin-reducer.h +++ b/deps/v8/src/compiler/js-builtin-reducer.h @@ -58,6 +58,7 @@ class V8_EXPORT_PRIVATE JSBuiltinReducer final Reduction ReduceArrayIsArray(Node* node); Reduction ReduceArrayPop(Node* node); Reduction ReduceArrayPush(Node* node); + Reduction ReduceArrayShift(Node* node); Reduction ReduceDateNow(Node* node); Reduction ReduceDateGetTime(Node* node); Reduction ReduceGlobalIsFinite(Node* node); diff --git a/deps/v8/src/crankshaft/hydrogen.cc b/deps/v8/src/crankshaft/hydrogen.cc index d9dc41221e509f..e3794e33ffb60b 100644 --- a/deps/v8/src/crankshaft/hydrogen.cc +++ b/deps/v8/src/crankshaft/hydrogen.cc @@ -8821,7 +8821,7 @@ bool HOptimizedGraphBuilder::TryInlineBuiltinMethodCall( Handle::null(), true); // Threshold for fast inlined Array.shift(). - HConstant* inline_threshold = Add(static_cast(16)); + HConstant* inline_threshold = Add(JSArray::kMaxCopyElements); Drop(args_count_no_receiver); HValue* result; diff --git a/deps/v8/src/objects.h b/deps/v8/src/objects.h index 118fab96de7973..46697b55cc8e14 100644 --- a/deps/v8/src/objects.h +++ b/deps/v8/src/objects.h @@ -9608,6 +9608,9 @@ class JSArray: public JSObject { static const int kLengthOffset = JSObject::kHeaderSize; static const int kSize = kLengthOffset + kPointerSize; + // Max. number of elements being copied in Array builtins. + static const int kMaxCopyElements = 16; + static const int kInitialMaxFastElementArray = (kMaxRegularHeapObjectSize - FixedArray::kHeaderSize - kSize - AllocationMemento::kSize) / diff --git a/deps/v8/test/mjsunit/array-shift5.js b/deps/v8/test/mjsunit/array-shift5.js new file mode 100644 index 00000000000000..c8fb90d830fba9 --- /dev/null +++ b/deps/v8/test/mjsunit/array-shift5.js @@ -0,0 +1,50 @@ +// 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() { + function doShift(a) { return a.shift(); } + + function test() { + var a = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]; + assertEquals(0, doShift(a)); + assertEquals([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16], a); + } + + test(); + test(); + %OptimizeFunctionOnNextCall(doShift); + test(); +})(); + +(function() { + function doShift(a) { return a.shift(); } + + function test() { + var a = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16.1]; + assertEquals(0, doShift(a)); + assertEquals([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16.1], a); + } + + test(); + test(); + %OptimizeFunctionOnNextCall(doShift); + test(); +})(); + +(function() { + function doShift(a) { return a.shift(); } + + function test() { + var a = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,"16"]; + assertEquals(0, doShift(a)); + assertEquals([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,"16"], a); + } + + test(); + test(); + %OptimizeFunctionOnNextCall(doShift); + test(); +})();