OLD | NEW |
(Empty) | |
| 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/property-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 std::ostream& operator<<(std::ostream& os, PropertyAccessMode access_mode) { |
| 20 switch (access_mode) { |
| 21 case PropertyAccessMode::kLoad: |
| 22 return os << "Load"; |
| 23 case PropertyAccessMode::kStore: |
| 24 return os << "Store"; |
| 25 } |
| 26 UNREACHABLE(); |
| 27 return os; |
| 28 } |
| 29 |
| 30 |
| 31 PropertyAccessInfo::PropertyAccessInfo() |
| 32 : kind_(kInvalid), receiver_type_(Type::None()), field_type_(Type::Any()) {} |
| 33 |
| 34 |
| 35 PropertyAccessInfo::PropertyAccessInfo(MaybeHandle<JSObject> holder, |
| 36 Handle<Object> constant, |
| 37 Type* receiver_type) |
| 38 : kind_(kDataConstant), |
| 39 receiver_type_(receiver_type), |
| 40 constant_(constant), |
| 41 holder_(holder), |
| 42 field_type_(Type::Any()) {} |
| 43 |
| 44 |
| 45 PropertyAccessInfo::PropertyAccessInfo(MaybeHandle<JSObject> holder, |
| 46 MaybeHandle<Map> transition_map, |
| 47 FieldIndex field_index, Type* field_type, |
| 48 Type* receiver_type) |
| 49 : kind_(kDataField), |
| 50 receiver_type_(receiver_type), |
| 51 transition_map_(transition_map), |
| 52 holder_(holder), |
| 53 field_index_(field_index), |
| 54 field_type_(field_type) {} |
| 55 |
| 56 |
| 57 PropertyAccessInfoFactory::PropertyAccessInfoFactory( |
| 58 CompilationDependencies* dependencies, Handle<Context> native_context, |
| 59 Zone* zone) |
| 60 : dependencies_(dependencies), |
| 61 native_context_(native_context), |
| 62 isolate_(native_context->GetIsolate()), |
| 63 type_cache_(TypeCache::Get()), |
| 64 zone_(zone) {} |
| 65 |
| 66 |
| 67 namespace { |
| 68 |
| 69 bool CanInlinePropertyAccess(Handle<Map> map) { |
| 70 // TODO(bmeurer): Do something about the number stuff. |
| 71 if (map->instance_type() == HEAP_NUMBER_TYPE) return false; |
| 72 if (map->instance_type() < FIRST_NONSTRING_TYPE) return true; |
| 73 return map->IsJSObjectMap() && !map->is_dictionary_map() && |
| 74 !map->has_named_interceptor() && |
| 75 // TODO(verwaest): Whitelist contexts to which we have access. |
| 76 !map->is_access_check_needed(); |
| 77 } |
| 78 |
| 79 } // namespace |
| 80 |
| 81 |
| 82 bool PropertyAccessInfoFactory::ComputePropertyAccessInfo( |
| 83 Handle<Map> map, Handle<Name> name, PropertyAccessMode access_mode, |
| 84 PropertyAccessInfo* access_info) { |
| 85 // Check if it is safe to inline property access for the {map}. |
| 86 if (!CanInlinePropertyAccess(map)) return false; |
| 87 |
| 88 // Compute the receiver type. |
| 89 Handle<Map> receiver_map = map; |
| 90 Type* receiver_type = Type::Class(receiver_map, zone()); |
| 91 |
| 92 // We support fast inline cases for certain JSObject getters. |
| 93 if (access_mode == PropertyAccessMode::kLoad) { |
| 94 // Check for special JSObject field accessors. |
| 95 int offset; |
| 96 if (Accessors::IsJSObjectFieldAccessor(map, name, &offset)) { |
| 97 FieldIndex field_index = FieldIndex::ForInObjectOffset(offset); |
| 98 Type* field_type = Type::Tagged(); |
| 99 if (map->IsStringMap()) { |
| 100 DCHECK(Name::Equals(factory()->length_string(), name)); |
| 101 // The String::length property is always a smi in the range |
| 102 // [0, String::kMaxLength]. |
| 103 field_type = type_cache_.kStringLengthType; |
| 104 } else if (map->IsJSArrayMap()) { |
| 105 DCHECK(Name::Equals(factory()->length_string(), name)); |
| 106 // The JSArray::length property is a smi in the range |
| 107 // [0, FixedDoubleArray::kMaxLength] in case of fast double |
| 108 // elements, a smi in the range [0, FixedArray::kMaxLength] |
| 109 // in case of other fast elements, and [0, kMaxUInt32] in |
| 110 // case of other arrays. |
| 111 if (IsFastDoubleElementsKind(map->elements_kind())) { |
| 112 field_type = type_cache_.kFixedDoubleArrayLengthType; |
| 113 } else if (IsFastElementsKind(map->elements_kind())) { |
| 114 field_type = type_cache_.kFixedArrayLengthType; |
| 115 } else { |
| 116 field_type = type_cache_.kJSArrayLengthType; |
| 117 } |
| 118 } |
| 119 *access_info = |
| 120 PropertyAccessInfo::DataField(receiver_type, field_index, field_type); |
| 121 return true; |
| 122 } |
| 123 } |
| 124 |
| 125 MaybeHandle<JSObject> holder; |
| 126 while (true) { |
| 127 // Lookup the named property on the {map}. |
| 128 Handle<DescriptorArray> descriptors(map->instance_descriptors(), isolate()); |
| 129 int const number = descriptors->SearchWithCache(*name, *map); |
| 130 if (number != DescriptorArray::kNotFound) { |
| 131 PropertyDetails const details = descriptors->GetDetails(number); |
| 132 if (access_mode == PropertyAccessMode::kStore) { |
| 133 // Don't bother optimizing stores to read-only properties. |
| 134 if (details.IsReadOnly()) { |
| 135 return false; |
| 136 } |
| 137 // Check for store to data property on a prototype. |
| 138 if (details.kind() == kData && !holder.is_null()) { |
| 139 // We need to add the data field to the receiver. Leave the loop |
| 140 // and check whether we already have a transition for this field. |
| 141 // Implemented according to ES6 section 9.1.9 [[Set]] (P, V, Receiver) |
| 142 break; |
| 143 } |
| 144 } |
| 145 if (details.type() == DATA_CONSTANT) { |
| 146 *access_info = PropertyAccessInfo::DataConstant( |
| 147 receiver_type, handle(descriptors->GetValue(number), isolate()), |
| 148 holder); |
| 149 return true; |
| 150 } else if (details.type() == DATA) { |
| 151 int index = descriptors->GetFieldIndex(number); |
| 152 Representation field_representation = details.representation(); |
| 153 FieldIndex field_index = FieldIndex::ForPropertyIndex( |
| 154 *map, index, field_representation.IsDouble()); |
| 155 Type* field_type = Type::Tagged(); |
| 156 if (field_representation.IsSmi()) { |
| 157 field_type = type_cache_.kSmi; |
| 158 } else if (field_representation.IsDouble()) { |
| 159 field_type = type_cache_.kFloat64; |
| 160 } else if (field_representation.IsHeapObject()) { |
| 161 // Extract the field type from the property details (make sure its |
| 162 // representation is TaggedPointer to reflect the heap object case). |
| 163 field_type = Type::Intersect( |
| 164 Type::Convert<HeapType>( |
| 165 handle(descriptors->GetFieldType(number), isolate()), zone()), |
| 166 Type::TaggedPointer(), zone()); |
| 167 if (field_type->Is(Type::None())) { |
| 168 // Store is not safe if the field type was cleared. |
| 169 if (access_mode == PropertyAccessMode::kStore) return false; |
| 170 |
| 171 // The field type was cleared by the GC, so we don't know anything |
| 172 // about the contents now. |
| 173 // TODO(bmeurer): It would be awesome to make this saner in the |
| 174 // runtime/GC interaction. |
| 175 field_type = Type::TaggedPointer(); |
| 176 } else if (!Type::Any()->Is(field_type)) { |
| 177 // Add proper code dependencies in case of stable field map(s). |
| 178 Handle<Map> field_owner_map(map->FindFieldOwner(number), isolate()); |
| 179 dependencies()->AssumeFieldType(field_owner_map); |
| 180 } |
| 181 DCHECK(field_type->Is(Type::TaggedPointer())); |
| 182 } |
| 183 *access_info = PropertyAccessInfo::DataField(receiver_type, field_index, |
| 184 field_type, holder); |
| 185 return true; |
| 186 } else { |
| 187 // TODO(bmeurer): Add support for accessors. |
| 188 return false; |
| 189 } |
| 190 } |
| 191 |
| 192 // Don't search on the prototype chain for special indices in case of |
| 193 // integer indexed exotic objects (see ES6 section 9.4.5). |
| 194 if (map->IsJSTypedArrayMap() && name->IsString() && |
| 195 IsSpecialIndex(isolate()->unicode_cache(), String::cast(*name))) { |
| 196 return false; |
| 197 } |
| 198 |
| 199 // Don't lookup private symbols on the prototype chain. |
| 200 if (name->IsPrivate()) return false; |
| 201 |
| 202 // Walk up the prototype chain. |
| 203 if (!map->prototype()->IsJSObject()) { |
| 204 // Perform the implicit ToObject for primitives here. |
| 205 // Implemented according to ES6 section 7.3.2 GetV (V, P). |
| 206 Handle<JSFunction> constructor; |
| 207 if (Map::GetConstructorFunction(map, native_context()) |
| 208 .ToHandle(&constructor)) { |
| 209 map = handle(constructor->initial_map(), isolate()); |
| 210 DCHECK(map->prototype()->IsJSObject()); |
| 211 } else if (map->prototype()->IsNull()) { |
| 212 // Store to property not found on the receiver or any prototype, we need |
| 213 // to transition to a new data property. |
| 214 // Implemented according to ES6 section 9.1.9 [[Set]] (P, V, Receiver) |
| 215 if (access_mode == PropertyAccessMode::kStore) { |
| 216 break; |
| 217 } |
| 218 // TODO(bmeurer): Handle the not found case if the prototype is null. |
| 219 return false; |
| 220 } else { |
| 221 return false; |
| 222 } |
| 223 } |
| 224 Handle<JSObject> map_prototype(JSObject::cast(map->prototype()), isolate()); |
| 225 if (map_prototype->map()->is_deprecated()) { |
| 226 // Try to migrate the prototype object so we don't embed the deprecated |
| 227 // map into the optimized code. |
| 228 JSObject::TryMigrateInstance(map_prototype); |
| 229 } |
| 230 map = handle(map_prototype->map(), isolate()); |
| 231 holder = map_prototype; |
| 232 |
| 233 // Check if it is safe to inline property access for the {map}. |
| 234 if (!CanInlinePropertyAccess(map)) return false; |
| 235 } |
| 236 DCHECK_EQ(PropertyAccessMode::kStore, access_mode); |
| 237 |
| 238 // Check if the {receiver_map} has a data transition with the given {name}. |
| 239 if (receiver_map->unused_property_fields() == 0) return false; |
| 240 if (Map* transition = TransitionArray::SearchTransition(*receiver_map, kData, |
| 241 *name, NONE)) { |
| 242 Handle<Map> transition_map(transition, isolate()); |
| 243 int const number = transition_map->LastAdded(); |
| 244 PropertyDetails const details = |
| 245 transition_map->instance_descriptors()->GetDetails(number); |
| 246 // Don't bother optimizing stores to read-only properties. |
| 247 if (details.IsReadOnly()) return false; |
| 248 // TODO(bmeurer): Handle transition to data constant? |
| 249 if (details.type() != DATA) return false; |
| 250 int const index = details.field_index(); |
| 251 Representation field_representation = details.representation(); |
| 252 FieldIndex field_index = FieldIndex::ForPropertyIndex( |
| 253 *transition_map, index, field_representation.IsDouble()); |
| 254 Type* field_type = Type::Tagged(); |
| 255 if (field_representation.IsSmi()) { |
| 256 field_type = type_cache_.kSmi; |
| 257 } else if (field_representation.IsDouble()) { |
| 258 // TODO(bmeurer): Add support for storing to double fields. |
| 259 return false; |
| 260 } else if (field_representation.IsHeapObject()) { |
| 261 // Extract the field type from the property details (make sure its |
| 262 // representation is TaggedPointer to reflect the heap object case). |
| 263 field_type = Type::Intersect( |
| 264 Type::Convert<HeapType>( |
| 265 handle( |
| 266 transition_map->instance_descriptors()->GetFieldType(number), |
| 267 isolate()), |
| 268 zone()), |
| 269 Type::TaggedPointer(), zone()); |
| 270 if (field_type->Is(Type::None())) { |
| 271 // Store is not safe if the field type was cleared. |
| 272 return false; |
| 273 } else if (!Type::Any()->Is(field_type)) { |
| 274 // Add proper code dependencies in case of stable field map(s). |
| 275 Handle<Map> field_owner_map(transition_map->FindFieldOwner(number), |
| 276 isolate()); |
| 277 dependencies()->AssumeFieldType(field_owner_map); |
| 278 } |
| 279 DCHECK(field_type->Is(Type::TaggedPointer())); |
| 280 } |
| 281 dependencies()->AssumeMapNotDeprecated(transition_map); |
| 282 *access_info = PropertyAccessInfo::DataField( |
| 283 receiver_type, field_index, field_type, holder, transition_map); |
| 284 return true; |
| 285 } |
| 286 return false; |
| 287 } |
| 288 |
| 289 |
| 290 bool PropertyAccessInfoFactory::ComputePropertyAccessInfos( |
| 291 MapHandleList const& maps, Handle<Name> name, |
| 292 PropertyAccessMode access_mode, |
| 293 ZoneVector<PropertyAccessInfo>* access_infos) { |
| 294 for (Handle<Map> map : maps) { |
| 295 if (Map::TryUpdate(map).ToHandle(&map)) { |
| 296 PropertyAccessInfo access_info; |
| 297 if (!ComputePropertyAccessInfo(map, name, access_mode, &access_info)) { |
| 298 return false; |
| 299 } |
| 300 access_infos->push_back(access_info); |
| 301 } |
| 302 } |
| 303 return true; |
| 304 } |
| 305 |
| 306 |
| 307 Factory* PropertyAccessInfoFactory::factory() const { |
| 308 return isolate()->factory(); |
| 309 } |
| 310 |
| 311 } // namespace compiler |
| 312 } // namespace internal |
| 313 } // namespace v8 |
OLD | NEW |