#include "src/ic/keyed-store-generic.h"
#include <optional>
#include "src/codegen/code-factory.h"
#include "src/codegen/code-stub-assembler-inl.h"
#include "src/codegen/interface-descriptors.h"
#include "src/common/globals.h"
#include "src/execution/isolate.h"
#include "src/ic/accessor-assembler.h"
#include "src/objects/contexts.h"
#include "src/objects/feedback-vector.h"
#include "src/objects/objects-inl.h"
namespace v8 {
namespace internal {
#include "src/codegen/define-code-stub-assembler-macros.inc"
enum class StoreMode {
kSet,
kDefineKeyedOwnInLiteral,
kDefineNamedOwn,
kDefineKeyedOwn
};
enum class PrivateNameSemantics { kUpdate, kDefine };
class KeyedStoreGenericAssembler : public AccessorAssembler {
public:
explicit KeyedStoreGenericAssembler(compiler::CodeAssemblerState* state,
StoreMode mode)
: AccessorAssembler(state), mode_(mode) {}
void KeyedStoreGeneric();
void KeyedStoreMegamorphic();
void StoreIC_NoFeedback();
void StoreProperty(TNode<Context> context, TNode<JSReceiver> receiver,
TNode<BoolT> is_simple_receiver, TNode<Name> unique_name,
TNode<Object> value, LanguageMode language_mode);
void StoreProperty(TNode<Context> context, TNode<JSAny> receiver,
TNode<Object> key, TNode<Object> value,
LanguageMode language_mode);
private:
StoreMode mode_;
enum UpdateLength {
kDontChangeLength,
kIncrementLengthByOne,
kBumpLengthWithGap
};
enum UseStubCache { kUseStubCache, kDontUseStubCache };
void KeyedStoreGeneric(TNode<Context> context, TNode<JSAny> receiver,
TNode<Object> key, TNode<Object> value,
Maybe<LanguageMode> language_mode,
UseStubCache use_stub_cache = kDontUseStubCache,
TNode<TaggedIndex> slot = {},
TNode<HeapObject> maybe_vector = {});
void EmitGenericElementStore(TNode<JSObject> receiver,
TNode<Map> receiver_map,
TNode<Uint16T> instance_type,
TNode<IntPtrT> index, TNode<Object> value,
TNode<Context> context, Label* slow);
void EmitGenericPropertyStore(TNode<JSReceiver> receiver,
TNode<Map> receiver_map,
TNode<Uint16T> instance_type,
const StoreICParameters* p,
ExitPoint* exit_point, Label* slow,
Maybe<LanguageMode> maybe_language_mode,
UseStubCache use_stub_cache);
void EmitGenericPropertyStore(TNode<JSReceiver> receiver,
TNode<Map> receiver_map,
TNode<Uint16T> instance_type,
const StoreICParameters* p, Label* slow) {
ExitPoint direct_exit(this);
EmitGenericPropertyStore(receiver, receiver_map, instance_type, p,
&direct_exit, slow, Nothing<LanguageMode>(),
kDontUseStubCache);
}
void BranchIfPrototypesMayHaveReadOnlyElements(
TNode<Map> receiver_map, Label* maybe_read_only_elements,
Label* only_fast_writable_elements);
void TryRewriteElements(TNode<JSObject> receiver, TNode<Map> receiver_map,
TNode<FixedArrayBase> elements,
TNode<NativeContext> native_context,
ElementsKind from_kind, ElementsKind to_kind,
Label* bailout);
void StoreSharedArrayElement(TNode<Context> context,
TNode<FixedArrayBase> elements,
TNode<IntPtrT> index, TNode<Object> value);
void StoreElementWithCapacity(TNode<JSObject> receiver,
TNode<Map> receiver_map,
TNode<FixedArrayBase> elements,
TNode<Word32T> elements_kind,
TNode<IntPtrT> index, TNode<Object> value,
TNode<Context> context, Label* slow,
UpdateLength update_length);
void MaybeUpdateLengthAndReturn(TNode<JSObject> receiver,
TNode<IntPtrT> index, TNode<Object> value,
UpdateLength update_length);
void TryChangeToHoleyMapHelper(TNode<JSObject> receiver,
TNode<Map> receiver_map,
TNode<NativeContext> native_context,
ElementsKind packed_kind,
ElementsKind holey_kind, Label* done,
Label* map_mismatch, Label* bailout);
void TryChangeToHoleyMap(TNode<JSObject> receiver, TNode<Map> receiver_map,
TNode<Word32T> current_elements_kind,
TNode<Context> context, ElementsKind packed_kind,
Label* bailout);
void TryChangeToHoleyMapMulti(TNode<JSObject> receiver,
TNode<Map> receiver_map,
TNode<Word32T> current_elements_kind,
TNode<Context> context,
ElementsKind packed_kind,
ElementsKind packed_kind_2, Label* bailout);
void LookupPropertyOnPrototypeChain(
TNode<Map> receiver_map, TNode<Name> name, Label* accessor,
TVariable<Object>* var_accessor_pair,
TVariable<HeapObject>* var_accessor_holder, Label* readonly,
Label* bailout);
TNode<Map> FindCandidateStoreICTransitionMapHandler(TNode<Map> map,
TNode<Name> name,
Label* slow);
bool IsSet() const { return mode_ == StoreMode::kSet; }
bool IsDefineKeyedOwnInLiteral() const {
return mode_ == StoreMode::kDefineKeyedOwnInLiteral;
}
bool IsDefineNamedOwn() const { return mode_ == StoreMode::kDefineNamedOwn; }
bool IsDefineKeyedOwn() const { return mode_ == StoreMode::kDefineKeyedOwn; }
bool IsAnyDefineOwn() const {
return IsDefineNamedOwn() || IsDefineKeyedOwn();
}
bool ShouldCheckPrototype() const { return IsSet(); }
bool ShouldReconfigureExisting() const { return IsDefineKeyedOwnInLiteral(); }
bool ShouldCallSetter() const { return IsSet(); }
bool ShouldCheckPrototypeValidity() const {
DCHECK_IMPLIES(!IsSet(), IsDefineKeyedOwnInLiteral() ||
IsDefineNamedOwn() || IsDefineKeyedOwn());
return IsSet();
}
};
void KeyedStoreMegamorphicGenerator::Generate(
compiler::CodeAssemblerState* state) {
KeyedStoreGenericAssembler assembler(state, StoreMode::kSet);
assembler.KeyedStoreMegamorphic();
}
void KeyedStoreGenericGenerator::Generate(compiler::CodeAssemblerState* state) {
KeyedStoreGenericAssembler assembler(state, StoreMode::kSet);
assembler.KeyedStoreGeneric();
}
void DefineKeyedOwnGenericGenerator::Generate(
compiler::CodeAssemblerState* state) {
KeyedStoreGenericAssembler assembler(state, StoreMode::kDefineKeyedOwn);
assembler.KeyedStoreGeneric();
}
void StoreICNoFeedbackGenerator::Generate(compiler::CodeAssemblerState* state) {
KeyedStoreGenericAssembler assembler(state, StoreMode::kSet);
assembler.StoreIC_NoFeedback();
}
void DefineNamedOwnICNoFeedbackGenerator::Generate(
compiler::CodeAssemblerState* state) {
KeyedStoreGenericAssembler assembler(state, StoreMode::kDefineNamedOwn);
assembler.StoreIC_NoFeedback();
}
void KeyedStoreGenericGenerator::SetProperty(
compiler::CodeAssemblerState* state, TNode<Context> context,
TNode<JSReceiver> receiver, TNode<BoolT> is_simple_receiver,
TNode<Name> name, TNode<Object> value, LanguageMode language_mode) {
KeyedStoreGenericAssembler assembler(state, StoreMode::kSet);
assembler.StoreProperty(context, receiver, is_simple_receiver, name, value,
language_mode);
}
void KeyedStoreGenericGenerator::SetProperty(
compiler::CodeAssemblerState* state, TNode<Context> context,
TNode<JSAny> receiver, TNode<Object> key, TNode<Object> value,
LanguageMode language_mode) {
KeyedStoreGenericAssembler assembler(state, StoreMode::kSet);
assembler.StoreProperty(context, receiver, key, value, language_mode);
}
void KeyedStoreGenericGenerator::CreateDataProperty(
compiler::CodeAssemblerState* state, TNode<Context> context,
TNode<JSObject> receiver, TNode<Object> key, TNode<Object> value) {
KeyedStoreGenericAssembler assembler(state,
StoreMode::kDefineKeyedOwnInLiteral);
assembler.StoreProperty(context, receiver, key, value, LanguageMode::kStrict);
}
void KeyedStoreGenericAssembler::BranchIfPrototypesMayHaveReadOnlyElements(
TNode<Map> receiver_map, Label* maybe_read_only_elements,
Label* only_fast_writable_elements) {
TVARIABLE(Map, var_map);
var_map = receiver_map;
Label loop_body(this, &var_map);
Goto(&loop_body);
BIND(&loop_body);
{
TNode<Map> map = var_map.value();
TNode<HeapObject> prototype = LoadMapPrototype(map);
GotoIf(IsNull(prototype), only_fast_writable_elements);
TNode<Map> prototype_map = LoadMap(prototype);
var_map = prototype_map;
TNode<Uint16T> instance_type = LoadMapInstanceType(prototype_map);
GotoIf(IsCustomElementsReceiverInstanceType(instance_type),
maybe_read_only_elements);
TNode<Int32T> elements_kind = LoadMapElementsKind(prototype_map);
GotoIf(IsFastOrNonExtensibleOrSealedElementsKind(elements_kind),
&loop_body);
GotoIf(Word32Equal(elements_kind, Int32Constant(NO_ELEMENTS)), &loop_body);
Goto(maybe_read_only_elements);
}
}
void KeyedStoreGenericAssembler::TryRewriteElements(
TNode<JSObject> receiver, TNode<Map> receiver_map,
TNode<FixedArrayBase> elements, TNode<NativeContext> 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::ShouldTrack(from_kind, to_kind)) {
TrapAllocationMemento(receiver, bailout);
}
Label perform_transition(this), check_holey_map(this);
TVARIABLE(Map, var_target_map);
{
TNode<Map> packed_map = LoadJSArrayElementsMap(from_kind, native_context);
GotoIf(TaggedNotEqual(receiver_map, packed_map), &check_holey_map);
var_target_map = CAST(LoadContextElementNoCell(
native_context, Context::ArrayMapIndex(to_kind)));
Goto(&perform_transition);
}
BIND(&check_holey_map);
{
TNode<Object> holey_map = LoadContextElementNoCell(
native_context, Context::ArrayMapIndex(holey_from_kind));
GotoIf(TaggedNotEqual(receiver_map, holey_map), bailout);
var_target_map = CAST(LoadContextElementNoCell(
native_context, Context::ArrayMapIndex(holey_to_kind)));
Goto(&perform_transition);
}
BIND(&perform_transition);
{
if (IsDoubleElementsKind(from_kind) != IsDoubleElementsKind(to_kind)) {
TNode<IntPtrT> capacity = LoadAndUntagFixedArrayBaseLength(elements);
GrowElementsCapacity(receiver, elements, from_kind, to_kind, capacity,
capacity, bailout);
}
StoreMap(receiver, var_target_map.value());
}
}
void KeyedStoreGenericAssembler::TryChangeToHoleyMapHelper(
TNode<JSObject> receiver, TNode<Map> receiver_map,
TNode<NativeContext> native_context, ElementsKind packed_kind,
ElementsKind holey_kind, Label* done, Label* map_mismatch, Label* bailout) {
TNode<Map> packed_map = LoadJSArrayElementsMap(packed_kind, native_context);
GotoIf(TaggedNotEqual(receiver_map, packed_map), map_mismatch);
if (AllocationSite::ShouldTrack(packed_kind, holey_kind)) {
TrapAllocationMemento(receiver, bailout);
}
TNode<Map> holey_map = CAST(LoadContextElementNoCell(
native_context, Context::ArrayMapIndex(holey_kind)));
StoreMap(receiver, holey_map);
Goto(done);
}
void KeyedStoreGenericAssembler::TryChangeToHoleyMap(
TNode<JSObject> receiver, TNode<Map> receiver_map,
TNode<Word32T> current_elements_kind, TNode<Context> 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);
TNode<NativeContext> native_context = LoadNativeContext(context);
TryChangeToHoleyMapHelper(receiver, receiver_map, native_context, packed_kind,
holey_kind, &already_holey, bailout, bailout);
BIND(&already_holey);
}
void KeyedStoreGenericAssembler::TryChangeToHoleyMapMulti(
TNode<JSObject> receiver, TNode<Map> receiver_map,
TNode<Word32T> current_elements_kind, TNode<Context> 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);
TNode<NativeContext> 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(
TNode<JSObject> receiver, TNode<IntPtrT> index, TNode<Object> value,
UpdateLength update_length) {
if (update_length != kDontChangeLength) {
TNode<Smi> new_length = SmiTag(Signed(IntPtrAdd(index, IntPtrConstant(1))));
StoreObjectFieldNoWriteBarrier(receiver, JSArray::kLengthOffset,
new_length);
}
Return(value);
}
void KeyedStoreGenericAssembler::StoreSharedArrayElement(
TNode<Context> context, TNode<FixedArrayBase> elements,
TNode<IntPtrT> index, TNode<Object> value) {
TVARIABLE(Object, shared_value, value);
SharedValueBarrier(context, &shared_value);
UnsafeStoreFixedArrayElement(CAST(elements), index, shared_value.value());
Return(value);
}
void KeyedStoreGenericAssembler::StoreElementWithCapacity(
TNode<JSObject> receiver, TNode<Map> receiver_map,
TNode<FixedArrayBase> elements, TNode<Word32T> elements_kind,
TNode<IntPtrT> index, TNode<Object> value, TNode<Context> context,
Label* slow, UpdateLength update_length) {
if (update_length != kDontChangeLength) {
CSA_DCHECK(this, IsJSArrayMap(receiver_map));
GotoIf(IsDictionaryMap(receiver_map), slow);
TNode<DescriptorArray> descriptors = LoadMapDescriptors(receiver_map);
TNode<Uint32T> details = LoadDetailsByDescriptorEntry(descriptors, 0);
GotoIf(IsSetWord32(details, PropertyDetails::kAttributesReadOnlyMask),
slow);
}
static_assert(OFFSET_OF_DATA_START(FixedArray) ==
OFFSET_OF_DATA_START(FixedDoubleArray));
const int kHeaderSize = OFFSET_OF_DATA_START(FixedArray) - kHeapObjectTag;
Label check_double_elements(this), check_cow_elements(this);
TNode<Map> elements_map = LoadMap(elements);
GotoIf(IsNotFixedArrayMap(elements_map), &check_double_elements);
{
TNode<IntPtrT> offset =
ElementOffsetFromIndex(index, PACKED_ELEMENTS, kHeaderSize);
if (!IsDefineKeyedOwnInLiteral()) {
{
Label hole_check_passed(this);
if (update_length == kDontChangeLength) {
TNode<Object> element =
CAST(Load(MachineType::AnyTagged(), elements, offset));
GotoIf(IsNotTheHole(element), &hole_check_passed);
}
BranchIfPrototypesMayHaveReadOnlyElements(receiver_map, slow,
&hole_check_passed);
BIND(&hole_check_passed);
}
}
{
Label non_smi_value(this);
GotoIfNot(TaggedIsSmi(value), &non_smi_value);
if (update_length == kBumpLengthWithGap) {
TryChangeToHoleyMapMulti(receiver, receiver_map, elements_kind, context,
PACKED_SMI_ELEMENTS, PACKED_ELEMENTS, slow);
}
StoreNoWriteBarrier(MachineRepresentation::kTaggedSigned, elements,
offset, value);
MaybeUpdateLengthAndReturn(receiver, index, value, update_length);
BIND(&non_smi_value);
}
{
Label must_transition(this);
static_assert(PACKED_SMI_ELEMENTS == 0);
static_assert(HOLEY_SMI_ELEMENTS == 1);
GotoIf(Int32LessThanOrEqual(elements_kind,
Int32Constant(HOLEY_SMI_ELEMENTS)),
&must_transition);
if (update_length == kBumpLengthWithGap) {
TryChangeToHoleyMap(receiver, receiver_map, elements_kind, context,
PACKED_ELEMENTS, slow);
}
Store(elements, offset, value);
MaybeUpdateLengthAndReturn(receiver, index, value, update_length);
BIND(&must_transition);
}
{
Label transition_to_double(this), transition_to_object(this);
TNode<NativeContext> native_context = LoadNativeContext(context);
#ifdef V8_ENABLE_UNDEFINED_DOUBLE
GotoIf(IsHeapNumber(CAST(value)), &transition_to_double);
GotoIfNot(IsUndefined(value), &transition_to_object);
{
TryRewriteElements(receiver, receiver_map, elements, native_context,
PACKED_SMI_ELEMENTS, HOLEY_DOUBLE_ELEMENTS, slow);
TNode<FixedArrayBase> double_elements = LoadElements(receiver);
TNode<IntPtrT> double_offset =
ElementOffsetFromIndex(index, PACKED_DOUBLE_ELEMENTS, kHeaderSize);
if (Is64()) {
StoreNoWriteBarrier(MachineRepresentation::kWord64, double_elements,
double_offset,
Uint64Constant(kUndefinedNanInt64));
} else {
static_assert(kUndefinedNanLower32 == kUndefinedNanUpper32);
StoreNoWriteBarrier(MachineRepresentation::kWord32, double_elements,
double_offset,
Uint32Constant(kUndefinedNanLower32));
StoreNoWriteBarrier(
MachineRepresentation::kWord32, double_elements,
IntPtrAdd(double_offset, IntPtrConstant(kInt32Size)),
Uint32Constant(kUndefinedNanLower32));
}
MaybeUpdateLengthAndReturn(receiver, index, value, update_length);
}
#else
Branch(IsHeapNumber(CAST(value)), &transition_to_double,
&transition_to_object);
#endif
BIND(&transition_to_double);
{
ElementsKind target_kind = update_length == kBumpLengthWithGap
? HOLEY_DOUBLE_ELEMENTS
: PACKED_DOUBLE_ELEMENTS;
TryRewriteElements(receiver, receiver_map, elements, native_context,
PACKED_SMI_ELEMENTS, target_kind, slow);
TNode<FixedArrayBase> double_elements = LoadElements(receiver);
TNode<IntPtrT> double_offset =
ElementOffsetFromIndex(index, PACKED_DOUBLE_ELEMENTS, kHeaderSize);
TNode<Float64T> double_value =
Float64SilenceNaN(LoadHeapNumberValue(CAST(value)));
StoreNoWriteBarrier(MachineRepresentation::kFloat64, double_elements,
double_offset, double_value);
MaybeUpdateLengthAndReturn(receiver, index, value, update_length);
}
BIND(&transition_to_object);
{
ElementsKind target_kind = update_length == kBumpLengthWithGap
? HOLEY_ELEMENTS
: PACKED_ELEMENTS;
TryRewriteElements(receiver, receiver_map, elements, native_context,
PACKED_SMI_ELEMENTS, target_kind, slow);
CSA_DCHECK(this, TaggedEqual(elements, LoadElements(receiver)));
Store(elements, offset, value);
MaybeUpdateLengthAndReturn(receiver, index, value, update_length);
}
}
}
BIND(&check_double_elements);
GotoIf(IsNotFixedDoubleArrayMap(elements_map), &check_cow_elements);
{
TNode<IntPtrT> offset =
ElementOffsetFromIndex(index, PACKED_DOUBLE_ELEMENTS, kHeaderSize);
if (!IsDefineKeyedOwnInLiteral()) {
{
Label hole_check_passed(this);
if (update_length == kDontChangeLength) {
Label found_hole(this);
LoadDoubleWithUndefinedAndHoleCheck(elements, offset,
&hole_check_passed, &found_hole,
MachineType::None());
Goto(&hole_check_passed);
BIND(&found_hole);
}
BranchIfPrototypesMayHaveReadOnlyElements(receiver_map, slow,
&hole_check_passed);
BIND(&hole_check_passed);
}
}
{
Label non_number_value(this);
Label undefined_value(this);
TNode<Float64T> double_value = TryTaggedToFloat64(value,
#ifdef V8_ENABLE_UNDEFINED_DOUBLE
&undefined_value,
#endif
&non_number_value);
double_value = Float64SilenceNaN(double_value);
if (update_length == kBumpLengthWithGap) {
TryChangeToHoleyMap(receiver, receiver_map, elements_kind, context,
PACKED_DOUBLE_ELEMENTS, slow);
}
StoreNoWriteBarrier(MachineRepresentation::kFloat64, elements, offset,
double_value);
MaybeUpdateLengthAndReturn(receiver, index, value, update_length);
#ifdef V8_ENABLE_UNDEFINED_DOUBLE
BIND(&undefined_value);
TryChangeToHoleyMap(receiver, receiver_map, elements_kind, context,
PACKED_DOUBLE_ELEMENTS, slow);
if (Is64()) {
StoreNoWriteBarrier(MachineRepresentation::kWord64, elements, offset,
Uint64Constant(kUndefinedNanInt64));
} else {
static_assert(kUndefinedNanLower32 == kUndefinedNanUpper32);
StoreNoWriteBarrier(MachineRepresentation::kWord32, elements, offset,
Uint32Constant(kUndefinedNanLower32));
StoreNoWriteBarrier(MachineRepresentation::kWord32, elements,
IntPtrAdd(offset, IntPtrConstant(kInt32Size)),
Uint32Constant(kUndefinedNanLower32));
}
MaybeUpdateLengthAndReturn(receiver, index, value, update_length);
#endif
BIND(&non_number_value);
}
{
TNode<NativeContext> native_context = LoadNativeContext(context);
ElementsKind target_kind = update_length == kBumpLengthWithGap
? HOLEY_ELEMENTS
: PACKED_ELEMENTS;
TryRewriteElements(receiver, receiver_map, elements, native_context,
PACKED_DOUBLE_ELEMENTS, target_kind, slow);
TNode<FixedArrayBase> fast_elements = LoadElements(receiver);
TNode<IntPtrT> fast_offset =
ElementOffsetFromIndex(index, PACKED_ELEMENTS, kHeaderSize);
Store(fast_elements, fast_offset, value);
MaybeUpdateLengthAndReturn(receiver, index, value, update_length);
}
}
BIND(&check_cow_elements);
{
Goto(slow);
}
}
void KeyedStoreGenericAssembler::EmitGenericElementStore(
TNode<JSObject> receiver, TNode<Map> receiver_map,
TNode<Uint16T> instance_type, TNode<IntPtrT> index, TNode<Object> value,
TNode<Context> context, Label* slow) {
Label if_fast(this), 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), if_shared_array(this);
TNode<FixedArrayBase> elements = LoadElements(receiver);
TNode<Int32T> elements_kind = LoadMapElementsKind(receiver_map);
Branch(IsFastElementsKind(elements_kind), &if_fast, &if_nonfast);
BIND(&if_fast);
Label if_array(this);
GotoIf(IsJSArrayInstanceType(instance_type), &if_array);
{
TNode<IntPtrT> capacity = LoadAndUntagFixedArrayBaseLength(elements);
Branch(UintPtrLessThan(index, capacity), &if_in_bounds, &if_grow);
}
BIND(&if_array);
{
TNode<IntPtrT> length =
PositiveSmiUntag(LoadFastJSArrayLength(CAST(receiver)));
#if defined(OHOS_MEM_USAGE_REPORT) && defined(USING_OHOS_WEB)
TNode<Smi> capacity2_smi = LoadFixedArrayBaseLength(elements);
CSA_MURCHECK(this, TaggedIsSmi(capacity2_smi), 5);
TNode<IntPtrT> capacity2 = SmiUntag(capacity2_smi);
CSA_MURCHECK(this, UintPtrLessThanOrEqual(length, capacity2), 0);
#endif
GotoIf(UintPtrLessThan(index, length), &if_in_bounds);
TNode<IntPtrT> capacity = LoadAndUntagFixedArrayBaseLength(elements);
GotoIf(UintPtrGreaterThanOrEqual(index, capacity), &if_grow);
Branch(WordEqual(index, length), &if_increment_length_by_one,
&if_bump_length_with_gap);
}
BIND(&if_in_bounds);
{
StoreElementWithCapacity(receiver, receiver_map, elements, elements_kind,
index, value, context, slow, kDontChangeLength);
}
BIND(&if_increment_length_by_one);
{
StoreElementWithCapacity(receiver, receiver_map, elements, elements_kind,
index, value, context, slow,
kIncrementLengthByOne);
}
BIND(&if_bump_length_with_gap);
{
StoreElementWithCapacity(receiver, receiver_map, elements, elements_kind,
index, value, context, slow, kBumpLengthWithGap);
}
BIND(&if_grow);
{
Comment("Grow backing store");
Goto(slow);
}
BIND(&if_nonfast);
{
static_assert(LAST_ELEMENTS_KIND ==
LAST_RAB_GSAB_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);
GotoIf(Word32Equal(elements_kind, Int32Constant(SHARED_ARRAY_ELEMENTS)),
&if_shared_array);
Goto(slow);
}
BIND(&if_dictionary);
{
Comment("Dictionary");
Goto(slow);
}
BIND(&if_typed_array);
{
Comment("Typed array");
Goto(slow);
}
BIND(&if_shared_array);
{
TNode<IntPtrT> length = LoadAndUntagFixedArrayBaseLength(elements);
GotoIf(UintPtrGreaterThanOrEqual(index, length), slow);
StoreSharedArrayElement(context, elements, index, value);
}
}
void KeyedStoreGenericAssembler::LookupPropertyOnPrototypeChain(
TNode<Map> receiver_map, TNode<Name> name, Label* accessor,
TVariable<Object>* var_accessor_pair,
TVariable<HeapObject>* var_accessor_holder, Label* readonly,
Label* bailout) {
Label ok_to_write(this);
TVARIABLE(HeapObject, var_holder);
TVARIABLE(Map, var_holder_map);
var_holder = LoadMapPrototype(receiver_map);
var_holder_map = LoadMap(var_holder.value());
Label loop(this, {&var_holder, &var_holder_map});
Goto(&loop);
BIND(&loop);
{
TNode<HeapObject> holder = var_holder.value();
GotoIf(IsNull(holder), &ok_to_write);
TNode<Map> holder_map = var_holder_map.value();
TNode<Uint16T> instance_type = LoadMapInstanceType(holder_map);
Label next_proto(this);
{
Label found(this), found_fast(this), found_dict(this), found_global(this);
TVARIABLE(HeapObject, var_meta_storage);
TVARIABLE(IntPtrT, var_entry);
TryLookupProperty(holder, holder_map, instance_type, name, &found_fast,
&found_dict, &found_global, &var_meta_storage,
&var_entry, &next_proto, bailout);
BIND(&found_fast);
{
TNode<DescriptorArray> descriptors = CAST(var_meta_storage.value());
TNode<IntPtrT> name_index = var_entry.value();
TNode<Uint32T> details = LoadDetailsByKeyIndex(descriptors, name_index);
JumpIfDataProperty(details, &ok_to_write, readonly);
LoadPropertyFromFastObject(holder, holder_map, descriptors, name_index,
details, var_accessor_pair);
*var_accessor_holder = holder;
Goto(accessor);
}
BIND(&found_dict);
{
TNode<PropertyDictionary> dictionary = CAST(var_meta_storage.value());
TNode<IntPtrT> entry = var_entry.value();
TNode<Uint32T> details = LoadDetailsByKeyIndex(dictionary, entry);
JumpIfDataProperty(details, &ok_to_write, readonly);
if (accessor != nullptr) {
*var_accessor_pair = LoadValueByKeyIndex(dictionary, entry);
*var_accessor_holder = holder;
Goto(accessor);
} else {
Goto(&ok_to_write);
}
}
BIND(&found_global);
{
TNode<GlobalDictionary> dictionary = CAST(var_meta_storage.value());
TNode<IntPtrT> entry = var_entry.value();
TNode<PropertyCell> property_cell =
CAST(LoadValueByKeyIndex(dictionary, entry));
TNode<Object> value =
LoadObjectField(property_cell, PropertyCell::kValueOffset);
GotoIf(TaggedEqual(value, TheHoleConstant()), &next_proto);
TNode<Uint32T> details = Unsigned(LoadAndUntagToWord32ObjectField(
property_cell, PropertyCell::kPropertyDetailsRawOffset));
JumpIfDataProperty(details, &ok_to_write, readonly);
if (accessor != nullptr) {
*var_accessor_pair = value;
*var_accessor_holder = holder;
Goto(accessor);
} else {
Goto(&ok_to_write);
}
}
}
BIND(&next_proto);
GotoIf(IsJSTypedArrayInstanceType(instance_type), bailout);
TNode<HeapObject> proto = LoadMapPrototype(holder_map);
GotoIf(IsNull(proto), &ok_to_write);
var_holder = proto;
var_holder_map = LoadMap(proto);
Goto(&loop);
}
BIND(&ok_to_write);
}
TNode<Map> KeyedStoreGenericAssembler::FindCandidateStoreICTransitionMapHandler(
TNode<Map> map, TNode<Name> name, Label* slow) {
TVARIABLE(Map, var_transition_map);
Label simple_transition(this), transition_array(this),
found_handler_candidate(this);
TNode<MaybeObject> maybe_handler =
LoadMaybeWeakObjectField(map, Map::kTransitionsOrPrototypeInfoOffset);
TVARIABLE(Object, var_transition_map_or_array);
DispatchMaybeObject(maybe_handler, slow, slow, &simple_transition,
&transition_array, &var_transition_map_or_array);
BIND(&simple_transition);
{
var_transition_map = CAST(var_transition_map_or_array.value());
Goto(&found_handler_candidate);
}
BIND(&transition_array);
{
TNode<Map> maybe_handler_map =
LoadMap(CAST(var_transition_map_or_array.value()));
GotoIfNot(IsTransitionArrayMap(maybe_handler_map), slow);
TVARIABLE(IntPtrT, var_name_index);
Label if_found_candidate(this);
TNode<TransitionArray> transitions =
CAST(var_transition_map_or_array.value());
TransitionLookup(name, transitions, &if_found_candidate, &var_name_index,
slow);
BIND(&if_found_candidate);
{
static_assert(static_cast<int>(PropertyKind::kData) == 0);
static_assert(NONE == 0);
const int kKeyToTargetOffset = (TransitionArray::kEntryTargetIndex -
TransitionArray::kEntryKeyIndex) *
kTaggedSize;
var_transition_map = CAST(GetHeapObjectAssumeWeak(
LoadArrayElement(transitions, OFFSET_OF_DATA_START(WeakFixedArray),
var_name_index.value(), kKeyToTargetOffset)));
Goto(&found_handler_candidate);
}
}
BIND(&found_handler_candidate);
return var_transition_map.value();
}
void KeyedStoreGenericAssembler::EmitGenericPropertyStore(
TNode<JSReceiver> receiver, TNode<Map> receiver_map,
TNode<Uint16T> instance_type, const StoreICParameters* p,
ExitPoint* exit_point, Label* slow, Maybe<LanguageMode> maybe_language_mode,
UseStubCache use_stub_cache) {
CSA_DCHECK(this, IsSimpleObjectMap(receiver_map));
TVARIABLE(Object, var_accessor_pair);
TVARIABLE(HeapObject, var_accessor_holder);
Label fast_properties(this), dictionary_properties(this), accessor(this),
readonly(this), try_stub_cache(this);
TNode<Uint32T> bitfield3 = LoadMapBitField3(receiver_map);
TNode<Name> name = CAST(p->name());
Branch(IsSetWord32<Map::Bits3::IsDictionaryMapBit>(bitfield3),
&dictionary_properties, &fast_properties);
BIND(&fast_properties);
{
Comment("fast property store");
TNode<DescriptorArray> descriptors = LoadMapDescriptors(receiver_map);
Label descriptor_found(this), lookup_transition(this);
TVARIABLE(IntPtrT, var_name_index);
DescriptorLookup(name, descriptors, bitfield3,
IsAnyDefineOwn() ? slow : &descriptor_found,
&var_name_index, &lookup_transition);
if (!IsAnyDefineOwn()) {
BIND(&descriptor_found);
{
TNode<IntPtrT> name_index = var_name_index.value();
TNode<Uint32T> details = LoadDetailsByKeyIndex(descriptors, name_index);
Label data_property(this);
JumpIfDataProperty(details, &data_property,
ShouldReconfigureExisting() ? nullptr : &readonly);
if (ShouldCallSetter()) {
LoadPropertyFromFastObject(receiver, receiver_map, descriptors,
name_index, details, &var_accessor_pair);
var_accessor_holder = receiver;
Goto(&accessor);
} else {
Goto(slow);
}
BIND(&data_property);
{
Label shared(this);
GotoIf(IsJSSharedStructInstanceType(instance_type), &shared);
CheckForAssociatedProtector(name, slow);
OverwriteExistingFastDataProperty(receiver, receiver_map, descriptors,
name_index, details, p->value(),
slow, false);
exit_point->Return(p->value());
BIND(&shared);
{
StoreJSSharedStructField(p->context(), receiver, receiver_map,
descriptors, name_index, details,
p->value());
exit_point->Return(p->value());
}
}
}
}
BIND(&lookup_transition);
{
Comment("lookup transition");
CheckForAssociatedProtector(name, slow);
DCHECK_IMPLIES(use_stub_cache == kUseStubCache, IsSet());
Label* if_not_found =
use_stub_cache == kUseStubCache ? &try_stub_cache : slow;
TNode<Map> transition_map = FindCandidateStoreICTransitionMapHandler(
receiver_map, name, if_not_found);
StoreTransitionMapFlags flags = kValidateTransitionHandler;
if (ShouldCheckPrototypeValidity()) {
flags = StoreTransitionMapFlags(flags | kCheckPrototypeValidity);
}
HandleStoreICTransitionMapHandlerCase(p, transition_map, slow, flags);
exit_point->Return(p->value());
}
}
BIND(&dictionary_properties);
{
Comment("dictionary property store");
TVARIABLE(IntPtrT, var_name_index);
Label dictionary_found(this, &var_name_index),
not_found(this, &var_name_index);
TNode<PropertyDictionary> properties = CAST(LoadSlowProperties(receiver));
NameDictionaryLookup<PropertyDictionary>(
properties, name, IsAnyDefineOwn() ? slow : &dictionary_found,
&var_name_index, ¬_found, kFindExistingOrInsertionIndex);
if (!IsAnyDefineOwn()) {
BIND(&dictionary_found);
{
Label check_const(this), overwrite(this), done(this);
TNode<Uint32T> details =
LoadDetailsByKeyIndex(properties, var_name_index.value());
JumpIfDataProperty(details, &check_const,
ShouldReconfigureExisting() ? nullptr : &readonly);
if (ShouldCallSetter()) {
var_accessor_pair =
LoadValueByKeyIndex(properties, var_name_index.value());
var_accessor_holder = receiver;
Goto(&accessor);
} else {
Goto(slow);
}
BIND(&check_const);
{
if (V8_DICT_PROPERTY_CONST_TRACKING_BOOL) {
GotoIfNot(IsPropertyDetailsConst(details), &overwrite);
TNode<Object> prev_value =
LoadValueByKeyIndex(properties, var_name_index.value());
Branch(TaggedEqual(prev_value, p->value()), &done, slow);
} else {
Goto(&overwrite);
}
}
BIND(&overwrite);
{
CheckForAssociatedProtector(name, slow);
StoreValueByKeyIndex<PropertyDictionary>(
properties, var_name_index.value(), p->value());
Goto(&done);
}
BIND(&done);
exit_point->Return(p->value());
}
}
BIND(¬_found);
{
GotoIf(IsJSTypedArrayMap(receiver_map), slow);
CheckForAssociatedProtector(name, slow);
Label extensible(this), is_private_symbol(this);
GotoIf(IsPrivateSymbol(name), &is_private_symbol);
Branch(IsSetWord32<Map::Bits3::IsExtensibleBit>(bitfield3), &extensible,
slow);
BIND(&is_private_symbol);
{
CSA_DCHECK(this, IsPrivateSymbol(name));
Branch(IsPrivateName(CAST(name)), slow, &extensible);
}
BIND(&extensible);
if (ShouldCheckPrototype()) {
DCHECK(ShouldCallSetter());
LookupPropertyOnPrototypeChain(
receiver_map, name, &accessor, &var_accessor_pair,
&var_accessor_holder,
ShouldReconfigureExisting() ? nullptr : &readonly, slow);
}
Label add_dictionary_property_slow(this);
InvalidateValidityCellIfPrototype(receiver_map, bitfield3);
UpdateMayHaveInterestingProperty(properties, name);
AddToDictionary<PropertyDictionary>(properties, name, p->value(),
&add_dictionary_property_slow,
var_name_index.value());
exit_point->Return(p->value());
BIND(&add_dictionary_property_slow);
exit_point->ReturnCallRuntime(Runtime::kAddDictionaryProperty,
p->context(), p->receiver(), name,
p->value());
}
}
if (ShouldCallSetter()) {
BIND(&accessor);
{
Label not_callable(this);
TNode<HeapObject> accessor_pair = CAST(var_accessor_pair.value());
GotoIf(IsAccessorInfo(accessor_pair), slow);
CSA_DCHECK(this, IsAccessorPair(accessor_pair));
TNode<HeapObject> setter =
CAST(LoadAccessorPairSetter(CAST(accessor_pair)));
TNode<Map> setter_map = LoadMap(setter);
GotoIf(IsFunctionTemplateInfoMap(setter_map), slow);
GotoIfNot(IsCallableMap(setter_map), ¬_callable);
Call(p->context(), setter, receiver, p->value());
exit_point->Return(p->value());
BIND(¬_callable);
{
LanguageMode language_mode;
if (maybe_language_mode.To(&language_mode)) {
if (language_mode == LanguageMode::kStrict) {
exit_point->ReturnCallRuntime(
Runtime::kThrowTypeError, p->context(),
SmiConstant(MessageTemplate::kNoSetterInCallback), name,
var_accessor_holder.value());
} else {
exit_point->Return(p->value());
}
} else {
CallRuntime(Runtime::kThrowTypeErrorIfStrict, p->context(),
SmiConstant(MessageTemplate::kNoSetterInCallback), name,
var_accessor_holder.value());
exit_point->Return(p->value());
}
}
}
}
if (!ShouldReconfigureExisting() && !IsAnyDefineOwn()) {
BIND(&readonly);
{
LanguageMode language_mode;
if (maybe_language_mode.To(&language_mode)) {
if (language_mode == LanguageMode::kStrict) {
TNode<String> type = Typeof(p->receiver());
ThrowTypeError(p->context(), MessageTemplate::kStrictReadOnlyProperty,
name, type, p->receiver());
} else {
exit_point->Return(p->value());
}
} else {
CallRuntime(Runtime::kThrowTypeErrorIfStrict, p->context(),
SmiConstant(MessageTemplate::kStrictReadOnlyProperty), name,
Typeof(p->receiver()), p->receiver());
exit_point->Return(p->value());
}
}
}
if (use_stub_cache == kUseStubCache) {
DCHECK(IsSet());
BIND(&try_stub_cache);
GotoIfNot(IsJSApiObjectInstanceType(instance_type), slow);
Comment("stub cache probe");
TVARIABLE(MaybeObject, var_handler);
Label found_handler(this, &var_handler), stub_cache_miss(this);
TryProbeStubCache(p->stub_cache(isolate()), receiver, name, &found_handler,
&var_handler, &stub_cache_miss);
BIND(&found_handler);
{
Comment("KeyedStoreGeneric found handler");
HandleStoreICHandlerCase(p, var_handler.value(), &stub_cache_miss,
ICMode::kNonGlobalIC);
}
BIND(&stub_cache_miss);
{
Comment("KeyedStoreGeneric_miss");
TailCallRuntime(Runtime::kKeyedStoreIC_Miss, p->context(), p->value(),
p->slot(), p->vector(), p->receiver(), name);
}
}
}
void KeyedStoreGenericAssembler::KeyedStoreGeneric(
TNode<Context> context, TNode<JSAny> receiver_maybe_smi, TNode<Object> key,
TNode<Object> value, Maybe<LanguageMode> language_mode,
UseStubCache use_stub_cache, TNode<TaggedIndex> slot,
TNode<HeapObject> maybe_vector) {
DCHECK_IMPLIES(use_stub_cache == kUseStubCache, IsSet());
TVARIABLE(IntPtrT, var_index);
TVARIABLE(Name, var_unique);
Label if_index(this, &var_index), if_unique_name(this),
not_internalized(this), slow(this);
GotoIf(TaggedIsSmi(receiver_maybe_smi), &slow);
TNode<JSAnyNotSmi> receiver = CAST(receiver_maybe_smi);
TNode<Map> receiver_map = LoadMap(receiver);
TNode<Uint16T> instance_type = LoadMapInstanceType(receiver_map);
GotoIf(IsCustomElementsReceiverInstanceType(instance_type), &slow);
TryToName(key, &if_index, &var_index, &if_unique_name, &var_unique, &slow,
¬_internalized);
BIND(&if_index);
{
Comment("integer index");
EmitGenericElementStore(CAST(receiver), receiver_map, instance_type,
var_index.value(), value, context, &slow);
}
BIND(&if_unique_name);
{
Comment("key is unique name");
StoreICParameters p(context, receiver, var_unique.value(), value,
std::nullopt, slot, maybe_vector,
StoreICMode::kDefault);
ExitPoint direct_exit(this);
EmitGenericPropertyStore(CAST(receiver), receiver_map, instance_type, &p,
&direct_exit, &slow, language_mode,
use_stub_cache);
}
BIND(¬_internalized);
{
if (v8_flags.internalize_on_the_fly) {
TryInternalizeString(CAST(key), &if_index, &var_index, &if_unique_name,
&var_unique, &slow, &slow);
} else {
Goto(&slow);
}
}
BIND(&slow);
{
if (IsSet() || IsDefineNamedOwn()) {
CSA_DCHECK(this, BoolConstant(!IsDefineNamedOwn()));
Comment("KeyedStoreGeneric_slow");
TailCallRuntime(Runtime::kSetKeyedProperty, context, receiver, key,
value);
} else if (IsDefineKeyedOwn()) {
TailCallRuntime(Runtime::kDefineObjectOwnProperty, context, receiver, key,
value);
} else {
DCHECK(IsDefineKeyedOwnInLiteral());
TNode<Smi> flags =
SmiConstant(DefineKeyedOwnPropertyInLiteralFlag::kNoFlags);
TNode<TaggedIndex> invalid_slot =
TaggedIndexConstant(FeedbackSlot::Invalid().ToInt());
TailCallRuntime(Runtime::kDefineKeyedOwnPropertyInLiteral, context,
receiver, key, value, flags, UndefinedConstant(),
invalid_slot);
}
}
}
void KeyedStoreGenericAssembler::KeyedStoreGeneric() {
using Descriptor = StoreNoFeedbackDescriptor;
auto receiver = Parameter<JSAny>(Descriptor::kReceiver);
auto name = Parameter<Object>(Descriptor::kName);
auto value = Parameter<Object>(Descriptor::kValue);
auto context = Parameter<Context>(Descriptor::kContext);
KeyedStoreGeneric(context, receiver, name, value, Nothing<LanguageMode>());
}
void KeyedStoreGenericAssembler::KeyedStoreMegamorphic() {
DCHECK(IsSet());
using Descriptor = StoreWithVectorDescriptor;
auto receiver = Parameter<JSAny>(Descriptor::kReceiver);
auto name = Parameter<Object>(Descriptor::kName);
auto value = Parameter<Object>(Descriptor::kValue);
auto context = Parameter<Context>(Descriptor::kContext);
auto slot = Parameter<TaggedIndex>(Descriptor::kSlot);
auto maybe_vector = Parameter<HeapObject>(Descriptor::kVector);
KeyedStoreGeneric(context, receiver, name, value, Nothing<LanguageMode>(),
kUseStubCache, slot, maybe_vector);
}
void KeyedStoreGenericAssembler::StoreProperty(TNode<Context> context,
TNode<JSAny> receiver,
TNode<Object> key,
TNode<Object> value,
LanguageMode language_mode) {
KeyedStoreGeneric(context, receiver, key, value, Just(language_mode));
}
void KeyedStoreGenericAssembler::StoreIC_NoFeedback() {
using Descriptor = StoreNoFeedbackDescriptor;
auto receiver_maybe_smi = Parameter<JSAny>(Descriptor::kReceiver);
auto name = Parameter<Object>(Descriptor::kName);
auto value = Parameter<Object>(Descriptor::kValue);
auto context = Parameter<Context>(Descriptor::kContext);
Label miss(this, Label::kDeferred), store_property(this);
GotoIf(TaggedIsSmi(receiver_maybe_smi), &miss);
{
TNode<JSAnyNotSmi> receiver = CAST(receiver_maybe_smi);
TNode<Map> receiver_map = LoadMap(receiver);
TNode<Uint16T> instance_type = LoadMapInstanceType(receiver_map);
GotoIf(IsSpecialReceiverInstanceType(instance_type), &miss);
{
StoreICParameters p(context, receiver, name, value, std::nullopt, {},
UndefinedConstant(),
IsDefineNamedOwn() ? StoreICMode::kDefineNamedOwn
: StoreICMode::kDefault);
EmitGenericPropertyStore(CAST(receiver), receiver_map, instance_type, &p,
&miss);
}
}
BIND(&miss);
{
auto runtime = IsDefineNamedOwn() ? Runtime::kDefineNamedOwnIC_Miss
: Runtime::kStoreIC_Miss;
TNode<TaggedIndex> slot =
TaggedIndexConstant(FeedbackSlot::Invalid().ToInt());
TailCallRuntime(runtime, context, value, slot, UndefinedConstant(),
receiver_maybe_smi, name);
}
}
void KeyedStoreGenericAssembler::StoreProperty(TNode<Context> context,
TNode<JSReceiver> receiver,
TNode<BoolT> is_simple_receiver,
TNode<Name> unique_name,
TNode<Object> value,
LanguageMode language_mode) {
StoreICParameters p(context, receiver, unique_name, value, std::nullopt, {},
UndefinedConstant(), StoreICMode::kDefault);
Label done(this), slow(this, Label::kDeferred);
ExitPoint exit_point(this, [&](TNode<Object> result) { Goto(&done); });
CSA_DCHECK(this, Word32Equal(is_simple_receiver,
IsSimpleObjectMap(LoadMap(receiver))));
GotoIfNot(is_simple_receiver, &slow);
TNode<Map> map = LoadMap(receiver);
TNode<Uint16T> instance_type = LoadMapInstanceType(map);
EmitGenericPropertyStore(receiver, map, instance_type, &p, &exit_point, &slow,
Just(language_mode), kDontUseStubCache);
BIND(&slow);
{
if (IsDefineKeyedOwnInLiteral()) {
TNode<Smi> flags =
SmiConstant(DefineKeyedOwnPropertyInLiteralFlag::kNoFlags);
TNode<TaggedIndex> slot =
TaggedIndexConstant(FeedbackSlot::Invalid().ToInt());
CallRuntime(Runtime::kDefineKeyedOwnPropertyInLiteral, context, receiver,
unique_name, value, flags, p.vector(), slot);
} else {
CallRuntime(Runtime::kSetKeyedProperty, context, receiver, unique_name,
value);
}
Goto(&done);
}
BIND(&done);
}
#include "src/codegen/undef-code-stub-assembler-macros.inc"
}
}