[super] Optimize super property access in JSNativeContextSpecialization
This is a reland of https://chromium-review.googlesource.com/c/v8/v8/+/2487122 Generalize the existing property lookup machinery (JSNCS::ReduceNamedAccess) to handle the case where the lookup_start_object and the receiver are different objects. Design doc: https://docs.google.com/document/d/1b_wgtExmJDLb8206jpJol-g4vJAxPs1XjEx95hwRboI/edit#heading=h.xqthbgih7l2l Bug: v8:9237 Change-Id: Ia8e79b00f7720f4e3e90801e49a0106e03b4767d Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2523197 Commit-Queue: Marja Hölttä <marja@chromium.org> Reviewed-by: Georg Neis <neis@chromium.org> Cr-Commit-Position: refs/heads/master@{#71052}
This commit is contained in:
parent
3669ecd6e4
commit
30ca51ec48
@ -70,12 +70,13 @@ std::ostream& operator<<(std::ostream& os, AccessMode access_mode) {
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
ElementAccessInfo::ElementAccessInfo(ZoneVector<Handle<Map>>&& receiver_maps,
|
||||
ElementsKind elements_kind, Zone* zone)
|
||||
ElementAccessInfo::ElementAccessInfo(
|
||||
ZoneVector<Handle<Map>>&& lookup_start_object_maps,
|
||||
ElementsKind elements_kind, Zone* zone)
|
||||
: elements_kind_(elements_kind),
|
||||
receiver_maps_(receiver_maps),
|
||||
lookup_start_object_maps_(lookup_start_object_maps),
|
||||
transition_sources_(zone) {
|
||||
CHECK(!receiver_maps.empty());
|
||||
CHECK(!lookup_start_object_maps.empty());
|
||||
}
|
||||
|
||||
// static
|
||||
@ -158,27 +159,26 @@ MinimorphicLoadPropertyAccessInfo MinimorphicLoadPropertyAccessInfo::Invalid() {
|
||||
|
||||
PropertyAccessInfo::PropertyAccessInfo(Zone* zone)
|
||||
: kind_(kInvalid),
|
||||
receiver_maps_(zone),
|
||||
lookup_start_object_maps_(zone),
|
||||
unrecorded_dependencies_(zone),
|
||||
field_representation_(Representation::None()),
|
||||
field_type_(Type::None()) {}
|
||||
|
||||
PropertyAccessInfo::PropertyAccessInfo(Zone* zone, Kind kind,
|
||||
MaybeHandle<JSObject> holder,
|
||||
ZoneVector<Handle<Map>>&& receiver_maps)
|
||||
PropertyAccessInfo::PropertyAccessInfo(
|
||||
Zone* zone, Kind kind, MaybeHandle<JSObject> holder,
|
||||
ZoneVector<Handle<Map>>&& lookup_start_object_maps)
|
||||
: kind_(kind),
|
||||
receiver_maps_(receiver_maps),
|
||||
lookup_start_object_maps_(lookup_start_object_maps),
|
||||
unrecorded_dependencies_(zone),
|
||||
holder_(holder),
|
||||
field_representation_(Representation::None()),
|
||||
field_type_(Type::None()) {}
|
||||
|
||||
PropertyAccessInfo::PropertyAccessInfo(Zone* zone, Kind kind,
|
||||
MaybeHandle<JSObject> holder,
|
||||
Handle<Object> constant,
|
||||
ZoneVector<Handle<Map>>&& receiver_maps)
|
||||
PropertyAccessInfo::PropertyAccessInfo(
|
||||
Zone* zone, Kind kind, MaybeHandle<JSObject> holder,
|
||||
Handle<Object> constant, ZoneVector<Handle<Map>>&& lookup_start_object_maps)
|
||||
: kind_(kind),
|
||||
receiver_maps_(receiver_maps),
|
||||
lookup_start_object_maps_(lookup_start_object_maps),
|
||||
unrecorded_dependencies_(zone),
|
||||
constant_(constant),
|
||||
holder_(holder),
|
||||
@ -189,10 +189,10 @@ 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>>&& receiver_maps,
|
||||
ZoneVector<Handle<Map>>&& lookup_start_object_maps,
|
||||
ZoneVector<CompilationDependency const*>&& unrecorded_dependencies)
|
||||
: kind_(kind),
|
||||
receiver_maps_(receiver_maps),
|
||||
lookup_start_object_maps_(lookup_start_object_maps),
|
||||
unrecorded_dependencies_(std::move(unrecorded_dependencies)),
|
||||
transition_map_(transition_map),
|
||||
holder_(holder),
|
||||
@ -265,9 +265,10 @@ bool PropertyAccessInfo::Merge(PropertyAccessInfo const* that,
|
||||
}
|
||||
this->field_type_ =
|
||||
Type::Union(this->field_type_, that->field_type_, zone);
|
||||
this->receiver_maps_.insert(this->receiver_maps_.end(),
|
||||
that->receiver_maps_.begin(),
|
||||
that->receiver_maps_.end());
|
||||
this->lookup_start_object_maps_.insert(
|
||||
this->lookup_start_object_maps_.end(),
|
||||
that->lookup_start_object_maps_.begin(),
|
||||
that->lookup_start_object_maps_.end());
|
||||
this->unrecorded_dependencies_.insert(
|
||||
this->unrecorded_dependencies_.end(),
|
||||
that->unrecorded_dependencies_.begin(),
|
||||
@ -282,9 +283,10 @@ bool PropertyAccessInfo::Merge(PropertyAccessInfo const* that,
|
||||
if (this->constant_.address() == that->constant_.address()) {
|
||||
DCHECK(this->unrecorded_dependencies_.empty());
|
||||
DCHECK(that->unrecorded_dependencies_.empty());
|
||||
this->receiver_maps_.insert(this->receiver_maps_.end(),
|
||||
that->receiver_maps_.begin(),
|
||||
that->receiver_maps_.end());
|
||||
this->lookup_start_object_maps_.insert(
|
||||
this->lookup_start_object_maps_.end(),
|
||||
that->lookup_start_object_maps_.begin(),
|
||||
that->lookup_start_object_maps_.end());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@ -294,9 +296,10 @@ bool PropertyAccessInfo::Merge(PropertyAccessInfo const* that,
|
||||
case kStringLength: {
|
||||
DCHECK(this->unrecorded_dependencies_.empty());
|
||||
DCHECK(that->unrecorded_dependencies_.empty());
|
||||
this->receiver_maps_.insert(this->receiver_maps_.end(),
|
||||
that->receiver_maps_.begin(),
|
||||
that->receiver_maps_.end());
|
||||
this->lookup_start_object_maps_.insert(
|
||||
this->lookup_start_object_maps_.end(),
|
||||
that->lookup_start_object_maps_.begin(),
|
||||
that->lookup_start_object_maps_.end());
|
||||
return true;
|
||||
}
|
||||
case kModuleExport:
|
||||
|
@ -37,25 +37,25 @@ 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>>&& receiver_maps,
|
||||
ElementAccessInfo(ZoneVector<Handle<Map>>&& lookup_start_object_maps,
|
||||
ElementsKind elements_kind, Zone* zone);
|
||||
|
||||
ElementsKind elements_kind() const { return elements_kind_; }
|
||||
ZoneVector<Handle<Map>> const& receiver_maps() const {
|
||||
return receiver_maps_;
|
||||
ZoneVector<Handle<Map>> const& lookup_start_object_maps() const {
|
||||
return lookup_start_object_maps_;
|
||||
}
|
||||
ZoneVector<Handle<Map>> const& transition_sources() const {
|
||||
return transition_sources_;
|
||||
}
|
||||
|
||||
void AddTransitionSource(Handle<Map> map) {
|
||||
CHECK_EQ(receiver_maps_.size(), 1);
|
||||
CHECK_EQ(lookup_start_object_maps_.size(), 1);
|
||||
transition_sources_.push_back(map);
|
||||
}
|
||||
|
||||
private:
|
||||
ElementsKind elements_kind_;
|
||||
ZoneVector<Handle<Map>> receiver_maps_;
|
||||
ZoneVector<Handle<Map>> lookup_start_object_maps_;
|
||||
ZoneVector<Handle<Map>> transition_sources_;
|
||||
};
|
||||
|
||||
@ -128,26 +128,26 @@ class PropertyAccessInfo final {
|
||||
Type field_type() const { return field_type_; }
|
||||
Representation field_representation() const { return field_representation_; }
|
||||
MaybeHandle<Map> field_map() const { return field_map_; }
|
||||
ZoneVector<Handle<Map>> const& receiver_maps() const {
|
||||
return receiver_maps_;
|
||||
ZoneVector<Handle<Map>> const& lookup_start_object_maps() const {
|
||||
return lookup_start_object_maps_;
|
||||
}
|
||||
|
||||
private:
|
||||
explicit PropertyAccessInfo(Zone* zone);
|
||||
PropertyAccessInfo(Zone* zone, Kind kind, MaybeHandle<JSObject> holder,
|
||||
ZoneVector<Handle<Map>>&& receiver_maps);
|
||||
ZoneVector<Handle<Map>>&& lookup_start_object_maps);
|
||||
PropertyAccessInfo(Zone* zone, Kind kind, MaybeHandle<JSObject> holder,
|
||||
Handle<Object> constant,
|
||||
ZoneVector<Handle<Map>>&& receiver_maps);
|
||||
ZoneVector<Handle<Map>>&& lookup_start_object_maps);
|
||||
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>>&& receiver_maps,
|
||||
ZoneVector<Handle<Map>>&& lookup_start_object_maps,
|
||||
ZoneVector<CompilationDependency const*>&& dependencies);
|
||||
|
||||
Kind kind_;
|
||||
ZoneVector<Handle<Map>> receiver_maps_;
|
||||
ZoneVector<Handle<Map>> lookup_start_object_maps_;
|
||||
ZoneVector<CompilationDependency const*> unrecorded_dependencies_;
|
||||
Handle<Object> constant_;
|
||||
MaybeHandle<Map> transition_map_;
|
||||
|
@ -264,7 +264,7 @@ class BytecodeGraphBuilder {
|
||||
const Operator* op, Node* receiver, FeedbackSlot load_slot,
|
||||
FeedbackSlot call_slot);
|
||||
JSTypeHintLowering::LoweringResult TryBuildSimplifiedLoadNamed(
|
||||
const Operator* op, Node* receiver, FeedbackSlot slot);
|
||||
const Operator* op, FeedbackSlot slot);
|
||||
JSTypeHintLowering::LoweringResult TryBuildSimplifiedLoadKeyed(
|
||||
const Operator* op, Node* receiver, Node* key, FeedbackSlot slot);
|
||||
JSTypeHintLowering::LoweringResult TryBuildSimplifiedStoreNamed(
|
||||
@ -2019,7 +2019,7 @@ void BytecodeGraphBuilder::VisitLdaNamedProperty() {
|
||||
const Operator* op = javascript()->LoadNamed(name.object(), feedback);
|
||||
|
||||
JSTypeHintLowering::LoweringResult lowering =
|
||||
TryBuildSimplifiedLoadNamed(op, object, feedback.slot);
|
||||
TryBuildSimplifiedLoadNamed(op, feedback.slot);
|
||||
if (lowering.IsExit()) return;
|
||||
|
||||
Node* node = nullptr;
|
||||
@ -2052,10 +2052,24 @@ void BytecodeGraphBuilder::VisitLdaNamedPropertyFromSuper() {
|
||||
Node* home_object = environment()->LookupAccumulator();
|
||||
NameRef name(broker(),
|
||||
bytecode_iterator().GetConstantForIndexOperand(1, isolate()));
|
||||
const Operator* op = javascript()->LoadNamedFromSuper(name.object());
|
||||
// TODO(marja, v8:9237): Use lowering.
|
||||
|
||||
Node* node = NewNode(op, receiver, home_object);
|
||||
FeedbackSource feedback =
|
||||
CreateFeedbackSource(bytecode_iterator().GetIndexOperand(2));
|
||||
const Operator* op =
|
||||
javascript()->LoadNamedFromSuper(name.object(), feedback);
|
||||
|
||||
JSTypeHintLowering::LoweringResult lowering =
|
||||
TryBuildSimplifiedLoadNamed(op, feedback.slot);
|
||||
if (lowering.IsExit()) return;
|
||||
|
||||
Node* node = nullptr;
|
||||
if (lowering.IsSideEffectFree()) {
|
||||
node = lowering.value();
|
||||
} else {
|
||||
DCHECK(!lowering.Changed());
|
||||
DCHECK(IrOpcode::IsFeedbackCollectingOpcode(op->opcode()));
|
||||
node = NewNode(op, receiver, home_object, feedback_vector_node());
|
||||
}
|
||||
environment()->BindAccumulator(node, Environment::kAttachFrameState);
|
||||
}
|
||||
|
||||
@ -4222,14 +4236,12 @@ BytecodeGraphBuilder::TryBuildSimplifiedGetIterator(const Operator* op,
|
||||
|
||||
JSTypeHintLowering::LoweringResult
|
||||
BytecodeGraphBuilder::TryBuildSimplifiedLoadNamed(const Operator* op,
|
||||
Node* receiver,
|
||||
FeedbackSlot slot) {
|
||||
if (!CanApplyTypeHintLowering(op)) return NoChange();
|
||||
Node* effect = environment()->GetEffectDependency();
|
||||
Node* control = environment()->GetControlDependency();
|
||||
JSTypeHintLowering::LoweringResult early_reduction =
|
||||
type_hint_lowering().ReduceLoadNamedOperation(op, receiver, effect,
|
||||
control, slot);
|
||||
type_hint_lowering().ReduceLoadNamedOperation(op, effect, control, slot);
|
||||
ApplyEarlyReduction(early_reduction);
|
||||
return early_reduction;
|
||||
}
|
||||
|
@ -7648,7 +7648,7 @@ Reduction JSCallReducer::ReduceRegExpPrototypeTest(Node* node) {
|
||||
|
||||
// Add proper dependencies on the {regexp}s [[Prototype]]s.
|
||||
dependencies()->DependOnStablePrototypeChains(
|
||||
ai_exec.receiver_maps(), kStartAtPrototype,
|
||||
ai_exec.lookup_start_object_maps(), kStartAtPrototype,
|
||||
JSObjectRef(broker(), holder));
|
||||
} else {
|
||||
return inference.NoChange();
|
||||
|
@ -318,8 +318,10 @@ void JSGenericLowering::LowerJSLoadNamed(Node* node) {
|
||||
}
|
||||
|
||||
void JSGenericLowering::LowerJSLoadNamedFromSuper(Node* node) {
|
||||
// TODO(marja, v8:9237): Call a builtin which collects feedback.
|
||||
JSLoadNamedFromSuperNode n(node);
|
||||
NamedAccess const& p = n.Parameters();
|
||||
node->RemoveInput(2); // Feedback vector
|
||||
node->InsertInput(zone(), 2, jsgraph()->HeapConstant(p.name()));
|
||||
ReplaceWithRuntimeCall(node, Runtime::kLoadFromSuper);
|
||||
}
|
||||
|
@ -172,10 +172,12 @@ Reduction JSHeapCopyReducer::Reduce(Node* node) {
|
||||
break;
|
||||
}
|
||||
case IrOpcode::kJSLoadNamedFromSuper: {
|
||||
// TODO(marja, v8:9237): Process feedback once it's added to the byte
|
||||
// code.
|
||||
NamedAccess const& p = NamedAccessOf(node->op());
|
||||
NameRef name(broker(), p.name());
|
||||
if (p.feedback().IsValid()) {
|
||||
broker()->ProcessFeedbackForPropertyAccess(p.feedback(),
|
||||
AccessMode::kLoad, name);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case IrOpcode::kJSStoreNamed: {
|
||||
|
@ -102,6 +102,8 @@ Reduction JSNativeContextSpecialization::Reduce(Node* node) {
|
||||
return ReduceJSStoreGlobal(node);
|
||||
case IrOpcode::kJSLoadNamed:
|
||||
return ReduceJSLoadNamed(node);
|
||||
case IrOpcode::kJSLoadNamedFromSuper:
|
||||
return ReduceJSLoadNamedFromSuper(node);
|
||||
case IrOpcode::kJSStoreNamed:
|
||||
return ReduceJSStoreNamed(node);
|
||||
case IrOpcode::kJSHasProperty:
|
||||
@ -432,12 +434,12 @@ Reduction JSNativeContextSpecialization::ReduceJSInstanceOf(Node* node) {
|
||||
// takes over, but that requires the constructor to be callable.
|
||||
if (!receiver_map.is_callable()) return NoChange();
|
||||
|
||||
dependencies()->DependOnStablePrototypeChains(access_info.receiver_maps(),
|
||||
kStartAtPrototype);
|
||||
dependencies()->DependOnStablePrototypeChains(
|
||||
access_info.lookup_start_object_maps(), kStartAtPrototype);
|
||||
|
||||
// Monomorphic property access.
|
||||
access_builder.BuildCheckMaps(constructor, &effect, control,
|
||||
access_info.receiver_maps());
|
||||
access_info.lookup_start_object_maps());
|
||||
|
||||
// Lower to OrdinaryHasInstance(C, O).
|
||||
NodeProperties::ReplaceValueInput(node, constructor, 0);
|
||||
@ -462,7 +464,7 @@ Reduction JSNativeContextSpecialization::ReduceJSInstanceOf(Node* node) {
|
||||
|
||||
if (found_on_proto) {
|
||||
dependencies()->DependOnStablePrototypeChains(
|
||||
access_info.receiver_maps(), kStartAtPrototype,
|
||||
access_info.lookup_start_object_maps(), kStartAtPrototype,
|
||||
JSObjectRef(broker(), holder));
|
||||
}
|
||||
|
||||
@ -472,7 +474,7 @@ Reduction JSNativeContextSpecialization::ReduceJSInstanceOf(Node* node) {
|
||||
|
||||
// Monomorphic property access.
|
||||
access_builder.BuildCheckMaps(constructor, &effect, control,
|
||||
access_info.receiver_maps());
|
||||
access_info.lookup_start_object_maps());
|
||||
|
||||
// Create a nested frame state inside the current method's most-recent frame
|
||||
// state that will ensure that deopts that happen after this point will not
|
||||
@ -521,10 +523,9 @@ JSNativeContextSpecialization::InferHasInPrototypeChainResult
|
||||
JSNativeContextSpecialization::InferHasInPrototypeChain(
|
||||
Node* receiver, Node* effect, HeapObjectRef const& prototype) {
|
||||
ZoneHandleSet<Map> receiver_maps;
|
||||
NodeProperties::InferReceiverMapsResult result =
|
||||
NodeProperties::InferReceiverMapsUnsafe(broker(), receiver, effect,
|
||||
&receiver_maps);
|
||||
if (result == NodeProperties::kNoReceiverMaps) return kMayBeInPrototypeChain;
|
||||
NodeProperties::InferMapsResult result = NodeProperties::InferMapsUnsafe(
|
||||
broker(), receiver, effect, &receiver_maps);
|
||||
if (result == NodeProperties::kNoMaps) return kMayBeInPrototypeChain;
|
||||
|
||||
// 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
|
||||
@ -533,7 +534,7 @@ JSNativeContextSpecialization::InferHasInPrototypeChain(
|
||||
bool none = true;
|
||||
for (size_t i = 0; i < receiver_maps.size(); ++i) {
|
||||
MapRef map(broker(), receiver_maps[i]);
|
||||
if (result == NodeProperties::kUnreliableReceiverMaps && !map.is_stable()) {
|
||||
if (result == NodeProperties::kUnreliableMaps && !map.is_stable()) {
|
||||
return kMayBeInPrototypeChain;
|
||||
}
|
||||
while (true) {
|
||||
@ -575,7 +576,7 @@ JSNativeContextSpecialization::InferHasInPrototypeChain(
|
||||
if (!prototype.map().is_stable()) return kMayBeInPrototypeChain;
|
||||
last_prototype = prototype.AsJSObject();
|
||||
}
|
||||
WhereToStart start = result == NodeProperties::kUnreliableReceiverMaps
|
||||
WhereToStart start = result == NodeProperties::kUnreliableMaps
|
||||
? kStartAtReceiver
|
||||
: kStartAtPrototype;
|
||||
dependencies()->DependOnStablePrototypeChains(receiver_maps, start,
|
||||
@ -749,8 +750,8 @@ Reduction JSNativeContextSpecialization::ReduceJSResolvePromise(Node* node) {
|
||||
return inference.NoChange();
|
||||
}
|
||||
|
||||
dependencies()->DependOnStablePrototypeChains(access_info.receiver_maps(),
|
||||
kStartAtPrototype);
|
||||
dependencies()->DependOnStablePrototypeChains(
|
||||
access_info.lookup_start_object_maps(), kStartAtPrototype);
|
||||
|
||||
// Simply fulfill the {promise} with the {resolution}.
|
||||
Node* value = effect =
|
||||
@ -781,23 +782,30 @@ FieldAccess ForPropertyCellValue(MachineRepresentation representation,
|
||||
} // namespace
|
||||
|
||||
Reduction JSNativeContextSpecialization::ReduceGlobalAccess(
|
||||
Node* node, Node* receiver, Node* value, NameRef const& name,
|
||||
AccessMode access_mode, Node* key) {
|
||||
Node* node, Node* lookup_start_object, Node* receiver, Node* value,
|
||||
NameRef const& name, AccessMode access_mode, Node* key, Node* effect) {
|
||||
base::Optional<PropertyCellRef> cell =
|
||||
native_context().global_object().GetPropertyCell(name);
|
||||
return cell.has_value() ? ReduceGlobalAccess(node, receiver, value, name,
|
||||
access_mode, key, *cell)
|
||||
: NoChange();
|
||||
return cell.has_value()
|
||||
? ReduceGlobalAccess(node, lookup_start_object, receiver, value,
|
||||
name, access_mode, key, *cell, effect)
|
||||
: NoChange();
|
||||
}
|
||||
|
||||
// TODO(neis): Try to merge this with ReduceNamedAccess by introducing a new
|
||||
// PropertyAccessInfo kind for global accesses and using the existing mechanism
|
||||
// for building loads/stores.
|
||||
// Note: The "receiver" parameter is only used for DCHECKS, but that's on
|
||||
// purpose. This way we can assert the super property access cases won't hit the
|
||||
// code which hasn't been modified to support super property access.
|
||||
Reduction JSNativeContextSpecialization::ReduceGlobalAccess(
|
||||
Node* node, Node* receiver, Node* value, NameRef const& name,
|
||||
AccessMode access_mode, Node* key, PropertyCellRef const& property_cell) {
|
||||
Node* effect = NodeProperties::GetEffectInput(node);
|
||||
Node* node, Node* lookup_start_object, Node* receiver, Node* value,
|
||||
NameRef const& name, AccessMode access_mode, Node* key,
|
||||
PropertyCellRef const& property_cell, Node* effect) {
|
||||
Node* control = NodeProperties::GetControlInput(node);
|
||||
if (effect == nullptr) {
|
||||
effect = NodeProperties::GetEffectInput(node);
|
||||
}
|
||||
|
||||
ObjectRef property_cell_value = property_cell.value();
|
||||
if (property_cell_value.IsHeapObject() &&
|
||||
@ -813,6 +821,7 @@ Reduction JSNativeContextSpecialization::ReduceGlobalAccess(
|
||||
|
||||
// We have additional constraints for stores.
|
||||
if (access_mode == AccessMode::kStore) {
|
||||
DCHECK_EQ(receiver, lookup_start_object);
|
||||
if (property_details.IsReadOnly()) {
|
||||
// Don't even bother trying to lower stores to read-only data properties.
|
||||
return NoChange();
|
||||
@ -828,6 +837,7 @@ Reduction JSNativeContextSpecialization::ReduceGlobalAccess(
|
||||
}
|
||||
}
|
||||
} else if (access_mode == AccessMode::kHas) {
|
||||
DCHECK_EQ(receiver, lookup_start_object);
|
||||
// has checks cannot follow the fast-path used by loads when these
|
||||
// conditions hold.
|
||||
if ((property_details.IsConfigurable() || !property_details.IsReadOnly()) &&
|
||||
@ -841,16 +851,16 @@ Reduction JSNativeContextSpecialization::ReduceGlobalAccess(
|
||||
effect = BuildCheckEqualsName(name, key, effect, control);
|
||||
}
|
||||
|
||||
// If we have a {receiver} to validate, we do so by checking that its map is
|
||||
// the (target) global proxy's map. This guarantees that in fact the receiver
|
||||
// is the global proxy.
|
||||
if (receiver != nullptr) {
|
||||
// If we have a {lookup_start_object} to validate, we do so by checking that
|
||||
// its map is the (target) global proxy's map. This guarantees that in fact
|
||||
// the lookup start object is the global proxy.
|
||||
if (lookup_start_object != nullptr) {
|
||||
effect = graph()->NewNode(
|
||||
simplified()->CheckMaps(
|
||||
CheckMapsFlag::kNone,
|
||||
ZoneHandleSet<Map>(
|
||||
HeapObjectRef(broker(), global_proxy()).map().object())),
|
||||
receiver, effect, control);
|
||||
lookup_start_object, effect, control);
|
||||
}
|
||||
|
||||
if (access_mode == AccessMode::kLoad || access_mode == AccessMode::kHas) {
|
||||
@ -916,6 +926,7 @@ Reduction JSNativeContextSpecialization::ReduceGlobalAccess(
|
||||
}
|
||||
} else {
|
||||
DCHECK_EQ(AccessMode::kStore, access_mode);
|
||||
DCHECK_EQ(receiver, lookup_start_object);
|
||||
DCHECK(!property_details.IsReadOnly());
|
||||
switch (property_details.cell_type()) {
|
||||
case PropertyCellType::kUndefined: {
|
||||
@ -1012,7 +1023,7 @@ Reduction JSNativeContextSpecialization::ReduceJSLoadGlobal(Node* node) {
|
||||
ReplaceWithValue(node, value, effect);
|
||||
return Replace(value);
|
||||
} else if (feedback.IsPropertyCell()) {
|
||||
return ReduceGlobalAccess(node, nullptr, nullptr,
|
||||
return ReduceGlobalAccess(node, nullptr, nullptr, nullptr,
|
||||
NameRef(broker(), p.name()), AccessMode::kLoad,
|
||||
nullptr, feedback.property_cell());
|
||||
} else {
|
||||
@ -1043,9 +1054,9 @@ Reduction JSNativeContextSpecialization::ReduceJSStoreGlobal(Node* node) {
|
||||
ReplaceWithValue(node, value, effect, control);
|
||||
return Replace(value);
|
||||
} else if (feedback.IsPropertyCell()) {
|
||||
return ReduceGlobalAccess(node, nullptr, value, NameRef(broker(), p.name()),
|
||||
AccessMode::kStore, nullptr,
|
||||
feedback.property_cell());
|
||||
return ReduceGlobalAccess(node, nullptr, nullptr, value,
|
||||
NameRef(broker(), p.name()), AccessMode::kStore,
|
||||
nullptr, feedback.property_cell());
|
||||
} else {
|
||||
DCHECK(feedback.IsMegamorphic());
|
||||
return NoChange();
|
||||
@ -1056,10 +1067,26 @@ Reduction JSNativeContextSpecialization::ReduceMinimorphicPropertyAccess(
|
||||
Node* node, Node* value,
|
||||
MinimorphicLoadPropertyAccessFeedback const& feedback,
|
||||
FeedbackSource const& source) {
|
||||
Node* receiver = NodeProperties::GetValueInput(node, 0);
|
||||
DCHECK(node->opcode() == IrOpcode::kJSLoadNamed ||
|
||||
node->opcode() == IrOpcode::kJSLoadProperty ||
|
||||
node->opcode() == IrOpcode::kJSLoadNamedFromSuper);
|
||||
STATIC_ASSERT(JSLoadNamedNode::ObjectIndex() == 0 &&
|
||||
JSLoadPropertyNode::ObjectIndex() == 0);
|
||||
|
||||
Node* effect = NodeProperties::GetEffectInput(node);
|
||||
Node* control = NodeProperties::GetControlInput(node);
|
||||
|
||||
Node* lookup_start_object;
|
||||
if (node->opcode() == IrOpcode::kJSLoadNamedFromSuper) {
|
||||
DCHECK(FLAG_super_ic);
|
||||
JSLoadNamedFromSuperNode n(node);
|
||||
// Lookup start object is the __proto__ of the home object.
|
||||
lookup_start_object = effect =
|
||||
BuildLoadPrototypeFromObject(n.home_object(), effect, control);
|
||||
} else {
|
||||
lookup_start_object = NodeProperties::GetValueInput(node, 0);
|
||||
}
|
||||
|
||||
MinimorphicLoadPropertyAccessInfo access_info =
|
||||
broker()->GetPropertyAccessInfo(
|
||||
feedback, source,
|
||||
@ -1091,9 +1118,9 @@ Reduction JSNativeContextSpecialization::ReduceMinimorphicPropertyAccess(
|
||||
|
||||
effect = graph()->NewNode(
|
||||
simplified()->DynamicCheckMaps(flags, feedback.handler(), maps, source),
|
||||
receiver, effect, control);
|
||||
lookup_start_object, effect, control);
|
||||
value = access_builder.BuildMinimorphicLoadDataField(
|
||||
feedback.name(), access_info, receiver, &effect, &control);
|
||||
feedback.name(), access_info, lookup_start_object, &effect, &control);
|
||||
|
||||
ReplaceWithValue(node, value, effect, control);
|
||||
return Replace(value);
|
||||
@ -1108,7 +1135,8 @@ Reduction JSNativeContextSpecialization::ReduceNamedAccess(
|
||||
node->opcode() == IrOpcode::kJSStoreProperty ||
|
||||
node->opcode() == IrOpcode::kJSStoreNamedOwn ||
|
||||
node->opcode() == IrOpcode::kJSStoreDataPropertyInLiteral ||
|
||||
node->opcode() == IrOpcode::kJSHasProperty);
|
||||
node->opcode() == IrOpcode::kJSHasProperty ||
|
||||
node->opcode() == IrOpcode::kJSLoadNamedFromSuper);
|
||||
STATIC_ASSERT(JSLoadNamedNode::ObjectIndex() == 0 &&
|
||||
JSStoreNamedNode::ObjectIndex() == 0 &&
|
||||
JSLoadPropertyNode::ObjectIndex() == 0 &&
|
||||
@ -1117,36 +1145,51 @@ Reduction JSNativeContextSpecialization::ReduceNamedAccess(
|
||||
JSStoreNamedNode::ObjectIndex() == 0 &&
|
||||
JSStoreDataPropertyInLiteralNode::ObjectIndex() == 0 &&
|
||||
JSHasPropertyNode::ObjectIndex() == 0);
|
||||
Node* receiver = NodeProperties::GetValueInput(node, 0);
|
||||
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);
|
||||
|
||||
// Either infer maps from the graph or use the feedback.
|
||||
ZoneVector<Handle<Map>> receiver_maps(zone());
|
||||
if (!InferReceiverMaps(receiver, effect, &receiver_maps)) {
|
||||
receiver_maps = feedback.maps();
|
||||
// receiver = the object we pass to the accessor (if any) as the "this" value.
|
||||
Node* receiver = NodeProperties::GetValueInput(node, 0);
|
||||
// lookup_start_object = the object where we start looking for the property.
|
||||
Node* lookup_start_object;
|
||||
if (node->opcode() == IrOpcode::kJSLoadNamedFromSuper) {
|
||||
DCHECK(FLAG_super_ic);
|
||||
JSLoadNamedFromSuperNode n(node);
|
||||
// Lookup start object is the __proto__ of the home object.
|
||||
lookup_start_object = effect =
|
||||
BuildLoadPrototypeFromObject(n.home_object(), effect, control);
|
||||
} else {
|
||||
lookup_start_object = receiver;
|
||||
}
|
||||
RemoveImpossibleReceiverMaps(receiver, &receiver_maps);
|
||||
|
||||
// Either infer maps from the graph or use the feedback.
|
||||
ZoneVector<Handle<Map>> lookup_start_object_maps(zone());
|
||||
if (!InferMaps(lookup_start_object, effect, &lookup_start_object_maps)) {
|
||||
lookup_start_object_maps = feedback.maps();
|
||||
}
|
||||
RemoveImpossibleMaps(lookup_start_object, &lookup_start_object_maps);
|
||||
|
||||
// Check if we have an access o.x or o.x=v where o is the target native
|
||||
// contexts' global proxy, and turn that into a direct access to the
|
||||
// corresponding global object instead.
|
||||
if (receiver_maps.size() == 1) {
|
||||
MapRef receiver_map(broker(), receiver_maps[0]);
|
||||
if (receiver_map.equals(
|
||||
if (lookup_start_object_maps.size() == 1) {
|
||||
MapRef lookup_start_object_map(broker(), lookup_start_object_maps[0]);
|
||||
if (lookup_start_object_map.equals(
|
||||
broker()->target_native_context().global_proxy_object().map()) &&
|
||||
!broker()->target_native_context().global_object().IsDetached()) {
|
||||
return ReduceGlobalAccess(node, receiver, value, feedback.name(),
|
||||
access_mode, key);
|
||||
return ReduceGlobalAccess(node, lookup_start_object, receiver, value,
|
||||
feedback.name(), access_mode, key, effect);
|
||||
}
|
||||
}
|
||||
|
||||
ZoneVector<PropertyAccessInfo> access_infos(zone());
|
||||
{
|
||||
ZoneVector<PropertyAccessInfo> access_infos_for_feedback(zone());
|
||||
for (Handle<Map> map_handle : receiver_maps) {
|
||||
for (Handle<Map> map_handle : lookup_start_object_maps) {
|
||||
MapRef map(broker(), map_handle);
|
||||
if (map.is_deprecated()) continue;
|
||||
PropertyAccessInfo access_info = broker()->GetPropertyAccessInfo(
|
||||
@ -1183,15 +1226,26 @@ Reduction JSNativeContextSpecialization::ReduceNamedAccess(
|
||||
// Check for the monomorphic cases.
|
||||
if (access_infos.size() == 1) {
|
||||
PropertyAccessInfo access_info = access_infos.front();
|
||||
// Try to build string check or number check if possible.
|
||||
// Otherwise build a map check.
|
||||
if (!access_builder.TryBuildStringCheck(broker(),
|
||||
access_info.receiver_maps(),
|
||||
&receiver, &effect, control) &&
|
||||
!access_builder.TryBuildNumberCheck(broker(),
|
||||
access_info.receiver_maps(),
|
||||
&receiver, &effect, control)) {
|
||||
if (HasNumberMaps(broker(), access_info.receiver_maps())) {
|
||||
if (receiver != lookup_start_object) {
|
||||
// Super property access. lookup_start_object is a JSReceiver or
|
||||
// null. It can't be a number, a string etc. So trying to build the
|
||||
// checks in the "else if" branch doesn't make sense.
|
||||
access_builder.BuildCheckMaps(lookup_start_object, &effect, control,
|
||||
access_info.lookup_start_object_maps());
|
||||
|
||||
} else if (!access_builder.TryBuildStringCheck(
|
||||
broker(), access_info.lookup_start_object_maps(), &receiver,
|
||||
&effect, control) &&
|
||||
!access_builder.TryBuildNumberCheck(
|
||||
broker(), access_info.lookup_start_object_maps(), &receiver,
|
||||
&effect, control)) {
|
||||
// Try to build string check or number check if possible. Otherwise build
|
||||
// a map check.
|
||||
|
||||
// TryBuildStringCheck and TryBuildNumberCheck don't update the receiver
|
||||
// if they fail.
|
||||
DCHECK_EQ(receiver, lookup_start_object);
|
||||
if (HasNumberMaps(broker(), access_info.lookup_start_object_maps())) {
|
||||
// We need to also let Smi {receiver}s through in this case, so
|
||||
// we construct a diamond, guarded by the Sminess of the {receiver}
|
||||
// and if {receiver} is not a Smi just emit a sequence of map checks.
|
||||
@ -1205,7 +1259,7 @@ Reduction JSNativeContextSpecialization::ReduceNamedAccess(
|
||||
Node* efalse = effect;
|
||||
{
|
||||
access_builder.BuildCheckMaps(receiver, &efalse, if_false,
|
||||
access_info.receiver_maps());
|
||||
access_info.lookup_start_object_maps());
|
||||
}
|
||||
|
||||
control = graph()->NewNode(common()->Merge(2), if_true, if_false);
|
||||
@ -1213,14 +1267,19 @@ Reduction JSNativeContextSpecialization::ReduceNamedAccess(
|
||||
graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control);
|
||||
} else {
|
||||
access_builder.BuildCheckMaps(receiver, &effect, control,
|
||||
access_info.receiver_maps());
|
||||
access_info.lookup_start_object_maps());
|
||||
}
|
||||
} else {
|
||||
// At least one of TryBuildStringCheck & TryBuildNumberCheck succeeded
|
||||
// and updated the receiver. Update lookup_start_object to match (they
|
||||
// should be the same).
|
||||
lookup_start_object = receiver;
|
||||
}
|
||||
|
||||
// Generate the actual property access.
|
||||
ValueEffectControl continuation = BuildPropertyAccess(
|
||||
receiver, value, context, frame_state, effect, control, feedback.name(),
|
||||
if_exceptions, access_info, access_mode);
|
||||
lookup_start_object, receiver, value, context, frame_state, effect,
|
||||
control, feedback.name(), if_exceptions, access_info, access_mode);
|
||||
value = continuation.value();
|
||||
effect = continuation.effect();
|
||||
control = continuation.control();
|
||||
@ -1231,24 +1290,27 @@ Reduction JSNativeContextSpecialization::ReduceNamedAccess(
|
||||
ZoneVector<Node*> effects(zone());
|
||||
ZoneVector<Node*> controls(zone());
|
||||
|
||||
// Check if {receiver} may be a number.
|
||||
bool receiverissmi_possible = false;
|
||||
for (PropertyAccessInfo const& access_info : access_infos) {
|
||||
if (HasNumberMaps(broker(), access_info.receiver_maps())) {
|
||||
receiverissmi_possible = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle the case that {receiver} may be a number.
|
||||
Node* receiverissmi_control = nullptr;
|
||||
Node* receiverissmi_effect = effect;
|
||||
if (receiverissmi_possible) {
|
||||
Node* check = graph()->NewNode(simplified()->ObjectIsSmi(), receiver);
|
||||
Node* branch = graph()->NewNode(common()->Branch(), check, control);
|
||||
control = graph()->NewNode(common()->IfFalse(), branch);
|
||||
receiverissmi_control = graph()->NewNode(common()->IfTrue(), branch);
|
||||
receiverissmi_effect = effect;
|
||||
|
||||
if (receiver == lookup_start_object) {
|
||||
// Check if {receiver} may be a number.
|
||||
bool receiverissmi_possible = false;
|
||||
for (PropertyAccessInfo const& access_info : access_infos) {
|
||||
if (HasNumberMaps(broker(), access_info.lookup_start_object_maps())) {
|
||||
receiverissmi_possible = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle the case that {receiver} may be a number.
|
||||
if (receiverissmi_possible) {
|
||||
Node* check = graph()->NewNode(simplified()->ObjectIsSmi(), receiver);
|
||||
Node* branch = graph()->NewNode(common()->Branch(), check, control);
|
||||
control = graph()->NewNode(common()->IfFalse(), branch);
|
||||
receiverissmi_control = graph()->NewNode(common()->IfTrue(), branch);
|
||||
receiverissmi_effect = effect;
|
||||
}
|
||||
}
|
||||
|
||||
// Generate code for the various different property access patterns.
|
||||
@ -1256,24 +1318,25 @@ Reduction JSNativeContextSpecialization::ReduceNamedAccess(
|
||||
for (size_t j = 0; j < access_infos.size(); ++j) {
|
||||
PropertyAccessInfo const& access_info = access_infos[j];
|
||||
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;
|
||||
|
||||
// Perform map check on {receiver}.
|
||||
ZoneVector<Handle<Map>> const& receiver_maps =
|
||||
access_info.receiver_maps();
|
||||
// Perform map check on {lookup_start_object}.
|
||||
ZoneVector<Handle<Map>> const& lookup_start_object_maps =
|
||||
access_info.lookup_start_object_maps();
|
||||
{
|
||||
// Whether to insert a dedicated MapGuard node into the
|
||||
// effect to be able to learn from the control flow.
|
||||
bool insert_map_guard = true;
|
||||
|
||||
// Check maps for the {receiver}s.
|
||||
// Check maps for the {lookup_start_object}s.
|
||||
if (j == access_infos.size() - 1) {
|
||||
// Last map check on the fallthrough control path, do a
|
||||
// conditional eager deoptimization exit here.
|
||||
access_builder.BuildCheckMaps(receiver, &this_effect, this_control,
|
||||
receiver_maps);
|
||||
access_builder.BuildCheckMaps(lookup_start_object, &this_effect,
|
||||
this_control, lookup_start_object_maps);
|
||||
fallthrough_control = nullptr;
|
||||
|
||||
// Don't insert a MapGuard in this case, as the CheckMaps
|
||||
@ -1281,14 +1344,14 @@ Reduction JSNativeContextSpecialization::ReduceNamedAccess(
|
||||
// along the effect chain.
|
||||
insert_map_guard = false;
|
||||
} else {
|
||||
// Explicitly branch on the {receiver_maps}.
|
||||
// Explicitly branch on the {lookup_start_object_maps}.
|
||||
ZoneHandleSet<Map> maps;
|
||||
for (Handle<Map> map : receiver_maps) {
|
||||
for (Handle<Map> map : lookup_start_object_maps) {
|
||||
maps.insert(map, graph()->zone());
|
||||
}
|
||||
Node* check = this_effect =
|
||||
graph()->NewNode(simplified()->CompareMaps(maps), receiver,
|
||||
this_effect, this_control);
|
||||
graph()->NewNode(simplified()->CompareMaps(maps),
|
||||
lookup_start_object, this_effect, this_control);
|
||||
Node* branch =
|
||||
graph()->NewNode(common()->Branch(), check, this_control);
|
||||
fallthrough_control = graph()->NewNode(common()->IfFalse(), branch);
|
||||
@ -1296,8 +1359,9 @@ Reduction JSNativeContextSpecialization::ReduceNamedAccess(
|
||||
}
|
||||
|
||||
// The Number case requires special treatment to also deal with Smis.
|
||||
if (HasNumberMaps(broker(), receiver_maps)) {
|
||||
if (HasNumberMaps(broker(), lookup_start_object_maps)) {
|
||||
// Join this check with the "receiver is smi" check above.
|
||||
DCHECK_EQ(receiver, lookup_start_object);
|
||||
DCHECK_NOT_NULL(receiverissmi_effect);
|
||||
DCHECK_NOT_NULL(receiverissmi_control);
|
||||
this_control = graph()->NewNode(common()->Merge(2), this_control,
|
||||
@ -1306,7 +1370,7 @@ Reduction JSNativeContextSpecialization::ReduceNamedAccess(
|
||||
receiverissmi_effect, this_control);
|
||||
receiverissmi_effect = receiverissmi_control = nullptr;
|
||||
|
||||
// The {receiver} can also be a Smi in this case, so
|
||||
// The {lookup_start_object} can also be a Smi in this case, so
|
||||
// a MapGuard doesn't make sense for this at all.
|
||||
insert_map_guard = false;
|
||||
}
|
||||
@ -1314,29 +1378,32 @@ Reduction JSNativeContextSpecialization::ReduceNamedAccess(
|
||||
// Introduce a MapGuard to learn from this on the effect chain.
|
||||
if (insert_map_guard) {
|
||||
ZoneHandleSet<Map> maps;
|
||||
for (auto receiver_map : receiver_maps) {
|
||||
maps.insert(receiver_map, graph()->zone());
|
||||
for (auto lookup_start_object_map : lookup_start_object_maps) {
|
||||
maps.insert(lookup_start_object_map, graph()->zone());
|
||||
}
|
||||
this_effect = graph()->NewNode(simplified()->MapGuard(maps), receiver,
|
||||
this_effect, this_control);
|
||||
this_effect =
|
||||
graph()->NewNode(simplified()->MapGuard(maps),
|
||||
lookup_start_object, this_effect, this_control);
|
||||
}
|
||||
|
||||
// If all {receiver_maps} are Strings we also need to rename the
|
||||
// {receiver} here to make sure that TurboFan knows that along this
|
||||
// path the {this_receiver} is a String. This is because we want
|
||||
// strict checking of types, for example for StringLength operators.
|
||||
if (HasOnlyStringMaps(broker(), receiver_maps)) {
|
||||
this_receiver = this_effect =
|
||||
graph()->NewNode(common()->TypeGuard(Type::String()), receiver,
|
||||
this_effect, this_control);
|
||||
// If all {lookup_start_object_maps} are Strings we also need to rename
|
||||
// the {lookup_start_object} here to make sure that TurboFan knows that
|
||||
// along this path the {this_lookup_start_object} is a String. This is
|
||||
// because we want strict checking of types, for example for
|
||||
// StringLength operators.
|
||||
if (HasOnlyStringMaps(broker(), lookup_start_object_maps)) {
|
||||
DCHECK_EQ(receiver, lookup_start_object);
|
||||
this_lookup_start_object = this_receiver = this_effect =
|
||||
graph()->NewNode(common()->TypeGuard(Type::String()),
|
||||
lookup_start_object, this_effect, this_control);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate the actual property access.
|
||||
ValueEffectControl continuation =
|
||||
BuildPropertyAccess(this_receiver, this_value, context, frame_state,
|
||||
this_effect, this_control, feedback.name(),
|
||||
if_exceptions, access_info, access_mode);
|
||||
ValueEffectControl continuation = BuildPropertyAccess(
|
||||
this_lookup_start_object, this_receiver, this_value, context,
|
||||
frame_state, this_effect, this_control, feedback.name(),
|
||||
if_exceptions, access_info, access_mode);
|
||||
values.push_back(continuation.value());
|
||||
effects.push_back(continuation.effect());
|
||||
controls.push_back(continuation.control());
|
||||
@ -1428,6 +1495,17 @@ Reduction JSNativeContextSpecialization::ReduceJSLoadNamed(Node* node) {
|
||||
FeedbackSource(p.feedback()), AccessMode::kLoad);
|
||||
}
|
||||
|
||||
Reduction JSNativeContextSpecialization::ReduceJSLoadNamedFromSuper(
|
||||
Node* node) {
|
||||
JSLoadNamedFromSuperNode n(node);
|
||||
NamedAccess const& p = n.Parameters();
|
||||
NameRef name(broker(), p.name());
|
||||
|
||||
if (!p.feedback().IsValid()) return NoChange();
|
||||
return ReducePropertyAccess(node, nullptr, name, jsgraph()->Dead(),
|
||||
FeedbackSource(p.feedback()), AccessMode::kLoad);
|
||||
}
|
||||
|
||||
Reduction JSNativeContextSpecialization::ReduceJSGetIterator(Node* node) {
|
||||
JSGetIteratorNode n(node);
|
||||
GetIteratorParameters const& p = n.Parameters();
|
||||
@ -1571,20 +1649,20 @@ base::Optional<JSTypedArrayRef> GetTypedArrayConstant(JSHeapBroker* broker,
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void JSNativeContextSpecialization::RemoveImpossibleReceiverMaps(
|
||||
Node* receiver, ZoneVector<Handle<Map>>* receiver_maps) const {
|
||||
base::Optional<MapRef> root_map = InferReceiverRootMap(receiver);
|
||||
void JSNativeContextSpecialization::RemoveImpossibleMaps(
|
||||
Node* object, ZoneVector<Handle<Map>>* maps) const {
|
||||
base::Optional<MapRef> root_map = InferRootMap(object);
|
||||
if (root_map.has_value()) {
|
||||
DCHECK(!root_map->is_abandoned_prototype_map());
|
||||
receiver_maps->erase(
|
||||
std::remove_if(receiver_maps->begin(), receiver_maps->end(),
|
||||
maps->erase(
|
||||
std::remove_if(maps->begin(), maps->end(),
|
||||
[root_map, this](Handle<Map> map) {
|
||||
MapRef map_ref(broker(), map);
|
||||
return map_ref.is_abandoned_prototype_map() ||
|
||||
(map_ref.FindRootMap().has_value() &&
|
||||
!map_ref.FindRootMap()->equals(*root_map));
|
||||
}),
|
||||
receiver_maps->end());
|
||||
maps->end());
|
||||
}
|
||||
}
|
||||
|
||||
@ -1598,9 +1676,9 @@ JSNativeContextSpecialization::TryRefineElementAccessFeedback(
|
||||
if (!use_inference) return feedback;
|
||||
|
||||
ZoneVector<Handle<Map>> inferred_maps(zone());
|
||||
if (!InferReceiverMaps(receiver, effect, &inferred_maps)) return feedback;
|
||||
if (!InferMaps(receiver, effect, &inferred_maps)) return feedback;
|
||||
|
||||
RemoveImpossibleReceiverMaps(receiver, &inferred_maps);
|
||||
RemoveImpossibleMaps(receiver, &inferred_maps);
|
||||
// TODO(neis): After Refine, the resulting feedback can still contain
|
||||
// impossible maps when a target is kept only because more than one of its
|
||||
// sources was inferred. Think of a way to completely rule out impossible
|
||||
@ -1667,7 +1745,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.receiver_maps()) {
|
||||
for (Handle<Map> map : access_info.lookup_start_object_maps()) {
|
||||
MapRef receiver_map(broker(), map);
|
||||
// If the {receiver_map} has a prototype and its elements backing
|
||||
// store is either holey, or we have a potentially growing store,
|
||||
@ -1714,9 +1792,10 @@ Reduction JSNativeContextSpecialization::ReduceElementAccess(
|
||||
ElementAccessInfo access_info = access_infos.front();
|
||||
|
||||
// Perform possible elements kind transitions.
|
||||
MapRef transition_target(broker(), access_info.receiver_maps().front());
|
||||
MapRef transition_target(broker(),
|
||||
access_info.lookup_start_object_maps().front());
|
||||
for (auto source : access_info.transition_sources()) {
|
||||
DCHECK_EQ(access_info.receiver_maps().size(), 1);
|
||||
DCHECK_EQ(access_info.lookup_start_object_maps().size(), 1);
|
||||
MapRef transition_source(broker(), source);
|
||||
effect = graph()->NewNode(
|
||||
simplified()->TransitionElementsKind(ElementsTransition(
|
||||
@ -1738,7 +1817,7 @@ Reduction JSNativeContextSpecialization::ReduceElementAccess(
|
||||
|
||||
// Perform map check on the {receiver}.
|
||||
access_builder.BuildCheckMaps(receiver, &effect, control,
|
||||
access_info.receiver_maps());
|
||||
access_info.lookup_start_object_maps());
|
||||
|
||||
// Access the actual element.
|
||||
ValueEffectControl continuation =
|
||||
@ -1765,10 +1844,11 @@ Reduction JSNativeContextSpecialization::ReduceElementAccess(
|
||||
Node* this_control = fallthrough_control;
|
||||
|
||||
// Perform possible elements kind transitions.
|
||||
MapRef transition_target(broker(), access_info.receiver_maps().front());
|
||||
MapRef transition_target(broker(),
|
||||
access_info.lookup_start_object_maps().front());
|
||||
for (auto source : access_info.transition_sources()) {
|
||||
MapRef transition_source(broker(), source);
|
||||
DCHECK_EQ(access_info.receiver_maps().size(), 1);
|
||||
DCHECK_EQ(access_info.lookup_start_object_maps().size(), 1);
|
||||
this_effect = graph()->NewNode(
|
||||
simplified()->TransitionElementsKind(ElementsTransition(
|
||||
IsSimpleMapChangeTransition(transition_source.elements_kind(),
|
||||
@ -1781,7 +1861,7 @@ Reduction JSNativeContextSpecialization::ReduceElementAccess(
|
||||
|
||||
// Perform map check(s) on {receiver}.
|
||||
ZoneVector<Handle<Map>> const& receiver_maps =
|
||||
access_info.receiver_maps();
|
||||
access_info.lookup_start_object_maps();
|
||||
if (j == access_infos.size() - 1) {
|
||||
// Last map check on the fallthrough control path, do a
|
||||
// conditional eager deoptimization exit here.
|
||||
@ -1928,7 +2008,8 @@ Reduction JSNativeContextSpecialization::ReducePropertyAccess(
|
||||
node->opcode() == IrOpcode::kJSHasProperty ||
|
||||
node->opcode() == IrOpcode::kJSLoadNamed ||
|
||||
node->opcode() == IrOpcode::kJSStoreNamed ||
|
||||
node->opcode() == IrOpcode::kJSStoreNamedOwn);
|
||||
node->opcode() == IrOpcode::kJSStoreNamedOwn ||
|
||||
node->opcode() == IrOpcode::kJSLoadNamedFromSuper);
|
||||
DCHECK_GE(node->op()->ControlOutputCount(), 1);
|
||||
|
||||
ProcessedFeedback const& feedback =
|
||||
@ -1949,6 +2030,7 @@ Reduction JSNativeContextSpecialization::ReducePropertyAccess(
|
||||
case ProcessedFeedback::kElementAccess:
|
||||
DCHECK_EQ(feedback.AsElementAccess().keyed_mode().access_mode(),
|
||||
access_mode);
|
||||
DCHECK_NE(node->opcode(), IrOpcode::kJSLoadNamedFromSuper);
|
||||
return ReduceElementAccess(node, key, value, feedback.AsElementAccess());
|
||||
default:
|
||||
UNREACHABLE();
|
||||
@ -2245,14 +2327,14 @@ Node* JSNativeContextSpecialization::InlineApiCall(
|
||||
|
||||
JSNativeContextSpecialization::ValueEffectControl
|
||||
JSNativeContextSpecialization::BuildPropertyLoad(
|
||||
Node* receiver, Node* context, Node* frame_state, Node* effect,
|
||||
Node* control, NameRef const& name, ZoneVector<Node*>* if_exceptions,
|
||||
PropertyAccessInfo const& access_info) {
|
||||
Node* lookup_start_object, Node* receiver, Node* context, Node* frame_state,
|
||||
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)) {
|
||||
dependencies()->DependOnStablePrototypeChains(
|
||||
access_info.receiver_maps(), kStartAtPrototype,
|
||||
access_info.lookup_start_object_maps(), kStartAtPrototype,
|
||||
JSObjectRef(broker(), holder));
|
||||
}
|
||||
|
||||
@ -2270,12 +2352,13 @@ JSNativeContextSpecialization::BuildPropertyLoad(
|
||||
graph()->NewNode(simplified()->LoadField(AccessBuilder::ForCellValue()),
|
||||
cell, effect, control);
|
||||
} else if (access_info.IsStringLength()) {
|
||||
DCHECK_EQ(receiver, lookup_start_object);
|
||||
value = graph()->NewNode(simplified()->StringLength(), receiver);
|
||||
} else {
|
||||
DCHECK(access_info.IsDataField() || access_info.IsDataConstant());
|
||||
PropertyAccessBuilder access_builder(jsgraph(), broker(), dependencies());
|
||||
value = access_builder.BuildLoadDataField(name, access_info, receiver,
|
||||
&effect, &control);
|
||||
value = access_builder.BuildLoadDataField(
|
||||
name, access_info, lookup_start_object, &effect, &control);
|
||||
}
|
||||
|
||||
return ValueEffectControl(value, effect, control);
|
||||
@ -2288,7 +2371,7 @@ JSNativeContextSpecialization::BuildPropertyTest(
|
||||
Handle<JSObject> holder;
|
||||
if (access_info.holder().ToHandle(&holder)) {
|
||||
dependencies()->DependOnStablePrototypeChains(
|
||||
access_info.receiver_maps(), kStartAtPrototype,
|
||||
access_info.lookup_start_object_maps(), kStartAtPrototype,
|
||||
JSObjectRef(broker(), holder));
|
||||
}
|
||||
|
||||
@ -2299,19 +2382,23 @@ JSNativeContextSpecialization::BuildPropertyTest(
|
||||
|
||||
JSNativeContextSpecialization::ValueEffectControl
|
||||
JSNativeContextSpecialization::BuildPropertyAccess(
|
||||
Node* receiver, Node* value, Node* context, Node* frame_state, Node* effect,
|
||||
Node* control, NameRef const& name, ZoneVector<Node*>* if_exceptions,
|
||||
PropertyAccessInfo const& access_info, AccessMode access_mode) {
|
||||
Node* lookup_start_object, Node* receiver, Node* value, Node* context,
|
||||
Node* frame_state, Node* effect, Node* control, NameRef const& name,
|
||||
ZoneVector<Node*>* if_exceptions, PropertyAccessInfo const& access_info,
|
||||
AccessMode access_mode) {
|
||||
switch (access_mode) {
|
||||
case AccessMode::kLoad:
|
||||
return BuildPropertyLoad(receiver, context, frame_state, effect, control,
|
||||
name, if_exceptions, access_info);
|
||||
return BuildPropertyLoad(lookup_start_object, receiver, context,
|
||||
frame_state, effect, control, name,
|
||||
if_exceptions, access_info);
|
||||
case AccessMode::kStore:
|
||||
case AccessMode::kStoreInLiteral:
|
||||
DCHECK_EQ(receiver, lookup_start_object);
|
||||
return BuildPropertyStore(receiver, value, context, frame_state, effect,
|
||||
control, name, if_exceptions, access_info,
|
||||
access_mode);
|
||||
case AccessMode::kHas:
|
||||
DCHECK_EQ(receiver, lookup_start_object);
|
||||
return BuildPropertyTest(effect, control, access_info);
|
||||
}
|
||||
UNREACHABLE();
|
||||
@ -2328,7 +2415,7 @@ JSNativeContextSpecialization::BuildPropertyStore(
|
||||
if (access_info.holder().ToHandle(&holder)) {
|
||||
DCHECK_NE(AccessMode::kStoreInLiteral, access_mode);
|
||||
dependencies()->DependOnStablePrototypeChains(
|
||||
access_info.receiver_maps(), kStartAtPrototype,
|
||||
access_info.lookup_start_object_maps(), kStartAtPrototype,
|
||||
JSObjectRef(broker(), holder));
|
||||
}
|
||||
|
||||
@ -2591,7 +2678,8 @@ 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 = access_info.receiver_maps();
|
||||
ZoneVector<Handle<Map>> const& receiver_maps =
|
||||
access_info.lookup_start_object_maps();
|
||||
|
||||
if (IsTypedArrayElementsKind(elements_kind)) {
|
||||
Node* buffer_or_receiver = receiver;
|
||||
@ -3350,42 +3438,40 @@ bool JSNativeContextSpecialization::CanTreatHoleAsUndefined(
|
||||
return dependencies()->DependOnNoElementsProtector();
|
||||
}
|
||||
|
||||
bool JSNativeContextSpecialization::InferReceiverMaps(
|
||||
Node* receiver, Node* effect,
|
||||
ZoneVector<Handle<Map>>* receiver_maps) const {
|
||||
ZoneHandleSet<Map> maps;
|
||||
NodeProperties::InferReceiverMapsResult result =
|
||||
NodeProperties::InferReceiverMapsUnsafe(broker(), receiver, effect,
|
||||
&maps);
|
||||
if (result == NodeProperties::kReliableReceiverMaps) {
|
||||
for (size_t i = 0; i < maps.size(); ++i) {
|
||||
receiver_maps->push_back(maps[i]);
|
||||
bool JSNativeContextSpecialization::InferMaps(
|
||||
Node* object, Node* effect, ZoneVector<Handle<Map>>* maps) const {
|
||||
ZoneHandleSet<Map> map_set;
|
||||
NodeProperties::InferMapsResult result =
|
||||
NodeProperties::InferMapsUnsafe(broker(), object, effect, &map_set);
|
||||
if (result == NodeProperties::kReliableMaps) {
|
||||
for (size_t i = 0; i < map_set.size(); ++i) {
|
||||
maps->push_back(map_set[i]);
|
||||
}
|
||||
return true;
|
||||
} else if (result == NodeProperties::kUnreliableReceiverMaps) {
|
||||
// For untrusted receiver maps, we can still use the information
|
||||
} else if (result == NodeProperties::kUnreliableMaps) {
|
||||
// For untrusted maps, we can still use the information
|
||||
// if the maps are stable.
|
||||
for (size_t i = 0; i < maps.size(); ++i) {
|
||||
MapRef map(broker(), maps[i]);
|
||||
for (size_t i = 0; i < map_set.size(); ++i) {
|
||||
MapRef map(broker(), map_set[i]);
|
||||
if (!map.is_stable()) return false;
|
||||
}
|
||||
for (size_t i = 0; i < maps.size(); ++i) {
|
||||
receiver_maps->push_back(maps[i]);
|
||||
for (size_t i = 0; i < map_set.size(); ++i) {
|
||||
maps->push_back(map_set[i]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
base::Optional<MapRef> JSNativeContextSpecialization::InferReceiverRootMap(
|
||||
Node* receiver) const {
|
||||
HeapObjectMatcher m(receiver);
|
||||
base::Optional<MapRef> JSNativeContextSpecialization::InferRootMap(
|
||||
Node* object) const {
|
||||
HeapObjectMatcher m(object);
|
||||
if (m.HasResolvedValue()) {
|
||||
MapRef map = m.Ref(broker()).map();
|
||||
return map.FindRootMap();
|
||||
} else if (m.IsJSCreate()) {
|
||||
base::Optional<MapRef> initial_map =
|
||||
NodeProperties::GetJSCreateMap(broker(), receiver);
|
||||
NodeProperties::GetJSCreateMap(broker(), object);
|
||||
if (initial_map.has_value()) {
|
||||
if (!initial_map->FindRootMap().has_value()) {
|
||||
return base::nullopt;
|
||||
@ -3397,6 +3483,16 @@ base::Optional<MapRef> JSNativeContextSpecialization::InferReceiverRootMap(
|
||||
return base::nullopt;
|
||||
}
|
||||
|
||||
Node* JSNativeContextSpecialization::BuildLoadPrototypeFromObject(
|
||||
Node* object, Node* effect, Node* control) {
|
||||
Node* map = effect =
|
||||
graph()->NewNode(simplified()->LoadField(AccessBuilder::ForMap()), object,
|
||||
effect, control);
|
||||
return graph()->NewNode(
|
||||
simplified()->LoadField(AccessBuilder::ForMapPrototype()), map, effect,
|
||||
control);
|
||||
}
|
||||
|
||||
Graph* JSNativeContextSpecialization::graph() const {
|
||||
return jsgraph()->graph();
|
||||
}
|
||||
|
@ -83,6 +83,7 @@ class V8_EXPORT_PRIVATE JSNativeContextSpecialization final
|
||||
Reduction ReduceJSLoadGlobal(Node* node);
|
||||
Reduction ReduceJSStoreGlobal(Node* node);
|
||||
Reduction ReduceJSLoadNamed(Node* node);
|
||||
Reduction ReduceJSLoadNamedFromSuper(Node* node);
|
||||
Reduction ReduceJSGetIterator(Node* node);
|
||||
Reduction ReduceJSStoreNamed(Node* node);
|
||||
Reduction ReduceJSHasProperty(Node* node);
|
||||
@ -94,7 +95,7 @@ class V8_EXPORT_PRIVATE JSNativeContextSpecialization final
|
||||
Reduction ReduceJSToObject(Node* node);
|
||||
|
||||
Reduction ReduceElementAccess(Node* node, Node* index, Node* value,
|
||||
ElementAccessFeedback const& processed);
|
||||
ElementAccessFeedback const& feedback);
|
||||
// In the case of non-keyed (named) accesses, pass the name as {static_name}
|
||||
// and use {nullptr} for {key} (load/store modes are irrelevant).
|
||||
Reduction ReducePropertyAccess(Node* node, Node* key,
|
||||
@ -102,18 +103,21 @@ class V8_EXPORT_PRIVATE JSNativeContextSpecialization final
|
||||
Node* value, FeedbackSource const& source,
|
||||
AccessMode access_mode);
|
||||
Reduction ReduceNamedAccess(Node* node, Node* value,
|
||||
NamedAccessFeedback const& processed,
|
||||
NamedAccessFeedback const& feedback,
|
||||
AccessMode access_mode, Node* key = nullptr);
|
||||
Reduction ReduceMinimorphicPropertyAccess(
|
||||
Node* node, Node* value,
|
||||
MinimorphicLoadPropertyAccessFeedback const& feedback,
|
||||
FeedbackSource const& source);
|
||||
Reduction ReduceGlobalAccess(Node* node, Node* receiver, Node* value,
|
||||
NameRef const& name, AccessMode access_mode,
|
||||
Node* key = nullptr);
|
||||
Reduction ReduceGlobalAccess(Node* node, Node* receiver, Node* value,
|
||||
NameRef const& name, AccessMode access_mode,
|
||||
Node* key, PropertyCellRef const& property_cell);
|
||||
Reduction ReduceGlobalAccess(Node* node, Node* lookup_start_object,
|
||||
Node* receiver, Node* value, NameRef const& name,
|
||||
AccessMode access_mode, Node* key = nullptr,
|
||||
Node* effect = nullptr);
|
||||
Reduction ReduceGlobalAccess(Node* node, Node* lookup_start_object,
|
||||
Node* receiver, Node* value, NameRef const& name,
|
||||
AccessMode access_mode, Node* key,
|
||||
PropertyCellRef const& property_cell,
|
||||
Node* effect = nullptr);
|
||||
Reduction ReduceElementLoadFromHeapConstant(Node* node, Node* key,
|
||||
AccessMode access_mode,
|
||||
KeyedAccessLoadMode load_mode);
|
||||
@ -146,14 +150,13 @@ class V8_EXPORT_PRIVATE JSNativeContextSpecialization final
|
||||
};
|
||||
|
||||
// Construct the appropriate subgraph for property access.
|
||||
ValueEffectControl BuildPropertyAccess(Node* receiver, Node* value,
|
||||
Node* context, Node* frame_state,
|
||||
Node* effect, Node* control,
|
||||
NameRef const& name,
|
||||
ZoneVector<Node*>* if_exceptions,
|
||||
PropertyAccessInfo const& access_info,
|
||||
AccessMode access_mode);
|
||||
ValueEffectControl BuildPropertyLoad(Node* receiver, Node* context,
|
||||
ValueEffectControl BuildPropertyAccess(
|
||||
Node* lookup_start_object, Node* receiver, Node* value, Node* context,
|
||||
Node* frame_state, Node* effect, Node* control, NameRef const& name,
|
||||
ZoneVector<Node*>* if_exceptions, PropertyAccessInfo const& access_info,
|
||||
AccessMode access_mode);
|
||||
ValueEffectControl BuildPropertyLoad(Node* lookup_start_object,
|
||||
Node* receiver, Node* context,
|
||||
Node* frame_state, Node* effect,
|
||||
Node* control, NameRef const& name,
|
||||
ZoneVector<Node*>* if_exceptions,
|
||||
@ -212,20 +215,19 @@ class V8_EXPORT_PRIVATE JSNativeContextSpecialization final
|
||||
// code dependencies and might use the array protector cell.
|
||||
bool CanTreatHoleAsUndefined(ZoneVector<Handle<Map>> const& receiver_maps);
|
||||
|
||||
void RemoveImpossibleReceiverMaps(
|
||||
Node* receiver, ZoneVector<Handle<Map>>* receiver_maps) const;
|
||||
void RemoveImpossibleMaps(Node* object, ZoneVector<Handle<Map>>* maps) const;
|
||||
|
||||
ElementAccessFeedback const& TryRefineElementAccessFeedback(
|
||||
ElementAccessFeedback const& feedback, Node* receiver,
|
||||
Node* effect) const;
|
||||
|
||||
// Try to infer maps for the given {receiver} at the current {effect}.
|
||||
bool InferReceiverMaps(Node* receiver, Node* effect,
|
||||
ZoneVector<Handle<Map>>* receiver_maps) const;
|
||||
// Try to infer maps for the given {object} at the current {effect}.
|
||||
bool InferMaps(Node* object, Node* effect,
|
||||
ZoneVector<Handle<Map>>* maps) const;
|
||||
|
||||
// Try to infer a root map for the {receiver} independent of the current
|
||||
// program location.
|
||||
base::Optional<MapRef> InferReceiverRootMap(Node* receiver) const;
|
||||
// Try to infer a root map for the {object} independent of the current program
|
||||
// location.
|
||||
base::Optional<MapRef> InferRootMap(Node* object) const;
|
||||
|
||||
// Checks if we know at compile time that the {receiver} either definitely
|
||||
// has the {prototype} in it's prototype chain, or the {receiver} definitely
|
||||
@ -238,6 +240,8 @@ class V8_EXPORT_PRIVATE JSNativeContextSpecialization final
|
||||
InferHasInPrototypeChainResult InferHasInPrototypeChain(
|
||||
Node* receiver, Node* effect, HeapObjectRef const& prototype);
|
||||
|
||||
Node* BuildLoadPrototypeFromObject(Node* object, Node* effect, Node* control);
|
||||
|
||||
Graph* graph() const;
|
||||
JSGraph* jsgraph() const { return jsgraph_; }
|
||||
|
||||
|
@ -935,12 +935,13 @@ const Operator* JSOperatorBuilder::LoadNamed(Handle<Name> name,
|
||||
access); // parameter
|
||||
}
|
||||
|
||||
const Operator* JSOperatorBuilder::LoadNamedFromSuper(Handle<Name> name) {
|
||||
const Operator* JSOperatorBuilder::LoadNamedFromSuper(
|
||||
Handle<Name> name, const FeedbackSource& feedback) {
|
||||
static constexpr int kReceiver = 1;
|
||||
static constexpr int kHomeObject = 1;
|
||||
static constexpr int kArity = kReceiver + kHomeObject;
|
||||
// TODO(marja, v8:9237): Use real feedback.
|
||||
NamedAccess access(LanguageMode::kSloppy, name, FeedbackSource());
|
||||
static constexpr int kFeedbackVector = 1;
|
||||
static constexpr int kArity = kReceiver + kHomeObject + kFeedbackVector;
|
||||
NamedAccess access(LanguageMode::kSloppy, name, feedback);
|
||||
return zone()->New<Operator1<NamedAccess>>( // --
|
||||
IrOpcode::kJSLoadNamedFromSuper, Operator::kNoProperties, // opcode
|
||||
"JSLoadNamedFromSuper", // name
|
||||
|
@ -937,7 +937,8 @@ class V8_EXPORT_PRIVATE JSOperatorBuilder final
|
||||
|
||||
const Operator* LoadProperty(FeedbackSource const& feedback);
|
||||
const Operator* LoadNamed(Handle<Name> name, FeedbackSource const& feedback);
|
||||
const Operator* LoadNamedFromSuper(Handle<Name> name);
|
||||
const Operator* LoadNamedFromSuper(Handle<Name> name,
|
||||
FeedbackSource const& feedback);
|
||||
|
||||
const Operator* StoreProperty(LanguageMode language_mode,
|
||||
FeedbackSource const& feedback);
|
||||
@ -1413,9 +1414,13 @@ class JSLoadNamedFromSuperNode final : public JSNodeWrapperBase {
|
||||
|
||||
const NamedAccess& Parameters() const { return NamedAccessOf(node()->op()); }
|
||||
|
||||
#define INPUTS(V) \
|
||||
V(Receiver, receiver, 0, Object) \
|
||||
V(Object, home_object, 1, Object)
|
||||
// TODO(marja, v8:9237): A more intuitive order would be (home_object,
|
||||
// receiver, feedback_vector). The order can be changed once we no longer
|
||||
// delegate to Runtime_LoadFromSuper.
|
||||
#define INPUTS(V) \
|
||||
V(Receiver, receiver, 0, Object) \
|
||||
V(HomeObject, home_object, 1, Object) \
|
||||
V(FeedbackVector, feedback_vector, 2, HeapObject)
|
||||
INPUTS(DEFINE_INPUT_ACCESSORS)
|
||||
#undef INPUTS
|
||||
};
|
||||
|
@ -513,9 +513,9 @@ JSTypeHintLowering::ReduceGetIteratorOperation(const Operator* op,
|
||||
}
|
||||
|
||||
JSTypeHintLowering::LoweringResult JSTypeHintLowering::ReduceLoadNamedOperation(
|
||||
const Operator* op, Node* receiver, Node* effect, Node* control,
|
||||
FeedbackSlot slot) const {
|
||||
DCHECK_EQ(IrOpcode::kJSLoadNamed, op->opcode());
|
||||
const Operator* op, Node* effect, Node* control, FeedbackSlot slot) const {
|
||||
DCHECK(op->opcode() == IrOpcode::kJSLoadNamed ||
|
||||
op->opcode() == IrOpcode::kJSLoadNamedFromSuper);
|
||||
if (Node* node = TryBuildSoftDeopt(
|
||||
slot, effect, control,
|
||||
DeoptimizeReason::kInsufficientTypeFeedbackForGenericNamedAccess)) {
|
||||
|
@ -144,8 +144,8 @@ class JSTypeHintLowering {
|
||||
FeedbackSlot call_slot) const;
|
||||
|
||||
// Potential reduction of property access operations.
|
||||
LoweringResult ReduceLoadNamedOperation(const Operator* op, Node* obj,
|
||||
Node* effect, Node* control,
|
||||
LoweringResult ReduceLoadNamedOperation(const Operator* op, Node* effect,
|
||||
Node* control,
|
||||
FeedbackSlot slot) const;
|
||||
LoweringResult ReduceLoadKeyedOperation(const Operator* op, Node* obj,
|
||||
Node* key, Node* effect,
|
||||
|
@ -19,12 +19,12 @@ MapInference::MapInference(JSHeapBroker* broker, Node* object, Node* effect)
|
||||
: broker_(broker), object_(object) {
|
||||
ZoneHandleSet<Map> maps;
|
||||
auto result =
|
||||
NodeProperties::InferReceiverMapsUnsafe(broker_, object_, effect, &maps);
|
||||
NodeProperties::InferMapsUnsafe(broker_, object_, effect, &maps);
|
||||
maps_.insert(maps_.end(), maps.begin(), maps.end());
|
||||
maps_state_ = (result == NodeProperties::kUnreliableReceiverMaps)
|
||||
maps_state_ = (result == NodeProperties::kUnreliableMaps)
|
||||
? kUnreliableDontNeedGuard
|
||||
: kReliableOrGuarded;
|
||||
DCHECK_EQ(maps_.empty(), result == NodeProperties::kNoReceiverMaps);
|
||||
DCHECK_EQ(maps_.empty(), result == NodeProperties::kNoMaps);
|
||||
}
|
||||
|
||||
MapInference::~MapInference() { CHECK(Safe()); }
|
||||
|
@ -349,7 +349,7 @@ base::Optional<MapRef> NodeProperties::GetJSCreateMap(JSHeapBroker* broker,
|
||||
}
|
||||
|
||||
// static
|
||||
NodeProperties::InferReceiverMapsResult NodeProperties::InferReceiverMapsUnsafe(
|
||||
NodeProperties::InferMapsResult NodeProperties::InferMapsUnsafe(
|
||||
JSHeapBroker* broker, Node* receiver, Node* effect,
|
||||
ZoneHandleSet<Map>* maps_return) {
|
||||
HeapObjectMatcher m(receiver);
|
||||
@ -368,11 +368,11 @@ NodeProperties::InferReceiverMapsResult NodeProperties::InferReceiverMapsUnsafe(
|
||||
// The {receiver_map} is only reliable when we install a stability
|
||||
// code dependency.
|
||||
*maps_return = ZoneHandleSet<Map>(receiver.map().object());
|
||||
return kUnreliableReceiverMaps;
|
||||
return kUnreliableMaps;
|
||||
}
|
||||
}
|
||||
}
|
||||
InferReceiverMapsResult result = kReliableReceiverMaps;
|
||||
InferMapsResult result = kReliableMaps;
|
||||
while (true) {
|
||||
switch (effect->opcode()) {
|
||||
case IrOpcode::kMapGuard: {
|
||||
@ -399,9 +399,9 @@ NodeProperties::InferReceiverMapsResult NodeProperties::InferReceiverMapsUnsafe(
|
||||
return result;
|
||||
}
|
||||
// We reached the allocation of the {receiver}.
|
||||
return kNoReceiverMaps;
|
||||
return kNoMaps;
|
||||
}
|
||||
result = kUnreliableReceiverMaps; // JSCreate can have side-effect.
|
||||
result = kUnreliableMaps; // JSCreate can have side-effect.
|
||||
break;
|
||||
}
|
||||
case IrOpcode::kJSCreatePromise: {
|
||||
@ -430,7 +430,7 @@ NodeProperties::InferReceiverMapsResult NodeProperties::InferReceiverMapsUnsafe(
|
||||
}
|
||||
// Without alias analysis we cannot tell whether this
|
||||
// StoreField[map] affects {receiver} or not.
|
||||
result = kUnreliableReceiverMaps;
|
||||
result = kUnreliableMaps;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -453,25 +453,25 @@ NodeProperties::InferReceiverMapsResult NodeProperties::InferReceiverMapsUnsafe(
|
||||
if (control->opcode() != IrOpcode::kLoop) {
|
||||
DCHECK(control->opcode() == IrOpcode::kDead ||
|
||||
control->opcode() == IrOpcode::kMerge);
|
||||
return kNoReceiverMaps;
|
||||
return kNoMaps;
|
||||
}
|
||||
|
||||
// Continue search for receiver map outside the loop. Since operations
|
||||
// inside the loop may change the map, the result is unreliable.
|
||||
effect = GetEffectInput(effect, 0);
|
||||
result = kUnreliableReceiverMaps;
|
||||
result = kUnreliableMaps;
|
||||
continue;
|
||||
}
|
||||
default: {
|
||||
DCHECK_EQ(1, effect->op()->EffectOutputCount());
|
||||
if (effect->op()->EffectInputCount() != 1) {
|
||||
// Didn't find any appropriate CheckMaps node.
|
||||
return kNoReceiverMaps;
|
||||
return kNoMaps;
|
||||
}
|
||||
if (!effect->op()->HasProperty(Operator::kNoWrite)) {
|
||||
// Without alias/escape analysis we cannot tell whether this
|
||||
// {effect} affects {receiver} or not.
|
||||
result = kUnreliableReceiverMaps;
|
||||
result = kUnreliableMaps;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -479,7 +479,7 @@ NodeProperties::InferReceiverMapsResult NodeProperties::InferReceiverMapsUnsafe(
|
||||
|
||||
// Stop walking the effect chain once we hit the definition of
|
||||
// the {receiver} along the {effect}s.
|
||||
if (IsSame(receiver, effect)) return kNoReceiverMaps;
|
||||
if (IsSame(receiver, effect)) return kNoMaps;
|
||||
|
||||
// Continue with the next {effect}.
|
||||
DCHECK_EQ(1, effect->op()->EffectInputCount());
|
||||
|
@ -203,15 +203,15 @@ class V8_EXPORT_PRIVATE NodeProperties final {
|
||||
// Walks up the {effect} chain to find a witness that provides map
|
||||
// information about the {receiver}. Can look through potentially
|
||||
// side effecting nodes.
|
||||
enum InferReceiverMapsResult {
|
||||
kNoReceiverMaps, // No receiver maps inferred.
|
||||
kReliableReceiverMaps, // Receiver maps can be trusted.
|
||||
kUnreliableReceiverMaps // Receiver maps might have changed (side-effect).
|
||||
enum InferMapsResult {
|
||||
kNoMaps, // No maps inferred.
|
||||
kReliableMaps, // Maps can be trusted.
|
||||
kUnreliableMaps // Maps might have changed (side-effect).
|
||||
};
|
||||
// DO NOT USE InferReceiverMapsUnsafe IN NEW CODE. Use MapInference instead.
|
||||
static InferReceiverMapsResult InferReceiverMapsUnsafe(
|
||||
JSHeapBroker* broker, Node* receiver, Node* effect,
|
||||
ZoneHandleSet<Map>* maps_return);
|
||||
// DO NOT USE InferMapsUnsafe IN NEW CODE. Use MapInference instead.
|
||||
static InferMapsResult InferMapsUnsafe(JSHeapBroker* broker, Node* object,
|
||||
Node* effect,
|
||||
ZoneHandleSet<Map>* maps);
|
||||
|
||||
// Return the initial map of the new-target if the allocation can be inlined.
|
||||
static base::Optional<MapRef> GetJSCreateMap(JSHeapBroker* broker,
|
||||
|
@ -1116,6 +1116,7 @@ class V8_EXPORT_PRIVATE IrOpcode {
|
||||
case kJSInstanceOf:
|
||||
case kJSLoadGlobal:
|
||||
case kJSLoadNamed:
|
||||
case kJSLoadNamedFromSuper:
|
||||
case kJSLoadProperty:
|
||||
case kJSStoreDataPropertyInLiteral:
|
||||
case kJSStoreGlobal:
|
||||
|
@ -82,30 +82,30 @@ bool PropertyAccessBuilder::TryBuildNumberCheck(
|
||||
}
|
||||
|
||||
void PropertyAccessBuilder::BuildCheckMaps(
|
||||
Node* receiver, Node** effect, Node* control,
|
||||
ZoneVector<Handle<Map>> const& receiver_maps) {
|
||||
HeapObjectMatcher m(receiver);
|
||||
Node* object, Node** effect, Node* control,
|
||||
ZoneVector<Handle<Map>> const& maps) {
|
||||
HeapObjectMatcher m(object);
|
||||
if (m.HasResolvedValue()) {
|
||||
MapRef receiver_map = m.Ref(broker()).map();
|
||||
if (receiver_map.is_stable()) {
|
||||
for (Handle<Map> map : receiver_maps) {
|
||||
if (MapRef(broker(), map).equals(receiver_map)) {
|
||||
dependencies()->DependOnStableMap(receiver_map);
|
||||
MapRef object_map = m.Ref(broker()).map();
|
||||
if (object_map.is_stable()) {
|
||||
for (Handle<Map> map : maps) {
|
||||
if (MapRef(broker(), map).equals(object_map)) {
|
||||
dependencies()->DependOnStableMap(object_map);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ZoneHandleSet<Map> maps;
|
||||
ZoneHandleSet<Map> map_set;
|
||||
CheckMapsFlags flags = CheckMapsFlag::kNone;
|
||||
for (Handle<Map> map : receiver_maps) {
|
||||
MapRef receiver_map(broker(), map);
|
||||
maps.insert(receiver_map.object(), graph()->zone());
|
||||
if (receiver_map.is_migration_target()) {
|
||||
for (Handle<Map> map : maps) {
|
||||
MapRef object_map(broker(), map);
|
||||
map_set.insert(object_map.object(), graph()->zone());
|
||||
if (object_map.is_migration_target()) {
|
||||
flags |= CheckMapsFlag::kTryMigrateInstance;
|
||||
}
|
||||
}
|
||||
*effect = graph()->NewNode(simplified()->CheckMaps(flags, maps), receiver,
|
||||
*effect = graph()->NewNode(simplified()->CheckMaps(flags, map_set), object,
|
||||
*effect, control);
|
||||
}
|
||||
|
||||
@ -124,12 +124,12 @@ Node* PropertyAccessBuilder::BuildCheckValue(Node* receiver, Effect* effect,
|
||||
}
|
||||
|
||||
Node* PropertyAccessBuilder::ResolveHolder(
|
||||
PropertyAccessInfo const& access_info, Node* receiver) {
|
||||
PropertyAccessInfo const& access_info, Node* lookup_start_object) {
|
||||
Handle<JSObject> holder;
|
||||
if (access_info.holder().ToHandle(&holder)) {
|
||||
return jsgraph()->Constant(ObjectRef(broker(), holder));
|
||||
}
|
||||
return receiver;
|
||||
return lookup_start_object;
|
||||
}
|
||||
|
||||
MachineRepresentation PropertyAccessBuilder::ConvertRepresentation(
|
||||
@ -150,25 +150,27 @@ MachineRepresentation PropertyAccessBuilder::ConvertRepresentation(
|
||||
|
||||
Node* PropertyAccessBuilder::TryBuildLoadConstantDataField(
|
||||
NameRef const& name, PropertyAccessInfo const& access_info,
|
||||
Node* receiver) {
|
||||
Node* lookup_start_object) {
|
||||
if (!access_info.IsDataConstant()) return nullptr;
|
||||
|
||||
// First, determine if we have a constant holder to load from.
|
||||
Handle<JSObject> holder;
|
||||
// If {access_info} has a holder, just use it.
|
||||
if (!access_info.holder().ToHandle(&holder)) {
|
||||
// Otherwise, try to match the {receiver} as a constant.
|
||||
HeapObjectMatcher m(receiver);
|
||||
// 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;
|
||||
|
||||
// Let us make sure the actual map of the constant receiver is among
|
||||
// the maps in {access_info}.
|
||||
MapRef receiver_map = m.Ref(broker()).map();
|
||||
if (std::find_if(access_info.receiver_maps().begin(),
|
||||
access_info.receiver_maps().end(), [&](Handle<Map> map) {
|
||||
return MapRef(broker(), map).equals(receiver_map);
|
||||
}) == access_info.receiver_maps().end()) {
|
||||
// The map of the receiver is not in the feedback, let us bail out.
|
||||
// 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 MapRef(broker(), 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();
|
||||
@ -253,7 +255,7 @@ Node* PropertyAccessBuilder::BuildLoadDataField(NameRef const& name,
|
||||
|
||||
Node* PropertyAccessBuilder::BuildMinimorphicLoadDataField(
|
||||
NameRef const& name, MinimorphicLoadPropertyAccessInfo const& access_info,
|
||||
Node* receiver, Node** effect, Node** control) {
|
||||
Node* lookup_start_object, Node** effect, Node** control) {
|
||||
DCHECK_NULL(dependencies());
|
||||
MachineRepresentation const field_representation =
|
||||
ConvertRepresentation(access_info.field_representation());
|
||||
@ -268,22 +270,22 @@ Node* PropertyAccessBuilder::BuildMinimorphicLoadDataField(
|
||||
kFullWriteBarrier,
|
||||
LoadSensitivity::kCritical,
|
||||
ConstFieldInfo::None()};
|
||||
return BuildLoadDataField(name, receiver, field_access,
|
||||
return BuildLoadDataField(name, lookup_start_object, field_access,
|
||||
access_info.is_inobject(), effect, control);
|
||||
}
|
||||
|
||||
Node* PropertyAccessBuilder::BuildLoadDataField(
|
||||
NameRef const& name, PropertyAccessInfo const& access_info, Node* receiver,
|
||||
Node** effect, Node** control) {
|
||||
NameRef const& name, PropertyAccessInfo const& access_info,
|
||||
Node* lookup_start_object, Node** effect, Node** control) {
|
||||
DCHECK(access_info.IsDataField() || access_info.IsDataConstant());
|
||||
if (Node* value =
|
||||
TryBuildLoadConstantDataField(name, access_info, receiver)) {
|
||||
if (Node* value = TryBuildLoadConstantDataField(name, access_info,
|
||||
lookup_start_object)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
MachineRepresentation const field_representation =
|
||||
ConvertRepresentation(access_info.field_representation());
|
||||
Node* storage = ResolveHolder(access_info, receiver);
|
||||
Node* storage = ResolveHolder(access_info, lookup_start_object);
|
||||
|
||||
FieldAccess field_access = {
|
||||
kTaggedBase,
|
||||
|
@ -45,13 +45,13 @@ class PropertyAccessBuilder {
|
||||
|
||||
// TODO(jgruber): Remove the untyped version once all uses are
|
||||
// updated.
|
||||
void BuildCheckMaps(Node* receiver, Node** effect, Node* control,
|
||||
ZoneVector<Handle<Map>> const& receiver_maps);
|
||||
void BuildCheckMaps(Node* receiver, Effect* effect, Control control,
|
||||
ZoneVector<Handle<Map>> const& receiver_maps) {
|
||||
void BuildCheckMaps(Node* object, Node** effect, Node* control,
|
||||
ZoneVector<Handle<Map>> const& maps);
|
||||
void BuildCheckMaps(Node* object, Effect* effect, Control control,
|
||||
ZoneVector<Handle<Map>> const& maps) {
|
||||
Node* e = *effect;
|
||||
Node* c = control;
|
||||
BuildCheckMaps(receiver, &e, c, receiver_maps);
|
||||
BuildCheckMaps(object, &e, c, maps);
|
||||
*effect = e;
|
||||
}
|
||||
Node* BuildCheckValue(Node* receiver, Effect* effect, Control control,
|
||||
@ -61,13 +61,14 @@ class PropertyAccessBuilder {
|
||||
// properties (without heap-object or map checks).
|
||||
Node* BuildLoadDataField(NameRef const& name,
|
||||
PropertyAccessInfo const& access_info,
|
||||
Node* receiver, Node** effect, Node** control);
|
||||
Node* lookup_start_object, Node** effect,
|
||||
Node** control);
|
||||
|
||||
// Builds the load for data-field access for minimorphic loads that use
|
||||
// dynamic map checks. These cannot depend on any information from the maps.
|
||||
Node* BuildMinimorphicLoadDataField(
|
||||
NameRef const& name, MinimorphicLoadPropertyAccessInfo const& access_info,
|
||||
Node* receiver, Node** effect, Node** control);
|
||||
Node* lookup_start_object, Node** effect, Node** control);
|
||||
|
||||
static MachineRepresentation ConvertRepresentation(
|
||||
Representation representation);
|
||||
@ -83,10 +84,11 @@ class PropertyAccessBuilder {
|
||||
|
||||
Node* TryBuildLoadConstantDataField(NameRef const& name,
|
||||
PropertyAccessInfo const& access_info,
|
||||
Node* receiver);
|
||||
Node* lookup_start_object);
|
||||
// Returns a node with the holder for the property access described by
|
||||
// {access_info}.
|
||||
Node* ResolveHolder(PropertyAccessInfo const& access_info, Node* receiver);
|
||||
Node* ResolveHolder(PropertyAccessInfo const& access_info,
|
||||
Node* lookup_start_object);
|
||||
|
||||
Node* BuildLoadDataField(NameRef const& name, Node* holder,
|
||||
FieldAccess& field_access, bool is_inobject,
|
||||
|
@ -460,8 +460,14 @@ class SerializerForBackgroundCompilation {
|
||||
bool honor_bailout_on_uninitialized);
|
||||
void ProcessNamedPropertyAccess(Hints* receiver, NameRef const& name,
|
||||
FeedbackSlot slot, AccessMode access_mode);
|
||||
void ProcessNamedSuperPropertyAccess(Hints* receiver, NameRef const& name,
|
||||
FeedbackSlot slot,
|
||||
AccessMode access_mode);
|
||||
void ProcessNamedAccess(Hints* receiver, NamedAccessFeedback const& feedback,
|
||||
AccessMode access_mode, Hints* result_hints);
|
||||
void ProcessNamedSuperAccess(Hints* receiver,
|
||||
NamedAccessFeedback const& feedback,
|
||||
AccessMode access_mode, Hints* result_hints);
|
||||
void ProcessElementAccess(Hints const& receiver, Hints const& key,
|
||||
ElementAccessFeedback const& feedback,
|
||||
AccessMode access_mode);
|
||||
@ -495,7 +501,8 @@ class SerializerForBackgroundCompilation {
|
||||
bool honor_bailout_on_uninitialized);
|
||||
|
||||
PropertyAccessInfo ProcessMapForNamedPropertyAccess(
|
||||
Hints* receiver, MapRef receiver_map, NameRef const& name,
|
||||
Hints* receiver, base::Optional<MapRef> receiver_map,
|
||||
MapRef lookup_start_object_map, NameRef const& name,
|
||||
AccessMode access_mode, base::Optional<JSObjectRef> concrete_receiver,
|
||||
Hints* result_hints);
|
||||
|
||||
@ -2155,7 +2162,7 @@ void SerializerForBackgroundCompilation::ProcessCallOrConstruct(
|
||||
arguments->insert(arguments->begin(), result_hints_from_new_target);
|
||||
}
|
||||
|
||||
// For JSNativeContextSpecialization::InferReceiverRootMap
|
||||
// For JSNativeContextSpecialization::InferRootMap
|
||||
Hints new_accumulator_hints = result_hints_from_new_target.Copy(zone());
|
||||
|
||||
ProcessCallOrConstructRecursive(callee, new_target, *arguments,
|
||||
@ -2963,18 +2970,20 @@ void SerializerForBackgroundCompilation::ProcessUnaryOrBinaryOperation(
|
||||
|
||||
PropertyAccessInfo
|
||||
SerializerForBackgroundCompilation::ProcessMapForNamedPropertyAccess(
|
||||
Hints* receiver, MapRef receiver_map, NameRef const& name,
|
||||
AccessMode access_mode, base::Optional<JSObjectRef> concrete_receiver,
|
||||
Hints* result_hints) {
|
||||
// For JSNativeContextSpecialization::InferReceiverRootMap
|
||||
receiver_map.SerializeRootMap();
|
||||
Hints* receiver, base::Optional<MapRef> receiver_map,
|
||||
MapRef lookup_start_object_map, NameRef const& name, AccessMode access_mode,
|
||||
base::Optional<JSObjectRef> concrete_receiver, Hints* result_hints) {
|
||||
DCHECK_IMPLIES(concrete_receiver.has_value(), receiver_map.has_value());
|
||||
|
||||
// For JSNativeContextSpecialization::InferRootMap
|
||||
lookup_start_object_map.SerializeRootMap();
|
||||
|
||||
// For JSNativeContextSpecialization::ReduceNamedAccess.
|
||||
JSGlobalProxyRef global_proxy =
|
||||
broker()->target_native_context().global_proxy_object();
|
||||
JSGlobalObjectRef global_object =
|
||||
broker()->target_native_context().global_object();
|
||||
if (receiver_map.equals(global_proxy.map())) {
|
||||
if (lookup_start_object_map.equals(global_proxy.map())) {
|
||||
base::Optional<PropertyCellRef> cell = global_object.GetPropertyCell(
|
||||
name, SerializationPolicy::kSerializeIfNeeded);
|
||||
if (access_mode == AccessMode::kLoad && cell.has_value()) {
|
||||
@ -2983,7 +2992,7 @@ SerializerForBackgroundCompilation::ProcessMapForNamedPropertyAccess(
|
||||
}
|
||||
|
||||
PropertyAccessInfo access_info = broker()->GetPropertyAccessInfo(
|
||||
receiver_map, name, access_mode, dependencies(),
|
||||
lookup_start_object_map, name, access_mode, dependencies(),
|
||||
SerializationPolicy::kSerializeIfNeeded);
|
||||
|
||||
// For JSNativeContextSpecialization::InlinePropertySetterCall
|
||||
@ -2992,25 +3001,27 @@ SerializerForBackgroundCompilation::ProcessMapForNamedPropertyAccess(
|
||||
if (access_info.constant()->IsJSFunction()) {
|
||||
JSFunctionRef function(broker(), access_info.constant());
|
||||
|
||||
// For JSCallReducer and JSInlining(Heuristic).
|
||||
HintsVector arguments({Hints::SingleMap(receiver_map.object(), zone())},
|
||||
zone());
|
||||
// In the case of a setter any added result hints won't make sense, but
|
||||
// they will be ignored anyways by Process*PropertyAccess due to the
|
||||
// access mode not being kLoad.
|
||||
ProcessCalleeForCallOrConstruct(
|
||||
function.object(), base::nullopt, arguments,
|
||||
SpeculationMode::kDisallowSpeculation, kMissingArgumentsAreUndefined,
|
||||
result_hints);
|
||||
if (receiver_map.has_value()) {
|
||||
// For JSCallReducer and JSInlining(Heuristic).
|
||||
HintsVector arguments(
|
||||
{Hints::SingleMap(receiver_map->object(), zone())}, zone());
|
||||
// In the case of a setter any added result hints won't make sense, but
|
||||
// they will be ignored anyways by Process*PropertyAccess due to the
|
||||
// access mode not being kLoad.
|
||||
ProcessCalleeForCallOrConstruct(
|
||||
function.object(), base::nullopt, arguments,
|
||||
SpeculationMode::kDisallowSpeculation,
|
||||
kMissingArgumentsAreUndefined, result_hints);
|
||||
|
||||
// For JSCallReducer::ReduceCallApiFunction.
|
||||
Handle<SharedFunctionInfo> sfi = function.shared().object();
|
||||
if (sfi->IsApiFunction()) {
|
||||
FunctionTemplateInfoRef fti_ref(
|
||||
broker(), handle(sfi->get_api_func_data(), broker()->isolate()));
|
||||
if (fti_ref.has_call_code()) {
|
||||
fti_ref.SerializeCallCode();
|
||||
ProcessReceiverMapForApiCall(fti_ref, receiver_map.object());
|
||||
// For JSCallReducer::ReduceCallApiFunction.
|
||||
Handle<SharedFunctionInfo> sfi = function.shared().object();
|
||||
if (sfi->IsApiFunction()) {
|
||||
FunctionTemplateInfoRef fti_ref(
|
||||
broker(), handle(sfi->get_api_func_data(), broker()->isolate()));
|
||||
if (fti_ref.has_call_code()) {
|
||||
fti_ref.SerializeCallCode();
|
||||
ProcessReceiverMapForApiCall(fti_ref, receiver_map->object());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (access_info.constant()->IsJSBoundFunction()) {
|
||||
@ -3038,7 +3049,7 @@ SerializerForBackgroundCompilation::ProcessMapForNamedPropertyAccess(
|
||||
holder = JSObjectRef(broker(), prototype);
|
||||
} else {
|
||||
CHECK_IMPLIES(concrete_receiver.has_value(),
|
||||
concrete_receiver->map().equals(receiver_map));
|
||||
concrete_receiver->map().equals(*receiver_map));
|
||||
holder = concrete_receiver;
|
||||
}
|
||||
|
||||
@ -3152,6 +3163,38 @@ void SerializerForBackgroundCompilation::ProcessNamedPropertyAccess(
|
||||
}
|
||||
}
|
||||
|
||||
void SerializerForBackgroundCompilation::ProcessNamedSuperPropertyAccess(
|
||||
Hints* receiver, NameRef const& name, FeedbackSlot slot,
|
||||
AccessMode access_mode) {
|
||||
if (slot.IsInvalid() || feedback_vector().is_null()) return;
|
||||
FeedbackSource source(feedback_vector(), slot);
|
||||
ProcessedFeedback const& feedback =
|
||||
broker()->ProcessFeedbackForPropertyAccess(source, access_mode, name);
|
||||
if (BailoutOnUninitialized(feedback)) return;
|
||||
|
||||
Hints new_accumulator_hints;
|
||||
switch (feedback.kind()) {
|
||||
case ProcessedFeedback::kNamedAccess:
|
||||
DCHECK(name.equals(feedback.AsNamedAccess().name()));
|
||||
ProcessNamedSuperAccess(receiver, feedback.AsNamedAccess(), access_mode,
|
||||
&new_accumulator_hints);
|
||||
break;
|
||||
case ProcessedFeedback::kMinimorphicPropertyAccess:
|
||||
DCHECK(name.equals(feedback.AsMinimorphicPropertyAccess().name()));
|
||||
ProcessMinimorphicPropertyAccess(feedback.AsMinimorphicPropertyAccess(),
|
||||
source);
|
||||
break;
|
||||
case ProcessedFeedback::kInsufficient:
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
if (access_mode == AccessMode::kLoad) {
|
||||
environment()->accumulator_hints() = new_accumulator_hints;
|
||||
}
|
||||
}
|
||||
|
||||
void SerializerForBackgroundCompilation::ProcessNamedAccess(
|
||||
Hints* receiver, NamedAccessFeedback const& feedback,
|
||||
AccessMode access_mode, Hints* result_hints) {
|
||||
@ -3165,17 +3208,18 @@ void SerializerForBackgroundCompilation::ProcessNamedAccess(
|
||||
for (Handle<Map> map :
|
||||
GetRelevantReceiverMaps(broker()->isolate(), receiver->maps())) {
|
||||
MapRef map_ref(broker(), map);
|
||||
ProcessMapForNamedPropertyAccess(receiver, map_ref, feedback.name(),
|
||||
access_mode, base::nullopt, result_hints);
|
||||
ProcessMapForNamedPropertyAccess(receiver, map_ref, map_ref,
|
||||
feedback.name(), access_mode,
|
||||
base::nullopt, result_hints);
|
||||
}
|
||||
|
||||
for (Handle<Object> hint : receiver->constants()) {
|
||||
ObjectRef object(broker(), hint);
|
||||
if (access_mode == AccessMode::kLoad && object.IsJSObject()) {
|
||||
MapRef map_ref = object.AsJSObject().map();
|
||||
ProcessMapForNamedPropertyAccess(receiver, map_ref, feedback.name(),
|
||||
access_mode, object.AsJSObject(),
|
||||
result_hints);
|
||||
ProcessMapForNamedPropertyAccess(receiver, map_ref, map_ref,
|
||||
feedback.name(), access_mode,
|
||||
object.AsJSObject(), result_hints);
|
||||
}
|
||||
// For JSNativeContextSpecialization::ReduceJSLoadNamed.
|
||||
if (access_mode == AccessMode::kLoad && object.IsJSFunction() &&
|
||||
@ -3193,6 +3237,30 @@ void SerializerForBackgroundCompilation::ProcessNamedAccess(
|
||||
}
|
||||
}
|
||||
|
||||
void SerializerForBackgroundCompilation::ProcessNamedSuperAccess(
|
||||
Hints* receiver, NamedAccessFeedback const& feedback,
|
||||
AccessMode access_mode, Hints* result_hints) {
|
||||
MapHandles receiver_maps =
|
||||
GetRelevantReceiverMaps(broker()->isolate(), receiver->maps());
|
||||
for (Handle<Map> receiver_map : receiver_maps) {
|
||||
MapRef receiver_map_ref(broker(), receiver_map);
|
||||
for (Handle<Map> feedback_map : feedback.maps()) {
|
||||
MapRef feedback_map_ref(broker(), feedback_map);
|
||||
ProcessMapForNamedPropertyAccess(
|
||||
receiver, receiver_map_ref, feedback_map_ref, feedback.name(),
|
||||
access_mode, base::nullopt, result_hints);
|
||||
}
|
||||
}
|
||||
if (receiver_maps.empty()) {
|
||||
for (Handle<Map> feedback_map : feedback.maps()) {
|
||||
MapRef feedback_map_ref(broker(), feedback_map);
|
||||
ProcessMapForNamedPropertyAccess(
|
||||
receiver, base::nullopt, feedback_map_ref, feedback.name(),
|
||||
access_mode, base::nullopt, result_hints);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SerializerForBackgroundCompilation::ProcessElementAccess(
|
||||
Hints const& receiver, Hints const& key,
|
||||
ElementAccessFeedback const& feedback, AccessMode access_mode) {
|
||||
@ -3217,7 +3285,7 @@ void SerializerForBackgroundCompilation::ProcessElementAccess(
|
||||
for (Handle<Object> hint : receiver.constants()) {
|
||||
ObjectRef receiver_ref(broker(), hint);
|
||||
|
||||
// For JSNativeContextSpecialization::InferReceiverRootMap
|
||||
// For JSNativeContextSpecialization::InferRootMap
|
||||
if (receiver_ref.IsHeapObject()) {
|
||||
receiver_ref.AsHeapObject().map().SerializeRootMap();
|
||||
}
|
||||
@ -3248,7 +3316,7 @@ void SerializerForBackgroundCompilation::ProcessElementAccess(
|
||||
}
|
||||
}
|
||||
|
||||
// For JSNativeContextSpecialization::InferReceiverRootMap
|
||||
// For JSNativeContextSpecialization::InferRootMap
|
||||
for (Handle<Map> map : receiver.maps()) {
|
||||
MapRef map_ref(broker(), map);
|
||||
map_ref.SerializeRootMap();
|
||||
@ -3266,9 +3334,11 @@ void SerializerForBackgroundCompilation::VisitLdaNamedProperty(
|
||||
|
||||
void SerializerForBackgroundCompilation::VisitLdaNamedPropertyFromSuper(
|
||||
BytecodeArrayIterator* iterator) {
|
||||
NameRef(broker(),
|
||||
iterator->GetConstantForIndexOperand(1, broker()->isolate()));
|
||||
// TODO(marja, v8:9237): Process feedback once it's added to the byte code.
|
||||
Hints* receiver = ®ister_hints(iterator->GetRegisterOperand(0));
|
||||
NameRef name(broker(),
|
||||
iterator->GetConstantForIndexOperand(1, broker()->isolate()));
|
||||
FeedbackSlot slot = iterator->GetSlotOperand(2);
|
||||
ProcessNamedSuperPropertyAccess(receiver, name, slot, AccessMode::kLoad);
|
||||
}
|
||||
|
||||
// TODO(neis): Do feedback-independent serialization also for *NoFeedback
|
||||
|
54
test/mjsunit/es6/super-ic-opt-dynamic-map-checks.js
Normal file
54
test/mjsunit/es6/super-ic-opt-dynamic-map-checks.js
Normal file
@ -0,0 +1,54 @@
|
||||
// Copyright 2020 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// Flags: --allow-natives-syntax --super-ic --opt --runtime-call-stats
|
||||
// Flags: --no-always-opt --no-stress-opt --turboprop
|
||||
// Flags: --turboprop-dynamic-map-checks
|
||||
|
||||
load("test/mjsunit/runtime-callstats-helpers.js");
|
||||
|
||||
%GetAndResetRuntimeCallStats();
|
||||
|
||||
// This file contains tests which require --dynamic-map-chekcs.
|
||||
|
||||
(function TestMinimorphicPropertyAccess() {
|
||||
class A {}
|
||||
A.prototype.bar = "wrong value: A.prototype.bar";
|
||||
|
||||
class B extends A {};
|
||||
B.prototype.bar = "correct value";
|
||||
|
||||
class C extends B {
|
||||
foo(should_bailout) {
|
||||
const r = super.bar;
|
||||
const did_bailout = (
|
||||
%GetOptimizationStatus(C.prototype.foo) &
|
||||
V8OptimizationStatus.kTopmostFrameIsTurboFanned) == 0;
|
||||
assertEquals(should_bailout, did_bailout);
|
||||
return r;
|
||||
}
|
||||
}
|
||||
C.prototype.bar = "wrong value: C.prototype.bar";
|
||||
%PrepareFunctionForOptimization(C.prototype.foo);
|
||||
|
||||
let o = new C();
|
||||
o.bar = "wrong value: o.bar";
|
||||
|
||||
// Fill in the feedback.
|
||||
let r = o.foo(true);
|
||||
assertEquals("correct value", r);
|
||||
%OptimizeFunctionOnNextCall(C.prototype.foo);
|
||||
|
||||
// Test the optimized function.
|
||||
r = o.foo(false);
|
||||
assertEquals("correct value", r);
|
||||
})();
|
||||
|
||||
// Assert that the tests so far generated real optimized code and not just a
|
||||
// bailout to Runtime_LoadFromSuper. TODO(marja, v8:9237): update this to track
|
||||
// the builtin we'll use for bailout cases.
|
||||
assertEquals(0, getRuntimeFunctionCallCount("LoadFromSuper"));
|
||||
|
||||
// Reset runtime stats so that we don't get extra printout.
|
||||
%GetAndResetRuntimeCallStats();
|
63
test/mjsunit/es6/super-ic-opt-no-turboprop.js
Normal file
63
test/mjsunit/es6/super-ic-opt-no-turboprop.js
Normal file
@ -0,0 +1,63 @@
|
||||
// Copyright 2020 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// Flags: --allow-natives-syntax --super-ic --opt --runtime-call-stats
|
||||
// Flags: --no-always-opt --no-stress-opt
|
||||
|
||||
load("test/mjsunit/runtime-callstats-helpers.js");
|
||||
|
||||
%GetAndResetRuntimeCallStats();
|
||||
|
||||
// This file contains tests which are disabled for TurboProp. TurboProp deopts
|
||||
// differently than TurboFan, so the assertions about when a function is
|
||||
// deoptimized won't hold.
|
||||
|
||||
(function TestPropertyIsConstant() {
|
||||
// Test for a case where the property is a constant found in the lookup start
|
||||
// object.
|
||||
class A {}
|
||||
A.prototype.bar = "wrong value: A.prototype.bar";
|
||||
|
||||
class B extends A {};
|
||||
B.prototype.bar = "correct value";
|
||||
|
||||
class C extends B {
|
||||
foo() { return super.bar; }
|
||||
}
|
||||
C.prototype.bar = "wrong value: C.prototype.bar";
|
||||
%PrepareFunctionForOptimization(C.prototype.foo);
|
||||
|
||||
let o = new C();
|
||||
o.bar = "wrong value: o.bar";
|
||||
|
||||
// Fill in the feedback.
|
||||
r = o.foo();
|
||||
assertEquals("correct value", r);
|
||||
|
||||
%OptimizeFunctionOnNextCall(C.prototype.foo);
|
||||
|
||||
// Test the optimized function.
|
||||
r = o.foo();
|
||||
assertEquals("correct value", r);
|
||||
|
||||
// Assert that the function was not deoptimized.
|
||||
assertOptimized(C.prototype.foo);
|
||||
|
||||
// Change the property value.
|
||||
B.prototype.bar = "new value";
|
||||
r = o.foo();
|
||||
assertEquals("new value", r);
|
||||
|
||||
// Assert that the function was deoptimized (dependency to the constant
|
||||
// value).
|
||||
assertFalse(isOptimized(C.prototype.foo));
|
||||
})();
|
||||
|
||||
// Assert that the tests so far generated real optimized code and not just a
|
||||
// bailout to Runtime_LoadFromSuper. TODO(marja, v8:9237): update this to track
|
||||
// the builtin we'll use for bailout cases.
|
||||
assertEquals(0, getRuntimeFunctionCallCount("LoadFromSuper"));
|
||||
|
||||
// Reset runtime stats so that we don't get extra printout.
|
||||
%GetAndResetRuntimeCallStats();
|
636
test/mjsunit/es6/super-ic-opt.js
Normal file
636
test/mjsunit/es6/super-ic-opt.js
Normal file
@ -0,0 +1,636 @@
|
||||
// Copyright 2020 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// Flags: --allow-natives-syntax --super-ic --opt --runtime-call-stats
|
||||
// Flags: --no-always-opt --no-stress-opt
|
||||
|
||||
load("test/mjsunit/runtime-callstats-helpers.js");
|
||||
|
||||
%GetAndResetRuntimeCallStats();
|
||||
|
||||
(function TestPropertyIsInTheHomeObjectsProto() {
|
||||
// Test where the property is a constant found on home object's proto. This
|
||||
// will generate a minimorphic property load.
|
||||
class A {}
|
||||
A.prototype.bar = "wrong value: A.prototype.bar";
|
||||
|
||||
class B extends A {};
|
||||
B.prototype.bar = "correct value";
|
||||
|
||||
class C extends B {
|
||||
foo() { return super.bar; }
|
||||
}
|
||||
C.prototype.bar = "wrong value: D.prototype.bar";
|
||||
%PrepareFunctionForOptimization(C.prototype.foo);
|
||||
|
||||
let o = new C();
|
||||
o.bar = "wrong value: o.bar";
|
||||
|
||||
// Fill in the feedback.
|
||||
let r = o.foo();
|
||||
assertEquals("correct value", r);
|
||||
%OptimizeFunctionOnNextCall(C.prototype.foo);
|
||||
|
||||
// Test the optimized function.
|
||||
r = o.foo();
|
||||
assertEquals("correct value", r);
|
||||
|
||||
// Assert that the function was not deoptimized.
|
||||
assertOptimized(C.prototype.foo);
|
||||
|
||||
// Change the property value.
|
||||
B.prototype.bar = "new value";
|
||||
r = o.foo();
|
||||
assertEquals("new value", r);
|
||||
})();
|
||||
|
||||
(function TestPropertyIsGetterInTheHomeObjectsProto() {
|
||||
// Test where the property is a constant found on home object's proto. This
|
||||
// will generate a minimorphic property load.
|
||||
class A {}
|
||||
A.prototype.bar = "wrong value: A.prototype.bar";
|
||||
|
||||
class B extends A {
|
||||
get bar() { return this.this_value; }
|
||||
}
|
||||
class C extends B {
|
||||
foo() { return super.bar; }
|
||||
}
|
||||
C.prototype.bar = "wrong value: D.prototype.bar";
|
||||
%PrepareFunctionForOptimization(C.prototype.foo);
|
||||
|
||||
let o = new C();
|
||||
o.bar = "wrong value: o.bar";
|
||||
o.this_value = "correct value";
|
||||
|
||||
// Fill in the feedback.
|
||||
let r = o.foo();
|
||||
assertEquals("correct value", r);
|
||||
%OptimizeFunctionOnNextCall(C.prototype.foo);
|
||||
|
||||
// Test the optimized function.
|
||||
r = o.foo();
|
||||
assertEquals("correct value", r);
|
||||
|
||||
// Assert that the function was not deoptimized.
|
||||
assertOptimized(C.prototype.foo);
|
||||
|
||||
// Change the property value.
|
||||
o.this_value = "new value";
|
||||
r = o.foo();
|
||||
assertEquals("new value", r);
|
||||
})();
|
||||
|
||||
(function TestPropertyIsConstantInThePrototypeChain() {
|
||||
// Test where the property is a constant found on the prototype chain of the
|
||||
// lookup start object.
|
||||
class A {}
|
||||
A.prototype.bar = "wrong value: A.prototype.bar";
|
||||
|
||||
class B extends A {};
|
||||
B.prototype.bar = "correct value";
|
||||
|
||||
class C extends B {};
|
||||
|
||||
class D extends C {
|
||||
foo() { return super.bar; }
|
||||
}
|
||||
D.prototype.bar = "wrong value: D.prototype.bar";
|
||||
%PrepareFunctionForOptimization(D.prototype.foo);
|
||||
|
||||
let o = new D();
|
||||
o.bar = "wrong value: o.bar";
|
||||
|
||||
// Fill in the feedback.
|
||||
let r = o.foo();
|
||||
assertEquals("correct value", r);
|
||||
|
||||
%OptimizeFunctionOnNextCall(D.prototype.foo);
|
||||
|
||||
// Test the optimized function.
|
||||
r = o.foo();
|
||||
assertEquals("correct value", r);
|
||||
|
||||
// Assert that the function was not deoptimized.
|
||||
assertOptimized(D.prototype.foo);
|
||||
|
||||
// Change the property value.
|
||||
B.prototype.bar = "new value";
|
||||
r = o.foo();
|
||||
assertEquals("new value", r);
|
||||
|
||||
// Assert that the function was deoptimized (dependency to the constant
|
||||
// value).
|
||||
assertFalse(isOptimized(D.prototype.foo));
|
||||
})();
|
||||
|
||||
(function TestPropertyIsNonConstantData() {
|
||||
// Test for a case where the property is a non-constant data property found
|
||||
// in the lookup start object.
|
||||
class A {}
|
||||
A.prototype.bar = "wrong value: A.prototype.bar";
|
||||
|
||||
class B extends A {};
|
||||
B.prototype.bar = "initial value";
|
||||
|
||||
class C extends B {
|
||||
foo() { return super.bar; }
|
||||
}
|
||||
C.prototype.bar = "wrong value: C.prototype.bar";
|
||||
%PrepareFunctionForOptimization(C.prototype.foo);
|
||||
|
||||
let o = new C();
|
||||
o.bar = "wrong value: o.bar";
|
||||
|
||||
// Make the property look like a non-constant.
|
||||
B.prototype.bar = "correct value";
|
||||
|
||||
// Fill in the feedback.
|
||||
let r = o.foo();
|
||||
assertEquals("correct value", r);
|
||||
%OptimizeFunctionOnNextCall(C.prototype.foo);
|
||||
|
||||
// Test the optimized function.
|
||||
r = o.foo();
|
||||
assertEquals("correct value", r);
|
||||
|
||||
// Assert that the function was not deoptimized.
|
||||
assertOptimized(C.prototype.foo);
|
||||
|
||||
// Change the property value.
|
||||
B.prototype.bar = "new value";
|
||||
r = o.foo();
|
||||
assertEquals("new value", r);
|
||||
|
||||
// Assert that the function was still not deoptimized (the value was not a
|
||||
// constant to begin with).
|
||||
assertOptimized(C.prototype.foo);
|
||||
})();
|
||||
|
||||
(function TestPropertyIsGetter() {
|
||||
class A {}
|
||||
A.prototype.bar = "wrong value: A.prototype.bar";
|
||||
|
||||
class B extends A {
|
||||
get bar() {
|
||||
return this.test_value;
|
||||
}
|
||||
};
|
||||
|
||||
class C extends B {}
|
||||
|
||||
class D extends C {
|
||||
foo() {
|
||||
const b = super.bar;
|
||||
return b;
|
||||
}
|
||||
}
|
||||
%PrepareFunctionForOptimization(D.prototype.foo);
|
||||
D.prototype.bar = "wrong value: D.prototype.bar";
|
||||
|
||||
let o = new D();
|
||||
o.bar = "wrong value: o.bar";
|
||||
o.test_value = "correct value";
|
||||
|
||||
// Fill in the feedback.
|
||||
let r = o.foo();
|
||||
assertEquals("correct value", r);
|
||||
|
||||
%OptimizeFunctionOnNextCall(D.prototype.foo);
|
||||
|
||||
// Test the optimized function.
|
||||
r = o.foo();
|
||||
assertEquals("correct value", r);
|
||||
|
||||
// Assert that the function was not deoptimized.
|
||||
assertOptimized(D.prototype.foo);
|
||||
})();
|
||||
|
||||
(function TestPropertyInsertedInTheMiddle() {
|
||||
// Test for a case where the property is a constant found in the lookup start
|
||||
// object.
|
||||
class A {}
|
||||
A.prototype.bar = "correct value";
|
||||
|
||||
class B extends A {};
|
||||
|
||||
class C extends B {
|
||||
foo() { return super.bar; }
|
||||
}
|
||||
C.prototype.bar = "wrong value: C.prototype.bar";
|
||||
%PrepareFunctionForOptimization(C.prototype.foo);
|
||||
|
||||
let o = new C();
|
||||
o.bar = "wrong value: o.bar";
|
||||
|
||||
// Fill in the feedback.
|
||||
let r = o.foo();
|
||||
assertEquals("correct value", r);
|
||||
%OptimizeFunctionOnNextCall(C.prototype.foo);
|
||||
|
||||
// Test the optimized function.
|
||||
r = o.foo();
|
||||
assertEquals("correct value", r);
|
||||
|
||||
// Assert that the function was not deoptimized.
|
||||
assertOptimized(C.prototype.foo);
|
||||
|
||||
// Insert the property into the prototype chain between the lookup start
|
||||
// object and the old holder.
|
||||
B.prototype.bar = "new value";
|
||||
r = o.foo();
|
||||
assertEquals("new value", r);
|
||||
|
||||
// Assert that the function was deoptimized (holder changed).
|
||||
assertFalse(isOptimized(C.prototype.foo));
|
||||
})();
|
||||
|
||||
(function TestUnexpectedHomeObjectPrototypeDeoptimizes() {
|
||||
class A {}
|
||||
A.prototype.bar = "wrong value: A.prototype.bar";
|
||||
|
||||
class B extends A {}
|
||||
B.prototype.bar = "correct value";
|
||||
|
||||
class C extends B {}
|
||||
|
||||
class D extends C {
|
||||
foo() { return super.bar; }
|
||||
}
|
||||
%PrepareFunctionForOptimization(D.prototype.foo);
|
||||
D.prototype.bar = "wrong value: D.prototype.bar";
|
||||
|
||||
const o = new D();
|
||||
|
||||
// Fill in the feedback.
|
||||
let r = o.foo();
|
||||
assertEquals("correct value", r);
|
||||
|
||||
%OptimizeFunctionOnNextCall(D.prototype.foo);
|
||||
|
||||
// Test the optimized function.
|
||||
r = o.foo();
|
||||
assertEquals("correct value", r);
|
||||
|
||||
// Assert that the function was not deoptimized.
|
||||
assertOptimized(D.prototype.foo);
|
||||
|
||||
// Change the home object's prototype.
|
||||
D.prototype.__proto__ = {"bar": "new value"};
|
||||
r = o.foo();
|
||||
assertEquals("new value", r);
|
||||
|
||||
// Assert that the function was deoptimized.
|
||||
assertEquals(false, isOptimized(D.prototype.foo));
|
||||
})();
|
||||
|
||||
(function TestUnexpectedReceiverDoesNotDeoptimize() {
|
||||
class A {}
|
||||
A.prototype.bar = "wrong value: A.prototype.bar";
|
||||
|
||||
class B extends A {};
|
||||
B.prototype.bar = "correct value";
|
||||
|
||||
class C extends B {
|
||||
foo() { return super.bar; }
|
||||
}
|
||||
C.prototype.bar = "wrong value: C.prototype.bar";
|
||||
%PrepareFunctionForOptimization(C.prototype.foo);
|
||||
|
||||
let o = new C();
|
||||
o.bar = "wrong value: o.bar";
|
||||
|
||||
// Fill in the feedback.
|
||||
let r = o.foo();
|
||||
assertEquals("correct value", r);
|
||||
|
||||
%OptimizeFunctionOnNextCall(C.prototype.foo);
|
||||
o.foo();
|
||||
assertOptimized(C.prototype.foo);
|
||||
|
||||
// Test the optimized function with an unexpected receiver.
|
||||
r = C.prototype.foo.call({'lol': 5});
|
||||
assertEquals("correct value", r);
|
||||
|
||||
// Assert that the function was not deoptimized.
|
||||
assertOptimized(C.prototype.foo);
|
||||
})();
|
||||
|
||||
(function TestPolymorphic() {
|
||||
class A {}
|
||||
A.prototype.bar = "wrong value: A.prototype.bar";
|
||||
|
||||
class B extends A {}
|
||||
B.prototype.bar = "correct value";
|
||||
|
||||
class C extends B {}
|
||||
|
||||
class D extends C {
|
||||
foo() { return super.bar; }
|
||||
}
|
||||
%PrepareFunctionForOptimization(D.prototype.foo);
|
||||
D.prototype.bar = "wrong value: D.prototype.bar";
|
||||
|
||||
const o = new D();
|
||||
|
||||
// Create objects which will act as the "home object's prototype" later.
|
||||
const prototypes = [{"a": 0}, {"b": 0}];
|
||||
for (p of prototypes) {
|
||||
p.__proto__ = B.prototype;
|
||||
}
|
||||
|
||||
// Fill in the feedback (polymorphic).
|
||||
for (p of prototypes) {
|
||||
D.prototype.__proto__ = p;
|
||||
const r = o.foo();
|
||||
assertEquals("correct value", r);
|
||||
}
|
||||
|
||||
%OptimizeFunctionOnNextCall(D.prototype.foo);
|
||||
|
||||
// Test the optimized function - don't change the home object's proto any
|
||||
// more.
|
||||
let r = o.foo();
|
||||
assertEquals("correct value", r);
|
||||
|
||||
// Assert that the function was not deoptimized.
|
||||
assertOptimized(D.prototype.foo);
|
||||
})();
|
||||
|
||||
(function TestPolymorphicWithGetter() {
|
||||
class A {}
|
||||
A.prototype.bar = "wrong value: A.prototype.bar";
|
||||
|
||||
class B extends A {
|
||||
get bar() {
|
||||
return this.test_value;
|
||||
}
|
||||
}
|
||||
|
||||
class C extends B {}
|
||||
|
||||
class D extends C {
|
||||
foo() { return super.bar; }
|
||||
}
|
||||
%PrepareFunctionForOptimization(D.prototype.foo);
|
||||
D.prototype.bar = "wrong value: D.prototype.bar";
|
||||
|
||||
const o = new D();
|
||||
o.test_value = "correct value";
|
||||
|
||||
// Create objects which will act as the "home object's prototype" later.
|
||||
const prototypes = [{"a": 0}, {"b": 0}];
|
||||
for (p of prototypes) {
|
||||
p.__proto__ = B.prototype;
|
||||
}
|
||||
|
||||
// Fill in the feedback.
|
||||
for (p of prototypes) {
|
||||
D.prototype.__proto__ = p;
|
||||
const r = o.foo();
|
||||
assertEquals("correct value", r);
|
||||
}
|
||||
|
||||
%OptimizeFunctionOnNextCall(D.prototype.foo);
|
||||
|
||||
// Test the optimized function - don't change the home object's proto any
|
||||
// more.
|
||||
const r = o.foo();
|
||||
assertEquals("correct value", r);
|
||||
|
||||
// Assert that the function was not deoptimized.
|
||||
assertOptimized(D.prototype.foo);
|
||||
})();
|
||||
|
||||
(function TestPolymorphicMixinDoesNotDeopt() {
|
||||
function createClasses() {
|
||||
class A {}
|
||||
A.prototype.bar = "correct value";
|
||||
class B extends A {
|
||||
foo() { return super.bar; }
|
||||
}
|
||||
return B;
|
||||
}
|
||||
|
||||
const b1 = createClasses();
|
||||
%PrepareFunctionForOptimization(b1.prototype.foo);
|
||||
const b2 = createClasses();
|
||||
%PrepareFunctionForOptimization(b2.prototype.foo);
|
||||
|
||||
class c1 extends b1 {};
|
||||
class c2 extends b2 {};
|
||||
|
||||
const objects = [new c1(), new c2()];
|
||||
|
||||
// Fill in the feedback.
|
||||
for (o of objects) {
|
||||
const r = o.foo();
|
||||
assertEquals("correct value", r);
|
||||
}
|
||||
%OptimizeFunctionOnNextCall(b1.prototype.foo);
|
||||
%OptimizeFunctionOnNextCall(b2.prototype.foo);
|
||||
|
||||
// Test the optimized function.
|
||||
for (o of objects) {
|
||||
const r = o.foo();
|
||||
assertEquals("correct value", r);
|
||||
}
|
||||
assertOptimized(b1.prototype.foo);
|
||||
assertOptimized(b2.prototype.foo);
|
||||
})();
|
||||
|
||||
(function TestHomeObjectProtoIsGlobalThis() {
|
||||
class A {}
|
||||
|
||||
class B extends A {
|
||||
foo() { return super.bar; }
|
||||
}
|
||||
B.prototype.__proto__ = globalThis;
|
||||
globalThis.bar = "correct value";
|
||||
%PrepareFunctionForOptimization(B.prototype.foo);
|
||||
|
||||
let o = new B();
|
||||
|
||||
// Fill in the feedback.
|
||||
let r = o.foo();
|
||||
assertEquals("correct value", r);
|
||||
|
||||
%OptimizeFunctionOnNextCall(B.prototype.foo);
|
||||
|
||||
// Test the optimized function.
|
||||
r = o.foo();
|
||||
assertEquals("correct value", r);
|
||||
|
||||
// Assert that the function was not deoptimized.
|
||||
assertOptimized(B.prototype.foo);
|
||||
|
||||
globalThis.bar = "new value";
|
||||
|
||||
r = o.foo();
|
||||
assertEquals("new value", r);
|
||||
})();
|
||||
|
||||
// Assert that the tests so far generated real optimized code and not just a
|
||||
// bailout to Runtime_LoadFromSuper. TODO(marja, v8:9237): update this to track
|
||||
// the builtin we'll use for bailout cases.
|
||||
assertEquals(0, getRuntimeFunctionCallCount("LoadFromSuper"));
|
||||
|
||||
(function TestMegamorphic() {
|
||||
class A {}
|
||||
A.prototype.bar = "wrong value: A.prototype.bar";
|
||||
|
||||
class B extends A {}
|
||||
B.prototype.bar = "correct value";
|
||||
|
||||
class C extends B {}
|
||||
|
||||
class D extends C {
|
||||
foo() { return super.bar; }
|
||||
}
|
||||
%PrepareFunctionForOptimization(D.prototype.foo);
|
||||
D.prototype.bar = "wrong value: D.prototype.bar";
|
||||
|
||||
const o = new D();
|
||||
|
||||
// Create objects which will act as the "home object's prototype" later.
|
||||
const prototypes = [{"a": 0}, {"b": 0}, {"c": 0}, {"d": 0}, {"e": 0},
|
||||
{"f": 0}, {"g": 0}, {"e": 0}];
|
||||
for (p of prototypes) {
|
||||
p.__proto__ = B.prototype;
|
||||
}
|
||||
|
||||
// Fill in the feedback (megamorphic).
|
||||
for (p of prototypes) {
|
||||
D.prototype.__proto__ = p;
|
||||
const r = o.foo();
|
||||
assertEquals("correct value", r);
|
||||
}
|
||||
|
||||
%OptimizeFunctionOnNextCall(D.prototype.foo);
|
||||
|
||||
// Test the optimized function - don't change the home object's proto any
|
||||
// more.
|
||||
let r = o.foo();
|
||||
assertEquals("correct value", r);
|
||||
|
||||
// Assert that the function was not deoptimized.
|
||||
assertOptimized(D.prototype.foo);
|
||||
|
||||
// The "optimized code" is just a runtime call though. TODO(marja, v8:9237):
|
||||
// update this to track the builtin we'll use for bailout cases.
|
||||
assertEquals(1, getRuntimeFunctionCallCount("LoadFromSuper"));
|
||||
})();
|
||||
|
||||
(function TestMegamorphicWithGetter() {
|
||||
class A {}
|
||||
A.prototype.bar = "wrong value: A.prototype.bar";
|
||||
|
||||
class B extends A {
|
||||
get bar() {
|
||||
return this.test_value;
|
||||
}
|
||||
};
|
||||
|
||||
class C extends B {}
|
||||
|
||||
class D extends C {
|
||||
foo() { return super.bar;}
|
||||
}
|
||||
%PrepareFunctionForOptimization(D.prototype.foo);
|
||||
D.prototype.bar = "wrong value: D.prototype.bar";
|
||||
|
||||
const o = new D();
|
||||
o.test_value = "correct value";
|
||||
|
||||
// Create objects which will act as the "home object's prototype" later.
|
||||
const prototypes = [{"a": 0}, {"b": 0}, {"c": 0}, {"d": 0}, {"e": 0},
|
||||
{"f": 0}, {"g": 0}, {"e": 0}];
|
||||
for (p of prototypes) {
|
||||
p.__proto__ = B.prototype;
|
||||
}
|
||||
|
||||
// Fill in the feedback (megamorphic).
|
||||
for (p of prototypes) {
|
||||
D.prototype.__proto__ = p;
|
||||
const r = o.foo();
|
||||
assertEquals("correct value", r);
|
||||
}
|
||||
|
||||
%OptimizeFunctionOnNextCall(D.prototype.foo);
|
||||
|
||||
// Test the optimized function - don't change the home object's proto any
|
||||
// more.
|
||||
const r = o.foo();
|
||||
assertEquals("correct value", r);
|
||||
|
||||
// The "optimized code" is just a runtime call though. TODO(marja, v8:9237):
|
||||
// update this to track the builtin we'll use for bailout cases.
|
||||
assertEquals(1, getRuntimeFunctionCallCount("LoadFromSuper"));
|
||||
})();
|
||||
|
||||
(function TestHomeObjectProtoIsGlobalThisGetterProperty() {
|
||||
class A {}
|
||||
|
||||
class B extends A {
|
||||
foo() { return super.bar; }
|
||||
}
|
||||
B.prototype.__proto__ = globalThis;
|
||||
Object.defineProperty(globalThis, "bar", {get: function() { return this.this_value; }});
|
||||
%PrepareFunctionForOptimization(B.prototype.foo);
|
||||
|
||||
let o = new B();
|
||||
o.this_value = "correct value";
|
||||
|
||||
// Fill in the feedback.
|
||||
let r = o.foo();
|
||||
assertEquals("correct value", r);
|
||||
|
||||
%OptimizeFunctionOnNextCall(B.prototype.foo);
|
||||
|
||||
// Test the optimized function.
|
||||
r = o.foo();
|
||||
assertEquals("correct value", r);
|
||||
|
||||
// Assert that the function was not deoptimized.
|
||||
assertOptimized(B.prototype.foo);
|
||||
|
||||
// The "optimized code" is just a runtime call though. TODO(marja, v8:9237):
|
||||
// update this to track the builtin we'll use for bailout cases.
|
||||
assertEquals(1, getRuntimeFunctionCallCount("LoadFromSuper"));
|
||||
})();
|
||||
|
||||
(function TestHomeObjectProtoIsFunctionAndPropertyIsPrototype() {
|
||||
// There are special optimizations for accessing Function.prototype. Test
|
||||
// that super property access which ends up accessing it works.
|
||||
class A {}
|
||||
|
||||
class B extends A {
|
||||
foo() { return super.prototype; }
|
||||
}
|
||||
function f() {}
|
||||
B.prototype.__proto__ = f;
|
||||
%PrepareFunctionForOptimization(B.prototype.foo);
|
||||
|
||||
let o = new B();
|
||||
|
||||
// Fill in the feedback.
|
||||
let r = o.foo();
|
||||
assertEquals(f.prototype, r);
|
||||
|
||||
%OptimizeFunctionOnNextCall(B.prototype.foo);
|
||||
|
||||
// Test the optimized function.
|
||||
r = o.foo();
|
||||
assertEquals(f.prototype, r);
|
||||
|
||||
// Assert that the function was not deoptimized.
|
||||
assertOptimized(B.prototype.foo);
|
||||
|
||||
// The "optimized code" is just a runtime call though. TODO(marja, v8:9237):
|
||||
// update this to track the builtin we'll use for bailout cases.
|
||||
assertEquals(1, getRuntimeFunctionCallCount("LoadFromSuper"));
|
||||
})();
|
||||
|
||||
// Reset runtime stats so that we don't get extra printout.
|
||||
%GetAndResetRuntimeCallStats();
|
@ -1144,6 +1144,7 @@
|
||||
'compiler/number-comparison-truncations': [SKIP],
|
||||
'compiler/redundancy-elimination': [SKIP],
|
||||
'compiler/regress-9945-*': [SKIP],
|
||||
'es6/super-ic-opt-no-turboprop': [SKIP],
|
||||
|
||||
# Static asserts for optimizations don't hold due to removed optimization
|
||||
# phases.
|
||||
@ -1432,6 +1433,8 @@
|
||||
'compiler/serializer-feedback-propagation-1': [SKIP],
|
||||
'compiler/serializer-feedback-propagation-2': [SKIP],
|
||||
'compiler/serializer-transition-propagation': [SKIP],
|
||||
# crbug.com/v8/11110
|
||||
'es6/super-ic-opt*': [SKIP],
|
||||
}], # variant == nci or variant == nci_as_midtier
|
||||
|
||||
['((arch == mipsel or arch == mips64el or arch == mips or arch == mips64) and not simd_mips) or (arch in [ppc64, s390x])', {
|
||||
|
18
test/mjsunit/regress-1146106.js
Normal file
18
test/mjsunit/regress-1146106.js
Normal file
@ -0,0 +1,18 @@
|
||||
// Copyright 2020 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// Flags: --allow-natives-syntax --concurrent-inlining --no-use-ic --super-ic
|
||||
|
||||
class A {
|
||||
bar() { }
|
||||
}
|
||||
class B extends A {
|
||||
foo() {
|
||||
return super.bar();
|
||||
}
|
||||
}
|
||||
%PrepareFunctionForOptimization(B.prototype.foo);
|
||||
new B().foo();
|
||||
%OptimizeFunctionOnNextCall(B.prototype.foo);
|
||||
new B().foo();
|
@ -14,7 +14,7 @@ function getRuntimeFunctionCallCount(function_name) {
|
||||
const line = lines[i];
|
||||
const m = line.match(/(?<name>\S+)\s+\S+\s+\S+\s+(?<count>\S+)/);
|
||||
if (function_name == m.groups.name) {
|
||||
return m.groups.count;
|
||||
return parseInt(m.groups.count);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
|
Loading…
Reference in New Issue
Block a user