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