Bug 1395509 - Track malloc memory associated with array buffers r=sfink
authorJon Coppeard <jcoppeard@mozilla.com>
Thu, 09 May 2019 16:15:28 +0100
changeset 535326 54227b6122129de93a5348d3ff6300acd04f80ae
parent 535325 95399bf8d949c78b5d7d67019f203fea85fb755e
child 535327 01140845ccccf650829cffd7c4631732204599f4
push id2082
push userffxbld-merge
push dateMon, 01 Jul 2019 08:34:18 +0000
treeherdermozilla-release@2fb19d0466d2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssfink
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 array buffers r=sfink Differential Revision: https://phabricator.services.mozilla.com/D30516
js/src/gc/Scheduling.h
js/src/vm/ArrayBufferObject.cpp
js/src/vm/ArrayBufferObject.h
--- a/js/src/gc/Scheduling.h
+++ b/js/src/gc/Scheduling.h
@@ -308,17 +308,18 @@
 #define gc_Scheduling_h
 
 #include "mozilla/Atomics.h"
 
 #include "js/HashTable.h"
 
 namespace js {
 
-#define JS_FOR_EACH_INTERNAL_MEMORY_USE(_)
+#define JS_FOR_EACH_INTERNAL_MEMORY_USE(_)      \
+  _(ArrayBufferContents)
 
 #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/ArrayBufferObject.cpp
+++ b/js/src/vm/ArrayBufferObject.cpp
@@ -29,17 +29,16 @@
 #include "jsfriendapi.h"
 #include "jsnum.h"
 #include "jstypes.h"
 #include "jsutil.h"
 
 #include "builtin/Array.h"
 #include "builtin/DataViewObject.h"
 #include "gc/Barrier.h"
-#include "gc/FreeOp.h"
 #include "gc/Memory.h"
 #include "js/ArrayBuffer.h"
 #include "js/Conversions.h"
 #include "js/MemoryMetrics.h"
 #include "js/PropertySpec.h"
 #include "js/SharedArrayBuffer.h"
 #include "js/Wrapper.h"
 #include "util/Windows.h"
@@ -47,16 +46,17 @@
 #include "vm/Interpreter.h"
 #include "vm/JSContext.h"
 #include "vm/JSObject.h"
 #include "vm/SharedArrayObject.h"
 #include "vm/WrapperObject.h"
 #include "wasm/WasmSignalHandlers.h"
 #include "wasm/WasmTypes.h"
 
+#include "gc/FreeOp-inl.h"
 #include "gc/Marking-inl.h"
 #include "gc/Nursery-inl.h"
 #include "vm/JSAtom-inl.h"
 #include "vm/NativeObject-inl.h"
 #include "vm/Shape-inl.h"
 
 using JS::ToInt32;
 
@@ -939,30 +939,34 @@ ArrayBufferObject::FreeInfo* ArrayBuffer
 }
 
 void ArrayBufferObject::releaseData(FreeOp* fop) {
   switch (bufferKind()) {
     case INLINE_DATA:
       // Inline data doesn't require releasing.
       break;
     case MALLOCED:
-      fop->free_(dataPointer());
+      fop->free_(this, dataPointer(), byteLength(),
+                 MemoryUse::ArrayBufferContents);
       break;
     case NO_DATA:
       // There's nothing to release if there's no data.
       MOZ_ASSERT(dataPointer() == nullptr);
       break;
     case USER_OWNED:
       // User-owned data is released by, well, the user.
       break;
     case MAPPED:
       gc::DeallocateMappedContent(dataPointer(), byteLength());
+      RemoveCellMemory(this, associatedBytes(),
+                       MemoryUse::ArrayBufferContents);
       break;
     case WASM:
       WasmArrayRawBuffer::Release(dataPointer());
+      RemoveCellMemory(this, byteLength(), MemoryUse::ArrayBufferContents);
       break;
     case EXTERNAL:
       if (freeInfo()->freeFunc) {
         // The analyzer can't know for sure whether the embedder-supplied
         // free function will GC. We give the analyzer a hint here.
         // (Doing a GC in the free function is considered a programmer
         // error.)
         JS::AutoSuppressGCAnalysis nogc;
@@ -985,16 +989,26 @@ void ArrayBufferObject::setDataPointer(B
     info->freeUserData = contents.freeUserData();
   }
 }
 
 uint32_t ArrayBufferObject::byteLength() const {
   return getFixedSlot(BYTE_LENGTH_SLOT).toInt32();
 }
 
+inline size_t ArrayBufferObject::associatedBytes() const {
+  if (bufferKind() == MALLOCED) {
+    return byteLength();
+  } else if (bufferKind() == MAPPED) {
+    return JS_ROUNDUP(byteLength(), js::gc::SystemPageSize());
+  } else {
+    MOZ_CRASH("Unexpected buffer kind");
+  }
+}
+
 void ArrayBufferObject::setByteLength(uint32_t length) {
   MOZ_ASSERT(length <= INT32_MAX);
   setFixedSlot(BYTE_LENGTH_SLOT, Int32Value(length));
 }
 
 size_t ArrayBufferObject::wasmMappedSize() const {
   if (isWasm()) {
     return contents().wasmBuffer()->mappedSize();
@@ -1066,20 +1080,24 @@ bool ArrayBufferObject::wasmGrowToSizeIn
 
   // Extract the grown contents from |oldBuf|.
   BufferContents oldContents = oldBuf->contents();
 
   // Overwrite |oldBuf|'s data pointer *without* releasing old data.
   oldBuf->setDataPointer(BufferContents::createNoData());
 
   // Detach |oldBuf| now that doing so won't release |oldContents|.
+  RemoveCellMemory(oldBuf, oldBuf->byteLength(),
+                   MemoryUse::ArrayBufferContents);
   ArrayBufferObject::detach(cx, oldBuf);
 
   // Set |newBuf|'s contents to |oldBuf|'s original contents.
   newBuf->initialize(newSize, oldContents);
+  AddCellMemory(newBuf, newSize, MemoryUse::ArrayBufferContents);
+
   return true;
 }
 
 #ifndef WASM_HUGE_MEMORY
 /* static */
 bool ArrayBufferObject::wasmMovingGrowToSize(
     uint32_t newSize, HandleArrayBufferObject oldBuf,
     MutableHandleArrayBufferObject newBuf, JSContext* cx) {
@@ -1101,16 +1119,19 @@ bool ArrayBufferObject::wasmMovingGrowTo
     return false;
   }
 
   WasmArrayRawBuffer* newRawBuf =
       WasmArrayRawBuffer::Allocate(newSize, Nothing());
   if (!newRawBuf) {
     return false;
   }
+
+  AddCellMemory(newBuf, newSize, MemoryUse::ArrayBufferContents);
+
   BufferContents contents =
       BufferContents::createWasm(newRawBuf->dataPointer());
   newBuf->initialize(newSize, contents);
 
   memcpy(newBuf->dataPointer(), oldBuf->dataPointer(), oldBuf->byteLength());
   ArrayBufferObject::detach(cx, oldBuf);
   return true;
 }
@@ -1170,41 +1191,36 @@ ArrayBufferObject* ArrayBufferObject::cr
     return nullptr;
   }
 
   // Some |contents| kinds need to store extra data in the ArrayBuffer beyond a
   // data pointer.  If needed for the particular kind, add extra fixed slots to
   // the ArrayBuffer for use as raw storage to store such information.
   size_t reservedSlots = JSCLASS_RESERVED_SLOTS(&class_);
 
+  size_t nAllocated = 0;
   size_t nslots = reservedSlots;
   if (contents.kind() == USER_OWNED) {
     // No accounting to do in this case.
   } else if (contents.kind() == EXTERNAL) {
     // Store the FreeInfo in the inline data slots so that we
     // don't use up slots for it in non-refcounted array buffers.
     size_t freeInfoSlots = JS_HOWMANY(sizeof(FreeInfo), sizeof(Value));
     MOZ_ASSERT(reservedSlots + freeInfoSlots <= NativeObject::MAX_FIXED_SLOTS,
                "FreeInfo must fit in inline slots");
     nslots += freeInfoSlots;
   } else {
     // The ABO is taking ownership, so account the bytes against the zone.
-    size_t nAllocated = nbytes;
+    nAllocated = nbytes;
     if (contents.kind() == MAPPED) {
       nAllocated = JS_ROUNDUP(nbytes, js::gc::SystemPageSize());
     } else {
       MOZ_ASSERT(contents.kind() == MALLOCED,
                  "should have handled all possible callers' kinds");
     }
-
-    // "mapped" bytes are fed into a "malloc" counter because (bug 1037358) this
-    // counter constitutes an input to the "when do we GC?" subsystem.  Arguably
-    // it deserves renaming to something that doesn't narrowly cabin it to just
-    // "malloc" stuff, if we're going to use it this way.
-    cx->updateMallocCounter(nAllocated);
   }
 
   MOZ_ASSERT(!(class_.flags & JSCLASS_HAS_PRIVATE));
   gc::AllocKind allocKind = gc::GetGCObjectKind(nslots);
 
   AutoSetNewObjectMetadata metadata(cx);
   Rooted<ArrayBufferObject*> buffer(
       cx, NewObjectWithClassProto<ArrayBufferObject>(cx, nullptr, allocKind,
@@ -1214,16 +1230,20 @@ ArrayBufferObject* ArrayBufferObject::cr
   }
 
   MOZ_ASSERT(!gc::IsInsideNursery(buffer),
              "ArrayBufferObject has a finalizer that must be called to not "
              "leak in some cases, so it can't be nursery-allocated");
 
   buffer->initialize(nbytes, contents);
 
+  if (contents.kind() == MAPPED || contents.kind() == MALLOCED) {
+    AddCellMemory(buffer, nAllocated, MemoryUse::ArrayBufferContents);
+  }
+
   return buffer;
 }
 
 ArrayBufferObject* ArrayBufferObject::createZeroed(
     JSContext* cx, uint32_t nbytes, HandleObject proto /* = nullptr */) {
   // 24.1.1.1, step 3 (Inlined 6.2.6.1 CreateByteDataBlock, step 2).
   if (!CheckArrayBufferTooLarge(cx, nbytes)) {
     return nullptr;
@@ -1262,16 +1282,17 @@ ArrayBufferObject* ArrayBufferObject::cr
   }
 
   MOZ_ASSERT(!gc::IsInsideNursery(buffer),
              "ArrayBufferObject has a finalizer that must be called to not "
              "leak in some cases, so it can't be nursery-allocated");
 
   if (data) {
     buffer->initialize(nbytes, BufferContents::createMalloced(data));
+    AddCellMemory(buffer, nbytes, MemoryUse::ArrayBufferContents);
   } else {
     void* inlineData = buffer->initializeToInlineData(nbytes);
     memset(inlineData, 0, nbytes);
   }
 
   return buffer;
 }
 
@@ -1297,30 +1318,33 @@ ArrayBufferObject* ArrayBufferObject::cr
 
   buffer->setByteLength(initialSize);
   buffer->setFlags(0);
   buffer->setFirstView(nullptr);
 
   auto contents = BufferContents::createWasm(rawBuffer->dataPointer());
   buffer->setDataPointer(contents);
 
-  cx->updateMallocCounter(initialSize);
+  AddCellMemory(buffer, initialSize, MemoryUse::ArrayBufferContents);
 
   return buffer;
 }
 
 /* static */ uint8_t* ArrayBufferObject::stealMallocedContents(
     JSContext* cx, Handle<ArrayBufferObject*> buffer) {
   CheckStealPreconditions(buffer, cx);
 
   switch (buffer->bufferKind()) {
     case MALLOCED: {
       uint8_t* stolenData = buffer->dataPointer();
       MOZ_ASSERT(stolenData);
 
+      RemoveCellMemory(buffer, buffer->byteLength(),
+                       MemoryUse::ArrayBufferContents);
+
       // Overwrite the old data pointer *without* releasing the contents
       // being stolen.
       buffer->setDataPointer(BufferContents::createNoData());
 
       // Detach |buffer| now that doing so won't free |stolenData|.
       ArrayBufferObject::detach(cx, buffer);
       return stolenData;
     }
@@ -1377,16 +1401,19 @@ ArrayBufferObject::extractStructuredClon
       ArrayBufferObject::detach(cx, buffer);
       return BufferContents::createMalloced(copiedData);
     }
 
     case MALLOCED:
     case MAPPED: {
       MOZ_ASSERT(contents);
 
+      RemoveCellMemory(buffer, buffer->associatedBytes(),
+                       MemoryUse::ArrayBufferContents);
+
       // Overwrite the old data pointer *without* releasing old data.
       buffer->setDataPointer(BufferContents::createNoData());
 
       // Detach |buffer| now that doing so won't release |oldContents|.
       ArrayBufferObject::detach(cx, buffer);
       return contents;
     }
 
--- a/js/src/vm/ArrayBufferObject.h
+++ b/js/src/vm/ArrayBufferObject.h
@@ -434,16 +434,18 @@ class ArrayBufferObject : public ArrayBu
   static size_t offsetOfDataSlot() { return getFixedSlotOffset(DATA_SLOT); }
 
   void setHasTypedObjectViews() { setFlags(flags() | TYPED_OBJECT_VIEWS); }
 
  protected:
   void setDataPointer(BufferContents contents);
   void setByteLength(uint32_t length);
 
+  size_t associatedBytes() const;
+
   uint32_t flags() const;
   void setFlags(uint32_t flags);
 
   bool hasTypedObjectViews() const { return flags() & TYPED_OBJECT_VIEWS; }
 
   void setIsDetached() { setFlags(flags() | DETACHED); }
   void setIsPreparedForAsmJS() {
     MOZ_ASSERT(!isWasm());