Bug 1284155 - Baldr: add Table.prototype.get (r=bbouvier)
authorLuke Wagner <luke@mozilla.com>
Mon, 18 Jul 2016 12:11:42 -0500
changeset 330680 4a992c10189fd39b2c9daeb49749127a5a2a0d7d
parent 330679 b4575688fc9f27dcf04e1a3525d597cc953e34b8
child 330681 05ce71421b6910fee9cf0b4451c4cd371d62bd83
push id9858
push userjlund@mozilla.com
push dateMon, 01 Aug 2016 14:37:10 +0000
treeherdermozilla-aurora@203106ef6cb6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbbouvier
bugs1284155
milestone50.0a1
Bug 1284155 - Baldr: add Table.prototype.get (r=bbouvier) MozReview-Commit-ID: 75QezDyi2og
js/src/asmjs/AsmJS.cpp
js/src/asmjs/WasmGenerator.cpp
js/src/asmjs/WasmGenerator.h
js/src/asmjs/WasmInstance.cpp
js/src/asmjs/WasmJS.cpp
js/src/asmjs/WasmJS.h
js/src/asmjs/WasmModule.cpp
js/src/asmjs/WasmModule.h
js/src/builtin/TestingFunctions.cpp
js/src/jit-test/tests/wasm/import-export.js
js/src/jit-test/tests/wasm/jsapi.js
js/src/jit-test/tests/wasm/profiling.js
js/src/jit-test/tests/wasm/tables.js
--- a/js/src/asmjs/AsmJS.cpp
+++ b/js/src/asmjs/AsmJS.cpp
@@ -7888,17 +7888,18 @@ TryInstantiate(JSContext* cx, CallArgs a
     }
 
     Rooted<FunctionVector> funcs(cx, FunctionVector(cx));
     for (const AsmJSImport& import : metadata.asmJSImports) {
         if (!funcs.append(ffis[import.ffiIndex()]))
             return false;
     }
 
-    if (!module.instantiate(cx, funcs, memory, nullptr, instanceObj))
+    RootedWasmTableObject table(cx);
+    if (!module.instantiate(cx, funcs, table, memory, nullptr, instanceObj))
         return false;
 
     RootedValue exportObjVal(cx);
     if (!JS_GetProperty(cx, instanceObj, InstanceExportField, &exportObjVal))
         return false;
 
     MOZ_RELEASE_ASSERT(exportObjVal.isObject());
     exportObj.set(&exportObjVal.toObject());
--- a/js/src/asmjs/WasmGenerator.cpp
+++ b/js/src/asmjs/WasmGenerator.cpp
@@ -46,16 +46,17 @@ ModuleGenerator::ModuleGenerator()
   : alwaysBaseline_(false),
     numSigs_(0),
     numTables_(0),
     lifo_(GENERATOR_LIFO_DEFAULT_CHUNK_SIZE),
     masmAlloc_(&lifo_),
     masm_(MacroAssembler::AsmJSToken(), masmAlloc_),
     lastPatchedCallsite_(0),
     startOfUnpatchedBranches_(0),
+    tableExported_(false),
     parallel_(false),
     outstanding_(0),
     activeFunc_(nullptr),
     startedFuncDefs_(false),
     finishedFuncDefs_(false)
 {
     MOZ_ASSERT(IsCompilingAsmJS());
 }
@@ -687,16 +688,18 @@ ModuleGenerator::addFuncExport(UniqueCha
 {
     return exports_.emplaceBack(Move(fieldName), funcIndex) &&
            exportedFuncs_.put(funcIndex);
 }
 
 bool
 ModuleGenerator::addTableExport(UniqueChars fieldName)
 {
+    MOZ_ASSERT(elemSegments_.empty());
+    tableExported_ = true;
     return exports_.emplaceBack(Move(fieldName), DefinitionKind::Table);
 }
 
 bool
 ModuleGenerator::addMemoryExport(UniqueChars fieldName)
 {
     return exports_.emplaceBack(Move(fieldName), DefinitionKind::Memory);
 }
@@ -835,16 +838,24 @@ ModuleGenerator::finishFuncDefs()
     finishedFuncDefs_ = true;
     return true;
 }
 
 bool
 ModuleGenerator::addElemSegment(ElemSegment&& seg)
 {
     MOZ_ASSERT(seg.offset + seg.elems.length() <= shared_->tables[seg.tableIndex].length);
+
+    if (tableExported_) {
+        for (uint32_t funcIndex : seg.elems) {
+            if (!exportedFuncs_.put(funcIndex))
+                return false;
+        }
+    }
+
     return elemSegments_.append(Move(seg));
 }
 
 void
 ModuleGenerator::setFuncNames(NameInBytecodeVector&& funcNames)
 {
     MOZ_ASSERT(metadata_->funcNames.empty());
     metadata_->funcNames = Move(funcNames);
--- a/js/src/asmjs/WasmGenerator.h
+++ b/js/src/asmjs/WasmGenerator.h
@@ -111,16 +111,17 @@ class MOZ_STACK_CLASS ModuleGenerator
     jit::JitContext                 jcx_;
     jit::TempAllocator              masmAlloc_;
     jit::MacroAssembler             masm_;
     Uint32Vector                    funcIndexToCodeRange_;
     Uint32Set                       exportedFuncs_;
     uint32_t                        lastPatchedCallsite_;
     uint32_t                        startOfUnpatchedBranches_;
     JumpSiteArray                   jumpThunks_;
+    bool                            tableExported_;
 
     // Parallel compilation
     bool                            parallel_;
     uint32_t                        outstanding_;
     IonCompileTaskVector            tasks_;
     IonCompileTaskPtrVector         freeTasks_;
 
     // Assertions
--- a/js/src/asmjs/WasmInstance.cpp
+++ b/js/src/asmjs/WasmInstance.cpp
@@ -381,16 +381,21 @@ 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/WasmJS.cpp
+++ b/js/src/asmjs/WasmJS.cpp
@@ -76,18 +76,22 @@ GetProperty(JSContext* cx, HandleObject 
     if (!atom)
         return false;
 
     RootedId id(cx, AtomToId(atom));
     return GetProperty(cx, obj, obj, id, v);
 }
 
 static bool
-GetImports(JSContext* cx, HandleObject importObj, const ImportVector& imports,
-           MutableHandle<FunctionVector> funcImports, MutableHandleWasmMemoryObject memoryImport)
+GetImports(JSContext* cx,
+           HandleObject importObj,
+           const ImportVector& imports,
+           MutableHandle<FunctionVector> funcImports,
+           MutableHandleWasmTableObject tableImport,
+           MutableHandleWasmMemoryObject memoryImport)
 {
     if (!imports.empty() && !importObj)
         return Throw(cx, "no import object given");
 
     for (const Import& import : imports) {
         RootedValue v(cx);
         if (!GetProperty(cx, importObj, import.module.get(), &v))
             return false;
@@ -161,21 +165,22 @@ wasm::Eval(JSContext* cx, Handle<TypedAr
         if (error)
             JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_FAIL, error.get());
         else
             ReportOutOfMemory(cx);
         return false;
     }
 
     Rooted<FunctionVector> funcs(cx, FunctionVector(cx));
+    RootedWasmTableObject table(cx);
     RootedWasmMemoryObject memory(cx);
-    if (!GetImports(cx, importObj, module->imports(), &funcs, &memory))
+    if (!GetImports(cx, importObj, module->imports(), &funcs, &table, &memory))
         return false;
 
-    return module->instantiate(cx, funcs, memory, nullptr, instanceObj);
+    return module->instantiate(cx, funcs, table, memory, nullptr, instanceObj);
 }
 
 static bool
 InstantiateModule(JSContext* cx, unsigned argc, Value* vp)
 {
     MOZ_ASSERT(cx->options().wasm());
     CallArgs args = CallArgsFromVp(argc, vp);
 
@@ -273,31 +278,34 @@ const Class WasmModuleObject::class_ =
     JSCLASS_DELAY_METADATA_BUILDER |
     JSCLASS_HAS_RESERVED_SLOTS(WasmModuleObject::RESERVED_SLOTS),
     &WasmModuleObject::classOps_,
 };
 
 const JSPropertySpec WasmModuleObject::properties[] =
 { JS_PS_END };
 
+const JSFunctionSpec WasmModuleObject::methods[] =
+{ JS_FS_END };
+
 /* static */ void
 WasmModuleObject::finalize(FreeOp* fop, JSObject* obj)
 {
     obj->as<WasmModuleObject>().module().Release();
 }
 
 /* static */ WasmModuleObject*
 WasmModuleObject::create(ExclusiveContext* cx, Module& module, HandleObject proto)
 {
     AutoSetNewObjectMetadata metadata(cx);
     auto* obj = NewObjectWithGivenProto<WasmModuleObject>(cx, proto);
     if (!obj)
         return nullptr;
 
-    obj->initReservedSlot(MODULE_SLOT, PrivateValue((void*)&module));
+    obj->initReservedSlot(MODULE_SLOT, PrivateValue(&module));
     module.AddRef();
     return obj;
 }
 
 /* static */ bool
 WasmModuleObject::construct(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs callArgs = CallArgsFromVp(argc, vp);
@@ -404,16 +412,19 @@ const Class WasmInstanceObject::class_ =
     JSCLASS_DELAY_METADATA_BUILDER |
     JSCLASS_HAS_RESERVED_SLOTS(WasmInstanceObject::RESERVED_SLOTS),
     &WasmInstanceObject::classOps_,
 };
 
 const JSPropertySpec WasmInstanceObject::properties[] =
 { JS_PS_END };
 
+const JSFunctionSpec WasmInstanceObject::methods[] =
+{ JS_FS_END };
+
 bool
 WasmInstanceObject::isNewborn() const
 {
     MOZ_ASSERT(is<WasmInstanceObject>());
     return getReservedSlot(INSTANCE_SLOT).isUndefined();
 }
 
 /* static */ void
@@ -481,23 +492,24 @@ WasmInstanceObject::construct(JSContext*
         if (!args[1].isObject()) {
             JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_IMPORT_ARG);
             return false;
         }
         importObj = &args[1].toObject();
     }
 
     Rooted<FunctionVector> funcs(cx, FunctionVector(cx));
+    RootedWasmTableObject table(cx);
     RootedWasmMemoryObject memory(cx);
-    if (!GetImports(cx, importObj, module.imports(), &funcs, &memory))
+    if (!GetImports(cx, importObj, module.imports(), &funcs, &table, &memory))
         return false;
 
     RootedObject instanceProto(cx, &cx->global()->getPrototype(JSProto_WasmInstance).toObject());
     RootedWasmInstanceObject instanceObj(cx);
-    if (!module.instantiate(cx, funcs, memory, instanceProto, &instanceObj))
+    if (!module.instantiate(cx, funcs, table, memory, instanceProto, &instanceObj))
         return false;
 
     args.rval().setObject(*instanceObj);
     return true;
 }
 
 Instance&
 WasmInstanceObject::instance() const
@@ -670,66 +682,109 @@ MemoryBufferGetter(JSContext* cx, unsign
 }
 
 const JSPropertySpec WasmMemoryObject::properties[] =
 {
     JS_PSG("buffer", MemoryBufferGetter, 0),
     JS_PS_END
 };
 
+const JSFunctionSpec WasmMemoryObject::methods[] =
+{ JS_FS_END };
+
 ArrayBufferObjectMaybeShared&
 WasmMemoryObject::buffer() const
 {
     return getReservedSlot(BUFFER_SLOT).toObject().as<ArrayBufferObjectMaybeShared>();
 }
 
 // ============================================================================
 // WebAssembly.Table class and methods
 
-static void
-WasmTableObject_finalize(FreeOp* fop, JSObject* obj)
-{
-    obj->as<WasmTableObject>().table().Release();
-}
-
-static const ClassOps WasmTableObject_classOps =
+const ClassOps WasmTableObject::classOps_ =
 {
     nullptr, /* addProperty */
     nullptr, /* delProperty */
     nullptr, /* getProperty */
     nullptr, /* setProperty */
     nullptr, /* enumerate */
     nullptr, /* resolve */
     nullptr, /* mayResolve */
-    WasmTableObject_finalize
+    WasmTableObject::finalize,
+    nullptr, /* call */
+    nullptr, /* hasInstance */
+    nullptr, /* construct */
+    WasmTableObject::trace
 };
 
 const Class WasmTableObject::class_ =
 {
     "WebAssembly.Table",
     JSCLASS_DELAY_METADATA_BUILDER |
     JSCLASS_HAS_RESERVED_SLOTS(WasmTableObject::RESERVED_SLOTS),
-    &WasmTableObject_classOps
+    &WasmTableObject::classOps_
 };
 
+/* static */ void
+WasmTableObject::finalize(FreeOp* fop, JSObject* obj)
+{
+    WasmTableObject& tableObj = obj->as<WasmTableObject>();
+    tableObj.table().Release();
+    if (tableObj.initialized())
+        fop->delete_(&tableObj.instanceVector());
+}
+
+/* static */ void
+WasmTableObject::trace(JSTracer* trc, JSObject* obj)
+{
+    WasmTableObject& tableObj = obj->as<WasmTableObject>();
+    if (tableObj.initialized())
+        tableObj.instanceVector().trace(trc);
+}
+
 /* static */ WasmTableObject*
 WasmTableObject::create(JSContext* cx, Table& table)
 {
     RootedObject proto(cx, &cx->global()->getPrototype(JSProto_WasmTable).toObject());
 
     AutoSetNewObjectMetadata metadata(cx);
     auto* obj = NewObjectWithGivenProto<WasmTableObject>(cx, proto);
     if (!obj)
         return nullptr;
 
-    obj->initReservedSlot(TABLE_SLOT, PrivateValue((void*)&table));
     table.AddRef();
+    obj->initReservedSlot(TABLE_SLOT, PrivateValue(&table));
+
+    MOZ_ASSERT(!obj->initialized());
     return obj;
 }
 
+bool
+WasmTableObject::initialized() const
+{
+    return !getReservedSlot(INSTANCE_VECTOR_SLOT).isUndefined();
+}
+
+bool
+WasmTableObject::init(JSContext* cx, HandleWasmInstanceObject instanceObj)
+{
+    MOZ_ASSERT(!initialized());
+
+    auto instanceVector = MakeUnique<InstanceVector>();
+    if (!instanceVector || !instanceVector->appendN(instanceObj.get(), table().length())) {
+        ReportOutOfMemory(cx);
+        return false;
+    }
+
+    initReservedSlot(INSTANCE_VECTOR_SLOT, PrivateValue(instanceVector.release()));
+
+    MOZ_ASSERT(initialized());
+    return true;
+}
+
 /* static */ bool
 WasmTableObject::construct(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     if (!ThrowIfNotConstructing(cx, args, "Table"))
         return false;
 
@@ -755,17 +810,20 @@ WasmTableObject::construct(JSContext* cx
     if (!ToInteger(cx, initialVal, &initialDbl))
         return false;
 
     if (initialDbl < 0 || initialDbl > INT32_MAX) {
         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_SIZE, "Table", "initial");
         return false;
     }
 
-    SharedTable table = Table::create(cx, TableKind::AnyFunction, uint32_t(initialDbl));
+    uint32_t initial = uint32_t(initialDbl);
+    MOZ_ASSERT(double(initial) == initialDbl);
+
+    SharedTable table = Table::create(cx, TableKind::AnyFunction, initial);
     if (!table)
         return false;
 
     RootedWasmTableObject tableObj(cx, WasmTableObject::create(cx, *table));
     if (!tableObj)
         return false;
 
     args.rval().setObject(*tableObj);
@@ -773,42 +831,113 @@ WasmTableObject::construct(JSContext* cx
 }
 
 static bool
 IsTable(HandleValue v)
 {
     return v.isObject() && v.toObject().is<WasmTableObject>();
 }
 
-static bool
-TableLengthGetterImpl(JSContext* cx, const CallArgs& args)
+/* static */ bool
+WasmTableObject::lengthGetterImpl(JSContext* cx, const CallArgs& args)
 {
     args.rval().setNumber(args.thisv().toObject().as<WasmTableObject>().table().length());
     return true;
 }
 
-static bool
-TableLengthGetter(JSContext* cx, unsigned argc, Value* vp)
+/* static */ bool
+WasmTableObject::lengthGetter(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
-    return CallNonGenericMethod<IsTable, TableLengthGetterImpl>(cx, args);
+    return CallNonGenericMethod<IsTable, lengthGetterImpl>(cx, args);
 }
 
 const JSPropertySpec WasmTableObject::properties[] =
 {
-    JS_PSG("length", TableLengthGetter, 0),
+    JS_PSG("length", WasmTableObject::lengthGetter, 0),
     JS_PS_END
 };
 
+/* static */ bool
+WasmTableObject::getImpl(JSContext* cx, const CallArgs& args)
+{
+    RootedWasmTableObject tableObj(cx, &args.thisv().toObject().as<WasmTableObject>());
+    const Table& table = tableObj->table();
+
+    double indexDbl;
+    if (!ToInteger(cx, args.get(0), &indexDbl))
+        return false;
+
+    if (indexDbl < 0 || indexDbl >= table.length()) {
+        JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_BAD_INDEX);
+        return false;
+    }
+
+    uint32_t index = uint32_t(indexDbl);
+    MOZ_ASSERT(double(index) == indexDbl);
+
+    if (!tableObj->initialized()) {
+        args.rval().setNull();
+        return true;
+    }
+
+    const InstanceVector& instanceVector = tableObj->instanceVector();
+    MOZ_ASSERT(instanceVector.length() == table.length());
+
+    RootedWasmInstanceObject instanceObj(cx, instanceVector[index]);
+    const CodeRange* codeRange = instanceObj->instance().lookupCodeRange(table.array()[index]);
+
+    // A non-function code range means the bad-indirect-call stub, so a null element.
+    if (!codeRange || !codeRange->isFunction()) {
+        args.rval().setNull();
+        return true;
+    }
+
+    RootedFunction fun(cx);
+    if (!instanceObj->getExportedFunction(cx, instanceObj, codeRange->funcIndex(), &fun))
+        return false;
+
+    args.rval().setObject(*fun);
+    return true;
+}
+
+/* static */ bool
+WasmTableObject::get(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    return CallNonGenericMethod<IsTable, getImpl>(cx, args);
+}
+
+const JSFunctionSpec WasmTableObject::methods[] =
+{
+    JS_FN("get", WasmTableObject::get, 1, 0),
+    JS_FS_END
+};
+
 Table&
 WasmTableObject::table() const
 {
     return *(Table*)getReservedSlot(TABLE_SLOT).toPrivate();
 }
 
+WasmTableObject::InstanceVector&
+WasmTableObject::instanceVector() const
+{
+    MOZ_ASSERT(initialized());
+    return *(InstanceVector*)getReservedSlot(INSTANCE_VECTOR_SLOT).toPrivate();
+}
+
+void
+WasmTableObject::setInstance(uint32_t index, HandleWasmInstanceObject instanceObj)
+{
+    MOZ_ASSERT(initialized());
+    MOZ_ASSERT(instanceObj->instance().codeSegment().containsCodePC(table().array()[index]));
+    instanceVector()[index] = instanceObj;
+}
+
 // ============================================================================
 // WebAssembly class and static methods
 
 #if JS_HAS_TOSOURCE
 static bool
 WebAssembly_toSource(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
@@ -834,17 +963,17 @@ const Class js::WebAssemblyClass =
 template <class Class>
 static bool
 InitConstructor(JSContext* cx, HandleObject wasm, const char* name, MutableHandleObject proto)
 {
     proto.set(NewBuiltinClassInstance<PlainObject>(cx, SingletonObject));
     if (!proto)
         return false;
 
-    if (!JS_DefineProperties(cx, proto, Class::properties))
+    if (!DefinePropertiesAndFunctions(cx, proto, Class::properties, Class::methods))
         return false;
 
     RootedAtom className(cx, Atomize(cx, name, strlen(name)));
     if (!className)
         return false;
 
     RootedFunction ctor(cx, NewNativeConstructor(cx, Class::construct, 1, className));
     if (!ctor)
--- a/js/src/asmjs/WasmJS.h
+++ b/js/src/asmjs/WasmJS.h
@@ -93,16 +93,17 @@ class WasmModuleObject : public NativeOb
 {
     static const unsigned MODULE_SLOT = 0;
     static const ClassOps classOps_;
     static void finalize(FreeOp* fop, JSObject* obj);
   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*);
 
     static WasmModuleObject* create(ExclusiveContext* cx,
                                     wasm::Module& module,
                                     HandleObject proto = nullptr);
     wasm::Module& module() const;
 };
 
@@ -132,16 +133,17 @@ class WasmInstanceObject : public Native
                                 SystemAllocPolicy>;
     using WeakExportMap = JS::WeakCache<ExportMap>;
     WeakExportMap& exports() const;
 
   public:
     static const unsigned RESERVED_SLOTS = 2;
     static const Class class_;
     static const JSPropertySpec properties[];
+    static const JSFunctionSpec methods[];
     static bool construct(JSContext*, unsigned, Value*);
 
     static WasmInstanceObject* create(JSContext* cx, HandleObject proto);
     void init(wasm::UniqueInstance instance);
     wasm::Instance& instance() const;
 
     static bool getExportedFunction(JSContext* cx,
                                     Handle<WasmInstanceObject*> instanceObj,
@@ -160,16 +162,17 @@ typedef MutableHandle<WasmInstanceObject
 class WasmMemoryObject : public NativeObject
 {
     static const unsigned BUFFER_SLOT = 0;
     static const ClassOps classOps_;
   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*);
 
     static WasmMemoryObject* create(ExclusiveContext* cx,
                                     Handle<ArrayBufferObjectMaybeShared*> buffer,
                                     HandleObject proto);
     ArrayBufferObjectMaybeShared& buffer() const;
 };
 
@@ -180,24 +183,48 @@ typedef MutableHandle<WasmMemoryObject*>
 
 // The class of WebAssembly.Table. A WasmTableObject holds a refcount on a
 // wasm::Table, allowing a Table to be shared between multiple Instances
 // (eventually between multiple threads).
 
 class WasmTableObject : public NativeObject
 {
     static const unsigned TABLE_SLOT = 0;
+    static const unsigned INSTANCE_VECTOR_SLOT = 1;
+    static const ClassOps classOps_;
+    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);
+
+    // InstanceVector has the same length as the Table and assigns, to each
+    // element, the instance of the exported function stored in that element.
+    using InstanceVector = GCVector<HeapPtr<WasmInstanceObject*>, 0, SystemAllocPolicy>;
+    InstanceVector& instanceVector() const;
+
   public:
-    static const unsigned RESERVED_SLOTS = 1;
+    static const unsigned RESERVED_SLOTS = 2;
     static const Class class_;
     static const JSPropertySpec properties[];
+    static const JSFunctionSpec methods[];
     static bool construct(JSContext*, unsigned, Value*);
 
     static WasmTableObject* create(JSContext* cx, wasm::Table& table);
+    bool initialized() const;
+    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);
 };
 
 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,16 +344,38 @@ 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
+{
+    Instance& instance = instanceObj->instance();
+    const CodeSegment& codeSegment = instance.codeSegment();
+    const SharedTableVector& tables = instance.tables();
+
+    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);
+        }
+    }
+}
+
 // 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);
@@ -398,81 +420,80 @@ Module::instantiateMemory(JSContext* cx,
     uint8_t* memoryBase = memory->buffer().dataPointerEither().unwrap(/* memcpy */);
     for (const DataSegment& seg : dataSegments_)
         memcpy(memoryBase + seg.memoryOffset, bytecode_->begin() + seg.bytecodeOffset, seg.length);
 
     return true;
 }
 
 bool
-Module::instantiateTable(JSContext* cx, const CodeSegment& cs, SharedTableVector* tables) const
+Module::instantiateTable(JSContext* cx, const CodeSegment& codeSegment,
+                         HandleWasmTableObject tableImport, SharedTableVector* tables) const
 {
     for (const TableDesc& tableDesc : metadata_->tables) {
-        SharedTable table = Table::create(cx, tableDesc.kind, tableDesc.length);
-        if (!table || !tables->emplaceBack(table))
+        SharedTable table;
+        if (tableImport) {
+            MOZ_CRASH("NYI: table imports");
+        } else {
+            table = Table::create(cx, tableDesc.kind, tableDesc.length);
+            if (!table)
+                return false;
+        }
+        if (!tables->emplaceBack(table)) {
+            ReportOutOfMemory(cx);
             return false;
-
-        for (size_t i = 0; i < table->length(); i++)
-            table->array()[i] = cs.badIndirectCallCode();
-    }
-
-    for (const ElemSegment& seg : elemSegments_) {
-        SharedTable& table = (*tables)[seg.tableIndex];
-        MOZ_ASSERT(seg.offset + seg.elems.length() <= table->length());
-        for (size_t i = 0; i < seg.elems.length(); i++)
-            table->array()[seg.offset + i] = cs.code() + seg.elems[i];
+        }
     }
 
     return true;
 }
 
 static bool
 CreateExportObject(JSContext* cx,
                    HandleWasmInstanceObject instanceObj,
+                   MutableHandleWasmTableObject tableObj,
                    HandleWasmMemoryObject memoryObj,
                    const ExportVector& exports,
                    MutableHandleObject exportObj)
 {
     const Instance& instance = instanceObj->instance();
     const Metadata& metadata = instance.metadata();
-    const SharedTableVector& tables = instance.tables();
 
     if (metadata.isAsmJS() && exports.length() == 1 && strlen(exports[0].fieldName()) == 0) {
         RootedFunction fun(cx);
         if (!instanceObj->getExportedFunction(cx, instanceObj, exports[0].funcIndex(), &fun))
             return false;
         exportObj.set(fun);
         return true;
     }
 
     exportObj.set(JS_NewPlainObject(cx));
     if (!exportObj)
         return false;
 
-    RootedWasmTableObject tableObj(cx);
     for (const Export& exp : exports) {
         JSAtom* atom = AtomizeUTF8Chars(cx, exp.fieldName(), strlen(exp.fieldName()));
         if (!atom)
             return false;
 
         RootedId id(cx, AtomToId(atom));
         RootedValue val(cx);
         switch (exp.kind()) {
           case DefinitionKind::Function: {
             RootedFunction fun(cx);
             if (!instanceObj->getExportedFunction(cx, instanceObj, exp.funcIndex(), &fun))
                 return false;
             val = ObjectValue(*fun);
             break;
           }
           case DefinitionKind::Table: {
-            MOZ_ASSERT(tables.length() == 1);
             if (!tableObj) {
-                tableObj = WasmTableObject::create(cx, *tables[0]);
-                if (!tableObj)
+                MOZ_ASSERT(instance.tables().length() == 1);
+                tableObj.set(WasmTableObject::create(cx, *instance.tables()[0]));
+                if (!tableObj || !tableObj->init(cx, instanceObj))
                     return false;
             }
             val = ObjectValue(*tableObj);
             break;
           }
           case DefinitionKind::Memory: {
             if (metadata.assumptions.newFormat)
                 val = ObjectValue(*memoryObj);
@@ -487,32 +508,33 @@ CreateExportObject(JSContext* cx,
     }
 
     return true;
 }
 
 bool
 Module::instantiate(JSContext* cx,
                     Handle<FunctionVector> funcImports,
-                    HandleWasmMemoryObject memImport,
+                    HandleWasmTableObject tableImport,
+                    HandleWasmMemoryObject memoryImport,
                     HandleObject instanceProto,
                     MutableHandleWasmInstanceObject instanceObj) const
 {
     MOZ_ASSERT(funcImports.length() == metadata_->funcImports.length());
 
-    RootedWasmMemoryObject memory(cx, memImport);
+    RootedWasmMemoryObject memory(cx, memoryImport);
     if (!instantiateMemory(cx, &memory))
         return false;
 
-    auto cs = CodeSegment::create(cx, code_, linkData_, *metadata_, memory);
-    if (!cs)
+    auto codeSegment = CodeSegment::create(cx, code_, linkData_, *metadata_, memory);
+    if (!codeSegment)
         return false;
 
     SharedTableVector tables;
-    if (!instantiateTable(cx, *cs, &tables))
+    if (!instantiateTable(cx, *codeSegment, tableImport, &tables))
         return false;
 
     // To support viewing the source of an instance (Instance::createText), the
     // instance must hold onto a ref of the bytecode (keeping it alive). This
     // wastes memory for most users, so we try to only save the source when a
     // developer actually cares: when the compartment is debuggable (which is
     // true when the web console is open) or a names section is present (since
     // this going to be stripped for non-developer builds).
@@ -525,43 +547,49 @@ Module::instantiate(JSContext* cx,
     // before any GC can occur and invalidate the pointers stored in global
     // memory.
 
     {
         instanceObj.set(WasmInstanceObject::create(cx, instanceProto));
         if (!instanceObj)
             return false;
 
-        auto instance = cx->make_unique<Instance>(Move(cs),
+        auto instance = cx->make_unique<Instance>(Move(codeSegment),
                                                   *metadata_,
                                                   maybeBytecode,
                                                   memory,
                                                   Move(tables),
                                                   funcImports);
         if (!instance)
             return false;
 
         instanceObj->init(Move(instance));
     }
 
     // Create the export object.
 
     RootedObject exportObj(cx);
-    if (!CreateExportObject(cx, instanceObj, memory, exports_, &exportObj))
+    RootedWasmTableObject table(cx, tableImport);
+    if (!CreateExportObject(cx, instanceObj, &table, memory, exports_, &exportObj))
         return false;
 
     JSAtom* atom = Atomize(cx, InstanceExportField, strlen(InstanceExportField));
     if (!atom)
         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.
+
+    initElems(instanceObj, table);
+
     // 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
@@ -181,17 +181,19 @@ class Module : public RefCounted<Module>
     const ImportVector      imports_;
     const ExportVector      exports_;
     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& cs, SharedTableVector* tables) const;
+    bool instantiateTable(JSContext* cx, const CodeSegment& codeSegment,
+                          HandleWasmTableObject tableImport, SharedTableVector* tables) const;
+    void initElems(HandleWasmInstanceObject instanceObj, HandleWasmTableObject tableObj) const;
 
   public:
     Module(Bytes&& code,
            LinkData&& linkData,
            ImportVector&& imports,
            ExportVector&& exports,
            DataSegmentVector&& dataSegments,
            ElemSegmentVector&& elemSegments,
@@ -209,16 +211,17 @@ class Module : public RefCounted<Module>
 
     const Metadata& metadata() const { return *metadata_; }
     const ImportVector& imports() const { return imports_; }
 
     // Instantiate this module with the given imports:
 
     bool instantiate(JSContext* cx,
                      Handle<FunctionVector> funcImports,
+                     HandleWasmTableObject tableImport,
                      HandleWasmMemoryObject memoryImport,
                      HandleObject instanceProto,
                      MutableHandleWasmInstanceObject instanceObj) const;
 
     // Structured clone support:
 
     size_t serializedSize() const;
     uint8_t* serialize(uint8_t* cursor) const;
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -1509,16 +1509,25 @@ static bool
 FinalizeCount(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     args.rval().setInt32(finalizeCount);
     return true;
 }
 
 static bool
+ResetFinalizeCount(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    finalizeCount = 0;
+    args.rval().setUndefined();
+    return true;
+}
+
+static bool
 DumpHeap(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     DumpHeapNurseryBehaviour nurseryBehaviour = js::IgnoreNurseryObjects;
     FILE* dumpFile = nullptr;
 
     unsigned i = 0;
@@ -3673,16 +3682,20 @@ static const JSFunctionSpecWithHelp Test
 "  Get a special object whose finalization increases the counter returned\n"
 "  by the finalizeCount function."),
 
     JS_FN_HELP("finalizeCount", FinalizeCount, 0, 0,
 "finalizeCount()",
 "  Return the current value of the finalization counter that is incremented\n"
 "  each time an object returned by the makeFinalizeObserver is finalized."),
 
+    JS_FN_HELP("resetFinalizeCount", ResetFinalizeCount, 0, 0,
+"resetFinalizeCount()",
+"  Reset the value returned by finalizeCount()."),
+
     JS_FN_HELP("gcPreserveCode", GCPreserveCode, 0, 0,
 "gcPreserveCode()",
 "  Preserve JIT code during garbage collections."),
 
 #ifdef JS_GC_ZEAL
     JS_FN_HELP("gczeal", GCZeal, 2, 0,
 "gczeal(level, [N])",
 gc::ZealModeHelpText),
--- a/js/src/jit-test/tests/wasm/import-export.js
+++ b/js/src/jit-test/tests/wasm/import-export.js
@@ -191,16 +191,46 @@ assertEq(e.m instanceof Memory, true);
 
 var code = textToBinary('(module (table) (export "" table))');
 var e = new Instance(new Module(code)).exports;
 assertEq(Object.keys(e).length, 1);
 assertEq(String(Object.keys(e)), "");
 assertEq(e[""] instanceof Table, true);
 +assertEq(e[""].length, 0);
 
+// Table export function identity
+
+var code = textToBinary(`(module
+    (func $f (result i32) (i32.const 1))
+    (func $g (result i32) (i32.const 2))
+    (func $h (result i32) (i32.const 3))
+    (table (resizable 4))
+    (elem 0 $f)
+    (elem 2 $g)
+    (export "f1" $f)
+    (export "tbl1" table)
+    (export "f2" $f)
+    (export "tbl2" table)
+    (export "f3" $h)
+)`);
+var e = new Instance(new Module(code)).exports;
+assertEq(String(Object.keys(e)), "f1,tbl1,f2,tbl2,f3");
+assertEq(e.f1, e.f2);
+assertEq(e.f1(), 1);
+assertEq(e.f3(), 3);
+assertEq(e.tbl1, e.tbl2);
+assertEq(e.tbl1.get(0), e.f1);
+assertEq(e.tbl1.get(0), e.tbl1.get(0));
+assertEq(e.tbl1.get(0)(), 1);
+assertEq(e.tbl1.get(1), null);
+assertEq(e.tbl1.get(2), e.tbl1.get(2));
+assertEq(e.tbl1.get(2)(), 2);
+assertEq(e.tbl1.get(3), null);
+assertErrorMessage(() => e.tbl1.get(4), RangeError, /out-of-range index/);
+
 // 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);
 
--- a/js/src/jit-test/tests/wasm/jsapi.js
+++ b/js/src/jit-test/tests/wasm/jsapi.js
@@ -186,27 +186,45 @@ assertEq(tableProtoDesc.configurable, fa
 
 // 'WebAssembly.Table.prototype' object
 const tableProto = Table.prototype;
 assertEq(tableProto, tableProtoDesc.value);
 assertEq(String(tableProto), "[object Object]");
 assertEq(Object.getPrototypeOf(tableProto), Object.prototype);
 
 // 'WebAssembly.Table' instance objects
-const tbl1 = new Table({initial:1});
+const tbl1 = new Table({initial:2});
 assertEq(typeof tbl1, "object");
 assertEq(String(tbl1), "[object WebAssembly.Table]");
 assertEq(Object.getPrototypeOf(tbl1), tableProto);
 
 // 'WebAssembly.Table.prototype.length' accessor property
 const lengthDesc = Object.getOwnPropertyDescriptor(tableProto, 'length');
 assertEq(typeof lengthDesc.get, "function");
 assertEq(lengthDesc.set, undefined);
 assertEq(lengthDesc.enumerable, false);
 assertEq(lengthDesc.configurable, true);
 
 // 'WebAssembly.Table.prototype.length' getter
 const lengthGetter = lengthDesc.get;
 assertErrorMessage(() => lengthGetter.call(), TypeError, /called on incompatible undefined/);
 assertErrorMessage(() => lengthGetter.call({}), TypeError, /called on incompatible Object/);
 assertEq(typeof lengthGetter.call(tbl1), "number");
-assertEq(lengthGetter.call(tbl1), 1);
+assertEq(lengthGetter.call(tbl1), 2);
+
+// 'WebAssembly.Table.prototype.get' property
+const getDesc = Object.getOwnPropertyDescriptor(tableProto, 'get');
+assertEq(typeof getDesc.value, "function");
+assertEq(getDesc.enumerable, false);
+assertEq(getDesc.configurable, true);
 
+// 'WebAssembly.Table.prototype.get' method
+const get = getDesc.value;
+assertErrorMessage(() => get.call(), TypeError, /called on incompatible undefined/);
+assertErrorMessage(() => get.call({}), TypeError, /called on incompatible Object/);
+assertEq(get.call(tbl1, 0), null);
+assertEq(get.call(tbl1, 1), null);
+assertEq(get.call(tbl1, 1.5), null);
+assertErrorMessage(() => get.call(tbl1, 2), RangeError, /out-of-range index/);
+assertErrorMessage(() => get.call(tbl1, 2.5), RangeError, /out-of-range index/);
+assertErrorMessage(() => get.call(tbl1, -1), RangeError, /out-of-range index/);
+assertErrorMessage(() => get.call(tbl1, Math.pow(2,33)), RangeError, /out-of-range index/);
+assertErrorMessage(() => get.call(tbl1, {valueOf() { throw new Error("hi") }}), Error, "hi");
--- a/js/src/jit-test/tests/wasm/profiling.js
+++ b/js/src/jit-test/tests/wasm/profiling.js
@@ -1,15 +1,23 @@
 load(libdir + "wasm.js");
 load(libdir + "asserts.js");
 
 // Single-step profiling currently only works in the ARM simulator
 if (!getBuildConfiguration()["arm-simulator"])
     quit();
 
+const Module = WebAssembly.Module;
+const Instance = WebAssembly.Instance;
+const Table = WebAssembly.Table;
+
+// 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');
+
 function normalize(stack)
 {
     var wasmFrameTypes = [
         {re:/^entry trampoline \(in asm.js\)$/,             sub:">"},
         {re:/^wasm-function\[(\d+)\] \(.*\)$/,              sub:"$1"},
         {re:/^(fast|slow) FFI trampoline \(in asm.js\)$/,   sub:"<"},
         {re:/ \(in asm.js\)$/,                              sub:""}
     ];
@@ -45,17 +53,17 @@ function assertEqStacks(got, expect)
         }
     }
 }
 
 function test(code, expect)
 {
     enableSPSProfiling();
 
-    var f = wasmEvalText(code);
+    var f = new Instance(new Module(textToBinary(code))).exports[""];
     enableSingleStepProfiling();
     f();
     assertEqStacks(disableSingleStepProfiling(), expect);
 
     disableSPSProfiling();
 }
 
 test(
@@ -97,8 +105,50 @@ testError(
     (type $good (func))
     (type $bad (func (param i32)))
     (func $foo (call_indirect $bad (i32.const 0) (i32.const 1)))
     (func $bar (type $good))
     (table $bar)
     (export "" $foo)
 )`,
 Error);
+
+(function() {
+    var e = new Instance(new Module(textToBinary(`
+    (module
+        (func $foo (result i32) (i32.const 42))
+        (export "foo" $foo)
+        (func $bar (result i32) (i32.const 13))
+        (table $foo $bar)
+        (export "tbl" table)
+    )
+    `))).exports;
+    assertEq(e.foo(), 42);
+    assertEq(e.tbl.get(0)(), 42);
+    assertEq(e.tbl.get(1)(), 13);
+
+    enableSPSProfiling();
+    enableSingleStepProfiling();
+    assertEq(e.tbl.get(0)(), 42);
+    assertEqStacks(disableSingleStepProfiling(), ["", ">", "0,>", ">", ""]);
+    disableSPSProfiling();
+
+    assertEq(e.foo(), 42);
+    assertEq(e.tbl.get(0)(), 42);
+    assertEq(e.tbl.get(1)(), 13);
+
+    enableSPSProfiling();
+    enableSingleStepProfiling();
+    assertEq(e.tbl.get(1)(), 13);
+    assertEqStacks(disableSingleStepProfiling(), ["", ">", "1,>", ">", ""]);
+    disableSPSProfiling();
+
+    assertEq(e.tbl.get(0)(), 42);
+    assertEq(e.tbl.get(1)(), 13);
+    assertEq(e.foo(), 42);
+
+    enableSPSProfiling();
+    enableSingleStepProfiling();
+    assertEq(e.foo(), 42);
+    assertEq(e.tbl.get(1)(), 13);
+    assertEqStacks(disableSingleStepProfiling(), ["", ">", "0,>", ">", "", ">", "1,>", ">", ""]);
+    disableSPSProfiling();
+})();
--- a/js/src/jit-test/tests/wasm/tables.js
+++ b/js/src/jit-test/tests/wasm/tables.js
@@ -1,47 +1,142 @@
+// |jit-test| --no-baseline
+
+// Turn off baseline and since it messes up the GC finalization assertions by
+// adding spurious edges to the GC graph.
+
 load(libdir + 'wasm.js');
 load(libdir + 'asserts.js');
 
 const Module = WebAssembly.Module;
 const Instance = WebAssembly.Instance;
+const Table = WebAssembly.Table;
 
 // 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 evalText = (str, imports) => new Instance(new Module(textToBinary(str)), imports).exports;
+const evalText = (str, imports) => new Instance(new Module(textToBinary(str)), imports);
 
-const caller = `(func $call (param $i i32) (result i32) (call_indirect 0 (get_local $i))) (export "call" $call)`
-const callee = i => `(func $f${i} (result i32) (i32.const ${i}))`;
+var callee = i => `(func $f${i} (result i32) (i32.const ${i}))`;
 
 assertErrorMessage(() => new Module(textToBinary(`(module (elem 0 $f0) ${callee(0)})`)), TypeError, /table index out of range/);
 assertErrorMessage(() => new Module(textToBinary(`(module (table (resizable 10)) (elem 0 0))`)), TypeError, /table element out of range/);
 assertErrorMessage(() => new Module(textToBinary(`(module (table (resizable 10)) (func) (elem 0 0 1))`)), TypeError, /table element out of range/);
 assertErrorMessage(() => new Module(textToBinary(`(module (table (resizable 10)) (elem 10 $f0) ${callee(0)})`)), TypeError, /element segment does not fit/);
 assertErrorMessage(() => new Module(textToBinary(`(module (table (resizable 10)) (elem 8 $f0 $f0 $f0) ${callee(0)})`)), TypeError, /element segment does not fit/);
 assertErrorMessage(() => new Module(textToBinary(`(module (table (resizable 10)) (elem 1 $f0 $f0) (elem 0 $f0) ${callee(0)})`)), TypeError, /must be.*ordered/);
 assertErrorMessage(() => new Module(textToBinary(`(module (table (resizable 10)) (elem 1 $f0 $f0) (elem 2 $f0) ${callee(0)})`)), TypeError, /must be.*disjoint/);
 
-var call = evalText(`(module (table (resizable 10)) ${callee(0)} ${caller})`).call;
+var caller = `(type $v2i (func (result i32))) (func $call (param $i i32) (result i32) (call_indirect $v2i (get_local $i))) (export "call" $call)`
+var callee = i => `(func $f${i} (type $v2i) (result i32) (i32.const ${i}))`;
+
+var call = evalText(`(module (table (resizable 10)) ${callee(0)} ${caller})`).exports.call;
 assertErrorMessage(() => call(0), Error, /bad wasm indirect call/);
 assertErrorMessage(() => call(10), Error, /out-of-range/);
 
-var call = evalText(`(module (table (resizable 10)) (elem 0) ${callee(0)} ${caller})`).call;
+var call = evalText(`(module (table (resizable 10)) (elem 0) ${callee(0)} ${caller})`).exports.call;
 assertErrorMessage(() => call(0), Error, /bad wasm indirect call/);
 assertErrorMessage(() => call(10), Error, /out-of-range/);
 
-var call = evalText(`(module (table (resizable 10)) (elem 0 $f0) ${callee(0)} ${caller})`).call;
+var call = evalText(`(module (table (resizable 10)) (elem 0 $f0) ${callee(0)} ${caller})`).exports.call;
 assertEq(call(0), 0);
 assertErrorMessage(() => call(1), Error, /bad wasm indirect call/);
 assertErrorMessage(() => call(2), Error, /bad wasm indirect call/);
 assertErrorMessage(() => call(10), Error, /out-of-range/);
 
-var call = evalText(`(module (table (resizable 10)) (elem 1 $f0 $f1) (elem 4 $f0 $f2) ${callee(0)} ${callee(1)} ${callee(2)} ${caller})`).call;
+var call = evalText(`(module (table (resizable 10)) (elem 1 $f0 $f1) (elem 4 $f0 $f2) ${callee(0)} ${callee(1)} ${callee(2)} ${caller})`).exports.call;
 assertErrorMessage(() => call(0), Error, /bad wasm indirect call/);
 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/);
 
+// 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);
+assertEq(f(), e.call(0));
+assertErrorMessage(() => e.call(1), Error, /bad wasm indirect call/);
+assertErrorMessage(() => e.call(2), Error, /out-of-range/);
+assertEq(finalizeCount(), 0);
+i.edge = makeFinalizeObserver();
+e.edge = makeFinalizeObserver();
+t.edge = makeFinalizeObserver();
+f.edge = makeFinalizeObserver();
+gc();
+assertEq(finalizeCount(), 0);
+f = null;
+gc();
+assertEq(finalizeCount(), 1);
+f = t.get(0);
+f.edge = makeFinalizeObserver();
+gc();
+assertEq(finalizeCount(), 1);
+i.exports = null;
+e = null;
+gc();
+assertEq(finalizeCount(), 2);
+t = null;
+gc();
+assertEq(finalizeCount(), 3);
+i = null;
+gc();
+assertEq(finalizeCount(), 3);
+assertEq(f(), 0);
+f = null;
+gc();
+assertEq(finalizeCount(), 5);
+
+// A table should hold the instance of any of its elements alive.
+resetFinalizeCount();
+var i = evalText(`(module (table (resizable 1)) (export "tbl" table) (elem 0 $f0) ${callee(0)} ${caller})`);
+var e = i.exports;
+var t = e.tbl;
+var f = t.get(0);
+i.edge = makeFinalizeObserver();
+e.edge = makeFinalizeObserver();
+t.edge = makeFinalizeObserver();
+f.edge = makeFinalizeObserver();
+gc();
+assertEq(finalizeCount(), 0);
+i.exports = null;
+e = null;
+gc();
+assertEq(finalizeCount(), 1);
+f = null;
+gc();
+assertEq(finalizeCount(), 2);
+i = null;
+gc();
+assertEq(finalizeCount(), 2);
+t = null;
+gc();
+assertEq(finalizeCount(), 4);
+
+// The bad-indirect-call stub should (currently, could be changed later) keep
+// the instance containing that stub alive.
+resetFinalizeCount();
+var i = evalText(`(module (table (resizable 2)) (export "tbl" table) ${caller})`);
+var e = i.exports;
+var t = e.tbl;
+i.edge = makeFinalizeObserver();
+e.edge = makeFinalizeObserver();
+t.edge = makeFinalizeObserver();
+gc();
+assertEq(finalizeCount(), 0);
+i.exports = null;
+e = null;
+gc();
+assertEq(finalizeCount(), 1);
+i = null;
+gc();
+assertEq(finalizeCount(), 1);
+t = null;
+gc();
+assertEq(finalizeCount(), 3);