1
2 // Copyright 2015 the V8 project authors. All rights reserved.
3 // Use of this source code is governed by a BSD-style license that can be
4 // found in the LICENSE file.
5
6 #include "src/compiler/access-info.h"
7
8 #include <ostream>
9
10 #include "src/builtins/accessors.h"
11 #include "src/compiler/compilation-dependencies.h"
12 #include "src/compiler/simplified-operator.h"
13 #include "src/compiler/type-cache.h"
14 #include "src/ic/call-optimization.h"
15 #include "src/ic/handler-configuration.h"
16 #include "src/logging/counters.h"
17 #include "src/objects/cell-inl.h"
18 #include "src/objects/field-index-inl.h"
19 #include "src/objects/field-type.h"
20 #include "src/objects/module-inl.h"
21 #include "src/objects/objects-inl.h"
22 #include "src/objects/struct-inl.h"
23 #include "src/objects/templates.h"
24
25 namespace v8 {
26 namespace internal {
27 namespace compiler {
28
29 namespace {
30
CanInlinePropertyAccess(MapRef map,AccessMode access_mode)31 bool CanInlinePropertyAccess(MapRef map, AccessMode access_mode) {
32 // We can inline property access to prototypes of all primitives, except
33 // the special Oddball ones that have no wrapper counterparts (i.e. Null,
34 // Undefined and TheHole).
35 // We can only inline accesses to dictionary mode holders if the access is a
36 // load and the holder is a prototype. The latter ensures a 1:1
37 // relationship between the map and the object (and therefore the property
38 // dictionary).
39 STATIC_ASSERT(ODDBALL_TYPE == LAST_PRIMITIVE_HEAP_OBJECT_TYPE);
40 if (map.object()->IsBooleanMap()) return true;
41 if (map.instance_type() < LAST_PRIMITIVE_HEAP_OBJECT_TYPE) return true;
42 if (map.object()->IsJSObjectMap()) {
43 if (map.is_dictionary_map()) {
44 if (!V8_DICT_PROPERTY_CONST_TRACKING_BOOL) return false;
45 return access_mode == AccessMode::kLoad &&
46 map.object()->is_prototype_map();
47 }
48 return !map.object()->has_named_interceptor() &&
49 // TODO(verwaest): Allowlist contexts to which we have access.
50 !map.is_access_check_needed();
51 }
52 return false;
53 }
54
55 #ifdef DEBUG
HasFieldRepresentationDependenciesOnMap(ZoneVector<CompilationDependency const * > & dependencies,Handle<Map> const & field_owner_map)56 bool HasFieldRepresentationDependenciesOnMap(
57 ZoneVector<CompilationDependency const*>& dependencies,
58 Handle<Map> const& field_owner_map) {
59 for (auto dep : dependencies) {
60 if (CompilationDependencies::IsFieldRepresentationDependencyOnMap(
61 dep, field_owner_map)) {
62 return true;
63 }
64 }
65 return false;
66 }
67 #endif
68
69 } // namespace
70
71
operator <<(std::ostream & os,AccessMode access_mode)72 std::ostream& operator<<(std::ostream& os, AccessMode access_mode) {
73 switch (access_mode) {
74 case AccessMode::kLoad:
75 return os << "Load";
76 case AccessMode::kStore:
77 return os << "Store";
78 case AccessMode::kStoreInLiteral:
79 return os << "StoreInLiteral";
80 case AccessMode::kHas:
81 return os << "Has";
82 case AccessMode::kDefine:
83 return os << "Define";
84 }
85 UNREACHABLE();
86 }
87
ElementAccessInfo(ZoneVector<MapRef> && lookup_start_object_maps,ElementsKind elements_kind,Zone * zone)88 ElementAccessInfo::ElementAccessInfo(
89 ZoneVector<MapRef>&& lookup_start_object_maps, ElementsKind elements_kind,
90 Zone* zone)
91 : elements_kind_(elements_kind),
92 lookup_start_object_maps_(lookup_start_object_maps),
93 transition_sources_(zone) {
94 CHECK(!lookup_start_object_maps.empty());
95 }
96
97 // static
Invalid(Zone * zone)98 PropertyAccessInfo PropertyAccessInfo::Invalid(Zone* zone) {
99 return PropertyAccessInfo(zone);
100 }
101
102 // static
NotFound(Zone * zone,MapRef receiver_map,base::Optional<JSObjectRef> holder)103 PropertyAccessInfo PropertyAccessInfo::NotFound(
104 Zone* zone, MapRef receiver_map, base::Optional<JSObjectRef> holder) {
105 return PropertyAccessInfo(zone, kNotFound, holder, {{receiver_map}, zone});
106 }
107
108 // static
DataField(Zone * zone,MapRef receiver_map,ZoneVector<CompilationDependency const * > && dependencies,FieldIndex field_index,Representation field_representation,Type field_type,MapRef field_owner_map,base::Optional<MapRef> field_map,base::Optional<JSObjectRef> holder,base::Optional<MapRef> transition_map)109 PropertyAccessInfo PropertyAccessInfo::DataField(
110 Zone* zone, MapRef receiver_map,
111 ZoneVector<CompilationDependency const*>&& dependencies,
112 FieldIndex field_index, Representation field_representation,
113 Type field_type, MapRef field_owner_map, base::Optional<MapRef> field_map,
114 base::Optional<JSObjectRef> holder, base::Optional<MapRef> transition_map) {
115 DCHECK(!field_representation.IsNone());
116 DCHECK_IMPLIES(
117 field_representation.IsDouble(),
118 HasFieldRepresentationDependenciesOnMap(
119 dependencies, transition_map.has_value()
120 ? transition_map->object()
121 : holder.has_value() ? holder->map().object()
122 : receiver_map.object()));
123 return PropertyAccessInfo(kDataField, holder, transition_map, field_index,
124 field_representation, field_type, field_owner_map,
125 field_map, {{receiver_map}, zone},
126 std::move(dependencies));
127 }
128
129 // static
FastDataConstant(Zone * zone,MapRef receiver_map,ZoneVector<CompilationDependency const * > && dependencies,FieldIndex field_index,Representation field_representation,Type field_type,MapRef field_owner_map,base::Optional<MapRef> field_map,base::Optional<JSObjectRef> holder,base::Optional<MapRef> transition_map)130 PropertyAccessInfo PropertyAccessInfo::FastDataConstant(
131 Zone* zone, MapRef receiver_map,
132 ZoneVector<CompilationDependency const*>&& dependencies,
133 FieldIndex field_index, Representation field_representation,
134 Type field_type, MapRef field_owner_map, base::Optional<MapRef> field_map,
135 base::Optional<JSObjectRef> holder, base::Optional<MapRef> transition_map) {
136 DCHECK(!field_representation.IsNone());
137 return PropertyAccessInfo(kFastDataConstant, holder, transition_map,
138 field_index, field_representation, field_type,
139 field_owner_map, field_map, {{receiver_map}, zone},
140 std::move(dependencies));
141 }
142
143 // static
FastAccessorConstant(Zone * zone,MapRef receiver_map,base::Optional<ObjectRef> constant,base::Optional<JSObjectRef> holder)144 PropertyAccessInfo PropertyAccessInfo::FastAccessorConstant(
145 Zone* zone, MapRef receiver_map, base::Optional<ObjectRef> constant,
146 base::Optional<JSObjectRef> holder) {
147 return PropertyAccessInfo(zone, kFastAccessorConstant, holder, constant, {},
148 {{receiver_map}, zone});
149 }
150
151 // static
ModuleExport(Zone * zone,MapRef receiver_map,CellRef cell)152 PropertyAccessInfo PropertyAccessInfo::ModuleExport(Zone* zone,
153 MapRef receiver_map,
154 CellRef cell) {
155 return PropertyAccessInfo(zone, kModuleExport, {}, cell, {},
156 {{receiver_map}, zone});
157 }
158
159 // static
StringLength(Zone * zone,MapRef receiver_map)160 PropertyAccessInfo PropertyAccessInfo::StringLength(Zone* zone,
161 MapRef receiver_map) {
162 return PropertyAccessInfo(zone, kStringLength, {}, {{receiver_map}, zone});
163 }
164
165 // static
DictionaryProtoDataConstant(Zone * zone,MapRef receiver_map,JSObjectRef holder,InternalIndex dictionary_index,NameRef name)166 PropertyAccessInfo PropertyAccessInfo::DictionaryProtoDataConstant(
167 Zone* zone, MapRef receiver_map, JSObjectRef holder,
168 InternalIndex dictionary_index, NameRef name) {
169 return PropertyAccessInfo(zone, kDictionaryProtoDataConstant, holder,
170 {{receiver_map}, zone}, dictionary_index, name);
171 }
172
173 // static
DictionaryProtoAccessorConstant(Zone * zone,MapRef receiver_map,base::Optional<JSObjectRef> holder,ObjectRef constant,NameRef property_name)174 PropertyAccessInfo PropertyAccessInfo::DictionaryProtoAccessorConstant(
175 Zone* zone, MapRef receiver_map, base::Optional<JSObjectRef> holder,
176 ObjectRef constant, NameRef property_name) {
177 return PropertyAccessInfo(zone, kDictionaryProtoAccessorConstant, holder,
178 constant, property_name, {{receiver_map}, zone});
179 }
180
PropertyAccessInfo(Zone * zone)181 PropertyAccessInfo::PropertyAccessInfo(Zone* zone)
182 : kind_(kInvalid),
183 lookup_start_object_maps_(zone),
184 unrecorded_dependencies_(zone),
185 field_representation_(Representation::None()),
186 field_type_(Type::None()),
187 dictionary_index_(InternalIndex::NotFound()) {}
188
PropertyAccessInfo(Zone * zone,Kind kind,base::Optional<JSObjectRef> holder,ZoneVector<MapRef> && lookup_start_object_maps)189 PropertyAccessInfo::PropertyAccessInfo(
190 Zone* zone, Kind kind, base::Optional<JSObjectRef> holder,
191 ZoneVector<MapRef>&& lookup_start_object_maps)
192 : kind_(kind),
193 lookup_start_object_maps_(lookup_start_object_maps),
194 holder_(holder),
195 unrecorded_dependencies_(zone),
196 field_representation_(Representation::None()),
197 field_type_(Type::None()),
198 dictionary_index_(InternalIndex::NotFound()) {}
199
PropertyAccessInfo(Zone * zone,Kind kind,base::Optional<JSObjectRef> holder,base::Optional<ObjectRef> constant,base::Optional<NameRef> name,ZoneVector<MapRef> && lookup_start_object_maps)200 PropertyAccessInfo::PropertyAccessInfo(
201 Zone* zone, Kind kind, base::Optional<JSObjectRef> holder,
202 base::Optional<ObjectRef> constant, base::Optional<NameRef> name,
203 ZoneVector<MapRef>&& lookup_start_object_maps)
204 : kind_(kind),
205 lookup_start_object_maps_(lookup_start_object_maps),
206 constant_(constant),
207 holder_(holder),
208 unrecorded_dependencies_(zone),
209 field_representation_(Representation::None()),
210 field_type_(Type::Any()),
211 dictionary_index_(InternalIndex::NotFound()),
212 name_(name) {
213 DCHECK_IMPLIES(kind == kDictionaryProtoAccessorConstant, name.has_value());
214 }
215
PropertyAccessInfo(Kind kind,base::Optional<JSObjectRef> holder,base::Optional<MapRef> transition_map,FieldIndex field_index,Representation field_representation,Type field_type,MapRef field_owner_map,base::Optional<MapRef> field_map,ZoneVector<MapRef> && lookup_start_object_maps,ZoneVector<CompilationDependency const * > && unrecorded_dependencies)216 PropertyAccessInfo::PropertyAccessInfo(
217 Kind kind, base::Optional<JSObjectRef> holder,
218 base::Optional<MapRef> transition_map, FieldIndex field_index,
219 Representation field_representation, Type field_type,
220 MapRef field_owner_map, base::Optional<MapRef> field_map,
221 ZoneVector<MapRef>&& lookup_start_object_maps,
222 ZoneVector<CompilationDependency const*>&& unrecorded_dependencies)
223 : kind_(kind),
224 lookup_start_object_maps_(lookup_start_object_maps),
225 holder_(holder),
226 unrecorded_dependencies_(std::move(unrecorded_dependencies)),
227 transition_map_(transition_map),
228 field_index_(field_index),
229 field_representation_(field_representation),
230 field_type_(field_type),
231 field_owner_map_(field_owner_map),
232 field_map_(field_map),
233 dictionary_index_(InternalIndex::NotFound()) {
234 DCHECK_IMPLIES(transition_map.has_value(),
235 field_owner_map.equals(transition_map.value()));
236 }
237
PropertyAccessInfo(Zone * zone,Kind kind,base::Optional<JSObjectRef> holder,ZoneVector<MapRef> && lookup_start_object_maps,InternalIndex dictionary_index,NameRef name)238 PropertyAccessInfo::PropertyAccessInfo(
239 Zone* zone, Kind kind, base::Optional<JSObjectRef> holder,
240 ZoneVector<MapRef>&& lookup_start_object_maps,
241 InternalIndex dictionary_index, NameRef name)
242 : kind_(kind),
243 lookup_start_object_maps_(lookup_start_object_maps),
244 holder_(holder),
245 unrecorded_dependencies_(zone),
246 field_representation_(Representation::None()),
247 field_type_(Type::Any()),
248 dictionary_index_(dictionary_index),
249 name_{name} {}
250
251 namespace {
252
253 template <class RefT>
OptionalRefEquals(base::Optional<RefT> lhs,base::Optional<RefT> rhs)254 bool OptionalRefEquals(base::Optional<RefT> lhs, base::Optional<RefT> rhs) {
255 if (!lhs.has_value()) return !rhs.has_value();
256 if (!rhs.has_value()) return false;
257 return lhs->equals(rhs.value());
258 }
259
260 template <class T>
AppendVector(ZoneVector<T> * dst,const ZoneVector<T> & src)261 void AppendVector(ZoneVector<T>* dst, const ZoneVector<T>& src) {
262 dst->insert(dst->end(), src.begin(), src.end());
263 }
264
265 } // namespace
266
Merge(PropertyAccessInfo const * that,AccessMode access_mode,Zone * zone)267 bool PropertyAccessInfo::Merge(PropertyAccessInfo const* that,
268 AccessMode access_mode, Zone* zone) {
269 if (kind_ != that->kind_) return false;
270 if (!OptionalRefEquals(holder_, that->holder_)) return false;
271
272 switch (kind_) {
273 case kInvalid:
274 DCHECK_EQ(that->kind_, kInvalid);
275 return true;
276
277 case kDataField:
278 case kFastDataConstant: {
279 // Check if we actually access the same field (we use the
280 // GetFieldAccessStubKey method here just like the ICs do
281 // since that way we only compare the relevant bits of the
282 // field indices).
283 if (field_index_.GetFieldAccessStubKey() !=
284 that->field_index_.GetFieldAccessStubKey()) {
285 return false;
286 }
287
288 switch (access_mode) {
289 case AccessMode::kHas:
290 case AccessMode::kLoad: {
291 if (!field_representation_.Equals(that->field_representation_)) {
292 if (field_representation_.IsDouble() ||
293 that->field_representation_.IsDouble()) {
294 return false;
295 }
296 field_representation_ = Representation::Tagged();
297 }
298 if (!OptionalRefEquals(field_map_, that->field_map_)) {
299 field_map_ = {};
300 }
301 break;
302 }
303 case AccessMode::kStore:
304 case AccessMode::kStoreInLiteral:
305 case AccessMode::kDefine: {
306 // For stores, the field map and field representation information
307 // must match exactly, otherwise we cannot merge the stores. We
308 // also need to make sure that in case of transitioning stores,
309 // the transition targets match.
310 if (!OptionalRefEquals(field_map_, that->field_map_) ||
311 !field_representation_.Equals(that->field_representation_) ||
312 !OptionalRefEquals(transition_map_, that->transition_map_)) {
313 return false;
314 }
315 break;
316 }
317 }
318
319 field_type_ = Type::Union(field_type_, that->field_type_, zone);
320 AppendVector(&lookup_start_object_maps_, that->lookup_start_object_maps_);
321 AppendVector(&unrecorded_dependencies_, that->unrecorded_dependencies_);
322 return true;
323 }
324
325 case kDictionaryProtoAccessorConstant:
326 case kFastAccessorConstant: {
327 // Check if we actually access the same constant.
328 if (!OptionalRefEquals(constant_, that->constant_)) return false;
329
330 DCHECK(unrecorded_dependencies_.empty());
331 DCHECK(that->unrecorded_dependencies_.empty());
332 AppendVector(&lookup_start_object_maps_, that->lookup_start_object_maps_);
333 return true;
334 }
335
336 case kDictionaryProtoDataConstant: {
337 DCHECK_EQ(AccessMode::kLoad, access_mode);
338 if (dictionary_index_ != that->dictionary_index_) return false;
339 AppendVector(&lookup_start_object_maps_, that->lookup_start_object_maps_);
340 return true;
341 }
342
343 case kNotFound:
344 case kStringLength: {
345 DCHECK(unrecorded_dependencies_.empty());
346 DCHECK(that->unrecorded_dependencies_.empty());
347 AppendVector(&lookup_start_object_maps_, that->lookup_start_object_maps_);
348 return true;
349 }
350 case kModuleExport:
351 return false;
352 }
353 }
354
GetConstFieldInfo() const355 ConstFieldInfo PropertyAccessInfo::GetConstFieldInfo() const {
356 return IsFastDataConstant() ? ConstFieldInfo(field_owner_map_->object())
357 : ConstFieldInfo::None();
358 }
359
AccessInfoFactory(JSHeapBroker * broker,CompilationDependencies * dependencies,Zone * zone)360 AccessInfoFactory::AccessInfoFactory(JSHeapBroker* broker,
361 CompilationDependencies* dependencies,
362 Zone* zone)
363 : broker_(broker),
364 dependencies_(dependencies),
365 type_cache_(TypeCache::Get()),
366 zone_(zone) {}
367
ComputeElementAccessInfo(MapRef map,AccessMode access_mode) const368 base::Optional<ElementAccessInfo> AccessInfoFactory::ComputeElementAccessInfo(
369 MapRef map, AccessMode access_mode) const {
370 if (!map.CanInlineElementAccess()) return {};
371 return ElementAccessInfo({{map}, zone()}, map.elements_kind(), zone());
372 }
373
ComputeElementAccessInfos(ElementAccessFeedback const & feedback,ZoneVector<ElementAccessInfo> * access_infos) const374 bool AccessInfoFactory::ComputeElementAccessInfos(
375 ElementAccessFeedback const& feedback,
376 ZoneVector<ElementAccessInfo>* access_infos) const {
377 AccessMode access_mode = feedback.keyed_mode().access_mode();
378 if (access_mode == AccessMode::kLoad || access_mode == AccessMode::kHas) {
379 // For polymorphic loads of similar elements kinds (i.e. all tagged or all
380 // double), always use the "worst case" code without a transition. This is
381 // much faster than transitioning the elements to the worst case, trading a
382 // TransitionElementsKind for a CheckMaps, avoiding mutation of the array.
383 base::Optional<ElementAccessInfo> access_info =
384 ConsolidateElementLoad(feedback);
385 if (access_info.has_value()) {
386 access_infos->push_back(*access_info);
387 return true;
388 }
389 }
390
391 for (auto const& group : feedback.transition_groups()) {
392 DCHECK(!group.empty());
393 base::Optional<MapRef> target =
394 MakeRefAssumeMemoryFence(broker(), group.front());
395 base::Optional<ElementAccessInfo> access_info =
396 ComputeElementAccessInfo(target.value(), access_mode);
397 if (!access_info.has_value()) return false;
398
399 for (size_t i = 1; i < group.size(); ++i) {
400 base::Optional<MapRef> map_ref =
401 MakeRefAssumeMemoryFence(broker(), group[i]);
402 if (!map_ref.has_value()) continue;
403 access_info->AddTransitionSource(map_ref.value());
404 }
405 access_infos->push_back(*access_info);
406 }
407 return true;
408 }
409
ComputeDataFieldAccessInfo(MapRef receiver_map,MapRef map,NameRef name,base::Optional<JSObjectRef> holder,InternalIndex descriptor,AccessMode access_mode) const410 PropertyAccessInfo AccessInfoFactory::ComputeDataFieldAccessInfo(
411 MapRef receiver_map, MapRef map, NameRef name,
412 base::Optional<JSObjectRef> holder, InternalIndex descriptor,
413 AccessMode access_mode) const {
414 DCHECK(descriptor.is_found());
415 // TODO(jgruber,v8:7790): Use DescriptorArrayRef instead.
416 Handle<DescriptorArray> descriptors = map.instance_descriptors().object();
417 PropertyDetails const details = descriptors->GetDetails(descriptor);
418 int index = descriptors->GetFieldIndex(descriptor);
419 Representation details_representation = details.representation();
420 if (details_representation.IsNone()) {
421 // The ICs collect feedback in PREMONOMORPHIC state already,
422 // but at this point the {receiver_map} might still contain
423 // fields for which the representation has not yet been
424 // determined by the runtime. So we need to catch this case
425 // here and fall back to use the regular IC logic instead.
426 return Invalid();
427 }
428 FieldIndex field_index = FieldIndex::ForPropertyIndex(*map.object(), index,
429 details_representation);
430 // Private brands are used when loading private methods, which are stored in a
431 // BlockContext, an internal object.
432 Type field_type = name.object()->IsPrivateBrand() ? Type::OtherInternal()
433 : Type::NonInternal();
434 base::Optional<MapRef> field_map;
435
436 ZoneVector<CompilationDependency const*> unrecorded_dependencies(zone());
437
438 Handle<FieldType> descriptors_field_type =
439 broker()->CanonicalPersistentHandle(
440 descriptors->GetFieldType(descriptor));
441 base::Optional<ObjectRef> descriptors_field_type_ref =
442 TryMakeRef<Object>(broker(), descriptors_field_type);
443 if (!descriptors_field_type_ref.has_value()) return Invalid();
444
445 if (details_representation.IsSmi()) {
446 field_type = Type::SignedSmall();
447 unrecorded_dependencies.push_back(
448 dependencies()->FieldRepresentationDependencyOffTheRecord(
449 map, descriptor, details_representation));
450 } else if (details_representation.IsDouble()) {
451 field_type = type_cache_->kFloat64;
452 unrecorded_dependencies.push_back(
453 dependencies()->FieldRepresentationDependencyOffTheRecord(
454 map, descriptor, details_representation));
455 } else if (details_representation.IsHeapObject()) {
456 if (descriptors_field_type->IsNone()) {
457 switch (access_mode) {
458 case AccessMode::kStore:
459 case AccessMode::kStoreInLiteral:
460 case AccessMode::kDefine:
461 // Store is not safe if the field type was cleared.
462 return Invalid();
463 case AccessMode::kLoad:
464 case AccessMode::kHas:
465 break;
466 }
467
468 // The field type was cleared by the GC, so we don't know anything
469 // about the contents now.
470 }
471 unrecorded_dependencies.push_back(
472 dependencies()->FieldRepresentationDependencyOffTheRecord(
473 map, descriptor, details_representation));
474 if (descriptors_field_type->IsClass()) {
475 // Remember the field map, and try to infer a useful type.
476 base::Optional<MapRef> maybe_field_map =
477 TryMakeRef(broker(), descriptors_field_type->AsClass());
478 if (!maybe_field_map.has_value()) return Invalid();
479 field_type = Type::For(maybe_field_map.value());
480 field_map = maybe_field_map;
481 }
482 } else {
483 CHECK(details_representation.IsTagged());
484 }
485 // TODO(turbofan): We may want to do this only depending on the use
486 // of the access info.
487 unrecorded_dependencies.push_back(
488 dependencies()->FieldTypeDependencyOffTheRecord(
489 map, descriptor, descriptors_field_type_ref.value()));
490
491 PropertyConstness constness;
492 if (details.IsReadOnly() && !details.IsConfigurable()) {
493 constness = PropertyConstness::kConst;
494 } else {
495 constness = dependencies()->DependOnFieldConstness(map, descriptor);
496 }
497
498 // Note: FindFieldOwner may be called multiple times throughout one
499 // compilation. This is safe since its result is fixed for a given map and
500 // descriptor.
501 MapRef field_owner_map = map.FindFieldOwner(descriptor);
502
503 switch (constness) {
504 case PropertyConstness::kMutable:
505 return PropertyAccessInfo::DataField(
506 zone(), receiver_map, std::move(unrecorded_dependencies), field_index,
507 details_representation, field_type, field_owner_map, field_map,
508 holder, {});
509
510 case PropertyConstness::kConst:
511 return PropertyAccessInfo::FastDataConstant(
512 zone(), receiver_map, std::move(unrecorded_dependencies), field_index,
513 details_representation, field_type, field_owner_map, field_map,
514 holder, {});
515 }
516 UNREACHABLE();
517 }
518
519 namespace {
520
521 using AccessorsObjectGetter = std::function<Handle<Object>()>;
522
AccessorAccessInfoHelper(Isolate * isolate,Zone * zone,JSHeapBroker * broker,const AccessInfoFactory * ai_factory,MapRef receiver_map,NameRef name,MapRef map,base::Optional<JSObjectRef> holder,AccessMode access_mode,AccessorsObjectGetter get_accessors)523 PropertyAccessInfo AccessorAccessInfoHelper(
524 Isolate* isolate, Zone* zone, JSHeapBroker* broker,
525 const AccessInfoFactory* ai_factory, MapRef receiver_map, NameRef name,
526 MapRef map, base::Optional<JSObjectRef> holder, AccessMode access_mode,
527 AccessorsObjectGetter get_accessors) {
528 if (map.instance_type() == JS_MODULE_NAMESPACE_TYPE) {
529 DCHECK(map.object()->is_prototype_map());
530 Handle<PrototypeInfo> proto_info = broker->CanonicalPersistentHandle(
531 PrototypeInfo::cast(map.object()->prototype_info()));
532 Handle<JSModuleNamespace> module_namespace =
533 broker->CanonicalPersistentHandle(
534 JSModuleNamespace::cast(proto_info->module_namespace()));
535 Handle<Cell> cell = broker->CanonicalPersistentHandle(
536 Cell::cast(module_namespace->module().exports().Lookup(
537 isolate, name.object(), Smi::ToInt(name.object()->GetHash()))));
538 if (cell->value(kRelaxedLoad).IsTheHole(isolate)) {
539 // This module has not been fully initialized yet.
540 return PropertyAccessInfo::Invalid(zone);
541 }
542 base::Optional<CellRef> cell_ref = TryMakeRef(broker, cell);
543 if (!cell_ref.has_value()) {
544 return PropertyAccessInfo::Invalid(zone);
545 }
546 return PropertyAccessInfo::ModuleExport(zone, receiver_map,
547 cell_ref.value());
548 }
549 if (access_mode == AccessMode::kHas) {
550 // kHas is not supported for dictionary mode objects.
551 DCHECK(!map.is_dictionary_map());
552
553 // HasProperty checks don't call getter/setters, existence is sufficient.
554 return PropertyAccessInfo::FastAccessorConstant(zone, receiver_map, {},
555 holder);
556 }
557 Handle<Object> maybe_accessors = get_accessors();
558 if (!maybe_accessors->IsAccessorPair()) {
559 return PropertyAccessInfo::Invalid(zone);
560 }
561 Handle<AccessorPair> accessors = Handle<AccessorPair>::cast(maybe_accessors);
562 Handle<Object> accessor = broker->CanonicalPersistentHandle(
563 access_mode == AccessMode::kLoad ? accessors->getter(kAcquireLoad)
564 : accessors->setter(kAcquireLoad));
565
566 base::Optional<ObjectRef> accessor_ref = TryMakeRef(broker, accessor);
567 if (!accessor_ref.has_value()) return PropertyAccessInfo::Invalid(zone);
568
569 if (!accessor->IsJSFunction()) {
570 CallOptimization optimization(broker->local_isolate_or_isolate(), accessor);
571 if (!optimization.is_simple_api_call() ||
572 optimization.IsCrossContextLazyAccessorPair(
573 *broker->target_native_context().object(), *map.object())) {
574 return PropertyAccessInfo::Invalid(zone);
575 }
576
577 CallOptimization::HolderLookup lookup;
578 Handle<JSObject> holder_handle = broker->CanonicalPersistentHandle(
579 optimization.LookupHolderOfExpectedType(
580 broker->local_isolate_or_isolate(), receiver_map.object(),
581 &lookup));
582 if (lookup == CallOptimization::kHolderNotFound) {
583 return PropertyAccessInfo::Invalid(zone);
584 }
585 DCHECK_IMPLIES(lookup == CallOptimization::kHolderIsReceiver,
586 holder_handle.is_null());
587 DCHECK_IMPLIES(lookup == CallOptimization::kHolderFound,
588 !holder_handle.is_null());
589
590 if (holder_handle.is_null()) {
591 holder = {};
592 } else {
593 holder = TryMakeRef(broker, holder_handle);
594 if (!holder.has_value()) return PropertyAccessInfo::Invalid(zone);
595 }
596 }
597 if (access_mode == AccessMode::kLoad) {
598 base::Optional<Name> cached_property_name =
599 FunctionTemplateInfo::TryGetCachedPropertyName(isolate, *accessor);
600 if (cached_property_name.has_value()) {
601 base::Optional<NameRef> cached_property_name_ref =
602 TryMakeRef(broker, cached_property_name.value());
603 if (cached_property_name_ref.has_value()) {
604 PropertyAccessInfo access_info = ai_factory->ComputePropertyAccessInfo(
605 map, cached_property_name_ref.value(), access_mode);
606 if (!access_info.IsInvalid()) return access_info;
607 }
608 }
609 }
610
611 if (map.is_dictionary_map()) {
612 return PropertyAccessInfo::DictionaryProtoAccessorConstant(
613 zone, receiver_map, holder, accessor_ref.value(), name);
614 } else {
615 return PropertyAccessInfo::FastAccessorConstant(
616 zone, receiver_map, accessor_ref.value(), holder);
617 }
618 }
619
620 } // namespace
621
ComputeAccessorDescriptorAccessInfo(MapRef receiver_map,NameRef name,MapRef holder_map,base::Optional<JSObjectRef> holder,InternalIndex descriptor,AccessMode access_mode) const622 PropertyAccessInfo AccessInfoFactory::ComputeAccessorDescriptorAccessInfo(
623 MapRef receiver_map, NameRef name, MapRef holder_map,
624 base::Optional<JSObjectRef> holder, InternalIndex descriptor,
625 AccessMode access_mode) const {
626 DCHECK(descriptor.is_found());
627 Handle<DescriptorArray> descriptors = broker()->CanonicalPersistentHandle(
628 holder_map.object()->instance_descriptors(kRelaxedLoad));
629 SLOW_DCHECK(descriptor ==
630 descriptors->Search(*name.object(), *holder_map.object()));
631
632 auto get_accessors = [&]() {
633 return broker()->CanonicalPersistentHandle(
634 descriptors->GetStrongValue(descriptor));
635 };
636 return AccessorAccessInfoHelper(isolate(), zone(), broker(), this,
637 receiver_map, name, holder_map, holder,
638 access_mode, get_accessors);
639 }
640
ComputeDictionaryProtoAccessInfo(MapRef receiver_map,NameRef name,JSObjectRef holder,InternalIndex dictionary_index,AccessMode access_mode,PropertyDetails details) const641 PropertyAccessInfo AccessInfoFactory::ComputeDictionaryProtoAccessInfo(
642 MapRef receiver_map, NameRef name, JSObjectRef holder,
643 InternalIndex dictionary_index, AccessMode access_mode,
644 PropertyDetails details) const {
645 CHECK(V8_DICT_PROPERTY_CONST_TRACKING_BOOL);
646 DCHECK(holder.map().object()->is_prototype_map());
647 DCHECK_EQ(access_mode, AccessMode::kLoad);
648
649 // We can only inline accesses to constant properties.
650 if (details.constness() != PropertyConstness::kConst) {
651 return Invalid();
652 }
653
654 if (details.kind() == PropertyKind::kData) {
655 return PropertyAccessInfo::DictionaryProtoDataConstant(
656 zone(), receiver_map, holder, dictionary_index, name);
657 }
658
659 auto get_accessors = [&]() {
660 return JSObject::DictionaryPropertyAt(isolate(), holder.object(),
661 dictionary_index);
662 };
663 return AccessorAccessInfoHelper(isolate(), zone(), broker(), this,
664 receiver_map, name, holder.map(), holder,
665 access_mode, get_accessors);
666 }
667
TryLoadPropertyDetails(MapRef map,base::Optional<JSObjectRef> maybe_holder,NameRef name,InternalIndex * index_out,PropertyDetails * details_out) const668 bool AccessInfoFactory::TryLoadPropertyDetails(
669 MapRef map, base::Optional<JSObjectRef> maybe_holder, NameRef name,
670 InternalIndex* index_out, PropertyDetails* details_out) const {
671 if (map.is_dictionary_map()) {
672 DCHECK(V8_DICT_PROPERTY_CONST_TRACKING_BOOL);
673 DCHECK(map.object()->is_prototype_map());
674
675 DisallowGarbageCollection no_gc;
676
677 if (!maybe_holder.has_value()) {
678 // TODO(v8:11457) In this situation, we have a dictionary mode prototype
679 // as a receiver. Consider other means of obtaining the holder in this
680 // situation.
681
682 // Without the holder, we can't get the property details.
683 return false;
684 }
685
686 Handle<JSObject> holder = maybe_holder->object();
687 if (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) {
688 SwissNameDictionary dict = holder->property_dictionary_swiss();
689 *index_out = dict.FindEntry(isolate(), name.object());
690 if (index_out->is_found()) {
691 *details_out = dict.DetailsAt(*index_out);
692 }
693 } else {
694 NameDictionary dict = holder->property_dictionary();
695 *index_out = dict.FindEntry(isolate(), name.object());
696 if (index_out->is_found()) {
697 *details_out = dict.DetailsAt(*index_out);
698 }
699 }
700 } else {
701 DescriptorArray descriptors = *map.instance_descriptors().object();
702 *index_out = descriptors.Search(*name.object(), *map.object(), true);
703 if (index_out->is_found()) {
704 *details_out = descriptors.GetDetails(*index_out);
705 }
706 }
707
708 return true;
709 }
710
ComputePropertyAccessInfo(MapRef map,NameRef name,AccessMode access_mode) const711 PropertyAccessInfo AccessInfoFactory::ComputePropertyAccessInfo(
712 MapRef map, NameRef name, AccessMode access_mode) const {
713 CHECK(name.IsUniqueName());
714
715 // Dictionary property const tracking is unsupported with concurrent inlining.
716 CHECK(!V8_DICT_PROPERTY_CONST_TRACKING_BOOL);
717
718 JSHeapBroker::MapUpdaterGuardIfNeeded mumd_scope(broker());
719
720 if (access_mode == AccessMode::kHas && !map.object()->IsJSReceiverMap()) {
721 return Invalid();
722 }
723
724 // Check if it is safe to inline property access for the {map}.
725 if (!CanInlinePropertyAccess(map, access_mode)) {
726 return Invalid();
727 }
728
729 // We support fast inline cases for certain JSObject getters.
730 if (access_mode == AccessMode::kLoad || access_mode == AccessMode::kHas) {
731 PropertyAccessInfo access_info = LookupSpecialFieldAccessor(map, name);
732 if (!access_info.IsInvalid()) return access_info;
733 }
734
735 // Only relevant if V8_DICT_PROPERTY_CONST_TRACKING enabled.
736 bool dictionary_prototype_on_chain = false;
737 bool fast_mode_prototype_on_chain = false;
738
739 // Remember the receiver map. We use {map} as loop variable.
740 MapRef receiver_map = map;
741 base::Optional<JSObjectRef> holder;
742
743 // Perform the implicit ToObject for primitives here.
744 // Implemented according to ES6 section 7.3.2 GetV (V, P).
745 // Note: Keep sync'd with
746 // CompilationDependencies::DependOnStablePrototypeChains.
747 if (receiver_map.IsPrimitiveMap()) {
748 base::Optional<JSFunctionRef> constructor =
749 broker()->target_native_context().GetConstructorFunction(receiver_map);
750 if (!constructor.has_value()) return Invalid();
751 map = constructor->initial_map(broker()->dependencies());
752 DCHECK(!map.IsPrimitiveMap());
753 }
754
755 while (true) {
756 PropertyDetails details = PropertyDetails::Empty();
757 InternalIndex index = InternalIndex::NotFound();
758 if (!TryLoadPropertyDetails(map, holder, name, &index, &details)) {
759 return Invalid();
760 }
761
762 if (index.is_found()) {
763 if (access_mode == AccessMode::kStore ||
764 access_mode == AccessMode::kStoreInLiteral) {
765 DCHECK(!map.is_dictionary_map());
766
767 // Don't bother optimizing stores to read-only properties.
768 if (details.IsReadOnly()) return Invalid();
769
770 if (details.kind() == PropertyKind::kData && holder.has_value()) {
771 // This is a store to a property not found on the receiver but on a
772 // prototype. According to ES6 section 9.1.9 [[Set]], we need to
773 // create a new data property on the receiver. We can still optimize
774 // if such a transition already exists.
775 return LookupTransition(receiver_map, name, holder, NONE);
776 }
777 }
778
779 if (map.is_dictionary_map()) {
780 DCHECK(V8_DICT_PROPERTY_CONST_TRACKING_BOOL);
781
782 if (fast_mode_prototype_on_chain) {
783 // TODO(v8:11248) While the work on dictionary mode prototypes is in
784 // progress, we may still see fast mode objects on the chain prior to
785 // reaching a dictionary mode prototype holding the property . Due to
786 // this only being an intermediate state, we don't stupport these kind
787 // of heterogenous prototype chains.
788 return Invalid();
789 }
790
791 // TryLoadPropertyDetails only succeeds if we know the holder.
792 return ComputeDictionaryProtoAccessInfo(
793 receiver_map, name, holder.value(), index, access_mode, details);
794 }
795
796 if (dictionary_prototype_on_chain) {
797 // If V8_DICT_PROPERTY_CONST_TRACKING_BOOL was disabled, then a
798 // dictionary prototype would have caused a bailout earlier.
799 DCHECK(V8_DICT_PROPERTY_CONST_TRACKING_BOOL);
800
801 // TODO(v8:11248) We have a fast mode holder, but there was a dictionary
802 // mode prototype earlier on the chain. Note that seeing a fast mode
803 // prototype even though V8_DICT_PROPERTY_CONST_TRACKING is enabled
804 // should only be possible while the implementation of dictionary mode
805 // prototypes is work in progress. Eventually, enabling
806 // V8_DICT_PROPERTY_CONST_TRACKING will guarantee that all prototypes
807 // are always in dictionary mode, making this case unreachable. However,
808 // due to the complications of checking dictionary mode prototypes for
809 // modification, we don't attempt to support dictionary mode prototypes
810 // occuring before a fast mode holder on the chain.
811 return Invalid();
812 }
813 if (details.location() == PropertyLocation::kField) {
814 if (details.kind() == PropertyKind::kData) {
815 return ComputeDataFieldAccessInfo(receiver_map, map, name, holder,
816 index, access_mode);
817 } else {
818 DCHECK_EQ(PropertyKind::kAccessor, details.kind());
819 // TODO(turbofan): Add support for general accessors?
820 return Invalid();
821 }
822 } else {
823 DCHECK_EQ(PropertyLocation::kDescriptor, details.location());
824 DCHECK_EQ(PropertyKind::kAccessor, details.kind());
825 return ComputeAccessorDescriptorAccessInfo(receiver_map, name, map,
826 holder, index, access_mode);
827 }
828
829 UNREACHABLE();
830 }
831
832 // The property wasn't found on {map}. Look on the prototype if appropriate.
833 DCHECK(!index.is_found());
834
835 // Don't search on the prototype chain for special indices in case of
836 // integer indexed exotic objects (see ES6 section 9.4.5).
837 if (map.object()->IsJSTypedArrayMap() && name.IsString()) {
838 if (broker()->IsMainThread()) {
839 if (IsSpecialIndex(String::cast(*name.object()))) {
840 return Invalid();
841 }
842 } else {
843 // TODO(jgruber): We are being conservative here since we can't access
844 // string contents from background threads. Should that become possible
845 // in the future, remove this bailout.
846 return Invalid();
847 }
848 }
849
850 // Don't search on the prototype when storing in literals, or performing a
851 // Define operation
852 if (access_mode == AccessMode::kStoreInLiteral ||
853 access_mode == AccessMode::kDefine) {
854 PropertyAttributes attrs = NONE;
855 if (name.object()->IsPrivate()) {
856 // When PrivateNames are added to an object, they are by definition
857 // non-enumerable.
858 attrs = DONT_ENUM;
859 }
860 return LookupTransition(receiver_map, name, holder, attrs);
861 }
862
863 // Don't lookup private symbols on the prototype chain.
864 if (name.object()->IsPrivate()) {
865 return Invalid();
866 }
867
868 if (V8_DICT_PROPERTY_CONST_TRACKING_BOOL && holder.has_value()) {
869 // At this point, we are past the first loop iteration.
870 DCHECK(holder->object()->map().is_prototype_map());
871 DCHECK(!holder->map().equals(receiver_map));
872
873 fast_mode_prototype_on_chain =
874 fast_mode_prototype_on_chain || !map.is_dictionary_map();
875 dictionary_prototype_on_chain =
876 dictionary_prototype_on_chain || map.is_dictionary_map();
877 }
878
879 // Walk up the prototype chain.
880 // Load the map's prototype's map to guarantee that every time we use it,
881 // we use the same Map.
882 HeapObjectRef prototype = map.prototype();
883
884 MapRef map_prototype_map = prototype.map();
885 if (!map_prototype_map.object()->IsJSObjectMap()) {
886 // Don't allow proxies on the prototype chain.
887 if (!prototype.IsNull()) {
888 DCHECK(prototype.object()->IsJSProxy());
889 return Invalid();
890 }
891
892 DCHECK(prototype.IsNull());
893
894 if (dictionary_prototype_on_chain) {
895 // TODO(v8:11248) See earlier comment about
896 // dictionary_prototype_on_chain. We don't support absent properties
897 // with dictionary mode prototypes on the chain, either. This is again
898 // just due to how we currently deal with dependencies for dictionary
899 // properties during finalization.
900 return Invalid();
901 }
902
903 // Store to property not found on the receiver or any prototype, we need
904 // to transition to a new data property.
905 // Implemented according to ES6 section 9.1.9 [[Set]] (P, V, Receiver)
906 if (access_mode == AccessMode::kStore) {
907 return LookupTransition(receiver_map, name, holder, NONE);
908 }
909
910 // The property was not found (access returns undefined or throws
911 // depending on the language mode of the load operation.
912 // Implemented according to ES6 section 9.1.8 [[Get]] (P, Receiver)
913 return PropertyAccessInfo::NotFound(zone(), receiver_map, holder);
914 }
915
916 holder = prototype.AsJSObject();
917 map = map_prototype_map;
918
919 if (!CanInlinePropertyAccess(map, access_mode)) {
920 return Invalid();
921 }
922
923 // Successful lookup on prototype chain needs to guarantee that all the
924 // prototypes up to the holder have stable maps, except for dictionary-mode
925 // prototypes. We currently do this by taking a
926 // DependOnStablePrototypeChains dependency in the caller.
927 //
928 // TODO(jgruber): This is brittle and easy to miss. Consider a refactor
929 // that moves the responsibility of taking the dependency into
930 // AccessInfoFactory.
931 }
932 UNREACHABLE();
933 }
934
FinalizePropertyAccessInfosAsOne(ZoneVector<PropertyAccessInfo> access_infos,AccessMode access_mode) const935 PropertyAccessInfo AccessInfoFactory::FinalizePropertyAccessInfosAsOne(
936 ZoneVector<PropertyAccessInfo> access_infos, AccessMode access_mode) const {
937 ZoneVector<PropertyAccessInfo> merged_access_infos(zone());
938 MergePropertyAccessInfos(access_infos, access_mode, &merged_access_infos);
939 if (merged_access_infos.size() == 1) {
940 PropertyAccessInfo& result = merged_access_infos.front();
941 if (!result.IsInvalid()) {
942 result.RecordDependencies(dependencies());
943 return result;
944 }
945 }
946 return Invalid();
947 }
948
RecordDependencies(CompilationDependencies * dependencies)949 void PropertyAccessInfo::RecordDependencies(
950 CompilationDependencies* dependencies) {
951 for (CompilationDependency const* d : unrecorded_dependencies_) {
952 dependencies->RecordDependency(d);
953 }
954 unrecorded_dependencies_.clear();
955 }
956
FinalizePropertyAccessInfos(ZoneVector<PropertyAccessInfo> access_infos,AccessMode access_mode,ZoneVector<PropertyAccessInfo> * result) const957 bool AccessInfoFactory::FinalizePropertyAccessInfos(
958 ZoneVector<PropertyAccessInfo> access_infos, AccessMode access_mode,
959 ZoneVector<PropertyAccessInfo>* result) const {
960 if (access_infos.empty()) return false;
961 MergePropertyAccessInfos(access_infos, access_mode, result);
962 for (PropertyAccessInfo const& info : *result) {
963 if (info.IsInvalid()) return false;
964 }
965 for (PropertyAccessInfo& info : *result) {
966 info.RecordDependencies(dependencies());
967 }
968 return true;
969 }
970
MergePropertyAccessInfos(ZoneVector<PropertyAccessInfo> infos,AccessMode access_mode,ZoneVector<PropertyAccessInfo> * result) const971 void AccessInfoFactory::MergePropertyAccessInfos(
972 ZoneVector<PropertyAccessInfo> infos, AccessMode access_mode,
973 ZoneVector<PropertyAccessInfo>* result) const {
974 DCHECK(result->empty());
975 for (auto it = infos.begin(), end = infos.end(); it != end; ++it) {
976 bool merged = false;
977 for (auto ot = it + 1; ot != end; ++ot) {
978 if (ot->Merge(&(*it), access_mode, zone())) {
979 merged = true;
980 break;
981 }
982 }
983 if (!merged) result->push_back(*it);
984 }
985 CHECK(!result->empty());
986 }
987
isolate() const988 Isolate* AccessInfoFactory::isolate() const { return broker()->isolate(); }
989
990 namespace {
991
GeneralizeElementsKind(ElementsKind this_kind,ElementsKind that_kind)992 Maybe<ElementsKind> GeneralizeElementsKind(ElementsKind this_kind,
993 ElementsKind that_kind) {
994 if (IsHoleyElementsKind(this_kind)) {
995 that_kind = GetHoleyElementsKind(that_kind);
996 } else if (IsHoleyElementsKind(that_kind)) {
997 this_kind = GetHoleyElementsKind(this_kind);
998 }
999 if (this_kind == that_kind) return Just(this_kind);
1000 if (IsDoubleElementsKind(that_kind) == IsDoubleElementsKind(this_kind)) {
1001 if (IsMoreGeneralElementsKindTransition(that_kind, this_kind)) {
1002 return Just(this_kind);
1003 }
1004 if (IsMoreGeneralElementsKindTransition(this_kind, that_kind)) {
1005 return Just(that_kind);
1006 }
1007 }
1008 return Nothing<ElementsKind>();
1009 }
1010
1011 } // namespace
1012
ConsolidateElementLoad(ElementAccessFeedback const & feedback) const1013 base::Optional<ElementAccessInfo> AccessInfoFactory::ConsolidateElementLoad(
1014 ElementAccessFeedback const& feedback) const {
1015 if (feedback.transition_groups().empty()) return {};
1016
1017 DCHECK(!feedback.transition_groups().front().empty());
1018 Handle<Map> first_map = feedback.transition_groups().front().front();
1019 base::Optional<MapRef> first_map_ref = TryMakeRef(broker(), first_map);
1020 if (!first_map_ref.has_value()) return {};
1021 InstanceType instance_type = first_map_ref->instance_type();
1022 ElementsKind elements_kind = first_map_ref->elements_kind();
1023
1024 ZoneVector<MapRef> maps(zone());
1025 for (auto const& group : feedback.transition_groups()) {
1026 for (Handle<Map> map_handle : group) {
1027 base::Optional<MapRef> map = TryMakeRef(broker(), map_handle);
1028 if (!map.has_value()) return {};
1029 if (map->instance_type() != instance_type ||
1030 !map->CanInlineElementAccess()) {
1031 return {};
1032 }
1033 if (!GeneralizeElementsKind(elements_kind, map->elements_kind())
1034 .To(&elements_kind)) {
1035 return {};
1036 }
1037 maps.push_back(map.value());
1038 }
1039 }
1040
1041 return ElementAccessInfo(std::move(maps), elements_kind, zone());
1042 }
1043
LookupSpecialFieldAccessor(MapRef map,NameRef name) const1044 PropertyAccessInfo AccessInfoFactory::LookupSpecialFieldAccessor(
1045 MapRef map, NameRef name) const {
1046 // Check for String::length field accessor.
1047 if (map.object()->IsStringMap()) {
1048 if (Name::Equals(isolate(), name.object(),
1049 isolate()->factory()->length_string())) {
1050 return PropertyAccessInfo::StringLength(zone(), map);
1051 }
1052 return Invalid();
1053 }
1054 // Check for special JSObject field accessors.
1055 FieldIndex field_index;
1056 if (Accessors::IsJSObjectFieldAccessor(isolate(), map.object(), name.object(),
1057 &field_index)) {
1058 Type field_type = Type::NonInternal();
1059 Representation field_representation = Representation::Tagged();
1060 if (map.object()->IsJSArrayMap()) {
1061 DCHECK(Name::Equals(isolate(), isolate()->factory()->length_string(),
1062 name.object()));
1063 // The JSArray::length property is a smi in the range
1064 // [0, FixedDoubleArray::kMaxLength] in case of fast double
1065 // elements, a smi in the range [0, FixedArray::kMaxLength]
1066 // in case of other fast elements, and [0, kMaxUInt32] in
1067 // case of other arrays.
1068 if (IsDoubleElementsKind(map.elements_kind())) {
1069 field_type = type_cache_->kFixedDoubleArrayLengthType;
1070 field_representation = Representation::Smi();
1071 } else if (IsFastElementsKind(map.elements_kind())) {
1072 field_type = type_cache_->kFixedArrayLengthType;
1073 field_representation = Representation::Smi();
1074 } else {
1075 field_type = type_cache_->kJSArrayLengthType;
1076 }
1077 }
1078 // Special fields are always mutable.
1079 return PropertyAccessInfo::DataField(zone(), map, {{}, zone()}, field_index,
1080 field_representation, field_type, map,
1081 {}, {}, {});
1082 }
1083 return Invalid();
1084 }
1085
LookupTransition(MapRef map,NameRef name,base::Optional<JSObjectRef> holder,PropertyAttributes attrs) const1086 PropertyAccessInfo AccessInfoFactory::LookupTransition(
1087 MapRef map, NameRef name, base::Optional<JSObjectRef> holder,
1088 PropertyAttributes attrs) const {
1089 // Check if the {map} has a data transition with the given {name}.
1090 Map transition =
1091 TransitionsAccessor(isolate(), *map.object(), true)
1092 .SearchTransition(*name.object(), PropertyKind::kData, attrs);
1093 if (transition.is_null()) return Invalid();
1094 base::Optional<MapRef> maybe_transition_map =
1095 TryMakeRef(broker(), transition);
1096 if (!maybe_transition_map.has_value()) return Invalid();
1097 MapRef transition_map = maybe_transition_map.value();
1098
1099 InternalIndex const number = transition_map.object()->LastAdded();
1100 Handle<DescriptorArray> descriptors =
1101 transition_map.instance_descriptors().object();
1102 PropertyDetails const details = descriptors->GetDetails(number);
1103
1104 // Don't bother optimizing stores to read-only properties.
1105 if (details.IsReadOnly()) return Invalid();
1106
1107 // TODO(bmeurer): Handle transition to data constant?
1108 if (details.location() != PropertyLocation::kField) return Invalid();
1109
1110 int const index = details.field_index();
1111 Representation details_representation = details.representation();
1112 if (details_representation.IsNone()) return Invalid();
1113
1114 FieldIndex field_index = FieldIndex::ForPropertyIndex(
1115 *transition_map.object(), index, details_representation);
1116 Type field_type = Type::NonInternal();
1117 base::Optional<MapRef> field_map;
1118
1119 ZoneVector<CompilationDependency const*> unrecorded_dependencies(zone());
1120 if (details_representation.IsSmi()) {
1121 field_type = Type::SignedSmall();
1122 unrecorded_dependencies.push_back(
1123 dependencies()->FieldRepresentationDependencyOffTheRecord(
1124 transition_map, number, details_representation));
1125 } else if (details_representation.IsDouble()) {
1126 field_type = type_cache_->kFloat64;
1127 unrecorded_dependencies.push_back(
1128 dependencies()->FieldRepresentationDependencyOffTheRecord(
1129 transition_map, number, details_representation));
1130 } else if (details_representation.IsHeapObject()) {
1131 // Extract the field type from the property details (make sure its
1132 // representation is TaggedPointer to reflect the heap object case).
1133 // TODO(jgruber,v8:7790): Use DescriptorArrayRef instead.
1134 Handle<FieldType> descriptors_field_type =
1135 broker()->CanonicalPersistentHandle(descriptors->GetFieldType(number));
1136 base::Optional<ObjectRef> descriptors_field_type_ref =
1137 TryMakeRef<Object>(broker(), descriptors_field_type);
1138 if (!descriptors_field_type_ref.has_value()) return Invalid();
1139
1140 if (descriptors_field_type->IsNone()) {
1141 // Store is not safe if the field type was cleared.
1142 return Invalid();
1143 }
1144 unrecorded_dependencies.push_back(
1145 dependencies()->FieldRepresentationDependencyOffTheRecord(
1146 transition_map, number, details_representation));
1147 if (descriptors_field_type->IsClass()) {
1148 unrecorded_dependencies.push_back(
1149 dependencies()->FieldTypeDependencyOffTheRecord(
1150 transition_map, number, *descriptors_field_type_ref));
1151 // Remember the field map, and try to infer a useful type.
1152 base::Optional<MapRef> maybe_field_map =
1153 TryMakeRef(broker(), descriptors_field_type->AsClass());
1154 if (!maybe_field_map.has_value()) return Invalid();
1155 field_type = Type::For(maybe_field_map.value());
1156 field_map = maybe_field_map;
1157 }
1158 }
1159
1160 unrecorded_dependencies.push_back(
1161 dependencies()->TransitionDependencyOffTheRecord(transition_map));
1162 // Transitioning stores *may* store to const fields. The resulting
1163 // DataConstant access infos can be distinguished from later, i.e. redundant,
1164 // stores to the same constant field by the presence of a transition map.
1165 switch (dependencies()->DependOnFieldConstness(transition_map, number)) {
1166 case PropertyConstness::kMutable:
1167 return PropertyAccessInfo::DataField(
1168 zone(), map, std::move(unrecorded_dependencies), field_index,
1169 details_representation, field_type, transition_map, field_map, holder,
1170 transition_map);
1171 case PropertyConstness::kConst:
1172 return PropertyAccessInfo::FastDataConstant(
1173 zone(), map, std::move(unrecorded_dependencies), field_index,
1174 details_representation, field_type, transition_map, field_map, holder,
1175 transition_map);
1176 }
1177 UNREACHABLE();
1178 }
1179
1180 } // namespace compiler
1181 } // namespace internal
1182 } // namespace v8
1183