[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:
parent
e8b6173ac6
commit
68e4d86c6e
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
|
@ -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: {
|
||||
|
@ -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(
|
||||
|
@ -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);
|
||||
|
239
test/mjsunit/compiler/array-push-1.js
Normal file
239
test/mjsunit/compiler/array-push-1.js
Normal 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']));
|
||||
})();
|
65
test/mjsunit/compiler/array-push-2.js
Normal file
65
test/mjsunit/compiler/array-push-2.js
Normal 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);
|
||||
})();
|
51
test/mjsunit/compiler/array-push-3.js
Normal file
51
test/mjsunit/compiler/array-push-3.js
Normal 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);
|
||||
})();
|
Loading…
Reference in New Issue
Block a user