Map update for integrity level transitions.
This adds support for integrity level transitions (preventExtensions, seal and freeze) to MapUpdater and Map::TryUpdate. In both cases, we first try to detect whether there were integrity level transitions in the transition tree to the old map and make note of the most restrictive integrity transition and the map just before the transition (integrity-source-map). Then we find an appropriate root (based on integrity-source-map's elements kind) and replay the transitions based on the integrity-source-map's descriptor array. Finally, if we saw an integrity level transition in the beginning, we will find-or-create that transition (on the updated version of integrity-source-map). For the following micro-benchmark, we get about 10x speedup. ``` function C() { this.x = 1; Object.seal(this); this.x = 0.1; } const start = Date.now(); for (let i = 0; i < 1e7; i++) { new C(); } console.log("Reconfigure sealed: " + (Date.now() - start)); ``` Before: > Reconfigure sealed: 5202 After: > Reconfigure sealed: 479 Bug: v8:8538 Change-Id: If695be7469d8b6ccd44ac4528be8aa34b65b3e4d Reviewed-on: https://chromium-review.googlesource.com/c/1442640 Commit-Queue: Jaroslav Sevcik <jarin@chromium.org> Reviewed-by: Igor Sheludko <ishell@chromium.org> Cr-Commit-Position: refs/heads/master@{#59295}
This commit is contained in:
parent
cbeeb86baa
commit
06ba822ead
@ -45,8 +45,16 @@ Name MapUpdater::GetKey(int descriptor) const {
|
||||
PropertyDetails MapUpdater::GetDetails(int descriptor) const {
|
||||
DCHECK_LE(0, descriptor);
|
||||
if (descriptor == modified_descriptor_) {
|
||||
return PropertyDetails(new_kind_, new_attributes_, new_location_,
|
||||
new_constness_, new_representation_);
|
||||
PropertyAttributes attributes = new_attributes_;
|
||||
// If the original map was sealed or frozen, let us used the old
|
||||
// attributes so that we follow the same transition path as before.
|
||||
// Note that the user could not have changed the attributes because
|
||||
// both seal and freeze make the properties non-configurable.
|
||||
if (integrity_level_ == SEALED || integrity_level_ == FROZEN) {
|
||||
attributes = old_descriptors_->GetDetails(descriptor).attributes();
|
||||
}
|
||||
return PropertyDetails(new_kind_, attributes, new_location_, new_constness_,
|
||||
new_representation_);
|
||||
}
|
||||
return old_descriptors_->GetDetails(descriptor);
|
||||
}
|
||||
@ -141,10 +149,12 @@ Handle<Map> MapUpdater::ReconfigureToDataField(int descriptor,
|
||||
isolate_, old_map_->instance_type(), &new_constness_,
|
||||
&new_representation_, &new_field_type_);
|
||||
|
||||
if (TryRecofigureToDataFieldInplace() == kEnd) return result_map_;
|
||||
if (TryReconfigureToDataFieldInplace() == kEnd) return result_map_;
|
||||
if (FindRootMap() == kEnd) return result_map_;
|
||||
if (FindTargetMap() == kEnd) return result_map_;
|
||||
ConstructNewMap();
|
||||
if (ConstructNewMap() == kAtIntegrityLevelSource) {
|
||||
ConstructNewMapWithIntegrityLevelTransition();
|
||||
}
|
||||
DCHECK_EQ(kEnd, state_);
|
||||
return result_map_;
|
||||
}
|
||||
@ -157,7 +167,9 @@ Handle<Map> MapUpdater::ReconfigureElementsKind(ElementsKind elements_kind) {
|
||||
|
||||
if (FindRootMap() == kEnd) return result_map_;
|
||||
if (FindTargetMap() == kEnd) return result_map_;
|
||||
ConstructNewMap();
|
||||
if (ConstructNewMap() == kAtIntegrityLevelSource) {
|
||||
ConstructNewMapWithIntegrityLevelTransition();
|
||||
}
|
||||
DCHECK_EQ(kEnd, state_);
|
||||
return result_map_;
|
||||
}
|
||||
@ -168,7 +180,9 @@ Handle<Map> MapUpdater::Update() {
|
||||
|
||||
if (FindRootMap() == kEnd) return result_map_;
|
||||
if (FindTargetMap() == kEnd) return result_map_;
|
||||
ConstructNewMap();
|
||||
if (ConstructNewMap() == kAtIntegrityLevelSource) {
|
||||
ConstructNewMapWithIntegrityLevelTransition();
|
||||
}
|
||||
DCHECK_EQ(kEnd, state_);
|
||||
if (FLAG_fast_map_update) {
|
||||
TransitionsAccessor(isolate_, old_map_).SetMigrationTarget(*result_map_);
|
||||
@ -183,7 +197,8 @@ void MapUpdater::GeneralizeField(Handle<Map> map, int modify_index,
|
||||
Map::GeneralizeField(isolate_, map, modify_index, new_constness,
|
||||
new_representation, new_field_type);
|
||||
|
||||
DCHECK_EQ(*old_descriptors_, old_map_->instance_descriptors());
|
||||
DCHECK(*old_descriptors_ == old_map_->instance_descriptors() ||
|
||||
*old_descriptors_ == integrity_source_map_->instance_descriptors());
|
||||
}
|
||||
|
||||
MapUpdater::State MapUpdater::CopyGeneralizeAllFields(const char* reason) {
|
||||
@ -194,7 +209,7 @@ MapUpdater::State MapUpdater::CopyGeneralizeAllFields(const char* reason) {
|
||||
return state_; // Done.
|
||||
}
|
||||
|
||||
MapUpdater::State MapUpdater::TryRecofigureToDataFieldInplace() {
|
||||
MapUpdater::State MapUpdater::TryReconfigureToDataFieldInplace() {
|
||||
// If it's just a representation generalization case (i.e. property kind and
|
||||
// attributes stays unchanged) it's fine to transition from None to anything
|
||||
// but double without any modification to the object, because the default
|
||||
@ -238,12 +253,44 @@ MapUpdater::State MapUpdater::TryRecofigureToDataFieldInplace() {
|
||||
return state_; // Done.
|
||||
}
|
||||
|
||||
void MapUpdater::SaveIntegrityLevelTransitions() {
|
||||
integrity_source_map_ =
|
||||
handle(Map::cast(old_map_->GetBackPointer()), isolate_);
|
||||
ReadOnlyRoots roots(isolate_);
|
||||
TransitionsAccessor transitions(isolate_, integrity_source_map_);
|
||||
|
||||
if (transitions.SearchSpecial(roots.frozen_symbol()) == *old_map_) {
|
||||
integrity_level_ = FROZEN;
|
||||
integrity_level_symbol_ = isolate_->factory()->frozen_symbol();
|
||||
} else if (transitions.SearchSpecial(roots.sealed_symbol()) == *old_map_) {
|
||||
integrity_level_ = SEALED;
|
||||
integrity_level_symbol_ = isolate_->factory()->sealed_symbol();
|
||||
} else {
|
||||
CHECK_EQ(transitions.SearchSpecial(roots.nonextensible_symbol()),
|
||||
*old_map_);
|
||||
integrity_level_ = NONE;
|
||||
integrity_level_symbol_ = isolate_->factory()->nonextensible_symbol();
|
||||
}
|
||||
|
||||
// Skip all the other integrity level transitions.
|
||||
while (!integrity_source_map_->is_extensible()) {
|
||||
integrity_source_map_ =
|
||||
handle(Map::cast(integrity_source_map_->GetBackPointer()), isolate_);
|
||||
}
|
||||
|
||||
has_integrity_level_transition_ = true;
|
||||
|
||||
old_descriptors_ =
|
||||
handle(integrity_source_map_->instance_descriptors(), isolate_);
|
||||
}
|
||||
|
||||
MapUpdater::State MapUpdater::FindRootMap() {
|
||||
DCHECK_EQ(kInitialized, state_);
|
||||
// Check the state of the root map.
|
||||
root_map_ = handle(old_map_->FindRootMap(isolate_), isolate_);
|
||||
ElementsKind from_kind = root_map_->elements_kind();
|
||||
ElementsKind to_kind = new_elements_kind_;
|
||||
|
||||
if (root_map_->is_deprecated()) {
|
||||
state_ = kEnd;
|
||||
result_map_ = handle(
|
||||
@ -252,9 +299,21 @@ MapUpdater::State MapUpdater::FindRootMap() {
|
||||
DCHECK(result_map_->is_dictionary_map());
|
||||
return state_;
|
||||
}
|
||||
int root_nof = root_map_->NumberOfOwnDescriptors();
|
||||
|
||||
if (!old_map_->EquivalentToForTransition(*root_map_)) {
|
||||
return CopyGeneralizeAllFields("GenAll_NotEquivalent");
|
||||
} else if (old_map_->is_extensible() != root_map_->is_extensible()) {
|
||||
DCHECK(!old_map_->is_extensible());
|
||||
DCHECK(root_map_->is_extensible());
|
||||
// We have an integrity level transition in the tree, let us make a note
|
||||
// of that transition to be able to replay it later.
|
||||
SaveIntegrityLevelTransitions();
|
||||
|
||||
// We want to build transitions to the original element kind (before
|
||||
// the seal transitions), so change {to_kind} accordingly.
|
||||
DCHECK(to_kind == DICTIONARY_ELEMENTS ||
|
||||
IsFixedTypedArrayElementsKind(to_kind));
|
||||
to_kind = integrity_source_map_->elements_kind();
|
||||
}
|
||||
|
||||
// TODO(ishell): Add a test for SLOW_SLOPPY_ARGUMENTS_ELEMENTS.
|
||||
@ -266,6 +325,7 @@ MapUpdater::State MapUpdater::FindRootMap() {
|
||||
return CopyGeneralizeAllFields("GenAll_InvalidElementsTransition");
|
||||
}
|
||||
|
||||
int root_nof = root_map_->NumberOfOwnDescriptors();
|
||||
if (modified_descriptor_ >= 0 && modified_descriptor_ < root_nof) {
|
||||
PropertyDetails old_details =
|
||||
old_descriptors_->GetDetails(modified_descriptor_);
|
||||
@ -379,7 +439,8 @@ MapUpdater::State MapUpdater::FindTargetMap() {
|
||||
PropertyDetails details =
|
||||
target_descriptors->GetDetails(modified_descriptor_);
|
||||
DCHECK_EQ(new_kind_, details.kind());
|
||||
DCHECK_EQ(new_attributes_, details.attributes());
|
||||
DCHECK_EQ(GetDetails(modified_descriptor_).attributes(),
|
||||
details.attributes());
|
||||
DCHECK(IsGeneralizableTo(new_constness_, details.constness()));
|
||||
DCHECK_EQ(new_location_, details.location());
|
||||
DCHECK(new_representation_.fits_into(details.representation()));
|
||||
@ -398,9 +459,20 @@ MapUpdater::State MapUpdater::FindTargetMap() {
|
||||
if (*target_map_ != *old_map_) {
|
||||
old_map_->NotifyLeafMapLayoutChange(isolate_);
|
||||
}
|
||||
result_map_ = target_map_;
|
||||
state_ = kEnd;
|
||||
return state_; // Done.
|
||||
if (!has_integrity_level_transition_) {
|
||||
result_map_ = target_map_;
|
||||
state_ = kEnd;
|
||||
return state_; // Done.
|
||||
}
|
||||
|
||||
// We try to replay the integrity level transition here.
|
||||
Map transition = TransitionsAccessor(isolate_, target_map_)
|
||||
.SearchSpecial(*integrity_level_symbol_);
|
||||
if (!transition.is_null()) {
|
||||
result_map_ = handle(transition, isolate_);
|
||||
state_ = kEnd;
|
||||
return state_; // Done.
|
||||
}
|
||||
}
|
||||
|
||||
// Find the last compatible target map in the transition tree.
|
||||
@ -653,7 +725,11 @@ MapUpdater::State MapUpdater::ConstructNewMap() {
|
||||
|
||||
Handle<Map> split_map = FindSplitMap(new_descriptors);
|
||||
int split_nof = split_map->NumberOfOwnDescriptors();
|
||||
DCHECK_NE(old_nof_, split_nof);
|
||||
if (old_nof_ == split_nof) {
|
||||
CHECK(has_integrity_level_transition_);
|
||||
state_ = kAtIntegrityLevelSource;
|
||||
return state_;
|
||||
}
|
||||
|
||||
PropertyDetails split_details = GetDetails(split_nof);
|
||||
TransitionsAccessor transitions(isolate_, split_map);
|
||||
@ -717,10 +793,31 @@ MapUpdater::State MapUpdater::ConstructNewMap() {
|
||||
split_map->ReplaceDescriptors(isolate_, *new_descriptors,
|
||||
*new_layout_descriptor);
|
||||
|
||||
result_map_ = new_map;
|
||||
state_ = kEnd;
|
||||
if (has_integrity_level_transition_) {
|
||||
target_map_ = new_map;
|
||||
state_ = kAtIntegrityLevelSource;
|
||||
} else {
|
||||
result_map_ = new_map;
|
||||
state_ = kEnd;
|
||||
}
|
||||
return state_; // Done.
|
||||
}
|
||||
|
||||
MapUpdater::State MapUpdater::ConstructNewMapWithIntegrityLevelTransition() {
|
||||
DCHECK_EQ(kAtIntegrityLevelSource, state_);
|
||||
|
||||
TransitionsAccessor transitions(isolate_, target_map_);
|
||||
if (!transitions.CanHaveMoreTransitions()) {
|
||||
return CopyGeneralizeAllFields("GenAll_CantHaveMoreTransitions");
|
||||
}
|
||||
|
||||
result_map_ = Map::CopyForPreventExtensions(
|
||||
isolate_, target_map_, integrity_level_, integrity_level_symbol_,
|
||||
"CopyForPreventExtensions");
|
||||
|
||||
state_ = kEnd;
|
||||
return state_;
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
@ -25,12 +25,16 @@ namespace internal {
|
||||
// rewrite the new type is deduced by merging the current type with any
|
||||
// potential new (partial) version of the type in the transition tree.
|
||||
// To do this, on each rewrite:
|
||||
// - Search the root of the transition tree using FindRootMap.
|
||||
// - Search the root of the transition tree using FindRootMap, remember
|
||||
// the integrity level (preventExtensions/seal/freeze) transitions.
|
||||
// - Find/create a |root_map| with requested |new_elements_kind|.
|
||||
// - Find |target_map|, the newest matching version of this map using the
|
||||
// "updated" |old_map|'s descriptor array (i.e. whose entry at |modify_index|
|
||||
// is considered to be of |new_kind| and having |new_attributes|) to walk
|
||||
// the transition tree.
|
||||
// the transition tree. If there was an integrity level transition on the path
|
||||
// to the old map, use the descriptor array of the map preceding the first
|
||||
// integrity level transition (|integrity_source_map|), and try to replay
|
||||
// the integrity level transition afterwards.
|
||||
// - Merge/generalize the "updated" descriptor array of the |old_map| and
|
||||
// descriptor array of the |target_map|.
|
||||
// - Generalize the |modify_index| descriptor using |new_representation| and
|
||||
@ -38,10 +42,11 @@ namespace internal {
|
||||
// - Walk the tree again starting from the root towards |target_map|. Stop at
|
||||
// |split_map|, the first map who's descriptor array does not match the merged
|
||||
// descriptor array.
|
||||
// - If |target_map| == |split_map|, |target_map| is in the expected state.
|
||||
// Return it.
|
||||
// - If |target_map| == |split_map|, and there are no integrity level
|
||||
// transitions, |target_map| is in the expected state. Return it.
|
||||
// - Otherwise, invalidate the outdated transition target from |target_map|, and
|
||||
// replace its transition tree with a new branch for the updated descriptors.
|
||||
// - If the |old_map| had integrity level transition, create the new map for it.
|
||||
class MapUpdater {
|
||||
public:
|
||||
MapUpdater(Isolate* isolate, Handle<Map> old_map);
|
||||
@ -63,11 +68,17 @@ class MapUpdater {
|
||||
Handle<Map> Update();
|
||||
|
||||
private:
|
||||
enum State { kInitialized, kAtRootMap, kAtTargetMap, kEnd };
|
||||
enum State {
|
||||
kInitialized,
|
||||
kAtRootMap,
|
||||
kAtTargetMap,
|
||||
kAtIntegrityLevelSource,
|
||||
kEnd
|
||||
};
|
||||
|
||||
// Try to reconfigure property in-place without rebuilding transition tree
|
||||
// and creating new maps. See implementation for details.
|
||||
State TryRecofigureToDataFieldInplace();
|
||||
State TryReconfigureToDataFieldInplace();
|
||||
|
||||
// Step 1.
|
||||
// - Search the root of the transition tree using FindRootMap.
|
||||
@ -75,10 +86,14 @@ class MapUpdater {
|
||||
State FindRootMap();
|
||||
|
||||
// Step 2.
|
||||
// - Find |target_map_|, the newest matching version of this map using the
|
||||
// - Find |target_map|, the newest matching version of this map using the
|
||||
// "updated" |old_map|'s descriptor array (i.e. whose entry at
|
||||
// |modified_descriptor_| is considered to be of |new_kind| and having
|
||||
// |new_attributes|) to walk the transition tree.
|
||||
// |modify_index| is considered to be of |new_kind| and having
|
||||
// |new_attributes|) to walk the transition tree. If there was an integrity
|
||||
// level transition on the path to the old map, use the descriptor array
|
||||
// of the map preceding the first integrity level transition
|
||||
// (|integrity_source_map|), and try to replay the integrity level
|
||||
// transition afterwards.
|
||||
State FindTargetMap();
|
||||
|
||||
// Step 3.
|
||||
@ -102,6 +117,11 @@ class MapUpdater {
|
||||
// descriptors.
|
||||
State ConstructNewMap();
|
||||
|
||||
// Step 6 (if there was
|
||||
// - If the |old_map| had integrity level transition, create the new map
|
||||
// for it.
|
||||
State ConstructNewMapWithIntegrityLevelTransition();
|
||||
|
||||
// When a requested reconfiguration can not be done the result is a copy
|
||||
// of |old_map_| where every field has |Tagged| representation and |Any|
|
||||
// field type. This map is disconnected from the transition tree.
|
||||
@ -143,6 +163,8 @@ class MapUpdater {
|
||||
Representation new_representation,
|
||||
Handle<FieldType> new_field_type);
|
||||
|
||||
void SaveIntegrityLevelTransitions();
|
||||
|
||||
Isolate* isolate_;
|
||||
Handle<Map> old_map_;
|
||||
Handle<DescriptorArray> old_descriptors_;
|
||||
@ -151,6 +173,12 @@ class MapUpdater {
|
||||
Handle<Map> result_map_;
|
||||
int old_nof_;
|
||||
|
||||
// Information about integrity level transitions.
|
||||
bool has_integrity_level_transition_ = false;
|
||||
PropertyAttributes integrity_level_ = NONE;
|
||||
Handle<Symbol> integrity_level_symbol_;
|
||||
Handle<Map> integrity_source_map_;
|
||||
|
||||
State state_ = kInitialized;
|
||||
ElementsKind new_elements_kind_;
|
||||
bool is_transitionable_fast_elements_kind_;
|
||||
|
@ -3897,8 +3897,6 @@ void DescriptorArray::GeneralizeAllFields() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Maybe<bool> JSObject::SetPropertyWithInterceptor(
|
||||
LookupIterator* it, Maybe<ShouldThrow> should_throw, Handle<Object> value) {
|
||||
DCHECK_EQ(LookupIterator::INTERCEPTOR, it->state());
|
||||
@ -8650,10 +8648,6 @@ void IteratingStringHasher::VisitConsString(ConsString cons_string) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void JSFunction::MarkForOptimization(ConcurrencyMode mode) {
|
||||
Isolate* isolate = GetIsolate();
|
||||
if (!isolate->concurrent_recompilation_enabled() ||
|
||||
|
@ -922,6 +922,52 @@ MaybeHandle<Map> Map::TryUpdate(Isolate* isolate, Handle<Map> old_map) {
|
||||
return handle(new_map, isolate);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
struct IntegrityLevelTransitionInfo {
|
||||
explicit IntegrityLevelTransitionInfo(Map map)
|
||||
: integrity_level_source_map(map) {}
|
||||
|
||||
bool has_integrity_level_transition = false;
|
||||
PropertyAttributes integrity_level = NONE;
|
||||
Map integrity_level_source_map;
|
||||
Symbol integrity_level_symbol;
|
||||
};
|
||||
|
||||
IntegrityLevelTransitionInfo DetectIntegrityLevelTransitions(
|
||||
Map map, Isolate* isolate, DisallowHeapAllocation* no_allocation) {
|
||||
IntegrityLevelTransitionInfo info(map);
|
||||
|
||||
DCHECK(!map->is_extensible());
|
||||
Map source_map = Map::cast(map->GetBackPointer());
|
||||
|
||||
ReadOnlyRoots roots(isolate);
|
||||
TransitionsAccessor transitions(isolate, source_map, no_allocation);
|
||||
if (transitions.SearchSpecial(roots.frozen_symbol()) == map) {
|
||||
info.integrity_level = FROZEN;
|
||||
info.integrity_level_symbol = roots.frozen_symbol();
|
||||
} else if (transitions.SearchSpecial(roots.sealed_symbol()) == map) {
|
||||
info.integrity_level = SEALED;
|
||||
info.integrity_level_symbol = roots.sealed_symbol();
|
||||
} else {
|
||||
CHECK_EQ(transitions.SearchSpecial(roots.nonextensible_symbol()), map);
|
||||
info.integrity_level = NONE;
|
||||
info.integrity_level_symbol = roots.nonextensible_symbol();
|
||||
}
|
||||
|
||||
// Skip all the other integrity level transitions.
|
||||
while (!source_map->is_extensible()) {
|
||||
source_map = Map::cast(source_map->GetBackPointer());
|
||||
}
|
||||
|
||||
info.has_integrity_level_transition = true;
|
||||
info.integrity_level_source_map = source_map;
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Map Map::TryUpdateSlow(Isolate* isolate, Map old_map) {
|
||||
DisallowHeapAllocation no_allocation;
|
||||
DisallowDeoptimization no_deoptimization(isolate);
|
||||
@ -942,13 +988,36 @@ Map Map::TryUpdateSlow(Isolate* isolate, Map old_map) {
|
||||
|
||||
ElementsKind from_kind = root_map->elements_kind();
|
||||
ElementsKind to_kind = old_map->elements_kind();
|
||||
|
||||
IntegrityLevelTransitionInfo info(old_map);
|
||||
if (root_map->is_extensible() != old_map->is_extensible()) {
|
||||
DCHECK(!old_map->is_extensible());
|
||||
DCHECK(root_map->is_extensible());
|
||||
info = DetectIntegrityLevelTransitions(old_map, isolate, &no_allocation);
|
||||
// Make sure replay the original elements kind transitions, before
|
||||
// the integrity level transition sets the elements to dictionary mode.
|
||||
DCHECK(to_kind == DICTIONARY_ELEMENTS ||
|
||||
IsFixedTypedArrayElementsKind(to_kind));
|
||||
to_kind = info.integrity_level_source_map->elements_kind();
|
||||
}
|
||||
if (from_kind != to_kind) {
|
||||
// Try to follow existing elements kind transitions.
|
||||
root_map = root_map->LookupElementsTransitionMap(isolate, to_kind);
|
||||
if (root_map.is_null()) return Map();
|
||||
// From here on, use the map with correct elements kind as root map.
|
||||
}
|
||||
return root_map->TryReplayPropertyTransitions(isolate, old_map);
|
||||
|
||||
// Replay the transitions as they were before the integrity level transition.
|
||||
Map result = root_map->TryReplayPropertyTransitions(
|
||||
isolate, info.integrity_level_source_map);
|
||||
if (result.is_null()) return Map();
|
||||
|
||||
if (info.has_integrity_level_transition) {
|
||||
// Now replay the integrity level transition.
|
||||
result = TransitionsAccessor(isolate, result, &no_allocation)
|
||||
.SearchSpecial(info.integrity_level_symbol);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Map Map::TryReplayPropertyTransitions(Isolate* isolate, Map old_map) {
|
||||
@ -2346,7 +2415,13 @@ bool CheckEquivalent(const Map first, const Map second) {
|
||||
} // namespace
|
||||
|
||||
bool Map::EquivalentToForTransition(const Map other) const {
|
||||
if (!CheckEquivalent(*this, other)) return false;
|
||||
CHECK_EQ(GetConstructor(), other->GetConstructor());
|
||||
CHECK_EQ(instance_type(), other->instance_type());
|
||||
CHECK_EQ(bit_field(), other->bit_field());
|
||||
CHECK_EQ(has_hidden_prototype(), other->has_hidden_prototype());
|
||||
|
||||
if (new_target_is_base() != other->new_target_is_base()) return false;
|
||||
if (prototype() != other->prototype()) return false;
|
||||
if (instance_type() == JS_FUNCTION_TYPE) {
|
||||
// JSFunctions require more checks to ensure that sloppy function is
|
||||
// not equivalent to strict function.
|
||||
|
@ -278,6 +278,7 @@ class Expectations {
|
||||
if (details.attributes() != expected_attributes) return false;
|
||||
|
||||
Representation expected_representation = representations_[descriptor];
|
||||
|
||||
if (!details.representation().Equals(expected_representation)) return false;
|
||||
|
||||
Object expected_value = *values_[descriptor];
|
||||
@ -2339,9 +2340,12 @@ static void TestGeneralizeFieldWithSpecialTransition(TestConfig& config,
|
||||
// If Map::TryUpdate() manages to succeed the result must match the result
|
||||
// of Map::Update().
|
||||
CHECK_EQ(*new_map2, *tmp_map);
|
||||
} else {
|
||||
// Equivalent transitions should always find the updated map.
|
||||
CHECK(config.is_non_equivalent_transition());
|
||||
}
|
||||
|
||||
if (config.is_non_equevalent_transition()) {
|
||||
if (config.is_non_equivalent_transition()) {
|
||||
// In case of non-equivalent transition currently we generalize all
|
||||
// representations.
|
||||
for (int i = 0; i < kPropCount; i++) {
|
||||
@ -2350,6 +2354,9 @@ static void TestGeneralizeFieldWithSpecialTransition(TestConfig& config,
|
||||
CHECK(new_map2->GetBackPointer()->IsUndefined(isolate));
|
||||
CHECK(expectations2.Check(*new_map2));
|
||||
} else {
|
||||
expectations2.SetDataField(i, expected.constness, expected.representation,
|
||||
expected.type);
|
||||
|
||||
CHECK(!new_map2->GetBackPointer()->IsUndefined(isolate));
|
||||
CHECK(expectations2.Check(*new_map2));
|
||||
}
|
||||
@ -2391,7 +2398,7 @@ TEST(ElementsKindTransitionFromMapOwningDescriptor) {
|
||||
}
|
||||
// TODO(ishell): remove once IS_PROTO_TRANS_ISSUE_FIXED is removed.
|
||||
bool generalizes_representations() const { return false; }
|
||||
bool is_non_equevalent_transition() const { return true; }
|
||||
bool is_non_equivalent_transition() const { return false; }
|
||||
|
||||
PropertyAttributes attributes;
|
||||
Handle<Symbol> symbol;
|
||||
@ -2443,7 +2450,7 @@ TEST(ElementsKindTransitionFromMapNotOwningDescriptor) {
|
||||
}
|
||||
// TODO(ishell): remove once IS_PROTO_TRANS_ISSUE_FIXED is removed.
|
||||
bool generalizes_representations() const { return false; }
|
||||
bool is_non_equevalent_transition() const { return true; }
|
||||
bool is_non_equivalent_transition() const { return false; }
|
||||
|
||||
PropertyAttributes attributes;
|
||||
Handle<Symbol> symbol;
|
||||
@ -2487,7 +2494,7 @@ TEST(PrototypeTransitionFromMapOwningDescriptor) {
|
||||
bool generalizes_representations() const {
|
||||
return !IS_PROTO_TRANS_ISSUE_FIXED;
|
||||
}
|
||||
bool is_non_equevalent_transition() const { return true; }
|
||||
bool is_non_equivalent_transition() const { return true; }
|
||||
};
|
||||
TestConfig config;
|
||||
TestGeneralizeFieldWithSpecialTransition(
|
||||
@ -2534,7 +2541,7 @@ TEST(PrototypeTransitionFromMapNotOwningDescriptor) {
|
||||
bool generalizes_representations() const {
|
||||
return !IS_PROTO_TRANS_ISSUE_FIXED;
|
||||
}
|
||||
bool is_non_equevalent_transition() const { return true; }
|
||||
bool is_non_equivalent_transition() const { return true; }
|
||||
};
|
||||
TestConfig config;
|
||||
TestGeneralizeFieldWithSpecialTransition(
|
||||
|
166
test/mjsunit/integrity-level-map-update.js
Normal file
166
test/mjsunit/integrity-level-map-update.js
Normal file
@ -0,0 +1,166 @@
|
||||
// Copyright 2019 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
|
||||
|
||||
(function SealAndReconfigure() {
|
||||
function C() { this.x = 1; this.y = 1; Object.seal(this); }
|
||||
|
||||
let c1 = new C();
|
||||
|
||||
c1.x = 0.1;
|
||||
|
||||
let c2 = new C();
|
||||
let c3 = new C();
|
||||
let c4 = new C();
|
||||
|
||||
// The objects c2, c3 and c4 should follow the same transition
|
||||
// path that we reconfigured c1 to.
|
||||
assertTrue(%HaveSameMap(c1, c2));
|
||||
assertTrue(%HaveSameMap(c1, c3));
|
||||
assertTrue(%HaveSameMap(c1, c4));
|
||||
|
||||
c2.x = 0.1;
|
||||
c3.x = 0.1;
|
||||
c4.x = 0.1;
|
||||
|
||||
assertTrue(%HaveSameMap(c1, c2));
|
||||
assertTrue(%HaveSameMap(c1, c3));
|
||||
assertTrue(%HaveSameMap(c1, c4));
|
||||
})();
|
||||
|
||||
(function SealAndReconfigureWithIC() {
|
||||
function C() { this.x = 1; this.y = 1; Object.seal(this); }
|
||||
|
||||
let c1 = new C();
|
||||
|
||||
function g(o) {
|
||||
o.x = 0.1;
|
||||
}
|
||||
|
||||
g(c1);
|
||||
|
||||
let c2 = new C();
|
||||
let c3 = new C();
|
||||
let c4 = new C();
|
||||
|
||||
// The objects c2, c3 and c4 should follow the same transition
|
||||
// path that we reconfigured c1 to.
|
||||
assertTrue(%HaveSameMap(c1, c2));
|
||||
assertTrue(%HaveSameMap(c1, c3));
|
||||
assertTrue(%HaveSameMap(c1, c4));
|
||||
|
||||
g(c2);
|
||||
g(c3);
|
||||
g(c4);
|
||||
|
||||
assertTrue(%HaveSameMap(c1, c2));
|
||||
assertTrue(%HaveSameMap(c1, c3));
|
||||
assertTrue(%HaveSameMap(c1, c4));
|
||||
})();
|
||||
|
||||
(function SealReconfigureAndMigrateWithIC() {
|
||||
function C() { this.x = 1; this.y = 1; Object.seal(this); }
|
||||
|
||||
let c1 = new C();
|
||||
let c2 = new C();
|
||||
let c3 = new C();
|
||||
let c4 = new C();
|
||||
|
||||
function g(o) {
|
||||
o.x = 0.1;
|
||||
}
|
||||
|
||||
g(c1);
|
||||
|
||||
// Now c2, c3 and c4 have deprecated maps.
|
||||
assertFalse(%HaveSameMap(c1, c2));
|
||||
assertFalse(%HaveSameMap(c1, c3));
|
||||
assertFalse(%HaveSameMap(c1, c4));
|
||||
|
||||
g(c2);
|
||||
g(c3);
|
||||
g(c4);
|
||||
|
||||
assertTrue(%HaveSameMap(c1, c2));
|
||||
assertTrue(%HaveSameMap(c1, c3));
|
||||
assertTrue(%HaveSameMap(c1, c4));
|
||||
})();
|
||||
|
||||
(function SealReconfigureAndMigrateWithOptCode() {
|
||||
function C() { this.x = 1; this.y = 1; Object.seal(this); }
|
||||
|
||||
let c1 = new C();
|
||||
let c2 = new C();
|
||||
let c3 = new C();
|
||||
let c4 = new C();
|
||||
|
||||
function g(o) {
|
||||
o.x = 0.1;
|
||||
}
|
||||
|
||||
g(c1);
|
||||
g(c2);
|
||||
g(c3);
|
||||
%OptimizeFunctionOnNextCall(g);
|
||||
g(c4);
|
||||
|
||||
assertTrue(%HaveSameMap(c1, c2));
|
||||
assertTrue(%HaveSameMap(c1, c3));
|
||||
assertTrue(%HaveSameMap(c1, c4));
|
||||
})();
|
||||
|
||||
(function PreventExtensionsAndReconfigure() {
|
||||
function C() { this.x = 1; this.y = 1; Object.preventExtensions(this); }
|
||||
|
||||
let c1 = new C();
|
||||
|
||||
function g(o) {
|
||||
o.x = 0.1;
|
||||
}
|
||||
|
||||
g(c1);
|
||||
|
||||
let c2 = new C();
|
||||
let c3 = new C();
|
||||
let c4 = new C();
|
||||
|
||||
c2.x = 0.1;
|
||||
c3.x = 0.1;
|
||||
c4.x = 0.1;
|
||||
|
||||
assertTrue(%HaveSameMap(c1, c2));
|
||||
assertTrue(%HaveSameMap(c1, c3));
|
||||
assertTrue(%HaveSameMap(c1, c4));
|
||||
})();
|
||||
|
||||
(function PreventExtensionsSealAndReconfigure() {
|
||||
function C() {
|
||||
this.x = 1;
|
||||
this.y = 1;
|
||||
Object.preventExtensions(this);
|
||||
Object.seal(this);
|
||||
}
|
||||
|
||||
let c1 = new C();
|
||||
|
||||
function g(o) {
|
||||
o.x = 0.1;
|
||||
}
|
||||
|
||||
g(c1);
|
||||
|
||||
let c2 = new C();
|
||||
let c3 = new C();
|
||||
let c4 = new C();
|
||||
|
||||
c2.x = 0.1;
|
||||
c3.x = 0.1;
|
||||
c4.x = 0.1;
|
||||
|
||||
// Ideally, all the objects would have the same map, but at the moment
|
||||
// we shortcut the unnecessary integrity level transitions.
|
||||
assertTrue(%HaveSameMap(c2, c3));
|
||||
assertTrue(%HaveSameMap(c2, c4));
|
||||
})();
|
Loading…
Reference in New Issue
Block a user