// Copyright 2016 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.
#include "src/ic/keyed-store-generic.h"
#include "src/compiler/code-assembler.h"
#include "src/contexts.h"
#include "src/isolate.h"
namespace v8 {
namespace internal {
using compiler::Node;
class KeyedStoreGenericAssembler : public CodeStubAssembler {
public:
void KeyedStoreGeneric(const StoreICParameters* p,
LanguageMode language_mode);
private:
enum UpdateLength {
kDontChangeLength,
kIncrementLengthByOne,
kBumpLengthWithGap
};
void EmitGenericElementStore(Node* receiver, Node* receiver_map,
Node* instance_type, Node* intptr_index,
Node* value, Node* context, Label* slow);
void EmitGenericPropertyStore(Node* receiver, Node* receiver_map,
const StoreICParameters* p, Label* slow);
void BranchIfPrototypesHaveNonFastElements(Node* receiver_map,
Label* non_fast_elements,
Label* only_fast_elements);
void TryRewriteElements(Node* receiver, Node* receiver_map, Node* elements,
Node* native_context, ElementsKind from_kind,
ElementsKind to_kind, Label* bailout);
void StoreElementWithCapacity(Node* receiver, Node* receiver_map,
Node* elements, Node* elements_kind,
Node* intptr_index, Node* value, Node* context,
Label* slow, UpdateLength update_length);
void MaybeUpdateLengthAndReturn(Node* receiver, Node* index, Node* value,
UpdateLength update_length);
void TryChangeToHoleyMapHelper(Node* receiver, Node* receiver_map,
Node* native_context, ElementsKind packed_kind,
ElementsKind holey_kind, Label* done,
Label* map_mismatch, Label* bailout);
void TryChangeToHoleyMap(Node* receiver, Node* receiver_map,
Node* current_elements_kind, Node* context,
ElementsKind packed_kind, Label* bailout);
void TryChangeToHoleyMapMulti(Node* receiver, Node* receiver_map,
Node* current_elements_kind, Node* context,
ElementsKind packed_kind,
ElementsKind packed_kind_2, Label* bailout);
// Do not add fields, so that this is safe to reinterpret_cast to CSA.
};
void KeyedStoreGenericGenerator::Generate(
CodeStubAssembler* assembler, const CodeStubAssembler::StoreICParameters* p,
LanguageMode language_mode) {
STATIC_ASSERT(sizeof(CodeStubAssembler) ==
sizeof(KeyedStoreGenericAssembler));
auto assm = reinterpret_cast<KeyedStoreGenericAssembler*>(assembler);
assm->KeyedStoreGeneric(p, language_mode);
}
void KeyedStoreGenericAssembler::BranchIfPrototypesHaveNonFastElements(
Node* receiver_map, Label* non_fast_elements, Label* only_fast_elements) {
Variable var_map(this, MachineRepresentation::kTagged);
var_map.Bind(receiver_map);
Label loop_body(this, &var_map);
Goto(&loop_body);
Bind(&loop_body);
{
Node* map = var_map.value();
Node* prototype = LoadMapPrototype(map);
GotoIf(WordEqual(prototype, NullConstant()), only_fast_elements);
Node* prototype_map = LoadMap(prototype);
var_map.Bind(prototype_map);
Node* instance_type = LoadMapInstanceType(prototype_map);
STATIC_ASSERT(JS_PROXY_TYPE < JS_OBJECT_TYPE);
STATIC_ASSERT(JS_VALUE_TYPE < JS_OBJECT_TYPE);
GotoIf(Int32LessThanOrEqual(instance_type,
Int32Constant(LAST_CUSTOM_ELEMENTS_RECEIVER)),
non_fast_elements);
Node* elements_kind = LoadMapElementsKind(prototype_map);
STATIC_ASSERT(FIRST_ELEMENTS_KIND == FIRST_FAST_ELEMENTS_KIND);
GotoIf(Int32LessThanOrEqual(elements_kind,
Int32Constant(LAST_FAST_ELEMENTS_KIND)),
&loop_body);
GotoIf(Word32Equal(elements_kind, Int32Constant(NO_ELEMENTS)), &loop_body);
Goto(non_fast_elements);
}
}
void KeyedStoreGenericAssembler::TryRewriteElements(
Node* receiver, Node* receiver_map, Node* elements, Node* native_context,
ElementsKind from_kind, ElementsKind to_kind, Label* bailout) {
DCHECK(IsFastPackedElementsKind(from_kind));
ElementsKind holey_from_kind = GetHoleyElementsKind(from_kind);
ElementsKind holey_to_kind = GetHoleyElementsKind(to_kind);
if (AllocationSite::GetMode(from_kind, to_kind) == TRACK_ALLOCATION_SITE) {
TrapAllocationMemento(receiver, bailout);
}
Label perform_transition(this), check_holey_map(this);
Variable var_target_map(this, MachineType::PointerRepresentation());
// Check if the receiver has the default |from_kind| map.
{
Node* packed_map =
LoadContextElement(native_context, Context::ArrayMapIndex(from_kind));
GotoIf(WordNotEqual(receiver_map, packed_map), &check_holey_map);
var_target_map.Bind(
LoadContextElement(native_context, Context::ArrayMapIndex(to_kind)));
Goto(&perform_transition);
}
// Check if the receiver has the default |holey_from_kind| map.
Bind(&check_holey_map);
{
Node* holey_map = LoadContextElement(
native_context, Context::ArrayMapIndex(holey_from_kind));
GotoIf(WordNotEqual(receiver_map, holey_map), bailout);
var_target_map.Bind(LoadContextElement(
native_context, Context::ArrayMapIndex(holey_to_kind)));
Goto(&perform_transition);
}
// Found a supported transition target map, perform the transition!
Bind(&perform_transition);
{
if (IsFastDoubleElementsKind(from_kind) !=
IsFastDoubleElementsKind(to_kind)) {
Node* capacity = SmiUntag(LoadFixedArrayBaseLength(elements));
GrowElementsCapacity(receiver, elements, from_kind, to_kind, capacity,
capacity, INTPTR_PARAMETERS, bailout);
}
StoreObjectField(receiver, JSObject::kMapOffset, var_target_map.value());
}
}
void KeyedStoreGenericAssembler::TryChangeToHoleyMapHelper(
Node* receiver, Node* receiver_map, Node* native_context,
ElementsKind packed_kind, ElementsKind holey_kind, Label* done,
Label* map_mismatch, Label* bailout) {
Node* packed_map =
LoadContextElement(native_context, Context::ArrayMapIndex(packed_kind));
GotoIf(WordNotEqual(receiver_map, packed_map), map_mismatch);
if (AllocationSite::GetMode(packed_kind, holey_kind) ==
TRACK_ALLOCATION_SITE) {
TrapAllocationMemento(receiver, bailout);
}
Node* holey_map =
LoadContextElement(native_context, Context::ArrayMapIndex(holey_kind));
StoreObjectField(receiver, JSObject::kMapOffset, holey_map);
Goto(done);
}
void KeyedStoreGenericAssembler::TryChangeToHoleyMap(
Node* receiver, Node* receiver_map, Node* current_elements_kind,
Node* context, ElementsKind packed_kind, Label* bailout) {
ElementsKind holey_kind = GetHoleyElementsKind(packed_kind);
Label already_holey(this);
GotoIf(Word32Equal(current_elements_kind, Int32Constant(holey_kind)),
&already_holey);
Node* native_context = LoadNativeContext(context);
TryChangeToHoleyMapHelper(receiver, receiver_map, native_context, packed_kind,
holey_kind, &already_holey, bailout, bailout);
Bind(&already_holey);
}
void KeyedStoreGenericAssembler::TryChangeToHoleyMapMulti(
Node* receiver, Node* receiver_map, Node* current_elements_kind,
Node* context, ElementsKind packed_kind, ElementsKind packed_kind_2,
Label* bailout) {
ElementsKind holey_kind = GetHoleyElementsKind(packed_kind);
ElementsKind holey_kind_2 = GetHoleyElementsKind(packed_kind_2);
Label already_holey(this), check_other_kind(this);
GotoIf(Word32Equal(current_elements_kind, Int32Constant(holey_kind)),
&already_holey);
GotoIf(Word32Equal(current_elements_kind, Int32Constant(holey_kind_2)),
&already_holey);
Node* native_context = LoadNativeContext(context);
TryChangeToHoleyMapHelper(receiver, receiver_map, native_context, packed_kind,
holey_kind, &already_holey, &check_other_kind,
bailout);
Bind(&check_other_kind);
TryChangeToHoleyMapHelper(receiver, receiver_map, native_context,
packed_kind_2, holey_kind_2, &already_holey,
bailout, bailout);
Bind(&already_holey);
}
void KeyedStoreGenericAssembler::MaybeUpdateLengthAndReturn(
Node* receiver, Node* index, Node* value, UpdateLength update_length) {
if (update_length != kDontChangeLength) {
Node* new_length = SmiTag(IntPtrAdd(index, IntPtrConstant(1)));
StoreObjectFieldNoWriteBarrier(receiver, JSArray::kLengthOffset, new_length,
MachineRepresentation::kTagged);
}
Return(value);
}
void KeyedStoreGenericAssembler::StoreElementWithCapacity(
Node* receiver, Node* receiver_map, Node* elements, Node* elements_kind,
Node* intptr_index, Node* value, Node* context, Label* slow,
UpdateLength update_length) {
if (update_length != kDontChangeLength) {
CSA_ASSERT(this, Word32Equal(LoadMapInstanceType(receiver_map),
Int32Constant(JS_ARRAY_TYPE)));
}
STATIC_ASSERT(FixedArray::kHeaderSize == FixedDoubleArray::kHeaderSize);
const int kHeaderSize = FixedArray::kHeaderSize - kHeapObjectTag;
Label check_double_elements(this), check_cow_elements(this);
Node* elements_map = LoadMap(elements);
GotoIf(WordNotEqual(elements_map, LoadRoot(Heap::kFixedArrayMapRootIndex)),
&check_double_elements);
// FixedArray backing store -> Smi or object elements.
{
Node* offset = ElementOffsetFromIndex(intptr_index, FAST_ELEMENTS,
INTPTR_PARAMETERS, kHeaderSize);
// Check if we're about to overwrite the hole. We can safely do that
// only if there can be no setters on the prototype chain.
// If we know that we're storing beyond the previous array length, we
// can skip the hole check (and always assume the hole).
{
Label hole_check_passed(this);
if (update_length == kDontChangeLength) {
Node* element = Load(MachineType::AnyTagged(), elements, offset);
GotoIf(WordNotEqual(element, TheHoleConstant()), &hole_check_passed);
}
BranchIfPrototypesHaveNonFastElements(receiver_map, slow,
&hole_check_passed);
Bind(&hole_check_passed);
}
// Check if the value we're storing matches the elements_kind. Smis
// can always be stored.
{
Label non_smi_value(this);
GotoUnless(TaggedIsSmi(value), &non_smi_value);
// If we're about to introduce holes, ensure holey elements.
if (update_length == kBumpLengthWithGap) {
TryChangeToHoleyMapMulti(receiver, receiver_map, elements_kind, context,
FAST_SMI_ELEMENTS, FAST_ELEMENTS, slow);
}
StoreNoWriteBarrier(MachineRepresentation::kTagged, elements, offset,
value);
MaybeUpdateLengthAndReturn(receiver, intptr_index, value, update_length);
Bind(&non_smi_value);
}
// Check if we already have object elements; just do the store if so.
{
Label must_transition(this);
STATIC_ASSERT(FAST_SMI_ELEMENTS == 0);
STATIC_ASSERT(FAST_HOLEY_SMI_ELEMENTS == 1);
GotoIf(Int32LessThanOrEqual(elements_kind,
Int32Constant(FAST_HOLEY_SMI_ELEMENTS)),
&must_transition);
if (update_length == kBumpLengthWithGap) {
TryChangeToHoleyMap(receiver, receiver_map, elements_kind, context,
FAST_ELEMENTS, slow);
}
Store(MachineRepresentation::kTagged, elements, offset, value);
MaybeUpdateLengthAndReturn(receiver, intptr_index, value, update_length);
Bind(&must_transition);
}
// Transition to the required ElementsKind.
{
Label transition_to_double(this), transition_to_object(this);
Node* native_context = LoadNativeContext(context);
Branch(WordEqual(LoadMap(value), LoadRoot(Heap::kHeapNumberMapRootIndex)),
&transition_to_double, &transition_to_object);
Bind(&transition_to_double);
{
// If we're adding holes at the end, always transition to a holey
// elements kind, otherwise try to remain packed.
ElementsKind target_kind = update_length == kBumpLengthWithGap
? FAST_HOLEY_DOUBLE_ELEMENTS
: FAST_DOUBLE_ELEMENTS;
TryRewriteElements(receiver, receiver_map, elements, native_context,
FAST_SMI_ELEMENTS, target_kind, slow);
// Reload migrated elements.
Node* double_elements = LoadElements(receiver);
Node* double_offset = ElementOffsetFromIndex(
intptr_index, FAST_DOUBLE_ELEMENTS, INTPTR_PARAMETERS, kHeaderSize);
// Make sure we do not store signalling NaNs into double arrays.
Node* double_value = Float64SilenceNaN(LoadHeapNumberValue(value));
StoreNoWriteBarrier(MachineRepresentation::kFloat64, double_elements,
double_offset, double_value);
MaybeUpdateLengthAndReturn(receiver, intptr_index, value,
update_length);
}
Bind(&transition_to_object);
{
// If we're adding holes at the end, always transition to a holey
// elements kind, otherwise try to remain packed.
ElementsKind target_kind = update_length == kBumpLengthWithGap
? FAST_HOLEY_ELEMENTS
: FAST_ELEMENTS;
TryRewriteElements(receiver, receiver_map, elements, native_context,
FAST_SMI_ELEMENTS, target_kind, slow);
// The elements backing store didn't change, no reload necessary.
CSA_ASSERT(this, WordEqual(elements, LoadElements(receiver)));
Store(MachineRepresentation::kTagged, elements, offset, value);
MaybeUpdateLengthAndReturn(receiver, intptr_index, value,
update_length);
}
}
}
Bind(&check_double_elements);
Node* fixed_double_array_map = LoadRoot(Heap::kFixedDoubleArrayMapRootIndex);
GotoIf(WordNotEqual(elements_map, fixed_double_array_map),
&check_cow_elements);
// FixedDoubleArray backing store -> double elements.
{
Node* offset = ElementOffsetFromIndex(intptr_index, FAST_DOUBLE_ELEMENTS,
INTPTR_PARAMETERS, kHeaderSize);
// Check if we're about to overwrite the hole. We can safely do that
// only if there can be no setters on the prototype chain.
{
Label hole_check_passed(this);
// If we know that we're storing beyond the previous array length, we
// can skip the hole check (and always assume the hole).
if (update_length == kDontChangeLength) {
Label found_hole(this);
LoadDoubleWithHoleCheck(elements, offset, &found_hole,
MachineType::None());
Goto(&hole_check_passed);
Bind(&found_hole);
}
BranchIfPrototypesHaveNonFastElements(receiver_map, slow,
&hole_check_passed);
Bind(&hole_check_passed);
}
// Try to store the value as a double.
{
Label non_number_value(this);
Node* double_value = PrepareValueForWrite(value, Representation::Double(),
&non_number_value);
// Make sure we do not store signalling NaNs into double arrays.
double_value = Float64SilenceNaN(double_value);
// If we're about to introduce holes, ensure holey elements.
if (update_length == kBumpLengthWithGap) {
TryChangeToHoleyMap(receiver, receiver_map, elements_kind, context,
FAST_DOUBLE_ELEMENTS, slow);
}
StoreNoWriteBarrier(MachineRepresentation::kFloat64, elements, offset,
double_value);
MaybeUpdateLengthAndReturn(receiver, intptr_index, value, update_length);
Bind(&non_number_value);
}
// Transition to object elements.
{
Node* native_context = LoadNativeContext(context);
ElementsKind target_kind = update_length == kBumpLengthWithGap
? FAST_HOLEY_ELEMENTS
: FAST_ELEMENTS;
TryRewriteElements(receiver, receiver_map, elements, native_context,
FAST_DOUBLE_ELEMENTS, target_kind, slow);
// Reload migrated elements.
Node* fast_elements = LoadElements(receiver);
Node* fast_offset = ElementOffsetFromIndex(
intptr_index, FAST_ELEMENTS, INTPTR_PARAMETERS, kHeaderSize);
Store(MachineRepresentation::kTagged, fast_elements, fast_offset, value);
MaybeUpdateLengthAndReturn(receiver, intptr_index, value, update_length);
}
}
Bind(&check_cow_elements);
{
// TODO(jkummerow): Use GrowElementsCapacity instead of bailing out.
Goto(slow);
}
}
void KeyedStoreGenericAssembler::EmitGenericElementStore(
Node* receiver, Node* receiver_map, Node* instance_type, Node* intptr_index,
Node* value, Node* context, Label* slow) {
Label if_in_bounds(this), if_increment_length_by_one(this),
if_bump_length_with_gap(this), if_grow(this), if_nonfast(this),
if_typed_array(this), if_dictionary(this);
Node* elements = LoadElements(receiver);
Node* elements_kind = LoadMapElementsKind(receiver_map);
GotoIf(
Int32GreaterThan(elements_kind, Int32Constant(LAST_FAST_ELEMENTS_KIND)),
&if_nonfast);
Label if_array(this);
GotoIf(Word32Equal(instance_type, Int32Constant(JS_ARRAY_TYPE)), &if_array);
{
Node* capacity = SmiUntag(LoadFixedArrayBaseLength(elements));
Branch(UintPtrLessThan(intptr_index, capacity), &if_in_bounds, &if_grow);
}
Bind(&if_array);
{
Node* length = SmiUntag(LoadJSArrayLength(receiver));
GotoIf(UintPtrLessThan(intptr_index, length), &if_in_bounds);
Node* capacity = SmiUntag(LoadFixedArrayBaseLength(elements));
GotoIf(UintPtrGreaterThanOrEqual(intptr_index, capacity), &if_grow);
Branch(WordEqual(intptr_index, length), &if_increment_length_by_one,
&if_bump_length_with_gap);
}
Bind(&if_in_bounds);
{
StoreElementWithCapacity(receiver, receiver_map, elements, elements_kind,
intptr_index, value, context, slow,
kDontChangeLength);
}
Bind(&if_increment_length_by_one);
{
StoreElementWithCapacity(receiver, receiver_map, elements, elements_kind,
intptr_index, value, context, slow,
kIncrementLengthByOne);
}
Bind(&if_bump_length_with_gap);
{
StoreElementWithCapacity(receiver, receiver_map, elements, elements_kind,
intptr_index, value, context, slow,
kBumpLengthWithGap);
}
// Out-of-capacity accesses (index >= capacity) jump here. Additionally,
// an ElementsKind transition might be necessary.
Bind(&if_grow);
{
Comment("Grow backing store");
// TODO(jkummerow): Support inline backing store growth.
Goto(slow);
}
// Any ElementsKind > LAST_FAST_ELEMENTS_KIND jumps here for further dispatch.
Bind(&if_nonfast);
{
STATIC_ASSERT(LAST_ELEMENTS_KIND == LAST_FIXED_TYPED_ARRAY_ELEMENTS_KIND);
GotoIf(Int32GreaterThanOrEqual(
elements_kind,
Int32Constant(FIRST_FIXED_TYPED_ARRAY_ELEMENTS_KIND)),
&if_typed_array);
GotoIf(Word32Equal(elements_kind, Int32Constant(DICTIONARY_ELEMENTS)),
&if_dictionary);
Goto(slow);
}
Bind(&if_dictionary);
{
Comment("Dictionary");
// TODO(jkummerow): Support storing to dictionary elements.
Goto(slow);
}
Bind(&if_typed_array);
{
Comment("Typed array");
// TODO(jkummerow): Support typed arrays.
Goto(slow);
}
}
void KeyedStoreGenericAssembler::EmitGenericPropertyStore(
Node* receiver, Node* receiver_map, const StoreICParameters* p,
Label* slow) {
Comment("stub cache probe");
// TODO(jkummerow): Don't rely on the stub cache as much.
// - existing properties can be overwritten inline (unless readonly).
// - for dictionary mode receivers, we can even add properties inline
// (unless the prototype chain prevents it).
Variable var_handler(this, MachineRepresentation::kTagged);
Label found_handler(this, &var_handler), stub_cache_miss(this);
TryProbeStubCache(isolate()->store_stub_cache(), receiver, p->name,
&found_handler, &var_handler, &stub_cache_miss);
Bind(&found_handler);
{
Comment("KeyedStoreGeneric found handler");
HandleStoreICHandlerCase(p, var_handler.value(), slow);
}
Bind(&stub_cache_miss);
{
Comment("KeyedStoreGeneric_miss");
TailCallRuntime(Runtime::kKeyedStoreIC_Miss, p->context, p->value, p->slot,
p->vector, p->receiver, p->name);
}
}
void KeyedStoreGenericAssembler::KeyedStoreGeneric(const StoreICParameters* p,
LanguageMode language_mode) {
Variable var_index(this, MachineType::PointerRepresentation());
Label if_index(this), if_unique_name(this), slow(this);
Node* receiver = p->receiver;
GotoIf(TaggedIsSmi(receiver), &slow);
Node* receiver_map = LoadMap(receiver);
Node* instance_type = LoadMapInstanceType(receiver_map);
// Receivers requiring non-standard element accesses (interceptors, access
// checks, strings and string wrappers, proxies) are handled in the runtime.
GotoIf(Int32LessThanOrEqual(instance_type,
Int32Constant(LAST_CUSTOM_ELEMENTS_RECEIVER)),
&slow);
TryToName(p->name, &if_index, &var_index, &if_unique_name, &slow);
Bind(&if_index);
{
Comment("integer index");
EmitGenericElementStore(receiver, receiver_map, instance_type,
var_index.value(), p->value, p->context, &slow);
}
Bind(&if_unique_name);
{
Comment("key is unique name");
EmitGenericPropertyStore(receiver, receiver_map, p, &slow);
}
Bind(&slow);
{
Comment("KeyedStoreGeneric_slow");
TailCallRuntime(Runtime::kSetProperty, p->context, p->receiver, p->name,
p->value, SmiConstant(language_mode));
}
}
} // namespace internal
} // namespace v8