[turbofan] Inline multi-parameter Array#push.

TurboFan wasn't able to inline calls to Array.prototype.push which
didn't have exactly one parameter. This was a rather artifical
limitation and was mostly due to the way the MaybeGrowFastElements
operator was implemented (which was not ideal by itself). Refactoring
this a bit, allows us to inline the operation in general, independent
of the number of values to push.

Array#push with multiple parameters is used quite a lot inside Ember (as
discovered by Apple, i.e. https://bugs.webkit.org/show_bug.cgi?id=175823)
and is also dominating the Six-Speed/SpreadLiterals/ES5 benchmark (see
https://twitter.com/SpiderMonkeyJS/status/906528938452832257 from the
SpiderMonkey folks). The micro-benchmark mentioned in the tracking bug
(v8:6808) improves from

  arrayPush0: 2422 ms.
  arrayPush1: 2567 ms.
  arrayPush2: 4092 ms.
  arrayPush3: 4308 ms.

to

  arrayPush0: 798 ms.
  arrayPush1: 2563 ms.
  arrayPush2: 2623 ms.
  arrayPush3: 2773 ms.

with this change, effectively removing the odd 50-60% performance
cliff that was associated with going from one parameter to two or
more.

Bug: v8:2229, v8:6808
Change-Id: Iffe4c1233903c04c3dc2062aad39d99769c8ab57
Reviewed-on: https://chromium-review.googlesource.com/657582
Reviewed-by: Jaroslav Sevcik <jarin@chromium.org>
Commit-Queue: Benedikt Meurer <bmeurer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#47940}
This commit is contained in:
Benedikt Meurer 2017-09-11 12:21:16 +02:00 committed by Commit Bot
parent e8b6173ac6
commit 68e4d86c6e
9 changed files with 541 additions and 179 deletions

View File

@ -2673,81 +2673,43 @@ Node* EffectControlLinearizer::LowerEnsureWritableFastElements(Node* node) {
Node* EffectControlLinearizer::LowerMaybeGrowFastElements(Node* node,
Node* frame_state) {
GrowFastElementsFlags flags = GrowFastElementsFlagsOf(node->op());
GrowFastElementsMode mode = GrowFastElementsModeOf(node->op());
Node* object = node->InputAt(0);
Node* elements = node->InputAt(1);
Node* index = node->InputAt(2);
Node* length = node->InputAt(3);
Node* elements_length = node->InputAt(3);
auto done = __ MakeLabel(MachineRepresentation::kTagged);
auto done_grow = __ MakeLabel(MachineRepresentation::kTagged);
auto if_grow = __ MakeDeferredLabel();
auto if_not_grow = __ MakeLabel();
Node* check0 = (flags & GrowFastElementsFlag::kHoleyElements)
? __ Uint32LessThanOrEqual(length, index)
: __ Word32Equal(length, index);
__ GotoIfNot(check0, &if_not_grow);
{
// Load the length of the {elements} backing store.
Node* elements_length =
__ LoadField(AccessBuilder::ForFixedArrayLength(), elements);
elements_length = ChangeSmiToInt32(elements_length);
// Check if we need to grow the {elements} backing store.
Node* check = __ Uint32LessThan(index, elements_length);
__ GotoIfNot(check, &if_grow);
__ Goto(&done, elements);
// Check if we need to grow the {elements} backing store.
Node* check1 = __ Uint32LessThan(index, elements_length);
__ GotoIfNot(check1, &if_grow);
__ Goto(&done_grow, elements);
__ Bind(&if_grow);
// We need to grow the {elements} for {object}.
Operator::Properties properties = Operator::kEliminatable;
Callable callable =
(flags & GrowFastElementsFlag::kDoubleElements)
? Builtins::CallableFor(isolate(),
Builtins::kGrowFastDoubleElements)
: Builtins::CallableFor(isolate(),
Builtins::kGrowFastSmiOrObjectElements);
CallDescriptor::Flags call_flags = CallDescriptor::kNoFlags;
CallDescriptor const* const desc = Linkage::GetStubCallDescriptor(
isolate(), graph()->zone(), callable.descriptor(), 0, call_flags,
properties);
Node* new_object = __ Call(desc, __ HeapConstant(callable.code()), object,
__ Bind(&if_grow);
// We need to grow the {elements} for {object}.
Operator::Properties properties = Operator::kEliminatable;
Callable callable =
(mode == GrowFastElementsMode::kDoubleElements)
? Builtins::CallableFor(isolate(), Builtins::kGrowFastDoubleElements)
: Builtins::CallableFor(isolate(),
Builtins::kGrowFastSmiOrObjectElements);
CallDescriptor::Flags call_flags = CallDescriptor::kNoFlags;
CallDescriptor const* const desc = Linkage::GetStubCallDescriptor(
isolate(), graph()->zone(), callable.descriptor(), 0, call_flags,
properties);
Node* new_elements = __ Call(desc, __ HeapConstant(callable.code()), object,
ChangeInt32ToSmi(index), __ NoContextConstant());
// Ensure that we were able to grow the {elements}.
// TODO(turbofan): We use kSmi as reason here similar to Crankshaft,
// but maybe we should just introduce a reason that makes sense.
__ DeoptimizeIf(DeoptimizeReason::kSmi, ObjectIsSmi(new_object),
frame_state);
__ Goto(&done_grow, new_object);
// Ensure that we were able to grow the {elements}.
// TODO(turbofan): We use kSmi as reason here similar to Crankshaft,
// but maybe we should just introduce a reason that makes sense.
__ DeoptimizeIf(DeoptimizeReason::kSmi, ObjectIsSmi(new_elements),
frame_state);
__ Goto(&done, new_elements);
__ Bind(&done_grow);
// For JSArray {object}s we also need to update the "length".
if (flags & GrowFastElementsFlag::kArrayObject) {
// Compute the new {length}.
Node* object_length =
ChangeInt32ToSmi(__ Int32Add(index, __ Int32Constant(1)));
// Update the "length" property of the {object}.
__ StoreField(AccessBuilder::ForJSArrayLength(PACKED_ELEMENTS), object,
object_length);
}
__ Goto(&done, done_grow.PhiAt(0));
}
__ Bind(&if_not_grow);
{
// In case of non-holey {elements}, we need to verify that the {index} is
// in-bounds, otherwise for holey {elements}, the check above already
// guards the index (and the operator forces {index} to be unsigned).
if (!(flags & GrowFastElementsFlag::kHoleyElements)) {
Node* check1 = __ Uint32LessThan(index, length);
__ DeoptimizeIfNot(DeoptimizeReason::kOutOfBounds, check1, frame_state);
}
__ Goto(&done, elements);
}
__ Bind(&done);
return done.PhiAt(0);
}

View File

@ -930,12 +930,11 @@ Reduction JSBuiltinReducer::ReduceArrayPop(Node* node) {
// ES6 section 22.1.3.18 Array.prototype.push ( )
Reduction JSBuiltinReducer::ReduceArrayPush(Node* node) {
// We need exactly target, receiver and value parameters.
if (node->op()->ValueInputCount() != 3) return NoChange();
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
int const num_values = node->op()->ValueInputCount() - 2;
Node* receiver = NodeProperties::GetValueInput(node, 1);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
Node* value = NodeProperties::GetValueInput(node, 2);
ZoneHandleSet<Map> receiver_maps;
NodeProperties::InferReceiverMapsResult result =
NodeProperties::InferReceiverMaps(receiver, effect, &receiver_maps);
@ -945,6 +944,12 @@ Reduction JSBuiltinReducer::ReduceArrayPush(Node* node) {
// TODO(turbofan): Relax this to deal with multiple {receiver} maps.
Handle<Map> receiver_map = receiver_maps[0];
if (CanInlineArrayResizeOperation(receiver_map)) {
// Collect the value inputs to push.
std::vector<Node*> values(num_values);
for (int i = 0; i < num_values; ++i) {
values[i] = NodeProperties::GetValueInput(node, 2 + i);
}
// Install code dependencies on the {receiver} prototype maps and the
// global array protector cell.
dependencies()->AssumePropertyCell(factory()->array_protector());
@ -966,22 +971,24 @@ Reduction JSBuiltinReducer::ReduceArrayPush(Node* node) {
}
}
// TODO(turbofan): Perform type checks on the {value}. We are not guaranteed
// to learn from these checks in case they fail, as the witness (i.e. the
// map check from the LoadIC for a.push) might not be executed in baseline
// code (after we stored the value in the builtin and thereby changed the
// elements kind of a) before be decide to optimize this function again. We
// currently don't have a proper way to deal with this; the proper solution
// here is to learn on deopt, i.e. disable Array.prototype.push inlining
// for this function.
if (IsSmiElementsKind(receiver_map->elements_kind())) {
value = effect =
graph()->NewNode(simplified()->CheckSmi(), value, effect, control);
} else if (IsDoubleElementsKind(receiver_map->elements_kind())) {
value = effect =
graph()->NewNode(simplified()->CheckNumber(), value, effect, control);
// Make sure we do not store signaling NaNs into double arrays.
value = graph()->NewNode(simplified()->NumberSilenceNaN(), value);
// TODO(turbofan): Perform type checks on the {values}. We are not
// guaranteed to learn from these checks in case they fail, as the witness
// (i.e. the map check from the LoadIC for a.push) might not be executed in
// baseline code (after we stored the value in the builtin and thereby
// changed the elements kind of a) before be decide to optimize this
// function again. We currently don't have a proper way to deal with this;
// the proper solution here is to learn on deopt, i.e. disable
// Array.prototype.push inlining for this function.
for (auto& value : values) {
if (IsSmiElementsKind(receiver_map->elements_kind())) {
value = effect =
graph()->NewNode(simplified()->CheckSmi(), value, effect, control);
} else if (IsDoubleElementsKind(receiver_map->elements_kind())) {
value = effect = graph()->NewNode(simplified()->CheckNumber(), value,
effect, control);
// Make sure we do not store signaling NaNs into double arrays.
value = graph()->NewNode(simplified()->NumberSilenceNaN(), value);
}
}
// Load the "length" property of the {receiver}.
@ -989,33 +996,54 @@ Reduction JSBuiltinReducer::ReduceArrayPush(Node* node) {
simplified()->LoadField(
AccessBuilder::ForJSArrayLength(receiver_map->elements_kind())),
receiver, effect, control);
Node* value = length;
// Load the elements backing store of the {receiver}.
Node* elements = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSObjectElements()), receiver,
effect, control);
// Check if we have any {values} to push.
if (num_values > 0) {
// Compute the resulting "length" of the {receiver}.
Node* new_length = value = graph()->NewNode(
simplified()->NumberAdd(), length, jsgraph()->Constant(num_values));
// TODO(turbofan): Check if we need to grow the {elements} backing store.
// This will deopt if we cannot grow the array further, and we currently
// don't necessarily learn from it. See the comment on the value type check
// above.
GrowFastElementsFlags flags = GrowFastElementsFlag::kArrayObject;
if (IsDoubleElementsKind(receiver_map->elements_kind())) {
flags |= GrowFastElementsFlag::kDoubleElements;
// Load the elements backing store of the {receiver}.
Node* elements = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSObjectElements()),
receiver, effect, control);
Node* elements_length = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForFixedArrayLength()),
elements, effect, control);
// TODO(turbofan): Check if we need to grow the {elements} backing store.
// This will deopt if we cannot grow the array further, and we currently
// don't necessarily learn from it. See the comment on the value type
// check above.
GrowFastElementsMode mode =
IsDoubleElementsKind(receiver_map->elements_kind())
? GrowFastElementsMode::kDoubleElements
: GrowFastElementsMode::kSmiOrObjectElements;
elements = effect = graph()->NewNode(
simplified()->MaybeGrowFastElements(mode), receiver, elements,
graph()->NewNode(simplified()->NumberAdd(), length,
jsgraph()->Constant(num_values - 1)),
elements_length, effect, control);
// Update the JSArray::length field. Since this is observable,
// there must be no other check after this.
effect = graph()->NewNode(
simplified()->StoreField(
AccessBuilder::ForJSArrayLength(receiver_map->elements_kind())),
receiver, new_length, effect, control);
// Append the {values} to the {elements}.
for (int i = 0; i < num_values; ++i) {
Node* value = values[i];
Node* index = graph()->NewNode(simplified()->NumberAdd(), length,
jsgraph()->Constant(i));
effect = graph()->NewNode(
simplified()->StoreElement(AccessBuilder::ForFixedArrayElement(
receiver_map->elements_kind())),
elements, index, value, effect, control);
}
}
elements = effect =
graph()->NewNode(simplified()->MaybeGrowFastElements(flags), receiver,
elements, length, length, effect, control);
// Append the value to the {elements}.
effect = graph()->NewNode(
simplified()->StoreElement(
AccessBuilder::ForFixedArrayElement(receiver_map->elements_kind())),
elements, length, value, effect, control);
// Return the new length of the {receiver}.
value = graph()->NewNode(simplified()->NumberAdd(), length,
jsgraph()->OneConstant());
ReplaceWithValue(node, value, effect, control);
return Replace(value);

View File

@ -2195,13 +2195,8 @@ JSNativeContextSpecialization::BuildElementAccess(
// Check if we might need to grow the {elements} backing store.
if (IsGrowStoreMode(store_mode)) {
// For growing stores we validate the {index} below.
DCHECK_EQ(AccessMode::kStore, access_mode);
// Check that the {index} is a valid array index; the actual checking
// happens below right before the element store.
index = effect = graph()->NewNode(simplified()->CheckBounds(), index,
jsgraph()->Constant(Smi::kMaxValue),
effect, control);
} else {
// Check that the {index} is in the valid range for the {receiver}.
index = effect = graph()->NewNode(simplified()->CheckBounds(), index,
@ -2282,23 +2277,69 @@ JSNativeContextSpecialization::BuildElementAccess(
graph()->NewNode(simplified()->EnsureWritableFastElements(),
receiver, elements, effect, control);
} else if (IsGrowStoreMode(store_mode)) {
// Grow {elements} backing store if necessary. Also updates the
// "length" property for JSArray {receiver}s, hence there must
// not be any other check after this operation, as the write
// to the "length" property is observable.
GrowFastElementsFlags flags = GrowFastElementsFlag::kNone;
if (receiver_is_jsarray) {
flags |= GrowFastElementsFlag::kArrayObject;
}
if (IsHoleyOrDictionaryElementsKind(elements_kind)) {
flags |= GrowFastElementsFlag::kHoleyElements;
}
if (IsDoubleElementsKind(elements_kind)) {
flags |= GrowFastElementsFlag::kDoubleElements;
}
// Determine the length of the {elements} backing store.
Node* elements_length = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForFixedArrayLength()),
elements, effect, control);
// Validate the {index} depending on holeyness:
//
// For HOLEY_*_ELEMENTS the {index} must not exceed the {elements}
// backing store capacity plus the maximum allowed gap, as otherwise
// the (potential) backing store growth would normalize and thus
// the elements kind of the {receiver} would change to slow mode.
//
// For PACKED_*_ELEMENTS the {index} must be within the range
// [0,length+1[ to be valid. In case {index} equals {length},
// the {receiver} will be extended, but kept packed.
Node* limit =
IsHoleyElementsKind(elements_kind)
? graph()->NewNode(simplified()->NumberAdd(), elements_length,
jsgraph()->Constant(JSObject::kMaxGap))
: graph()->NewNode(simplified()->NumberAdd(), length,
jsgraph()->OneConstant());
index = effect = graph()->NewNode(simplified()->CheckBounds(), index,
limit, effect, control);
// Grow {elements} backing store if necessary.
GrowFastElementsMode mode =
IsDoubleElementsKind(elements_kind)
? GrowFastElementsMode::kDoubleElements
: GrowFastElementsMode::kSmiOrObjectElements;
elements = effect = graph()->NewNode(
simplified()->MaybeGrowFastElements(flags), receiver, elements,
index, length, effect, control);
simplified()->MaybeGrowFastElements(mode), receiver, elements,
index, elements_length, effect, control);
// Also update the "length" property if {receiver} is a JSArray.
if (receiver_is_jsarray) {
Node* check =
graph()->NewNode(simplified()->NumberLessThan(), index, length);
Node* branch = graph()->NewNode(common()->Branch(), check, control);
Node* if_true = graph()->NewNode(common()->IfTrue(), branch);
Node* etrue = effect;
{
// We don't need to do anything, the {index} is within
// the valid bounds for the JSArray {receiver}.
}
Node* if_false = graph()->NewNode(common()->IfFalse(), branch);
Node* efalse = effect;
{
// Update the JSArray::length field. Since this is observable,
// there must be no other check after this.
Node* new_length = graph()->NewNode(
simplified()->NumberAdd(), index, jsgraph()->OneConstant());
efalse = graph()->NewNode(
simplified()->StoreField(
AccessBuilder::ForJSArrayLength(elements_kind)),
receiver, new_length, efalse, if_false);
}
control = graph()->NewNode(common()->Merge(2), if_true, if_false);
effect =
graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control);
}
}
// Perform the actual element access.

View File

@ -741,12 +741,12 @@ Reduction LoadElimination::ReduceEnsureWritableFastElements(Node* node) {
}
Reduction LoadElimination::ReduceMaybeGrowFastElements(Node* node) {
GrowFastElementsFlags flags = GrowFastElementsFlagsOf(node->op());
GrowFastElementsMode mode = GrowFastElementsModeOf(node->op());
Node* const object = NodeProperties::GetValueInput(node, 0);
Node* const effect = NodeProperties::GetEffectInput(node);
AbstractState const* state = node_states_.Get(effect);
if (state == nullptr) return NoChange();
if (flags & GrowFastElementsFlag::kDoubleElements) {
if (mode == GrowFastElementsMode::kDoubleElements) {
// We know that the resulting elements have the fixed double array map.
state = state->AddMaps(
node, ZoneHandleSet<Map>(factory()->fixed_double_array_map()), zone());
@ -755,11 +755,6 @@ Reduction LoadElimination::ReduceMaybeGrowFastElements(Node* node) {
state = state->AddMaps(
node, ZoneHandleSet<Map>(factory()->fixed_array_map()), zone());
}
if (flags & GrowFastElementsFlag::kArrayObject) {
// Kill the previous Array::length on {object}.
state = state->KillField(object, FieldIndexOf(JSArray::kLengthOffset),
factory()->length_string(), zone());
}
// Kill the previous elements on {object}.
state = state->KillField(object, FieldIndexOf(JSObject::kElementsOffset),
MaybeHandle<Name>(), zone());
@ -1130,17 +1125,10 @@ LoadElimination::AbstractState const* LoadElimination::ComputeLoopState(
break;
}
case IrOpcode::kMaybeGrowFastElements: {
GrowFastElementsFlags flags =
GrowFastElementsFlagsOf(current->op());
Node* const object = NodeProperties::GetValueInput(current, 0);
state = state->KillField(object,
FieldIndexOf(JSObject::kElementsOffset),
MaybeHandle<Name>(), zone());
if (flags & GrowFastElementsFlag::kArrayObject) {
state =
state->KillField(object, FieldIndexOf(JSArray::kLengthOffset),
factory()->length_string(), zone());
}
break;
}
case IrOpcode::kTransitionElementsKind: {

View File

@ -240,29 +240,19 @@ CheckTaggedInputMode CheckTaggedInputModeOf(const Operator* op) {
return OpParameter<CheckTaggedInputMode>(op);
}
std::ostream& operator<<(std::ostream& os, GrowFastElementsFlags flags) {
bool empty = true;
if (flags & GrowFastElementsFlag::kArrayObject) {
os << "ArrayObject";
empty = false;
std::ostream& operator<<(std::ostream& os, GrowFastElementsMode mode) {
switch (mode) {
case GrowFastElementsMode::kDoubleElements:
return os << "DoubleElements";
case GrowFastElementsMode::kSmiOrObjectElements:
return os << "SmiOrObjectElements";
}
if (flags & GrowFastElementsFlag::kDoubleElements) {
if (!empty) os << "|";
os << "DoubleElements";
empty = false;
}
if (flags & GrowFastElementsFlag::kHoleyElements) {
if (!empty) os << "|";
os << "HoleyElements";
empty = false;
}
if (empty) os << "None";
return os;
UNREACHABLE();
}
GrowFastElementsFlags GrowFastElementsFlagsOf(const Operator* op) {
GrowFastElementsMode GrowFastElementsModeOf(const Operator* op) {
DCHECK_EQ(IrOpcode::kMaybeGrowFastElements, op->opcode());
return OpParameter<GrowFastElementsFlags>(op);
return OpParameter<GrowFastElementsMode>(op);
}
bool operator==(ElementsTransition const& lhs, ElementsTransition const& rhs) {
@ -917,13 +907,13 @@ const Operator* SimplifiedOperatorBuilder::EnsureWritableFastElements() {
}
const Operator* SimplifiedOperatorBuilder::MaybeGrowFastElements(
GrowFastElementsFlags flags) {
return new (zone()) Operator1<GrowFastElementsFlags>( // --
IrOpcode::kMaybeGrowFastElements, // opcode
Operator::kNoThrow, // flags
"MaybeGrowFastElements", // name
4, 1, 1, 1, 1, 0, // counts
flags); // parameter
GrowFastElementsMode mode) {
return new (zone()) Operator1<GrowFastElementsMode>( // --
IrOpcode::kMaybeGrowFastElements, // opcode
Operator::kNoThrow, // flags
"MaybeGrowFastElements", // name
4, 1, 1, 1, 1, 0, // counts
mode); // parameter
}
const Operator* SimplifiedOperatorBuilder::TransitionElementsKind(

View File

@ -162,20 +162,18 @@ ZoneHandleSet<Map> const& CompareMapsParametersOf(Operator const*)
WARN_UNUSED_RESULT;
// A descriptor for growing elements backing stores.
enum class GrowFastElementsFlag : uint8_t {
kNone = 0u,
kArrayObject = 1u << 0, // Update JSArray::length field.
kHoleyElements = 1u << 1, // Backing store is holey.
kDoubleElements = 1u << 2, // Backing store contains doubles.
enum class GrowFastElementsMode : uint8_t {
kDoubleElements,
kSmiOrObjectElements
};
typedef base::Flags<GrowFastElementsFlag> GrowFastElementsFlags;
DEFINE_OPERATORS_FOR_FLAGS(GrowFastElementsFlags)
inline size_t hash_value(GrowFastElementsMode mode) {
return static_cast<uint8_t>(mode);
}
std::ostream& operator<<(std::ostream&, GrowFastElementsFlags);
std::ostream& operator<<(std::ostream&, GrowFastElementsMode);
GrowFastElementsFlags GrowFastElementsFlagsOf(const Operator*)
WARN_UNUSED_RESULT;
GrowFastElementsMode GrowFastElementsModeOf(const Operator*) WARN_UNUSED_RESULT;
// A descriptor for elements kind transitions.
class ElementsTransition final {
@ -462,7 +460,7 @@ class V8_EXPORT_PRIVATE SimplifiedOperatorBuilder final
const Operator* EnsureWritableFastElements();
// maybe-grow-fast-elements object, elements, index, length
const Operator* MaybeGrowFastElements(GrowFastElementsFlags flags);
const Operator* MaybeGrowFastElements(GrowFastElementsMode mode);
// transition-elements-kind object, from-map, to-map
const Operator* TransitionElementsKind(ElementsTransition transition);

View File

@ -0,0 +1,239 @@
// 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 --opt
// Test multiple arguments push for PACKED_SMI_ELEMENTS.
(function() {
function push0(a) {
return a.push();
}
assertEquals(0, push0([]));
assertEquals(1, push0([1]));
%OptimizeFunctionOnNextCall(push0);
assertEquals(2, push0([1, 2]));
function push1(a) {
return a.push(1);
}
assertEquals(1, push1([]));
assertEquals(2, push1([1]));
%OptimizeFunctionOnNextCall(push1);
assertEquals(3, push1([1, 2]));
function push2(a) {
return a.push(1, 2);
}
assertEquals(2, push2([]));
assertEquals(3, push2([1]));
%OptimizeFunctionOnNextCall(push2);
assertEquals(4, push2([1, 2]));
function push3(a) {
return a.push(1, 2, 3);
}
assertEquals(3, push3([]));
assertEquals(4, push3([1]));
%OptimizeFunctionOnNextCall(push3);
assertEquals(5, push3([1, 2]));
})();
// Test multiple arguments push for HOLEY_SMI_ELEMENTS.
(function() {
function push0(a) {
return a.push();
}
assertEquals(1, push0(new Array(1)));
assertEquals(2, push0(new Array(2)));
%OptimizeFunctionOnNextCall(push0);
assertEquals(3, push0(new Array(3)));
function push1(a) {
return a.push(1);
}
assertEquals(2, push1(new Array(1)));
assertEquals(3, push1(new Array(2)));
%OptimizeFunctionOnNextCall(push1);
assertEquals(4, push1(new Array(3)));
function push2(a) {
return a.push(1, 2);
}
assertEquals(3, push2(new Array(1)));
assertEquals(4, push2(new Array(2)));
%OptimizeFunctionOnNextCall(push2);
assertEquals(5, push2(new Array(3)));
function push3(a) {
return a.push(1, 2, 3);
}
assertEquals(4, push3(new Array(1)));
assertEquals(5, push3(new Array(2)));
%OptimizeFunctionOnNextCall(push3);
assertEquals(6, push3(new Array(3)));
})();
// Test multiple arguments push for PACKED_DOUBLE_ELEMENTS.
(function() {
function push0(a) {
return a.push();
}
assertEquals(1, push0([1.1]));
assertEquals(2, push0([1.1, 2.2]));
%OptimizeFunctionOnNextCall(push0);
assertEquals(3, push0([1.1, 2.2, 3.3]));
function push1(a) {
return a.push(1.1);
}
assertEquals(2, push1([1.1]));
assertEquals(3, push1([1.1, 2.2]));
%OptimizeFunctionOnNextCall(push1);
assertEquals(4, push1([1.1, 2.2, 3.3]));
function push2(a) {
return a.push(1.1, 2.2);
}
assertEquals(3, push2([1.1]));
assertEquals(4, push2([1.1, 2.2]));
%OptimizeFunctionOnNextCall(push2);
assertEquals(5, push2([1.1, 2.2, 3.3]));
function push3(a) {
return a.push(1.1, 2.2, 3.3);
}
assertEquals(4, push3([1.1]));
assertEquals(5, push3([1.1, 2.2]));
%OptimizeFunctionOnNextCall(push3);
assertEquals(6, push3([1.1, 2.2, 3.3]));
})();
// Test multiple arguments push for HOLEY_DOUBLE_ELEMENTS.
(function() {
function push0(a) {
return a.push();
}
assertEquals(2, push0([, 1.1]));
assertEquals(3, push0([, 1.1, 2.2]));
%OptimizeFunctionOnNextCall(push0);
assertEquals(4, push0([, 1.1, 2.2, 3.3]));
function push1(a) {
return a.push(1.1);
}
assertEquals(3, push1([, 1.1]));
assertEquals(4, push1([, 1.1, 2.2]));
%OptimizeFunctionOnNextCall(push1);
assertEquals(5, push1([, 1.1, 2.2, 3.3]));
function push2(a) {
return a.push(1.1, 2.2);
}
assertEquals(4, push2([, 1.1]));
assertEquals(5, push2([, 1.1, 2.2]));
%OptimizeFunctionOnNextCall(push2);
assertEquals(6, push2([, 1.1, 2.2, 3.3]));
function push3(a) {
return a.push(1.1, 2.2, 3.3);
}
assertEquals(5, push3([, 1.1]));
assertEquals(6, push3([, 1.1, 2.2]));
%OptimizeFunctionOnNextCall(push3);
assertEquals(7, push3([, 1.1, 2.2, 3.3]));
})();
// Test multiple arguments push for PACKED_ELEMENTS.
(function() {
function push0(a) {
return a.push();
}
assertEquals(1, push0(['1']));
assertEquals(2, push0(['1', '2']));
%OptimizeFunctionOnNextCall(push0);
assertEquals(3, push0(['1', '2', '3']));
function push1(a) {
return a.push('1');
}
assertEquals(2, push1(['1']));
assertEquals(3, push1(['1', '2']));
%OptimizeFunctionOnNextCall(push1);
assertEquals(4, push1(['1', '2', '3']));
function push2(a) {
return a.push('1', '2');
}
assertEquals(3, push2(['1']));
assertEquals(4, push2(['1', '2']));
%OptimizeFunctionOnNextCall(push2);
assertEquals(5, push2(['1', '2', '3']));
function push3(a) {
return a.push('1', '2', '3');
}
assertEquals(4, push3(['1']));
assertEquals(5, push3(['1', '2']));
%OptimizeFunctionOnNextCall(push3);
assertEquals(6, push3(['1', '2', '3']));
})();
// Test multiple arguments push for HOLEY_ELEMENTS.
(function() {
function push0(a) {
return a.push();
}
assertEquals(2, push0([, '1']));
assertEquals(3, push0([, '1', '2']));
%OptimizeFunctionOnNextCall(push0);
assertEquals(4, push0([, '1', '2', '3']));
function push1(a) {
return a.push('1');
}
assertEquals(3, push1([, '1']));
assertEquals(4, push1([, '1', '2']));
%OptimizeFunctionOnNextCall(push1);
assertEquals(5, push1([, '1', '2', '3']));
function push2(a) {
return a.push('1', '2');
}
assertEquals(4, push2([, '1']));
assertEquals(5, push2([, '1', '2']));
%OptimizeFunctionOnNextCall(push2);
assertEquals(6, push2([, '1', '2', '3']));
function push3(a) {
return a.push('1', '2', '3');
}
assertEquals(5, push3([, '1']));
assertEquals(6, push3([, '1', '2']));
%OptimizeFunctionOnNextCall(push3);
assertEquals(7, push3([, '1', '2', '3']));
})();

View File

@ -0,0 +1,65 @@
// 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 --opt
// Test elements transition from SMI to DOUBLE.
(function() {
const a = [];
const foo = (x, y) => a.push(x, y);
foo(1, 2);
foo(3, 4);
%OptimizeFunctionOnNextCall(foo);
foo(5, 6.6);
assertEquals([1, 2, 3, 4, 5, 6.6], a);
})();
(function() {
const a = [];
const foo = (x, y) => a.push(x, y);
foo(1, 2);
foo(3, 4);
%OptimizeFunctionOnNextCall(foo);
foo(5.5, 6.6);
assertEquals([1, 2, 3, 4, 5.5, 6.6], a);
})();
// Test elements transition from SMI to OBJECT.
(function() {
const a = [];
const foo = (x, y) => a.push(x, y);
foo(1, 2);
foo(3, 4);
%OptimizeFunctionOnNextCall(foo);
foo(5, '6');
assertEquals([1, 2, 3, 4, 5, '6'], a);
})();
(function() {
const a = [];
const foo = (x, y) => a.push(x, y);
foo(1, 2);
foo(3, 4);
%OptimizeFunctionOnNextCall(foo);
foo('5', '6');
assertEquals([1, 2, 3, 4, '5', '6'], a);
})();
// Test elements transition from DOUBLE to OBJECT.
(function() {
const a = [0.5];
const foo = (x, y) => a.push(x, y);
foo(1, 2);
foo(3, 4);
%OptimizeFunctionOnNextCall(foo);
foo(5, '6');
assertEquals([0.5, 1, 2, 3, 4, 5, '6'], a);
})();
(function() {
const a = [0.5];
const foo = (x, y) => a.push(x, y);
foo(1, 2);
foo(3, 4);
%OptimizeFunctionOnNextCall(foo);
foo('5', '6');
assertEquals([0.5, 1, 2, 3, 4, '5', '6'], a);
})();

View File

@ -0,0 +1,51 @@
// 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 --opt
// Test side effects on arguments evaluation.
(function() {
const a = [];
const bar = x => { a.push(x); return x; };
const foo = x => a.push(bar(x), bar(x));
foo(1);
foo(2);
%OptimizeFunctionOnNextCall(foo);
foo(3);
assertEquals([1,1,1,1, 2,2,2,2, 3,3,3,3], a);
})();
// Test invalidation on arguments evaluation.
(function() {
let y = 1;
const a = [];
const bar = x => { a.push(y); return x; }
const foo = x => a.push(bar(x), bar(x));
foo(1);
y = 2;
foo(2);
%OptimizeFunctionOnNextCall(foo);
y = 3;
foo(3);
assertOptimized(foo);
y = 4.4;
foo(4);
assertEquals([1,1,1,1, 2,2,2,2, 3,3,3,3, 4.4,4.4,4,4], a);
})();
(function() {
let y = 1;
const a = [0.5];
const bar = x => { a.push(y); return x; }
const foo = x => a.push(bar(x), bar(x));
foo(1);
y = 2;
foo(2);
%OptimizeFunctionOnNextCall(foo);
y = 3;
foo(3);
assertOptimized(foo);
y = '4';
foo(4);
assertEquals([0.5, 1,1,1,1, 2,2,2,2, 3,3,3,3, '4','4',4,4], a);
})();