summaryrefslogtreecommitdiffstats
path: root/js/src/jit-test/tests/heap-analysis
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /js/src/jit-test/tests/heap-analysis
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/jit-test/tests/heap-analysis')
-rw-r--r--js/src/jit-test/tests/heap-analysis/bug-1249107.js1
-rw-r--r--js/src/jit-test/tests/heap-analysis/bug-1252912.js6
-rw-r--r--js/src/jit-test/tests/heap-analysis/bug-1254105.js3
-rw-r--r--js/src/jit-test/tests/heap-analysis/byteSize-of-bigint.js120
-rw-r--r--js/src/jit-test/tests/heap-analysis/byteSize-of-object.js77
-rw-r--r--js/src/jit-test/tests/heap-analysis/byteSize-of-scripts.js50
-rw-r--r--js/src/jit-test/tests/heap-analysis/byteSize-of-string.js259
-rw-r--r--js/src/jit-test/tests/heap-analysis/byteSize-of-symbol.js23
-rw-r--r--js/src/jit-test/tests/heap-analysis/findPath.js50
-rw-r--r--js/src/jit-test/tests/heap-analysis/pointerByteSize.js3
-rw-r--r--js/src/jit-test/tests/heap-analysis/shortestPaths.js95
11 files changed, 687 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..20addba86c
--- /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 a GC thing
+
+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..e16a3219da
--- /dev/null
+++ b/js/src/jit-test/tests/heap-analysis/byteSize-of-bigint.js
@@ -0,0 +1,120 @@
+// |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 pointerByteSize = getBuildConfiguration("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..c120d68f86
--- /dev/null
+++ b/js/src/jit-test/tests/heap-analysis/byteSize-of-object.js
@@ -0,0 +1,77 @@
+// |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
+
+// 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(48, 56));
+
+// Try objects with only named properties.
+assertEq(tByteSize({ w: 1 }), s(32, 40));
+assertEq(tByteSize({ w: 1, x: 2 }), s(32, 40));
+assertEq(tByteSize({ w: 1, x: 2, y: 3 }), s(48, 56));
+assertEq(tByteSize({ w: 1, x: 2, y: 3, z:4 }), s(48, 56));
+assertEq(tByteSize({ w: 1, x: 2, y: 3, z:4, a: 5 }), s(80, 88));
+
+// Try objects with only indexed properties.
+assertEq(tByteSize({ 0:0 }), s(80, 88));
+assertEq(tByteSize({ 0:0, 1:1 }), s(80, 88));
+assertEq(tByteSize({ 0:0, 1:1, 2:2 }), s(96, 104));
+assertEq(tByteSize({ 0:0, 1:1, 2:2, 3:3 }), s(96, 104));
+assertEq(tByteSize({ 0:0, 1:1, 2:2, 3:3, 4:4 }), s(144, 152));
+
+// 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(80, 88));
+assertEq(tByteSize({ w:1, 0:0, 1:1, 2:2 }), s(96, 104));
+assertEq(tByteSize({ w:1, 0:0, 1:1, 2:2, 3:3, 4:4 }), s(144, 152));
+assertEq(tByteSize({ w:1, x:2, y:3, 0:0 }), s(96, 104));
+assertEq(tByteSize({ w:1, x:2, y:3, 0:0, 1:1, 2:2 }), s(128, 136));
+assertEq(tByteSize({ w:1, x:2, y:3, 0:0, 1:1, 2:2, 3:3, 4:4 }), s(144, 152));
+assertEq(tByteSize({ w:1, x:2, y:3, z:4, a:6, 0:0 }), s(128, 136));
+assertEq(tByteSize({ w:1, x:2, y:3, z:4, a:6, 0:0, 1:1, 2:2 }), s(128, 136));
+assertEq(tByteSize({ w:1, x:2, y:3, z:4, a:6, 0:0, 1:1, 2:2, 3:3, 4:4 }), s(176, 184));
+
+// Check various lengths of array.
+assertEq(tByteSize([]), s(80, 88));
+assertEq(tByteSize([1]), s(48, 56));
+assertEq(tByteSize([1, 2]), s(48, 56));
+assertEq(tByteSize([1, 2, 3]), s(80, 88));
+assertEq(tByteSize([1, 2, 3, 4]), s(80, 88));
+assertEq(tByteSize([1, 2, 3, 4, 5]), s(80, 88));
+assertEq(tByteSize([1, 2, 3, 4, 5, 6]), s(80, 88));
+assertEq(tByteSize([1, 2, 3, 4, 5, 6, 7]), s(112, 120));
+assertEq(tByteSize([1, 2, 3, 4, 5, 6, 7, 8]), s(112, 120));
+
+// Various forms of functions.
+assertEq(tByteSize(function () {}), s(48, 56));
+assertEq(tByteSize(function () {}.bind()), s(80, 88));
+assertEq(tByteSize(() => 1), s(48, 56));
+assertEq(tByteSize(Math.sin), s(48, 56));
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..aaac0c4f1f
--- /dev/null
+++ b/js/src/jit-test/tests/heap-analysis/byteSize-of-string.js
@@ -0,0 +1,259 @@
+// |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.
+
+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 (getBuildConfiguration("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. Note
+// that by going through the flatting process here, this also ensures that the
+// char data is always malloced and expanded by doubling, which would not be
+// the case for nursery-allocated chars (as would be produced by newString()).
+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 = (getBuildConfiguration("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 (getBuildConfiguration("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));
+
+// Nursery-allocated chars.
+//
+// byteSize will not include the space used by the nursery for the chars.
+assertEq(byteSize(newString("123456789.123456789.12345")), s(Nursery(XN)+0,Nursery(XN)+0));
+assertEq(byteSize(newString("123456789.123456789.123456789.123")), s(Nursery(XN)+0,Nursery(XN)+0));
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..3262b20993
--- /dev/null
+++ b/js/src/jit-test/tests/heap-analysis/byteSize-of-symbol.js
@@ -0,0 +1,23 @@
+// |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.
+
+const SIZE_OF_SYMBOL = getBuildConfiguration("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..c70728ee92
--- /dev/null
+++ b/js/src/jit-test/tests/heap-analysis/findPath.js
@@ -0,0 +1,50 @@
+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: "shape"},
+ {node: Match.Pattern.ANY, edge: "base"},
+ {node: Match.Pattern.ANY, edge: "baseshape_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: "**UNKNOWN SLOT 1**"},
+ {node: Match.Pattern.ANY, edge: "x"}])
+ .assert(findPath(gc, o));
+print(JSON.stringify(findPath(gc, o)));
+
+Match.Pattern([{node: {}, edge: "shape"},
+ {node: Match.Pattern.ANY, edge: "base"},
+ {node: Match.Pattern.ANY, edge: "baseshape_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..261ab96508
--- /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..cdb82ba662
--- /dev/null
+++ b/js/src/jit-test/tests/heap-analysis/shortestPaths.js
@@ -0,0 +1,95 @@
+// 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");
+
+print();
+print("shortestPaths([1234n])")
+paths = shortestPaths([1234n]);
+dumpPaths(paths);
+
+// Error messages are more generic under PBL.
+if (!getBuildConfiguration('pbl')) {
+ 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");
+
+ try { paths = shortestPaths([f, {}, {}, {}], { maxNumPaths: 0x40000000 }); } catch (exc) { e = "" + exc; };
+ assertEq(e, "out of memory");
+
+ try { paths = shortestPaths([f], { maxNumPaths: -1 }); } catch (exc) { e = "" + exc; };
+ assertEq(e, "TypeError: -1 is not greater than 0");
+
+ // Bug 1799824.
+ let arr = [{}];
+ let objWithGetter = {get start() { arr.length = 0; return {}; }};
+ try { paths = shortestPaths(arr, objWithGetter); } catch (exc) { e = ""+exc; }
+ assertEq(e, "TypeError: arr is not a dense array object with one or more elements");
+}