[turbofan] Allow polymorphic inlining for Array push / pop / shift

Array push / pop / shift were inlined if the elements kind of the
receiver maps is the same. This cl extends it by inlining these
builtins even when the receiver maps have different elements kinds.
It still limits it to only fast elements kinds. This is required to
prevent regressions in deltablue when lazy feedback allocation is
enabled. With lazy feedback allocation we may see polymorphic
feedback more often, since we don't have allocation site feedback
till the feedback vectors are allocated.

Bug: v8:9078
Change-Id: Id4a7b84be6305b125913b6ce0fb4f3eb3e3b15ec
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1632239
Commit-Queue: Mythri Alle <mythria@chromium.org>
Reviewed-by: Benedikt Meurer <bmeurer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#61949}
This commit is contained in:
Mythri A 2019-05-31 14:56:31 +01:00 committed by Commit Bot
parent 40c6892643
commit 3e90eee96b
3 changed files with 488 additions and 267 deletions

View File

@ -1003,25 +1003,26 @@ bool CanInlineArrayIteratingBuiltin(JSHeapBroker* broker,
bool CanInlineArrayResizingBuiltin(JSHeapBroker* broker, bool CanInlineArrayResizingBuiltin(JSHeapBroker* broker,
MapHandles const& receiver_maps, MapHandles const& receiver_maps,
ElementsKind* kind_return, std::vector<ElementsKind>& kinds,
bool builtin_is_push = false) { bool builtin_is_push = false) {
DCHECK_NE(0, receiver_maps.size()); DCHECK_NE(0, receiver_maps.size());
*kind_return = MapRef(broker, receiver_maps[0]).elements_kind();
for (auto receiver_map : receiver_maps) { for (auto receiver_map : receiver_maps) {
MapRef map(broker, receiver_map); MapRef map(broker, receiver_map);
if (!map.supports_fast_array_resize()) return false; if (!map.supports_fast_array_resize()) return false;
if (builtin_is_push) { // TODO(turbofan): We should also handle fast holey double elements once
if (!UnionElementsKindUptoPackedness(kind_return, map.elements_kind())) { // we got the hole NaN mess sorted out in TurboFan/V8.
return false; if (map.elements_kind() == HOLEY_DOUBLE_ELEMENTS && !builtin_is_push) {
} return false;
} else { }
// TODO(turbofan): We should also handle fast holey double elements once ElementsKind current_kind = map.elements_kind();
// we got the hole NaN mess sorted out in TurboFan/V8. auto kind_ptr = kinds.data();
if (map.elements_kind() == HOLEY_DOUBLE_ELEMENTS || size_t i;
!UnionElementsKindUptoSize(kind_return, map.elements_kind())) { for (i = 0; i < kinds.size(); i++, kind_ptr++) {
return false; if (UnionElementsKindUptoPackedness(kind_ptr, current_kind)) {
break;
} }
} }
if (i == kinds.size()) kinds.push_back(current_kind);
} }
return true; return true;
} }
@ -4249,6 +4250,52 @@ Reduction JSCallReducer::ReduceSoftDeoptimize(Node* node,
return Changed(node); return Changed(node);
} }
Node* JSCallReducer::LoadReceiverElementsKind(Node* receiver, Node** effect,
Node** control) {
Node* receiver_map = *effect =
graph()->NewNode(simplified()->LoadField(AccessBuilder::ForMap()),
receiver, *effect, *control);
Node* receiver_bit_field2 = *effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForMapBitField2()), receiver_map,
*effect, *control);
Node* receiver_elements_kind = graph()->NewNode(
simplified()->NumberShiftRightLogical(),
graph()->NewNode(simplified()->NumberBitwiseAnd(), receiver_bit_field2,
jsgraph()->Constant(Map::ElementsKindBits::kMask)),
jsgraph()->Constant(Map::ElementsKindBits::kShift));
return receiver_elements_kind;
}
void JSCallReducer::CheckIfElementsKind(Node* receiver_elements_kind,
ElementsKind kind, Node* control,
Node** if_true, Node** if_false) {
Node* is_packed_kind =
graph()->NewNode(simplified()->NumberEqual(), receiver_elements_kind,
jsgraph()->Constant(GetPackedElementsKind(kind)));
Node* packed_branch =
graph()->NewNode(common()->Branch(), is_packed_kind, control);
Node* if_packed = graph()->NewNode(common()->IfTrue(), packed_branch);
if (IsHoleyElementsKind(kind)) {
Node* if_not_packed = graph()->NewNode(common()->IfFalse(), packed_branch);
Node* is_holey_kind =
graph()->NewNode(simplified()->NumberEqual(), receiver_elements_kind,
jsgraph()->Constant(GetHoleyElementsKind(kind)));
Node* holey_branch =
graph()->NewNode(common()->Branch(), is_holey_kind, if_not_packed);
Node* if_holey = graph()->NewNode(common()->IfTrue(), holey_branch);
Node* if_not_packed_not_holey =
graph()->NewNode(common()->IfFalse(), holey_branch);
*if_true = graph()->NewNode(common()->Merge(2), if_packed, if_holey);
*if_false = if_not_packed_not_holey;
} else {
*if_true = if_packed;
*if_false = graph()->NewNode(common()->IfFalse(), packed_branch);
}
}
// ES6 section 22.1.3.18 Array.prototype.push ( ) // ES6 section 22.1.3.18 Array.prototype.push ( )
Reduction JSCallReducer::ReduceArrayPrototypePush(Node* node) { Reduction JSCallReducer::ReduceArrayPrototypePush(Node* node) {
DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
@ -4266,81 +4313,121 @@ Reduction JSCallReducer::ReduceArrayPrototypePush(Node* node) {
if (!inference.HaveMaps()) return NoChange(); if (!inference.HaveMaps()) return NoChange();
MapHandles const& receiver_maps = inference.GetMaps(); MapHandles const& receiver_maps = inference.GetMaps();
ElementsKind kind; std::vector<ElementsKind> kinds;
if (!CanInlineArrayResizingBuiltin(broker(), receiver_maps, &kind, true)) { if (!CanInlineArrayResizingBuiltin(broker(), receiver_maps, kinds, true)) {
return inference.NoChange(); return inference.NoChange();
} }
if (!dependencies()->DependOnNoElementsProtector()) UNREACHABLE(); if (!dependencies()->DependOnNoElementsProtector()) UNREACHABLE();
inference.RelyOnMapsPreferStability(dependencies(), jsgraph(), &effect, inference.RelyOnMapsPreferStability(dependencies(), jsgraph(), &effect,
control, p.feedback()); control, p.feedback());
// Collect the value inputs to push. std::vector<Node*> controls_to_merge;
std::vector<Node*> values(num_values); std::vector<Node*> effects_to_merge;
for (int i = 0; i < num_values; ++i) { std::vector<Node*> values_to_merge;
values[i] = NodeProperties::GetValueInput(node, 2 + i); Node* return_value = jsgraph()->UndefinedConstant();
}
for (auto& value : values) { Node* receiver_elements_kind =
if (IsSmiElementsKind(kind)) { LoadReceiverElementsKind(receiver, &effect, &control);
value = effect = graph()->NewNode(simplified()->CheckSmi(p.feedback()), Node* next_control = control;
value, effect, control); Node* next_effect = effect;
} else if (IsDoubleElementsKind(kind)) { for (size_t i = 0; i < kinds.size(); i++) {
value = effect = graph()->NewNode(simplified()->CheckNumber(p.feedback()), ElementsKind kind = kinds[i];
value, effect, control); control = next_control;
// Make sure we do not store signaling NaNs into double arrays. effect = next_effect;
value = graph()->NewNode(simplified()->NumberSilenceNaN(), value); // We do not need branch for the last elements kind.
if (i != kinds.size() - 1) {
CheckIfElementsKind(receiver_elements_kind, kind, control, &control,
&next_control);
} }
}
// Load the "length" property of the {receiver}. // Collect the value inputs to push.
Node* length = effect = graph()->NewNode( std::vector<Node*> values(num_values);
simplified()->LoadField(AccessBuilder::ForJSArrayLength(kind)), receiver,
effect, control);
Node* value = length;
// 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));
// 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);
GrowFastElementsMode mode =
IsDoubleElementsKind(kind) ? GrowFastElementsMode::kDoubleElements
: GrowFastElementsMode::kSmiOrObjectElements;
elements = effect = graph()->NewNode(
simplified()->MaybeGrowFastElements(mode, p.feedback()), 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(kind)),
receiver, new_length, effect, control);
// Append the {values} to the {elements}.
for (int i = 0; i < num_values; ++i) { for (int i = 0; i < num_values; ++i) {
Node* value = values[i]; values[i] = NodeProperties::GetValueInput(node, 2 + i);
Node* index = graph()->NewNode(simplified()->NumberAdd(), length,
jsgraph()->Constant(i));
effect = graph()->NewNode(
simplified()->StoreElement(AccessBuilder::ForFixedArrayElement(kind)),
elements, index, value, effect, control);
} }
for (auto& value : values) {
if (IsSmiElementsKind(kind)) {
value = effect = graph()->NewNode(simplified()->CheckSmi(p.feedback()),
value, effect, control);
} else if (IsDoubleElementsKind(kind)) {
value = effect = graph()->NewNode(
simplified()->CheckNumber(p.feedback()), 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}.
Node* length = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSArrayLength(kind)),
receiver, effect, control);
return_value = length;
// Check if we have any {values} to push.
if (num_values > 0) {
// Compute the resulting "length" of the {receiver}.
Node* new_length = return_value = graph()->NewNode(
simplified()->NumberAdd(), length, jsgraph()->Constant(num_values));
// 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);
GrowFastElementsMode mode =
IsDoubleElementsKind(kind)
? GrowFastElementsMode::kDoubleElements
: GrowFastElementsMode::kSmiOrObjectElements;
elements = effect = graph()->NewNode(
simplified()->MaybeGrowFastElements(mode, p.feedback()), 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(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(kind)),
elements, index, value, effect, control);
}
}
controls_to_merge.push_back(control);
effects_to_merge.push_back(effect);
values_to_merge.push_back(return_value);
} }
ReplaceWithValue(node, value, effect, control); if (controls_to_merge.size() > 1) {
return Replace(value); int const count = static_cast<int>(controls_to_merge.size());
control = graph()->NewNode(common()->Merge(count), count,
&controls_to_merge.front());
effects_to_merge.push_back(control);
effect = graph()->NewNode(common()->EffectPhi(count), count + 1,
&effects_to_merge.front());
values_to_merge.push_back(control);
return_value =
graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, count),
count + 1, &values_to_merge.front());
}
ReplaceWithValue(node, return_value, effect, control);
return Replace(return_value);
} }
// ES6 section 22.1.3.17 Array.prototype.pop ( ) // ES6 section 22.1.3.17 Array.prototype.pop ( )
@ -4359,79 +4446,117 @@ Reduction JSCallReducer::ReduceArrayPrototypePop(Node* node) {
if (!inference.HaveMaps()) return NoChange(); if (!inference.HaveMaps()) return NoChange();
MapHandles const& receiver_maps = inference.GetMaps(); MapHandles const& receiver_maps = inference.GetMaps();
ElementsKind kind; std::vector<ElementsKind> kinds;
if (!CanInlineArrayResizingBuiltin(broker(), receiver_maps, &kind)) { if (!CanInlineArrayResizingBuiltin(broker(), receiver_maps, kinds)) {
return inference.NoChange(); return inference.NoChange();
} }
if (!dependencies()->DependOnNoElementsProtector()) UNREACHABLE(); if (!dependencies()->DependOnNoElementsProtector()) UNREACHABLE();
inference.RelyOnMapsPreferStability(dependencies(), jsgraph(), &effect, inference.RelyOnMapsPreferStability(dependencies(), jsgraph(), &effect,
control, p.feedback()); control, p.feedback());
// Load the "length" property of the {receiver}. std::vector<Node*> controls_to_merge;
Node* length = effect = graph()->NewNode( std::vector<Node*> effects_to_merge;
simplified()->LoadField(AccessBuilder::ForJSArrayLength(kind)), receiver, std::vector<Node*> values_to_merge;
effect, control); Node* value = jsgraph()->UndefinedConstant();
// Check if the {receiver} has any elements. Node* receiver_elements_kind =
Node* check = graph()->NewNode(simplified()->NumberEqual(), length, LoadReceiverElementsKind(receiver, &effect, &control);
jsgraph()->ZeroConstant()); Node* next_control = control;
Node* branch = Node* next_effect = effect;
graph()->NewNode(common()->Branch(BranchHint::kFalse), check, control); for (size_t i = 0; i < kinds.size(); i++) {
ElementsKind kind = kinds[i];
Node* if_true = graph()->NewNode(common()->IfTrue(), branch); control = next_control;
Node* etrue = effect; effect = next_effect;
Node* vtrue = jsgraph()->UndefinedConstant(); // We do not need branch for the last elements kind.
if (i != kinds.size() - 1) {
Node* if_false = graph()->NewNode(common()->IfFalse(), branch); CheckIfElementsKind(receiver_elements_kind, kind, control, &control,
Node* efalse = effect; &next_control);
Node* vfalse;
{
// TODO(tebbi): We should trim the backing store if the capacity is too
// big, as implemented in elements.cc:ElementsAccessorBase::SetLengthImpl.
// Load the elements backing store from the {receiver}.
Node* elements = efalse = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSObjectElements()), receiver,
efalse, if_false);
// Ensure that we aren't popping from a copy-on-write backing store.
if (IsSmiOrObjectElementsKind(kind)) {
elements = efalse =
graph()->NewNode(simplified()->EnsureWritableFastElements(), receiver,
elements, efalse, if_false);
} }
// Compute the new {length}. // Load the "length" property of the {receiver}.
length = graph()->NewNode(simplified()->NumberSubtract(), length, Node* length = effect = graph()->NewNode(
jsgraph()->OneConstant()); simplified()->LoadField(AccessBuilder::ForJSArrayLength(kind)),
receiver, effect, control);
// Store the new {length} to the {receiver}. // Check if the {receiver} has any elements.
efalse = graph()->NewNode( Node* check = graph()->NewNode(simplified()->NumberEqual(), length,
simplified()->StoreField(AccessBuilder::ForJSArrayLength(kind)), jsgraph()->ZeroConstant());
receiver, length, efalse, if_false); Node* branch =
graph()->NewNode(common()->Branch(BranchHint::kFalse), check, control);
// Load the last entry from the {elements}. Node* if_true = graph()->NewNode(common()->IfTrue(), branch);
vfalse = efalse = graph()->NewNode( Node* etrue = effect;
simplified()->LoadElement(AccessBuilder::ForFixedArrayElement(kind)), Node* vtrue = jsgraph()->UndefinedConstant();
elements, length, efalse, if_false);
// Store a hole to the element we just removed from the {receiver}. Node* if_false = graph()->NewNode(common()->IfFalse(), branch);
efalse = graph()->NewNode( Node* efalse = effect;
simplified()->StoreElement( Node* vfalse;
AccessBuilder::ForFixedArrayElement(GetHoleyElementsKind(kind))), {
elements, length, jsgraph()->TheHoleConstant(), efalse, if_false); // TODO(tebbi): We should trim the backing store if the capacity is too
// big, as implemented in elements.cc:ElementsAccessorBase::SetLengthImpl.
// Load the elements backing store from the {receiver}.
Node* elements = efalse = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSObjectElements()),
receiver, efalse, if_false);
// Ensure that we aren't popping from a copy-on-write backing store.
if (IsSmiOrObjectElementsKind(kind)) {
elements = efalse =
graph()->NewNode(simplified()->EnsureWritableFastElements(),
receiver, elements, efalse, if_false);
}
// Compute the new {length}.
length = graph()->NewNode(simplified()->NumberSubtract(), length,
jsgraph()->OneConstant());
// Store the new {length} to the {receiver}.
efalse = graph()->NewNode(
simplified()->StoreField(AccessBuilder::ForJSArrayLength(kind)),
receiver, length, efalse, if_false);
// Load the last entry from the {elements}.
vfalse = efalse = graph()->NewNode(
simplified()->LoadElement(AccessBuilder::ForFixedArrayElement(kind)),
elements, length, efalse, if_false);
// Store a hole to the element we just removed from the {receiver}.
efalse = graph()->NewNode(
simplified()->StoreElement(
AccessBuilder::ForFixedArrayElement(GetHoleyElementsKind(kind))),
elements, length, jsgraph()->TheHoleConstant(), efalse, if_false);
}
control = graph()->NewNode(common()->Merge(2), if_true, if_false);
effect = graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control);
value = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2),
vtrue, vfalse, 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 (IsHoleyElementsKind(kind)) {
value =
graph()->NewNode(simplified()->ConvertTaggedHoleToUndefined(), value);
}
controls_to_merge.push_back(control);
effects_to_merge.push_back(effect);
values_to_merge.push_back(value);
} }
control = graph()->NewNode(common()->Merge(2), if_true, if_false); if (controls_to_merge.size() > 1) {
effect = graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control); int const count = static_cast<int>(controls_to_merge.size());
Node* value = graph()->NewNode(
common()->Phi(MachineRepresentation::kTagged, 2), vtrue, vfalse, control);
// Convert the hole to undefined. Do this last, so that we can optimize control = graph()->NewNode(common()->Merge(count), count,
// conversion operator via some smart strength reduction in many cases. &controls_to_merge.front());
if (IsHoleyElementsKind(kind)) { effects_to_merge.push_back(control);
effect = graph()->NewNode(common()->EffectPhi(count), count + 1,
&effects_to_merge.front());
values_to_merge.push_back(control);
value = value =
graph()->NewNode(simplified()->ConvertTaggedHoleToUndefined(), value); graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, count),
count + 1, &values_to_merge.front());
} }
ReplaceWithValue(node, value, effect, control); ReplaceWithValue(node, value, effect, control);
@ -4457,151 +4582,172 @@ Reduction JSCallReducer::ReduceArrayPrototypeShift(Node* node) {
if (!inference.HaveMaps()) return NoChange(); if (!inference.HaveMaps()) return NoChange();
MapHandles const& receiver_maps = inference.GetMaps(); MapHandles const& receiver_maps = inference.GetMaps();
ElementsKind kind; std::vector<ElementsKind> kinds;
if (!CanInlineArrayResizingBuiltin(broker(), receiver_maps, &kind)) { if (!CanInlineArrayResizingBuiltin(broker(), receiver_maps, kinds)) {
return inference.NoChange(); return inference.NoChange();
} }
if (!dependencies()->DependOnNoElementsProtector()) UNREACHABLE(); if (!dependencies()->DependOnNoElementsProtector()) UNREACHABLE();
inference.RelyOnMapsPreferStability(dependencies(), jsgraph(), &effect, inference.RelyOnMapsPreferStability(dependencies(), jsgraph(), &effect,
control, p.feedback()); control, p.feedback());
// Load length of the {receiver}. std::vector<Node*> controls_to_merge;
Node* length = effect = graph()->NewNode( std::vector<Node*> effects_to_merge;
simplified()->LoadField(AccessBuilder::ForJSArrayLength(kind)), receiver, std::vector<Node*> values_to_merge;
effect, control); Node* value = jsgraph()->UndefinedConstant();
// Return undefined if {receiver} has no elements. Node* receiver_elements_kind =
Node* check0 = graph()->NewNode(simplified()->NumberEqual(), length, LoadReceiverElementsKind(receiver, &effect, &control);
jsgraph()->ZeroConstant()); Node* next_control = control;
Node* branch0 = Node* next_effect = effect;
graph()->NewNode(common()->Branch(BranchHint::kFalse), check0, control); for (size_t i = 0; i < kinds.size(); i++) {
ElementsKind kind = kinds[i];
control = next_control;
effect = next_effect;
// We do not need branch for the last elements kind.
if (i != kinds.size() - 1) {
CheckIfElementsKind(receiver_elements_kind, kind, control, &control,
&next_control);
}
Node* if_true0 = graph()->NewNode(common()->IfTrue(), branch0); // Load length of the {receiver}.
Node* etrue0 = effect; Node* length = effect = graph()->NewNode(
Node* vtrue0 = jsgraph()->UndefinedConstant(); simplified()->LoadField(AccessBuilder::ForJSArrayLength(kind)),
receiver, effect, control);
Node* if_false0 = graph()->NewNode(common()->IfFalse(), branch0); // Return undefined if {receiver} has no elements.
Node* efalse0 = effect; Node* check0 = graph()->NewNode(simplified()->NumberEqual(), length,
Node* vfalse0; jsgraph()->ZeroConstant());
{ Node* branch0 =
// Check if we should take the fast-path. graph()->NewNode(common()->Branch(BranchHint::kFalse), check0, control);
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* if_true0 = graph()->NewNode(common()->IfTrue(), branch0);
Node* etrue1 = efalse0; Node* etrue0 = effect;
Node* vtrue1; Node* vtrue0 = jsgraph()->UndefinedConstant();
Node* if_false0 = graph()->NewNode(common()->IfFalse(), branch0);
Node* efalse0 = effect;
Node* vfalse0;
{ {
Node* elements = etrue1 = graph()->NewNode( // Check if we should take the fast-path.
simplified()->LoadField(AccessBuilder::ForJSObjectElements()), Node* check1 =
receiver, etrue1, if_true1); graph()->NewNode(simplified()->NumberLessThanOrEqual(), length,
jsgraph()->Constant(JSArray::kMaxCopyElements));
// Load the first element here, which we return below. Node* branch1 = graph()->NewNode(common()->Branch(BranchHint::kTrue),
vtrue1 = etrue1 = graph()->NewNode( check1, if_false0);
simplified()->LoadElement(AccessBuilder::ForFixedArrayElement(kind)),
elements, jsgraph()->ZeroConstant(), etrue1, if_true1);
// Ensure that we aren't shifting a copy-on-write backing store.
if (IsSmiOrObjectElementsKind(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* terminate = graph()->NewNode(common()->Terminate(), eloop, loop);
NodeProperties::MergeControlToEnd(graph(), common(), terminate);
Node* index = graph()->NewNode(
common()->Phi(MachineRepresentation::kTagged, 2),
jsgraph()->OneConstant(),
jsgraph()->Constant(JSArray::kMaxCopyElements - 1), loop);
Node* if_true1 = graph()->NewNode(common()->IfTrue(), branch1);
Node* etrue1 = efalse0;
Node* vtrue1;
{ {
Node* check2 = Node* elements = etrue1 = graph()->NewNode(
graph()->NewNode(simplified()->NumberLessThan(), index, length); simplified()->LoadField(AccessBuilder::ForJSObjectElements()),
Node* branch2 = graph()->NewNode(common()->Branch(), check2, loop); receiver, etrue1, if_true1);
if_true1 = graph()->NewNode(common()->IfFalse(), branch2); // Load the first element here, which we return below.
etrue1 = eloop; vtrue1 = etrue1 = graph()->NewNode(
simplified()->LoadElement(
AccessBuilder::ForFixedArrayElement(kind)),
elements, jsgraph()->ZeroConstant(), etrue1, if_true1);
Node* control = graph()->NewNode(common()->IfTrue(), branch2); // Ensure that we aren't shifting a copy-on-write backing store.
Node* effect = etrue1; if (IsSmiOrObjectElementsKind(kind)) {
elements = etrue1 =
graph()->NewNode(simplified()->EnsureWritableFastElements(),
receiver, elements, etrue1, if_true1);
}
ElementAccess const access = AccessBuilder::ForFixedArrayElement(kind); // Shift the remaining {elements} by one towards the start.
Node* value = effect = Node* loop = graph()->NewNode(common()->Loop(2), if_true1, if_true1);
graph()->NewNode(simplified()->LoadElement(access), elements, index, Node* eloop =
effect, control); graph()->NewNode(common()->EffectPhi(2), etrue1, etrue1, loop);
effect = Node* terminate = graph()->NewNode(common()->Terminate(), eloop, loop);
graph()->NewNode(simplified()->StoreElement(access), elements, NodeProperties::MergeControlToEnd(graph(), common(), terminate);
graph()->NewNode(simplified()->NumberSubtract(), Node* index = graph()->NewNode(
index, jsgraph()->OneConstant()), common()->Phi(MachineRepresentation::kTagged, 2),
value, effect, control); jsgraph()->OneConstant(),
jsgraph()->Constant(JSArray::kMaxCopyElements - 1), loop);
loop->ReplaceInput(1, control); {
eloop->ReplaceInput(1, effect); Node* check2 =
index->ReplaceInput(1, graph()->NewNode(simplified()->NumberLessThan(), index, length);
graph()->NewNode(simplified()->NumberAdd(), index, Node* branch2 = graph()->NewNode(common()->Branch(), check2, loop);
jsgraph()->OneConstant()));
if_true1 = graph()->NewNode(common()->IfFalse(), branch2);
etrue1 = eloop;
Node* control = graph()->NewNode(common()->IfTrue(), branch2);
Node* effect = etrue1;
ElementAccess const access =
AccessBuilder::ForFixedArrayElement(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(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(kind))),
elements, length, jsgraph()->TheHoleConstant(), etrue1, if_true1);
} }
// Compute the new {length}. Node* if_false1 = graph()->NewNode(common()->IfFalse(), branch1);
length = graph()->NewNode(simplified()->NumberSubtract(), length, Node* efalse1 = efalse0;
jsgraph()->OneConstant()); Node* vfalse1;
{
// Call the generic C++ implementation.
const int builtin_index = Builtins::kArrayShift;
auto call_descriptor = 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::Create(builtin_entry));
Node* argc =
jsgraph()->Constant(BuiltinArguments::kNumExtraArgsWithReceiver);
if_false1 = efalse1 = vfalse1 =
graph()->NewNode(common()->Call(call_descriptor), stub_code,
receiver, jsgraph()->PaddingConstant(), argc,
target, jsgraph()->UndefinedConstant(), entry,
argc, context, frame_state, efalse1, if_false1);
}
// Store the new {length} to the {receiver}. if_false0 = graph()->NewNode(common()->Merge(2), if_true1, if_false1);
etrue1 = graph()->NewNode( efalse0 =
simplified()->StoreField(AccessBuilder::ForJSArrayLength(kind)), graph()->NewNode(common()->EffectPhi(2), etrue1, efalse1, if_false0);
receiver, length, etrue1, if_true1); vfalse0 =
graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2),
// Store a hole to the element we just removed from the {receiver}. vtrue1, vfalse1, if_false0);
etrue1 = graph()->NewNode(
simplified()->StoreElement(
AccessBuilder::ForFixedArrayElement(GetHoleyElementsKind(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;
auto call_descriptor = 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::Create(builtin_entry));
Node* argc =
jsgraph()->Constant(BuiltinArguments::kNumExtraArgsWithReceiver);
if_false1 = efalse1 = vfalse1 =
graph()->NewNode(common()->Call(call_descriptor), stub_code, receiver,
jsgraph()->PaddingConstant(), 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); control = graph()->NewNode(common()->Merge(2), if_true0, if_false0);
effect = graph()->NewNode(common()->EffectPhi(2), etrue0, efalse0, control); effect = graph()->NewNode(common()->EffectPhi(2), etrue0, efalse0, control);
Node* value = value = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2),
graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), vtrue0, vfalse0, control);
vtrue0, vfalse0, control);
// Convert the hole to undefined. Do this last, so that we can optimize // Convert the hole to undefined. Do this last, so that we can optimize
// conversion operator via some smart strength reduction in many cases. // conversion operator via some smart strength reduction in many cases.
@ -4610,8 +4756,27 @@ Reduction JSCallReducer::ReduceArrayPrototypeShift(Node* node) {
graph()->NewNode(simplified()->ConvertTaggedHoleToUndefined(), value); graph()->NewNode(simplified()->ConvertTaggedHoleToUndefined(), value);
} }
ReplaceWithValue(node, value, effect, control); controls_to_merge.push_back(control);
return Replace(value); effects_to_merge.push_back(effect);
values_to_merge.push_back(value);
}
if (controls_to_merge.size() > 1) {
int const count = static_cast<int>(controls_to_merge.size());
control = graph()->NewNode(common()->Merge(count), count,
&controls_to_merge.front());
effects_to_merge.push_back(control);
effect = graph()->NewNode(common()->EffectPhi(count), count + 1,
&effects_to_merge.front());
values_to_merge.push_back(control);
value =
graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, count),
count + 1, &values_to_merge.front());
}
ReplaceWithValue(node, value, effect, control);
return Replace(value);
} }
// ES6 section 22.1.3.23 Array.prototype.slice ( ) // ES6 section 22.1.3.23 Array.prototype.slice ( )

View File

@ -231,6 +231,10 @@ class V8_EXPORT_PRIVATE JSCallReducer final : public AdvancedReducer {
const SharedFunctionInfoRef& shared, const SharedFunctionInfoRef& shared,
Node* context = nullptr); Node* context = nullptr);
void CheckIfElementsKind(Node* receiver_elements_kind, ElementsKind kind,
Node* control, Node** if_true, Node** if_false);
Node* LoadReceiverElementsKind(Node* receiver, Node** effect, Node** control);
Graph* graph() const; Graph* graph() const;
JSGraph* jsgraph() const { return jsgraph_; } JSGraph* jsgraph() const { return jsgraph_; }
JSHeapBroker* broker() const { return broker_; } JSHeapBroker* broker() const { return broker_; }

View File

@ -7,8 +7,8 @@
let id = 0; let id = 0;
function runTest(f, message, mkICTraining, deoptArg) { function runTest(f, message, mkICTraining, deoptArg, speculationCheck) {
function test(f, message, ictraining, deoptArg) { function test(f, message, ictraining, deoptArg, speculationCheck) {
// Train the call ic to the maps. // Train the call ic to the maps.
let t = ictraining; let t = ictraining;
@ -41,15 +41,22 @@ function runTest(f, message, mkICTraining, deoptArg) {
// Trigger deopt, causing no-speculation bit to be set. // Trigger deopt, causing no-speculation bit to be set.
let a1 = deoptArg; let a1 = deoptArg;
let a2 = deoptArg; let a2 = deoptArg;
let a3 = deoptArg;
message += " for args " + JSON.stringify(a1); message += " for args " + JSON.stringify(a1);
message_unoptimized = message + " should have been unoptimized" message_unoptimized = message + " should have been unoptimized"
message_optimized = message + " should have been unoptimized" message_optimized = message + " should have been optimized"
f(a1.arr, () => a1.el); f(a1.darr, () => a1.del);
assertUnoptimized(f, undefined, message_unoptimized); assertUnoptimized(f, undefined, message_unoptimized);
if (speculationCheck) {
%PrepareFunctionForOptimization(f);
%OptimizeFunctionOnNextCall(f);
f(a2.darr, () => a2.del);
assertUnoptimized(f, undefined, message_unoptimized);
}
%PrepareFunctionForOptimization(f); %PrepareFunctionForOptimization(f);
%OptimizeFunctionOnNextCall(f); %OptimizeFunctionOnNextCall(f);
// No speculation should protect against further deopts. // No speculation should protect against further deopts.
f(a2.arr, () => a2.el); f(a3.darr, () => a3.del);
assertOptimized(f, undefined, message_optimized); assertOptimized(f, undefined, message_optimized);
} }
} }
@ -64,6 +71,8 @@ function runTest(f, message, mkICTraining, deoptArg) {
testString = testString.replace(new RegExp("ictraining", 'g'), mkICTraining.toString()); testString = testString.replace(new RegExp("ictraining", 'g'), mkICTraining.toString());
testString = testString.replace(new RegExp("deoptArg", 'g'), testString = testString.replace(new RegExp("deoptArg", 'g'),
deoptArg ? JSON.stringify(deoptArg).replace(/"/g,'') : "undefined"); deoptArg ? JSON.stringify(deoptArg).replace(/"/g,'') : "undefined");
testString = testString.replace(new RegExp("speculationCheck", 'g'),
speculationCheck ? JSON.stringify(deoptArg).replace(/"/g,'') : "undefined");
// Make field names unique to avoid learning of types. // Make field names unique to avoid learning of types.
id = id + 1; id = id + 1;
@ -71,16 +80,17 @@ function runTest(f, message, mkICTraining, deoptArg) {
testString = testString.replace(/el:/g, 'el' + id + ':'); testString = testString.replace(/el:/g, 'el' + id + ':');
testString = testString.replace(/[.]arr/g, '.arr' + id); testString = testString.replace(/[.]arr/g, '.arr' + id);
testString = testString.replace(/arr:/g, 'arr' + id + ':'); testString = testString.replace(/arr:/g, 'arr' + id + ':');
testString = testString.replace(/[.]del/g, '.del' + id);
testString = testString.replace(/[.]darr/g, '.darr' + id);
var modTest = new Function("message", testString); var modTest = new Function("message", testString);
//print(modTest);
modTest(message); modTest(message);
} }
let checks = { let checks = {
smiReceiver: smiReceiver:
{ mkTrainingArguments : () => [{arr:[1], el:3}], { mkTrainingArguments : () => [{arr:[1], el:3}],
deoptingArguments : [{arr:[0.1], el:1}, {arr:[{}], el:1}] deoptingArguments : [{darr:[0.1], del:1}, {darr:[{}], del:1}]
}, },
objectReceiver: objectReceiver:
{ mkTrainingArguments : () => [{arr:[{}], el:0.1}], { mkTrainingArguments : () => [{arr:[{}], el:0.1}],
@ -88,30 +98,53 @@ let checks = {
}, },
multipleSmiReceivers: multipleSmiReceivers:
{ mkTrainingArguments : () => { let b = [1]; b.x=3; return [{arr:[1], el:3}, {arr:b, el:3}] }, { mkTrainingArguments : () => { let b = [1]; b.x=3; return [{arr:[1], el:3}, {arr:b, el:3}] },
deoptingArguments : [{arr:[0.1], el:1}, {arr:[{}], el:1}] deoptingArguments : [{darr:[0.1], del:1}, {darr:[{}], del:1}]
}, },
multipleSmiReceiversPackedUnpacked: multipleSmiReceiversPackedUnpacked:
{ mkTrainingArguments : () => { let b = [1]; b[100] = 3; return [{arr:[1], el:3}, {arr:b, el:3}] }, { mkTrainingArguments : () => { let b = [1]; b[100] = 3; return [{arr:[1], el:3}, {arr:b, el:3}] },
deoptingArguments : [{arr:[0.1], el:1}, {arr:[{}], el:1}] deoptingArguments : [{darr:[0.1], del:1}, {darr:[{}], del:1}]
}, },
multipleDoubleReceivers: multipleDoubleReceivers:
{ mkTrainingArguments : () => { let b = [0.1]; b.x=0.3; return [{arr:[0.1], el:0.3}, {arr:b, el:0.3}] }, { mkTrainingArguments : () => { let b = [0.1]; b.x=0.3; return [{arr:[0.1], el:0.3}, {arr:b, el:0.3}] },
deoptingArguments : [{arr:[{}], el:true}, {arr:[1], el:true}] deoptingArguments : [{darr:[{}], del:true}, {darr:[1], del: 1}]
}, },
multipleDoubleReceiversPackedUnpacked: multipleDoubleReceiversPackedUnpacked:
{ mkTrainingArguments : () => { let b = [0.1]; b[100] = 0.3; return [{arr:[0.1], el:0.3}, {arr:b, el:0.3}] }, { mkTrainingArguments : () => { let b = [0.1]; b[100] = 0.3; return [{arr:[0.1], el:0.3}, {arr:b, el:0.3}] },
deoptingArguments : [{arr:[{}], el:true}, {arr:[1], el:true}] deoptingArguments : [{darr:[{}], del:true}, {darr:[1], del: 1}]
}, },
multipleMixedReceivers: multipleMixedReceivers:
{ mkTrainingArguments : () => { let b = [0.1]; b.x=0.3; return [{arr:[1], el:0.3}, {arr:[{}], el:true}, {arr:b, el:0.3}] }, { mkTrainingArguments : () => { let b = [0.1]; b.x=0.3; return [{arr:[1], el:1}, {arr:[{}], el:true}, {arr:b, el:0.3}] },
deoptingArguments : [] deoptingArguments : []
}, },
multipleMixedReceiversPackedUnpacked: multipleMixedReceiversPackedUnpacked:
{ mkTrainingArguments : () => { let b = [0.1]; b[100] = 0.3; return [{arr:[1], el:0.3}, {arr:[{}], el:true}, {arr:b, el:0.3}] }, { mkTrainingArguments : () => { let b = [0.1]; b[100] = 0.3; return [{arr:[1], el:1}, {arr:[{}], el:true}, {arr:b, el:0.3}] },
deoptingArguments : [] deoptingArguments : []
}, },
}; };
let no_speculation_checks = {
smiReceiver:
{ mkTrainingArguments : () => [{arr:[1], el:3}],
deoptingArguments : [{darr:[0.1], del:true}]
},
multipleSmiReceivers:
{ mkTrainingArguments : () => { let b = [1]; b.x=3; return [{arr:[1], el:3}, {arr:[1], el:3}] },
deoptingArguments : [{darr:[0.1], del:true}]
},
multipleSmiReceiversPackedUnpacked:
{ mkTrainingArguments : () => { let b = [1]; b[100] = 3; return [{arr:[1], el:3}, {arr:b, el:3}] },
deoptingArguments : [{darr:[0.1], del:true}]
},
multipleDoubleReceivers:
{ mkTrainingArguments : () => { let b = [0.1]; b.x=0.3; return [{arr:[0.1], el:0.3}, {arr:b, el:0.3}] },
deoptingArguments : [{darr:[1], del:true}]
},
multipleDoubleReceiversPackedUnpacked:
{ mkTrainingArguments : () => { let b = [0.1]; b[100] = 0.3; return [{arr:[0.1], el:0.3}, {arr:b, el:0.3}] },
deoptingArguments : [{darr:[1], del:true}]
},
};
const functions = { const functions = {
push_reliable: (a,g) => { let b = g(); return a.push(2, b); }, push_reliable: (a,g) => { let b = g(); return a.push(2, b); },
push_unreliable: (a,g) => { return a.push(2, g()); }, push_unreliable: (a,g) => { return a.push(2, g()); },
@ -121,6 +154,11 @@ const functions = {
shift_unreliable: (a,g) => { return a.shift(2, g()); } shift_unreliable: (a,g) => { return a.shift(2, g()); }
} }
const push_functions = {
push_reliable: (a,g) => { let b = g(); return a.push(2, b); },
push_unreliable: (a,g) => { return a.push(2, g()); },
}
Object.keys(checks).forEach( Object.keys(checks).forEach(
key => { key => {
let check = checks[key]; let check = checks[key];
@ -134,3 +172,17 @@ Object.keys(checks).forEach(
} }
} }
); );
Object.keys(no_speculation_checks).forEach(
key => {
let check = no_speculation_checks[key];
for (fnc in push_functions) {
runTest(functions[fnc], "test-spec-check-" + fnc + "-" + key, check.mkTrainingArguments);
// Test each deopting arg separately.
for (let deoptArg of check.deoptingArguments) {
runTest(functions[fnc], "testDeopt-spec-check-" + fnc + "-" + key, check.mkTrainingArguments, deoptArg, true);
}
}
}
);