diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /js/src/jit-test/tests/heap-analysis | |
parent | Initial commit. (diff) | |
download | firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/jit-test/tests/heap-analysis')
11 files changed, 667 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 0000000000..0ee1871a5c --- /dev/null +++ b/js/src/jit-test/tests/heap-analysis/bug-1249107.js @@ -0,0 +1 @@ +shortestPaths([this], {start: this, maxNumPaths: 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 0000000000..409ef59006 --- /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(["$4"], {start: this, maxNumPaths: 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 0000000000..f1bae6b152 --- /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([, , , undefined], {start: this, maxNumPaths: 5}) diff --git a/js/src/jit-test/tests/heap-analysis/byteSize-of-bigint.js b/js/src/jit-test/tests/heap-analysis/byteSize-of-bigint.js new file mode 100644 index 0000000000..8b5994469e --- /dev/null +++ b/js/src/jit-test/tests/heap-analysis/byteSize-of-bigint.js @@ -0,0 +1,122 @@ +// |jit-test| skip-if: !getBuildConfiguration()['moz-memory'] +// 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. + +// Check JS::ubi::Node::size results for BigInts. + +// 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. + +const config = getBuildConfiguration(); + +const pointerByteSize = config["pointer-byte-size"]; +assertEq(pointerByteSize === 4 || pointerByteSize === 8, true); + +const m32 = pointerByteSize === 4; + +// 32-bit: sizeof(CellWithLengthAndFlags) + 2 * sizeof(BigInt::Digit) = 8 + 2 * 4 = 16 +// 64-bit: sizeof(CellWithLengthAndFlags) + sizeof(BigInt::Digit) = 8 + 8 = 16 +const SIZE_OF_BIGINT = 16; + +// sizeof(BigInt::Digit) +const SIZE_OF_DIGIT = pointerByteSize; + +// sizeof(JS::Value) +const SIZE_OF_VALUE = 8; + +// See Nursery::bigIntHeaderSize(). +const SIZE_OF_BIGINT_HEADER = 8; + +const SIZE_OF_TENURED_BIGINT = SIZE_OF_BIGINT; +const SIZE_OF_NURSERY_BIGINT = SIZE_OF_BIGINT + SIZE_OF_BIGINT_HEADER; + +function nurseryDigitSize(length) { + // See <https://bugzilla.mozilla.org/show_bug.cgi?id=1607186> for why we currently + // overallocate on 32-bit. + if (m32) { + length += (length & 1); + } + return length * SIZE_OF_DIGIT; +} + +function mallocDigitSize(length) { + // See <https://bugzilla.mozilla.org/show_bug.cgi?id=1607186> for why we currently + // overallocate on 32-bit. + if (m32) { + length += (length & 1); + } + + // Malloc buffer sizes are always a power of two. + return 1 << Math.ceil(Math.log2(length * SIZE_OF_DIGIT)); +} + +// Constant BigInts (tenured, inline digits). +assertEq(byteSize(10n), SIZE_OF_TENURED_BIGINT); +assertEq(byteSize(0xffff_ffff_ffff_ffffn), SIZE_OF_TENURED_BIGINT); + +// Constant BigInt (tenured, heap digits). +assertEq(byteSize(0x1_0000_0000_0000_0000n), + SIZE_OF_TENURED_BIGINT + mallocDigitSize(m32 ? 3 : 2)); +assertEq(byteSize(0x1_0000_0000_0000_0000_0000_0000n), + SIZE_OF_TENURED_BIGINT + mallocDigitSize(m32 ? 4 : 2)); +assertEq(byteSize(0x1_0000_0000_0000_0000_0000_0000_0000_0000n), + SIZE_OF_TENURED_BIGINT + mallocDigitSize(m32 ? 5 : 3)); + + +/////////////////////////////////////////////////////////////////////////////// +// Nursery BigInt tests below this point. // +/////////////////////////////////////////////////////////////////////////////// + +// Hack to skip this test if BigInts are not allocated in the nursery. +{ + const sample_nursery = BigInt(123456789); + + const before = byteSize(sample_nursery); + gc(); + const after = byteSize(sample_nursery); + + let nursery_disabled = before == after; + if (nursery_disabled) { + printErr("nursery BigInts appear to be disabled"); + quit(0); + } +} + +// Convert an input BigInt, which is probably tenured because it's a literal in +// the source text, to a nursery-allocated BigInt with the same contents. +function copyBigInt(bi) { + var plusOne = bi + 1n; + return plusOne - 1n; +} + +// Return the nursery byte size of |bi|. +function nByteSize(bi) { + // BigInts that appear in the source will always be tenured. + return byteSize(copyBigInt(bi)); +} + +// BigInts (nursery, inline digits). +assertEq(nByteSize(10n), SIZE_OF_NURSERY_BIGINT); +assertEq(nByteSize(0xffff_ffff_ffff_ffffn), SIZE_OF_NURSERY_BIGINT); + +// BigInt (nursery, nursery heap digits). +// +// This assumes small nursery buffer allocations always succeed. +assertEq(nByteSize(0x1_0000_0000_0000_0000n), + SIZE_OF_NURSERY_BIGINT + nurseryDigitSize(m32 ? 3 : 2)); +assertEq(nByteSize(0x1_0000_0000_0000_0000_0000_0000n), + SIZE_OF_NURSERY_BIGINT + nurseryDigitSize(m32 ? 4 : 2)); +assertEq(nByteSize(0x1_0000_0000_0000_0000_0000_0000_0000_0000n), + SIZE_OF_NURSERY_BIGINT + nurseryDigitSize(m32 ? 5 : 3)); + +// BigInt (nursery, malloc heap digits). +// +// |Nursery::MaxNurseryBufferSize| is 1024, so when +// |BigInt::digitLength * sizeof(BigInt::Digit)| exceeds 1024, the digits buffer +// should be malloc'ed. Pick a larger number to be future-proof. +assertEq(nByteSize(2n ** (64n * 1000n)), + SIZE_OF_NURSERY_BIGINT + mallocDigitSize(m32 ? 2002 : 1001)); 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 0000000000..b7fa36bb72 --- /dev/null +++ b/js/src/jit-test/tests/heap-analysis/byteSize-of-object.js @@ -0,0 +1,82 @@ +// |jit-test| skip-if: !getBuildConfiguration()['moz-memory'] +// 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. + +// 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. + +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 0000000000..c35e07ef2d --- /dev/null +++ b/js/src/jit-test/tests/heap-analysis/byteSize-of-scripts.js @@ -0,0 +1,50 @@ +// 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++) { + (function() { + this.x += i; + print(String(i)); + obj[i] = i * i; + if (i > 10) { + f2(i / f1()); + } + })(); + } + } + + if (i % 3 == 0) { + for (var i = 0; i < n; i++) { + (function() { + this.x *= i; + print(String(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 0000000000..1c14e9ff6e --- /dev/null +++ b/js/src/jit-test/tests/heap-analysis/byteSize-of-string.js @@ -0,0 +1,252 @@ +// |jit-test| skip-if: !getBuildConfiguration()['moz-memory'] +// 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. + +// 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. + +var config = getBuildConfiguration(); + +gczeal(0); // Need to control when tenuring happens + +// Hack to skip this test if strings are not allocated in the nursery. +{ + const sample_nursery = "x" + "abc".substr(1); + let nursery_enabled = true; + const before = byteSize(sample_nursery); + gc(); + const after = byteSize(sample_nursery); + if (before == after) + nursery_enabled = false; + if (!nursery_enabled) { + printErr("nursery strings appear to be disabled"); + quit(0); + } +} + +// Ion eager runs much of this code in Ion, and Ion nursery-allocates more +// aggressively than other modes. +if (getJitCompilerOptions()["ion.warmup.trigger"] <= 100) + setJitCompilerOption("ion.warmup.trigger", 100); + +if (config['pointer-byte-size'] == 4) + var s = (s32, s64) => s32 +else + var s = (s32, s64) => s64 + +// Convert an input string, which is probably an atom because it's a literal in +// the source text, to a nursery-allocated string with the same contents. +function copyString(str) { + if (str.length == 0) + return str; // Nothing we can do here + return ensureLinearString(str.substr(0, 1) + str.substr(1)); +} + +// Return the nursery byte size of |str|. +function nByteSize(str) { + // Strings that appear in the source will always be atomized and therefore + // will never be in the nursery. + return byteSize(copyString(str)); +} + +// Return the tenured byte size of |str|. +function tByteSize(str) { + // Strings that appear in the source will always be atomized and therefore + // will never be in the nursery. But we'll make them get tenured instead of + // using the atom. + str = copyString(str); + minorgc(); + return byteSize(str); +} + +// There are four representations of linear strings, with the following +// capacities: +// +// 32-bit 64-bit test +// representation Latin-1 char16_t Latin-1 char16_t label +// ======================================================================== +// JSExternalString - limited by MaxStringLength - E +// JSThinInlineString 8 4 16 8 T +// JSFatInlineString 24 12 24 12 F +// JSExtensibleString - limited by MaxStringLength - X + +// Notes: +// - labels are suffixed with A for atoms and N for non-atoms +// - atoms are 8 bytes larger than non-atoms, to store the atom's hash code. +// - Nursery-allocated strings require a header that stores the zone. + +// Expected sizes based on type of string +const m32 = (config['pointer-byte-size'] == 4); +const TA = m32 ? 24 : 32; // ThinInlineString atom, includes a hash value +const TN = m32 ? 16 : 24; // ThinInlineString +const FN = m32 ? 32 : 32; // FatInlineString +const XN = m32 ? 16 : 24; // ExtensibleString, has additional storage buffer +const RN = m32 ? 16 : 24; // Rope +const DN = m32 ? 16 : 24; // DependentString +const EN = m32 ? 16 : 24; // ExternalString + +// A function that pads out a tenured size to the nursery size. We store a zone +// pointer in the nursery just before the string (4 bytes on 32-bit, 8 bytes on +// 64-bit), and the string struct itself must be 8-byte aligned (resulting in +// +4 bytes on 32-bit, +0 bytes on 64-bit). The end result? Nursery strings are +// 8 bytes larger. +const Nursery = m32 ? s => s + 4 + 4 : s => s + 8 + 0; + +// Latin-1 +assertEq(tByteSize(""), s(TA, TA)); +assertEq(tByteSize("1"), s(TA, TA)); +assertEq(tByteSize("1234567"), s(TN, TN)); +assertEq(tByteSize("12345678"), s(TN, TN)); +assertEq(tByteSize("123456789"), s(FN, TN)); +assertEq(tByteSize("123456789.12345"), s(FN, TN)); +assertEq(tByteSize("123456789.123456"), s(FN, TN)); +assertEq(tByteSize("123456789.1234567"), s(FN, FN)); +assertEq(tByteSize("123456789.123456789.123"), s(FN, FN)); +assertEq(tByteSize("123456789.123456789.1234"), s(FN, FN)); +assertEq(tByteSize("123456789.123456789.12345"), s(XN+32, XN+32)); +assertEq(tByteSize("123456789.123456789.123456789.1"), s(XN+32, XN+32)); +assertEq(tByteSize("123456789.123456789.123456789.12"), s(XN+32, XN+32)); +assertEq(tByteSize("123456789.123456789.123456789.123"), s(XN+64, XN+64)); + +assertEq(nByteSize(""), s(TA, TA)); +assertEq(nByteSize("1"), s(TA, TA)); +assertEq(nByteSize("1234567"), s(Nursery(TN), Nursery(TN))); +assertEq(nByteSize("12345678"), s(Nursery(TN), Nursery(TN))); +assertEq(nByteSize("123456789"), s(Nursery(FN), Nursery(TN))); +assertEq(nByteSize("123456789.12345"), s(Nursery(FN), Nursery(TN))); +assertEq(nByteSize("123456789.123456"), s(Nursery(FN), Nursery(TN))); +assertEq(nByteSize("123456789.1234567"), s(Nursery(FN), Nursery(FN))); +assertEq(nByteSize("123456789.123456789.123"), s(Nursery(FN), Nursery(FN))); +assertEq(nByteSize("123456789.123456789.1234"), s(Nursery(FN), Nursery(FN))); +assertEq(nByteSize("123456789.123456789.12345"), s(Nursery(XN)+32,Nursery(XN)+32)); +assertEq(nByteSize("123456789.123456789.123456789.1"), s(Nursery(XN)+32,Nursery(XN)+32)); +assertEq(nByteSize("123456789.123456789.123456789.12"), s(Nursery(XN)+32,Nursery(XN)+32)); +assertEq(nByteSize("123456789.123456789.123456789.123"), s(Nursery(XN)+64,Nursery(XN)+64)); + +// Inline char16_t atoms. +// "Impassionate gods have never seen the red that is the Tatsuta River." +// - Ariwara no Narihira +assertEq(tByteSize("千"), s(TA, TA)); +assertEq(tByteSize("千早"), s(TN, TN)); +assertEq(tByteSize("千早ぶ"), s(TN, TN)); +assertEq(tByteSize("千早ぶる"), s(TN, TN)); +assertEq(tByteSize("千早ぶる神"), s(FN, TN)); +assertEq(tByteSize("千早ぶる神代"), s(FN, TN)); +assertEq(tByteSize("千早ぶる神代も"), s(FN, TN)); +assertEq(tByteSize("千早ぶる神代もき"), s(FN, TN)); +assertEq(tByteSize("千早ぶる神代もきか"), s(FN, FN)); +assertEq(tByteSize("千早ぶる神代もきかず龍"), s(FN, FN)); +assertEq(tByteSize("千早ぶる神代もきかず龍田"), s(FN, FN)); +assertEq(tByteSize("千早ぶる神代もきかず龍田川"), s(XN+32, XN+32)); +assertEq(tByteSize("千早ぶる神代もきかず龍田川 か"), s(XN+32, XN+32)); +assertEq(tByteSize("千早ぶる神代もきかず龍田川 から"), s(XN+32, XN+32)); +assertEq(tByteSize("千早ぶる神代もきかず龍田川 からく"), s(XN+64, XN+64)); +assertEq(tByteSize("千早ぶる神代もきかず龍田川 からくれなゐに水く"), s(XN+64, XN+64)); +assertEq(tByteSize("千早ぶる神代もきかず龍田川 からくれなゐに水くく"), s(XN+64, XN+64)); +assertEq(tByteSize("千早ぶる神代もきかず龍田川 からくれなゐに水くくるとは"), s(XN+64, XN+64)); + +assertEq(nByteSize("千"), s(TA, TA)); +assertEq(nByteSize("千早"), s(Nursery(TN), Nursery(TN))); +assertEq(nByteSize("千早ぶ"), s(Nursery(TN), Nursery(TN))); +assertEq(nByteSize("千早ぶる"), s(Nursery(TN), Nursery(TN))); +assertEq(nByteSize("千早ぶる神"), s(Nursery(FN), Nursery(TN))); +assertEq(nByteSize("千早ぶる神代"), s(Nursery(FN), Nursery(TN))); +assertEq(nByteSize("千早ぶる神代も"), s(Nursery(FN), Nursery(TN))); +assertEq(nByteSize("千早ぶる神代もき"), s(Nursery(FN), Nursery(TN))); +assertEq(nByteSize("千早ぶる神代もきか"), s(Nursery(FN), Nursery(FN))); +assertEq(nByteSize("千早ぶる神代もきかず龍"), s(Nursery(FN), Nursery(FN))); +assertEq(nByteSize("千早ぶる神代もきかず龍田"), s(Nursery(FN), Nursery(FN))); +assertEq(nByteSize("千早ぶる神代もきかず龍田川"), s(Nursery(XN)+32, Nursery(XN)+32)); +assertEq(nByteSize("千早ぶる神代もきかず龍田川 か"), s(Nursery(XN)+32, Nursery(XN)+32)); +assertEq(nByteSize("千早ぶる神代もきかず龍田川 から"), s(Nursery(XN)+32, Nursery(XN)+32)); +assertEq(nByteSize("千早ぶる神代もきかず龍田川 からく"), s(Nursery(XN)+64, Nursery(XN)+64)); +assertEq(nByteSize("千早ぶる神代もきかず龍田川 からくれなゐに水く"), s(Nursery(XN)+64, Nursery(XN)+64)); +assertEq(nByteSize("千早ぶる神代もきかず龍田川 からくれなゐに水くく"), s(Nursery(XN)+64, Nursery(XN)+64)); +assertEq(nByteSize("千早ぶる神代もきかず龍田川 からくれなゐに水くくるとは"), s(Nursery(XN)+64, Nursery(XN)+64)); + +// 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(byteSize(rope8), s(Nursery(RN), Nursery(RN))); +minorgc(); +assertEq(byteSize(rope8), s(RN, RN)); +var matches8 = rope8.match(/(de cuyo nombre no quiero acordarme)/); +assertEq(byteSize(rope8), s(XN + 65536, XN + 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 same size as the original. +rope8a = rope8 + fragment8; +assertEq(byteSize(rope8a), s(Nursery(RN), Nursery(RN))); +rope8a.match(/x/, function() { assertEq(true, false); }); +assertEq(byteSize(rope8a), s(Nursery(XN) + 65536, Nursery(XN) + 65536)); +assertEq(byteSize(rope8), s(RN, RN)); + + +// 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(byteSize(rope16), s(Nursery(RN), Nursery(RN))); +let matches16 = rope16.match(/(Ἑλικωνιάδων ἀρχώμεθ᾽)/); +assertEq(byteSize(rope16), s(Nursery(RN) + 131072, Nursery(RN) + 131072)); + +// Latin-1 and char16_t dependent strings. +assertEq(byteSize(rope8.substr(1000, 2000)), s(Nursery(DN), Nursery(DN))); +assertEq(byteSize(rope16.substr(1000, 2000)), s(Nursery(DN), Nursery(DN))); +assertEq(byteSize(matches8[0]), s(Nursery(DN), Nursery(DN))); +assertEq(byteSize(matches8[1]), s(Nursery(DN), Nursery(DN))); +assertEq(byteSize(matches16[0]), s(Nursery(DN), Nursery(DN))); +assertEq(byteSize(matches16[1]), s(Nursery(DN), Nursery(DN))); + +// 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(byteSize(rope16a), s(Nursery(RN), Nursery(RN))); +rope16a.match(/x/, function() { assertEq(true, false); }); +assertEq(byteSize(rope16a), s(Nursery(XN) + 131072, Nursery(XN) + 131072)); +assertEq(byteSize(rope16), s(Nursery(XN), Nursery(XN))); + +// Test external strings. +// +// We only support char16_t external strings and external strings are never +// allocated in the nursery. If this ever changes, please add tests for the new +// cases. Also note that on Windows mozmalloc's smallest allocation size is +// two words compared to one word on other platforms. +if (config['windows']) { + assertEq(byteSize(newString("", {external: true})), s(EN+8, EN+16)); + assertEq(byteSize(newString("1", {external: true})), s(EN+8, EN+16)); + assertEq(byteSize(newString("12", {external: true})), s(EN+8, EN+16)); + assertEq(byteSize(newString("123", {external: true})), s(EN+8, EN+16)); + assertEq(byteSize(newString("1234", {external: true})), s(EN+8, EN+16)); +} else { + assertEq(byteSize(newString("", {external: true})), s(EN+4, EN+8)); + assertEq(byteSize(newString("1", {external: true})), s(EN+4, EN+8)); + assertEq(byteSize(newString("12", {external: true})), s(EN+4, EN+8)); + assertEq(byteSize(newString("123", {external: true})), s(EN+8, EN+8)); + assertEq(byteSize(newString("1234", {external: true})), s(EN+8, EN+8)); +} +assertEq(byteSize(newString("12345", {external: true})), s(EN+16, EN+16)); +assertEq(byteSize(newString("123456789.123456789.1234", {external: true})), s(EN+48, EN+48)); +assertEq(byteSize(newString("123456789.123456789.12345", {external: true})), s(EN+64, EN+64)); 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 0000000000..a90f723e36 --- /dev/null +++ b/js/src/jit-test/tests/heap-analysis/byteSize-of-symbol.js @@ -0,0 +1,25 @@ +// |jit-test| skip-if: !getBuildConfiguration()['moz-memory'] +// 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. + +// 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. + +var config = getBuildConfiguration(); + +const SIZE_OF_SYMBOL = config['pointer-byte-size'] == 4 ? 16 : 16; + +// 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 0000000000..b453e06034 --- /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(JSON.stringify(findPath(o, o.w.x.y.z))); + +var a = [ , o ]; +Match.Pattern([{node: {}, edge: "objectElements[1]"}]) + .assert(findPath(a, o)); +print(JSON.stringify(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(JSON.stringify(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(JSON.stringify(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 0000000000..617972deb0 --- /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 0000000000..e4cd3faced --- /dev/null +++ b/js/src/jit-test/tests/heap-analysis/shortestPaths.js @@ -0,0 +1,75 @@ +// The shortestPaths function exists solely to let the fuzzers go to town and +// exercise the code paths it calls into, hence the only things to assert +// relate to the shortestPaths test function API. +// +// 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 describe(v) { + return v === undefined ? "(undefined)" + : v === null ? "(null)" + : typeof(v) === "object" ? Object.prototype.toString.call(v) + : typeof(v); +} + +function dumpPaths(results) { + results = results.map(paths => + paths.map(path => + path.map(({predecessor, edge}) => + predecessor !== undefined ? + { predecessor: describe(predecessor), edge } + : + { edge } + ) + ) + ); + print(JSON.stringify(results, null, 2)); +} + +print("shortestPaths([Object, f, o.p], {start: this, maxNumPaths: 5})"); +var paths = shortestPaths([Object, f, o.p], {start: this, maxNumPaths: 5}); +dumpPaths(paths); + +print(); +print("shortestPaths([f], {start: o, maxNumPaths: 1})") +paths = shortestPaths([f], {start: o, maxNumPaths: 1}); +dumpPaths(paths); + +print(); +print("shortestPaths([f], {start: this, maxNumPaths: 5})") +paths = shortestPaths([f], {start: this, maxNumPaths: 5}); +dumpPaths(paths); + +print(); +print("shortestPaths([f], {maxNumPaths: 5})") +paths = shortestPaths([f], {maxNumPaths: 5}); +dumpPaths(paths); +assertEq(paths[0].length <= 5, true, "Too many paths reported"); + +paths = shortestPaths([f, g]); +assertEq(paths.length, 2, "Two sets of paths expected"); + +paths = shortestPaths([f], {maxNumPaths: 1}); +assertEq(paths[0].length, 1, "Single path expected"); + +var exc; + +try { paths = shortestPaths(); } catch (exc) { e = ""+exc; }; +assertEq(e.includes("TypeError") && e.includes("1 argument required"), true); + +try { paths = shortestPaths(100, {}); } catch (exc) { e = ""+exc; }; +assertEq(e, "TypeError: 100 is not an array object"); + +try { paths = shortestPaths([f], {start: 200}); } catch (exc) { e = ""+exc; }; +assertEq(e, "TypeError: 200 is not a GC thing"); |