blob: 29dd9fabf756e213daf8d0de39346a395a945854 [file] [log] [blame]
// Copyright 2017 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/ic/handler-configuration.h"
#include "src/codegen/code-factory.h"
#include "src/ic/handler-configuration-inl.h"
#include "src/objects/data-handler-inl.h"
#include "src/objects/maybe-object.h"
#include "src/objects/transitions.h"
namespace v8 {
namespace internal {
namespace {
template <typename BitField>
Handle<Smi> SetBitFieldValue(Isolate* isolate, Handle<Smi> smi_handler,
typename BitField::FieldType value) {
int config = smi_handler->value();
config = BitField::update(config, true);
return handle(Smi::FromInt(config), isolate);
}
// TODO(ishell): Remove templatezation once we move common bits from
// Load/StoreHandler to the base class.
template <typename ICHandler, bool fill_handler = true>
int InitPrototypeChecksImpl(Isolate* isolate, Handle<ICHandler> handler,
Handle<Smi>* smi_handler,
Handle<Map> lookup_start_object_map,
MaybeObjectHandle data1,
MaybeObjectHandle maybe_data2) {
int data_size = 1;
// Holder-is-receiver case itself does not add entries unless there is an
// optional data2 value provided.
DCHECK_IMPLIES(lookup_start_object_map->IsJSGlobalObjectMap(),
lookup_start_object_map->is_prototype_map());
if (lookup_start_object_map->IsPrimitiveMap() ||
lookup_start_object_map->is_access_check_needed()) {
DCHECK(!lookup_start_object_map->IsJSGlobalObjectMap());
// The validity cell check for primitive and global proxy receivers does
// not guarantee that certain native context ever had access to other
// native context. However, a handler created for one native context could
// be used in other native context through the megamorphic stub cache.
// So we record the original native context to which this handler
// corresponds.
if (fill_handler) {
Handle<Context> native_context = isolate->native_context();
handler->set_data2(HeapObjectReference::Weak(*native_context));
} else {
// Enable access checks on the lookup start object.
*smi_handler = SetBitFieldValue<
typename ICHandler::DoAccessCheckOnLookupStartObjectBits>(
isolate, *smi_handler, true);
}
data_size++;
} else if (lookup_start_object_map->is_dictionary_map() &&
!lookup_start_object_map->IsJSGlobalObjectMap()) {
if (!fill_handler) {
// Enable lookup on lookup start object.
*smi_handler =
SetBitFieldValue<typename ICHandler::LookupOnLookupStartObjectBits>(
isolate, *smi_handler, true);
}
}
if (fill_handler) {
handler->set_data1(*data1);
}
if (!maybe_data2.is_null()) {
if (fill_handler) {
// This value will go either to data2 or data3 slot depending on whether
// data2 slot is already occupied by native context.
if (data_size == 1) {
handler->set_data2(*maybe_data2);
} else {
DCHECK_EQ(2, data_size);
handler->set_data3(*maybe_data2);
}
}
data_size++;
}
return data_size;
}
// Returns 0 if the validity cell check is enough to ensure that the
// prototype chain from |lookup_start_object_map| till |holder| did not change.
// If the |holder| is an empty handle then the full prototype chain is
// checked.
template <typename ICHandler>
int GetHandlerDataSize(Isolate* isolate, Handle<Smi>* smi_handler,
Handle<Map> lookup_start_object_map,
MaybeObjectHandle data1,
MaybeObjectHandle maybe_data2 = MaybeObjectHandle()) {
DCHECK_NOT_NULL(smi_handler);
return InitPrototypeChecksImpl<ICHandler, false>(
isolate, Handle<ICHandler>(), smi_handler, lookup_start_object_map, data1,
maybe_data2);
}
template <typename ICHandler>
void InitPrototypeChecks(Isolate* isolate, Handle<ICHandler> handler,
Handle<Map> lookup_start_object_map,
MaybeObjectHandle data1,
MaybeObjectHandle maybe_data2 = MaybeObjectHandle()) {
InitPrototypeChecksImpl<ICHandler, true>(
isolate, handler, nullptr, lookup_start_object_map, data1, maybe_data2);
}
} // namespace
// static
Handle<Object> LoadHandler::LoadFromPrototype(
Isolate* isolate, Handle<Map> lookup_start_object_map,
Handle<JSReceiver> holder, Handle<Smi> smi_handler,
MaybeObjectHandle maybe_data1, MaybeObjectHandle maybe_data2) {
MaybeObjectHandle data1;
if (maybe_data1.is_null()) {
data1 = MaybeObjectHandle::Weak(holder);
} else {
data1 = maybe_data1;
}
int data_size = GetHandlerDataSize<LoadHandler>(
isolate, &smi_handler, lookup_start_object_map, data1, maybe_data2);
Handle<Object> validity_cell = Map::GetOrCreatePrototypeChainValidityCell(
lookup_start_object_map, isolate);
Handle<LoadHandler> handler = isolate->factory()->NewLoadHandler(data_size);
handler->set_smi_handler(*smi_handler);
handler->set_validity_cell(*validity_cell);
InitPrototypeChecks(isolate, handler, lookup_start_object_map, data1,
maybe_data2);
return handler;
}
// static
Handle<Object> LoadHandler::LoadFullChain(Isolate* isolate,
Handle<Map> lookup_start_object_map,
const MaybeObjectHandle& holder,
Handle<Smi> smi_handler) {
MaybeObjectHandle data1 = holder;
int data_size = GetHandlerDataSize<LoadHandler>(
isolate, &smi_handler, lookup_start_object_map, data1);
Handle<Object> validity_cell = Map::GetOrCreatePrototypeChainValidityCell(
lookup_start_object_map, isolate);
if (validity_cell->IsSmi()) {
DCHECK_EQ(1, data_size);
// Lookup on lookup start object isn't supported in case of a simple smi
// handler.
if (!LookupOnLookupStartObjectBits::decode(smi_handler->value())) {
return smi_handler;
}
}
Handle<LoadHandler> handler = isolate->factory()->NewLoadHandler(data_size);
handler->set_smi_handler(*smi_handler);
handler->set_validity_cell(*validity_cell);
InitPrototypeChecks(isolate, handler, lookup_start_object_map, data1);
return handler;
}
// static
KeyedAccessLoadMode LoadHandler::GetKeyedAccessLoadMode(MaybeObject handler) {
DisallowGarbageCollection no_gc;
if (handler->IsSmi()) {
int const raw_handler = handler.ToSmi().value();
Kind const kind = KindBits::decode(raw_handler);
if ((kind == Kind::kElement || kind == Kind::kIndexedString) &&
AllowOutOfBoundsBits::decode(raw_handler)) {
return LOAD_IGNORE_OUT_OF_BOUNDS;
}
}
return STANDARD_LOAD;
}
// static
KeyedAccessStoreMode StoreHandler::GetKeyedAccessStoreMode(
MaybeObject handler) {
DisallowGarbageCollection no_gc;
if (handler->IsSmi()) {
int const raw_handler = handler.ToSmi().value();
Kind const kind = KindBits::decode(raw_handler);
// All the handlers except the Slow Handler that use the
// KeyedAccessStoreMode, compute it using KeyedAccessStoreModeForBuiltin
// method. Hence if any other Handler get to this path, just return
// STANDARD_STORE.
if (kind != Kind::kSlow) {
return STANDARD_STORE;
}
KeyedAccessStoreMode store_mode =
KeyedAccessStoreModeBits::decode(raw_handler);
return store_mode;
}
return STANDARD_STORE;
}
// static
Handle<Object> StoreHandler::StoreElementTransition(
Isolate* isolate, Handle<Map> receiver_map, Handle<Map> transition,
KeyedAccessStoreMode store_mode, MaybeHandle<Object> prev_validity_cell) {
Handle<Object> code = ElementsTransitionAndStoreBuiltin(isolate, store_mode);
Handle<Object> validity_cell;
if (!prev_validity_cell.ToHandle(&validity_cell)) {
validity_cell =
Map::GetOrCreatePrototypeChainValidityCell(receiver_map, isolate);
}
Handle<StoreHandler> handler = isolate->factory()->NewStoreHandler(1);
handler->set_smi_handler(*code);
handler->set_validity_cell(*validity_cell);
handler->set_data1(HeapObjectReference::Weak(*transition));
return handler;
}
// static
MaybeObjectHandle StoreHandler::StoreOwnTransition(Isolate* isolate,
Handle<Map> transition_map) {
bool is_dictionary_map = transition_map->is_dictionary_map();
#ifdef DEBUG
if (!is_dictionary_map) {
InternalIndex descriptor = transition_map->LastAdded();
Handle<DescriptorArray> descriptors(
transition_map->instance_descriptors(isolate), isolate);
PropertyDetails details = descriptors->GetDetails(descriptor);
if (descriptors->GetKey(descriptor).IsPrivate()) {
DCHECK_EQ(DONT_ENUM, details.attributes());
} else {
DCHECK_EQ(NONE, details.attributes());
}
Representation representation = details.representation();
DCHECK(!representation.IsNone());
}
#endif
// Declarative handlers don't support access checks.
DCHECK(!transition_map->is_access_check_needed());
// StoreOwnTransition does not involve any prototype checks.
if (is_dictionary_map) {
DCHECK(!transition_map->IsJSGlobalObjectMap());
int config = KindBits::encode(Kind::kNormal);
return MaybeObjectHandle(Smi::FromInt(config), isolate);
} else {
return MaybeObjectHandle::Weak(transition_map);
}
}
// static
MaybeObjectHandle StoreHandler::StoreTransition(Isolate* isolate,
Handle<Map> transition_map) {
bool is_dictionary_map = transition_map->is_dictionary_map();
#ifdef DEBUG
if (!is_dictionary_map) {
InternalIndex descriptor = transition_map->LastAdded();
Handle<DescriptorArray> descriptors(
transition_map->instance_descriptors(isolate), isolate);
// Private fields must be added via StoreOwnTransition handler.
DCHECK(!descriptors->GetKey(descriptor).IsPrivateName());
PropertyDetails details = descriptors->GetDetails(descriptor);
if (descriptors->GetKey(descriptor).IsPrivate()) {
DCHECK_EQ(DONT_ENUM, details.attributes());
} else {
DCHECK_EQ(NONE, details.attributes());
}
Representation representation = details.representation();
DCHECK(!representation.IsNone());
}
#endif
// Declarative handlers don't support access checks.
DCHECK(!transition_map->is_access_check_needed());
// Get validity cell value if it is necessary for the handler.
Handle<Object> validity_cell;
if (is_dictionary_map || !transition_map->IsPrototypeValidityCellValid()) {
validity_cell =
Map::GetOrCreatePrototypeChainValidityCell(transition_map, isolate);
}
if (is_dictionary_map) {
DCHECK(!transition_map->IsJSGlobalObjectMap());
Handle<StoreHandler> handler = isolate->factory()->NewStoreHandler(0);
// Store normal with enabled lookup on receiver.
int config = KindBits::encode(Kind::kNormal) |
LookupOnLookupStartObjectBits::encode(true);
handler->set_smi_handler(Smi::FromInt(config));
handler->set_validity_cell(*validity_cell);
return MaybeObjectHandle(handler);
} else {
// Ensure the transition map contains a valid prototype validity cell.
if (!validity_cell.is_null()) {
transition_map->set_prototype_validity_cell(*validity_cell,
kRelaxedStore);
}
return MaybeObjectHandle::Weak(transition_map);
}
}
// static
Handle<Object> StoreHandler::StoreThroughPrototype(
Isolate* isolate, Handle<Map> receiver_map, Handle<JSReceiver> holder,
Handle<Smi> smi_handler, MaybeObjectHandle maybe_data1,
MaybeObjectHandle maybe_data2) {
MaybeObjectHandle data1;
if (maybe_data1.is_null()) {
data1 = MaybeObjectHandle::Weak(holder);
} else {
data1 = maybe_data1;
}
int data_size = GetHandlerDataSize<StoreHandler>(
isolate, &smi_handler, receiver_map, data1, maybe_data2);
Handle<Object> validity_cell =
Map::GetOrCreatePrototypeChainValidityCell(receiver_map, isolate);
Handle<StoreHandler> handler = isolate->factory()->NewStoreHandler(data_size);
handler->set_smi_handler(*smi_handler);
handler->set_validity_cell(*validity_cell);
InitPrototypeChecks(isolate, handler, receiver_map, data1, maybe_data2);
return handler;
}
// static
MaybeObjectHandle StoreHandler::StoreGlobal(Handle<PropertyCell> cell) {
return MaybeObjectHandle::Weak(cell);
}
// static
Handle<Object> StoreHandler::StoreProxy(Isolate* isolate,
Handle<Map> receiver_map,
Handle<JSProxy> proxy,
Handle<JSReceiver> receiver) {
Handle<Smi> smi_handler = StoreProxy(isolate);
if (receiver.is_identical_to(proxy)) return smi_handler;
return StoreThroughPrototype(isolate, receiver_map, proxy, smi_handler,
MaybeObjectHandle::Weak(proxy));
}
bool LoadHandler::CanHandleHolderNotLookupStart(Object handler) {
if (handler.IsSmi()) {
auto kind = LoadHandler::KindBits::decode(handler.ToSmi().value());
return kind == LoadHandler::Kind::kSlow ||
kind == LoadHandler::Kind::kNonExistent;
}
return handler.IsLoadHandler();
}
#if defined(OBJECT_PRINT)
namespace {
void PrintSmiLoadHandler(int raw_handler, std::ostream& os) {
LoadHandler::Kind kind = LoadHandler::KindBits::decode(raw_handler);
os << "kind = ";
switch (kind) {
case LoadHandler::Kind::kElement:
os << "kElement, ";
if (LoadHandler::IsWasmArrayBits::decode(raw_handler)) {
os << "WasmArray, "
<< LoadHandler::WasmArrayTypeBits::decode(raw_handler);
} else {
os << "allow out of bounds = "
<< LoadHandler::AllowOutOfBoundsBits::decode(raw_handler)
<< ", is JSArray = "
<< LoadHandler::IsJsArrayBits::decode(raw_handler)
<< ", convert hole = "
<< LoadHandler::ConvertHoleBits::decode(raw_handler)
<< ", elements kind = "
<< ElementsKindToString(
LoadHandler::ElementsKindBits::decode(raw_handler));
}
break;
case LoadHandler::Kind::kIndexedString:
os << "kIndexedString, allow out of bounds = "
<< LoadHandler::AllowOutOfBoundsBits::decode(raw_handler);
break;
case LoadHandler::Kind::kNormal:
os << "kNormal";
break;
case LoadHandler::Kind::kGlobal:
os << "kGlobal";
break;
case LoadHandler::Kind::kField: {
if (LoadHandler::IsWasmStructBits::decode(raw_handler)) {
os << "kField, WasmStruct, type = "
<< LoadHandler::WasmFieldTypeBits::decode(raw_handler)
<< ", field offset = "
<< LoadHandler::WasmFieldOffsetBits::decode(raw_handler);
} else {
os << "kField, is in object = "
<< LoadHandler::IsInobjectBits::decode(raw_handler)
<< ", is double = " << LoadHandler::IsDoubleBits::decode(raw_handler)
<< ", field index = "
<< LoadHandler::FieldIndexBits::decode(raw_handler);
}
break;
}
case LoadHandler::Kind::kConstantFromPrototype:
os << "kConstantFromPrototype";
break;
case LoadHandler::Kind::kAccessorFromPrototype:
os << "kAccessorFromPrototype";
break;
case LoadHandler::Kind::kNativeDataProperty:
os << "kNativeDataProperty, descriptor = "
<< LoadHandler::DescriptorBits::decode(raw_handler);
break;
case LoadHandler::Kind::kApiGetter:
os << "kApiGetter";
break;
case LoadHandler::Kind::kApiGetterHolderIsPrototype:
os << "kApiGetterHolderIsPrototype";
break;
case LoadHandler::Kind::kInterceptor:
os << "kInterceptor";
break;
case LoadHandler::Kind::kSlow:
os << "kSlow";
break;
case LoadHandler::Kind::kProxy:
os << "kProxy";
break;
case LoadHandler::Kind::kNonExistent:
os << "kNonExistent";
break;
case LoadHandler::Kind::kModuleExport:
os << "kModuleExport, exports index = "
<< LoadHandler::ExportsIndexBits::decode(raw_handler);
break;
default:
os << "<invalid value " << static_cast<int>(kind) << ">";
break;
}
}
const char* KeyedAccessStoreModeToString(KeyedAccessStoreMode mode) {
switch (mode) {
case STANDARD_STORE:
return "STANDARD_STORE";
case STORE_AND_GROW_HANDLE_COW:
return "STORE_AND_GROW_HANDLE_COW";
case STORE_IGNORE_OUT_OF_BOUNDS:
return "STORE_IGNORE_OUT_OF_BOUNDS";
case STORE_HANDLE_COW:
return "STORE_HANDLE_COW";
}
UNREACHABLE();
}
void PrintSmiStoreHandler(int raw_handler, std::ostream& os) {
StoreHandler::Kind kind = StoreHandler::KindBits::decode(raw_handler);
os << "kind = ";
switch (kind) {
case StoreHandler::Kind::kField:
case StoreHandler::Kind::kConstField: {
os << "k";
if (kind == StoreHandler::Kind::kConstField) {
os << "Const";
}
Representation representation = Representation::FromKind(
StoreHandler::RepresentationBits::decode(raw_handler));
os << "Field, descriptor = "
<< StoreHandler::DescriptorBits::decode(raw_handler)
<< ", is in object = "
<< StoreHandler::IsInobjectBits::decode(raw_handler)
<< ", representation = " << representation.Mnemonic()
<< ", field index = "
<< StoreHandler::FieldIndexBits::decode(raw_handler);
break;
}
case StoreHandler::Kind::kAccessor:
os << "kAccessor, descriptor = "
<< StoreHandler::DescriptorBits::decode(raw_handler);
break;
case StoreHandler::Kind::kNativeDataProperty:
os << "kNativeDataProperty, descriptor = "
<< StoreHandler::DescriptorBits::decode(raw_handler);
break;
case StoreHandler::Kind::kApiSetter:
os << "kApiSetter";
break;
case StoreHandler::Kind::kApiSetterHolderIsPrototype:
os << "kApiSetterHolderIsPrototype";
break;
case StoreHandler::Kind::kGlobalProxy:
os << "kGlobalProxy";
break;
case StoreHandler::Kind::kNormal:
os << "kNormal";
break;
case StoreHandler::Kind::kInterceptor:
os << "kInterceptor";
break;
case StoreHandler::Kind::kSlow: {
KeyedAccessStoreMode keyed_access_store_mode =
StoreHandler::KeyedAccessStoreModeBits::decode(raw_handler);
os << "kSlow, keyed access store mode = "
<< KeyedAccessStoreModeToString(keyed_access_store_mode);
break;
}
case StoreHandler::Kind::kProxy:
os << "kProxy";
break;
case StoreHandler::Kind::kSharedStructField:
os << "kSharedStructField";
break;
case StoreHandler::Kind::kKindsNumber:
UNREACHABLE();
}
}
} // namespace
// static
void LoadHandler::PrintHandler(Object handler, std::ostream& os) {
DisallowGarbageCollection no_gc;
if (handler.IsSmi()) {
int raw_handler = handler.ToSmi().value();
os << "LoadHandler(Smi)(";
PrintSmiLoadHandler(raw_handler, os);
os << ")";
} else if (handler.IsCode()) {
os << "LoadHandler(Code)("
<< Builtins::name(Code::cast(handler).builtin_id()) << ")";
} else if (handler.IsSymbol()) {
os << "LoadHandler(Symbol)(" << Brief(Symbol::cast(handler)) << ")";
} else if (handler.IsLoadHandler()) {
LoadHandler load_handler = LoadHandler::cast(handler);
int raw_handler = load_handler.smi_handler().ToSmi().value();
os << "LoadHandler(do access check on lookup start object = "
<< DoAccessCheckOnLookupStartObjectBits::decode(raw_handler)
<< ", lookup on lookup start object = "
<< LookupOnLookupStartObjectBits::decode(raw_handler) << ", ";
PrintSmiLoadHandler(raw_handler, os);
if (load_handler.data_field_count() >= 1) {
os << ", data1 = ";
load_handler.data1().ShortPrint(os);
}
if (load_handler.data_field_count() >= 2) {
os << ", data2 = ";
load_handler.data2().ShortPrint(os);
}
if (load_handler.data_field_count() >= 3) {
os << ", data3 = ";
load_handler.data3().ShortPrint(os);
}
os << ", validity cell = ";
load_handler.validity_cell().ShortPrint(os);
os << ")";
} else {
os << "LoadHandler(<unexpected>)(" << Brief(handler) << ")";
}
}
void StoreHandler::PrintHandler(Object handler, std::ostream& os) {
DisallowGarbageCollection no_gc;
if (handler.IsSmi()) {
int raw_handler = handler.ToSmi().value();
os << "StoreHandler(Smi)(";
PrintSmiStoreHandler(raw_handler, os);
os << ")" << std::endl;
} else if (handler.IsStoreHandler()) {
os << "StoreHandler(";
StoreHandler store_handler = StoreHandler::cast(handler);
if (store_handler.smi_handler().IsCode()) {
Code code = Code::cast(store_handler.smi_handler());
os << "builtin = ";
code.ShortPrint(os);
} else {
int raw_handler = store_handler.smi_handler().ToSmi().value();
os << "do access check on lookup start object = "
<< DoAccessCheckOnLookupStartObjectBits::decode(raw_handler)
<< ", lookup on lookup start object = "
<< LookupOnLookupStartObjectBits::decode(raw_handler) << ", ";
PrintSmiStoreHandler(raw_handler, os);
}
if (store_handler.data_field_count() >= 1) {
os << ", data1 = ";
store_handler.data1().ShortPrint(os);
}
if (store_handler.data_field_count() >= 2) {
os << ", data2 = ";
store_handler.data2().ShortPrint(os);
}
if (store_handler.data_field_count() >= 3) {
os << ", data3 = ";
store_handler.data3().ShortPrint(os);
}
os << ", validity cell = ";
store_handler.validity_cell().ShortPrint(os);
os << ")" << std::endl;
} else {
os << "StoreHandler(<unexpected>)(" << Brief(handler) << ")";
}
}
std::ostream& operator<<(std::ostream& os, WasmValueType type) {
return os << WasmValueType2String(type);
}
#endif // defined(OBJECT_PRINT)
} // namespace internal
} // namespace v8