Home | History | Annotate | Download | only in compiler
      1 // Copyright 2015 the V8 project authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include <ostream>
      6 
      7 #include "src/accessors.h"
      8 #include "src/compilation-dependencies.h"
      9 #include "src/compiler/access-info.h"
     10 #include "src/field-index-inl.h"
     11 #include "src/objects-inl.h"  // TODO(mstarzinger): Temporary cycle breaker!
     12 #include "src/type-cache.h"
     13 #include "src/types-inl.h"
     14 
     15 namespace v8 {
     16 namespace internal {
     17 namespace compiler {
     18 
     19 namespace {
     20 
     21 bool CanInlineElementAccess(Handle<Map> map) {
     22   if (!map->IsJSObjectMap()) return false;
     23   if (map->is_access_check_needed()) return false;
     24   if (map->has_indexed_interceptor()) return false;
     25   ElementsKind const elements_kind = map->elements_kind();
     26   if (IsFastElementsKind(elements_kind)) return true;
     27   // TODO(bmeurer): Add support for other elements kind.
     28   return false;
     29 }
     30 
     31 
     32 bool CanInlinePropertyAccess(Handle<Map> map) {
     33   // We can inline property access to prototypes of all primitives, except
     34   // the special Oddball ones that have no wrapper counterparts (i.e. Null,
     35   // Undefined and TheHole).
     36   STATIC_ASSERT(ODDBALL_TYPE == LAST_PRIMITIVE_TYPE);
     37   if (map->IsBooleanMap()) return true;
     38   if (map->instance_type() < LAST_PRIMITIVE_TYPE) return true;
     39   return map->IsJSObjectMap() && !map->is_dictionary_map() &&
     40          !map->has_named_interceptor() &&
     41          // TODO(verwaest): Whitelist contexts to which we have access.
     42          !map->is_access_check_needed();
     43 }
     44 
     45 }  // namespace
     46 
     47 
     48 std::ostream& operator<<(std::ostream& os, AccessMode access_mode) {
     49   switch (access_mode) {
     50     case AccessMode::kLoad:
     51       return os << "Load";
     52     case AccessMode::kStore:
     53       return os << "Store";
     54   }
     55   UNREACHABLE();
     56   return os;
     57 }
     58 
     59 
     60 // static
     61 PropertyAccessInfo PropertyAccessInfo::NotFound(Type* receiver_type,
     62                                                 MaybeHandle<JSObject> holder) {
     63   return PropertyAccessInfo(holder, receiver_type);
     64 }
     65 
     66 
     67 // static
     68 PropertyAccessInfo PropertyAccessInfo::DataConstant(
     69     Type* receiver_type, Handle<Object> constant,
     70     MaybeHandle<JSObject> holder) {
     71   return PropertyAccessInfo(holder, constant, receiver_type);
     72 }
     73 
     74 
     75 // static
     76 PropertyAccessInfo PropertyAccessInfo::DataField(
     77     Type* receiver_type, FieldIndex field_index, Type* field_type,
     78     FieldCheck field_check, MaybeHandle<JSObject> holder,
     79     MaybeHandle<Map> transition_map) {
     80   return PropertyAccessInfo(holder, transition_map, field_index, field_check,
     81                             field_type, receiver_type);
     82 }
     83 
     84 
     85 ElementAccessInfo::ElementAccessInfo() : receiver_type_(Type::None()) {}
     86 
     87 
     88 ElementAccessInfo::ElementAccessInfo(Type* receiver_type,
     89                                      ElementsKind elements_kind,
     90                                      MaybeHandle<JSObject> holder)
     91     : elements_kind_(elements_kind),
     92       holder_(holder),
     93       receiver_type_(receiver_type) {}
     94 
     95 
     96 PropertyAccessInfo::PropertyAccessInfo()
     97     : kind_(kInvalid), receiver_type_(Type::None()), field_type_(Type::Any()) {}
     98 
     99 
    100 PropertyAccessInfo::PropertyAccessInfo(MaybeHandle<JSObject> holder,
    101                                        Type* receiver_type)
    102     : kind_(kNotFound),
    103       receiver_type_(receiver_type),
    104       holder_(holder),
    105       field_type_(Type::Any()) {}
    106 
    107 
    108 PropertyAccessInfo::PropertyAccessInfo(MaybeHandle<JSObject> holder,
    109                                        Handle<Object> constant,
    110                                        Type* receiver_type)
    111     : kind_(kDataConstant),
    112       receiver_type_(receiver_type),
    113       constant_(constant),
    114       holder_(holder),
    115       field_type_(Type::Any()) {}
    116 
    117 
    118 PropertyAccessInfo::PropertyAccessInfo(MaybeHandle<JSObject> holder,
    119                                        MaybeHandle<Map> transition_map,
    120                                        FieldIndex field_index,
    121                                        FieldCheck field_check, Type* field_type,
    122                                        Type* receiver_type)
    123     : kind_(kDataField),
    124       receiver_type_(receiver_type),
    125       transition_map_(transition_map),
    126       holder_(holder),
    127       field_index_(field_index),
    128       field_check_(field_check),
    129       field_type_(field_type) {}
    130 
    131 
    132 AccessInfoFactory::AccessInfoFactory(CompilationDependencies* dependencies,
    133                                      Handle<Context> native_context, Zone* zone)
    134     : dependencies_(dependencies),
    135       native_context_(native_context),
    136       isolate_(native_context->GetIsolate()),
    137       type_cache_(TypeCache::Get()),
    138       zone_(zone) {
    139   DCHECK(native_context->IsNativeContext());
    140 }
    141 
    142 
    143 bool AccessInfoFactory::ComputeElementAccessInfo(
    144     Handle<Map> map, AccessMode access_mode, ElementAccessInfo* access_info) {
    145   // Check if it is safe to inline element access for the {map}.
    146   if (!CanInlineElementAccess(map)) return false;
    147 
    148   ElementsKind const elements_kind = map->elements_kind();
    149 
    150   // Certain (monomorphic) stores need a prototype chain check because shape
    151   // changes could allow callbacks on elements in the chain that are not
    152   // compatible with monomorphic keyed stores.
    153   MaybeHandle<JSObject> holder;
    154   if (access_mode == AccessMode::kStore && map->prototype()->IsJSObject()) {
    155     for (PrototypeIterator i(map); !i.IsAtEnd(); i.Advance()) {
    156       Handle<JSReceiver> prototype =
    157           PrototypeIterator::GetCurrent<JSReceiver>(i);
    158       if (!prototype->IsJSObject()) return false;
    159       // TODO(bmeurer): We do not currently support unstable prototypes.
    160       // We might want to revisit the way we handle certain keyed stores
    161       // because this whole prototype chain check is essential a hack,
    162       // and I'm not sure that it is correct at all with dictionaries in
    163       // the prototype chain.
    164       if (!prototype->map()->is_stable()) return false;
    165       holder = Handle<JSObject>::cast(prototype);
    166     }
    167   }
    168 
    169   *access_info =
    170       ElementAccessInfo(Type::Class(map, zone()), elements_kind, holder);
    171   return true;
    172 }
    173 
    174 
    175 bool AccessInfoFactory::ComputeElementAccessInfos(
    176     MapHandleList const& maps, AccessMode access_mode,
    177     ZoneVector<ElementAccessInfo>* access_infos) {
    178   // Collect possible transition targets.
    179   MapHandleList possible_transition_targets(maps.length());
    180   for (Handle<Map> map : maps) {
    181     if (Map::TryUpdate(map).ToHandle(&map)) {
    182       if (CanInlineElementAccess(map) &&
    183           IsFastElementsKind(map->elements_kind()) &&
    184           GetInitialFastElementsKind() != map->elements_kind()) {
    185         possible_transition_targets.Add(map);
    186       }
    187     }
    188   }
    189 
    190   // Separate the actual receiver maps and the possible transition sources.
    191   MapHandleList receiver_maps(maps.length());
    192   MapTransitionList transitions(maps.length());
    193   for (Handle<Map> map : maps) {
    194     if (Map::TryUpdate(map).ToHandle(&map)) {
    195       Handle<Map> transition_target =
    196           Map::FindTransitionedMap(map, &possible_transition_targets);
    197       if (transition_target.is_null()) {
    198         receiver_maps.Add(map);
    199       } else {
    200         transitions.push_back(std::make_pair(map, transition_target));
    201       }
    202     }
    203   }
    204 
    205   for (Handle<Map> receiver_map : receiver_maps) {
    206     // Compute the element access information.
    207     ElementAccessInfo access_info;
    208     if (!ComputeElementAccessInfo(receiver_map, access_mode, &access_info)) {
    209       return false;
    210     }
    211 
    212     // Collect the possible transitions for the {receiver_map}.
    213     for (auto transition : transitions) {
    214       if (transition.second.is_identical_to(receiver_map)) {
    215         access_info.transitions().push_back(transition);
    216       }
    217     }
    218 
    219     // Schedule the access information.
    220     access_infos->push_back(access_info);
    221   }
    222   return true;
    223 }
    224 
    225 
    226 bool AccessInfoFactory::ComputePropertyAccessInfo(
    227     Handle<Map> map, Handle<Name> name, AccessMode access_mode,
    228     PropertyAccessInfo* access_info) {
    229   // Check if it is safe to inline property access for the {map}.
    230   if (!CanInlinePropertyAccess(map)) return false;
    231 
    232   // Compute the receiver type.
    233   Handle<Map> receiver_map = map;
    234 
    235   // We support fast inline cases for certain JSObject getters.
    236   if (access_mode == AccessMode::kLoad &&
    237       LookupSpecialFieldAccessor(map, name, access_info)) {
    238     return true;
    239   }
    240 
    241   MaybeHandle<JSObject> holder;
    242   do {
    243     // Lookup the named property on the {map}.
    244     Handle<DescriptorArray> descriptors(map->instance_descriptors(), isolate());
    245     int const number = descriptors->SearchWithCache(*name, *map);
    246     if (number != DescriptorArray::kNotFound) {
    247       PropertyDetails const details = descriptors->GetDetails(number);
    248       if (access_mode == AccessMode::kStore) {
    249         // Don't bother optimizing stores to read-only properties.
    250         if (details.IsReadOnly()) {
    251           return false;
    252         }
    253         // Check for store to data property on a prototype.
    254         if (details.kind() == kData && !holder.is_null()) {
    255           // Store to property not found on the receiver but on a prototype, we
    256           // need to transition to a new data property.
    257           // Implemented according to ES6 section 9.1.9 [[Set]] (P, V, Receiver)
    258           return LookupTransition(receiver_map, name, holder, access_info);
    259         }
    260       }
    261       if (details.type() == DATA_CONSTANT) {
    262         *access_info = PropertyAccessInfo::DataConstant(
    263             Type::Class(receiver_map, zone()),
    264             handle(descriptors->GetValue(number), isolate()), holder);
    265         return true;
    266       } else if (details.type() == DATA) {
    267         int index = descriptors->GetFieldIndex(number);
    268         Representation field_representation = details.representation();
    269         FieldIndex field_index = FieldIndex::ForPropertyIndex(
    270             *map, index, field_representation.IsDouble());
    271         Type* field_type = Type::Tagged();
    272         if (field_representation.IsSmi()) {
    273           field_type = type_cache_.kSmi;
    274         } else if (field_representation.IsDouble()) {
    275           field_type = type_cache_.kFloat64;
    276         } else if (field_representation.IsHeapObject()) {
    277           // Extract the field type from the property details (make sure its
    278           // representation is TaggedPointer to reflect the heap object case).
    279           field_type = Type::Intersect(
    280               Type::Convert<HeapType>(
    281                   handle(descriptors->GetFieldType(number), isolate()), zone()),
    282               Type::TaggedPointer(), zone());
    283           if (field_type->Is(Type::None())) {
    284             // Store is not safe if the field type was cleared.
    285             if (access_mode == AccessMode::kStore) return false;
    286 
    287             // The field type was cleared by the GC, so we don't know anything
    288             // about the contents now.
    289             // TODO(bmeurer): It would be awesome to make this saner in the
    290             // runtime/GC interaction.
    291             field_type = Type::TaggedPointer();
    292           } else if (!Type::Any()->Is(field_type)) {
    293             // Add proper code dependencies in case of stable field map(s).
    294             Handle<Map> field_owner_map(map->FindFieldOwner(number), isolate());
    295             dependencies()->AssumeFieldType(field_owner_map);
    296           }
    297           DCHECK(field_type->Is(Type::TaggedPointer()));
    298         }
    299         *access_info = PropertyAccessInfo::DataField(
    300             Type::Class(receiver_map, zone()), field_index, field_type,
    301             FieldCheck::kNone, holder);
    302         return true;
    303       } else {
    304         // TODO(bmeurer): Add support for accessors.
    305         return false;
    306       }
    307     }
    308 
    309     // Don't search on the prototype chain for special indices in case of
    310     // integer indexed exotic objects (see ES6 section 9.4.5).
    311     if (map->IsJSTypedArrayMap() && name->IsString() &&
    312         IsSpecialIndex(isolate()->unicode_cache(), String::cast(*name))) {
    313       return false;
    314     }
    315 
    316     // Don't lookup private symbols on the prototype chain.
    317     if (name->IsPrivate()) return false;
    318 
    319     // Walk up the prototype chain.
    320     if (!map->prototype()->IsJSObject()) {
    321       // Perform the implicit ToObject for primitives here.
    322       // Implemented according to ES6 section 7.3.2 GetV (V, P).
    323       Handle<JSFunction> constructor;
    324       if (Map::GetConstructorFunction(map, native_context())
    325               .ToHandle(&constructor)) {
    326         map = handle(constructor->initial_map(), isolate());
    327         DCHECK(map->prototype()->IsJSObject());
    328       } else if (map->prototype()->IsNull()) {
    329         // Store to property not found on the receiver or any prototype, we need
    330         // to transition to a new data property.
    331         // Implemented according to ES6 section 9.1.9 [[Set]] (P, V, Receiver)
    332         if (access_mode == AccessMode::kStore) {
    333           return LookupTransition(receiver_map, name, holder, access_info);
    334         }
    335         // The property was not found, return undefined or throw depending
    336         // on the language mode of the load operation.
    337         // Implemented according to ES6 section 9.1.8 [[Get]] (P, Receiver)
    338         *access_info = PropertyAccessInfo::NotFound(
    339             Type::Class(receiver_map, zone()), holder);
    340         return true;
    341       } else {
    342         return false;
    343       }
    344     }
    345     Handle<JSObject> map_prototype(JSObject::cast(map->prototype()), isolate());
    346     if (map_prototype->map()->is_deprecated()) {
    347       // Try to migrate the prototype object so we don't embed the deprecated
    348       // map into the optimized code.
    349       JSObject::TryMigrateInstance(map_prototype);
    350     }
    351     map = handle(map_prototype->map(), isolate());
    352     holder = map_prototype;
    353   } while (CanInlinePropertyAccess(map));
    354   return false;
    355 }
    356 
    357 
    358 bool AccessInfoFactory::ComputePropertyAccessInfos(
    359     MapHandleList const& maps, Handle<Name> name, AccessMode access_mode,
    360     ZoneVector<PropertyAccessInfo>* access_infos) {
    361   for (Handle<Map> map : maps) {
    362     if (Map::TryUpdate(map).ToHandle(&map)) {
    363       PropertyAccessInfo access_info;
    364       if (!ComputePropertyAccessInfo(map, name, access_mode, &access_info)) {
    365         return false;
    366       }
    367       access_infos->push_back(access_info);
    368     }
    369   }
    370   return true;
    371 }
    372 
    373 
    374 bool AccessInfoFactory::LookupSpecialFieldAccessor(
    375     Handle<Map> map, Handle<Name> name, PropertyAccessInfo* access_info) {
    376   // Check for special JSObject field accessors.
    377   int offset;
    378   if (Accessors::IsJSObjectFieldAccessor(map, name, &offset)) {
    379     FieldIndex field_index = FieldIndex::ForInObjectOffset(offset);
    380     Type* field_type = Type::Tagged();
    381     if (map->IsStringMap()) {
    382       DCHECK(Name::Equals(factory()->length_string(), name));
    383       // The String::length property is always a smi in the range
    384       // [0, String::kMaxLength].
    385       field_type = type_cache_.kStringLengthType;
    386     } else if (map->IsJSArrayMap()) {
    387       DCHECK(Name::Equals(factory()->length_string(), name));
    388       // The JSArray::length property is a smi in the range
    389       // [0, FixedDoubleArray::kMaxLength] in case of fast double
    390       // elements, a smi in the range [0, FixedArray::kMaxLength]
    391       // in case of other fast elements, and [0, kMaxUInt32] in
    392       // case of other arrays.
    393       if (IsFastDoubleElementsKind(map->elements_kind())) {
    394         field_type = type_cache_.kFixedDoubleArrayLengthType;
    395       } else if (IsFastElementsKind(map->elements_kind())) {
    396         field_type = type_cache_.kFixedArrayLengthType;
    397       } else {
    398         field_type = type_cache_.kJSArrayLengthType;
    399       }
    400     }
    401     *access_info = PropertyAccessInfo::DataField(Type::Class(map, zone()),
    402                                                  field_index, field_type);
    403     return true;
    404   }
    405   // Check for special JSArrayBufferView field accessors.
    406   if (Accessors::IsJSArrayBufferViewFieldAccessor(map, name, &offset)) {
    407     FieldIndex field_index = FieldIndex::ForInObjectOffset(offset);
    408     Type* field_type = Type::Tagged();
    409     if (Name::Equals(factory()->byte_length_string(), name) ||
    410         Name::Equals(factory()->byte_offset_string(), name)) {
    411       // The JSArrayBufferView::byte_length and JSArrayBufferView::byte_offset
    412       // properties are always numbers in the range [0, kMaxSafeInteger].
    413       field_type = type_cache_.kPositiveSafeInteger;
    414     } else if (map->IsJSTypedArrayMap()) {
    415       DCHECK(Name::Equals(factory()->length_string(), name));
    416       // The JSTypedArray::length property is always a number in the range
    417       // [0, kMaxSafeInteger].
    418       field_type = type_cache_.kPositiveSafeInteger;
    419     }
    420     *access_info = PropertyAccessInfo::DataField(
    421         Type::Class(map, zone()), field_index, field_type,
    422         FieldCheck::kJSArrayBufferViewBufferNotNeutered);
    423     return true;
    424   }
    425   return false;
    426 }
    427 
    428 
    429 bool AccessInfoFactory::LookupTransition(Handle<Map> map, Handle<Name> name,
    430                                          MaybeHandle<JSObject> holder,
    431                                          PropertyAccessInfo* access_info) {
    432   // Check if the {map} has a data transition with the given {name}.
    433   if (map->unused_property_fields() == 0) return false;
    434   Handle<Map> transition_map;
    435   if (TransitionArray::SearchTransition(map, kData, name, NONE)
    436           .ToHandle(&transition_map)) {
    437     int const number = transition_map->LastAdded();
    438     PropertyDetails const details =
    439         transition_map->instance_descriptors()->GetDetails(number);
    440     // Don't bother optimizing stores to read-only properties.
    441     if (details.IsReadOnly()) return false;
    442     // TODO(bmeurer): Handle transition to data constant?
    443     if (details.type() != DATA) return false;
    444     int const index = details.field_index();
    445     Representation field_representation = details.representation();
    446     FieldIndex field_index = FieldIndex::ForPropertyIndex(
    447         *transition_map, index, field_representation.IsDouble());
    448     Type* field_type = Type::Tagged();
    449     if (field_representation.IsSmi()) {
    450       field_type = type_cache_.kSmi;
    451     } else if (field_representation.IsDouble()) {
    452       field_type = type_cache_.kFloat64;
    453     } else if (field_representation.IsHeapObject()) {
    454       // Extract the field type from the property details (make sure its
    455       // representation is TaggedPointer to reflect the heap object case).
    456       field_type = Type::Intersect(
    457           Type::Convert<HeapType>(
    458               handle(
    459                   transition_map->instance_descriptors()->GetFieldType(number),
    460                   isolate()),
    461               zone()),
    462           Type::TaggedPointer(), zone());
    463       if (field_type->Is(Type::None())) {
    464         // Store is not safe if the field type was cleared.
    465         return false;
    466       } else if (!Type::Any()->Is(field_type)) {
    467         // Add proper code dependencies in case of stable field map(s).
    468         Handle<Map> field_owner_map(transition_map->FindFieldOwner(number),
    469                                     isolate());
    470         dependencies()->AssumeFieldType(field_owner_map);
    471       }
    472       DCHECK(field_type->Is(Type::TaggedPointer()));
    473     }
    474     dependencies()->AssumeMapNotDeprecated(transition_map);
    475     *access_info = PropertyAccessInfo::DataField(
    476         Type::Class(map, zone()), field_index, field_type, FieldCheck::kNone,
    477         holder, transition_map);
    478     return true;
    479   }
    480   return false;
    481 }
    482 
    483 
    484 Factory* AccessInfoFactory::factory() const { return isolate()->factory(); }
    485 
    486 }  // namespace compiler
    487 }  // namespace internal
    488 }  // namespace v8
    489