diff --git a/js/public/UbiNode.h b/js/public/UbiNode.h index 604404c6a38..2c97157f7ce 100644 --- a/js/public/UbiNode.h +++ b/js/public/UbiNode.h @@ -567,7 +567,6 @@ class TracerConcreteWithCompartment : public TracerConcrete { // Define specializations for some commonly-used public JSAPI types. // These can use the generic templates above. -template<> struct Concrete : TracerConcrete { }; template<> struct Concrete : TracerConcrete { }; template<> struct Concrete : TracerConcreteWithCompartment { }; @@ -586,6 +585,17 @@ class Concrete : public TracerConcreteWithCompartment { } }; +// For JSString, we extend the generic template with a 'size' implementation. +template<> struct Concrete : TracerConcrete { + size_t size(mozilla::MallocSizeOf mallocSizeOf) const override; + + protected: + explicit Concrete(JSString *ptr) : TracerConcrete(ptr) { } + + public: + static void construct(void *storage, JSString *ptr) { new (storage) Concrete(ptr); } +}; + // The ubi::Node null pointer. Any attempt to operate on a null ubi::Node asserts. template<> class Concrete : public Base { 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 index b2a48bd0ed0..ae452decb3b 100644 --- a/js/src/jit-test/tests/heap-analysis/byteSize-of-object.js +++ b/js/src/jit-test/tests/heap-analysis/byteSize-of-object.js @@ -26,7 +26,7 @@ function tenure(obj) { // being tenured. (We use 'survives a GC' as an approximation for 'tenuring'.) function tByteSize(obj) { var size = byteSize(obj); - gc(); + minorgc(); if (size != byteSize(obj)) return 0; return size; 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 00000000000..4d342cff337 --- /dev/null +++ b/js/src/jit-test/tests/heap-analysis/byteSize-of-string.js @@ -0,0 +1,131 @@ +// 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 - + +// Latin-1 +assertEq(tByteSize(""), s(16, 24)); // T, T +assertEq(tByteSize("1"), s(16, 24)); // T, T +assertEq(tByteSize("1234567"), s(16, 24)); // T, T +assertEq(tByteSize("12345678"), s(32, 24)); // F, T +assertEq(tByteSize("123456789.12345"), s(32, 24)); // F, T +assertEq(tByteSize("123456789.123456"), s(32, 32)); // F, F +assertEq(tByteSize("123456789.123456789.123"), s(32, 32)); // F, F +assertEq(tByteSize("123456789.123456789.1234"), s(48, 56)); // X, X +assertEq(tByteSize("123456789.123456789.123456789.1"), s(48, 56)); // X, X +assertEq(tByteSize("123456789.123456789.123456789.12"), s(64, 72)); // X, X + +// Inline char16_t atoms. +// "Impassionate gods have never seen the red that is the Tatsuta River." +// - Ariwara no Narihira +assertEq(tByteSize("千"), s(16, 24)); // T, T +assertEq(tByteSize("千早"), s(16, 24)); // T, T +assertEq(tByteSize("千早ぶ"), s(16, 24)); // T, T +assertEq(tByteSize("千早ぶる"), s(32, 24)); // F, T +assertEq(tByteSize("千早ぶる神"), s(32, 24)); // F, T +assertEq(tByteSize("千早ぶる神代"), s(32, 24)); // F, T +assertEq(tByteSize("千早ぶる神代も"), s(32, 24)); // F, T +assertEq(tByteSize("千早ぶる神代もき"), s(32, 32)); // F, F +assertEq(tByteSize("千早ぶる神代もきかず龍"), s(32, 32)); // F, F +assertEq(tByteSize("千早ぶる神代もきかず龍田"), s(48, 56)); // X, X +assertEq(tByteSize("千早ぶる神代もきかず龍田川 か"), s(48, 56)); // X, X +assertEq(tByteSize("千早ぶる神代もきかず龍田川 から"), s(64, 72)); // X, X +assertEq(tByteSize("千早ぶる神代もきかず龍田川 からくれなゐに水く"), s(64, 72)); // X, X +assertEq(tByteSize("千早ぶる神代もきかず龍田川 からくれなゐに水くく"), s(80, 88)); // X, X +assertEq(tByteSize("千早ぶる神代もきかず龍田川 からくれなゐに水くくるとは"), s(80, 88)); // 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/vm/String.cpp b/js/src/vm/String.cpp index 191b7572542..95f1e125c89 100644 --- a/js/src/vm/String.cpp +++ b/js/src/vm/String.cpp @@ -13,6 +13,7 @@ #include "mozilla/TypeTraits.h" #include "gc/Marking.h" +#include "js/UbiNode.h" #include "jscntxtinlines.h" #include "jscompartmentinlines.h" @@ -66,6 +67,23 @@ JSString::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) : mallocSizeOf(flat.rawTwoByteChars()); } +size_t +JS::ubi::Concrete::size(mozilla::MallocSizeOf mallocSizeOf) const +{ + JSString &str = get(); + size_t size = str.isFatInline() ? sizeof(JSFatInlineString) : sizeof(JSString); + + // We can't use mallocSizeof on things in the nursery. At the moment, + // strings are never in the nursery, but that may change. + MOZ_ASSERT(!IsInsideNursery(&str)); + size += str.sizeOfExcludingThis(mallocSizeOf); + + return size; +} + +template<> const char16_t JS::ubi::TracerConcrete::concreteTypeName[] = + MOZ_UTF16("JSString"); + #ifdef DEBUG template diff --git a/js/src/vm/UbiNode.cpp b/js/src/vm/UbiNode.cpp index 16d2687eaaa..ee96b2e2ad2 100644 --- a/js/src/vm/UbiNode.cpp +++ b/js/src/vm/UbiNode.cpp @@ -228,8 +228,6 @@ Concrete::jsObjectClassName() const return Concrete::get().getClass()->name; } -template<> const char16_t TracerConcrete::concreteTypeName[] = - MOZ_UTF16("JSString"); template<> const char16_t TracerConcrete::concreteTypeName[] = MOZ_UTF16("JS::Symbol"); template<> const char16_t TracerConcrete::concreteTypeName[] =