Bug 1284156 - Baldr: prevent use of wasm buffers for asm.js (r=bbouvier)
authorLuke Wagner <luke@mozilla.com>
Fri, 09 Sep 2016 10:50:50 -0500
changeset 354714 d6bdd39e759b184d5d84b3c22fdd894aeb43464a
parent 354713 6086880cb49abfc9d0adba2b59d980d5c54bc027
child 354715 5114a02a7b19f1fb097b5259ae7f0d8e8097cd1e
push id6570
push userraliiev@mozilla.com
push dateMon, 14 Nov 2016 12:26:13 +0000
treeherdermozilla-beta@f455459b2ae5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbbouvier
bugs1284156
milestone51.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 1284156 - Baldr: prevent use of wasm buffers for asm.js (r=bbouvier) MozReview-Commit-ID: POJm82OLqD
js/src/asmjs/AsmJS.cpp
js/src/asmjs/WasmModule.cpp
js/src/jit-test/tests/asm.js/testAsmJSWasmMixing.js
js/src/js.msg
js/src/vm/ArrayBufferObject-inl.h
js/src/vm/ArrayBufferObject.cpp
js/src/vm/ArrayBufferObject.h
js/src/vm/StructuredClone.cpp
--- a/js/src/asmjs/AsmJS.cpp
+++ b/js/src/asmjs/AsmJS.cpp
@@ -7845,23 +7845,22 @@ CheckBuffer(JSContext* cx, const AsmJSMe
 #ifdef WASM_HUGE_MEMORY
         bool needGuard = true;
 #else
         bool needGuard = metadata.usesSimd;
 #endif
         Rooted<ArrayBufferObject*> arrayBuffer(cx, &buffer->as<ArrayBufferObject>());
         if (!ArrayBufferObject::prepareForAsmJS(cx, arrayBuffer, needGuard))
             return LinkFail(cx, "Unable to prepare ArrayBuffer for asm.js use");
-
-        MOZ_ASSERT(arrayBuffer->isAsmJSMalloced() || arrayBuffer->isWasmMapped());
     } else {
         if (!buffer->as<SharedArrayBufferObject>().isPreparedForAsmJS())
             return LinkFail(cx, "SharedArrayBuffer must be created with wasm test mode enabled");
     }
 
+    MOZ_ASSERT(buffer->isPreparedForAsmJS());
     return true;
 }
 
 static bool
 GetImports(JSContext* cx, const AsmJSMetadata& metadata, HandleValue globalVal,
            HandleValue importVal, MutableHandle<FunctionVector> funcImports, ValVector* valImports)
 {
     Rooted<FunctionVector> ffis(cx, FunctionVector(cx));
--- a/js/src/asmjs/WasmModule.cpp
+++ b/js/src/asmjs/WasmModule.cpp
@@ -561,31 +561,31 @@ Module::instantiateMemory(JSContext* cx,
         MOZ_ASSERT(dataSegments_.empty());
         return true;
     }
 
     uint32_t declaredMin = metadata_->minMemoryLength;
     Maybe<uint32_t> declaredMax = metadata_->maxMemoryLength;
 
     if (memory) {
-        RootedArrayBufferObjectMaybeShared buffer(cx, &memory->buffer());
-        MOZ_RELEASE_ASSERT(buffer->is<SharedArrayBufferObject>() ||
-                           buffer->as<ArrayBufferObject>().isWasm());
+        ArrayBufferObjectMaybeShared& buffer = memory->buffer();
+        MOZ_ASSERT_IF(metadata_->isAsmJS(), buffer.isPreparedForAsmJS());
+        MOZ_ASSERT_IF(!metadata_->isAsmJS(), buffer.as<ArrayBufferObject>().isWasm());
 
-        uint32_t actualLength = buffer->wasmActualByteLength();
+        uint32_t actualLength = buffer.wasmActualByteLength();
         if (actualLength < declaredMin || actualLength > declaredMax.valueOr(UINT32_MAX)) {
             JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_IMP_SIZE, "Memory");
             return false;
         }
 
         if (metadata_->isAsmJS()) {
             MOZ_ASSERT(IsValidAsmJSHeapLength(actualLength));
-            MOZ_ASSERT(actualLength == buffer->wasmMaxSize().value());
+            MOZ_ASSERT(actualLength == buffer.wasmMaxSize().value());
         } else {
-            Maybe<uint32_t> actualMax = buffer->as<ArrayBufferObject>().wasmMaxSize();
+            Maybe<uint32_t> actualMax = buffer.as<ArrayBufferObject>().wasmMaxSize();
             if (declaredMax.isSome() != actualMax.isSome() || declaredMax < actualMax) {
                 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_IMP_SIZE, "Memory");
                 return false;
             }
         }
     } else {
         MOZ_ASSERT(!metadata_->isAsmJS());
         MOZ_ASSERT(metadata_->memoryUsage == MemoryUsage::Unshared);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/asm.js/testAsmJSWasmMixing.js
@@ -0,0 +1,25 @@
+load(libdir + "asm.js");
+load(libdir + "wasm.js");
+
+const Module = WebAssembly.Module;
+const Instance = WebAssembly.Instance;
+const Memory = WebAssembly.Memory;
+
+var asmJS = asmCompile('stdlib', 'ffis', 'buf', USE_ASM + 'var i32 = new stdlib.Int32Array(buf); return {}');
+
+var asmJSBuf = new ArrayBuffer(BUF_MIN);
+asmLink(asmJS, this, null, asmJSBuf);
+
+var wasmMem = evalText('(module (memory 1 1) (export "mem" memory))').exports.mem;
+assertAsmLinkFail(asmJS, this, null, wasmMem.buffer);
+
+if (!getBuildConfiguration().x64 && isSimdAvailable() && this["SIMD"]) {
+    var simdJS = asmCompile('stdlib', 'ffis', 'buf', USE_ASM + 'var i32 = new stdlib.Int32Array(buf); var i32x4 = stdlib.SIMD.Int32x4; return {}');
+    assertAsmLinkFail(simdJS, this, null, asmJSBuf);
+    assertAsmLinkFail(simdJS, this, null, wasmMem.buffer);
+
+    var simdJSBuf = new ArrayBuffer(BUF_MIN);
+    asmLink(simdJS, this, null, simdJSBuf);
+    asmLink(simdJS, this, null, simdJSBuf);  // multiple SIMD.js instantiations succeed
+    assertAsmLinkFail(asmJS, this, null, simdJSBuf);  // but not asm.js
+}
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -362,17 +362,17 @@ MSG_DEF(JSMSG_WASM_BAD_TABLE_VALUE,    0
 MSG_DEF(JSMSG_WASM_BAD_I64,            0, JSEXN_TYPEERR,     "cannot pass i64 to or from JS")
 MSG_DEF(JSMSG_WASM_BAD_FIT,            2, JSEXN_RANGEERR,    "{0} segment does not fit in {1}")
 MSG_DEF(JSMSG_WASM_UNREACHABLE,        0, JSEXN_ERR,         "unreachable executed")
 MSG_DEF(JSMSG_WASM_INTEGER_OVERFLOW,   0, JSEXN_ERR,         "integer overflow")
 MSG_DEF(JSMSG_WASM_INVALID_CONVERSION, 0, JSEXN_ERR,         "invalid conversion to integer")
 MSG_DEF(JSMSG_WASM_INT_DIVIDE_BY_ZERO, 0, JSEXN_ERR,         "integer divide by zero")
 MSG_DEF(JSMSG_WASM_UNALIGNED_ACCESS,   0, JSEXN_ERR,         "unaligned memory access")
 MSG_DEF(JSMSG_WASM_OVERRECURSED,       0, JSEXN_INTERNALERR, "call stack exhausted")
-MSG_DEF(JSMSG_WASM_NO_TRANSFER,        0, JSEXN_INTERNALERR, "cannot transfer buffer aliasing WebAssembly Memory")
+MSG_DEF(JSMSG_WASM_NO_TRANSFER,        0, JSEXN_INTERNALERR, "cannot transfer WebAssembly/asm.js ArrayBuffer")
 
 // Proxy
 MSG_DEF(JSMSG_BAD_TRAP_RETURN_VALUE,   2, JSEXN_TYPEERR,"trap {1} for {0} returned a primitive value")
 MSG_DEF(JSMSG_BAD_GETPROTOTYPEOF_TRAP_RETURN,0,JSEXN_TYPEERR,"proxy getPrototypeOf handler returned a non-object, non-null value")
 MSG_DEF(JSMSG_INCONSISTENT_GETPROTOTYPEOF_TRAP,0,JSEXN_TYPEERR,"proxy getPrototypeOf handler didn't return the target object's prototype")
 MSG_DEF(JSMSG_PROXY_SETPROTOTYPEOF_RETURNED_FALSE, 0, JSEXN_TYPEERR, "proxy setPrototypeOf handler returned false")
 MSG_DEF(JSMSG_PROXY_ISEXTENSIBLE_RETURNED_FALSE,0,JSEXN_TYPEERR,"proxy isExtensible handler must return the same extensibility as target")
 MSG_DEF(JSMSG_INCONSISTENT_SETPROTOTYPEOF_TRAP,0,JSEXN_TYPEERR,"proxy setPrototypeOf handler returned true, even though the target's prototype is immutable because the target is non-extensible")
--- a/js/src/vm/ArrayBufferObject-inl.h
+++ b/js/src/vm/ArrayBufferObject-inl.h
@@ -66,16 +66,24 @@ WasmArrayBufferActualByteLength(const Ar
 inline mozilla::Maybe<uint32_t>
 WasmArrayBufferMaxSize(const ArrayBufferObjectMaybeShared* buf)
 {
     if (buf->is<ArrayBufferObject>())
         return buf->as<ArrayBufferObject>().wasmMaxSize();
     return mozilla::Some(buf->as<SharedArrayBufferObject>().byteLength());
 }
 
+inline bool
+AnyArrayBufferIsPreparedForAsmJS(const ArrayBufferObjectMaybeShared* buf)
+{
+    if (buf->is<ArrayBufferObject>())
+        return buf->as<ArrayBufferObject>().isPreparedForAsmJS();
+    return buf->as<SharedArrayBufferObject>().isPreparedForAsmJS();
+}
+
 inline ArrayBufferObjectMaybeShared&
 AsAnyArrayBuffer(HandleValue val)
 {
     if (val.toObject().is<ArrayBufferObject>())
         return val.toObject().as<ArrayBufferObject>();
     return val.toObject().as<SharedArrayBufferObject>();
 }
 
--- a/js/src/vm/ArrayBufferObject.cpp
+++ b/js/src/vm/ArrayBufferObject.cpp
@@ -270,16 +270,17 @@ NoteViewBufferWasDetached(ArrayBufferVie
 
 /* static */ void
 ArrayBufferObject::detach(JSContext* cx, Handle<ArrayBufferObject*> buffer,
                           BufferContents newContents)
 {
     assertSameCompartment(cx, buffer);
 
     MOZ_ASSERT(!buffer->isWasm());
+    MOZ_ASSERT(!buffer->isPreparedForAsmJS());
 
     // When detaching buffers where we don't know all views, the new data must
     // match the old data. All missing views are typed objects, which do not
     // expect their data to ever change.
     MOZ_ASSERT_IF(buffer->forInlineTypedObject(),
                   newContents.data() == buffer->dataPointer());
 
     // When detaching a buffer with typed object views, any jitcode accessing
@@ -645,17 +646,17 @@ WasmArrayRawBuffer::Release(void* mem)
 #  if defined(MOZ_VALGRIND) && defined(VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE)
     VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE(base, mappedSizeWithHeader);
 #  endif
 }
 
 WasmArrayRawBuffer*
 ArrayBufferObject::BufferContents::wasmBuffer() const
 {
-    MOZ_RELEASE_ASSERT(kind_ == WASM_MAPPED);
+    MOZ_RELEASE_ASSERT(kind_ == WASM);
     return (WasmArrayRawBuffer*)(data_ - sizeof(WasmArrayRawBuffer));
 }
 
 #define ROUND_UP(v, a) ((v) % (a) == 0 ? (v) : v + a - ((v) % (a)))
 
 /* static */ ArrayBufferObject*
 ArrayBufferObject::createForWasm(JSContext* cx, uint32_t initialSize, Maybe<uint32_t> maxSize)
 {
@@ -703,84 +704,83 @@ ArrayBufferObject::createForWasm(JSConte
         }
 
         // Try to grow our chunk as much as possible.
         for (size_t d = cur / 2; d >= wasm::PageSize; d /= 2)
             wasmBuf->tryGrowMaxSizeInPlace(ROUND_UP(d, wasm::PageSize));
 #endif
     }
 
-    auto contents = BufferContents::create<WASM_MAPPED>(wasmBuf->dataPointer());
+    auto contents = BufferContents::create<WASM>(wasmBuf->dataPointer());
     buffer->initialize(initialSize, contents, OwnsData);
     cx->zone()->updateMallocCounter(wasmBuf->mappedSize());
     return buffer;
 }
 
+// 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, bool needGuard)
 {
 #ifdef WASM_HUGE_MEMORY
     MOZ_ASSERT(needGuard);
 #endif
     MOZ_ASSERT(buffer->byteLength() % wasm::PageSize == 0);
     MOZ_RELEASE_ASSERT(wasm::HaveSignalHandlers());
 
-    if (buffer->forInlineTypedObject()) {
-        JS_ReportError(cx, "ArrayBuffer can't be used by asm.js");
+    if (buffer->forInlineTypedObject())
         return false;
-    }
-
-    // Since asm.js doesn't grow, assume max is same as length.
-    uint32_t length = buffer->byteLength();
-    uint32_t maxSize = length;
 
     if (needGuard) {
-        if (buffer->isWasmMapped())
+        if (buffer->isWasm() && buffer->isPreparedForAsmJS())
             return true;
 
-        if (buffer->isAsmJSMalloced()) {
-            // needGuard is only set for SIMD.js (which isn't shipping, so this
-            // error isn't content-visible).
-            JS_ReportError(cx, "ArrayBuffer can't be prepared for asm.js with SIMD.js");
+        // Non-prepared-for-asm.js wasm buffers can be detached at any time.
+        // This error can only be triggered for SIMD.js (which isn't shipping)
+        // on !WASM_HUGE_MEMORY so this error is only visible in testing.
+        if (buffer->isWasm() || buffer->isPreparedForAsmJS())
             return false;
-        }
 
-        WasmArrayRawBuffer* wasmBuf = WasmArrayRawBuffer::Allocate(length, Some(maxSize));
+        uint32_t length = buffer->byteLength();
+        WasmArrayRawBuffer* wasmBuf = WasmArrayRawBuffer::Allocate(length, Some(length));
         if (!wasmBuf) {
-            // Note - we don't need the same backoff search as in WASM, since we don't over-map to
-            // allow growth in asm.js
             ReportOutOfMemory(cx);
             return false;
         }
 
-        // Copy over the current contents of the typed array.
         void* data = wasmBuf->dataPointer();
         memcpy(data, buffer->dataPointer(), length);
 
         // Swap the new elements into the ArrayBufferObject. Mark the
         // ArrayBufferObject so we don't do this again.
-        BufferContents newContents = BufferContents::create<WASM_MAPPED>(data);
-        buffer->changeContents(cx, newContents);
+        buffer->changeContents(cx, BufferContents::create<WASM>(data));
+        buffer->setIsPreparedForAsmJS();
         MOZ_ASSERT(data == buffer->dataPointer());
         cx->zone()->updateMallocCounter(wasmBuf->mappedSize());
         return true;
     }
 
-    if (buffer->isAsmJSMalloced())
+    if (!buffer->isWasm() && buffer->isPreparedForAsmJS())
         return true;
 
+    // Non-prepared-for-asm.js wasm buffers can be detached at any time.
+    if (buffer->isWasm())
+        return false;
+
     if (!buffer->ownsData()) {
         BufferContents contents = AllocateArrayBufferContents(cx, buffer->byteLength());
         if (!contents)
             return false;
         memcpy(contents.data(), buffer->dataPointer(), buffer->byteLength());
         buffer->changeContents(cx, contents);
     }
 
-    buffer->setIsAsmJSMalloced();
+    buffer->setIsPreparedForAsmJS();
     return true;
 }
 
 ArrayBufferObject::BufferContents
 ArrayBufferObject::createMappedContents(int fd, size_t offset, size_t length)
 {
     void* data = AllocateMappedContent(fd, offset, length, ARRAY_BUFFER_ALIGNMENT);
     MemProfiler::SampleNative(data, length);
@@ -807,26 +807,27 @@ ArrayBufferObject::dataPointerShared() c
 
 void
 ArrayBufferObject::releaseData(FreeOp* fop)
 {
     MOZ_ASSERT(ownsData());
 
     switch (bufferKind()) {
       case PLAIN:
-      case ASMJS_MALLOCED:
         fop->free_(dataPointer());
         break;
       case MAPPED:
         MemProfiler::RemoveNative(dataPointer());
         DeallocateMappedContent(dataPointer(), byteLength());
         break;
-      case WASM_MAPPED:
+      case WASM:
         WasmArrayRawBuffer::Release(dataPointer());
         break;
+      case KIND_MASK:
+        MOZ_CRASH("bad bufferKind()");
     }
 }
 
 void
 ArrayBufferObject::setDataPointer(BufferContents contents, OwnsState ownsData)
 {
     setSlot(DATA_SLOT, PrivateValue(contents.data()));
     setOwnsData(ownsData);
@@ -844,38 +845,38 @@ ArrayBufferObject::setByteLength(uint32_
 {
     MOZ_ASSERT(length <= INT32_MAX);
     setSlot(BYTE_LENGTH_SLOT, Int32Value(length));
 }
 
 size_t
 ArrayBufferObject::wasmMappedSize() const
 {
-    if (isWasmMapped()) {
+    if (isWasm()) {
         return contents().wasmBuffer()->mappedSize();
     } else {
         // Can use byteLength() instead of actualByteLength since if !wasmMapped()
         // then this is an asm.js buffer, and thus cannot grow.
         return byteLength();
     }
 }
 
 Maybe<uint32_t>
 ArrayBufferObject::wasmMaxSize() const
 {
-    if (isWasmMapped())
+    if (isWasm())
         return contents().wasmBuffer()->maxSize();
     else
         return Some<uint32_t>(byteLength());
 }
 
 uint32_t
 ArrayBufferObject::wasmActualByteLength() const
 {
-    if (isWasmMapped())
+    if (isWasm())
         return contents().wasmBuffer()->actualByteLength();
     else
         return byteLength();
 }
 
 bool
 ArrayBufferObject::wasmGrowToSizeInPlace(uint32_t newSize)
 {
@@ -893,25 +894,25 @@ ArrayBufferObject::wasmMovingGrowToSize(
 
     WasmArrayRawBuffer* newBuf = WasmArrayRawBuffer::Allocate(newSize, Nothing());
     if (!newBuf)
         return false;
 
     void* newData = newBuf->dataPointer();
     memcpy(newData, curBuf->dataPointer(), curBuf->actualByteLength());
 
-    BufferContents newContents = BufferContents::create<WASM_MAPPED>(newData);
+    BufferContents newContents = BufferContents::create<WASM>(newData);
     changeContents(GetJSContextFromMainThread(), newContents);
     return true;
 }
 
 uint32_t
 ArrayBufferObject::wasmBoundsCheckLimit() const
 {
-    if (isWasmMapped())
+    if (isWasm())
         return contents().wasmBuffer()->boundsCheckLimit();
     else
         return byteLength();
 }
 
 uint32_t
 ArrayBufferObjectMaybeShared::wasmBoundsCheckLimit() const
 {
@@ -951,17 +952,17 @@ ArrayBufferObject::create(JSContext* cx,
     size_t nslots = reservedSlots;
     bool allocated = false;
     if (contents) {
         if (ownsState == OwnsData) {
             // The ABO is taking ownership, so account the bytes against the zone.
             size_t nAllocated = nbytes;
             if (contents.kind() == MAPPED)
                 nAllocated = JS_ROUNDUP(nbytes, js::gc::SystemPageSize());
-            else if (contents.kind() == WASM_MAPPED)
+            else if (contents.kind() == WASM)
                 nAllocated = contents.wasmBuffer()->allocatedBytes();
             cx->zone()->updateMallocCounter(nAllocated);
         }
     } else {
         MOZ_ASSERT(ownsState == OwnsData);
         size_t usableSlots = NativeObject::MAX_FIXED_SLOTS - reservedSlots;
         if (nbytes <= usableSlots * sizeof(Value)) {
             int newSlots = (nbytes - 1) / sizeof(Value) + 1;
@@ -1100,22 +1101,21 @@ ArrayBufferObject::addSizeOfExcludingThi
 
     switch (buffer.bufferKind()) {
       case PLAIN:
         info->objectsMallocHeapElementsNormal += mallocSizeOf(buffer.dataPointer());
         break;
       case MAPPED:
         info->objectsNonHeapElementsNormal += buffer.byteLength();
         break;
-      case ASMJS_MALLOCED:
-        info->objectsMallocHeapElementsAsmJS += mallocSizeOf(buffer.dataPointer());
-        break;
-      case WASM_MAPPED:
+      case WASM:
         info->objectsNonHeapElementsAsmJS += buffer.byteLength();
         break;
+      case KIND_MASK:
+        MOZ_CRASH("bad bufferKind()");
     }
 }
 
 /* static */ void
 ArrayBufferObject::finalize(FreeOp* fop, JSObject* obj)
 {
     ArrayBufferObject& buffer = obj->as<ArrayBufferObject>();
 
@@ -1519,18 +1519,18 @@ JS_DetachArrayBuffer(JSContext* cx, Hand
 
     if (!obj->is<ArrayBufferObject>()) {
         JS_ReportError(cx, "ArrayBuffer object required");
         return false;
     }
 
     Rooted<ArrayBufferObject*> buffer(cx, &obj->as<ArrayBufferObject>());
 
-    if (buffer->isWasm()) {
-        JS_ReportError(cx, "Cannot detach WebAssembly ArrayBuffer");
+    if (buffer->isWasm() || buffer->isPreparedForAsmJS()) {
+        JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_NO_TRANSFER);
         return false;
     }
 
     ArrayBufferObject::BufferContents newContents =
         buffer->hasStealableContents() ? ArrayBufferObject::BufferContents::createPlain(nullptr)
                                        : buffer->contents();
 
     ArrayBufferObject::detach(cx, buffer, newContents);
@@ -1627,17 +1627,17 @@ JS_StealArrayBufferContents(JSContext* c
     }
 
     Rooted<ArrayBufferObject*> buffer(cx, &obj->as<ArrayBufferObject>());
     if (buffer->isDetached()) {
         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
         return nullptr;
     }
 
-    if (buffer->isWasm()) {
+    if (buffer->isWasm() || buffer->isPreparedForAsmJS()) {
         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_NO_TRANSFER);
         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.
--- a/js/src/vm/ArrayBufferObject.h
+++ b/js/src/vm/ArrayBufferObject.h
@@ -73,16 +73,17 @@ class WasmArrayRawBuffer;
 
 class ArrayBufferObjectMaybeShared;
 
 uint32_t AnyArrayBufferByteLength(const ArrayBufferObjectMaybeShared* buf);
 uint32_t WasmArrayBufferActualByteLength(const ArrayBufferObjectMaybeShared* buf);
 mozilla::Maybe<uint32_t> WasmArrayBufferMaxSize(const ArrayBufferObjectMaybeShared* buf);
 size_t WasmArrayBufferMappedSize(const ArrayBufferObjectMaybeShared* buf);
 bool WasmArrayBufferGrowForWasm(ArrayBufferObjectMaybeShared* buf, uint32_t delta);
+bool AnyArrayBufferIsPreparedForAsmJS(const ArrayBufferObjectMaybeShared* buf);
 ArrayBufferObjectMaybeShared& AsAnyArrayBuffer(HandleValue val);
 
 class ArrayBufferObjectMaybeShared : public NativeObject
 {
   public:
     uint32_t byteLength() {
         return AnyArrayBufferByteLength(this);
     }
@@ -102,16 +103,20 @@ class ArrayBufferObjectMaybeShared : pub
         return WasmArrayBufferMaxSize(this);
     }
     size_t wasmMappedSize() const {
         return WasmArrayBufferMappedSize(this);
     }
 #ifndef WASM_HUGE_MEMORY
     uint32_t wasmBoundsCheckLimit() const;
 #endif
+
+    bool isPreparedForAsmJS() const {
+        return AnyArrayBufferIsPreparedForAsmJS(this);
+    }
 };
 
 typedef Rooted<ArrayBufferObjectMaybeShared*> RootedArrayBufferObjectMaybeShared;
 typedef Handle<ArrayBufferObjectMaybeShared*> HandleArrayBufferObjectMaybeShared;
 typedef MutableHandle<ArrayBufferObjectMaybeShared*> MutableHandleArrayBufferObjectMaybeShared;
 
 /*
  * ArrayBufferObject
@@ -150,19 +155,18 @@ class ArrayBufferObject : public ArrayBu
 
     enum OwnsState {
         DoesntOwnData = 0,
         OwnsData = 1,
     };
 
     enum BufferKind {
         PLAIN               = 0, // malloced or inline data
-        ASMJS_MALLOCED      = 1,
-        WASM_MAPPED         = 2,
-        MAPPED              = 3,
+        WASM                = 1,
+        MAPPED              = 2,
 
         KIND_MASK           = 0x3
     };
 
   protected:
 
     enum ArrayBufferFlags {
         // The flags also store the BufferKind
@@ -183,17 +187,21 @@ class ArrayBufferObject : public ArrayBu
 
         // This array buffer was created lazily for a typed object with inline
         // data. This implies both that the typed object owns the buffer's data
         // and that the list of views sharing this buffer's data might be
         // incomplete. Any missing views will be typed objects.
         FOR_INLINE_TYPED_OBJECT = 0x10,
 
         // Views of this buffer might include typed objects.
-        TYPED_OBJECT_VIEWS  = 0x20
+        TYPED_OBJECT_VIEWS  = 0x20,
+
+        // This PLAIN or WASM buffer has been prepared for asm.js and cannot
+        // henceforth be transferred/detached.
+        FOR_ASMJS           = 0x40
     };
 
     static_assert(JS_ARRAYBUFFER_DETACHED_FLAG == DETACHED,
                   "self-hosted code with burned-in constants must use the "
                   "correct DETACHED bit value");
   public:
 
     class BufferContents {
@@ -273,17 +281,17 @@ class ArrayBufferObject : public ArrayBu
     static void objectMoved(JSObject* obj, const JSObject* old);
 
     static BufferContents stealContents(JSContext* cx,
                                         Handle<ArrayBufferObject*> buffer,
                                         bool hasStealableContents);
 
     bool hasStealableContents() const {
         // Inline elements strictly adhere to the corresponding buffer.
-        return ownsData();
+        return ownsData() && !isPreparedForAsmJS() && !isWasm();
     }
 
     static void addSizeOfExcludingThis(JSObject* obj, mozilla::MallocSizeOf mallocSizeOf,
                                        JS::ClassInfo* info);
 
     // ArrayBufferObjects (strongly) store the first view added to them, while
     // later views are (weakly) stored in the compartment's InnerViewTable
     // below. Buffers usually only have one view, so this slot optimizes for
@@ -327,26 +335,26 @@ 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 isPlain() const { return bufferKind() == PLAIN; }
-    bool isWasmMapped() const { return bufferKind() == WASM_MAPPED; }
-    bool isAsmJSMalloced() const { return bufferKind() == ASMJS_MALLOCED; }
-    bool isWasm() const { return isWasmMapped() || isAsmJSMalloced(); }
+    bool isWasm() const { return bufferKind() == WASM; }
     bool isMapped() const { return bufferKind() == MAPPED; }
     bool isDetached() const { return flags() & DETACHED; }
+    bool isPreparedForAsmJS() const { return flags() & FOR_ASMJS; }
 
     // WebAssembly support:
     static ArrayBufferObject* createForWasm(JSContext* cx, uint32_t initialSize,
                                             mozilla::Maybe<uint32_t> maxSize);
-    static bool prepareForAsmJS(JSContext* cx, Handle<ArrayBufferObject*> buffer, bool needGuard);
+    static MOZ_MUST_USE bool prepareForAsmJS(JSContext* cx, Handle<ArrayBufferObject*> buffer,
+                                             bool needGuard);
     uint32_t wasmActualByteLength() const;
     size_t wasmMappedSize() const;
     mozilla::Maybe<uint32_t> wasmMaxSize() const;
     MOZ_MUST_USE bool wasmGrowToSizeInPlace(uint32_t newSize);
 #ifndef WASM_HUGE_MEMORY
     MOZ_MUST_USE bool wasmMovingGrowToSize(uint32_t newSize);
     uint32_t wasmBoundsCheckLimit() const;
 #endif
@@ -380,18 +388,18 @@ class ArrayBufferObject : public ArrayBu
 
     bool ownsData() const { return flags() & OWNS_DATA; }
     void setOwnsData(OwnsState owns) {
         setFlags(owns ? (flags() | OWNS_DATA) : (flags() & ~OWNS_DATA));
     }
 
     bool hasTypedObjectViews() const { return flags() & TYPED_OBJECT_VIEWS; }
 
-    void setIsAsmJSMalloced() { setFlags((flags() & ~KIND_MASK) | ASMJS_MALLOCED); }
     void setIsDetached() { setFlags(flags() | DETACHED); }
+    void setIsPreparedForAsmJS() { setFlags(flags() | FOR_ASMJS); }
 
     void initialize(size_t byteLength, BufferContents contents, OwnsState ownsState) {
         setByteLength(byteLength);
         setFlags(0);
         setFirstView(nullptr);
         setDataPointer(contents, ownsState);
     }
 
--- a/js/src/vm/StructuredClone.cpp
+++ b/js/src/vm/StructuredClone.cpp
@@ -1468,17 +1468,17 @@ JSStructuredCloneWriter::transferOwnersh
 
         if (cls == ESClass::ArrayBuffer) {
             // The current setup of the array buffer inheritance hierarchy doesn't
             // lend itself well to generic manipulation via proxies.
             Rooted<ArrayBufferObject*> arrayBuffer(context(), &CheckedUnwrap(obj)->as<ArrayBufferObject>());
             JSAutoCompartment ac(context(), arrayBuffer);
             size_t nbytes = arrayBuffer->byteLength();
 
-            if (arrayBuffer->isWasm()) {
+            if (arrayBuffer->isWasm() || arrayBuffer->isPreparedForAsmJS()) {
                 JS_ReportErrorNumber(context(), GetErrorMessage, nullptr, JSMSG_WASM_NO_TRANSFER);
                 return false;
             }
 
             bool hasStealableContents = arrayBuffer->hasStealableContents() &&
                                         (scope != JS::StructuredCloneScope::DifferentProcess);
 
             ArrayBufferObject::BufferContents bufContents =