Commit acca8e28 authored by Mike Stanton's avatar Mike Stanton Committed by Commit Bot

[Turbofan] Inline Array.prototype.map

Bug: v8:1956
Change-Id: I41af0cf5eb2fbb9f1d9d4172f3f546bcc2a715dc
Reviewed-on: https://chromium-review.googlesource.com/548639
Commit-Queue: Michael Stanton <mvstanton@chromium.org>
Reviewed-by: 's avatarBenedikt Meurer <bmeurer@chromium.org>
Reviewed-by: 's avatarDaniel Clifford <danno@chromium.org>
Cr-Commit-Position: refs/heads/master@{#46618}
parent 44cce0fc
......@@ -1586,6 +1586,47 @@ TF_BUILTIN(ArrayMapLoopContinuation, ArrayBuiltinCodeStubAssembler) {
&ArrayBuiltinCodeStubAssembler::NullPostLoopAction);
}
TF_BUILTIN(ArrayMapLoopEagerDeoptContinuation, ArrayBuiltinCodeStubAssembler) {
Node* context = Parameter(Descriptor::kContext);
Node* receiver = Parameter(Descriptor::kReceiver);
Node* callbackfn = Parameter(Descriptor::kCallbackFn);
Node* this_arg = Parameter(Descriptor::kThisArg);
Node* array = Parameter(Descriptor::kArray);
Node* initial_k = Parameter(Descriptor::kInitialK);
Node* len = Parameter(Descriptor::kLength);
Callable stub(
Builtins::CallableFor(isolate(), Builtins::kArrayMapLoopContinuation));
Return(CallStub(stub, context, receiver, callbackfn, this_arg, array,
receiver, initial_k, len, UndefinedConstant()));
}
TF_BUILTIN(ArrayMapLoopLazyDeoptContinuation, ArrayBuiltinCodeStubAssembler) {
Node* context = Parameter(Descriptor::kContext);
Node* receiver = Parameter(Descriptor::kReceiver);
Node* callbackfn = Parameter(Descriptor::kCallbackFn);
Node* this_arg = Parameter(Descriptor::kThisArg);
Node* array = Parameter(Descriptor::kArray);
Node* initial_k = Parameter(Descriptor::kInitialK);
Node* len = Parameter(Descriptor::kLength);
Node* result = Parameter(Descriptor::kResult);
// This custom lazy deopt point is right after the callback. map() needs
// to pick up at the next step, which is setting the callback result in
// the output array. After incrementing k, we can glide into the loop
// continuation builtin.
// iii. Perform ? CreateDataPropertyOrThrow(A, Pk, mappedValue).
CallRuntime(Runtime::kCreateDataProperty, context, array, initial_k, result);
// Then we have to increment k before going on.
initial_k = NumberInc(initial_k);
Callable stub(
Builtins::CallableFor(isolate(), Builtins::kArrayMapLoopContinuation));
Return(CallStub(stub, context, receiver, callbackfn, this_arg, array,
receiver, initial_k, len, UndefinedConstant()));
}
TF_BUILTIN(ArrayMap, ArrayBuiltinCodeStubAssembler) {
Node* argc =
ChangeInt32ToIntPtr(Parameter(BuiltinDescriptor::kArgumentsCount));
......
......@@ -318,6 +318,10 @@ namespace internal {
/* ES6 #sec-array.prototype.foreach */ \
TFS(ArrayMapLoopContinuation, kReceiver, kCallbackFn, kThisArg, kArray, \
kObject, kInitialK, kLength, kTo) \
TFJ(ArrayMapLoopEagerDeoptContinuation, 5, kCallbackFn, kThisArg, kArray, \
kInitialK, kLength) \
TFJ(ArrayMapLoopLazyDeoptContinuation, 6, kCallbackFn, kThisArg, kArray, \
kInitialK, kLength, kResult) \
TFJ(ArrayMap, SharedFunctionInfo::kDontAdaptArgumentsSentinel) \
/* ES6 #sec-array.prototype.reduce */ \
TFS(ArrayReduceLoopContinuation, kReceiver, kCallbackFn, kThisArg, \
......
......@@ -169,6 +169,16 @@ Callable Builtins::CallableFor(Isolate* isolate, Name name) {
isolate->builtins()->ArrayForEachLoopLazyDeoptContinuation();
return Callable(code, BuiltinDescriptor(isolate));
}
case kArrayMapLoopEagerDeoptContinuation: {
Handle<Code> code =
isolate->builtins()->ArrayMapLoopEagerDeoptContinuation();
return Callable(code, BuiltinDescriptor(isolate));
}
case kArrayMapLoopLazyDeoptContinuation: {
Handle<Code> code =
isolate->builtins()->ArrayMapLoopLazyDeoptContinuation();
return Callable(code, BuiltinDescriptor(isolate));
}
default:
UNREACHABLE();
}
......
......@@ -501,6 +501,14 @@ FieldAccess AccessBuilder::ForMapBitField() {
return access;
}
// static
FieldAccess AccessBuilder::ForMapBitField2() {
FieldAccess access = {
kTaggedBase, Map::kBitField2Offset, Handle<Name>(),
MaybeHandle<Map>(), TypeCache::Get().kUint8, MachineType::Uint8(),
kNoWriteBarrier};
return access;
}
// static
FieldAccess AccessBuilder::ForMapBitField3() {
......
......@@ -167,6 +167,9 @@ class V8_EXPORT_PRIVATE AccessBuilder final
// Provides access to Map::bit_field() byte.
static FieldAccess ForMapBitField();
// Provides access to Map::bit_field2() byte.
static FieldAccess ForMapBitField2();
// Provides access to Map::bit_field3() field.
static FieldAccess ForMapBitField3();
......
......@@ -828,6 +828,8 @@ bool EffectControlLinearizer::TryWireInStateEffect(Node* node,
break;
case IrOpcode::kLoadHashMapValue:
result = LowerLoadHashMapValue(node);
case IrOpcode::kTransitionAndStoreElement:
LowerTransitionAndStoreElement(node);
break;
case IrOpcode::kFloat64RoundUp:
if (!LowerFloat64RoundUp(node).To(&result)) {
......@@ -2814,6 +2816,164 @@ void EffectControlLinearizer::LowerStoreTypedElement(Node* node) {
storage, index, value);
}
void EffectControlLinearizer::TransitionElementsTo(Node* node, Node* array,
ElementsKind from,
ElementsKind to) {
DCHECK(IsMoreGeneralElementsKindTransition(from, to));
DCHECK(to == HOLEY_ELEMENTS || to == HOLEY_DOUBLE_ELEMENTS);
Handle<Map> target(to == HOLEY_ELEMENTS ? FastMapParameterOf(node->op())
: DoubleMapParameterOf(node->op()));
Node* target_map = __ HeapConstant(target);
if (IsSimpleMapChangeTransition(from, to)) {
__ StoreField(AccessBuilder::ForMap(), array, target_map);
} else {
// Instance migration, call out to the runtime for {array}.
Operator::Properties properties = Operator::kNoDeopt | Operator::kNoThrow;
Runtime::FunctionId id = Runtime::kTransitionElementsKind;
CallDescriptor const* desc = Linkage::GetRuntimeCallDescriptor(
graph()->zone(), id, 2, properties, CallDescriptor::kNoFlags);
__ Call(desc, __ CEntryStubConstant(1), array, target_map,
__ ExternalConstant(ExternalReference(id, isolate())),
__ Int32Constant(2), __ NoContextConstant());
}
}
Node* EffectControlLinearizer::IsElementsKindGreaterThan(
Node* kind, ElementsKind reference_kind) {
Node* ref_kind = __ Int32Constant(reference_kind);
Node* ret = __ Int32LessThanOrEqual(ref_kind, kind);
return ret;
}
void EffectControlLinearizer::LowerTransitionAndStoreElement(Node* node) {
Node* array = node->InputAt(0);
Node* index = node->InputAt(1);
Node* value = node->InputAt(2);
// Possibly transition array based on input and store.
//
// -- TRANSITION PHASE -----------------
// kind = ElementsKind(array)
// if value is not smi {
// if kind == HOLEY_SMI_ELEMENTS {
// if value is heap number {
// Transition array to HOLEY_DOUBLE_ELEMENTS
// } else {
// Transition array to HOLEY_ELEMENTS
// }
// } else if kind == HOLEY_DOUBLE_ELEMENTS {
// if value is not heap number {
// Transition array to HOLEY_ELEMENTS
// }
// }
// }
//
// -- STORE PHASE ----------------------
// if kind == HOLEY_DOUBLE_ELEMENTS {
// if value is smi {
// float_value = convert smi to float
// Store array[index] = float_value
// } else {
// float_value = value
// Store array[index] = float_value
// }
// } else {
// // kind is HOLEY_SMI_ELEMENTS or HOLEY_ELEMENTS
// Store array[index] = value
// }
//
Node* map = __ LoadField(AccessBuilder::ForMap(), array);
Node* kind;
{
Node* bit_field2 = __ LoadField(AccessBuilder::ForMapBitField2(), map);
Node* mask = __ Int32Constant(Map::ElementsKindBits::kMask);
Node* andit = __ Word32And(bit_field2, mask);
Node* shift = __ Int32Constant(Map::ElementsKindBits::kShift);
kind = __ Word32Shr(andit, shift);
}
auto do_store = __ MakeLabel<6>();
Node* check1 = ObjectIsSmi(value);
__ GotoIf(check1, &do_store);
{
// {value} is a HeapObject.
Node* check2 = IsElementsKindGreaterThan(kind, HOLEY_SMI_ELEMENTS);
auto if_array_not_fast_smi = __ MakeLabel<1>();
__ GotoIf(check2, &if_array_not_fast_smi);
{
// Transition {array} from HOLEY_SMI_ELEMENTS to HOLEY_DOUBLE_ELEMENTS or
// to HOLEY_ELEMENTS.
Node* value_map = __ LoadField(AccessBuilder::ForMap(), value);
Node* heap_number_map = __ HeapNumberMapConstant();
Node* check3 = __ WordEqual(value_map, heap_number_map);
auto if_value_not_heap_number = __ MakeLabel<1>();
__ GotoUnless(check3, &if_value_not_heap_number);
{
// {value} is a HeapNumber.
TransitionElementsTo(node, array, HOLEY_SMI_ELEMENTS,
HOLEY_DOUBLE_ELEMENTS);
__ Goto(&do_store);
}
__ Bind(&if_value_not_heap_number);
{
TransitionElementsTo(node, array, HOLEY_SMI_ELEMENTS, HOLEY_ELEMENTS);
__ Goto(&do_store);
}
}
__ Bind(&if_array_not_fast_smi);
{
Node* check3 = IsElementsKindGreaterThan(kind, HOLEY_ELEMENTS);
__ GotoUnless(check3, &do_store);
// We have double elements kind.
Node* value_map = __ LoadField(AccessBuilder::ForMap(), value);
Node* heap_number_map = __ HeapNumberMapConstant();
Node* check4 = __ WordEqual(value_map, heap_number_map);
__ GotoIf(check4, &do_store);
// But the value is not a heap number, so we must transition.
TransitionElementsTo(node, array, HOLEY_DOUBLE_ELEMENTS, HOLEY_ELEMENTS);
__ Goto(&do_store);
}
}
__ Bind(&do_store);
Node* elements = __ LoadField(AccessBuilder::ForJSObjectElements(), array);
Node* check2 = IsElementsKindGreaterThan(kind, HOLEY_ELEMENTS);
auto if_kind_is_double = __ MakeLabel<1>();
auto done = __ MakeLabel<3>();
__ GotoIf(check2, &if_kind_is_double);
{
// Our ElementsKind is HOLEY_SMI_ELEMENTS or HOLEY_ELEMENTS.
__ StoreElement(AccessBuilder::ForFixedArrayElement(HOLEY_ELEMENTS),
elements, index, value);
__ Goto(&done);
}
__ Bind(&if_kind_is_double);
{
// Our ElementsKind is HOLEY_DOUBLE_ELEMENTS.
Node* check1 = ObjectIsSmi(value);
auto do_double_store = __ MakeLabel<1>();
__ GotoUnless(check1, &do_double_store);
{
Node* int_value = ChangeSmiToInt32(value);
Node* float_value = __ ChangeInt32ToFloat64(int_value);
__ StoreElement(AccessBuilder::ForFixedDoubleArrayElement(), elements,
index, float_value);
__ Goto(&done);
}
__ Bind(&do_double_store);
{
Node* float_value =
__ LoadField(AccessBuilder::ForHeapNumberValue(), value);
__ StoreElement(AccessBuilder::ForFixedDoubleArrayElement(), elements,
index, float_value);
__ Goto(&done);
}
}
__ Bind(&done);
}
Maybe<Node*> EffectControlLinearizer::LowerFloat64RoundUp(Node* node) {
// Nothing to be done if a fast hardware instruction is available.
if (machine()->Float64RoundUp().IsSupported()) {
......
......@@ -121,6 +121,7 @@ class V8_EXPORT_PRIVATE EffectControlLinearizer {
void LowerStoreTypedElement(Node* node);
Node* LowerLookupHashStorageIndex(Node* node);
Node* LowerLoadHashMapValue(Node* node);
void LowerTransitionAndStoreElement(Node* node);
// Lowering of optional operators.
Maybe<Node*> LowerFloat64RoundUp(Node* node);
......@@ -136,6 +137,7 @@ class V8_EXPORT_PRIVATE EffectControlLinearizer {
Node* frame_state);
Node* BuildFloat64RoundDown(Node* value);
Node* LowerStringComparison(Callable const& callable, Node* node);
Node* IsElementsKindGreaterThan(Node* kind, ElementsKind reference_kind);
Node* ChangeInt32ToSmi(Node* value);
Node* ChangeUint32ToSmi(Node* value);
......@@ -144,6 +146,8 @@ class V8_EXPORT_PRIVATE EffectControlLinearizer {
Node* SmiMaxValueConstant();
Node* SmiShiftBitsConstant();
void TransitionElementsTo(Node* node, Node* array, ElementsKind from,
ElementsKind to);
Factory* factory() const;
Isolate* isolate() const;
......
......@@ -721,6 +721,176 @@ Reduction JSCallReducer::ReduceArrayForEach(Handle<JSFunction> function,
return Replace(jsgraph()->UndefinedConstant());
}
Reduction JSCallReducer::ReduceArrayMap(Handle<JSFunction> function,
Node* node) {
if (!FLAG_turbo_inline_array_builtins) return NoChange();
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
Node* outer_frame_state = NodeProperties::GetFrameStateInput(node);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
Node* context = NodeProperties::GetContextInput(node);
CallParameters const& p = CallParametersOf(node->op());
// Try to determine the {receiver} map.
Node* receiver = NodeProperties::GetValueInput(node, 1);
Node* fncallback = node->op()->ValueInputCount() > 2
? NodeProperties::GetValueInput(node, 2)
: jsgraph()->UndefinedConstant();
Node* this_arg = node->op()->ValueInputCount() > 3
? NodeProperties::GetValueInput(node, 3)
: jsgraph()->UndefinedConstant();
ZoneHandleSet<Map> receiver_maps;
NodeProperties::InferReceiverMapsResult result =
NodeProperties::InferReceiverMaps(receiver, effect, &receiver_maps);
if (result != NodeProperties::kReliableReceiverMaps) {
return NoChange();
}
if (receiver_maps.size() != 1) return NoChange();
Handle<Map> receiver_map(receiver_maps[0]);
ElementsKind kind = receiver_map->elements_kind();
// TODO(danno): Handle holey Smi and Object fast elements kinds and double
// packed.
if (!IsFastPackedElementsKind(kind) || IsDoubleElementsKind(kind)) {
return NoChange();
}
// TODO(danno): map can throw. Hook up exceptional edges.
if (NodeProperties::IsExceptionalCall(node)) return NoChange();
// We want the input to be a generic Array.
const int map_index = Context::ArrayMapIndex(kind);
Handle<JSFunction> handle_constructor(
JSFunction::cast(
Map::cast(native_context()->get(map_index))->GetConstructor()),
isolate());
Node* array_constructor = jsgraph()->HeapConstant(handle_constructor);
if (receiver_map->prototype() !=
native_context()->get(Context::INITIAL_ARRAY_PROTOTYPE_INDEX)) {
return NoChange();
}
// And ensure that any changes to the Array species constructor cause deopt.
if (!isolate()->IsArraySpeciesLookupChainIntact()) return NoChange();
dependencies()->AssumePropertyCell(factory()->species_protector());
Node* k = jsgraph()->ZeroConstant();
Node* orig_map = jsgraph()->HeapConstant(receiver_map);
// Make sure the map hasn't changed before we construct the output array.
{
Node* array_map = effect =
graph()->NewNode(simplified()->LoadField(AccessBuilder::ForMap()),
receiver, effect, control);
Node* check_map =
graph()->NewNode(simplified()->ReferenceEqual(), array_map, orig_map);
effect =
graph()->NewNode(simplified()->CheckIf(), check_map, effect, control);
}
Node* original_length = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSArrayLength(PACKED_ELEMENTS)),
receiver, effect, control);
// This array should be HOLEY_SMI_ELEMENTS because of the non-zero length.
Node* a = control = effect = graph()->NewNode(
javascript()->CreateArray(1, Handle<AllocationSite>::null()),
array_constructor, array_constructor, original_length, context,
outer_frame_state, effect, control);
Node* loop = control = graph()->NewNode(common()->Loop(2), control, control);
Node* eloop = effect =
graph()->NewNode(common()->EffectPhi(2), effect, effect, loop);
Node* vloop = k = graph()->NewNode(
common()->Phi(MachineRepresentation::kTagged, 2), k, k, loop);
control = loop;
effect = eloop;
Node* continue_test =
graph()->NewNode(simplified()->NumberLessThan(), k, original_length);
Node* continue_branch = graph()->NewNode(common()->Branch(BranchHint::kTrue),
continue_test, control);
Node* if_true = graph()->NewNode(common()->IfTrue(), continue_branch);
Node* if_false = graph()->NewNode(common()->IfFalse(), continue_branch);
control = if_true;
std::vector<Node*> checkpoint_params(
{receiver, fncallback, this_arg, a, k, original_length});
const int stack_parameters = static_cast<int>(checkpoint_params.size());
Node* frame_state = CreateJavaScriptBuiltinContinuationFrameState(
jsgraph(), function, Builtins::kArrayMapLoopEagerDeoptContinuation,
node->InputAt(0), context, &checkpoint_params[0], stack_parameters,
outer_frame_state, ContinuationFrameStateMode::EAGER);
effect =
graph()->NewNode(common()->Checkpoint(), frame_state, effect, control);
// Make sure the map hasn't changed during the iteration
Node* array_map = effect =
graph()->NewNode(simplified()->LoadField(AccessBuilder::ForMap()),
receiver, effect, control);
Node* check_map =
graph()->NewNode(simplified()->ReferenceEqual(), array_map, orig_map);
effect =
graph()->NewNode(simplified()->CheckIf(), check_map, effect, control);
// Make sure that the access is still in bounds, since the callback could have
// changed the array's size.
Node* length = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSArrayLength(PACKED_ELEMENTS)),
receiver, effect, control);
k = effect =
graph()->NewNode(simplified()->CheckBounds(), k, length, effect, control);
// Reload the elements pointer before calling the callback, since the previous
// callback might have resized the array causing the elements buffer to be
// re-allocated.
Node* elements = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSObjectElements()), receiver,
effect, control);
Node* element = graph()->NewNode(
simplified()->LoadElement(AccessBuilder::ForFixedArrayElement()),
elements, k, effect, control);
Node* next_k =
graph()->NewNode(simplified()->NumberAdd(), k, jsgraph()->OneConstant());
// This frame state is dealt with by hand in
// ArrayMapLoopLazyDeoptContinuation.
frame_state = CreateJavaScriptBuiltinContinuationFrameState(
jsgraph(), function, Builtins::kArrayMapLoopLazyDeoptContinuation,
node->InputAt(0), context, &checkpoint_params[0], stack_parameters,
outer_frame_state, ContinuationFrameStateMode::LAZY);
Node* callback_value = control = effect = graph()->NewNode(
javascript()->Call(5, p.frequency()), fncallback, this_arg, element, k,
receiver, context, frame_state, effect, control);
Handle<Map> double_map(Map::cast(
native_context()->get(Context::ArrayMapIndex(HOLEY_DOUBLE_ELEMENTS))));
Handle<Map> fast_map(
Map::cast(native_context()->get(Context::ArrayMapIndex(HOLEY_ELEMENTS))));
effect = graph()->NewNode(
simplified()->TransitionAndStoreElement(double_map, fast_map), a, k,
callback_value, effect, control);
k = next_k;
loop->ReplaceInput(1, control);
vloop->ReplaceInput(1, k);
eloop->ReplaceInput(1, effect);
control = if_false;
effect = eloop;
ReplaceWithValue(node, a, effect, control);
return Replace(a);
}
Reduction JSCallReducer::ReduceCallApiFunction(
Node* node, Handle<FunctionTemplateInfo> function_template_info) {
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
......@@ -1077,6 +1247,8 @@ Reduction JSCallReducer::ReduceJSCall(Node* node) {
return ReduceSetPrototypeGetSize(node);
case Builtins::kArrayForEach:
return ReduceArrayForEach(function, node);
case Builtins::kArrayMap:
return ReduceArrayMap(function, node);
case Builtins::kReturnReceiver:
return ReduceReturnReceiver(node);
default:
......
......@@ -69,6 +69,7 @@ class JSCallReducer final : public AdvancedReducer {
Reduction ReduceReflectGetPrototypeOf(Node* node);
Reduction ReduceSetPrototypeGetSize(Node* node);
Reduction ReduceArrayForEach(Handle<JSFunction> function, Node* node);
Reduction ReduceArrayMap(Handle<JSFunction> function, Node* node);
Reduction ReduceCallOrConstructWithArrayLikeOrSpread(
Node* node, int arity, CallFrequency const& frequency);
Reduction ReduceJSConstruct(Node* node);
......
......@@ -74,6 +74,8 @@ class V8_EXPORT_PRIVATE JSCreateLowering final
Node* AllocateElements(Node* effect, Node* control,
ElementsKind elements_kind, int capacity,
PretenureFlag pretenure);
Node* AllocateElements(Node* effect, Node* control,
ElementsKind elements_kind, Node* capacity_and_length);
Node* AllocateElements(Node* effect, Node* control,
ElementsKind elements_kind,
std::vector<Node*> const& values,
......
......@@ -114,6 +114,8 @@ Reduction LoadElimination::Reduce(Node* node) {
return ReduceLoadElement(node);
case IrOpcode::kStoreElement:
return ReduceStoreElement(node);
case IrOpcode::kTransitionAndStoreElement:
return ReduceTransitionAndStoreElement(node);
case IrOpcode::kStoreTypedElement:
return ReduceStoreTypedElement(node);
case IrOpcode::kLookupHashStorageIndex:
......@@ -784,6 +786,30 @@ Reduction LoadElimination::ReduceTransitionElementsKind(Node* node) {
return UpdateState(node, state);
}
Reduction LoadElimination::ReduceTransitionAndStoreElement(Node* node) {
Node* const object = NodeProperties::GetValueInput(node, 0);
Handle<Map> double_map(DoubleMapParameterOf(node->op()));
Handle<Map> fast_map(FastMapParameterOf(node->op()));
Node* const effect = NodeProperties::GetEffectInput(node);
AbstractState const* state = node_states_.Get(effect);
if (state == nullptr) return NoChange();
// We need to add the double and fast maps to the set of possible maps for
// this object, because we don't know which of those we'll transition to.
// Additionally, we should kill all alias information.
ZoneHandleSet<Map> object_maps;
if (state->LookupMaps(object, &object_maps)) {
object_maps.insert(double_map, zone());
object_maps.insert(fast_map, zone());
state = state->KillMaps(object, zone());
state = state->AddMaps(object, object_maps, zone());
}
// Kill the elements as well.
state =
state->KillField(object, FieldIndexOf(JSObject::kElementsOffset), zone());
return UpdateState(node, state);
}
Reduction LoadElimination::ReduceLoadField(Node* node) {
FieldAccess const& access = FieldAccessOf(node->op());
Node* const object = NodeProperties::GetValueInput(node, 0);
......@@ -1110,6 +1136,15 @@ LoadElimination::AbstractState const* LoadElimination::ComputeLoopState(
}
break;
}
case IrOpcode::kTransitionAndStoreElement: {
Node* const object = NodeProperties::GetValueInput(current, 0);
// Invalidate what we know about the {object}s map.
state = state->KillMaps(object, zone());
// Kill the elements as well.
state = state->KillField(
object, FieldIndexOf(JSObject::kElementsOffset), zone());
break;
}
case IrOpcode::kStoreField: {
FieldAccess const& access = FieldAccessOf(current->op());
Node* const object = NodeProperties::GetValueInput(current, 0);
......
......@@ -320,6 +320,7 @@ class V8_EXPORT_PRIVATE LoadElimination final
Reduction ReduceStoreField(Node* node);
Reduction ReduceLoadElement(Node* node);
Reduction ReduceStoreElement(Node* node);
Reduction ReduceTransitionAndStoreElement(Node* node);
Reduction ReduceStoreTypedElement(Node* node);
Reduction ReduceLookupHashStorageIndex(Node* node);
Reduction ReduceEffectPhi(Node* node);
......
......@@ -346,6 +346,7 @@
V(StoreBuffer) \
V(StoreElement) \
V(StoreTypedElement) \
V(TransitionAndStoreElement) \
V(ObjectIsDetectableCallable) \
V(ObjectIsNaN) \
V(ObjectIsNonCallable) \
......
......@@ -2591,6 +2591,14 @@ class RepresentationSelector {
}
return;
}
case IrOpcode::kTransitionAndStoreElement: {
ProcessInput(node, 0, UseInfo::AnyTagged()); // array
ProcessInput(node, 1, UseInfo::TruncatingWord32()); // index
ProcessInput(node, 2, UseInfo::AnyTagged()); // value
ProcessRemainingInputs(node, 3);
SetOutput(node, MachineRepresentation::kNone);
return;
}
case IrOpcode::kLoadTypedElement: {
MachineRepresentation const rep =
MachineRepresentationFromArrayType(ExternalArrayTypeOf(node->op()));
......
......@@ -348,6 +348,55 @@ ElementsTransition const& ElementsTransitionOf(const Operator* op) {
return OpParameter<ElementsTransition>(op);
}
namespace {
// Parameters for the TransitionAndStoreElement opcode.
class TransitionAndStoreElementParameters final {
public:
TransitionAndStoreElementParameters(Handle<Map> double_map,
Handle<Map> fast_map);
Handle<Map> double_map() const { return double_map_; }
Handle<Map> fast_map() const { return fast_map_; }
private:
Handle<Map> const double_map_;
Handle<Map> const fast_map_;
};
TransitionAndStoreElementParameters::TransitionAndStoreElementParameters(
Handle<Map> double_map, Handle<Map> fast_map)
: double_map_(double_map), fast_map_(fast_map) {}
bool operator==(TransitionAndStoreElementParameters const& lhs,
TransitionAndStoreElementParameters const& rhs) {
return lhs.fast_map().address() == rhs.fast_map().address() &&
lhs.double_map().address() == rhs.double_map().address();
}
size_t hash_value(TransitionAndStoreElementParameters parameters) {
return base::hash_combine(parameters.fast_map().address(),
parameters.double_map().address());
}
std::ostream& operator<<(std::ostream& os,
TransitionAndStoreElementParameters parameters) {
return os << "fast-map" << Brief(*parameters.fast_map()) << " double-map"
<< Brief(*parameters.double_map());
}
} // namespace
Handle<Map> DoubleMapParameterOf(const Operator* op) {
DCHECK(op->opcode() == IrOpcode::kTransitionAndStoreElement);
return OpParameter<TransitionAndStoreElementParameters>(op).double_map();
}
Handle<Map> FastMapParameterOf(const Operator* op) {
DCHECK(op->opcode() == IrOpcode::kTransitionAndStoreElement);
return OpParameter<TransitionAndStoreElementParameters>(op).fast_map();
}
std::ostream& operator<<(std::ostream& os, NumberOperationHint hint) {
switch (hint) {
case NumberOperationHint::kSignedSmall:
......@@ -1053,6 +1102,15 @@ SPECULATIVE_NUMBER_BINOP_LIST(SPECULATIVE_NUMBER_BINOP)
ACCESS_OP_LIST(ACCESS)
#undef ACCESS
const Operator* SimplifiedOperatorBuilder::TransitionAndStoreElement(
Handle<Map> double_map, Handle<Map> fast_map) {
TransitionAndStoreElementParameters parameters(double_map, fast_map);
return new (zone()) Operator1<TransitionAndStoreElementParameters>(
IrOpcode::kTransitionAndStoreElement,
Operator::kNoDeopt | Operator::kNoThrow, "TransitionAndStoreElement", 3,
1, 1, 0, 1, 0, parameters);
}
} // namespace compiler
} // namespace internal
} // namespace v8
......@@ -228,6 +228,10 @@ std::ostream& operator<<(std::ostream&, ElementsTransition);
ElementsTransition const& ElementsTransitionOf(const Operator* op)
WARN_UNUSED_RESULT;
// Parameters for TransitionAndStoreElement.
Handle<Map> DoubleMapParameterOf(const Operator* op);
Handle<Map> FastMapParameterOf(const Operator* op);
// A hint for speculative number operations.
enum class NumberOperationHint : uint8_t {
kSignedSmall, // Inputs were always Smi so far, output was in Smi range.
......@@ -493,6 +497,10 @@ class V8_EXPORT_PRIVATE SimplifiedOperatorBuilder final
// store-element [base + index], value
const Operator* StoreElement(ElementAccess const&);
// store-element [base + index], value, only with fast arrays.
const Operator* TransitionAndStoreElement(Handle<Map> double_map,
Handle<Map> fast_map);
// load-typed-element buffer, [base + external + index]
const Operator* LoadTypedElement(ExternalArrayType const&);
......
......@@ -1963,6 +1963,10 @@ Type* Typer::Visitor::TypeStoreElement(Node* node) {
UNREACHABLE();
}
Type* Typer::Visitor::TypeTransitionAndStoreElement(Node* node) {
UNREACHABLE();
}
Type* Typer::Visitor::TypeStoreTypedElement(Node* node) {
UNREACHABLE();
}
......
......@@ -1286,6 +1286,9 @@ void Verifier::Visitor::Check(Node* node) {
// CheckValueInputIs(node, 1, ElementAccessOf(node->op()).type));
CheckNotTyped(node);
break;
case IrOpcode::kTransitionAndStoreElement:
CheckNotTyped(node);
break;
case IrOpcode::kStoreTypedElement:
CheckNotTyped(node);
break;
......
// Copyright 2017 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --allow-natives-syntax --expose-gc --turbo-inline-array-builtins
// Flags: --opt --no-always-opt
var a = [0, 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,0,0];
var b = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25];
var c = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25];
// Unknown field access leads to soft-deopt unrelated to map, should still
// lead to correct result.
(function() {
var result = 0;
var eagerDeoptInCalled = function(deopt) {
var callback = function(v,i,o) {
result += v;
if (i == 13 && deopt) {
a.abc = 25;
}
return v;
}
a.map(callback);
}
eagerDeoptInCalled();
eagerDeoptInCalled();
%OptimizeFunctionOnNextCall(eagerDeoptInCalled);
eagerDeoptInCalled();
eagerDeoptInCalled(true);
eagerDeoptInCalled();
assertEquals(1500, result);
})();
// Length change detected during loop, must cause properly handled eager deopt.
(function() {
var result = 0;
var eagerDeoptInCalled = function(deopt) {
var callback = function(v,i,o) {
result += v;
a.length = (i == 13 && deopt) ? 25 : 27;
return v;
}
a.map(callback);
}
eagerDeoptInCalled();
eagerDeoptInCalled();
%OptimizeFunctionOnNextCall(eagerDeoptInCalled);
eagerDeoptInCalled();
eagerDeoptInCalled(true);
eagerDeoptInCalled();
assertEquals(1500, result);
})();
// Escape analyzed array
(function() {
var result = 0;
var eagerDeoptInCalled = function(deopt) {
var a_noescape = [0,1,2,3,4,5];
var callback = function(v,i,o) {
result += v;
if (i == 13 && deopt) {
a_noescape.length = 25;
}
return v;
}
a_noescape.map(callback);
}
eagerDeoptInCalled();
eagerDeoptInCalled();
%OptimizeFunctionOnNextCall(eagerDeoptInCalled);
eagerDeoptInCalled();
eagerDeoptInCalled(true);
eagerDeoptInCalled();
assertEquals(75, result);
})();
// Escape analyzed array where callback function isn't inlined, forcing a lazy
// deopt with GC that relies on the stashed-away return result fro the lazy
// deopt being properly stored in a place on the stack that gets GC'ed.
(function() {
var result = 0;
var lazyDeopt = function(deopt) {
var b = [1,2,3];
var callback = function(v,i,o) {
result += i;
if (i == 1 && deopt) {
%DeoptimizeFunction(lazyDeopt);
}
gc(); gc();
return v;
};
%NeverOptimizeFunction(callback);
b.map(callback);
}
lazyDeopt();
lazyDeopt();
%OptimizeFunctionOnNextCall(lazyDeopt);
lazyDeopt();
lazyDeopt(true);
lazyDeopt();
})();
// Lazy deopt from runtime call from inlined callback function.
(function() {
var result = 0;
var lazyDeopt = function(deopt) {
var callback = function(v,i,o) {
result += i;
if (i == 13 && deopt) {
%DeoptimizeNow();
}
return v;
}
b.map(callback);
}
lazyDeopt();
lazyDeopt();
%OptimizeFunctionOnNextCall(lazyDeopt);
lazyDeopt();
lazyDeopt(true);
lazyDeopt();
assertEquals(1500, result);
})();
// Lazy deopt from runtime call from non-inline callback function.
(function() {
var result = 0;
var lazyDeopt = function(deopt) {
var callback = function(v,i,o) {
result += i;
if (i == 13 && deopt) {
%DeoptimizeNow();
}
return v;
};
%NeverOptimizeFunction(callback);
b.map(callback);
}
lazyDeopt();
lazyDeopt();
%OptimizeFunctionOnNextCall(lazyDeopt);
lazyDeopt();
lazyDeopt(true);
lazyDeopt();
assertEquals(1500, result);
})();
(function() {
var result = 0;
var lazyDeopt = function(deopt) {
var callback = function(v,i,o) {
result += i;
if (i == 13 && deopt) {
%DeoptimizeNow();
gc();
gc();
gc();
}
return v;
}
c.map(callback);
}
lazyDeopt();
lazyDeopt();
%OptimizeFunctionOnNextCall(lazyDeopt);
lazyDeopt();
lazyDeopt(true);
lazyDeopt();
assertEquals(1500, result);
})();
// Call to a.map is done inside a try-catch block and the callback function
// being called actually throws.
(function() {
var caught = false;
var result = 0;
var lazyDeopt = function(deopt) {
var callback = function(v,i,o) {
result += i;
if (i == 1 && deopt) {
throw("a");
}
return v;
}
try {
c.map(callback);
} catch (e) {
caught = true;
}
}
lazyDeopt();
lazyDeopt();
%OptimizeFunctionOnNextCall(lazyDeopt);
lazyDeopt();
assertDoesNotThrow(lazyDeopt.bind(this, true));
assertTrue(caught);
lazyDeopt();
})();
// Call to a.map is done inside a try-catch block and the callback function
// being called actually throws, but the callback is not inlined.
(function() {
var caught = false;
var result = 0;
var lazyDeopt = function(deopt) {
var callback = function(v,i,o) {
result += i;
if (i == 1 && deopt) {
throw("a");
}
return v;
};
%NeverOptimizeFunction(callback);
try {
c.map(callback);
} catch (e) {
caught = true;
}
}
lazyDeopt();
lazyDeopt();
%OptimizeFunctionOnNextCall(lazyDeopt);
lazyDeopt();
assertDoesNotThrow(lazyDeopt.bind(this, true));
assertTrue(caught);
lazyDeopt();
})();
(function() {
var re = /Array\.map/;
var lazyDeopt = function(deopt) {
var b = [1,2,3];
var result = 0;
var callback = function(v,i,o) {
result += v;
if (i == 1) {
var e = new Error();
assertTrue(re.exec(e.stack) !== null);
}
return v;
};
var o = [1,2,3];
b.map(callback);
}
lazyDeopt();
lazyDeopt();
%OptimizeFunctionOnNextCall(lazyDeopt);
lazyDeopt();
})();
(function() {
var re = /Array\.map/;
var lazyDeopt = function(deopt) {
var b = [1,2,3];
var result = 0;
var callback = function(v,i,o) {
result += v;
if (i == 1) {
var e = new Error();
assertTrue(re.exec(e.stack) !== null);
}
return v;
};
%NeverOptimizeFunction(callback);
var o = [1,2,3];
b.map(callback);
}
lazyDeopt();
lazyDeopt();
%OptimizeFunctionOnNextCall(lazyDeopt);
lazyDeopt();
})();
(function() {
var re = /Array\.map/;
var lazyDeopt = function(deopt) {
var b = [1,2,3];
var result = 0;
var callback = function(v,i,o) {
result += v;
if (i == 1) {
%DeoptimizeNow();
} else if (i == 2) {
var e = new Error();
assertTrue(re.exec(e.stack) !== null);
}
return v;
};
var o = [1,2,3];
b.map(callback);
}
lazyDeopt();
lazyDeopt();
%OptimizeFunctionOnNextCall(lazyDeopt);
lazyDeopt();
})();
(function() {
var re = /Array\.map/;
var a = [1,2,3];
var result = 0;
var lazyDeopt = function() {
var callback = function(v,i,o) {
result += i;
if (i == 1) {
%DeoptimizeFunction(lazyDeopt);
throw new Error();
}
return v;
};
a.map(callback);
}
assertThrows(() => lazyDeopt());
assertThrows(() => lazyDeopt());
try {
lazyDeopt();
} catch (e) {
assertTrue(re.exec(e.stack) !== null);
}
%OptimizeFunctionOnNextCall(lazyDeopt);
try {
lazyDeopt();
} catch (e) {
assertTrue(re.exec(e.stack) !== null);
}
})();
// Verify that we remain in optimized code despite transitions in the output
// array.
(function() {
var result = 0;
var to_double = function() {
var callback = function(v,i,o) {
result += v;
if (i < 5) {
// First transition the output array to PACKED_DOUBLE_ELEMENTS.
return v + 0.5;
} else if (i < 10) {
// Then return smi values and make sure they can live in the double
// array.
return v;
} else {
// Later, to PACKED_ELEMENTS.
return v + 'hello';
}
}
return c.map(callback);
}
to_double();
to_double();
%OptimizeFunctionOnNextCall(to_double);
var output = to_double();
assertEquals(975, result);
assertEquals("11hello", output[10]);
assertOptimized(to_double);
})();
// Messing with the Array species constructor causes deoptimization.
(function() {
var result = 0;
var a = [1,2,3];
var species_breakage = function() {
var callback = function(v,i,o) {
result += v;
return v;
}
a.map(callback);
}
species_breakage();
species_breakage();
%OptimizeFunctionOnNextCall(species_breakage);
species_breakage();
a.constructor = {};
a.constructor[Symbol.species] = function() {};
species_breakage();
assertUnoptimized(species_breakage);
assertEquals(24, result);
})();
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment