[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:
Marja Hölttä 2020-11-09 15:23:00 +01:00 committed by Commit Bot
parent 3669ecd6e4
commit 30ca51ec48
25 changed files with 1315 additions and 341 deletions

View File

@ -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:

View File

@ -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_;

View File

@ -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;
}

View File

@ -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();

View File

@ -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);
}

View File

@ -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: {

View File

@ -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();
}

View File

@ -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_; }

View File

@ -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

View File

@ -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
};

View File

@ -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)) {

View File

@ -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,

View File

@ -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()); }

View File

@ -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());

View File

@ -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,

View File

@ -1116,6 +1116,7 @@ class V8_EXPORT_PRIVATE IrOpcode {
case kJSInstanceOf:
case kJSLoadGlobal:
case kJSLoadNamed:
case kJSLoadNamedFromSuper:
case kJSLoadProperty:
case kJSStoreDataPropertyInLiteral:
case kJSStoreGlobal:

View File

@ -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,

View File

@ -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,

View File

@ -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 = &register_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

View 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();

View 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();

View 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();

View File

@ -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])', {

View 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();

View File

@ -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;