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 
CanInlineElementAccess(Handle<Map> map)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 
CanInlinePropertyAccess(Handle<Map> map)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 
operator <<(std::ostream & os,AccessMode access_mode)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
NotFound(Type * receiver_type,MaybeHandle<JSObject> holder)61 PropertyAccessInfo PropertyAccessInfo::NotFound(Type* receiver_type,
62                                                 MaybeHandle<JSObject> holder) {
63   return PropertyAccessInfo(holder, receiver_type);
64 }
65 
66 
67 // static
DataConstant(Type * receiver_type,Handle<Object> constant,MaybeHandle<JSObject> holder)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
DataField(Type * receiver_type,FieldIndex field_index,Type * field_type,FieldCheck field_check,MaybeHandle<JSObject> holder,MaybeHandle<Map> transition_map)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 
ElementAccessInfo()85 ElementAccessInfo::ElementAccessInfo() : receiver_type_(Type::None()) {}
86 
87 
ElementAccessInfo(Type * receiver_type,ElementsKind elements_kind,MaybeHandle<JSObject> holder)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 
PropertyAccessInfo()96 PropertyAccessInfo::PropertyAccessInfo()
97     : kind_(kInvalid), receiver_type_(Type::None()), field_type_(Type::Any()) {}
98 
99 
PropertyAccessInfo(MaybeHandle<JSObject> holder,Type * receiver_type)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 
PropertyAccessInfo(MaybeHandle<JSObject> holder,Handle<Object> constant,Type * receiver_type)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 
PropertyAccessInfo(MaybeHandle<JSObject> holder,MaybeHandle<Map> transition_map,FieldIndex field_index,FieldCheck field_check,Type * field_type,Type * receiver_type)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 
AccessInfoFactory(CompilationDependencies * dependencies,Handle<Context> native_context,Zone * zone)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 
ComputeElementAccessInfo(Handle<Map> map,AccessMode access_mode,ElementAccessInfo * access_info)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 
ComputeElementAccessInfos(MapHandleList const & maps,AccessMode access_mode,ZoneVector<ElementAccessInfo> * access_infos)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 
ComputePropertyAccessInfo(Handle<Map> map,Handle<Name> name,AccessMode access_mode,PropertyAccessInfo * access_info)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 
ComputePropertyAccessInfos(MapHandleList const & maps,Handle<Name> name,AccessMode access_mode,ZoneVector<PropertyAccessInfo> * access_infos)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 
LookupSpecialFieldAccessor(Handle<Map> map,Handle<Name> name,PropertyAccessInfo * access_info)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 
LookupTransition(Handle<Map> map,Handle<Name> name,MaybeHandle<JSObject> holder,PropertyAccessInfo * access_info)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 
factory() const484 Factory* AccessInfoFactory::factory() const { return isolate()->factory(); }
485 
486 }  // namespace compiler
487 }  // namespace internal
488 }  // namespace v8
489