[turboprop] Migrate deprecated maps in dynamic map check operator

If incoming map is deprecated, generate code to migrate the map. Since
this involves generating additional code and a call to runtime, we only
do this if one of the receiver maps was a migration target when
optimizing this function. If not, we deoptimize and discard the
optimized code if we see a deprecated map. This is to avoid bailout
loops when we see deprecated maps.

This change does the following:
// We generated code to migrate deprecated maps only if one of the maps
// in feedback vector is a migration target.
if ( there are migration targets in feedback)
{
  if (checkMaps fails) {
     if (incoming map is deprecated) {
        migrate the map
        checkMaps with the new map
     } else {
       bailout
     }
  }
} else {
  if (checkMaps fails)
    bailout;
}

Bug: v8:10582, v8:9684
Change-Id: I8a04c77ed209dd2fb0300a783d844f2335a678c8
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2292231
Reviewed-by: Sathya Gunasekaran  <gsathya@chromium.org>
Reviewed-by: Georg Neis <neis@chromium.org>
Commit-Queue: Mythri Alle <mythria@chromium.org>
Cr-Commit-Position: refs/heads/master@{#69179}
This commit is contained in:
Mythri A 2020-07-31 12:28:02 +01:00 committed by Commit Bot
parent 065dde954a
commit 9b9ba19e3c
9 changed files with 267 additions and 101 deletions

View File

@ -272,6 +272,22 @@ class EffectControlLinearizer {
void TransitionElementsTo(Node* node, Node* array, ElementsKind from,
ElementsKind to);
// This function tries to migrate |value| if its map |value_map| is
// deprecated. It deopts, if either |value_map| isn't deprecated or migration
// fails.
void MigrateInstanceOrDeopt(Node* value, Node* value_map, Node* frame_state,
FeedbackSource const& feedback_source,
DeoptimizeReason reason);
// Helper functions used in LowerDynamicCheckMaps
void CheckPolymorphic(Node* feedback, Node* value_map, Node* handler,
GraphAssemblerLabel<0>* migrate,
GraphAssemblerLabel<0>* done, Node* frame_state);
void CheckMonomorphic(Node* feedback, Node* value_map, Node* handler,
GraphAssemblerLabel<0>* done,
GraphAssemblerLabel<0>* map_check_failed,
Node* frame_state, int slot, Node* vector);
bool should_maintain_schedule() const {
return maintain_schedule_ == MaintainSchedule::kMaintain;
}
@ -1763,6 +1779,29 @@ Node* EffectControlLinearizer::LowerCheckClosure(Node* node,
return value;
}
void EffectControlLinearizer::MigrateInstanceOrDeopt(
Node* value, Node* value_map, Node* frame_state,
FeedbackSource const& feedback_source, DeoptimizeReason reason) {
// If map is not deprecated the migration attempt does not make sense.
Node* bitfield3 = __ LoadField(AccessBuilder::ForMapBitField3(), value_map);
Node* is_not_deprecated = __ Word32Equal(
__ Word32And(bitfield3,
__ Int32Constant(Map::Bits3::IsDeprecatedBit::kMask)),
__ Int32Constant(0));
__ DeoptimizeIf(reason, feedback_source, is_not_deprecated, frame_state,
IsSafetyCheck::kCriticalSafetyCheck);
Operator::Properties properties = Operator::kNoDeopt | Operator::kNoThrow;
Runtime::FunctionId id = Runtime::kTryMigrateInstance;
auto call_descriptor = Linkage::GetRuntimeCallDescriptor(
graph()->zone(), id, 1, properties, CallDescriptor::kNoFlags);
Node* result = __ Call(call_descriptor, __ CEntryStubConstant(1), value,
__ ExternalConstant(ExternalReference::Create(id)),
__ Int32Constant(1), __ NoContextConstant());
Node* check = ObjectIsSmi(result);
__ DeoptimizeIf(DeoptimizeReason::kInstanceMigrationFailed, feedback_source,
check, frame_state, IsSafetyCheck::kCriticalSafetyCheck);
}
void EffectControlLinearizer::LowerCheckMaps(Node* node, Node* frame_state) {
CheckMapsParameters const& p = CheckMapsParametersOf(node->op());
Node* value = node->InputAt(0);
@ -1792,29 +1831,8 @@ void EffectControlLinearizer::LowerCheckMaps(Node* node, Node* frame_state) {
// Perform the (deferred) instance migration.
__ Bind(&migrate);
{
// If map is not deprecated the migration attempt does not make sense.
Node* bitfield3 =
__ LoadField(AccessBuilder::ForMapBitField3(), value_map);
Node* if_not_deprecated = __ Word32Equal(
__ Word32And(bitfield3,
__ Int32Constant(Map::Bits3::IsDeprecatedBit::kMask)),
__ Int32Constant(0));
__ DeoptimizeIf(DeoptimizeReason::kWrongMap, p.feedback(),
if_not_deprecated, frame_state,
IsSafetyCheck::kCriticalSafetyCheck);
Operator::Properties properties = Operator::kNoDeopt | Operator::kNoThrow;
Runtime::FunctionId id = Runtime::kTryMigrateInstance;
auto call_descriptor = Linkage::GetRuntimeCallDescriptor(
graph()->zone(), id, 1, properties, CallDescriptor::kNoFlags);
Node* result = __ Call(call_descriptor, __ CEntryStubConstant(1), value,
__ ExternalConstant(ExternalReference::Create(id)),
__ Int32Constant(1), __ NoContextConstant());
Node* check = ObjectIsSmi(result);
__ DeoptimizeIf(DeoptimizeReason::kInstanceMigrationFailed, p.feedback(),
check, frame_state, IsSafetyCheck::kCriticalSafetyCheck);
}
MigrateInstanceOrDeopt(value, value_map, frame_state, p.feedback(),
DeoptimizeReason::kWrongMap);
// Reload the current map of the {value}.
value_map = __ LoadField(AccessBuilder::ForMap(), value);
@ -1859,6 +1877,80 @@ void EffectControlLinearizer::LowerCheckMaps(Node* node, Node* frame_state) {
}
}
void EffectControlLinearizer::CheckPolymorphic(Node* feedback_slot,
Node* value_map, Node* handler,
GraphAssemblerLabel<0>* migrate,
GraphAssemblerLabel<0>* done,
Node* frame_state) {
Node* feedback_slot_map =
__ LoadField(AccessBuilder::ForMap(), feedback_slot);
Node* is_weak_fixed_array_check =
__ TaggedEqual(feedback_slot_map, __ WeakFixedArrayMapConstant());
__ DeoptimizeIfNot(DeoptimizeReason::kTransitionedToMegamorphicIC,
FeedbackSource(), is_weak_fixed_array_check, frame_state,
IsSafetyCheck::kCriticalSafetyCheck);
Node* length = ChangeSmiToInt32(
__ LoadField(AccessBuilder::ForWeakFixedArrayLength(), feedback_slot));
auto loop = __ MakeLoopLabel(MachineRepresentation::kWord32);
__ Goto(&loop, __ Int32Constant(0));
__ Bind(&loop);
{
Node* index = loop.PhiAt(0);
Node* check = __ Int32LessThan(index, length);
if (migrate != nullptr) {
__ GotoIfNot(check, migrate);
} else {
__ DeoptimizeIfNot(DeoptimizeKind::kBailout,
DeoptimizeReason::kMissingMap, FeedbackSource(), check,
frame_state, IsSafetyCheck::kCriticalSafetyCheck);
}
Node* maybe_map = __ LoadElement(AccessBuilder::ForWeakFixedArrayElement(),
feedback_slot, index);
auto continue_loop = __ MakeLabel();
__ GotoIfNot(BuildIsWeakReferenceTo(maybe_map, value_map), &continue_loop);
constexpr int kHandlerOffsetInEntry = 1;
Node* maybe_handler = __ LoadElement(
AccessBuilder::ForWeakFixedArrayElement(), feedback_slot,
__ Int32Add(index, __ Int32Constant(kHandlerOffsetInEntry)));
Node* handler_check = __ TaggedEqual(maybe_handler, handler);
__ DeoptimizeIfNot(DeoptimizeReason::kWrongHandler, FeedbackSource(),
handler_check, frame_state,
IsSafetyCheck::kCriticalSafetyCheck);
__ Goto(done);
__ Bind(&continue_loop);
constexpr int kEntrySize = 2;
index = __ Int32Add(index, __ Int32Constant(kEntrySize));
__ Goto(&loop, index);
}
}
void EffectControlLinearizer::CheckMonomorphic(
Node* feedback, Node* value_map, Node* handler,
GraphAssemblerLabel<0>* done, GraphAssemblerLabel<0>* map_check_failed,
Node* frame_state, int slot, Node* vector) {
Node* mono_check = BuildIsWeakReferenceTo(feedback, value_map);
if (map_check_failed != nullptr) {
__ GotoIfNot(mono_check, map_check_failed);
} else {
__ DeoptimizeIfNot(DeoptimizeKind::kBailout, DeoptimizeReason::kMissingMap,
FeedbackSource(), mono_check, frame_state,
IsSafetyCheck::kCriticalSafetyCheck);
}
Node* feedback_slot_handler =
__ LoadField(AccessBuilder::ForFeedbackVectorSlot(slot + 1), vector);
Node* handler_check = __ TaggedEqual(handler, feedback_slot_handler);
__ DeoptimizeIfNot(DeoptimizeReason::kWrongHandler, FeedbackSource(),
handler_check, frame_state,
IsSafetyCheck::kCriticalSafetyCheck);
__ Goto(done);
}
void EffectControlLinearizer::LowerDynamicCheckMaps(Node* node,
Node* frame_state) {
DynamicCheckMapsParameters const& p =
@ -1881,17 +1973,8 @@ void EffectControlLinearizer::LowerDynamicCheckMaps(Node* node,
// case the current state is polymorphic, and if we ever go back to
// monomorphic start, we will deopt and reoptimize the code.
if (p.state() == DynamicCheckMapsParameters::kMonomorphic) {
Node* mono_check = BuildIsWeakReferenceTo(feedback_slot, value_map);
__ GotoIfNot(mono_check, &maybe_poly);
Node* feedback_slot_handler = __ LoadField(
AccessBuilder::ForFeedbackVectorSlot(feedback.index() + 1), vector);
mono_check = __ TaggedEqual(handler, feedback_slot_handler);
__ DeoptimizeIfNot(DeoptimizeReason::kWrongHandler, FeedbackSource(),
mono_check, frame_state,
IsSafetyCheck::kCriticalSafetyCheck);
__ Goto(&done);
CheckMonomorphic(feedback_slot, value_map, handler, &done, &maybe_poly,
frame_state, feedback.index(), vector);
} else {
DCHECK(p.state() == DynamicCheckMapsParameters::kPolymorphic);
__ Goto(&maybe_poly);
@ -1899,62 +1982,47 @@ void EffectControlLinearizer::LowerDynamicCheckMaps(Node* node,
__ Bind(&maybe_poly);
{
Node* poly_check = BuildIsStrongReference(feedback_slot);
if (p.state() == DynamicCheckMapsParameters::kMonomorphic) {
__ DeoptimizeIfNot(DeoptimizeKind::kBailout,
DeoptimizeReason::kMissingMap, FeedbackSource(),
poly_check, frame_state,
IsSafetyCheck::kCriticalSafetyCheck);
Node* is_poly_or_megamorphic = BuildIsStrongReference(feedback_slot);
// If the IC state at code generation time is not monomorphic, we don't
// handle monomorphic states and just deoptimize if IC transitions to
// monomorphic. For polymorphic ICs it is not required to migrate deprecated
// maps since ICs don't discard deprecated maps from feedback. Only generate
// codeneed to migrate maps for Monomoprhic state.
if (p.flags() & CheckMapsFlag::kTryMigrateInstance &&
p.state() == DynamicCheckMapsParameters::kMonomorphic) {
auto migrate = __ MakeDeferredLabel();
__ GotoIfNot(is_poly_or_megamorphic, &migrate);
// TODO(mythria): ICs don't drop deprecated maps from feedback vector.
// So it is not equired to migrate the instance for polymorphic case.
// When we change dynamic map checks to check only four maps re-evaluate
// if this is required.
CheckPolymorphic(feedback_slot, value_map, handler, nullptr, &done,
frame_state);
__ Bind(&migrate);
{
MigrateInstanceOrDeopt(value, value_map, frame_state, FeedbackSource(),
DeoptimizeReason::kMissingMap);
Node* new_value_map = __ LoadField(AccessBuilder::ForMap(), value);
// Check if new map matches.
CheckMonomorphic(feedback_slot, new_value_map, handler, &done, nullptr,
frame_state, feedback.index(), vector);
}
} else {
__ DeoptimizeIfNot(DeoptimizeReason::kTransitionedToMonomorphicIC,
FeedbackSource(), poly_check, frame_state,
IsSafetyCheck::kCriticalSafetyCheck);
}
Node* feedback_slot_map =
__ LoadField(AccessBuilder::ForMap(), feedback_slot);
Node* is_weak_fixed_array_check =
__ TaggedEqual(feedback_slot_map, __ WeakFixedArrayMapConstant());
__ DeoptimizeIfNot(DeoptimizeReason::kTransitionedToMegamorphicIC,
FeedbackSource(), is_weak_fixed_array_check, frame_state,
IsSafetyCheck::kCriticalSafetyCheck);
Node* length = ChangeSmiToInt32(
__ LoadField(AccessBuilder::ForWeakFixedArrayLength(), feedback_slot));
auto loop = __ MakeLoopLabel(MachineRepresentation::kWord32);
__ Goto(&loop, __ Int32Constant(0));
__ Bind(&loop);
{
Node* index = loop.PhiAt(0);
Node* check = __ Int32LessThan(index, length);
__ DeoptimizeIfNot(DeoptimizeKind::kBailout,
DeoptimizeReason::kMissingMap, FeedbackSource(), check,
DeoptimizeReason reason = DeoptimizeReason::kMissingMap;
DeoptimizeKind kind = DeoptimizeKind::kBailout;
if (p.state() != DynamicCheckMapsParameters::kMonomorphic) {
reason = DeoptimizeReason::kTransitionedToMonomorphicIC;
kind = DeoptimizeKind::kEager;
}
__ DeoptimizeIfNot(kind, reason, FeedbackSource(), is_poly_or_megamorphic,
frame_state, IsSafetyCheck::kCriticalSafetyCheck);
Node* maybe_map = __ LoadElement(
AccessBuilder::ForWeakFixedArrayElement(), feedback_slot, index);
auto continue_loop = __ MakeLabel();
__ GotoIfNot(BuildIsWeakReferenceTo(maybe_map, value_map),
&continue_loop);
constexpr int kHandlerOffsetInEntry = 1;
Node* maybe_handler = __ LoadElement(
AccessBuilder::ForWeakFixedArrayElement(), feedback_slot,
__ Int32Add(index, __ Int32Constant(kHandlerOffsetInEntry)));
Node* handler_check = __ TaggedEqual(maybe_handler, handler);
__ DeoptimizeIfNot(DeoptimizeReason::kWrongHandler, FeedbackSource(),
handler_check, frame_state,
IsSafetyCheck::kCriticalSafetyCheck);
__ Goto(&done);
__ Bind(&continue_loop);
constexpr int kEntrySize = 2;
index = __ Int32Add(index, __ Int32Constant(kEntrySize));
__ Goto(&loop, index);
CheckPolymorphic(feedback_slot, value_map, handler, nullptr, &done,
frame_state);
}
}
__ Bind(&done);
}

View File

@ -4676,11 +4676,12 @@ bool ElementAccessFeedback::HasOnlyStringMaps(JSHeapBroker* broker) const {
MinimorphicLoadPropertyAccessFeedback::MinimorphicLoadPropertyAccessFeedback(
NameRef const& name, FeedbackSlotKind slot_kind, bool is_monomorphic,
Handle<Object> handler)
Handle<Object> handler, bool has_migration_target_maps)
: ProcessedFeedback(kMinimorphicPropertyAccess, slot_kind),
name_(name),
is_monomorphic_(is_monomorphic),
handler_(handler) {
handler_(handler),
has_migration_target_maps_(has_migration_target_maps) {
DCHECK(IsLoadICKind(slot_kind));
}
@ -4779,6 +4780,13 @@ MaybeObjectHandle TryGetMinimorphicHandler(
}
return initial_handler;
}
bool HasMigrationTargets(const MapHandles& maps) {
for (Handle<Map> map : maps) {
if (map->is_migration_target()) return true;
}
return false;
}
} // namespace
bool JSHeapBroker::CanUseFeedback(const FeedbackNexus& nexus) const {
@ -4801,19 +4809,20 @@ ProcessedFeedback const& JSHeapBroker::ReadFeedbackForPropertyAccess(
std::vector<MapAndHandler> maps_and_handlers;
nexus.ExtractMapsAndFeedback(&maps_and_handlers);
MapHandles maps;
for (auto const& entry : maps_and_handlers) {
maps.push_back(entry.first);
}
base::Optional<NameRef> name =
static_name.has_value() ? static_name : GetNameFeedback(nexus);
MaybeObjectHandle handler = TryGetMinimorphicHandler(maps_and_handlers, kind);
if (!handler.is_null()) {
return *zone()->New<MinimorphicLoadPropertyAccessFeedback>(
*name, kind, nexus.ic_state() == MONOMORPHIC, handler.object());
*name, kind, nexus.ic_state() == MONOMORPHIC, handler.object(),
HasMigrationTargets(maps));
}
MapHandles maps;
for (auto const& entry : maps_and_handlers) {
maps.push_back(entry.first);
}
FilterRelevantReceiverMaps(isolate(), &maps);
// If no maps were found for a non-megamorphic access, then our maps died

View File

@ -1067,9 +1067,13 @@ Reduction JSNativeContextSpecialization::ReduceMinimorphicPropertyAccess(
if (access_info.IsInvalid()) return NoChange();
PropertyAccessBuilder access_builder(jsgraph(), broker(), nullptr);
CheckMapsFlags flags = CheckMapsFlag::kNone;
if (feedback.has_migration_target_maps()) {
flags |= CheckMapsFlag::kTryMigrateInstance;
}
effect = graph()->NewNode(
simplified()->DynamicCheckMaps(
feedback.handler(), source,
flags, feedback.handler(), source,
feedback.is_monomorphic()
? DynamicCheckMapsParameters::ICState::kMonomorphic
: DynamicCheckMapsParameters::ICState::kPolymorphic),

View File

@ -178,16 +178,19 @@ class MinimorphicLoadPropertyAccessFeedback : public ProcessedFeedback {
MinimorphicLoadPropertyAccessFeedback(NameRef const& name,
FeedbackSlotKind slot_kind,
bool is_monomorphic,
Handle<Object> handler);
Handle<Object> handler,
bool has_migration_target_maps);
NameRef const& name() const { return name_; }
bool is_monomorphic() const { return is_monomorphic_; }
Handle<Object> handler() const { return handler_; }
bool has_migration_target_maps() const { return has_migration_target_maps_; }
private:
NameRef const name_;
bool is_monomorphic_;
Handle<Object> handler_;
bool const is_monomorphic_;
Handle<Object> const handler_;
bool const has_migration_target_maps_;
};
class CallFeedback : public ProcessedFeedback {

View File

@ -1474,9 +1474,11 @@ const Operator* SimplifiedOperatorBuilder::CheckMaps(
}
const Operator* SimplifiedOperatorBuilder::DynamicCheckMaps(
Handle<Object> handler, const FeedbackSource& feedback,
CheckMapsFlags flags, Handle<Object> handler,
const FeedbackSource& feedback,
DynamicCheckMapsParameters::ICState ic_state) {
DynamicCheckMapsParameters const parameters(handler, feedback, ic_state);
DynamicCheckMapsParameters const parameters(flags, handler, feedback,
ic_state);
return zone()->New<Operator1<DynamicCheckMapsParameters>>( // --
IrOpcode::kDynamicCheckMaps, // opcode
Operator::kNoThrow | Operator::kNoWrite, // flags

View File

@ -431,15 +431,17 @@ class DynamicCheckMapsParameters final {
public:
enum ICState { kMonomorphic, kPolymorphic };
DynamicCheckMapsParameters(Handle<Object> handler,
DynamicCheckMapsParameters(CheckMapsFlags flags, Handle<Object> handler,
const FeedbackSource& feedback, ICState state)
: handler_(handler), feedback_(feedback), state_(state) {}
: flags_(flags), handler_(handler), feedback_(feedback), state_(state) {}
CheckMapsFlags flags() const { return flags_; }
Handle<Object> handler() const { return handler_; }
FeedbackSource const& feedback() const { return feedback_; }
ICState const& state() const { return state_; }
private:
CheckMapsFlags const flags_;
Handle<Object> const handler_;
FeedbackSource const feedback_;
ICState const state_;
@ -874,7 +876,8 @@ class V8_EXPORT_PRIVATE SimplifiedOperatorBuilder final
const Operator* CheckMaps(CheckMapsFlags, ZoneHandleSet<Map>,
const FeedbackSource& = FeedbackSource());
const Operator* DynamicCheckMaps(
Handle<Object> handler, const FeedbackSource& feedback,
CheckMapsFlags flags, Handle<Object> handler,
const FeedbackSource& feedback,
DynamicCheckMapsParameters::ICState ic_state);
const Operator* CheckNotTaggedHole();
const Operator* CheckNumber(const FeedbackSource& feedback);

View File

@ -63,6 +63,7 @@ namespace internal {
V(WrongInstanceType, "wrong instance type") \
V(WrongMap, "wrong map") \
V(MissingMap, "missing map") \
V(DeprecatedMap, "deprecated map") \
V(WrongHandler, "wrong handler") \
V(WrongName, "wrong name") \
V(WrongValue, "wrong value") \

View File

@ -0,0 +1,35 @@
// 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 --turboprop --dynamic-map-checks --opt
// Flags: --no-always-opt
function f(o) {
return o.b;
}
var o = {a:10, b:20};
var o1 = {a:10, b:20};
var o2 = {a:10, b:20};
var o3 = {a:10, b:20, c:30};
%PrepareFunctionForOptimization(f);
// Transition IC state to polymorphic.
f(o);
f(o3);
%OptimizeFunctionOnNextCall(f);
f(o);
assertOptimized(f);
f(o);
// Deprecates O's map.
o1.b = 10.23;
// Deoptimizes but retains code.
f(o1);
assertOptimized(f);
// Continues to use optimized code since deprecated map is still in the
// feedback. ICs don't drop deprecated maps in the polymoprhic case.
f(o);
f(o);
assertOptimized(f);

View File

@ -0,0 +1,41 @@
// 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 --turboprop --dynamic-map-checks --opt
// Flags: --no-always-opt
function f(o) {
return o.b;
}
var o = {a:10, b:20};
var o1 = {a:10, b:20};
var o2 = {a:10, b:20};
%PrepareFunctionForOptimization(f);
f(o);
%OptimizeFunctionOnNextCall(f);
f(o);
assertOptimized(f);
%PrepareFunctionForOptimization(f);
f(o);
// Deprecates O's map.
o1.b = 10.23;
// Deoptimizes but retains code.
f(o1);
assertOptimized(f);
// Deoptimizes and discards code.
f(o);
f(o);
assertUnoptimized(f);
// When we reoptimize we should include code for migrating deprecated maps.
%OptimizeFunctionOnNextCall(f);
f(o);
assertOptimized(f);
f(o2);
f(o2);
assertOptimized(f);