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 
CanInlineElementAccess(Handle<Map> map)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 
CanInlinePropertyAccess(Handle<Map> map)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 
operator <<(std::ostream & os,AccessMode access_mode)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 
ElementAccessInfo()60 ElementAccessInfo::ElementAccessInfo() {}
61 
ElementAccessInfo(MapList const & receiver_maps,ElementsKind elements_kind)62 ElementAccessInfo::ElementAccessInfo(MapList const& receiver_maps,
63                                      ElementsKind elements_kind)
64     : elements_kind_(elements_kind), receiver_maps_(receiver_maps) {}
65 
66 // static
NotFound(MapList const & receiver_maps,MaybeHandle<JSObject> holder)67 PropertyAccessInfo PropertyAccessInfo::NotFound(MapList const& receiver_maps,
68                                                 MaybeHandle<JSObject> holder) {
69   return PropertyAccessInfo(holder, receiver_maps);
70 }
71 
72 // static
DataConstant(MapList const & receiver_maps,Handle<Object> constant,MaybeHandle<JSObject> holder)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
DataField(MapList const & receiver_maps,FieldIndex field_index,MachineRepresentation field_representation,Type * field_type,MaybeHandle<Map> field_map,MaybeHandle<JSObject> holder,MaybeHandle<Map> transition_map)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
AccessorConstant(MapList const & receiver_maps,Handle<Object> constant,MaybeHandle<JSObject> holder)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
Generic(MapList const & receiver_maps)98 PropertyAccessInfo PropertyAccessInfo::Generic(MapList const& receiver_maps) {
99   return PropertyAccessInfo(kGeneric, MaybeHandle<JSObject>(), Handle<Object>(),
100                             receiver_maps);
101 }
102 
PropertyAccessInfo()103 PropertyAccessInfo::PropertyAccessInfo()
104     : kind_(kInvalid),
105       field_representation_(MachineRepresentation::kNone),
106       field_type_(Type::None()) {}
107 
PropertyAccessInfo(MaybeHandle<JSObject> holder,MapList const & receiver_maps)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 
PropertyAccessInfo(Kind kind,MaybeHandle<JSObject> holder,Handle<Object> constant,MapList const & receiver_maps)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 
PropertyAccessInfo(MaybeHandle<JSObject> holder,MaybeHandle<Map> transition_map,FieldIndex field_index,MachineRepresentation field_representation,Type * field_type,MaybeHandle<Map> field_map,MapList const & receiver_maps)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 
Merge(PropertyAccessInfo const * that)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 
AccessInfoFactory(CompilationDependencies * dependencies,Handle<Context> native_context,Zone * zone)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 
ComputeElementAccessInfo(Handle<Map> map,AccessMode access_mode,ElementAccessInfo * access_info)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 
ComputeElementAccessInfos(MapHandleList const & maps,AccessMode access_mode,ZoneVector<ElementAccessInfo> * access_infos)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 
ComputePropertyAccessInfo(Handle<Map> map,Handle<Name> name,AccessMode access_mode,PropertyAccessInfo * access_info)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 
ComputePropertyAccessInfos(MapHandleList const & maps,Handle<Name> name,AccessMode access_mode,ZoneVector<PropertyAccessInfo> * access_infos)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 
LookupSpecialFieldAccessor(Handle<Map> map,Handle<Name> name,PropertyAccessInfo * access_info)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 
LookupTransition(Handle<Map> map,Handle<Name> name,MaybeHandle<JSObject> holder,PropertyAccessInfo * access_info)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 
factory() const551 Factory* AccessInfoFactory::factory() const { return isolate()->factory(); }
552 
553 }  // namespace compiler
554 }  // namespace internal
555 }  // namespace v8
556