v8/test/mjsunit/const-dict-tracking.js
Jakob Gruber 1277bb5c55 [compiler] Support GetPropertyAccessInfo in a concurrent setting
Until this CL, the JSHeapBroker::GetPropertyAccessInfo (GPAI) process
was as follows:

 1. GPAI is called on the main thread (MT) during the serialization
    phase to create and cache PAIs.
 2. GPAI is called again from the background thread (BT); only cached
    PAIs from step 1 are usable.

As part of concurrent inlining, the goal is to move GPAI fully to the
background thread. This CL takes a major step in that direction by
making GPAI itself callable from the BT without resorting solely to PAIs
that were previously cached on the MT.

There are two main reasons why GPAI previously had to run on the MT:

 a) Concurrent access to Maps and other heap objects.
 b) Serialization and creation of ObjectRefs for objects discovered
    during GPAI.

This CL addresses only reason a) and leaves b) for future work. This
is done by keeping the two-pass approach, s.t. the initial call of
GPAI on the MT discovers and serializes objects. We then clear all
cached PAIs. The second call of GPAI on the BT thus runs full logic in a
concurrent setting.

Once all relevant objects (= maps and prototypes) no longer require
MT-serialization, reason b) is also addressed and the first pass can be
removed.

The new logic is implemented behind the runtime flag
--turbo-concurrent-get-property-access-info (default true), intended
to be removed in the future.

Bug: v8:7790
Change-Id: Idbdbfe091d7316529246a686bb6d71c2a0f06f8b
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2817793
Commit-Queue: Jakob Gruber <jgruber@chromium.org>
Auto-Submit: Jakob Gruber <jgruber@chromium.org>
Reviewed-by: Igor Sheludko <ishell@chromium.org>
Reviewed-by: Georg Neis <neis@chromium.org>
Cr-Commit-Position: refs/heads/master@{#74120}
2021-04-22 10:59:01 +00:00

734 lines
18 KiB
JavaScript

// 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 --opt --no-always-opt
// Flags: --no-stress-flush-bytecode
// Flags: --block-concurrent-recompilation
// Flags: --no-turbo-concurrent-get-property-access-info
//
// 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));
%OptimizeFunctionOnNextCall(read_length, "concurrent");
assertEquals(1, read_length(o));
assertUnoptimized(read_length, "no sync");
var other_proto1 = [];
Object.setPrototypeOf(proto2, other_proto1);
%UnblockConcurrentRecompilation();
assertUnoptimized(read_length, "sync");
assertEquals(0, read_length(o));
if (%IsDictPropertyConstTrackingEnabled()) {
assertFalse(%HasFastProperties(proto1));
assertFalse(%HasFastProperties(proto2));
assertFalse(%HasFastProperties(other_proto1));
assertUnoptimized(read_length);
}
})();