Bug 1298202 - Baldr: allocate a guard page on wasm 32-bit (r=sunfish)
authorLuke Wagner <luke@mozilla.com>
Thu, 08 Sep 2016 00:09:07 -0500
changeset 354463 7a59052a5a5a313e1d8f66c6731c59385ffb2327
parent 354462 40854f9eaeb5e47d55aae70b35600f7230d78695
child 354464 b96ee3dad4d8eb59ba72e2b5735461ec7cfb2540
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)
reviewerssunfish
bugs1298202
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 1298202 - Baldr: allocate a guard page on wasm 32-bit (r=sunfish) MozReview-Commit-ID: IpYhSxkB4Lb
js/src/asmjs/AsmJS.cpp
js/src/asmjs/AsmJS.h
js/src/asmjs/WasmCode.cpp
js/src/asmjs/WasmModule.cpp
js/src/asmjs/WasmSignalHandlers.cpp
js/src/asmjs/WasmTypes.cpp
js/src/asmjs/WasmTypes.h
js/src/jit-test/tests/wasm/big-resize.js
js/src/jit/MIRGraph.cpp
js/src/vm/ArrayBufferObject-inl.h
js/src/vm/ArrayBufferObject.cpp
js/src/vm/ArrayBufferObject.h
js/src/vm/SharedArrayObject.cpp
--- a/js/src/asmjs/AsmJS.cpp
+++ b/js/src/asmjs/AsmJS.cpp
@@ -61,16 +61,32 @@ using mozilla::Move;
 using mozilla::PodCopy;
 using mozilla::PodEqual;
 using mozilla::PodZero;
 using mozilla::PositiveInfinity;
 using JS::AsmJSOption;
 using JS::GenericNaN;
 
 /*****************************************************************************/
+
+// The asm.js valid heap lengths are precisely the WASM valid heap lengths for ARM
+// greater or equal to MinHeapLength
+static const size_t MinHeapLength = PageSize;
+
+static uint32_t
+RoundUpToNextValidAsmJSHeapLength(uint32_t length)
+{
+    if (length <= MinHeapLength)
+        return MinHeapLength;
+
+    return wasm::RoundUpToNextValidARMImmediate(length);
+}
+
+
+/*****************************************************************************/
 // asm.js module object
 
 // The asm.js spec recognizes this set of builtin Math functions.
 enum AsmJSMathBuiltinFunction
 {
     AsmJSMathBuiltin_sin, AsmJSMathBuiltin_cos, AsmJSMathBuiltin_tan,
     AsmJSMathBuiltin_asin, AsmJSMathBuiltin_acos, AsmJSMathBuiltin_atan,
     AsmJSMathBuiltin_ceil, AsmJSMathBuiltin_floor, AsmJSMathBuiltin_exp,
@@ -8822,29 +8838,16 @@ js::AsmJSFunctionToString(JSContext* cx,
             return nullptr;
         if (!out.append(src))
             return nullptr;
     }
 
     return out.finishString();
 }
 
-// The asm.js valid heap lengths are precisely the WASM valid heap lengths for ARM
-// greater or equal to MinHeapLength
-static const size_t MinHeapLength = PageSize;
-
 bool
 js::IsValidAsmJSHeapLength(uint32_t length)
 {
     if (length < MinHeapLength)
         return false;
 
-    return wasm::IsValidARMLengthImmediate(length);
-}
-
-uint32_t
-js::RoundUpToNextValidAsmJSHeapLength(uint32_t length)
-{
-    if (length <= MinHeapLength)
-        return MinHeapLength;
-
-    return wasm::RoundUpToNextValidARMLengthImmediate(length);
-}
+    return wasm::IsValidARMImmediate(length);
+}
--- a/js/src/asmjs/AsmJS.h
+++ b/js/src/asmjs/AsmJS.h
@@ -79,14 +79,11 @@ AsmJSFunctionToString(JSContext* cx, Han
 extern JSString*
 AsmJSModuleToString(JSContext* cx, HandleFunction fun, bool addParenToLambda);
 
 // asm.js heap:
 
 extern bool
 IsValidAsmJSHeapLength(uint32_t length);
 
-extern uint32_t
-RoundUpToNextValidAsmJSHeapLength(uint32_t length);
-
 } // namespace js
 
 #endif // asmjs_asmjs_h
--- a/js/src/asmjs/WasmCode.cpp
+++ b/js/src/asmjs/WasmCode.cpp
@@ -103,24 +103,25 @@ StaticallyLink(CodeSegment& cs, const Li
 
     *(double*)(cs.globalData() + NaN64GlobalDataOffset) = GenericNaN();
     *(float*)(cs.globalData() + NaN32GlobalDataOffset) = GenericNaN();
 }
 
 static void
 SpecializeToMemory(CodeSegment& cs, const Metadata& metadata, HandleWasmMemoryObject memory)
 {
-    if (!metadata.boundsChecks.empty()) {
-        uint32_t length = memory->buffer().wasmBoundsCheckLimit();
-        MOZ_RELEASE_ASSERT(length == LegalizeMapLength(length));
-        MOZ_RELEASE_ASSERT(length >= memory->buffer().wasmActualByteLength());
+#ifdef WASM_HUGE_MEMORY
+    MOZ_RELEASE_ASSERT(metadata.boundsChecks.empty());
+#else
+    uint32_t limit = memory->buffer().wasmBoundsCheckLimit();
+    MOZ_RELEASE_ASSERT(IsValidBoundsCheckImmediate(limit));
 
-        for (const BoundsCheck& check : metadata.boundsChecks)
-            Assembler::UpdateBoundsCheck(check.patchAt(cs.base()), length);
-    }
+    for (const BoundsCheck& check : metadata.boundsChecks)
+        Assembler::UpdateBoundsCheck(check.patchAt(cs.base()), limit);
+#endif
 
 #if defined(JS_CODEGEN_X86)
     uint8_t* base = memory->buffer().dataPointerEither().unwrap();
     for (const MemoryAccess& access : metadata.memoryAccesses) {
         // Patch memory pointer immediate.
         void* addr = access.patchMemoryPtrImmAt(cs.base());
         uint32_t disp = reinterpret_cast<uint32_t>(X86Encoding::GetPointer(addr));
         MOZ_ASSERT(disp <= INT32_MAX);
--- a/js/src/asmjs/WasmModule.cpp
+++ b/js/src/asmjs/WasmModule.cpp
@@ -571,20 +571,20 @@ Module::instantiateMemory(JSContext* cx,
                            buffer->as<ArrayBufferObject>().isWasm());
 
         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;
         }
 
-        // For asm.js maxMemoryLength doesn't play a role since we can't grow memory.
-        // For wasm we require that either both memory and module don't specify a max size
-        // OR that the memory's max size is less than the modules.
-        if (!metadata_->isAsmJS()) {
+        if (metadata_->isAsmJS()) {
+            MOZ_ASSERT(IsValidAsmJSHeapLength(actualLength));
+            MOZ_ASSERT(actualLength == buffer->wasmMaxSize().value());
+        } else {
             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());
--- a/js/src/asmjs/WasmSignalHandlers.cpp
+++ b/js/src/asmjs/WasmSignalHandlers.cpp
@@ -660,20 +660,20 @@ EmulateHeapAccess(EMULATOR_CONTEXT* cont
 
     // The basic sandbox model is that all heap accesses are a heap base
     // register plus an index, and the index is always computed with 32-bit
     // operations, so we know it can only be 4 GiB off of the heap base.
     //
     // However, we wish to support the optimization of folding immediates
     // and scaled indices into addresses, and any address arithmetic we fold
     // gets done at full pointer width, so it doesn't get properly wrapped.
-    // We support this by extending MappedSize to the greatest size that could
-    // be reached by such an unwrapped address, and then when we arrive here in
-    // the signal handler for such an access, we compute the fully wrapped
-    // address, and perform the load or store on it.
+    // We support this by extending HugeMappedSize to the greatest size that
+    // could be reached by such an unwrapped address, and then when we arrive
+    // here in the signal handler for such an access, we compute the fully
+    // wrapped address, and perform the load or store on it.
     //
     // Taking a signal is really slow, but in theory programs really shouldn't
     // be hitting this anyway.
     intptr_t unwrappedOffset = accessAddress - instance.memoryBase().unwrap(/* for value */);
     uint32_t wrappedOffset = uint32_t(unwrappedOffset);
     size_t size = access.size();
     MOZ_RELEASE_ASSERT(wrappedOffset + size > wrappedOffset);
     bool inBounds = wrappedOffset + size < instance.memoryLength();
--- a/js/src/asmjs/WasmTypes.cpp
+++ b/js/src/asmjs/WasmTypes.cpp
@@ -619,55 +619,67 @@ Assumptions::sizeOfExcludingThis(MallocS
 }
 
 //  Heap length on ARM should fit in an ARM immediate. We approximate the set
 //  of valid ARM immediates with the predicate:
 //    2^n for n in [16, 24)
 //  or
 //    2^24 * n for n >= 1.
 bool
-wasm::IsValidARMLengthImmediate(uint32_t length)
+wasm::IsValidARMImmediate(uint32_t i)
 {
-    bool valid = (IsPowerOfTwo(length) ||
-                  (length & 0x00ffffff) == 0);
+    bool valid = (IsPowerOfTwo(i) ||
+                  (i & 0x00ffffff) == 0);
 
-    MOZ_ASSERT_IF(valid, length % PageSize == 0);
+    MOZ_ASSERT_IF(valid, i % PageSize == 0);
 
     return valid;
 }
 
 uint32_t
-wasm::RoundUpToNextValidARMLengthImmediate(uint32_t length)
+wasm::RoundUpToNextValidARMImmediate(uint32_t i)
 {
-    MOZ_ASSERT(length <= 0xff000000);
+    MOZ_ASSERT(i <= 0xff000000);
+
+    if (i <= 16 * 1024 * 1024)
+        i = i ? mozilla::RoundUpPow2(i) : 0;
+    else
+        i = (i + 0x00ffffff) & ~0x00ffffff;
+
+    MOZ_ASSERT(IsValidARMImmediate(i));
 
-    if (length <= 16 * 1024 * 1024)
-        length = length ? mozilla::RoundUpPow2(length) : 0;
-    else
-        length = (length + 0x00ffffff) & ~0x00ffffff;
+    return i;
+}
+
+#ifndef WASM_HUGE_MEMORY
 
-    MOZ_ASSERT(IsValidARMLengthImmediate(length));
-
-    return length;
+bool
+wasm::IsValidBoundsCheckImmediate(uint32_t i)
+{
+#ifdef JS_CODEGEN_ARM
+    return IsValidARMImmediate(i);
+#else
+    return true;
+#endif
 }
 
 size_t
-wasm::LegalizeMapLength(size_t requestedSize)
+wasm::ComputeMappedSize(uint32_t maxSize)
 {
-#ifdef WASM_HUGE_MEMORY
-    // On 64-bit platforms just give us a 4G guard region
-    return wasm::MappedSize;
-#else
-    uint32_t res = requestedSize;
+    MOZ_ASSERT(maxSize % PageSize == 0);
 
-    // On 32-bit platforms clamp down to 1GB
-    uint32_t MaxMappedSize = (1 << 30);
-    res = Min(res, MaxMappedSize);
+    // It is the bounds-check limit, not the mapped size, that gets baked into
+    // code. Thus round up the maxSize to the next valid immediate value
+    // *before* adding in the guard page.
 
 # ifdef JS_CODEGEN_ARM
-    // On Arm round so that it fits in a single instruction
-    res = RoundUpToNextValidARMLengthImmediate(res);
-    MOZ_RELEASE_ASSERT(res <= MaxMappedSize);
+    uint32_t boundsCheckLimit = RoundUpToNextValidARMImmediate(maxSize);
+# else
+    uint32_t boundsCheckLimit = maxSize;
 # endif
+    MOZ_ASSERT(IsValidBoundsCheckImmediate(boundsCheckLimit));
 
-    return res;
-#endif
+    MOZ_ASSERT(boundsCheckLimit % gc::SystemPageSize() == 0);
+    MOZ_ASSERT(GuardSize % gc::SystemPageSize() == 0);
+    return boundsCheckLimit + GuardSize;
 }
+
+#endif  // WASM_HUGE_MEMORY
--- a/js/src/asmjs/WasmTypes.h
+++ b/js/src/asmjs/WasmTypes.h
@@ -1254,31 +1254,75 @@ struct ExternalTableElem
     //    WasmTableCallSigReg holds the signature id.
     void* code;
 
     // The pointer to the callee's instance's TlsData. This must be loaded into
     // WasmTlsReg before calling 'code'.
     TlsData* tls;
 };
 
-// Constants:
+// Because ARM has a fixed-width instruction encoding, ARM can only express a
+// limited subset of immediates (in a single instruction).
+
+extern bool
+IsValidARMImmediate(uint32_t i);
+
+extern uint32_t
+RoundUpToNextValidARMImmediate(uint32_t i);
 
 // The WebAssembly spec hard-codes the virtual page size to be 64KiB and
-// requires linear memory to always be a multiple of 64KiB.
+// requires the size of linear memory to always be a multiple of 64KiB.
+
 static const unsigned PageSize = 64 * 1024;
 
 #ifdef JS_CODEGEN_X64
-#define WASM_HUGE_MEMORY
+
+// All other code should use WASM_HUGE_MEMORY instead of JS_CODEGEN_X64 so that
+// it is easy to use the huge-mapping optimization for other 64-bit platforms in
+// the future.
+# define WASM_HUGE_MEMORY
+
+// On WASM_HUGE_MEMORY platforms, every asm.js or WebAssembly memory
+// unconditionally allocates a huge region of virtual memory of size
+// wasm::HugeMappedSize. This allows all memory resizing to work without
+// reallocation and provides enough guard space for all offsets to be folded
+// into memory accesses.
+
 static const uint64_t Uint32Range = uint64_t(UINT32_MAX) + 1;
-static const uint64_t MappedSize = 2 * Uint32Range + PageSize;
-#endif
+static const uint64_t HugeMappedSize = 2 * Uint32Range + PageSize;
+
+#else // !WASM_HUGE_MEMORY
+
+// On !WASM_HUGE_MEMORY platforms:
+//  - To avoid OOM in ArrayBuffer::prepareForAsmJS, asm.js continues to use the
+//    original ArrayBuffer allocation which has no guard region at all.
+//  - For WebAssembly memories, additional memory is mapped after the accessible
+//    region of the memory; see the js::WasmArrayRawBuffer comment for more
+//    details.
+
+static const size_t GuardSize = PageSize;
+
+// Return whether the given immediate satisfies the constraints of the platform
+// (viz. that, on ARM, IsValidARMImmediate).
 
-bool IsValidARMLengthImmediate(uint32_t length);
-uint32_t RoundUpToNextValidARMLengthImmediate(uint32_t length);
-size_t LegalizeMapLength(size_t requestedSize);
+extern bool
+IsValidBoundsCheckImmediate(uint32_t i);
+
+// For a given WebAssembly/asm.js max size, return the number of bytes to
+// map which will necessarily be a multiple of the system page size and greater
+// than maxSize. For a returned mappedSize:
+//   boundsCheckLimit = mappedSize - GuardSize
+//   IsValidBoundsCheckImmediate(boundsCheckLimit)
+
+extern size_t
+ComputeMappedSize(uint32_t maxSize);
+
+#endif // WASM_HUGE_MEMORY
+
+// Constants:
 
 static const unsigned NaN64GlobalDataOffset       = 0;
 static const unsigned NaN32GlobalDataOffset       = NaN64GlobalDataOffset + sizeof(double);
 static const unsigned InitialGlobalDataBytes      = NaN32GlobalDataOffset + sizeof(float);
 
 static const unsigned MaxSigs                     =        4 * 1024;
 static const unsigned MaxFuncs                    =      512 * 1024;
 static const unsigned MaxGlobals                  =        4 * 1024;
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/big-resize.js
@@ -0,0 +1,10 @@
+load(libdir + "wasm.js");
+
+assertEq(new WebAssembly.Instance(new WebAssembly.Module(textToBinary(`(module
+    (memory 1 32768)
+    (func $test (result i32)
+        (if (i32.eq (grow_memory (i32.const 16384)) (i32.const -1)) (return (i32.const 42)))
+        (i32.store (i32.const 1073807356) (i32.const 42))
+        (i32.load (i32.const 1073807356)))
+    (export "test" $test)
+)`))).exports.test(), 42);
--- a/js/src/jit/MIRGraph.cpp
+++ b/js/src/jit/MIRGraph.cpp
@@ -126,17 +126,17 @@ MIRGenerator::foldableOffsetRange(const 
     // This determines whether it's ok to fold up to WasmImmediateRange
     // offsets, instead of just WasmCheckedImmediateRange.
 
     static_assert(WasmCheckedImmediateRange <= WasmImmediateRange,
                   "WasmImmediateRange should be the size of an unconstrained "
                   "address immediate");
 
 #ifdef WASM_HUGE_MEMORY
-    static_assert(wasm::Uint32Range + WasmImmediateRange + sizeof(wasm::Val) < wasm::MappedSize,
+    static_assert(wasm::Uint32Range + WasmImmediateRange + sizeof(wasm::Val) < wasm::HugeMappedSize,
                   "When using signal handlers for bounds checking, a uint32 is added to the base "
                   "address followed by an immediate in the range [0, WasmImmediateRange). An "
                   "unaligned access (whose size is conservatively approximated by wasm::Val) may "
                   "spill over, so ensure a space at the end.");
     return WasmImmediateRange;
 #else
     // On 32-bit platforms, if we've proven the access is in bounds after
     // 32-bit wrapping, we can fold full offsets because they're added with
--- a/js/src/vm/ArrayBufferObject-inl.h
+++ b/js/src/vm/ArrayBufferObject-inl.h
@@ -44,30 +44,38 @@ AnyArrayBufferByteLength(const ArrayBuff
 }
 
 inline size_t
 WasmArrayBufferMappedSize(const ArrayBufferObjectMaybeShared* buf)
 {
     if (buf->is<ArrayBufferObject>())
         return buf->as<ArrayBufferObject>().wasmMappedSize();
 #ifdef WASM_HUGE_MEMORY
-    return wasm::MappedSize;
+    return wasm::HugeMappedSize;
 #else
     return buf->as<SharedArrayBufferObject>().byteLength();
 #endif
 }
 
 inline uint32_t
 WasmArrayBufferActualByteLength(const ArrayBufferObjectMaybeShared* buf)
 {
     if (buf->is<ArrayBufferObject>())
         return buf->as<ArrayBufferObject>().wasmActualByteLength();
     return buf->as<SharedArrayBufferObject>().byteLength();
 }
 
+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 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
@@ -381,27 +381,27 @@ ArrayBufferObject::changeContents(JSCont
 
 /*
  * Wasm Raw Buf Linear Memory Structure
  *
  * The linear heap in Wasm is an mmaped array buffer. Several
  * constants manage its lifetime:
  *
  *  - length - the wasm-visible current length of the buffer. Acesses in the
- *  range [0, length] succeed. May only increase
+ *    range [0, length] succeed. May only increase
  *
- *  - boundsCheckLimit - size against which we perform bounds checks. It is
- *  always a constant offset smaller than mapped_size. Currently that constant
- *  offset is 0.
+ *  - boundsCheckLimit - when !WASM_HUGE_MEMORY, the size against which we
+ *    perform bounds checks. It is always a constant offset smaller than
+ *    mappedSize. Currently that constant offset is 0.
  *
  *  - max - the optional declared limit on how much length can grow.
  *
- *  - mapped_size - the actual mmaped size. Access in the range
- *  [0, mapped_size] will either succeed, or be handled by the wasm signal
- *  handlers.
+ *  - mappedSize - the actual mmaped size. Access in the range
+ *    [0, mappedSize] will either succeed, or be handled by the wasm signal
+ *    handlers.
  *
  * The below diagram shows the layout of the wams heap. The wasm-visible
  * portion of the heap starts at 0. There is one extra page prior to the
  * start of the wasm heap which contains the WasmArrayRawBuffer struct at
  * its end. (i.e. right before the start of the WASM heap).
  *
  *  WasmArrayRawBuffer
  *      \    ArrayBufferObject::dataPointer()
@@ -489,24 +489,24 @@ class js::WasmArrayRawBuffer
     Maybe<uint32_t> maxSize() const {
         return maxSize_;
     }
 
     size_t allocatedBytes() const {
         return mappedSize_ + gc::SystemPageSize();
     }
 
+#ifndef WASM_HUGE_MEMORY
     uint32_t boundsCheckLimit() const {
-#ifdef WASM_HUGE_MEMORY
-        MOZ_CRASH();
-        return 0;
-#else
-        return (uint32_t) mappedSize_;
+        MOZ_ASSERT(mappedSize_ <= UINT32_MAX);
+        MOZ_ASSERT(mappedSize_ >= wasm::GuardSize);
+        MOZ_ASSERT(wasm::IsValidBoundsCheckImmediate(mappedSize_ - wasm::GuardSize));
+        return mappedSize_ - wasm::GuardSize;
+    }
 #endif
-    }
 
     MOZ_MUST_USE bool growLength(uint32_t deltaLength)
     {
         // This should be guaranteed by Instance::growMemory
         MOZ_ASSERT(maxSize_);
         MOZ_ASSERT(deltaLength % wasm::PageSize == 0);
 
         CheckedInt<uint32_t> curLength = actualByteLength();
@@ -529,53 +529,64 @@ class js::WasmArrayRawBuffer
 #  endif
 
         MemProfiler::SampleNative(dataEnd, deltaLength);
 
         length_ = newLength.value();
         return true;
     }
 
+#ifndef WASM_HUGE_MEMORY
     // Try and grow the mapped region of memory. Does not changes current or
     // max size. Does not move memory if no space to grow.
     void tryGrowMaxSize(uint32_t deltaMaxSize)
     {
         MOZ_ASSERT(maxSize_);
         MOZ_RELEASE_ASSERT(deltaMaxSize % wasm::PageSize  == 0);
 
-        CheckedInt<uint32_t> curMax = maxSize_.value();
-        CheckedInt<uint32_t> newMax = curMax + deltaMaxSize;
-        MOZ_RELEASE_ASSERT(newMax.isValid());
-        MOZ_RELEASE_ASSERT(newMax.value() % wasm::PageSize == 0);
+        CheckedInt<uint32_t> newMaxSize = maxSize_.value() + deltaMaxSize;
+        MOZ_RELEASE_ASSERT(newMaxSize.isValid());
+        MOZ_RELEASE_ASSERT(newMaxSize.value() % wasm::PageSize == 0);
 
-        size_t newMapped = wasm::LegalizeMapLength(newMax.value());
+        size_t newMappedSize = wasm::ComputeMappedSize(newMaxSize.value());
+        MOZ_ASSERT(newMappedSize >= mappedSize_);
+        if (mappedSize_ == newMappedSize)
+            return;
 
 # ifdef XP_WIN
-        if (!VirtualAlloc(dataPointer(), newMapped, MEM_RESERVE, PAGE_NOACCESS))
+        uint8_t* mappedEnd = dataPointer() + mappedSize_;
+        uint32_t delta = newMappedSize - mappedSize_;
+        if (!VirtualAlloc(mappedEnd, delta, MEM_RESERVE, PAGE_NOACCESS))
             return;
 # elif defined(XP_LINUX)
         // Note this will not move memory (no MREMAP_MAYMOVE specified)
-        if (MAP_FAILED == mremap(dataPointer(), mappedSize_, newMapped, 0))
+        if (MAP_FAILED == mremap(dataPointer(), mappedSize_, newMappedSize, 0))
             return;
 # else
-        // No mechanism for remapping on MaxOS. Luckily shouldn't need it here
-        // as most MacOS configs are 64 bit
+        // No mechanism for remapping on MacOS and other Unices. Luckily
+        // shouldn't need it here as most of these are 64-bit.
         return;
-# endif  // !XP_WIN
+# endif
 
-        mappedSize_ = newMapped;
-        maxSize_ = Some(newMax.value());
+        mappedSize_ = newMappedSize;
+        maxSize_ = Some(newMaxSize.value());
         return;
     }
+#endif // WASM_HUGE_MEMORY
 };
 
 /* static */ WasmArrayRawBuffer*
 WasmArrayRawBuffer::Allocate(uint32_t numBytes, Maybe<uint32_t> maxSize)
 {
-    size_t mappedSize = wasm::LegalizeMapLength(maxSize.valueOr(numBytes));
+    size_t mappedSize;
+#ifdef WASM_HUGE_MEMORY
+    mappedSize = wasm::HugeMappedSize;
+#else
+    mappedSize = wasm::ComputeMappedSize(maxSize.valueOr(numBytes));
+#endif
 
     MOZ_RELEASE_ASSERT(mappedSize <= SIZE_MAX - gc::SystemPageSize());
     MOZ_RELEASE_ASSERT(numBytes <= maxSize.valueOr(UINT32_MAX));
     MOZ_ASSERT(numBytes % gc::SystemPageSize() == 0);
     MOZ_ASSERT(mappedSize % gc::SystemPageSize() == 0);
 
     uint64_t mappedSizeWithHeader = mappedSize + gc::SystemPageSize();
     uint64_t numBytesWithHeader = numBytes + gc::SystemPageSize();
@@ -640,39 +651,48 @@ ArrayBufferObject::BufferContents::wasmB
 {
     MOZ_RELEASE_ASSERT(kind_ == WASM_MAPPED);
     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 numBytes, Maybe<uint32_t> maxSize)
+ArrayBufferObject::createForWasm(JSContext* cx, uint32_t initialSize, Maybe<uint32_t> maxSize)
 {
-    MOZ_ASSERT(numBytes % wasm::PageSize == 0);
+    MOZ_ASSERT(initialSize % wasm::PageSize == 0);
     MOZ_RELEASE_ASSERT(wasm::HaveSignalHandlers());
 
-    // First try to map the maximum requested memory
-    WasmArrayRawBuffer* wasmBuf = WasmArrayRawBuffer::Allocate(numBytes, maxSize);
+    // Prevent applications specifying a large max (like UINT32_MAX) from
+    // unintentially OOMing the browser on 32-bit: they just want "a lot of
+    // memory". Maintain the invariant that initialSize <= maxSize.
+    if (sizeof(void*) == 4 && maxSize) {
+        static const uint32_t OneGiB = 1 << 30;
+        uint32_t clamp = Max(OneGiB, initialSize);
+        maxSize = Some(Min(clamp, maxSize.value()));
+    }
+
+    // Try to reserve the maximum requested memory
+    WasmArrayRawBuffer* wasmBuf = WasmArrayRawBuffer::Allocate(initialSize, maxSize);
     if (!wasmBuf) {
 #ifdef  WASM_HUGE_MEMORY
         ReportOutOfMemory(cx);
         return nullptr;
 #else
         // If we fail, and have a maxSize, try to reserve the biggest chunk in
-        // the range [numBytes, maxSize) using log backoff.
+        // the range [initialSize, maxSize) using log backoff.
         if (!maxSize) {
             ReportOutOfMemory(cx);
             return nullptr;
         }
 
         uint32_t cur = maxSize.value() / 2;
 
-        for (; cur > numBytes; cur = cur / 2) {
-            wasmBuf = WasmArrayRawBuffer::Allocate(numBytes, Some(ROUND_UP(cur, wasm::PageSize)));
+        for (; cur > initialSize; cur = cur / 2) {
+            wasmBuf = WasmArrayRawBuffer::Allocate(initialSize, Some(ROUND_UP(cur, wasm::PageSize)));
             if (wasmBuf)
                 break;
         }
 
         if (!wasmBuf) {
             ReportOutOfMemory(cx);
             return nullptr;
         }
@@ -680,19 +700,18 @@ ArrayBufferObject::createForWasm(JSConte
         // Try to grow our chunk as much as possible.
         for (size_t d = cur / 2; d >= wasm::PageSize; d /= 2)
             wasmBuf->tryGrowMaxSize(ROUND_UP(d, wasm::PageSize));
 #endif
     }
 
     void *data = wasmBuf->dataPointer();
     BufferContents contents = BufferContents::create<WASM_MAPPED>(data);
-    ArrayBufferObject* buffer = ArrayBufferObject::create(cx, numBytes, contents);
+    ArrayBufferObject* buffer = ArrayBufferObject::create(cx, initialSize, contents);
     if (!buffer) {
-        ReportOutOfMemory(cx);
         WasmArrayRawBuffer::Release(data);
         return nullptr;
     }
 
     return buffer;
 }
 
 /* static */ bool
@@ -832,44 +851,46 @@ Maybe<uint32_t>
 ArrayBufferObject::wasmMaxSize() const
 {
     if (isWasmMapped())
         return contents().wasmBuffer()->maxSize();
     else
         return Some<uint32_t>(byteLength());
 }
 
+#ifndef WASM_HUGE_MEMORY
 uint32_t
 ArrayBufferObject::wasmBoundsCheckLimit() const
 {
     if (isWasmMapped())
         return contents().wasmBuffer()->boundsCheckLimit();
     else
         return byteLength();
 }
+#endif
 
 uint32_t
 ArrayBufferObject::wasmActualByteLength() const
 {
     if (isWasmMapped())
         return contents().wasmBuffer()->actualByteLength();
     else
         return byteLength();
 }
 
+#ifndef WASM_HUGE_MEMORY
 uint32_t
 ArrayBufferObjectMaybeShared::wasmBoundsCheckLimit() const
 {
-    if (this->is<ArrayBufferObject>())
-        return this->as<ArrayBufferObject>().wasmBoundsCheckLimit();
+    if (is<ArrayBufferObject>())
+        return as<ArrayBufferObject>().wasmBoundsCheckLimit();
 
-    // TODO: When SharedArrayBuffer can be used from wasm, this should be
-    // replaced by SharedArrayBufferObject::wasmBoundsCheckLimit().
-    return wasmMappedSize();
+    return as<SharedArrayBufferObject>().byteLength();
 }
+#endif
 
 bool
 ArrayBufferObject::growForWasm(uint32_t delta)
 {
     MOZ_ASSERT(isWasmMapped());
 
     if (delta == 0)
         return true;
--- a/js/src/vm/ArrayBufferObject.h
+++ b/js/src/vm/ArrayBufferObject.h
@@ -70,39 +70,48 @@ class WasmArrayRawBuffer;
 //
 // During a minor GC, (3) and (4) may move. During a compacting GC, (2), (3),
 // and (4) may move.
 
 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);
 ArrayBufferObjectMaybeShared& AsAnyArrayBuffer(HandleValue val);
 
 class ArrayBufferObjectMaybeShared : public NativeObject
 {
   public:
     uint32_t byteLength() {
         return AnyArrayBufferByteLength(this);
     }
 
+    inline bool isDetached() const;
+
+    inline SharedMem<uint8_t*> dataPointerEither();
+
+    // WebAssembly support:
+    // Note: the eventual goal is to remove this from ArrayBuffer and have
+    // (Shared)ArrayBuffers alias memory owned by some wasm::Memory object.
+
+    uint32_t wasmActualByteLength() const {
+        return WasmArrayBufferActualByteLength(this);
+    }
+    mozilla::Maybe<uint32_t> wasmMaxSize() const {
+        return WasmArrayBufferMaxSize(this);
+    }
     size_t wasmMappedSize() const {
         return WasmArrayBufferMappedSize(this);
     }
-
+#ifndef WASM_HUGE_MEMORY
     uint32_t wasmBoundsCheckLimit() const;
-    uint32_t wasmActualByteLength() const {
-        return WasmArrayBufferActualByteLength(this);
-    }
-
-    inline bool isDetached() const;
-
-    inline SharedMem<uint8_t*> dataPointerEither();
+#endif
 };
 
 typedef Rooted<ArrayBufferObjectMaybeShared*> RootedArrayBufferObjectMaybeShared;
 typedef Handle<ArrayBufferObjectMaybeShared*> HandleArrayBufferObjectMaybeShared;
 typedef MutableHandle<ArrayBufferObjectMaybeShared*> MutableHandleArrayBufferObjectMaybeShared;
 
 /*
  * ArrayBufferObject
@@ -300,21 +309,17 @@ class ArrayBufferObject : public ArrayBu
     void setFirstView(ArrayBufferViewObject* view);
 
     uint8_t* inlineDataPointer() const;
 
   public:
     uint8_t* dataPointer() const;
     SharedMem<uint8_t*> dataPointerShared() const;
     uint32_t byteLength() const;
-    uint32_t wasmActualByteLength() const;
-    size_t wasmMappedSize() const;
-    uint32_t wasmBoundsCheckLimit() const;
-    mozilla::Maybe<uint32_t> wasmMaxSize() const;
-    MOZ_MUST_USE bool growForWasm(uint32_t delta);
+
     BufferContents contents() const {
         return BufferContents(dataPointer(), bufferKind());
     }
     bool hasInlineData() const {
         return dataPointer() == inlineDataPointer();
     }
 
     void releaseData(FreeOp* fop);
@@ -330,19 +335,27 @@ class ArrayBufferObject : public ArrayBu
     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 isMapped() const { return bufferKind() == MAPPED; }
     bool isDetached() const { return flags() & DETACHED; }
 
-    static ArrayBufferObject* createForWasm(JSContext* cx, uint32_t numBytes,
+    // WebAssembly support:
+    static ArrayBufferObject* createForWasm(JSContext* cx, uint32_t initialSize,
                                             mozilla::Maybe<uint32_t> maxSize);
     static bool prepareForAsmJS(JSContext* cx, Handle<ArrayBufferObject*> buffer);
+    uint32_t wasmActualByteLength() const;
+    size_t wasmMappedSize() const;
+    mozilla::Maybe<uint32_t> wasmMaxSize() const;
+    MOZ_MUST_USE bool growForWasm(uint32_t delta);
+#ifndef WASM_HUGE_MEMORY
+    uint32_t wasmBoundsCheckLimit() const;
+#endif
 
     static void finalize(FreeOp* fop, JSObject* obj);
 
     static BufferContents createMappedContents(int fd, size_t offset, size_t length);
 
     static size_t offsetOfFlagsSlot() {
         return getFixedSlotOffset(FLAGS_SLOT);
     }
--- a/js/src/vm/SharedArrayObject.cpp
+++ b/js/src/vm/SharedArrayObject.cpp
@@ -78,17 +78,17 @@ MarkValidRegion(void* addr, size_t len)
 // for asm.js by mapping the 4gb protected zone described in WasmTypes.h.
 // Since we want to put the SharedArrayBuffer header immediately before the
 // heap but keep the heap page-aligned, allocate an extra page before the heap.
 static uint64_t
 SharedArrayMappedSize()
 {
     MOZ_RELEASE_ASSERT(jit::JitOptions.wasmTestMode);
     MOZ_RELEASE_ASSERT(sizeof(SharedArrayRawBuffer) < gc::SystemPageSize());
-    return wasm::MappedSize + gc::SystemPageSize();
+    return wasm::HugeMappedSize + gc::SystemPageSize();
 }
 
 // If there are too many 4GB buffers live we run up against system resource
 // exhaustion (address space or number of memory map descriptors), see
 // bug 1068684, bug 1073934 for details.  The limiting case seems to be
 // Windows Vista Home 64-bit, where the per-process address space is limited
 // to 8TB.  Thus we track the number of live objects, and set a limit of
 // 1000 live objects per process; we run synchronous GC if necessary; and