Bug 945152 - Part 1: Support mapped array buffer type. r=sfink
authorShian-Yow Wu <swu@mozilla.com>
Wed, 16 Apr 2014 19:26:39 +0800
changeset 197305 00e9070e38e66a05fafeb31b88b960ea3d2e3e29
parent 197304 fea1517890061a66ea5b73f5c053e086497b65e3
child 197306 b1de460b83dcb73399d7a400d67203f4bd5a59a0
push id3624
push userasasaki@mozilla.com
push dateMon, 09 Jun 2014 21:49:01 +0000
treeherdermozilla-beta@b1a5da15899a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssfink
bugs945152
milestone31.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 945152 - Part 1: Support mapped array buffer type. r=sfink
js/public/StructuredClone.h
js/src/gc/Memory.cpp
js/src/gc/Memory.h
js/src/jsapi-tests/moz.build
js/src/jsapi-tests/testMappedArrayBuffer.cpp
js/src/jsapi.h
js/src/jsfriendapi.h
js/src/vm/ArrayBufferObject.cpp
js/src/vm/ArrayBufferObject.h
js/src/vm/StructuredClone.cpp
--- a/js/public/StructuredClone.h
+++ b/js/public/StructuredClone.h
@@ -35,20 +35,23 @@ enum TransferableOwnership {
     SCTAG_TMO_FIRST_OWNED = 2,
 
     // Data is a pointer that can be freed
     SCTAG_TMO_ALLOC_DATA = 2,
 
     // Data is a SharedArrayBufferObject's buffer
     SCTAG_TMO_SHARED_BUFFER = 3,
 
+    // Data is a memory mapped pointer
+    SCTAG_TMO_MAPPED_DATA = 4,
+
     // Data is embedding-specific. The engine can free it by calling the
     // freeTransfer op. The embedding can also use SCTAG_TMO_USER_MIN and
     // greater, up to 32 bits, to distinguish specific ownership variants.
-    SCTAG_TMO_CUSTOM = 4,
+    SCTAG_TMO_CUSTOM = 5,
 
     SCTAG_TMO_USER_MIN
 };
 } /* namespace JS */
 
 // Read structured data from the reader r. This hook is used to read a value
 // previously serialized by a call to the WriteStructuredCloneOp hook.
 //
--- a/js/src/gc/Memory.cpp
+++ b/js/src/gc/Memory.cpp
@@ -103,16 +103,30 @@ size_t
 gc::GetPageFaultCount()
 {
     PROCESS_MEMORY_COUNTERS pmc;
     if (!GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc)))
         return 0;
     return pmc.PageFaultCount;
 }
 
+void *
+gc::AllocateMappedContent(int fd, size_t offset, size_t length, size_t alignment)
+{
+    // TODO: Bug 988813 - Support memory mapped array buffer for Windows platform.
+    return nullptr;
+}
+
+// Deallocate mapped memory for object.
+void
+gc::DeallocateMappedContent(void *p, size_t length)
+{
+    // TODO: Bug 988813 - Support memory mapped array buffer for Windows platform.
+}
+
 #elif defined(SOLARIS)
 
 #include <sys/mman.h>
 #include <unistd.h>
 
 #ifndef MAP_NOSYNC
 # define MAP_NOSYNC 0
 #endif
@@ -161,20 +175,37 @@ gc::MarkPagesInUse(JSRuntime *rt, void *
 }
 
 size_t
 gc::GetPageFaultCount()
 {
     return 0;
 }
 
+void *
+gc::AllocateMappedContent(int fd, size_t offset, size_t length, size_t alignment)
+{
+    // Not implemented.
+    return nullptr;
+}
+
+// Deallocate mapped memory for object.
+void
+gc::DeallocateMappedContent(void *p, size_t length)
+{
+    // Not implemented.
+}
+
 #elif defined(XP_UNIX)
 
+#include <algorithm>
 #include <sys/mman.h>
 #include <sys/resource.h>
+#include <sys/stat.h>
+#include <sys/types.h>
 #include <unistd.h>
 
 void
 gc::InitMemorySubsystem(JSRuntime *rt)
 {
     rt->gcSystemPageSize = rt->gcSystemAllocGranularity = size_t(sysconf(_SC_PAGESIZE));
 }
 
@@ -281,11 +312,72 @@ gc::GetPageFaultCount()
 {
     struct rusage usage;
     int err = getrusage(RUSAGE_SELF, &usage);
     if (err)
         return 0;
     return usage.ru_majflt;
 }
 
+void *
+gc::AllocateMappedContent(int fd, size_t offset, size_t length, size_t alignment)
+{
+#define NEED_PAGE_ALIGNED 0
+    size_t pa_start; // Page aligned starting
+    size_t pa_end; // Page aligned ending
+    size_t pa_size; // Total page aligned size
+    size_t page_size = sysconf(_SC_PAGESIZE); // Page size
+    struct stat st;
+    uint8_t *buf;
+
+    // Make sure file exists and do sanity check for offset and size.
+    if (fstat(fd, &st) < 0 || offset >= (size_t) st.st_size ||
+        length == 0 || length > (size_t) st.st_size - offset)
+        return nullptr;
+
+    // Check for minimal alignment requirement.
+#if NEED_PAGE_ALIGNED
+    alignment = std::max(alignment, page_size);
+#endif
+    if (offset & (alignment - 1))
+        return nullptr;
+
+    // Page aligned starting of the offset.
+    pa_start = offset & ~(page_size - 1);
+    // Calculate page aligned ending by adding one page to the page aligned
+    // starting of data end position(offset + length - 1).
+    pa_end = ((offset + length - 1) & ~(page_size - 1)) + page_size;
+    pa_size = pa_end - pa_start;
+
+    // Ask for a continuous memory location.
+    buf = (uint8_t *) MapMemory(pa_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
+    if (buf == MAP_FAILED)
+        return nullptr;
+
+    buf = (uint8_t *) mmap(buf, pa_size, PROT_READ | PROT_WRITE,
+                           MAP_PRIVATE | MAP_FIXED, fd, pa_start);
+    if (buf == MAP_FAILED)
+        return nullptr;
+
+    // Reset the data before target file, which we don't need to see.
+    memset(buf, 0, offset - pa_start);
+
+    // Reset the data after target file, which we don't need to see.
+    memset(buf + (offset - pa_start) + length, 0, pa_end - (offset + length));
+
+    return buf + (offset - pa_start);
+}
+
+void
+gc::DeallocateMappedContent(void *p, size_t length)
+{
+    void *pa_start; // Page aligned starting
+    size_t page_size = sysconf(_SC_PAGESIZE); // Page size
+    size_t total_size; // Total allocated size
+
+    pa_start = (void *)(uintptr_t(p) & ~(page_size - 1));
+    total_size = ((uintptr_t(p) + length) & ~(page_size - 1)) + page_size - uintptr_t(pa_start);
+    munmap(pa_start, total_size);
+}
+
 #else
 #error "Memory mapping functions are not defined for your OS."
 #endif
--- a/js/src/gc/Memory.h
+++ b/js/src/gc/Memory.h
@@ -36,12 +36,21 @@ MarkPagesUnused(JSRuntime *rt, void *p, 
 // platforms.
 bool
 MarkPagesInUse(JSRuntime *rt, void *p, size_t size);
 
 // Returns #(hard faults) + #(soft faults)
 size_t
 GetPageFaultCount();
 
+// Allocate memory mapped content.
+// The offset must be aligned according to alignment requirement.
+void *
+AllocateMappedContent(int fd, size_t offset, size_t length, size_t alignment);
+
+// Deallocate memory mapped content.
+void
+DeallocateMappedContent(void *p, size_t length);
+
 } // namespace gc
 } // namespace js
 
 #endif /* gc_Memory_h */
--- a/js/src/jsapi-tests/moz.build
+++ b/js/src/jsapi-tests/moz.build
@@ -40,16 +40,17 @@ UNIFIED_SOURCES += [
     'testHashTableInit.cpp',
     'testIndexToString.cpp',
     'testIntern.cpp',
     'testIntString.cpp',
     'testIntTypesABI.cpp',
     'testJSEvaluateScript.cpp',
     'testLookup.cpp',
     'testLooselyEqual.cpp',
+    'testMappedArrayBuffer.cpp',
     'testNewObject.cpp',
     'testNullRoot.cpp',
     'testObjectEmulatingUndefined.cpp',
     'testOOM.cpp',
     'testOps.cpp',
     'testOriginPrincipals.cpp',
     'testParseJSON.cpp',
     'testPersistentRooted.cpp',
new file mode 100644
--- /dev/null
+++ b/js/src/jsapi-tests/testMappedArrayBuffer.cpp
@@ -0,0 +1,185 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ */
+
+#ifdef XP_UNIX
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "jsfriendapi.h"
+#include "js/StructuredClone.h"
+#include "jsapi-tests/tests.h"
+#include "vm/ArrayBufferObject.h"
+
+const char test_data[] = "1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+const char test_filename[] = "temp-bug945152_MappedArrayBuffer";
+
+BEGIN_TEST(testMappedArrayBuffer_bug945152)
+{
+    TempFile test_file;
+    FILE *test_stream = test_file.open(test_filename);
+    CHECK(fputs(test_data, test_stream) != EOF);
+    test_file.close();
+
+    // Offset 0.
+    CHECK(TestCreateObject(0, 12));
+
+    // Aligned offset.
+    CHECK(TestCreateObject(8, 12));
+
+    // Unaligned offset.
+    CHECK(CreateNewObject(11, 12) == nullptr);
+
+    // Offset + length greater than file size.
+    CHECK(CreateNewObject(8, sizeof(test_data) - 7) == nullptr);
+
+    // Release the mapped content.
+    CHECK(TestReleaseContents());
+
+    // Neuter mapped array buffer.
+    CHECK(TestNeuterObject());
+
+    // Clone mapped array buffer.
+    CHECK(TestCloneObject());
+
+    // Steal mapped array buffer contents.
+    CHECK(TestStealContents());
+
+    // Transfer mapped array buffer contents.
+    CHECK(TestTransferObject());
+
+    test_file.remove();
+
+    return true;
+}
+
+JSObject *CreateNewObject(const int offset, const int length)
+{
+    int fd = open(test_filename, O_RDONLY);
+    void *ptr = JS_CreateMappedArrayBufferContents(fd, offset, length);
+    close(fd);
+    if (!ptr)
+        return nullptr;
+    JSObject *obj = JS_NewMappedArrayBufferWithContents(cx, length, ptr);
+
+    return obj;
+}
+
+bool VerifyObject(JS::HandleObject obj, const int offset, const int length, const bool mapped)
+{
+    CHECK(obj);
+    CHECK(JS_IsArrayBufferObject(obj));
+    CHECK_EQUAL(JS_GetArrayBufferByteLength(obj), length);
+    if (mapped)
+        CHECK(JS_IsMappedArrayBufferObject(obj));
+    else
+        CHECK(!JS_IsMappedArrayBufferObject(obj));
+    const char *data = reinterpret_cast<const char *>(JS_GetArrayBufferData(obj));
+    CHECK(data);
+    CHECK(memcmp(data, test_data + offset, length) == 0);
+
+    return true;
+}
+
+bool TestCreateObject(const int offset, const int length)
+{
+    JS::RootedObject obj(cx, CreateNewObject(offset, length));
+    CHECK(VerifyObject(obj, offset, length, true));
+
+    return true;
+}
+
+bool TestReleaseContents()
+{
+    int fd = open(test_filename, O_RDONLY);
+    void *ptr = JS_CreateMappedArrayBufferContents(fd, 0, 12);
+    close(fd);
+    if (!ptr)
+        return false;
+    JS_ReleaseMappedArrayBufferContents(ptr, 12);
+
+    return true;
+}
+
+bool TestNeuterObject()
+{
+    JS::RootedObject obj(cx, CreateNewObject(8, 12));
+    CHECK(obj);
+    JS_NeuterArrayBuffer(cx, obj);
+    CHECK(isNeutered(obj));
+
+    return true;
+}
+
+bool TestCloneObject()
+{
+    JS::RootedObject obj1(cx, CreateNewObject(8, 12));
+    CHECK(obj1);
+    JSAutoStructuredCloneBuffer cloned_buffer;
+    JS::RootedValue v1(cx, OBJECT_TO_JSVAL(obj1));
+    const JSStructuredCloneCallbacks *callbacks = js::GetContextStructuredCloneCallbacks(cx);
+    CHECK(cloned_buffer.write(cx, v1, callbacks, nullptr));
+    JS::RootedValue v2(cx);
+    CHECK(cloned_buffer.read(cx, &v2, callbacks, nullptr));
+    JS::RootedObject obj2(cx, JSVAL_TO_OBJECT(v2));
+    CHECK(VerifyObject(obj2, 8, 12, false));
+
+    return true;
+}
+
+bool TestStealContents()
+{
+    JS::RootedObject obj(cx, CreateNewObject(8, 12));
+    CHECK(obj);
+    void *contents = JS_StealArrayBufferContents(cx, obj);
+    CHECK(contents);
+    CHECK(memcmp(contents, test_data + 8, 12) == 0);
+    CHECK(isNeutered(obj));
+
+    return true;
+}
+
+bool TestTransferObject()
+{
+    JS::RootedObject obj1(cx, CreateNewObject(8, 12));
+    CHECK(obj1);
+    JS::RootedValue v1(cx, OBJECT_TO_JSVAL(obj1));
+
+    // Create an Array of transferable values.
+    JS::AutoValueVector argv(cx);
+    argv.append(v1);
+    JS::RootedObject obj(cx, JS_NewArrayObject(cx, JS::HandleValueArray::subarray(argv, 0, 1)));
+    CHECK(obj);
+    JS::RootedValue transferable(cx, OBJECT_TO_JSVAL(obj));
+
+    JSAutoStructuredCloneBuffer cloned_buffer;
+    const JSStructuredCloneCallbacks *callbacks = js::GetContextStructuredCloneCallbacks(cx);
+    CHECK(cloned_buffer.write(cx, v1, transferable, callbacks, nullptr));
+    JS::RootedValue v2(cx);
+    CHECK(cloned_buffer.read(cx, &v2, callbacks, nullptr));
+    JS::RootedObject obj2(cx, JSVAL_TO_OBJECT(v2));
+    CHECK(VerifyObject(obj2, 8, 12, true));
+    CHECK(isNeutered(obj1));
+
+    return true;
+}
+
+bool isNeutered(JS::HandleObject obj)
+{
+    JS::RootedValue v(cx);
+    return JS_GetProperty(cx, obj, "byteLength", &v) && v.toInt32() == 0;
+}
+
+static void GC(JSContext *cx)
+{
+    JS_GC(JS_GetRuntime(cx));
+    // Trigger another to wait for background finalization to end.
+    JS_GC(JS_GetRuntime(cx));
+}
+
+END_TEST(testMappedArrayBuffer_bug945152)
+#endif
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -3135,16 +3135,39 @@ JS_AllocateArrayBufferContents(JSContext
 /*
  * Reallocate memory allocated by JS_AllocateArrayBufferContents, growing or
  * shrinking it as appropriate. If oldContents is null then this behaves like
  * JS_AllocateArrayBufferContents.
  */
 extern JS_PUBLIC_API(void *)
 JS_ReallocateArrayBufferContents(JSContext *cx, uint32_t nbytes, void *oldContents, uint32_t oldNbytes);
 
+/*
+ * Create a new mapped array buffer with the given memory mapped contents.
+ */
+extern JS_PUBLIC_API(JSObject *)
+JS_NewMappedArrayBufferWithContents(JSContext *cx, size_t nbytes, void *contents);
+
+/*
+ * Create memory mapped array buffer contents.
+ * Caller must take care of closing fd after calling this function.
+ */
+extern JS_PUBLIC_API(void *)
+JS_CreateMappedArrayBufferContents(int fd, size_t offset, size_t length);
+
+/*
+ * Release the allocated resource of mapped array buffer contents before the
+ * object is created.
+ * If a new object has been created by JS_NewMappedArrayBufferWithContents()
+ * with this content, then JS_NeuterArrayBuffer() should be used instead to
+ * release the resource used by the object.
+ */
+extern JS_PUBLIC_API(void)
+JS_ReleaseMappedArrayBufferContents(void *contents, size_t length);
+
 extern JS_PUBLIC_API(JSIdArray *)
 JS_Enumerate(JSContext *cx, JS::HandleObject obj);
 
 /*
  * Create an object to iterate over enumerable properties of obj, in arbitrary
  * property definition order.  NB: This differs from longstanding for..in loop
  * order, which uses order of property definition in obj.
  */
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -1334,16 +1334,24 @@ JS_IsArrayBufferObject(JSObject *obj);
  * |obj| must have passed a JS_IsArrayBufferObject test, or somehow be known
  * that it would pass such a test: it is an ArrayBuffer or a wrapper of an
  * ArrayBuffer, and the unwrapping will succeed.
  */
 extern JS_FRIEND_API(uint32_t)
 JS_GetArrayBufferByteLength(JSObject *obj);
 
 /*
+ * Check whether the obj is ArrayBufferObject and memory mapped. Note that this
+ * may return false if a security wrapper is encountered that denies the
+ * unwrapping.
+ */
+extern JS_FRIEND_API(bool)
+JS_IsMappedArrayBufferObject(JSObject *obj);
+
+/*
  * Return the number of elements in a typed array.
  *
  * |obj| must have passed a JS_IsTypedArrayObject/JS_Is*Array test, or somehow
  * be known that it would pass such a test: it is a typed array or a wrapper of
  * a typed array, and the unwrapping will succeed.
  */
 extern JS_FRIEND_API(uint32_t)
 JS_GetTypedArrayLength(JSObject *obj);
--- a/js/src/vm/ArrayBufferObject.cpp
+++ b/js/src/vm/ArrayBufferObject.cpp
@@ -29,16 +29,17 @@
 #include "jsutil.h"
 #ifdef XP_WIN
 # include "jswin.h"
 #endif
 #include "jswrapper.h"
 
 #include "gc/Barrier.h"
 #include "gc/Marking.h"
+#include "gc/Memory.h"
 #include "jit/AsmJS.h"
 #include "jit/AsmJSModule.h"
 #include "js/MemoryMetrics.h"
 #include "vm/GlobalObject.h"
 #include "vm/Interpreter.h"
 #include "vm/NumericConversions.h"
 #include "vm/SharedArrayObject.h"
 #include "vm/WrapperObject.h"
@@ -337,17 +338,19 @@ ArrayBufferObject::neuter(JSContext *cx,
 
     for (ArrayBufferViewObject *view = buffer->viewList(); view; view = view->nextView()) {
         view->neuter(newData);
 
         // Notify compiled jit code that the base pointer has moved.
         MarkObjectStateChange(cx, view);
     }
 
-    if (newData != buffer->dataPointer())
+    if (buffer->isMappedArrayBuffer())
+        buffer->changeContents(cx, nullptr);
+    else if (newData != buffer->dataPointer())
         buffer->changeContents(cx, newData);
 
     buffer->setByteLength(0);
     buffer->setViewList(nullptr);
     buffer->setIsNeutered();
 
     // If this is happening during an incremental GC, remove the buffer from
     // the list of live buffers with multiple views if necessary.
@@ -367,16 +370,17 @@ ArrayBufferObject::neuter(JSContext *cx,
     }
 }
 
 void
 ArrayBufferObject::changeContents(JSContext *cx, void *newData)
 {
     JS_ASSERT(!isAsmJSArrayBuffer());
     JS_ASSERT(!isSharedArrayBuffer());
+    JS_ASSERT_IF(isMappedArrayBuffer(), !newData);
 
     // Update all views.
     ArrayBufferViewObject *viewListHead = viewList();
     for (ArrayBufferViewObject *view = viewListHead; view; view = view->nextView()) {
         // Watch out for NULL data pointers in views. This means that the view
         // is not fully initialized (in which case it'll be initialized later
         // with the correct pointer).
         uint8_t *viewDataPointer = view->dataPointer();
@@ -515,16 +519,31 @@ ArrayBufferObject::canNeuterAsmJSArrayBu
         return true;
 
     return false;
 #else
     return true;
 #endif
 }
 
+void *
+ArrayBufferObject::createMappedArrayBuffer(int fd, size_t offset, size_t length)
+{
+    return AllocateMappedContent(fd, offset, length, ARRAY_BUFFER_ALIGNMENT);
+}
+
+void
+ArrayBufferObject::releaseMappedArray()
+{
+    if(!isMappedArrayBuffer() || isNeutered())
+        return;
+
+    DeallocateMappedContent(dataPointer(), byteLength());
+}
+
 void
 ArrayBufferObject::addView(ArrayBufferViewObject *view)
 {
     // Note that pre-barriers are not needed here because either the list was
     // previously empty, in which case no pointer is being overwritten, or the
     // list was nonempty and will be made weak during this call (and weak
     // pointers cannot violate the snapshot-at-the-beginning invariant.)
 
@@ -550,24 +569,26 @@ ArrayBufferObject::dataPointer() const
 
 void
 ArrayBufferObject::releaseData(FreeOp *fop)
 {
     JS_ASSERT(ownsData());
 
     if (isAsmJSArrayBuffer())
         releaseAsmJSArray(fop);
+    else if (isMappedArrayBuffer())
+        releaseMappedArray();
     else
         fop->free_(dataPointer());
 }
 
 void
 ArrayBufferObject::setDataPointer(void *data, OwnsState ownsData)
 {
-    MOZ_ASSERT_IF(!is<SharedArrayBufferObject>(), data != nullptr);
+    MOZ_ASSERT_IF(!is<SharedArrayBufferObject>() && !isMappedArrayBuffer(), data != nullptr);
     setSlot(DATA_SLOT, PrivateValue(data));
     setOwnsData(ownsData);
 }
 
 size_t
 ArrayBufferObject::byteLength() const
 {
     return size_t(getSlot(BYTE_LENGTH_SLOT).toDouble());
@@ -588,18 +609,21 @@ ArrayBufferObject::flags() const
 void
 ArrayBufferObject::setFlags(uint32_t flags)
 {
     setSlot(FLAGS_SLOT, Int32Value(flags));
 }
 
 ArrayBufferObject *
 ArrayBufferObject::create(JSContext *cx, uint32_t nbytes, void *data /* = nullptr */,
-                          NewObjectKind newKind /* = GenericObject */)
+                          NewObjectKind newKind /* = GenericObject */,
+                          bool mapped /* = false */)
 {
+    JS_ASSERT_IF(mapped, data);
+
     // If we need to allocate data, try to use a larger object size class so
     // that the array buffer's data can be allocated inline with the object.
     // The extra space will be left unused by the object's fixed slots and
     // available for the buffer's data, see NewObject().
     size_t reservedSlots = JSCLASS_RESERVED_SLOTS(&class_);
 
     size_t nslots = reservedSlots;
     if (!data) {
@@ -625,16 +649,18 @@ ArrayBufferObject::create(JSContext *cx,
         return nullptr;
     }
     JS_ASSERT(obj->getClass() == &class_);
 
     JS_ASSERT(!gc::IsInsideNursery(cx->runtime(), obj));
 
     if (data) {
         obj->initialize(nbytes, data, OwnsData);
+        if (mapped)
+            obj->setIsMappedArrayBuffer();
     } else {
         void *data = obj->fixedData(reservedSlots);
         memset(data, 0, nbytes);
         obj->initialize(nbytes, data, DoesntOwnData);
     }
 
     return obj;
 }
@@ -707,19 +733,24 @@ ArrayBufferObject::ensureNonInline(JSCon
 ArrayBufferObject::stealContents(JSContext *cx, Handle<ArrayBufferObject*> buffer)
 {
     if (!buffer->canNeuter(cx)) {
         js_ReportOverRecursed(cx);
         return nullptr;
     }
 
     void *oldData = buffer->dataPointer();
-    void *newData = AllocateArrayBufferContents(cx, buffer->byteLength());
-    if (!newData)
-        return nullptr;
+    void *newData;
+    if (buffer->isMappedArrayBuffer())
+        newData = oldData;
+    else {
+        newData = AllocateArrayBufferContents(cx, buffer->byteLength());
+        if (!newData)
+            return nullptr;
+    }
 
     if (buffer->hasStealableContents()) {
         buffer->setOwnsData(DoesntOwnData);
         ArrayBufferObject::neuter(cx, buffer, newData);
         return oldData;
     } else {
         memcpy(newData, oldData, buffer->byteLength());
         ArrayBufferObject::neuter(cx, buffer, oldData);
@@ -1004,17 +1035,17 @@ JS_NewArrayBuffer(JSContext *cx, uint32_
     JS_ASSERT(nbytes <= INT32_MAX);
     return ArrayBufferObject::create(cx, nbytes);
 }
 
 JS_PUBLIC_API(JSObject *)
 JS_NewArrayBufferWithContents(JSContext *cx, size_t nbytes, void *contents)
 {
     JS_ASSERT(contents);
-    return ArrayBufferObject::create(cx, nbytes, contents, TenuredObject);
+    return ArrayBufferObject::create(cx, nbytes, contents, TenuredObject, false);
 }
 
 JS_PUBLIC_API(void *)
 JS_AllocateArrayBufferContents(JSContext *maybecx, uint32_t nbytes)
 {
     return AllocateArrayBufferContents(maybecx, nbytes);
 }
 
@@ -1042,16 +1073,47 @@ JS_StealArrayBufferContents(JSContext *c
         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS);
         return nullptr;
     }
 
     Rooted<ArrayBufferObject*> buffer(cx, &obj->as<ArrayBufferObject>());
     return ArrayBufferObject::stealContents(cx, buffer);
 }
 
+JS_PUBLIC_API(JSObject *)
+JS_NewMappedArrayBufferWithContents(JSContext *cx, size_t nbytes, void *contents)
+{
+    JS_ASSERT(contents);
+    return ArrayBufferObject::create(cx, nbytes, contents, TenuredObject, true);
+}
+
+JS_PUBLIC_API(void *)
+JS_CreateMappedArrayBufferContents(int fd, size_t offset, size_t length)
+{
+    return ArrayBufferObject::createMappedArrayBuffer(fd, offset, length);
+}
+
+JS_PUBLIC_API(void)
+JS_ReleaseMappedArrayBufferContents(void *contents, size_t length)
+{
+    DeallocateMappedContent(contents, length);
+}
+
+JS_FRIEND_API(bool)
+JS_IsMappedArrayBufferObject(JSObject *obj)
+{
+    obj = CheckedUnwrap(obj);
+    if (!obj)
+        return false;
+
+    return obj->is<ArrayBufferObject>()
+           ? obj->as<ArrayBufferObject>().isMappedArrayBuffer()
+           : false;
+}
+
 JS_FRIEND_API(void *)
 JS_GetArrayBufferViewData(JSObject *obj)
 {
     obj = CheckedUnwrap(obj);
     if (!obj)
         return nullptr;
     return obj->is<DataViewObject>() ? obj->as<DataViewObject>().dataPointer()
                                      : obj->as<TypedArrayObject>().viewData();
--- a/js/src/vm/ArrayBufferObject.h
+++ b/js/src/vm/ArrayBufferObject.h
@@ -55,32 +55,34 @@ class ArrayBufferObject : public JSObjec
   public:
     static const uint8_t DATA_SLOT = 0;
     static const uint8_t BYTE_LENGTH_SLOT = 1;
     static const uint8_t VIEW_LIST_SLOT = 2;
     static const uint8_t FLAGS_SLOT = 3;
 
     static const uint8_t RESERVED_SLOTS = 4;
 
+    static const size_t ARRAY_BUFFER_ALIGNMENT = 8;
+
     static const Class class_;
 
     static const Class protoClass;
     static const JSFunctionSpec jsfuncs[];
     static const JSFunctionSpec jsstaticfuncs[];
 
     static bool byteLengthGetter(JSContext *cx, unsigned argc, Value *vp);
 
     static bool fun_slice(JSContext *cx, unsigned argc, Value *vp);
 
     static bool fun_isView(JSContext *cx, unsigned argc, Value *vp);
 
     static bool class_constructor(JSContext *cx, unsigned argc, Value *vp);
 
     static ArrayBufferObject *create(JSContext *cx, uint32_t nbytes, void *contents = nullptr,
-                                     NewObjectKind newKind = GenericObject);
+                                     NewObjectKind newKind = GenericObject, bool mapped = false);
 
     static JSObject *createSlice(JSContext *cx, Handle<ArrayBufferObject*> arrayBuffer,
                                  uint32_t begin, uint32_t end);
 
     static bool createDataViewForThisImpl(JSContext *cx, CallArgs args);
     static bool createDataViewForThis(JSContext *cx, unsigned argc, Value *vp);
 
     template<typename T>
@@ -145,23 +147,26 @@ class ArrayBufferObject : public JSObjec
      * ArrayBuffer.prototype and neutered ArrayBuffers.
      */
     bool hasData() const {
         return getClass() == &class_;
     }
 
     bool isAsmJSArrayBuffer() const { return flags() & ASMJS_BUFFER; }
     bool isSharedArrayBuffer() const { return flags() & SHARED_BUFFER; }
+    bool isMappedArrayBuffer() const { return flags() & MAPPED_BUFFER; }
     bool isNeutered() const { return flags() & NEUTERED_BUFFER; }
 
     static bool prepareForAsmJS(JSContext *cx, Handle<ArrayBufferObject*> buffer);
     static bool canNeuterAsmJSArrayBuffer(JSContext *cx, ArrayBufferObject &buffer);
 
     static void finalize(FreeOp *fop, JSObject *obj);
 
+    static void *createMappedArrayBuffer(int fd, size_t offset, size_t length);
+
     static size_t flagsOffset() {
         return getFixedSlotOffset(FLAGS_SLOT);
     }
 
     static uint32_t neuteredFlag() { return NEUTERED_BUFFER; }
 
   protected:
     enum OwnsState {
@@ -183,17 +188,18 @@ class ArrayBufferObject : public JSObjec
         // The dataPointer() is owned by this buffer and should be released
         // when no longer in use. Releasing the pointer may be done by either
         // freeing or unmapping it, and how to do this is determined by the
         // buffer's other flags.
         OWNS_DATA          =  0x2,
 
         ASMJS_BUFFER       =  0x4,
         SHARED_BUFFER      =  0x8,
-        NEUTERED_BUFFER    = 0x10
+        MAPPED_BUFFER      = 0x10,
+        NEUTERED_BUFFER    = 0x20
     };
 
     uint32_t flags() const;
     void setFlags(uint32_t flags);
 
     bool inLiveList() const { return flags() & IN_LIVE_LIST; }
     void setInLiveList(bool value) {
         setFlags(value ? (flags() | IN_LIVE_LIST) : (flags() & ~IN_LIVE_LIST));
@@ -201,26 +207,28 @@ class ArrayBufferObject : public JSObjec
 
     bool ownsData() const { return flags() & OWNS_DATA; }
     void setOwnsData(OwnsState owns) {
         setFlags(owns ? (flags() | OWNS_DATA) : (flags() & ~OWNS_DATA));
     }
 
     void setIsAsmJSArrayBuffer() { setFlags(flags() | ASMJS_BUFFER); }
     void setIsSharedArrayBuffer() { setFlags(flags() | SHARED_BUFFER); }
+    void setIsMappedArrayBuffer() { setFlags(flags() | MAPPED_BUFFER); }
     void setIsNeutered() { setFlags(flags() | NEUTERED_BUFFER); }
 
     void initialize(size_t byteLength, void *data, OwnsState ownsState) {
         setByteLength(byteLength);
         setFlags(0);
         setViewListNoBarrier(nullptr);
         setDataPointer(data, ownsState);
     }
 
     void releaseAsmJSArray(FreeOp *fop);
+    void releaseMappedArray();
 };
 
 /*
  * ArrayBufferViewObject
  *
  * Common definitions shared by all ArrayBufferViews.
  */
 
--- a/js/src/vm/StructuredClone.cpp
+++ b/js/src/vm/StructuredClone.cpp
@@ -383,16 +383,18 @@ Discard(uint64_t *buffer, size_t nbytes,
 
         uint64_t extraData = LittleEndian::readUint64(point++);
 
         if (ownership < JS::SCTAG_TMO_FIRST_OWNED)
             continue;
 
         if (ownership == JS::SCTAG_TMO_ALLOC_DATA) {
             js_free(content);
+        } else if (ownership == JS::SCTAG_TMO_MAPPED_DATA) {
+            JS_ReleaseMappedArrayBufferContents(content, extraData);
         } else if (ownership == JS::SCTAG_TMO_SHARED_BUFFER) {
             SharedArrayRawBuffer *raw = static_cast<SharedArrayRawBuffer*>(content);
             if (raw)
                 raw->dropReference();
         } else if (cb && cb->freeTransfer) {
             cb->freeTransfer(tag, JS::TransferableOwnership(ownership), content, extraData, cbClosure);
         } else {
             MOZ_ASSERT(false, "unknown ownership");
@@ -843,16 +845,17 @@ JSStructuredCloneWriter::writeTypedArray
 
     return out.write(tarr->byteOffset());
 }
 
 bool
 JSStructuredCloneWriter::writeArrayBuffer(HandleObject obj)
 {
     ArrayBufferObject &buffer = obj->as<ArrayBufferObject>();
+
     return out.writePair(SCTAG_ARRAY_BUFFER_OBJECT, buffer.byteLength()) &&
            out.writeBytes(buffer.dataPointer(), buffer.byteLength());
 }
 
 bool
 JSStructuredCloneWriter::startObject(HandleObject obj, bool *backref)
 {
     /* Handle cycles in the object graph. */
@@ -1031,17 +1034,20 @@ JSStructuredCloneWriter::transferOwnersh
 #endif
 
         if (obj->is<ArrayBufferObject>()) {
             size_t nbytes = obj->as<ArrayBufferObject>().byteLength();
             content = JS_StealArrayBufferContents(context(), obj);
             if (!content)
                 return false; // Destructor will clean up the already-transferred data
             tag = SCTAG_TRANSFER_MAP_ARRAY_BUFFER;
-            ownership = JS::SCTAG_TMO_ALLOC_DATA;
+            if (obj->as<ArrayBufferObject>().isMappedArrayBuffer())
+                ownership = JS::SCTAG_TMO_MAPPED_DATA;
+            else
+                ownership = JS::SCTAG_TMO_ALLOC_DATA;
             extraData = nbytes;
         } else if (obj->is<SharedArrayBufferObject>()) {
             SharedArrayRawBuffer *rawbuf = obj->as<SharedArrayBufferObject>().rawBufferObject();
 
             // Avoids a race condition where the parent thread frees the buffer
             // before the child has accepted the transferable.
             rawbuf->addReference();
 
@@ -1542,18 +1548,22 @@ JSStructuredCloneReader::readTransferMap
             return false;
 
         uint64_t extraData;
         if (!in.read(&extraData))
             return false;
 
         if (tag == SCTAG_TRANSFER_MAP_ARRAY_BUFFER) {
             size_t nbytes = extraData;
-            JS_ASSERT(data == JS::SCTAG_TMO_ALLOC_DATA);
-            obj = JS_NewArrayBufferWithContents(cx, nbytes, content);
+            JS_ASSERT(data == JS::SCTAG_TMO_ALLOC_DATA ||
+                      data == JS::SCTAG_TMO_MAPPED_DATA);
+            if (data == JS::SCTAG_TMO_ALLOC_DATA)
+                obj = JS_NewArrayBufferWithContents(cx, nbytes, content);
+            else if (data == JS::SCTAG_TMO_MAPPED_DATA)
+                obj = JS_NewMappedArrayBufferWithContents(cx, nbytes, content);
         } else if (tag == SCTAG_TRANSFER_MAP_SHARED_BUFFER) {
             JS_ASSERT(data == JS::SCTAG_TMO_SHARED_BUFFER);
             obj = SharedArrayBufferObject::New(context(), (SharedArrayRawBuffer *)content);
         } else {
             if (!callbacks || !callbacks->readTransfer) {
                 ReportErrorTransferable(cx, callbacks);
                 return false;
             }