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/compiler/access-info.h"
8
9 #include "src/builtins/accessors.h"
10 #include "src/compiler/compilation-dependencies.h"
11 #include "src/compiler/compilation-dependency.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/logging/counters.h"
16 #include "src/objects/cell-inl.h"
17 #include "src/objects/field-index-inl.h"
18 #include "src/objects/field-type.h"
19 #include "src/objects/module-inl.h"
20 #include "src/objects/objects-inl.h"
21 #include "src/objects/struct-inl.h"
22 #include "src/objects/templates.h"
23
24 namespace v8 {
25 namespace internal {
26 namespace compiler {
27
28 namespace {
29
CanInlinePropertyAccess(Handle<Map> map)30 bool CanInlinePropertyAccess(Handle<Map> map) {
31 // We can inline property access to prototypes of all primitives, except
32 // the special Oddball ones that have no wrapper counterparts (i.e. Null,
33 // Undefined and TheHole).
34 STATIC_ASSERT(ODDBALL_TYPE == LAST_PRIMITIVE_HEAP_OBJECT_TYPE);
35 if (map->IsBooleanMap()) return true;
36 if (map->instance_type() < LAST_PRIMITIVE_HEAP_OBJECT_TYPE) return true;
37 return map->IsJSObjectMap() && !map->is_dictionary_map() &&
38 !map->has_named_interceptor() &&
39 // TODO(verwaest): Allowlist contexts to which we have access.
40 !map->is_access_check_needed();
41 }
42
43 #ifdef DEBUG
HasFieldRepresentationDependenciesOnMap(ZoneVector<CompilationDependency const * > & dependencies,Handle<Map> const & field_owner_map)44 bool HasFieldRepresentationDependenciesOnMap(
45 ZoneVector<CompilationDependency const*>& dependencies,
46 Handle<Map> const& field_owner_map) {
47 for (auto dep : dependencies) {
48 if (dep->IsFieldRepresentationDependencyOnMap(field_owner_map)) {
49 return true;
50 }
51 }
52 return false;
53 }
54 #endif
55
56 } // namespace
57
58
operator <<(std::ostream & os,AccessMode access_mode)59 std::ostream& operator<<(std::ostream& os, AccessMode access_mode) {
60 switch (access_mode) {
61 case AccessMode::kLoad:
62 return os << "Load";
63 case AccessMode::kStore:
64 return os << "Store";
65 case AccessMode::kStoreInLiteral:
66 return os << "StoreInLiteral";
67 case AccessMode::kHas:
68 return os << "Has";
69 }
70 UNREACHABLE();
71 }
72
ElementAccessInfo(ZoneVector<Handle<Map>> && lookup_start_object_maps,ElementsKind elements_kind,Zone * zone)73 ElementAccessInfo::ElementAccessInfo(
74 ZoneVector<Handle<Map>>&& lookup_start_object_maps,
75 ElementsKind elements_kind, Zone* zone)
76 : elements_kind_(elements_kind),
77 lookup_start_object_maps_(lookup_start_object_maps),
78 transition_sources_(zone) {
79 CHECK(!lookup_start_object_maps.empty());
80 }
81
82 // static
Invalid(Zone * zone)83 PropertyAccessInfo PropertyAccessInfo::Invalid(Zone* zone) {
84 return PropertyAccessInfo(zone);
85 }
86
87 // static
NotFound(Zone * zone,Handle<Map> receiver_map,MaybeHandle<JSObject> holder)88 PropertyAccessInfo PropertyAccessInfo::NotFound(Zone* zone,
89 Handle<Map> receiver_map,
90 MaybeHandle<JSObject> holder) {
91 return PropertyAccessInfo(zone, kNotFound, holder, {{receiver_map}, zone});
92 }
93
94 // static
DataField(Zone * zone,Handle<Map> receiver_map,ZoneVector<CompilationDependency const * > && dependencies,FieldIndex field_index,Representation field_representation,Type field_type,Handle<Map> field_owner_map,MaybeHandle<Map> field_map,MaybeHandle<JSObject> holder,MaybeHandle<Map> transition_map)95 PropertyAccessInfo PropertyAccessInfo::DataField(
96 Zone* zone, Handle<Map> receiver_map,
97 ZoneVector<CompilationDependency const*>&& dependencies,
98 FieldIndex field_index, Representation field_representation,
99 Type field_type, Handle<Map> field_owner_map, MaybeHandle<Map> field_map,
100 MaybeHandle<JSObject> holder, MaybeHandle<Map> transition_map) {
101 DCHECK_IMPLIES(
102 field_representation.IsDouble(),
103 HasFieldRepresentationDependenciesOnMap(dependencies, field_owner_map));
104 return PropertyAccessInfo(kDataField, holder, transition_map, field_index,
105 field_representation, field_type, field_owner_map,
106 field_map, {{receiver_map}, zone},
107 std::move(dependencies));
108 }
109
110 // static
DataConstant(Zone * zone,Handle<Map> receiver_map,ZoneVector<CompilationDependency const * > && dependencies,FieldIndex field_index,Representation field_representation,Type field_type,Handle<Map> field_owner_map,MaybeHandle<Map> field_map,MaybeHandle<JSObject> holder,MaybeHandle<Map> transition_map)111 PropertyAccessInfo PropertyAccessInfo::DataConstant(
112 Zone* zone, Handle<Map> receiver_map,
113 ZoneVector<CompilationDependency const*>&& dependencies,
114 FieldIndex field_index, Representation field_representation,
115 Type field_type, Handle<Map> field_owner_map, MaybeHandle<Map> field_map,
116 MaybeHandle<JSObject> holder, MaybeHandle<Map> transition_map) {
117 return PropertyAccessInfo(kDataConstant, holder, transition_map, field_index,
118 field_representation, field_type, field_owner_map,
119 field_map, {{receiver_map}, zone},
120 std::move(dependencies));
121 }
122
123 // static
AccessorConstant(Zone * zone,Handle<Map> receiver_map,Handle<Object> constant,MaybeHandle<JSObject> holder)124 PropertyAccessInfo PropertyAccessInfo::AccessorConstant(
125 Zone* zone, Handle<Map> receiver_map, Handle<Object> constant,
126 MaybeHandle<JSObject> holder) {
127 return PropertyAccessInfo(zone, kAccessorConstant, holder, constant,
128 {{receiver_map}, zone});
129 }
130
131 // static
ModuleExport(Zone * zone,Handle<Map> receiver_map,Handle<Cell> cell)132 PropertyAccessInfo PropertyAccessInfo::ModuleExport(Zone* zone,
133 Handle<Map> receiver_map,
134 Handle<Cell> cell) {
135 return PropertyAccessInfo(zone, kModuleExport, MaybeHandle<JSObject>(), cell,
136 {{receiver_map}, zone});
137 }
138
139 // static
StringLength(Zone * zone,Handle<Map> receiver_map)140 PropertyAccessInfo PropertyAccessInfo::StringLength(Zone* zone,
141 Handle<Map> receiver_map) {
142 return PropertyAccessInfo(zone, kStringLength, MaybeHandle<JSObject>(),
143 {{receiver_map}, zone});
144 }
145
146 // static
DataField(int offset,bool is_inobject,Representation field_representation,Type field_type)147 MinimorphicLoadPropertyAccessInfo MinimorphicLoadPropertyAccessInfo::DataField(
148 int offset, bool is_inobject, Representation field_representation,
149 Type field_type) {
150 return MinimorphicLoadPropertyAccessInfo(kDataField, offset, is_inobject,
151 field_representation, field_type);
152 }
153
154 // static
Invalid()155 MinimorphicLoadPropertyAccessInfo MinimorphicLoadPropertyAccessInfo::Invalid() {
156 return MinimorphicLoadPropertyAccessInfo(
157 kInvalid, -1, false, Representation::None(), Type::None());
158 }
159
PropertyAccessInfo(Zone * zone)160 PropertyAccessInfo::PropertyAccessInfo(Zone* zone)
161 : kind_(kInvalid),
162 lookup_start_object_maps_(zone),
163 unrecorded_dependencies_(zone),
164 field_representation_(Representation::None()),
165 field_type_(Type::None()) {}
166
PropertyAccessInfo(Zone * zone,Kind kind,MaybeHandle<JSObject> holder,ZoneVector<Handle<Map>> && lookup_start_object_maps)167 PropertyAccessInfo::PropertyAccessInfo(
168 Zone* zone, Kind kind, MaybeHandle<JSObject> holder,
169 ZoneVector<Handle<Map>>&& lookup_start_object_maps)
170 : kind_(kind),
171 lookup_start_object_maps_(lookup_start_object_maps),
172 unrecorded_dependencies_(zone),
173 holder_(holder),
174 field_representation_(Representation::None()),
175 field_type_(Type::None()) {}
176
PropertyAccessInfo(Zone * zone,Kind kind,MaybeHandle<JSObject> holder,Handle<Object> constant,ZoneVector<Handle<Map>> && lookup_start_object_maps)177 PropertyAccessInfo::PropertyAccessInfo(
178 Zone* zone, Kind kind, MaybeHandle<JSObject> holder,
179 Handle<Object> constant, ZoneVector<Handle<Map>>&& lookup_start_object_maps)
180 : kind_(kind),
181 lookup_start_object_maps_(lookup_start_object_maps),
182 unrecorded_dependencies_(zone),
183 constant_(constant),
184 holder_(holder),
185 field_representation_(Representation::None()),
186 field_type_(Type::Any()) {}
187
PropertyAccessInfo(Kind kind,MaybeHandle<JSObject> holder,MaybeHandle<Map> transition_map,FieldIndex field_index,Representation field_representation,Type field_type,Handle<Map> field_owner_map,MaybeHandle<Map> field_map,ZoneVector<Handle<Map>> && lookup_start_object_maps,ZoneVector<CompilationDependency const * > && unrecorded_dependencies)188 PropertyAccessInfo::PropertyAccessInfo(
189 Kind kind, MaybeHandle<JSObject> holder, MaybeHandle<Map> transition_map,
190 FieldIndex field_index, Representation field_representation,
191 Type field_type, Handle<Map> field_owner_map, MaybeHandle<Map> field_map,
192 ZoneVector<Handle<Map>>&& lookup_start_object_maps,
193 ZoneVector<CompilationDependency const*>&& unrecorded_dependencies)
194 : kind_(kind),
195 lookup_start_object_maps_(lookup_start_object_maps),
196 unrecorded_dependencies_(std::move(unrecorded_dependencies)),
197 transition_map_(transition_map),
198 holder_(holder),
199 field_index_(field_index),
200 field_representation_(field_representation),
201 field_type_(field_type),
202 field_owner_map_(field_owner_map),
203 field_map_(field_map) {
204 DCHECK_IMPLIES(!transition_map.is_null(),
205 field_owner_map.address() == transition_map.address());
206 }
207
MinimorphicLoadPropertyAccessInfo(Kind kind,int offset,bool is_inobject,Representation field_representation,Type field_type)208 MinimorphicLoadPropertyAccessInfo::MinimorphicLoadPropertyAccessInfo(
209 Kind kind, int offset, bool is_inobject,
210 Representation field_representation, Type field_type)
211 : kind_(kind),
212 is_inobject_(is_inobject),
213 offset_(offset),
214 field_representation_(field_representation),
215 field_type_(field_type) {}
216
Merge(PropertyAccessInfo const * that,AccessMode access_mode,Zone * zone)217 bool PropertyAccessInfo::Merge(PropertyAccessInfo const* that,
218 AccessMode access_mode, Zone* zone) {
219 if (this->kind_ != that->kind_) return false;
220 if (this->holder_.address() != that->holder_.address()) return false;
221
222 switch (this->kind_) {
223 case kInvalid:
224 return that->kind_ == kInvalid;
225
226 case kDataField:
227 case kDataConstant: {
228 // Check if we actually access the same field (we use the
229 // GetFieldAccessStubKey method here just like the ICs do
230 // since that way we only compare the relevant bits of the
231 // field indices).
232 if (this->field_index_.GetFieldAccessStubKey() ==
233 that->field_index_.GetFieldAccessStubKey()) {
234 switch (access_mode) {
235 case AccessMode::kHas:
236 case AccessMode::kLoad: {
237 if (!this->field_representation_.Equals(
238 that->field_representation_)) {
239 if (this->field_representation_.IsDouble() ||
240 that->field_representation_.IsDouble()) {
241 return false;
242 }
243 this->field_representation_ = Representation::Tagged();
244 }
245 if (this->field_map_.address() != that->field_map_.address()) {
246 this->field_map_ = MaybeHandle<Map>();
247 }
248 break;
249 }
250 case AccessMode::kStore:
251 case AccessMode::kStoreInLiteral: {
252 // For stores, the field map and field representation information
253 // must match exactly, otherwise we cannot merge the stores. We
254 // also need to make sure that in case of transitioning stores,
255 // the transition targets match.
256 if (this->field_map_.address() != that->field_map_.address() ||
257 !this->field_representation_.Equals(
258 that->field_representation_) ||
259 this->transition_map_.address() !=
260 that->transition_map_.address()) {
261 return false;
262 }
263 break;
264 }
265 }
266 this->field_type_ =
267 Type::Union(this->field_type_, that->field_type_, zone);
268 this->lookup_start_object_maps_.insert(
269 this->lookup_start_object_maps_.end(),
270 that->lookup_start_object_maps_.begin(),
271 that->lookup_start_object_maps_.end());
272 this->unrecorded_dependencies_.insert(
273 this->unrecorded_dependencies_.end(),
274 that->unrecorded_dependencies_.begin(),
275 that->unrecorded_dependencies_.end());
276 return true;
277 }
278 return false;
279 }
280
281 case kAccessorConstant: {
282 // Check if we actually access the same constant.
283 if (this->constant_.address() == that->constant_.address()) {
284 DCHECK(this->unrecorded_dependencies_.empty());
285 DCHECK(that->unrecorded_dependencies_.empty());
286 this->lookup_start_object_maps_.insert(
287 this->lookup_start_object_maps_.end(),
288 that->lookup_start_object_maps_.begin(),
289 that->lookup_start_object_maps_.end());
290 return true;
291 }
292 return false;
293 }
294
295 case kNotFound:
296 case kStringLength: {
297 DCHECK(this->unrecorded_dependencies_.empty());
298 DCHECK(that->unrecorded_dependencies_.empty());
299 this->lookup_start_object_maps_.insert(
300 this->lookup_start_object_maps_.end(),
301 that->lookup_start_object_maps_.begin(),
302 that->lookup_start_object_maps_.end());
303 return true;
304 }
305 case kModuleExport:
306 return false;
307 }
308 }
309
GetConstFieldInfo() const310 ConstFieldInfo PropertyAccessInfo::GetConstFieldInfo() const {
311 if (IsDataConstant()) {
312 return ConstFieldInfo(field_owner_map_.ToHandleChecked());
313 }
314 return ConstFieldInfo::None();
315 }
316
AccessInfoFactory(JSHeapBroker * broker,CompilationDependencies * dependencies,Zone * zone)317 AccessInfoFactory::AccessInfoFactory(JSHeapBroker* broker,
318 CompilationDependencies* dependencies,
319 Zone* zone)
320 : broker_(broker),
321 dependencies_(dependencies),
322 type_cache_(TypeCache::Get()),
323 zone_(zone) {}
324
ComputeElementAccessInfo(Handle<Map> map,AccessMode access_mode) const325 base::Optional<ElementAccessInfo> AccessInfoFactory::ComputeElementAccessInfo(
326 Handle<Map> map, AccessMode access_mode) const {
327 // Check if it is safe to inline element access for the {map}.
328 MapRef map_ref(broker(), map);
329 if (!CanInlineElementAccess(map_ref)) return base::nullopt;
330 ElementsKind const elements_kind = map_ref.elements_kind();
331 return ElementAccessInfo({{map}, zone()}, elements_kind, zone());
332 }
333
ComputeElementAccessInfos(ElementAccessFeedback const & feedback,ZoneVector<ElementAccessInfo> * access_infos) const334 bool AccessInfoFactory::ComputeElementAccessInfos(
335 ElementAccessFeedback const& feedback,
336 ZoneVector<ElementAccessInfo>* access_infos) const {
337 AccessMode access_mode = feedback.keyed_mode().access_mode();
338 if (access_mode == AccessMode::kLoad || access_mode == AccessMode::kHas) {
339 // For polymorphic loads of similar elements kinds (i.e. all tagged or all
340 // double), always use the "worst case" code without a transition. This is
341 // much faster than transitioning the elements to the worst case, trading a
342 // TransitionElementsKind for a CheckMaps, avoiding mutation of the array.
343 base::Optional<ElementAccessInfo> access_info =
344 ConsolidateElementLoad(feedback);
345 if (access_info.has_value()) {
346 access_infos->push_back(*access_info);
347 return true;
348 }
349 }
350
351 for (auto const& group : feedback.transition_groups()) {
352 DCHECK(!group.empty());
353 Handle<Map> target = group.front();
354 base::Optional<ElementAccessInfo> access_info =
355 ComputeElementAccessInfo(target, access_mode);
356 if (!access_info.has_value()) return false;
357
358 for (size_t i = 1; i < group.size(); ++i) {
359 access_info->AddTransitionSource(group[i]);
360 }
361 access_infos->push_back(*access_info);
362 }
363 return true;
364 }
365
ComputeDataFieldAccessInfo(Handle<Map> receiver_map,Handle<Map> map,MaybeHandle<JSObject> holder,InternalIndex descriptor,AccessMode access_mode) const366 PropertyAccessInfo AccessInfoFactory::ComputeDataFieldAccessInfo(
367 Handle<Map> receiver_map, Handle<Map> map, MaybeHandle<JSObject> holder,
368 InternalIndex descriptor, AccessMode access_mode) const {
369 DCHECK(descriptor.is_found());
370 Handle<DescriptorArray> descriptors(map->instance_descriptors(kRelaxedLoad),
371 isolate());
372 PropertyDetails const details = descriptors->GetDetails(descriptor);
373 int index = descriptors->GetFieldIndex(descriptor);
374 Representation details_representation = details.representation();
375 if (details_representation.IsNone()) {
376 // The ICs collect feedback in PREMONOMORPHIC state already,
377 // but at this point the {receiver_map} might still contain
378 // fields for which the representation has not yet been
379 // determined by the runtime. So we need to catch this case
380 // here and fall back to use the regular IC logic instead.
381 return PropertyAccessInfo::Invalid(zone());
382 }
383 FieldIndex field_index =
384 FieldIndex::ForPropertyIndex(*map, index, details_representation);
385 Type field_type = Type::NonInternal();
386 MaybeHandle<Map> field_map;
387 MapRef map_ref(broker(), map);
388 ZoneVector<CompilationDependency const*> unrecorded_dependencies(zone());
389 map_ref.SerializeOwnDescriptor(descriptor);
390 if (details_representation.IsSmi()) {
391 field_type = Type::SignedSmall();
392 unrecorded_dependencies.push_back(
393 dependencies()->FieldRepresentationDependencyOffTheRecord(map_ref,
394 descriptor));
395 } else if (details_representation.IsDouble()) {
396 field_type = type_cache_->kFloat64;
397 if (!FLAG_unbox_double_fields) {
398 unrecorded_dependencies.push_back(
399 dependencies()->FieldRepresentationDependencyOffTheRecord(
400 map_ref, descriptor));
401 }
402 } else if (details_representation.IsHeapObject()) {
403 // Extract the field type from the property details (make sure its
404 // representation is TaggedPointer to reflect the heap object case).
405 Handle<FieldType> descriptors_field_type(
406 descriptors->GetFieldType(descriptor), isolate());
407 if (descriptors_field_type->IsNone()) {
408 // Store is not safe if the field type was cleared.
409 if (access_mode == AccessMode::kStore) {
410 return PropertyAccessInfo::Invalid(zone());
411 }
412
413 // The field type was cleared by the GC, so we don't know anything
414 // about the contents now.
415 }
416 unrecorded_dependencies.push_back(
417 dependencies()->FieldRepresentationDependencyOffTheRecord(map_ref,
418 descriptor));
419 if (descriptors_field_type->IsClass()) {
420 // Remember the field map, and try to infer a useful type.
421 Handle<Map> map(descriptors_field_type->AsClass(), isolate());
422 field_type = Type::For(MapRef(broker(), map));
423 field_map = MaybeHandle<Map>(map);
424 }
425 } else {
426 CHECK(details_representation.IsTagged());
427 }
428 // TODO(turbofan): We may want to do this only depending on the use
429 // of the access info.
430 unrecorded_dependencies.push_back(
431 dependencies()->FieldTypeDependencyOffTheRecord(map_ref, descriptor));
432
433 PropertyConstness constness;
434 if (details.IsReadOnly() && !details.IsConfigurable()) {
435 constness = PropertyConstness::kConst;
436 } else if (broker()->is_turboprop() && !map->is_prototype_map()) {
437 // The constness feedback is too unstable for the aggresive compilation
438 // of turboprop.
439 constness = PropertyConstness::kMutable;
440 } else {
441 map_ref.SerializeOwnDescriptor(descriptor);
442 constness = dependencies()->DependOnFieldConstness(map_ref, descriptor);
443 }
444 Handle<Map> field_owner_map(map->FindFieldOwner(isolate(), descriptor),
445 isolate());
446 switch (constness) {
447 case PropertyConstness::kMutable:
448 return PropertyAccessInfo::DataField(
449 zone(), receiver_map, std::move(unrecorded_dependencies), field_index,
450 details_representation, field_type, field_owner_map, field_map,
451 holder);
452 case PropertyConstness::kConst:
453 return PropertyAccessInfo::DataConstant(
454 zone(), receiver_map, std::move(unrecorded_dependencies), field_index,
455 details_representation, field_type, field_owner_map, field_map,
456 holder);
457 }
458 UNREACHABLE();
459 }
460
ComputeAccessorDescriptorAccessInfo(Handle<Map> receiver_map,Handle<Name> name,Handle<Map> map,MaybeHandle<JSObject> holder,InternalIndex descriptor,AccessMode access_mode) const461 PropertyAccessInfo AccessInfoFactory::ComputeAccessorDescriptorAccessInfo(
462 Handle<Map> receiver_map, Handle<Name> name, Handle<Map> map,
463 MaybeHandle<JSObject> holder, InternalIndex descriptor,
464 AccessMode access_mode) const {
465 DCHECK(descriptor.is_found());
466 Handle<DescriptorArray> descriptors(map->instance_descriptors(kRelaxedLoad),
467 isolate());
468 SLOW_DCHECK(descriptor == descriptors->Search(*name, *map));
469 if (map->instance_type() == JS_MODULE_NAMESPACE_TYPE) {
470 DCHECK(map->is_prototype_map());
471 Handle<PrototypeInfo> proto_info(PrototypeInfo::cast(map->prototype_info()),
472 isolate());
473 Handle<JSModuleNamespace> module_namespace(
474 JSModuleNamespace::cast(proto_info->module_namespace()), isolate());
475 Handle<Cell> cell(Cell::cast(module_namespace->module().exports().Lookup(
476 isolate(), name, Smi::ToInt(name->GetHash()))),
477 isolate());
478 if (cell->value().IsTheHole(isolate())) {
479 // This module has not been fully initialized yet.
480 return PropertyAccessInfo::Invalid(zone());
481 }
482 return PropertyAccessInfo::ModuleExport(zone(), receiver_map, cell);
483 }
484 if (access_mode == AccessMode::kHas) {
485 // HasProperty checks don't call getter/setters, existence is sufficient.
486 return PropertyAccessInfo::AccessorConstant(zone(), receiver_map,
487 Handle<Object>(), holder);
488 }
489 Handle<Object> accessors(descriptors->GetStrongValue(descriptor), isolate());
490 if (!accessors->IsAccessorPair()) {
491 return PropertyAccessInfo::Invalid(zone());
492 }
493 Handle<Object> accessor(access_mode == AccessMode::kLoad
494 ? Handle<AccessorPair>::cast(accessors)->getter()
495 : Handle<AccessorPair>::cast(accessors)->setter(),
496 isolate());
497 if (!accessor->IsJSFunction()) {
498 CallOptimization optimization(isolate(), accessor);
499 if (!optimization.is_simple_api_call() ||
500 optimization.IsCrossContextLazyAccessorPair(
501 *broker()->target_native_context().object(), *map)) {
502 return PropertyAccessInfo::Invalid(zone());
503 }
504
505 CallOptimization::HolderLookup lookup;
506 holder = optimization.LookupHolderOfExpectedType(receiver_map, &lookup);
507 if (lookup == CallOptimization::kHolderNotFound) {
508 return PropertyAccessInfo::Invalid(zone());
509 }
510 DCHECK_IMPLIES(lookup == CallOptimization::kHolderIsReceiver,
511 holder.is_null());
512 DCHECK_IMPLIES(lookup == CallOptimization::kHolderFound, !holder.is_null());
513 }
514 if (access_mode == AccessMode::kLoad) {
515 Handle<Name> cached_property_name;
516 if (FunctionTemplateInfo::TryGetCachedPropertyName(isolate(), accessor)
517 .ToHandle(&cached_property_name)) {
518 PropertyAccessInfo access_info =
519 ComputePropertyAccessInfo(map, cached_property_name, access_mode);
520 if (!access_info.IsInvalid()) return access_info;
521 }
522 }
523 return PropertyAccessInfo::AccessorConstant(zone(), receiver_map, accessor,
524 holder);
525 }
526
ComputePropertyAccessInfo(MinimorphicLoadPropertyAccessFeedback const & feedback) const527 MinimorphicLoadPropertyAccessInfo AccessInfoFactory::ComputePropertyAccessInfo(
528 MinimorphicLoadPropertyAccessFeedback const& feedback) const {
529 DCHECK(feedback.handler()->IsSmi());
530 int handler = Smi::cast(*feedback.handler()).value();
531 bool is_inobject = LoadHandler::IsInobjectBits::decode(handler);
532 bool is_double = LoadHandler::IsDoubleBits::decode(handler);
533 int offset = LoadHandler::FieldIndexBits::decode(handler) * kTaggedSize;
534 Representation field_rep =
535 is_double ? Representation::Double() : Representation::Tagged();
536 Type field_type = is_double ? Type::Number() : Type::Any();
537 return MinimorphicLoadPropertyAccessInfo::DataField(offset, is_inobject,
538 field_rep, field_type);
539 }
540
ComputePropertyAccessInfo(Handle<Map> map,Handle<Name> name,AccessMode access_mode) const541 PropertyAccessInfo AccessInfoFactory::ComputePropertyAccessInfo(
542 Handle<Map> map, Handle<Name> name, AccessMode access_mode) const {
543 CHECK(name->IsUniqueName());
544
545 if (access_mode == AccessMode::kHas && !map->IsJSReceiverMap()) {
546 return PropertyAccessInfo::Invalid(zone());
547 }
548
549 // Check if it is safe to inline property access for the {map}.
550 if (!CanInlinePropertyAccess(map)) {
551 return PropertyAccessInfo::Invalid(zone());
552 }
553
554 // We support fast inline cases for certain JSObject getters.
555 if (access_mode == AccessMode::kLoad || access_mode == AccessMode::kHas) {
556 PropertyAccessInfo access_info = LookupSpecialFieldAccessor(map, name);
557 if (!access_info.IsInvalid()) return access_info;
558 }
559
560 // Remember the receiver map. We use {map} as loop variable.
561 Handle<Map> receiver_map = map;
562 MaybeHandle<JSObject> holder;
563 while (true) {
564 // Lookup the named property on the {map}.
565 Handle<DescriptorArray> descriptors(map->instance_descriptors(kAcquireLoad),
566 isolate());
567 InternalIndex const number =
568 descriptors->Search(*name, *map, broker()->is_concurrent_inlining());
569 if (number.is_found()) {
570 PropertyDetails const details = descriptors->GetDetails(number);
571 if (access_mode == AccessMode::kStore ||
572 access_mode == AccessMode::kStoreInLiteral) {
573 // Don't bother optimizing stores to read-only properties.
574 if (details.IsReadOnly()) {
575 return PropertyAccessInfo::Invalid(zone());
576 }
577 if (details.kind() == kData && !holder.is_null()) {
578 // This is a store to a property not found on the receiver but on a
579 // prototype. According to ES6 section 9.1.9 [[Set]], we need to
580 // create a new data property on the receiver. We can still optimize
581 // if such a transition already exists.
582 return LookupTransition(receiver_map, name, holder);
583 }
584 }
585 if (details.location() == kField) {
586 if (details.kind() == kData) {
587 return ComputeDataFieldAccessInfo(receiver_map, map, holder, number,
588 access_mode);
589 } else {
590 DCHECK_EQ(kAccessor, details.kind());
591 // TODO(turbofan): Add support for general accessors?
592 return PropertyAccessInfo::Invalid(zone());
593 }
594 } else {
595 DCHECK_EQ(kDescriptor, details.location());
596 DCHECK_EQ(kAccessor, details.kind());
597 return ComputeAccessorDescriptorAccessInfo(receiver_map, name, map,
598 holder, number, access_mode);
599 }
600 UNREACHABLE();
601 }
602
603 // The property wasn't found on {map}. Look on the prototype if appropriate.
604
605 // Don't search on the prototype chain for special indices in case of
606 // integer indexed exotic objects (see ES6 section 9.4.5).
607 if (map->IsJSTypedArrayMap() && name->IsString() &&
608 IsSpecialIndex(String::cast(*name))) {
609 return PropertyAccessInfo::Invalid(zone());
610 }
611
612 // Don't search on the prototype when storing in literals.
613 if (access_mode == AccessMode::kStoreInLiteral) {
614 return LookupTransition(receiver_map, name, holder);
615 }
616
617 // Don't lookup private symbols on the prototype chain.
618 if (name->IsPrivate()) {
619 return PropertyAccessInfo::Invalid(zone());
620 }
621
622 // Walk up the prototype chain.
623 MapRef(broker(), map).SerializePrototype();
624 // Acquire synchronously the map's prototype's map to guarantee that every
625 // time we use it, we use the same Map.
626 Handle<Map> map_prototype_map(map->prototype().synchronized_map(),
627 isolate());
628 if (!map_prototype_map->IsJSObjectMap()) {
629 // Perform the implicit ToObject for primitives here.
630 // Implemented according to ES6 section 7.3.2 GetV (V, P).
631 Handle<JSFunction> constructor;
632 if (Map::GetConstructorFunction(
633 map, broker()->target_native_context().object())
634 .ToHandle(&constructor)) {
635 map = handle(constructor->initial_map(), isolate());
636 map_prototype_map =
637 handle(map->prototype().synchronized_map(), isolate());
638 DCHECK(map_prototype_map->IsJSObjectMap());
639 } else if (map->prototype().IsNull()) {
640 // Store to property not found on the receiver or any prototype, we need
641 // to transition to a new data property.
642 // Implemented according to ES6 section 9.1.9 [[Set]] (P, V, Receiver)
643 if (access_mode == AccessMode::kStore) {
644 return LookupTransition(receiver_map, name, holder);
645 }
646 // The property was not found (access returns undefined or throws
647 // depending on the language mode of the load operation.
648 // Implemented according to ES6 section 9.1.8 [[Get]] (P, Receiver)
649 return PropertyAccessInfo::NotFound(zone(), receiver_map, holder);
650 } else {
651 return PropertyAccessInfo::Invalid(zone());
652 }
653 }
654
655 holder = handle(JSObject::cast(map->prototype()), isolate());
656 map = map_prototype_map;
657 CHECK(!map->is_deprecated());
658
659 if (!CanInlinePropertyAccess(map)) {
660 return PropertyAccessInfo::Invalid(zone());
661 }
662
663 // Successful lookup on prototype chain needs to guarantee that all
664 // the prototypes up to the holder have stable maps. Let us make sure
665 // the prototype maps are stable here.
666 CHECK(map->is_stable());
667 }
668 UNREACHABLE();
669 }
670
FinalizePropertyAccessInfosAsOne(ZoneVector<PropertyAccessInfo> access_infos,AccessMode access_mode) const671 PropertyAccessInfo AccessInfoFactory::FinalizePropertyAccessInfosAsOne(
672 ZoneVector<PropertyAccessInfo> access_infos, AccessMode access_mode) const {
673 ZoneVector<PropertyAccessInfo> merged_access_infos(zone());
674 MergePropertyAccessInfos(access_infos, access_mode, &merged_access_infos);
675 if (merged_access_infos.size() == 1) {
676 PropertyAccessInfo& result = merged_access_infos.front();
677 if (!result.IsInvalid()) {
678 result.RecordDependencies(dependencies());
679 return result;
680 }
681 }
682 return PropertyAccessInfo::Invalid(zone());
683 }
684
ComputePropertyAccessInfos(MapHandles const & maps,Handle<Name> name,AccessMode access_mode,ZoneVector<PropertyAccessInfo> * access_infos) const685 void AccessInfoFactory::ComputePropertyAccessInfos(
686 MapHandles const& maps, Handle<Name> name, AccessMode access_mode,
687 ZoneVector<PropertyAccessInfo>* access_infos) const {
688 DCHECK(access_infos->empty());
689 for (Handle<Map> map : maps) {
690 access_infos->push_back(ComputePropertyAccessInfo(map, name, access_mode));
691 }
692 }
693
RecordDependencies(CompilationDependencies * dependencies)694 void PropertyAccessInfo::RecordDependencies(
695 CompilationDependencies* dependencies) {
696 for (CompilationDependency const* d : unrecorded_dependencies_) {
697 dependencies->RecordDependency(d);
698 }
699 unrecorded_dependencies_.clear();
700 }
701
FinalizePropertyAccessInfos(ZoneVector<PropertyAccessInfo> access_infos,AccessMode access_mode,ZoneVector<PropertyAccessInfo> * result) const702 bool AccessInfoFactory::FinalizePropertyAccessInfos(
703 ZoneVector<PropertyAccessInfo> access_infos, AccessMode access_mode,
704 ZoneVector<PropertyAccessInfo>* result) const {
705 if (access_infos.empty()) return false;
706 MergePropertyAccessInfos(access_infos, access_mode, result);
707 for (PropertyAccessInfo const& info : *result) {
708 if (info.IsInvalid()) return false;
709 }
710 for (PropertyAccessInfo& info : *result) {
711 info.RecordDependencies(dependencies());
712 }
713 return true;
714 }
715
MergePropertyAccessInfos(ZoneVector<PropertyAccessInfo> infos,AccessMode access_mode,ZoneVector<PropertyAccessInfo> * result) const716 void AccessInfoFactory::MergePropertyAccessInfos(
717 ZoneVector<PropertyAccessInfo> infos, AccessMode access_mode,
718 ZoneVector<PropertyAccessInfo>* result) const {
719 DCHECK(result->empty());
720 for (auto it = infos.begin(), end = infos.end(); it != end; ++it) {
721 bool merged = false;
722 for (auto ot = it + 1; ot != end; ++ot) {
723 if (ot->Merge(&(*it), access_mode, zone())) {
724 merged = true;
725 break;
726 }
727 }
728 if (!merged) result->push_back(*it);
729 }
730 CHECK(!result->empty());
731 }
732
isolate() const733 Isolate* AccessInfoFactory::isolate() const { return broker()->isolate(); }
734
735 namespace {
736
GeneralizeElementsKind(ElementsKind this_kind,ElementsKind that_kind)737 Maybe<ElementsKind> GeneralizeElementsKind(ElementsKind this_kind,
738 ElementsKind that_kind) {
739 if (IsHoleyElementsKind(this_kind)) {
740 that_kind = GetHoleyElementsKind(that_kind);
741 } else if (IsHoleyElementsKind(that_kind)) {
742 this_kind = GetHoleyElementsKind(this_kind);
743 }
744 if (this_kind == that_kind) return Just(this_kind);
745 if (IsDoubleElementsKind(that_kind) == IsDoubleElementsKind(this_kind)) {
746 if (IsMoreGeneralElementsKindTransition(that_kind, this_kind)) {
747 return Just(this_kind);
748 }
749 if (IsMoreGeneralElementsKindTransition(this_kind, that_kind)) {
750 return Just(that_kind);
751 }
752 }
753 return Nothing<ElementsKind>();
754 }
755
756 } // namespace
757
ConsolidateElementLoad(ElementAccessFeedback const & feedback) const758 base::Optional<ElementAccessInfo> AccessInfoFactory::ConsolidateElementLoad(
759 ElementAccessFeedback const& feedback) const {
760 if (feedback.transition_groups().empty()) return base::nullopt;
761
762 DCHECK(!feedback.transition_groups().front().empty());
763 MapRef first_map(broker(), feedback.transition_groups().front().front());
764 InstanceType instance_type = first_map.instance_type();
765 ElementsKind elements_kind = first_map.elements_kind();
766
767 ZoneVector<Handle<Map>> maps(zone());
768 for (auto const& group : feedback.transition_groups()) {
769 for (Handle<Map> map_handle : group) {
770 MapRef map(broker(), map_handle);
771 if (map.instance_type() != instance_type ||
772 !CanInlineElementAccess(map)) {
773 return base::nullopt;
774 }
775 if (!GeneralizeElementsKind(elements_kind, map.elements_kind())
776 .To(&elements_kind)) {
777 return base::nullopt;
778 }
779 maps.push_back(map.object());
780 }
781 }
782
783 return ElementAccessInfo(std::move(maps), elements_kind, zone());
784 }
785
LookupSpecialFieldAccessor(Handle<Map> map,Handle<Name> name) const786 PropertyAccessInfo AccessInfoFactory::LookupSpecialFieldAccessor(
787 Handle<Map> map, Handle<Name> name) const {
788 // Check for String::length field accessor.
789 if (map->IsStringMap()) {
790 if (Name::Equals(isolate(), name, isolate()->factory()->length_string())) {
791 return PropertyAccessInfo::StringLength(zone(), map);
792 }
793 return PropertyAccessInfo::Invalid(zone());
794 }
795 // Check for special JSObject field accessors.
796 FieldIndex field_index;
797 if (Accessors::IsJSObjectFieldAccessor(isolate(), map, name, &field_index)) {
798 Type field_type = Type::NonInternal();
799 Representation field_representation = Representation::Tagged();
800 if (map->IsJSArrayMap()) {
801 DCHECK(
802 Name::Equals(isolate(), isolate()->factory()->length_string(), name));
803 // The JSArray::length property is a smi in the range
804 // [0, FixedDoubleArray::kMaxLength] in case of fast double
805 // elements, a smi in the range [0, FixedArray::kMaxLength]
806 // in case of other fast elements, and [0, kMaxUInt32] in
807 // case of other arrays.
808 if (IsDoubleElementsKind(map->elements_kind())) {
809 field_type = type_cache_->kFixedDoubleArrayLengthType;
810 field_representation = Representation::Smi();
811 } else if (IsFastElementsKind(map->elements_kind())) {
812 field_type = type_cache_->kFixedArrayLengthType;
813 field_representation = Representation::Smi();
814 } else {
815 field_type = type_cache_->kJSArrayLengthType;
816 }
817 }
818 // Special fields are always mutable.
819 return PropertyAccessInfo::DataField(zone(), map, {{}, zone()}, field_index,
820 field_representation, field_type, map);
821 }
822 return PropertyAccessInfo::Invalid(zone());
823 }
824
LookupTransition(Handle<Map> map,Handle<Name> name,MaybeHandle<JSObject> holder) const825 PropertyAccessInfo AccessInfoFactory::LookupTransition(
826 Handle<Map> map, Handle<Name> name, MaybeHandle<JSObject> holder) const {
827 // Check if the {map} has a data transition with the given {name}.
828 Map transition =
829 TransitionsAccessor(isolate(), map, broker()->is_concurrent_inlining())
830 .SearchTransition(*name, kData, NONE);
831 if (transition.is_null()) {
832 return PropertyAccessInfo::Invalid(zone());
833 }
834
835 Handle<Map> transition_map(transition, isolate());
836 InternalIndex const number = transition_map->LastAdded();
837 Handle<DescriptorArray> descriptors(
838 transition_map->instance_descriptors(kAcquireLoad), isolate());
839 PropertyDetails const details = descriptors->GetDetails(number);
840 // Don't bother optimizing stores to read-only properties.
841 if (details.IsReadOnly()) {
842 return PropertyAccessInfo::Invalid(zone());
843 }
844 // TODO(bmeurer): Handle transition to data constant?
845 if (details.location() != kField) {
846 return PropertyAccessInfo::Invalid(zone());
847 }
848 int const index = details.field_index();
849 Representation details_representation = details.representation();
850 FieldIndex field_index = FieldIndex::ForPropertyIndex(*transition_map, index,
851 details_representation);
852 Type field_type = Type::NonInternal();
853 MaybeHandle<Map> field_map;
854 MapRef transition_map_ref(broker(), transition_map);
855 ZoneVector<CompilationDependency const*> unrecorded_dependencies(zone());
856 if (details_representation.IsSmi()) {
857 field_type = Type::SignedSmall();
858 transition_map_ref.SerializeOwnDescriptor(number);
859 unrecorded_dependencies.push_back(
860 dependencies()->FieldRepresentationDependencyOffTheRecord(
861 transition_map_ref, number));
862 } else if (details_representation.IsDouble()) {
863 field_type = type_cache_->kFloat64;
864 if (!FLAG_unbox_double_fields) {
865 transition_map_ref.SerializeOwnDescriptor(number);
866 unrecorded_dependencies.push_back(
867 dependencies()->FieldRepresentationDependencyOffTheRecord(
868 transition_map_ref, number));
869 }
870 } else if (details_representation.IsHeapObject()) {
871 // Extract the field type from the property details (make sure its
872 // representation is TaggedPointer to reflect the heap object case).
873 Handle<FieldType> descriptors_field_type(descriptors->GetFieldType(number),
874 isolate());
875 if (descriptors_field_type->IsNone()) {
876 // Store is not safe if the field type was cleared.
877 return PropertyAccessInfo::Invalid(zone());
878 }
879 transition_map_ref.SerializeOwnDescriptor(number);
880 unrecorded_dependencies.push_back(
881 dependencies()->FieldRepresentationDependencyOffTheRecord(
882 transition_map_ref, number));
883 if (descriptors_field_type->IsClass()) {
884 unrecorded_dependencies.push_back(
885 dependencies()->FieldTypeDependencyOffTheRecord(transition_map_ref,
886 number));
887 // Remember the field map, and try to infer a useful type.
888 Handle<Map> map(descriptors_field_type->AsClass(), isolate());
889 field_type = Type::For(MapRef(broker(), map));
890 field_map = MaybeHandle<Map>(map);
891 }
892 }
893 unrecorded_dependencies.push_back(
894 dependencies()->TransitionDependencyOffTheRecord(
895 MapRef(broker(), transition_map)));
896 transition_map_ref.SerializeBackPointer(); // For BuildPropertyStore.
897 // Transitioning stores *may* store to const fields. The resulting
898 // DataConstant access infos can be distinguished from later, i.e. redundant,
899 // stores to the same constant field by the presence of a transition map.
900 switch (details.constness()) {
901 case PropertyConstness::kMutable:
902 return PropertyAccessInfo::DataField(
903 zone(), map, std::move(unrecorded_dependencies), field_index,
904 details_representation, field_type, transition_map, field_map, holder,
905 transition_map);
906 case PropertyConstness::kConst:
907 return PropertyAccessInfo::DataConstant(
908 zone(), map, std::move(unrecorded_dependencies), field_index,
909 details_representation, field_type, transition_map, field_map, holder,
910 transition_map);
911 }
912 UNREACHABLE();
913 }
914
915 } // namespace compiler
916 } // namespace internal
917 } // namespace v8
918