Commit 30ca51ec authored by Marja Hölttä's avatar Marja Hölttä Committed by Commit Bot

[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: 's avatarGeorg Neis <neis@chromium.org>
Cr-Commit-Position: refs/heads/master@{#71052}
parent 3669ecd6
......@@ -70,12 +70,13 @@ std::ostream& operator<<(std::ostream& os, AccessMode access_mode) {
UNREACHABLE();
}
ElementAccessInfo::ElementAccessInfo(ZoneVector<Handle<Map>>&& receiver_maps,
ElementsKind elements_kind, Zone* zone)
ElementAccessInfo::ElementAccessInfo(
ZoneVector<Handle<Map>>&& lookup_start_object_maps,
ElementsKind elements_kind, Zone* zone)
: elements_kind_(elements_kind),
receiver_maps_(receiver_maps),
lookup_start_object_maps_(lookup_start_object_maps),
transition_sources_(zone) {
CHECK(!receiver_maps.empty());
CHECK(!lookup_start_object_maps.empty());
}
// static
......@@ -158,27 +159,26 @@ MinimorphicLoadPropertyAccessInfo MinimorphicLoadPropertyAccessInfo::Invalid() {
PropertyAccessInfo::PropertyAccessInfo(Zone* zone)
: kind_(kInvalid),
receiver_maps_(zone),
lookup_start_object_maps_(zone),
unrecorded_dependencies_(zone),
field_representation_(Representation::None()),
field_type_(Type::None()) {}
PropertyAccessInfo::PropertyAccessInfo(Zone* zone, Kind kind,
MaybeHandle<JSObject> holder,
ZoneVector<Handle<Map>>&& receiver_maps)
PropertyAccessInfo::PropertyAccessInfo(
Zone* zone, Kind kind, MaybeHandle<JSObject> holder,
ZoneVector<Handle<Map>>&& lookup_start_object_maps)
: kind_(kind),
receiver_maps_(receiver_maps),
lookup_start_object_maps_(lookup_start_object_maps),
unrecorded_dependencies_(zone),
holder_(holder),
field_representation_(Representation::None()),
field_type_(Type::None()) {}
PropertyAccessInfo::PropertyAccessInfo(Zone* zone, Kind kind,
MaybeHandle<JSObject> holder,
Handle<Object> constant,
ZoneVector<Handle<Map>>&& receiver_maps)
PropertyAccessInfo::PropertyAccessInfo(
Zone* zone, Kind kind, MaybeHandle<JSObject> holder,
Handle<Object> constant, ZoneVector<Handle<Map>>&& lookup_start_object_maps)
: kind_(kind),
receiver_maps_(receiver_maps),
lookup_start_object_maps_(lookup_start_object_maps),
unrecorded_dependencies_(zone),
constant_(constant),
holder_(holder),
......@@ -189,10 +189,10 @@ PropertyAccessInfo::PropertyAccessInfo(
Kind kind, MaybeHandle<JSObject> holder, MaybeHandle<Map> transition_map,
FieldIndex field_index, Representation field_representation,
Type field_type, Handle<Map> field_owner_map, MaybeHandle<Map> field_map,
ZoneVector<Handle<Map>>&& receiver_maps,
ZoneVector<Handle<Map>>&& lookup_start_object_maps,
ZoneVector<CompilationDependency const*>&& unrecorded_dependencies)
: kind_(kind),
receiver_maps_(receiver_maps),
lookup_start_object_maps_(lookup_start_object_maps),
unrecorded_dependencies_(std::move(unrecorded_dependencies)),
transition_map_(transition_map),
holder_(holder),
......@@ -265,9 +265,10 @@ bool PropertyAccessInfo::Merge(PropertyAccessInfo const* that,
}
this->field_type_ =
Type::Union(this->field_type_, that->field_type_, zone);
this->receiver_maps_.insert(this->receiver_maps_.end(),
that->receiver_maps_.begin(),
that->receiver_maps_.end());
this->lookup_start_object_maps_.insert(
this->lookup_start_object_maps_.end(),
that->lookup_start_object_maps_.begin(),
that->lookup_start_object_maps_.end());
this->unrecorded_dependencies_.insert(
this->unrecorded_dependencies_.end(),
that->unrecorded_dependencies_.begin(),
......@@ -282,9 +283,10 @@ bool PropertyAccessInfo::Merge(PropertyAccessInfo const* that,
if (this->constant_.address() == that->constant_.address()) {
DCHECK(this->unrecorded_dependencies_.empty());
DCHECK(that->unrecorded_dependencies_.empty());
this->receiver_maps_.insert(this->receiver_maps_.end(),
that->receiver_maps_.begin(),
that->receiver_maps_.end());
this->lookup_start_object_maps_.insert(
this->lookup_start_object_maps_.end(),
that->lookup_start_object_maps_.begin(),
that->lookup_start_object_maps_.end());
return true;
}
return false;
......@@ -294,9 +296,10 @@ bool PropertyAccessInfo::Merge(PropertyAccessInfo const* that,
case kStringLength: {
DCHECK(this->unrecorded_dependencies_.empty());
DCHECK(that->unrecorded_dependencies_.empty());
this->receiver_maps_.insert(this->receiver_maps_.end(),
that->receiver_maps_.begin(),
that->receiver_maps_.end());
this->lookup_start_object_maps_.insert(
this->lookup_start_object_maps_.end(),
that->lookup_start_object_maps_.begin(),
that->lookup_start_object_maps_.end());
return true;
}
case kModuleExport:
......
......@@ -37,25 +37,25 @@ std::ostream& operator<<(std::ostream&, AccessMode);
// This class encapsulates all information required to access a certain element.
class ElementAccessInfo final {
public:
ElementAccessInfo(ZoneVector<Handle<Map>>&& receiver_maps,
ElementAccessInfo(ZoneVector<Handle<Map>>&& lookup_start_object_maps,
ElementsKind elements_kind, Zone* zone);
ElementsKind elements_kind() const { return elements_kind_; }
ZoneVector<Handle<Map>> const& receiver_maps() const {
return receiver_maps_;
ZoneVector<Handle<Map>> const& lookup_start_object_maps() const {
return lookup_start_object_maps_;
}
ZoneVector<Handle<Map>> const& transition_sources() const {
return transition_sources_;
}
void AddTransitionSource(Handle<Map> map) {
CHECK_EQ(receiver_maps_.size(), 1);
CHECK_EQ(lookup_start_object_maps_.size(), 1);
transition_sources_.push_back(map);
}
private:
ElementsKind elements_kind_;
ZoneVector<Handle<Map>> receiver_maps_;
ZoneVector<Handle<Map>> lookup_start_object_maps_;
ZoneVector<Handle<Map>> transition_sources_;
};
......@@ -128,26 +128,26 @@ class PropertyAccessInfo final {
Type field_type() const { return field_type_; }
Representation field_representation() const { return field_representation_; }
MaybeHandle<Map> field_map() const { return field_map_; }
ZoneVector<Handle<Map>> const& receiver_maps() const {
return receiver_maps_;
ZoneVector<Handle<Map>> const& lookup_start_object_maps() const {
return lookup_start_object_maps_;
}
private:
explicit PropertyAccessInfo(Zone* zone);
PropertyAccessInfo(Zone* zone, Kind kind, MaybeHandle<JSObject> holder,
ZoneVector<Handle<Map>>&& receiver_maps);
ZoneVector<Handle<Map>>&& lookup_start_object_maps);
PropertyAccessInfo(Zone* zone, Kind kind, MaybeHandle<JSObject> holder,
Handle<Object> constant,
ZoneVector<Handle<Map>>&& receiver_maps);
ZoneVector<Handle<Map>>&& lookup_start_object_maps);
PropertyAccessInfo(Kind kind, MaybeHandle<JSObject> holder,
MaybeHandle<Map> transition_map, FieldIndex field_index,
Representation field_representation, Type field_type,
Handle<Map> field_owner_map, MaybeHandle<Map> field_map,
ZoneVector<Handle<Map>>&& receiver_maps,
ZoneVector<Handle<Map>>&& lookup_start_object_maps,
ZoneVector<CompilationDependency const*>&& dependencies);
Kind kind_;
ZoneVector<Handle<Map>> receiver_maps_;
ZoneVector<Handle<Map>> lookup_start_object_maps_;
ZoneVector<CompilationDependency const*> unrecorded_dependencies_;
Handle<Object> constant_;
MaybeHandle<Map> transition_map_;
......
......@@ -264,7 +264,7 @@ class BytecodeGraphBuilder {
const Operator* op, Node* receiver, FeedbackSlot load_slot,
FeedbackSlot call_slot);
JSTypeHintLowering::LoweringResult TryBuildSimplifiedLoadNamed(
const Operator* op, Node* receiver, FeedbackSlot slot);
const Operator* op, FeedbackSlot slot);
JSTypeHintLowering::LoweringResult TryBuildSimplifiedLoadKeyed(
const Operator* op, Node* receiver, Node* key, FeedbackSlot slot);
JSTypeHintLowering::LoweringResult TryBuildSimplifiedStoreNamed(
......@@ -2019,7 +2019,7 @@ void BytecodeGraphBuilder::VisitLdaNamedProperty() {
const Operator* op = javascript()->LoadNamed(name.object(), feedback);
JSTypeHintLowering::LoweringResult lowering =
TryBuildSimplifiedLoadNamed(op, object, feedback.slot);
TryBuildSimplifiedLoadNamed(op, feedback.slot);
if (lowering.IsExit()) return;
Node* node = nullptr;
......@@ -2052,10 +2052,24 @@ void BytecodeGraphBuilder::VisitLdaNamedPropertyFromSuper() {
Node* home_object = environment()->LookupAccumulator();
NameRef name(broker(),
bytecode_iterator().GetConstantForIndexOperand(1, isolate()));
const Operator* op = javascript()->LoadNamedFromSuper(name.object());
// TODO(marja, v8:9237): Use lowering.
Node* node = NewNode(op, receiver, home_object);
FeedbackSource feedback =
CreateFeedbackSource(bytecode_iterator().GetIndexOperand(2));
const Operator* op =
javascript()->LoadNamedFromSuper(name.object(), feedback);
JSTypeHintLowering::LoweringResult lowering =
TryBuildSimplifiedLoadNamed(op, feedback.slot);
if (lowering.IsExit()) return;
Node* node = nullptr;
if (lowering.IsSideEffectFree()) {
node = lowering.value();
} else {
DCHECK(!lowering.Changed());
DCHECK(IrOpcode::IsFeedbackCollectingOpcode(op->opcode()));
node = NewNode(op, receiver, home_object, feedback_vector_node());
}
environment()->BindAccumulator(node, Environment::kAttachFrameState);
}
......@@ -4222,14 +4236,12 @@ BytecodeGraphBuilder::TryBuildSimplifiedGetIterator(const Operator* op,
JSTypeHintLowering::LoweringResult
BytecodeGraphBuilder::TryBuildSimplifiedLoadNamed(const Operator* op,
Node* receiver,
FeedbackSlot slot) {
if (!CanApplyTypeHintLowering(op)) return NoChange();
Node* effect = environment()->GetEffectDependency();
Node* control = environment()->GetControlDependency();
JSTypeHintLowering::LoweringResult early_reduction =
type_hint_lowering().ReduceLoadNamedOperation(op, receiver, effect,
control, slot);
type_hint_lowering().ReduceLoadNamedOperation(op, effect, control, slot);
ApplyEarlyReduction(early_reduction);
return early_reduction;
}
......
......@@ -7648,7 +7648,7 @@ Reduction JSCallReducer::ReduceRegExpPrototypeTest(Node* node) {
// Add proper dependencies on the {regexp}s [[Prototype]]s.
dependencies()->DependOnStablePrototypeChains(
ai_exec.receiver_maps(), kStartAtPrototype,
ai_exec.lookup_start_object_maps(), kStartAtPrototype,
JSObjectRef(broker(), holder));
} else {
return inference.NoChange();
......
......@@ -318,8 +318,10 @@ void JSGenericLowering::LowerJSLoadNamed(Node* node) {
}
void JSGenericLowering::LowerJSLoadNamedFromSuper(Node* node) {
// TODO(marja, v8:9237): Call a builtin which collects feedback.
JSLoadNamedFromSuperNode n(node);
NamedAccess const& p = n.Parameters();
node->RemoveInput(2); // Feedback vector
node->InsertInput(zone(), 2, jsgraph()->HeapConstant(p.name()));
ReplaceWithRuntimeCall(node, Runtime::kLoadFromSuper);
}
......
......@@ -172,10 +172,12 @@ Reduction JSHeapCopyReducer::Reduce(Node* node) {
break;
}
case IrOpcode::kJSLoadNamedFromSuper: {
// TODO(marja, v8:9237): Process feedback once it's added to the byte
// code.
NamedAccess const& p = NamedAccessOf(node->op());
NameRef name(broker(), p.name());
if (p.feedback().IsValid()) {
broker()->ProcessFeedbackForPropertyAccess(p.feedback(),
AccessMode::kLoad, name);
}
break;
}
case IrOpcode::kJSStoreNamed: {
......
......@@ -83,6 +83,7 @@ class V8_EXPORT_PRIVATE JSNativeContextSpecialization final
Reduction ReduceJSLoadGlobal(Node* node);
Reduction ReduceJSStoreGlobal(Node* node);
Reduction ReduceJSLoadNamed(Node* node);
Reduction ReduceJSLoadNamedFromSuper(Node* node);
Reduction ReduceJSGetIterator(Node* node);
Reduction ReduceJSStoreNamed(Node* node);
Reduction ReduceJSHasProperty(Node* node);
......@@ -94,7 +95,7 @@ class V8_EXPORT_PRIVATE JSNativeContextSpecialization final
Reduction ReduceJSToObject(Node* node);
Reduction ReduceElementAccess(Node* node, Node* index, Node* value,
ElementAccessFeedback const& processed);
ElementAccessFeedback const& feedback);
// In the case of non-keyed (named) accesses, pass the name as {static_name}
// and use {nullptr} for {key} (load/store modes are irrelevant).
Reduction ReducePropertyAccess(Node* node, Node* key,
......@@ -102,18 +103,21 @@ class V8_EXPORT_PRIVATE JSNativeContextSpecialization final
Node* value, FeedbackSource const& source,
AccessMode access_mode);
Reduction ReduceNamedAccess(Node* node, Node* value,
NamedAccessFeedback const& processed,
NamedAccessFeedback const& feedback,
AccessMode access_mode, Node* key = nullptr);
Reduction ReduceMinimorphicPropertyAccess(
Node* node, Node* value,
MinimorphicLoadPropertyAccessFeedback const& feedback,
FeedbackSource const& source);
Reduction ReduceGlobalAccess(Node* node, Node* receiver, Node* value,
NameRef const& name, AccessMode access_mode,
Node* key = nullptr);
Reduction ReduceGlobalAccess(Node* node, Node* receiver, Node* value,
NameRef const& name, AccessMode access_mode,
Node* key, PropertyCellRef const& property_cell);
Reduction ReduceGlobalAccess(Node* node, Node* lookup_start_object,
Node* receiver, Node* value, NameRef const& name,
AccessMode access_mode, Node* key = nullptr,
Node* effect = nullptr);
Reduction ReduceGlobalAccess(Node* node, Node* lookup_start_object,
Node* receiver, Node* value, NameRef const& name,
AccessMode access_mode, Node* key,
PropertyCellRef const& property_cell,
Node* effect = nullptr);
Reduction ReduceElementLoadFromHeapConstant(Node* node, Node* key,
AccessMode access_mode,
KeyedAccessLoadMode load_mode);
......@@ -146,14 +150,13 @@ class V8_EXPORT_PRIVATE JSNativeContextSpecialization final
};
// Construct the appropriate subgraph for property access.
ValueEffectControl BuildPropertyAccess(Node* receiver, Node* value,
Node* context, Node* frame_state,
Node* effect, Node* control,
NameRef const& name,
ZoneVector<Node*>* if_exceptions,
PropertyAccessInfo const& access_info,
AccessMode access_mode);
ValueEffectControl BuildPropertyLoad(Node* receiver, Node* context,
ValueEffectControl BuildPropertyAccess(
Node* lookup_start_object, Node* receiver, Node* value, Node* context,
Node* frame_state, Node* effect, Node* control, NameRef const& name,
ZoneVector<Node*>* if_exceptions, PropertyAccessInfo const& access_info,
AccessMode access_mode);
ValueEffectControl BuildPropertyLoad(Node* lookup_start_object,
Node* receiver, Node* context,
Node* frame_state, Node* effect,
Node* control, NameRef const& name,
ZoneVector<Node*>* if_exceptions,
......@@ -212,20 +215,19 @@ class V8_EXPORT_PRIVATE JSNativeContextSpecialization final
// code dependencies and might use the array protector cell.
bool CanTreatHoleAsUndefined(ZoneVector<Handle<Map>> const& receiver_maps);
void RemoveImpossibleReceiverMaps(
Node* receiver, ZoneVector<Handle<Map>>* receiver_maps) const;
void RemoveImpossibleMaps(Node* object, ZoneVector<Handle<Map>>* maps) const;
ElementAccessFeedback const& TryRefineElementAccessFeedback(
ElementAccessFeedback const& feedback, Node* receiver,
Node* effect) const;
// Try to infer maps for the given {receiver} at the current {effect}.
bool InferReceiverMaps(Node* receiver, Node* effect,
ZoneVector<Handle<Map>>* receiver_maps) const;
// Try to infer maps for the given {object} at the current {effect}.
bool InferMaps(Node* object, Node* effect,
ZoneVector<Handle<Map>>* maps) const;
// Try to infer a root map for the {receiver} independent of the current
// program location.
base::Optional<MapRef> InferReceiverRootMap(Node* receiver) const;
// Try to infer a root map for the {object} independent of the current program
// location.
base::Optional<MapRef> InferRootMap(Node* object) const;
// Checks if we know at compile time that the {receiver} either definitely
// has the {prototype} in it's prototype chain, or the {receiver} definitely
......@@ -238,6 +240,8 @@ class V8_EXPORT_PRIVATE JSNativeContextSpecialization final
InferHasInPrototypeChainResult InferHasInPrototypeChain(
Node* receiver, Node* effect, HeapObjectRef const& prototype);
Node* BuildLoadPrototypeFromObject(Node* object, Node* effect, Node* control);
Graph* graph() const;
JSGraph* jsgraph() const { return jsgraph_; }
......
......@@ -935,12 +935,13 @@ const Operator* JSOperatorBuilder::LoadNamed(Handle<Name> name,
access); // parameter
}
const Operator* JSOperatorBuilder::LoadNamedFromSuper(Handle<Name> name) {
const Operator* JSOperatorBuilder::LoadNamedFromSuper(
Handle<Name> name, const FeedbackSource& feedback) {
static constexpr int kReceiver = 1;
static constexpr int kHomeObject = 1;
static constexpr int kArity = kReceiver + kHomeObject;
// TODO(marja, v8:9237): Use real feedback.
NamedAccess access(LanguageMode::kSloppy, name, FeedbackSource());
static constexpr int kFeedbackVector = 1;
static constexpr int kArity = kReceiver + kHomeObject + kFeedbackVector;
NamedAccess access(LanguageMode::kSloppy, name, feedback);
return zone()->New<Operator1<NamedAccess>>( // --
IrOpcode::kJSLoadNamedFromSuper, Operator::kNoProperties, // opcode
"JSLoadNamedFromSuper", // name
......
......@@ -937,7 +937,8 @@ class V8_EXPORT_PRIVATE JSOperatorBuilder final
const Operator* LoadProperty(FeedbackSource const& feedback);
const Operator* LoadNamed(Handle<Name> name, FeedbackSource const& feedback);
const Operator* LoadNamedFromSuper(Handle<Name> name);
const Operator* LoadNamedFromSuper(Handle<Name> name,
FeedbackSource const& feedback);
const Operator* StoreProperty(LanguageMode language_mode,
FeedbackSource const& feedback);
......@@ -1413,9 +1414,13 @@ class JSLoadNamedFromSuperNode final : public JSNodeWrapperBase {
const NamedAccess& Parameters() const { return NamedAccessOf(node()->op()); }
#define INPUTS(V) \
V(Receiver, receiver, 0, Object) \
V(Object, home_object, 1, Object)
// TODO(marja, v8:9237): A more intuitive order would be (home_object,
// receiver, feedback_vector). The order can be changed once we no longer
// delegate to Runtime_LoadFromSuper.
#define INPUTS(V) \
V(Receiver, receiver, 0, Object) \
V(HomeObject, home_object, 1, Object) \
V(FeedbackVector, feedback_vector, 2, HeapObject)
INPUTS(DEFINE_INPUT_ACCESSORS)
#undef INPUTS
};
......
......@@ -513,9 +513,9 @@ JSTypeHintLowering::ReduceGetIteratorOperation(const Operator* op,
}
JSTypeHintLowering::LoweringResult JSTypeHintLowering::ReduceLoadNamedOperation(
const Operator* op, Node* receiver, Node* effect, Node* control,
FeedbackSlot slot) const {
DCHECK_EQ(IrOpcode::kJSLoadNamed, op->opcode());
const Operator* op, Node* effect, Node* control, FeedbackSlot slot) const {
DCHECK(op->opcode() == IrOpcode::kJSLoadNamed ||
op->opcode() == IrOpcode::kJSLoadNamedFromSuper);
if (Node* node = TryBuildSoftDeopt(
slot, effect, control,
DeoptimizeReason::kInsufficientTypeFeedbackForGenericNamedAccess)) {
......
......@@ -144,8 +144,8 @@ class JSTypeHintLowering {
FeedbackSlot call_slot) const;
// Potential reduction of property access operations.
LoweringResult ReduceLoadNamedOperation(const Operator* op, Node* obj,
Node* effect, Node* control,
LoweringResult ReduceLoadNamedOperation(const Operator* op, Node* effect,
Node* control,
FeedbackSlot slot) const;
LoweringResult ReduceLoadKeyedOperation(const Operator* op, Node* obj,
Node* key, Node* effect,
......
......@@ -19,12 +19,12 @@ MapInference::MapInference(JSHeapBroker* broker, Node* object, Node* effect)
: broker_(broker), object_(object) {
ZoneHandleSet<Map> maps;
auto result =
NodeProperties::InferReceiverMapsUnsafe(broker_, object_, effect, &maps);
NodeProperties::InferMapsUnsafe(broker_, object_, effect, &maps);
maps_.insert(maps_.end(), maps.begin(), maps.end());
maps_state_ = (result == NodeProperties::kUnreliableReceiverMaps)
maps_state_ = (result == NodeProperties::kUnreliableMaps)
? kUnreliableDontNeedGuard
: kReliableOrGuarded;
DCHECK_EQ(maps_.empty(), result == NodeProperties::kNoReceiverMaps);
DCHECK_EQ(maps_.empty(), result == NodeProperties::kNoMaps);
}
MapInference::~MapInference() { CHECK(Safe()); }
......
......@@ -349,7 +349,7 @@ base::Optional<MapRef> NodeProperties::GetJSCreateMap(JSHeapBroker* broker,
}
// static
NodeProperties::InferReceiverMapsResult NodeProperties::InferReceiverMapsUnsafe(
NodeProperties::InferMapsResult NodeProperties::InferMapsUnsafe(
JSHeapBroker* broker, Node* receiver, Node* effect,
ZoneHandleSet<Map>* maps_return) {
HeapObjectMatcher m(receiver);
......@@ -368,11 +368,11 @@ NodeProperties::InferReceiverMapsResult NodeProperties::InferReceiverMapsUnsafe(
// The {receiver_map} is only reliable when we install a stability
// code dependency.
*maps_return = ZoneHandleSet<Map>(receiver.map().object());
return kUnreliableReceiverMaps;
return kUnreliableMaps;
}
}
}
InferReceiverMapsResult result = kReliableReceiverMaps;
InferMapsResult result = kReliableMaps;
while (true) {
switch (effect->opcode()) {
case IrOpcode::kMapGuard: {
......@@ -399,9 +399,9 @@ NodeProperties::InferReceiverMapsResult NodeProperties::InferReceiverMapsUnsafe(
return result;
}
// We reached the allocation of the {receiver}.
return kNoReceiverMaps;
return kNoMaps;
}
result = kUnreliableReceiverMaps; // JSCreate can have side-effect.
result = kUnreliableMaps; // JSCreate can have side-effect.
break;
}
case IrOpcode::kJSCreatePromise: {
......@@ -430,7 +430,7 @@ NodeProperties::InferReceiverMapsResult NodeProperties::InferReceiverMapsUnsafe(
}
// Without alias analysis we cannot tell whether this
// StoreField[map] affects {receiver} or not.
result = kUnreliableReceiverMaps;
result = kUnreliableMaps;
}
break;
}
......@@ -453,25 +453,25 @@ NodeProperties::InferReceiverMapsResult NodeProperties::InferReceiverMapsUnsafe(
if (control->opcode() != IrOpcode::kLoop) {
DCHECK(control->opcode() == IrOpcode::kDead ||
control->opcode() == IrOpcode::kMerge);
return kNoReceiverMaps;
return kNoMaps;
}
// Continue search for receiver map outside the loop. Since operations
// inside the loop may change the map, the result is unreliable.
effect = GetEffectInput(effect, 0);
result = kUnreliableReceiverMaps;
result = kUnreliableMaps;
continue;
}
default: {
DCHECK_EQ(1, effect->op()->EffectOutputCount());
if (effect->op()->EffectInputCount() != 1) {
// Didn't find any appropriate CheckMaps node.
return kNoReceiverMaps;
return kNoMaps;
}
if (!effect->op()->HasProperty(Operator::kNoWrite)) {
// Without alias/escape analysis we cannot tell whether this
// {effect} affects {receiver} or not.
result = kUnreliableReceiverMaps;
result = kUnreliableMaps;
}
break;
}
......@@ -479,7 +479,7 @@ NodeProperties::InferReceiverMapsResult NodeProperties::InferReceiverMapsUnsafe(
// Stop walking the effect chain once we hit the definition of
// the {receiver} along the {effect}s.
if (IsSame(receiver, effect)) return kNoReceiverMaps;
if (IsSame(receiver, effect)) return kNoMaps;
// Continue with the next {effect}.
DCHECK_EQ(1, effect->op()->EffectInputCount());
......
......@@ -203,15 +203,15 @@ class V8_EXPORT_PRIVATE NodeProperties final {
// Walks up the {effect} chain to find a witness that provides map
// information about the {receiver}. Can look through potentially
// side effecting nodes.
enum InferReceiverMapsResult {
kNoReceiverMaps, // No receiver maps inferred.
kReliableReceiverMaps, // Receiver maps can be trusted.
kUnreliableReceiverMaps // Receiver maps might have changed (side-effect).
enum InferMapsResult {
kNoMaps, // No maps inferred.
kReliableMaps, // Maps can be trusted.
kUnreliableMaps // Maps might have changed (side-effect).
};
// DO NOT USE InferReceiverMapsUnsafe IN NEW CODE. Use MapInference instead.
static InferReceiverMapsResult InferReceiverMapsUnsafe(
JSHeapBroker* broker, Node* receiver, Node* effect,
ZoneHandleSet<Map>* maps_return);
// DO NOT USE InferMapsUnsafe IN NEW CODE. Use MapInference instead.
static InferMapsResult InferMapsUnsafe(JSHeapBroker* broker, Node* object,
Node* effect,
ZoneHandleSet<Map>* maps);
// Return the initial map of the new-target if the allocation can be inlined.
static base::Optional<MapRef> GetJSCreateMap(JSHeapBroker* broker,
......
......@@ -1116,6 +1116,7 @@ class V8_EXPORT_PRIVATE IrOpcode {
case kJSInstanceOf:
case kJSLoadGlobal:
case kJSLoadNamed:
case kJSLoadNamedFromSuper:
case kJSLoadProperty:
case kJSStoreDataPropertyInLiteral:
case kJSStoreGlobal:
......
......@@ -82,30 +82,30 @@ bool PropertyAccessBuilder::TryBuildNumberCheck(
}
void PropertyAccessBuilder::BuildCheckMaps(
Node* receiver, Node** effect, Node* control,
ZoneVector<Handle<Map>> const& receiver_maps) {
HeapObjectMatcher m(receiver);
Node* object, Node** effect, Node* control,
ZoneVector<Handle<Map>> const& maps) {
HeapObjectMatcher m(object);
if (m.HasResolvedValue()) {
MapRef receiver_map = m.Ref(broker()).map();
if (receiver_map.is_stable()) {
for (Handle<Map> map : receiver_maps) {
if (MapRef(broker(), map).equals(receiver_map)) {
dependencies()->DependOnStableMap(receiver_map);
MapRef object_map = m.Ref(broker()).map();
if (object_map.is_stable()) {
for (Handle<Map> map : maps) {
if (MapRef(broker(), map).equals(object_map)) {
dependencies()->DependOnStableMap(object_map);
return;
}
}
}
}
ZoneHandleSet<Map> maps;
ZoneHandleSet<Map> map_set;
CheckMapsFlags flags = CheckMapsFlag::kNone;
for (Handle<Map> map : receiver_maps) {
MapRef receiver_map(broker(), map);
maps.insert(receiver_map.object(), graph()->zone());
if (receiver_map.is_migration_target()) {
for (Handle<Map> map : maps) {
MapRef object_map(broker(), map);
map_set.insert(object_map.object(), graph()->zone());
if (object_map.is_migration_target()) {
flags |= CheckMapsFlag::kTryMigrateInstance;
}
}
*effect = graph()->NewNode(simplified()->CheckMaps(flags, maps), receiver,
*effect = graph()->NewNode(simplified()->CheckMaps(flags, map_set), object,
*effect, control);
}
......@@ -124,12 +124,12 @@ Node* PropertyAccessBuilder::BuildCheckValue(Node* receiver, Effect* effect,
}
Node* PropertyAccessBuilder::ResolveHolder(
PropertyAccessInfo const& access_info, Node* receiver) {
PropertyAccessInfo const& access_info, Node* lookup_start_object) {
Handle<JSObject> holder;
if (access_info.holder().ToHandle(&holder)) {
return jsgraph()->Constant(ObjectRef(broker(), holder));
}
return receiver;
return lookup_start_object;
}
MachineRepresentation PropertyAccessBuilder::ConvertRepresentation(
......@@ -150,25 +150,27 @@ MachineRepresentation PropertyAccessBuilder::ConvertRepresentation(
Node* PropertyAccessBuilder::TryBuildLoadConstantDataField(
NameRef const& name, PropertyAccessInfo const& access_info,
Node* receiver) {
Node* lookup_start_object) {
if (!access_info.IsDataConstant()) return nullptr;
// First, determine if we have a constant holder to load from.
Handle<JSObject> holder;
// If {access_info} has a holder, just use it.
if (!access_info.holder().ToHandle(&holder)) {
// Otherwise, try to match the {receiver} as a constant.
HeapObjectMatcher m(receiver);
// Otherwise, try to match the {lookup_start_object} as a constant.
HeapObjectMatcher m(lookup_start_object);
if (!m.HasResolvedValue() || !m.Ref(broker()).IsJSObject()) return nullptr;
// Let us make sure the actual map of the constant receiver is among
// the maps in {access_info}.
MapRef receiver_map = m.Ref(broker()).map();
if (std::find_if(access_info.receiver_maps().begin(),
access_info.receiver_maps().end(), [&](Handle<Map> map) {
return MapRef(broker(), map).equals(receiver_map);
}) == access_info.receiver_maps().end()) {
// The map of the receiver is not in the feedback, let us bail out.
// Let us make sure the actual map of the constant lookup_start_object is
// among the maps in {access_info}.
MapRef lookup_start_object_map = m.Ref(broker()).map();
if (std::find_if(
access_info.lookup_start_object_maps().begin(),
access_info.lookup_start_object_maps().end(), [&](Handle<Map> map) {
return MapRef(broker(), map).equals(lookup_start_object_map);
}) == access_info.lookup_start_object_maps().end()) {
// The map of the lookup_start_object is not in the feedback, let us bail
// out.
return nullptr;
}
holder = m.Ref(broker()).AsJSObject().object();
......@@ -253,7 +255,7 @@ Node* PropertyAccessBuilder::BuildLoadDataField(NameRef const& name,
Node* PropertyAccessBuilder::BuildMinimorphicLoadDataField(
NameRef const& name, MinimorphicLoadPropertyAccessInfo const& access_info,
Node* receiver, Node** effect, Node** control) {
Node* lookup_start_object, Node** effect, Node** control) {
DCHECK_NULL(dependencies());
MachineRepresentation const field_representation =
ConvertRepresentation(access_info.field_representation());
......@@ -268,22 +270,22 @@ Node* PropertyAccessBuilder::BuildMinimorphicLoadDataField(
kFullWriteBarrier,
LoadSensitivity::kCritical,
ConstFieldInfo::None()};
return BuildLoadDataField(name, receiver, field_access,
return BuildLoadDataField(name, lookup_start_object, field_access,
access_info.is_inobject(), effect, control);
}
Node* PropertyAccessBuilder::BuildLoadDataField(
NameRef const& name, PropertyAccessInfo const& access_info, Node* receiver,
Node** effect, Node** control) {
NameRef const& name, PropertyAccessInfo const& access_info,
Node* lookup_start_object, Node** effect, Node** control) {
DCHECK(access_info.IsDataField() || access_info.IsDataConstant());
if (Node* value =
TryBuildLoadConstantDataField(name, access_info, receiver)) {
if (Node* value = TryBuildLoadConstantDataField(name, access_info,
lookup_start_object)) {
return value;
}
MachineRepresentation const field_representation =
ConvertRepresentation(access_info.field_representation());
Node* storage = ResolveHolder(access_info, receiver);
Node* storage = ResolveHolder(access_info, lookup_start_object);
FieldAccess field_access = {
kTaggedBase,
......
......@@ -45,13 +45,13 @@ class PropertyAccessBuilder {
// TODO(jgruber): Remove the untyped version once all uses are
// updated.
void BuildCheckMaps(Node* receiver, Node** effect, Node* control,
ZoneVector<Handle<Map>> const& receiver_maps);
void BuildCheckMaps(Node* receiver, Effect* effect, Control control,
ZoneVector<Handle<Map>> const& receiver_maps) {
void BuildCheckMaps(Node* object, Node** effect, Node* control,
ZoneVector<Handle<Map>> const& maps);
void BuildCheckMaps(Node* object, Effect* effect, Control control,
ZoneVector<Handle<Map>> const& maps) {
Node* e = *effect;
Node* c = control;
BuildCheckMaps(receiver, &e, c, receiver_maps);
BuildCheckMaps(object, &e, c, maps);
*effect = e;
}
Node* BuildCheckValue(Node* receiver, Effect* effect, Control control,
......@@ -61,13 +61,14 @@ class PropertyAccessBuilder {
// properties (without heap-object or map checks).
Node* BuildLoadDataField(NameRef const& name,
PropertyAccessInfo const& access_info,
Node* receiver, Node** effect, Node** control);
Node* lookup_start_object, Node** effect,
Node** control);
// Builds the load for data-field access for minimorphic loads that use
// dynamic map checks. These cannot depend on any information from the maps.
Node* BuildMinimorphicLoadDataField(
NameRef const& name, MinimorphicLoadPropertyAccessInfo const& access_info,
Node* receiver, Node** effect, Node** control);
Node* lookup_start_object, Node** effect, Node** control);
static MachineRepresentation ConvertRepresentation(
Representation representation);
......@@ -83,10 +84,11 @@ class PropertyAccessBuilder {
Node* TryBuildLoadConstantDataField(NameRef const& name,
PropertyAccessInfo const& access_info,
Node* receiver);
Node* lookup_start_object);
// Returns a node with the holder for the property access described by
// {access_info}.
Node* ResolveHolder(PropertyAccessInfo const& access_info, Node* receiver);
Node* ResolveHolder(PropertyAccessInfo const& access_info,
Node* lookup_start_object);
Node* BuildLoadDataField(NameRef const& name, Node* holder,
FieldAccess& field_access, bool is_inobject,
......
// 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();
// 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();
This diff is collapsed.
......@@ -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])', {
......
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --allow-natives-syntax --concurrent-inlining --no-use-ic --super-ic
class A {
bar() { }
}
class B extends A {
foo() {
return super.bar();
}
}
%PrepareFunctionForOptimization(B.prototype.foo);
new B().foo();
%OptimizeFunctionOnNextCall(B.prototype.foo);
new B().foo();
......@@ -14,7 +14,7 @@ function getRuntimeFunctionCallCount(function_name) {
const line = lines[i];
const m = line.match(/(?<name>\S+)\s+\S+\s+\S+\s+(?<count>\S+)/);
if (function_name == m.groups.name) {
return m.groups.count;
return parseInt(m.groups.count);
}
}
return 0;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment