[turbofan] Add support for (deferred) instance migration.
Fix a deoptimization loop in TurboFan, where we'd constantly fail the same map check due to not trying instance migration, when there are migration targets in the map check list. This deoptimization loop showed up on the React test in Speedometer. R=jarin@chromium.org Review-Url: https://codereview.chromium.org/2621423006 Cr-Commit-Position: refs/heads/master@{#42323}
This commit is contained in:
parent
124fbe55ec
commit
1ff4a8178e
@ -1058,25 +1058,79 @@ Node* EffectControlLinearizer::LowerCheckMaps(Node* node, Node* frame_state) {
|
||||
Node* value = node->InputAt(0);
|
||||
|
||||
ZoneHandleSet<Map> const& maps = p.maps();
|
||||
int const map_count = static_cast<int>(maps.size());
|
||||
size_t const map_count = maps.size();
|
||||
|
||||
auto done = __ MakeLabelFor(GraphAssemblerLabelType::kNonDeferred,
|
||||
static_cast<size_t>(map_count));
|
||||
if (p.flags() & CheckMapsFlag::kTryMigrateInstance) {
|
||||
auto done =
|
||||
__ MakeLabelFor(GraphAssemblerLabelType::kNonDeferred, map_count * 2);
|
||||
auto migrate = __ MakeDeferredLabel<1>();
|
||||
|
||||
// Load the current map of the {value}.
|
||||
Node* value_map = __ LoadField(AccessBuilder::ForMap(), value);
|
||||
// Load the current map of the {value}.
|
||||
Node* value_map = __ LoadField(AccessBuilder::ForMap(), value);
|
||||
|
||||
for (int i = 0; i < map_count; ++i) {
|
||||
Node* map = __ HeapConstant(maps[i]);
|
||||
Node* check = __ WordEqual(value_map, map);
|
||||
if (i == map_count - 1) {
|
||||
__ DeoptimizeUnless(DeoptimizeReason::kWrongMap, check, frame_state);
|
||||
} else {
|
||||
__ GotoIf(check, &done);
|
||||
// Perform the map checks.
|
||||
for (size_t i = 0; i < map_count; ++i) {
|
||||
Node* map = __ HeapConstant(maps[i]);
|
||||
Node* check = __ WordEqual(value_map, map);
|
||||
if (i == map_count - 1) {
|
||||
__ GotoUnless(check, &migrate);
|
||||
__ Goto(&done);
|
||||
} else {
|
||||
__ GotoIf(check, &done);
|
||||
}
|
||||
}
|
||||
|
||||
// Perform the (deferred) instance migration.
|
||||
__ Bind(&migrate);
|
||||
{
|
||||
Operator::Properties properties = Operator::kNoDeopt | Operator::kNoThrow;
|
||||
Runtime::FunctionId id = Runtime::kTryMigrateInstance;
|
||||
CallDescriptor const* desc = Linkage::GetRuntimeCallDescriptor(
|
||||
graph()->zone(), id, 1, properties, CallDescriptor::kNoFlags);
|
||||
Node* result =
|
||||
__ Call(desc, __ CEntryStubConstant(1), value,
|
||||
__ ExternalConstant(ExternalReference(id, isolate())),
|
||||
__ Int32Constant(1), __ NoContextConstant());
|
||||
Node* check = ObjectIsSmi(result);
|
||||
__ DeoptimizeIf(DeoptimizeReason::kInstanceMigrationFailed, check,
|
||||
frame_state);
|
||||
}
|
||||
|
||||
// Reload the current map of the {value}.
|
||||
value_map = __ LoadField(AccessBuilder::ForMap(), value);
|
||||
|
||||
// Perform the map checks again.
|
||||
for (size_t i = 0; i < map_count; ++i) {
|
||||
Node* map = __ HeapConstant(maps[i]);
|
||||
Node* check = __ WordEqual(value_map, map);
|
||||
if (i == map_count - 1) {
|
||||
__ DeoptimizeUnless(DeoptimizeReason::kWrongMap, check, frame_state);
|
||||
} else {
|
||||
__ GotoIf(check, &done);
|
||||
}
|
||||
}
|
||||
|
||||
__ Goto(&done);
|
||||
__ Bind(&done);
|
||||
} else {
|
||||
auto done =
|
||||
__ MakeLabelFor(GraphAssemblerLabelType::kNonDeferred, map_count);
|
||||
|
||||
// Load the current map of the {value}.
|
||||
Node* value_map = __ LoadField(AccessBuilder::ForMap(), value);
|
||||
|
||||
for (size_t i = 0; i < map_count; ++i) {
|
||||
Node* map = __ HeapConstant(maps[i]);
|
||||
Node* check = __ WordEqual(value_map, map);
|
||||
if (i == map_count - 1) {
|
||||
__ DeoptimizeUnless(DeoptimizeReason::kWrongMap, check, frame_state);
|
||||
} else {
|
||||
__ GotoIf(check, &done);
|
||||
}
|
||||
}
|
||||
__ Goto(&done);
|
||||
__ Bind(&done);
|
||||
}
|
||||
__ Goto(&done);
|
||||
__ Bind(&done);
|
||||
return value;
|
||||
}
|
||||
|
||||
|
@ -214,9 +214,11 @@ Reduction JSGlobalObjectSpecialization::ReduceJSStoreGlobal(Node* node) {
|
||||
value, effect, control);
|
||||
|
||||
// Check {value} map agains the {property_cell} map.
|
||||
effect = graph()->NewNode(simplified()->CheckMaps(ZoneHandleSet<Map>(
|
||||
property_cell_value_map)),
|
||||
value, effect, control);
|
||||
effect =
|
||||
graph()->NewNode(simplified()->CheckMaps(
|
||||
CheckMapsFlag::kNone,
|
||||
ZoneHandleSet<Map>(property_cell_value_map)),
|
||||
value, effect, control);
|
||||
property_cell_value_type = Type::OtherInternal();
|
||||
representation = MachineRepresentation::kTaggedPointer;
|
||||
} else {
|
||||
|
@ -1197,8 +1197,9 @@ JSNativeContextSpecialization::BuildPropertyAccess(
|
||||
if (access_info.field_map().ToHandle(&field_map)) {
|
||||
// Emit a map check for the value.
|
||||
effect = graph()->NewNode(
|
||||
simplified()->CheckMaps(ZoneHandleSet<Map>(field_map)), value,
|
||||
effect, control);
|
||||
simplified()->CheckMaps(CheckMapsFlag::kNone,
|
||||
ZoneHandleSet<Map>(field_map)),
|
||||
value, effect, control);
|
||||
}
|
||||
field_access.write_barrier_kind = kPointerWriteBarrier;
|
||||
break;
|
||||
@ -1519,9 +1520,11 @@ JSNativeContextSpecialization::BuildElementAccess(
|
||||
if (access_mode == AccessMode::kStore &&
|
||||
IsFastSmiOrObjectElementsKind(elements_kind) &&
|
||||
store_mode != STORE_NO_TRANSITION_HANDLE_COW) {
|
||||
effect = graph()->NewNode(simplified()->CheckMaps(ZoneHandleSet<Map>(
|
||||
factory()->fixed_array_map())),
|
||||
elements, effect, control);
|
||||
effect = graph()->NewNode(
|
||||
simplified()->CheckMaps(
|
||||
CheckMapsFlag::kNone,
|
||||
ZoneHandleSet<Map>(factory()->fixed_array_map())),
|
||||
elements, effect, control);
|
||||
}
|
||||
|
||||
// Check if the {receiver} is a JSArray.
|
||||
@ -1748,11 +1751,15 @@ Node* JSNativeContextSpecialization::BuildCheckMaps(
|
||||
}
|
||||
}
|
||||
ZoneHandleSet<Map> maps;
|
||||
CheckMapsFlags flags = CheckMapsFlag::kNone;
|
||||
for (Handle<Map> map : receiver_maps) {
|
||||
maps.insert(map, graph()->zone());
|
||||
if (map->is_migration_target()) {
|
||||
flags |= CheckMapsFlag::kTryMigrateInstance;
|
||||
}
|
||||
}
|
||||
return graph()->NewNode(simplified()->CheckMaps(maps), receiver, effect,
|
||||
control);
|
||||
return graph()->NewNode(simplified()->CheckMaps(flags, maps), receiver,
|
||||
effect, control);
|
||||
}
|
||||
|
||||
void JSNativeContextSpecialization::AssumePrototypesStable(
|
||||
|
@ -234,9 +234,19 @@ std::ostream& operator<<(std::ostream& os, CheckForMinusZeroMode mode) {
|
||||
return os;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, CheckMapsFlags flags) {
|
||||
bool empty = true;
|
||||
if (flags & CheckMapsFlag::kTryMigrateInstance) {
|
||||
os << "TryMigrateInstance";
|
||||
empty = false;
|
||||
}
|
||||
if (empty) os << "None";
|
||||
return os;
|
||||
}
|
||||
|
||||
bool operator==(CheckMapsParameters const& lhs,
|
||||
CheckMapsParameters const& rhs) {
|
||||
return lhs.maps() == rhs.maps();
|
||||
return lhs.flags() == rhs.flags() && lhs.maps() == rhs.maps();
|
||||
}
|
||||
|
||||
bool operator!=(CheckMapsParameters const& lhs,
|
||||
@ -244,13 +254,15 @@ bool operator!=(CheckMapsParameters const& lhs,
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
size_t hash_value(CheckMapsParameters const& p) { return hash_value(p.maps()); }
|
||||
size_t hash_value(CheckMapsParameters const& p) {
|
||||
return base::hash_combine(p.flags(), p.maps());
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, CheckMapsParameters const& p) {
|
||||
ZoneHandleSet<Map> const& maps = p.maps();
|
||||
os << p.flags();
|
||||
for (size_t i = 0; i < maps.size(); ++i) {
|
||||
if (i != 0) os << ", ";
|
||||
os << Brief(*maps[i]);
|
||||
os << ", " << Brief(*maps[i]);
|
||||
}
|
||||
return os;
|
||||
}
|
||||
@ -742,8 +754,9 @@ const Operator* SimplifiedOperatorBuilder::CheckedTaggedToFloat64(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const Operator* SimplifiedOperatorBuilder::CheckMaps(ZoneHandleSet<Map> maps) {
|
||||
CheckMapsParameters const parameters(maps);
|
||||
const Operator* SimplifiedOperatorBuilder::CheckMaps(CheckMapsFlags flags,
|
||||
ZoneHandleSet<Map> maps) {
|
||||
CheckMapsParameters const parameters(flags, maps);
|
||||
return new (zone()) Operator1<CheckMapsParameters>( // --
|
||||
IrOpcode::kCheckMaps, // opcode
|
||||
Operator::kNoThrow | Operator::kNoWrite, // flags
|
||||
|
@ -145,14 +145,28 @@ std::ostream& operator<<(std::ostream&, CheckForMinusZeroMode);
|
||||
|
||||
CheckForMinusZeroMode CheckMinusZeroModeOf(const Operator*) WARN_UNUSED_RESULT;
|
||||
|
||||
// Flags for map checks.
|
||||
enum class CheckMapsFlag : uint8_t {
|
||||
kNone = 0u,
|
||||
kTryMigrateInstance = 1u << 0, // Try instance migration.
|
||||
};
|
||||
typedef base::Flags<CheckMapsFlag> CheckMapsFlags;
|
||||
|
||||
DEFINE_OPERATORS_FOR_FLAGS(CheckMapsFlags)
|
||||
|
||||
std::ostream& operator<<(std::ostream&, CheckMapsFlags);
|
||||
|
||||
// A descriptor for map checks.
|
||||
class CheckMapsParameters final {
|
||||
public:
|
||||
explicit CheckMapsParameters(ZoneHandleSet<Map> const& maps) : maps_(maps) {}
|
||||
CheckMapsParameters(CheckMapsFlags flags, ZoneHandleSet<Map> const& maps)
|
||||
: flags_(flags), maps_(maps) {}
|
||||
|
||||
CheckMapsFlags flags() const { return flags_; }
|
||||
ZoneHandleSet<Map> const& maps() const { return maps_; }
|
||||
|
||||
private:
|
||||
CheckMapsFlags const flags_;
|
||||
ZoneHandleSet<Map> const maps_;
|
||||
};
|
||||
|
||||
@ -364,7 +378,7 @@ class V8_EXPORT_PRIVATE SimplifiedOperatorBuilder final
|
||||
|
||||
const Operator* CheckIf();
|
||||
const Operator* CheckBounds();
|
||||
const Operator* CheckMaps(ZoneHandleSet<Map>);
|
||||
const Operator* CheckMaps(CheckMapsFlags, ZoneHandleSet<Map>);
|
||||
|
||||
const Operator* CheckHeapObject();
|
||||
const Operator* CheckInternalizedString();
|
||||
|
Loading…
Reference in New Issue
Block a user