// 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/accessor-assembler.h"
|
|
#include "src/code-factory.h"
|
#include "src/code-stubs.h"
|
#include "src/counters.h"
|
#include "src/ic/handler-configuration.h"
|
#include "src/ic/ic.h"
|
#include "src/ic/stub-cache.h"
|
#include "src/objects-inl.h"
|
#include "src/objects/module.h"
|
|
namespace v8 {
|
namespace internal {
|
|
using compiler::CodeAssemblerState;
|
using compiler::Node;
|
template <typename T>
|
using TNode = compiler::TNode<T>;
|
template <typename T>
|
using SloppyTNode = compiler::SloppyTNode<T>;
|
|
//////////////////// Private helpers.
|
|
// Loads dataX field from the DataHandler object.
|
TNode<MaybeObject> AccessorAssembler::LoadHandlerDataField(
|
SloppyTNode<DataHandler> handler, int data_index) {
|
#ifdef DEBUG
|
TNode<Map> handler_map = LoadMap(handler);
|
TNode<Int32T> instance_type = LoadMapInstanceType(handler_map);
|
#endif
|
CSA_ASSERT(this,
|
Word32Or(InstanceTypeEqual(instance_type, LOAD_HANDLER_TYPE),
|
InstanceTypeEqual(instance_type, STORE_HANDLER_TYPE)));
|
int offset = 0;
|
int minimum_size = 0;
|
switch (data_index) {
|
case 1:
|
offset = DataHandler::kData1Offset;
|
minimum_size = DataHandler::kSizeWithData1;
|
break;
|
case 2:
|
offset = DataHandler::kData2Offset;
|
minimum_size = DataHandler::kSizeWithData2;
|
break;
|
case 3:
|
offset = DataHandler::kData3Offset;
|
minimum_size = DataHandler::kSizeWithData3;
|
break;
|
default:
|
UNREACHABLE();
|
break;
|
}
|
USE(minimum_size);
|
CSA_ASSERT(this, UintPtrGreaterThanOrEqual(
|
LoadMapInstanceSizeInWords(handler_map),
|
IntPtrConstant(minimum_size / kPointerSize)));
|
return LoadMaybeWeakObjectField(handler, offset);
|
}
|
|
TNode<MaybeObject> AccessorAssembler::TryMonomorphicCase(
|
Node* slot, Node* vector, Node* receiver_map, Label* if_handler,
|
TVariable<MaybeObject>* var_handler, Label* if_miss) {
|
Comment("TryMonomorphicCase");
|
DCHECK_EQ(MachineRepresentation::kTagged, var_handler->rep());
|
|
// TODO(ishell): add helper class that hides offset computations for a series
|
// of loads.
|
CSA_ASSERT(this, IsFeedbackVector(vector), vector);
|
int32_t header_size = FeedbackVector::kFeedbackSlotsOffset - kHeapObjectTag;
|
// Adding |header_size| with a separate IntPtrAdd rather than passing it
|
// into ElementOffsetFromIndex() allows it to be folded into a single
|
// [base, index, offset] indirect memory access on x64.
|
Node* offset = ElementOffsetFromIndex(slot, HOLEY_ELEMENTS, SMI_PARAMETERS);
|
TNode<MaybeObject> feedback = ReinterpretCast<MaybeObject>(
|
Load(MachineType::AnyTagged(), vector,
|
IntPtrAdd(offset, IntPtrConstant(header_size))));
|
|
// Try to quickly handle the monomorphic case without knowing for sure
|
// if we have a weak reference in feedback.
|
GotoIf(IsNotWeakReferenceTo(feedback, CAST(receiver_map)), if_miss);
|
|
TNode<MaybeObject> handler = UncheckedCast<MaybeObject>(
|
Load(MachineType::AnyTagged(), vector,
|
IntPtrAdd(offset, IntPtrConstant(header_size + kPointerSize))));
|
|
*var_handler = handler;
|
Goto(if_handler);
|
return feedback;
|
}
|
|
void AccessorAssembler::HandlePolymorphicCase(
|
Node* receiver_map, TNode<WeakFixedArray> feedback, Label* if_handler,
|
TVariable<MaybeObject>* var_handler, Label* if_miss,
|
int min_feedback_capacity) {
|
Comment("HandlePolymorphicCase");
|
DCHECK_EQ(MachineRepresentation::kTagged, var_handler->rep());
|
|
// Deferred so the unrolled case can omit frame construction in bytecode
|
// handler.
|
Label loop(this, Label::kDeferred);
|
|
// Iterate {feedback} array.
|
const int kEntrySize = 2;
|
|
// Loading feedback's length is delayed until we need it when looking past
|
// the first {min_feedback_capacity} (map, handler) pairs.
|
Node* length = nullptr;
|
CSA_ASSERT(this, SmiGreaterThanOrEqual(
|
LoadWeakFixedArrayLength(feedback),
|
SmiConstant(min_feedback_capacity * kEntrySize)));
|
|
const int kUnrolledIterations = IC::kMaxPolymorphicMapCount;
|
for (int i = 0; i < kUnrolledIterations; i++) {
|
int map_index = i * kEntrySize;
|
int handler_index = i * kEntrySize + 1;
|
|
if (i >= min_feedback_capacity) {
|
if (length == nullptr) length = LoadWeakFixedArrayLength(feedback);
|
GotoIf(SmiGreaterThanOrEqual(SmiConstant(handler_index), CAST(length)),
|
if_miss);
|
}
|
|
Label next_entry(this);
|
TNode<MaybeObject> maybe_cached_map =
|
LoadWeakFixedArrayElement(feedback, map_index);
|
CSA_ASSERT(this, IsWeakOrClearedHeapObject(maybe_cached_map));
|
GotoIf(IsNotWeakReferenceTo(maybe_cached_map, CAST(receiver_map)),
|
&next_entry);
|
|
// Found, now call handler.
|
TNode<MaybeObject> handler =
|
LoadWeakFixedArrayElement(feedback, handler_index);
|
*var_handler = handler;
|
Goto(if_handler);
|
|
BIND(&next_entry);
|
}
|
Goto(&loop);
|
|
// Loop from {kUnrolledIterations}*kEntrySize to {length}.
|
BIND(&loop);
|
Node* start_index = IntPtrConstant(kUnrolledIterations * kEntrySize);
|
Node* end_index = LoadAndUntagWeakFixedArrayLength(feedback);
|
BuildFastLoop(
|
start_index, end_index,
|
[this, receiver_map, feedback, if_handler, var_handler](Node* index) {
|
Label next_entry(this);
|
TNode<MaybeObject> maybe_cached_map =
|
LoadWeakFixedArrayElement(feedback, index);
|
CSA_ASSERT(this, IsWeakOrClearedHeapObject(maybe_cached_map));
|
GotoIf(IsNotWeakReferenceTo(maybe_cached_map, CAST(receiver_map)),
|
&next_entry);
|
|
// Found, now call handler.
|
TNode<MaybeObject> handler =
|
LoadWeakFixedArrayElement(feedback, index, kPointerSize);
|
*var_handler = handler;
|
Goto(if_handler);
|
|
BIND(&next_entry);
|
},
|
kEntrySize, INTPTR_PARAMETERS, IndexAdvanceMode::kPost);
|
// The loop falls through if no handler was found.
|
Goto(if_miss);
|
}
|
|
void AccessorAssembler::HandleLoadICHandlerCase(
|
const LoadICParameters* p, TNode<Object> handler, Label* miss,
|
ExitPoint* exit_point, ICMode ic_mode, OnNonExistent on_nonexistent,
|
ElementSupport support_elements) {
|
Comment("have_handler");
|
|
VARIABLE(var_holder, MachineRepresentation::kTagged, p->holder);
|
VARIABLE(var_smi_handler, MachineRepresentation::kTagged, handler);
|
|
Variable* vars[] = {&var_holder, &var_smi_handler};
|
Label if_smi_handler(this, 2, vars);
|
Label try_proto_handler(this, Label::kDeferred),
|
call_handler(this, Label::kDeferred);
|
|
Branch(TaggedIsSmi(handler), &if_smi_handler, &try_proto_handler);
|
|
// |handler| is a Smi, encoding what to do. See SmiHandler methods
|
// for the encoding format.
|
BIND(&if_smi_handler);
|
{
|
HandleLoadICSmiHandlerCase(p, var_holder.value(), var_smi_handler.value(),
|
handler, miss, exit_point, on_nonexistent,
|
support_elements);
|
}
|
|
BIND(&try_proto_handler);
|
{
|
GotoIf(IsCodeMap(LoadMap(CAST(handler))), &call_handler);
|
HandleLoadICProtoHandler(p, handler, &var_holder, &var_smi_handler,
|
&if_smi_handler, miss, exit_point, ic_mode);
|
}
|
|
BIND(&call_handler);
|
{
|
exit_point->ReturnCallStub(LoadWithVectorDescriptor{}, handler, p->context,
|
p->receiver, p->name, p->slot, p->vector);
|
}
|
}
|
|
void AccessorAssembler::HandleLoadCallbackProperty(const LoadICParameters* p,
|
TNode<JSObject> holder,
|
TNode<WordT> handler_word,
|
ExitPoint* exit_point) {
|
Comment("native_data_property_load");
|
Node* descriptor = DecodeWord<LoadHandler::DescriptorBits>(handler_word);
|
|
Label runtime(this, Label::kDeferred);
|
Callable callable = CodeFactory::ApiGetter(isolate());
|
TNode<AccessorInfo> accessor_info =
|
CAST(LoadDescriptorValue(LoadMap(holder), descriptor));
|
|
GotoIf(IsRuntimeCallStatsEnabled(), &runtime);
|
exit_point->ReturnCallStub(callable, p->context, p->receiver, holder,
|
accessor_info);
|
|
BIND(&runtime);
|
exit_point->ReturnCallRuntime(Runtime::kLoadCallbackProperty, p->context,
|
p->receiver, holder, accessor_info, p->name);
|
}
|
|
void AccessorAssembler::HandleLoadAccessor(
|
const LoadICParameters* p, TNode<CallHandlerInfo> call_handler_info,
|
TNode<WordT> handler_word, TNode<DataHandler> handler,
|
TNode<IntPtrT> handler_kind, ExitPoint* exit_point) {
|
Comment("api_getter");
|
Label runtime(this, Label::kDeferred);
|
// Context is stored either in data2 or data3 field depending on whether
|
// the access check is enabled for this handler or not.
|
TNode<MaybeObject> maybe_context = Select<MaybeObject>(
|
IsSetWord<LoadHandler::DoAccessCheckOnReceiverBits>(handler_word),
|
[=] { return LoadHandlerDataField(handler, 3); },
|
[=] { return LoadHandlerDataField(handler, 2); });
|
|
CSA_ASSERT(this, IsWeakOrClearedHeapObject(maybe_context));
|
CSA_CHECK(this, IsNotClearedWeakHeapObject(maybe_context));
|
TNode<Object> context = ToWeakHeapObject(maybe_context);
|
|
GotoIf(IsRuntimeCallStatsEnabled(), &runtime);
|
{
|
TNode<Foreign> foreign = CAST(
|
LoadObjectField(call_handler_info, CallHandlerInfo::kJsCallbackOffset));
|
TNode<WordT> callback = TNode<WordT>::UncheckedCast(LoadObjectField(
|
foreign, Foreign::kForeignAddressOffset, MachineType::Pointer()));
|
TNode<Object> data =
|
LoadObjectField(call_handler_info, CallHandlerInfo::kDataOffset);
|
|
VARIABLE(api_holder, MachineRepresentation::kTagged, p->receiver);
|
Label load(this);
|
GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kApiGetter)),
|
&load);
|
|
CSA_ASSERT(
|
this,
|
WordEqual(handler_kind,
|
IntPtrConstant(LoadHandler::kApiGetterHolderIsPrototype)));
|
|
api_holder.Bind(LoadMapPrototype(LoadMap(p->receiver)));
|
Goto(&load);
|
|
BIND(&load);
|
Callable callable = CodeFactory::CallApiCallback(isolate(), 0);
|
exit_point->Return(CallStub(callable, nullptr, context, data,
|
api_holder.value(), callback, p->receiver));
|
}
|
|
BIND(&runtime);
|
exit_point->ReturnCallRuntime(Runtime::kLoadAccessorProperty, context,
|
p->receiver, SmiTag(handler_kind),
|
call_handler_info);
|
}
|
|
void AccessorAssembler::HandleLoadField(Node* holder, Node* handler_word,
|
Variable* var_double_value,
|
Label* rebox_double,
|
ExitPoint* exit_point) {
|
Comment("field_load");
|
Node* index = DecodeWord<LoadHandler::FieldIndexBits>(handler_word);
|
Node* offset = IntPtrMul(index, IntPtrConstant(kPointerSize));
|
|
Label inobject(this), out_of_object(this);
|
Branch(IsSetWord<LoadHandler::IsInobjectBits>(handler_word), &inobject,
|
&out_of_object);
|
|
BIND(&inobject);
|
{
|
Label is_double(this);
|
GotoIf(IsSetWord<LoadHandler::IsDoubleBits>(handler_word), &is_double);
|
exit_point->Return(LoadObjectField(holder, offset));
|
|
BIND(&is_double);
|
if (FLAG_unbox_double_fields) {
|
var_double_value->Bind(
|
LoadObjectField(holder, offset, MachineType::Float64()));
|
} else {
|
Node* mutable_heap_number = LoadObjectField(holder, offset);
|
var_double_value->Bind(LoadHeapNumberValue(mutable_heap_number));
|
}
|
Goto(rebox_double);
|
}
|
|
BIND(&out_of_object);
|
{
|
Label is_double(this);
|
Node* properties = LoadFastProperties(holder);
|
Node* value = LoadObjectField(properties, offset);
|
GotoIf(IsSetWord<LoadHandler::IsDoubleBits>(handler_word), &is_double);
|
exit_point->Return(value);
|
|
BIND(&is_double);
|
var_double_value->Bind(LoadHeapNumberValue(value));
|
Goto(rebox_double);
|
}
|
}
|
|
TNode<Object> AccessorAssembler::LoadDescriptorValue(TNode<Map> map,
|
Node* descriptor) {
|
return CAST(LoadDescriptorValueOrFieldType(map, descriptor));
|
}
|
|
TNode<MaybeObject> AccessorAssembler::LoadDescriptorValueOrFieldType(
|
TNode<Map> map, SloppyTNode<IntPtrT> descriptor) {
|
TNode<DescriptorArray> descriptors = LoadMapDescriptors(map);
|
TNode<IntPtrT> scaled_descriptor =
|
IntPtrMul(descriptor, IntPtrConstant(DescriptorArray::kEntrySize));
|
TNode<IntPtrT> value_index = IntPtrAdd(
|
scaled_descriptor, IntPtrConstant(DescriptorArray::kFirstIndex +
|
DescriptorArray::kEntryValueIndex));
|
CSA_ASSERT(this, UintPtrLessThan(descriptor, LoadAndUntagWeakFixedArrayLength(
|
descriptors)));
|
return LoadWeakFixedArrayElement(descriptors, value_index);
|
}
|
|
void AccessorAssembler::HandleLoadICSmiHandlerCase(
|
const LoadICParameters* p, Node* holder, SloppyTNode<Smi> smi_handler,
|
SloppyTNode<Object> handler, Label* miss, ExitPoint* exit_point,
|
OnNonExistent on_nonexistent, ElementSupport support_elements) {
|
VARIABLE(var_double_value, MachineRepresentation::kFloat64);
|
Label rebox_double(this, &var_double_value);
|
|
TNode<WordT> handler_word = SmiUntag(smi_handler);
|
TNode<IntPtrT> handler_kind =
|
Signed(DecodeWord<LoadHandler::KindBits>(handler_word));
|
if (support_elements == kSupportElements) {
|
Label if_element(this), if_indexed_string(this), if_property(this);
|
GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kElement)),
|
&if_element);
|
Branch(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kIndexedString)),
|
&if_indexed_string, &if_property);
|
|
BIND(&if_element);
|
Comment("element_load");
|
Node* intptr_index = TryToIntptr(p->name, miss);
|
Node* elements = LoadElements(holder);
|
Node* is_jsarray_condition =
|
IsSetWord<LoadHandler::IsJsArrayBits>(handler_word);
|
Node* elements_kind =
|
DecodeWord32FromWord<LoadHandler::ElementsKindBits>(handler_word);
|
Label if_hole(this), unimplemented_elements_kind(this),
|
if_oob(this, Label::kDeferred);
|
EmitElementLoad(holder, elements, elements_kind, intptr_index,
|
is_jsarray_condition, &if_hole, &rebox_double,
|
&var_double_value, &unimplemented_elements_kind, &if_oob,
|
miss, exit_point);
|
|
BIND(&unimplemented_elements_kind);
|
{
|
// Smi handlers should only be installed for supported elements kinds.
|
// Crash if we get here.
|
DebugBreak();
|
Goto(miss);
|
}
|
|
BIND(&if_oob);
|
{
|
Comment("out of bounds elements access");
|
Label return_undefined(this);
|
|
// Check if we're allowed to handle OOB accesses.
|
Node* allow_out_of_bounds =
|
IsSetWord<LoadHandler::AllowOutOfBoundsBits>(handler_word);
|
GotoIfNot(allow_out_of_bounds, miss);
|
|
// Negative indices aren't valid array indices (according to
|
// the ECMAScript specification), and are stored as properties
|
// in V8, not elements. So we cannot handle them here, except
|
// in case of typed arrays, where integer indexed properties
|
// aren't looked up in the prototype chain.
|
GotoIf(IsJSTypedArray(holder), &return_undefined);
|
GotoIf(IntPtrLessThan(intptr_index, IntPtrConstant(0)), miss);
|
|
// For all other receivers we need to check that the prototype chain
|
// doesn't contain any elements.
|
BranchIfPrototypesHaveNoElements(LoadMap(holder), &return_undefined,
|
miss);
|
|
BIND(&return_undefined);
|
exit_point->Return(UndefinedConstant());
|
}
|
|
BIND(&if_hole);
|
{
|
Comment("convert hole");
|
GotoIfNot(IsSetWord<LoadHandler::ConvertHoleBits>(handler_word), miss);
|
GotoIf(IsNoElementsProtectorCellInvalid(), miss);
|
exit_point->Return(UndefinedConstant());
|
}
|
|
BIND(&if_indexed_string);
|
{
|
Label if_oob(this, Label::kDeferred);
|
|
Comment("indexed string");
|
Node* intptr_index = TryToIntptr(p->name, miss);
|
Node* length = LoadStringLengthAsWord(holder);
|
GotoIf(UintPtrGreaterThanOrEqual(intptr_index, length), &if_oob);
|
TNode<Int32T> code = StringCharCodeAt(holder, intptr_index);
|
TNode<String> result = StringFromSingleCharCode(code);
|
Return(result);
|
|
BIND(&if_oob);
|
Node* allow_out_of_bounds =
|
IsSetWord<LoadHandler::AllowOutOfBoundsBits>(handler_word);
|
GotoIfNot(allow_out_of_bounds, miss);
|
GotoIf(IsNoElementsProtectorCellInvalid(), miss);
|
Return(UndefinedConstant());
|
}
|
|
BIND(&if_property);
|
Comment("property_load");
|
}
|
|
Label constant(this), field(this), normal(this, Label::kDeferred),
|
interceptor(this, Label::kDeferred), nonexistent(this),
|
accessor(this, Label::kDeferred), global(this, Label::kDeferred),
|
module_export(this, Label::kDeferred), proxy(this, Label::kDeferred),
|
native_data_property(this), api_getter(this);
|
GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kField)), &field);
|
|
GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kConstant)),
|
&constant);
|
|
GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kNonExistent)),
|
&nonexistent);
|
|
GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kNormal)),
|
&normal);
|
|
GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kAccessor)),
|
&accessor);
|
|
GotoIf(
|
WordEqual(handler_kind, IntPtrConstant(LoadHandler::kNativeDataProperty)),
|
&native_data_property);
|
|
GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kApiGetter)),
|
&api_getter);
|
|
GotoIf(WordEqual(handler_kind,
|
IntPtrConstant(LoadHandler::kApiGetterHolderIsPrototype)),
|
&api_getter);
|
|
GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kGlobal)),
|
&global);
|
|
GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kProxy)), &proxy);
|
|
Branch(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kModuleExport)),
|
&module_export, &interceptor);
|
|
BIND(&field);
|
HandleLoadField(holder, handler_word, &var_double_value, &rebox_double,
|
exit_point);
|
|
BIND(&nonexistent);
|
// This is a handler for a load of a non-existent value.
|
if (on_nonexistent == OnNonExistent::kThrowReferenceError) {
|
exit_point->ReturnCallRuntime(Runtime::kThrowReferenceError, p->context,
|
p->name);
|
} else {
|
DCHECK_EQ(OnNonExistent::kReturnUndefined, on_nonexistent);
|
exit_point->Return(UndefinedConstant());
|
}
|
|
BIND(&constant);
|
{
|
Comment("constant_load");
|
Node* descriptor = DecodeWord<LoadHandler::DescriptorBits>(handler_word);
|
Node* value = LoadDescriptorValue(LoadMap(holder), descriptor);
|
|
exit_point->Return(value);
|
}
|
|
BIND(&normal);
|
{
|
Comment("load_normal");
|
TNode<NameDictionary> properties = CAST(LoadSlowProperties(holder));
|
TVARIABLE(IntPtrT, var_name_index);
|
Label found(this, &var_name_index);
|
NameDictionaryLookup<NameDictionary>(properties, CAST(p->name), &found,
|
&var_name_index, miss);
|
BIND(&found);
|
{
|
VARIABLE(var_details, MachineRepresentation::kWord32);
|
VARIABLE(var_value, MachineRepresentation::kTagged);
|
LoadPropertyFromNameDictionary(properties, var_name_index.value(),
|
&var_details, &var_value);
|
Node* value = CallGetterIfAccessor(var_value.value(), var_details.value(),
|
p->context, p->receiver, miss);
|
exit_point->Return(value);
|
}
|
}
|
|
BIND(&accessor);
|
{
|
Comment("accessor_load");
|
Node* descriptor = DecodeWord<LoadHandler::DescriptorBits>(handler_word);
|
Node* accessor_pair = LoadDescriptorValue(LoadMap(holder), descriptor);
|
CSA_ASSERT(this, IsAccessorPair(accessor_pair));
|
Node* getter = LoadObjectField(accessor_pair, AccessorPair::kGetterOffset);
|
CSA_ASSERT(this, Word32BinaryNot(IsTheHole(getter)));
|
|
Callable callable = CodeFactory::Call(isolate());
|
exit_point->Return(CallJS(callable, p->context, getter, p->receiver));
|
}
|
|
BIND(&native_data_property);
|
HandleLoadCallbackProperty(p, CAST(holder), handler_word, exit_point);
|
|
BIND(&api_getter);
|
HandleLoadAccessor(p, CAST(holder), handler_word, CAST(handler), handler_kind,
|
exit_point);
|
|
BIND(&proxy);
|
{
|
VARIABLE(var_index, MachineType::PointerRepresentation());
|
VARIABLE(var_unique, MachineRepresentation::kTagged);
|
|
Label if_index(this), if_unique_name(this),
|
to_name_failed(this, Label::kDeferred);
|
|
if (support_elements == kSupportElements) {
|
DCHECK_NE(on_nonexistent, OnNonExistent::kThrowReferenceError);
|
|
TryToName(p->name, &if_index, &var_index, &if_unique_name, &var_unique,
|
&to_name_failed);
|
|
BIND(&if_unique_name);
|
exit_point->ReturnCallStub(
|
Builtins::CallableFor(isolate(), Builtins::kProxyGetProperty),
|
p->context, holder, var_unique.value(), p->receiver,
|
SmiConstant(on_nonexistent));
|
|
BIND(&if_index);
|
// TODO(mslekova): introduce TryToName that doesn't try to compute
|
// the intptr index value
|
Goto(&to_name_failed);
|
|
BIND(&to_name_failed);
|
exit_point->ReturnCallRuntime(Runtime::kGetPropertyWithReceiver,
|
p->context, holder, p->name, p->receiver,
|
SmiConstant(on_nonexistent));
|
} else {
|
exit_point->ReturnCallStub(
|
Builtins::CallableFor(isolate(), Builtins::kProxyGetProperty),
|
p->context, holder, p->name, p->receiver,
|
SmiConstant(on_nonexistent));
|
}
|
}
|
|
BIND(&global);
|
{
|
CSA_ASSERT(this, IsPropertyCell(holder));
|
// Ensure the property cell doesn't contain the hole.
|
Node* value = LoadObjectField(holder, PropertyCell::kValueOffset);
|
Node* details =
|
LoadAndUntagToWord32ObjectField(holder, PropertyCell::kDetailsOffset);
|
GotoIf(IsTheHole(value), miss);
|
|
exit_point->Return(
|
CallGetterIfAccessor(value, details, p->context, p->receiver, miss));
|
}
|
|
BIND(&interceptor);
|
{
|
Comment("load_interceptor");
|
exit_point->ReturnCallRuntime(Runtime::kLoadPropertyWithInterceptor,
|
p->context, p->name, p->receiver, holder,
|
p->slot, p->vector);
|
}
|
|
BIND(&module_export);
|
{
|
Comment("module export");
|
Node* index = DecodeWord<LoadHandler::ExportsIndexBits>(handler_word);
|
Node* module =
|
LoadObjectField(p->receiver, JSModuleNamespace::kModuleOffset,
|
MachineType::TaggedPointer());
|
TNode<ObjectHashTable> exports = CAST(LoadObjectField(
|
module, Module::kExportsOffset, MachineType::TaggedPointer()));
|
Node* cell = LoadFixedArrayElement(exports, index);
|
// The handler is only installed for exports that exist.
|
CSA_ASSERT(this, IsCell(cell));
|
Node* value = LoadCellValue(cell);
|
Label is_the_hole(this, Label::kDeferred);
|
GotoIf(IsTheHole(value), &is_the_hole);
|
exit_point->Return(value);
|
|
BIND(&is_the_hole);
|
{
|
Node* message = SmiConstant(MessageTemplate::kNotDefined);
|
exit_point->ReturnCallRuntime(Runtime::kThrowReferenceError, p->context,
|
message, p->name);
|
}
|
}
|
|
BIND(&rebox_double);
|
exit_point->Return(AllocateHeapNumberWithValue(var_double_value.value()));
|
}
|
|
// Performs actions common to both load and store handlers:
|
// 1. Checks prototype validity cell.
|
// 2. If |on_code_handler| is provided, then it checks if the sub handler is
|
// a smi or code and if it's a code then it calls |on_code_handler| to
|
// generate a code that handles Code handlers.
|
// If |on_code_handler| is not provided, then only smi sub handler are
|
// expected.
|
// 3. Does access check on receiver if ICHandler::DoAccessCheckOnReceiverBits
|
// bit is set in the smi handler.
|
// 4. Does dictionary lookup on receiver if ICHandler::LookupOnReceiverBits bit
|
// is set in the smi handler. If |on_found_on_receiver| is provided then
|
// it calls it to generate a code that handles the "found on receiver case"
|
// or just misses if the |on_found_on_receiver| is not provided.
|
// 5. Falls through in a case of a smi handler which is returned from this
|
// function (tagged!).
|
// TODO(ishell): Remove templatezation once we move common bits from
|
// Load/StoreHandler to the base class.
|
template <typename ICHandler, typename ICParameters>
|
Node* AccessorAssembler::HandleProtoHandler(
|
const ICParameters* p, Node* handler, const OnCodeHandler& on_code_handler,
|
const OnFoundOnReceiver& on_found_on_receiver, Label* miss,
|
ICMode ic_mode) {
|
//
|
// Check prototype validity cell.
|
//
|
{
|
Node* maybe_validity_cell =
|
LoadObjectField(handler, ICHandler::kValidityCellOffset);
|
CheckPrototypeValidityCell(maybe_validity_cell, miss);
|
}
|
|
//
|
// Check smi handler bits.
|
//
|
{
|
Node* smi_or_code_handler =
|
LoadObjectField(handler, ICHandler::kSmiHandlerOffset);
|
if (on_code_handler) {
|
Label if_smi_handler(this);
|
GotoIf(TaggedIsSmi(smi_or_code_handler), &if_smi_handler);
|
|
CSA_ASSERT(this, IsCodeMap(LoadMap(smi_or_code_handler)));
|
on_code_handler(smi_or_code_handler);
|
|
BIND(&if_smi_handler);
|
} else {
|
CSA_ASSERT(this, TaggedIsSmi(smi_or_code_handler));
|
}
|
Node* handler_flags = SmiUntag(smi_or_code_handler);
|
|
// Lookup on receiver and access checks are not necessary for global ICs
|
// because in the former case the validity cell check guards modifications
|
// of the global object and the latter is not applicable to the global
|
// object.
|
int mask = ICHandler::LookupOnReceiverBits::kMask |
|
ICHandler::DoAccessCheckOnReceiverBits::kMask;
|
if (ic_mode == ICMode::kGlobalIC) {
|
CSA_ASSERT(this, IsClearWord(handler_flags, mask));
|
} else {
|
DCHECK_EQ(ICMode::kNonGlobalIC, ic_mode);
|
|
Label done(this), if_do_access_check(this), if_lookup_on_receiver(this);
|
GotoIf(IsClearWord(handler_flags, mask), &done);
|
// Only one of the bits can be set at a time.
|
CSA_ASSERT(this,
|
WordNotEqual(WordAnd(handler_flags, IntPtrConstant(mask)),
|
IntPtrConstant(mask)));
|
Branch(IsSetWord<LoadHandler::DoAccessCheckOnReceiverBits>(handler_flags),
|
&if_do_access_check, &if_lookup_on_receiver);
|
|
BIND(&if_do_access_check);
|
{
|
TNode<MaybeObject> data2 = LoadHandlerDataField(handler, 2);
|
CSA_ASSERT(this, IsWeakOrClearedHeapObject(data2));
|
TNode<Object> expected_native_context = ToWeakHeapObject(data2, miss);
|
EmitAccessCheck(expected_native_context, p->context, p->receiver, &done,
|
miss);
|
}
|
|
// Dictionary lookup on receiver is not necessary for Load/StoreGlobalIC
|
// because prototype validity cell check already guards modifications of
|
// the global object.
|
BIND(&if_lookup_on_receiver);
|
{
|
DCHECK_EQ(ICMode::kNonGlobalIC, ic_mode);
|
CSA_ASSERT(this, Word32BinaryNot(HasInstanceType(
|
p->receiver, JS_GLOBAL_OBJECT_TYPE)));
|
|
TNode<NameDictionary> properties =
|
CAST(LoadSlowProperties(p->receiver));
|
TVARIABLE(IntPtrT, var_name_index);
|
Label found(this, &var_name_index);
|
NameDictionaryLookup<NameDictionary>(properties, CAST(p->name), &found,
|
&var_name_index, &done);
|
BIND(&found);
|
{
|
if (on_found_on_receiver) {
|
on_found_on_receiver(properties, var_name_index.value());
|
} else {
|
Goto(miss);
|
}
|
}
|
}
|
|
BIND(&done);
|
}
|
return smi_or_code_handler;
|
}
|
}
|
|
void AccessorAssembler::HandleLoadICProtoHandler(
|
const LoadICParameters* p, Node* handler, Variable* var_holder,
|
Variable* var_smi_handler, Label* if_smi_handler, Label* miss,
|
ExitPoint* exit_point, ICMode ic_mode) {
|
DCHECK_EQ(MachineRepresentation::kTagged, var_holder->rep());
|
DCHECK_EQ(MachineRepresentation::kTagged, var_smi_handler->rep());
|
|
Node* smi_handler = HandleProtoHandler<LoadHandler>(
|
p, handler,
|
// Code sub-handlers are not expected in LoadICs, so no |on_code_handler|.
|
nullptr,
|
// on_found_on_receiver
|
[=](Node* properties, Node* name_index) {
|
VARIABLE(var_details, MachineRepresentation::kWord32);
|
VARIABLE(var_value, MachineRepresentation::kTagged);
|
LoadPropertyFromNameDictionary(properties, name_index, &var_details,
|
&var_value);
|
Node* value =
|
CallGetterIfAccessor(var_value.value(), var_details.value(),
|
p->context, p->receiver, miss);
|
exit_point->Return(value);
|
},
|
miss, ic_mode);
|
|
TNode<MaybeObject> maybe_holder = LoadHandlerDataField(handler, 1);
|
|
Label load_from_cached_holder(this), done(this);
|
|
Branch(IsStrongReferenceTo(maybe_holder, NullConstant()), &done,
|
&load_from_cached_holder);
|
|
BIND(&load_from_cached_holder);
|
{
|
// For regular holders, having passed the receiver map check and the
|
// validity cell check implies that |holder| is alive. However, for global
|
// object receivers, |maybe_holder| may be cleared.
|
CSA_ASSERT(this, IsWeakOrClearedHeapObject(maybe_holder));
|
Node* holder = ToWeakHeapObject(maybe_holder, miss);
|
|
var_holder->Bind(holder);
|
Goto(&done);
|
}
|
|
BIND(&done);
|
{
|
var_smi_handler->Bind(smi_handler);
|
Goto(if_smi_handler);
|
}
|
}
|
|
void AccessorAssembler::EmitAccessCheck(Node* expected_native_context,
|
Node* context, Node* receiver,
|
Label* can_access, Label* miss) {
|
CSA_ASSERT(this, IsNativeContext(expected_native_context));
|
|
Node* native_context = LoadNativeContext(context);
|
GotoIf(WordEqual(expected_native_context, native_context), can_access);
|
// If the receiver is not a JSGlobalProxy then we miss.
|
GotoIfNot(IsJSGlobalProxy(receiver), miss);
|
// For JSGlobalProxy receiver try to compare security tokens of current
|
// and expected native contexts.
|
Node* expected_token = LoadContextElement(expected_native_context,
|
Context::SECURITY_TOKEN_INDEX);
|
Node* current_token =
|
LoadContextElement(native_context, Context::SECURITY_TOKEN_INDEX);
|
Branch(WordEqual(expected_token, current_token), can_access, miss);
|
}
|
|
void AccessorAssembler::JumpIfDataProperty(Node* details, Label* writable,
|
Label* readonly) {
|
// Accessor properties never have the READ_ONLY attribute set.
|
GotoIf(IsSetWord32(details, PropertyDetails::kAttributesReadOnlyMask),
|
readonly);
|
Node* kind = DecodeWord32<PropertyDetails::KindField>(details);
|
GotoIf(Word32Equal(kind, Int32Constant(kData)), writable);
|
// Fall through if it's an accessor property.
|
}
|
|
void AccessorAssembler::HandleStoreICNativeDataProperty(
|
const StoreICParameters* p, Node* holder, Node* handler_word) {
|
Comment("native_data_property_store");
|
Node* descriptor = DecodeWord<StoreHandler::DescriptorBits>(handler_word);
|
Node* accessor_info = LoadDescriptorValue(LoadMap(holder), descriptor);
|
CSA_CHECK(this, IsAccessorInfo(accessor_info));
|
|
Node* language_mode = GetLanguageMode(p->vector, p->slot);
|
|
TailCallRuntime(Runtime::kStoreCallbackProperty, p->context, p->receiver,
|
holder, accessor_info, p->name, p->value, language_mode);
|
}
|
|
void AccessorAssembler::HandleStoreICHandlerCase(
|
const StoreICParameters* p, TNode<MaybeObject> handler, Label* miss,
|
ICMode ic_mode, ElementSupport support_elements) {
|
Label if_smi_handler(this), if_nonsmi_handler(this);
|
Label if_proto_handler(this), if_element_handler(this), call_handler(this),
|
store_transition_or_global(this);
|
|
Branch(TaggedIsSmi(handler), &if_smi_handler, &if_nonsmi_handler);
|
|
// |handler| is a Smi, encoding what to do. See SmiHandler methods
|
// for the encoding format.
|
BIND(&if_smi_handler);
|
{
|
Node* holder = p->receiver;
|
Node* handler_word = SmiUntag(CAST(handler));
|
|
Label if_fast_smi(this), if_proxy(this);
|
|
STATIC_ASSERT(StoreHandler::kGlobalProxy + 1 == StoreHandler::kNormal);
|
STATIC_ASSERT(StoreHandler::kNormal + 1 == StoreHandler::kProxy);
|
STATIC_ASSERT(StoreHandler::kProxy + 1 == StoreHandler::kKindsNumber);
|
|
Node* handler_kind = DecodeWord<StoreHandler::KindBits>(handler_word);
|
GotoIf(IntPtrLessThan(handler_kind,
|
IntPtrConstant(StoreHandler::kGlobalProxy)),
|
&if_fast_smi);
|
GotoIf(WordEqual(handler_kind, IntPtrConstant(StoreHandler::kProxy)),
|
&if_proxy);
|
CSA_ASSERT(this,
|
WordEqual(handler_kind, IntPtrConstant(StoreHandler::kNormal)));
|
TNode<NameDictionary> properties = CAST(LoadSlowProperties(holder));
|
|
TVARIABLE(IntPtrT, var_name_index);
|
Label dictionary_found(this, &var_name_index);
|
NameDictionaryLookup<NameDictionary>(
|
properties, CAST(p->name), &dictionary_found, &var_name_index, miss);
|
BIND(&dictionary_found);
|
{
|
Node* details = LoadDetailsByKeyIndex<NameDictionary>(
|
properties, var_name_index.value());
|
// Check that the property is a writable data property (no accessor).
|
const int kTypeAndReadOnlyMask = PropertyDetails::KindField::kMask |
|
PropertyDetails::kAttributesReadOnlyMask;
|
STATIC_ASSERT(kData == 0);
|
GotoIf(IsSetWord32(details, kTypeAndReadOnlyMask), miss);
|
|
StoreValueByKeyIndex<NameDictionary>(properties, var_name_index.value(),
|
p->value);
|
Return(p->value);
|
}
|
|
BIND(&if_fast_smi);
|
{
|
Node* handler_kind = DecodeWord<StoreHandler::KindBits>(handler_word);
|
|
Label data(this), accessor(this), native_data_property(this);
|
GotoIf(WordEqual(handler_kind, IntPtrConstant(StoreHandler::kAccessor)),
|
&accessor);
|
Branch(WordEqual(handler_kind,
|
IntPtrConstant(StoreHandler::kNativeDataProperty)),
|
&native_data_property, &data);
|
|
BIND(&accessor);
|
HandleStoreAccessor(p, holder, handler_word);
|
|
BIND(&native_data_property);
|
HandleStoreICNativeDataProperty(p, holder, handler_word);
|
|
BIND(&data);
|
// Handle non-transitioning field stores.
|
HandleStoreICSmiHandlerCase(handler_word, holder, p->value, miss);
|
}
|
|
BIND(&if_proxy);
|
HandleStoreToProxy(p, holder, miss, support_elements);
|
}
|
|
BIND(&if_nonsmi_handler);
|
{
|
GotoIf(IsWeakOrClearedHeapObject(handler), &store_transition_or_global);
|
TNode<HeapObject> strong_handler = CAST(handler);
|
TNode<Map> handler_map = LoadMap(strong_handler);
|
Branch(IsCodeMap(handler_map), &call_handler, &if_proto_handler);
|
|
BIND(&if_proto_handler);
|
{
|
HandleStoreICProtoHandler(p, CAST(strong_handler), miss, ic_mode,
|
support_elements);
|
}
|
|
// |handler| is a heap object. Must be code, call it.
|
BIND(&call_handler);
|
{
|
TailCallStub(StoreWithVectorDescriptor{}, CAST(strong_handler),
|
CAST(p->context), p->receiver, p->name, p->value, p->slot,
|
p->vector);
|
}
|
}
|
|
BIND(&store_transition_or_global);
|
{
|
// Load value or miss if the {handler} weak cell is cleared.
|
CSA_ASSERT(this, IsWeakOrClearedHeapObject(handler));
|
TNode<HeapObject> map_or_property_cell = ToWeakHeapObject(handler, miss);
|
|
Label store_global(this), store_transition(this);
|
Branch(IsMap(map_or_property_cell), &store_transition, &store_global);
|
|
BIND(&store_global);
|
{
|
TNode<PropertyCell> property_cell = CAST(map_or_property_cell);
|
ExitPoint direct_exit(this);
|
StoreGlobalIC_PropertyCellCase(property_cell, p->value, &direct_exit,
|
miss);
|
}
|
BIND(&store_transition);
|
{
|
TNode<Map> map = CAST(map_or_property_cell);
|
HandleStoreICTransitionMapHandlerCase(p, map, miss, false);
|
Return(p->value);
|
}
|
}
|
}
|
|
void AccessorAssembler::HandleStoreICTransitionMapHandlerCase(
|
const StoreICParameters* p, TNode<Map> transition_map, Label* miss,
|
bool validate_transition_handler) {
|
Node* maybe_validity_cell =
|
LoadObjectField(transition_map, Map::kPrototypeValidityCellOffset);
|
CheckPrototypeValidityCell(maybe_validity_cell, miss);
|
|
TNode<Uint32T> bitfield3 = LoadMapBitField3(transition_map);
|
CSA_ASSERT(this, IsClearWord32<Map::IsDictionaryMapBit>(bitfield3));
|
GotoIf(IsSetWord32<Map::IsDeprecatedBit>(bitfield3), miss);
|
|
// Load last descriptor details.
|
Node* nof = DecodeWordFromWord32<Map::NumberOfOwnDescriptorsBits>(bitfield3);
|
CSA_ASSERT(this, WordNotEqual(nof, IntPtrConstant(0)));
|
TNode<DescriptorArray> descriptors = LoadMapDescriptors(transition_map);
|
|
Node* factor = IntPtrConstant(DescriptorArray::kEntrySize);
|
TNode<IntPtrT> last_key_index = UncheckedCast<IntPtrT>(IntPtrAdd(
|
IntPtrConstant(DescriptorArray::ToKeyIndex(-1)), IntPtrMul(nof, factor)));
|
if (validate_transition_handler) {
|
Node* key = LoadWeakFixedArrayElement(descriptors, last_key_index);
|
GotoIf(WordNotEqual(key, p->name), miss);
|
} else {
|
CSA_ASSERT(this,
|
WordEqual(BitcastMaybeObjectToWord(LoadWeakFixedArrayElement(
|
descriptors, last_key_index)),
|
p->name));
|
}
|
Node* details = LoadDetailsByKeyIndex(descriptors, last_key_index);
|
if (validate_transition_handler) {
|
// Follow transitions only in the following cases:
|
// 1) name is a non-private symbol and attributes equal to NONE,
|
// 2) name is a private symbol and attributes equal to DONT_ENUM.
|
Label attributes_ok(this);
|
const int kAttributesDontDeleteReadOnlyMask =
|
PropertyDetails::kAttributesDontDeleteMask |
|
PropertyDetails::kAttributesReadOnlyMask;
|
// Both DontDelete and ReadOnly attributes must not be set.
|
GotoIf(IsSetWord32(details, kAttributesDontDeleteReadOnlyMask), miss);
|
|
// DontEnum attribute is allowed only for private symbols and vice versa.
|
Branch(Word32Equal(
|
IsSetWord32(details, PropertyDetails::kAttributesDontEnumMask),
|
IsPrivateSymbol(p->name)),
|
&attributes_ok, miss);
|
|
BIND(&attributes_ok);
|
}
|
|
OverwriteExistingFastDataProperty(p->receiver, transition_map, descriptors,
|
last_key_index, details, p->value, miss,
|
true);
|
}
|
|
void AccessorAssembler::CheckFieldType(TNode<DescriptorArray> descriptors,
|
Node* name_index, Node* representation,
|
Node* value, Label* bailout) {
|
Label r_smi(this), r_double(this), r_heapobject(this), all_fine(this);
|
// Ignore FLAG_track_fields etc. and always emit code for all checks,
|
// because this builtin is part of the snapshot and therefore should
|
// be flag independent.
|
GotoIf(Word32Equal(representation, Int32Constant(Representation::kSmi)),
|
&r_smi);
|
GotoIf(Word32Equal(representation, Int32Constant(Representation::kDouble)),
|
&r_double);
|
GotoIf(
|
Word32Equal(representation, Int32Constant(Representation::kHeapObject)),
|
&r_heapobject);
|
GotoIf(Word32Equal(representation, Int32Constant(Representation::kNone)),
|
bailout);
|
CSA_ASSERT(this, Word32Equal(representation,
|
Int32Constant(Representation::kTagged)));
|
Goto(&all_fine);
|
|
BIND(&r_smi);
|
{ Branch(TaggedIsSmi(value), &all_fine, bailout); }
|
|
BIND(&r_double);
|
{
|
GotoIf(TaggedIsSmi(value), &all_fine);
|
Node* value_map = LoadMap(value);
|
// While supporting mutable HeapNumbers would be straightforward, such
|
// objects should not end up here anyway.
|
CSA_ASSERT(this,
|
WordNotEqual(value_map,
|
LoadRoot(Heap::kMutableHeapNumberMapRootIndex)));
|
Branch(IsHeapNumberMap(value_map), &all_fine, bailout);
|
}
|
|
BIND(&r_heapobject);
|
{
|
GotoIf(TaggedIsSmi(value), bailout);
|
TNode<MaybeObject> field_type = LoadFieldTypeByKeyIndex(
|
descriptors, UncheckedCast<IntPtrT>(name_index));
|
intptr_t kNoneType = reinterpret_cast<intptr_t>(FieldType::None());
|
intptr_t kAnyType = reinterpret_cast<intptr_t>(FieldType::Any());
|
DCHECK_NE(kNoneType, kClearedWeakHeapObject);
|
DCHECK_NE(kAnyType, kClearedWeakHeapObject);
|
// FieldType::None can't hold any value.
|
GotoIf(WordEqual(BitcastMaybeObjectToWord(field_type),
|
IntPtrConstant(kNoneType)),
|
bailout);
|
// FieldType::Any can hold any value.
|
GotoIf(WordEqual(BitcastMaybeObjectToWord(field_type),
|
IntPtrConstant(kAnyType)),
|
&all_fine);
|
// Cleared weak references count as FieldType::None, which can't hold any
|
// value.
|
TNode<Map> field_type_map = CAST(ToWeakHeapObject(field_type, bailout));
|
// FieldType::Class(...) performs a map check.
|
Branch(WordEqual(LoadMap(value), field_type_map), &all_fine, bailout);
|
}
|
|
BIND(&all_fine);
|
}
|
|
void AccessorAssembler::OverwriteExistingFastDataProperty(
|
Node* object, Node* object_map, Node* descriptors,
|
Node* descriptor_name_index, Node* details, Node* value, Label* slow,
|
bool do_transitioning_store) {
|
Label done(this), if_field(this), if_descriptor(this);
|
|
CSA_ASSERT(this,
|
Word32Equal(DecodeWord32<PropertyDetails::KindField>(details),
|
Int32Constant(kData)));
|
|
Branch(Word32Equal(DecodeWord32<PropertyDetails::LocationField>(details),
|
Int32Constant(kField)),
|
&if_field, &if_descriptor);
|
|
BIND(&if_field);
|
{
|
if (FLAG_track_constant_fields && !do_transitioning_store) {
|
// TODO(ishell): Taking the slow path is not necessary if new and old
|
// values are identical.
|
GotoIf(Word32Equal(
|
DecodeWord32<PropertyDetails::ConstnessField>(details),
|
Int32Constant(static_cast<int32_t>(VariableMode::kConst))),
|
slow);
|
}
|
|
Node* representation =
|
DecodeWord32<PropertyDetails::RepresentationField>(details);
|
|
CheckFieldType(CAST(descriptors), descriptor_name_index, representation,
|
value, slow);
|
|
Node* field_index =
|
DecodeWordFromWord32<PropertyDetails::FieldIndexField>(details);
|
field_index = IntPtrAdd(field_index,
|
LoadMapInobjectPropertiesStartInWords(object_map));
|
Node* instance_size_in_words = LoadMapInstanceSizeInWords(object_map);
|
|
Label inobject(this), backing_store(this);
|
Branch(UintPtrLessThan(field_index, instance_size_in_words), &inobject,
|
&backing_store);
|
|
BIND(&inobject);
|
{
|
Node* field_offset = TimesPointerSize(field_index);
|
Label tagged_rep(this), double_rep(this);
|
Branch(
|
Word32Equal(representation, Int32Constant(Representation::kDouble)),
|
&double_rep, &tagged_rep);
|
BIND(&double_rep);
|
{
|
Node* double_value = ChangeNumberToFloat64(value);
|
if (FLAG_unbox_double_fields) {
|
if (do_transitioning_store) {
|
StoreMap(object, object_map);
|
}
|
StoreObjectFieldNoWriteBarrier(object, field_offset, double_value,
|
MachineRepresentation::kFloat64);
|
} else {
|
if (do_transitioning_store) {
|
Node* mutable_heap_number =
|
AllocateMutableHeapNumberWithValue(double_value);
|
StoreMap(object, object_map);
|
StoreObjectField(object, field_offset, mutable_heap_number);
|
} else {
|
Node* mutable_heap_number = LoadObjectField(object, field_offset);
|
StoreHeapNumberValue(mutable_heap_number, double_value);
|
}
|
}
|
Goto(&done);
|
}
|
|
BIND(&tagged_rep);
|
{
|
if (do_transitioning_store) {
|
StoreMap(object, object_map);
|
}
|
StoreObjectField(object, field_offset, value);
|
Goto(&done);
|
}
|
}
|
|
BIND(&backing_store);
|
{
|
Node* backing_store_index =
|
IntPtrSub(field_index, instance_size_in_words);
|
|
if (do_transitioning_store) {
|
// Allocate mutable heap number before extending properties backing
|
// store to ensure that heap verifier will not see the heap in
|
// inconsistent state.
|
VARIABLE(var_value, MachineRepresentation::kTagged, value);
|
{
|
Label cont(this);
|
GotoIf(Word32NotEqual(representation,
|
Int32Constant(Representation::kDouble)),
|
&cont);
|
{
|
Node* double_value = ChangeNumberToFloat64(value);
|
Node* mutable_heap_number =
|
AllocateMutableHeapNumberWithValue(double_value);
|
var_value.Bind(mutable_heap_number);
|
Goto(&cont);
|
}
|
BIND(&cont);
|
}
|
|
TNode<PropertyArray> properties =
|
CAST(ExtendPropertiesBackingStore(object, backing_store_index));
|
StorePropertyArrayElement(properties, backing_store_index,
|
var_value.value());
|
StoreMap(object, object_map);
|
Goto(&done);
|
|
} else {
|
Label tagged_rep(this), double_rep(this);
|
TNode<PropertyArray> properties = CAST(LoadFastProperties(object));
|
Branch(
|
Word32Equal(representation, Int32Constant(Representation::kDouble)),
|
&double_rep, &tagged_rep);
|
BIND(&double_rep);
|
{
|
Node* mutable_heap_number =
|
LoadPropertyArrayElement(properties, backing_store_index);
|
Node* double_value = ChangeNumberToFloat64(value);
|
StoreHeapNumberValue(mutable_heap_number, double_value);
|
Goto(&done);
|
}
|
BIND(&tagged_rep);
|
{
|
StorePropertyArrayElement(properties, backing_store_index, value);
|
Goto(&done);
|
}
|
}
|
}
|
}
|
|
BIND(&if_descriptor);
|
{
|
// Check that constant matches value.
|
Node* constant = LoadValueByKeyIndex(
|
CAST(descriptors), UncheckedCast<IntPtrT>(descriptor_name_index));
|
GotoIf(WordNotEqual(value, constant), slow);
|
|
if (do_transitioning_store) {
|
StoreMap(object, object_map);
|
}
|
Goto(&done);
|
}
|
BIND(&done);
|
}
|
|
void AccessorAssembler::CheckPrototypeValidityCell(Node* maybe_validity_cell,
|
Label* miss) {
|
Label done(this);
|
GotoIf(WordEqual(maybe_validity_cell, SmiConstant(Map::kPrototypeChainValid)),
|
&done);
|
CSA_ASSERT(this, TaggedIsNotSmi(maybe_validity_cell));
|
|
Node* cell_value = LoadObjectField(maybe_validity_cell, Cell::kValueOffset);
|
Branch(WordEqual(cell_value, SmiConstant(Map::kPrototypeChainValid)), &done,
|
miss);
|
|
BIND(&done);
|
}
|
|
void AccessorAssembler::HandleStoreAccessor(const StoreICParameters* p,
|
Node* holder, Node* handler_word) {
|
Comment("accessor_store");
|
Node* descriptor = DecodeWord<StoreHandler::DescriptorBits>(handler_word);
|
Node* accessor_pair = LoadDescriptorValue(LoadMap(holder), descriptor);
|
CSA_ASSERT(this, IsAccessorPair(accessor_pair));
|
Node* setter = LoadObjectField(accessor_pair, AccessorPair::kSetterOffset);
|
CSA_ASSERT(this, Word32BinaryNot(IsTheHole(setter)));
|
|
Callable callable = CodeFactory::Call(isolate());
|
Return(CallJS(callable, p->context, setter, p->receiver, p->value));
|
}
|
|
void AccessorAssembler::HandleStoreICProtoHandler(
|
const StoreICParameters* p, TNode<StoreHandler> handler, Label* miss,
|
ICMode ic_mode, ElementSupport support_elements) {
|
Comment("HandleStoreICProtoHandler");
|
|
OnCodeHandler on_code_handler;
|
if (support_elements == kSupportElements) {
|
// Code sub-handlers are expected only in KeyedStoreICs.
|
on_code_handler = [=](Node* code_handler) {
|
// This is either element store or transitioning element store.
|
Label if_element_store(this), if_transitioning_element_store(this);
|
Branch(IsStoreHandler0Map(LoadMap(handler)), &if_element_store,
|
&if_transitioning_element_store);
|
BIND(&if_element_store);
|
{
|
TailCallStub(StoreWithVectorDescriptor{}, code_handler, p->context,
|
p->receiver, p->name, p->value, p->slot, p->vector);
|
}
|
|
BIND(&if_transitioning_element_store);
|
{
|
TNode<MaybeObject> maybe_transition_map =
|
LoadHandlerDataField(handler, 1);
|
TNode<Map> transition_map =
|
CAST(ToWeakHeapObject(maybe_transition_map, miss));
|
|
GotoIf(IsDeprecatedMap(transition_map), miss);
|
|
TailCallStub(StoreTransitionDescriptor{}, code_handler, p->context,
|
p->receiver, p->name, transition_map, p->value, p->slot,
|
p->vector);
|
}
|
};
|
}
|
|
Node* smi_handler = HandleProtoHandler<StoreHandler>(
|
p, handler, on_code_handler,
|
// on_found_on_receiver
|
[=](Node* properties, Node* name_index) {
|
Node* details =
|
LoadDetailsByKeyIndex<NameDictionary>(properties, name_index);
|
// Check that the property is a writable data property (no accessor).
|
const int kTypeAndReadOnlyMask =
|
PropertyDetails::KindField::kMask |
|
PropertyDetails::kAttributesReadOnlyMask;
|
STATIC_ASSERT(kData == 0);
|
GotoIf(IsSetWord32(details, kTypeAndReadOnlyMask), miss);
|
|
StoreValueByKeyIndex<NameDictionary>(
|
CAST(properties), UncheckedCast<IntPtrT>(name_index), p->value);
|
Return(p->value);
|
},
|
miss, ic_mode);
|
|
{
|
Label if_add_normal(this), if_store_global_proxy(this), if_api_setter(this),
|
if_accessor(this), if_native_data_property(this);
|
|
CSA_ASSERT(this, TaggedIsSmi(smi_handler));
|
Node* handler_word = SmiUntag(smi_handler);
|
|
Node* handler_kind = DecodeWord<StoreHandler::KindBits>(handler_word);
|
GotoIf(WordEqual(handler_kind, IntPtrConstant(StoreHandler::kNormal)),
|
&if_add_normal);
|
|
TNode<MaybeObject> maybe_holder = LoadHandlerDataField(handler, 1);
|
CSA_ASSERT(this, IsWeakOrClearedHeapObject(maybe_holder));
|
TNode<Object> holder = ToWeakHeapObject(maybe_holder, miss);
|
|
GotoIf(WordEqual(handler_kind, IntPtrConstant(StoreHandler::kGlobalProxy)),
|
&if_store_global_proxy);
|
|
GotoIf(WordEqual(handler_kind, IntPtrConstant(StoreHandler::kAccessor)),
|
&if_accessor);
|
|
GotoIf(WordEqual(handler_kind,
|
IntPtrConstant(StoreHandler::kNativeDataProperty)),
|
&if_native_data_property);
|
|
GotoIf(WordEqual(handler_kind, IntPtrConstant(StoreHandler::kApiSetter)),
|
&if_api_setter);
|
|
GotoIf(WordEqual(handler_kind,
|
IntPtrConstant(StoreHandler::kApiSetterHolderIsPrototype)),
|
&if_api_setter);
|
|
CSA_ASSERT(this,
|
WordEqual(handler_kind, IntPtrConstant(StoreHandler::kProxy)));
|
HandleStoreToProxy(p, holder, miss, support_elements);
|
|
BIND(&if_add_normal);
|
{
|
// This is a case of "transitioning store" to a dictionary mode object
|
// when the property is still does not exist. The "existing property"
|
// case is covered above by LookupOnReceiver bit handling of the smi
|
// handler.
|
Label slow(this);
|
TNode<Map> receiver_map = LoadMap(p->receiver);
|
InvalidateValidityCellIfPrototype(receiver_map);
|
|
TNode<NameDictionary> properties = CAST(LoadSlowProperties(p->receiver));
|
Add<NameDictionary>(properties, CAST(p->name), p->value, &slow);
|
Return(p->value);
|
|
BIND(&slow);
|
TailCallRuntime(Runtime::kAddDictionaryProperty, p->context, p->receiver,
|
p->name, p->value);
|
}
|
|
BIND(&if_accessor);
|
HandleStoreAccessor(p, holder, handler_word);
|
|
BIND(&if_native_data_property);
|
HandleStoreICNativeDataProperty(p, holder, handler_word);
|
|
BIND(&if_api_setter);
|
{
|
Comment("api_setter");
|
CSA_ASSERT(this, TaggedIsNotSmi(handler));
|
Node* call_handler_info = holder;
|
|
// Context is stored either in data2 or data3 field depending on whether
|
// the access check is enabled for this handler or not.
|
TNode<MaybeObject> maybe_context = Select<MaybeObject>(
|
IsSetWord<LoadHandler::DoAccessCheckOnReceiverBits>(handler_word),
|
[=] { return LoadHandlerDataField(handler, 3); },
|
[=] { return LoadHandlerDataField(handler, 2); });
|
|
CSA_ASSERT(this, IsWeakOrClearedHeapObject(maybe_context));
|
TNode<Object> context =
|
Select<Object>(IsClearedWeakHeapObject(maybe_context),
|
[=] { return SmiConstant(0); },
|
[=] { return ToWeakHeapObject(maybe_context); });
|
|
Node* foreign = LoadObjectField(call_handler_info,
|
CallHandlerInfo::kJsCallbackOffset);
|
Node* callback = LoadObjectField(foreign, Foreign::kForeignAddressOffset,
|
MachineType::Pointer());
|
Node* data =
|
LoadObjectField(call_handler_info, CallHandlerInfo::kDataOffset);
|
|
VARIABLE(api_holder, MachineRepresentation::kTagged, p->receiver);
|
Label store(this);
|
GotoIf(WordEqual(handler_kind, IntPtrConstant(StoreHandler::kApiSetter)),
|
&store);
|
|
CSA_ASSERT(
|
this,
|
WordEqual(handler_kind,
|
IntPtrConstant(StoreHandler::kApiSetterHolderIsPrototype)));
|
|
api_holder.Bind(LoadMapPrototype(LoadMap(p->receiver)));
|
Goto(&store);
|
|
BIND(&store);
|
Callable callable = CodeFactory::CallApiCallback(isolate(), 1);
|
Return(CallStub(callable, nullptr, context, data, api_holder.value(),
|
callback, p->receiver, p->value));
|
}
|
|
BIND(&if_store_global_proxy);
|
{
|
ExitPoint direct_exit(this);
|
StoreGlobalIC_PropertyCellCase(holder, p->value, &direct_exit, miss);
|
}
|
}
|
}
|
|
Node* AccessorAssembler::GetLanguageMode(Node* vector, Node* slot) {
|
VARIABLE(var_language_mode, MachineRepresentation::kTaggedSigned,
|
SmiConstant(LanguageMode::kStrict));
|
Label language_mode_determined(this);
|
BranchIfStrictMode(vector, slot, &language_mode_determined);
|
var_language_mode.Bind(SmiConstant(LanguageMode::kSloppy));
|
Goto(&language_mode_determined);
|
BIND(&language_mode_determined);
|
return var_language_mode.value();
|
}
|
|
void AccessorAssembler::HandleStoreToProxy(const StoreICParameters* p,
|
Node* proxy, Label* miss,
|
ElementSupport support_elements) {
|
VARIABLE(var_index, MachineType::PointerRepresentation());
|
VARIABLE(var_unique, MachineRepresentation::kTagged);
|
|
Label if_index(this), if_unique_name(this),
|
to_name_failed(this, Label::kDeferred);
|
|
Node* language_mode = GetLanguageMode(p->vector, p->slot);
|
|
if (support_elements == kSupportElements) {
|
TryToName(p->name, &if_index, &var_index, &if_unique_name, &var_unique,
|
&to_name_failed);
|
|
BIND(&if_unique_name);
|
CallBuiltin(Builtins::kProxySetProperty, p->context, proxy,
|
var_unique.value(), p->value, p->receiver, language_mode);
|
Return(p->value);
|
|
// The index case is handled earlier by the runtime.
|
BIND(&if_index);
|
// TODO(mslekova): introduce TryToName that doesn't try to compute
|
// the intptr index value
|
Goto(&to_name_failed);
|
|
BIND(&to_name_failed);
|
TailCallRuntime(Runtime::kSetPropertyWithReceiver, p->context, proxy,
|
p->name, p->value, p->receiver, language_mode);
|
} else {
|
Node* name = ToName(p->context, p->name);
|
TailCallBuiltin(Builtins::kProxySetProperty, p->context, proxy, name,
|
p->value, p->receiver, language_mode);
|
}
|
}
|
|
void AccessorAssembler::HandleStoreICSmiHandlerCase(Node* handler_word,
|
Node* holder, Node* value,
|
Label* miss) {
|
Comment("field store");
|
#ifdef DEBUG
|
Node* handler_kind = DecodeWord<StoreHandler::KindBits>(handler_word);
|
if (FLAG_track_constant_fields) {
|
CSA_ASSERT(
|
this,
|
Word32Or(WordEqual(handler_kind, IntPtrConstant(StoreHandler::kField)),
|
WordEqual(handler_kind,
|
IntPtrConstant(StoreHandler::kConstField))));
|
} else {
|
CSA_ASSERT(this,
|
WordEqual(handler_kind, IntPtrConstant(StoreHandler::kField)));
|
}
|
#endif
|
|
Node* field_representation =
|
DecodeWord<StoreHandler::FieldRepresentationBits>(handler_word);
|
|
Label if_smi_field(this), if_double_field(this), if_heap_object_field(this),
|
if_tagged_field(this);
|
|
GotoIf(WordEqual(field_representation, IntPtrConstant(StoreHandler::kTagged)),
|
&if_tagged_field);
|
GotoIf(WordEqual(field_representation,
|
IntPtrConstant(StoreHandler::kHeapObject)),
|
&if_heap_object_field);
|
GotoIf(WordEqual(field_representation, IntPtrConstant(StoreHandler::kDouble)),
|
&if_double_field);
|
CSA_ASSERT(this, WordEqual(field_representation,
|
IntPtrConstant(StoreHandler::kSmi)));
|
Goto(&if_smi_field);
|
|
BIND(&if_tagged_field);
|
{
|
Comment("store tagged field");
|
HandleStoreFieldAndReturn(handler_word, holder, Representation::Tagged(),
|
value, miss);
|
}
|
|
BIND(&if_double_field);
|
{
|
Comment("store double field");
|
HandleStoreFieldAndReturn(handler_word, holder, Representation::Double(),
|
value, miss);
|
}
|
|
BIND(&if_heap_object_field);
|
{
|
Comment("store heap object field");
|
HandleStoreFieldAndReturn(handler_word, holder,
|
Representation::HeapObject(), value, miss);
|
}
|
|
BIND(&if_smi_field);
|
{
|
Comment("store smi field");
|
HandleStoreFieldAndReturn(handler_word, holder, Representation::Smi(),
|
value, miss);
|
}
|
}
|
|
void AccessorAssembler::HandleStoreFieldAndReturn(Node* handler_word,
|
Node* holder,
|
Representation representation,
|
Node* value, Label* miss) {
|
Node* prepared_value =
|
PrepareValueForStore(handler_word, holder, representation, value, miss);
|
|
Label if_inobject(this), if_out_of_object(this);
|
Branch(IsSetWord<StoreHandler::IsInobjectBits>(handler_word), &if_inobject,
|
&if_out_of_object);
|
|
BIND(&if_inobject);
|
{
|
StoreNamedField(handler_word, holder, true, representation, prepared_value,
|
miss);
|
Return(value);
|
}
|
|
BIND(&if_out_of_object);
|
{
|
StoreNamedField(handler_word, holder, false, representation, prepared_value,
|
miss);
|
Return(value);
|
}
|
}
|
|
Node* AccessorAssembler::PrepareValueForStore(Node* handler_word, Node* holder,
|
Representation representation,
|
Node* value, Label* bailout) {
|
if (representation.IsDouble()) {
|
value = TryTaggedToFloat64(value, bailout);
|
|
} else if (representation.IsHeapObject()) {
|
GotoIf(TaggedIsSmi(value), bailout);
|
|
Label done(this);
|
if (FLAG_track_constant_fields) {
|
// Skip field type check in favor of constant value check when storing
|
// to constant field.
|
GotoIf(WordEqual(DecodeWord<StoreHandler::KindBits>(handler_word),
|
IntPtrConstant(StoreHandler::kConstField)),
|
&done);
|
}
|
Node* descriptor = DecodeWord<StoreHandler::DescriptorBits>(handler_word);
|
TNode<MaybeObject> maybe_field_type =
|
LoadDescriptorValueOrFieldType(LoadMap(holder), descriptor);
|
|
GotoIf(TaggedIsSmi(maybe_field_type), &done);
|
// Check that value type matches the field type.
|
{
|
Node* field_type = ToWeakHeapObject(maybe_field_type, bailout);
|
Branch(WordEqual(LoadMap(value), field_type), &done, bailout);
|
}
|
BIND(&done);
|
|
} else if (representation.IsSmi()) {
|
GotoIfNot(TaggedIsSmi(value), bailout);
|
|
} else {
|
DCHECK(representation.IsTagged());
|
}
|
return value;
|
}
|
|
Node* AccessorAssembler::ExtendPropertiesBackingStore(Node* object,
|
Node* index) {
|
Comment("[ Extend storage");
|
|
ParameterMode mode = OptimalParameterMode();
|
|
// TODO(gsathya): Clean up the type conversions by creating smarter
|
// helpers that do the correct op based on the mode.
|
VARIABLE(var_properties, MachineRepresentation::kTaggedPointer);
|
VARIABLE(var_encoded_hash, MachineRepresentation::kWord32);
|
VARIABLE(var_length, ParameterRepresentation(mode));
|
|
Node* properties = LoadObjectField(object, JSObject::kPropertiesOrHashOffset);
|
var_properties.Bind(properties);
|
|
Label if_smi_hash(this), if_property_array(this), extend_store(this);
|
Branch(TaggedIsSmi(properties), &if_smi_hash, &if_property_array);
|
|
BIND(&if_smi_hash);
|
{
|
Node* hash = SmiToInt32(properties);
|
Node* encoded_hash =
|
Word32Shl(hash, Int32Constant(PropertyArray::HashField::kShift));
|
var_encoded_hash.Bind(encoded_hash);
|
var_length.Bind(IntPtrOrSmiConstant(0, mode));
|
var_properties.Bind(EmptyFixedArrayConstant());
|
Goto(&extend_store);
|
}
|
|
BIND(&if_property_array);
|
{
|
Node* length_and_hash_int32 = LoadAndUntagToWord32ObjectField(
|
var_properties.value(), PropertyArray::kLengthAndHashOffset);
|
var_encoded_hash.Bind(Word32And(
|
length_and_hash_int32, Int32Constant(PropertyArray::HashField::kMask)));
|
Node* length_intptr = ChangeInt32ToIntPtr(
|
Word32And(length_and_hash_int32,
|
Int32Constant(PropertyArray::LengthField::kMask)));
|
Node* length = IntPtrToParameter(length_intptr, mode);
|
var_length.Bind(length);
|
Goto(&extend_store);
|
}
|
|
BIND(&extend_store);
|
{
|
VARIABLE(var_new_properties, MachineRepresentation::kTaggedPointer,
|
var_properties.value());
|
Label done(this);
|
// Previous property deletion could have left behind unused backing store
|
// capacity even for a map that think it doesn't have any unused fields.
|
// Perform a bounds check to see if we actually have to grow the array.
|
GotoIf(UintPtrLessThan(index, ParameterToIntPtr(var_length.value(), mode)),
|
&done);
|
|
Node* delta = IntPtrOrSmiConstant(JSObject::kFieldsAdded, mode);
|
Node* new_capacity = IntPtrOrSmiAdd(var_length.value(), delta, mode);
|
|
// Grow properties array.
|
DCHECK(kMaxNumberOfDescriptors + JSObject::kFieldsAdded <
|
FixedArrayBase::GetMaxLengthForNewSpaceAllocation(PACKED_ELEMENTS));
|
// The size of a new properties backing store is guaranteed to be small
|
// enough that the new backing store will be allocated in new space.
|
CSA_ASSERT(this,
|
UintPtrOrSmiLessThan(
|
new_capacity,
|
IntPtrOrSmiConstant(
|
kMaxNumberOfDescriptors + JSObject::kFieldsAdded, mode),
|
mode));
|
|
Node* new_properties = AllocatePropertyArray(new_capacity, mode);
|
var_new_properties.Bind(new_properties);
|
|
FillPropertyArrayWithUndefined(new_properties, var_length.value(),
|
new_capacity, mode);
|
|
// |new_properties| is guaranteed to be in new space, so we can skip
|
// the write barrier.
|
CopyPropertyArrayValues(var_properties.value(), new_properties,
|
var_length.value(), SKIP_WRITE_BARRIER, mode);
|
|
// TODO(gsathya): Clean up the type conversions by creating smarter
|
// helpers that do the correct op based on the mode.
|
Node* new_capacity_int32 =
|
TruncateIntPtrToInt32(ParameterToIntPtr(new_capacity, mode));
|
Node* new_length_and_hash_int32 =
|
Word32Or(var_encoded_hash.value(), new_capacity_int32);
|
StoreObjectField(new_properties, PropertyArray::kLengthAndHashOffset,
|
SmiFromInt32(new_length_and_hash_int32));
|
StoreObjectField(object, JSObject::kPropertiesOrHashOffset, new_properties);
|
Comment("] Extend storage");
|
Goto(&done);
|
BIND(&done);
|
return var_new_properties.value();
|
}
|
}
|
|
void AccessorAssembler::StoreNamedField(Node* handler_word, Node* object,
|
bool is_inobject,
|
Representation representation,
|
Node* value, Label* bailout) {
|
bool store_value_as_double = representation.IsDouble();
|
Node* property_storage = object;
|
if (!is_inobject) {
|
property_storage = LoadFastProperties(object);
|
}
|
|
Node* index = DecodeWord<StoreHandler::FieldIndexBits>(handler_word);
|
Node* offset = IntPtrMul(index, IntPtrConstant(kPointerSize));
|
if (representation.IsDouble()) {
|
if (!FLAG_unbox_double_fields || !is_inobject) {
|
// Load the mutable heap number.
|
property_storage = LoadObjectField(property_storage, offset);
|
// Store the double value into it.
|
offset = IntPtrConstant(HeapNumber::kValueOffset);
|
}
|
}
|
|
// Do constant value check if necessary.
|
if (FLAG_track_constant_fields) {
|
Label done(this);
|
GotoIfNot(WordEqual(DecodeWord<StoreHandler::KindBits>(handler_word),
|
IntPtrConstant(StoreHandler::kConstField)),
|
&done);
|
{
|
if (store_value_as_double) {
|
Node* current_value =
|
LoadObjectField(property_storage, offset, MachineType::Float64());
|
GotoIfNot(Float64Equal(current_value, value), bailout);
|
} else {
|
Node* current_value = LoadObjectField(property_storage, offset);
|
GotoIfNot(WordEqual(current_value, value), bailout);
|
}
|
Goto(&done);
|
}
|
BIND(&done);
|
}
|
|
// Do the store.
|
if (store_value_as_double) {
|
StoreObjectFieldNoWriteBarrier(property_storage, offset, value,
|
MachineRepresentation::kFloat64);
|
} else if (representation.IsSmi()) {
|
StoreObjectFieldNoWriteBarrier(property_storage, offset, value);
|
} else {
|
StoreObjectField(property_storage, offset, value);
|
}
|
}
|
|
void AccessorAssembler::EmitFastElementsBoundsCheck(Node* object,
|
Node* elements,
|
Node* intptr_index,
|
Node* is_jsarray_condition,
|
Label* miss) {
|
VARIABLE(var_length, MachineType::PointerRepresentation());
|
Comment("Fast elements bounds check");
|
Label if_array(this), length_loaded(this, &var_length);
|
GotoIf(is_jsarray_condition, &if_array);
|
{
|
var_length.Bind(SmiUntag(LoadFixedArrayBaseLength(elements)));
|
Goto(&length_loaded);
|
}
|
BIND(&if_array);
|
{
|
var_length.Bind(SmiUntag(LoadFastJSArrayLength(object)));
|
Goto(&length_loaded);
|
}
|
BIND(&length_loaded);
|
GotoIfNot(UintPtrLessThan(intptr_index, var_length.value()), miss);
|
}
|
|
void AccessorAssembler::EmitElementLoad(
|
Node* object, Node* elements, Node* elements_kind,
|
SloppyTNode<IntPtrT> intptr_index, Node* is_jsarray_condition,
|
Label* if_hole, Label* rebox_double, Variable* var_double_value,
|
Label* unimplemented_elements_kind, Label* out_of_bounds, Label* miss,
|
ExitPoint* exit_point) {
|
Label if_typed_array(this), if_fast_packed(this), if_fast_holey(this),
|
if_fast_double(this), if_fast_holey_double(this), if_nonfast(this),
|
if_dictionary(this);
|
GotoIf(
|
Int32GreaterThan(elements_kind, Int32Constant(LAST_FAST_ELEMENTS_KIND)),
|
&if_nonfast);
|
|
EmitFastElementsBoundsCheck(object, elements, intptr_index,
|
is_jsarray_condition, out_of_bounds);
|
int32_t kinds[] = {// Handled by if_fast_packed.
|
PACKED_SMI_ELEMENTS, PACKED_ELEMENTS,
|
// Handled by if_fast_holey.
|
HOLEY_SMI_ELEMENTS, HOLEY_ELEMENTS,
|
// Handled by if_fast_double.
|
PACKED_DOUBLE_ELEMENTS,
|
// Handled by if_fast_holey_double.
|
HOLEY_DOUBLE_ELEMENTS};
|
Label* labels[] = {// FAST_{SMI,}_ELEMENTS
|
&if_fast_packed, &if_fast_packed,
|
// FAST_HOLEY_{SMI,}_ELEMENTS
|
&if_fast_holey, &if_fast_holey,
|
// PACKED_DOUBLE_ELEMENTS
|
&if_fast_double,
|
// HOLEY_DOUBLE_ELEMENTS
|
&if_fast_holey_double};
|
Switch(elements_kind, unimplemented_elements_kind, kinds, labels,
|
arraysize(kinds));
|
|
BIND(&if_fast_packed);
|
{
|
Comment("fast packed elements");
|
exit_point->Return(LoadFixedArrayElement(CAST(elements), intptr_index));
|
}
|
|
BIND(&if_fast_holey);
|
{
|
Comment("fast holey elements");
|
Node* element = LoadFixedArrayElement(CAST(elements), intptr_index);
|
GotoIf(WordEqual(element, TheHoleConstant()), if_hole);
|
exit_point->Return(element);
|
}
|
|
BIND(&if_fast_double);
|
{
|
Comment("packed double elements");
|
var_double_value->Bind(LoadFixedDoubleArrayElement(elements, intptr_index,
|
MachineType::Float64()));
|
Goto(rebox_double);
|
}
|
|
BIND(&if_fast_holey_double);
|
{
|
Comment("holey double elements");
|
Node* value = LoadFixedDoubleArrayElement(elements, intptr_index,
|
MachineType::Float64(), 0,
|
INTPTR_PARAMETERS, if_hole);
|
var_double_value->Bind(value);
|
Goto(rebox_double);
|
}
|
|
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(unimplemented_elements_kind);
|
}
|
|
BIND(&if_dictionary);
|
{
|
Comment("dictionary elements");
|
GotoIf(IntPtrLessThan(intptr_index, IntPtrConstant(0)), out_of_bounds);
|
|
TNode<Object> value = BasicLoadNumberDictionaryElement(
|
CAST(elements), intptr_index, miss, if_hole);
|
exit_point->Return(value);
|
}
|
|
BIND(&if_typed_array);
|
{
|
Comment("typed elements");
|
// Check if buffer has been neutered.
|
Node* buffer = LoadObjectField(object, JSArrayBufferView::kBufferOffset);
|
GotoIf(IsDetachedBuffer(buffer), miss);
|
|
// Bounds check.
|
Node* length = SmiUntag(LoadTypedArrayLength(CAST(object)));
|
GotoIfNot(UintPtrLessThan(intptr_index, length), out_of_bounds);
|
|
Node* backing_store = LoadFixedTypedArrayBackingStore(CAST(elements));
|
|
Label uint8_elements(this), int8_elements(this), uint16_elements(this),
|
int16_elements(this), uint32_elements(this), int32_elements(this),
|
float32_elements(this), float64_elements(this), bigint64_elements(this),
|
biguint64_elements(this);
|
Label* elements_kind_labels[] = {
|
&uint8_elements, &uint8_elements, &int8_elements,
|
&uint16_elements, &int16_elements, &uint32_elements,
|
&int32_elements, &float32_elements, &float64_elements,
|
&bigint64_elements, &biguint64_elements};
|
int32_t elements_kinds[] = {
|
UINT8_ELEMENTS, UINT8_CLAMPED_ELEMENTS, INT8_ELEMENTS,
|
UINT16_ELEMENTS, INT16_ELEMENTS, UINT32_ELEMENTS,
|
INT32_ELEMENTS, FLOAT32_ELEMENTS, FLOAT64_ELEMENTS,
|
BIGINT64_ELEMENTS, BIGUINT64_ELEMENTS};
|
const size_t kTypedElementsKindCount =
|
LAST_FIXED_TYPED_ARRAY_ELEMENTS_KIND -
|
FIRST_FIXED_TYPED_ARRAY_ELEMENTS_KIND + 1;
|
DCHECK_EQ(kTypedElementsKindCount, arraysize(elements_kinds));
|
DCHECK_EQ(kTypedElementsKindCount, arraysize(elements_kind_labels));
|
Switch(elements_kind, miss, elements_kinds, elements_kind_labels,
|
kTypedElementsKindCount);
|
BIND(&uint8_elements);
|
{
|
Comment("UINT8_ELEMENTS"); // Handles UINT8_CLAMPED_ELEMENTS too.
|
Node* element = Load(MachineType::Uint8(), backing_store, intptr_index);
|
exit_point->Return(SmiFromInt32(element));
|
}
|
BIND(&int8_elements);
|
{
|
Comment("INT8_ELEMENTS");
|
Node* element = Load(MachineType::Int8(), backing_store, intptr_index);
|
exit_point->Return(SmiFromInt32(element));
|
}
|
BIND(&uint16_elements);
|
{
|
Comment("UINT16_ELEMENTS");
|
Node* index = WordShl(intptr_index, IntPtrConstant(1));
|
Node* element = Load(MachineType::Uint16(), backing_store, index);
|
exit_point->Return(SmiFromInt32(element));
|
}
|
BIND(&int16_elements);
|
{
|
Comment("INT16_ELEMENTS");
|
Node* index = WordShl(intptr_index, IntPtrConstant(1));
|
Node* element = Load(MachineType::Int16(), backing_store, index);
|
exit_point->Return(SmiFromInt32(element));
|
}
|
BIND(&uint32_elements);
|
{
|
Comment("UINT32_ELEMENTS");
|
Node* index = WordShl(intptr_index, IntPtrConstant(2));
|
Node* element = Load(MachineType::Uint32(), backing_store, index);
|
exit_point->Return(ChangeUint32ToTagged(element));
|
}
|
BIND(&int32_elements);
|
{
|
Comment("INT32_ELEMENTS");
|
Node* index = WordShl(intptr_index, IntPtrConstant(2));
|
Node* element = Load(MachineType::Int32(), backing_store, index);
|
exit_point->Return(ChangeInt32ToTagged(element));
|
}
|
BIND(&float32_elements);
|
{
|
Comment("FLOAT32_ELEMENTS");
|
Node* index = WordShl(intptr_index, IntPtrConstant(2));
|
Node* element = Load(MachineType::Float32(), backing_store, index);
|
var_double_value->Bind(ChangeFloat32ToFloat64(element));
|
Goto(rebox_double);
|
}
|
BIND(&float64_elements);
|
{
|
Comment("FLOAT64_ELEMENTS");
|
Node* index = WordShl(intptr_index, IntPtrConstant(3));
|
Node* element = Load(MachineType::Float64(), backing_store, index);
|
var_double_value->Bind(element);
|
Goto(rebox_double);
|
}
|
BIND(&bigint64_elements);
|
{
|
Comment("BIGINT64_ELEMENTS");
|
exit_point->Return(LoadFixedTypedArrayElementAsTagged(
|
backing_store, intptr_index, BIGINT64_ELEMENTS, INTPTR_PARAMETERS));
|
}
|
BIND(&biguint64_elements);
|
{
|
Comment("BIGUINT64_ELEMENTS");
|
exit_point->Return(LoadFixedTypedArrayElementAsTagged(
|
backing_store, intptr_index, BIGUINT64_ELEMENTS, INTPTR_PARAMETERS));
|
}
|
}
|
}
|
|
void AccessorAssembler::NameDictionaryNegativeLookup(Node* object,
|
SloppyTNode<Name> name,
|
Label* miss) {
|
CSA_ASSERT(this, IsDictionaryMap(LoadMap(object)));
|
TNode<NameDictionary> properties = CAST(LoadSlowProperties(object));
|
// Ensure the property does not exist in a dictionary-mode object.
|
TVARIABLE(IntPtrT, var_name_index);
|
Label done(this);
|
NameDictionaryLookup<NameDictionary>(properties, name, miss, &var_name_index,
|
&done);
|
BIND(&done);
|
}
|
|
void AccessorAssembler::BranchIfStrictMode(Node* vector, Node* slot,
|
Label* if_strict) {
|
Node* sfi =
|
LoadObjectField(vector, FeedbackVector::kSharedFunctionInfoOffset);
|
TNode<FeedbackMetadata> metadata = CAST(LoadObjectField(
|
sfi, SharedFunctionInfo::kOuterScopeInfoOrFeedbackMetadataOffset));
|
Node* slot_int = SmiToInt32(slot);
|
|
// See VectorICComputer::index().
|
const int kItemsPerWord = FeedbackMetadata::VectorICComputer::kItemsPerWord;
|
Node* word_index = Int32Div(slot_int, Int32Constant(kItemsPerWord));
|
Node* word_offset = Int32Mod(slot_int, Int32Constant(kItemsPerWord));
|
|
int32_t first_item = FeedbackMetadata::kHeaderSize - kHeapObjectTag;
|
Node* offset =
|
ElementOffsetFromIndex(ChangeInt32ToIntPtr(word_index), UINT32_ELEMENTS,
|
INTPTR_PARAMETERS, first_item);
|
|
Node* data = Load(MachineType::Int32(), metadata, offset);
|
|
// See VectorICComputer::decode().
|
const int kBitsPerItem = FeedbackMetadata::kFeedbackSlotKindBits;
|
Node* shift = Int32Mul(word_offset, Int32Constant(kBitsPerItem));
|
const int kMask = FeedbackMetadata::VectorICComputer::kMask;
|
Node* kind = Word32And(Word32Shr(data, shift), Int32Constant(kMask));
|
|
STATIC_ASSERT(FeedbackSlotKind::kStoreGlobalSloppy <=
|
FeedbackSlotKind::kLastSloppyKind);
|
STATIC_ASSERT(FeedbackSlotKind::kStoreKeyedSloppy <=
|
FeedbackSlotKind::kLastSloppyKind);
|
STATIC_ASSERT(FeedbackSlotKind::kStoreNamedSloppy <=
|
FeedbackSlotKind::kLastSloppyKind);
|
GotoIfNot(Int32LessThanOrEqual(kind, Int32Constant(static_cast<int>(
|
FeedbackSlotKind::kLastSloppyKind))),
|
if_strict);
|
}
|
|
void AccessorAssembler::InvalidateValidityCellIfPrototype(Node* map,
|
Node* bitfield2) {
|
Label is_prototype(this), cont(this);
|
if (bitfield2 == nullptr) {
|
bitfield2 = LoadMapBitField2(map);
|
}
|
|
Branch(IsSetWord32(bitfield2, Map::IsPrototypeMapBit::kMask), &is_prototype,
|
&cont);
|
|
BIND(&is_prototype);
|
{
|
Node* maybe_prototype_info =
|
LoadObjectField(map, Map::kTransitionsOrPrototypeInfoOffset);
|
// If there's no prototype info then there's nothing to invalidate.
|
GotoIf(TaggedIsSmi(maybe_prototype_info), &cont);
|
|
Node* function = ExternalConstant(
|
ExternalReference::invalidate_prototype_chains_function());
|
CallCFunction1(MachineType::AnyTagged(), MachineType::AnyTagged(), function,
|
map);
|
Goto(&cont);
|
}
|
BIND(&cont);
|
}
|
|
void AccessorAssembler::GenericElementLoad(Node* receiver, Node* receiver_map,
|
SloppyTNode<Int32T> instance_type,
|
Node* index, Label* slow) {
|
Comment("integer index");
|
|
ExitPoint direct_exit(this);
|
|
Label if_custom(this), if_element_hole(this), if_oob(this);
|
// Receivers requiring non-standard element accesses (interceptors, access
|
// checks, strings and string wrappers, proxies) are handled in the runtime.
|
GotoIf(IsCustomElementsReceiverInstanceType(instance_type), &if_custom);
|
Node* elements = LoadElements(receiver);
|
Node* elements_kind = LoadMapElementsKind(receiver_map);
|
Node* is_jsarray_condition = InstanceTypeEqual(instance_type, JS_ARRAY_TYPE);
|
VARIABLE(var_double_value, MachineRepresentation::kFloat64);
|
Label rebox_double(this, &var_double_value);
|
|
// Unimplemented elements kinds fall back to a runtime call.
|
Label* unimplemented_elements_kind = slow;
|
IncrementCounter(isolate()->counters()->ic_keyed_load_generic_smi(), 1);
|
EmitElementLoad(receiver, elements, elements_kind, index,
|
is_jsarray_condition, &if_element_hole, &rebox_double,
|
&var_double_value, unimplemented_elements_kind, &if_oob, slow,
|
&direct_exit);
|
|
BIND(&rebox_double);
|
Return(AllocateHeapNumberWithValue(var_double_value.value()));
|
|
BIND(&if_oob);
|
{
|
Comment("out of bounds");
|
// Positive OOB indices are effectively the same as hole loads.
|
GotoIf(IntPtrGreaterThanOrEqual(index, IntPtrConstant(0)),
|
&if_element_hole);
|
// Negative keys can't take the fast OOB path, except for typed arrays.
|
GotoIfNot(InstanceTypeEqual(instance_type, JS_TYPED_ARRAY_TYPE), slow);
|
Return(UndefinedConstant());
|
}
|
|
BIND(&if_element_hole);
|
{
|
Comment("found the hole");
|
Label return_undefined(this);
|
BranchIfPrototypesHaveNoElements(receiver_map, &return_undefined, slow);
|
|
BIND(&return_undefined);
|
Return(UndefinedConstant());
|
}
|
|
BIND(&if_custom);
|
{
|
Comment("check if string");
|
GotoIfNot(IsStringInstanceType(instance_type), slow);
|
Comment("load string character");
|
Node* length = LoadAndUntagObjectField(receiver, String::kLengthOffset);
|
GotoIfNot(UintPtrLessThan(index, length), slow);
|
IncrementCounter(isolate()->counters()->ic_keyed_load_generic_smi(), 1);
|
TailCallBuiltin(Builtins::kStringCharAt, NoContextConstant(), receiver,
|
index);
|
}
|
}
|
|
void AccessorAssembler::GenericPropertyLoad(Node* receiver, Node* receiver_map,
|
SloppyTNode<Int32T> instance_type,
|
const LoadICParameters* p,
|
Label* slow,
|
UseStubCache use_stub_cache) {
|
ExitPoint direct_exit(this);
|
|
Comment("key is unique name");
|
Label if_found_on_receiver(this), if_property_dictionary(this),
|
lookup_prototype_chain(this), special_receiver(this);
|
VARIABLE(var_details, MachineRepresentation::kWord32);
|
VARIABLE(var_value, MachineRepresentation::kTagged);
|
|
// Receivers requiring non-standard accesses (interceptors, access
|
// checks, strings and string wrappers) are handled in the runtime.
|
GotoIf(IsSpecialReceiverInstanceType(instance_type), &special_receiver);
|
|
// Check if the receiver has fast or slow properties.
|
Node* bitfield3 = LoadMapBitField3(receiver_map);
|
GotoIf(IsSetWord32<Map::IsDictionaryMapBit>(bitfield3),
|
&if_property_dictionary);
|
|
// Try looking up the property on the receiver; if unsuccessful, look
|
// for a handler in the stub cache.
|
TNode<DescriptorArray> descriptors = LoadMapDescriptors(receiver_map);
|
|
Label if_descriptor_found(this), stub_cache(this);
|
TVARIABLE(IntPtrT, var_name_index);
|
Label* notfound =
|
use_stub_cache == kUseStubCache ? &stub_cache : &lookup_prototype_chain;
|
DescriptorLookup(p->name, descriptors, bitfield3, &if_descriptor_found,
|
&var_name_index, notfound);
|
|
BIND(&if_descriptor_found);
|
{
|
LoadPropertyFromFastObject(receiver, receiver_map, descriptors,
|
var_name_index.value(), &var_details,
|
&var_value);
|
Goto(&if_found_on_receiver);
|
}
|
|
if (use_stub_cache == kUseStubCache) {
|
BIND(&stub_cache);
|
Comment("stub cache probe for fast property load");
|
TVARIABLE(MaybeObject, var_handler);
|
Label found_handler(this, &var_handler), stub_cache_miss(this);
|
TryProbeStubCache(isolate()->load_stub_cache(), receiver, p->name,
|
&found_handler, &var_handler, &stub_cache_miss);
|
BIND(&found_handler);
|
{
|
HandleLoadICHandlerCase(p, CAST(var_handler.value()), &stub_cache_miss,
|
&direct_exit);
|
}
|
|
BIND(&stub_cache_miss);
|
{
|
// TODO(jkummerow): Check if the property exists on the prototype
|
// chain. If it doesn't, then there's no point in missing.
|
Comment("KeyedLoadGeneric_miss");
|
TailCallRuntime(Runtime::kKeyedLoadIC_Miss, p->context, p->receiver,
|
p->name, p->slot, p->vector);
|
}
|
}
|
|
BIND(&if_property_dictionary);
|
{
|
Comment("dictionary property load");
|
// We checked for LAST_CUSTOM_ELEMENTS_RECEIVER before, which rules out
|
// seeing global objects here (which would need special handling).
|
|
TVARIABLE(IntPtrT, var_name_index);
|
Label dictionary_found(this, &var_name_index);
|
TNode<NameDictionary> properties = CAST(LoadSlowProperties(receiver));
|
NameDictionaryLookup<NameDictionary>(properties, CAST(p->name),
|
&dictionary_found, &var_name_index,
|
&lookup_prototype_chain);
|
BIND(&dictionary_found);
|
{
|
LoadPropertyFromNameDictionary(properties, var_name_index.value(),
|
&var_details, &var_value);
|
Goto(&if_found_on_receiver);
|
}
|
}
|
|
BIND(&if_found_on_receiver);
|
{
|
Node* value = CallGetterIfAccessor(var_value.value(), var_details.value(),
|
p->context, receiver, slow);
|
IncrementCounter(isolate()->counters()->ic_keyed_load_generic_symbol(), 1);
|
Return(value);
|
}
|
|
BIND(&lookup_prototype_chain);
|
{
|
VARIABLE(var_holder_map, MachineRepresentation::kTagged);
|
VARIABLE(var_holder_instance_type, MachineRepresentation::kWord32);
|
Label return_undefined(this);
|
Variable* merged_variables[] = {&var_holder_map, &var_holder_instance_type};
|
Label loop(this, arraysize(merged_variables), merged_variables);
|
|
var_holder_map.Bind(receiver_map);
|
var_holder_instance_type.Bind(instance_type);
|
// Private symbols must not be looked up on the prototype chain.
|
GotoIf(IsPrivateSymbol(p->name), &return_undefined);
|
Goto(&loop);
|
BIND(&loop);
|
{
|
// Bailout if it can be an integer indexed exotic case.
|
GotoIf(InstanceTypeEqual(var_holder_instance_type.value(),
|
JS_TYPED_ARRAY_TYPE),
|
slow);
|
Node* proto = LoadMapPrototype(var_holder_map.value());
|
GotoIf(WordEqual(proto, NullConstant()), &return_undefined);
|
Node* proto_map = LoadMap(proto);
|
Node* proto_instance_type = LoadMapInstanceType(proto_map);
|
var_holder_map.Bind(proto_map);
|
var_holder_instance_type.Bind(proto_instance_type);
|
Label next_proto(this), return_value(this, &var_value), goto_slow(this);
|
TryGetOwnProperty(p->context, receiver, proto, proto_map,
|
proto_instance_type, p->name, &return_value, &var_value,
|
&next_proto, &goto_slow);
|
|
// This trampoline and the next are required to appease Turbofan's
|
// variable merging.
|
BIND(&next_proto);
|
Goto(&loop);
|
|
BIND(&goto_slow);
|
Goto(slow);
|
|
BIND(&return_value);
|
Return(var_value.value());
|
}
|
|
BIND(&return_undefined);
|
Return(UndefinedConstant());
|
}
|
|
BIND(&special_receiver);
|
{
|
// TODO(jkummerow): Consider supporting JSModuleNamespace.
|
GotoIfNot(InstanceTypeEqual(instance_type, JS_PROXY_TYPE), slow);
|
|
// Private field/symbol lookup is not supported.
|
GotoIf(IsPrivateSymbol(p->name), slow);
|
|
direct_exit.ReturnCallStub(
|
Builtins::CallableFor(isolate(), Builtins::kProxyGetProperty),
|
p->context, receiver /*holder is the same as receiver*/, p->name,
|
receiver, SmiConstant(OnNonExistent::kReturnUndefined));
|
}
|
}
|
|
//////////////////// Stub cache access helpers.
|
|
enum AccessorAssembler::StubCacheTable : int {
|
kPrimary = static_cast<int>(StubCache::kPrimary),
|
kSecondary = static_cast<int>(StubCache::kSecondary)
|
};
|
|
Node* AccessorAssembler::StubCachePrimaryOffset(Node* name, Node* map) {
|
// See v8::internal::StubCache::PrimaryOffset().
|
STATIC_ASSERT(StubCache::kCacheIndexShift == Name::kHashShift);
|
// Compute the hash of the name (use entire hash field).
|
Node* hash_field = LoadNameHashField(name);
|
CSA_ASSERT(this,
|
Word32Equal(Word32And(hash_field,
|
Int32Constant(Name::kHashNotComputedMask)),
|
Int32Constant(0)));
|
|
// Using only the low bits in 64-bit mode is unlikely to increase the
|
// risk of collision even if the heap is spread over an area larger than
|
// 4Gb (and not at all if it isn't).
|
Node* map32 = TruncateIntPtrToInt32(BitcastTaggedToWord(map));
|
// Base the offset on a simple combination of name and map.
|
Node* hash = Int32Add(hash_field, map32);
|
uint32_t mask = (StubCache::kPrimaryTableSize - 1)
|
<< StubCache::kCacheIndexShift;
|
return ChangeUint32ToWord(Word32And(hash, Int32Constant(mask)));
|
}
|
|
Node* AccessorAssembler::StubCacheSecondaryOffset(Node* name, Node* seed) {
|
// See v8::internal::StubCache::SecondaryOffset().
|
|
// Use the seed from the primary cache in the secondary cache.
|
Node* name32 = TruncateIntPtrToInt32(BitcastTaggedToWord(name));
|
Node* hash = Int32Sub(TruncateIntPtrToInt32(seed), name32);
|
hash = Int32Add(hash, Int32Constant(StubCache::kSecondaryMagic));
|
int32_t mask = (StubCache::kSecondaryTableSize - 1)
|
<< StubCache::kCacheIndexShift;
|
return ChangeUint32ToWord(Word32And(hash, Int32Constant(mask)));
|
}
|
|
void AccessorAssembler::TryProbeStubCacheTable(
|
StubCache* stub_cache, StubCacheTable table_id, Node* entry_offset,
|
Node* name, Node* map, Label* if_handler,
|
TVariable<MaybeObject>* var_handler, Label* if_miss) {
|
StubCache::Table table = static_cast<StubCache::Table>(table_id);
|
#ifdef DEBUG
|
if (FLAG_test_secondary_stub_cache && table == StubCache::kPrimary) {
|
Goto(if_miss);
|
return;
|
} else if (FLAG_test_primary_stub_cache && table == StubCache::kSecondary) {
|
Goto(if_miss);
|
return;
|
}
|
#endif
|
// The {table_offset} holds the entry offset times four (due to masking
|
// and shifting optimizations).
|
const int kMultiplier = sizeof(StubCache::Entry) >> Name::kHashShift;
|
entry_offset = IntPtrMul(entry_offset, IntPtrConstant(kMultiplier));
|
|
// Check that the key in the entry matches the name.
|
Node* key_base = ExternalConstant(
|
ExternalReference::Create(stub_cache->key_reference(table)));
|
Node* entry_key = Load(MachineType::Pointer(), key_base, entry_offset);
|
GotoIf(WordNotEqual(name, entry_key), if_miss);
|
|
// Get the map entry from the cache.
|
DCHECK_EQ(kPointerSize * 2, stub_cache->map_reference(table).address() -
|
stub_cache->key_reference(table).address());
|
Node* entry_map =
|
Load(MachineType::Pointer(), key_base,
|
IntPtrAdd(entry_offset, IntPtrConstant(kPointerSize * 2)));
|
GotoIf(WordNotEqual(map, entry_map), if_miss);
|
|
DCHECK_EQ(kPointerSize, stub_cache->value_reference(table).address() -
|
stub_cache->key_reference(table).address());
|
TNode<MaybeObject> handler = ReinterpretCast<MaybeObject>(
|
Load(MachineType::TaggedPointer(), key_base,
|
IntPtrAdd(entry_offset, IntPtrConstant(kPointerSize))));
|
|
// We found the handler.
|
*var_handler = handler;
|
Goto(if_handler);
|
}
|
|
void AccessorAssembler::TryProbeStubCache(StubCache* stub_cache, Node* receiver,
|
Node* name, Label* if_handler,
|
TVariable<MaybeObject>* var_handler,
|
Label* if_miss) {
|
Label try_secondary(this), miss(this);
|
|
Counters* counters = isolate()->counters();
|
IncrementCounter(counters->megamorphic_stub_cache_probes(), 1);
|
|
// Check that the {receiver} isn't a smi.
|
GotoIf(TaggedIsSmi(receiver), &miss);
|
|
Node* receiver_map = LoadMap(receiver);
|
|
// Probe the primary table.
|
Node* primary_offset = StubCachePrimaryOffset(name, receiver_map);
|
TryProbeStubCacheTable(stub_cache, kPrimary, primary_offset, name,
|
receiver_map, if_handler, var_handler, &try_secondary);
|
|
BIND(&try_secondary);
|
{
|
// Probe the secondary table.
|
Node* secondary_offset = StubCacheSecondaryOffset(name, primary_offset);
|
TryProbeStubCacheTable(stub_cache, kSecondary, secondary_offset, name,
|
receiver_map, if_handler, var_handler, &miss);
|
}
|
|
BIND(&miss);
|
{
|
IncrementCounter(counters->megamorphic_stub_cache_misses(), 1);
|
Goto(if_miss);
|
}
|
}
|
|
//////////////////// Entry points into private implementation (one per stub).
|
|
void AccessorAssembler::LoadIC_BytecodeHandler(const LoadICParameters* p,
|
ExitPoint* exit_point) {
|
// Must be kept in sync with LoadIC.
|
|
// This function is hand-tuned to omit frame construction for common cases,
|
// e.g.: monomorphic field and constant loads through smi handlers.
|
// Polymorphic ICs with a hit in the first two entries also omit frames.
|
// TODO(jgruber): Frame omission is fragile and can be affected by minor
|
// changes in control flow and logic. We currently have no way of ensuring
|
// that no frame is constructed, so it's easy to break this optimization by
|
// accident.
|
Label stub_call(this, Label::kDeferred), miss(this, Label::kDeferred);
|
|
// Inlined fast path.
|
{
|
Comment("LoadIC_BytecodeHandler_fast");
|
|
Node* recv_map = LoadReceiverMap(p->receiver);
|
GotoIf(IsDeprecatedMap(recv_map), &miss);
|
|
TVARIABLE(MaybeObject, var_handler);
|
Label try_polymorphic(this), if_handler(this, &var_handler);
|
|
TNode<MaybeObject> feedback =
|
TryMonomorphicCase(p->slot, p->vector, recv_map, &if_handler,
|
&var_handler, &try_polymorphic);
|
|
BIND(&if_handler);
|
HandleLoadICHandlerCase(p, CAST(var_handler.value()), &miss, exit_point);
|
|
BIND(&try_polymorphic);
|
{
|
TNode<HeapObject> strong_feedback = ToStrongHeapObject(feedback, &miss);
|
GotoIfNot(IsWeakFixedArrayMap(LoadMap(strong_feedback)), &stub_call);
|
HandlePolymorphicCase(recv_map, CAST(strong_feedback), &if_handler,
|
&var_handler, &miss, 2);
|
}
|
}
|
|
BIND(&stub_call);
|
{
|
Comment("LoadIC_BytecodeHandler_noninlined");
|
|
// Call into the stub that implements the non-inlined parts of LoadIC.
|
Callable ic =
|
Builtins::CallableFor(isolate(), Builtins::kLoadIC_Noninlined);
|
Node* code_target = HeapConstant(ic.code());
|
exit_point->ReturnCallStub(ic.descriptor(), code_target, p->context,
|
p->receiver, p->name, p->slot, p->vector);
|
}
|
|
BIND(&miss);
|
{
|
Comment("LoadIC_BytecodeHandler_miss");
|
|
exit_point->ReturnCallRuntime(Runtime::kLoadIC_Miss, p->context,
|
p->receiver, p->name, p->slot, p->vector);
|
}
|
}
|
|
void AccessorAssembler::LoadIC(const LoadICParameters* p) {
|
// Must be kept in sync with LoadIC_BytecodeHandler.
|
|
ExitPoint direct_exit(this);
|
|
TVARIABLE(MaybeObject, var_handler);
|
Label if_handler(this, &var_handler), non_inlined(this, Label::kDeferred),
|
try_polymorphic(this), miss(this, Label::kDeferred);
|
|
Node* receiver_map = LoadReceiverMap(p->receiver);
|
GotoIf(IsDeprecatedMap(receiver_map), &miss);
|
|
// Check monomorphic case.
|
TNode<MaybeObject> feedback =
|
TryMonomorphicCase(p->slot, p->vector, receiver_map, &if_handler,
|
&var_handler, &try_polymorphic);
|
BIND(&if_handler);
|
HandleLoadICHandlerCase(p, CAST(var_handler.value()), &miss, &direct_exit);
|
|
BIND(&try_polymorphic);
|
TNode<HeapObject> strong_feedback = ToStrongHeapObject(feedback, &miss);
|
{
|
// Check polymorphic case.
|
Comment("LoadIC_try_polymorphic");
|
GotoIfNot(IsWeakFixedArrayMap(LoadMap(strong_feedback)), &non_inlined);
|
HandlePolymorphicCase(receiver_map, CAST(strong_feedback), &if_handler,
|
&var_handler, &miss, 2);
|
}
|
|
BIND(&non_inlined);
|
{
|
LoadIC_Noninlined(p, receiver_map, strong_feedback, &var_handler,
|
&if_handler, &miss, &direct_exit);
|
}
|
|
BIND(&miss);
|
direct_exit.ReturnCallRuntime(Runtime::kLoadIC_Miss, p->context, p->receiver,
|
p->name, p->slot, p->vector);
|
}
|
|
void AccessorAssembler::LoadIC_Noninlined(const LoadICParameters* p,
|
Node* receiver_map,
|
TNode<HeapObject> feedback,
|
TVariable<MaybeObject>* var_handler,
|
Label* if_handler, Label* miss,
|
ExitPoint* exit_point) {
|
Label try_uninitialized(this, Label::kDeferred);
|
|
// Neither deprecated map nor monomorphic. These cases are handled in the
|
// bytecode handler.
|
CSA_ASSERT(this, Word32BinaryNot(IsDeprecatedMap(receiver_map)));
|
CSA_ASSERT(this, WordNotEqual(receiver_map, feedback));
|
CSA_ASSERT(this, Word32BinaryNot(IsWeakFixedArrayMap(LoadMap(feedback))));
|
DCHECK_EQ(MachineRepresentation::kTagged, var_handler->rep());
|
|
{
|
// Check megamorphic case.
|
GotoIfNot(WordEqual(feedback, LoadRoot(Heap::kmegamorphic_symbolRootIndex)),
|
&try_uninitialized);
|
|
TryProbeStubCache(isolate()->load_stub_cache(), p->receiver, p->name,
|
if_handler, var_handler, miss);
|
}
|
|
BIND(&try_uninitialized);
|
{
|
// Check uninitialized case.
|
GotoIfNot(
|
WordEqual(feedback, LoadRoot(Heap::kuninitialized_symbolRootIndex)),
|
miss);
|
exit_point->ReturnCallStub(
|
Builtins::CallableFor(isolate(), Builtins::kLoadIC_Uninitialized),
|
p->context, p->receiver, p->name, p->slot, p->vector);
|
}
|
}
|
|
void AccessorAssembler::LoadIC_Uninitialized(const LoadICParameters* p) {
|
Label miss(this, Label::kDeferred);
|
Node* receiver = p->receiver;
|
GotoIf(TaggedIsSmi(receiver), &miss);
|
Node* receiver_map = LoadMap(receiver);
|
Node* instance_type = LoadMapInstanceType(receiver_map);
|
|
// Optimistically write the state transition to the vector.
|
StoreFeedbackVectorSlot(p->vector, p->slot,
|
LoadRoot(Heap::kpremonomorphic_symbolRootIndex),
|
SKIP_WRITE_BARRIER, 0, SMI_PARAMETERS);
|
StoreWeakReferenceInFeedbackVector(p->vector, p->slot, receiver_map,
|
kPointerSize, SMI_PARAMETERS);
|
|
{
|
// Special case for Function.prototype load, because it's very common
|
// for ICs that are only executed once (MyFunc.prototype.foo = ...).
|
Label not_function_prototype(this, Label::kDeferred);
|
GotoIfNot(InstanceTypeEqual(instance_type, JS_FUNCTION_TYPE),
|
¬_function_prototype);
|
GotoIfNot(IsPrototypeString(p->name), ¬_function_prototype);
|
|
GotoIfPrototypeRequiresRuntimeLookup(CAST(receiver), CAST(receiver_map),
|
¬_function_prototype);
|
Return(LoadJSFunctionPrototype(receiver, &miss));
|
BIND(¬_function_prototype);
|
}
|
|
GenericPropertyLoad(receiver, receiver_map, instance_type, p, &miss,
|
kDontUseStubCache);
|
|
BIND(&miss);
|
{
|
// Undo the optimistic state transition.
|
StoreFeedbackVectorSlot(p->vector, p->slot,
|
LoadRoot(Heap::kuninitialized_symbolRootIndex),
|
SKIP_WRITE_BARRIER, 0, SMI_PARAMETERS);
|
|
TailCallRuntime(Runtime::kLoadIC_Miss, p->context, p->receiver, p->name,
|
p->slot, p->vector);
|
}
|
}
|
|
void AccessorAssembler::LoadGlobalIC(TNode<FeedbackVector> vector, Node* slot,
|
const LazyNode<Context>& lazy_context,
|
const LazyNode<Name>& lazy_name,
|
TypeofMode typeof_mode,
|
ExitPoint* exit_point,
|
ParameterMode slot_mode) {
|
Label try_handler(this, Label::kDeferred), miss(this, Label::kDeferred);
|
LoadGlobalIC_TryPropertyCellCase(vector, slot, lazy_context, exit_point,
|
&try_handler, &miss, slot_mode);
|
|
BIND(&try_handler);
|
LoadGlobalIC_TryHandlerCase(vector, slot, lazy_context, lazy_name,
|
typeof_mode, exit_point, &miss, slot_mode);
|
|
BIND(&miss);
|
{
|
Comment("LoadGlobalIC_MissCase");
|
TNode<Context> context = lazy_context();
|
TNode<Name> name = lazy_name();
|
exit_point->ReturnCallRuntime(Runtime::kLoadGlobalIC_Miss, context, name,
|
ParameterToTagged(slot, slot_mode), vector);
|
}
|
}
|
|
void AccessorAssembler::LoadGlobalIC_TryPropertyCellCase(
|
TNode<FeedbackVector> vector, Node* slot,
|
const LazyNode<Context>& lazy_context, ExitPoint* exit_point,
|
Label* try_handler, Label* miss, ParameterMode slot_mode) {
|
Comment("LoadGlobalIC_TryPropertyCellCase");
|
|
Label if_lexical_var(this), if_property_cell(this);
|
TNode<MaybeObject> maybe_weak_ref =
|
LoadFeedbackVectorSlot(vector, slot, 0, slot_mode);
|
Branch(TaggedIsSmi(maybe_weak_ref), &if_lexical_var, &if_property_cell);
|
|
BIND(&if_property_cell);
|
{
|
// Load value or try handler case if the weak reference is cleared.
|
CSA_ASSERT(this, IsWeakOrClearedHeapObject(maybe_weak_ref));
|
TNode<PropertyCell> property_cell =
|
CAST(ToWeakHeapObject(maybe_weak_ref, try_handler));
|
TNode<Object> value =
|
LoadObjectField(property_cell, PropertyCell::kValueOffset);
|
GotoIf(WordEqual(value, TheHoleConstant()), miss);
|
exit_point->Return(value);
|
}
|
|
BIND(&if_lexical_var);
|
{
|
Comment("Load lexical variable");
|
TNode<IntPtrT> lexical_handler = SmiUntag(CAST(maybe_weak_ref));
|
TNode<IntPtrT> context_index =
|
Signed(DecodeWord<FeedbackNexus::ContextIndexBits>(lexical_handler));
|
TNode<IntPtrT> slot_index =
|
Signed(DecodeWord<FeedbackNexus::SlotIndexBits>(lexical_handler));
|
TNode<Context> context = lazy_context();
|
TNode<Context> script_context = LoadScriptContext(context, context_index);
|
TNode<Object> result = LoadContextElement(script_context, slot_index);
|
exit_point->Return(result);
|
}
|
}
|
|
void AccessorAssembler::LoadGlobalIC_TryHandlerCase(
|
TNode<FeedbackVector> vector, Node* slot,
|
const LazyNode<Context>& lazy_context, const LazyNode<Name>& lazy_name,
|
TypeofMode typeof_mode, ExitPoint* exit_point, Label* miss,
|
ParameterMode slot_mode) {
|
Comment("LoadGlobalIC_TryHandlerCase");
|
|
Label call_handler(this), non_smi(this);
|
|
TNode<MaybeObject> feedback_element =
|
LoadFeedbackVectorSlot(vector, slot, kPointerSize, slot_mode);
|
TNode<Object> handler = CAST(feedback_element);
|
GotoIf(WordEqual(handler, LoadRoot(Heap::kuninitialized_symbolRootIndex)),
|
miss);
|
|
OnNonExistent on_nonexistent = typeof_mode == NOT_INSIDE_TYPEOF
|
? OnNonExistent::kThrowReferenceError
|
: OnNonExistent::kReturnUndefined;
|
|
TNode<Context> context = lazy_context();
|
TNode<Context> native_context = LoadNativeContext(context);
|
TNode<JSGlobalProxy> receiver =
|
CAST(LoadContextElement(native_context, Context::GLOBAL_PROXY_INDEX));
|
Node* holder = LoadContextElement(native_context, Context::EXTENSION_INDEX);
|
|
LoadICParameters p(context, receiver, lazy_name(),
|
ParameterToTagged(slot, slot_mode), vector, holder);
|
|
HandleLoadICHandlerCase(&p, handler, miss, exit_point, ICMode::kGlobalIC,
|
on_nonexistent);
|
}
|
|
void AccessorAssembler::KeyedLoadIC(const LoadICParameters* p) {
|
ExitPoint direct_exit(this);
|
|
TVARIABLE(MaybeObject, var_handler);
|
Label if_handler(this, &var_handler), try_polymorphic(this, Label::kDeferred),
|
try_megamorphic(this, Label::kDeferred),
|
try_polymorphic_name(this, Label::kDeferred),
|
miss(this, Label::kDeferred);
|
|
Node* receiver_map = LoadReceiverMap(p->receiver);
|
GotoIf(IsDeprecatedMap(receiver_map), &miss);
|
|
// Check monomorphic case.
|
TNode<MaybeObject> feedback =
|
TryMonomorphicCase(p->slot, p->vector, receiver_map, &if_handler,
|
&var_handler, &try_polymorphic);
|
BIND(&if_handler);
|
{
|
HandleLoadICHandlerCase(p, CAST(var_handler.value()), &miss, &direct_exit,
|
ICMode::kNonGlobalIC,
|
OnNonExistent::kReturnUndefined, kSupportElements);
|
}
|
|
BIND(&try_polymorphic);
|
TNode<HeapObject> strong_feedback = ToStrongHeapObject(feedback, &miss);
|
{
|
// Check polymorphic case.
|
Comment("KeyedLoadIC_try_polymorphic");
|
GotoIfNot(IsWeakFixedArrayMap(LoadMap(strong_feedback)), &try_megamorphic);
|
HandlePolymorphicCase(receiver_map, CAST(strong_feedback), &if_handler,
|
&var_handler, &miss, 2);
|
}
|
|
BIND(&try_megamorphic);
|
{
|
// Check megamorphic case.
|
Comment("KeyedLoadIC_try_megamorphic");
|
GotoIfNot(WordEqual(strong_feedback,
|
LoadRoot(Heap::kmegamorphic_symbolRootIndex)),
|
&try_polymorphic_name);
|
// TODO(jkummerow): Inline this? Or some of it?
|
TailCallBuiltin(Builtins::kKeyedLoadIC_Megamorphic, p->context, p->receiver,
|
p->name, p->slot, p->vector);
|
}
|
BIND(&try_polymorphic_name);
|
{
|
// We might have a name in feedback, and a weak fixed array in the next
|
// slot.
|
Node* name = p->name;
|
Comment("KeyedLoadIC_try_polymorphic_name");
|
VARIABLE(var_name, MachineRepresentation::kTagged, name);
|
VARIABLE(var_index, MachineType::PointerRepresentation());
|
Label if_polymorphic_name(this, &var_name), if_internalized(this),
|
if_notinternalized(this, Label::kDeferred);
|
|
// Fast-case: The recorded {feedback} matches the {name}.
|
GotoIf(WordEqual(strong_feedback, name), &if_polymorphic_name);
|
|
// Try to internalize the {name} if it isn't already.
|
TryToName(name, &miss, &var_index, &if_internalized, &var_name, &miss,
|
&if_notinternalized);
|
|
BIND(&if_internalized);
|
{
|
// The {var_name} now contains a unique name.
|
Branch(WordEqual(strong_feedback, var_name.value()), &if_polymorphic_name,
|
&miss);
|
}
|
|
BIND(&if_notinternalized);
|
{
|
// Try to internalize the {name}.
|
Node* function = ExternalConstant(
|
ExternalReference::try_internalize_string_function());
|
Node* const isolate_ptr =
|
ExternalConstant(ExternalReference::isolate_address(isolate()));
|
var_name.Bind(CallCFunction2(
|
MachineType::AnyTagged(), MachineType::Pointer(),
|
MachineType::AnyTagged(), function, isolate_ptr, name));
|
Goto(&if_internalized);
|
}
|
|
BIND(&if_polymorphic_name);
|
{
|
// If the name comparison succeeded, we know we have a weak fixed array
|
// with at least one map/handler pair.
|
Node* name = var_name.value();
|
TailCallBuiltin(Builtins::kKeyedLoadIC_PolymorphicName, p->context,
|
p->receiver, name, p->slot, p->vector);
|
}
|
}
|
|
BIND(&miss);
|
{
|
Comment("KeyedLoadIC_miss");
|
TailCallRuntime(Runtime::kKeyedLoadIC_Miss, p->context, p->receiver,
|
p->name, p->slot, p->vector);
|
}
|
}
|
|
void AccessorAssembler::KeyedLoadICGeneric(const LoadICParameters* p) {
|
VARIABLE(var_index, MachineType::PointerRepresentation());
|
VARIABLE(var_unique, MachineRepresentation::kTagged);
|
var_unique.Bind(p->name); // Dummy initialization.
|
Label if_index(this), if_unique_name(this), if_notunique(this), slow(this);
|
|
Node* receiver = p->receiver;
|
GotoIf(TaggedIsSmi(receiver), &slow);
|
Node* receiver_map = LoadMap(receiver);
|
Node* instance_type = LoadMapInstanceType(receiver_map);
|
|
TryToName(p->name, &if_index, &var_index, &if_unique_name, &var_unique, &slow,
|
&if_notunique);
|
|
BIND(&if_index);
|
{
|
GenericElementLoad(receiver, receiver_map, instance_type, var_index.value(),
|
&slow);
|
}
|
|
BIND(&if_unique_name);
|
{
|
LoadICParameters pp = *p;
|
pp.name = var_unique.value();
|
GenericPropertyLoad(receiver, receiver_map, instance_type, &pp, &slow);
|
}
|
|
BIND(&if_notunique);
|
{
|
if (FLAG_internalize_on_the_fly) {
|
// Ideally we could return undefined directly here if the name is not
|
// found in the string table, i.e. it was never internalized, but that
|
// invariant doesn't hold with named property interceptors (at this
|
// point), so we take the {slow} path instead.
|
Label if_in_string_table(this);
|
TryInternalizeString(p->name, &if_index, &var_index, &if_in_string_table,
|
&var_unique, &slow, &slow);
|
|
BIND(&if_in_string_table);
|
{
|
// TODO(bmeurer): We currently use a version of GenericPropertyLoad
|
// here, where we don't try to probe the megamorphic stub cache after
|
// successfully internalizing the incoming string. Past experiments
|
// with this have shown that it causes too much traffic on the stub
|
// cache. We may want to re-evaluate that in the future.
|
LoadICParameters pp = *p;
|
pp.name = var_unique.value();
|
GenericPropertyLoad(receiver, receiver_map, instance_type, &pp, &slow,
|
kDontUseStubCache);
|
}
|
} else {
|
Goto(&slow);
|
}
|
}
|
|
BIND(&slow);
|
{
|
Comment("KeyedLoadGeneric_slow");
|
IncrementCounter(isolate()->counters()->ic_keyed_load_generic_slow(), 1);
|
// TODO(jkummerow): Should we use the GetProperty TF stub instead?
|
TailCallRuntime(Runtime::kKeyedGetProperty, p->context, p->receiver,
|
p->name);
|
}
|
}
|
|
void AccessorAssembler::KeyedLoadICPolymorphicName(const LoadICParameters* p) {
|
TVARIABLE(MaybeObject, var_handler);
|
Label if_handler(this, &var_handler), miss(this, Label::kDeferred);
|
|
Node* receiver = p->receiver;
|
Node* receiver_map = LoadReceiverMap(receiver);
|
Node* name = p->name;
|
Node* vector = p->vector;
|
Node* slot = p->slot;
|
Node* context = p->context;
|
|
// When we get here, we know that the {name} matches the recorded
|
// feedback name in the {vector} and can safely be used for the
|
// LoadIC handler logic below.
|
CSA_ASSERT(this, IsName(name));
|
CSA_ASSERT(this, Word32BinaryNot(IsDeprecatedMap(receiver_map)));
|
CSA_ASSERT(this, WordEqual(name, CAST(LoadFeedbackVectorSlot(
|
vector, slot, 0, SMI_PARAMETERS))));
|
|
// Check if we have a matching handler for the {receiver_map}.
|
TNode<MaybeObject> feedback_element =
|
LoadFeedbackVectorSlot(vector, slot, kPointerSize, SMI_PARAMETERS);
|
TNode<WeakFixedArray> array = CAST(feedback_element);
|
HandlePolymorphicCase(receiver_map, array, &if_handler, &var_handler, &miss,
|
1);
|
|
BIND(&if_handler);
|
{
|
ExitPoint direct_exit(this);
|
HandleLoadICHandlerCase(p, CAST(var_handler.value()), &miss, &direct_exit,
|
ICMode::kNonGlobalIC,
|
OnNonExistent::kReturnUndefined, kOnlyProperties);
|
}
|
|
BIND(&miss);
|
{
|
Comment("KeyedLoadIC_miss");
|
TailCallRuntime(Runtime::kKeyedLoadIC_Miss, context, receiver, name, slot,
|
vector);
|
}
|
}
|
|
void AccessorAssembler::StoreIC(const StoreICParameters* p) {
|
TVARIABLE(MaybeObject, var_handler,
|
ReinterpretCast<MaybeObject>(SmiConstant(0)));
|
|
Label if_handler(this, &var_handler),
|
if_handler_from_stub_cache(this, &var_handler, Label::kDeferred),
|
try_polymorphic(this, Label::kDeferred),
|
try_megamorphic(this, Label::kDeferred),
|
try_uninitialized(this, Label::kDeferred), miss(this, Label::kDeferred);
|
|
Node* receiver_map = LoadReceiverMap(p->receiver);
|
GotoIf(IsDeprecatedMap(receiver_map), &miss);
|
|
// Check monomorphic case.
|
TNode<MaybeObject> feedback =
|
TryMonomorphicCase(p->slot, p->vector, receiver_map, &if_handler,
|
&var_handler, &try_polymorphic);
|
BIND(&if_handler);
|
{
|
Comment("StoreIC_if_handler");
|
HandleStoreICHandlerCase(p, var_handler.value(), &miss,
|
ICMode::kNonGlobalIC);
|
}
|
|
BIND(&try_polymorphic);
|
TNode<HeapObject> strong_feedback = ToStrongHeapObject(feedback, &miss);
|
{
|
// Check polymorphic case.
|
Comment("StoreIC_try_polymorphic");
|
GotoIfNot(IsWeakFixedArrayMap(LoadMap(strong_feedback)), &try_megamorphic);
|
HandlePolymorphicCase(receiver_map, CAST(strong_feedback), &if_handler,
|
&var_handler, &miss, 2);
|
}
|
|
BIND(&try_megamorphic);
|
{
|
// Check megamorphic case.
|
GotoIfNot(WordEqual(strong_feedback,
|
LoadRoot(Heap::kmegamorphic_symbolRootIndex)),
|
&try_uninitialized);
|
|
TryProbeStubCache(isolate()->store_stub_cache(), p->receiver, p->name,
|
&if_handler, &var_handler, &miss);
|
}
|
BIND(&try_uninitialized);
|
{
|
// Check uninitialized case.
|
GotoIfNot(WordEqual(strong_feedback,
|
LoadRoot(Heap::kuninitialized_symbolRootIndex)),
|
&miss);
|
TailCallBuiltin(Builtins::kStoreIC_Uninitialized, p->context, p->receiver,
|
p->name, p->value, p->slot, p->vector);
|
}
|
BIND(&miss);
|
{
|
TailCallRuntime(Runtime::kStoreIC_Miss, p->context, p->value, p->slot,
|
p->vector, p->receiver, p->name);
|
}
|
}
|
|
void AccessorAssembler::StoreGlobalIC(const StoreICParameters* pp) {
|
Label if_lexical_var(this), if_property_cell(this);
|
TNode<MaybeObject> maybe_weak_ref =
|
LoadFeedbackVectorSlot(pp->vector, pp->slot, 0, SMI_PARAMETERS);
|
Branch(TaggedIsSmi(maybe_weak_ref), &if_lexical_var, &if_property_cell);
|
|
BIND(&if_property_cell);
|
{
|
Label try_handler(this), miss(this, Label::kDeferred);
|
CSA_ASSERT(this, IsWeakOrClearedHeapObject(maybe_weak_ref));
|
TNode<PropertyCell> property_cell =
|
CAST(ToWeakHeapObject(maybe_weak_ref, &try_handler));
|
|
ExitPoint direct_exit(this);
|
StoreGlobalIC_PropertyCellCase(property_cell, pp->value, &direct_exit,
|
&miss);
|
|
BIND(&try_handler);
|
{
|
Comment("StoreGlobalIC_try_handler");
|
TNode<MaybeObject> handler = LoadFeedbackVectorSlot(
|
pp->vector, pp->slot, kPointerSize, SMI_PARAMETERS);
|
|
GotoIf(WordEqual(handler, LoadRoot(Heap::kuninitialized_symbolRootIndex)),
|
&miss);
|
|
StoreICParameters p = *pp;
|
DCHECK_NULL(p.receiver);
|
Node* native_context = LoadNativeContext(p.context);
|
p.receiver =
|
LoadContextElement(native_context, Context::GLOBAL_PROXY_INDEX);
|
|
HandleStoreICHandlerCase(&p, handler, &miss, ICMode::kGlobalIC);
|
}
|
|
BIND(&miss);
|
{
|
TailCallRuntime(Runtime::kStoreGlobalIC_Miss, pp->context, pp->value,
|
pp->slot, pp->vector, pp->name);
|
}
|
}
|
|
BIND(&if_lexical_var);
|
{
|
Comment("Store lexical variable");
|
TNode<IntPtrT> lexical_handler = SmiUntag(CAST(maybe_weak_ref));
|
TNode<IntPtrT> context_index =
|
Signed(DecodeWord<FeedbackNexus::ContextIndexBits>(lexical_handler));
|
TNode<IntPtrT> slot_index =
|
Signed(DecodeWord<FeedbackNexus::SlotIndexBits>(lexical_handler));
|
TNode<Context> script_context =
|
LoadScriptContext(CAST(pp->context), context_index);
|
StoreContextElement(script_context, slot_index, pp->value);
|
Return(pp->value);
|
}
|
}
|
|
void AccessorAssembler::StoreGlobalIC_PropertyCellCase(Node* property_cell,
|
Node* value,
|
ExitPoint* exit_point,
|
Label* miss) {
|
Comment("StoreGlobalIC_TryPropertyCellCase");
|
CSA_ASSERT(this, IsPropertyCell(property_cell));
|
|
// Load the payload of the global parameter cell. A hole indicates that
|
// the cell has been invalidated and that the store must be handled by the
|
// runtime.
|
Node* cell_contents =
|
LoadObjectField(property_cell, PropertyCell::kValueOffset);
|
Node* details = LoadAndUntagToWord32ObjectField(property_cell,
|
PropertyCell::kDetailsOffset);
|
GotoIf(IsSetWord32(details, PropertyDetails::kAttributesReadOnlyMask), miss);
|
CSA_ASSERT(this,
|
Word32Equal(DecodeWord32<PropertyDetails::KindField>(details),
|
Int32Constant(kData)));
|
|
Node* type = DecodeWord32<PropertyDetails::PropertyCellTypeField>(details);
|
|
Label constant(this), store(this), not_smi(this);
|
|
GotoIf(Word32Equal(type, Int32Constant(
|
static_cast<int>(PropertyCellType::kConstant))),
|
&constant);
|
|
GotoIf(IsTheHole(cell_contents), miss);
|
|
GotoIf(Word32Equal(
|
type, Int32Constant(static_cast<int>(PropertyCellType::kMutable))),
|
&store);
|
CSA_ASSERT(this,
|
Word32Or(Word32Equal(type, Int32Constant(static_cast<int>(
|
PropertyCellType::kConstantType))),
|
Word32Equal(type, Int32Constant(static_cast<int>(
|
PropertyCellType::kUndefined)))));
|
|
GotoIfNot(TaggedIsSmi(cell_contents), ¬_smi);
|
GotoIfNot(TaggedIsSmi(value), miss);
|
Goto(&store);
|
|
BIND(¬_smi);
|
{
|
GotoIf(TaggedIsSmi(value), miss);
|
Node* expected_map = LoadMap(cell_contents);
|
Node* map = LoadMap(value);
|
GotoIfNot(WordEqual(expected_map, map), miss);
|
Goto(&store);
|
}
|
|
BIND(&store);
|
{
|
StoreObjectField(property_cell, PropertyCell::kValueOffset, value);
|
exit_point->Return(value);
|
}
|
|
BIND(&constant);
|
{
|
GotoIfNot(WordEqual(cell_contents, value), miss);
|
exit_point->Return(value);
|
}
|
}
|
|
void AccessorAssembler::KeyedStoreIC(const StoreICParameters* p) {
|
Label miss(this, Label::kDeferred);
|
{
|
TVARIABLE(MaybeObject, var_handler);
|
|
Label if_handler(this, &var_handler),
|
try_polymorphic(this, Label::kDeferred),
|
try_megamorphic(this, Label::kDeferred),
|
try_polymorphic_name(this, Label::kDeferred);
|
|
Node* receiver_map = LoadReceiverMap(p->receiver);
|
GotoIf(IsDeprecatedMap(receiver_map), &miss);
|
|
// Check monomorphic case.
|
TNode<MaybeObject> feedback =
|
TryMonomorphicCase(p->slot, p->vector, receiver_map, &if_handler,
|
&var_handler, &try_polymorphic);
|
BIND(&if_handler);
|
{
|
Comment("KeyedStoreIC_if_handler");
|
HandleStoreICHandlerCase(p, var_handler.value(), &miss,
|
ICMode::kNonGlobalIC, kSupportElements);
|
}
|
|
BIND(&try_polymorphic);
|
TNode<HeapObject> strong_feedback = ToStrongHeapObject(feedback, &miss);
|
{
|
// CheckPolymorphic case.
|
Comment("KeyedStoreIC_try_polymorphic");
|
GotoIfNot(IsWeakFixedArrayMap(LoadMap(strong_feedback)),
|
&try_megamorphic);
|
HandlePolymorphicCase(receiver_map, CAST(strong_feedback), &if_handler,
|
&var_handler, &miss, 2);
|
}
|
|
BIND(&try_megamorphic);
|
{
|
// Check megamorphic case.
|
Comment("KeyedStoreIC_try_megamorphic");
|
GotoIfNot(WordEqual(strong_feedback,
|
LoadRoot(Heap::kmegamorphic_symbolRootIndex)),
|
&try_polymorphic_name);
|
TailCallBuiltin(Builtins::kKeyedStoreIC_Megamorphic, p->context,
|
p->receiver, p->name, p->value, p->slot, p->vector);
|
}
|
|
BIND(&try_polymorphic_name);
|
{
|
// We might have a name in feedback, and a fixed array in the next slot.
|
Comment("KeyedStoreIC_try_polymorphic_name");
|
GotoIfNot(WordEqual(strong_feedback, p->name), &miss);
|
// If the name comparison succeeded, we know we have a feedback vector
|
// with at least one map/handler pair.
|
TNode<MaybeObject> feedback_element = LoadFeedbackVectorSlot(
|
p->vector, p->slot, kPointerSize, SMI_PARAMETERS);
|
TNode<WeakFixedArray> array = CAST(feedback_element);
|
HandlePolymorphicCase(receiver_map, array, &if_handler, &var_handler,
|
&miss, 1);
|
}
|
}
|
BIND(&miss);
|
{
|
Comment("KeyedStoreIC_miss");
|
TailCallRuntime(Runtime::kKeyedStoreIC_Miss, p->context, p->value, p->slot,
|
p->vector, p->receiver, p->name);
|
}
|
}
|
|
void AccessorAssembler::StoreInArrayLiteralIC(const StoreICParameters* p) {
|
Label miss(this, Label::kDeferred);
|
{
|
TVARIABLE(MaybeObject, var_handler);
|
|
Label if_handler(this, &var_handler),
|
try_polymorphic(this, Label::kDeferred),
|
try_megamorphic(this, Label::kDeferred);
|
|
Node* array_map = LoadReceiverMap(p->receiver);
|
GotoIf(IsDeprecatedMap(array_map), &miss);
|
TNode<MaybeObject> feedback =
|
TryMonomorphicCase(p->slot, p->vector, array_map, &if_handler,
|
&var_handler, &try_polymorphic);
|
|
BIND(&if_handler);
|
{
|
Comment("StoreInArrayLiteralIC_if_handler");
|
// This is a stripped-down version of HandleStoreICHandlerCase.
|
|
TNode<HeapObject> handler = CAST(var_handler.value());
|
Label if_transitioning_element_store(this);
|
GotoIfNot(IsCode(handler), &if_transitioning_element_store);
|
TailCallStub(StoreWithVectorDescriptor{}, CAST(handler), CAST(p->context),
|
p->receiver, p->name, p->value, p->slot, p->vector);
|
|
BIND(&if_transitioning_element_store);
|
{
|
TNode<MaybeObject> maybe_transition_map =
|
LoadHandlerDataField(CAST(handler), 1);
|
TNode<Map> transition_map =
|
CAST(ToWeakHeapObject(maybe_transition_map, &miss));
|
GotoIf(IsDeprecatedMap(transition_map), &miss);
|
Node* code = LoadObjectField(handler, StoreHandler::kSmiHandlerOffset);
|
CSA_ASSERT(this, IsCode(code));
|
TailCallStub(StoreTransitionDescriptor{}, code, p->context, p->receiver,
|
p->name, transition_map, p->value, p->slot, p->vector);
|
}
|
}
|
|
BIND(&try_polymorphic);
|
TNode<HeapObject> strong_feedback = ToStrongHeapObject(feedback, &miss);
|
{
|
Comment("StoreInArrayLiteralIC_try_polymorphic");
|
GotoIfNot(IsWeakFixedArrayMap(LoadMap(strong_feedback)),
|
&try_megamorphic);
|
HandlePolymorphicCase(array_map, CAST(strong_feedback), &if_handler,
|
&var_handler, &miss, 2);
|
}
|
|
BIND(&try_megamorphic);
|
{
|
Comment("StoreInArrayLiteralIC_try_megamorphic");
|
CSA_ASSERT(
|
this,
|
Word32Or(WordEqual(strong_feedback,
|
LoadRoot(Heap::kuninitialized_symbolRootIndex)),
|
WordEqual(strong_feedback,
|
LoadRoot(Heap::kmegamorphic_symbolRootIndex))));
|
GotoIfNot(WordEqual(strong_feedback,
|
LoadRoot(Heap::kmegamorphic_symbolRootIndex)),
|
&miss);
|
TailCallRuntime(Runtime::kStoreInArrayLiteralIC_Slow, p->context,
|
p->value, p->receiver, p->name);
|
}
|
}
|
|
BIND(&miss);
|
{
|
Comment("StoreInArrayLiteralIC_miss");
|
// TODO(neis): Introduce Runtime::kStoreInArrayLiteralIC_Miss.
|
TailCallRuntime(Runtime::kKeyedStoreIC_Miss, p->context, p->value, p->slot,
|
p->vector, p->receiver, p->name);
|
}
|
}
|
|
//////////////////// Public methods.
|
|
void AccessorAssembler::GenerateLoadIC() {
|
typedef LoadWithVectorDescriptor Descriptor;
|
|
Node* receiver = Parameter(Descriptor::kReceiver);
|
Node* name = Parameter(Descriptor::kName);
|
Node* slot = Parameter(Descriptor::kSlot);
|
Node* vector = Parameter(Descriptor::kVector);
|
Node* context = Parameter(Descriptor::kContext);
|
|
LoadICParameters p(context, receiver, name, slot, vector);
|
LoadIC(&p);
|
}
|
|
void AccessorAssembler::GenerateLoadIC_Noninlined() {
|
typedef LoadWithVectorDescriptor Descriptor;
|
|
Node* receiver = Parameter(Descriptor::kReceiver);
|
Node* name = Parameter(Descriptor::kName);
|
Node* slot = Parameter(Descriptor::kSlot);
|
Node* vector = Parameter(Descriptor::kVector);
|
Node* context = Parameter(Descriptor::kContext);
|
|
ExitPoint direct_exit(this);
|
TVARIABLE(MaybeObject, var_handler);
|
Label if_handler(this, &var_handler), miss(this, Label::kDeferred);
|
|
Node* receiver_map = LoadReceiverMap(receiver);
|
TNode<MaybeObject> feedback_element =
|
LoadFeedbackVectorSlot(vector, slot, 0, SMI_PARAMETERS);
|
TNode<HeapObject> feedback = CAST(feedback_element);
|
|
LoadICParameters p(context, receiver, name, slot, vector);
|
LoadIC_Noninlined(&p, receiver_map, feedback, &var_handler, &if_handler,
|
&miss, &direct_exit);
|
|
BIND(&if_handler);
|
HandleLoadICHandlerCase(&p, CAST(var_handler.value()), &miss, &direct_exit);
|
|
BIND(&miss);
|
direct_exit.ReturnCallRuntime(Runtime::kLoadIC_Miss, context, receiver, name,
|
slot, vector);
|
}
|
|
void AccessorAssembler::GenerateLoadIC_Uninitialized() {
|
typedef LoadWithVectorDescriptor Descriptor;
|
|
Node* receiver = Parameter(Descriptor::kReceiver);
|
Node* name = Parameter(Descriptor::kName);
|
Node* slot = Parameter(Descriptor::kSlot);
|
Node* vector = Parameter(Descriptor::kVector);
|
Node* context = Parameter(Descriptor::kContext);
|
|
LoadICParameters p(context, receiver, name, slot, vector);
|
LoadIC_Uninitialized(&p);
|
}
|
|
void AccessorAssembler::GenerateLoadICTrampoline() {
|
typedef LoadDescriptor Descriptor;
|
|
Node* receiver = Parameter(Descriptor::kReceiver);
|
Node* name = Parameter(Descriptor::kName);
|
Node* slot = Parameter(Descriptor::kSlot);
|
Node* context = Parameter(Descriptor::kContext);
|
Node* vector = LoadFeedbackVectorForStub();
|
|
TailCallBuiltin(Builtins::kLoadIC, context, receiver, name, slot, vector);
|
}
|
|
void AccessorAssembler::GenerateLoadGlobalIC(TypeofMode typeof_mode) {
|
typedef LoadGlobalWithVectorDescriptor Descriptor;
|
|
Node* name = Parameter(Descriptor::kName);
|
Node* slot = Parameter(Descriptor::kSlot);
|
Node* vector = Parameter(Descriptor::kVector);
|
Node* context = Parameter(Descriptor::kContext);
|
|
ExitPoint direct_exit(this);
|
LoadGlobalIC(CAST(vector), slot,
|
// lazy_context
|
[=] { return CAST(context); },
|
// lazy_name
|
[=] { return CAST(name); }, typeof_mode, &direct_exit);
|
}
|
|
void AccessorAssembler::GenerateLoadGlobalICTrampoline(TypeofMode typeof_mode) {
|
typedef LoadGlobalDescriptor Descriptor;
|
|
Node* name = Parameter(Descriptor::kName);
|
Node* slot = Parameter(Descriptor::kSlot);
|
Node* context = Parameter(Descriptor::kContext);
|
Node* vector = LoadFeedbackVectorForStub();
|
|
Callable callable =
|
CodeFactory::LoadGlobalICInOptimizedCode(isolate(), typeof_mode);
|
TailCallStub(callable, context, name, slot, vector);
|
}
|
|
void AccessorAssembler::GenerateKeyedLoadIC() {
|
typedef LoadWithVectorDescriptor Descriptor;
|
|
Node* receiver = Parameter(Descriptor::kReceiver);
|
Node* name = Parameter(Descriptor::kName);
|
Node* slot = Parameter(Descriptor::kSlot);
|
Node* vector = Parameter(Descriptor::kVector);
|
Node* context = Parameter(Descriptor::kContext);
|
|
LoadICParameters p(context, receiver, name, slot, vector);
|
KeyedLoadIC(&p);
|
}
|
|
void AccessorAssembler::GenerateKeyedLoadICTrampoline() {
|
typedef LoadDescriptor Descriptor;
|
|
Node* receiver = Parameter(Descriptor::kReceiver);
|
Node* name = Parameter(Descriptor::kName);
|
Node* slot = Parameter(Descriptor::kSlot);
|
Node* context = Parameter(Descriptor::kContext);
|
Node* vector = LoadFeedbackVectorForStub();
|
|
TailCallBuiltin(Builtins::kKeyedLoadIC, context, receiver, name, slot,
|
vector);
|
}
|
|
void AccessorAssembler::GenerateKeyedLoadIC_Megamorphic() {
|
typedef LoadWithVectorDescriptor Descriptor;
|
|
Node* receiver = Parameter(Descriptor::kReceiver);
|
Node* name = Parameter(Descriptor::kName);
|
Node* slot = Parameter(Descriptor::kSlot);
|
Node* vector = Parameter(Descriptor::kVector);
|
Node* context = Parameter(Descriptor::kContext);
|
|
LoadICParameters p(context, receiver, name, slot, vector);
|
KeyedLoadICGeneric(&p);
|
}
|
|
void AccessorAssembler::GenerateKeyedLoadIC_PolymorphicName() {
|
typedef LoadWithVectorDescriptor Descriptor;
|
|
Node* receiver = Parameter(Descriptor::kReceiver);
|
Node* name = Parameter(Descriptor::kName);
|
Node* slot = Parameter(Descriptor::kSlot);
|
Node* vector = Parameter(Descriptor::kVector);
|
Node* context = Parameter(Descriptor::kContext);
|
|
LoadICParameters p(context, receiver, name, slot, vector);
|
KeyedLoadICPolymorphicName(&p);
|
}
|
|
void AccessorAssembler::GenerateStoreGlobalIC() {
|
typedef StoreGlobalWithVectorDescriptor Descriptor;
|
|
Node* name = Parameter(Descriptor::kName);
|
Node* value = Parameter(Descriptor::kValue);
|
Node* slot = Parameter(Descriptor::kSlot);
|
Node* vector = Parameter(Descriptor::kVector);
|
Node* context = Parameter(Descriptor::kContext);
|
|
StoreICParameters p(context, nullptr, name, value, slot, vector);
|
StoreGlobalIC(&p);
|
}
|
|
void AccessorAssembler::GenerateStoreGlobalICTrampoline() {
|
typedef StoreGlobalDescriptor Descriptor;
|
|
Node* name = Parameter(Descriptor::kName);
|
Node* value = Parameter(Descriptor::kValue);
|
Node* slot = Parameter(Descriptor::kSlot);
|
Node* context = Parameter(Descriptor::kContext);
|
Node* vector = LoadFeedbackVectorForStub();
|
|
TailCallBuiltin(Builtins::kStoreGlobalIC, context, name, value, slot, vector);
|
}
|
|
void AccessorAssembler::GenerateStoreIC() {
|
typedef StoreWithVectorDescriptor Descriptor;
|
|
Node* receiver = Parameter(Descriptor::kReceiver);
|
Node* name = Parameter(Descriptor::kName);
|
Node* value = Parameter(Descriptor::kValue);
|
Node* slot = Parameter(Descriptor::kSlot);
|
Node* vector = Parameter(Descriptor::kVector);
|
Node* context = Parameter(Descriptor::kContext);
|
|
StoreICParameters p(context, receiver, name, value, slot, vector);
|
StoreIC(&p);
|
}
|
|
void AccessorAssembler::GenerateStoreICTrampoline() {
|
typedef StoreDescriptor Descriptor;
|
|
Node* receiver = Parameter(Descriptor::kReceiver);
|
Node* name = Parameter(Descriptor::kName);
|
Node* value = Parameter(Descriptor::kValue);
|
Node* slot = Parameter(Descriptor::kSlot);
|
Node* context = Parameter(Descriptor::kContext);
|
Node* vector = LoadFeedbackVectorForStub();
|
|
TailCallBuiltin(Builtins::kStoreIC, context, receiver, name, value, slot,
|
vector);
|
}
|
|
void AccessorAssembler::GenerateKeyedStoreIC() {
|
typedef StoreWithVectorDescriptor Descriptor;
|
|
Node* receiver = Parameter(Descriptor::kReceiver);
|
Node* name = Parameter(Descriptor::kName);
|
Node* value = Parameter(Descriptor::kValue);
|
Node* slot = Parameter(Descriptor::kSlot);
|
Node* vector = Parameter(Descriptor::kVector);
|
Node* context = Parameter(Descriptor::kContext);
|
|
StoreICParameters p(context, receiver, name, value, slot, vector);
|
KeyedStoreIC(&p);
|
}
|
|
void AccessorAssembler::GenerateKeyedStoreICTrampoline() {
|
typedef StoreDescriptor Descriptor;
|
|
Node* receiver = Parameter(Descriptor::kReceiver);
|
Node* name = Parameter(Descriptor::kName);
|
Node* value = Parameter(Descriptor::kValue);
|
Node* slot = Parameter(Descriptor::kSlot);
|
Node* context = Parameter(Descriptor::kContext);
|
Node* vector = LoadFeedbackVectorForStub();
|
|
TailCallBuiltin(Builtins::kKeyedStoreIC, context, receiver, name, value, slot,
|
vector);
|
}
|
|
void AccessorAssembler::GenerateStoreInArrayLiteralIC() {
|
typedef StoreWithVectorDescriptor Descriptor;
|
|
Node* array = Parameter(Descriptor::kReceiver);
|
Node* index = Parameter(Descriptor::kName);
|
Node* value = Parameter(Descriptor::kValue);
|
Node* slot = Parameter(Descriptor::kSlot);
|
Node* vector = Parameter(Descriptor::kVector);
|
Node* context = Parameter(Descriptor::kContext);
|
|
StoreICParameters p(context, array, index, value, slot, vector);
|
StoreInArrayLiteralIC(&p);
|
}
|
|
void AccessorAssembler::GenerateCloneObjectIC() {
|
typedef CloneObjectWithVectorDescriptor Descriptor;
|
Node* source = Parameter(Descriptor::kSource);
|
Node* flags = Parameter(Descriptor::kFlags);
|
Node* slot = Parameter(Descriptor::kSlot);
|
Node* vector = Parameter(Descriptor::kVector);
|
Node* context = Parameter(Descriptor::kContext);
|
TVARIABLE(MaybeObject, var_handler);
|
Label if_handler(this, &var_handler);
|
Label miss(this, Label::kDeferred), try_polymorphic(this, Label::kDeferred),
|
try_megamorphic(this, Label::kDeferred);
|
|
CSA_SLOW_ASSERT(this, TaggedIsNotSmi(source));
|
Node* source_map = LoadMap(UncheckedCast<HeapObject>(source));
|
GotoIf(IsDeprecatedMap(source_map), &miss);
|
TNode<MaybeObject> feedback = TryMonomorphicCase(
|
slot, vector, source_map, &if_handler, &var_handler, &try_polymorphic);
|
|
BIND(&if_handler);
|
{
|
Comment("CloneObjectIC_if_handler");
|
|
// Handlers for the CloneObjectIC stub are weak references to the Map of
|
// a result object.
|
TNode<Map> result_map = CAST(var_handler.value());
|
TVARIABLE(Object, var_properties, EmptyFixedArrayConstant());
|
TVARIABLE(FixedArrayBase, var_elements, EmptyFixedArrayConstant());
|
|
Label allocate_object(this);
|
GotoIf(IsNullOrUndefined(source), &allocate_object);
|
CSA_SLOW_ASSERT(this, IsJSObjectMap(result_map));
|
|
// The IC fast case should only be taken if the result map a compatible
|
// elements kind with the source object.
|
TNode<FixedArrayBase> source_elements = LoadElements(source);
|
|
auto flags = ExtractFixedArrayFlag::kAllFixedArraysDontCopyCOW;
|
var_elements = CAST(CloneFixedArray(source_elements, flags));
|
|
// Copy the PropertyArray backing store. The source PropertyArray must be
|
// either an Smi, or a PropertyArray.
|
// FIXME: Make a CSA macro for this
|
TNode<Object> source_properties =
|
LoadObjectField(source, JSObject::kPropertiesOrHashOffset);
|
{
|
GotoIf(TaggedIsSmi(source_properties), &allocate_object);
|
GotoIf(IsEmptyFixedArray(source_properties), &allocate_object);
|
|
// This IC requires that the source object has fast properties
|
CSA_SLOW_ASSERT(this, IsPropertyArray(CAST(source_properties)));
|
TNode<IntPtrT> length = LoadPropertyArrayLength(
|
UncheckedCast<PropertyArray>(source_properties));
|
GotoIf(IntPtrEqual(length, IntPtrConstant(0)), &allocate_object);
|
|
auto mode = INTPTR_PARAMETERS;
|
var_properties = CAST(AllocatePropertyArray(length, mode));
|
CopyPropertyArrayValues(source_properties, var_properties.value(), length,
|
SKIP_WRITE_BARRIER, mode);
|
}
|
|
Goto(&allocate_object);
|
BIND(&allocate_object);
|
TNode<JSObject> object = UncheckedCast<JSObject>(AllocateJSObjectFromMap(
|
result_map, var_properties.value(), var_elements.value()));
|
ReturnIf(IsNullOrUndefined(source), object);
|
|
// Lastly, clone any in-object properties.
|
// Determine the inobject property capacity of both objects, and copy the
|
// smaller number into the resulting object.
|
Node* source_start = LoadMapInobjectPropertiesStartInWords(source_map);
|
Node* source_size = LoadMapInstanceSizeInWords(source_map);
|
Node* result_start = LoadMapInobjectPropertiesStartInWords(result_map);
|
Node* field_offset_difference =
|
TimesPointerSize(IntPtrSub(result_start, source_start));
|
BuildFastLoop(source_start, source_size,
|
[=](Node* field_index) {
|
Node* field_offset = TimesPointerSize(field_index);
|
Node* field = LoadObjectField(source, field_offset);
|
Node* result_offset =
|
IntPtrAdd(field_offset, field_offset_difference);
|
StoreObjectFieldNoWriteBarrier(object, result_offset,
|
field);
|
},
|
1, INTPTR_PARAMETERS, IndexAdvanceMode::kPost);
|
Return(object);
|
}
|
|
BIND(&try_polymorphic);
|
TNode<HeapObject> strong_feedback = ToStrongHeapObject(feedback, &miss);
|
{
|
Comment("CloneObjectIC_try_polymorphic");
|
GotoIfNot(IsWeakFixedArrayMap(LoadMap(strong_feedback)), &try_megamorphic);
|
HandlePolymorphicCase(source_map, CAST(strong_feedback), &if_handler,
|
&var_handler, &miss, 2);
|
}
|
|
BIND(&try_megamorphic);
|
{
|
Comment("CloneObjectIC_try_megamorphic");
|
CSA_ASSERT(
|
this,
|
Word32Or(WordEqual(strong_feedback,
|
LoadRoot(Heap::kuninitialized_symbolRootIndex)),
|
WordEqual(strong_feedback,
|
LoadRoot(Heap::kmegamorphic_symbolRootIndex))));
|
GotoIfNot(WordEqual(strong_feedback,
|
LoadRoot(Heap::kmegamorphic_symbolRootIndex)),
|
&miss);
|
TailCallRuntime(Runtime::kCloneObjectIC_Slow, context, source, flags);
|
}
|
|
BIND(&miss);
|
{
|
Comment("CloneObjectIC_miss");
|
Node* map_or_result = CallRuntime(Runtime::kCloneObjectIC_Miss, context,
|
source, flags, slot, vector);
|
var_handler = UncheckedCast<MaybeObject>(map_or_result);
|
GotoIf(IsMap(map_or_result), &if_handler);
|
CSA_ASSERT(this, IsJSObject(map_or_result));
|
Return(map_or_result);
|
}
|
}
|
|
} // namespace internal
|
} // namespace v8
|