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