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