v8/test/mjsunit/track-fields.js

419 lines
11 KiB
JavaScript
Raw Normal View History

// Copyright 2013 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Flags: --track-fields --track-double-fields --allow-natives-syntax
[map] Support in-place field representation changes. This adds a new flag --modify-field-representation-inplace (enabled by default), which lets the runtime perform field representation changes for Smi to Tagged or for HeapObject to Tagged in-place instead of creating new maps and marking the previous map tree as deprecated. That means we create (a lot) fewer Maps and DescriptorArrays in the beginning and also need to self-heal fewer objects later (migrating off the deprecated maps). In TurboFan we just take the "field owner dependency" whenever we use the field representation, which is very similar to what we already do for the field types. That means if we change the representation of a field that we used in optimized code, we will simply deoptimize that code and have TurboFan potentially later optimize it again with the new field representation. On the Speedometer2/ElmJS-TodoMVC test, this reduces the total execution time from around 415ms to around 352ms, which corresponds to a **15%** improvement. The overall Speedometer2 score improves from around 74.1 to around 78.3 (on local runs with content_shell), corresponding to a **5.6%** improvement here. 🎉 On the CNN desktop browsing story, it seems that we reduce map space utilization/fragmentation by about 4-5%. But since we allocate a lot less (fewer Maps and DescriptorArrays) we also significantly change the GC timing, which heavily influences the results here. So take this with a grain of salt. 🤷 Note: For Double fields, this doesn't change anything, meaning they still create new maps and deprecate the previous map trees. Bug: v8:8749, v8:8865, v8:9114 Change-Id: Ibd70efcb59be982863905663dbfaa89aa5b31e14 Cq-Include-Trybots: luci.chromium.try:linux-rel,win7-rel Doc: http://bit.ly/v8-in-place-field-representation-changes Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1565891 Commit-Queue: Benedikt Meurer <bmeurer@chromium.org> Commit-Queue: Igor Sheludko <ishell@chromium.org> Auto-Submit: Benedikt Meurer <bmeurer@chromium.org> Reviewed-by: Igor Sheludko <ishell@chromium.org> Cr-Commit-Position: refs/heads/master@{#60822}
2019-04-12 13:38:34 +00:00
// Flags: --modify-field-representation-inplace
// Test transitions caused by changes to field representations.
function create_smi_object() {
var o = {};
o.x = 1;
o.y = 2;
o.z = 3;
return o;
}
var o1 = create_smi_object();
var o2 = create_smi_object();
// o1,o2 are smi, smi, smi
assertTrue(%HaveSameMap(o1, o2));
o1.y = 1.3;
// o1 is smi, double, smi
assertFalse(%HaveSameMap(o1, o2));
o2.y = 1.5;
// o2 is smi, double, smi
assertTrue(%HaveSameMap(o1, o2));
// o3 is initialized as smi, double, smi
var o3 = create_smi_object();
assertTrue(%HaveSameMap(o1, o3));
function set_large(o, v) {
o.x01 = v; o.x02 = v; o.x03 = v; o.x04 = v; o.x05 = v; o.x06 = v; o.x07 = v;
o.x08 = v; o.x09 = v; o.x10 = v; o.x11 = v; o.x12 = v; o.x13 = v; o.x14 = v;
o.x15 = v; o.x16 = v; o.x17 = v; o.x18 = v; o.x19 = v; o.x20 = v; o.x21 = v;
o.x22 = v; o.x23 = v; o.x24 = v; o.x25 = v; o.x26 = v; o.x27 = v; o.x28 = v;
o.x29 = v; o.x30 = v; o.x31 = v; o.x32 = v; o.x33 = v; o.x34 = v; o.x35 = v;
o.x36 = v; o.x37 = v; o.x38 = v; o.x39 = v; o.x40 = v; o.x41 = v; o.x42 = v;
o.y01 = v; o.y02 = v; o.y03 = v; o.y04 = v; o.y05 = v; o.y06 = v; o.y07 = v;
o.y08 = v; o.y09 = v; o.y10 = v; o.y11 = v; o.y12 = v; o.y13 = v; o.y14 = v;
o.y15 = v; o.y16 = v; o.y17 = v; o.y18 = v; o.y19 = v; o.y20 = v; o.y21 = v;
}
// Check that large object migrations work.
var o4 = {};
// All smi.
set_large(o4, 0);
assertTrue(%HasFastProperties(o4));
// All double.
set_large(o4, 1.5);
// o5 is immediately allocated with doubles.
var o5 = {};
set_large(o5, 0);
assertTrue(%HaveSameMap(o4, o5));
function create_smi_object2() {
var o = {};
o.a = 1;
o.b = 2;
o.c = 3;
return o;
}
// All smi
var o6 = create_smi_object2();
var o7 = create_smi_object2();
assertTrue(%HaveSameMap(o6, o7));
// Smi, double, smi.
o6.b = 1.5;
assertFalse(%HaveSameMap(o6, o7));
// Smi, double, object.
o7.c = {};
[map] Support in-place field representation changes. This adds a new flag --modify-field-representation-inplace (enabled by default), which lets the runtime perform field representation changes for Smi to Tagged or for HeapObject to Tagged in-place instead of creating new maps and marking the previous map tree as deprecated. That means we create (a lot) fewer Maps and DescriptorArrays in the beginning and also need to self-heal fewer objects later (migrating off the deprecated maps). In TurboFan we just take the "field owner dependency" whenever we use the field representation, which is very similar to what we already do for the field types. That means if we change the representation of a field that we used in optimized code, we will simply deoptimize that code and have TurboFan potentially later optimize it again with the new field representation. On the Speedometer2/ElmJS-TodoMVC test, this reduces the total execution time from around 415ms to around 352ms, which corresponds to a **15%** improvement. The overall Speedometer2 score improves from around 74.1 to around 78.3 (on local runs with content_shell), corresponding to a **5.6%** improvement here. 🎉 On the CNN desktop browsing story, it seems that we reduce map space utilization/fragmentation by about 4-5%. But since we allocate a lot less (fewer Maps and DescriptorArrays) we also significantly change the GC timing, which heavily influences the results here. So take this with a grain of salt. 🤷 Note: For Double fields, this doesn't change anything, meaning they still create new maps and deprecate the previous map trees. Bug: v8:8749, v8:8865, v8:9114 Change-Id: Ibd70efcb59be982863905663dbfaa89aa5b31e14 Cq-Include-Trybots: luci.chromium.try:linux-rel,win7-rel Doc: http://bit.ly/v8-in-place-field-representation-changes Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1565891 Commit-Queue: Benedikt Meurer <bmeurer@chromium.org> Commit-Queue: Igor Sheludko <ishell@chromium.org> Auto-Submit: Benedikt Meurer <bmeurer@chromium.org> Reviewed-by: Igor Sheludko <ishell@chromium.org> Cr-Commit-Position: refs/heads/master@{#60822}
2019-04-12 13:38:34 +00:00
assertTrue(%HaveSameMap(o6, o7));
// Smi, double, object.
o6.c = {};
assertTrue(%HaveSameMap(o6, o7));
function poly_load(o, b) {
var v = o.field;
if (b) {
return v + 10;
}
return o;
}
var of1 = {a:0};
of1.field = {};
var of2 = {b:0};
of2.field = 10;
%PrepareFunctionForOptimization(poly_load);
poly_load(of1, false);
poly_load(of1, false);
poly_load(of2, true);
%OptimizeFunctionOnNextCall(poly_load);
assertEquals("[object Object]10", poly_load(of1, true));
// Ensure small object literals with doubles do not share double storage.
function object_literal() { return {"a":1.5}; }
var o8 = object_literal();
var o9 = object_literal();
o8.a = 4.6
assertEquals(1.5, o9.a);
// Ensure double storage is not leaked in the case of polymorphic loads.
function load_poly(o) {
return o.a;
}
%PrepareFunctionForOptimization(load_poly);
var o10 = { "a": 1.6 };
var o11 = { "b": 1, "a": 1.7 };
load_poly(o10);
load_poly(o10);
load_poly(o11);
%OptimizeFunctionOnNextCall(load_poly);
var val = load_poly(o10);
o10.a = 19.5;
assertFalse(o10.a == val);
// Ensure polymorphic loads only go monomorphic when the representations are
// compatible.
// Check polymorphic load from double + object fields.
function load_mono(o) {
return o.a1;
}
%PrepareFunctionForOptimization(load_mono);
var object = {"x": 1};
var o10 = { "a1": 1.6 };
var o11 = { "a1": object, "b": 1 };
load_mono(o10);
load_mono(o10);
load_mono(o11);
%OptimizeFunctionOnNextCall(load_mono);
assertEquals(object, load_mono(o11));
// Check polymorphic load from smi + object fields.
function load_mono2(o) {
return o.a2;
}
%PrepareFunctionForOptimization(load_mono2);
var o12 = { "a2": 5 };
var o13 = { "a2": object, "b": 1 };
load_mono2(o12);
load_mono2(o12);
load_mono2(o13);
%OptimizeFunctionOnNextCall(load_mono2);
assertEquals(object, load_mono2(o13));
// Check polymorphic load from double + double fields.
function load_mono3(o) {
return o.a3;
}
%PrepareFunctionForOptimization(load_mono3);
var o14 = { "a3": 1.6 };
var o15 = { "a3": 1.8, "b": 1 };
load_mono3(o14);
load_mono3(o14);
load_mono3(o15);
%OptimizeFunctionOnNextCall(load_mono3);
assertEquals(1.6, load_mono3(o14));
assertEquals(1.8, load_mono3(o15));
// Check that JSON parsing respects existing representations.
var o16 = JSON.parse('{"a":1.5}');
var o17 = JSON.parse('{"a":100}');
assertTrue(%HaveSameMap(o16, o17));
var o17_a = o17.a;
assertEquals(100, o17_a);
o17.a = 200;
assertEquals(100, o17_a);
assertEquals(200, o17.a);
// Ensure normalizing results in ignored representations.
var o18 = {};
o18.field1 = 100;
o18.field2 = 1;
o18.to_delete = 100;
var o19 = {};
o19.field1 = 100;
o19.field2 = 1.6;
o19.to_delete = 100;
assertFalse(%HaveSameMap(o18, o19));
delete o18.to_delete;
delete o19.to_delete;
assertEquals(1, o18.field2);
assertEquals(1.6, o19.field2);
// Test megamorphic keyed stub behaviour in combination with representations.
var some_object20 = {"a":1};
var o20 = {};
o20.smi = 1;
o20.dbl = 1.5;
o20.obj = some_object20;
function keyed_load(o, k) {
return o[k];
}
function keyed_store(o, k, v) {
return o[k] = v;
}
var smi20 = keyed_load(o20, "smi");
var dbl20 = keyed_load(o20, "dbl");
var obj20 = keyed_load(o20, "obj");
keyed_load(o20, "smi");
keyed_load(o20, "dbl");
keyed_load(o20, "obj");
keyed_load(o20, "smi");
keyed_load(o20, "dbl");
keyed_load(o20, "obj");
assertEquals(1, smi20);
assertEquals(1.5, dbl20);
assertEquals(some_object20, obj20);
keyed_store(o20, "smi", 100);
keyed_store(o20, "dbl", 100);
keyed_store(o20, "obj", 100);
keyed_store(o20, "smi", 100);
keyed_store(o20, "dbl", 100);
keyed_store(o20, "obj", 100);
keyed_store(o20, "smi", 100);
keyed_store(o20, "dbl", 100);
keyed_store(o20, "obj", 100);
assertEquals(1, smi20);
assertEquals(1.5, dbl20);
assertEquals(some_object20, obj20);
assertEquals(100, o20.smi);
assertEquals(100, o20.dbl);
assertEquals(100, o20.dbl);
function attr_mismatch_obj(v, writable) {
var o = {};
// Assign twice to make the field non-constant.
o.some_value = 0;
o.some_value = v;
Object.defineProperty(o, "second_value", {value:10, writable:writable});
return o;
}
function is_writable(o, p) {
return Object.getOwnPropertyDescriptor(o,p).writable;
}
var writable = attr_mismatch_obj(10, true);
var non_writable1 = attr_mismatch_obj(10.5, false);
assertTrue(is_writable(writable,"second_value"));
assertFalse(is_writable(non_writable1,"second_value"));
writable.some_value = 20.5;
assertTrue(is_writable(writable,"second_value"));
var non_writable2 = attr_mismatch_obj(10.5, false);
assertTrue(%HaveSameMap(non_writable1, non_writable2));
function test_f(v) {
var o = {};
o.vbf = v;
o.func = test_f;
return o;
}
function test_fic(o) {
return o.vbf;
}
var ftest1 = test_f(10);
var ftest2 = test_f(10);
var ftest3 = test_f(10.5);
var ftest4 = test_f(10);
assertFalse(%HaveSameMap(ftest1, ftest3));
assertTrue(%HaveSameMap(ftest3, ftest4));
ftest2.func = is_writable;
test_fic(ftest1);
test_fic(ftest2);
test_fic(ftest3);
test_fic(ftest4);
assertTrue(%HaveSameMap(ftest1, ftest3));
assertTrue(%HaveSameMap(ftest3, ftest4));
// Test representations and transition conversions.
function read_first_double(o) {
return o.first_double;
}
var df1 = {};
df1.first_double=1.6;
read_first_double(df1);
read_first_double(df1);
function some_function1() { return 10; }
var df2 = {};
df2.first_double = 1.7;
df2.second_function = some_function1;
function some_function2() { return 20; }
var df3 = {};
df3.first_double = 1.7;
df3.second_function = some_function2;
df1.first_double = 10;
read_first_double(df1);
// Test boilerplates with computed values.
function none_boilerplate(a) {
return {"a_none":a};
};
%PrepareFunctionForOptimization(none_boilerplate);
%OptimizeFunctionOnNextCall(none_boilerplate);
var none_double1 = none_boilerplate(1.7);
var none_double2 = none_boilerplate(1.9);
assertTrue(%HaveSameMap(none_double1, none_double2));
assertEquals(1.7, none_double1.a_none);
assertEquals(1.9, none_double2.a_none);
none_double2.a_none = 3.5;
var none_double1 = none_boilerplate(1.7);
var none_double2 = none_boilerplate(3.5);
function none_to_smi(a) {
return {"a_smi":a};
}
%PrepareFunctionForOptimization(none_to_smi);
var none_smi1 = none_to_smi(1);
var none_smi2 = none_to_smi(2);
%OptimizeFunctionOnNextCall(none_to_smi);
var none_smi3 = none_to_smi(3);
assertTrue(%HaveSameMap(none_smi1, none_smi2));
assertTrue(%HaveSameMap(none_smi1, none_smi3));
assertEquals(1, none_smi1.a_smi);
assertEquals(2, none_smi2.a_smi);
assertEquals(3, none_smi3.a_smi);
function none_to_double(a) {
return {"a_double":a};
}
%PrepareFunctionForOptimization(none_to_double);
var none_to_double1 = none_to_double(1.5);
var none_to_double2 = none_to_double(2.8);
%OptimizeFunctionOnNextCall(none_to_double);
var none_to_double3 = none_to_double(3.7);
assertTrue(%HaveSameMap(none_to_double1, none_to_double2));
assertTrue(%HaveSameMap(none_to_double1, none_to_double3));
assertEquals(1.5, none_to_double1.a_double);
assertEquals(2.8, none_to_double2.a_double);
assertEquals(3.7, none_to_double3.a_double);
function none_to_object(a) {
return {"an_object":a};
}
%PrepareFunctionForOptimization(none_to_object);
var none_to_object1 = none_to_object(true);
var none_to_object2 = none_to_object(false);
%OptimizeFunctionOnNextCall(none_to_object);
var none_to_object3 = none_to_object(3.7);
assertTrue(%HaveSameMap(none_to_object1, none_to_object2));
assertTrue(%HaveSameMap(none_to_object1, none_to_object3));
assertEquals(true, none_to_object1.an_object);
assertEquals(false, none_to_object2.an_object);
assertEquals(3.7, none_to_object3.an_object);
function double_to_object(a) {
var o = {"d_to_h":1.8};
o.d_to_h = a;
return o;
}
var dh1 = double_to_object(true);
var dh2 = double_to_object(false);
assertTrue(%HaveSameMap(dh1, dh2));
assertEquals(true, dh1.d_to_h);
assertEquals(false, dh2.d_to_h);
function smi_to_object(a) {
var o = {"s_to_t":18};
o.s_to_t = a;
return o;
}
var st1 = smi_to_object(true);
var st2 = smi_to_object(false);
assertTrue(%HaveSameMap(st1, st2));
assertEquals(true, st1.s_to_t);
assertEquals(false, st2.s_to_t);