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/compiler/type-cache.h" 11 #include "src/field-index-inl.h" 12 #include "src/field-type.h" 13 #include "src/ic/call-optimization.h" 14 #include "src/objects-inl.h" 15 16 namespace v8 { 17 namespace internal { 18 namespace compiler { 19 20 namespace { 21 22 bool CanInlineElementAccess(Handle<Map> map) { 23 if (!map->IsJSObjectMap()) return false; 24 if (map->is_access_check_needed()) return false; 25 if (map->has_indexed_interceptor()) return false; 26 ElementsKind const elements_kind = map->elements_kind(); 27 if (IsFastElementsKind(elements_kind)) return true; 28 if (IsFixedTypedArrayElementsKind(elements_kind)) return true; 29 return false; 30 } 31 32 33 bool CanInlinePropertyAccess(Handle<Map> map) { 34 // We can inline property access to prototypes of all primitives, except 35 // the special Oddball ones that have no wrapper counterparts (i.e. Null, 36 // Undefined and TheHole). 37 STATIC_ASSERT(ODDBALL_TYPE == LAST_PRIMITIVE_TYPE); 38 if (map->IsBooleanMap()) return true; 39 if (map->instance_type() < LAST_PRIMITIVE_TYPE) return true; 40 return map->IsJSObjectMap() && !map->is_dictionary_map() && 41 !map->has_named_interceptor() && 42 // TODO(verwaest): Whitelist contexts to which we have access. 43 !map->is_access_check_needed(); 44 } 45 46 } // namespace 47 48 49 std::ostream& operator<<(std::ostream& os, AccessMode access_mode) { 50 switch (access_mode) { 51 case AccessMode::kLoad: 52 return os << "Load"; 53 case AccessMode::kStore: 54 return os << "Store"; 55 case AccessMode::kStoreInLiteral: 56 return os << "StoreInLiteral"; 57 } 58 UNREACHABLE(); 59 return os; 60 } 61 62 ElementAccessInfo::ElementAccessInfo() {} 63 64 ElementAccessInfo::ElementAccessInfo(MapList const& receiver_maps, 65 ElementsKind elements_kind) 66 : elements_kind_(elements_kind), receiver_maps_(receiver_maps) {} 67 68 // static 69 PropertyAccessInfo PropertyAccessInfo::NotFound(MapList const& receiver_maps, 70 MaybeHandle<JSObject> holder) { 71 return PropertyAccessInfo(holder, receiver_maps); 72 } 73 74 // static 75 PropertyAccessInfo PropertyAccessInfo::DataConstant( 76 MapList const& receiver_maps, Handle<Object> constant, 77 MaybeHandle<JSObject> holder) { 78 return PropertyAccessInfo(kDataConstant, holder, constant, receiver_maps); 79 } 80 81 // static 82 PropertyAccessInfo PropertyAccessInfo::DataField( 83 PropertyConstness constness, MapList const& receiver_maps, 84 FieldIndex field_index, MachineRepresentation field_representation, 85 Type* field_type, MaybeHandle<Map> field_map, MaybeHandle<JSObject> holder, 86 MaybeHandle<Map> transition_map) { 87 Kind kind = constness == kConst ? kDataConstantField : kDataField; 88 return PropertyAccessInfo(kind, holder, transition_map, field_index, 89 field_representation, field_type, field_map, 90 receiver_maps); 91 } 92 93 // static 94 PropertyAccessInfo PropertyAccessInfo::AccessorConstant( 95 MapList const& receiver_maps, Handle<Object> constant, 96 MaybeHandle<JSObject> holder) { 97 return PropertyAccessInfo(kAccessorConstant, holder, constant, receiver_maps); 98 } 99 100 // static 101 PropertyAccessInfo PropertyAccessInfo::Generic(MapList const& receiver_maps) { 102 return PropertyAccessInfo(kGeneric, MaybeHandle<JSObject>(), Handle<Object>(), 103 receiver_maps); 104 } 105 106 PropertyAccessInfo::PropertyAccessInfo() 107 : kind_(kInvalid), 108 field_representation_(MachineRepresentation::kNone), 109 field_type_(Type::None()) {} 110 111 PropertyAccessInfo::PropertyAccessInfo(MaybeHandle<JSObject> holder, 112 MapList const& receiver_maps) 113 : kind_(kNotFound), 114 receiver_maps_(receiver_maps), 115 holder_(holder), 116 field_representation_(MachineRepresentation::kNone), 117 field_type_(Type::None()) {} 118 119 PropertyAccessInfo::PropertyAccessInfo(Kind kind, MaybeHandle<JSObject> holder, 120 Handle<Object> constant, 121 MapList const& receiver_maps) 122 : kind_(kind), 123 receiver_maps_(receiver_maps), 124 constant_(constant), 125 holder_(holder), 126 field_representation_(MachineRepresentation::kNone), 127 field_type_(Type::Any()) {} 128 129 PropertyAccessInfo::PropertyAccessInfo( 130 Kind kind, MaybeHandle<JSObject> holder, MaybeHandle<Map> transition_map, 131 FieldIndex field_index, MachineRepresentation field_representation, 132 Type* field_type, MaybeHandle<Map> field_map, MapList const& receiver_maps) 133 : kind_(kind), 134 receiver_maps_(receiver_maps), 135 transition_map_(transition_map), 136 holder_(holder), 137 field_index_(field_index), 138 field_representation_(field_representation), 139 field_type_(field_type), 140 field_map_(field_map) {} 141 142 bool PropertyAccessInfo::Merge(PropertyAccessInfo const* that) { 143 if (this->kind_ != that->kind_) return false; 144 if (this->holder_.address() != that->holder_.address()) return false; 145 146 switch (this->kind_) { 147 case kInvalid: 148 break; 149 150 case kDataField: 151 case kDataConstantField: { 152 // Check if we actually access the same field. 153 if (this->kind_ == that->kind_ && 154 this->transition_map_.address() == that->transition_map_.address() && 155 this->field_index_ == that->field_index_ && 156 this->field_map_.address() == that->field_map_.address() && 157 this->field_type_->Is(that->field_type_) && 158 that->field_type_->Is(this->field_type_) && 159 this->field_representation_ == that->field_representation_) { 160 this->receiver_maps_.insert(this->receiver_maps_.end(), 161 that->receiver_maps_.begin(), 162 that->receiver_maps_.end()); 163 return true; 164 } 165 return false; 166 } 167 168 case kDataConstant: 169 case kAccessorConstant: { 170 // Check if we actually access the same constant. 171 if (this->constant_.address() == that->constant_.address()) { 172 this->receiver_maps_.insert(this->receiver_maps_.end(), 173 that->receiver_maps_.begin(), 174 that->receiver_maps_.end()); 175 return true; 176 } 177 return false; 178 } 179 180 case kNotFound: 181 case kGeneric: { 182 this->receiver_maps_.insert(this->receiver_maps_.end(), 183 that->receiver_maps_.begin(), 184 that->receiver_maps_.end()); 185 return true; 186 } 187 } 188 189 UNREACHABLE(); 190 return false; 191 } 192 193 AccessInfoFactory::AccessInfoFactory(CompilationDependencies* dependencies, 194 Handle<Context> native_context, Zone* zone) 195 : dependencies_(dependencies), 196 native_context_(native_context), 197 isolate_(native_context->GetIsolate()), 198 type_cache_(TypeCache::Get()), 199 zone_(zone) { 200 DCHECK(native_context->IsNativeContext()); 201 } 202 203 204 bool AccessInfoFactory::ComputeElementAccessInfo( 205 Handle<Map> map, AccessMode access_mode, ElementAccessInfo* access_info) { 206 // Check if it is safe to inline element access for the {map}. 207 if (!CanInlineElementAccess(map)) return false; 208 ElementsKind const elements_kind = map->elements_kind(); 209 *access_info = ElementAccessInfo(MapList{map}, elements_kind); 210 return true; 211 } 212 213 214 bool AccessInfoFactory::ComputeElementAccessInfos( 215 MapHandleList const& maps, AccessMode access_mode, 216 ZoneVector<ElementAccessInfo>* access_infos) { 217 // Collect possible transition targets. 218 MapHandleList possible_transition_targets(maps.length()); 219 for (Handle<Map> map : maps) { 220 if (Map::TryUpdate(map).ToHandle(&map)) { 221 if (CanInlineElementAccess(map) && 222 IsFastElementsKind(map->elements_kind()) && 223 GetInitialFastElementsKind() != map->elements_kind()) { 224 possible_transition_targets.Add(map); 225 } 226 } 227 } 228 229 // Separate the actual receiver maps and the possible transition sources. 230 MapHandleList receiver_maps(maps.length()); 231 MapTransitionList transitions(maps.length()); 232 for (Handle<Map> map : maps) { 233 if (Map::TryUpdate(map).ToHandle(&map)) { 234 Map* transition_target = 235 map->FindElementsKindTransitionedMap(&possible_transition_targets); 236 if (transition_target == nullptr) { 237 receiver_maps.Add(map); 238 } else { 239 transitions.push_back(std::make_pair(map, handle(transition_target))); 240 } 241 } 242 } 243 244 for (Handle<Map> receiver_map : receiver_maps) { 245 // Compute the element access information. 246 ElementAccessInfo access_info; 247 if (!ComputeElementAccessInfo(receiver_map, access_mode, &access_info)) { 248 return false; 249 } 250 251 // Collect the possible transitions for the {receiver_map}. 252 for (auto transition : transitions) { 253 if (transition.second.is_identical_to(receiver_map)) { 254 access_info.transitions().push_back(transition); 255 } 256 } 257 258 // Schedule the access information. 259 access_infos->push_back(access_info); 260 } 261 return true; 262 } 263 264 265 bool AccessInfoFactory::ComputePropertyAccessInfo( 266 Handle<Map> map, Handle<Name> name, AccessMode access_mode, 267 PropertyAccessInfo* access_info) { 268 // Check if it is safe to inline property access for the {map}. 269 if (!CanInlinePropertyAccess(map)) return false; 270 271 // Compute the receiver type. 272 Handle<Map> receiver_map = map; 273 274 // Property lookups require the name to be internalized. 275 name = isolate()->factory()->InternalizeName(name); 276 277 // We support fast inline cases for certain JSObject getters. 278 if (access_mode == AccessMode::kLoad && 279 LookupSpecialFieldAccessor(map, name, access_info)) { 280 return true; 281 } 282 283 MaybeHandle<JSObject> holder; 284 do { 285 // Lookup the named property on the {map}. 286 Handle<DescriptorArray> descriptors(map->instance_descriptors(), isolate()); 287 int const number = descriptors->SearchWithCache(isolate(), *name, *map); 288 if (number != DescriptorArray::kNotFound) { 289 PropertyDetails const details = descriptors->GetDetails(number); 290 if (access_mode == AccessMode::kStore || 291 access_mode == AccessMode::kStoreInLiteral) { 292 // Don't bother optimizing stores to read-only properties. 293 if (details.IsReadOnly()) { 294 return false; 295 } 296 // Check for store to data property on a prototype. 297 if (details.kind() == kData && !holder.is_null()) { 298 // Store to property not found on the receiver but on a prototype, we 299 // need to transition to a new data property. 300 // Implemented according to ES6 section 9.1.9 [[Set]] (P, V, Receiver) 301 return LookupTransition(receiver_map, name, holder, access_info); 302 } 303 } 304 if (details.location() == kField) { 305 if (details.kind() == kData) { 306 int index = descriptors->GetFieldIndex(number); 307 Representation details_representation = details.representation(); 308 FieldIndex field_index = FieldIndex::ForPropertyIndex( 309 *map, index, details_representation.IsDouble()); 310 Type* field_type = Type::NonInternal(); 311 MachineRepresentation field_representation = 312 MachineRepresentation::kTagged; 313 MaybeHandle<Map> field_map; 314 if (details_representation.IsSmi()) { 315 field_type = Type::SignedSmall(); 316 field_representation = MachineRepresentation::kTaggedSigned; 317 } else if (details_representation.IsDouble()) { 318 field_type = type_cache_.kFloat64; 319 field_representation = MachineRepresentation::kFloat64; 320 } else if (details_representation.IsHeapObject()) { 321 // Extract the field type from the property details (make sure its 322 // representation is TaggedPointer to reflect the heap object case). 323 field_representation = MachineRepresentation::kTaggedPointer; 324 Handle<FieldType> descriptors_field_type( 325 descriptors->GetFieldType(number), isolate()); 326 if (descriptors_field_type->IsNone()) { 327 // Store is not safe if the field type was cleared. 328 if (access_mode == AccessMode::kStore) return false; 329 330 // The field type was cleared by the GC, so we don't know anything 331 // about the contents now. 332 } else if (descriptors_field_type->IsClass()) { 333 // Add proper code dependencies in case of stable field map(s). 334 Handle<Map> field_owner_map(map->FindFieldOwner(number), 335 isolate()); 336 dependencies()->AssumeFieldOwner(field_owner_map); 337 338 // Remember the field map, and try to infer a useful type. 339 field_type = Type::For(descriptors_field_type->AsClass()); 340 field_map = descriptors_field_type->AsClass(); 341 } 342 } 343 *access_info = PropertyAccessInfo::DataField( 344 details.constness(), MapList{receiver_map}, field_index, 345 field_representation, field_type, field_map, holder); 346 return true; 347 } else { 348 DCHECK_EQ(kAccessor, details.kind()); 349 // TODO(turbofan): Add support for general accessors? 350 return false; 351 } 352 353 } else { 354 DCHECK_EQ(kDescriptor, details.location()); 355 if (details.kind() == kData) { 356 DCHECK(!FLAG_track_constant_fields); 357 *access_info = PropertyAccessInfo::DataConstant( 358 MapList{receiver_map}, 359 handle(descriptors->GetValue(number), isolate()), holder); 360 return true; 361 } else { 362 DCHECK_EQ(kAccessor, details.kind()); 363 Handle<Object> accessors(descriptors->GetValue(number), isolate()); 364 if (!accessors->IsAccessorPair()) return false; 365 Handle<Object> accessor( 366 access_mode == AccessMode::kLoad 367 ? Handle<AccessorPair>::cast(accessors)->getter() 368 : Handle<AccessorPair>::cast(accessors)->setter(), 369 isolate()); 370 if (!accessor->IsJSFunction()) { 371 CallOptimization optimization(accessor); 372 if (!optimization.is_simple_api_call()) { 373 return false; 374 } 375 if (optimization.api_call_info()->fast_handler()->IsCode()) { 376 return false; 377 } 378 if (V8_UNLIKELY(FLAG_runtime_stats)) return false; 379 } 380 if (access_mode == AccessMode::kLoad) { 381 Handle<Name> cached_property_name; 382 if (FunctionTemplateInfo::TryGetCachedPropertyName(isolate(), 383 accessor) 384 .ToHandle(&cached_property_name)) { 385 if (ComputePropertyAccessInfo(map, cached_property_name, 386 access_mode, access_info)) { 387 return true; 388 } 389 } 390 } 391 *access_info = PropertyAccessInfo::AccessorConstant( 392 MapList{receiver_map}, accessor, holder); 393 return true; 394 } 395 } 396 UNREACHABLE(); 397 return false; 398 } 399 400 // Don't search on the prototype chain for special indices in case of 401 // integer indexed exotic objects (see ES6 section 9.4.5). 402 if (map->IsJSTypedArrayMap() && name->IsString() && 403 IsSpecialIndex(isolate()->unicode_cache(), String::cast(*name))) { 404 return false; 405 } 406 407 // Don't search on the prototype when storing in literals 408 if (access_mode == AccessMode::kStoreInLiteral) { 409 return LookupTransition(receiver_map, name, holder, access_info); 410 } 411 412 // Don't lookup private symbols on the prototype chain. 413 if (name->IsPrivate()) return false; 414 415 // Walk up the prototype chain. 416 if (!map->prototype()->IsJSObject()) { 417 // Perform the implicit ToObject for primitives here. 418 // Implemented according to ES6 section 7.3.2 GetV (V, P). 419 Handle<JSFunction> constructor; 420 if (Map::GetConstructorFunction(map, native_context()) 421 .ToHandle(&constructor)) { 422 map = handle(constructor->initial_map(), isolate()); 423 DCHECK(map->prototype()->IsJSObject()); 424 } else if (map->prototype()->IsNull(isolate())) { 425 // Store to property not found on the receiver or any prototype, we need 426 // to transition to a new data property. 427 // Implemented according to ES6 section 9.1.9 [[Set]] (P, V, Receiver) 428 if (access_mode == AccessMode::kStore) { 429 return LookupTransition(receiver_map, name, holder, access_info); 430 } 431 // The property was not found, return undefined or throw depending 432 // on the language mode of the load operation. 433 // Implemented according to ES6 section 9.1.8 [[Get]] (P, Receiver) 434 *access_info = 435 PropertyAccessInfo::NotFound(MapList{receiver_map}, holder); 436 return true; 437 } else { 438 return false; 439 } 440 } 441 Handle<JSObject> map_prototype(JSObject::cast(map->prototype()), isolate()); 442 if (map_prototype->map()->is_deprecated()) { 443 // Try to migrate the prototype object so we don't embed the deprecated 444 // map into the optimized code. 445 JSObject::TryMigrateInstance(map_prototype); 446 } 447 map = handle(map_prototype->map(), isolate()); 448 holder = map_prototype; 449 } while (CanInlinePropertyAccess(map)); 450 return false; 451 } 452 453 bool AccessInfoFactory::ComputePropertyAccessInfos( 454 MapHandleList const& maps, Handle<Name> name, AccessMode access_mode, 455 ZoneVector<PropertyAccessInfo>* access_infos) { 456 for (Handle<Map> map : maps) { 457 if (Map::TryUpdate(map).ToHandle(&map)) { 458 PropertyAccessInfo access_info; 459 if (!ComputePropertyAccessInfo(map, name, access_mode, &access_info)) { 460 return false; 461 } 462 // Try to merge the {access_info} with an existing one. 463 bool merged = false; 464 for (PropertyAccessInfo& other_info : *access_infos) { 465 if (other_info.Merge(&access_info)) { 466 merged = true; 467 break; 468 } 469 } 470 if (!merged) access_infos->push_back(access_info); 471 } 472 } 473 return true; 474 } 475 476 477 bool AccessInfoFactory::LookupSpecialFieldAccessor( 478 Handle<Map> map, Handle<Name> name, PropertyAccessInfo* access_info) { 479 // Check for special JSObject field accessors. 480 int offset; 481 if (Accessors::IsJSObjectFieldAccessor(map, name, &offset)) { 482 FieldIndex field_index = FieldIndex::ForInObjectOffset(offset); 483 Type* field_type = Type::NonInternal(); 484 MachineRepresentation field_representation = MachineRepresentation::kTagged; 485 if (map->IsStringMap()) { 486 DCHECK(Name::Equals(factory()->length_string(), name)); 487 // The String::length property is always a smi in the range 488 // [0, String::kMaxLength]. 489 field_type = type_cache_.kStringLengthType; 490 field_representation = MachineRepresentation::kTaggedSigned; 491 } else if (map->IsJSArrayMap()) { 492 DCHECK(Name::Equals(factory()->length_string(), name)); 493 // The JSArray::length property is a smi in the range 494 // [0, FixedDoubleArray::kMaxLength] in case of fast double 495 // elements, a smi in the range [0, FixedArray::kMaxLength] 496 // in case of other fast elements, and [0, kMaxUInt32] in 497 // case of other arrays. 498 if (IsFastDoubleElementsKind(map->elements_kind())) { 499 field_type = type_cache_.kFixedDoubleArrayLengthType; 500 field_representation = MachineRepresentation::kTaggedSigned; 501 } else if (IsFastElementsKind(map->elements_kind())) { 502 field_type = type_cache_.kFixedArrayLengthType; 503 field_representation = MachineRepresentation::kTaggedSigned; 504 } else { 505 field_type = type_cache_.kJSArrayLengthType; 506 } 507 } 508 // Special fields are always mutable. 509 *access_info = PropertyAccessInfo::DataField( 510 kMutable, MapList{map}, field_index, field_representation, field_type); 511 return true; 512 } 513 return false; 514 } 515 516 517 bool AccessInfoFactory::LookupTransition(Handle<Map> map, Handle<Name> name, 518 MaybeHandle<JSObject> holder, 519 PropertyAccessInfo* access_info) { 520 // Check if the {map} has a data transition with the given {name}. 521 if (map->unused_property_fields() == 0) { 522 *access_info = PropertyAccessInfo::Generic(MapList{map}); 523 return true; 524 } 525 Handle<Map> transition_map; 526 if (TransitionArray::SearchTransition(map, kData, name, NONE) 527 .ToHandle(&transition_map)) { 528 int const number = transition_map->LastAdded(); 529 PropertyDetails const details = 530 transition_map->instance_descriptors()->GetDetails(number); 531 // Don't bother optimizing stores to read-only properties. 532 if (details.IsReadOnly()) return false; 533 // TODO(bmeurer): Handle transition to data constant? 534 if (details.location() != kField) return false; 535 int const index = details.field_index(); 536 Representation details_representation = details.representation(); 537 FieldIndex field_index = FieldIndex::ForPropertyIndex( 538 *transition_map, index, details_representation.IsDouble()); 539 Type* field_type = Type::NonInternal(); 540 MaybeHandle<Map> field_map; 541 MachineRepresentation field_representation = MachineRepresentation::kTagged; 542 if (details_representation.IsSmi()) { 543 field_type = Type::SignedSmall(); 544 field_representation = MachineRepresentation::kTaggedSigned; 545 } else if (details_representation.IsDouble()) { 546 field_type = type_cache_.kFloat64; 547 field_representation = MachineRepresentation::kFloat64; 548 } else if (details_representation.IsHeapObject()) { 549 // Extract the field type from the property details (make sure its 550 // representation is TaggedPointer to reflect the heap object case). 551 field_representation = MachineRepresentation::kTaggedPointer; 552 Handle<FieldType> descriptors_field_type( 553 transition_map->instance_descriptors()->GetFieldType(number), 554 isolate()); 555 if (descriptors_field_type->IsNone()) { 556 // Store is not safe if the field type was cleared. 557 return false; 558 } else if (descriptors_field_type->IsClass()) { 559 // Add proper code dependencies in case of stable field map(s). 560 Handle<Map> field_owner_map(transition_map->FindFieldOwner(number), 561 isolate()); 562 dependencies()->AssumeFieldOwner(field_owner_map); 563 564 // Remember the field map, and try to infer a useful type. 565 field_type = Type::For(descriptors_field_type->AsClass()); 566 field_map = descriptors_field_type->AsClass(); 567 } 568 } 569 dependencies()->AssumeMapNotDeprecated(transition_map); 570 // Transitioning stores are never stores to constant fields. 571 *access_info = PropertyAccessInfo::DataField( 572 kMutable, MapList{map}, field_index, field_representation, field_type, 573 field_map, holder, transition_map); 574 return true; 575 } 576 return false; 577 } 578 579 580 Factory* AccessInfoFactory::factory() const { return isolate()->factory(); } 581 582 } // namespace compiler 583 } // namespace internal 584 } // namespace v8 585