Bug 1284155 - Baldr: add support for same-instance Table imports (r=bbouvier)
authorLuke Wagner <luke@mozilla.com>
Tue, 19 Jul 2016 15:49:31 -0500
changeset 345778 dbbe25db2d61505c8a6c8561412b4b204ad30257
parent 345777 42550e82d1d27075cc8fa2eb2f807afef7d0dbfc
child 345779 6765609dcb46095329ddab3c8791564a44e994c0
push id6389
push userraliiev@mozilla.com
push dateMon, 19 Sep 2016 13:38:22 +0000
treeherdermozilla-beta@01d67bfe6c81 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbbouvier
bugs1284155
milestone50.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 1284155 - Baldr: add support for same-instance Table imports (r=bbouvier) MozReview-Commit-ID: DMMtDimNiAt
js/src/asmjs/AsmJS.cpp
js/src/asmjs/WasmAST.h
js/src/asmjs/WasmBaselineCompile.cpp
js/src/asmjs/WasmCode.h
js/src/asmjs/WasmCompile.cpp
js/src/asmjs/WasmGenerator.cpp
js/src/asmjs/WasmGenerator.h
js/src/asmjs/WasmInstance.cpp
js/src/asmjs/WasmIonCompile.cpp
js/src/asmjs/WasmJS.cpp
js/src/asmjs/WasmJS.h
js/src/asmjs/WasmModule.cpp
js/src/asmjs/WasmModule.h
js/src/asmjs/WasmTable.cpp
js/src/asmjs/WasmTable.h
js/src/asmjs/WasmTextToBinary.cpp
js/src/jit-test/tests/wasm/import-export.js
js/src/jit-test/tests/wasm/tables.js
js/src/js.msg
--- a/js/src/asmjs/AsmJS.cpp
+++ b/js/src/asmjs/AsmJS.cpp
@@ -1672,16 +1672,17 @@ class MOZ_STACK_CLASS ModuleValidator
         validationLifo_(VALIDATION_LIFO_DEFAULT_CHUNK_SIZE),
         functions_(cx),
         funcPtrTables_(cx),
         globalMap_(cx),
         sigMap_(cx),
         importMap_(cx),
         arrayViews_(cx),
         atomicsPresent_(false),
+        mg_(ImportVector()),
         errorString_(nullptr),
         errorOffset_(UINT32_MAX),
         errorOverRecursed_(false)
     {}
 
     ~ModuleValidator() {
         if (errorString_) {
             MOZ_ASSERT(errorOffset_ != UINT32_MAX);
@@ -2298,27 +2299,23 @@ class MOZ_STACK_CLASS ModuleValidator
         uint32_t endBeforeCurly = tokenStream().currentToken().pos.end;
         asmJSMetadata_->srcLength = endBeforeCurly - asmJSMetadata_->srcStart;
 
         TokenPos pos;
         JS_ALWAYS_TRUE(tokenStream().peekTokenPos(&pos, TokenStream::Operand));
         uint32_t endAfterCurly = pos.end;
         asmJSMetadata_->srcLengthWithRightBrace = endAfterCurly - asmJSMetadata_->srcStart;
 
-        // asm.js has its own, different, version of imports through
-        // AsmJSGlobal.
-        ImportVector imports;
-
         // asm.js does not have any wasm bytecode to save; view-source is
         // provided through the ScriptSource.
         SharedBytes bytes = js_new<ShareableBytes>();
         if (!bytes)
             return nullptr;
 
-        return mg_.finish(Move(imports), *bytes);
+        return mg_.finish(*bytes);
     }
 };
 
 /*****************************************************************************/
 // Numeric literal utilities
 
 static bool
 IsNumericNonFloatLiteral(ParseNode* pn)
--- a/js/src/asmjs/WasmAST.h
+++ b/js/src/asmjs/WasmAST.h
@@ -525,25 +525,25 @@ class AstImport : public AstNode
     DefinitionKind kind_;
     AstRef funcSig_;
     AstResizable resizable_;
 
   public:
     AstImport(AstName name, AstName module, AstName field, AstRef funcSig)
       : name_(name), module_(module), field_(field), kind_(DefinitionKind::Function), funcSig_(funcSig)
     {}
-    AstImport(AstName name, AstName module, AstName field, AstResizable resizable)
-      : name_(name), module_(module), field_(field), kind_(DefinitionKind::Memory), resizable_(resizable)
+    AstImport(AstName name, AstName module, AstName field, DefinitionKind kind, AstResizable resizable)
+      : name_(name), module_(module), field_(field), kind_(kind), resizable_(resizable)
     {}
     AstName name() const { return name_; }
     AstName module() const { return module_; }
     AstName field() const { return field_; }
     DefinitionKind kind() const { return kind_; }
     AstRef& funcSig() { MOZ_ASSERT(kind_ == DefinitionKind::Function); return funcSig_; }
-    AstResizable memory() const { MOZ_ASSERT(kind_ == DefinitionKind::Memory); return resizable_; }
+    AstResizable resizable() const { MOZ_ASSERT(kind_ != DefinitionKind::Function); return resizable_; }
 };
 
 class AstExport : public AstNode
 {
     AstName name_;
     DefinitionKind kind_;
     AstRef func_;
 
--- a/js/src/asmjs/WasmBaselineCompile.cpp
+++ b/js/src/asmjs/WasmBaselineCompile.cpp
@@ -5195,17 +5195,17 @@ BaseCompiler::emitCallIndirect(uint32_t 
         return false;
 
     Stk& callee = peek(numArgs);
 
     const TableDesc& table = isCompilingAsmJS()
                              ? mg_.tables[mg_.asmJSSigToTableIndex[sigIndex]]
                              : mg_.tables[0];
 
-    funcPtrCall(sig, sigIndex, table.length, table.globalDataOffset, callee, baselineCall);
+    funcPtrCall(sig, sigIndex, table.initial, table.globalDataOffset, callee, baselineCall);
 
     endCall(baselineCall);
 
     // TODO / OPTIMIZE: It would be better to merge this freeStack()
     // into the one in endCall, if we can.
 
     popValueStackBy(numArgs+1);
     masm.freeStack(stackSpace);
--- a/js/src/asmjs/WasmCode.h
+++ b/js/src/asmjs/WasmCode.h
@@ -227,20 +227,21 @@ enum class TableKind
     AnyFunction,
     TypedFunction
 };
 
 struct TableDesc
 {
     TableKind kind;
     uint32_t globalDataOffset;
-    uint32_t length;
+    uint32_t initial;
+    uint32_t maximum;
 
-    TableDesc() : kind(TableKind::AnyFunction), globalDataOffset(0), length(0) {}
-    explicit TableDesc(TableKind kind) : kind(kind) {}
+    TableDesc() { PodZero(this); }
+    explicit TableDesc(TableKind kind) : kind(kind), globalDataOffset(0), initial(0), maximum(0) {}
 };
 
 WASM_DECLARE_POD_VECTOR(TableDesc, TableDescVector)
 
 // A CodeRange describes a single contiguous range of code within a wasm
 // module's code segment. A CodeRange describes what the code does and, for
 // function bodies, the name and source coordinates of the function.
 
--- a/js/src/asmjs/WasmCompile.cpp
+++ b/js/src/asmjs/WasmCompile.cpp
@@ -709,17 +709,18 @@ DecodeResizableTable(Decoder& d, ModuleG
     Resizable resizable;
     if (!DecodeResizable(d, &resizable))
         return false;
 
     if (!init->tables.empty())
         return Fail(d, "already have default table");
 
     TableDesc table(TableKind::AnyFunction);
-    table.length = resizable.initial;
+    table.initial = resizable.initial;
+    table.maximum = resizable.maximum ? *resizable.maximum : UINT32_MAX;
     return init->tables.append(table);
 }
 
 static bool
 DecodeImport(Decoder& d, bool newFormat, ModuleGeneratorData* init, ImportVector* imports)
 {
     if (!newFormat) {
         const DeclaredSig* sig = nullptr;
@@ -767,16 +768,21 @@ DecodeImport(Decoder& d, bool newFormat,
         if (!DecodeSignatureIndex(d, *init, &sig))
             return false;
         if (!CheckTypeForJS(d, *sig))
             return false;
         if (!init->funcImports.emplaceBack(sig))
             return false;
         break;
       }
+      case DefinitionKind::Table: {
+        if (!DecodeResizableTable(d, init))
+            return false;
+        break;
+      }
       case DefinitionKind::Memory: {
         if (!DecodeResizableMemory(d, init))
             return false;
         break;
       }
       default:
         return Fail(d, "unsupported import kind");
     }
@@ -820,27 +826,28 @@ DecodeTableSection(Decoder& d, bool newF
     if (sectionStart == Decoder::NotStarted)
         return true;
 
     if (newFormat) {
         if (!DecodeResizableTable(d, init))
             return false;
     } else {
         TableDesc table(TableKind::AnyFunction);
+        table.maximum = UINT32_MAX;
 
-        if (!d.readVarU32(&table.length))
+        if (!d.readVarU32(&table.initial))
             return Fail(d, "expected number of table elems");
 
-        if (table.length > MaxTableElems)
+        if (table.initial > MaxTableElems)
             return Fail(d, "too many table elements");
 
-        if (!oldElems->resize(table.length))
+        if (!oldElems->resize(table.initial))
             return false;
 
-        for (uint32_t i = 0; i < table.length; i++) {
+        for (uint32_t i = 0; i < table.initial; i++) {
             uint32_t funcIndex;
             if (!d.readVarU32(&funcIndex))
                 return Fail(d, "expected table element");
 
             if (funcIndex >= init->funcSigs.length())
                 return Fail(d, "table element out of range");
 
             (*oldElems)[i] = funcIndex;
@@ -1209,17 +1216,17 @@ DecodeElemSection(Decoder& d, bool newFo
 
         if (seg.offset < prevEnd)
             return Fail(d, "elem segments must be disjoint and ordered");
 
         uint32_t numElems;
         if (!d.readVarU32(&numElems))
             return Fail(d, "expected segment size");
 
-        uint32_t tableLength = mg.tables()[seg.tableIndex].length;
+        uint32_t tableLength = mg.tables()[seg.tableIndex].initial;
         if (seg.offset > tableLength || tableLength - seg.offset < numElems)
             return Fail(d, "element segment does not fit");
 
         if (!seg.elems.resize(numElems))
             return false;
 
         for (uint32_t i = 0; i < numElems; i++) {
             if (!d.readVarU32(&seg.elems[i]))
@@ -1420,17 +1427,17 @@ wasm::Compile(const ShareableBytes& byte
     Uint32Vector oldElems;
     if (!DecodeTableSection(d, newFormat, init.get(), &oldElems))
         return nullptr;
 
     bool memoryExported = false;
     if (!DecodeMemorySection(d, newFormat, init.get(), &memoryExported))
         return nullptr;
 
-    ModuleGenerator mg;
+    ModuleGenerator mg(Move(imports));
     if (!mg.init(Move(init), Move(args)))
         return nullptr;
 
     if (!DecodeExportSection(d, newFormat, memoryExported, mg))
         return nullptr;
 
     if (!DecodeStartSection(d, mg))
         return nullptr;
@@ -1447,10 +1454,10 @@ wasm::Compile(const ShareableBytes& byte
     if (!DecodeNameSection(d, mg))
         return nullptr;
 
     if (!DecodeUnknownSections(d))
         return nullptr;
 
     MOZ_ASSERT(!*error, "unreported error in decoding");
 
-    return mg.finish(Move(imports), bytecode);
+    return mg.finish(bytecode);
 }
--- a/js/src/asmjs/WasmGenerator.cpp
+++ b/js/src/asmjs/WasmGenerator.cpp
@@ -37,33 +37,41 @@ using mozilla::CheckedInt;
 using mozilla::MakeEnumeratedRange;
 
 // ****************************************************************************
 // ModuleGenerator
 
 static const unsigned GENERATOR_LIFO_DEFAULT_CHUNK_SIZE = 4 * 1024;
 static const unsigned COMPILATION_LIFO_DEFAULT_CHUNK_SIZE = 64 * 1024;
 
-ModuleGenerator::ModuleGenerator()
+ModuleGenerator::ModuleGenerator(ImportVector&& imports)
   : alwaysBaseline_(false),
+    imports_(Move(imports)),
     numSigs_(0),
     numTables_(0),
     lifo_(GENERATOR_LIFO_DEFAULT_CHUNK_SIZE),
     masmAlloc_(&lifo_),
     masm_(MacroAssembler::AsmJSToken(), masmAlloc_),
     lastPatchedCallsite_(0),
     startOfUnpatchedBranches_(0),
-    tableExported_(false),
+    externalTable_(false),
     parallel_(false),
     outstanding_(0),
     activeFunc_(nullptr),
     startedFuncDefs_(false),
     finishedFuncDefs_(false)
 {
     MOZ_ASSERT(IsCompilingAsmJS());
+
+    for (const Import& import : imports_) {
+        if (import.kind == DefinitionKind::Table) {
+            externalTable_ = true;
+            break;
+        }
+    }
 }
 
 ModuleGenerator::~ModuleGenerator()
 {
     if (parallel_) {
         // Wait for any outstanding jobs to fail or complete.
         if (outstanding_) {
             AutoLockHelperThreadState lock;
@@ -690,17 +698,17 @@ ModuleGenerator::addFuncExport(UniqueCha
     return exports_.emplaceBack(Move(fieldName), funcIndex) &&
            exportedFuncs_.put(funcIndex);
 }
 
 bool
 ModuleGenerator::addTableExport(UniqueChars fieldName)
 {
     MOZ_ASSERT(elemSegments_.empty());
-    tableExported_ = true;
+    externalTable_ = true;
     return exports_.emplaceBack(Move(fieldName), DefinitionKind::Table);
 }
 
 bool
 ModuleGenerator::addMemoryExport(UniqueChars fieldName)
 {
     return exports_.emplaceBack(Move(fieldName), DefinitionKind::Memory);
 }
@@ -838,19 +846,19 @@ ModuleGenerator::finishFuncDefs()
     linkData_.functionCodeLength = masm_.size();
     finishedFuncDefs_ = true;
     return true;
 }
 
 bool
 ModuleGenerator::addElemSegment(ElemSegment&& seg)
 {
-    MOZ_ASSERT(seg.offset + seg.elems.length() <= shared_->tables[seg.tableIndex].length);
+    MOZ_ASSERT(seg.offset + seg.elems.length() <= shared_->tables[seg.tableIndex].initial);
 
-    if (tableExported_) {
+    if (externalTable_) {
         for (uint32_t funcIndex : seg.elems) {
             if (!exportedFuncs_.put(funcIndex))
                 return false;
         }
     }
 
     return elemSegments_.append(Move(seg));
 }
@@ -869,35 +877,36 @@ ModuleGenerator::initSigTableLength(uint
     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.length == 0);
+    MOZ_ASSERT(table.initial == 0);
     table.kind = TableKind::TypedFunction;
-    table.length = length;
+    table.initial = length;
+    table.maximum = UINT32_MAX;
     return allocateGlobalBytes(sizeof(void*), sizeof(void*), &table.globalDataOffset);
 }
 
 bool
 ModuleGenerator::initSigTableElems(uint32_t sigIndex, Uint32Vector&& elemFuncIndices)
 {
     MOZ_ASSERT(isAsmJS());
 
     uint32_t tableIndex = shared_->asmJSSigToTableIndex[sigIndex];
-    MOZ_ASSERT(shared_->tables[tableIndex].length == elemFuncIndices.length());
+    MOZ_ASSERT(shared_->tables[tableIndex].initial == elemFuncIndices.length());
 
     return elemSegments_.emplaceBack(tableIndex, 0, Move(elemFuncIndices));
 }
 
 SharedModule
-ModuleGenerator::finish(ImportVector&& imports, const ShareableBytes& bytecode)
+ModuleGenerator::finish(const ShareableBytes& bytecode)
 {
     MOZ_ASSERT(!activeFunc_);
     MOZ_ASSERT(finishedFuncDefs_);
 
     // Now that all asm.js tables have been created and the compiler threads are
     // done, shrink the (no longer shared) tables vector down to size.
     if (isAsmJS() && !shared_->tables.resize(numTables_))
         return nullptr;
@@ -971,15 +980,15 @@ ModuleGenerator::finish(ImportVector&& i
         }
     }
 
     if (!finishLinkData(code))
         return nullptr;
 
     return SharedModule(js_new<Module>(Move(code),
                                        Move(linkData_),
-                                       Move(imports),
+                                       Move(imports_),
                                        Move(exports_),
                                        Move(dataSegments_),
                                        Move(elemSegments_),
                                        *metadata_,
                                        bytecode));
 }
--- a/js/src/asmjs/WasmGenerator.h
+++ b/js/src/asmjs/WasmGenerator.h
@@ -95,33 +95,34 @@ class MOZ_STACK_CLASS ModuleGenerator
 
     // Constant parameters
     bool                            alwaysBaseline_;
 
     // Data that is moved into the result of finish()
     LinkData                        linkData_;
     MutableMetadata                 metadata_;
     ExportVector                    exports_;
+    ImportVector                    imports_;
     DataSegmentVector               dataSegments_;
     ElemSegmentVector               elemSegments_;
 
     // Data scoped to the ModuleGenerator's lifetime
     UniqueModuleGeneratorData       shared_;
     uint32_t                        numSigs_;
     uint32_t                        numTables_;
     LifoAlloc                       lifo_;
     jit::JitContext                 jcx_;
     jit::TempAllocator              masmAlloc_;
     jit::MacroAssembler             masm_;
     Uint32Vector                    funcIndexToCodeRange_;
     Uint32Set                       exportedFuncs_;
     uint32_t                        lastPatchedCallsite_;
     uint32_t                        startOfUnpatchedBranches_;
     JumpSiteArray                   jumpThunks_;
-    bool                            tableExported_;
+    bool                            externalTable_;
 
     // Parallel compilation
     bool                            parallel_;
     uint32_t                        outstanding_;
     IonCompileTaskVector            tasks_;
     IonCompileTaskPtrVector         freeTasks_;
 
     // Assertions
@@ -136,17 +137,17 @@ class MOZ_STACK_CLASS ModuleGenerator
     MOZ_MUST_USE bool finishTask(IonCompileTask* task);
     MOZ_MUST_USE bool finishFuncExports();
     MOZ_MUST_USE bool finishCodegen();
     MOZ_MUST_USE bool finishLinkData(Bytes& code);
     MOZ_MUST_USE bool addFuncImport(const Sig& sig, uint32_t globalDataOffset);
     MOZ_MUST_USE bool allocateGlobalBytes(uint32_t bytes, uint32_t align, uint32_t* globalDataOff);
 
   public:
-    explicit ModuleGenerator();
+    explicit ModuleGenerator(ImportVector&& imports);
     ~ModuleGenerator();
 
     MOZ_MUST_USE bool init(UniqueModuleGeneratorData shared, CompileArgs&& args,
                            Metadata* maybeAsmJSMetadata = nullptr);
 
     bool isAsmJS() const { return metadata_->kind == ModuleKind::AsmJS; }
     SignalUsage usesSignal() const { return metadata_->assumptions.usesSignal; }
     jit::MacroAssembler& masm() { return masm_; }
@@ -202,17 +203,17 @@ class MOZ_STACK_CLASS ModuleGenerator
     MOZ_MUST_USE bool initSigTableLength(uint32_t sigIndex, uint32_t length);
     MOZ_MUST_USE bool initSigTableElems(uint32_t sigIndex, Uint32Vector&& elemFuncIndices);
     void initMemoryUsage(MemoryUsage memoryUsage);
     void bumpMinMemoryLength(uint32_t newMinMemoryLength);
 
     // Finish compilation, provided the list of imports and source bytecode.
     // Both these Vectors may be empty (viz., b/c asm.js does different things
     // for imports and source).
-    SharedModule finish(ImportVector&& imports, const ShareableBytes& bytecode);
+    SharedModule finish(const ShareableBytes& bytecode);
 };
 
 // A FunctionGenerator encapsulates the generation of a single function body.
 // ModuleGenerator::startFunc must be called after construction and before doing
 // anything else. After the body is complete, ModuleGenerator::finishFunc must
 // be called before the FunctionGenerator is destroyed and the next function is
 // started.
 
--- a/js/src/asmjs/WasmInstance.cpp
+++ b/js/src/asmjs/WasmInstance.cpp
@@ -381,21 +381,16 @@ Instance::Instance(UniqueCodeSegment cod
         exit.baselineScript = nullptr;
     }
 
     if (memory)
         *addressOfMemoryBase() = memory->buffer().dataPointerEither().unwrap();
 
     for (size_t i = 0; i < tables_.length(); i++)
         *addressOfTableBase(i) = tables_[i]->array();
-
-    for (SharedTable& table : tables_) {
-        for (size_t i = 0; i < table->length(); i++)
-            table->array()[i] = codeSegment_->badIndirectCallCode();
-    }
 }
 
 Instance::~Instance()
 {
     for (unsigned i = 0; i < metadata_->funcImports.length(); i++) {
         FuncImportExit& exit = funcImportToExit(metadata_->funcImports[i]);
         if (exit.baselineScript)
             exit.baselineScript->removeDependentWasmImport(*this, i);
--- a/js/src/asmjs/WasmIonCompile.cpp
+++ b/js/src/asmjs/WasmIonCompile.cpp
@@ -1732,17 +1732,17 @@ EmitCallIndirect(FunctionCompiler& f, ui
     if (!f.iter().readCallReturn(sig.ret()))
         return false;
 
     const TableDesc& table = f.mg().isAsmJS()
                              ? f.mg().tables[f.mg().asmJSSigToTableIndex[sigIndex]]
                              : f.mg().tables[0];
 
     MDefinition* def;
-    if (!f.funcPtrCall(sigIndex, table.length, table.globalDataOffset, callee, args, &def))
+    if (!f.funcPtrCall(sigIndex, table.initial, table.globalDataOffset, callee, args, &def))
         return false;
 
     if (IsVoid(sig.ret()))
         return true;
 
     f.iter().setResult(def);
     return true;
 }
--- a/js/src/asmjs/WasmJS.cpp
+++ b/js/src/asmjs/WasmJS.cpp
@@ -65,16 +65,23 @@ CheckCompilerSupport(JSContext* cx)
 static bool
 Throw(JSContext* cx, const char* str)
 {
     JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_FAIL, str);
     return false;
 }
 
 static bool
+Throw(JSContext* cx, unsigned errorNumber, const char* str)
+{
+    JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, errorNumber, str);
+    return false;
+}
+
+static bool
 GetProperty(JSContext* cx, HandleObject obj, const char* chars, MutableHandleValue v)
 {
     JSAtom* atom = AtomizeUTF8Chars(cx, chars, strlen(chars));
     if (!atom)
         return false;
 
     RootedId id(cx, AtomToId(atom));
     return GetProperty(cx, obj, obj, id, v);
@@ -93,37 +100,42 @@ GetImports(JSContext* cx,
 
     for (const Import& import : imports) {
         RootedValue v(cx);
         if (!GetProperty(cx, importObj, import.module.get(), &v))
             return false;
 
         if (strlen(import.func.get()) > 0) {
             if (!v.isObject())
-                return Throw(cx, "import object field is not an Object");
+                return Throw(cx, JSMSG_WASM_BAD_IMPORT_FIELD, "an Object");
 
             RootedObject obj(cx, &v.toObject());
             if (!GetProperty(cx, obj, import.func.get(), &v))
                 return false;
         }
 
         switch (import.kind) {
           case DefinitionKind::Function:
             if (!IsFunctionObject(v))
-                return Throw(cx, "import object field is not a Function");
+                return Throw(cx, JSMSG_WASM_BAD_IMPORT_FIELD, "a Function");
 
             if (!funcImports.append(&v.toObject().as<JSFunction>()))
                 return false;
 
             break;
           case DefinitionKind::Table:
-            MOZ_CRASH("NYI");
+            if (!v.isObject() || !v.toObject().is<WasmTableObject>())
+                return Throw(cx, JSMSG_WASM_BAD_IMPORT_FIELD, "a Table");
+
+            MOZ_ASSERT(!tableImport);
+            tableImport.set(&v.toObject().as<WasmTableObject>());
+            break;
           case DefinitionKind::Memory:
             if (!v.isObject() || !v.toObject().is<WasmMemoryObject>())
-                return Throw(cx, "import object field is not a Memory");
+                return Throw(cx, JSMSG_WASM_BAD_IMPORT_FIELD, "a Memory");
 
             MOZ_ASSERT(!memoryImport);
             memoryImport.set(&v.toObject().as<WasmMemoryObject>());
             break;
         }
     }
 
     return true;
@@ -782,26 +794,33 @@ WasmTableObject::initialized() const
 {
     return !getReservedSlot(INSTANCE_VECTOR_SLOT).isUndefined();
 }
 
 bool
 WasmTableObject::init(JSContext* cx, HandleWasmInstanceObject instanceObj)
 {
     MOZ_ASSERT(!initialized());
+    MOZ_ASSERT(!table().initialized());
+
+    // Ensure initialization is atomic so that the table is never left in an
+    // inconsistent state (where the Table is initialized but the
+    // WasmTableObject is not).
 
     auto instanceVector = MakeUnique<InstanceVector>();
     if (!instanceVector || !instanceVector->appendN(instanceObj.get(), table().length())) {
         ReportOutOfMemory(cx);
         return false;
     }
 
     initReservedSlot(INSTANCE_VECTOR_SLOT, PrivateValue(instanceVector.release()));
+    table().init(instanceObj->instance().codeSegment());
 
     MOZ_ASSERT(initialized());
+    MOZ_ASSERT(table().initialized());
     return true;
 }
 
 /* static */ bool
 WasmTableObject::construct(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
@@ -972,20 +991,18 @@ WasmTableObject::setImpl(JSContext* cx, 
         uint32_t funcIndex = ExportedFunctionToIndex(value);
 
 #ifdef DEBUG
         RootedFunction f(cx);
         MOZ_ASSERT(instanceObj->getExportedFunction(cx, instanceObj, funcIndex, &f));
         MOZ_ASSERT(value == f);
 #endif
 
-        if (instanceVector[index] != instanceObj) {
-            JS_ReportError(cx, "cross-module Table.prototype.set NYI");
+        if (!tableObj->setInstance(cx, index, instanceObj))
             return false;
-        }
 
         Instance& instance = instanceObj->instance();
         const FuncExport& funcExport = instance.metadata().lookupFuncExport(funcIndex);
         table.array()[index] = instance.codeSegment().code() + funcExport.tableEntryOffset();
     } else {
         table.array()[index] = instanceVector[index]->instance().codeSegment().badIndirectCallCode();
     }
 
@@ -1015,22 +1032,29 @@ WasmTableObject::table() const
 
 WasmTableObject::InstanceVector&
 WasmTableObject::instanceVector() const
 {
     MOZ_ASSERT(initialized());
     return *(InstanceVector*)getReservedSlot(INSTANCE_VECTOR_SLOT).toPrivate();
 }
 
-void
-WasmTableObject::setInstance(uint32_t index, HandleWasmInstanceObject instanceObj)
+bool
+WasmTableObject::setInstance(JSContext* cx, uint32_t index, HandleWasmInstanceObject instanceObj)
 {
     MOZ_ASSERT(initialized());
     MOZ_ASSERT(instanceObj->instance().codeSegment().containsCodePC(table().array()[index]));
+
+    if (instanceVector()[index] != instanceObj) {
+        JS_ReportError(cx, "cross-module Table import NYI");
+        return false;
+    }
+
     instanceVector()[index] = instanceObj;
+    return true;
 }
 
 // ============================================================================
 // WebAssembly class and static methods
 
 #if JS_HAS_TOSOURCE
 static bool
 WebAssembly_toSource(JSContext* cx, unsigned argc, Value* vp)
--- a/js/src/asmjs/WasmJS.h
+++ b/js/src/asmjs/WasmJS.h
@@ -222,17 +222,17 @@ class WasmTableObject : public NativeObj
     bool init(JSContext* cx, HandleWasmInstanceObject instanceObj);
 
     // As a global invariant, any time an element of tableObj->table() is
     // updated to a new exported function, table->setInstance() must be called
     // to update the instance of that new exported function in the instance
     // vector.
 
     wasm::Table& table() const;
-    void setInstance(uint32_t index, HandleWasmInstanceObject instanceObj);
+    bool setInstance(JSContext* cx, uint32_t index, HandleWasmInstanceObject instanceObj);
 };
 
 typedef Rooted<WasmTableObject*> RootedWasmTableObject;
 typedef Handle<WasmTableObject*> HandleWasmTableObject;
 typedef MutableHandle<WasmTableObject*> MutableHandleWasmTableObject;
 
 } // namespace js
 
--- a/js/src/asmjs/WasmModule.cpp
+++ b/js/src/asmjs/WasmModule.cpp
@@ -344,36 +344,53 @@ Module::addSizeOfMisc(MallocSizeOf mallo
              SizeOfVectorExcludingThis(imports_, mallocSizeOf) +
              SizeOfVectorExcludingThis(exports_, mallocSizeOf) +
              dataSegments_.sizeOfExcludingThis(mallocSizeOf) +
              SizeOfVectorExcludingThis(elemSegments_, mallocSizeOf) +
              metadata_->sizeOfIncludingThisIfNotSeen(mallocSizeOf, seenMetadata) +
              bytecode_->sizeOfIncludingThisIfNotSeen(mallocSizeOf, seenBytes);
 }
 
-void
-Module::initElems(HandleWasmInstanceObject instanceObj, HandleWasmTableObject tableObj) const
+bool
+Module::initElems(JSContext* cx, HandleWasmInstanceObject instanceObj,
+                  HandleWasmTableObject tableObj) const
 {
     Instance& instance = instanceObj->instance();
     const CodeSegment& codeSegment = instance.codeSegment();
     const SharedTableVector& tables = instance.tables();
 
+    // Initialize tables that have a WasmTableObject first, so that this
+    // initialization can be done atomically.
+    if (tableObj && !tableObj->initialized() && !tableObj->init(cx, instanceObj))
+        return false;
+
+    // Initialize all remaining Tables that do not have objects.
+    for (const SharedTable& table : tables) {
+        if (!table->initialized())
+            table->init(codeSegment);
+    }
+
+    // Now that all tables have been initialized, write elements.
     for (const ElemSegment& seg : elemSegments_) {
         Table& table = *tables[seg.tableIndex];
         MOZ_ASSERT(seg.offset + seg.elems.length() <= table.length());
 
-        for (uint32_t i = 0; i < seg.elems.length(); i++)
-            table.array()[seg.offset + i] = codeSegment.code() + seg.elems[i];
-
         if (tableObj) {
             MOZ_ASSERT(seg.tableIndex == 0);
-            for (uint32_t i = 0; i < seg.elems.length(); i++)
-                tableObj->setInstance(seg.offset + i, instanceObj);
+            for (uint32_t i = 0; i < seg.elems.length(); i++) {
+                if (!tableObj->setInstance(cx, seg.offset + i, instanceObj))
+                    return false;
+            }
         }
+
+        for (uint32_t i = 0; i < seg.elems.length(); i++)
+            table.array()[seg.offset + i] = codeSegment.code() + seg.elems[i];
     }
+
+    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
 {
@@ -383,17 +400,17 @@ Module::instantiateMemory(JSContext* cx,
         return true;
     }
 
     RootedArrayBufferObjectMaybeShared buffer(cx);
     if (memory) {
         buffer = &memory->buffer();
         uint32_t length = buffer->byteLength();
         if (length < metadata_->minMemoryLength || length > metadata_->maxMemoryLength) {
-            JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_MEM_IMP_SIZE);
+            JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_IMP_SIZE, "Memory");
             return false;
         }
 
         // This can't happen except via the shell toggling signals.enabled.
         if (metadata_->assumptions.usesSignal.forOOB &&
             !buffer->is<SharedArrayBufferObject>() &&
             !buffer->as<ArrayBufferObject>().isWasmMapped())
         {
@@ -426,22 +443,27 @@ Module::instantiateMemory(JSContext* cx,
 
 bool
 Module::instantiateTable(JSContext* cx, const CodeSegment& codeSegment,
                          HandleWasmTableObject tableImport, SharedTableVector* tables) const
 {
     for (const TableDesc& tableDesc : metadata_->tables) {
         SharedTable table;
         if (tableImport) {
-            MOZ_CRASH("NYI: table imports");
+            table = &tableImport->table();
+            if (table->length() < tableDesc.initial || table->length() > tableDesc.maximum) {
+                JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_IMP_SIZE, "Table");
+                return false;
+            }
         } else {
-            table = Table::create(cx, tableDesc.kind, tableDesc.length);
+            table = Table::create(cx, tableDesc.kind, tableDesc.initial);
             if (!table)
                 return false;
         }
+
         if (!tables->emplaceBack(table)) {
             ReportOutOfMemory(cx);
             return false;
         }
     }
 
     return true;
 }
@@ -483,17 +505,17 @@ CreateExportObject(JSContext* cx,
                 return false;
             val = ObjectValue(*fun);
             break;
           }
           case DefinitionKind::Table: {
             if (!tableObj) {
                 MOZ_ASSERT(instance.tables().length() == 1);
                 tableObj.set(WasmTableObject::create(cx, *instance.tables()[0]));
-                if (!tableObj || !tableObj->init(cx, instanceObj))
+                if (!tableObj)
                     return false;
             }
             val = ObjectValue(*tableObj);
             break;
           }
           case DefinitionKind::Memory: {
             if (metadata.assumptions.newFormat)
                 val = ObjectValue(*memoryObj);
@@ -576,19 +598,22 @@ Module::instantiate(JSContext* cx,
         return false;
     RootedId id(cx, AtomToId(atom));
 
     RootedValue val(cx, ObjectValue(*exportObj));
     if (!JS_DefinePropertyById(cx, instanceObj, id, val, JSPROP_ENUMERATE))
         return false;
 
     // Initialize table elements only after the instance is fully initialized
-    // since the Table object needs to point to a valid instance object.
+    // since the Table object needs to point to a valid instance object. Perform
+    // initialization as the final step after the instance is fully live since
+    // it is observable (in the case of an imported Table object).
 
-    initElems(instanceObj, table);
+    if (!initElems(cx, instanceObj, table))
+        return false;
 
     // Done! Notify the Debugger of the new Instance.
 
     Debugger::onNewWasmInstance(cx, instanceObj);
 
     // Call the start function, if there's one. By specification, it does not
     // take any arguments nor does it return a value, so just create a dummy
     // arguments object.
--- a/js/src/asmjs/WasmModule.h
+++ b/js/src/asmjs/WasmModule.h
@@ -183,17 +183,18 @@ class Module : public RefCounted<Module>
     const DataSegmentVector dataSegments_;
     const ElemSegmentVector elemSegments_;
     const SharedMetadata    metadata_;
     const SharedBytes       bytecode_;
 
     bool instantiateMemory(JSContext* cx, MutableHandleWasmMemoryObject memory) const;
     bool instantiateTable(JSContext* cx, const CodeSegment& codeSegment,
                           HandleWasmTableObject tableImport, SharedTableVector* tables) const;
-    void initElems(HandleWasmInstanceObject instanceObj, HandleWasmTableObject tableObj) const;
+    bool initElems(JSContext* cx, HandleWasmInstanceObject instanceObj,
+                   HandleWasmTableObject tableObj) const;
 
   public:
     Module(Bytes&& code,
            LinkData&& linkData,
            ImportVector&& imports,
            ExportVector&& exports,
            DataSegmentVector&& dataSegments,
            ElemSegmentVector&& elemSegments,
--- a/js/src/asmjs/WasmTable.cpp
+++ b/js/src/asmjs/WasmTable.cpp
@@ -19,26 +19,40 @@
 #include "asmjs/WasmTable.h"
 
 using namespace js;
 using namespace js::wasm;
 
 /* static */ SharedTable
 Table::create(JSContext* cx, TableKind kind, uint32_t length)
 {
-    SharedTable table = js_new<Table>();
+    SharedTable table = cx->new_<Table>();
     if (!table)
         return nullptr;
 
-    table->array_.reset(js_pod_calloc<void*>(length));
+    table->array_.reset(cx->pod_calloc<void*>(length));
     if (!table->array_)
         return nullptr;
 
     table->kind_ = kind;
     table->length_ = length;
+    table->initialized_ = false;
     return table;
 }
 
+void
+Table::init(const CodeSegment& codeSegment)
+{
+    MOZ_ASSERT(!initialized());
+
+    for (uint32_t i = 0; i < length_; i++) {
+        MOZ_ASSERT(!array_.get()[i]);
+        array_.get()[i] = codeSegment.badIndirectCallCode();
+    }
+
+    initialized_ = true;
+}
+
 size_t
 Table::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const
 {
     return mallocSizeOf(array_.get());
 }
--- a/js/src/asmjs/WasmTable.h
+++ b/js/src/asmjs/WasmTable.h
@@ -28,24 +28,32 @@ namespace wasm {
 // stateful objects exposed to WebAssembly. asm.js also uses Tables to represent
 // its homogeneous function-pointer tables.
 
 class Table : public ShareableBase<Table>
 {
     TableKind kind_;
     UniquePtr<void*> array_;
     uint32_t length_;
+    bool initialized_;
 
   public:
     static RefPtr<Table> create(JSContext* cx, TableKind kind, uint32_t length);
 
+    // These accessors may be used before initialization.
+
     bool isTypedFunction() const { return kind_ == TableKind::TypedFunction; }
     void** array() const { return array_.get(); }
     uint32_t length() const { return length_; }
 
+    // A Table must be initialized before any dependent instance can execute.
+
+    bool initialized() const { return initialized_; }
+    void init(const CodeSegment& codeSegment);
+
     // about:memory reporting:
 
     size_t sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const;
 };
 
 typedef RefPtr<Table> SharedTable;
 typedef Vector<SharedTable, 0, SystemAllocPolicy> SharedTableVector;
 
--- a/js/src/asmjs/WasmTextToBinary.cpp
+++ b/js/src/asmjs/WasmTextToBinary.cpp
@@ -2434,23 +2434,35 @@ ParseImport(WasmParseContext& c, bool ne
 
     WasmToken fieldName;
     if (!c.ts.match(WasmToken::Text, &fieldName, c.error))
         return nullptr;
 
     AstRef sigRef;
     WasmToken openParen;
     if (c.ts.getIf(WasmToken::OpenParen, &openParen)) {
-        if (newFormat && c.ts.getIf(WasmToken::Memory)) {
-            AstResizable memory;
-            if (!ParseResizable(c, &memory))
-                return nullptr;
-            if (!c.ts.match(WasmToken::CloseParen, c.error))
-                return nullptr;
-            return new(c.lifo) AstImport(name, moduleName.text(), fieldName.text(), memory);
+        if (newFormat) {
+            if (c.ts.getIf(WasmToken::Memory)) {
+                AstResizable memory;
+                if (!ParseResizable(c, &memory))
+                    return nullptr;
+                if (!c.ts.match(WasmToken::CloseParen, c.error))
+                    return nullptr;
+                return new(c.lifo) AstImport(name, moduleName.text(), fieldName.text(),
+                                             DefinitionKind::Memory, memory);
+            }
+            if (c.ts.getIf(WasmToken::Table)) {
+                AstResizable table;
+                if (!ParseResizable(c, &table))
+                    return nullptr;
+                if (!c.ts.match(WasmToken::CloseParen, c.error))
+                    return nullptr;
+                return new(c.lifo) AstImport(name, moduleName.text(), fieldName.text(),
+                                             DefinitionKind::Table, table);
+            }
         }
 
         if (c.ts.getIf(WasmToken::Type)) {
             if (!c.ts.matchRef(&sigRef, c.error))
                 return nullptr;
             if (!c.ts.match(WasmToken::CloseParen, c.error))
                 return nullptr;
         } else {
@@ -3553,19 +3565,18 @@ EncodeImport(Encoder& e, bool newFormat,
         return false;
 
     switch (imp.kind()) {
       case DefinitionKind::Function:
         if (!e.writeVarU32(imp.funcSig().index()))
             return false;
         break;
       case DefinitionKind::Table:
-        MOZ_CRASH("NYI");
       case DefinitionKind::Memory:
-        if (!EncodeResizable(e, imp.memory()))
+        if (!EncodeResizable(e, imp.resizable()))
             return false;
         break;
     }
 
     return true;
 }
 
 static bool
--- a/js/src/jit-test/tests/wasm/import-export.js
+++ b/js/src/jit-test/tests/wasm/import-export.js
@@ -5,16 +5,20 @@ const Module = WebAssembly.Module;
 const Instance = WebAssembly.Instance;
 const Memory = WebAssembly.Memory;
 const Table = WebAssembly.Table;
 
 const mem1Page = new Memory({initial:1});
 const mem2Page = new Memory({initial:2});
 const mem3Page = new Memory({initial:3});
 const mem4Page = new Memory({initial:4});
+const tab1Elem = new Table({initial:1});
+const tab2Elem = new Table({initial:2});
+const tab3Elem = new Table({initial:3});
+const tab4Elem = new Table({initial:4});
 
 // Explicitly opt into the new binary format for imports and exports until it
 // is used by default everywhere.
 const textToBinary = str => wasmTextToBinary(str, 'new-format');
 
 const m1 = new Module(textToBinary('(module (import "foo" "bar") (import "baz" "quux"))'));
 assertErrorMessage(() => new Instance(m1), TypeError, /no import object given/);
 assertErrorMessage(() => new Instance(m1, {foo:null}), TypeError, /import object field is not an Object/);
@@ -49,19 +53,32 @@ assertErrorMessage(() => new Instance(m4
 assertEq(new Instance(m3, {baz:{quux:()=>{}}, foo:{bar:mem1Page}}) 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: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 order:
 
 var arr = [];
 var importObj = {
     get foo() { arr.push("foo") },
     get baz() { arr.push("bad") },
 };
@@ -236,26 +253,34 @@ assertEq(e.tbl1.get(0), e.tbl1.get(3));
 // Re-exports:
 
 var code = textToBinary('(module (import "a" "b" (memory 1 1)) (export "foo" memory) (export "bar" memory))');
 var mem = new Memory({initial: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});
+var e = new Instance(new Module(code), {a:{b:tbl}}).exports;
+assertEq(tbl, e.foo);
+assertEq(tbl, e.bar);
+
 // Non-existent export errors
 
 assertErrorMessage(() => new Module(textToBinary('(module (export "a" 0))')), TypeError, /exported function index out of bounds/);
 assertErrorMessage(() => new Module(textToBinary('(module (export "a" memory))')), TypeError, /exported memory index out of bounds/);
 assertErrorMessage(() => new Module(textToBinary('(module (export "a" table))')), TypeError, /exported table index out of bounds/);
 
-// Default memory rules
+// Default memory/table rules
 
 assertErrorMessage(() => new Module(textToBinary('(module (import "a" "b" (memory 1 1)) (memory 1 1))')), TypeError, /already have default memory/);
 assertErrorMessage(() => new Module(textToBinary('(module (import "a" "b" (memory 1 1)) (import "x" "y" (memory 2 2)))')), TypeError, /already have default memory/);
+assertErrorMessage(() => new Module(textToBinary('(module (import "a" "b" (table 1 1)) (table 1 1))')), TypeError, /already have default table/);
+assertErrorMessage(() => new Module(textToBinary('(module (import "a" "b" (table 1 1)) (import "x" "y" (table 2 2)))')), TypeError, /already have default table/);
 
 // Data segments on imports
 
 var m = new Module(textToBinary(`
     (module
         (import "a" "b" (memory 1 1))
         (data 0 "\\0a\\0b")
         (data 100 "\\0c\\0d")
@@ -273,8 +298,30 @@ assertEq(get(101), 0xd);
 assertEq(get(102), 0x0);
 var i8 = new Uint8Array(mem.buffer);
 assertEq(i8[0], 0xa);
 assertEq(i8[1], 0xb);
 assertEq(i8[2], 0x0);
 assertEq(i8[100], 0xc);
 assertEq(i8[101], 0xd);
 assertEq(i8[102], 0x0);
+
+// Elem segments on imports
+
+var m = new Module(textToBinary(`
+    (module
+        (import "a" "b" (table 10))
+        (elem 0 $one $two)
+        (elem 3 $three $four)
+        (func $one (result i32) (i32.const 1))
+        (func $two (result i32) (i32.const 2))
+        (func $three (result i32) (i32.const 3))
+        (func $four (result i32) (i32.const 4)))
+`));
+var tbl = new Table({initial:10});
+new Instance(m, {a:{b:tbl}});
+assertEq(tbl.get(0)(), 1);
+assertEq(tbl.get(1)(), 2);
+assertEq(tbl.get(2), null);
+assertEq(tbl.get(3)(), 3);
+assertEq(tbl.get(4)(), 4);
+for (var i = 5; i < 10; i++)
+    assertEq(tbl.get(i), null);
--- a/js/src/jit-test/tests/wasm/tables.js
+++ b/js/src/jit-test/tests/wasm/tables.js
@@ -48,16 +48,23 @@ assertErrorMessage(() => call(0), Error,
 assertEq(call(1), 0);
 assertEq(call(2), 1);
 assertErrorMessage(() => call(3), Error, /bad wasm indirect call/);
 assertEq(call(4), 0);
 assertEq(call(5), 2);
 assertErrorMessage(() => call(6), Error, /bad wasm indirect call/);
 assertErrorMessage(() => call(10), Error, /out-of-range/);
 
+var tbl = new Table({initial:3});
+var call = evalText(`(module (import "a" "b" (table 2)) (export "tbl" table) (elem 0 $f0 $f1) ${callee(0)} ${callee(1)} ${caller})`, {a:{b:tbl}}).exports.call;
+assertEq(call(0), 0);
+assertEq(call(1), 1);
+assertEq(tbl.get(0)(), 0);
+assertEq(tbl.get(1)(), 1);
+
 // A table should not hold exported functions alive and exported functions
 // should not hold their originating table alive. Live exported functions should
 // hold instances alive. Nothing should hold the export object alive.
 resetFinalizeCount();
 var i = evalText(`(module (table (resizable 2)) (export "tbl" table) (elem 0 $f0) ${callee(0)} ${caller})`);
 var e = i.exports;
 var t = e.tbl;
 var f = t.get(0);
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -343,19 +343,20 @@ MSG_DEF(JSMSG_USE_ASM_TYPE_OK,         1
 // 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_BAD_IND_CALL,       0, JSEXN_ERR,         "bad wasm indirect call")
 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_MEM_IMP_SIZE,   0, JSEXN_TYPEERR,     "imported Memory with incompatible size")
+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_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_SET_VALUE,      0, JSEXN_TYPEERR,     "second argument must be null or an exported WebAssembly Function object")
 MSG_DEF(JSMSG_WASM_UNREACHABLE,        0, JSEXN_ERR,         "unreachable executed")
 MSG_DEF(JSMSG_WASM_INTEGER_OVERFLOW,   0, JSEXN_ERR,         "integer overflow")
 MSG_DEF(JSMSG_WASM_INVALID_CONVERSION, 0, JSEXN_ERR,         "invalid conversion to integer")
 MSG_DEF(JSMSG_WASM_INT_DIVIDE_BY_ZERO, 0, JSEXN_ERR,         "integer divide by zero")
 MSG_DEF(JSMSG_WASM_UNALIGNED_ACCESS,   0, JSEXN_ERR,         "unaligned memory access")
 MSG_DEF(JSMSG_WASM_OVERRECURSED,       0, JSEXN_INTERNALERR, "call stack exhausted")