[runtime] Don't stay const on store to field
This was a slightly overzealous optimization that ended up being more tricky than expected without measurable value on the benchmarks (as far as we know). Let's try to remove it and see whether an important benchmark notices. Bug: chromium:1385941 Change-Id: If2e81f6cb6758f9c373e7c2c8beaa308ed323f93 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/4088624 Commit-Queue: Toon Verwaest <verwaest@chromium.org> Auto-Submit: Toon Verwaest <verwaest@chromium.org> Reviewed-by: Igor Sheludko <ishell@chromium.org> Cr-Commit-Position: refs/heads/main@{#84739}
This commit is contained in:
parent
7bcac055b5
commit
54256360ff
@ -1745,6 +1745,7 @@ Reduction JSNativeContextSpecialization::ReduceNamedAccess(
|
|||||||
// trimming.
|
// trimming.
|
||||||
return NoChange();
|
return NoChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
values.push_back(continuation->value());
|
values.push_back(continuation->value());
|
||||||
effects.push_back(continuation->effect());
|
effects.push_back(continuation->effect());
|
||||||
controls.push_back(continuation->control());
|
controls.push_back(continuation->control());
|
||||||
@ -2898,9 +2899,15 @@ JSNativeContextSpecialization::BuildPropertyStore(
|
|||||||
AccessBuilder::ForJSObjectPropertiesOrHashKnownPointer()),
|
AccessBuilder::ForJSObjectPropertiesOrHashKnownPointer()),
|
||||||
storage, effect, control);
|
storage, effect, control);
|
||||||
}
|
}
|
||||||
bool store_to_existing_constant_field = access_info.IsFastDataConstant() &&
|
if (access_info.IsFastDataConstant() && access_mode == AccessMode::kStore &&
|
||||||
access_mode == AccessMode::kStore &&
|
!access_info.HasTransitionMap()) {
|
||||||
!access_info.HasTransitionMap();
|
Node* deoptimize = graph()->NewNode(
|
||||||
|
common()->DeoptimizeIf(DeoptimizeReason::kStoreToConstant,
|
||||||
|
FeedbackSource()),
|
||||||
|
jsgraph()->TrueConstant(), frame_state, effect, control);
|
||||||
|
return ValueEffectControl(jsgraph()->UndefinedConstant(), deoptimize,
|
||||||
|
deoptimize);
|
||||||
|
}
|
||||||
FieldAccess field_access = {
|
FieldAccess field_access = {
|
||||||
kTaggedBase,
|
kTaggedBase,
|
||||||
field_index.offset(),
|
field_index.offset(),
|
||||||
@ -2953,40 +2960,11 @@ JSNativeContextSpecialization::BuildPropertyStore(
|
|||||||
field_access.name = MaybeHandle<Name>();
|
field_access.name = MaybeHandle<Name>();
|
||||||
field_access.machine_type = MachineType::Float64();
|
field_access.machine_type = MachineType::Float64();
|
||||||
}
|
}
|
||||||
if (store_to_existing_constant_field) {
|
|
||||||
DCHECK(!access_info.HasTransitionMap());
|
|
||||||
// If the field is constant check that the value we are going
|
|
||||||
// to store matches current value.
|
|
||||||
Node* current_value = effect = graph()->NewNode(
|
|
||||||
simplified()->LoadField(field_access), storage, effect, control);
|
|
||||||
|
|
||||||
Node* check =
|
|
||||||
graph()->NewNode(simplified()->SameValue(), current_value, value);
|
|
||||||
effect = graph()->NewNode(
|
|
||||||
simplified()->CheckIf(DeoptimizeReason::kWrongValue), check,
|
|
||||||
effect, control);
|
|
||||||
return ValueEffectControl(value, effect, control);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MachineRepresentation::kTaggedSigned:
|
case MachineRepresentation::kTaggedSigned:
|
||||||
case MachineRepresentation::kTaggedPointer:
|
case MachineRepresentation::kTaggedPointer:
|
||||||
case MachineRepresentation::kTagged:
|
case MachineRepresentation::kTagged:
|
||||||
if (store_to_existing_constant_field) {
|
|
||||||
DCHECK(!access_info.HasTransitionMap());
|
|
||||||
// If the field is constant check that the value we are going
|
|
||||||
// to store matches current value.
|
|
||||||
Node* current_value = effect = graph()->NewNode(
|
|
||||||
simplified()->LoadField(field_access), storage, effect, control);
|
|
||||||
|
|
||||||
Node* check = graph()->NewNode(simplified()->SameValueNumbersOnly(),
|
|
||||||
current_value, value);
|
|
||||||
effect = graph()->NewNode(
|
|
||||||
simplified()->CheckIf(DeoptimizeReason::kWrongValue), check,
|
|
||||||
effect, control);
|
|
||||||
return ValueEffectControl(value, effect, control);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (field_representation == MachineRepresentation::kTaggedSigned) {
|
if (field_representation == MachineRepresentation::kTaggedSigned) {
|
||||||
value = effect = graph()->NewNode(
|
value = effect = graph()->NewNode(
|
||||||
simplified()->CheckSmi(FeedbackSource()), value, effect, control);
|
simplified()->CheckSmi(FeedbackSource()), value, effect, control);
|
||||||
|
@ -57,6 +57,7 @@ namespace internal {
|
|||||||
V(OutOfBounds, "out of bounds") \
|
V(OutOfBounds, "out of bounds") \
|
||||||
V(Overflow, "overflow") \
|
V(Overflow, "overflow") \
|
||||||
V(Smi, "Smi") \
|
V(Smi, "Smi") \
|
||||||
|
V(StoreToConstant, "Storing to a constant field") \
|
||||||
V(SuspendGeneratorIsDead, "SuspendGenerator is in a dead branch") \
|
V(SuspendGeneratorIsDead, "SuspendGenerator is in a dead branch") \
|
||||||
V(TransitionedToMonomorphicIC, "IC transitioned to monomorphic") \
|
V(TransitionedToMonomorphicIC, "IC transitioned to monomorphic") \
|
||||||
V(TransitionedToMegamorphicIC, "IC transitioned to megamorphic") \
|
V(TransitionedToMegamorphicIC, "IC transitioned to megamorphic") \
|
||||||
|
@ -1304,24 +1304,12 @@ void AccessorAssembler::HandleStoreICHandlerCase(
|
|||||||
GotoIf(IsSetWord32(details, kTypeAndReadOnlyMask), miss);
|
GotoIf(IsSetWord32(details, kTypeAndReadOnlyMask), miss);
|
||||||
|
|
||||||
if (V8_DICT_PROPERTY_CONST_TRACKING_BOOL) {
|
if (V8_DICT_PROPERTY_CONST_TRACKING_BOOL) {
|
||||||
GotoIf(IsPropertyDetailsConst(details), &if_constant);
|
GotoIf(IsPropertyDetailsConst(details), miss);
|
||||||
}
|
}
|
||||||
|
|
||||||
StoreValueByKeyIndex<PropertyDictionary>(
|
StoreValueByKeyIndex<PropertyDictionary>(
|
||||||
properties, var_name_index.value(), p->value());
|
properties, var_name_index.value(), p->value());
|
||||||
Return(p->value());
|
Return(p->value());
|
||||||
|
|
||||||
if (V8_DICT_PROPERTY_CONST_TRACKING_BOOL) {
|
|
||||||
BIND(&if_constant);
|
|
||||||
{
|
|
||||||
TNode<Object> prev_value =
|
|
||||||
LoadValueByKeyIndex(properties, var_name_index.value());
|
|
||||||
Branch(TaggedEqual(prev_value, p->value()), &done, miss);
|
|
||||||
}
|
|
||||||
|
|
||||||
BIND(&done);
|
|
||||||
Return(p->value());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BIND(&if_fast_smi);
|
BIND(&if_fast_smi);
|
||||||
@ -1612,14 +1600,9 @@ void AccessorAssembler::OverwriteExistingFastDataProperty(
|
|||||||
StoreMap(object, object_map);
|
StoreMap(object, object_map);
|
||||||
StoreObjectField(object, field_offset, heap_number);
|
StoreObjectField(object, field_offset, heap_number);
|
||||||
} else {
|
} else {
|
||||||
|
GotoIf(IsPropertyDetailsConst(details), slow);
|
||||||
TNode<HeapNumber> heap_number =
|
TNode<HeapNumber> heap_number =
|
||||||
CAST(LoadObjectField(object, field_offset));
|
CAST(LoadObjectField(object, field_offset));
|
||||||
Label store_value(this);
|
|
||||||
GotoIfNot(IsPropertyDetailsConst(details), &store_value);
|
|
||||||
TNode<Float64T> current_value = LoadHeapNumberValue(heap_number);
|
|
||||||
GotoIfNotSameNumberBitPattern(current_value, double_value, slow);
|
|
||||||
Goto(&done);
|
|
||||||
BIND(&store_value);
|
|
||||||
StoreHeapNumberValue(heap_number, double_value);
|
StoreHeapNumberValue(heap_number, double_value);
|
||||||
}
|
}
|
||||||
Goto(&done);
|
Goto(&done);
|
||||||
@ -1630,11 +1613,7 @@ void AccessorAssembler::OverwriteExistingFastDataProperty(
|
|||||||
if (do_transitioning_store) {
|
if (do_transitioning_store) {
|
||||||
StoreMap(object, object_map);
|
StoreMap(object, object_map);
|
||||||
} else {
|
} else {
|
||||||
Label if_mutable(this);
|
GotoIf(IsPropertyDetailsConst(details), slow);
|
||||||
GotoIfNot(IsPropertyDetailsConst(details), &if_mutable);
|
|
||||||
TNode<Object> current_value = LoadObjectField(object, field_offset);
|
|
||||||
Branch(TaggedEqual(current_value, value), &done, slow);
|
|
||||||
BIND(&if_mutable);
|
|
||||||
}
|
}
|
||||||
StoreObjectField(object, field_offset, value);
|
StoreObjectField(object, field_offset, value);
|
||||||
Goto(&done);
|
Goto(&done);
|
||||||
@ -1682,29 +1661,16 @@ void AccessorAssembler::OverwriteExistingFastDataProperty(
|
|||||||
&double_rep, &tagged_rep);
|
&double_rep, &tagged_rep);
|
||||||
BIND(&double_rep);
|
BIND(&double_rep);
|
||||||
{
|
{
|
||||||
|
GotoIf(IsPropertyDetailsConst(details), slow);
|
||||||
TNode<HeapNumber> heap_number =
|
TNode<HeapNumber> heap_number =
|
||||||
CAST(LoadPropertyArrayElement(properties, backing_store_index));
|
CAST(LoadPropertyArrayElement(properties, backing_store_index));
|
||||||
TNode<Float64T> double_value = ChangeNumberToFloat64(CAST(value));
|
TNode<Float64T> double_value = ChangeNumberToFloat64(CAST(value));
|
||||||
|
|
||||||
Label if_mutable(this);
|
|
||||||
GotoIfNot(IsPropertyDetailsConst(details), &if_mutable);
|
|
||||||
TNode<Float64T> current_value = LoadHeapNumberValue(heap_number);
|
|
||||||
GotoIfNotSameNumberBitPattern(current_value, double_value, slow);
|
|
||||||
Goto(&done);
|
|
||||||
|
|
||||||
BIND(&if_mutable);
|
|
||||||
StoreHeapNumberValue(heap_number, double_value);
|
StoreHeapNumberValue(heap_number, double_value);
|
||||||
Goto(&done);
|
Goto(&done);
|
||||||
}
|
}
|
||||||
BIND(&tagged_rep);
|
BIND(&tagged_rep);
|
||||||
{
|
{
|
||||||
Label if_mutable(this);
|
GotoIf(IsPropertyDetailsConst(details), slow);
|
||||||
GotoIfNot(IsPropertyDetailsConst(details), &if_mutable);
|
|
||||||
TNode<Object> current_value =
|
|
||||||
LoadPropertyArrayElement(properties, backing_store_index);
|
|
||||||
Branch(TaggedEqual(current_value, value), &done, slow);
|
|
||||||
|
|
||||||
BIND(&if_mutable);
|
|
||||||
StorePropertyArrayElement(properties, backing_store_index, value);
|
StorePropertyArrayElement(properties, backing_store_index, value);
|
||||||
Goto(&done);
|
Goto(&done);
|
||||||
}
|
}
|
||||||
|
@ -1833,22 +1833,9 @@ bool MaglevGraphBuilder::TryBuildStoreField(
|
|||||||
if (original_map.UnusedPropertyFields() == 0) {
|
if (original_map.UnusedPropertyFields() == 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if (access_info.IsFastDataConstant() &&
|
} else if (access_info.IsFastDataConstant()) {
|
||||||
access_mode != compiler::AccessMode::kStoreInLiteral) {
|
// TODO(verwaest): Deoptimize instead.
|
||||||
// For object literals we want to just store, similar to StoreInLiteral, but
|
return false;
|
||||||
// Define is also used for classes so we can't. Bail out for now.
|
|
||||||
if (access_mode == compiler::AccessMode::kDefine) return false;
|
|
||||||
// TODO(verwaest): Support doubles.
|
|
||||||
if (field_representation.IsDouble()) return false;
|
|
||||||
ValueNode* value = GetAccumulatorTagged();
|
|
||||||
ValueNode* expected = BuildLoadField(access_info, receiver);
|
|
||||||
if (Constant* constant = expected->TryCast<Constant>()) {
|
|
||||||
BuildCheckValue(value, constant->ref());
|
|
||||||
} else {
|
|
||||||
AddNewNode<CheckDynamicValue>({value, expected});
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ValueNode* store_target;
|
ValueNode* store_target;
|
||||||
|
@ -1532,7 +1532,7 @@ Maybe<bool> JSReceiver::ValidateAndApplyPropertyDescriptor(
|
|||||||
desc->enumerable() == current->enumerable()) &&
|
desc->enumerable() == current->enumerable()) &&
|
||||||
(!desc->has_configurable() ||
|
(!desc->has_configurable() ||
|
||||||
desc->configurable() == current->configurable()) &&
|
desc->configurable() == current->configurable()) &&
|
||||||
(!desc->has_value() || current->value().is_identical_to(desc->value())) &&
|
!desc->has_value() &&
|
||||||
(!desc->has_writable() ||
|
(!desc->has_writable() ||
|
||||||
(current->has_writable() && current->writable() == desc->writable())) &&
|
(current->has_writable() && current->writable() == desc->writable())) &&
|
||||||
(!desc->has_get() ||
|
(!desc->has_get() ||
|
||||||
|
@ -375,11 +375,9 @@ void LookupIterator::PrepareForDataProperty(Handle<Object> value) {
|
|||||||
// Check that current value matches new value otherwise we should make
|
// Check that current value matches new value otherwise we should make
|
||||||
// the property mutable.
|
// the property mutable.
|
||||||
if (holder->HasFastProperties(isolate_)) {
|
if (holder->HasFastProperties(isolate_)) {
|
||||||
if (!IsConstFieldValueEqualTo(*value)) {
|
if (!CanStayConst(*value)) new_constness = PropertyConstness::kMutable;
|
||||||
new_constness = PropertyConstness::kMutable;
|
|
||||||
}
|
|
||||||
} else if (V8_DICT_PROPERTY_CONST_TRACKING_BOOL) {
|
} else if (V8_DICT_PROPERTY_CONST_TRACKING_BOOL) {
|
||||||
if (!IsConstDictValueEqualTo(*value)) {
|
if (!DictCanStayConst(*value)) {
|
||||||
property_details_ =
|
property_details_ =
|
||||||
property_details_.CopyWithConstness(PropertyConstness::kMutable);
|
property_details_.CopyWithConstness(PropertyConstness::kMutable);
|
||||||
|
|
||||||
@ -902,7 +900,7 @@ Handle<Object> LookupIterator::FetchValue(
|
|||||||
return handle(result, isolate_);
|
return handle(result, isolate_);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool LookupIterator::IsConstFieldValueEqualTo(Object value) const {
|
bool LookupIterator::CanStayConst(Object value) const {
|
||||||
DCHECK(!IsElement(*holder_));
|
DCHECK(!IsElement(*holder_));
|
||||||
DCHECK(holder_->HasFastProperties(isolate_));
|
DCHECK(holder_->HasFastProperties(isolate_));
|
||||||
DCHECK_EQ(PropertyLocation::kField, property_details_.location());
|
DCHECK_EQ(PropertyLocation::kField, property_details_.location());
|
||||||
@ -932,12 +930,10 @@ bool LookupIterator::IsConstFieldValueEqualTo(Object value) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Object current_value = holder->RawFastPropertyAt(isolate_, field_index);
|
Object current_value = holder->RawFastPropertyAt(isolate_, field_index);
|
||||||
// Only allow exact same objects to ensure we don't need expensive
|
return current_value.IsUninitialized(isolate());
|
||||||
// validation in optimized code.
|
|
||||||
return current_value.IsUninitialized(isolate()) || current_value == value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool LookupIterator::IsConstDictValueEqualTo(Object value) const {
|
bool LookupIterator::DictCanStayConst(Object value) const {
|
||||||
DCHECK(!IsElement(*holder_));
|
DCHECK(!IsElement(*holder_));
|
||||||
DCHECK(!holder_->HasFastProperties(isolate_));
|
DCHECK(!holder_->HasFastProperties(isolate_));
|
||||||
DCHECK(!holder_->IsJSGlobalObject());
|
DCHECK(!holder_->IsJSGlobalObject());
|
||||||
@ -962,11 +958,7 @@ bool LookupIterator::IsConstDictValueEqualTo(Object value) const {
|
|||||||
current_value = dict.ValueAt(dictionary_entry());
|
current_value = dict.ValueAt(dictionary_entry());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (current_value.IsUninitialized(isolate()) || current_value == value) {
|
return current_value.IsUninitialized(isolate());
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return current_value.IsNumber(isolate_) && value.IsNumber(isolate_) &&
|
|
||||||
Object::SameNumberValue(current_value.Number(), value.Number());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int LookupIterator::GetFieldDescriptorIndex() const {
|
int LookupIterator::GetFieldDescriptorIndex() const {
|
||||||
@ -1053,7 +1045,7 @@ void LookupIterator::WriteDataValue(Handle<Object> value,
|
|||||||
// equal to |value|.
|
// equal to |value|.
|
||||||
DCHECK_IMPLIES(!initializing_store && property_details_.constness() ==
|
DCHECK_IMPLIES(!initializing_store && property_details_.constness() ==
|
||||||
PropertyConstness::kConst,
|
PropertyConstness::kConst,
|
||||||
IsConstFieldValueEqualTo(*value));
|
CanStayConst(*value));
|
||||||
JSObject::cast(*holder).WriteToField(descriptor_number(),
|
JSObject::cast(*holder).WriteToField(descriptor_number(),
|
||||||
property_details_, *value);
|
property_details_, *value);
|
||||||
} else {
|
} else {
|
||||||
@ -1077,7 +1069,7 @@ void LookupIterator::WriteDataValue(Handle<Object> value,
|
|||||||
DCHECK_IMPLIES(
|
DCHECK_IMPLIES(
|
||||||
V8_DICT_PROPERTY_CONST_TRACKING_BOOL && !initializing_store &&
|
V8_DICT_PROPERTY_CONST_TRACKING_BOOL && !initializing_store &&
|
||||||
property_details_.constness() == PropertyConstness::kConst,
|
property_details_.constness() == PropertyConstness::kConst,
|
||||||
holder->IsJSProxy(isolate_) || IsConstDictValueEqualTo(*value));
|
holder->IsJSProxy(isolate_) || DictCanStayConst(*value));
|
||||||
|
|
||||||
if (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) {
|
if (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) {
|
||||||
SwissNameDictionary dictionary =
|
SwissNameDictionary dictionary =
|
||||||
|
@ -257,8 +257,8 @@ class V8_EXPORT_PRIVATE LookupIterator final {
|
|||||||
void RestartInternal(InterceptorState interceptor_state);
|
void RestartInternal(InterceptorState interceptor_state);
|
||||||
Handle<Object> FetchValue(AllocationPolicy allocation_policy =
|
Handle<Object> FetchValue(AllocationPolicy allocation_policy =
|
||||||
AllocationPolicy::kAllocationAllowed) const;
|
AllocationPolicy::kAllocationAllowed) const;
|
||||||
bool IsConstFieldValueEqualTo(Object value) const;
|
bool CanStayConst(Object value) const;
|
||||||
bool IsConstDictValueEqualTo(Object value) const;
|
bool DictCanStayConst(Object value) const;
|
||||||
|
|
||||||
template <bool is_element>
|
template <bool is_element>
|
||||||
void ReloadPropertyInformation();
|
void ReloadPropertyInformation();
|
||||||
|
@ -1,734 +0,0 @@
|
|||||||
// 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 --turbofan --no-always-turbofan
|
|
||||||
// Flags: --no-stress-flush-code --concurrent-recompilation
|
|
||||||
//
|
|
||||||
// Tests tracking of constness of properties stored in dictionary
|
|
||||||
// mode prototypes.
|
|
||||||
|
|
||||||
|
|
||||||
var unique_id = 0;
|
|
||||||
// Creates a function with unique SharedFunctionInfo to ensure the feedback
|
|
||||||
// vector is unique for each test case.
|
|
||||||
function MakeFunctionWithUniqueSFI(...args) {
|
|
||||||
assertTrue(args.length > 0);
|
|
||||||
var body = `/* Unique comment: ${unique_id++} */ ` + args.pop();
|
|
||||||
return new Function(...args, body);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Invalidation by store handler.
|
|
||||||
(function() {
|
|
||||||
var proto = Object.create(null);
|
|
||||||
proto.z = 1;
|
|
||||||
assertFalse(%HasFastProperties(proto));
|
|
||||||
|
|
||||||
var o = Object.create(proto);
|
|
||||||
|
|
||||||
function read_z() {
|
|
||||||
return o.z;
|
|
||||||
}
|
|
||||||
function update_z(new_value) {
|
|
||||||
proto.z = new_value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allocate feedback vector, but we don't want to optimize the function.
|
|
||||||
%PrepareFunctionForOptimization(read_z);
|
|
||||||
for (var i = 0; i < 4; i++) {
|
|
||||||
read_z();
|
|
||||||
}
|
|
||||||
assertTrue(%HasOwnConstDataProperty(proto, "z"));
|
|
||||||
|
|
||||||
// Allocate feedback vector, but we don't want to optimize the function.
|
|
||||||
%PrepareFunctionForOptimization(update_z);
|
|
||||||
for (var i = 0; i < 4; i++) {
|
|
||||||
// Overwriting with same value maintains const-ness.
|
|
||||||
update_z(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
assertTrue(%HasOwnConstDataProperty(proto, "z"));
|
|
||||||
|
|
||||||
update_z(2);
|
|
||||||
|
|
||||||
assertFalse(%HasOwnConstDataProperty(proto, "z"));
|
|
||||||
assertEquals(2, read_z());
|
|
||||||
})();
|
|
||||||
|
|
||||||
// Properties become const when dict mode object becomes prototype.
|
|
||||||
(function() {
|
|
||||||
var proto = Object.create(null);
|
|
||||||
var proto_shadow = Object.create(null);
|
|
||||||
|
|
||||||
proto.z = 1;
|
|
||||||
proto_shadow.z = 1;
|
|
||||||
|
|
||||||
// Make sure that z is marked as mutable.
|
|
||||||
proto.z = 2;
|
|
||||||
proto_shadow.z = 2;
|
|
||||||
|
|
||||||
assertFalse(%HasFastProperties(proto));
|
|
||||||
assertTrue(%HaveSameMap(proto, proto_shadow));
|
|
||||||
|
|
||||||
var o = Object.create(proto);
|
|
||||||
|
|
||||||
assertFalse(%HasFastProperties(proto));
|
|
||||||
// proto must have received new map.
|
|
||||||
assertFalse(%HaveSameMap(proto, proto_shadow));
|
|
||||||
assertEquals(%IsDictPropertyConstTrackingEnabled(),
|
|
||||||
%HasOwnConstDataProperty(proto, "z"));
|
|
||||||
})();
|
|
||||||
|
|
||||||
// Properties become const when fast mode object becomes prototype.
|
|
||||||
(function() {
|
|
||||||
var proto = {}
|
|
||||||
var proto_shadow = {};
|
|
||||||
|
|
||||||
proto.z = 1;
|
|
||||||
proto_shadow.z = 1;
|
|
||||||
|
|
||||||
// Make sure that z is marked as mutable.
|
|
||||||
proto.z = 2;
|
|
||||||
proto_shadow.z = 2;
|
|
||||||
|
|
||||||
assertTrue(%HasFastProperties(proto));
|
|
||||||
assertTrue(%HaveSameMap(proto, proto_shadow));
|
|
||||||
|
|
||||||
var o = Object.create(proto);
|
|
||||||
|
|
||||||
assertFalse(%HasFastProperties(proto));
|
|
||||||
// proto must have received new map.
|
|
||||||
assertFalse(%HaveSameMap(proto, proto_shadow));
|
|
||||||
assertEquals(%IsDictPropertyConstTrackingEnabled(),
|
|
||||||
%HasOwnConstDataProperty(proto, "z"));
|
|
||||||
})();
|
|
||||||
|
|
||||||
function testbench(o, proto, update_proto, check_constness) {
|
|
||||||
var check_z = MakeFunctionWithUniqueSFI("obj", "return obj.z;");
|
|
||||||
|
|
||||||
if (check_constness && %IsDictPropertyConstTrackingEnabled())
|
|
||||||
assertTrue(%HasOwnConstDataProperty(proto, "z"));
|
|
||||||
|
|
||||||
// Allocate feedback vector, but we don't want to optimize the function.
|
|
||||||
%PrepareFunctionForOptimization(check_z);
|
|
||||||
for (var i = 0; i < 4; i++) {
|
|
||||||
check_z(o);
|
|
||||||
}
|
|
||||||
|
|
||||||
update_proto();
|
|
||||||
|
|
||||||
if (%IsDictPropertyConstTrackingEnabled()) {
|
|
||||||
if (check_constness)
|
|
||||||
assertFalse(%HasOwnConstDataProperty(proto, "z"));
|
|
||||||
assertFalse(%HasFastProperties(proto));
|
|
||||||
}
|
|
||||||
|
|
||||||
assertEquals("2", check_z(o));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Simple update.
|
|
||||||
(function() {
|
|
||||||
var proto = Object.create(null);
|
|
||||||
proto.z = "1";
|
|
||||||
assertFalse(%HasFastProperties(proto));
|
|
||||||
|
|
||||||
var o = Object.create(proto);
|
|
||||||
|
|
||||||
function update_z() {
|
|
||||||
proto.z = "2";
|
|
||||||
}
|
|
||||||
|
|
||||||
testbench(o, proto, update_z, true);
|
|
||||||
})();
|
|
||||||
|
|
||||||
// Update using Object.assign.
|
|
||||||
(function() {
|
|
||||||
var proto = Object.create(null);
|
|
||||||
proto.z = "1";
|
|
||||||
assertFalse(%HasFastProperties(proto));
|
|
||||||
|
|
||||||
var o = Object.create(proto);
|
|
||||||
|
|
||||||
function update_z() {
|
|
||||||
Object.assign(proto, {z: "2"});
|
|
||||||
}
|
|
||||||
|
|
||||||
testbench(o, proto, update_z, true);
|
|
||||||
})();
|
|
||||||
|
|
||||||
// Update using Object.defineProperty
|
|
||||||
(function() {
|
|
||||||
var proto = Object.create(null);
|
|
||||||
proto.z = "1";
|
|
||||||
assertFalse(%HasFastProperties(proto));
|
|
||||||
|
|
||||||
var o = Object.create(proto);
|
|
||||||
|
|
||||||
function update_z() {
|
|
||||||
Object.defineProperty(proto, 'z', {
|
|
||||||
value: "2",
|
|
||||||
configurable: true,
|
|
||||||
enumerable: true,
|
|
||||||
writable: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
testbench(o, proto, update_z, true);
|
|
||||||
})();
|
|
||||||
|
|
||||||
|
|
||||||
// Update using setter
|
|
||||||
(function() {
|
|
||||||
var proto = Object.create(null);
|
|
||||||
Object.defineProperty(proto, "z", {
|
|
||||||
get : function () {return this.z_val;},
|
|
||||||
set : function (new_z) {this.z_val = new_z;}
|
|
||||||
});
|
|
||||||
|
|
||||||
proto.z = "1";
|
|
||||||
assertFalse(%HasFastProperties(proto));
|
|
||||||
|
|
||||||
var o = Object.create(proto);
|
|
||||||
|
|
||||||
function update_z() {
|
|
||||||
proto.z = "2";
|
|
||||||
}
|
|
||||||
|
|
||||||
testbench(o, proto, update_z, false);
|
|
||||||
})();
|
|
||||||
|
|
||||||
// Proxy test 1: Update via proxy.
|
|
||||||
(function() {
|
|
||||||
var proto = Object.create(null);
|
|
||||||
|
|
||||||
var proxy = new Proxy(proto, {});
|
|
||||||
|
|
||||||
proxy.z = "1";
|
|
||||||
assertFalse(%HasFastProperties(proto));
|
|
||||||
|
|
||||||
var o = Object.create(proxy);
|
|
||||||
|
|
||||||
function update_z() {
|
|
||||||
proxy.z = "2";
|
|
||||||
}
|
|
||||||
|
|
||||||
testbench(o, proto, update_z, false);
|
|
||||||
})();
|
|
||||||
|
|
||||||
// Proxy test 2: Update on proto.
|
|
||||||
(function() {
|
|
||||||
var proto = Object.create(null);
|
|
||||||
|
|
||||||
var proxy = new Proxy(proto, {});
|
|
||||||
|
|
||||||
proto.z = "1";
|
|
||||||
assertFalse(%HasFastProperties(proto));
|
|
||||||
|
|
||||||
var o = Object.create(proxy);
|
|
||||||
|
|
||||||
function update_z() {
|
|
||||||
proto.z = "2";
|
|
||||||
}
|
|
||||||
|
|
||||||
testbench(o, proto, update_z, false);
|
|
||||||
})();
|
|
||||||
|
|
||||||
// Proxy test 3: Update intercepted.
|
|
||||||
(function() {
|
|
||||||
var proto = Object.create(null);
|
|
||||||
|
|
||||||
var handler = {
|
|
||||||
get: function(target, prop) {
|
|
||||||
return target.the_value;
|
|
||||||
},
|
|
||||||
set: function(target, prop, value) {
|
|
||||||
return target.the_value = value;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var proxy = new Proxy(proto, handler);
|
|
||||||
|
|
||||||
proxy.z = "1";
|
|
||||||
assertFalse(%HasFastProperties(proto));
|
|
||||||
|
|
||||||
var o = Object.create(proxy);
|
|
||||||
|
|
||||||
function update_z() {
|
|
||||||
proxy.z = "2";
|
|
||||||
}
|
|
||||||
|
|
||||||
testbench(o, proto, update_z, false);
|
|
||||||
|
|
||||||
})();
|
|
||||||
|
|
||||||
//
|
|
||||||
// Below: Testing TF optimization of accessing constants in dictionary mode
|
|
||||||
// protoypes.
|
|
||||||
//
|
|
||||||
|
|
||||||
// Test inlining with fast mode receiver.
|
|
||||||
(function() {
|
|
||||||
var proto = Object.create(null);
|
|
||||||
proto.x = 1;
|
|
||||||
var o = Object.create(proto);
|
|
||||||
assertTrue(%HasFastProperties(o));
|
|
||||||
assertFalse(%HasFastProperties(proto));
|
|
||||||
|
|
||||||
function read_x(arg_o) {
|
|
||||||
return arg_o.x;
|
|
||||||
}
|
|
||||||
|
|
||||||
%PrepareFunctionForOptimization(read_x);
|
|
||||||
assertEquals(1, read_x(o));
|
|
||||||
%OptimizeFunctionOnNextCall(read_x);
|
|
||||||
assertEquals(1, read_x(o));
|
|
||||||
assertOptimized(read_x);
|
|
||||||
|
|
||||||
|
|
||||||
// Test that we inlined the access:
|
|
||||||
var dummy = {x : 123};
|
|
||||||
read_x(dummy);
|
|
||||||
|
|
||||||
if (%IsDictPropertyConstTrackingEnabled()) {
|
|
||||||
assertTrue(%HasFastProperties(o));
|
|
||||||
assertFalse(%HasFastProperties(proto));
|
|
||||||
assertUnoptimized(read_x);
|
|
||||||
}
|
|
||||||
|
|
||||||
})();
|
|
||||||
|
|
||||||
// Test inlining with dictionary mode receiver that is a prototype.
|
|
||||||
|
|
||||||
(function() {
|
|
||||||
|
|
||||||
var proto1 = Object.create(null);
|
|
||||||
proto1.x = 1;
|
|
||||||
var proto2 = Object.create(null);
|
|
||||||
var o = Object.create(proto1);
|
|
||||||
Object.setPrototypeOf(proto1, proto2);
|
|
||||||
|
|
||||||
assertTrue(%HasFastProperties(o));
|
|
||||||
assertFalse(%HasFastProperties(proto1));
|
|
||||||
assertFalse(%HasFastProperties(proto2));
|
|
||||||
|
|
||||||
function read_x(arg_o) {
|
|
||||||
return arg_o.x;
|
|
||||||
}
|
|
||||||
|
|
||||||
%PrepareFunctionForOptimization(read_x);
|
|
||||||
assertEquals(1, read_x(proto1));
|
|
||||||
%OptimizeFunctionOnNextCall(read_x);
|
|
||||||
assertEquals(1, read_x(proto1));
|
|
||||||
assertOptimized(read_x);
|
|
||||||
|
|
||||||
// Test that we inlined the access:
|
|
||||||
var dummy = {x : 123};
|
|
||||||
read_x(dummy);
|
|
||||||
|
|
||||||
// TODO(v8:11457) This test doesn't work yet, see TODO in
|
|
||||||
// AccessInfoFactory::TryLoadPropertyDetails. Currently, we can't inline
|
|
||||||
// accesses with dictionary mode receivers.
|
|
||||||
// if (%IsDictPropertyConstTrackingEnabled()) {
|
|
||||||
// assertTrue(%HasFastProperties(o));
|
|
||||||
// assertFalse(%HasFastProperties(proto1));
|
|
||||||
// assertFalse(%HasFastProperties(proto2));
|
|
||||||
// assertUnoptimized(read_x);
|
|
||||||
// }
|
|
||||||
})();
|
|
||||||
|
|
||||||
// The machinery we use for detecting the invalidation of constants held by
|
|
||||||
// dictionary mode objects (related to the prototype validity cell mechanism) is
|
|
||||||
// specific to prototypes. This means that for non-prototype dictionary mode
|
|
||||||
// objects, we have no way of detecting changes invalidating folded
|
|
||||||
// constants. Therefore, we must not fold constants held by non-prototype
|
|
||||||
// dictionary mode objects. This is tested here.
|
|
||||||
(function() {
|
|
||||||
var proto = Object.create(null);
|
|
||||||
proto.x = 1;
|
|
||||||
var o = Object.create(null);
|
|
||||||
Object.setPrototypeOf(o, proto);
|
|
||||||
assertFalse(%HasFastProperties(o));
|
|
||||||
assertFalse(%HasFastProperties(proto));
|
|
||||||
|
|
||||||
function read_x(arg_o) {
|
|
||||||
return arg_o.x;
|
|
||||||
}
|
|
||||||
|
|
||||||
%PrepareFunctionForOptimization(read_x);
|
|
||||||
assertEquals(1, read_x(o));
|
|
||||||
%OptimizeFunctionOnNextCall(read_x);
|
|
||||||
assertEquals(1, read_x(o));
|
|
||||||
assertOptimized(read_x);
|
|
||||||
|
|
||||||
var dummy = {x : 123};
|
|
||||||
read_x(dummy);
|
|
||||||
|
|
||||||
if (%IsDictPropertyConstTrackingEnabled()) {
|
|
||||||
assertFalse(%HasFastProperties(o));
|
|
||||||
assertFalse(%HasFastProperties(proto));
|
|
||||||
|
|
||||||
// We never inlined the acceess, so it's still optimized.
|
|
||||||
assertOptimized(read_x);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
// Test inlining of accessor.
|
|
||||||
(function() {
|
|
||||||
var proto = Object.create(null);
|
|
||||||
proto.x_val = 1;
|
|
||||||
Object.defineProperty(proto, "x", {
|
|
||||||
get : function () {return this.x_val;}
|
|
||||||
});
|
|
||||||
|
|
||||||
var o = Object.create(proto);
|
|
||||||
assertFalse(%HasFastProperties(proto))
|
|
||||||
|
|
||||||
function read_x(arg_o) {
|
|
||||||
return arg_o.x;
|
|
||||||
}
|
|
||||||
|
|
||||||
%PrepareFunctionForOptimization(read_x);
|
|
||||||
assertEquals(1, read_x(o));
|
|
||||||
%OptimizeFunctionOnNextCall(read_x);
|
|
||||||
assertEquals(1, read_x(o));
|
|
||||||
assertOptimized(read_x);
|
|
||||||
|
|
||||||
// Test that we inlined the access:
|
|
||||||
var dummy = {x : 123};
|
|
||||||
read_x(dummy);
|
|
||||||
|
|
||||||
if (%IsDictPropertyConstTrackingEnabled()) {
|
|
||||||
assertTrue(%HasFastProperties(o));
|
|
||||||
assertFalse(%HasFastProperties(proto));
|
|
||||||
assertUnoptimized(read_x);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
// Invalidation by adding same property to receiver.
|
|
||||||
(function() {
|
|
||||||
var proto = Object.create(null);
|
|
||||||
proto.x = 1;
|
|
||||||
var o = Object.create(proto);
|
|
||||||
assertTrue(%HasFastProperties(o));
|
|
||||||
assertFalse(%HasFastProperties(proto));
|
|
||||||
|
|
||||||
function read_x(arg_o) {
|
|
||||||
return arg_o.x;
|
|
||||||
}
|
|
||||||
|
|
||||||
%PrepareFunctionForOptimization(read_x);
|
|
||||||
assertEquals(1, read_x(o));
|
|
||||||
%OptimizeFunctionOnNextCall(read_x);
|
|
||||||
assertEquals(1, read_x(o));
|
|
||||||
assertOptimized(read_x);
|
|
||||||
|
|
||||||
o.x = 2;
|
|
||||||
|
|
||||||
assertEquals(2, read_x(o));
|
|
||||||
|
|
||||||
if (%IsDictPropertyConstTrackingEnabled()) {
|
|
||||||
assertTrue(%HasFastProperties(o));
|
|
||||||
assertFalse(%HasFastProperties(proto));
|
|
||||||
assertUnoptimized(read_x);
|
|
||||||
}
|
|
||||||
|
|
||||||
})();
|
|
||||||
|
|
||||||
// Invalidation by adding property to intermediate prototype.
|
|
||||||
(function() {
|
|
||||||
var proto = Object.create(null);
|
|
||||||
proto.x = 1;
|
|
||||||
var in_between = Object.create(null);
|
|
||||||
Object.setPrototypeOf(in_between, proto);
|
|
||||||
|
|
||||||
var o = Object.create(in_between);
|
|
||||||
assertTrue(%HasFastProperties(o));
|
|
||||||
assertFalse(%HasFastProperties(in_between));
|
|
||||||
assertFalse(%HasFastProperties(proto));
|
|
||||||
|
|
||||||
function read_x(arg_o) {
|
|
||||||
return arg_o.x;
|
|
||||||
}
|
|
||||||
|
|
||||||
%PrepareFunctionForOptimization(read_x);
|
|
||||||
assertEquals(1, read_x(o));
|
|
||||||
%OptimizeFunctionOnNextCall(read_x);
|
|
||||||
assertEquals(1, read_x(o));
|
|
||||||
assertOptimized(read_x);
|
|
||||||
|
|
||||||
in_between.x = 2;
|
|
||||||
|
|
||||||
if (%IsDictPropertyConstTrackingEnabled()) {
|
|
||||||
assertFalse(%HasFastProperties(in_between));
|
|
||||||
assertFalse(%HasFastProperties(proto));
|
|
||||||
assertUnoptimized(read_x);
|
|
||||||
}
|
|
||||||
|
|
||||||
assertEquals(2, read_x(o));
|
|
||||||
})();
|
|
||||||
|
|
||||||
// Invalidation by changing prototype of receiver.
|
|
||||||
(function() {
|
|
||||||
var proto = Object.create(null);
|
|
||||||
proto.x = 1;
|
|
||||||
var other_proto = Object.create(null);
|
|
||||||
other_proto.x = 2;
|
|
||||||
|
|
||||||
var o = Object.create(proto);
|
|
||||||
assertTrue(%HasFastProperties(o));
|
|
||||||
assertFalse(%HasFastProperties(proto));
|
|
||||||
|
|
||||||
function read_x(arg_o) {
|
|
||||||
return arg_o.x;
|
|
||||||
}
|
|
||||||
|
|
||||||
%PrepareFunctionForOptimization(read_x);
|
|
||||||
assertEquals(1, read_x(o));
|
|
||||||
%OptimizeFunctionOnNextCall(read_x);
|
|
||||||
assertEquals(1, read_x(o));
|
|
||||||
|
|
||||||
Object.setPrototypeOf(o, other_proto);
|
|
||||||
assertEquals(2, read_x(o));
|
|
||||||
|
|
||||||
if (%IsDictPropertyConstTrackingEnabled()) {
|
|
||||||
assertFalse(%HasFastProperties(proto));
|
|
||||||
assertFalse(%HasFastProperties(other_proto));
|
|
||||||
assertUnoptimized(read_x);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
// Invalidation by changing [[Prototype]] of a prototype on the chain from the
|
|
||||||
// receiver to the holder.
|
|
||||||
(function() {
|
|
||||||
var proto = Object.create(null);
|
|
||||||
proto.x = 1;
|
|
||||||
var other_proto = Object.create(null);
|
|
||||||
other_proto.x = 2;
|
|
||||||
var in_between = Object.create(null);
|
|
||||||
Object.setPrototypeOf(in_between, proto);
|
|
||||||
|
|
||||||
var o = Object.create(in_between);
|
|
||||||
assertTrue(%HasFastProperties(o));
|
|
||||||
assertFalse(%HasFastProperties(in_between));
|
|
||||||
assertFalse(%HasFastProperties(proto));
|
|
||||||
|
|
||||||
function read_x(arg_o) {
|
|
||||||
return arg_o.x;
|
|
||||||
}
|
|
||||||
|
|
||||||
%PrepareFunctionForOptimization(read_x);
|
|
||||||
assertEquals(1, read_x(o));
|
|
||||||
%OptimizeFunctionOnNextCall(read_x);
|
|
||||||
assertEquals(1, read_x(o));
|
|
||||||
assertOptimized(read_x);
|
|
||||||
|
|
||||||
Object.setPrototypeOf(in_between, other_proto);
|
|
||||||
|
|
||||||
if (%IsDictPropertyConstTrackingEnabled()) {
|
|
||||||
assertFalse(%HasFastProperties(in_between));
|
|
||||||
assertFalse(%HasFastProperties(proto));
|
|
||||||
assertFalse(%HasFastProperties(other_proto));
|
|
||||||
assertUnoptimized(read_x);
|
|
||||||
}
|
|
||||||
|
|
||||||
assertEquals(2, read_x(o));
|
|
||||||
})();
|
|
||||||
|
|
||||||
// Invalidation by changing property on prototype itself.
|
|
||||||
(function() {
|
|
||||||
var proto = Object.create(null);
|
|
||||||
proto.x = 1;
|
|
||||||
var o = Object.create(proto);
|
|
||||||
assertTrue(%HasFastProperties(o));
|
|
||||||
assertFalse(%HasFastProperties(proto));
|
|
||||||
|
|
||||||
function read_x(arg_o) {
|
|
||||||
return arg_o.x;
|
|
||||||
}
|
|
||||||
|
|
||||||
%PrepareFunctionForOptimization(read_x);
|
|
||||||
assertEquals(1, read_x(o));
|
|
||||||
%OptimizeFunctionOnNextCall(read_x);
|
|
||||||
assertEquals(1, read_x(o));
|
|
||||||
assertOptimized(read_x);
|
|
||||||
|
|
||||||
proto.x = 2;
|
|
||||||
|
|
||||||
if (%IsDictPropertyConstTrackingEnabled()) {
|
|
||||||
assertFalse(%HasFastProperties(proto));
|
|
||||||
assertUnoptimized(read_x);
|
|
||||||
}
|
|
||||||
|
|
||||||
assertEquals(2, read_x(o));
|
|
||||||
})();
|
|
||||||
|
|
||||||
// Invalidation by deleting property on prototype.
|
|
||||||
(function() {
|
|
||||||
var proto = Object.create(null);
|
|
||||||
proto.x = 1;
|
|
||||||
var o = Object.create(proto);
|
|
||||||
assertTrue(%HasFastProperties(o));
|
|
||||||
assertFalse(%HasFastProperties(proto));
|
|
||||||
|
|
||||||
function read_x(arg_o) {
|
|
||||||
return arg_o.x;
|
|
||||||
}
|
|
||||||
|
|
||||||
%PrepareFunctionForOptimization(read_x);
|
|
||||||
read_x(o);
|
|
||||||
%OptimizeFunctionOnNextCall(read_x);
|
|
||||||
read_x(o);
|
|
||||||
|
|
||||||
delete proto.x;
|
|
||||||
|
|
||||||
if (%IsDictPropertyConstTrackingEnabled()) {
|
|
||||||
assertFalse(%HasFastProperties(proto));
|
|
||||||
assertUnoptimized(read_x);
|
|
||||||
}
|
|
||||||
|
|
||||||
assertEquals(undefined, read_x(o));
|
|
||||||
})();
|
|
||||||
|
|
||||||
// Storing the same value does not invalidate const-ness. Store done from
|
|
||||||
// runtime/without feedback.
|
|
||||||
(function() {
|
|
||||||
var proto = Object.create(null);
|
|
||||||
var some_object = {bla: 123};
|
|
||||||
proto.x = 1;
|
|
||||||
proto.y = some_object
|
|
||||||
|
|
||||||
var o = Object.create(proto);
|
|
||||||
assertTrue(%HasFastProperties(o));
|
|
||||||
assertFalse(%HasFastProperties(proto));
|
|
||||||
|
|
||||||
function read_xy(arg_o) {
|
|
||||||
return [arg_o.x, arg_o.y];
|
|
||||||
}
|
|
||||||
|
|
||||||
%PrepareFunctionForOptimization(read_xy);
|
|
||||||
assertEquals([1, some_object], read_xy(o));
|
|
||||||
%OptimizeFunctionOnNextCall(read_xy);
|
|
||||||
assertEquals([1, some_object], read_xy(o));
|
|
||||||
assertOptimized(read_xy);
|
|
||||||
|
|
||||||
// Build value 1 without re-using proto.x.
|
|
||||||
var x2 = 0;
|
|
||||||
for(var i = 0; i < 5; ++i) {
|
|
||||||
x2 += 0.2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Storing the same values for x and y again:
|
|
||||||
proto.x = x2;
|
|
||||||
proto.y = some_object;
|
|
||||||
assertEquals(x2, proto.x);
|
|
||||||
|
|
||||||
if (%IsDictPropertyConstTrackingEnabled()) {
|
|
||||||
assertFalse(%HasFastProperties(proto));
|
|
||||||
assertTrue(%HasOwnConstDataProperty(proto, "x"));
|
|
||||||
assertOptimized(read_xy);
|
|
||||||
}
|
|
||||||
|
|
||||||
proto.x = 2;
|
|
||||||
if (%IsDictPropertyConstTrackingEnabled()) {
|
|
||||||
assertFalse(%HasFastProperties(proto));
|
|
||||||
assertFalse(%HasOwnConstDataProperty(proto, "x"));
|
|
||||||
assertUnoptimized(read_xy);
|
|
||||||
}
|
|
||||||
|
|
||||||
assertEquals(2, read_xy(o)[0]);
|
|
||||||
})();
|
|
||||||
|
|
||||||
// Storing the same value does not invalidate const-ness. Store done by IC
|
|
||||||
// handler.
|
|
||||||
(function() {
|
|
||||||
var proto = Object.create(null);
|
|
||||||
var some_object = {bla: 123};
|
|
||||||
proto.x = 1;
|
|
||||||
proto.y = some_object
|
|
||||||
|
|
||||||
var o = Object.create(proto);
|
|
||||||
assertTrue(%HasFastProperties(o));
|
|
||||||
assertFalse(%HasFastProperties(proto));
|
|
||||||
|
|
||||||
function read_xy(arg_o) {
|
|
||||||
return [arg_o.x, arg_o.y];
|
|
||||||
}
|
|
||||||
|
|
||||||
%PrepareFunctionForOptimization(read_xy);
|
|
||||||
assertEquals([1, some_object], read_xy(o));
|
|
||||||
%OptimizeFunctionOnNextCall(read_xy);
|
|
||||||
assertEquals([1, some_object], read_xy(o));
|
|
||||||
assertOptimized(read_xy);
|
|
||||||
|
|
||||||
// Build value 1 without re-using proto.x.
|
|
||||||
var x2 = 0;
|
|
||||||
for(var i = 0; i < 5; ++i) {
|
|
||||||
x2 += 0.2;
|
|
||||||
}
|
|
||||||
|
|
||||||
function change_xy(obj, x, y) {
|
|
||||||
obj.x = x;
|
|
||||||
obj.y = y;
|
|
||||||
}
|
|
||||||
|
|
||||||
%PrepareFunctionForOptimization(change_xy);
|
|
||||||
// Storing the same values for x and y again:
|
|
||||||
change_xy(proto, 1, some_object);
|
|
||||||
change_xy(proto, 1, some_object);
|
|
||||||
|
|
||||||
if (%IsDictPropertyConstTrackingEnabled()) {
|
|
||||||
assertFalse(%HasFastProperties(proto));
|
|
||||||
assertTrue(%HasOwnConstDataProperty(proto, "x"));
|
|
||||||
assertOptimized(read_xy);
|
|
||||||
}
|
|
||||||
|
|
||||||
change_xy(proto, 2, some_object);
|
|
||||||
|
|
||||||
if (%IsDictPropertyConstTrackingEnabled()) {
|
|
||||||
assertFalse(%HasFastProperties(proto));
|
|
||||||
assertFalse(%HasOwnConstDataProperty(proto, "x"));
|
|
||||||
assertUnoptimized(read_xy);
|
|
||||||
}
|
|
||||||
|
|
||||||
assertEquals(2, read_xy(o)[0]);
|
|
||||||
})();
|
|
||||||
|
|
||||||
// Invalidation by replacing a prototype. Just like the old prototype, the new
|
|
||||||
// prototype owns the property as an accessor, but in the form of an
|
|
||||||
// AccessorInfo rather than an AccessorPair.
|
|
||||||
(function() {
|
|
||||||
var proto1 = Object.create(null);
|
|
||||||
Object.defineProperty(proto1, 'length', {get() {return 1}});
|
|
||||||
var proto2 = Object.create(proto1);
|
|
||||||
var o = Object.create(proto2);
|
|
||||||
assertTrue(%HasFastProperties(o));
|
|
||||||
assertFalse(%HasFastProperties(proto1));
|
|
||||||
assertFalse(%HasFastProperties(proto2));
|
|
||||||
|
|
||||||
function read_length(arg_o) {
|
|
||||||
return arg_o.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
%PrepareFunctionForOptimization(read_length);
|
|
||||||
assertEquals(1, read_length(o));
|
|
||||||
%DisableOptimizationFinalization();
|
|
||||||
%OptimizeFunctionOnNextCall(read_length, "concurrent");
|
|
||||||
assertEquals(1, read_length(o));
|
|
||||||
assertUnoptimized(read_length);
|
|
||||||
|
|
||||||
%WaitForBackgroundOptimization();
|
|
||||||
var other_proto1 = [];
|
|
||||||
Object.setPrototypeOf(proto2, other_proto1);
|
|
||||||
%FinalizeOptimization();
|
|
||||||
|
|
||||||
assertUnoptimized(read_length);
|
|
||||||
assertEquals(0, read_length(o));
|
|
||||||
|
|
||||||
if (%IsDictPropertyConstTrackingEnabled()) {
|
|
||||||
assertFalse(%HasFastProperties(proto1));
|
|
||||||
assertFalse(%HasFastProperties(proto2));
|
|
||||||
assertFalse(%HasFastProperties(other_proto1));
|
|
||||||
assertUnoptimized(read_length);
|
|
||||||
}
|
|
||||||
})();
|
|
@ -1,237 +0,0 @@
|
|||||||
// Copyright 2017 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 --turbofan --no-always-turbofan
|
|
||||||
|
|
||||||
var global = this;
|
|
||||||
var unique_id = 0;
|
|
||||||
// Creates a function with unique SharedFunctionInfo to ensure the feedback
|
|
||||||
// vector is unique for each test case.
|
|
||||||
function MakeFunctionWithUniqueSFI(...args) {
|
|
||||||
assertTrue(args.length > 0);
|
|
||||||
var body = `/* Unique comment: ${unique_id++} */ ` + args.pop();
|
|
||||||
return new Function(...args, body);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//
|
|
||||||
// Load constant field from constant object directly.
|
|
||||||
//
|
|
||||||
function TestLoadFromConstantFieldOfAConstantObject(the_value, other_value) {
|
|
||||||
function A(v) { this.v = v; }
|
|
||||||
function O() { this.a = new A(the_value); }
|
|
||||||
var the_object = new O();
|
|
||||||
|
|
||||||
// Ensure that {the_object.a}'s map is not stable to complicate compiler's
|
|
||||||
// life.
|
|
||||||
new A(the_value).blah = 0;
|
|
||||||
|
|
||||||
// Ensure that constant tracking is enabled for {contant_object}.
|
|
||||||
delete global.constant_object;
|
|
||||||
global.constant_object = the_object;
|
|
||||||
assertEquals(the_object, constant_object);
|
|
||||||
|
|
||||||
assertTrue(%HasFastProperties(the_object));
|
|
||||||
|
|
||||||
// {constant_object} is known to the compiler via global property cell
|
|
||||||
// tracking.
|
|
||||||
var load = MakeFunctionWithUniqueSFI("return constant_object.a.v;");
|
|
||||||
%PrepareFunctionForOptimization(load);
|
|
||||||
load();
|
|
||||||
load();
|
|
||||||
%OptimizeFunctionOnNextCall(load);
|
|
||||||
assertEquals(the_value, load());
|
|
||||||
assertOptimized(load);
|
|
||||||
var a = new A(other_value);
|
|
||||||
assertTrue(%HaveSameMap(a, the_object.a));
|
|
||||||
// Make constant field mutable by assigning another value
|
|
||||||
// to some other instance of A.
|
|
||||||
new A(the_value).v = other_value;
|
|
||||||
assertTrue(%HaveSameMap(a, new A(the_value)));
|
|
||||||
assertTrue(%HaveSameMap(a, the_object.a));
|
|
||||||
assertUnoptimized(load);
|
|
||||||
assertEquals(the_value, load());
|
|
||||||
assertUnoptimized(load);
|
|
||||||
assertEquals(the_value, load());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test constant tracking with Smi value.
|
|
||||||
(function() {
|
|
||||||
var the_value = 42;
|
|
||||||
var other_value = 153;
|
|
||||||
TestLoadFromConstantFieldOfAConstantObject(the_value, other_value);
|
|
||||||
})();
|
|
||||||
|
|
||||||
// Test constant tracking with double value.
|
|
||||||
(function() {
|
|
||||||
var the_value = 0.9;
|
|
||||||
var other_value = 0.42;
|
|
||||||
TestLoadFromConstantFieldOfAConstantObject(the_value, other_value);
|
|
||||||
})();
|
|
||||||
|
|
||||||
// Test constant tracking with function value.
|
|
||||||
(function() {
|
|
||||||
var the_value = function V() {};
|
|
||||||
var other_value = function W() {};
|
|
||||||
TestLoadFromConstantFieldOfAConstantObject(the_value, other_value);
|
|
||||||
})();
|
|
||||||
|
|
||||||
// Test constant tracking with heap object value.
|
|
||||||
(function() {
|
|
||||||
function V() {}
|
|
||||||
var the_value = new V();
|
|
||||||
var other_value = new V();
|
|
||||||
TestLoadFromConstantFieldOfAConstantObject(the_value, other_value);
|
|
||||||
})();
|
|
||||||
|
|
||||||
|
|
||||||
//
|
|
||||||
// Load constant field from a prototype.
|
|
||||||
//
|
|
||||||
function TestLoadFromConstantFieldOfAPrototype(the_value, other_value) {
|
|
||||||
function Proto() { this.v = the_value; }
|
|
||||||
var the_prototype = new Proto();
|
|
||||||
|
|
||||||
function O() {}
|
|
||||||
O.prototype = the_prototype;
|
|
||||||
var the_object = new O();
|
|
||||||
|
|
||||||
// Ensure O.prototype is in fast mode by loading from its field.
|
|
||||||
function warmup() { return new O().v; }
|
|
||||||
%EnsureFeedbackVectorForFunction(warmup);
|
|
||||||
warmup(); warmup(); warmup();
|
|
||||||
if (!%IsDictPropertyConstTrackingEnabled())
|
|
||||||
assertTrue(%HasFastProperties(O.prototype));
|
|
||||||
|
|
||||||
// The parameter object is not constant but all the values have the same
|
|
||||||
// map and therefore the compiler knows the prototype object and can
|
|
||||||
// optimize load of "v".
|
|
||||||
var load = MakeFunctionWithUniqueSFI("o", "return o.v;");
|
|
||||||
%PrepareFunctionForOptimization(load);
|
|
||||||
load(new O());
|
|
||||||
load(new O());
|
|
||||||
%OptimizeFunctionOnNextCall(load);
|
|
||||||
assertEquals(the_value, load(new O()));
|
|
||||||
assertOptimized(load);
|
|
||||||
// Invalidation of mutability should trigger deoptimization with a
|
|
||||||
// "field-owner" reason.
|
|
||||||
the_prototype.v = other_value;
|
|
||||||
assertUnoptimized(load);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test constant tracking with Smi value.
|
|
||||||
(function() {
|
|
||||||
var the_value = 42;
|
|
||||||
var other_value = 153;
|
|
||||||
TestLoadFromConstantFieldOfAPrototype(the_value, other_value);
|
|
||||||
})();
|
|
||||||
|
|
||||||
// Test constant tracking with double value.
|
|
||||||
(function() {
|
|
||||||
var the_value = 0.9;
|
|
||||||
var other_value = 0.42;
|
|
||||||
TestLoadFromConstantFieldOfAPrototype(the_value, other_value);
|
|
||||||
})();
|
|
||||||
|
|
||||||
// Test constant tracking with function value.
|
|
||||||
(function() {
|
|
||||||
var the_value = function V() {};
|
|
||||||
var other_value = function W() {};
|
|
||||||
TestLoadFromConstantFieldOfAPrototype(the_value, other_value);
|
|
||||||
})();
|
|
||||||
|
|
||||||
// Test constant tracking with heap object value.
|
|
||||||
(function() {
|
|
||||||
function V() {}
|
|
||||||
var the_value = new V();
|
|
||||||
var other_value = new V();
|
|
||||||
TestLoadFromConstantFieldOfAPrototype(the_value, other_value);
|
|
||||||
})();
|
|
||||||
|
|
||||||
|
|
||||||
//
|
|
||||||
// Store to constant field of a constant object.
|
|
||||||
//
|
|
||||||
function TestStoreToConstantFieldOfConstantObject(the_value, other_value) {
|
|
||||||
function A(v) { this.v = v; }
|
|
||||||
function O() { this.a = new A(the_value); }
|
|
||||||
var the_object = new O();
|
|
||||||
|
|
||||||
// Ensure that {the_object.a}'s map is not stable to complicate compiler's
|
|
||||||
// life.
|
|
||||||
new A(the_value).blah = 0;
|
|
||||||
|
|
||||||
// Ensure that constant tracking is enabled for {contant_object}.
|
|
||||||
delete global.constant_object;
|
|
||||||
global.constant_object = the_object;
|
|
||||||
assertEquals(the_object, constant_object);
|
|
||||||
|
|
||||||
assertTrue(%HasFastProperties(the_object));
|
|
||||||
|
|
||||||
// {constant_object} is known to the compiler via global property cell
|
|
||||||
// tracking.
|
|
||||||
var store = MakeFunctionWithUniqueSFI("v", "constant_object.a.v = v;");
|
|
||||||
%PrepareFunctionForOptimization(store);
|
|
||||||
store(the_value);
|
|
||||||
store(the_value);
|
|
||||||
%OptimizeFunctionOnNextCall(store);
|
|
||||||
store(the_value);
|
|
||||||
assertEquals(the_value, constant_object.a.v);
|
|
||||||
assertOptimized(store);
|
|
||||||
// Storing of the same value does not deoptimize.
|
|
||||||
store(the_value);
|
|
||||||
assertEquals(the_value, constant_object.a.v);
|
|
||||||
assertOptimized(store);
|
|
||||||
|
|
||||||
var a = new A(other_value);
|
|
||||||
|
|
||||||
if (typeof the_value == "function" || typeof the_value == "object") {
|
|
||||||
// For heap object fields "field-owner" dependency is installed for
|
|
||||||
// any access of the field, therefore making constant field mutable by
|
|
||||||
// assigning other value to some other instance of A should already
|
|
||||||
// trigger deoptimization.
|
|
||||||
assertTrue(%HaveSameMap(a, the_object.a));
|
|
||||||
new A(the_value).v = other_value;
|
|
||||||
assertTrue(%HaveSameMap(a, new A(the_value)));
|
|
||||||
assertTrue(%HaveSameMap(a, the_object.a));
|
|
||||||
assertUnoptimized(store);
|
|
||||||
} else {
|
|
||||||
assertOptimized(store);
|
|
||||||
}
|
|
||||||
// Storing other value deoptimizes because of failed value check.
|
|
||||||
store(other_value);
|
|
||||||
assertUnoptimized(store);
|
|
||||||
assertEquals(other_value, constant_object.a.v);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test constant tracking with Smi values.
|
|
||||||
(function() {
|
|
||||||
var the_value = 42;
|
|
||||||
var other_value = 153;
|
|
||||||
TestStoreToConstantFieldOfConstantObject(the_value, other_value);
|
|
||||||
})();
|
|
||||||
|
|
||||||
// Test constant tracking with double values.
|
|
||||||
/*
|
|
||||||
(function() {
|
|
||||||
var the_value = 0.9;
|
|
||||||
var other_value = 0.42
|
|
||||||
TestStoreToConstantFieldOfConstantObject(the_value, other_value);
|
|
||||||
})();
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Test constant tracking with function values.
|
|
||||||
(function() {
|
|
||||||
var the_value = function V() {};
|
|
||||||
var other_value = function W() {};
|
|
||||||
TestStoreToConstantFieldOfConstantObject(the_value, other_value);
|
|
||||||
})();
|
|
||||||
|
|
||||||
// Test constant tracking with heap object values.
|
|
||||||
(function() {
|
|
||||||
function V() {}
|
|
||||||
var the_value = new V();
|
|
||||||
var other_value = new V();
|
|
||||||
TestStoreToConstantFieldOfConstantObject(the_value, other_value);
|
|
||||||
})();
|
|
@ -1588,7 +1588,6 @@
|
|||||||
'regress/regress-11519': [SKIP],
|
'regress/regress-11519': [SKIP],
|
||||||
'regress/regress-4121': [SKIP],
|
'regress/regress-4121': [SKIP],
|
||||||
'packed-elements': [SKIP],
|
'packed-elements': [SKIP],
|
||||||
'const-dict-tracking': [SKIP],
|
|
||||||
'compiler/native-context-specialization-hole-check': [SKIP],
|
'compiler/native-context-specialization-hole-check': [SKIP],
|
||||||
'compiler/test-literal-map-migration': [SKIP],
|
'compiler/test-literal-map-migration': [SKIP],
|
||||||
'compiler/deopt-pretenure': [SKIP],
|
'compiler/deopt-pretenure': [SKIP],
|
||||||
@ -1612,8 +1611,6 @@
|
|||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
['third_party_heap', {
|
['third_party_heap', {
|
||||||
# Requires local heaps
|
|
||||||
'const-field-tracking': [SKIP],
|
|
||||||
# Requires --concurrent_inlining / --finalize_streaming_on_background:
|
# Requires --concurrent_inlining / --finalize_streaming_on_background:
|
||||||
'regress/regress-1220974': [SKIP],
|
'regress/regress-1220974': [SKIP],
|
||||||
'regress-1146106': [SKIP],
|
'regress-1146106': [SKIP],
|
||||||
@ -1775,7 +1772,6 @@
|
|||||||
|
|
||||||
# TODO(b/201757247):
|
# TODO(b/201757247):
|
||||||
'array-constructor-feedback': [FAIL],
|
'array-constructor-feedback': [FAIL],
|
||||||
'const-dict-tracking': [FAIL],
|
|
||||||
'compiler/deopt-pretenure': [FAIL],
|
'compiler/deopt-pretenure': [FAIL],
|
||||||
'compiler/fast-api-sequences-x64': [FAIL],
|
'compiler/fast-api-sequences-x64': [FAIL],
|
||||||
'compiler/native-context-specialization-hole-check': [FAIL],
|
'compiler/native-context-specialization-hole-check': [FAIL],
|
||||||
|
Loading…
Reference in New Issue
Block a user