Bug 1529298 - Split PLAIN into INLINE_DATA/MALLOCED for ArrayBuffer kinds. r=sfink
☠☠ backed out by eed1098d0d6c ☠ ☠
authorJeff Walden <jwalden@mit.edu>
Mon, 18 Feb 2019 22:52:25 -0800
changeset 460361 9294b0d54597d7ef4ec4615739306f9bb9f3d1e5
parent 460360 c5a391fd808f66366fb776e2df7e5d204d87c3ab
child 460362 3512de18097c797cdbeec56b56efdf3e36eb3399
push id112082
push userjwalden@mit.edu
push dateFri, 22 Feb 2019 02:22:21 +0000
treeherdermozilla-inbound@d80b681a68e6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssfink
bugs1529298
milestone67.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 1529298 - Split PLAIN into INLINE_DATA/MALLOCED for ArrayBuffer kinds. r=sfink
js/src/jsfriendapi.h
js/src/shell/js.cpp
js/src/vm/ArrayBufferObject.cpp
js/src/vm/ArrayBufferObject.h
js/src/vm/NativeObject.h
js/src/vm/StructuredClone.cpp
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -591,17 +591,17 @@ class Shape {
  * depending on the object's specific layout.
  */
 struct Object {
   shadow::ObjectGroup* group;
   shadow::Shape* shape;
   JS::Value* slots;
   void* _1;
 
-  static const size_t MAX_FIXED_SLOTS = 16;
+  static constexpr size_t MAX_FIXED_SLOTS = 16;
 
   size_t numFixedSlots() const {
     return (shape->immutableFlags & Shape::FIXED_SLOTS_MASK) >>
            Shape::FIXED_SLOTS_SHIFT;
   }
 
   JS::Value* fixedSlots() const {
     return (JS::Value*)(uintptr_t(this) + sizeof(shadow::Object));
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -1913,17 +1913,17 @@ static uint8_t* CacheEntry_getBytecode(J
 }
 
 static bool CacheEntry_setBytecode(JSContext* cx, HandleObject cache,
                                    uint8_t* buffer, uint32_t length) {
   MOZ_ASSERT(CacheEntry_isCacheEntry(cache));
 
   using BufferContents = ArrayBufferObject::BufferContents;
 
-  BufferContents contents = BufferContents::createPlainData(buffer);
+  BufferContents contents = BufferContents::createMalloced(buffer);
   Rooted<ArrayBufferObject*> arrayBuffer(
       cx, ArrayBufferObject::create(cx, length, contents));
   if (!arrayBuffer) {
     return false;
   }
 
   SetReservedSlot(cache, CacheEntry_BYTECODE, ObjectValue(*arrayBuffer));
   return true;
--- a/js/src/vm/ArrayBufferObject.cpp
+++ b/js/src/vm/ArrayBufferObject.cpp
@@ -440,17 +440,17 @@ bool ArrayBufferObject::class_constructo
   args.rval().setObject(*bufobj);
   return true;
 }
 
 static ArrayBufferObject::BufferContents AllocateArrayBufferContents(
     JSContext* cx, uint32_t nbytes) {
   uint8_t* p =
       cx->pod_callocCanGC<uint8_t>(nbytes, js::ArrayBufferContentsArena);
-  return ArrayBufferObject::BufferContents::createPlainData(p);
+  return ArrayBufferObject::BufferContents::createMalloced(p);
 }
 
 static void NoteViewBufferWasDetached(
     ArrayBufferViewObject* view, ArrayBufferObject::BufferContents newContents,
     JSContext* cx) {
   MOZ_ASSERT(!view->isSharedMemory());
 
   view->notifyBufferDetached(newContents.data());
@@ -917,17 +917,26 @@ bool js::CreateWasmBuffer(JSContext* cx,
 }
 
 // Note this function can return false with or without an exception pending. The
 // asm.js caller checks cx->isExceptionPending before propagating failure.
 // Returning false without throwing means that asm.js linking will fail which
 // will recompile as non-asm.js.
 /* static */ bool ArrayBufferObject::prepareForAsmJS(
     JSContext* cx, Handle<ArrayBufferObject*> buffer) {
-  MOZ_ASSERT(buffer->byteLength() % wasm::PageSize == 0);
+  MOZ_ASSERT(buffer->byteLength() % wasm::PageSize == 0,
+             "prior size checking should have guaranteed page-size multiple");
+  MOZ_ASSERT(buffer->byteLength() > 0,
+             "prior size checking should have excluded empty buffers");
+
+  static_assert(wasm::PageSize > MaxInlineBytes,
+                "inline data must be too small to be a page size multiple");
+  MOZ_ASSERT(!buffer->isInlineData(),
+             "inline-data buffers are implicitly excluded by size checks");
+
   // Don't assert cx->wasmHaveSignalHandlers because (1) they aren't needed
   // for asm.js, (2) they are only installed for WebAssembly, not asm.js.
 
   // wasm buffers can be detached at any time.
   if (buffer->isWasm()) {
     MOZ_ASSERT(!buffer->isPreparedForAsmJS());
     return false;
   }
@@ -938,17 +947,17 @@ bool js::CreateWasmBuffer(JSContext* cx,
   // this edge case by not allowing buffers with user-provided content to be
   // used with asm.js, as no callers exist that want to use such buffer with
   // asm.js.
   if (buffer->hasUserOwnedData()) {
     MOZ_ASSERT(!buffer->isPreparedForAsmJS());
     return false;
   }
 
-  MOZ_ASSERT(buffer->isPlainData() || buffer->isMapped() ||
+  MOZ_ASSERT(buffer->isMalloced() || buffer->isMapped() ||
              buffer->isExternal());
 
   // Buffers already prepared for asm.js need no further work.
   if (buffer->isPreparedForAsmJS()) {
     return true;
   }
 
   if (!buffer->ownsData()) {
@@ -988,17 +997,20 @@ ArrayBufferObject::FreeInfo* ArrayBuffer
   MOZ_ASSERT(isExternal());
   return reinterpret_cast<FreeInfo*>(inlineDataPointer());
 }
 
 void ArrayBufferObject::releaseData(FreeOp* fop) {
   MOZ_ASSERT(ownsData());
 
   switch (bufferKind()) {
-    case PLAIN_DATA:
+    case INLINE_DATA:
+      MOZ_ASSERT_UNREACHABLE("inline data should never be owned");
+      break;
+    case MALLOCED:
       fop->free_(dataPointer());
       break;
     case USER_OWNED:
       MOZ_ASSERT_UNREACHABLE("user-owned data should never be owned by this");
       break;
     case MAPPED:
       gc::DeallocateMappedContent(dataPointer(), byteLength());
       break;
@@ -1012,17 +1024,16 @@ void ArrayBufferObject::releaseData(Free
         // (Doing a GC in the free function is considered a programmer
         // error.)
         JS::AutoSuppressGCAnalysis nogc;
         freeInfo()->freeFunc(dataPointer(), freeInfo()->freeUserData);
       }
       break;
     case BAD1:
     case BAD2:
-    case BAD3:
       MOZ_CRASH("invalid BufferKind encountered");
       break;
   }
 }
 
 void ArrayBufferObject::setDataPointer(BufferContents contents,
                                        OwnsState ownsData) {
   setFixedSlot(DATA_SLOT, PrivateValue(contents.data()));
@@ -1134,17 +1145,17 @@ Maybe<uint32_t> js::WasmArrayBufferMaxSi
     return false;
   }
   BufferContents contents =
       BufferContents::create<WASM>(newRawBuf->dataPointer());
   newBuf->initialize(newSize, contents, OwnsData);
 
   memcpy(newBuf->dataPointer(), oldBuf->dataPointer(), oldBuf->byteLength());
   ArrayBufferObject::detach(cx, oldBuf,
-                            BufferContents::createPlainData(nullptr));
+                            BufferContents::createMalloced(nullptr));
   return true;
 }
 
 uint32_t ArrayBufferObject::wasmBoundsCheckLimit() const {
   if (isWasm()) {
     return contents().wasmBuffer()->boundsCheckLimit();
   }
   return byteLength();
@@ -1213,22 +1224,21 @@ ArrayBufferObject* ArrayBufferObject::cr
         if (contents.kind() == MAPPED) {
           nAllocated = JS_ROUNDUP(nbytes, js::gc::SystemPageSize());
         }
         cx->updateMallocCounter(nAllocated);
       }
     }
   } else {
     MOZ_ASSERT(ownsState == OwnsData);
-    size_t usableSlots = NativeObject::MAX_FIXED_SLOTS - reservedSlots;
-    if (nbytes <= usableSlots * sizeof(Value)) {
+    if (nbytes <= MaxInlineBytes) {
       int newSlots = JS_HOWMANY(nbytes, sizeof(Value));
       MOZ_ASSERT(int(nbytes) <= newSlots * int(sizeof(Value)));
       nslots = reservedSlots + newSlots;
-      contents = BufferContents::createPlainData(nullptr);
+      contents = BufferContents::createInlineData(nullptr);
     } else {
       contents = AllocateArrayBufferContents(cx, nbytes);
       if (!contents) {
         ReportOutOfMemory(cx);
         return nullptr;
       }
       allocated = true;
     }
@@ -1246,44 +1256,48 @@ ArrayBufferObject* ArrayBufferObject::cr
     }
     return nullptr;
   }
 
   MOZ_ASSERT(obj->getClass() == &class_);
   MOZ_ASSERT(!gc::IsInsideNursery(obj));
 
   if (!contents) {
+    MOZ_ASSERT(contents.kind() == ArrayBufferObject::INLINE_DATA);
     void* data = obj->inlineDataPointer();
     memset(data, 0, nbytes);
-    obj->initialize(nbytes, BufferContents::createPlainData(data),
+    obj->initialize(nbytes, BufferContents::createInlineData(data),
                     DoesntOwnData);
   } else {
     obj->initialize(nbytes, contents, ownsState);
   }
 
   return obj;
 }
 
 ArrayBufferObject* ArrayBufferObject::create(
     JSContext* cx, uint32_t nbytes, HandleObject proto /* = nullptr */) {
-  return create(cx, nbytes, BufferContents::createPlainData(nullptr),
+  // The contents supplied here are not used other than for a nullity-check, so
+  // the choice to pass |createMalloced()| contents is arbitrary.  (Hopefully
+  // in time we can eliminate this internal wart.)
+  return create(cx, nbytes, BufferContents::createMalloced(nullptr),
                 OwnsState::OwnsData, proto);
 }
 
 ArrayBufferObject* ArrayBufferObject::createEmpty(JSContext* cx) {
   AutoSetNewObjectMetadata metadata(cx);
   ArrayBufferObject* obj = NewBuiltinClassInstance<ArrayBufferObject>(cx);
   if (!obj) {
     return nullptr;
   }
 
   obj->setByteLength(0);
   obj->setFlags(0);
   obj->setFirstView(nullptr);
-  obj->setDataPointer(BufferContents::createPlainData(nullptr), DoesntOwnData);
+  obj->setDataPointer(BufferContents::createMalloced(nullptr), DoesntOwnData);
 
   return obj;
 }
 
 ArrayBufferObject* ArrayBufferObject::createFromNewRawBuffer(
     JSContext* cx, WasmArrayRawBuffer* buffer, uint32_t initialSize) {
   AutoSetNewObjectMetadata metadata(cx);
   ArrayBufferObject* obj = NewBuiltinClassInstance<ArrayBufferObject>(cx);
@@ -1303,18 +1317,19 @@ ArrayBufferObject* ArrayBufferObject::cr
 
   return obj;
 }
 
 /* static */ ArrayBufferObject::BufferContents
 ArrayBufferObject::externalizeContents(JSContext* cx,
                                        Handle<ArrayBufferObject*> buffer,
                                        bool hasStealableContents) {
-  MOZ_ASSERT(buffer->isPlainData(),
-             "only support doing this on ABOs containing plain data");
+  MOZ_ASSERT(buffer->isInlineData() || buffer->isMalloced(),
+             "only support doing this on ABOs containing inline or malloced "
+             "data");
   MOZ_ASSERT(!buffer->isDetached(), "must have contents to externalize");
   MOZ_ASSERT_IF(hasStealableContents, buffer->hasStealableContents());
 
   BufferContents contents = buffer->contents();
 
   if (hasStealableContents) {
     buffer->setOwnsData(DoesntOwnData);
     return contents;
@@ -1343,17 +1358,17 @@ ArrayBufferObject::externalizeContents(J
                     (buffer->isWasm() && !buffer->isPreparedForAsmJS()));
   cx->check(buffer);
 
   BufferContents oldContents = buffer->contents();
 
   if (hasStealableContents) {
     // Return the old contents and reset the detached buffer's data
     // pointer. This pointer should never be accessed.
-    auto newContents = BufferContents::createPlainData(nullptr);
+    auto newContents = BufferContents::createMalloced(nullptr);
     buffer->setOwnsData(DoesntOwnData);  // Do not free the stolen data.
     ArrayBufferObject::detach(cx, buffer, newContents);
     buffer->setOwnsData(DoesntOwnData);  // Do not free the nullptr.
     return oldContents;
   }
 
   // Create a new chunk of memory to return since we cannot steal the
   // existing contents away from the buffer.
@@ -1374,17 +1389,21 @@ ArrayBufferObject::externalizeContents(J
     JSObject* obj, mozilla::MallocSizeOf mallocSizeOf, JS::ClassInfo* info) {
   ArrayBufferObject& buffer = AsArrayBuffer(obj);
 
   if (!buffer.ownsData()) {
     return;
   }
 
   switch (buffer.bufferKind()) {
-    case PLAIN_DATA:
+    case INLINE_DATA:
+      MOZ_ASSERT_UNREACHABLE("inline data should never be owned and should be "
+                             "accounted for in |this|'s memory");
+      break;
+    case MALLOCED:
       if (buffer.isPreparedForAsmJS()) {
         info->objectsMallocHeapElementsAsmJS +=
             mallocSizeOf(buffer.dataPointer());
       } else {
         info->objectsMallocHeapElementsNormal +=
             mallocSizeOf(buffer.dataPointer());
       }
       break;
@@ -1401,17 +1420,16 @@ ArrayBufferObject::externalizeContents(J
       MOZ_ASSERT(buffer.wasmMappedSize() >= buffer.byteLength());
       info->wasmGuardPages += buffer.wasmMappedSize() - buffer.byteLength();
       break;
     case EXTERNAL:
       MOZ_CRASH("external buffers not currently supported");
       break;
     case BAD1:
     case BAD2:
-    case BAD3:
       MOZ_CRASH("bad bufferKind()");
   }
 }
 
 /* static */ void ArrayBufferObject::finalize(FreeOp* fop, JSObject* obj) {
   ArrayBufferObject& buffer = obj->as<ArrayBufferObject>();
 
   if (buffer.ownsData()) {
@@ -1638,17 +1656,17 @@ JS_FRIEND_API bool JS_DetachArrayBuffer(
   if (buffer->isWasm() || buffer->isPreparedForAsmJS()) {
     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                               JSMSG_WASM_NO_TRANSFER);
     return false;
   }
 
   ArrayBufferObject::BufferContents newContents =
       buffer->hasStealableContents()
-          ? ArrayBufferObject::BufferContents::createPlainData(nullptr)
+          ? ArrayBufferObject::BufferContents::createMalloced(nullptr)
           : buffer->contents();
 
   ArrayBufferObject::detach(cx, buffer, newContents);
 
   return true;
 }
 
 JS_FRIEND_API bool JS_IsDetachedArrayBufferObject(JSObject* obj) {
@@ -1671,17 +1689,17 @@ JS_PUBLIC_API JSObject* JS_NewArrayBuffe
                                                       size_t nbytes,
                                                       void* data) {
   AssertHeapIsIdle();
   CHECK_THREAD(cx);
   MOZ_ASSERT_IF(!data, nbytes == 0);
 
   using BufferContents = ArrayBufferObject::BufferContents;
 
-  BufferContents contents = BufferContents::createPlainData(data);
+  BufferContents contents = BufferContents::createMalloced(data);
   return ArrayBufferObject::create(cx, nbytes, contents,
                                    ArrayBufferObject::OwnsData,
                                    /* proto = */ nullptr, TenuredObject);
 }
 
 JS_PUBLIC_API JSObject* JS_NewExternalArrayBuffer(
     JSContext* cx, size_t nbytes, void* data,
     JS::BufferContentsFreeFunc freeFunc, void* freeUserData) {
@@ -1738,33 +1756,45 @@ JS_PUBLIC_API void* JS_ExternalizeArrayB
 
   if (!obj->is<ArrayBufferObject>()) {
     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                               JSMSG_TYPED_ARRAY_BAD_ARGS);
     return nullptr;
   }
 
   Handle<ArrayBufferObject*> buffer = obj.as<ArrayBufferObject>();
-  if (!buffer->isPlainData()) {
-    // This operation isn't supported on mapped or wasm ArrayBufferObjects, or
-    // on ArrayBufferObjects with user-provided data.
+  if (!buffer->isMalloced() && !buffer->isInlineData()) {
+    // This operation is supported only for malloced or inline data, because
+    // this function is documented to return only JS_free-able memory.
+    //
+    // Mapped and wasm ArrayBufferObjects' memory is not so allocated, and we
+    // can't blithely allocate a copy of the data and swap it into |buffer|
+    // because JIT code may hold pointers into the mapped/wasm data.
+    //
+    // We also don't support replacing user-provided data (with malloced data)
+    // because such ArrayBuffers must be passed to JS_DetachArrayBuffer before
+    // the associated memory is freed, and that call is guaranteed to succeed
+    // -- but malloced data can be used with asm.js, and JS_DetachArrayBuffer
+    // will fail on an ArrayBuffer used with asm.js.
+    //
+    // These restrictions are very hoary.  Possibly, in a future where
+    // ArrayBuffer's contents are not represented so trickily, we might want to
+    // relax them -- maybe by returning something that has the ability to
+    // release the returned memory.
     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                               JSMSG_TYPED_ARRAY_BAD_ARGS);
     return nullptr;
   }
   if (buffer->isDetached()) {
     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                               JSMSG_TYPED_ARRAY_DETACHED);
     return nullptr;
   }
 
   // The caller assumes that a plain malloc'd buffer is returned.
-  // hasStealableContents is true for mapped buffers, so we must additionally
-  // require that the buffer is plain. In the future, we could consider
-  // returning something that handles releasing the memory.
   bool hasStealableContents = buffer->hasStealableContents();
 
   return ArrayBufferObject::externalizeContents(cx, buffer,
                                                 hasStealableContents)
       .data();
 }
 
 JS_PUBLIC_API void* JS_StealArrayBufferContents(JSContext* cx,
@@ -1795,24 +1825,26 @@ JS_PUBLIC_API void* JS_StealArrayBufferC
   if (buffer->isWasm() || buffer->isPreparedForAsmJS()) {
     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                               JSMSG_WASM_NO_TRANSFER);
     return nullptr;
   }
 
   // The caller assumes that a plain malloc'd buffer is returned.  To steal
   // actual contents, then, we must have |hasStealableContents()| *and* the
-  // contents must be |isPlainData()|.  (Mapped data would not be malloc'd;
+  // contents must be |isMalloced()|.  (Mapped data would not be malloc'd;
   // user-provided data we flat-out know nothing about at all -- although it
-  // *should* have not passed the |hasStealableContents()| check anyway.)
+  // *should* have not passed the |hasStealableContents()| check anyway; inline
+  // data would not be malloc'd *and* shouldn't pass |hasStealableContents()|
+  // either.)
   //
   // In the future, we could consider returning something that handles
   // releasing the memory, in the mapped-data case.
   bool hasStealableContents =
-      buffer->hasStealableContents() && buffer->isPlainData();
+      buffer->hasStealableContents() && buffer->isMalloced();
 
   AutoRealm ar(cx, buffer);
   return ArrayBufferObject::stealContents(cx, buffer, hasStealableContents)
       .data();
 }
 
 JS_PUBLIC_API JSObject* JS_NewMappedArrayBufferWithContents(JSContext* cx,
                                                             size_t nbytes,
--- a/js/src/vm/ArrayBufferObject.h
+++ b/js/src/vm/ArrayBufferObject.h
@@ -163,42 +163,48 @@ class ArrayBufferObject : public ArrayBu
                 "self-hosted code with burned-in constants must get the "
                 "right flags slot");
 
   // The length of an ArrayBuffer or SharedArrayBuffer can be at most
   // INT32_MAX, and much code must change if this changes.
 
   static const size_t MaxBufferByteLength = INT32_MAX;
 
+  /** The largest number of bytes that can be stored inline. */
+  static constexpr size_t MaxInlineBytes =
+    (NativeObject::MAX_FIXED_SLOTS - RESERVED_SLOTS) * sizeof(JS::Value);
+
  public:
   enum OwnsState {
     DoesntOwnData = 0,
     OwnsData = 1,
   };
 
   enum BufferKind {
-    /** Malloced or inline data. */
-    PLAIN_DATA = 0b000,
+    /** Inline data kept in the repurposed slots of this ArrayBufferObject. */
+    INLINE_DATA = 0b000,
+
+    /* Data allocated using the SpiderMonkey allocator. */
+    MALLOCED = 0b001,
 
     /**
      * User-owned memory.  The associated buffer must be manually detached
      * before the user invalidates (deallocates, reuses the storage of, &c.)
      * the user-owned memory.
      */
-    USER_OWNED = 0b001,
+    USER_OWNED = 0b010,
 
-    WASM = 0b010,
-    MAPPED = 0b011,
-    EXTERNAL = 0b100,
+    WASM = 0b011,
+    MAPPED = 0b100,
+    EXTERNAL = 0b101,
 
     // These kind-values are currently invalid.  We intend to expand valid
     // BufferKinds in the future to either partly or fully use these values.
-    BAD1 = 0b101,
-    BAD2 = 0b110,
-    BAD3 = 0b111,
+    BAD1 = 0b110,
+    BAD2 = 0b111,
 
     KIND_MASK = 0b111
   };
 
  protected:
   enum ArrayBufferFlags {
     // The flags also store the BufferKind
     BUFFER_KIND_MASK = BufferKind::KIND_MASK,
@@ -214,19 +220,21 @@ class ArrayBufferObject : public ArrayBu
     // allocate their data inline, and buffers that are created lazily for
     // typed objects with inline storage, in which case the buffer points
     // directly to the typed object's storage.
     OWNS_DATA = 0b1'0000,
 
     // Views of this buffer might include typed objects.
     TYPED_OBJECT_VIEWS = 0b10'0000,
 
-    // This PLAIN_DATA, MAPPED, or EXTERNAL buffer (only WASM and USER_OWNED
-    // are excluded) has been prepared for asm.js and cannot henceforth be
-    // transferred/detached.
+    // This MALLOCED, MAPPED, or EXTERNAL buffer has been prepared for asm.js
+    // and cannot henceforth be transferred/detached.  (WASM, USER_OWNED, and
+    // INLINE_DATA buffers can't be prepared for asm.js -- although if an
+    // INLINE_DATA buffer is used with asm.js, it's silently rewritten into a
+    // MALLOCED buffer which *can* be prepared.)
     FOR_ASMJS = 0b100'0000,
   };
 
   static_assert(JS_ARRAYBUFFER_DETACHED_FLAG == DETACHED,
                 "self-hosted code with burned-in constants must use the "
                 "correct DETACHED bit value");
 
  public:
@@ -253,33 +261,40 @@ class ArrayBufferObject : public ArrayBu
     }
 
    public:
     template <BufferKind Kind>
     static BufferContents create(void* data) {
       return BufferContents(static_cast<uint8_t*>(data), Kind);
     }
 
-    static BufferContents createPlainData(void* data) {
-      return BufferContents(static_cast<uint8_t*>(data), PLAIN_DATA);
+    static BufferContents createInlineData(void* data) {
+      return BufferContents(static_cast<uint8_t*>(data), INLINE_DATA);
+    }
+
+    static BufferContents createMalloced(void* data) {
+      return BufferContents(static_cast<uint8_t*>(data), MALLOCED);
     }
 
     static BufferContents createUserOwned(void* data) {
       return BufferContents(static_cast<uint8_t*>(data), USER_OWNED);
     }
 
     static BufferContents createExternal(void* data,
                                          JS::BufferContentsFreeFunc freeFunc,
                                          void* freeUserData = nullptr) {
       return BufferContents(static_cast<uint8_t*>(data), EXTERNAL, freeFunc,
                             freeUserData);
     }
 
     static BufferContents createFailed() {
-      return BufferContents(nullptr, PLAIN_DATA);
+      // There's no harm in tagging this as MALLOCED, even tho obviously it
+      // isn't.  And adding an extra tag purely for this case is a complication
+      // that presently appears avoidable.
+      return BufferContents(nullptr, MALLOCED);
     }
 
     uint8_t* data() const { return data_; }
     BufferKind kind() const { return kind_; }
     JS::BufferContentsFreeFunc freeFunc() const { return free_; }
     void* freeUserData() const { return freeUserData_; }
 
     explicit operator bool() const { return data_ != nullptr; }
@@ -392,22 +407,24 @@ class ArrayBufferObject : public ArrayBu
    * ArrayBuffer.prototype and detached ArrayBuffers.
    */
   bool hasData() const { return getClass() == &class_; }
 
   BufferKind bufferKind() const {
     return BufferKind(flags() & BUFFER_KIND_MASK);
   }
 
-  bool isPlainData() const { return bufferKind() == PLAIN_DATA; }
+  bool isInlineData() const { return bufferKind() == INLINE_DATA; }
+  bool isMalloced() const { return bufferKind() == MALLOCED; }
   bool hasUserOwnedData() const { return bufferKind() == USER_OWNED; }
 
   bool isWasm() const { return bufferKind() == WASM; }
   bool isMapped() const { return bufferKind() == MAPPED; }
   bool isExternal() const { return bufferKind() == EXTERNAL; }
+
   bool isDetached() const { return flags() & DETACHED; }
   bool isPreparedForAsmJS() const { return flags() & FOR_ASMJS; }
 
   // WebAssembly support:
   static MOZ_MUST_USE bool prepareForAsmJS(JSContext* cx,
                                            Handle<ArrayBufferObject*> buffer);
   size_t wasmMappedSize() const;
   mozilla::Maybe<uint32_t> wasmMaxSize() const;
@@ -444,17 +461,18 @@ class ArrayBufferObject : public ArrayBu
   }
 
   bool hasTypedObjectViews() const { return flags() & TYPED_OBJECT_VIEWS; }
 
   void setIsDetached() { setFlags(flags() | DETACHED); }
   void setIsPreparedForAsmJS() {
     MOZ_ASSERT(!isWasm());
     MOZ_ASSERT(!hasUserOwnedData());
-    MOZ_ASSERT(isPlainData() || isMapped() || isExternal());
+    MOZ_ASSERT(!isInlineData());
+    MOZ_ASSERT(isMalloced() || isMapped() || isExternal());
     setFlags(flags() | FOR_ASMJS);
   }
 
   void initialize(size_t byteLength, BufferContents contents,
                   OwnsState ownsState) {
     setByteLength(byteLength);
     setFlags(0);
     setFirstView(nullptr);
--- a/js/src/vm/NativeObject.h
+++ b/js/src/vm/NativeObject.h
@@ -1028,17 +1028,17 @@ class NativeObject : public ShapedObject
   }
 
   MOZ_ALWAYS_INLINE void initSlotUnchecked(uint32_t slot, const Value& value) {
     getSlotAddressUnchecked(slot)->init(this, HeapSlot::Slot, slot, value);
   }
 
   // MAX_FIXED_SLOTS is the biggest number of fixed slots our GC
   // size classes will give an object.
-  static const uint32_t MAX_FIXED_SLOTS = shadow::Object::MAX_FIXED_SLOTS;
+  static constexpr uint32_t MAX_FIXED_SLOTS = shadow::Object::MAX_FIXED_SLOTS;
 
  protected:
   MOZ_ALWAYS_INLINE bool updateSlotsForSpan(JSContext* cx, size_t oldSpan,
                                             size_t newSpan);
 
  private:
   void prepareElementRangeForOverwrite(size_t start, size_t end) {
     MOZ_ASSERT(end <= getDenseInitializedLength());
--- a/js/src/vm/StructuredClone.cpp
+++ b/js/src/vm/StructuredClone.cpp
@@ -1882,17 +1882,17 @@ bool JSStructuredCloneWriter::transferOw
         if (!bufContents) {
           return false;  // out of memory
         }
 
         content = bufContents.data();
         if (bufContents.kind() == ArrayBufferObject::MAPPED) {
           ownership = JS::SCTAG_TMO_MAPPED_DATA;
         } else {
-          MOZ_ASSERT(bufContents.kind() == ArrayBufferObject::PLAIN_DATA,
+          MOZ_ASSERT(bufContents.kind() == ArrayBufferObject::MALLOCED,
                      "failing to handle new ArrayBuffer kind?");
           ownership = JS::SCTAG_TMO_ALLOC_DATA;
         }
         extraData = nbytes;
       }
     } else {
       if (!out.buf.callbacks_ || !out.buf.callbacks_->writeTransfer) {
         return reportDataCloneError(JS_SCERR_TRANSFERABLE);