Commit eea2cc65 authored by Jakob Gruber's avatar Jakob Gruber Committed by V8 LUCI CQ

[compiler] Refactor PropertyAccessInfo to contain refs

.. instead of handles and update all uses. Likewise with
ElementAccessInfo. Essentially, this creates the needed refs up-front
and removes useless MakeRef calls from PAI users.

Bug: v8:7790, v8:11671
Change-Id: I175e77dcca27760101606587de615e3497e68c68
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3030701
Commit-Queue: Jakob Gruber <jgruber@chromium.org>
Reviewed-by: 's avatarMichael Stanton <mvstanton@chromium.org>
Cr-Commit-Position: refs/heads/master@{#75805}
parent d430856d
......@@ -82,8 +82,8 @@ std::ostream& operator<<(std::ostream& os, AccessMode access_mode) {
}
ElementAccessInfo::ElementAccessInfo(
ZoneVector<Handle<Map>>&& lookup_start_object_maps,
ElementsKind elements_kind, Zone* zone)
ZoneVector<MapRef>&& lookup_start_object_maps, ElementsKind elements_kind,
Zone* zone)
: elements_kind_(elements_kind),
lookup_start_object_maps_(lookup_start_object_maps),
transition_sources_(zone) {
......@@ -96,22 +96,21 @@ PropertyAccessInfo PropertyAccessInfo::Invalid(Zone* zone) {
}
// static
PropertyAccessInfo PropertyAccessInfo::NotFound(Zone* zone,
Handle<Map> receiver_map,
MaybeHandle<JSObject> holder) {
PropertyAccessInfo PropertyAccessInfo::NotFound(
Zone* zone, MapRef receiver_map, base::Optional<JSObjectRef> holder) {
return PropertyAccessInfo(zone, kNotFound, holder, {{receiver_map}, zone});
}
// static
PropertyAccessInfo PropertyAccessInfo::DataField(
Zone* zone, Handle<Map> receiver_map,
Zone* zone, MapRef 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) {
DCHECK_IMPLIES(
field_representation.IsDouble(),
HasFieldRepresentationDependenciesOnMap(dependencies, field_owner_map));
Type field_type, MapRef field_owner_map, base::Optional<MapRef> field_map,
base::Optional<JSObjectRef> holder, base::Optional<MapRef> transition_map) {
DCHECK_IMPLIES(field_representation.IsDouble(),
HasFieldRepresentationDependenciesOnMap(
dependencies, field_owner_map.object()));
return PropertyAccessInfo(kDataField, holder, transition_map, field_index,
field_representation, field_type, field_owner_map,
field_map, {{receiver_map}, zone},
......@@ -120,11 +119,11 @@ PropertyAccessInfo PropertyAccessInfo::DataField(
// static
PropertyAccessInfo PropertyAccessInfo::FastDataConstant(
Zone* zone, Handle<Map> receiver_map,
Zone* zone, MapRef 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) {
Type field_type, MapRef field_owner_map, base::Optional<MapRef> field_map,
base::Optional<JSObjectRef> holder, base::Optional<MapRef> transition_map) {
return PropertyAccessInfo(kFastDataConstant, holder, transition_map,
field_index, field_representation, field_type,
field_owner_map, field_map, {{receiver_map}, zone},
......@@ -133,39 +132,38 @@ PropertyAccessInfo PropertyAccessInfo::FastDataConstant(
// static
PropertyAccessInfo PropertyAccessInfo::FastAccessorConstant(
Zone* zone, Handle<Map> receiver_map, Handle<Object> constant,
MaybeHandle<JSObject> holder) {
return PropertyAccessInfo(zone, kFastAccessorConstant, holder, constant,
MaybeHandle<Name>(), {{receiver_map}, zone});
Zone* zone, MapRef receiver_map, base::Optional<ObjectRef> constant,
base::Optional<JSObjectRef> holder) {
return PropertyAccessInfo(zone, kFastAccessorConstant, holder, constant, {},
{{receiver_map}, zone});
}
// static
PropertyAccessInfo PropertyAccessInfo::ModuleExport(Zone* zone,
Handle<Map> receiver_map,
Handle<Cell> cell) {
return PropertyAccessInfo(zone, kModuleExport, MaybeHandle<JSObject>(), cell,
MaybeHandle<Name>{}, {{receiver_map}, zone});
MapRef receiver_map,
CellRef cell) {
return PropertyAccessInfo(zone, kModuleExport, {}, cell, {},
{{receiver_map}, zone});
}
// static
PropertyAccessInfo PropertyAccessInfo::StringLength(Zone* zone,
Handle<Map> receiver_map) {
return PropertyAccessInfo(zone, kStringLength, MaybeHandle<JSObject>(),
{{receiver_map}, zone});
MapRef receiver_map) {
return PropertyAccessInfo(zone, kStringLength, {}, {{receiver_map}, zone});
}
// static
PropertyAccessInfo PropertyAccessInfo::DictionaryProtoDataConstant(
Zone* zone, Handle<Map> receiver_map, Handle<JSObject> holder,
InternalIndex dictionary_index, Handle<Name> name) {
Zone* zone, MapRef receiver_map, JSObjectRef holder,
InternalIndex dictionary_index, NameRef name) {
return PropertyAccessInfo(zone, kDictionaryProtoDataConstant, holder,
{{receiver_map}, zone}, dictionary_index, name);
}
// static
PropertyAccessInfo PropertyAccessInfo::DictionaryProtoAccessorConstant(
Zone* zone, Handle<Map> receiver_map, MaybeHandle<JSObject> holder,
Handle<Object> constant, Handle<Name> property_name) {
Zone* zone, MapRef receiver_map, base::Optional<JSObjectRef> holder,
ObjectRef constant, NameRef property_name) {
return PropertyAccessInfo(zone, kDictionaryProtoAccessorConstant, holder,
constant, property_name, {{receiver_map}, zone});
}
......@@ -193,8 +191,8 @@ PropertyAccessInfo::PropertyAccessInfo(Zone* zone)
dictionary_index_(InternalIndex::NotFound()) {}
PropertyAccessInfo::PropertyAccessInfo(
Zone* zone, Kind kind, MaybeHandle<JSObject> holder,
ZoneVector<Handle<Map>>&& lookup_start_object_maps)
Zone* zone, Kind kind, base::Optional<JSObjectRef> holder,
ZoneVector<MapRef>&& lookup_start_object_maps)
: kind_(kind),
lookup_start_object_maps_(lookup_start_object_maps),
holder_(holder),
......@@ -204,9 +202,9 @@ PropertyAccessInfo::PropertyAccessInfo(
dictionary_index_(InternalIndex::NotFound()) {}
PropertyAccessInfo::PropertyAccessInfo(
Zone* zone, Kind kind, MaybeHandle<JSObject> holder,
Handle<Object> constant, MaybeHandle<Name> property_name,
ZoneVector<Handle<Map>>&& lookup_start_object_maps)
Zone* zone, Kind kind, base::Optional<JSObjectRef> holder,
base::Optional<ObjectRef> constant, base::Optional<NameRef> name,
ZoneVector<MapRef>&& lookup_start_object_maps)
: kind_(kind),
lookup_start_object_maps_(lookup_start_object_maps),
constant_(constant),
......@@ -215,15 +213,16 @@ PropertyAccessInfo::PropertyAccessInfo(
field_representation_(Representation::None()),
field_type_(Type::Any()),
dictionary_index_(InternalIndex::NotFound()),
name_(property_name) {
DCHECK_IMPLIES(kind == kDictionaryProtoAccessorConstant,
!property_name.is_null());
name_(name) {
DCHECK_IMPLIES(kind == kDictionaryProtoAccessorConstant, name.has_value());
}
PropertyAccessInfo::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,
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)
: kind_(kind),
lookup_start_object_maps_(lookup_start_object_maps),
......@@ -236,14 +235,14 @@ PropertyAccessInfo::PropertyAccessInfo(
field_owner_map_(field_owner_map),
field_map_(field_map),
dictionary_index_(InternalIndex::NotFound()) {
DCHECK_IMPLIES(!transition_map.is_null(),
field_owner_map.address() == transition_map.address());
DCHECK_IMPLIES(transition_map.has_value(),
field_owner_map.equals(transition_map.value()));
}
PropertyAccessInfo::PropertyAccessInfo(
Zone* zone, Kind kind, MaybeHandle<JSObject> holder,
ZoneVector<Handle<Map>>&& lookup_start_object_maps,
InternalIndex dictionary_index, Handle<Name> name)
Zone* zone, Kind kind, base::Optional<JSObjectRef> holder,
ZoneVector<MapRef>&& lookup_start_object_maps,
InternalIndex dictionary_index, NameRef name)
: kind_(kind),
lookup_start_object_maps_(lookup_start_object_maps),
holder_(holder),
......@@ -262,10 +261,21 @@ MinimorphicLoadPropertyAccessInfo::MinimorphicLoadPropertyAccessInfo(
field_representation_(field_representation),
field_type_(field_type) {}
namespace {
template <class RefT>
bool OptionalRefEquals(base::Optional<RefT> lhs, base::Optional<RefT> rhs) {
if (!lhs.has_value()) return !rhs.has_value();
if (!rhs.has_value()) return false;
return lhs->equals(rhs.value());
}
} // namespace
bool PropertyAccessInfo::Merge(PropertyAccessInfo const* that,
AccessMode access_mode, Zone* zone) {
if (this->kind_ != that->kind_) return false;
if (this->holder_.address() != that->holder_.address()) return false;
if (!OptionalRefEquals(holder_, that->holder_)) return false;
switch (this->kind_) {
case kInvalid:
......@@ -290,8 +300,8 @@ bool PropertyAccessInfo::Merge(PropertyAccessInfo const* that,
}
this->field_representation_ = Representation::Tagged();
}
if (this->field_map_.address() != that->field_map_.address()) {
this->field_map_ = MaybeHandle<Map>();
if (!OptionalRefEquals(field_map_, that->field_map_)) {
field_map_ = {};
}
break;
}
......@@ -301,11 +311,10 @@ bool PropertyAccessInfo::Merge(PropertyAccessInfo const* that,
// must match exactly, otherwise we cannot merge the stores. We
// also need to make sure that in case of transitioning stores,
// the transition targets match.
if (this->field_map_.address() != that->field_map_.address() ||
if (!OptionalRefEquals(field_map_, that->field_map_) ||
!this->field_representation_.Equals(
that->field_representation_) ||
this->transition_map_.address() !=
that->transition_map_.address()) {
!OptionalRefEquals(transition_map_, that->transition_map_)) {
return false;
}
break;
......@@ -329,7 +338,7 @@ bool PropertyAccessInfo::Merge(PropertyAccessInfo const* that,
case kDictionaryProtoAccessorConstant:
case kFastAccessorConstant: {
// Check if we actually access the same constant.
if (this->constant_.address() == that->constant_.address()) {
if (OptionalRefEquals(constant_, that->constant_)) {
DCHECK(this->unrecorded_dependencies_.empty());
DCHECK(that->unrecorded_dependencies_.empty());
this->lookup_start_object_maps_.insert(
......@@ -369,10 +378,8 @@ bool PropertyAccessInfo::Merge(PropertyAccessInfo const* that,
}
ConstFieldInfo PropertyAccessInfo::GetConstFieldInfo() const {
if (IsFastDataConstant()) {
return ConstFieldInfo(field_owner_map_.ToHandleChecked());
}
return ConstFieldInfo::None();
return IsFastDataConstant() ? ConstFieldInfo(field_owner_map_->object())
: ConstFieldInfo::None();
}
AccessInfoFactory::AccessInfoFactory(JSHeapBroker* broker,
......@@ -390,7 +397,7 @@ base::Optional<ElementAccessInfo> AccessInfoFactory::ComputeElementAccessInfo(
if (!map_ref.has_value()) return {};
if (!CanInlineElementAccess(*map_ref)) return base::nullopt;
ElementsKind const elements_kind = map_ref->elements_kind();
return ElementAccessInfo({{map}, zone()}, elements_kind, zone());
return ElementAccessInfo({{map_ref.value()}, zone()}, elements_kind, zone());
}
bool AccessInfoFactory::ComputeElementAccessInfos(
......@@ -418,7 +425,9 @@ bool AccessInfoFactory::ComputeElementAccessInfos(
if (!access_info.has_value()) return false;
for (size_t i = 1; i < group.size(); ++i) {
access_info->AddTransitionSource(group[i]);
base::Optional<MapRef> map_ref = TryMakeRef(broker(), group[i]);
if (!map_ref.has_value()) continue;
access_info->AddTransitionSource(map_ref.value());
}
access_infos->push_back(*access_info);
}
......@@ -511,17 +520,33 @@ PropertyAccessInfo AccessInfoFactory::ComputeDataFieldAccessInfo(
// TODO(v8:11670): Make FindFieldOwner and friends robust wrt concurrency.
Handle<Map> field_owner_map = broker()->CanonicalPersistentHandle(
map->FindFieldOwner(isolate(), descriptor));
base::Optional<MapRef> field_owner_map_ref =
TryMakeRef(broker(), field_owner_map);
if (!field_owner_map_ref.has_value()) return Invalid();
base::Optional<MapRef> field_map_ref;
if (!field_map.is_null()) {
field_map_ref = TryMakeRef(broker(), field_map.ToHandleChecked());
}
base::Optional<MapRef> receiver_map_ref = TryMakeRef(broker(), receiver_map);
if (!receiver_map_ref.has_value()) return Invalid();
base::Optional<JSObjectRef> holder_ref;
if (!holder.is_null()) {
holder_ref = TryMakeRef(broker(), holder.ToHandleChecked());
}
switch (constness) {
case PropertyConstness::kMutable:
return PropertyAccessInfo::DataField(
zone(), receiver_map, std::move(unrecorded_dependencies), field_index,
details_representation, field_type, field_owner_map, field_map,
holder);
zone(), receiver_map_ref.value(), std::move(unrecorded_dependencies),
field_index, details_representation, field_type,
field_owner_map_ref.value(), field_map_ref, holder_ref, {});
case PropertyConstness::kConst:
return PropertyAccessInfo::FastDataConstant(
zone(), receiver_map, std::move(unrecorded_dependencies), field_index,
details_representation, field_type, field_owner_map, field_map,
holder);
zone(), receiver_map_ref.value(), std::move(unrecorded_dependencies),
field_index, details_representation, field_type,
field_owner_map_ref.value(), field_map_ref, holder_ref, {});
}
UNREACHABLE();
}
......@@ -534,6 +559,11 @@ PropertyAccessInfo AccessorAccessInfoHelper(
const AccessInfoFactory* ai_factory, Handle<Map> receiver_map,
Handle<Name> name, Handle<Map> map, MaybeHandle<JSObject> holder,
AccessMode access_mode, AccessorsObjectGetter get_accessors) {
base::Optional<MapRef> receiver_map_ref = TryMakeRef(broker, receiver_map);
if (!receiver_map_ref.has_value()) {
return PropertyAccessInfo::Invalid(zone);
}
if (map->instance_type() == JS_MODULE_NAMESPACE_TYPE) {
DCHECK(map->is_prototype_map());
Handle<PrototypeInfo> proto_info = broker->CanonicalPersistentHandle(
......@@ -548,15 +578,25 @@ PropertyAccessInfo AccessorAccessInfoHelper(
// This module has not been fully initialized yet.
return PropertyAccessInfo::Invalid(zone);
}
return PropertyAccessInfo::ModuleExport(zone, receiver_map, cell);
base::Optional<CellRef> cell_ref = TryMakeRef(broker, cell);
if (!cell_ref.has_value()) {
return PropertyAccessInfo::Invalid(zone);
}
return PropertyAccessInfo::ModuleExport(zone, receiver_map_ref.value(),
cell_ref.value());
}
if (access_mode == AccessMode::kHas) {
// kHas is not supported for dictionary mode objects.
DCHECK(!map->is_dictionary_map());
base::Optional<JSObjectRef> holder_ref;
if (!holder.is_null()) {
holder_ref = TryMakeRef(broker, holder.ToHandleChecked());
}
// HasProperty checks don't call getter/setters, existence is sufficient.
return PropertyAccessInfo::FastAccessorConstant(zone, receiver_map,
Handle<Object>(), holder);
return PropertyAccessInfo::FastAccessorConstant(
zone, receiver_map_ref.value(), {}, holder_ref);
}
Handle<Object> maybe_accessors = get_accessors();
if (!maybe_accessors->IsAccessorPair()) {
......@@ -567,8 +607,8 @@ PropertyAccessInfo AccessorAccessInfoHelper(
access_mode == AccessMode::kLoad ? accessors->getter()
: accessors->setter());
ObjectData* data = broker->TryGetOrCreateData(accessor);
if (data == nullptr) return PropertyAccessInfo::Invalid(zone);
base::Optional<ObjectRef> accessor_ref = TryMakeRef(broker, accessor);
if (!accessor_ref.has_value()) return PropertyAccessInfo::Invalid(zone);
if (!accessor->IsJSFunction()) {
CallOptimization optimization(broker->local_isolate_or_isolate(), accessor);
......@@ -600,12 +640,21 @@ PropertyAccessInfo AccessorAccessInfoHelper(
if (!access_info.IsInvalid()) return access_info;
}
}
base::Optional<JSObjectRef> holder_ref;
if (!holder.is_null()) {
holder_ref = TryMakeRef(broker, holder.ToHandleChecked());
}
if (map->is_dictionary_map()) {
base::Optional<NameRef> name_ref = TryMakeRef(broker, name);
if (!name_ref.has_value()) return PropertyAccessInfo::Invalid(zone);
return PropertyAccessInfo::DictionaryProtoAccessorConstant(
zone, receiver_map, holder, accessor, name);
zone, receiver_map_ref.value(), holder_ref, accessor_ref.value(),
name_ref.value());
} else {
return PropertyAccessInfo::FastAccessorConstant(zone, receiver_map,
accessor, holder);
return PropertyAccessInfo::FastAccessorConstant(
zone, receiver_map_ref.value(), accessor_ref.value(), holder_ref);
}
}
......@@ -643,8 +692,16 @@ PropertyAccessInfo AccessInfoFactory::ComputeDictionaryProtoAccessInfo(
}
if (details.kind() == PropertyKind::kData) {
base::Optional<MapRef> receiver_map_ref =
TryMakeRef(broker(), receiver_map);
if (!receiver_map_ref.has_value()) return Invalid();
base::Optional<JSObjectRef> holder_ref = TryMakeRef(broker(), holder);
if (!holder_ref.has_value()) return Invalid();
base::Optional<NameRef> name_ref = TryMakeRef(broker(), name);
if (!name_ref.has_value()) return Invalid();
return PropertyAccessInfo::DictionaryProtoDataConstant(
zone(), receiver_map, holder, dictionary_index, name);
zone(), receiver_map_ref.value(), holder_ref.value(), dictionary_index,
name_ref.value());
}
auto get_accessors = [&]() {
......@@ -899,10 +956,20 @@ PropertyAccessInfo AccessInfoFactory::ComputePropertyAccessInfo(
if (access_mode == AccessMode::kStore) {
return LookupTransition(receiver_map, name, holder);
}
base::Optional<MapRef> receiver_map_ref =
TryMakeRef(broker(), receiver_map);
if (!receiver_map_ref.has_value()) return Invalid();
base::Optional<JSObjectRef> holder_ref;
if (!holder.is_null()) {
holder_ref = TryMakeRef(broker(), holder.ToHandleChecked());
}
// The property was not found (access returns undefined or throws
// depending on the language mode of the load operation.
// Implemented according to ES6 section 9.1.8 [[Get]] (P, Receiver)
return PropertyAccessInfo::NotFound(zone(), receiver_map, holder);
return PropertyAccessInfo::NotFound(zone(), receiver_map_ref.value(),
holder_ref);
} else {
return Invalid();
}
......@@ -1014,7 +1081,7 @@ Maybe<ElementsKind> GeneralizeElementsKind(ElementsKind this_kind,
base::Optional<ElementAccessInfo> AccessInfoFactory::ConsolidateElementLoad(
ElementAccessFeedback const& feedback) const {
if (feedback.transition_groups().empty()) return base::nullopt;
if (feedback.transition_groups().empty()) return {};
DCHECK(!feedback.transition_groups().front().empty());
Handle<Map> first_map = feedback.transition_groups().front().front();
......@@ -1023,20 +1090,20 @@ base::Optional<ElementAccessInfo> AccessInfoFactory::ConsolidateElementLoad(
InstanceType instance_type = first_map_ref->instance_type();
ElementsKind elements_kind = first_map_ref->elements_kind();
ZoneVector<Handle<Map>> maps(zone());
ZoneVector<MapRef> maps(zone());
for (auto const& group : feedback.transition_groups()) {
for (Handle<Map> map_handle : group) {
base::Optional<MapRef> map = TryMakeRef(broker(), map_handle);
if (!map.has_value()) return {};
if (map->instance_type() != instance_type ||
!CanInlineElementAccess(*map)) {
return base::nullopt;
return {};
}
if (!GeneralizeElementsKind(elements_kind, map->elements_kind())
.To(&elements_kind)) {
return base::nullopt;
return {};
}
maps.push_back(map->object());
maps.push_back(map.value());
}
}
......@@ -1048,7 +1115,9 @@ PropertyAccessInfo AccessInfoFactory::LookupSpecialFieldAccessor(
// Check for String::length field accessor.
if (map->IsStringMap()) {
if (Name::Equals(isolate(), name, isolate()->factory()->length_string())) {
return PropertyAccessInfo::StringLength(zone(), map);
base::Optional<MapRef> map_ref = TryMakeRef(broker(), map);
if (!map_ref.has_value()) return Invalid();
return PropertyAccessInfo::StringLength(zone(), map_ref.value());
}
return Invalid();
}
......@@ -1075,9 +1144,12 @@ PropertyAccessInfo AccessInfoFactory::LookupSpecialFieldAccessor(
field_type = type_cache_->kJSArrayLengthType;
}
}
base::Optional<MapRef> map_ref = TryMakeRef(broker(), map);
if (!map_ref.has_value()) return Invalid();
// Special fields are always mutable.
return PropertyAccessInfo::DataField(zone(), map, {{}, zone()}, field_index,
field_representation, field_type, map);
return PropertyAccessInfo::DataField(
zone(), map_ref.value(), {{}, zone()}, field_index,
field_representation, field_type, map_ref.value(), {}, {}, {});
}
return Invalid();
}
......@@ -1176,20 +1248,35 @@ PropertyAccessInfo AccessInfoFactory::LookupTransition(
transition_map_ref->SerializeBackPointer(
NotConcurrentInliningTag{broker()}); // For BuildPropertyStore.
}
base::Optional<MapRef> field_map_ref;
if (!field_map.is_null()) {
field_map_ref = TryMakeRef(broker(), field_map.ToHandleChecked());
}
base::Optional<MapRef> map_ref = TryMakeRef(broker(), map);
if (!map_ref.has_value()) return Invalid();
base::Optional<JSObjectRef> holder_ref;
if (!holder.is_null()) {
holder_ref = TryMakeRef(broker(), holder.ToHandleChecked());
}
// Transitioning stores *may* store to const fields. The resulting
// DataConstant access infos can be distinguished from later, i.e. redundant,
// stores to the same constant field by the presence of a transition map.
switch (dependencies()->DependOnFieldConstness(*transition_map_ref, number)) {
case PropertyConstness::kMutable:
return PropertyAccessInfo::DataField(
zone(), map, std::move(unrecorded_dependencies), field_index,
details_representation, field_type, transition_map, field_map, holder,
transition_map);
zone(), map_ref.value(), std::move(unrecorded_dependencies),
field_index, details_representation, field_type,
transition_map_ref.value(), field_map_ref, holder_ref,
transition_map_ref.value());
case PropertyConstness::kConst:
return PropertyAccessInfo::FastDataConstant(
zone(), map, std::move(unrecorded_dependencies), field_index,
details_representation, field_type, transition_map, field_map, holder,
transition_map);
zone(), map_ref.value(), std::move(unrecorded_dependencies),
field_index, details_representation, field_type,
transition_map_ref.value(), field_map_ref, holder_ref,
transition_map_ref.value());
}
UNREACHABLE();
}
......
......@@ -37,26 +37,26 @@ std::ostream& operator<<(std::ostream&, AccessMode);
// This class encapsulates all information required to access a certain element.
class ElementAccessInfo final {
public:
ElementAccessInfo(ZoneVector<Handle<Map>>&& lookup_start_object_maps,
ElementAccessInfo(ZoneVector<MapRef>&& lookup_start_object_maps,
ElementsKind elements_kind, Zone* zone);
ElementsKind elements_kind() const { return elements_kind_; }
ZoneVector<Handle<Map>> const& lookup_start_object_maps() const {
ZoneVector<MapRef> const& lookup_start_object_maps() const {
return lookup_start_object_maps_;
}
ZoneVector<Handle<Map>> const& transition_sources() const {
ZoneVector<MapRef> const& transition_sources() const {
return transition_sources_;
}
void AddTransitionSource(Handle<Map> map) {
void AddTransitionSource(MapRef map) {
CHECK_EQ(lookup_start_object_maps_.size(), 1);
transition_sources_.push_back(map);
}
private:
ElementsKind elements_kind_;
ZoneVector<Handle<Map>> lookup_start_object_maps_;
ZoneVector<Handle<Map>> transition_sources_;
ZoneVector<MapRef> lookup_start_object_maps_;
ZoneVector<MapRef> transition_sources_;
};
// This class encapsulates all information required to access a certain
......@@ -75,37 +75,35 @@ class PropertyAccessInfo final {
kStringLength
};
static PropertyAccessInfo NotFound(Zone* zone, Handle<Map> receiver_map,
MaybeHandle<JSObject> holder);
static PropertyAccessInfo NotFound(Zone* zone, MapRef receiver_map,
base::Optional<JSObjectRef> holder);
static PropertyAccessInfo DataField(
Zone* zone, Handle<Map> receiver_map,
Zone* zone, MapRef receiver_map,
ZoneVector<CompilationDependency const*>&& unrecorded_dependencies,
FieldIndex field_index, Representation field_representation,
Type field_type, Handle<Map> field_owner_map,
MaybeHandle<Map> field_map = MaybeHandle<Map>(),
MaybeHandle<JSObject> holder = MaybeHandle<JSObject>(),
MaybeHandle<Map> transition_map = MaybeHandle<Map>());
Type field_type, MapRef field_owner_map, base::Optional<MapRef> field_map,
base::Optional<JSObjectRef> holder,
base::Optional<MapRef> transition_map);
static PropertyAccessInfo FastDataConstant(
Zone* zone, Handle<Map> receiver_map,
Zone* zone, MapRef receiver_map,
ZoneVector<CompilationDependency const*>&& unrecorded_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 = MaybeHandle<Map>());
static PropertyAccessInfo FastAccessorConstant(Zone* zone,
Handle<Map> receiver_map,
Handle<Object> constant,
MaybeHandle<JSObject> holder);
static PropertyAccessInfo ModuleExport(Zone* zone, Handle<Map> receiver_map,
Handle<Cell> cell);
static PropertyAccessInfo StringLength(Zone* zone, Handle<Map> receiver_map);
Type field_type, MapRef field_owner_map, base::Optional<MapRef> field_map,
base::Optional<JSObjectRef> holder,
base::Optional<MapRef> transition_map);
static PropertyAccessInfo FastAccessorConstant(
Zone* zone, MapRef receiver_map, base::Optional<ObjectRef> constant,
base::Optional<JSObjectRef> holder);
static PropertyAccessInfo ModuleExport(Zone* zone, MapRef receiver_map,
CellRef cell);
static PropertyAccessInfo StringLength(Zone* zone, MapRef receiver_map);
static PropertyAccessInfo Invalid(Zone* zone);
static PropertyAccessInfo DictionaryProtoDataConstant(
Zone* zone, Handle<Map> receiver_map, Handle<JSObject> holder,
InternalIndex dict_index, Handle<Name> name);
Zone* zone, MapRef receiver_map, JSObjectRef holder,
InternalIndex dict_index, NameRef name);
static PropertyAccessInfo DictionaryProtoAccessorConstant(
Zone* zone, Handle<Map> receiver_map, MaybeHandle<JSObject> holder,
Handle<Object> constant, Handle<Name> name);
Zone* zone, MapRef receiver_map, base::Optional<JSObjectRef> holder,
ObjectRef constant, NameRef name);
bool Merge(PropertyAccessInfo const* that, AccessMode access_mode,
Zone* zone) V8_WARN_UNUSED_RESULT;
......@@ -128,7 +126,7 @@ class PropertyAccessInfo final {
return kind() == kDictionaryProtoAccessorConstant;
}
bool HasTransitionMap() const { return !transition_map().is_null(); }
bool HasTransitionMap() const { return transition_map().has_value(); }
bool HasDictionaryHolder() const {
return kind_ == kDictionaryProtoDataConstant ||
kind_ == kDictionaryProtoAccessorConstant;
......@@ -136,17 +134,17 @@ class PropertyAccessInfo final {
ConstFieldInfo GetConstFieldInfo() const;
Kind kind() const { return kind_; }
MaybeHandle<JSObject> holder() const {
base::Optional<JSObjectRef> holder() const {
// TODO(neis): There was a CHECK here that tries to protect against
// using the access info without recording its dependencies first.
// Find a more suitable place for it.
return holder_;
}
MaybeHandle<Map> transition_map() const {
base::Optional<MapRef> transition_map() const {
DCHECK(!HasDictionaryHolder());
return transition_map_;
}
Handle<Object> constant() const { return constant_; }
base::Optional<ObjectRef> constant() const { return constant_; }
FieldIndex field_index() const {
DCHECK(!HasDictionaryHolder());
return field_index_;
......@@ -160,11 +158,11 @@ class PropertyAccessInfo final {
DCHECK(!HasDictionaryHolder());
return field_representation_;
}
MaybeHandle<Map> field_map() const {
base::Optional<MapRef> field_map() const {
DCHECK(!HasDictionaryHolder());
return field_map_;
}
ZoneVector<Handle<Map>> const& lookup_start_object_maps() const {
ZoneVector<MapRef> const& lookup_start_object_maps() const {
return lookup_start_object_maps_;
}
......@@ -173,46 +171,48 @@ class PropertyAccessInfo final {
return dictionary_index_;
}
Handle<Name> name() const {
NameRef name() const {
DCHECK(HasDictionaryHolder());
return name_.ToHandleChecked();
return name_.value();
}
private:
explicit PropertyAccessInfo(Zone* zone);
PropertyAccessInfo(Zone* zone, Kind kind, MaybeHandle<JSObject> holder,
ZoneVector<Handle<Map>>&& lookup_start_object_maps);
PropertyAccessInfo(Zone* zone, Kind kind, MaybeHandle<JSObject> holder,
Handle<Object> constant, MaybeHandle<Name> name,
ZoneVector<Handle<Map>>&& lookup_start_object_maps);
PropertyAccessInfo(Kind kind, MaybeHandle<JSObject> holder,
MaybeHandle<Map> transition_map, FieldIndex field_index,
PropertyAccessInfo(Zone* zone, Kind kind, base::Optional<JSObjectRef> holder,
ZoneVector<MapRef>&& lookup_start_object_maps);
PropertyAccessInfo(Zone* zone, Kind kind, base::Optional<JSObjectRef> holder,
base::Optional<ObjectRef> constant,
base::Optional<NameRef> name,
ZoneVector<MapRef>&& lookup_start_object_maps);
PropertyAccessInfo(Kind kind, base::Optional<JSObjectRef> holder,
base::Optional<MapRef> 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,
MapRef field_owner_map, base::Optional<MapRef> field_map,
ZoneVector<MapRef>&& lookup_start_object_maps,
ZoneVector<CompilationDependency const*>&& dependencies);
PropertyAccessInfo(Zone* zone, Kind kind, MaybeHandle<JSObject> holder,
ZoneVector<Handle<Map>>&& lookup_start_object_maps,
InternalIndex dictionary_index, Handle<Name> name);
PropertyAccessInfo(Zone* zone, Kind kind, base::Optional<JSObjectRef> holder,
ZoneVector<MapRef>&& lookup_start_object_maps,
InternalIndex dictionary_index, NameRef name);
// Members used for fast and dictionary mode holders:
Kind kind_;
ZoneVector<Handle<Map>> lookup_start_object_maps_;
Handle<Object> constant_;
MaybeHandle<JSObject> holder_;
ZoneVector<MapRef> lookup_start_object_maps_;
base::Optional<ObjectRef> constant_;
base::Optional<JSObjectRef> holder_;
// Members only used for fast mode holders:
ZoneVector<CompilationDependency const*> unrecorded_dependencies_;
MaybeHandle<Map> transition_map_;
base::Optional<MapRef> transition_map_;
FieldIndex field_index_;
Representation field_representation_;
Type field_type_;
MaybeHandle<Map> field_owner_map_;
MaybeHandle<Map> field_map_;
base::Optional<MapRef> field_owner_map_;
base::Optional<MapRef> field_map_;
// Members only used for dictionary mode holders:
InternalIndex dictionary_index_;
MaybeHandle<Name> name_;
base::Optional<NameRef> name_;
};
// This class encapsulates information required to generate load properties
......
......@@ -881,12 +881,10 @@ void DependOnStablePrototypeChain(CompilationDependencies* deps, MapRef map,
}
} // namespace
template <class MapContainer>
void CompilationDependencies::DependOnStablePrototypeChains(
MapContainer const& receiver_maps, WhereToStart start,
ZoneVector<MapRef> const& receiver_maps, WhereToStart start,
base::Optional<JSObjectRef> last_prototype) {
for (auto map : receiver_maps) {
MapRef receiver_map = MakeRef(broker_, map);
for (MapRef receiver_map : receiver_maps) {
if (start == kStartAtReceiver) DependOnStableMap(receiver_map);
if (receiver_map.IsPrimitiveMap()) {
// Perform the implicit ToObject for primitives here.
......@@ -900,12 +898,6 @@ void CompilationDependencies::DependOnStablePrototypeChains(
DependOnStablePrototypeChain(this, receiver_map, last_prototype);
}
}
template void CompilationDependencies::DependOnStablePrototypeChains(
ZoneVector<Handle<Map>> const& receiver_maps, WhereToStart start,
base::Optional<JSObjectRef> last_prototype);
template void CompilationDependencies::DependOnStablePrototypeChains(
ZoneHandleSet<Map> const& receiver_maps, WhereToStart start,
base::Optional<JSObjectRef> last_prototype);
void CompilationDependencies::DependOnElementsKinds(
const AllocationSiteRef& site) {
......
......@@ -116,9 +116,8 @@ class V8_EXPORT_PRIVATE CompilationDependencies : public ZoneObject {
// For each given map, depend on the stability of (the maps of) all prototypes
// up to (and including) the {last_prototype}.
template <class MapContainer>
void DependOnStablePrototypeChains(
MapContainer const& receiver_maps, WhereToStart start,
ZoneVector<MapRef> const& receiver_maps, WhereToStart start,
base::Optional<JSObjectRef> last_prototype =
base::Optional<JSObjectRef>());
......
......@@ -8002,14 +8002,12 @@ Reduction JSCallReducer::ReduceRegExpPrototypeTest(Node* node) {
// If "exec" has been modified on {regexp}, we can't do anything.
if (ai_exec.IsFastDataConstant()) {
Handle<JSObject> holder;
base::Optional<JSObjectRef> holder = ai_exec.holder();
// Do not reduce if the exec method is not on the prototype chain.
if (!ai_exec.holder().ToHandle(&holder)) return inference.NoChange();
JSObjectRef holder_ref = MakeRef(broker(), holder);
if (!holder.has_value()) return inference.NoChange();
// Bail out if the exec method is not the original one.
base::Optional<ObjectRef> constant = holder_ref.GetOwnFastDataProperty(
base::Optional<ObjectRef> constant = holder->GetOwnFastDataProperty(
ai_exec.field_representation(), ai_exec.field_index(), dependencies());
if (!constant.has_value() ||
!constant->equals(native_context().regexp_exec_function())) {
......@@ -8018,8 +8016,7 @@ Reduction JSCallReducer::ReduceRegExpPrototypeTest(Node* node) {
// Add proper dependencies on the {regexp}s [[Prototype]]s.
dependencies()->DependOnStablePrototypeChains(
ai_exec.lookup_start_object_maps(), kStartAtPrototype,
MakeRef(broker(), holder));
ai_exec.lookup_start_object_maps(), kStartAtPrototype, holder.value());
} else {
// TODO(v8:11457) Support dictionary mode protoypes here.
return inference.NoChange();
......
......@@ -35,19 +35,16 @@ namespace compiler {
namespace {
bool HasNumberMaps(JSHeapBroker* broker, ZoneVector<Handle<Map>> const& maps) {
for (auto map : maps) {
MapRef map_ref = MakeRef(broker, map);
if (map_ref.IsHeapNumberMap()) return true;
bool HasNumberMaps(JSHeapBroker* broker, ZoneVector<MapRef> const& maps) {
for (MapRef map : maps) {
if (map.IsHeapNumberMap()) return true;
}
return false;
}
bool HasOnlyJSArrayMaps(JSHeapBroker* broker,
ZoneVector<Handle<Map>> const& maps) {
for (auto map : maps) {
MapRef map_ref = MakeRef(broker, map);
if (!map_ref.IsJSArrayMap()) return false;
bool HasOnlyJSArrayMaps(JSHeapBroker* broker, ZoneVector<MapRef> const& maps) {
for (MapRef map : maps) {
if (!map.IsJSArrayMap()) return false;
}
return true;
}
......@@ -427,8 +424,9 @@ Reduction JSNativeContextSpecialization::ReduceJSInstanceOf(Node* node) {
}
// TODO(v8:11457) Support dictionary mode holders here.
if (access_info.IsInvalid() || access_info.HasDictionaryHolder())
if (access_info.IsInvalid() || access_info.HasDictionaryHolder()) {
return NoChange();
}
access_info.RecordDependencies(dependencies());
PropertyAccessBuilder access_builder(jsgraph(), broker(), dependencies());
......@@ -456,10 +454,9 @@ Reduction JSNativeContextSpecialization::ReduceJSInstanceOf(Node* node) {
}
if (access_info.IsFastDataConstant()) {
Handle<JSObject> holder;
bool found_on_proto = access_info.holder().ToHandle(&holder);
JSObjectRef holder_ref =
found_on_proto ? MakeRef(broker(), holder) : receiver_ref;
base::Optional<JSObjectRef> holder = access_info.holder();
bool found_on_proto = holder.has_value();
JSObjectRef holder_ref = found_on_proto ? holder.value() : receiver_ref;
base::Optional<ObjectRef> constant = holder_ref.GetOwnFastDataProperty(
access_info.field_representation(), access_info.field_index(),
dependencies());
......@@ -470,7 +467,7 @@ Reduction JSNativeContextSpecialization::ReduceJSInstanceOf(Node* node) {
if (found_on_proto) {
dependencies()->DependOnStablePrototypeChains(
access_info.lookup_start_object_maps(), kStartAtPrototype,
MakeRef(broker(), holder));
holder.value());
}
// Check that {constructor} is actually {receiver}.
......@@ -532,6 +529,8 @@ JSNativeContextSpecialization::InferHasInPrototypeChain(
broker(), receiver, effect, &receiver_maps);
if (result == NodeProperties::kNoMaps) return kMayBeInPrototypeChain;
ZoneVector<MapRef> receiver_map_refs(zone());
// Try to determine either that all of the {receiver_maps} have the given
// {prototype} in their chain, or that none do. If we can't tell, return
// kMayBeInPrototypeChain.
......@@ -539,6 +538,7 @@ JSNativeContextSpecialization::InferHasInPrototypeChain(
bool none = true;
for (size_t i = 0; i < receiver_maps.size(); ++i) {
MapRef map = MakeRef(broker(), receiver_maps[i]);
receiver_map_refs.push_back(map);
if (result == NodeProperties::kUnreliableMaps && !map.is_stable()) {
return kMayBeInPrototypeChain;
}
......@@ -584,7 +584,7 @@ JSNativeContextSpecialization::InferHasInPrototypeChain(
WhereToStart start = result == NodeProperties::kUnreliableMaps
? kStartAtReceiver
: kStartAtPrototype;
dependencies()->DependOnStablePrototypeChains(receiver_maps, start,
dependencies()->DependOnStablePrototypeChains(receiver_map_refs, start,
last_prototype);
}
......@@ -1135,9 +1135,9 @@ Reduction JSNativeContextSpecialization::ReduceNamedAccess(
STATIC_ASSERT(JSLoadNamedFromSuperNode::ReceiverIndex() == 0);
Node* context = NodeProperties::GetContextInput(node);
Node* frame_state = NodeProperties::GetFrameStateInput(node);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
FrameState frame_state{NodeProperties::GetFrameStateInput(node)};
Effect effect{NodeProperties::GetEffectInput(node)};
Control control{NodeProperties::GetControlInput(node)};
// receiver = the object we pass to the accessor (if any) as the "this" value.
Node* receiver = NodeProperties::GetValueInput(node, 0);
......@@ -1250,12 +1250,10 @@ Reduction JSNativeContextSpecialization::ReduceNamedAccess(
Node* if_true = graph()->NewNode(common()->IfTrue(), branch);
Node* etrue = effect;
Node* if_false = graph()->NewNode(common()->IfFalse(), branch);
Node* efalse = effect;
{
Control if_false{graph()->NewNode(common()->IfFalse(), branch)};
Effect efalse = effect;
access_builder.BuildCheckMaps(receiver, &efalse, if_false,
access_info.lookup_start_object_maps());
}
control = graph()->NewNode(common()->Merge(2), if_true, if_false);
effect =
......@@ -1323,11 +1321,11 @@ Reduction JSNativeContextSpecialization::ReduceNamedAccess(
Node* this_value = value;
Node* this_lookup_start_object = lookup_start_object;
Node* this_receiver = receiver;
Node* this_effect = effect;
Node* this_control = fallthrough_control;
Effect this_effect = effect;
Control this_control{fallthrough_control};
// Perform map check on {lookup_start_object}.
ZoneVector<Handle<Map>> const& lookup_start_object_maps =
ZoneVector<MapRef> const& lookup_start_object_maps =
access_info.lookup_start_object_maps();
{
// Whether to insert a dedicated MapGuard node into the
......@@ -1349,8 +1347,8 @@ Reduction JSNativeContextSpecialization::ReduceNamedAccess(
} else {
// Explicitly branch on the {lookup_start_object_maps}.
ZoneHandleSet<Map> maps;
for (Handle<Map> map : lookup_start_object_maps) {
maps.insert(map, graph()->zone());
for (MapRef map : lookup_start_object_maps) {
maps.insert(map.object(), graph()->zone());
}
Node* check = this_effect =
graph()->NewNode(simplified()->CompareMaps(maps),
......@@ -1381,8 +1379,8 @@ Reduction JSNativeContextSpecialization::ReduceNamedAccess(
// Introduce a MapGuard to learn from this on the effect chain.
if (insert_map_guard) {
ZoneHandleSet<Map> maps;
for (auto lookup_start_object_map : lookup_start_object_maps) {
maps.insert(lookup_start_object_map, graph()->zone());
for (MapRef map : lookup_start_object_maps) {
maps.insert(map.object(), graph()->zone());
}
this_effect =
graph()->NewNode(simplified()->MapGuard(maps),
......@@ -1709,10 +1707,10 @@ Reduction JSNativeContextSpecialization::ReduceElementAccess(
JSHasPropertyNode::ObjectIndex() == 0);
Node* receiver = NodeProperties::GetValueInput(node, 0);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
Node* frame_state =
NodeProperties::FindFrameStateBefore(node, jsgraph()->Dead());
FrameState frame_state{
NodeProperties::FindFrameStateBefore(node, jsgraph()->Dead())};
Effect effect{NodeProperties::GetEffectInput(node)};
Control control{NodeProperties::GetControlInput(node)};
// TODO(neis): It's odd that we do optimizations below that don't really care
// about the feedback, but we don't do them when the feedback is megamorphic.
......@@ -1753,8 +1751,7 @@ Reduction JSNativeContextSpecialization::ReduceElementAccess(
// the zone allocation of this vector.
ZoneVector<MapRef> prototype_maps(zone());
for (ElementAccessInfo const& access_info : access_infos) {
for (Handle<Map> map : access_info.lookup_start_object_maps()) {
MapRef receiver_map = MakeRef(broker(), map);
for (MapRef receiver_map : access_info.lookup_start_object_maps()) {
// If the {receiver_map} has a prototype and its elements backing
// store is either holey, or we have a potentially growing store,
// then we need to check that all prototypes have stable maps with
......@@ -1797,11 +1794,9 @@ Reduction JSNativeContextSpecialization::ReduceElementAccess(
ElementAccessInfo access_info = access_infos.front();
// Perform possible elements kind transitions.
MapRef transition_target =
MakeRef(broker(), access_info.lookup_start_object_maps().front());
for (auto source : access_info.transition_sources()) {
MapRef transition_target = access_info.lookup_start_object_maps().front();
for (MapRef transition_source : access_info.transition_sources()) {
DCHECK_EQ(access_info.lookup_start_object_maps().size(), 1);
MapRef transition_source = MakeRef(broker(), source);
effect = graph()->NewNode(
simplified()->TransitionElementsKind(ElementsTransition(
IsSimpleMapChangeTransition(transition_source.elements_kind(),
......@@ -1845,14 +1840,12 @@ Reduction JSNativeContextSpecialization::ReduceElementAccess(
Node* this_receiver = receiver;
Node* this_value = value;
Node* this_index = index;
Node* this_effect = effect;
Node* this_control = fallthrough_control;
Effect this_effect = effect;
Control this_control{fallthrough_control};
// Perform possible elements kind transitions.
MapRef transition_target =
MakeRef(broker(), access_info.lookup_start_object_maps().front());
for (auto source : access_info.transition_sources()) {
MapRef transition_source = MakeRef(broker(), source);
MapRef transition_target = access_info.lookup_start_object_maps().front();
for (MapRef transition_source : access_info.transition_sources()) {
DCHECK_EQ(access_info.lookup_start_object_maps().size(), 1);
this_effect = graph()->NewNode(
simplified()->TransitionElementsKind(ElementsTransition(
......@@ -1865,7 +1858,7 @@ Reduction JSNativeContextSpecialization::ReduceElementAccess(
}
// Perform map check(s) on {receiver}.
ZoneVector<Handle<Map>> const& receiver_maps =
ZoneVector<MapRef> const& receiver_maps =
access_info.lookup_start_object_maps();
if (j == access_infos.size() - 1) {
// Last map check on the fallthrough control path, do a
......@@ -1876,8 +1869,8 @@ Reduction JSNativeContextSpecialization::ReduceElementAccess(
} else {
// Explicitly branch on the {receiver_maps}.
ZoneHandleSet<Map> maps;
for (Handle<Map> map : receiver_maps) {
maps.insert(map, graph()->zone());
for (MapRef map : receiver_maps) {
maps.insert(map.object(), graph()->zone());
}
Node* check = this_effect =
graph()->NewNode(simplified()->CompareMaps(maps), receiver,
......@@ -2217,14 +2210,13 @@ Node* JSNativeContextSpecialization::InlinePropertyGetterCall(
Node* receiver, ConvertReceiverMode receiver_mode, Node* context,
Node* frame_state, Node** effect, Node** control,
ZoneVector<Node*>* if_exceptions, PropertyAccessInfo const& access_info) {
ObjectRef constant = MakeRef(broker(), access_info.constant());
ObjectRef constant = access_info.constant().value();
if (access_info.IsDictionaryProtoAccessorConstant()) {
// For fast mode holders we recorded dependencies in BuildPropertyLoad.
for (const Handle<Map> map : access_info.lookup_start_object_maps()) {
for (const MapRef map : access_info.lookup_start_object_maps()) {
dependencies()->DependOnConstantInDictionaryPrototypeChain(
MakeRef(broker(), map), MakeRef(broker(), access_info.name()),
constant, PropertyKind::kAccessor);
map, access_info.name(), constant, PropertyKind::kAccessor);
}
}
......@@ -2239,10 +2231,9 @@ Node* JSNativeContextSpecialization::InlinePropertyGetterCall(
receiver_mode),
target, receiver, feedback, context, frame_state, *effect, *control);
} else {
Node* holder = access_info.holder().is_null()
? receiver
: jsgraph()->Constant(MakeRef(
broker(), access_info.holder().ToHandleChecked()));
Node* holder = access_info.holder().has_value()
? jsgraph()->Constant(access_info.holder().value())
: receiver;
value = InlineApiCall(receiver, holder, frame_state, nullptr, effect,
control, constant.AsFunctionTemplateInfo());
}
......@@ -2262,7 +2253,7 @@ void JSNativeContextSpecialization::InlinePropertySetterCall(
Node* receiver, Node* value, Node* context, Node* frame_state,
Node** effect, Node** control, ZoneVector<Node*>* if_exceptions,
PropertyAccessInfo const& access_info) {
ObjectRef constant = MakeRef(broker(), access_info.constant());
ObjectRef constant = access_info.constant().value();
Node* target = jsgraph()->Constant(constant);
// Introduce the call to the setter function.
if (constant.IsJSFunction()) {
......@@ -2274,10 +2265,9 @@ void JSNativeContextSpecialization::InlinePropertySetterCall(
target, receiver, value, feedback, context, frame_state, *effect,
*control);
} else {
Node* holder = access_info.holder().is_null()
? receiver
: jsgraph()->Constant(MakeRef(
broker(), access_info.holder().ToHandleChecked()));
Node* holder = access_info.holder().has_value()
? jsgraph()->Constant(access_info.holder().value())
: receiver;
InlineApiCall(receiver, holder, frame_state, value, effect, control,
constant.AsFunctionTemplateInfo());
}
......@@ -2351,12 +2341,11 @@ JSNativeContextSpecialization::BuildPropertyLoad(
Node* effect, Node* control, NameRef const& name,
ZoneVector<Node*>* if_exceptions, PropertyAccessInfo const& access_info) {
// Determine actual holder and perform prototype chain checks.
Handle<JSObject> holder;
if (access_info.holder().ToHandle(&holder) &&
!access_info.HasDictionaryHolder()) {
base::Optional<JSObjectRef> holder = access_info.holder();
if (holder.has_value() && !access_info.HasDictionaryHolder()) {
dependencies()->DependOnStablePrototypeChains(
access_info.lookup_start_object_maps(), kStartAtPrototype,
MakeRef(broker(), holder));
holder.value());
}
// Generate the actual property access.
......@@ -2373,8 +2362,7 @@ JSNativeContextSpecialization::BuildPropertyLoad(
InlinePropertyGetterCall(receiver, receiver_mode, context, frame_state,
&effect, &control, if_exceptions, access_info);
} else if (access_info.IsModuleExport()) {
Node* cell =
jsgraph()->Constant(MakeRef(broker(), access_info.constant()).AsCell());
Node* cell = jsgraph()->Constant(access_info.constant().value().AsCell());
value = effect =
graph()->NewNode(simplified()->LoadField(AccessBuilder::ForCellValue()),
cell, effect, control);
......@@ -2406,11 +2394,11 @@ JSNativeContextSpecialization::BuildPropertyTest(
DCHECK(!access_info.HasDictionaryHolder());
// Determine actual holder and perform prototype chain checks.
Handle<JSObject> holder;
if (access_info.holder().ToHandle(&holder)) {
base::Optional<JSObjectRef> holder = access_info.holder();
if (holder.has_value()) {
dependencies()->DependOnStablePrototypeChains(
access_info.lookup_start_object_maps(), kStartAtPrototype,
MakeRef(broker(), holder));
holder.value());
}
Node* value = access_info.IsNotFound() ? jsgraph()->FalseConstant()
......@@ -2448,13 +2436,13 @@ JSNativeContextSpecialization::BuildPropertyStore(
Node* control, NameRef const& name, ZoneVector<Node*>* if_exceptions,
PropertyAccessInfo const& access_info, AccessMode access_mode) {
// Determine actual holder and perform prototype chain checks.
Handle<JSObject> holder;
PropertyAccessBuilder access_builder(jsgraph(), broker(), dependencies());
if (access_info.holder().ToHandle(&holder)) {
base::Optional<JSObjectRef> holder = access_info.holder();
if (holder.has_value()) {
DCHECK_NE(AccessMode::kStoreInLiteral, access_mode);
dependencies()->DependOnStablePrototypeChains(
access_info.lookup_start_object_maps(), kStartAtPrototype,
MakeRef(broker(), holder));
holder.value());
}
DCHECK(!access_info.IsNotFound());
......@@ -2575,12 +2563,13 @@ JSNativeContextSpecialization::BuildPropertyStore(
} else if (field_representation ==
MachineRepresentation::kTaggedPointer) {
Handle<Map> field_map;
if (access_info.field_map().ToHandle(&field_map)) {
base::Optional<MapRef> field_map = access_info.field_map();
if (field_map.has_value()) {
// Emit a map check for the value.
effect = graph()->NewNode(
simplified()->CheckMaps(CheckMapsFlag::kNone,
ZoneHandleSet<Map>(field_map)),
effect =
graph()->NewNode(simplified()->CheckMaps(
CheckMapsFlag::kNone,
ZoneHandleSet<Map>(field_map->object())),
value, effect, control);
} else {
// Ensure that {value} is a HeapObject.
......@@ -2607,11 +2596,11 @@ JSNativeContextSpecialization::BuildPropertyStore(
UNREACHABLE();
}
// Check if we need to perform a transitioning store.
Handle<Map> transition_map;
if (access_info.transition_map().ToHandle(&transition_map)) {
base::Optional<MapRef> transition_map = access_info.transition_map();
if (transition_map.has_value()) {
// Check if we need to grow the properties backing store
// with this transitioning store.
MapRef transition_map_ref = MakeRef(broker(), transition_map);
MapRef transition_map_ref = transition_map.value();
MapRef original_map = transition_map_ref.GetBackPointer().AsMap();
if (original_map.UnusedPropertyFields() == 0) {
DCHECK(!field_index.is_inobject());
......@@ -2713,7 +2702,7 @@ JSNativeContextSpecialization::BuildElementAccess(
// TODO(bmeurer): We currently specialize based on elements kind. We should
// also be able to properly support strings and other JSObjects here.
ElementsKind elements_kind = access_info.elements_kind();
ZoneVector<Handle<Map>> const& receiver_maps =
ZoneVector<MapRef> const& receiver_maps =
access_info.lookup_start_object_maps();
if (IsTypedArrayElementsKind(elements_kind)) {
......@@ -3455,12 +3444,11 @@ Node* JSNativeContextSpecialization::BuildCheckEqualsName(NameRef const& name,
}
bool JSNativeContextSpecialization::CanTreatHoleAsUndefined(
ZoneVector<Handle<Map>> const& receiver_maps) {
ZoneVector<MapRef> const& receiver_maps) {
// Check if all {receiver_maps} have one of the initial Array.prototype
// or Object.prototype objects as their prototype (in any of the current
// native contexts, as the global Array protector works isolate-wide).
for (Handle<Map> map : receiver_maps) {
MapRef receiver_map = MakeRef(broker(), map);
for (MapRef receiver_map : receiver_maps) {
ObjectRef receiver_prototype = receiver_map.prototype().value();
if (!receiver_prototype.IsJSObject() ||
!broker()->IsArrayOrObjectPrototype(receiver_prototype.AsJSObject())) {
......
......@@ -210,7 +210,7 @@ class V8_EXPORT_PRIVATE JSNativeContextSpecialization final
// Checks if we can turn the hole into undefined when loading an element
// from an object with one of the {receiver_maps}; sets up appropriate
// code dependencies and might use the array protector cell.
bool CanTreatHoleAsUndefined(ZoneVector<Handle<Map>> const& receiver_maps);
bool CanTreatHoleAsUndefined(ZoneVector<MapRef> const& receiver_maps);
void RemoveImpossibleMaps(Node* object, ZoneVector<Handle<Map>>* maps) const;
......
......@@ -34,31 +34,28 @@ SimplifiedOperatorBuilder* PropertyAccessBuilder::simplified() const {
return jsgraph()->simplified();
}
bool HasOnlyStringMaps(JSHeapBroker* broker,
ZoneVector<Handle<Map>> const& maps) {
for (auto map : maps) {
MapRef map_ref = MakeRef(broker, map);
if (!map_ref.IsStringMap()) return false;
bool HasOnlyStringMaps(JSHeapBroker* broker, ZoneVector<MapRef> const& maps) {
for (MapRef map : maps) {
if (!map.IsStringMap()) return false;
}
return true;
}
namespace {
bool HasOnlyNumberMaps(JSHeapBroker* broker,
ZoneVector<Handle<Map>> const& maps) {
for (auto map : maps) {
MapRef map_ref = MakeRef(broker, map);
if (map_ref.instance_type() != HEAP_NUMBER_TYPE) return false;
bool HasOnlyNumberMaps(JSHeapBroker* broker, ZoneVector<MapRef> const& maps) {
for (MapRef map : maps) {
if (map.instance_type() != HEAP_NUMBER_TYPE) return false;
}
return true;
}
} // namespace
bool PropertyAccessBuilder::TryBuildStringCheck(
JSHeapBroker* broker, ZoneVector<Handle<Map>> const& maps, Node** receiver,
Node** effect, Node* control) {
bool PropertyAccessBuilder::TryBuildStringCheck(JSHeapBroker* broker,
ZoneVector<MapRef> const& maps,
Node** receiver, Effect* effect,
Control control) {
if (HasOnlyStringMaps(broker, maps)) {
// Monormorphic string access (ignoring the fact that there are multiple
// String maps).
......@@ -70,9 +67,10 @@ bool PropertyAccessBuilder::TryBuildStringCheck(
return false;
}
bool PropertyAccessBuilder::TryBuildNumberCheck(
JSHeapBroker* broker, ZoneVector<Handle<Map>> const& maps, Node** receiver,
Node** effect, Node* control) {
bool PropertyAccessBuilder::TryBuildNumberCheck(JSHeapBroker* broker,
ZoneVector<MapRef> const& maps,
Node** receiver, Effect* effect,
Control control) {
if (HasOnlyNumberMaps(broker, maps)) {
// Monomorphic number access (we also deal with Smis here).
*receiver = *effect =
......@@ -83,15 +81,15 @@ bool PropertyAccessBuilder::TryBuildNumberCheck(
return false;
}
void PropertyAccessBuilder::BuildCheckMaps(
Node* object, Node** effect, Node* control,
ZoneVector<Handle<Map>> const& maps) {
void PropertyAccessBuilder::BuildCheckMaps(Node* object, Effect* effect,
Control control,
ZoneVector<MapRef> const& maps) {
HeapObjectMatcher m(object);
if (m.HasResolvedValue()) {
MapRef object_map = m.Ref(broker()).map();
if (object_map.is_stable()) {
for (Handle<Map> map : maps) {
if (MakeRef(broker(), map).equals(object_map)) {
for (MapRef map : maps) {
if (map.equals(object_map)) {
dependencies()->DependOnStableMap(object_map);
return;
}
......@@ -100,10 +98,9 @@ void PropertyAccessBuilder::BuildCheckMaps(
}
ZoneHandleSet<Map> map_set;
CheckMapsFlags flags = CheckMapsFlag::kNone;
for (Handle<Map> map : maps) {
MapRef object_map = MakeRef(broker(), map);
map_set.insert(object_map.object(), graph()->zone());
if (object_map.is_migration_target()) {
for (MapRef map : maps) {
map_set.insert(map.object(), graph()->zone());
if (map.is_migration_target()) {
flags |= CheckMapsFlag::kTryMigrateInstance;
}
}
......@@ -127,9 +124,9 @@ Node* PropertyAccessBuilder::BuildCheckValue(Node* receiver, Effect* effect,
Node* PropertyAccessBuilder::ResolveHolder(
PropertyAccessInfo const& access_info, Node* lookup_start_object) {
Handle<JSObject> holder;
if (access_info.holder().ToHandle(&holder)) {
return jsgraph()->Constant(MakeRef(broker(), holder));
base::Optional<JSObjectRef> holder = access_info.holder();
if (holder.has_value()) {
return jsgraph()->Constant(holder.value());
}
return lookup_start_object;
}
......@@ -155,29 +152,27 @@ base::Optional<Node*> PropertyAccessBuilder::FoldLoadDictPrototypeConstant(
DCHECK(V8_DICT_PROPERTY_CONST_TRACKING_BOOL);
DCHECK(access_info.IsDictionaryProtoDataConstant());
JSObjectRef holder =
MakeRef(broker(), access_info.holder().ToHandleChecked());
InternalIndex index = access_info.dictionary_index();
base::Optional<ObjectRef> value =
holder.GetOwnDictionaryProperty(index, dependencies());
access_info.holder()->GetOwnDictionaryProperty(index, dependencies());
if (!value) return {};
for (Handle<Map> map : access_info.lookup_start_object_maps()) {
for (MapRef map : access_info.lookup_start_object_maps()) {
Handle<Map> map_handle = map.object();
// Non-JSReceivers that passed AccessInfoFactory::ComputePropertyAccessInfo
// must have different lookup start map.
if (!map->IsJSReceiverMap()) {
if (!map_handle->IsJSReceiverMap()) {
// Perform the implicit ToObject for primitives here.
// Implemented according to ES6 section 7.3.2 GetV (V, P).
JSFunction constructor =
Map::GetConstructorFunction(
*map, *broker()->target_native_context().object())
*map_handle, *broker()->target_native_context().object())
.value();
map = MakeRef(broker(), constructor.initial_map()).object();
DCHECK(map->IsJSObjectMap());
map = MakeRef(broker(), constructor.initial_map());
DCHECK(map.object()->IsJSObjectMap());
}
dependencies()->DependOnConstantInDictionaryPrototypeChain(
MakeRef(broker(), map), MakeRef(broker(), access_info.name()),
value.value(), PropertyKind::kData);
map, access_info.name(), value.value(), PropertyKind::kData);
}
return jsgraph()->Constant(value.value());
......@@ -189,9 +184,10 @@ Node* PropertyAccessBuilder::TryFoldLoadConstantDataField(
if (!access_info.IsFastDataConstant()) return nullptr;
// First, determine if we have a constant holder to load from.
Handle<JSObject> holder;
base::Optional<JSObjectRef> holder = access_info.holder();
// If {access_info} has a holder, just use it.
if (!access_info.holder().ToHandle(&holder)) {
if (!holder.has_value()) {
// Otherwise, try to match the {lookup_start_object} as a constant.
HeapObjectMatcher m(lookup_start_object);
if (!m.HasResolvedValue() || !m.Ref(broker()).IsJSObject()) return nullptr;
......@@ -199,26 +195,22 @@ Node* PropertyAccessBuilder::TryFoldLoadConstantDataField(
// Let us make sure the actual map of the constant lookup_start_object is
// among the maps in {access_info}.
MapRef lookup_start_object_map = m.Ref(broker()).map();
if (std::find_if(
access_info.lookup_start_object_maps().begin(),
access_info.lookup_start_object_maps().end(), [&](Handle<Map> map) {
return MakeRef(broker(), map).equals(lookup_start_object_map);
if (std::find_if(access_info.lookup_start_object_maps().begin(),
access_info.lookup_start_object_maps().end(),
[&](MapRef map) {
return map.equals(lookup_start_object_map);
}) == access_info.lookup_start_object_maps().end()) {
// The map of the lookup_start_object is not in the feedback, let us bail
// out.
return nullptr;
}
holder = m.Ref(broker()).AsJSObject().object();
holder = m.Ref(broker()).AsJSObject();
}
JSObjectRef holder_ref = MakeRef(broker(), holder);
base::Optional<ObjectRef> value = holder_ref.GetOwnFastDataProperty(
access_info.field_representation(), access_info.field_index(),
dependencies());
if (!value.has_value()) {
return nullptr;
}
return jsgraph()->Constant(*value);
base::Optional<ObjectRef> value =
holder->GetOwnFastDataProperty(access_info.field_representation(),
access_info.field_index(), dependencies());
return value.has_value() ? jsgraph()->Constant(*value) : nullptr;
}
Node* PropertyAccessBuilder::BuildLoadDataField(NameRef const& name,
......@@ -333,12 +325,11 @@ Node* PropertyAccessBuilder::BuildLoadDataField(
field_representation == MachineRepresentation::kCompressedPointer) {
// Remember the map of the field value, if its map is stable. This is
// used by the LoadElimination to eliminate map checks on the result.
Handle<Map> field_map;
if (access_info.field_map().ToHandle(&field_map)) {
MapRef field_map_ref = MakeRef(broker(), field_map);
if (field_map_ref.is_stable()) {
dependencies()->DependOnStableMap(field_map_ref);
field_access.map = field_map;
base::Optional<MapRef> field_map = access_info.field_map();
if (field_map.has_value()) {
if (field_map->is_stable()) {
dependencies()->DependOnStableMap(field_map.value());
field_access.map = field_map->object();
}
}
}
......
......@@ -36,25 +36,15 @@ class PropertyAccessBuilder {
// Builds the appropriate string check if the maps are only string
// maps.
bool TryBuildStringCheck(JSHeapBroker* broker,
ZoneVector<Handle<Map>> const& maps, Node** receiver,
Node** effect, Node* control);
bool TryBuildStringCheck(JSHeapBroker* broker, ZoneVector<MapRef> const& maps,
Node** receiver, Effect* effect, Control control);
// Builds a number check if all maps are number maps.
bool TryBuildNumberCheck(JSHeapBroker* broker,
ZoneVector<Handle<Map>> const& maps, Node** receiver,
Node** effect, Node* control);
// TODO(jgruber): Remove the untyped version once all uses are
// updated.
void BuildCheckMaps(Node* object, Node** effect, Node* control,
ZoneVector<Handle<Map>> const& maps);
bool TryBuildNumberCheck(JSHeapBroker* broker, ZoneVector<MapRef> const& maps,
Node** receiver, Effect* effect, Control control);
void BuildCheckMaps(Node* object, Effect* effect, Control control,
ZoneVector<Handle<Map>> const& maps) {
Node* e = *effect;
Node* c = control;
BuildCheckMaps(object, &e, c, maps);
*effect = e;
}
ZoneVector<MapRef> const& maps);
Node* BuildCheckValue(Node* receiver, Effect* effect, Control control,
Handle<HeapObject> value);
......@@ -106,8 +96,7 @@ class PropertyAccessBuilder {
CompilationDependencies* dependencies_;
};
bool HasOnlyStringMaps(JSHeapBroker* broker,
ZoneVector<Handle<Map>> const& maps);
bool HasOnlyStringMaps(JSHeapBroker* broker, ZoneVector<MapRef> const& maps);
} // namespace compiler
} // namespace internal
......
......@@ -2613,11 +2613,10 @@ PropertyAccessInfo SerializerForBackgroundCompilation::ProcessMapForRegExpTest(
AccessMode::kLoad, dependencies(),
SerializationPolicy::kSerializeIfNeeded);
Handle<JSObject> holder;
if (ai_exec.IsFastDataConstant() && ai_exec.holder().ToHandle(&holder)) {
base::Optional<JSObjectRef> holder = ai_exec.holder();
if (ai_exec.IsFastDataConstant() && holder.has_value()) {
// The property is on the prototype chain.
JSObjectRef holder_ref = MakeRef(broker(), holder);
holder_ref.GetOwnFastDataProperty(ai_exec.field_representation(),
holder->GetOwnFastDataProperty(ai_exec.field_representation(),
ai_exec.field_index(), nullptr,
SerializationPolicy::kSerializeIfNeeded);
}
......@@ -2632,12 +2631,11 @@ void SerializerForBackgroundCompilation::ProcessHintsForRegExpTest(
Handle<Map> regexp_map(regexp->map(), broker()->isolate());
PropertyAccessInfo ai_exec =
ProcessMapForRegExpTest(MakeRef(broker(), regexp_map));
Handle<JSObject> holder;
if (ai_exec.IsFastDataConstant() && !ai_exec.holder().ToHandle(&holder)) {
base::Optional<JSObjectRef> holder = ai_exec.holder();
if (ai_exec.IsFastDataConstant() && holder.has_value()) {
// The property is on the object itself.
JSObjectRef holder_ref = MakeRef(broker(), regexp);
holder_ref.GetOwnFastDataProperty(
ai_exec.field_representation(), ai_exec.field_index(), nullptr,
holder->GetOwnFastDataProperty(ai_exec.field_representation(),
ai_exec.field_index(), nullptr,
SerializationPolicy::kSerializeIfNeeded);
}
}
......@@ -2927,12 +2925,12 @@ void SerializerForBackgroundCompilation::ProcessMapForNamedPropertyAccess(
// For JSNativeContextSpecialization::InlinePropertySetterCall
// and InlinePropertyGetterCall.
base::Optional<ObjectRef> constant = access_info.constant();
if ((access_info.IsFastAccessorConstant() ||
access_info.IsDictionaryProtoAccessorConstant()) &&
!access_info.constant().is_null()) {
if (access_info.constant()->IsJSFunction()) {
JSFunctionRef function =
MakeRef(broker(), Handle<JSFunction>::cast(access_info.constant()));
constant.has_value()) {
if (constant->IsJSFunction()) {
JSFunctionRef function = constant->AsJSFunction();
if (receiver_map.has_value()) {
// For JSCallReducer and JSInlining(Heuristic).
......@@ -2957,16 +2955,10 @@ void SerializerForBackgroundCompilation::ProcessMapForNamedPropertyAccess(
}
}
}
} else if (access_info.constant()->IsJSBoundFunction()) {
// For JSCallReducer::ReduceJSCall.
MakeRef(broker(), Handle<JSBoundFunction>::cast(access_info.constant()));
} else {
MakeRef(broker(), FunctionTemplateInfo::cast(*access_info.constant()));
}
} else if (access_info.IsModuleExport()) {
// For JSNativeContextSpecialization::BuildPropertyLoad
DCHECK(!access_info.constant().is_null());
MakeRef(broker(), Handle<Cell>::cast(access_info.constant()));
DCHECK(access_info.constant()->IsCell());
}
switch (access_mode) {
......@@ -2975,11 +2967,8 @@ void SerializerForBackgroundCompilation::ProcessMapForNamedPropertyAccess(
// PropertyAccessBuilder::BuildLoadDictPrototypeConstant
if (access_info.IsFastDataConstant() ||
access_info.IsDictionaryProtoDataConstant()) {
base::Optional<JSObjectRef> holder;
Handle<JSObject> prototype;
if (access_info.holder().ToHandle(&prototype)) {
holder = MakeRef(broker(), prototype);
} else {
base::Optional<JSObjectRef> holder = access_info.holder();
if (!holder.has_value()) {
CHECK_IMPLIES(concrete_receiver.has_value(),
concrete_receiver->map().equals(*receiver_map));
holder = concrete_receiver;
......@@ -3004,12 +2993,12 @@ void SerializerForBackgroundCompilation::ProcessMapForNamedPropertyAccess(
case AccessMode::kStoreInLiteral:
// For MapInference (StoreField case).
if (access_info.IsDataField() || access_info.IsFastDataConstant()) {
Handle<Map> transition_map;
if (access_info.transition_map().ToHandle(&transition_map)) {
MapRef map_ref = MakeRef(broker(), transition_map);
base::Optional<MapRef> transition_map = access_info.transition_map();
if (transition_map.has_value()) {
TRACE_BROKER(broker(), "Propagating transition map "
<< map_ref << " to receiver hints.");
receiver->AddMap(transition_map, zone(), broker_, false);
<< transition_map.value()
<< " to receiver hints.");
receiver->AddMap(transition_map->object(), zone(), broker_, false);
}
}
break;
......@@ -3323,10 +3312,10 @@ void SerializerForBackgroundCompilation::ProcessConstantForInstanceOf(
ProcessConstantForOrdinaryHasInstance(constructor_heap_object,
walk_prototypes);
} else if (access_info.IsFastDataConstant()) {
Handle<JSObject> holder;
bool found_on_proto = access_info.holder().ToHandle(&holder);
base::Optional<JSObjectRef> holder = access_info.holder();
bool found_on_proto = holder.has_value();
JSObjectRef holder_ref =
found_on_proto ? MakeRef(broker(), holder) : constructor.AsJSObject();
found_on_proto ? holder.value() : constructor.AsJSObject();
base::Optional<ObjectRef> constant = holder_ref.GetOwnFastDataProperty(
access_info.field_representation(), access_info.field_index(), nullptr,
SerializationPolicy::kSerializeIfNeeded);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment