%PDF- %PDF-
Direktori : /home/vacivi36/vittasync.vacivitta.com.br/vittasync/node/deps/v8/src/compiler/ |
Current File : /home/vacivi36/vittasync.vacivitta.com.br/vittasync/node/deps/v8/src/compiler/access-info.cc |
// Copyright 2015 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/compiler/access-info.h" #include <ostream> #include "src/builtins/accessors.h" #include "src/compiler/compilation-dependencies.h" #include "src/compiler/heap-refs.h" #include "src/compiler/js-heap-broker-inl.h" #include "src/compiler/simplified-operator.h" #include "src/compiler/type-cache.h" #include "src/ic/call-optimization.h" #include "src/objects/cell-inl.h" #include "src/objects/field-index-inl.h" #include "src/objects/field-type.h" #include "src/objects/objects-inl.h" #include "src/objects/struct-inl.h" #include "src/objects/templates.h" namespace v8 { namespace internal { namespace compiler { namespace { bool CanInlinePropertyAccess(MapRef map, AccessMode access_mode) { // We can inline property access to prototypes of all primitives, except // the special Oddball ones that have no wrapper counterparts (i.e. Null, // Undefined and TheHole). // We can only inline accesses to dictionary mode holders if the access is a // load and the holder is a prototype. The latter ensures a 1:1 // relationship between the map and the object (and therefore the property // dictionary). static_assert(ODDBALL_TYPE == LAST_PRIMITIVE_HEAP_OBJECT_TYPE); if (IsBooleanMap(*map.object())) return true; if (map.instance_type() < LAST_PRIMITIVE_HEAP_OBJECT_TYPE) return true; if (IsJSObjectMap(*map.object())) { if (map.is_dictionary_map()) { if (!V8_DICT_PROPERTY_CONST_TRACKING_BOOL) return false; return access_mode == AccessMode::kLoad && map.object()->is_prototype_map(); } return !map.object()->has_named_interceptor() && // TODO(verwaest): Allowlist contexts to which we have access. !map.is_access_check_needed(); } return false; } #ifdef DEBUG bool HasFieldRepresentationDependenciesOnMap( ZoneVector<CompilationDependency const*>& dependencies, Handle<Map> const& field_owner_map) { for (auto dep : dependencies) { if (CompilationDependencies::IsFieldRepresentationDependencyOnMap( dep, field_owner_map)) { return true; } } return false; } #endif } // namespace std::ostream& operator<<(std::ostream& os, AccessMode access_mode) { switch (access_mode) { case AccessMode::kLoad: return os << "Load"; case AccessMode::kStore: return os << "Store"; case AccessMode::kStoreInLiteral: return os << "StoreInLiteral"; case AccessMode::kHas: return os << "Has"; case AccessMode::kDefine: return os << "Define"; } UNREACHABLE(); } ElementAccessInfo::ElementAccessInfo( ZoneVector<MapRef>&& lookup_start_object_maps, ElementsKind elements_kind, Zone* zone) : elements_kind_(elements_kind), lookup_start_object_maps_(lookup_start_object_maps), transition_sources_(zone) { CHECK(!lookup_start_object_maps.empty()); } // static PropertyAccessInfo PropertyAccessInfo::Invalid(Zone* zone) { return PropertyAccessInfo(zone); } // static PropertyAccessInfo PropertyAccessInfo::NotFound(Zone* zone, MapRef receiver_map, OptionalJSObjectRef holder) { return PropertyAccessInfo(zone, kNotFound, holder, {{receiver_map}, zone}); } // static PropertyAccessInfo PropertyAccessInfo::DataField( JSHeapBroker* broker, Zone* zone, MapRef receiver_map, ZoneVector<CompilationDependency const*>&& dependencies, FieldIndex field_index, Representation field_representation, Type field_type, MapRef field_owner_map, OptionalMapRef field_map, OptionalJSObjectRef holder, OptionalMapRef transition_map) { DCHECK(!field_representation.IsNone()); DCHECK_IMPLIES( field_representation.IsDouble(), HasFieldRepresentationDependenciesOnMap( dependencies, transition_map.has_value() ? transition_map->object() : holder.has_value() ? holder->map(broker).object() : receiver_map.object())); return PropertyAccessInfo(kDataField, holder, transition_map, field_index, field_representation, field_type, field_owner_map, field_map, {{receiver_map}, zone}, std::move(dependencies)); } // static PropertyAccessInfo PropertyAccessInfo::FastDataConstant( Zone* zone, MapRef receiver_map, ZoneVector<CompilationDependency const*>&& dependencies, FieldIndex field_index, Representation field_representation, Type field_type, MapRef field_owner_map, OptionalMapRef field_map, OptionalJSObjectRef holder, OptionalMapRef transition_map) { DCHECK(!field_representation.IsNone()); return PropertyAccessInfo(kFastDataConstant, holder, transition_map, field_index, field_representation, field_type, field_owner_map, field_map, {{receiver_map}, zone}, std::move(dependencies)); } // static PropertyAccessInfo PropertyAccessInfo::FastAccessorConstant( Zone* zone, MapRef receiver_map, OptionalJSObjectRef holder, OptionalObjectRef constant, OptionalJSObjectRef api_holder) { return PropertyAccessInfo(zone, kFastAccessorConstant, holder, constant, api_holder, {} /* name */, {{receiver_map}, zone}); } // static PropertyAccessInfo PropertyAccessInfo::ModuleExport(Zone* zone, MapRef receiver_map, CellRef cell) { return PropertyAccessInfo(zone, kModuleExport, {} /* holder */, cell /* constant */, {} /* api_holder */, {} /* name */, {{receiver_map}, zone}); } // static PropertyAccessInfo PropertyAccessInfo::StringLength(Zone* zone, MapRef receiver_map) { return PropertyAccessInfo(zone, kStringLength, {}, {{receiver_map}, zone}); } // static PropertyAccessInfo PropertyAccessInfo::DictionaryProtoDataConstant( Zone* zone, MapRef receiver_map, JSObjectRef holder, InternalIndex dictionary_index, NameRef name) { return PropertyAccessInfo(zone, kDictionaryProtoDataConstant, holder, {{receiver_map}, zone}, dictionary_index, name); } // static PropertyAccessInfo PropertyAccessInfo::DictionaryProtoAccessorConstant( Zone* zone, MapRef receiver_map, OptionalJSObjectRef holder, ObjectRef constant, OptionalJSObjectRef api_holder, NameRef property_name) { return PropertyAccessInfo(zone, kDictionaryProtoAccessorConstant, holder, constant, api_holder, property_name, {{receiver_map}, zone}); } PropertyAccessInfo::PropertyAccessInfo(Zone* zone) : kind_(kInvalid), lookup_start_object_maps_(zone), unrecorded_dependencies_(zone), field_representation_(Representation::None()), field_type_(Type::None()), dictionary_index_(InternalIndex::NotFound()) {} PropertyAccessInfo::PropertyAccessInfo( Zone* zone, Kind kind, OptionalJSObjectRef holder, ZoneVector<MapRef>&& lookup_start_object_maps) : kind_(kind), lookup_start_object_maps_(lookup_start_object_maps), holder_(holder), unrecorded_dependencies_(zone), field_representation_(Representation::None()), field_type_(Type::None()), dictionary_index_(InternalIndex::NotFound()) {} PropertyAccessInfo::PropertyAccessInfo( Zone* zone, Kind kind, OptionalJSObjectRef holder, OptionalObjectRef constant, OptionalJSObjectRef api_holder, OptionalNameRef name, ZoneVector<MapRef>&& lookup_start_object_maps) : kind_(kind), lookup_start_object_maps_(lookup_start_object_maps), constant_(constant), holder_(holder), api_holder_(api_holder), unrecorded_dependencies_(zone), field_representation_(Representation::None()), field_type_(Type::Any()), dictionary_index_(InternalIndex::NotFound()), name_(name) { DCHECK_IMPLIES(kind == kDictionaryProtoAccessorConstant, name.has_value()); } PropertyAccessInfo::PropertyAccessInfo( Kind kind, OptionalJSObjectRef holder, OptionalMapRef transition_map, FieldIndex field_index, Representation field_representation, Type field_type, MapRef field_owner_map, OptionalMapRef field_map, ZoneVector<MapRef>&& lookup_start_object_maps, ZoneVector<CompilationDependency const*>&& unrecorded_dependencies) : kind_(kind), lookup_start_object_maps_(lookup_start_object_maps), holder_(holder), unrecorded_dependencies_(std::move(unrecorded_dependencies)), transition_map_(transition_map), field_index_(field_index), field_representation_(field_representation), field_type_(field_type), field_owner_map_(field_owner_map), field_map_(field_map), dictionary_index_(InternalIndex::NotFound()) { DCHECK_IMPLIES(transition_map.has_value(), field_owner_map.equals(transition_map.value())); } PropertyAccessInfo::PropertyAccessInfo( Zone* zone, Kind kind, OptionalJSObjectRef holder, ZoneVector<MapRef>&& lookup_start_object_maps, InternalIndex dictionary_index, NameRef name) : kind_(kind), lookup_start_object_maps_(lookup_start_object_maps), holder_(holder), unrecorded_dependencies_(zone), field_representation_(Representation::None()), field_type_(Type::Any()), dictionary_index_(dictionary_index), name_{name} {} namespace { template <class RefT> bool OptionalRefEquals(OptionalRef<RefT> lhs, OptionalRef<RefT> rhs) { if (!lhs.has_value()) return !rhs.has_value(); if (!rhs.has_value()) return false; return lhs->equals(rhs.value()); } template <class T> void AppendVector(ZoneVector<T>* dst, const ZoneVector<T>& src) { dst->insert(dst->end(), src.begin(), src.end()); } } // namespace bool PropertyAccessInfo::Merge(PropertyAccessInfo const* that, AccessMode access_mode, Zone* zone) { if (kind_ != that->kind_) return false; if (!OptionalRefEquals(holder_, that->holder_)) return false; switch (kind_) { case kInvalid: DCHECK_EQ(that->kind_, kInvalid); return true; case kDataField: case kFastDataConstant: { // Check if we actually access the same field (we use the // GetFieldAccessStubKey method here just like the ICs do // since that way we only compare the relevant bits of the // field indices). if (field_index_.GetFieldAccessStubKey() != that->field_index_.GetFieldAccessStubKey()) { return false; } switch (access_mode) { case AccessMode::kHas: case AccessMode::kLoad: { if (!field_representation_.Equals(that->field_representation_)) { if (field_representation_.IsDouble() || that->field_representation_.IsDouble()) { return false; } field_representation_ = Representation::Tagged(); } if (!OptionalRefEquals(field_map_, that->field_map_)) { field_map_ = {}; } break; } case AccessMode::kStore: case AccessMode::kStoreInLiteral: case AccessMode::kDefine: { // For stores, the field map and field representation information // must match exactly, otherwise we cannot merge the stores. We // also need to make sure that in case of transitioning stores, // the transition targets match. if (!OptionalRefEquals(field_map_, that->field_map_) || !field_representation_.Equals(that->field_representation_) || !OptionalRefEquals(transition_map_, that->transition_map_)) { return false; } break; } } field_type_ = Type::Union(field_type_, that->field_type_, zone); AppendVector(&lookup_start_object_maps_, that->lookup_start_object_maps_); AppendVector(&unrecorded_dependencies_, that->unrecorded_dependencies_); return true; } case kDictionaryProtoAccessorConstant: case kFastAccessorConstant: { // Check if we actually access the same constant. if (!OptionalRefEquals(constant_, that->constant_)) return false; DCHECK(unrecorded_dependencies_.empty()); DCHECK(that->unrecorded_dependencies_.empty()); AppendVector(&lookup_start_object_maps_, that->lookup_start_object_maps_); return true; } case kDictionaryProtoDataConstant: { DCHECK_EQ(AccessMode::kLoad, access_mode); if (dictionary_index_ != that->dictionary_index_) return false; AppendVector(&lookup_start_object_maps_, that->lookup_start_object_maps_); return true; } case kNotFound: case kStringLength: { DCHECK(unrecorded_dependencies_.empty()); DCHECK(that->unrecorded_dependencies_.empty()); AppendVector(&lookup_start_object_maps_, that->lookup_start_object_maps_); return true; } case kModuleExport: return false; } } ConstFieldInfo PropertyAccessInfo::GetConstFieldInfo() const { return IsFastDataConstant() ? ConstFieldInfo(*field_owner_map_) : ConstFieldInfo::None(); } AccessInfoFactory::AccessInfoFactory(JSHeapBroker* broker, Zone* zone) : broker_(broker), type_cache_(TypeCache::Get()), zone_(zone) {} base::Optional<ElementAccessInfo> AccessInfoFactory::ComputeElementAccessInfo( MapRef map, AccessMode access_mode) const { if (!map.CanInlineElementAccess()) return {}; return ElementAccessInfo({{map}, zone()}, map.elements_kind(), zone()); } bool AccessInfoFactory::ComputeElementAccessInfos( ElementAccessFeedback const& feedback, ZoneVector<ElementAccessInfo>* access_infos) const { AccessMode access_mode = feedback.keyed_mode().access_mode(); if (access_mode == AccessMode::kLoad || access_mode == AccessMode::kHas) { // For polymorphic loads of similar elements kinds (i.e. all tagged or all // double), always use the "worst case" code without a transition. This is // much faster than transitioning the elements to the worst case, trading a // TransitionElementsKind for a CheckMaps, avoiding mutation of the array. base::Optional<ElementAccessInfo> access_info = ConsolidateElementLoad(feedback); if (access_info.has_value()) { access_infos->push_back(*access_info); return true; } } for (auto const& group : feedback.transition_groups()) { DCHECK(!group.empty()); OptionalMapRef target = group.front(); base::Optional<ElementAccessInfo> access_info = ComputeElementAccessInfo(target.value(), access_mode); if (!access_info.has_value()) return false; for (size_t i = 1; i < group.size(); ++i) { OptionalMapRef map_ref = group[i]; if (!map_ref.has_value()) continue; access_info->AddTransitionSource(map_ref.value()); } access_infos->push_back(*access_info); } return true; } PropertyAccessInfo AccessInfoFactory::ComputeDataFieldAccessInfo( MapRef receiver_map, MapRef map, NameRef name, OptionalJSObjectRef holder, InternalIndex descriptor, AccessMode access_mode) const { DCHECK(descriptor.is_found()); // TODO(jgruber,v8:7790): Use DescriptorArrayRef instead. Handle<DescriptorArray> descriptors = map.instance_descriptors(broker()).object(); PropertyDetails const details = descriptors->GetDetails(descriptor); int index = descriptors->GetFieldIndex(descriptor); Representation details_representation = details.representation(); if (details_representation.IsNone()) { // The ICs collect feedback in PREMONOMORPHIC state already, // but at this point the {receiver_map} might still contain // fields for which the representation has not yet been // determined by the runtime. So we need to catch this case // here and fall back to use the regular IC logic instead. return Invalid(); } FieldIndex field_index = FieldIndex::ForPropertyIndex(*map.object(), index, details_representation); // Private brands are used when loading private methods, which are stored in a // BlockContext, an internal object. Type field_type = name.object()->IsPrivateBrand() ? Type::OtherInternal() : Type::NonInternal(); OptionalMapRef field_map; ZoneVector<CompilationDependency const*> unrecorded_dependencies(zone()); Handle<FieldType> descriptors_field_type = broker()->CanonicalPersistentHandle( descriptors->GetFieldType(descriptor)); OptionalObjectRef descriptors_field_type_ref = TryMakeRef<Object>(broker(), descriptors_field_type); if (!descriptors_field_type_ref.has_value()) return Invalid(); // Note: FindFieldOwner may be called multiple times throughout one // compilation. This is safe since its result is fixed for a given map and // descriptor. MapRef field_owner_map = map.FindFieldOwner(broker(), descriptor); if (details_representation.IsSmi()) { field_type = Type::SignedSmall(); unrecorded_dependencies.push_back( dependencies()->FieldRepresentationDependencyOffTheRecord( map, field_owner_map, descriptor, details_representation)); } else if (details_representation.IsDouble()) { field_type = type_cache_->kFloat64; unrecorded_dependencies.push_back( dependencies()->FieldRepresentationDependencyOffTheRecord( map, field_owner_map, descriptor, details_representation)); } else if (details_representation.IsHeapObject()) { if (IsNone(*descriptors_field_type)) { switch (access_mode) { case AccessMode::kStore: case AccessMode::kStoreInLiteral: case AccessMode::kDefine: // Store is not safe if the field type was cleared. return Invalid(); case AccessMode::kLoad: case AccessMode::kHas: break; } // The field type was cleared by the GC, so we don't know anything // about the contents now. } unrecorded_dependencies.push_back( dependencies()->FieldRepresentationDependencyOffTheRecord( map, field_owner_map, descriptor, details_representation)); if (IsClass(*descriptors_field_type)) { // Remember the field map, and try to infer a useful type. OptionalMapRef maybe_field_map = TryMakeRef(broker(), FieldType::AsClass(*descriptors_field_type)); if (!maybe_field_map.has_value()) return Invalid(); field_type = Type::For(maybe_field_map.value(), broker()); field_map = maybe_field_map; } } else { CHECK(details_representation.IsTagged()); } // TODO(turbofan): We may want to do this only depending on the use // of the access info. unrecorded_dependencies.push_back( dependencies()->FieldTypeDependencyOffTheRecord( map, field_owner_map, descriptor, descriptors_field_type_ref.value())); PropertyConstness constness = dependencies()->DependOnFieldConstness(map, field_owner_map, descriptor); switch (constness) { case PropertyConstness::kMutable: return PropertyAccessInfo::DataField( broker(), zone(), receiver_map, std::move(unrecorded_dependencies), field_index, details_representation, field_type, field_owner_map, field_map, holder, {}); case PropertyConstness::kConst: return PropertyAccessInfo::FastDataConstant( zone(), receiver_map, std::move(unrecorded_dependencies), field_index, details_representation, field_type, field_owner_map, field_map, holder, {}); } UNREACHABLE(); } namespace { using AccessorsObjectGetter = std::function<Handle<Object>()>; PropertyAccessInfo AccessorAccessInfoHelper( Isolate* isolate, Zone* zone, JSHeapBroker* broker, const AccessInfoFactory* ai_factory, MapRef receiver_map, NameRef name, MapRef holder_map, OptionalJSObjectRef holder, AccessMode access_mode, AccessorsObjectGetter get_accessors) { if (holder_map.instance_type() == JS_MODULE_NAMESPACE_TYPE) { DCHECK(holder_map.object()->is_prototype_map()); Handle<PrototypeInfo> proto_info = broker->CanonicalPersistentHandle( PrototypeInfo::cast(holder_map.object()->prototype_info())); Handle<JSModuleNamespace> module_namespace = broker->CanonicalPersistentHandle( JSModuleNamespace::cast(proto_info->module_namespace())); Handle<Cell> cell = broker->CanonicalPersistentHandle( Cell::cast(module_namespace->module()->exports()->Lookup( isolate, name.object(), Smi::ToInt(Object::GetHash(*name.object()))))); if (IsTheHole(cell->value(kRelaxedLoad), isolate)) { // This module has not been fully initialized yet. return PropertyAccessInfo::Invalid(zone); } OptionalCellRef cell_ref = TryMakeRef(broker, cell); if (!cell_ref.has_value()) { return PropertyAccessInfo::Invalid(zone); } return PropertyAccessInfo::ModuleExport(zone, receiver_map, cell_ref.value()); } if (access_mode == AccessMode::kHas) { // kHas is not supported for dictionary mode objects. DCHECK(!holder_map.is_dictionary_map()); // HasProperty checks don't call getter/setters, existence is sufficient. return PropertyAccessInfo::FastAccessorConstant(zone, receiver_map, holder, {}, {}); } Handle<Object> maybe_accessors = get_accessors(); if (!IsAccessorPair(*maybe_accessors)) { return PropertyAccessInfo::Invalid(zone); } Handle<AccessorPair> accessors = Handle<AccessorPair>::cast(maybe_accessors); Handle<Object> accessor = broker->CanonicalPersistentHandle( access_mode == AccessMode::kLoad ? accessors->getter(kAcquireLoad) : accessors->setter(kAcquireLoad)); OptionalObjectRef accessor_ref = TryMakeRef(broker, accessor); if (!accessor_ref.has_value()) return PropertyAccessInfo::Invalid(zone); OptionalJSObjectRef api_holder_ref; if (!IsJSFunction(*accessor)) { CallOptimization optimization(broker->local_isolate_or_isolate(), accessor); if (!optimization.is_simple_api_call() || optimization.IsCrossContextLazyAccessorPair( *broker->target_native_context().object(), *holder_map.object())) { return PropertyAccessInfo::Invalid(zone); } if (DEBUG_BOOL && holder.has_value()) { base::Optional<Tagged<NativeContext>> holder_creation_context = holder->object()->GetCreationContextRaw(); CHECK(holder_creation_context.has_value()); CHECK_EQ(*broker->target_native_context().object(), holder_creation_context.value()); } CallOptimization::HolderLookup holder_lookup; Handle<JSObject> api_holder = broker->CanonicalPersistentHandle( optimization.LookupHolderOfExpectedType( broker->local_isolate_or_isolate(), receiver_map.object(), &holder_lookup)); if (holder_lookup == CallOptimization::kHolderNotFound) { return PropertyAccessInfo::Invalid(zone); } DCHECK_IMPLIES(holder_lookup == CallOptimization::kHolderIsReceiver, api_holder.is_null()); DCHECK_IMPLIES(holder_lookup == CallOptimization::kHolderFound, !api_holder.is_null()); if (!api_holder.is_null()) { api_holder_ref = TryMakeRef(broker, api_holder); if (!api_holder_ref.has_value()) return PropertyAccessInfo::Invalid(zone); } } if (access_mode == AccessMode::kLoad) { base::Optional<Tagged<Name>> cached_property_name = FunctionTemplateInfo::TryGetCachedPropertyName(isolate, *accessor); if (cached_property_name.has_value()) { OptionalNameRef cached_property_name_ref = TryMakeRef(broker, cached_property_name.value()); if (cached_property_name_ref.has_value()) { PropertyAccessInfo access_info = ai_factory->ComputePropertyAccessInfo( holder_map, cached_property_name_ref.value(), access_mode); if (!access_info.IsInvalid()) return access_info; } } } if (holder_map.is_dictionary_map()) { CHECK(!api_holder_ref.has_value()); return PropertyAccessInfo::DictionaryProtoAccessorConstant( zone, receiver_map, holder, accessor_ref.value(), api_holder_ref, name); } else { return PropertyAccessInfo::FastAccessorConstant( zone, receiver_map, holder, accessor_ref.value(), api_holder_ref); } } } // namespace PropertyAccessInfo AccessInfoFactory::ComputeAccessorDescriptorAccessInfo( MapRef receiver_map, NameRef name, MapRef holder_map, OptionalJSObjectRef holder, InternalIndex descriptor, AccessMode access_mode) const { DCHECK(descriptor.is_found()); Handle<DescriptorArray> descriptors = broker()->CanonicalPersistentHandle( holder_map.object()->instance_descriptors(kRelaxedLoad)); SLOW_DCHECK(descriptor == descriptors->Search(*name.object(), *holder_map.object(), true)); auto get_accessors = [&]() { return broker()->CanonicalPersistentHandle( descriptors->GetStrongValue(descriptor)); }; return AccessorAccessInfoHelper(isolate(), zone(), broker(), this, receiver_map, name, holder_map, holder, access_mode, get_accessors); } PropertyAccessInfo AccessInfoFactory::ComputeDictionaryProtoAccessInfo( MapRef receiver_map, NameRef name, JSObjectRef holder, InternalIndex dictionary_index, AccessMode access_mode, PropertyDetails details) const { CHECK(V8_DICT_PROPERTY_CONST_TRACKING_BOOL); DCHECK(holder.map(broker()).object()->is_prototype_map()); DCHECK_EQ(access_mode, AccessMode::kLoad); // We can only inline accesses to constant properties. if (details.constness() != PropertyConstness::kConst) { return Invalid(); } if (details.kind() == PropertyKind::kData) { return PropertyAccessInfo::DictionaryProtoDataConstant( zone(), receiver_map, holder, dictionary_index, name); } auto get_accessors = [&]() { return JSObject::DictionaryPropertyAt(isolate(), holder.object(), dictionary_index); }; return AccessorAccessInfoHelper(isolate(), zone(), broker(), this, receiver_map, name, holder.map(broker()), holder, access_mode, get_accessors); } bool AccessInfoFactory::TryLoadPropertyDetails( MapRef map, OptionalJSObjectRef maybe_holder, NameRef name, InternalIndex* index_out, PropertyDetails* details_out) const { if (map.is_dictionary_map()) { DCHECK(V8_DICT_PROPERTY_CONST_TRACKING_BOOL); DCHECK(map.object()->is_prototype_map()); DisallowGarbageCollection no_gc; if (!maybe_holder.has_value()) { // TODO(v8:11457) In this situation, we have a dictionary mode prototype // as a receiver. Consider other means of obtaining the holder in this // situation. // Without the holder, we can't get the property details. return false; } Handle<JSObject> holder = maybe_holder->object(); if (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) { Tagged<SwissNameDictionary> dict = holder->property_dictionary_swiss(); *index_out = dict->FindEntry(isolate(), name.object()); if (index_out->is_found()) { *details_out = dict->DetailsAt(*index_out); } } else { Tagged<NameDictionary> dict = holder->property_dictionary(); *index_out = dict->FindEntry(isolate(), name.object()); if (index_out->is_found()) { *details_out = dict->DetailsAt(*index_out); } } } else { Tagged<DescriptorArray> descriptors = *map.instance_descriptors(broker()).object(); *index_out = descriptors->Search(*name.object(), *map.object(), true); if (index_out->is_found()) { *details_out = descriptors->GetDetails(*index_out); } } return true; } PropertyAccessInfo AccessInfoFactory::ComputePropertyAccessInfo( MapRef map, NameRef name, AccessMode access_mode) const { CHECK(name.IsUniqueName()); // Dictionary property const tracking is unsupported with concurrent inlining. CHECK(!V8_DICT_PROPERTY_CONST_TRACKING_BOOL); JSHeapBroker::MapUpdaterGuardIfNeeded mumd_scope(broker()); if (access_mode == AccessMode::kHas && !IsJSReceiverMap(*map.object())) { return Invalid(); } // Check if it is safe to inline property access for the {map}. if (!CanInlinePropertyAccess(map, access_mode)) { return Invalid(); } // We support fast inline cases for certain JSObject getters. if (access_mode == AccessMode::kLoad || access_mode == AccessMode::kHas) { PropertyAccessInfo access_info = LookupSpecialFieldAccessor(map, name); if (!access_info.IsInvalid()) return access_info; } // Only relevant if V8_DICT_PROPERTY_CONST_TRACKING enabled. bool dictionary_prototype_on_chain = false; bool fast_mode_prototype_on_chain = false; // Remember the receiver map. We use {map} as loop variable. MapRef receiver_map = map; OptionalJSObjectRef holder; // Perform the implicit ToObject for primitives here. // Implemented according to ES6 section 7.3.2 GetV (V, P). // Note: Keep sync'd with // CompilationDependencies::DependOnStablePrototypeChains. if (receiver_map.IsPrimitiveMap()) { OptionalJSFunctionRef constructor = broker()->target_native_context().GetConstructorFunction(broker(), receiver_map); if (!constructor.has_value()) return Invalid(); map = constructor->initial_map(broker()); DCHECK(!map.IsPrimitiveMap()); } while (true) { PropertyDetails details = PropertyDetails::Empty(); InternalIndex index = InternalIndex::NotFound(); if (!TryLoadPropertyDetails(map, holder, name, &index, &details)) { return Invalid(); } if (index.is_found()) { if (IsAnyStore(access_mode)) { DCHECK(!map.is_dictionary_map()); // Don't bother optimizing stores to read-only properties. if (details.IsReadOnly()) return Invalid(); if (details.kind() == PropertyKind::kData && holder.has_value()) { // This is a store to a property not found on the receiver but on a // prototype. According to ES6 section 9.1.9 [[Set]], we need to // create a new data property on the receiver. We can still optimize // if such a transition already exists. return LookupTransition(receiver_map, name, holder, NONE); } } if (map.is_dictionary_map()) { DCHECK(V8_DICT_PROPERTY_CONST_TRACKING_BOOL); if (fast_mode_prototype_on_chain) { // TODO(v8:11248) While the work on dictionary mode prototypes is in // progress, we may still see fast mode objects on the chain prior to // reaching a dictionary mode prototype holding the property . Due to // this only being an intermediate state, we don't stupport these kind // of heterogenous prototype chains. return Invalid(); } // TryLoadPropertyDetails only succeeds if we know the holder. return ComputeDictionaryProtoAccessInfo( receiver_map, name, holder.value(), index, access_mode, details); } if (dictionary_prototype_on_chain) { // If V8_DICT_PROPERTY_CONST_TRACKING_BOOL was disabled, then a // dictionary prototype would have caused a bailout earlier. DCHECK(V8_DICT_PROPERTY_CONST_TRACKING_BOOL); // TODO(v8:11248) We have a fast mode holder, but there was a dictionary // mode prototype earlier on the chain. Note that seeing a fast mode // prototype even though V8_DICT_PROPERTY_CONST_TRACKING is enabled // should only be possible while the implementation of dictionary mode // prototypes is work in progress. Eventually, enabling // V8_DICT_PROPERTY_CONST_TRACKING will guarantee that all prototypes // are always in dictionary mode, making this case unreachable. However, // due to the complications of checking dictionary mode prototypes for // modification, we don't attempt to support dictionary mode prototypes // occuring before a fast mode holder on the chain. return Invalid(); } if (details.location() == PropertyLocation::kField) { if (details.kind() == PropertyKind::kData) { return ComputeDataFieldAccessInfo(receiver_map, map, name, holder, index, access_mode); } else { DCHECK_EQ(PropertyKind::kAccessor, details.kind()); // TODO(turbofan): Add support for general accessors? return Invalid(); } } else { DCHECK_EQ(PropertyLocation::kDescriptor, details.location()); DCHECK_EQ(PropertyKind::kAccessor, details.kind()); return ComputeAccessorDescriptorAccessInfo(receiver_map, name, map, holder, index, access_mode); } UNREACHABLE(); } // The property wasn't found on {map}. Look on the prototype if appropriate. DCHECK(!index.is_found()); // Don't search on the prototype chain for special indices in case of // integer indexed exotic objects (see ES6 section 9.4.5). if (IsJSTypedArrayMap(*map.object()) && name.IsString()) { StringRef name_str = name.AsString(); SharedStringAccessGuardIfNeeded access_guard( *name_str.object(), broker()->local_isolate_or_isolate()); if (IsSpecialIndex(*name_str.object(), access_guard)) return Invalid(); } // Don't search on the prototype when storing in literals, or performing a // Define operation if (access_mode == AccessMode::kStoreInLiteral || access_mode == AccessMode::kDefine) { PropertyAttributes attrs = NONE; if (name.object()->IsPrivate()) { // When PrivateNames are added to an object, they are by definition // non-enumerable. attrs = DONT_ENUM; } return LookupTransition(receiver_map, name, holder, attrs); } // Don't lookup private symbols on the prototype chain. if (name.object()->IsPrivate()) { return Invalid(); } if (V8_DICT_PROPERTY_CONST_TRACKING_BOOL && holder.has_value()) { // At this point, we are past the first loop iteration. DCHECK(holder->object()->map()->is_prototype_map()); DCHECK(!holder->map(broker()).equals(receiver_map)); fast_mode_prototype_on_chain = fast_mode_prototype_on_chain || !map.is_dictionary_map(); dictionary_prototype_on_chain = dictionary_prototype_on_chain || map.is_dictionary_map(); } // Walk up the prototype chain. // Load the map's prototype's map to guarantee that every time we use it, // we use the same Map. HeapObjectRef prototype = map.prototype(broker()); MapRef map_prototype_map = prototype.map(broker()); if (!IsJSObjectMap(*map_prototype_map.object())) { // Don't allow proxies on the prototype chain. if (!prototype.IsNull()) { DCHECK(IsJSProxy(*prototype.object()) || IsWasmObject(*prototype.object())); return Invalid(); } DCHECK(prototype.IsNull()); if (dictionary_prototype_on_chain) { // TODO(v8:11248) See earlier comment about // dictionary_prototype_on_chain. We don't support absent properties // with dictionary mode prototypes on the chain, either. This is again // just due to how we currently deal with dependencies for dictionary // properties during finalization. return Invalid(); } // Store to property not found on the receiver or any prototype, we need // to transition to a new data property. // Implemented according to ES6 section 9.1.9 [[Set]] (P, V, Receiver) if (access_mode == AccessMode::kStore) { return LookupTransition(receiver_map, name, holder, NONE); } // The property was not found (access returns undefined or throws // depending on the language mode of the load operation. // Implemented according to ES6 section 9.1.8 [[Get]] (P, Receiver) return PropertyAccessInfo::NotFound(zone(), receiver_map, holder); } holder = prototype.AsJSObject(); map = map_prototype_map; if (!CanInlinePropertyAccess(map, access_mode)) { return Invalid(); } // Successful lookup on prototype chain needs to guarantee that all the // prototypes up to the holder have stable maps, except for dictionary-mode // prototypes. We currently do this by taking a // DependOnStablePrototypeChains dependency in the caller. // // TODO(jgruber): This is brittle and easy to miss. Consider a refactor // that moves the responsibility of taking the dependency into // AccessInfoFactory. } UNREACHABLE(); } PropertyAccessInfo AccessInfoFactory::FinalizePropertyAccessInfosAsOne( ZoneVector<PropertyAccessInfo> access_infos, AccessMode access_mode) const { ZoneVector<PropertyAccessInfo> merged_access_infos(zone()); MergePropertyAccessInfos(access_infos, access_mode, &merged_access_infos); if (merged_access_infos.size() == 1) { PropertyAccessInfo& result = merged_access_infos.front(); if (!result.IsInvalid()) { result.RecordDependencies(dependencies()); return result; } } return Invalid(); } void PropertyAccessInfo::RecordDependencies( CompilationDependencies* dependencies) { for (CompilationDependency const* d : unrecorded_dependencies_) { dependencies->RecordDependency(d); } unrecorded_dependencies_.clear(); } bool AccessInfoFactory::FinalizePropertyAccessInfos( ZoneVector<PropertyAccessInfo> access_infos, AccessMode access_mode, ZoneVector<PropertyAccessInfo>* result) const { if (access_infos.empty()) return false; MergePropertyAccessInfos(access_infos, access_mode, result); for (PropertyAccessInfo const& info : *result) { if (info.IsInvalid()) return false; } for (PropertyAccessInfo& info : *result) { info.RecordDependencies(dependencies()); } return true; } void AccessInfoFactory::MergePropertyAccessInfos( ZoneVector<PropertyAccessInfo> infos, AccessMode access_mode, ZoneVector<PropertyAccessInfo>* result) const { DCHECK(result->empty()); for (auto it = infos.begin(), end = infos.end(); it != end; ++it) { bool merged = false; for (auto ot = it + 1; ot != end; ++ot) { if (ot->Merge(&(*it), access_mode, zone())) { merged = true; break; } } if (!merged) result->push_back(*it); } CHECK(!result->empty()); } CompilationDependencies* AccessInfoFactory::dependencies() const { return broker()->dependencies(); } Isolate* AccessInfoFactory::isolate() const { return broker()->isolate(); } namespace { Maybe<ElementsKind> GeneralizeElementsKind(ElementsKind this_kind, ElementsKind that_kind) { if (IsHoleyElementsKind(this_kind)) { that_kind = GetHoleyElementsKind(that_kind); } else if (IsHoleyElementsKind(that_kind)) { this_kind = GetHoleyElementsKind(this_kind); } if (this_kind == that_kind) return Just(this_kind); if (IsDoubleElementsKind(that_kind) == IsDoubleElementsKind(this_kind)) { if (IsMoreGeneralElementsKindTransition(that_kind, this_kind)) { return Just(this_kind); } if (IsMoreGeneralElementsKindTransition(this_kind, that_kind)) { return Just(that_kind); } } return Nothing<ElementsKind>(); } } // namespace base::Optional<ElementAccessInfo> AccessInfoFactory::ConsolidateElementLoad( ElementAccessFeedback const& feedback) const { if (feedback.transition_groups().empty()) return {}; DCHECK(!feedback.transition_groups().front().empty()); MapRef first_map = feedback.transition_groups().front().front(); InstanceType instance_type = first_map.instance_type(); ElementsKind elements_kind = first_map.elements_kind(); ZoneVector<MapRef> maps(zone()); for (auto const& group : feedback.transition_groups()) { for (MapRef map : group) { if (map.instance_type() != instance_type || !map.CanInlineElementAccess()) { return {}; } if (!GeneralizeElementsKind(elements_kind, map.elements_kind()) .To(&elements_kind)) { return {}; } maps.push_back(map); } } return ElementAccessInfo(std::move(maps), elements_kind, zone()); } PropertyAccessInfo AccessInfoFactory::LookupSpecialFieldAccessor( MapRef map, NameRef name) const { // Check for String::length field accessor. if (IsStringMap(*map.object())) { if (Name::Equals(isolate(), name.object(), isolate()->factory()->length_string())) { return PropertyAccessInfo::StringLength(zone(), map); } return Invalid(); } // Check for special JSObject field accessors. FieldIndex field_index; if (Accessors::IsJSObjectFieldAccessor(isolate(), map.object(), name.object(), &field_index)) { Type field_type = Type::NonInternal(); Representation field_representation = Representation::Tagged(); if (IsJSArrayMap(*map.object())) { DCHECK(Name::Equals(isolate(), isolate()->factory()->length_string(), name.object())); // The JSArray::length property is a smi in the range // [0, FixedDoubleArray::kMaxLength] in case of fast double // elements, a smi in the range [0, FixedArray::kMaxLength] // in case of other fast elements, and [0, kMaxUInt32] in // case of other arrays. if (IsDoubleElementsKind(map.elements_kind())) { field_type = type_cache_->kFixedDoubleArrayLengthType; field_representation = Representation::Smi(); } else if (IsFastElementsKind(map.elements_kind())) { field_type = type_cache_->kFixedArrayLengthType; field_representation = Representation::Smi(); } else { field_type = type_cache_->kJSArrayLengthType; } } // Special fields are always mutable. return PropertyAccessInfo::DataField(broker(), zone(), map, {{}, zone()}, field_index, field_representation, field_type, map, {}, {}, {}); } return Invalid(); } PropertyAccessInfo AccessInfoFactory::LookupTransition( MapRef map, NameRef name, OptionalJSObjectRef holder, PropertyAttributes attrs) const { // Check if the {map} has a data transition with the given {name}. Tagged<Map> transition = TransitionsAccessor(isolate(), *map.object(), true) .SearchTransition(*name.object(), PropertyKind::kData, attrs); if (transition.is_null()) return Invalid(); OptionalMapRef maybe_transition_map = TryMakeRef(broker(), transition); if (!maybe_transition_map.has_value()) return Invalid(); MapRef transition_map = maybe_transition_map.value(); InternalIndex const number = transition_map.object()->LastAdded(); Handle<DescriptorArray> descriptors = transition_map.instance_descriptors(broker()).object(); PropertyDetails const details = descriptors->GetDetails(number); // Don't bother optimizing stores to read-only properties. if (details.IsReadOnly()) return Invalid(); // TODO(bmeurer): Handle transition to data constant? if (details.location() != PropertyLocation::kField) return Invalid(); int const index = details.field_index(); Representation details_representation = details.representation(); if (details_representation.IsNone()) return Invalid(); FieldIndex field_index = FieldIndex::ForPropertyIndex( *transition_map.object(), index, details_representation); Type field_type = Type::NonInternal(); OptionalMapRef field_map; DCHECK_EQ(transition_map, transition_map.FindFieldOwner(broker(), number)); ZoneVector<CompilationDependency const*> unrecorded_dependencies(zone()); if (details_representation.IsSmi()) { field_type = Type::SignedSmall(); unrecorded_dependencies.push_back( dependencies()->FieldRepresentationDependencyOffTheRecord( transition_map, transition_map, number, details_representation)); } else if (details_representation.IsDouble()) { field_type = type_cache_->kFloat64; unrecorded_dependencies.push_back( dependencies()->FieldRepresentationDependencyOffTheRecord( transition_map, transition_map, number, details_representation)); } else if (details_representation.IsHeapObject()) { // Extract the field type from the property details (make sure its // representation is TaggedPointer to reflect the heap object case). // TODO(jgruber,v8:7790): Use DescriptorArrayRef instead. Handle<FieldType> descriptors_field_type = broker()->CanonicalPersistentHandle(descriptors->GetFieldType(number)); OptionalObjectRef descriptors_field_type_ref = TryMakeRef<Object>(broker(), descriptors_field_type); if (!descriptors_field_type_ref.has_value()) return Invalid(); if (IsNone(*descriptors_field_type)) { // Store is not safe if the field type was cleared. return Invalid(); } unrecorded_dependencies.push_back( dependencies()->FieldRepresentationDependencyOffTheRecord( transition_map, transition_map, number, details_representation)); if (IsClass(*descriptors_field_type)) { unrecorded_dependencies.push_back( dependencies()->FieldTypeDependencyOffTheRecord( transition_map, transition_map, number, *descriptors_field_type_ref)); // Remember the field map, and try to infer a useful type. OptionalMapRef maybe_field_map = TryMakeRef(broker(), FieldType::AsClass(*descriptors_field_type)); if (!maybe_field_map.has_value()) return Invalid(); field_type = Type::For(maybe_field_map.value(), broker()); field_map = maybe_field_map; } } unrecorded_dependencies.push_back( dependencies()->TransitionDependencyOffTheRecord(transition_map)); // Transitioning stores *may* store to const fields. The resulting // DataConstant access infos can be distinguished from later, i.e. redundant, // stores to the same constant field by the presence of a transition map. switch (dependencies()->DependOnFieldConstness(transition_map, transition_map, number)) { case PropertyConstness::kMutable: return PropertyAccessInfo::DataField( broker(), zone(), map, std::move(unrecorded_dependencies), field_index, details_representation, field_type, transition_map, field_map, holder, transition_map); case PropertyConstness::kConst: return PropertyAccessInfo::FastDataConstant( zone(), map, std::move(unrecorded_dependencies), field_index, details_representation, field_type, transition_map, field_map, holder, transition_map); } UNREACHABLE(); } } // namespace compiler } // namespace internal } // namespace v8