Bug 1298202 - Baldr: also support growing memory when no max is specified (r=sunfish)
authorLuke Wagner <luke@mozilla.com>
Thu, 08 Sep 2016 00:53:06 -0500
changeset 354465 daeb588a5de3816a191a7f45e5808dc343325886
parent 354464 b96ee3dad4d8eb59ba72e2b5735461ec7cfb2540
child 354466 e6e3c770c9e9bc67f9bf20f260835a239969e802
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: also support growing memory when no max is specified (r=sunfish) MozReview-Commit-ID: 27W5phJe8Q6
js/src/asmjs/WasmCode.cpp
js/src/asmjs/WasmCode.h
js/src/asmjs/WasmInstance.cpp
js/src/asmjs/WasmInstance.h
js/src/asmjs/WasmIonCompile.cpp
js/src/asmjs/WasmJS.cpp
js/src/asmjs/WasmJS.h
js/src/jit-test/tests/wasm/resizing.js
js/src/jit-test/tests/wasm/spec/memory_trap.wast
js/src/jit-test/tests/wasm/spec/memory_trap.wast.js
js/src/jit-test/tests/wasm/spec/resizing.wast.js
js/src/jit/MIR.cpp
js/src/jit/MIR.h
js/src/vm/ArrayBufferObject.cpp
js/src/vm/ArrayBufferObject.h
--- a/js/src/asmjs/WasmCode.cpp
+++ b/js/src/asmjs/WasmCode.cpp
@@ -101,37 +101,44 @@ StaticallyLink(CodeSegment& cs, const Li
 
     // These constants are logically part of the code:
 
     *(double*)(cs.globalData() + NaN64GlobalDataOffset) = GenericNaN();
     *(float*)(cs.globalData() + NaN32GlobalDataOffset) = GenericNaN();
 }
 
 static void
-SpecializeToMemory(CodeSegment& cs, const Metadata& metadata, HandleWasmMemoryObject memory)
+SpecializeToMemory(uint8_t* prevMemoryBase, CodeSegment& cs, const Metadata& metadata,
+                   ArrayBufferObjectMaybeShared& buffer)
 {
 #ifdef WASM_HUGE_MEMORY
     MOZ_RELEASE_ASSERT(metadata.boundsChecks.empty());
     MOZ_RELEASE_ASSERT(metadata.isAsmJS() || metadata.memoryAccesses.empty());
 #else
-    uint32_t limit = memory->buffer().wasmBoundsCheckLimit();
+    uint32_t limit = buffer.wasmBoundsCheckLimit();
     MOZ_RELEASE_ASSERT(IsValidBoundsCheckImmediate(limit));
 
     for (const BoundsCheck& check : metadata.boundsChecks)
         MacroAssembler::wasmPatchBoundsCheck(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);
-        X86Encoding::SetPointer(addr, (void*)(base + disp));
+    uint8_t* memoryBase = buffer.dataPointerEither().unwrap(/* code patching */);
+    if (prevMemoryBase != memoryBase) {
+        for (const MemoryAccess& access : metadata.memoryAccesses) {
+            void* patchAt = access.patchMemoryPtrImmAt(cs.base());
+
+            uint8_t* prevImm = (uint8_t*)X86Encoding::GetPointer(patchAt);
+            MOZ_ASSERT(prevImm >= prevMemoryBase);
+
+            uint32_t offset = prevImm - prevMemoryBase;
+            MOZ_ASSERT(offset <= INT32_MAX);
+
+            X86Encoding::SetPointer(patchAt, memoryBase + offset);
+        }
     }
 #endif
 }
 
 static bool
 SendCodeRangesToProfiler(JSContext* cx, CodeSegment& cs, const Bytes& bytecode,
                          const Metadata& metadata)
 {
@@ -229,17 +236,17 @@ CodeSegment::create(JSContext* cx,
     {
         JitContext jcx(CompileRuntime::get(cx->compartment()->runtimeFromAnyThread()));
         AutoFlushICache afc("CodeSegment::create");
         AutoFlushICache::setRange(uintptr_t(codeBase), cs->codeLength());
 
         memcpy(codeBase, bytecode.begin(), bytecode.length());
         StaticallyLink(*cs, linkData, cx);
         if (memory)
-            SpecializeToMemory(*cs, metadata, memory);
+            SpecializeToMemory(nullptr, *cs, metadata, memory->buffer());
     }
 
     if (!ExecutableAllocator::makeExecutable(codeBase, cs->codeLength())) {
         ReportOutOfMemory(cx);
         return nullptr;
     }
 
     if (!SendCodeRangesToProfiler(cx, *cs, bytecode, metadata))
@@ -255,16 +262,26 @@ CodeSegment::~CodeSegment()
 
     MOZ_ASSERT(wasmCodeAllocations > 0);
     wasmCodeAllocations--;
 
     MOZ_ASSERT(totalLength() > 0);
     DeallocateExecutableMemory(bytes_, totalLength(), gc::SystemPageSize());
 }
 
+void
+CodeSegment::onMovingGrow(uint8_t* prevMemoryBase, const Metadata& metadata, ArrayBufferObject& buffer)
+{
+    AutoWritableJitCode awjc(base(), codeLength());
+    AutoFlushICache afc("CodeSegment::onMovingGrow");
+    AutoFlushICache::setRange(uintptr_t(base()), codeLength());
+
+    SpecializeToMemory(prevMemoryBase, *this, metadata, buffer);
+}
+
 size_t
 FuncDefExport::serializedSize() const
 {
     return sig_.serializedSize() +
            sizeof(pod);
 }
 
 uint8_t*
--- a/js/src/asmjs/WasmCode.h
+++ b/js/src/asmjs/WasmCode.h
@@ -95,16 +95,21 @@ class CodeSegment
     // enter/exit.
 
     bool containsFunctionPC(const void* pc) const {
         return pc >= base() && pc < (base() + functionCodeLength_);
     }
     bool containsCodePC(const void* pc) const {
         return pc >= base() && pc < (base() + codeLength_);
     }
+
+    // onMovingGrow must be called if the memory passed to 'create' performs a
+    // moving grow operation.
+
+    void onMovingGrow(uint8_t* prevMemoryBase, const Metadata& metadata, ArrayBufferObject& buffer);
 };
 
 // ShareableBytes is a ref-counted vector of bytes which are incrementally built
 // during compilation and then immutably shared.
 
 struct ShareableBytes : ShareableBase<ShareableBytes>
 {
     // Vector is 'final', so instead make Vector a member and add boilerplate.
@@ -512,16 +517,17 @@ class Code
     CacheableCharsVector     funcLabels_;
     bool                     profilingEnabled_;
 
   public:
     Code(UniqueCodeSegment segment,
          const Metadata& metadata,
          const ShareableBytes* maybeBytecode);
 
+    CodeSegment& segment() { return *segment_; }
     const CodeSegment& segment() const { return *segment_; }
     const Metadata& metadata() const { return *metadata_; }
 
     // Frame iterator support:
 
     const CallSite* lookupCallSite(void* returnAddress) const;
     const CodeRange* lookupRange(void* pc) const;
 #ifdef WASM_HUGE_MEMORY
--- a/js/src/asmjs/WasmInstance.cpp
+++ b/js/src/asmjs/WasmInstance.cpp
@@ -287,52 +287,16 @@ Instance::growMemory_i32(Instance* insta
 }
 
 /* static */ uint32_t
 Instance::currentMemory_i32(Instance* instance)
 {
     return instance->currentMemory();
 }
 
-uint32_t
-Instance::growMemory(uint32_t delta)
-{
-    MOZ_RELEASE_ASSERT(memory_);
-
-    // Using uint64_t to avoid worrying about overflows in safety comp.
-    uint64_t curNumPages = currentMemory();
-    uint64_t newNumPages = curNumPages + (uint64_t) delta;
-
-    if (metadata().maxMemoryLength) {
-        ArrayBufferObject &buf = memory_->buffer().as<ArrayBufferObject>();
-        // Guaranteed by instantiateMemory
-        MOZ_RELEASE_ASSERT(buf.wasmMaxSize() && buf.wasmMaxSize() <= metadata().maxMemoryLength);
-
-        if (newNumPages * wasm::PageSize > buf.wasmMaxSize().value())
-            return (uint32_t) -1;
-
-        // Try to grow the memory
-        if (!buf.growForWasm(delta))
-            return (uint32_t) -1;
-    } else {
-        return -1; // TODO: implement grow_memory w/o max when we add realloc
-    }
-
-    return curNumPages;
-}
-
-uint32_t
-Instance::currentMemory()
-{
-    MOZ_RELEASE_ASSERT(memory_);
-    uint32_t curMemByteLen = memory_->buffer().wasmActualByteLength();
-    MOZ_ASSERT(curMemByteLen % wasm::PageSize == 0);
-    return curMemByteLen / wasm::PageSize;
-}
-
 Instance::Instance(JSContext* cx,
                    Handle<WasmInstanceObject*> object,
                    UniqueCode code,
                    HandleWasmMemoryObject memory,
                    SharedTableVector&& tables,
                    Handle<FunctionVector> funcImports,
                    const ValVector& globalImports)
   : compartment_(cx->compartment()),
@@ -406,16 +370,19 @@ Instance::Instance(JSContext* cx,
 
     for (size_t i = 0; i < tables_.length(); i++)
         *addressOfTableBase(i) = tables_[i]->base();
 }
 
 bool
 Instance::init(JSContext* cx)
 {
+    if (memory_ && memory_->movingGrowable() && !memory_->addMovingGrowObserver(cx, object_))
+        return false;
+
     if (!metadata().sigIds.empty()) {
         ExclusiveData<SigIdSet>::Guard lockedSigIdSet = sigIdSet.lock();
 
         if (!lockedSigIdSet->ensureInitialized(cx))
             return false;
 
         for (const SigWithId& sig : metadata().sigIds) {
             const void* sigId;
@@ -585,16 +552,19 @@ WasmInstanceObject*
 Instance::object() const
 {
     return object_;
 }
 
 bool
 Instance::callExport(JSContext* cx, uint32_t funcDefIndex, CallArgs args)
 {
+    // If there has been a moving grow, this Instance should have been notified.
+    MOZ_RELEASE_ASSERT(!memory_ || tlsData_.memoryBase == memory_->buffer().dataPointerEither());
+
     if (!cx->compartment()->wasm.ensureProfilingState(cx))
         return false;
 
     const FuncDefExport& func = metadata().lookupFuncDefExport(funcDefIndex);
 
     // The calling convention for an external call into wasm is to pass an
     // array of 16-byte values where each value contains either a coerced int32
     // (in the low word), a double value (in the low dword) or a SIMD vector
@@ -801,16 +771,43 @@ Instance::callExport(JSContext* cx, uint
     }
 
     if (retObj)
         args.rval().set(ObjectValue(*retObj));
 
     return true;
 }
 
+uint32_t
+Instance::currentMemory()
+{
+    MOZ_RELEASE_ASSERT(memory_);
+    uint32_t byteLength = memory_->buffer().wasmActualByteLength();
+    MOZ_ASSERT(byteLength % wasm::PageSize == 0);
+    return byteLength / wasm::PageSize;
+}
+
+uint32_t
+Instance::growMemory(uint32_t delta)
+{
+    MOZ_ASSERT(!isAsmJS());
+    uint32_t ret = memory_->grow(delta);
+    MOZ_RELEASE_ASSERT(tlsData_.memoryBase == memory_->buffer().dataPointerEither());
+    return ret;
+}
+
+void
+Instance::onMovingGrow(uint8_t* prevMemoryBase)
+{
+    MOZ_ASSERT(!isAsmJS());
+    ArrayBufferObject& buffer = memory_->buffer().as<ArrayBufferObject>();
+    tlsData_.memoryBase = buffer.dataPointer();
+    code_->segment().onMovingGrow(prevMemoryBase, metadata(), buffer);
+}
+
 void
 Instance::deoptimizeImportExit(uint32_t funcImportIndex)
 {
     const FuncImport& fi = metadata().funcImports[funcImportIndex];
     FuncImportTls& import = funcImportTls(fi);
     import.code = codeBase() + fi.interpExitCodeOffset();
     import.baselineScript = nullptr;
 }
--- a/js/src/asmjs/WasmInstance.h
+++ b/js/src/asmjs/WasmInstance.h
@@ -53,26 +53,27 @@ class Instance
     // Import call slow paths which are called directly from wasm code.
     friend void* AddressOf(SymbolicAddress, ExclusiveContext*);
     static int32_t callImport_void(Instance*, int32_t, int32_t, uint64_t*);
     static int32_t callImport_i32(Instance*, int32_t, int32_t, uint64_t*);
     static int32_t callImport_i64(Instance*, int32_t, int32_t, uint64_t*);
     static int32_t callImport_f64(Instance*, int32_t, int32_t, uint64_t*);
     static uint32_t growMemory_i32(Instance* instance, uint32_t delta);
     static uint32_t currentMemory_i32(Instance* instance);
-
     bool callImport(JSContext* cx, uint32_t funcImportIndex, unsigned argc, const uint64_t* argv,
                     MutableHandleValue rval);
-    uint32_t growMemory(uint32_t delta);
-    uint32_t currentMemory();
 
     // Only WasmInstanceObject can call the private trace function.
     friend class js::WasmInstanceObject;
     void tracePrivate(JSTracer* trc);
 
+    // Only WasmMemoryObject can call the private onMovingGrow notification.
+    friend class js::WasmMemoryObject;
+    void onMovingGrow(uint8_t* prevMemoryBase);
+
   public:
     Instance(JSContext* cx,
              HandleWasmInstanceObject object,
              UniqueCode code,
              HandleWasmMemoryObject memory,
              SharedTableVector&& tables,
              Handle<FunctionVector> funcImports,
              const ValVector& globalImports);
@@ -101,16 +102,22 @@ class Instance
 
     WasmInstanceObject* object() const;
 
     // Execute the given export given the JS call arguments, storing the return
     // value in args.rval.
 
     MOZ_MUST_USE bool callExport(JSContext* cx, uint32_t funcDefIndex, CallArgs args);
 
+    // These methods implement their respective wasm operator but may also be
+    // called via the Memory JS API.
+
+    uint32_t currentMemory();
+    uint32_t growMemory(uint32_t delta);
+
     // Initially, calls to imports in wasm code call out through the generic
     // callImport method. If the imported callee gets JIT compiled and the types
     // match up, callImport will patch the code to instead call through a thunk
     // directly into the JIT code. If the JIT code is released, the Instance must
     // be notified so it can go back to the generic callImport.
 
     void deoptimizeImportExit(uint32_t funcImportIndex);
 
--- a/js/src/asmjs/WasmIonCompile.cpp
+++ b/js/src/asmjs/WasmIonCompile.cpp
@@ -1089,17 +1089,18 @@ class FunctionCompiler
         if (inDeadCode()) {
             *def = nullptr;
             return true;
         }
 
         CallSiteDesc desc(call.lineOrBytecode_, CallSiteDesc::Register);
         auto* ins = MWasmCall::NewBuiltinInstanceMethodCall(alloc(), desc, builtin,
                                                             call.instanceArg_, call.regArgs_,
-                                                            ToMIRType(ret), call.spIncrement_);
+                                                            ToMIRType(ret), call.spIncrement_,
+                                                            call.tlsStackOffset_);
         if (!ins)
             return false;
 
         curBlock_->add(ins);
         *def = ins;
         return true;
     }
 
@@ -3001,17 +3002,20 @@ EmitGrowMemory(FunctionCompiler& f, uint
 
     MDefinition* delta;
     if (!f.iter().readUnary(ValType::I32, &delta))
         return false;
 
     if (!f.passArg(delta, ValType::I32, &args))
         return false;
 
-    f.finishCall(&args, PassTls::False, InterModule::False);
+    // As a short-cut, pretend this is an inter-module call so that any pinned
+    // heap pointer will be reloaded after the call. This hack will go away once
+    // we can stop pinning registers.
+    f.finishCall(&args, PassTls::True, InterModule::True);
 
     MDefinition* ret;
     if (!f.builtinInstanceMethodCall(SymbolicAddress::GrowMemory, args, ValType::I32, &ret))
         return false;
 
     f.iter().setResult(ret);
     return true;
 }
--- a/js/src/asmjs/WasmJS.cpp
+++ b/js/src/asmjs/WasmJS.cpp
@@ -13,16 +13,17 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 #include "asmjs/WasmJS.h"
 
+#include "mozilla/CheckedInt.h"
 #include "mozilla/Maybe.h"
 
 #include "asmjs/WasmCompile.h"
 #include "asmjs/WasmInstance.h"
 #include "asmjs/WasmModule.h"
 #include "asmjs/WasmSignalHandlers.h"
 #include "builtin/Promise.h"
 #include "jit/JitOptions.h"
@@ -30,16 +31,17 @@
 
 #include "jsobjinlines.h"
 
 #include "vm/NativeObject-inl.h"
 
 using namespace js;
 using namespace js::jit;
 using namespace js::wasm;
+using mozilla::CheckedInt;
 using mozilla::Nothing;
 
 bool
 wasm::HasCompilerSupport(ExclusiveContext* cx)
 {
     if (!cx->jitSupportsFloatingPoint())
         return false;
 
@@ -705,33 +707,56 @@ wasm::ExportedFunctionToDefinitionIndex(
     MOZ_ASSERT(IsExportedFunction(fun));
     const Value& v = fun->getExtendedSlot(FunctionExtended::WASM_FUNC_DEF_INDEX_SLOT);
     return v.toInt32();
 }
 
 // ============================================================================
 // WebAssembly.Memory class and methods
 
+const ClassOps WasmMemoryObject::classOps_ =
+{
+    nullptr, /* addProperty */
+    nullptr, /* delProperty */
+    nullptr, /* getProperty */
+    nullptr, /* setProperty */
+    nullptr, /* enumerate */
+    nullptr, /* resolve */
+    nullptr, /* mayResolve */
+    WasmMemoryObject::finalize
+};
+
 const Class WasmMemoryObject::class_ =
 {
     "WebAssembly.Memory",
     JSCLASS_DELAY_METADATA_BUILDER |
-    JSCLASS_HAS_RESERVED_SLOTS(WasmMemoryObject::RESERVED_SLOTS)
+    JSCLASS_HAS_RESERVED_SLOTS(WasmMemoryObject::RESERVED_SLOTS) |
+    JSCLASS_FOREGROUND_FINALIZE,
+    &WasmMemoryObject::classOps_
 };
 
+/* static */ void
+WasmMemoryObject::finalize(FreeOp* fop, JSObject* obj)
+{
+    WasmMemoryObject& memory = obj->as<WasmMemoryObject>();
+    if (memory.hasObservers())
+        fop->delete_(&memory.observers());
+}
+
 /* static */ WasmMemoryObject*
 WasmMemoryObject::create(ExclusiveContext* cx, HandleArrayBufferObjectMaybeShared buffer,
                          HandleObject proto)
 {
     AutoSetNewObjectMetadata metadata(cx);
     auto* obj = NewObjectWithGivenProto<WasmMemoryObject>(cx, proto);
     if (!obj)
         return nullptr;
 
     obj->initReservedSlot(BUFFER_SLOT, ObjectValue(*buffer));
+    MOZ_ASSERT(!obj->hasObservers());
     return obj;
 }
 
 /* static */ bool
 WasmMemoryObject::construct(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
@@ -835,16 +860,114 @@ const JSFunctionSpec WasmMemoryObject::m
 { JS_FS_END };
 
 ArrayBufferObjectMaybeShared&
 WasmMemoryObject::buffer() const
 {
     return getReservedSlot(BUFFER_SLOT).toObject().as<ArrayBufferObjectMaybeShared>();
 }
 
+bool
+WasmMemoryObject::hasObservers() const
+{
+    return !getReservedSlot(OBSERVERS_SLOT).isUndefined();
+}
+
+WasmMemoryObject::WeakInstanceSet&
+WasmMemoryObject::observers() const
+{
+    MOZ_ASSERT(hasObservers());
+    return *reinterpret_cast<WeakInstanceSet*>(getReservedSlot(OBSERVERS_SLOT).toPrivate());
+}
+
+WasmMemoryObject::WeakInstanceSet*
+WasmMemoryObject::getOrCreateObservers(JSContext* cx)
+{
+    if (!hasObservers()) {
+        auto observers = MakeUnique<WeakInstanceSet>(cx->zone(), InstanceSet());
+        if (!observers || !observers->init()) {
+            ReportOutOfMemory(cx);
+            return nullptr;
+        }
+
+        setReservedSlot(OBSERVERS_SLOT, PrivateValue(observers.release()));
+    }
+
+    return &observers();
+}
+
+bool
+WasmMemoryObject::movingGrowable() const
+{
+#ifdef WASM_HUGE_MEMORY
+    return false;
+#else
+    return !buffer().wasmMaxSize();
+#endif
+}
+
+bool
+WasmMemoryObject::addMovingGrowObserver(JSContext* cx, WasmInstanceObject* instance)
+{
+    MOZ_ASSERT(movingGrowable());
+
+    WeakInstanceSet* observers = getOrCreateObservers(cx);
+    if (!observers)
+        return false;
+
+    if (!observers->putNew(instance)) {
+        ReportOutOfMemory(cx);
+        return false;
+    }
+
+    return true;
+}
+
+uint32_t
+WasmMemoryObject::grow(uint32_t delta)
+{
+    ArrayBufferObject &buf = buffer().as<ArrayBufferObject>();
+
+    MOZ_ASSERT(buf.wasmActualByteLength() % PageSize == 0);
+    uint32_t oldNumPages = buf.wasmActualByteLength() / PageSize;
+
+    CheckedInt<uint32_t> newSize = oldNumPages;
+    newSize += delta;
+    newSize *= PageSize;
+    if (!newSize.isValid())
+        return -1;
+
+    if (Maybe<uint32_t> maxSize = buf.wasmMaxSize()) {
+        if (newSize.value() > maxSize.value())
+            return -1;
+
+        if (!buf.wasmGrowToSizeInPlace(newSize.value()))
+            return -1;
+    } else {
+#ifdef WASM_HUGE_MEMORY
+        if (!buf.wasmGrowToSizeInPlace(newSize.value()))
+            return -1;
+#else
+        MOZ_ASSERT(movingGrowable());
+
+        uint8_t* prevMemoryBase = buf.dataPointer();
+
+        if (!buf.wasmMovingGrowToSize(newSize.value()))
+            return -1;
+
+        if (hasObservers()) {
+            for (InstanceSet::Range r = observers().all(); !r.empty(); r.popFront())
+                r.front()->instance().onMovingGrow(prevMemoryBase);
+        }
+#endif
+    }
+
+    return oldNumPages;
+}
+
 // ============================================================================
 // WebAssembly.Table class and methods
 
 const ClassOps WasmTableObject::classOps_ =
 {
     nullptr, /* addProperty */
     nullptr, /* delProperty */
     nullptr, /* getProperty */
--- a/js/src/asmjs/WasmJS.h
+++ b/js/src/asmjs/WasmJS.h
@@ -155,28 +155,43 @@ class WasmInstanceObject : public Native
 };
 
 // The class of WebAssembly.Memory. A WasmMemoryObject references an ArrayBuffer
 // or SharedArrayBuffer object which owns the actual memory.
 
 class WasmMemoryObject : public NativeObject
 {
     static const unsigned BUFFER_SLOT = 0;
+    static const unsigned OBSERVERS_SLOT = 1;
     static const ClassOps classOps_;
+    static void finalize(FreeOp* fop, JSObject* obj);
+
+    using InstanceSet = GCHashSet<ReadBarrieredWasmInstanceObject,
+                                  MovableCellHasher<ReadBarrieredWasmInstanceObject>,
+                                  SystemAllocPolicy>;
+    using WeakInstanceSet = JS::WeakCache<InstanceSet>;
+    bool hasObservers() const;
+    WeakInstanceSet& observers() const;
+    WeakInstanceSet* getOrCreateObservers(JSContext* cx);
+
   public:
-    static const unsigned RESERVED_SLOTS = 1;
+    static const unsigned RESERVED_SLOTS = 2;
     static const Class class_;
     static const JSPropertySpec properties[];
     static const JSFunctionSpec methods[];
     static bool construct(JSContext*, unsigned, Value*);
 
     static WasmMemoryObject* create(ExclusiveContext* cx,
                                     Handle<ArrayBufferObjectMaybeShared*> buffer,
                                     HandleObject proto);
     ArrayBufferObjectMaybeShared& buffer() const;
+
+    bool movingGrowable() const;
+    bool addMovingGrowObserver(JSContext* cx, WasmInstanceObject* instance);
+    uint32_t grow(uint32_t delta);
 };
 
 // The class of WebAssembly.Table. A WasmTableObject holds a refcount on a
 // wasm::Table, allowing a Table to be shared between multiple Instances
 // (eventually between multiple threads).
 
 class WasmTableObject : public NativeObject
 {
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/resizing.js
@@ -0,0 +1,77 @@
+// |jit-test| test-also-wasm-baseline
+load(libdir + "wasm.js");
+
+const Module = WebAssembly.Module;
+const Instance = WebAssembly.Instance;
+const Table = WebAssembly.Table;
+const Memory = WebAssembly.Memory;
+
+// Test for stale heap pointers after resize
+
+// Grow directly from builtin call:
+assertEq(evalText(`(module
+    (memory 1)
+    (func $test (result i32)
+        (i32.store (i32.const 0) (i32.const 1))
+        (i32.store (i32.const 65532) (i32.const 10))
+        (grow_memory (i32.const 99))
+        (i32.store (i32.const 6553596) (i32.const 100))
+        (i32.add
+            (i32.load (i32.const 0))
+            (i32.add
+                (i32.load (i32.const 65532))
+                (i32.load (i32.const 6553596)))))
+    (export "test" $test)
+)`).exports.test(), 111);
+
+// Grow during call_import:
+var exports = evalText(`(module
+    (import $imp "a" "imp")
+    (memory 1)
+    (func $grow (grow_memory (i32.const 99)))
+    (export "grow" $grow)
+    (func $test (result i32)
+        (i32.store (i32.const 0) (i32.const 1))
+        (i32.store (i32.const 65532) (i32.const 10))
+        (call $imp)
+        (i32.store (i32.const 6553596) (i32.const 100))
+        (i32.add
+            (i32.load (i32.const 0))
+            (i32.add
+                (i32.load (i32.const 65532))
+                (i32.load (i32.const 6553596)))))
+    (export "test" $test)
+)`, {a:{imp() { exports.grow() }}}).exports;
+
+setJitCompilerOption("baseline.warmup.trigger", 2);
+setJitCompilerOption("ion.warmup.trigger", 4);
+for (var i = 0; i < 10; i++)
+    assertEq(exports.test(), 111);
+
+// Grow during call_indirect:
+var mem = new Memory({initial:1});
+var tbl = new Table({initial:1, element:"anyfunc"});
+var exports1 = evalText(`(module
+    (import "a" "mem" (memory 1))
+    (func $grow
+        (i32.store (i32.const 65532) (i32.const 10))
+        (grow_memory (i32.const 99))
+        (i32.store (i32.const 6553596) (i32.const 100)))
+    (export "grow" $grow)
+)`, {a:{mem}}).exports;
+var exports2 = evalText(`(module
+    (import "a" "tbl" (table 1))
+    (import "a" "mem" (memory 1))
+    (type $v2v (func))
+    (func $test (result i32)
+        (i32.store (i32.const 0) (i32.const 1))
+        (call_indirect $v2v (i32.const 0))
+        (i32.add
+            (i32.load (i32.const 0))
+            (i32.add
+                (i32.load (i32.const 65532))
+                (i32.load (i32.const 6553596)))))
+    (export "test" $test)
+)`, {a:{tbl, mem}}).exports;
+tbl.set(0, exports1.grow);
+assertEq(exports2.test(), 111);
--- a/js/src/jit-test/tests/wasm/spec/memory_trap.wast
+++ b/js/src/jit-test/tests/wasm/spec/memory_trap.wast
@@ -11,26 +11,26 @@
     )
 
     (export "load" $load)
     (func $load (param $i i32) (result i32)
       (i32.load (i32.add (call $addr_limit) (get_local $i)))
     )
 
     (export "grow_memory" $grow_memory)
-    (func $grow_memory (param i32)
+    (func $grow_memory (param i32) (result i32)
       (grow_memory (get_local 0))
     )
 )
 
 (assert_return (invoke "store" (i32.const -4) (i32.const 42)) (i32.const 42))
 (assert_return (invoke "load" (i32.const -4)) (i32.const 42))
 (assert_trap (invoke "store" (i32.const -3) (i32.const 13)) "out of bounds memory access")
 (assert_trap (invoke "load" (i32.const -3)) "out of bounds memory access")
 (assert_trap (invoke "store" (i32.const -2) (i32.const 13)) "out of bounds memory access")
 (assert_trap (invoke "load" (i32.const -2)) "out of bounds memory access")
 (assert_trap (invoke "store" (i32.const -1) (i32.const 13)) "out of bounds memory access")
 (assert_trap (invoke "load" (i32.const -1)) "out of bounds memory access")
 (assert_trap (invoke "store" (i32.const 0) (i32.const 13)) "out of bounds memory access")
 (assert_trap (invoke "load" (i32.const 0)) "out of bounds memory access")
 (assert_trap (invoke "store" (i32.const 0x80000000) (i32.const 13)) "out of bounds memory access")
 (assert_trap (invoke "load" (i32.const 0x80000000)) "out of bounds memory access")
-(assert_trap (invoke "grow_memory" (i32.const 0x80000000)) "memory size exceeds implementation limit")
+(assert_return (invoke "grow_memory" (i32.const 0x80000000)) (i32.const -1))
--- a/js/src/jit-test/tests/wasm/spec/memory_trap.wast.js
+++ b/js/src/jit-test/tests/wasm/spec/memory_trap.wast.js
@@ -1,4 +1,2 @@
 // |jit-test| test-also-wasm-baseline
-// TODO current_memory opcode + traps on OOB
-quit();
 var importedArgs = ['memory_trap.wast']; load(scriptdir + '../spec.js');
--- a/js/src/jit-test/tests/wasm/spec/resizing.wast.js
+++ b/js/src/jit-test/tests/wasm/spec/resizing.wast.js
@@ -1,4 +1,2 @@
 // |jit-test| test-also-wasm-baseline
-// TODO memory resizing (you don't say)
-quit();
 var importedArgs = ['resizing.wast']; load(scriptdir + '../spec.js');
--- a/js/src/jit/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -5440,25 +5440,26 @@ MWasmCall::New(TempAllocator& alloc, con
 
 MWasmCall*
 MWasmCall::NewBuiltinInstanceMethodCall(TempAllocator& alloc,
                                         const wasm::CallSiteDesc& desc,
                                         const wasm::SymbolicAddress builtin,
                                         const ABIArg& instanceArg,
                                         const Args& args,
                                         MIRType resultType,
-                                        uint32_t spIncrement)
+                                        uint32_t spIncrement,
+                                        uint32_t tlsStackOffset)
 {
     auto callee = wasm::CalleeDesc::builtinInstanceMethod(builtin);
     MWasmCall* call = MWasmCall::New(alloc, desc, callee, args, resultType, spIncrement,
-                                     MWasmCall::DontSaveTls, nullptr);
-
+                                     tlsStackOffset, nullptr);
     if (!call)
         return nullptr;
-    MOZ_ASSERT(instanceArg != ABIArg()); // instanceArg must be initialized.
+
+    MOZ_ASSERT(instanceArg != ABIArg());
     call->instanceArg_ = instanceArg;
     return call;
 }
 
 void
 MSqrt::trySpecializeFloat32(TempAllocator& alloc) {
     if (!input()->canProduceFloat32() || !CheckUsesAreFloat32Consumers(this)) {
         if (input()->type() == MIRType::Float32)
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -13683,28 +13683,33 @@ class MWasmCall final
         AnyRegister reg;
         MDefinition* def;
         Arg(AnyRegister reg, MDefinition* def) : reg(reg), def(def) {}
     };
     typedef Vector<Arg, 8, SystemAllocPolicy> Args;
 
     static const uint32_t DontSaveTls = UINT32_MAX;
 
-    static MWasmCall* New(TempAllocator& alloc, const wasm::CallSiteDesc& desc,
-                          const wasm::CalleeDesc& callee, const Args& args, MIRType resultType,
-                          uint32_t spIncrement, uint32_t tlsStackOffset,
+    static MWasmCall* New(TempAllocator& alloc,
+                          const wasm::CallSiteDesc& desc,
+                          const wasm::CalleeDesc& callee,
+                          const Args& args,
+                          MIRType resultType,
+                          uint32_t spIncrement,
+                          uint32_t tlsStackOffset,
                           MDefinition* tableIndex = nullptr);
 
     static MWasmCall* NewBuiltinInstanceMethodCall(TempAllocator& alloc,
                                                    const wasm::CallSiteDesc& desc,
                                                    const wasm::SymbolicAddress builtin,
                                                    const ABIArg& instanceArg,
                                                    const Args& args,
                                                    MIRType resultType,
-                                                   uint32_t spIncrement);
+                                                   uint32_t spIncrement,
+                                                   uint32_t tlsStackOffset);
 
     size_t numArgs() const {
         return argRegs_.length();
     }
     AnyRegister registerForArg(size_t index) const {
         MOZ_ASSERT(index < numArgs());
         return argRegs_[index];
     }
--- a/js/src/vm/ArrayBufferObject.cpp
+++ b/js/src/vm/ArrayBufferObject.cpp
@@ -55,16 +55,17 @@
 #include "vm/Shape-inl.h"
 
 using JS::ToInt32;
 
 using mozilla::DebugOnly;
 using mozilla::CheckedInt;
 using mozilla::Some;
 using mozilla::Maybe;
+using mozilla::Nothing;
 
 using namespace js;
 using namespace js::gc;
 
 /*
  * Convert |v| to an array index for an array of length |length| per
  * the Typed Array Specification section 7.0, |subarray|. If successful,
  * the output value is in the range [0, length].
@@ -498,82 +499,82 @@ class js::WasmArrayRawBuffer
     uint32_t boundsCheckLimit() const {
         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);
+    MOZ_MUST_USE bool growToSizeInPlace(uint32_t newSize) {
+        MOZ_ASSERT(newSize >= actualByteLength());
+        MOZ_ASSERT_IF(maxSize(), newSize <= maxSize().value());
+        MOZ_ASSERT(newSize <= mappedSize());
 
-        CheckedInt<uint32_t> curLength = actualByteLength();
-        CheckedInt<uint32_t> newLength = curLength + deltaLength;
-        MOZ_RELEASE_ASSERT(newLength.isValid());
-        MOZ_ASSERT(newLength.value() <= maxSize_.value());
+        uint32_t delta = newSize - actualByteLength();
+        MOZ_ASSERT(delta % wasm::PageSize == 0);
 
-        uint8_t* dataEnd = dataPointer() + curLength.value();
-        MOZ_ASSERT(((intptr_t)dataEnd) % gc::SystemPageSize() == 0);
+        uint8_t* dataEnd = dataPointer() + actualByteLength();
+        MOZ_ASSERT(uintptr_t(dataEnd) % gc::SystemPageSize() == 0);
 # ifdef XP_WIN
-        if (deltaLength && !VirtualAlloc(dataEnd, deltaLength, MEM_COMMIT, PAGE_READWRITE))
+        if (delta && !VirtualAlloc(dataEnd, delta, MEM_COMMIT, PAGE_READWRITE))
             return false;
 # else  // XP_WIN
-        if (deltaLength && mprotect(dataEnd, deltaLength, PROT_READ | PROT_WRITE))
+        if (delta && mprotect(dataEnd, delta, PROT_READ | PROT_WRITE))
             return false;
 # endif  // !XP_WIN
 
 #  if defined(MOZ_VALGRIND) && defined(VALGRIND_DISABLE_ADDR_ERROR_REPORTING_IN_RANGE)
-        VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE((unsigned char*)dataEnd, deltaLength);
+        VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE((unsigned char*)dataEnd, delta);
 #  endif
 
-        MemProfiler::SampleNative(dataEnd, deltaLength);
+        MemProfiler::SampleNative(dataEnd, delta);
 
-        length_ = newLength.value();
+        length_ = newSize;
         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> newMaxSize = maxSize_.value() + deltaMaxSize;
-        MOZ_RELEASE_ASSERT(newMaxSize.isValid());
-        MOZ_RELEASE_ASSERT(newMaxSize.value() % wasm::PageSize == 0);
-
-        size_t newMappedSize = wasm::ComputeMappedSize(newMaxSize.value());
-        MOZ_ASSERT(newMappedSize >= mappedSize_);
+    bool extendMappedSize(uint32_t maxSize) {
+        size_t newMappedSize = wasm::ComputeMappedSize(maxSize);
+        MOZ_ASSERT(mappedSize_ <= newMappedSize);
         if (mappedSize_ == newMappedSize)
-            return;
+            return true;
 
 # ifdef XP_WIN
         uint8_t* mappedEnd = dataPointer() + mappedSize_;
         uint32_t delta = newMappedSize - mappedSize_;
         if (!VirtualAlloc(mappedEnd, delta, MEM_RESERVE, PAGE_NOACCESS))
-            return;
+            return false;
 # elif defined(XP_LINUX)
         // Note this will not move memory (no MREMAP_MAYMOVE specified)
         if (MAP_FAILED == mremap(dataPointer(), mappedSize_, newMappedSize, 0))
-            return;
+            return false;
 # else
         // No mechanism for remapping on MacOS and other Unices. Luckily
         // shouldn't need it here as most of these are 64-bit.
-        return;
+        return false;
 # endif
 
         mappedSize_ = newMappedSize;
+        return true;
+    }
+
+    // Try and grow the mapped region of memory. Does not changes current size.
+    // Does not move memory if no space to grow.
+    void tryGrowMaxSizeInPlace(uint32_t deltaMaxSize) {
+        CheckedInt<uint32_t> newMaxSize = maxSize_.value();
+        newMaxSize += deltaMaxSize;
+        MOZ_ASSERT(newMaxSize.isValid());
+        MOZ_ASSERT(newMaxSize.value() % wasm::PageSize == 0);
+
+        if (!extendMappedSize(newMaxSize.value()))
+            return;
+
         maxSize_ = Some(newMaxSize.value());
-        return;
     }
 #endif // WASM_HUGE_MEMORY
 };
 
 /* static */ WasmArrayRawBuffer*
 WasmArrayRawBuffer::Allocate(uint32_t numBytes, Maybe<uint32_t> maxSize)
 {
     size_t mappedSize;
@@ -681,30 +682,30 @@ ArrayBufferObject::createForWasm(JSConte
         // the range [initialSize, maxSize) using log backoff.
         if (!maxSize) {
             ReportOutOfMemory(cx);
             return nullptr;
         }
 
         uint32_t cur = maxSize.value() / 2;
 
-        for (; cur > initialSize; cur = cur / 2) {
+        for (; cur > initialSize; cur /= 2) {
             wasmBuf = WasmArrayRawBuffer::Allocate(initialSize, Some(ROUND_UP(cur, wasm::PageSize)));
             if (wasmBuf)
                 break;
         }
 
         if (!wasmBuf) {
             ReportOutOfMemory(cx);
             return nullptr;
         }
 
         // 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));
+            wasmBuf->tryGrowMaxSizeInPlace(ROUND_UP(d, wasm::PageSize));
 #endif
     }
 
     void *data = wasmBuf->dataPointer();
     BufferContents contents = BufferContents::create<WASM_MAPPED>(data);
     ArrayBufferObject* buffer = ArrayBufferObject::create(cx, initialSize, contents);
     if (!buffer) {
         WasmArrayRawBuffer::Release(data);
@@ -861,63 +862,71 @@ 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();
 }
 
+bool
+ArrayBufferObject::wasmGrowToSizeInPlace(uint32_t newSize)
+{
+    return contents().wasmBuffer()->growToSizeInPlace(newSize);
+}
+
 #ifndef WASM_HUGE_MEMORY
+bool
+ArrayBufferObject::wasmMovingGrowToSize(uint32_t newSize)
+{
+    WasmArrayRawBuffer* curBuf = contents().wasmBuffer();
+
+    if (newSize <= curBuf->boundsCheckLimit() || curBuf->extendMappedSize(newSize))
+        return curBuf->growToSizeInPlace(newSize);
+
+    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);
+    changeContents(GetJSContextFromMainThread(), newContents);
+    return true;
+}
+
+uint32_t
+ArrayBufferObject::wasmBoundsCheckLimit() const
+{
+    if (isWasmMapped())
+        return contents().wasmBuffer()->boundsCheckLimit();
+    else
+        return byteLength();
+}
+
 uint32_t
 ArrayBufferObjectMaybeShared::wasmBoundsCheckLimit() const
 {
     if (is<ArrayBufferObject>())
         return as<ArrayBufferObject>().wasmBoundsCheckLimit();
 
     return as<SharedArrayBufferObject>().byteLength();
 }
 #endif
 
-bool
-ArrayBufferObject::growForWasm(uint32_t delta)
-{
-    MOZ_ASSERT(isWasmMapped());
-
-    if (delta == 0)
-        return true;
-
-    // Should be guaranteed by Instance::growMemory
-    CheckedInt<uint32_t> curSize = wasmActualByteLength();
-    CheckedInt<uint32_t> newSize = curSize + CheckedInt<uint32_t>(delta) * wasm ::PageSize;
-    MOZ_RELEASE_ASSERT(newSize.isValid());
-
-    return contents().wasmBuffer()->growLength(delta * wasm::PageSize);
-}
-
 uint32_t
 ArrayBufferObject::flags() const
 {
     return uint32_t(getSlot(FLAGS_SLOT).toInt32());
 }
 
 void
 ArrayBufferObject::setFlags(uint32_t flags)
--- a/js/src/vm/ArrayBufferObject.h
+++ b/js/src/vm/ArrayBufferObject.h
@@ -342,18 +342,19 @@ class ArrayBufferObject : public ArrayBu
 
     // 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);
     uint32_t wasmActualByteLength() const;
     size_t wasmMappedSize() const;
     mozilla::Maybe<uint32_t> wasmMaxSize() const;
-    MOZ_MUST_USE bool growForWasm(uint32_t delta);
+    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
 
     static void finalize(FreeOp* fop, JSObject* obj);
 
     static BufferContents createMappedContents(int fd, size_t offset, size_t length);
 
     static size_t offsetOfFlagsSlot() {