diff options
Diffstat (limited to 'js/src/jit-test/tests/heap-analysis')
10 files changed, 392 insertions, 0 deletions
diff --git a/js/src/jit-test/tests/heap-analysis/bug-1249107.js b/js/src/jit-test/tests/heap-analysis/bug-1249107.js new file mode 100644 index 000000000..81792398b --- /dev/null +++ b/js/src/jit-test/tests/heap-analysis/bug-1249107.js @@ -0,0 +1 @@ +shortestPaths(this, [this], 5) diff --git a/js/src/jit-test/tests/heap-analysis/bug-1252912.js b/js/src/jit-test/tests/heap-analysis/bug-1252912.js new file mode 100644 index 000000000..1240b4545 --- /dev/null +++ b/js/src/jit-test/tests/heap-analysis/bug-1252912.js @@ -0,0 +1,6 @@ +try { + x = evalcx('') + toSource = (function() { + }) +} catch (foo) {} +shortestPaths(this, ["$4"], 5) diff --git a/js/src/jit-test/tests/heap-analysis/bug-1254105.js b/js/src/jit-test/tests/heap-analysis/bug-1254105.js new file mode 100644 index 000000000..5d6ff8ba6 --- /dev/null +++ b/js/src/jit-test/tests/heap-analysis/bug-1254105.js @@ -0,0 +1,3 @@ +// |jit-test| error:Error: Each target must be an object, string, or symbol + +shortestPaths(this, [, , , undefined], 5) diff --git a/js/src/jit-test/tests/heap-analysis/byteSize-of-object.js b/js/src/jit-test/tests/heap-analysis/byteSize-of-object.js new file mode 100644 index 000000000..3b667aa4a --- /dev/null +++ b/js/src/jit-test/tests/heap-analysis/byteSize-of-object.js @@ -0,0 +1,83 @@ +// Check that JS::ubi::Node::size returns reasonable results for objects. + +// We actually hard-code specific sizes into this test, even though they're +// implementation details, because in practice there are only two architecture +// variants to consider (32-bit and 64-bit), and if these sizes change, that's +// something SpiderMonkey hackers really want to know; they're supposed to be +// stable. + +// Run this test only if we're using jemalloc. Other malloc implementations +// exhibit surprising behaviors. For example, 32-bit Fedora builds have +// non-deterministic allocation sizes. +if (!getBuildConfiguration()['moz-memory']) + quit(0); + +if (getBuildConfiguration()['pointer-byte-size'] == 4) + var s = (s32, s64) => s32 +else + var s = (s32, s64) => s64 + +function tenure(obj) { + gc(); + return obj; +} + +// Return the byte size of |obj|, ensuring that the size is not affected by +// being tenured. (We use 'survives a GC' as an approximation for 'tenuring'.) +function tByteSize(obj) { + var size = byteSize(obj); + minorgc(); + if (size != byteSize(obj)) + return 0; + return size; +} + +assertEq(tByteSize({}), s(16, 32)); + +// Try objects with only named properties. +assertEq(tByteSize({ w: 1 }), s(32, 48)); +assertEq(tByteSize({ w: 1, x: 2 }), s(32, 48)); +assertEq(tByteSize({ w: 1, x: 2, y: 3 }), s(48, 64)); +assertEq(tByteSize({ w: 1, x: 2, y: 3, z:4 }), s(48, 64)); +assertEq(tByteSize({ w: 1, x: 2, y: 3, z:4, a: 5 }), s(80, 96)); + +// Try objects with only indexed properties. +assertEq(tByteSize({ 0:0 }), s(96, 112)); +assertEq(tByteSize({ 0:0, 1:1 }), s(96, 112)); +assertEq(tByteSize({ 0:0, 1:1, 2:2 }), s(112, 128)); +assertEq(tByteSize({ 0:0, 1:1, 2:2, 3:3 }), s(112, 128)); +assertEq(tByteSize({ 0:0, 1:1, 2:2, 3:3, 4:4 }), s(144, 160)); + +// Mix indexed and named properties, exploring each combination of the size +// classes above. +// +// Oddly, the changes here as the objects grow are not simply the sums of the +// changes above: for example, with one named property, the objects with three +// and five indexed properties are in different size classes; but with three +// named properties, there's no break there. +assertEq(tByteSize({ w:1, 0:0 }), s(96, 112)); +assertEq(tByteSize({ w:1, 0:0, 1:1, 2:2 }), s(112, 128)); +assertEq(tByteSize({ w:1, 0:0, 1:1, 2:2, 3:3, 4:4 }), s(144, 160)); +assertEq(tByteSize({ w:1, x:2, y:3, 0:0 }), s(112, 128)); +assertEq(tByteSize({ w:1, x:2, y:3, 0:0, 1:1, 2:2 }), s(144, 160)); +assertEq(tByteSize({ w:1, x:2, y:3, 0:0, 1:1, 2:2, 3:3, 4:4 }), s(144, 160)); +assertEq(tByteSize({ w:1, x:2, y:3, z:4, a:6, 0:0 }), s(144, 160)); +assertEq(tByteSize({ w:1, x:2, y:3, z:4, a:6, 0:0, 1:1, 2:2 }), s(144, 160)); +assertEq(tByteSize({ w:1, x:2, y:3, z:4, a:6, 0:0, 1:1, 2:2, 3:3, 4:4 }), s(176, 192)); + +// Check various lengths of array. +assertEq(tByteSize([]), s(80, 96)); +assertEq(tByteSize([1]), s(48, 64)); +assertEq(tByteSize([1, 2]), s(48, 64)); +assertEq(tByteSize([1, 2, 3]), s(80, 96)); +assertEq(tByteSize([1, 2, 3, 4]), s(80, 96)); +assertEq(tByteSize([1, 2, 3, 4, 5]), s(80, 96)); +assertEq(tByteSize([1, 2, 3, 4, 5, 6]), s(80, 96)); +assertEq(tByteSize([1, 2, 3, 4, 5, 6, 7]), s(112, 128)); +assertEq(tByteSize([1, 2, 3, 4, 5, 6, 7, 8]), s(112, 128)); + +// Various forms of functions. +assertEq(tByteSize(function () {}), s(32, 64)); +assertEq(tByteSize(function () {}.bind()), s(48, 80)); +assertEq(tByteSize(() => 1), s(48, 80)); +assertEq(tByteSize(Math.sin), s(32, 64)); diff --git a/js/src/jit-test/tests/heap-analysis/byteSize-of-scripts.js b/js/src/jit-test/tests/heap-analysis/byteSize-of-scripts.js new file mode 100644 index 000000000..11923b35b --- /dev/null +++ b/js/src/jit-test/tests/heap-analysis/byteSize-of-scripts.js @@ -0,0 +1,46 @@ +// Check JS::ubi::Node::size results for scripts. We don't attempt to check +// exact sizes in this test (deemed to difficult and non-deterministic), just +// some sanity checks. + +function f1() { + return 42; +} + +print("byteSizeOfScript(f1) = " + byteSizeOfScript(f1)); +assertEq(byteSizeOfScript(f1) > 1, true); + +function f2(n) { + var obj = { + x: 1, + y: 2, + z: 3, + }; + + if (i % 2 == 0) { + for (var i = 0; i < n; i++) { + this.x += i; + print(uneval(i)); + obj[i] = i * i; + if (i > 10) { + f2(i / f1()); + } + } + } + + if (i % 3 == 0) { + for (var i = 0; i < n; i++) { + this.x *= i; + print(uneval(i)); + obj[i] = i * i; + if (i > 10) { + f2(i / f1()); + } + } + } + + return this.x; +} + +print("byteSizeOfScript(f2) = " + byteSizeOfScript(f2)); +assertEq(byteSizeOfScript(f2) > 1, true); +assertEq(byteSizeOfScript(f2) > byteSizeOfScript(f1), true); diff --git a/js/src/jit-test/tests/heap-analysis/byteSize-of-string.js b/js/src/jit-test/tests/heap-analysis/byteSize-of-string.js new file mode 100644 index 000000000..251d30d86 --- /dev/null +++ b/js/src/jit-test/tests/heap-analysis/byteSize-of-string.js @@ -0,0 +1,133 @@ +// Check JS::ubi::Node::size results for strings. + +// We actually hard-code specific sizes into this test, even though they're +// implementation details, because in practice there are only two architecture +// variants to consider (32-bit and 64-bit), and if these sizes change, that's +// something SpiderMonkey hackers really want to know; they're supposed to be +// stable. + +// Run this test only if we're using jemalloc. Other malloc implementations +// exhibit surprising behaviors. For example, 32-bit Fedora builds have +// non-deterministic allocation sizes. +var config = getBuildConfiguration(); +if (!config['moz-memory']) + quit(0); + +if (config['pointer-byte-size'] == 4) + var s = (s32, s64) => s32 +else + var s = (s32, s64) => s64 + +// Return the byte size of |obj|, ensuring that the size is not affected by +// being tenured. (We use 'survives a GC' as an approximation for 'tenuring'.) +function tByteSize(obj) { + var nurserySize = byteSize(obj); + minorgc(); + var tenuredSize = byteSize(obj); + if (nurserySize != tenuredSize) { + print("nursery size: " + nurserySize + " tenured size: " + tenuredSize); + return -1; // make the stack trace point at the real test + } + + return tenuredSize; +} + +// There are four representations of flat strings, with the following capacities +// (excluding a terminating null character): +// +// 32-bit 64-bit test +// representation Latin-1 char16_t Latin-1 char16_t label +// ======================================================================== +// JSExternalString (cannot be tested in shell) - +// JSThinInlineString 7 3 15 7 T +// JSFatInlineString 23 11 23 11 F +// JSExtensibleString - limited by available memory - X +// JSUndependedString - same as JSExtensibleString - + +// Note that atoms are 8 bytes larger than non-atoms, to store the atom's hash code. + +// Latin-1 +assertEq(tByteSize(""), s(24, 32)); // T, T +assertEq(tByteSize("1"), s(24, 32)); // T, T +assertEq(tByteSize("1234567"), s(24, 32)); // T, T +assertEq(tByteSize("12345678"), s(40, 32)); // F, T +assertEq(tByteSize("123456789.12345"), s(40, 32)); // F, T +assertEq(tByteSize("123456789.123456"), s(40, 40)); // F, F +assertEq(tByteSize("123456789.123456789.123"), s(40, 40)); // F, F +assertEq(tByteSize("123456789.123456789.1234"), s(56, 64)); // X, X +assertEq(tByteSize("123456789.123456789.123456789.1"), s(56, 64)); // X, X +assertEq(tByteSize("123456789.123456789.123456789.12"), s(72, 80)); // X, X + +// Inline char16_t atoms. +// "Impassionate gods have never seen the red that is the Tatsuta River." +// - Ariwara no Narihira +assertEq(tByteSize("千"), s(24, 32)); // T, T +assertEq(tByteSize("千早"), s(24, 32)); // T, T +assertEq(tByteSize("千早ぶ"), s(24, 32)); // T, T +assertEq(tByteSize("千早ぶる"), s(40, 32)); // F, T +assertEq(tByteSize("千早ぶる神"), s(40, 32)); // F, T +assertEq(tByteSize("千早ぶる神代"), s(40, 32)); // F, T +assertEq(tByteSize("千早ぶる神代も"), s(40, 32)); // F, T +assertEq(tByteSize("千早ぶる神代もき"), s(40, 40)); // F, F +assertEq(tByteSize("千早ぶる神代もきかず龍"), s(40, 40)); // F, F +assertEq(tByteSize("千早ぶる神代もきかず龍田"), s(56, 64)); // X, X +assertEq(tByteSize("千早ぶる神代もきかず龍田川 か"), s(56, 64)); // X, X +assertEq(tByteSize("千早ぶる神代もきかず龍田川 から"), s(72, 80)); // X, X +assertEq(tByteSize("千早ぶる神代もきかず龍田川 からくれなゐに水く"), s(72, 80)); // X, X +assertEq(tByteSize("千早ぶる神代もきかず龍田川 からくれなゐに水くく"), s(88, 96)); // X, X +assertEq(tByteSize("千早ぶる神代もきかず龍田川 からくれなゐに水くくるとは"), s(88, 96)); // X, X + +// A Latin-1 rope. This changes size when flattened. +// "In a village of La Mancha, the name of which I have no desire to call to mind" +// - Miguel de Cervantes, Don Quixote +var fragment8 = "En un lugar de la Mancha, de cuyo nombre no quiero acordarme"; // 60 characters +var rope8 = fragment8; +for (var i = 0; i < 10; i++) // 1024 repetitions + rope8 = rope8 + rope8; +assertEq(tByteSize(rope8), s(16, 24)); +var matches8 = rope8.match(/(de cuyo nombre no quiero acordarme)/); +assertEq(tByteSize(rope8), s(16 + 65536, 24 + 65536)); + +// Test extensible strings. +// +// Appending another copy of the fragment should yield another rope. +// +// Flatting that should turn the original rope into a dependent string, and +// yield a new linear string, of the some size as the original. +rope8a = rope8 + fragment8; +assertEq(tByteSize(rope8a), s(16, 24)); +rope8a.match(/x/, function() { assertEq(true, false); }); +assertEq(tByteSize(rope8a), s(16 + 65536, 24 + 65536)); +assertEq(tByteSize(rope8), s(16, 24)); + + +// A char16_t rope. This changes size when flattened. +// "From the Heliconian Muses let us begin to sing" +// --- Hesiod, Theogony +var fragment16 = "μουσάων Ἑλικωνιάδων ἀρχώμεθ᾽ ἀείδειν"; +var rope16 = fragment16; +for (var i = 0; i < 10; i++) // 1024 repetitions + rope16 = rope16 + rope16; +assertEq(tByteSize(rope16), s(16, 24)); +let matches16 = rope16.match(/(Ἑλικωνιάδων ἀρχώμεθ᾽)/); +assertEq(tByteSize(rope16), s(16 + 131072, 24 + 131072)); + +// Latin-1 and char16_t dependent strings. +assertEq(tByteSize(rope8.substr(1000, 2000)), s(16, 24)); +assertEq(tByteSize(rope16.substr(1000, 2000)), s(16, 24)); +assertEq(tByteSize(matches8[0]), s(16, 24)); +assertEq(tByteSize(matches8[1]), s(16, 24)); +assertEq(tByteSize(matches16[0]), s(16, 24)); +assertEq(tByteSize(matches16[1]), s(16, 24)); + +// Test extensible strings. +// +// Appending another copy of the fragment should yield another rope. +// +// Flatting that should turn the original rope into a dependent string, and +// yield a new linear string, of the some size as the original. +rope16a = rope16 + fragment16; +assertEq(tByteSize(rope16a), s(16, 24)); +rope16a.match(/x/, function() { assertEq(true, false); }); +assertEq(tByteSize(rope16a), s(16 + 131072, 24 + 131072)); +assertEq(tByteSize(rope16), s(16, 24)); diff --git a/js/src/jit-test/tests/heap-analysis/byteSize-of-symbol.js b/js/src/jit-test/tests/heap-analysis/byteSize-of-symbol.js new file mode 100644 index 000000000..4a98bd424 --- /dev/null +++ b/js/src/jit-test/tests/heap-analysis/byteSize-of-symbol.js @@ -0,0 +1,25 @@ +// Check JS::ubi::Node::size results for symbols. + +// We actually hard-code specific sizes into this test, even though they're +// implementation details, because in practice there are only two architecture +// variants to consider (32-bit and 64-bit), and if these sizes change, that's +// something SpiderMonkey hackers really want to know; they're supposed to be +// stable. + +// Run this test only if we're using jemalloc. Other malloc implementations +// exhibit surprising behaviors. For example, 32-bit Fedora builds have +// non-deterministic allocation sizes. +var config = getBuildConfiguration(); +if (!config['moz-memory']) + quit(0); + +const SIZE_OF_SYMBOL = config['pointer-byte-size'] == 4 ? 16 : 24; + +// Without a description. +assertEq(byteSize(Symbol()), SIZE_OF_SYMBOL); + +// With a description. +assertEq(byteSize(Symbol("This is a relatively long description to be passed to " + + "Symbol() but it doesn't matter because it just gets " + + "interned as a JSAtom* anyways.")), + SIZE_OF_SYMBOL); diff --git a/js/src/jit-test/tests/heap-analysis/findPath.js b/js/src/jit-test/tests/heap-analysis/findPath.js new file mode 100644 index 000000000..7cff55dad --- /dev/null +++ b/js/src/jit-test/tests/heap-analysis/findPath.js @@ -0,0 +1,48 @@ +load(libdir + "match.js") + +// At the moment, findPath just returns the names as provided by ubi::Node, +// which just uses js::TraceChildren for now. However, we have various plans +// to improve the quality of ubi::Node's metadata, to improve the precision +// and clarity of the results here. + +var o = { w: { x: { y: { z: {} } } } }; +Match.Pattern([{node: {}, edge: "w"}, + {node: {}, edge: "x"}, + {node: {}, edge: "y"}, + {node: {}, edge: "z"}]) + .assert(findPath(o, o.w.x.y.z)); +print(uneval(findPath(o, o.w.x.y.z))); + +var a = [ , o ]; +Match.Pattern([{node: {}, edge: "objectElements[1]"}]) + .assert(findPath(a, o)); +print(uneval(findPath(a, o))); + +function C() {} +C.prototype.obj = {}; +var c = new C; +Match.Pattern([{node: {}, edge: "group"}, + {node: Match.Pattern.ANY, edge: "group_proto"}, + {node: { constructor: Match.Pattern.ANY }, edge: "obj"}]) + .assert(findPath(c, c.obj)); +print(uneval(findPath(c, c.obj))); + +function f(x) { return function g(y) { return x+y; }; } +var o = {} +var gc = f(o); +Match.Pattern([{node: gc, edge: "fun_environment"}, + {node: Match.Pattern.ANY, edge: "x"}]) + .assert(findPath(gc, o)); +print(uneval(findPath(gc, o))); + +Match.Pattern([{node: {}, edge: "group"}, + {node: Match.Pattern.ANY, edge: "group_global"}, + {node: {}, edge: "o"}]) + .assert(findPath(o, o)); +print(findPath(o, o).map((e) => e.edge).toString()); + +// Check that we can generate ubi::Nodes for Symbols. +var so = { sym: Symbol() }; +Match.Pattern([{node: {}, edge: "sym" }]) + .assert(findPath(so, so.sym)); +print(findPath(so, so.sym).map((e) => e.edge).toString()); diff --git a/js/src/jit-test/tests/heap-analysis/pointerByteSize.js b/js/src/jit-test/tests/heap-analysis/pointerByteSize.js new file mode 100644 index 000000000..617972deb --- /dev/null +++ b/js/src/jit-test/tests/heap-analysis/pointerByteSize.js @@ -0,0 +1,3 @@ +// Try out the pointerByteSize shell function. +var size = getBuildConfiguration()["pointer-byte-size"]; +assertEq(size == 4 || size == 8, true); diff --git a/js/src/jit-test/tests/heap-analysis/shortestPaths.js b/js/src/jit-test/tests/heap-analysis/shortestPaths.js new file mode 100644 index 000000000..d0c050c29 --- /dev/null +++ b/js/src/jit-test/tests/heap-analysis/shortestPaths.js @@ -0,0 +1,44 @@ +// The shortestPaths function exists solely to let the fuzzers go to town and +// exercise the code paths it calls into, hence there is nothing to assert here. +// +// The actual behavior of JS::ubi::ShortestPaths is tested in +// js/src/jsapi-tests/testUbiNode.cpp, where we can actually control the +// structure of the heap graph to test specific shapes. + +function f(x) { + return x + x; +} + +var g = f.bind(null, 5); + +var o = { + p: g +}; + +function dumpPaths(results) { + results = results.map(paths => { + return paths.map(path => { + return path.map(part => { + return { + predecessor: Object.prototype.toString.call(part.predecessor), + edge: part.edge + }; + }); + }); + }); + print(JSON.stringify(results, null, 2)); +} + +print("shortestPaths(this, [Object, f, o.p], 5)"); +var paths = shortestPaths(this, [Object, f, o.p], 5); +dumpPaths(paths); + +print(); +print("shortestPaths(o, [f], 1)") +paths = shortestPaths(o, [f], 1); +dumpPaths(paths); + +print(); +print("shortestPaths(this, [f], 5)") +paths = shortestPaths(this, [f], 5); +dumpPaths(paths); |