Bug 1395509 - Track malloc memory associated with strings r=jandem
authorJon Coppeard <jcoppeard@mozilla.com>
Thu, 09 May 2019 16:23:52 +0100
changeset 532260 01140845ccccf650829cffd7c4631732204599f4
parent 532259 54227b6122129de93a5348d3ff6300acd04f80ae
child 532261 2b0e7375950d4baa9882c688ff914b1cb7830c00
push id11265
push userffxbld-merge
push dateMon, 13 May 2019 10:53:39 +0000
treeherdermozilla-beta@77e0fe8dbdd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem
bugs1395509
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1395509 - Track malloc memory associated with strings r=jandem This adds memory tracking for string contents while leaving the current scheme in place for the time being. Differential Revision: https://phabricator.services.mozilla.com/D30517
js/src/gc/Marking.cpp
js/src/gc/Scheduling.h
js/src/vm/StringType-inl.h
js/src/vm/StringType.cpp
js/src/vm/StringType.h
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -3131,16 +3131,21 @@ size_t js::TenuringTracer::moveStringToT
 
   if (!src->isInline() && src->isLinear()) {
     if (src->isUndepended() || !src->hasBase()) {
       void* chars = src->asLinear().nonInlineCharsRaw();
       nursery().removeMallocedBuffer(chars);
     }
   }
 
+  if (dst->isFlat() && !dst->isInline()) {
+    AddCellMemory(dst, dst->asFlat().allocSize(),
+                  MemoryUse::StringContents);
+  }
+
   return size;
 }
 
 /*** IsMarked / IsAboutToBeFinalized ****************************************/
 
 template <typename T>
 static inline void CheckIsMarkedThing(T* thingp) {
 #define IS_SAME_TYPE_OR(name, type, _, _1) mozilla::IsSame<type*, T>::value ||
--- a/js/src/gc/Scheduling.h
+++ b/js/src/gc/Scheduling.h
@@ -309,17 +309,18 @@
 
 #include "mozilla/Atomics.h"
 
 #include "js/HashTable.h"
 
 namespace js {
 
 #define JS_FOR_EACH_INTERNAL_MEMORY_USE(_)      \
-  _(ArrayBufferContents)
+  _(ArrayBufferContents)                        \
+  _(StringContents)
 
 #define JS_FOR_EACH_MEMORY_USE(_)               \
   JS_FOR_EACH_PUBLIC_MEMORY_USE(_)              \
   JS_FOR_EACH_INTERNAL_MEMORY_USE(_)
 
 enum class MemoryUse : uint8_t {
 #define DEFINE_MEMORY_USE(Name) Name,
   JS_FOR_EACH_MEMORY_USE(DEFINE_MEMORY_USE)
--- a/js/src/vm/StringType-inl.h
+++ b/js/src/vm/StringType-inl.h
@@ -8,23 +8,23 @@
 #define vm_StringType_inl_h
 
 #include "vm/StringType.h"
 
 #include "mozilla/PodOperations.h"
 #include "mozilla/Range.h"
 
 #include "gc/Allocator.h"
-#include "gc/FreeOp.h"
 #include "gc/Marking.h"
 #include "gc/StoreBuffer.h"
 #include "js/UniquePtr.h"
 #include "vm/JSContext.h"
 #include "vm/Realm.h"
 
+#include "gc/FreeOp-inl.h"
 #include "gc/StoreBuffer-inl.h"
 
 namespace js {
 
 // Allocate a thin inline string if possible, and a fat inline string if not.
 template <AllowGC allowGC, typename CharT>
 static MOZ_ALWAYS_INLINE JSInlineString* AllocateInlineString(JSContext* cx,
                                                               size_t len,
@@ -288,16 +288,20 @@ MOZ_ALWAYS_INLINE JSFlatString* JSFlatSt
     // uninitialized memory.
     if (!cx->runtime()->gc.nursery().registerMallocedBuffer(chars.get())) {
       str->init(static_cast<JS::Latin1Char*>(nullptr), 0);
       if (allowGC) {
         ReportOutOfMemory(cx);
       }
       return nullptr;
     }
+  } else {
+    // This can happen off the main thread for the atoms zone.
+    cx->zone()->addCellMemory(str, (length + 1) * sizeof(CharT),
+                              js::MemoryUse::StringContents);
   }
 
   str->init(chars.release(), length);
   return str;
 }
 
 inline js::PropertyName* JSFlatString::toPropertyName(JSContext* cx) {
 #ifdef DEBUG
@@ -380,17 +384,22 @@ MOZ_ALWAYS_INLINE JSExternalString* JSEx
   if (!validateLength(cx, length)) {
     return nullptr;
   }
   JSExternalString* str = js::Allocate<JSExternalString>(cx);
   if (!str) {
     return nullptr;
   }
   str->init(chars, length, fin);
-  cx->updateMallocCounter((length + 1) * sizeof(char16_t));
+  size_t nbytes = (length + 1) * sizeof(char16_t);
+  cx->updateMallocCounter(nbytes);
+
+  MOZ_ASSERT(str->isTenured());
+  js::AddCellMemory(str, nbytes, js::MemoryUse::StringContents);
+
   return str;
 }
 
 inline JSLinearString* js::StaticStrings::getUnitStringForElement(
     JSContext* cx, JSString* str, size_t index) {
   MOZ_ASSERT(index < str->length());
 
   char16_t c;
@@ -415,50 +424,52 @@ MOZ_ALWAYS_INLINE void JSString::finaliz
   }
 }
 
 inline void JSFlatString::finalize(js::FreeOp* fop) {
   MOZ_ASSERT(getAllocKind() != js::gc::AllocKind::FAT_INLINE_STRING);
   MOZ_ASSERT(getAllocKind() != js::gc::AllocKind::FAT_INLINE_ATOM);
 
   if (!isInline()) {
-    fop->free_(nonInlineCharsRaw());
+    fop->free_(this, nonInlineCharsRaw(), allocSize(),
+               js::MemoryUse::StringContents);
   }
 }
 
+inline size_t JSFlatString::allocSize() const {
+  MOZ_ASSERT(!isInline());
+
+  size_t charSize = hasLatin1Chars() ? sizeof(JS::Latin1Char)
+                                     : sizeof(char16_t);
+  size_t count = isExtensible() ? asExtensible().capacity() : length();
+  return (count + 1) * charSize;
+}
+
 inline void JSFatInlineString::finalize(js::FreeOp* fop) {
   MOZ_ASSERT(getAllocKind() == js::gc::AllocKind::FAT_INLINE_STRING);
   MOZ_ASSERT(isInline());
 
   // Nothing to do.
 }
 
-inline void JSAtom::finalize(js::FreeOp* fop) {
-  MOZ_ASSERT(JSString::isAtom());
-  MOZ_ASSERT(JSString::isFlat());
-  MOZ_ASSERT(getAllocKind() == js::gc::AllocKind::ATOM);
-
-  if (!isInline()) {
-    fop->free_(nonInlineCharsRaw());
-  }
-}
-
 inline void js::FatInlineAtom::finalize(js::FreeOp* fop) {
   MOZ_ASSERT(JSString::isAtom());
   MOZ_ASSERT(getAllocKind() == js::gc::AllocKind::FAT_INLINE_ATOM);
 
   // Nothing to do.
 }
 
 inline void JSExternalString::finalize(js::FreeOp* fop) {
   if (!JSString::isExternal()) {
     // This started out as an external string, but was turned into a
     // non-external string by JSExternalString::ensureFlat.
-    MOZ_ASSERT(isFlat());
-    fop->free_(nonInlineCharsRaw());
+    asFlat().finalize(fop);
     return;
   }
 
+  size_t nbytes = (length() + 1) * sizeof(char16_t);
+  js::RemoveCellMemory(this, nbytes, js::MemoryUse::StringContents);
+
   const JSStringFinalizer* fin = externalFinalizer();
   fin->finalize(fin, const_cast<char16_t*>(rawTwoByteChars()));
 }
 
 #endif /* vm_StringType_inl_h */
--- a/js/src/vm/StringType.cpp
+++ b/js/src/vm/StringType.cpp
@@ -568,16 +568,24 @@ JSFlatString* JSRope::flattenInternal(JS
       }
       if (b == WithIncrementalBarrier) {
         JSString::writeBarrierPre(str->d.s.u2.left);
         JSString::writeBarrierPre(str->d.s.u3.right);
       }
       str->setNonInlineChars(wholeChars);
       uint32_t left_len = left.length();
       pos = wholeChars + left_len;
+
+      // Remove memory association for left node we're about to make into a
+      // dependent string.
+      if (left.isTenured()) {
+        RemoveCellMemory(&left, left.allocSize(),
+                         MemoryUse::StringContents);
+      }
+
       if (IsSame<CharT, char16_t>::value) {
         left.setLengthAndFlags(left_len, DEPENDENT_FLAGS);
       } else {
         left.setLengthAndFlags(left_len, DEPENDENT_FLAGS | LATIN1_CHARS_BIT);
       }
       left.d.s.u3.base = (JSLinearString*)this; /* will be true on exit */
       Nursery& nursery = runtimeFromMainThread()->gc.nursery();
       bool inTenured = !bufferIfNursery;
@@ -650,16 +658,22 @@ finish_node : {
     *pos = '\0';
     if (IsSame<CharT, char16_t>::value) {
       str->setLengthAndFlags(wholeLength, EXTENSIBLE_FLAGS);
     } else {
       str->setLengthAndFlags(wholeLength, EXTENSIBLE_FLAGS | LATIN1_CHARS_BIT);
     }
     str->setNonInlineChars(wholeChars);
     str->d.s.u3.capacity = wholeCapacity;
+
+    if (str->isTenured()) {
+      AddCellMemory(str, str->asFlat().allocSize(),
+                    MemoryUse::StringContents);
+    }
+
     return &this->asFlat();
   }
   uintptr_t flattenData;
   uint32_t len = pos - str->nonInlineCharsRaw<CharT>();
   if (IsSame<CharT, char16_t>::value) {
     flattenData = str->unsetFlattenData(len, DEPENDENT_FLAGS);
   } else {
     flattenData =
@@ -866,16 +880,18 @@ JSFlatString* JSDependentString::undepen
     return nullptr;
   }
 
   if (!isTenured()) {
     if (!cx->runtime()->gc.nursery().registerMallocedBuffer(s.get())) {
       ReportOutOfMemory(cx);
       return nullptr;
     }
+  } else {
+    AddCellMemory(this, (n + 1) * sizeof(CharT), MemoryUse::StringContents);
   }
 
   AutoCheckCannotGC nogc;
   FillAndTerminate(s.get(), nonInlineChars<CharT>(nogc), n);
   setNonInlineChars<CharT>(s.release());
 
   /*
    * Transform *this into an undepended string so 'base' will remain rooted
@@ -1445,32 +1461,29 @@ JSFlatString* JSExternalString::ensureFl
   MOZ_ASSERT(hasTwoByteChars());
 
   size_t n = length();
   auto s = cx->make_pod_array<char16_t>(n + 1, js::StringBufferArena);
   if (!s) {
     return nullptr;
   }
 
-  if (!isTenured()) {
-    if (!cx->runtime()->gc.nursery().registerMallocedBuffer(s.get())) {
-      ReportOutOfMemory(cx);
-      return nullptr;
-    }
-  }
-
   // Copy the chars before finalizing the string.
   {
     AutoCheckCannotGC nogc;
     FillAndTerminate(s.get(), nonInlineChars<char16_t>(nogc), n);
   }
 
   // Release the external chars.
   finalize(cx->runtime()->defaultFreeOp());
 
+  MOZ_ASSERT(isTenured());
+  AddCellMemory(this, (n + 1) * sizeof(char16_t),
+                MemoryUse::StringContents);
+
   // Transform the string into a non-external, flat string. Note that the
   // resulting string will still be in an AllocKind::EXTERNAL_STRING arena,
   // but will no longer be an external string.
   setLengthAndFlags(n, INIT_FLAT_FLAGS);
   setNonInlineChars<char16_t>(s.release());
 
   return &this->asFlat();
 }
@@ -1577,16 +1590,19 @@ static JSFlatString* NewStringDeflated(J
 
   if (JSInlineString::lengthFits<Latin1Char>(n)) {
     return NewInlineStringDeflated<allowGC>(
         cx, mozilla::Range<const char16_t>(s, n));
   }
 
   auto news = cx->make_pod_array<Latin1Char>(n + 1, js::StringBufferArena);
   if (!news) {
+    if (!allowGC) {
+      cx->recoverFromOutOfMemory();
+    }
     return nullptr;
   }
 
   MOZ_ASSERT(CanStoreCharsAsLatin1(s, n));
   FillFromCompatibleAndTerminate(news.get(), s, n);
 
   return JSFlatString::new_<allowGC>(cx, std::move(news), n);
 }
--- a/js/src/vm/StringType.h
+++ b/js/src/vm/StringType.h
@@ -1038,16 +1038,18 @@ class JSFlatString : public JSLinearStri
   /*
    * Once a JSFlatString sub-class has been added to the atom state, this
    * operation changes the string to the JSAtom type, in place.
    */
   MOZ_ALWAYS_INLINE JSAtom* morphAtomizedStringIntoAtom(js::HashNumber hash);
   MOZ_ALWAYS_INLINE JSAtom* morphAtomizedStringIntoPermanentAtom(
       js::HashNumber hash);
 
+  inline size_t allocSize() const;
+
   inline void finalize(js::FreeOp* fop);
 
 #if defined(DEBUG) || defined(JS_JITSPEW)
   void dumpRepresentation(js::GenericPrinter& out, int indent) const;
 #endif
 };
 
 static_assert(sizeof(JSFlatString) == sizeof(JSString),
@@ -1236,18 +1238,16 @@ class JSAtom : public JSFlatString {
   /* Vacuous and therefore unimplemented. */
   bool isAtom() const = delete;
   JSAtom& asAtom() const = delete;
 
  public:
   /* Returns the PropertyName for this.  isIndex() must be false. */
   inline js::PropertyName* asPropertyName();
 
-  inline void finalize(js::FreeOp* fop);
-
   MOZ_ALWAYS_INLINE
   bool isPermanent() const { return JSString::isPermanentAtom(); }
 
   // Transform this atom into a permanent atom. This is only done during
   // initialization of the runtime. Permanent atoms are always pinned.
   MOZ_ALWAYS_INLINE void morphIntoPermanentAtom() {
     MOZ_ASSERT(static_cast<JSString*>(this)->isAtom());
     setFlagBit(PERMANENT_ATOM_FLAGS | PINNED_ATOM_BIT);