[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:
Toon Verwaest 2022-12-08 18:15:03 +01:00 committed by V8 LUCI CQ
parent 7bcac055b5
commit 54256360ff
10 changed files with 30 additions and 1081 deletions

View File

@ -1745,6 +1745,7 @@ Reduction JSNativeContextSpecialization::ReduceNamedAccess(
// trimming.
return NoChange();
}
values.push_back(continuation->value());
effects.push_back(continuation->effect());
controls.push_back(continuation->control());
@ -2898,9 +2899,15 @@ JSNativeContextSpecialization::BuildPropertyStore(
AccessBuilder::ForJSObjectPropertiesOrHashKnownPointer()),
storage, effect, control);
}
bool store_to_existing_constant_field = access_info.IsFastDataConstant() &&
access_mode == AccessMode::kStore &&
!access_info.HasTransitionMap();
if (access_info.IsFastDataConstant() && access_mode == AccessMode::kStore &&
!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 = {
kTaggedBase,
field_index.offset(),
@ -2953,40 +2960,11 @@ JSNativeContextSpecialization::BuildPropertyStore(
field_access.name = MaybeHandle<Name>();
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;
}
case MachineRepresentation::kTaggedSigned:
case MachineRepresentation::kTaggedPointer:
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) {
value = effect = graph()->NewNode(
simplified()->CheckSmi(FeedbackSource()), value, effect, control);

View File

@ -57,6 +57,7 @@ namespace internal {
V(OutOfBounds, "out of bounds") \
V(Overflow, "overflow") \
V(Smi, "Smi") \
V(StoreToConstant, "Storing to a constant field") \
V(SuspendGeneratorIsDead, "SuspendGenerator is in a dead branch") \
V(TransitionedToMonomorphicIC, "IC transitioned to monomorphic") \
V(TransitionedToMegamorphicIC, "IC transitioned to megamorphic") \

View File

@ -1304,24 +1304,12 @@ void AccessorAssembler::HandleStoreICHandlerCase(
GotoIf(IsSetWord32(details, kTypeAndReadOnlyMask), miss);
if (V8_DICT_PROPERTY_CONST_TRACKING_BOOL) {
GotoIf(IsPropertyDetailsConst(details), &if_constant);
GotoIf(IsPropertyDetailsConst(details), miss);
}
StoreValueByKeyIndex<PropertyDictionary>(
properties, var_name_index.value(), 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);
@ -1612,14 +1600,9 @@ void AccessorAssembler::OverwriteExistingFastDataProperty(
StoreMap(object, object_map);
StoreObjectField(object, field_offset, heap_number);
} else {
GotoIf(IsPropertyDetailsConst(details), slow);
TNode<HeapNumber> heap_number =
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);
}
Goto(&done);
@ -1630,11 +1613,7 @@ void AccessorAssembler::OverwriteExistingFastDataProperty(
if (do_transitioning_store) {
StoreMap(object, object_map);
} else {
Label if_mutable(this);
GotoIfNot(IsPropertyDetailsConst(details), &if_mutable);
TNode<Object> current_value = LoadObjectField(object, field_offset);
Branch(TaggedEqual(current_value, value), &done, slow);
BIND(&if_mutable);
GotoIf(IsPropertyDetailsConst(details), slow);
}
StoreObjectField(object, field_offset, value);
Goto(&done);
@ -1682,29 +1661,16 @@ void AccessorAssembler::OverwriteExistingFastDataProperty(
&double_rep, &tagged_rep);
BIND(&double_rep);
{
GotoIf(IsPropertyDetailsConst(details), slow);
TNode<HeapNumber> heap_number =
CAST(LoadPropertyArrayElement(properties, backing_store_index));
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);
Goto(&done);
}
BIND(&tagged_rep);
{
Label if_mutable(this);
GotoIfNot(IsPropertyDetailsConst(details), &if_mutable);
TNode<Object> current_value =
LoadPropertyArrayElement(properties, backing_store_index);
Branch(TaggedEqual(current_value, value), &done, slow);
BIND(&if_mutable);
GotoIf(IsPropertyDetailsConst(details), slow);
StorePropertyArrayElement(properties, backing_store_index, value);
Goto(&done);
}

View File

@ -1833,22 +1833,9 @@ bool MaglevGraphBuilder::TryBuildStoreField(
if (original_map.UnusedPropertyFields() == 0) {
return false;
}
} else if (access_info.IsFastDataConstant() &&
access_mode != compiler::AccessMode::kStoreInLiteral) {
// For object literals we want to just store, similar to StoreInLiteral, but
// 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;
} else if (access_info.IsFastDataConstant()) {
// TODO(verwaest): Deoptimize instead.
return false;
}
ValueNode* store_target;

View File

@ -1532,7 +1532,7 @@ Maybe<bool> JSReceiver::ValidateAndApplyPropertyDescriptor(
desc->enumerable() == current->enumerable()) &&
(!desc->has_configurable() ||
desc->configurable() == current->configurable()) &&
(!desc->has_value() || current->value().is_identical_to(desc->value())) &&
!desc->has_value() &&
(!desc->has_writable() ||
(current->has_writable() && current->writable() == desc->writable())) &&
(!desc->has_get() ||

View File

@ -375,11 +375,9 @@ void LookupIterator::PrepareForDataProperty(Handle<Object> value) {
// Check that current value matches new value otherwise we should make
// the property mutable.
if (holder->HasFastProperties(isolate_)) {
if (!IsConstFieldValueEqualTo(*value)) {
new_constness = PropertyConstness::kMutable;
}
if (!CanStayConst(*value)) new_constness = PropertyConstness::kMutable;
} else if (V8_DICT_PROPERTY_CONST_TRACKING_BOOL) {
if (!IsConstDictValueEqualTo(*value)) {
if (!DictCanStayConst(*value)) {
property_details_ =
property_details_.CopyWithConstness(PropertyConstness::kMutable);
@ -902,7 +900,7 @@ Handle<Object> LookupIterator::FetchValue(
return handle(result, isolate_);
}
bool LookupIterator::IsConstFieldValueEqualTo(Object value) const {
bool LookupIterator::CanStayConst(Object value) const {
DCHECK(!IsElement(*holder_));
DCHECK(holder_->HasFastProperties(isolate_));
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);
// Only allow exact same objects to ensure we don't need expensive
// validation in optimized code.
return current_value.IsUninitialized(isolate()) || current_value == value;
return current_value.IsUninitialized(isolate());
}
bool LookupIterator::IsConstDictValueEqualTo(Object value) const {
bool LookupIterator::DictCanStayConst(Object value) const {
DCHECK(!IsElement(*holder_));
DCHECK(!holder_->HasFastProperties(isolate_));
DCHECK(!holder_->IsJSGlobalObject());
@ -962,11 +958,7 @@ bool LookupIterator::IsConstDictValueEqualTo(Object value) const {
current_value = dict.ValueAt(dictionary_entry());
}
if (current_value.IsUninitialized(isolate()) || current_value == value) {
return true;
}
return current_value.IsNumber(isolate_) && value.IsNumber(isolate_) &&
Object::SameNumberValue(current_value.Number(), value.Number());
return current_value.IsUninitialized(isolate());
}
int LookupIterator::GetFieldDescriptorIndex() const {
@ -1053,7 +1045,7 @@ void LookupIterator::WriteDataValue(Handle<Object> value,
// equal to |value|.
DCHECK_IMPLIES(!initializing_store && property_details_.constness() ==
PropertyConstness::kConst,
IsConstFieldValueEqualTo(*value));
CanStayConst(*value));
JSObject::cast(*holder).WriteToField(descriptor_number(),
property_details_, *value);
} else {
@ -1077,7 +1069,7 @@ void LookupIterator::WriteDataValue(Handle<Object> value,
DCHECK_IMPLIES(
V8_DICT_PROPERTY_CONST_TRACKING_BOOL && !initializing_store &&
property_details_.constness() == PropertyConstness::kConst,
holder->IsJSProxy(isolate_) || IsConstDictValueEqualTo(*value));
holder->IsJSProxy(isolate_) || DictCanStayConst(*value));
if (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) {
SwissNameDictionary dictionary =

View File

@ -257,8 +257,8 @@ class V8_EXPORT_PRIVATE LookupIterator final {
void RestartInternal(InterceptorState interceptor_state);
Handle<Object> FetchValue(AllocationPolicy allocation_policy =
AllocationPolicy::kAllocationAllowed) const;
bool IsConstFieldValueEqualTo(Object value) const;
bool IsConstDictValueEqualTo(Object value) const;
bool CanStayConst(Object value) const;
bool DictCanStayConst(Object value) const;
template <bool is_element>
void ReloadPropertyInformation();

View File

@ -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);
}
})();

View File

@ -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);
})();

View File

@ -1588,7 +1588,6 @@
'regress/regress-11519': [SKIP],
'regress/regress-4121': [SKIP],
'packed-elements': [SKIP],
'const-dict-tracking': [SKIP],
'compiler/native-context-specialization-hole-check': [SKIP],
'compiler/test-literal-map-migration': [SKIP],
'compiler/deopt-pretenure': [SKIP],
@ -1612,8 +1611,6 @@
################################################################################
['third_party_heap', {
# Requires local heaps
'const-field-tracking': [SKIP],
# Requires --concurrent_inlining / --finalize_streaming_on_background:
'regress/regress-1220974': [SKIP],
'regress-1146106': [SKIP],
@ -1775,7 +1772,6 @@
# TODO(b/201757247):
'array-constructor-feedback': [FAIL],
'const-dict-tracking': [FAIL],
'compiler/deopt-pretenure': [FAIL],
'compiler/fast-api-sequences-x64': [FAIL],
'compiler/native-context-specialization-hole-check': [FAIL],