| // 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/code-stubs.h" |
| #include "src/ic/handler-configuration-inl.h" |
| #include "src/transitions.h" |
| |
| namespace v8 { |
| namespace internal { |
| |
| namespace { |
| |
| template <bool fill_array = true> |
| int InitPrototypeChecks(Isolate* isolate, Handle<Map> receiver_map, |
| Handle<JSReceiver> holder, Handle<Name> name, |
| Handle<FixedArray> array, int first_index) { |
| if (!holder.is_null() && holder->map() == *receiver_map) return 0; |
| |
| HandleScope scope(isolate); |
| int checks_count = 0; |
| |
| if (receiver_map->IsPrimitiveMap() || receiver_map->IsJSGlobalProxyMap()) { |
| // 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_array) { |
| Handle<Context> native_context = isolate->native_context(); |
| array->set(first_index + checks_count, native_context->self_weak_cell()); |
| } |
| checks_count++; |
| |
| } else if (receiver_map->IsJSGlobalObjectMap()) { |
| // If we are creating a handler for [Load/Store]GlobalIC then we need to |
| // check that the property did not appear in the global object. |
| if (fill_array) { |
| Handle<JSGlobalObject> global = isolate->global_object(); |
| Handle<PropertyCell> cell = JSGlobalObject::EnsureEmptyPropertyCell( |
| global, name, PropertyCellType::kInvalidated); |
| DCHECK(cell->value()->IsTheHole(isolate)); |
| Handle<WeakCell> weak_cell = isolate->factory()->NewWeakCell(cell); |
| array->set(first_index + checks_count, *weak_cell); |
| } |
| checks_count++; |
| } |
| |
| // Create/count entries for each global or dictionary prototype appeared in |
| // the prototype chain contains from receiver till holder. |
| PrototypeIterator::WhereToEnd end = name->IsPrivate() |
| ? PrototypeIterator::END_AT_NON_HIDDEN |
| : PrototypeIterator::END_AT_NULL; |
| for (PrototypeIterator iter(receiver_map, end); !iter.IsAtEnd(); |
| iter.Advance()) { |
| Handle<JSReceiver> current = |
| PrototypeIterator::GetCurrent<JSReceiver>(iter); |
| if (holder.is_identical_to(current)) break; |
| Handle<Map> current_map(current->map(), isolate); |
| |
| if (current_map->IsJSGlobalObjectMap()) { |
| if (fill_array) { |
| Handle<JSGlobalObject> global = Handle<JSGlobalObject>::cast(current); |
| Handle<PropertyCell> cell = JSGlobalObject::EnsureEmptyPropertyCell( |
| global, name, PropertyCellType::kInvalidated); |
| DCHECK(cell->value()->IsTheHole(isolate)); |
| Handle<WeakCell> weak_cell = isolate->factory()->NewWeakCell(cell); |
| array->set(first_index + checks_count, *weak_cell); |
| } |
| checks_count++; |
| |
| } else if (current_map->is_dictionary_map()) { |
| DCHECK(!current_map->IsJSGlobalProxyMap()); // Proxy maps are fast. |
| if (fill_array) { |
| DCHECK_EQ(NameDictionary::kNotFound, |
| current->property_dictionary()->FindEntry(name)); |
| Handle<WeakCell> weak_cell = |
| Map::GetOrCreatePrototypeWeakCell(current, isolate); |
| array->set(first_index + checks_count, *weak_cell); |
| } |
| checks_count++; |
| } |
| } |
| return checks_count; |
| } |
| |
| // Returns 0 if the validity cell check is enough to ensure that the |
| // prototype chain from |receiver_map| till |holder| did not change. |
| // If the |holder| is an empty handle then the full prototype chain is |
| // checked. |
| // Returns -1 if the handler has to be compiled or the number of prototype |
| // checks otherwise. |
| int GetPrototypeCheckCount(Isolate* isolate, Handle<Map> receiver_map, |
| Handle<JSReceiver> holder, Handle<Name> name) { |
| return InitPrototypeChecks<false>(isolate, receiver_map, holder, name, |
| Handle<FixedArray>(), 0); |
| } |
| |
| enum class HolderCellRequest { |
| kGlobalPropertyCell, |
| kHolder, |
| }; |
| |
| Handle<WeakCell> HolderCell(Isolate* isolate, Handle<JSReceiver> holder, |
| Handle<Name> name, HolderCellRequest request) { |
| if (request == HolderCellRequest::kGlobalPropertyCell) { |
| DCHECK(holder->IsJSGlobalObject()); |
| Handle<JSGlobalObject> global = Handle<JSGlobalObject>::cast(holder); |
| GlobalDictionary* dict = global->global_dictionary(); |
| int number = dict->FindEntry(name); |
| DCHECK_NE(NameDictionary::kNotFound, number); |
| Handle<PropertyCell> cell(dict->CellAt(number), isolate); |
| return isolate->factory()->NewWeakCell(cell); |
| } |
| return Map::GetOrCreatePrototypeWeakCell(holder, isolate); |
| } |
| |
| } // namespace |
| |
| // static |
| Handle<Object> LoadHandler::LoadFromPrototype(Isolate* isolate, |
| Handle<Map> receiver_map, |
| Handle<JSReceiver> holder, |
| Handle<Name> name, |
| Handle<Smi> smi_handler) { |
| int checks_count = |
| GetPrototypeCheckCount(isolate, receiver_map, holder, name); |
| DCHECK_LE(0, checks_count); |
| |
| if (receiver_map->IsPrimitiveMap() || |
| receiver_map->is_access_check_needed()) { |
| DCHECK(!receiver_map->is_dictionary_map()); |
| DCHECK_LE(1, checks_count); // For native context. |
| smi_handler = EnableAccessCheckOnReceiver(isolate, smi_handler); |
| } else if (receiver_map->is_dictionary_map() && |
| !receiver_map->IsJSGlobalObjectMap()) { |
| smi_handler = EnableLookupOnReceiver(isolate, smi_handler); |
| } |
| |
| Handle<Cell> validity_cell = |
| Map::GetOrCreatePrototypeChainValidityCell(receiver_map, isolate); |
| DCHECK(!validity_cell.is_null()); |
| |
| // LoadIC dispatcher expects PropertyCell as a "holder" in case of kGlobal |
| // handler kind. |
| HolderCellRequest request = GetHandlerKind(*smi_handler) == kGlobal |
| ? HolderCellRequest::kGlobalPropertyCell |
| : HolderCellRequest::kHolder; |
| |
| Handle<WeakCell> holder_cell = HolderCell(isolate, holder, name, request); |
| |
| if (checks_count == 0) { |
| return isolate->factory()->NewTuple3(holder_cell, smi_handler, |
| validity_cell, TENURED); |
| } |
| Handle<FixedArray> handler_array(isolate->factory()->NewFixedArray( |
| kFirstPrototypeIndex + checks_count, TENURED)); |
| handler_array->set(kSmiHandlerIndex, *smi_handler); |
| handler_array->set(kValidityCellIndex, *validity_cell); |
| handler_array->set(kHolderCellIndex, *holder_cell); |
| InitPrototypeChecks(isolate, receiver_map, holder, name, handler_array, |
| kFirstPrototypeIndex); |
| return handler_array; |
| } |
| |
| // static |
| Handle<Object> LoadHandler::LoadFullChain(Isolate* isolate, |
| Handle<Map> receiver_map, |
| Handle<Object> holder, |
| Handle<Name> name, |
| Handle<Smi> smi_handler) { |
| Handle<JSReceiver> end; // null handle |
| int checks_count = GetPrototypeCheckCount(isolate, receiver_map, end, name); |
| DCHECK_LE(0, checks_count); |
| |
| if (receiver_map->IsPrimitiveMap() || |
| receiver_map->is_access_check_needed()) { |
| DCHECK(!receiver_map->is_dictionary_map()); |
| DCHECK_LE(1, checks_count); // For native context. |
| smi_handler = EnableAccessCheckOnReceiver(isolate, smi_handler); |
| } else if (receiver_map->is_dictionary_map() && |
| !receiver_map->IsJSGlobalObjectMap()) { |
| smi_handler = EnableLookupOnReceiver(isolate, smi_handler); |
| } |
| |
| Handle<Object> validity_cell = |
| Map::GetOrCreatePrototypeChainValidityCell(receiver_map, isolate); |
| if (validity_cell.is_null()) { |
| DCHECK_EQ(0, checks_count); |
| // Lookup on receiver isn't supported in case of a simple smi handler. |
| if (!LookupOnReceiverBits::decode(smi_handler->value())) return smi_handler; |
| validity_cell = handle(Smi::kZero, isolate); |
| } |
| |
| Factory* factory = isolate->factory(); |
| if (checks_count == 0) { |
| return factory->NewTuple3(holder, smi_handler, validity_cell, TENURED); |
| } |
| Handle<FixedArray> handler_array(factory->NewFixedArray( |
| LoadHandler::kFirstPrototypeIndex + checks_count, TENURED)); |
| handler_array->set(kSmiHandlerIndex, *smi_handler); |
| handler_array->set(kValidityCellIndex, *validity_cell); |
| handler_array->set(kHolderCellIndex, *holder); |
| InitPrototypeChecks(isolate, receiver_map, end, name, handler_array, |
| kFirstPrototypeIndex); |
| return handler_array; |
| } |
| |
| // static |
| Handle<Object> StoreHandler::StoreElementTransition( |
| Isolate* isolate, Handle<Map> receiver_map, Handle<Map> transition, |
| KeyedAccessStoreMode store_mode) { |
| bool is_js_array = receiver_map->instance_type() == JS_ARRAY_TYPE; |
| ElementsKind elements_kind = receiver_map->elements_kind(); |
| Handle<Code> stub = ElementsTransitionAndStoreStub( |
| isolate, elements_kind, transition->elements_kind(), |
| is_js_array, store_mode) |
| .GetCode(); |
| Handle<Object> validity_cell = |
| Map::GetOrCreatePrototypeChainValidityCell(receiver_map, isolate); |
| if (validity_cell.is_null()) { |
| validity_cell = handle(Smi::kZero, isolate); |
| } |
| Handle<WeakCell> cell = Map::WeakCellForMap(transition); |
| return isolate->factory()->NewTuple3(cell, stub, validity_cell, TENURED); |
| } |
| |
| // static |
| Handle<Object> StoreHandler::StoreTransition(Isolate* isolate, |
| Handle<Map> receiver_map, |
| Handle<JSObject> holder, |
| Handle<Map> transition, |
| Handle<Name> name) { |
| Handle<Object> smi_handler; |
| if (transition->is_dictionary_map()) { |
| smi_handler = StoreNormal(isolate); |
| } else { |
| int descriptor = transition->LastAdded(); |
| Handle<DescriptorArray> descriptors(transition->instance_descriptors()); |
| PropertyDetails details = descriptors->GetDetails(descriptor); |
| Representation representation = details.representation(); |
| DCHECK(!representation.IsNone()); |
| |
| // Declarative handlers don't support access checks. |
| DCHECK(!transition->is_access_check_needed()); |
| |
| DCHECK_EQ(kData, details.kind()); |
| if (details.location() == kDescriptor) { |
| smi_handler = TransitionToConstant(isolate, descriptor); |
| |
| } else { |
| DCHECK_EQ(kField, details.location()); |
| bool extend_storage = |
| Map::cast(transition->GetBackPointer())->unused_property_fields() == |
| 0; |
| |
| FieldIndex index = FieldIndex::ForDescriptor(*transition, descriptor); |
| smi_handler = TransitionToField(isolate, descriptor, index, |
| representation, extend_storage); |
| } |
| } |
| // |holder| is either a receiver if the property is non-existent or |
| // one of the prototypes. |
| DCHECK(!holder.is_null()); |
| bool is_nonexistent = holder->map() == transition->GetBackPointer(); |
| if (is_nonexistent) holder = Handle<JSObject>::null(); |
| |
| int checks_count = |
| GetPrototypeCheckCount(isolate, receiver_map, holder, name); |
| |
| DCHECK_LE(0, checks_count); |
| DCHECK(!receiver_map->IsJSGlobalObjectMap()); |
| |
| Handle<Object> validity_cell = |
| Map::GetOrCreatePrototypeChainValidityCell(receiver_map, isolate); |
| if (validity_cell.is_null()) { |
| DCHECK_EQ(0, checks_count); |
| validity_cell = handle(Smi::kZero, isolate); |
| } |
| |
| Handle<WeakCell> transition_cell = Map::WeakCellForMap(transition); |
| |
| Factory* factory = isolate->factory(); |
| if (checks_count == 0) { |
| return factory->NewTuple3(transition_cell, smi_handler, validity_cell, |
| TENURED); |
| } |
| Handle<FixedArray> handler_array( |
| factory->NewFixedArray(kFirstPrototypeIndex + checks_count, TENURED)); |
| handler_array->set(kSmiHandlerIndex, *smi_handler); |
| handler_array->set(kValidityCellIndex, *validity_cell); |
| handler_array->set(kTransitionCellIndex, *transition_cell); |
| InitPrototypeChecks(isolate, receiver_map, holder, name, handler_array, |
| kFirstPrototypeIndex); |
| return handler_array; |
| } |
| |
| // static |
| Handle<Object> StoreHandler::StoreProxy(Isolate* isolate, |
| Handle<Map> receiver_map, |
| Handle<JSProxy> proxy, |
| Handle<JSReceiver> receiver, |
| Handle<Name> name) { |
| Handle<Object> smi_handler = StoreProxy(isolate); |
| |
| if (receiver.is_identical_to(proxy)) return smi_handler; |
| |
| int checks_count = GetPrototypeCheckCount(isolate, receiver_map, proxy, name); |
| |
| DCHECK_LE(0, checks_count); |
| |
| Handle<Object> validity_cell = |
| Map::GetOrCreatePrototypeChainValidityCell(receiver_map, isolate); |
| if (validity_cell.is_null()) { |
| DCHECK_EQ(0, checks_count); |
| validity_cell = handle(Smi::kZero, isolate); |
| } |
| |
| Factory* factory = isolate->factory(); |
| Handle<WeakCell> holder_cell = factory->NewWeakCell(proxy); |
| |
| if (checks_count == 0) { |
| return factory->NewTuple3(holder_cell, smi_handler, validity_cell, TENURED); |
| } |
| Handle<FixedArray> handler_array( |
| factory->NewFixedArray(kFirstPrototypeIndex + checks_count, TENURED)); |
| handler_array->set(kSmiHandlerIndex, *smi_handler); |
| handler_array->set(kValidityCellIndex, *validity_cell); |
| handler_array->set(kTransitionCellIndex, *holder_cell); |
| InitPrototypeChecks(isolate, receiver_map, proxy, name, handler_array, |
| kFirstPrototypeIndex); |
| return handler_array; |
| } |
| |
| Object* StoreHandler::ValidHandlerOrNull(Object* raw_handler, Name* name, |
| Handle<Map>* out_transition) { |
| STATIC_ASSERT(kValidityCellOffset == Tuple3::kValue3Offset); |
| |
| Smi* valid = Smi::FromInt(Map::kPrototypeChainValid); |
| |
| if (raw_handler->IsTuple3()) { |
| // Check validity cell. |
| Tuple3* handler = Tuple3::cast(raw_handler); |
| |
| Object* raw_validity_cell = handler->value3(); |
| // |raw_valitity_cell| can be Smi::kZero if no validity cell is required |
| // (which counts as valid). |
| if (raw_validity_cell->IsCell() && |
| Cell::cast(raw_validity_cell)->value() != valid) { |
| return nullptr; |
| } |
| |
| } else { |
| DCHECK(raw_handler->IsFixedArray()); |
| FixedArray* handler = FixedArray::cast(raw_handler); |
| |
| // Check validity cell. |
| Object* value = Cell::cast(handler->get(kValidityCellIndex))->value(); |
| if (value != valid) return nullptr; |
| |
| // Check prototypes. |
| Heap* heap = handler->GetHeap(); |
| Isolate* isolate = heap->isolate(); |
| Handle<Name> name_handle(name, isolate); |
| for (int i = kFirstPrototypeIndex; i < handler->length(); i++) { |
| // This mirrors AccessorAssembler::CheckPrototype. |
| WeakCell* prototype_cell = WeakCell::cast(handler->get(i)); |
| if (prototype_cell->cleared()) return nullptr; |
| HeapObject* maybe_prototype = HeapObject::cast(prototype_cell->value()); |
| if (maybe_prototype->IsPropertyCell()) { |
| Object* value = PropertyCell::cast(maybe_prototype)->value(); |
| if (value != heap->the_hole_value()) return nullptr; |
| } else { |
| DCHECK(maybe_prototype->map()->is_dictionary_map()); |
| // Do a negative dictionary lookup. |
| NameDictionary* dict = |
| JSObject::cast(maybe_prototype)->property_dictionary(); |
| int number = dict->FindEntry(isolate, name_handle); |
| if (number != NameDictionary::kNotFound) { |
| PropertyDetails details = dict->DetailsAt(number); |
| if (details.IsReadOnly() || details.kind() == kAccessor) { |
| return nullptr; |
| } |
| break; |
| } |
| } |
| } |
| } |
| |
| // Check if the transition target is deprecated. |
| WeakCell* target_cell = GetTransitionCell(raw_handler); |
| Map* transition = Map::cast(target_cell->value()); |
| if (transition->is_deprecated()) return nullptr; |
| *out_transition = handle(transition); |
| return raw_handler; |
| } |
| |
| } // namespace internal |
| } // namespace v8 |