Bug 1284156 - Baldr: add Table.prototype.grow (r=bbouvier)
authorLuke Wagner <luke@mozilla.com>
Wed, 14 Sep 2016 11:46:10 -0500
changeset 355258 88a175eed324e053a3ab25bf0bf1704c026a2a6b
parent 355257 61e6762b20d0fa6f64b61e9ee2fe4adaa9417a20
child 355259 be756757b11f0ba559c1bdcafcfdfee5381198a1
push id6570
push userraliiev@mozilla.com
push dateMon, 14 Nov 2016 12:26:13 +0000
treeherdermozilla-beta@f455459b2ae5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbbouvier
bugs1284156
milestone51.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1284156 - Baldr: add Table.prototype.grow (r=bbouvier) MozReview-Commit-ID: 8g92o4GXQ82
js/src/asmjs/WasmBaselineCompile.cpp
js/src/asmjs/WasmCompile.cpp
js/src/asmjs/WasmGenerator.cpp
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/asmjs/WasmModule.cpp
js/src/asmjs/WasmTable.cpp
js/src/asmjs/WasmTable.h
js/src/asmjs/WasmTypes.h
js/src/jit-test/tests/wasm/import-export.js
js/src/jit-test/tests/wasm/jsapi.js
js/src/jit-test/tests/wasm/resizing.js
js/src/jit/MacroAssembler.cpp
js/src/js.msg
--- a/js/src/asmjs/WasmBaselineCompile.cpp
+++ b/js/src/asmjs/WasmBaselineCompile.cpp
@@ -2141,18 +2141,18 @@ class BaseCompiler
 
         const SigWithId& sig = mg_.sigs[sigIndex];
 
         CalleeDesc callee;
         if (isCompilingAsmJS()) {
             MOZ_ASSERT(sig.id.kind() == SigIdDesc::Kind::None);
             const TableDesc& table = mg_.tables[mg_.asmJSSigToTableIndex[sigIndex]];
 
-            MOZ_ASSERT(IsPowerOfTwo(table.initial));
-            masm.andPtr(Imm32((table.initial - 1)), WasmTableCallIndexReg);
+            MOZ_ASSERT(IsPowerOfTwo(table.limits.initial));
+            masm.andPtr(Imm32((table.limits.initial - 1)), WasmTableCallIndexReg);
 
             callee = CalleeDesc::asmJSTable(table);
         } else {
             MOZ_ASSERT(sig.id.kind() != SigIdDesc::Kind::None);
             MOZ_ASSERT(mg_.tables.length() == 1);
             const TableDesc& table = mg_.tables[0];
 
             callee = CalleeDesc::wasmTable(table, sig.id);
--- a/js/src/asmjs/WasmCompile.cpp
+++ b/js/src/asmjs/WasmCompile.cpp
@@ -628,73 +628,67 @@ DecodeName(Decoder& d)
         return nullptr;
 
     memcpy(name.get(), bytes, numBytes);
     name[numBytes] = '\0';
 
     return name;
 }
 
-struct Resizable
-{
-    uint32_t initial;
-    Maybe<uint32_t> maximum;
-};
-
 static bool
-DecodeResizable(Decoder& d, Resizable* resizable)
+DecodeResizable(Decoder& d, ResizableLimits* limits)
 {
     uint32_t flags;
     if (!d.readVarU32(&flags))
         return Fail(d, "expected flags");
 
     if (flags & ~uint32_t(ResizableFlags::AllowedMask))
         return Fail(d, "unexpected bits set in flags");
 
     if (!(flags & uint32_t(ResizableFlags::Default)))
         return Fail(d, "currently, every memory/table must be declared default");
 
-    if (!d.readVarU32(&resizable->initial))
+    if (!d.readVarU32(&limits->initial))
         return Fail(d, "expected initial length");
 
     if (flags & uint32_t(ResizableFlags::HasMaximum)) {
         uint32_t maximum;
         if (!d.readVarU32(&maximum))
             return Fail(d, "expected maximum length");
 
-        if (resizable->initial > maximum)
+        if (limits->initial > maximum)
             return Fail(d, "maximum length less than initial length");
 
-        resizable->maximum.emplace(maximum);
+        limits->maximum.emplace(maximum);
     }
 
     return true;
 }
 
 static bool
 DecodeResizableMemory(Decoder& d, ModuleGeneratorData* init)
 {
     if (UsesMemory(init->memoryUsage))
         return Fail(d, "already have default memory");
 
-    Resizable resizable;
-    if (!DecodeResizable(d, &resizable))
+    ResizableLimits limits;
+    if (!DecodeResizable(d, &limits))
         return false;
 
     init->memoryUsage = MemoryUsage::Unshared;
 
-    CheckedInt<uint32_t> initialBytes = resizable.initial;
+    CheckedInt<uint32_t> initialBytes = limits.initial;
     initialBytes *= PageSize;
     if (!initialBytes.isValid() || initialBytes.value() > uint32_t(INT32_MAX))
         return Fail(d, "initial memory size too big");
 
     init->minMemoryLength = initialBytes.value();
 
-    if (resizable.maximum) {
-        CheckedInt<uint32_t> maximumBytes = *resizable.maximum;
+    if (limits.maximum) {
+        CheckedInt<uint32_t> maximumBytes = *limits.maximum;
         maximumBytes *= PageSize;
         if (!maximumBytes.isValid())
             return Fail(d, "maximum memory size too big");
 
         init->maxMemoryLength = Some(maximumBytes.value());
     }
 
     return true;
@@ -705,28 +699,24 @@ DecodeResizableTable(Decoder& d, ModuleG
 {
     uint32_t elementType;
     if (!d.readVarU32(&elementType))
         return Fail(d, "expected table element type");
 
     if (elementType != uint32_t(TypeConstructor::AnyFunc))
         return Fail(d, "expected 'anyfunc' element type");
 
-    Resizable resizable;
-    if (!DecodeResizable(d, &resizable))
+    ResizableLimits limits;
+    if (!DecodeResizable(d, &limits))
         return false;
 
     if (!init->tables.empty())
         return Fail(d, "already have default table");
 
-    TableDesc table;
-    table.kind = TableKind::AnyFunction;
-    table.initial = resizable.initial;
-    table.maximum = resizable.maximum ? *resizable.maximum : UINT32_MAX;
-    return init->tables.append(table);
+    return init->tables.emplaceBack(TableKind::AnyFunction, limits);
 }
 
 static bool
 DecodeGlobalType(Decoder& d, ValType* type, bool* isMutable)
 {
     if (!d.readValType(type))
         return Fail(d, "bad global type");
 
@@ -869,42 +859,41 @@ DecodeTableSection(Decoder& d, bool newF
         return Fail(d, "failed to start section");
     if (sectionStart == Decoder::NotStarted)
         return true;
 
     if (newFormat) {
         if (!DecodeResizableTable(d, init))
             return false;
     } else {
-        TableDesc table;
-        table.kind = TableKind::AnyFunction;
-        table.maximum = UINT32_MAX;
-
-        if (!d.readVarU32(&table.initial))
+        ResizableLimits limits;
+        if (!d.readVarU32(&limits.initial))
             return Fail(d, "expected number of table elems");
 
-        if (table.initial > MaxTableElems)
+        if (limits.initial > MaxTableElems)
             return Fail(d, "too many table elements");
 
-        if (!oldElems->resize(table.initial))
+        limits.maximum = Some(limits.initial);
+
+        if (!oldElems->resize(limits.initial))
             return false;
 
-        for (uint32_t i = 0; i < table.initial; i++) {
+        for (uint32_t i = 0; i < limits.initial; i++) {
             uint32_t funcDefIndex;
             if (!d.readVarU32(&funcDefIndex))
                 return Fail(d, "expected table element");
 
             if (funcDefIndex >= init->funcDefSigs.length())
                 return Fail(d, "table element out of range");
 
             (*oldElems)[i] = init->funcImports.length() + funcDefIndex;
         }
 
         MOZ_ASSERT(init->tables.empty());
-        if (!init->tables.append(table))
+        if (!init->tables.emplaceBack(TableKind::AnyFunction, limits))
             return false;
     }
 
     if (!d.finishSection(sectionStart, sectionSize))
         return Fail(d, "table section byte size mismatch");
 
     return true;
 }
@@ -1359,17 +1348,17 @@ DecodeElemSection(Decoder& d, bool newFo
         InitExpr offset;
         if (!DecodeInitializerExpression(d, mg.globals(), ValType::I32, &offset))
             return false;
 
         uint32_t numElems;
         if (!d.readVarU32(&numElems))
             return Fail(d, "expected segment size");
 
-        uint32_t tableLength = mg.tables()[tableIndex].initial;
+        uint32_t tableLength = mg.tables()[tableIndex].limits.initial;
         if (offset.isVal()) {
             uint32_t off = offset.val().i32();
             if (off > tableLength || tableLength - off < numElems)
                 return Fail(d, "element segment does not fit");
         }
 
         Uint32Vector elemFuncIndices;
         if (!elemFuncIndices.resize(numElems))
--- a/js/src/asmjs/WasmGenerator.cpp
+++ b/js/src/asmjs/WasmGenerator.cpp
@@ -152,17 +152,17 @@ ModuleGenerator::init(UniqueModuleGenera
             if (import.kind == DefinitionKind::Table) {
                 MOZ_ASSERT(shared_->tables.length() == 1);
                 shared_->tables[0].external = true;
                 break;
             }
         }
 
         for (TableDesc& table : shared_->tables) {
-            if (!allocateGlobalBytes(sizeof(void*), sizeof(void*), &table.globalDataOffset))
+            if (!allocateGlobalBytes(sizeof(TableTls), sizeof(void*), &table.globalDataOffset))
                 return false;
         }
 
         for (uint32_t i = 0; i < numSigs_; i++) {
             SigWithId& sig = shared_->sigs[i];
             if (SigIdDesc::isGlobal(sig)) {
                 uint32_t globalDataOffset;
                 if (!allocateGlobalBytes(sizeof(void*), sizeof(void*), &globalDataOffset))
@@ -1011,32 +1011,30 @@ ModuleGenerator::initSigTableLength(uint
     MOZ_ASSERT(isAsmJS());
     MOZ_ASSERT(length != 0);
     MOZ_ASSERT(length <= MaxTableElems);
 
     MOZ_ASSERT(shared_->asmJSSigToTableIndex[sigIndex] == 0);
     shared_->asmJSSigToTableIndex[sigIndex] = numTables_;
 
     TableDesc& table = shared_->tables[numTables_++];
-    MOZ_ASSERT(table.globalDataOffset == 0);
-    MOZ_ASSERT(table.initial == 0);
     table.kind = TableKind::TypedFunction;
-    table.initial = length;
-    table.maximum = UINT32_MAX;
-    return allocateGlobalBytes(sizeof(void*), sizeof(void*), &table.globalDataOffset);
+    table.limits.initial = length;
+    table.limits.maximum = Some(length);
+    return allocateGlobalBytes(sizeof(TableTls), sizeof(void*), &table.globalDataOffset);
 }
 
 bool
 ModuleGenerator::initSigTableElems(uint32_t sigIndex, Uint32Vector&& elemFuncDefIndices)
 {
     MOZ_ASSERT(isAsmJS());
     MOZ_ASSERT(finishedFuncDefs_);
 
     uint32_t tableIndex = shared_->asmJSSigToTableIndex[sigIndex];
-    MOZ_ASSERT(shared_->tables[tableIndex].initial == elemFuncDefIndices.length());
+    MOZ_ASSERT(shared_->tables[tableIndex].limits.initial == elemFuncDefIndices.length());
 
     Uint32Vector codeRangeIndices;
     if (!codeRangeIndices.resize(elemFuncDefIndices.length()))
         return false;
     for (size_t i = 0; i < elemFuncDefIndices.length(); i++) {
         codeRangeIndices[i] = funcDefIndexToCodeRange_[elemFuncDefIndices[i]];
         elemFuncDefIndices[i] += numFuncImports();
     }
--- a/js/src/asmjs/WasmInstance.cpp
+++ b/js/src/asmjs/WasmInstance.cpp
@@ -83,37 +83,37 @@ class SigIdSet
             js_delete(p->key());
             map_.remove(p);
         }
     }
 };
 
 ExclusiveData<SigIdSet> sigIdSet;
 
-void**
-Instance::addressOfTableBase(size_t tableIndex) const
-{
-    MOZ_ASSERT(metadata().tables[tableIndex].globalDataOffset >= InitialGlobalDataBytes);
-    return (void**)(codeSegment().globalData() + metadata().tables[tableIndex].globalDataOffset);
-}
-
 const void**
 Instance::addressOfSigId(const SigIdDesc& sigId) const
 {
     MOZ_ASSERT(sigId.globalDataOffset() >= InitialGlobalDataBytes);
     return (const void**)(codeSegment().globalData() + sigId.globalDataOffset());
 }
 
 FuncImportTls&
 Instance::funcImportTls(const FuncImport& fi)
 {
     MOZ_ASSERT(fi.tlsDataOffset() >= InitialGlobalDataBytes);
     return *(FuncImportTls*)(codeSegment().globalData() + fi.tlsDataOffset());
 }
 
+TableTls&
+Instance::tableTls(const TableDesc& td) const
+{
+    MOZ_ASSERT(td.globalDataOffset >= InitialGlobalDataBytes);
+    return *(TableTls*)(codeSegment().globalData() + td.globalDataOffset);
+}
+
 bool
 Instance::callImport(JSContext* cx, uint32_t funcImportIndex, unsigned argc, const uint64_t* argv,
                      MutableHandleValue rval)
 {
     const FuncImport& fi = metadata().funcImports[funcImportIndex];
 
     InvokeArgs args(cx);
     if (!args.init(argc))
@@ -342,16 +342,23 @@ Instance::Instance(JSContext* cx,
         } else {
             import.tls = &tlsData_;
             import.code = codeBase() + fi.interpExitCodeOffset();
             import.baselineScript = nullptr;
             import.obj = f;
         }
     }
 
+    for (size_t i = 0; i < tables_.length(); i++) {
+        const TableDesc& td = metadata().tables[i];
+        TableTls& table = tableTls(td);
+        table.length = tables_[i]->length();
+        table.base = tables_[i]->base();
+    }
+
     uint8_t* globalData = code_->segment().globalData();
 
     for (size_t i = 0; i < metadata().globals.length(); i++) {
         const GlobalDesc& global = metadata().globals[i];
         if (global.isConstant())
             continue;
 
         uint8_t* globalAddr = globalData + global.offset();
@@ -375,27 +382,29 @@ Instance::Instance(JSContext* cx,
             }
             break;
           }
           case GlobalKind::Constant: {
             MOZ_CRASH("skipped at the top");
           }
         }
     }
-
-    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;
 
+    for (const SharedTable& table : tables_) {
+        if (table->movingGrowable() && !table->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;
@@ -791,25 +800,35 @@ Instance::callExport(JSContext* cx, uint
 
     if (retObj)
         args.rval().set(ObjectValue(*retObj));
 
     return true;
 }
 
 void
-Instance::onMovingGrow(uint8_t* prevMemoryBase)
+Instance::onMovingGrowMemory(uint8_t* prevMemoryBase)
 {
     MOZ_ASSERT(!isAsmJS());
     ArrayBufferObject& buffer = memory_->buffer().as<ArrayBufferObject>();
     tlsData_.memoryBase = buffer.dataPointer();
     code_->segment().onMovingGrow(prevMemoryBase, metadata(), buffer);
 }
 
 void
+Instance::onMovingGrowTable()
+{
+    MOZ_ASSERT(!isAsmJS());
+    MOZ_ASSERT(tables_.length() == 1);
+    TableTls& table = tableTls(metadata().tables[0]);
+    table.length = tables_[0]->length();
+    table.base = tables_[0]->base();
+}
+
+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
@@ -41,43 +41,35 @@ class Instance
     JSCompartment* const                 compartment_;
     ReadBarrieredWasmInstanceObject      object_;
     const UniqueCode                     code_;
     GCPtrWasmMemoryObject                memory_;
     SharedTableVector                    tables_;
     TlsData                              tlsData_;
 
     // Internal helpers:
-    void** addressOfTableBase(size_t tableIndex) const;
     const void** addressOfSigId(const SigIdDesc& sigId) const;
     FuncImportTls& funcImportTls(const FuncImport& fi);
+    TableTls& tableTls(const TableDesc& td) const;
 
     // 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);
 
     // 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);
-
-    // Called by WasmTableObject to barrier table writes.
-    friend class Table;
-    WasmInstanceObject* objectUnbarriered() const;
-
   public:
     Instance(JSContext* cx,
              HandleWasmInstanceObject object,
              UniqueCode code,
              HandleWasmMemoryObject memory,
              SharedTableVector&& tables,
              Handle<FunctionVector> funcImports,
              const ValVector& globalImports);
@@ -97,19 +89,21 @@ class Instance
     SharedMem<uint8_t*> memoryBase() const;
     size_t memoryLength() const;
     size_t memoryMappedSize() const;
     bool memoryAccessInGuardRegion(uint8_t* addr, unsigned numBytes) const;
     TlsData& tlsData() { return tlsData_; }
 
     // This method returns a pointer to the GC object that owns this Instance.
     // Instances may be reached via weak edges (e.g., Compartment::instances_)
-    // so this perform a read-barrier on the returned object.
+    // so this perform a read-barrier on the returned object unless the barrier
+    // is explicitly waived.
 
     WasmInstanceObject* object() const;
+    WasmInstanceObject* objectUnbarriered() 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);
 
     // Initially, calls to imports in wasm code call out through the generic
     // callImport method. If the imported callee gets JIT compiled and the types
@@ -119,16 +113,21 @@ class Instance
 
     void deoptimizeImportExit(uint32_t funcImportIndex);
 
     // Called by simulators to check whether accessing 'numBytes' starting at
     // 'addr' would trigger a fault and be safely handled by signal handlers.
 
     bool memoryAccessWouldFault(uint8_t* addr, unsigned numBytes);
 
+    // Called by Wasm(Memory|Table)Object when a moving resize occurs:
+
+    void onMovingGrowMemory(uint8_t* prevMemoryBase);
+    void onMovingGrowTable();
+
     // See Code::ensureProfilingState comment.
 
     MOZ_MUST_USE bool ensureProfilingState(JSContext* cx, bool enabled);
 
     // about:memory reporting:
 
     void addSizeOfMisc(MallocSizeOf mallocSizeOf,
                        Metadata::SeenSet* seenMetadata,
--- a/js/src/asmjs/WasmIonCompile.cpp
+++ b/js/src/asmjs/WasmIonCompile.cpp
@@ -1005,21 +1005,21 @@ class FunctionCompiler
         }
 
         const SigWithId& sig = mg_.sigs[sigIndex];
 
         CalleeDesc callee;
         if (mg_.isAsmJS()) {
             MOZ_ASSERT(sig.id.kind() == SigIdDesc::Kind::None);
             const TableDesc& table = mg_.tables[mg_.asmJSSigToTableIndex[sigIndex]];
-            MOZ_ASSERT(IsPowerOfTwo(table.initial));
+            MOZ_ASSERT(IsPowerOfTwo(table.limits.initial));
             MOZ_ASSERT(!table.external);
             MOZ_ASSERT(call.tlsStackOffset_ == MWasmCall::DontSaveTls);
 
-            MConstant* mask = MConstant::New(alloc(), Int32Value(table.initial - 1));
+            MConstant* mask = MConstant::New(alloc(), Int32Value(table.limits.initial - 1));
             curBlock_->add(mask);
             MBitAnd* maskedIndex = MBitAnd::NewAsmJS(alloc(), index, mask, MIRType::Int32);
             curBlock_->add(maskedIndex);
 
             index = maskedIndex;
             callee = CalleeDesc::asmJSTable(table);
         } else {
             MOZ_ASSERT(sig.id.kind() != SigIdDesc::Kind::None);
--- a/js/src/asmjs/WasmJS.cpp
+++ b/js/src/asmjs/WasmJS.cpp
@@ -322,16 +322,68 @@ js::InitWasmClass(JSContext* cx, HandleO
     if (!JS_DefineFunctions(cx, Wasm, wasm_static_methods))
         return nullptr;
 
     global->as<GlobalObject>().setConstructor(JSProto_Wasm, ObjectValue(*Wasm));
     return Wasm;
 }
 
 // ============================================================================
+// Common functions
+
+static bool
+GetResizableLimits(JSContext* cx, HandleObject obj, const char* kind, ResizableLimits* limits)
+{
+    JSAtom* initialAtom = Atomize(cx, "initial", strlen("initial"));
+    if (!initialAtom)
+        return false;
+    RootedId initialId(cx, AtomToId(initialAtom));
+
+    RootedValue initialVal(cx);
+    if (!GetProperty(cx, obj, obj, initialId, &initialVal))
+        return false;
+
+    double initialDbl;
+    if (!ToInteger(cx, initialVal, &initialDbl))
+        return false;
+
+    if (initialDbl < 0 || initialDbl > UINT32_MAX) {
+        JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_SIZE, kind, "initial");
+        return false;
+    }
+
+    limits->initial = uint32_t(initialDbl);
+
+    JSAtom* maximumAtom = Atomize(cx, "maximum", strlen("maximum"));
+    if (!maximumAtom)
+        return false;
+    RootedId maximumId(cx, AtomToId(maximumAtom));
+
+    bool found;
+    if (HasProperty(cx, obj, maximumId, &found) && found) {
+        RootedValue maxVal(cx);
+        if (!GetProperty(cx, obj, obj, maximumId, &maxVal))
+            return false;
+
+        double maxDbl;
+        if (!ToInteger(cx, maxVal, &maxDbl))
+            return false;
+
+        if (maxDbl < initialDbl || maxDbl > UINT32_MAX) {
+            JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_SIZE, kind, "maximum");
+            return false;
+        }
+
+        limits->maximum = Some(uint32_t(maxDbl));
+    }
+
+    return true;
+}
+
+// ============================================================================
 // WebAssembly.Module class and methods
 
 const ClassOps WasmModuleObject::classOps_ =
 {
     nullptr, /* addProperty */
     nullptr, /* delProperty */
     nullptr, /* getProperty */
     nullptr, /* setProperty */
@@ -768,63 +820,39 @@ WasmMemoryObject::construct(JSContext* c
     if (!args.requireAtLeast(cx, "WebAssembly.Memory", 1))
         return false;
 
     if (!args.get(0).isObject()) {
         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_DESC_ARG, "memory");
         return false;
     }
 
-    JSAtom* initialAtom = Atomize(cx, "initial", strlen("initial"));
-    if (!initialAtom)
-        return false;
-    RootedId initialId(cx, AtomToId(initialAtom));
-
-    JSAtom* maximumAtom = Atomize(cx, "maximum", strlen("maximum"));
-    if (!maximumAtom)
-        return false;
-    RootedId maximumId(cx, AtomToId(maximumAtom));
-
     RootedObject obj(cx, &args[0].toObject());
-    RootedValue initialVal(cx);
-    if (!GetProperty(cx, obj, obj, initialId, &initialVal))
+    ResizableLimits limits;
+    if (!GetResizableLimits(cx, obj, "Memory", &limits))
         return false;
 
-    double initialDbl;
-    if (!ToInteger(cx, initialVal, &initialDbl))
-        return false;
-
-    if (initialDbl < 0 || initialDbl > INT32_MAX / PageSize) {
+    if (limits.initial > UINT32_MAX / PageSize) {
         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_SIZE, "Memory", "initial");
         return false;
     }
 
-    Maybe<uint32_t> maxSize;
-
-    bool found;
-    if (HasProperty(cx, obj, maximumId, &found) && found) {
-        RootedValue maxVal(cx);
-        if (!GetProperty(cx, obj, obj, maximumId, &maxVal))
-            return false;
+    limits.initial *= PageSize;
 
-        double maxDbl;
-        if (!ToInteger(cx, maxVal, &maxDbl))
-            return false;
-
-        if (maxDbl < initialDbl || maxDbl > UINT32_MAX / PageSize) {
-            JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_SIZE, "Memory",
-                                 "maximum");
+    if (limits.maximum) {
+        if (limits.maximum.value() > UINT32_MAX / PageSize) {
+            JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_SIZE, "Memory", "maximum");
             return false;
         }
 
-        maxSize = Some<uint32_t>(uint32_t(maxDbl) * PageSize);
+        limits.maximum = Some(limits.maximum.value() * PageSize);
     }
 
-    uint32_t initialSize = uint32_t(initialDbl) * PageSize;
-    RootedArrayBufferObject buffer(cx, ArrayBufferObject::createForWasm(cx, initialSize, maxSize));
+    RootedArrayBufferObject buffer(cx,
+        ArrayBufferObject::createForWasm(cx, limits.initial, limits.maximum));
     if (!buffer)
         return false;
 
     RootedObject proto(cx, &cx->global()->getPrototype(JSProto_WasmMemory).toObject());
     RootedWasmMemoryObject memoryObj(cx, WasmMemoryObject::create(cx, buffer, proto));
     if (!memoryObj)
         return false;
 
@@ -863,24 +891,24 @@ WasmMemoryObject::growImpl(JSContext* cx
 {
     RootedWasmMemoryObject memory(cx, &args.thisv().toObject().as<WasmMemoryObject>());
 
     double deltaDbl;
     if (!ToInteger(cx, args.get(0), &deltaDbl))
         return false;
 
     if (deltaDbl < 0 || deltaDbl > UINT32_MAX) {
-        JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_GROW);
+        JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_GROW, "memory");
         return false;
     }
 
     uint32_t ret = grow(memory, uint32_t(deltaDbl), cx);
 
     if (ret == uint32_t(-1)) {
-        JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_GROW);
+        JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_GROW, "memory");
         return false;
     }
 
     args.rval().setInt32(ret);
     return true;
 }
 
 /* static */ bool
@@ -995,17 +1023,17 @@ WasmMemoryObject::grow(HandleWasmMemoryO
 
     memory->setReservedSlot(BUFFER_SLOT, ObjectValue(*newBuf));
 
     // Only notify moving-grow-observers after the BUFFER_SLOT has been updated
     // since observers will call buffer().
     if (memory->hasObservers()) {
         MOZ_ASSERT(prevMemoryBase);
         for (InstanceSet::Range r = memory->observers().all(); !r.empty(); r.popFront())
-            r.front()->instance().onMovingGrow(prevMemoryBase);
+            r.front()->instance().onMovingGrowMemory(prevMemoryBase);
     }
 
     return oldNumPages;
 }
 
 // ============================================================================
 // WebAssembly.Table class and methods
 
@@ -1053,34 +1081,31 @@ WasmTableObject::finalize(FreeOp* fop, J
 WasmTableObject::trace(JSTracer* trc, JSObject* obj)
 {
     WasmTableObject& tableObj = obj->as<WasmTableObject>();
     if (!tableObj.isNewborn())
         tableObj.table().tracePrivate(trc);
 }
 
 /* static */ WasmTableObject*
-WasmTableObject::create(JSContext* cx, uint32_t length)
+WasmTableObject::create(JSContext* cx, ResizableLimits limits)
 {
     RootedObject proto(cx, &cx->global()->getPrototype(JSProto_WasmTable).toObject());
 
     AutoSetNewObjectMetadata metadata(cx);
     RootedWasmTableObject obj(cx, NewObjectWithGivenProto<WasmTableObject>(cx, proto));
     if (!obj)
         return nullptr;
 
     MOZ_ASSERT(obj->isNewborn());
 
-    TableDesc desc;
-    desc.kind = TableKind::AnyFunction;
-    desc.external = true;
-    desc.initial = length;
-    desc.maximum = length;
+    TableDesc td(TableKind::AnyFunction, limits);
+    td.external = true;
 
-    SharedTable table = Table::create(cx, desc, obj);
+    SharedTable table = Table::create(cx, td, obj);
     if (!table)
         return nullptr;
 
     obj->initReservedSlot(TABLE_SLOT, PrivateValue(table.forget().take()));
 
     MOZ_ASSERT(!obj->isNewborn());
     return obj;
 }
@@ -1097,65 +1122,45 @@ WasmTableObject::construct(JSContext* cx
         return false;
 
     if (!args.get(0).isObject()) {
         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_DESC_ARG, "table");
         return false;
     }
 
     RootedObject obj(cx, &args[0].toObject());
-    RootedId id(cx);
-    RootedValue val(cx);
 
     JSAtom* elementAtom = Atomize(cx, "element", strlen("element"));
     if (!elementAtom)
         return false;
-    id = AtomToId(elementAtom);
-    if (!GetProperty(cx, obj, obj, id, &val))
+    RootedId elementId(cx, AtomToId(elementAtom));
+
+    RootedValue elementVal(cx);
+    if (!GetProperty(cx, obj, obj, elementId, &elementVal))
         return false;
 
-    if (!val.isString()) {
-        JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_ELEMENT);
-        return false;
-    }
-
-    JSLinearString* str = val.toString()->ensureLinear(cx);
-    if (!str)
-        return false;
-
-    if (!StringEqualsAscii(str, "anyfunc")) {
+    if (!elementVal.isString()) {
         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_ELEMENT);
         return false;
     }
 
-    JSAtom* initialAtom = Atomize(cx, "initial", strlen("initial"));
-    if (!initialAtom)
-        return false;
-    id = AtomToId(initialAtom);
-    if (!GetProperty(cx, obj, obj, id, &val))
+    JSLinearString* elementStr = elementVal.toString()->ensureLinear(cx);
+    if (!elementStr)
         return false;
 
-    double initialDbl;
-    if (!ToInteger(cx, val, &initialDbl))
-        return false;
-
-    if (initialDbl < 0 || initialDbl > INT32_MAX) {
-        JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_SIZE, "Table", "initial");
+    if (!StringEqualsAscii(elementStr, "anyfunc")) {
+        JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_ELEMENT);
         return false;
     }
 
-    uint32_t initial = uint32_t(initialDbl);
-    MOZ_ASSERT(double(initial) == initialDbl);
+    ResizableLimits limits;
+    if (!GetResizableLimits(cx, obj, "Table", &limits))
+        return false;
 
-    if (initial > MaxTableElems) {
-        JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_SIZE, "Table", "initial");
-        return false;
-    }
-
-    RootedWasmTableObject table(cx, WasmTableObject::create(cx, initial));
+    RootedWasmTableObject table(cx, WasmTableObject::create(cx, limits));
     if (!table)
         return false;
 
     args.rval().setObject(*table);
     return true;
 }
 
 static bool
@@ -1280,20 +1285,53 @@ WasmTableObject::setImpl(JSContext* cx, 
 
 /* static */ bool
 WasmTableObject::set(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     return CallNonGenericMethod<IsTable, setImpl>(cx, args);
 }
 
+/* static */ bool
+WasmTableObject::growImpl(JSContext* cx, const CallArgs& args)
+{
+    RootedWasmTableObject table(cx, &args.thisv().toObject().as<WasmTableObject>());
+
+    double deltaDbl;
+    if (!ToInteger(cx, args.get(0), &deltaDbl))
+        return false;
+
+    if (deltaDbl < 0 || deltaDbl > UINT32_MAX) {
+        JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_GROW, "table");
+        return false;
+    }
+
+    uint32_t ret = table->table().grow(uint32_t(deltaDbl), cx);
+
+    if (ret == uint32_t(-1)) {
+        JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_GROW, "table");
+        return false;
+    }
+
+    args.rval().setInt32(ret);
+    return true;
+}
+
+/* static */ bool
+WasmTableObject::grow(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    return CallNonGenericMethod<IsTable, growImpl>(cx, args);
+}
+
 const JSFunctionSpec WasmTableObject::methods[] =
 {
     JS_FN("get", WasmTableObject::get, 1, 0),
     JS_FN("set", WasmTableObject::set, 2, 0),
+    JS_FN("grow", WasmTableObject::grow, 1, 0),
     JS_FS_END
 };
 
 Table&
 WasmTableObject::table() const
 {
     return *(Table*)getReservedSlot(TABLE_SLOT).toPrivate();
 }
--- a/js/src/asmjs/WasmJS.h
+++ b/js/src/asmjs/WasmJS.h
@@ -205,26 +205,28 @@ class WasmTableObject : public NativeObj
     static void finalize(FreeOp* fop, JSObject* obj);
     static void trace(JSTracer* trc, JSObject* obj);
     static bool lengthGetterImpl(JSContext* cx, const CallArgs& args);
     static bool lengthGetter(JSContext* cx, unsigned argc, Value* vp);
     static bool getImpl(JSContext* cx, const CallArgs& args);
     static bool get(JSContext* cx, unsigned argc, Value* vp);
     static bool setImpl(JSContext* cx, const CallArgs& args);
     static bool set(JSContext* cx, unsigned argc, Value* vp);
+    static bool growImpl(JSContext* cx, const CallArgs& args);
+    static bool grow(JSContext* cx, unsigned argc, Value* vp);
 
   public:
     static const unsigned RESERVED_SLOTS = 1;
     static const Class class_;
     static const JSPropertySpec properties[];
     static const JSFunctionSpec methods[];
     static bool construct(JSContext*, unsigned, Value*);
 
     // Note that, after creation, a WasmTableObject's table() is not initialized
     // and must be initialized before use.
 
-    static WasmTableObject* create(JSContext* cx, uint32_t length);
+    static WasmTableObject* create(JSContext* cx, wasm::ResizableLimits limits);
     wasm::Table& table() const;
 };
 
 } // namespace js
 
 #endif // wasm_js_h
--- a/js/src/asmjs/WasmModule.cpp
+++ b/js/src/asmjs/WasmModule.cpp
@@ -538,16 +538,41 @@ Module::instantiateFunctions(JSContext* 
             JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_IMPORT_SIG);
             return false;
         }
     }
 
     return true;
 }
 
+static bool
+CheckResizableLimits(JSContext* cx, uint32_t declaredMin, Maybe<uint32_t> declaredMax,
+                     uint32_t actualLength, Maybe<uint32_t> actualMax,
+                     bool isAsmJS, const char* kind)
+{
+    if (isAsmJS) {
+        MOZ_ASSERT(actualLength >= declaredMin);
+        MOZ_ASSERT(!declaredMax);
+        MOZ_ASSERT(actualLength == actualMax.value());
+        return true;
+    }
+
+    if (actualLength < declaredMin || actualLength > declaredMax.valueOr(UINT32_MAX)) {
+        JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_IMP_SIZE, kind);
+        return false;
+    }
+
+    if ((actualMax && (!declaredMax || *actualMax > *declaredMax)) || (!actualMax && declaredMax)) {
+        JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_IMP_MAX, kind);
+        return false;
+    }
+
+    return true;
+}
+
 // asm.js module instantiation supplies its own buffer, but for wasm, create and
 // initialize the buffer if one is requested. Either way, the buffer is wrapped
 // in a WebAssembly.Memory object which is what the Instance stores.
 bool
 Module::instantiateMemory(JSContext* cx, MutableHandleWasmMemoryObject memory) const
 {
     if (!metadata_->usesMemory()) {
         MOZ_ASSERT(!memory);
@@ -558,32 +583,21 @@ Module::instantiateMemory(JSContext* cx,
     uint32_t declaredMin = metadata_->minMemoryLength;
     Maybe<uint32_t> declaredMax = metadata_->maxMemoryLength;
 
     if (memory) {
         ArrayBufferObjectMaybeShared& buffer = memory->buffer();
         MOZ_ASSERT_IF(metadata_->isAsmJS(), buffer.isPreparedForAsmJS());
         MOZ_ASSERT_IF(!metadata_->isAsmJS(), buffer.as<ArrayBufferObject>().isWasm());
 
-        uint32_t actualLength = buffer.byteLength();
-        if (actualLength < declaredMin || actualLength > declaredMax.valueOr(UINT32_MAX)) {
-            JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_IMP_SIZE, "Memory");
+        if (!CheckResizableLimits(cx, declaredMin, declaredMax,
+                                  buffer.byteLength(), buffer.wasmMaxSize(),
+                                  metadata_->isAsmJS(), "Memory")) {
             return false;
         }
-
-        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());
         MOZ_ASSERT(metadata_->memoryUsage == MemoryUsage::Unshared);
 
         RootedArrayBufferObjectMaybeShared buffer(cx,
             ArrayBufferObject::createForWasm(cx, declaredMin, declaredMax));
         if (!buffer)
             return false;
@@ -603,43 +617,44 @@ Module::instantiateMemory(JSContext* cx,
 bool
 Module::instantiateTable(JSContext* cx, MutableHandleWasmTableObject tableObj,
                          SharedTableVector* tables) const
 {
     if (tableObj) {
         MOZ_ASSERT(!metadata_->isAsmJS());
 
         MOZ_ASSERT(metadata_->tables.length() == 1);
-        const TableDesc& tableDesc = metadata_->tables[0];
-        MOZ_ASSERT(tableDesc.external);
+        const TableDesc& td = metadata_->tables[0];
+        MOZ_ASSERT(td.external);
 
         Table& table = tableObj->table();
-        if (table.length() < tableDesc.initial || table.length() > tableDesc.maximum) {
-            JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_IMP_SIZE, "Table");
+        if (!CheckResizableLimits(cx, td.limits.initial, td.limits.maximum,
+                                  table.length(), table.maximum(),
+                                  metadata_->isAsmJS(), "Table")) {
             return false;
         }
 
         if (!tables->append(&table)) {
             ReportOutOfMemory(cx);
             return false;
         }
     } else {
-        for (const TableDesc& tableDesc : metadata_->tables) {
+        for (const TableDesc& td : metadata_->tables) {
             SharedTable table;
-            if (tableDesc.external) {
+            if (td.external) {
                 MOZ_ASSERT(!tableObj);
-                MOZ_ASSERT(tableDesc.kind == TableKind::AnyFunction);
+                MOZ_ASSERT(td.kind == TableKind::AnyFunction);
 
-                tableObj.set(WasmTableObject::create(cx, tableDesc.initial));
+                tableObj.set(WasmTableObject::create(cx, td.limits));
                 if (!tableObj)
                     return false;
 
                 table = &tableObj->table();
             } else {
-                table = Table::create(cx, tableDesc, /* HandleWasmTableObject = */ nullptr);
+                table = Table::create(cx, td, /* HandleWasmTableObject = */ nullptr);
                 if (!table)
                     return false;
             }
 
             if (!tables->emplaceBack(table)) {
                 ReportOutOfMemory(cx);
                 return false;
             }
--- a/js/src/asmjs/WasmTable.cpp
+++ b/js/src/asmjs/WasmTable.cpp
@@ -13,48 +13,53 @@
  * 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/WasmTable.h"
 
+#include "mozilla/CheckedInt.h"
+
 #include "jscntxt.h"
 
 #include "asmjs/WasmInstance.h"
 #include "asmjs/WasmJS.h"
 
 using namespace js;
 using namespace js::wasm;
+using mozilla::CheckedInt;
+
+Table::Table(JSContext* cx, const TableDesc& desc, HandleWasmTableObject maybeObject,
+             UniqueByteArray array)
+  : maybeObject_(maybeObject),
+    observers_(cx->zone(), InstanceSet()),
+    array_(Move(array)),
+    kind_(desc.kind),
+    length_(desc.limits.initial),
+    maximum_(desc.limits.maximum),
+    external_(desc.external)
+{}
 
 /* static */ SharedTable
 Table::create(JSContext* cx, const TableDesc& desc, HandleWasmTableObject maybeObject)
 {
-    SharedTable table = cx->new_<Table>();
-    if (!table)
-        return nullptr;
-
     // The raw element type of a Table depends on whether it is external: an
     // external table can contain functions from multiple instances and thus
     // must store an additional instance pointer in each element.
-    void* array;
+    UniqueByteArray array;
     if (desc.external)
-        array = cx->pod_calloc<ExternalTableElem>(desc.initial);
+        array.reset((uint8_t*)cx->pod_calloc<ExternalTableElem>(desc.limits.initial));
     else
-        array = cx->pod_calloc<void*>(desc.initial);
+        array.reset((uint8_t*)cx->pod_calloc<void*>(desc.limits.initial));
     if (!array)
         return nullptr;
 
-    table->maybeObject_.set(maybeObject);
-    table->array_.reset((uint8_t*)array);
-    table->kind_ = desc.kind;
-    table->length_ = desc.initial;
-    table->external_ = desc.external;
-    return table;
+    return SharedTable(cx->new_<Table>(cx, desc, maybeObject, Move(array)));
 }
 
 void
 Table::tracePrivate(JSTracer* trc)
 {
     // If this table has a WasmTableObject, then this method is only called by
     // WasmTableObject's trace hook so maybeObject_ must already be marked.
     // TraceEdge is called so that the pointer can be updated during a moving
@@ -128,13 +133,79 @@ Table::setNull(uint32_t index)
     ExternalTableElem& elem = externalArray()[index];
     if (elem.tls)
         JSObject::writeBarrierPre(elem.tls->instance->objectUnbarriered());
 
     elem.code = nullptr;
     elem.tls = nullptr;
 }
 
+uint32_t
+Table::grow(uint32_t delta, JSContext* cx)
+{
+    // This isn't just an optimization: movingGrowable() assumes that
+    // onMovingGrowTable does not fire when length == maximum.
+    if (!delta)
+        return length_;
+
+    uint32_t oldLength = length_;
+
+    CheckedInt<uint32_t> newLength = oldLength;
+    newLength += delta;
+    if (!newLength.isValid())
+        return -1;
+
+    if (maximum_ && newLength.value() > maximum_.value())
+        return -1;
+
+    MOZ_ASSERT(movingGrowable());
+
+    JSRuntime* rt = cx;  // Use JSRuntime's MallocProvider to avoid throwing.
+
+    // Note that realloc does not release array_'s pointee (which is returned by
+    // externalArray()) on failure which is exactly what we need here.
+    ExternalTableElem* newArray = rt->pod_realloc(externalArray(), length_, newLength.value());
+    if (!newArray)
+        return -1;
+    Unused << array_.release();
+    array_.reset((uint8_t*)newArray);
+
+    // Realloc does not zero the delta for us.
+    PodZero(newArray + length_, delta);
+    length_ = newLength.value();
+
+    if (observers_.initialized()) {
+        for (InstanceSet::Range r = observers_.all(); !r.empty(); r.popFront())
+            r.front()->instance().onMovingGrowTable();
+    }
+
+    return oldLength;
+}
+
+bool
+Table::movingGrowable() const
+{
+    return !maximum_ || length_ < maximum_.value();
+}
+
+bool
+Table::addMovingGrowObserver(JSContext* cx, WasmInstanceObject* instance)
+{
+    MOZ_ASSERT(movingGrowable());
+
+    if (!observers_.initialized() && !observers_.init()) {
+        ReportOutOfMemory(cx);
+        return false;
+    }
+
+    if (!observers_.putNew(instance)) {
+        ReportOutOfMemory(cx);
+        return false;
+    }
+
+    return true;
+}
+
 size_t
 Table::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const
 {
     return mallocSizeOf(array_.get());
 }
--- a/js/src/asmjs/WasmTable.h
+++ b/js/src/asmjs/WasmTable.h
@@ -15,56 +15,71 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 #ifndef wasm_table_h
 #define wasm_table_h
 
 #include "asmjs/WasmCode.h"
+#include "gc/Policy.h"
 
 namespace js {
 namespace wasm {
 
 // A Table is an indexable array of opaque values. Tables are first-class
 // stateful objects exposed to WebAssembly. asm.js also uses Tables to represent
 // its homogeneous function-pointer tables.
 
 class Table : public ShareableBase<Table>
 {
+    using InstanceSet = GCHashSet<ReadBarrieredWasmInstanceObject,
+                                  MovableCellHasher<ReadBarrieredWasmInstanceObject>,
+                                  SystemAllocPolicy>;
     typedef UniquePtr<uint8_t[], JS::FreePolicy> UniqueByteArray;
 
     ReadBarrieredWasmTableObject maybeObject_;
+    JS::WeakCache<InstanceSet>   observers_;
     UniqueByteArray              array_;
-    TableKind                    kind_;
+    const TableKind              kind_;
     uint32_t                     length_;
-    bool                         external_;
+    const Maybe<uint32_t>        maximum_;
+    const bool                   external_;
+
+    template <class> friend struct js::MallocProvider;
+    Table(JSContext* cx, const TableDesc& td, HandleWasmTableObject maybeObject,
+          UniqueByteArray array);
 
     void tracePrivate(JSTracer* trc);
     friend class js::WasmTableObject;
 
   public:
     static RefPtr<Table> create(JSContext* cx, const TableDesc& desc,
                                 HandleWasmTableObject maybeObject);
     void trace(JSTracer* trc);
 
     bool external() const { return external_; }
     bool isTypedFunction() const { return kind_ == TableKind::TypedFunction; }
     uint32_t length() const { return length_; }
+    Maybe<uint32_t> maximum() const { return maximum_; }
     uint8_t* base() const { return array_.get(); }
 
     // All updates must go through a set() function with the exception of
     // (profiling) updates to the callee pointer that do not change which
     // logical function is being called.
 
     void** internalArray() const;
     ExternalTableElem* externalArray() const;
     void set(uint32_t index, void* code, Instance& instance);
     void setNull(uint32_t index);
 
+    uint32_t grow(uint32_t delta, JSContext* cx);
+    bool movingGrowable() const;
+    bool addMovingGrowObserver(JSContext* cx, WasmInstanceObject* instance);
+
     // about:memory reporting:
 
     size_t sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const;
 };
 
 typedef RefPtr<Table> SharedTable;
 typedef Vector<SharedTable, 0, SystemAllocPolicy> SharedTableVector;
 
--- a/js/src/asmjs/WasmTypes.h
+++ b/js/src/asmjs/WasmTypes.h
@@ -940,156 +940,51 @@ struct Assumptions
 // A Module can either be asm.js or wasm.
 
 enum ModuleKind
 {
     Wasm,
     AsmJS
 };
 
+// Represents the resizable limits of memories and tables.
+
+struct ResizableLimits
+{
+    uint32_t initial;
+    Maybe<uint32_t> maximum;
+};
+
 // TableDesc describes a table as well as the offset of the table's base pointer
 // in global memory. Currently, wasm only has "any function" and asm.js only
 // "typed function".
 
 enum class TableKind
 {
     AnyFunction,
     TypedFunction
 };
 
 struct TableDesc
 {
     TableKind kind;
     bool external;
     uint32_t globalDataOffset;
-    uint32_t initial;
-    uint32_t maximum;
+    ResizableLimits limits;
 
-    TableDesc() { PodZero(this); }
+    TableDesc() = default;
+    TableDesc(TableKind kind, ResizableLimits limits)
+     : kind(kind),
+       external(false),
+       globalDataOffset(UINT32_MAX),
+       limits(limits)
+    {}
 };
 
-WASM_DECLARE_POD_VECTOR(TableDesc, TableDescVector)
-
-// CalleeDesc describes how to compile one of the variety of asm.js/wasm calls.
-// This is hoisted into WasmTypes.h for sharing between Ion and Baseline.
-
-class CalleeDesc
-{
-  public:
-    enum Which {
-        // Calls a function defined in the same module by its index.
-        Definition,
-
-        // Calls the import identified by the offset of its FuncImportTls in
-        // thread-local data.
-        Import,
-
-        // Calls a WebAssembly table (heterogeneous, index must be bounds
-        // checked, callee instance depends on TableDesc).
-        WasmTable,
-
-        // Calls an asm.js table (homogeneous, masked index, same-instance).
-        AsmJSTable,
-
-        // Call a C++ function identified by SymbolicAddress.
-        Builtin,
-
-        // Like Builtin, but automatically passes Instance* as first argument.
-        BuiltinInstanceMethod
-    };
-
-  private:
-    Which which_;
-    union U {
-        U() {}
-        uint32_t funcDefIndex_;
-        struct {
-            uint32_t globalDataOffset_;
-        } import;
-        struct {
-            TableDesc desc_;
-            SigIdDesc sigId_;
-        } table;
-        SymbolicAddress builtin_;
-    } u;
-
-  public:
-    CalleeDesc() {}
-    static CalleeDesc definition(uint32_t funcDefIndex) {
-        CalleeDesc c;
-        c.which_ = Definition;
-        c.u.funcDefIndex_ = funcDefIndex;
-        return c;
-    }
-    static CalleeDesc import(uint32_t globalDataOffset) {
-        CalleeDesc c;
-        c.which_ = Import;
-        c.u.import.globalDataOffset_ = globalDataOffset;
-        return c;
-    }
-    static CalleeDesc wasmTable(const TableDesc& desc, SigIdDesc sigId) {
-        CalleeDesc c;
-        c.which_ = WasmTable;
-        c.u.table.desc_ = desc;
-        c.u.table.sigId_ = sigId;
-        return c;
-    }
-    static CalleeDesc asmJSTable(const TableDesc& desc) {
-        CalleeDesc c;
-        c.which_ = AsmJSTable;
-        c.u.table.desc_ = desc;
-        return c;
-    }
-    static CalleeDesc builtin(SymbolicAddress callee) {
-        CalleeDesc c;
-        c.which_ = Builtin;
-        c.u.builtin_ = callee;
-        return c;
-    }
-    static CalleeDesc builtinInstanceMethod(SymbolicAddress callee) {
-        CalleeDesc c;
-        c.which_ = BuiltinInstanceMethod;
-        c.u.builtin_ = callee;
-        return c;
-    }
-    Which which() const {
-        return which_;
-    }
-    uint32_t funcDefIndex() const {
-        MOZ_ASSERT(which_ == Definition);
-        return u.funcDefIndex_;
-    }
-    uint32_t importGlobalDataOffset() const {
-        MOZ_ASSERT(which_ == Import);
-        return u.import.globalDataOffset_;
-    }
-    bool isTable() const {
-        return which_ == WasmTable || which_ == AsmJSTable;
-    }
-    uint32_t tableGlobalDataOffset() const {
-        MOZ_ASSERT(isTable());
-        return u.table.desc_.globalDataOffset;
-    }
-    uint32_t wasmTableLength() const {
-        MOZ_ASSERT(which_ == WasmTable);
-        return u.table.desc_.initial;
-    }
-    bool wasmTableIsExternal() const {
-        MOZ_ASSERT(which_ == WasmTable);
-        return u.table.desc_.external;
-    }
-    SigIdDesc wasmTableSigId() const {
-        MOZ_ASSERT(which_ == WasmTable);
-        return u.table.sigId_;
-    }
-    SymbolicAddress builtin() const {
-        MOZ_ASSERT(which_ == Builtin || which_ == BuiltinInstanceMethod);
-        return u.builtin_;
-    }
-};
+typedef Vector<TableDesc, 0, SystemAllocPolicy> TableDescVector;
 
 // ExportArg holds the unboxed operands to the wasm entry trampoline which can
 // be called through an ExportFuncPtr.
 
 struct ExportArg
 {
     uint64_t lo;
     uint64_t hi;
@@ -1150,34 +1045,168 @@ struct FuncImportTls
 
     // A GC pointer which keeps the callee alive. For imported wasm functions,
     // this points to the wasm function's WasmInstanceObject. For all other
     // imported functions, 'obj' points to the JSFunction.
     GCPtrObject obj;
     static_assert(sizeof(GCPtrObject) == sizeof(void*), "for JIT access");
 };
 
-// When a table can be shared between instances (it is "external"), the internal
-// representation is an array of ExternalTableElem instead of just an array of
-// code pointers.
+// TableTls describes the region of wasm global memory allocated in the
+// instance's thread-local storage which is accessed directly from JIT code
+// to bounds-check and index the table.
+
+struct TableTls
+{
+    // Length of the table in number of elements (not bytes).
+    uint32_t length;
+
+    // Pointer to the array of elements (of type either ExternalTableElem or
+    // void*).
+    void* base;
+};
+
+// When a table can contain functions from other instances (it is "external"),
+// the internal representation is an array of ExternalTableElem instead of just
+// an array of code pointers.
 
 struct ExternalTableElem
 {
     // The code to call when calling this element. The table ABI is the system
     // ABI with the additional ABI requirements that:
     //  - WasmTlsReg and any pinned registers have been loaded appropriately
     //  - if this is a heterogeneous table that requires a signature check,
     //    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;
 };
 
+// CalleeDesc describes how to compile one of the variety of asm.js/wasm calls.
+// This is hoisted into WasmTypes.h for sharing between Ion and Baseline.
+
+class CalleeDesc
+{
+  public:
+    enum Which {
+        // Calls a function defined in the same module by its index.
+        Definition,
+
+        // Calls the import identified by the offset of its FuncImportTls in
+        // thread-local data.
+        Import,
+
+        // Calls a WebAssembly table (heterogeneous, index must be bounds
+        // checked, callee instance depends on TableDesc).
+        WasmTable,
+
+        // Calls an asm.js table (homogeneous, masked index, same-instance).
+        AsmJSTable,
+
+        // Call a C++ function identified by SymbolicAddress.
+        Builtin,
+
+        // Like Builtin, but automatically passes Instance* as first argument.
+        BuiltinInstanceMethod
+    };
+
+  private:
+    Which which_;
+    union U {
+        U() {}
+        uint32_t funcDefIndex_;
+        struct {
+            uint32_t globalDataOffset_;
+        } import;
+        struct {
+            uint32_t globalDataOffset_;
+            bool external_;
+            SigIdDesc sigId_;
+        } table;
+        SymbolicAddress builtin_;
+    } u;
+
+  public:
+    CalleeDesc() {}
+    static CalleeDesc definition(uint32_t funcDefIndex) {
+        CalleeDesc c;
+        c.which_ = Definition;
+        c.u.funcDefIndex_ = funcDefIndex;
+        return c;
+    }
+    static CalleeDesc import(uint32_t globalDataOffset) {
+        CalleeDesc c;
+        c.which_ = Import;
+        c.u.import.globalDataOffset_ = globalDataOffset;
+        return c;
+    }
+    static CalleeDesc wasmTable(const TableDesc& desc, SigIdDesc sigId) {
+        CalleeDesc c;
+        c.which_ = WasmTable;
+        c.u.table.globalDataOffset_ = desc.globalDataOffset;
+        c.u.table.external_ = desc.external;
+        c.u.table.sigId_ = sigId;
+        return c;
+    }
+    static CalleeDesc asmJSTable(const TableDesc& desc) {
+        CalleeDesc c;
+        c.which_ = AsmJSTable;
+        c.u.table.globalDataOffset_ = desc.globalDataOffset;
+        return c;
+    }
+    static CalleeDesc builtin(SymbolicAddress callee) {
+        CalleeDesc c;
+        c.which_ = Builtin;
+        c.u.builtin_ = callee;
+        return c;
+    }
+    static CalleeDesc builtinInstanceMethod(SymbolicAddress callee) {
+        CalleeDesc c;
+        c.which_ = BuiltinInstanceMethod;
+        c.u.builtin_ = callee;
+        return c;
+    }
+    Which which() const {
+        return which_;
+    }
+    uint32_t funcDefIndex() const {
+        MOZ_ASSERT(which_ == Definition);
+        return u.funcDefIndex_;
+    }
+    uint32_t importGlobalDataOffset() const {
+        MOZ_ASSERT(which_ == Import);
+        return u.import.globalDataOffset_;
+    }
+    bool isTable() const {
+        return which_ == WasmTable || which_ == AsmJSTable;
+    }
+    uint32_t tableLengthGlobalDataOffset() const {
+        MOZ_ASSERT(isTable());
+        return u.table.globalDataOffset_ + offsetof(TableTls, length);
+    }
+    uint32_t tableBaseGlobalDataOffset() const {
+        MOZ_ASSERT(isTable());
+        return u.table.globalDataOffset_ + offsetof(TableTls, base);
+    }
+    bool wasmTableIsExternal() const {
+        MOZ_ASSERT(which_ == WasmTable);
+        return u.table.external_;
+    }
+    SigIdDesc wasmTableSigId() const {
+        MOZ_ASSERT(which_ == WasmTable);
+        return u.table.sigId_;
+    }
+    SymbolicAddress builtin() const {
+        MOZ_ASSERT(which_ == Builtin || which_ == BuiltinInstanceMethod);
+        return u.builtin_;
+    }
+};
+
 // 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);
--- a/js/src/jit-test/tests/wasm/import-export.js
+++ b/js/src/jit-test/tests/wasm/import-export.js
@@ -35,57 +35,57 @@ assertEq(new Instance(m1, {foo:{bar:()=>
 const m2 = new Module(textToBinary('(module (import "x" "y" (memory 2 3)))'));
 assertErrorMessage(() => new Instance(m2), TypeError, /no import object given/);
 assertErrorMessage(() => new Instance(m2, {x:null}), TypeError, /import object field is not an Object/);
 assertErrorMessage(() => new Instance(m2, {x:{y:{}}}), TypeError, /import object field is not a Memory/);
 assertErrorMessage(() => new Instance(m2, {x:{y:mem1Page}}), TypeError, /imported Memory with incompatible size/);
 assertErrorMessage(() => new Instance(m2, {x:{y:mem1PageMax1}}), TypeError, /imported Memory with incompatible size/);
 assertErrorMessage(() => new Instance(m2, {x:{y:mem4Page}}), TypeError, /imported Memory with incompatible size/);
 assertErrorMessage(() => new Instance(m2, {x:{y:mem4PageMax4}}), TypeError, /imported Memory with incompatible size/);
-assertErrorMessage(() => new Instance(m2, {x:{y:mem2Page}}), TypeError, /imported Memory with incompatible size/);
+assertErrorMessage(() => new Instance(m2, {x:{y:mem2Page}}), TypeError, /imported Memory with incompatible maximum size/);
 assertEq(new Instance(m2, {x:{y:mem2PageMax2}}) instanceof Instance, true);
-assertErrorMessage(() => new Instance(m2, {x:{y:mem3Page}}), TypeError, /imported Memory with incompatible size/);
+assertErrorMessage(() => new Instance(m2, {x:{y:mem3Page}}), TypeError, /imported Memory with incompatible maximum size/);
 assertEq(new Instance(m2, {x:{y:mem3PageMax3}}) instanceof Instance, true);
 assertEq(new Instance(m2, {x:{y:mem2PageMax3}}) instanceof Instance, true);
-assertErrorMessage(() => new Instance(m2, {x:{y:mem2PageMax4}}), TypeError, /imported Memory with incompatible size/);
+assertErrorMessage(() => new Instance(m2, {x:{y:mem2PageMax4}}), TypeError, /imported Memory with incompatible maximum size/);
 
 const m3 = new Module(textToBinary('(module (import "foo" "bar" (memory 1 1)) (import "baz" "quux"))'));
 assertErrorMessage(() => new Instance(m3), TypeError, /no import object given/);
 assertErrorMessage(() => new Instance(m3, {foo:null}), TypeError, /import object field is not an Object/);
 assertErrorMessage(() => new Instance(m3, {foo:{bar:{}}}), TypeError, /import object field is not a Memory/);
 assertErrorMessage(() => new Instance(m3, {foo:{bar:mem1Page}, baz:null}), TypeError, /import object field is not an Object/);
 assertErrorMessage(() => new Instance(m3, {foo:{bar:mem1Page}, baz:{quux:mem1Page}}), TypeError, /import object field is not a Function/);
-assertErrorMessage(() => new Instance(m3, {foo:{bar:mem1Page}, baz:{quux:()=>{}}}), TypeError, /imported Memory with incompatible size/);
+assertErrorMessage(() => new Instance(m3, {foo:{bar:mem1Page}, baz:{quux:()=>{}}}), TypeError, /imported Memory with incompatible maximum size/);
 assertEq(new Instance(m3, {foo:{bar:mem1PageMax1}, baz:{quux:()=>{}}}) instanceof Instance, true);
 
 const m4 = new Module(textToBinary('(module (import "baz" "quux") (import "foo" "bar" (memory 1 1)))'));
 assertErrorMessage(() => new Instance(m4), TypeError, /no import object given/);
 assertErrorMessage(() => new Instance(m4, {baz:null}), TypeError, /import object field is not an Object/);
 assertErrorMessage(() => new Instance(m4, {baz:{quux:{}}}), TypeError, /import object field is not a Function/);
 assertErrorMessage(() => new Instance(m4, {baz:{quux:()=>{}}, foo:null}), TypeError, /import object field is not an Object/);
 assertErrorMessage(() => new Instance(m4, {baz:{quux:()=>{}}, foo:{bar:()=>{}}}), TypeError, /import object field is not a Memory/);
-assertErrorMessage(() => new Instance(m4, {baz:{quux:()=>{}}, foo:{bar:mem1Page}}), TypeError, /imported Memory with incompatible size/);
+assertErrorMessage(() => new Instance(m4, {baz:{quux:()=>{}}, foo:{bar:mem1Page}}), TypeError, /imported Memory with incompatible maximum size/);
 assertEq(new Instance(m3, {baz:{quux:()=>{}}, foo:{bar:mem1PageMax1}}) instanceof Instance, true);
 
 const m5 = new Module(textToBinary('(module (import "a" "b" (memory 2)))'));
 assertErrorMessage(() => new Instance(m5, {a:{b:mem1Page}}), TypeError, /imported Memory with incompatible size/);
 assertEq(new Instance(m5, {a:{b:mem2Page}}) instanceof Instance, true);
 assertEq(new Instance(m5, {a:{b:mem3Page}}) instanceof Instance, true);
 assertEq(new Instance(m5, {a:{b:mem4Page}}) instanceof Instance, true);
 
 const m6 = new Module(textToBinary('(module (import "a" "b" (table 2)))'));
 assertErrorMessage(() => new Instance(m6, {a:{b:tab1Elem}}), TypeError, /imported Table with incompatible size/);
 assertEq(new Instance(m6, {a:{b:tab2Elem}}) instanceof Instance, true);
 assertEq(new Instance(m6, {a:{b:tab3Elem}}) instanceof Instance, true);
 assertEq(new Instance(m6, {a:{b:tab4Elem}}) instanceof Instance, true);
 
 const m7 = new Module(textToBinary('(module (import "a" "b" (table 2 3)))'));
 assertErrorMessage(() => new Instance(m7, {a:{b:tab1Elem}}), TypeError, /imported Table with incompatible size/);
-assertEq(new Instance(m7, {a:{b:tab2Elem}}) instanceof Instance, true);
-assertEq(new Instance(m7, {a:{b:tab3Elem}}) instanceof Instance, true);
+assertErrorMessage(() => new Instance(m7, {a:{b:tab2Elem}}), TypeError, /imported Table with incompatible maximum size/);
+assertErrorMessage(() => new Instance(m7, {a:{b:tab3Elem}}), TypeError, /imported Table with incompatible maximum size/);
 assertErrorMessage(() => new Instance(m7, {a:{b:tab4Elem}}), TypeError, /imported Table with incompatible size/);
 
 assertErrorMessage(() => new Module(textToBinary('(module (memory 2 1))')), TypeError, /maximum length less than initial length/);
 assertErrorMessage(() => new Module(textToBinary('(module (import "a" "b" (memory 2 1)))')), TypeError, /maximum length less than initial length/);
 assertErrorMessage(() => new Module(textToBinary('(module (table (resizable 2 1)))')), TypeError, /maximum length less than initial length/);
 assertErrorMessage(() => new Module(textToBinary('(module (import "a" "b" (table 2 1)))')), TypeError, /maximum length less than initial length/);
 
 // Import wasm-wasm type mismatch
@@ -277,23 +277,23 @@ assertEq(e.tbl1.get(0), e.tbl1.get(3));
 
 var code = textToBinary('(module (import "a" "b" (memory 1 1)) (export "foo" memory) (export "bar" memory))');
 var mem = new Memory({initial:1, maximum:1});
 var e = new Instance(new Module(code), {a:{b:mem}}).exports;
 assertEq(mem, e.foo);
 assertEq(mem, e.bar);
 
 var code = textToBinary('(module (import "a" "b" (table 1 1)) (export "foo" table) (export "bar" table))');
-var tbl = new Table({initial:1, element:"anyfunc"});
+var tbl = new Table({initial:1, maximum:1, element:"anyfunc"});
 var e = new Instance(new Module(code), {a:{b:tbl}}).exports;
 assertEq(tbl, e.foo);
 assertEq(tbl, e.bar);
 
 var code = textToBinary('(module (import "a" "b" (table 2 2)) (func $foo) (elem (i32.const 0) $foo) (export "foo" $foo))');
-var tbl = new Table({initial:2, element:"anyfunc"});
+var tbl = new Table({initial:2, maximum:2, element:"anyfunc"});
 var e1 = new Instance(new Module(code), {a:{b:tbl}}).exports;
 assertEq(e1.foo, tbl.get(0));
 tbl.set(1, e1.foo);
 assertEq(e1.foo, tbl.get(1));
 var e2 = new Instance(new Module(code), {a:{b:tbl}}).exports;
 assertEq(e2.foo, tbl.get(0));
 assertEq(e1.foo, tbl.get(1));
 assertEq(tbl.get(0) === e1.foo, false);
--- a/js/src/jit-test/tests/wasm/jsapi.js
+++ b/js/src/jit-test/tests/wasm/jsapi.js
@@ -154,28 +154,28 @@ assertEq(bufferDesc.configurable, true);
 // 'WebAssembly.Memory.prototype.buffer' getter
 const bufferGetter = bufferDesc.get;
 assertErrorMessage(() => bufferGetter.call(), TypeError, /called on incompatible undefined/);
 assertErrorMessage(() => bufferGetter.call({}), TypeError, /called on incompatible Object/);
 assertEq(bufferGetter.call(mem1) instanceof ArrayBuffer, true);
 assertEq(bufferGetter.call(mem1).byteLength, WasmPage);
 
 // 'WebAssembly.Memory.prototype.grow' data property
-const growDesc = Object.getOwnPropertyDescriptor(memoryProto, 'grow');
-assertEq(typeof growDesc.value, "function");
-assertEq(growDesc.enumerable, false);
-assertEq(growDesc.configurable, true);
+const memGrowDesc = Object.getOwnPropertyDescriptor(memoryProto, 'grow');
+assertEq(typeof memGrowDesc.value, "function");
+assertEq(memGrowDesc.enumerable, false);
+assertEq(memGrowDesc.configurable, true);
 
 // 'WebAssembly.Memory.prototype.grow' method
-const grow = growDesc.value;
-assertEq(grow.length, 1);
-assertErrorMessage(() => grow.call(), TypeError, /called on incompatible undefined/);
-assertErrorMessage(() => grow.call({}), TypeError, /called on incompatible Object/);
-assertErrorMessage(() => grow.call(mem1, -1), Error, /failed to grow memory/);
-assertErrorMessage(() => grow.call(mem1, Math.pow(2,32)), Error, /failed to grow memory/);
+const memGrow = memGrowDesc.value;
+assertEq(memGrow.length, 1);
+assertErrorMessage(() => memGrow.call(), TypeError, /called on incompatible undefined/);
+assertErrorMessage(() => memGrow.call({}), TypeError, /called on incompatible Object/);
+assertErrorMessage(() => memGrow.call(mem1, -1), Error, /failed to grow memory/);
+assertErrorMessage(() => memGrow.call(mem1, Math.pow(2,32)), Error, /failed to grow memory/);
 var mem = new Memory({initial:1, maximum:2});
 var buf = mem.buffer;
 assertEq(buf.byteLength, WasmPage);
 assertEq(mem.grow(0), 1);
 assertEq(buf !== mem.buffer, true);
 assertEq(buf.byteLength, 0);
 buf = mem.buffer;
 assertEq(buf.byteLength, WasmPage);
@@ -282,16 +282,37 @@ assertErrorMessage(() => set.call(tbl1, 
 assertErrorMessage(() => set.call(tbl1, 0, undefined), TypeError, /can only assign WebAssembly exported functions to Table/);
 assertErrorMessage(() => set.call(tbl1, 0, {}), TypeError, /can only assign WebAssembly exported functions to Table/);
 assertErrorMessage(() => set.call(tbl1, 0, function() {}), TypeError, /can only assign WebAssembly exported functions to Table/);
 assertErrorMessage(() => set.call(tbl1, 0, Math.sin), TypeError, /can only assign WebAssembly exported functions to Table/);
 assertErrorMessage(() => set.call(tbl1, {valueOf() { throw Error("hai") }}, null), Error, "hai");
 assertEq(set.call(tbl1, 0, null), undefined);
 assertEq(set.call(tbl1, 1, null), undefined);
 
+// 'WebAssembly.Table.prototype.grow' data property
+const tblGrowDesc = Object.getOwnPropertyDescriptor(tableProto, 'grow');
+assertEq(typeof tblGrowDesc.value, "function");
+assertEq(tblGrowDesc.enumerable, false);
+assertEq(tblGrowDesc.configurable, true);
+
+// 'WebAssembly.Table.prototype.grow' method
+const tblGrow = tblGrowDesc.value;
+assertEq(tblGrow.length, 1);
+assertErrorMessage(() => tblGrow.call(), TypeError, /called on incompatible undefined/);
+assertErrorMessage(() => tblGrow.call({}), TypeError, /called on incompatible Object/);
+assertErrorMessage(() => tblGrow.call(tbl1, -1), Error, /failed to grow table/);
+assertErrorMessage(() => tblGrow.call(tbl1, Math.pow(2,32)), Error, /failed to grow table/);
+var tbl = new Table({element:"anyfunc", initial:1, maximum:2});
+assertEq(tbl.length, 1);
+assertEq(tbl.grow(0), 1);
+assertEq(tbl.length, 1);
+assertEq(tbl.grow(1), 1);
+assertEq(tbl.length, 2);
+assertErrorMessage(() => tbl.grow(1), Error, /failed to grow table/);
+
 // 'WebAssembly.compile' data property
 const compileDesc = Object.getOwnPropertyDescriptor(WebAssembly, 'compile');
 assertEq(typeof compileDesc.value, "function");
 assertEq(compileDesc.writable, true);
 assertEq(compileDesc.enumerable, false);
 assertEq(compileDesc.configurable, true);
 
 // 'WebAssembly.compile' function
--- a/js/src/jit-test/tests/wasm/resizing.js
+++ b/js/src/jit-test/tests/wasm/resizing.js
@@ -1,16 +1,20 @@
 // |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;
 
+// ======
+// 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))
@@ -21,63 +25,63 @@ assertEq(evalText(`(module
             (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")
+    (import $imp "" "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;
+)`, {"":{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))
+    (import "" "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;
+)`, {"":{mem}}).exports;
 var exports2 = evalText(`(module
-    (import "a" "tbl" (table 1))
-    (import "a" "mem" (memory 1))
+    (import "" "tbl" (table 1))
+    (import "" "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, mem}}).exports;
 tbl.set(0, exports1.grow);
 assertEq(exports2.test(), 111);
 
 // Test for coherent length/contents
 
 var mem = new Memory({initial:1});
 new Int32Array(mem.buffer)[0] = 42;
 var mod = new Module(textToBinary(`(module
@@ -108,8 +112,105 @@ assertEq(exp2.load(0), 42);
 assertEq(exp2.load(64*1024), 13);
 exp1.grow_memory(2);
 assertEq(exp1.current_memory(), 4);
 exp1.store(3*64*1024, 99);
 assertEq(exp2.current_memory(), 4);
 assertEq(exp2.load(3*64*1024), 99);
 assertEq(mem.buffer.byteLength, 4*64*1024);
 assertEq(new Int32Array(mem.buffer)[3*64*1024/4], 99);
+
+// ======
+// TABLE
+// ======
+
+// Test for stale table base pointers after resize
+
+// Grow during call_import:
+var exports = evalText(`(module
+    (type $v2i (func (result i32)))
+    (import $grow "" "grow")
+    (table (resizable 1))
+    (func $test (result i32)
+        (i32.add
+            (call_indirect $v2i (i32.const 0))
+            (block
+                (call $grow)
+                (call_indirect $v2i (i32.const 1)))))
+    (func $one (result i32) (i32.const 1))
+    (elem (i32.const 0) $one)
+    (func $two (result i32) (i32.const 2))
+    (export "tbl" table)
+    (export "test" $test)
+    (export "two" $two)
+)`, {"":{grow() { exports.tbl.grow(1); exports.tbl.set(1, exports.two) }}}).exports;
+
+setJitCompilerOption("baseline.warmup.trigger", 2);
+setJitCompilerOption("ion.warmup.trigger", 4);
+for (var i = 0; i < 10; i++)
+    assertEq(exports.test(), 3);
+assertEq(exports.tbl.length, 11);
+
+// Grow during call_indirect:
+var exports1 = evalText(`(module
+    (import $grow "" "grow")
+    (func $exp (call $grow))
+    (export "exp" $exp)
+)`, {"":{grow() { exports2.tbl.grow(1); exports2.tbl.set(2, exports2.eleven) }}}).exports;
+var exports2 = evalText(`(module
+    (type $v2v (func))
+    (type $v2i (func (result i32)))
+    (import $imp "" "imp")
+    (elem (i32.const 0) $imp)
+    (table (resizable 2))
+    (func $test (result i32)
+        (i32.add
+            (call_indirect $v2i (i32.const 1))
+            (block
+                (call_indirect $v2v (i32.const 0))
+                (call_indirect $v2i (i32.const 2)))))
+    (func $ten (result i32) (i32.const 10))
+    (elem (i32.const 1) $ten)
+    (func $eleven (result i32) (i32.const 11))
+    (export "tbl" table)
+    (export "test" $test)
+    (export "eleven" $eleven)
+)`, {"":{imp:exports1.exp}}).exports;
+assertEq(exports2.test(), 21);
+
+// Test for coherent length/contents
+
+var src = evalText(`(module
+    (func $one (result i32) (i32.const 1))
+    (export "one" $one)
+    (func $two (result i32) (i32.const 2))
+    (export "two" $two)
+    (func $three (result i32) (i32.const 3))
+    (export "three" $three)
+)`).exports;
+var tbl = new Table({element:"anyfunc", initial:1});
+tbl.set(0, src.one);
+
+var mod = new Module(textToBinary(`(module
+    (type $v2i (func (result i32)))
+    (import "" "tbl" (table 1))
+    (func $ci (param i32) (result i32) (call_indirect $v2i (get_local 0)))
+    (export "call_indirect" $ci)
+)`));
+var exp1 = new Instance(mod, {"":{tbl}}).exports;
+var exp2 = new Instance(mod, {"":{tbl}}).exports;
+assertEq(exp1.call_indirect(0), 1);
+assertErrorMessage(() => exp1.call_indirect(1), Error, /out-of-range/);
+assertEq(exp2.call_indirect(0), 1);
+assertErrorMessage(() => exp2.call_indirect(1), Error, /out-of-range/);
+assertEq(tbl.grow(1), 1);
+assertEq(tbl.length, 2);
+assertEq(exp1.call_indirect(0), 1);
+assertErrorMessage(() => exp1.call_indirect(1), Error, /indirect call to null/);
+tbl.set(1, src.two);
+assertEq(exp1.call_indirect(1), 2);
+assertErrorMessage(() => exp1.call_indirect(2), Error, /out-of-range/);
+assertEq(tbl.grow(2), 2);
+assertEq(tbl.length, 4);
+assertEq(exp2.call_indirect(0), 1);
+assertEq(exp2.call_indirect(1), 2);
+assertErrorMessage(() => exp2.call_indirect(2), Error, /indirect call to null/);
+assertErrorMessage(() => exp2.call_indirect(3), Error, /indirect call to null/);
--- a/js/src/jit/MacroAssembler.cpp
+++ b/js/src/jit/MacroAssembler.cpp
@@ -2772,17 +2772,17 @@ void
 MacroAssembler::wasmCallIndirect(const wasm::CallSiteDesc& desc, const wasm::CalleeDesc& callee)
 {
     Register scratch = WasmTableCallScratchReg;
     Register index = WasmTableCallIndexReg;
 
     if (callee.which() == wasm::CalleeDesc::AsmJSTable) {
         // asm.js tables require no signature check, have had their index masked
         // into range and thus need no bounds check and cannot be external.
-        loadWasmGlobalPtr(callee.tableGlobalDataOffset(), scratch);
+        loadWasmGlobalPtr(callee.tableBaseGlobalDataOffset(), scratch);
         loadPtr(BaseIndex(scratch, index, ScalePointer), scratch);
         call(desc, scratch);
         return;
     }
 
     MOZ_ASSERT(callee.which() == wasm::CalleeDesc::WasmTable);
 
     // Write the sig-id into the ABI sig-id register.
@@ -2794,22 +2794,21 @@ MacroAssembler::wasmCallIndirect(const w
       case wasm::SigIdDesc::Kind::Immediate:
         move32(Imm32(sigId.immediate()), WasmTableCallSigReg);
         break;
       case wasm::SigIdDesc::Kind::None:
         break;
     }
 
     // WebAssembly throws if the index is out-of-bounds.
-    branch32(Assembler::Condition::AboveOrEqual,
-             index, Imm32(callee.wasmTableLength()),
-             wasm::JumpTarget::OutOfBounds);
+    loadWasmGlobalPtr(callee.tableLengthGlobalDataOffset(), scratch);
+    branch32(Assembler::Condition::AboveOrEqual, index, scratch, wasm::JumpTarget::OutOfBounds);
 
     // Load the base pointer of the table.
-    loadWasmGlobalPtr(callee.tableGlobalDataOffset(), scratch);
+    loadWasmGlobalPtr(callee.tableBaseGlobalDataOffset(), scratch);
 
     // Load the callee from the table.
     if (callee.wasmTableIsExternal()) {
         static_assert(sizeof(wasm::ExternalTableElem) == 8 || sizeof(wasm::ExternalTableElem) == 16,
                       "elements of external tables are two words");
         if (sizeof(wasm::ExternalTableElem) == 8) {
             computeEffectiveAddress(BaseIndex(scratch, index, TimesEight), scratch);
         } else {
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -345,22 +345,23 @@ MSG_DEF(JSMSG_USE_ASM_LINK_FAIL,       1
 MSG_DEF(JSMSG_USE_ASM_TYPE_OK,         1, JSEXN_WARN,    "Successfully compiled asm.js code ({0})")
 
 // wasm
 MSG_DEF(JSMSG_WASM_FAIL,               1, JSEXN_TYPEERR,     "wasm error: {0}")
 MSG_DEF(JSMSG_WASM_DECODE_FAIL,        2, JSEXN_TYPEERR,     "wasm validation error at offset {0}: {1}")
 MSG_DEF(JSMSG_WASM_TEXT_FAIL,          1, JSEXN_SYNTAXERR,   "wasm text error: {0}")
 MSG_DEF(JSMSG_WASM_IND_CALL_TO_NULL,   0, JSEXN_ERR,         "indirect call to null")
 MSG_DEF(JSMSG_WASM_IND_CALL_BAD_SIG,   0, JSEXN_ERR,         "indirect call signature mismatch")
-MSG_DEF(JSMSG_WASM_BAD_GROW,           0, JSEXN_ERR,         "failed to grow memory")
+MSG_DEF(JSMSG_WASM_BAD_GROW,           1, JSEXN_ERR,         "failed to grow {0}")
 MSG_DEF(JSMSG_WASM_BAD_BUF_ARG,        0, JSEXN_TYPEERR,     "first argument must be an ArrayBuffer or typed array object")
 MSG_DEF(JSMSG_WASM_BAD_MOD_ARG,        0, JSEXN_TYPEERR,     "first argument must be a WebAssembly.Module")
 MSG_DEF(JSMSG_WASM_BAD_DESC_ARG,       1, JSEXN_TYPEERR,     "first argument must be a {0} descriptor")
 MSG_DEF(JSMSG_WASM_BAD_IMP_SIZE,       1, JSEXN_TYPEERR,     "imported {0} with incompatible size")
 MSG_DEF(JSMSG_WASM_BAD_SIZE,           2, JSEXN_TYPEERR,     "bad {0} {1} size")
+MSG_DEF(JSMSG_WASM_BAD_IMP_MAX,        1, JSEXN_TYPEERR,     "imported {0} with incompatible maximum size")
 MSG_DEF(JSMSG_WASM_BAD_ELEMENT,        0, JSEXN_TYPEERR,     "\"element\" property of table descriptor must be \"anyfunc\"")
 MSG_DEF(JSMSG_WASM_BAD_IMPORT_ARG,     0, JSEXN_TYPEERR,     "second argument, if present, must be an object")
 MSG_DEF(JSMSG_WASM_BAD_IMPORT_FIELD,   1, JSEXN_TYPEERR,     "import object field is not {0}")
 MSG_DEF(JSMSG_WASM_BAD_IMPORT_SIG,     0, JSEXN_TYPEERR,     "imported function signature mismatch")
 MSG_DEF(JSMSG_WASM_BAD_TABLE_VALUE,    0, JSEXN_TYPEERR,     "can only assign WebAssembly exported functions to Table")
 MSG_DEF(JSMSG_WASM_BAD_I64,            0, JSEXN_TYPEERR,     "cannot pass i64 to or from JS")
 MSG_DEF(JSMSG_WASM_BAD_FIT,            2, JSEXN_RANGEERR,    "{0} segment does not fit in {1}")
 MSG_DEF(JSMSG_WASM_UNREACHABLE,        0, JSEXN_ERR,         "unreachable executed")