Bug 1313180 - Baldr: allow JS imports in Tables (r=bbouvier)
authorLuke Wagner <luke@mozilla.com>
Fri, 04 Nov 2016 17:05:57 -0500
changeset 321093 ad7523af20b9386d4770e7d124ba42ba8b061a70
parent 321092 a938e94dc04db48f56a39f4a798977d015a5050e
child 321094 0394048027e82fc887c9e8008b6bd468d4c4a49e
push id83526
push userlwagner@mozilla.com
push dateFri, 04 Nov 2016 23:44:07 +0000
treeherdermozilla-inbound@ad7523af20b9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbbouvier
bugs1313180
milestone52.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 1313180 - Baldr: allow JS imports in Tables (r=bbouvier) MozReview-Commit-ID: 7yCF1SqsUUv
js/src/jit-test/tests/wasm/import-export.js
js/src/jit-test/tests/wasm/js-reexport.js
js/src/jit-test/tests/wasm/profiling.js
js/src/jit-test/tests/wasm/spec/imports.wast.js
js/src/jit-test/tests/wasm/spec/linking.wast.js
js/src/wasm/WasmGenerator.cpp
js/src/wasm/WasmGenerator.h
js/src/wasm/WasmModule.cpp
js/src/wasm/WasmStubs.cpp
js/src/wasm/WasmStubs.h
js/src/wasm/WasmTextToBinary.cpp
--- a/js/src/jit-test/tests/wasm/import-export.js
+++ b/js/src/jit-test/tests/wasm/import-export.js
@@ -232,31 +232,33 @@ var code = wasmTextToBinary('(module (ta
 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 = wasmTextToBinary(`(module
+var text = `(module
     (func $f (result i32) (i32.const 1))
     (func $g (result i32) (i32.const 2))
     (func $h (result i32) (i32.const 3))
     (table 4 anyfunc)
     (elem (i32.const 0) $f)
     (elem (i32.const 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");
+    (func (export "run") (result i32) (call_indirect 0 (i32.const 2)))
+)`;
+wasmFullPass(text, 2);
+var e = new Instance(new Module(wasmTextToBinary(text))).exports;
+assertEq(String(Object.keys(e)), "f1,tbl1,f2,tbl2,f3,run");
 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);
@@ -267,16 +269,52 @@ assertErrorMessage(() => e.tbl1.get(4), 
 assertEq(e.tbl1.get(1), null);
 e.tbl1.set(1, e.f3);
 assertEq(e.tbl1.get(1), e.f3);
 e.tbl1.set(1, null);
 assertEq(e.tbl1.get(1), null);
 e.tbl1.set(3, e.f1);
 assertEq(e.tbl1.get(0), e.tbl1.get(3));
 
+// JS re-exports
+
+var args;
+var m = new Module(wasmTextToBinary(`(module
+    (export "a" $a) (import $a "" "a" (param f32))
+    (export "b" $b) (import $b "" "b" (param i32) (result i32))
+    (export "c" $c) (import $c "" "c" (result i32))
+    (export "d" $d) (import $d "" "d")
+)`));
+var js = function() { args = arguments; return 42 }
+var e = new Instance(m, {"":{a:js, b:js, c:js, d:js}}).exports;
+assertEq(e.a.length, 1);
+assertEq(e.a(), undefined);
+assertEq(args.length, 1);
+assertEq(args[0], NaN);
+assertEq(e.a(99.5), undefined);
+assertEq(args.length, 1);
+assertEq(args[0], 99.5);
+assertEq(e.b.length, 1);
+assertEq(e.b(), 42);
+assertEq(args.length, 1);
+assertEq(args[0], 0);
+assertEq(e.b(99.5), 42);
+assertEq(args.length, 1);
+assertEq(args[0], 99);
+assertEq(e.c.length, 0);
+assertEq(e.c(), 42);
+assertEq(args.length, 0);
+assertEq(e.c(99), 42);
+assertEq(args.length, 0);
+assertEq(e.d.length, 0);
+assertEq(e.d(), undefined);
+assertEq(args.length, 0);
+assertEq(e.d(99), undefined);
+assertEq(args.length, 0);
+
 // Re-exports and Identity:
 
 var code = wasmTextToBinary('(module (import "a" "b" (memory 1 1)) (export "foo" memory) (export "bar" memory))');
 var mem = new Memory({initial:1, maximum:1});
 var e = new Instance(new Module(code), {a:{b:mem}}).exports;
 assertEq(mem, e.foo);
 assertEq(mem, e.bar);
 
@@ -293,30 +331,66 @@ assertEq(e1.foo, tbl.get(0));
 tbl.set(1, e1.foo);
 assertEq(e1.foo, tbl.get(1));
 var e2 = new Instance(new Module(code), {a:{b:tbl}}).exports;
 assertEq(e2.foo, tbl.get(0));
 assertEq(e1.foo, tbl.get(1));
 assertEq(tbl.get(0) === e1.foo, false);
 assertEq(e1.foo === e2.foo, false);
 
-var code = wasmTextToBinary('(module (table 2 2 anyfunc) (import $foo "a" "b" (result i32)) (func $bar (result i32) (i32.const 13)) (elem (i32.const 0) $foo $bar) (export "foo" $foo) (export "bar" $bar) (export "tbl" table))');
-var foo = new Instance(new Module(wasmTextToBinary('(module (func (result i32) (i32.const 42)) (export "foo" 0))'))).exports.foo;
-var e1 = new Instance(new Module(code), {a:{b:foo}}).exports;
-assertEq(foo, e1.foo);
-assertEq(foo, e1.tbl.get(0));
+var m = new Module(wasmTextToBinary(`(module
+    (table 3 anyfunc)
+    (import $foo "" "foo" (result i32))
+    (import $bar "" "bar" (result i32))
+    (func $baz (result i32) (i32.const 13))
+    (elem (i32.const 0) $foo $bar $baz)
+    (export "foo" $foo)
+    (export "bar" $bar)
+    (export "baz" $baz)
+    (export "tbl" table)
+)`));
+var jsFun = () => 83;
+var wasmFun = new Instance(new Module(wasmTextToBinary('(module (func (result i32) (i32.const 42)) (export "foo" 0))'))).exports.foo;
+var e1 = new Instance(m, {"":{foo:jsFun, bar:wasmFun}}).exports;
+assertEq(jsFun === e1.foo, false);
+assertEq(wasmFun, e1.bar);
+assertEq(e1.foo, e1.tbl.get(0));
 assertEq(e1.bar, e1.tbl.get(1));
-assertEq(e1.tbl.get(0)(), 42);
-assertEq(e1.tbl.get(1)(), 13);
-var e2 = new Instance(new Module(code), {a:{b:foo}}).exports;
-assertEq(e1.foo, e2.foo);
-assertEq(e1.bar === e2.bar, false);
-assertEq(e1.tbl === e2.tbl, false);
-assertEq(e1.tbl.get(0), e2.tbl.get(0));
-assertEq(e1.tbl.get(1) === e2.tbl.get(1), false);
+assertEq(e1.baz, e1.tbl.get(2));
+assertEq(e1.tbl.get(0)(), 83);
+assertEq(e1.tbl.get(1)(), 42);
+assertEq(e1.tbl.get(2)(), 13);
+var e2 = new Instance(m, {"":{foo:jsFun, bar:jsFun}}).exports;
+assertEq(jsFun === e2.foo, false);
+assertEq(jsFun === e2.bar, false);
+assertEq(e2.foo === e1.foo, false);
+assertEq(e2.bar === e1.bar, false);
+assertEq(e2.baz === e1.baz, false);
+assertEq(e2.tbl === e1.tbl, false);
+assertEq(e2.foo, e2.tbl.get(0));
+assertEq(e2.bar, e2.tbl.get(1));
+assertEq(e2.baz, e2.tbl.get(2));
+var e3 = new Instance(m, {"":{foo:wasmFun, bar:wasmFun}}).exports;
+assertEq(wasmFun, e3.foo);
+assertEq(wasmFun, e3.bar);
+assertEq(e3.baz === e3.foo, false);
+assertEq(e3.baz === e1.baz, false);
+assertEq(e3.tbl === e1.tbl, false);
+assertEq(e3.foo, e3.tbl.get(0));
+assertEq(e3.bar, e3.tbl.get(1));
+assertEq(e3.baz, e3.tbl.get(2));
+var e4 = new Instance(m, {"":{foo:e1.foo, bar:e1.foo}}).exports;
+assertEq(e4.foo, e1.foo);
+assertEq(e4.bar, e1.foo);
+assertEq(e4.baz === e4.foo, false);
+assertEq(e4.baz === e1.baz, false);
+assertEq(e4.tbl === e1.tbl, false);
+assertEq(e4.foo, e4.tbl.get(0));
+assertEq(e4.foo, e4.tbl.get(1));
+assertEq(e4.baz, e4.tbl.get(2));
 
 // i64 is fully allowed for imported wasm functions
 
 var code1 = wasmTextToBinary('(module (func $exp (param i64) (result i64) (i64.add (get_local 0) (i64.const 10))) (export "exp" $exp))');
 var e1 = new Instance(new Module(code1)).exports;
 var code2 = wasmTextToBinary('(module (import $i "a" "b" (param i64) (result i64)) (func $f (result i32) (i32.wrap/i64 (call $i (i64.const 42)))) (export "f" $f))');
 var e2 = new Instance(new Module(code2), {a:{b:e1.exp}}).exports;
 assertEq(e2.f(), 52);
@@ -425,17 +499,17 @@ assertEq(tbl.get(0), null);
 assertEq(tbl.get(1), null);
 
 var i = new Instance(m, {a:{mem, tbl, memOff:npages*64*1024-1, tblOff:1}});
 assertEq(mem8[0], 1);
 assertEq(mem8[npages*64*1024-1], 2);
 assertEq(tbl.get(0), i.exports.f);
 assertEq(tbl.get(1), i.exports.g);
 
-// Elem segments on imports
+// Elem segments on imported tables
 
 var m = new Module(wasmTextToBinary(`
     (module
         (import "a" "b" (table 10 anyfunc))
         (elem (i32.const 0) $one $two)
         (elem (i32.const 3) $three $four)
         (func $one (result i32) (i32.const 1))
         (func $two (result i32) (i32.const 2))
@@ -449,28 +523,32 @@ 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);
 
 var m = new Module(wasmTextToBinary(`
     (module
-        (func $their (import "" "func"))
-        (table (import "" "table") 3 anyfunc)
-        (func $my)
+        (func $their1 (import "" "func") (result i32))
+        (func $their2 (import "" "func"))
+        (table (import "" "table") 4 anyfunc)
+        (func $my (result i32) i32.const 13)
         (elem (i32.const 1) $my)
-        (elem (i32.const 2) $their)
+        (elem (i32.const 2) $their1)
+        (elem (i32.const 3) $their2)
     )
 `));
-var tbl = new Table({initial:3, element:"anyfunc"});
-assertErrorMessage(() => new Instance(m, { "": { table: tbl, func: () => {}} }), TypeError, /can only assign WebAssembly exported functions to Table/);
-for (var i = 0; i < 3; i++) {
-    assertEq(tbl.get(i), null);
-}
+var tbl = new Table({initial:4, element:"anyfunc"});
+var f = () => 42;
+new Instance(m, { "": { table: tbl, func: f} });
+assertEq(tbl.get(0), null);
+assertEq(tbl.get(1)(), 13);
+assertEq(tbl.get(2)(), 42);
+assertEq(tbl.get(3)(), undefined);
 
 // Cross-instance calls
 
 var i1 = new Instance(new Module(wasmTextToBinary(`(module (func) (func (param i32) (result i32) (i32.add (get_local 0) (i32.const 1))) (func) (export "f" 1))`)));
 var i2 = new Instance(new Module(wasmTextToBinary(`(module (import $imp "a" "b" (param i32) (result i32)) (func $g (result i32) (call $imp (i32.const 13))) (export "g" $g))`)), {a:{b:i1.exports.f}});
 assertEq(i2.exports.g(), 14);
 
 var i1 = new Instance(new Module(wasmTextToBinary(`(module
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/js-reexport.js
@@ -0,0 +1,55 @@
+// |jit-test| test-also-wasm-baseline
+load(libdir + "wasm.js");
+
+const Module = WebAssembly.Module;
+const Instance = WebAssembly.Instance;
+const Memory = WebAssembly.Memory;
+const Table = WebAssembly.Table;
+
+function accum(...args) {
+    var sum = 0;
+    for (var i = 0; i < args.length; i++)
+        sum += args[i];
+    return sum;
+}
+
+var e = wasmEvalText(`(module
+    (import $a "" "a" (param i32 i32 i32 i32 i32 i32 i32 i32 i32 i32) (result i32))
+    (import $b "" "b" (param f32 f32 f32 f32 f32 f32 f32 f32 f32 f32) (result f32))
+    (import $c "" "c" (param f64 f64 f64 f64 f64 f64 f64 f64 f64 f64) (result f64))
+    (import $d "" "d" (param i32 f32 f64 i32 f32 f64 i32 f32 f64 i32) (result f64))
+    (func (export "a") (result i32)
+        i32.const 1 i32.const 2 i32.const 3 i32.const 4 i32.const 5
+        i32.const 6 i32.const 7 i32.const 8 i32.const 9 i32.const 10
+        call $a
+    )
+    (func (export "b") (result f32)
+        f32.const 1.1 f32.const 2.1 f32.const 3.1 f32.const 4.1 f32.const 5.1
+        f32.const 6.1 f32.const 7.1 f32.const 8.1 f32.const 9.1 f32.const 10.1
+        call $b
+    )
+    (func (export "c") (result f64)
+        f64.const 1.2 f64.const 2.2 f64.const 3.2 f64.const 4.2 f64.const 5.2
+        f64.const 6.2 f64.const 7.2 f64.const 8.2 f64.const 9.2 f64.const 10.2
+        call $c
+    )
+    (func (export "d") (result f64)
+        i32.const 1 f32.const 2.1 f64.const 3.1 i32.const 4 f32.const 5.1
+        f64.const 6.1 i32.const 7 f32.const 8.3 f64.const 9.3 i32.const 10
+        call $d
+    )
+)`, {"":{a:accum, b:accum, c:accum, d:accum, e:accum}}).exports;
+assertEq(e.a(), 55);
+assertEq(e.b(), 56);
+assertEq(e.c(), 57);
+assertEq(e.d(), 56);
+
+setJitCompilerOption("baseline.warmup.trigger", 5);
+setJitCompilerOption("ion.warmup.trigger", 10);
+
+var e = wasmEvalText(`(module
+    (import $a "" "a" (param i32 f64) (result f64))
+    (export "a" $a)
+)`, {"":{a:(a,b)=>a+b}}).exports;
+for (var i = 0; i < 100; i++)
+    assertEq(e.a(1.5, 2.5), 3.5);
--- a/js/src/jit-test/tests/wasm/profiling.js
+++ b/js/src/jit-test/tests/wasm/profiling.js
@@ -57,52 +57,65 @@ function assertEqStacks(got, expect)
     for (let i = 0; i < got.length; i++) {
         if (got[i] !== expect[i]) {
             print(`On stack ${i}, Got:\n${got[i]}\nExpect:\n${expect[i]}`);
             assertEq(got[i], expect[i]);
         }
     }
 }
 
-function test(code, expect)
+function test(code, importObj, expect)
 {
     enableSPSProfiling();
 
-    var f = wasmEvalText(code).exports[""];
+    var f = wasmEvalText(code, importObj).exports[""];
     enableSingleStepProfiling();
     f();
     assertEqStacks(disableSingleStepProfiling(), expect);
 
     disableSPSProfiling();
 }
 
 test(
 `(module
     (func (result i32) (i32.const 42))
     (export "" 0)
 )`,
+{},
 ["", ">", "0,>", ">", ""]);
 
 test(
 `(module
     (func (result i32) (i32.add (call 1) (i32.const 1)))
     (func (result i32) (i32.const 42))
     (export "" 0)
 )`,
+{},
 ["", ">", "0,>", "1,0,>", "0,>", ">", ""]);
 
 test(
 `(module
     (func $foo (call_indirect 0 (i32.const 0)))
     (func $bar)
     (table anyfunc (elem $bar))
     (export "" $foo)
 )`,
+{},
 ["", ">", "0,>", "1,0,>", "0,>", ">", ""]);
 
+test(
+`(module
+    (import $foo "" "foo")
+    (table anyfunc (elem $foo))
+    (func $bar (call_indirect 0 (i32.const 0)))
+    (export "" $bar)
+)`,
+{"":{foo:()=>{}}},
+["", ">", "1,>", "0,1,>", "<,0,1,>", "0,1,>", "1,>", ">", ""]);
+
 function testError(code, error, expect)
 {
     enableSPSProfiling();
     var f = wasmEvalText(code).exports[""];
     enableSingleStepProfiling();
     assertThrowsInstanceOf(f, error);
     assertEqStacks(disableSingleStepProfiling(), expect);
     disableSPSProfiling();
--- a/js/src/jit-test/tests/wasm/spec/imports.wast.js
+++ b/js/src/jit-test/tests/wasm/spec/imports.wast.js
@@ -1,4 +1,2 @@
 // |jit-test| test-also-wasm-baseline
-// TODO imports of host functions
-quit();
 var importedArgs = ['imports.wast']; load(scriptdir + '../spec.js');
--- a/js/src/jit-test/tests/wasm/spec/linking.wast.js
+++ b/js/src/jit-test/tests/wasm/spec/linking.wast.js
@@ -1,4 +1,2 @@
 // |jit-test| test-also-wasm-baseline
-// TODO type checks against exotic exported function objects
-quit();
 var importedArgs = ['linking.wast']; load(scriptdir + '../spec.js');
--- a/js/src/wasm/WasmGenerator.cpp
+++ b/js/src/wasm/WasmGenerator.cpp
@@ -220,22 +220,16 @@ ModuleGenerator::finishOutstandingTask()
             HelperThreadState().wait(lock, GlobalHelperThreadState::CONSUMER);
         }
     }
 
     return finishTask(task);
 }
 
 bool
-ModuleGenerator::funcIsImport(uint32_t funcIndex) const
-{
-    return funcIndex < shared_->funcImportGlobalDataOffsets.length();
-}
-
-bool
 ModuleGenerator::funcIsCompiled(uint32_t funcIndex) const
 {
     return funcToCodeRange_[funcIndex] != BAD_CODE_RANGE;
 }
 
 const CodeRange&
 ModuleGenerator::funcCodeRange(uint32_t funcIndex) const
 {
@@ -391,18 +385,16 @@ bool
 ModuleGenerator::finishFuncExports()
 {
     // In addition to all the functions that were explicitly exported, any
     // element of an exported table is also exported.
 
     for (ElemSegment& elems : elemSegments_) {
         if (shared_->tables[elems.tableIndex].external) {
             for (uint32_t funcIndex : elems.elemFuncIndices) {
-                if (funcIsImport(funcIndex))
-                    continue;
                 if (!exportedFuncs_.put(funcIndex))
                     return false;
             }
         }
     }
 
     // ModuleGenerator::exportedFuncs_ is an unordered HashSet. The
     // FuncExportVector stored in Metadata needs to be stored sorted by
@@ -468,18 +460,18 @@ ModuleGenerator::finishCodegen()
         for (uint32_t i = 0; i < numFuncExports; i++)
             entries[i] = GenerateEntry(masm, metadata_->funcExports[i]);
 
         if (!interpExits.resize(numFuncImports()))
             return false;
         if (!jitExits.resize(numFuncImports()))
             return false;
         for (uint32_t i = 0; i < numFuncImports(); i++) {
-            interpExits[i] = GenerateInterpExit(masm, metadata_->funcImports[i], i, &throwLabel);
-            jitExits[i] = GenerateJitExit(masm, metadata_->funcImports[i], &throwLabel);
+            interpExits[i] = GenerateImportInterpExit(masm, metadata_->funcImports[i], i, &throwLabel);
+            jitExits[i] = GenerateImportJitExit(masm, metadata_->funcImports[i], &throwLabel);
         }
 
         for (Trap trap : MakeEnumeratedRange(Trap::Limit))
             trapExits[trap] = GenerateTrapExit(masm, trap, &throwLabel);
 
         outOfBoundsExit = GenerateOutOfBoundsExit(masm, &throwLabel);
         unalignedAccessExit = GenerateUnalignedExit(masm, &throwLabel);
         interruptExit = GenerateInterruptExit(masm, &throwLabel);
@@ -614,16 +606,18 @@ ModuleGenerator::finishLinkData(Bytes& c
 #endif
 
     return true;
 }
 
 bool
 ModuleGenerator::addFuncImport(const Sig& sig, uint32_t globalDataOffset)
 {
+    MOZ_ASSERT(!finishedFuncDefs_);
+
     Sig copy;
     if (!copy.clone(sig))
         return false;
 
     return metadata_->funcImports.emplaceBack(Move(copy), globalDataOffset);
 }
 
 bool
@@ -791,22 +785,18 @@ ModuleGenerator::funcSig(uint32_t funcIn
 {
     MOZ_ASSERT(shared_->funcSigs[funcIndex]);
     return *shared_->funcSigs[funcIndex];
 }
 
 bool
 ModuleGenerator::addFuncExport(UniqueChars fieldName, uint32_t funcIndex)
 {
-    if (!funcIsImport(funcIndex)) {
-       if (!exportedFuncs_.put(funcIndex))
-           return false;
-    }
-
-    return exports_.emplaceBack(Move(fieldName), funcIndex, DefinitionKind::Function);
+    return exportedFuncs_.put(funcIndex) &&
+           exports_.emplaceBack(Move(fieldName), funcIndex, DefinitionKind::Function);
 }
 
 bool
 ModuleGenerator::addTableExport(UniqueChars fieldName)
 {
     MOZ_ASSERT(!startedFuncDefs_);
     MOZ_ASSERT(shared_->tables.length() == 1);
     shared_->tables[0].external = true;
@@ -823,34 +813,29 @@ bool
 ModuleGenerator::addGlobalExport(UniqueChars fieldName, uint32_t globalIndex)
 {
     return exports_.emplaceBack(Move(fieldName), globalIndex, DefinitionKind::Global);
 }
 
 bool
 ModuleGenerator::setStartFunction(uint32_t funcIndex)
 {
-    if (!funcIsImport(funcIndex)) {
-        if (!exportedFuncs_.put(funcIndex))
-            return false;
-    }
-
     metadata_->startFuncIndex.emplace(funcIndex);
-    return true;
+    return exportedFuncs_.put(funcIndex);
 }
 
 bool
 ModuleGenerator::addElemSegment(InitExpr offset, Uint32Vector&& elemFuncIndices)
 {
     MOZ_ASSERT(!isAsmJS());
     MOZ_ASSERT(!startedFuncDefs_);
     MOZ_ASSERT(shared_->tables.length() == 1);
 
     for (uint32_t funcIndex : elemFuncIndices) {
-        if (funcIsImport(funcIndex)) {
+        if (funcIndex < numFuncImports()) {
             shared_->tables[0].external = true;
             break;
         }
     }
 
     return elemSegments_.emplaceBack(0, offset, Move(elemFuncIndices));
 }
 
@@ -980,52 +965,67 @@ ModuleGenerator::finishFuncDefs()
     while (outstanding_ > 0) {
         if (!finishOutstandingTask())
             return false;
     }
 
     linkData_.functionCodeLength = masm_.size();
     finishedFuncDefs_ = true;
 
-    // In this patch, imports never have an associated code range.
+    // Generate wrapper functions for every import. These wrappers turn imports
+    // into plain functions so they can be put into tables and re-exported.
+    // asm.js cannot do either and so no wrappers are generated.
+
+    if (!isAsmJS()) {
+        for (size_t funcIndex = 0; funcIndex < numFuncImports(); funcIndex++) {
+            const FuncImport& funcImport = metadata_->funcImports[funcIndex];
+            const SigWithId& sig = funcSig(funcIndex);
+
+            FuncOffsets offsets = GenerateImportFunction(masm_, funcImport, sig.id);
+            if (masm_.oom())
+                return false;
+
+            uint32_t codeRangeIndex = metadata_->codeRanges.length();
+            if (!metadata_->codeRanges.emplaceBack(funcIndex, /* bytecodeOffset = */ 0, offsets))
+                return false;
+
+            MOZ_ASSERT(!funcIsCompiled(funcIndex));
+            funcToCodeRange_[funcIndex] = codeRangeIndex;
+        }
+    }
+
+    // All function indices should have an associated code range at this point
+    // (except in asm.js, which doesn't have import wrapper functions).
 
 #ifdef DEBUG
     if (isAsmJS()) {
         MOZ_ASSERT(numFuncImports() < AsmJSFirstDefFuncIndex);
         for (uint32_t i = 0; i < AsmJSFirstDefFuncIndex; i++)
             MOZ_ASSERT(funcToCodeRange_[i] == BAD_CODE_RANGE);
         for (uint32_t i = AsmJSFirstDefFuncIndex; i < numFinishedFuncDefs_; i++)
             MOZ_ASSERT(funcCodeRange(i).funcIndex() == i);
     } else {
         MOZ_ASSERT(numFinishedFuncDefs_ == numFuncDefs());
-        for (uint32_t i = 0; i < numFuncImports(); i++)
-            MOZ_ASSERT(funcToCodeRange_[i] == BAD_CODE_RANGE);
-        for (uint32_t i = numFuncImports(); i < numFuncs(); i++)
+        for (uint32_t i = 0; i < numFuncs(); i++)
             MOZ_ASSERT(funcCodeRange(i).funcIndex() == i);
     }
 #endif
 
     // Complete element segments with the code range index of every element, now
     // that all functions have been compiled.
 
     for (ElemSegment& elems : elemSegments_) {
         Uint32Vector& codeRangeIndices = elems.elemCodeRangeIndices;
 
         MOZ_ASSERT(codeRangeIndices.empty());
         if (!codeRangeIndices.reserve(elems.elemFuncIndices.length()))
             return false;
 
-        for (uint32_t funcIndex : elems.elemFuncIndices) {
-            if (funcIsImport(funcIndex)) {
-                codeRangeIndices.infallibleAppend(UINT32_MAX);
-                continue;
-            }
-
+        for (uint32_t funcIndex : elems.elemFuncIndices)
             codeRangeIndices.infallibleAppend(funcToCodeRange_[funcIndex]);
-        }
     }
 
     return true;
 }
 
 void
 ModuleGenerator::setFuncNames(NameInBytecodeVector&& funcNames)
 {
--- a/js/src/wasm/WasmGenerator.h
+++ b/js/src/wasm/WasmGenerator.h
@@ -111,17 +111,16 @@ class MOZ_STACK_CLASS ModuleGenerator
     IonCompileTaskPtrVector         freeTasks_;
 
     // Assertions
     DebugOnly<FunctionGenerator*>   activeFuncDef_;
     DebugOnly<bool>                 startedFuncDefs_;
     DebugOnly<bool>                 finishedFuncDefs_;
     DebugOnly<uint32_t>             numFinishedFuncDefs_;
 
-    bool funcIsImport(uint32_t funcIndex) const;
     bool funcIsCompiled(uint32_t funcIndex) const;
     const CodeRange& funcCodeRange(uint32_t funcIndex) const;
     MOZ_MUST_USE bool patchCallSites(TrapExitOffsetArray* maybeTrapExits = nullptr);
     MOZ_MUST_USE bool finishTask(IonCompileTask* task);
     MOZ_MUST_USE bool finishOutstandingTask();
     MOZ_MUST_USE bool finishFuncExports();
     MOZ_MUST_USE bool finishCodegen();
     MOZ_MUST_USE bool finishLinkData(Bytes& code);
--- a/js/src/wasm/WasmModule.cpp
+++ b/js/src/wasm/WasmModule.cpp
@@ -594,27 +594,16 @@ Module::initSegments(JSContext* cx,
         uint32_t tableLength = tables[seg.tableIndex]->length();
         uint32_t offset = EvaluateInitExpr(globalImports, seg.offset);
 
         if (offset > tableLength || tableLength - offset < numElems) {
             JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_FIT,
                                       "elem", "table");
             return false;
         }
-
-        for (uint32_t elemFuncIndex : seg.elemFuncIndices) {
-            if (elemFuncIndex < funcImports.length()) {
-                HandleFunction f = funcImports[elemFuncIndex];
-                if (!IsExportedWasmFunction(f)) {
-                    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
-                                              JSMSG_WASM_BAD_TABLE_VALUE);
-                    return false;
-                }
-            }
-        }
     }
 
     if (memoryObj) {
         for (const DataSegment& seg : dataSegments_) {
             if (!seg.length)
                 continue;
 
             uint32_t memoryLength = memoryObj->buffer().byteLength();
@@ -636,30 +625,27 @@ Module::initSegments(JSContext* cx,
     for (const ElemSegment& seg : elemSegments_) {
         Table& table = *tables[seg.tableIndex];
         uint32_t offset = EvaluateInitExpr(globalImports, seg.offset);
         bool profilingEnabled = instance.code().profilingEnabled();
         const CodeRangeVector& codeRanges = metadata().codeRanges;
         uint8_t* codeBase = instance.codeBase();
 
         for (uint32_t i = 0; i < seg.elemCodeRangeIndices.length(); i++) {
-            uint32_t elemFuncIndex = seg.elemFuncIndices[i];
-            if (elemFuncIndex < funcImports.length()) {
+            uint32_t funcIndex = seg.elemFuncIndices[i];
+            if (funcIndex < funcImports.length() && IsExportedWasmFunction(funcImports[funcIndex])) {
                 MOZ_ASSERT(!metadata().isAsmJS());
                 MOZ_ASSERT(!table.isTypedFunction());
-                MOZ_ASSERT(seg.elemCodeRangeIndices[i] == UINT32_MAX);
 
-                HandleFunction f = funcImports[elemFuncIndex];
+                HandleFunction f = funcImports[funcIndex];
                 WasmInstanceObject* exportInstanceObj = ExportedFunctionToInstanceObject(f);
                 const CodeRange& cr = exportInstanceObj->getExportedFunctionCodeRange(f);
                 Instance& exportInstance = exportInstanceObj->instance();
                 table.set(offset + i, exportInstance.codeBase() + cr.funcTableEntry(), exportInstance);
             } else {
-                MOZ_ASSERT(seg.elemCodeRangeIndices[i] != UINT32_MAX);
-
                 const CodeRange& cr = codeRanges[seg.elemCodeRangeIndices[i]];
                 uint32_t entryOffset = table.isTypedFunction()
                                        ? profilingEnabled
                                          ? cr.funcProfilingEntry()
                                          : cr.funcNonProfilingEntry()
                                        : cr.funcTableEntry();
                 table.set(offset + i, codeBase + entryOffset, instance);
             }
@@ -824,17 +810,19 @@ Module::instantiateTable(JSContext* cx, 
 
 static bool
 GetFunctionExport(JSContext* cx,
                   HandleWasmInstanceObject instanceObj,
                   Handle<FunctionVector> funcImports,
                   const Export& exp,
                   MutableHandleValue val)
 {
-    if (exp.funcIndex() < funcImports.length()) {
+    if (exp.funcIndex() < funcImports.length() &&
+        IsExportedWasmFunction(funcImports[exp.funcIndex()]))
+    {
         val.setObject(*funcImports[exp.funcIndex()]);
         return true;
     }
 
     RootedFunction fun(cx);
     if (!instanceObj->getExportedFunction(cx, instanceObj, exp.funcIndex(), &fun))
         return false;
 
@@ -1036,27 +1024,18 @@ Module::instantiate(JSContext* cx,
     if (!initSegments(cx, instance, funcImports, memory, globalImports))
         return false;
 
     // Now that the instance is fully live and initialized, the start function.
     // Note that failure may cause instantiation to throw, but the instance may
     // still be live via edges created by initSegments or the start function.
 
     if (metadata_->startFuncIndex) {
-        uint32_t startFuncIndex = *metadata_->startFuncIndex;
         FixedInvokeArgs<0> args(cx);
-        if (startFuncIndex < funcImports.length()) {
-            RootedValue fval(cx, ObjectValue(*funcImports[startFuncIndex]));
-            RootedValue thisv(cx);
-            RootedValue rval(cx);
-            if (!Call(cx, fval, thisv, args, &rval))
-                return false;
-        } else {
-            if (!instance->instance().callExport(cx, startFuncIndex, args))
-                return false;
-        }
+        if (!instance->instance().callExport(cx, *metadata_->startFuncIndex, args))
+            return false;
     }
 
     uint32_t mode = uint32_t(metadata().isAsmJS() ? Telemetry::ASMJS : Telemetry::WASM);
     cx->runtime()->addTelemetry(JS_TELEMETRY_AOT_USAGE, mode);
 
     return true;
 }
--- a/js/src/wasm/WasmStubs.cpp
+++ b/js/src/wasm/WasmStubs.cpp
@@ -317,130 +317,188 @@ wasm::GenerateEntry(MacroAssembler& masm
 
     masm.move32(Imm32(true), ReturnReg);
     masm.ret();
 
     offsets.end = masm.currentOffset();
     return offsets;
 }
 
+static void
+StackCopy(MacroAssembler& masm, MIRType type, Register scratch, Address src, Address dst)
+{
+    if (type == MIRType::Int32) {
+        masm.load32(src, scratch);
+        masm.store32(scratch, dst);
+    } else if (type == MIRType::Int64) {
+#if JS_BITS_PER_WORD == 32
+        masm.load32(Address(src.base, src.offset + INT64LOW_OFFSET), scratch);
+        masm.store32(scratch, Address(dst.base, dst.offset + INT64LOW_OFFSET));
+        masm.load32(Address(src.base, src.offset + INT64HIGH_OFFSET), scratch);
+        masm.store32(scratch, Address(dst.base, dst.offset + INT64HIGH_OFFSET));
+#else
+        Register64 scratch64(scratch);
+        masm.load64(src, scratch64);
+        masm.store64(scratch64, dst);
+#endif
+    } else {
+        MOZ_ASSERT(IsFloatingPointType(type));
+        masm.loadDouble(src, ScratchDoubleReg);
+        masm.storeDouble(ScratchDoubleReg, dst);
+    }
+}
+
 typedef bool ToValue;
 
 static void
 FillArgumentArray(MacroAssembler& masm, const ValTypeVector& args, unsigned argOffset,
                   unsigned offsetToCallerStackArgs, Register scratch, ToValue toValue)
 {
     for (ABIArgValTypeIter i(args); !i.done(); i++) {
-        Address dstAddr(masm.getStackPointer(), argOffset + i.index() * sizeof(Value));
+        Address dst(masm.getStackPointer(), argOffset + i.index() * sizeof(Value));
 
         MIRType type = i.mirType();
         switch (i->kind()) {
           case ABIArg::GPR:
             if (type == MIRType::Int32) {
                 if (toValue)
-                    masm.storeValue(JSVAL_TYPE_INT32, i->gpr(), dstAddr);
+                    masm.storeValue(JSVAL_TYPE_INT32, i->gpr(), dst);
                 else
-                    masm.store32(i->gpr(), dstAddr);
+                    masm.store32(i->gpr(), dst);
             } else if (type == MIRType::Int64) {
                 // We can't box int64 into Values (yet).
                 if (toValue)
                     masm.breakpoint();
                 else
-                    masm.store64(i->gpr64(), dstAddr);
+                    masm.store64(i->gpr64(), dst);
             } else {
                 MOZ_CRASH("unexpected input type?");
             }
             break;
 #ifdef JS_CODEGEN_REGISTER_PAIR
           case ABIArg::GPR_PAIR:
             if (type == MIRType::Int64)
-                masm.store64(i->gpr64(), dstAddr);
+                masm.store64(i->gpr64(), dst);
             else
                 MOZ_CRASH("wasm uses hardfp for function calls.");
             break;
 #endif
           case ABIArg::FPU: {
             MOZ_ASSERT(IsFloatingPointType(type));
             FloatRegister srcReg = i->fpu();
             if (type == MIRType::Double) {
                 if (toValue) {
                     // Preserve the NaN pattern in the input.
                     masm.moveDouble(srcReg, ScratchDoubleReg);
                     srcReg = ScratchDoubleReg;
                     masm.canonicalizeDouble(srcReg);
                 }
-                masm.storeDouble(srcReg, dstAddr);
+                masm.storeDouble(srcReg, dst);
             } else {
                 MOZ_ASSERT(type == MIRType::Float32);
                 if (toValue) {
                     // JS::Values can't store Float32, so convert to a Double.
                     masm.convertFloat32ToDouble(srcReg, ScratchDoubleReg);
                     masm.canonicalizeDouble(ScratchDoubleReg);
-                    masm.storeDouble(ScratchDoubleReg, dstAddr);
+                    masm.storeDouble(ScratchDoubleReg, dst);
                 } else {
                     // Preserve the NaN pattern in the input.
                     masm.moveFloat32(srcReg, ScratchFloat32Reg);
                     masm.canonicalizeFloat(ScratchFloat32Reg);
-                    masm.storeFloat32(ScratchFloat32Reg, dstAddr);
+                    masm.storeFloat32(ScratchFloat32Reg, dst);
                 }
             }
             break;
           }
-          case ABIArg::Stack:
-            if (type == MIRType::Int32) {
-                Address src(masm.getStackPointer(), offsetToCallerStackArgs + i->offsetFromArgBase());
-                masm.load32(src, scratch);
-                if (toValue)
-                    masm.storeValue(JSVAL_TYPE_INT32, scratch, dstAddr);
-                else
-                    masm.store32(scratch, dstAddr);
-            } else if (type == MIRType::Int64) {
-                // We can't box int64 into Values (yet).
-                if (toValue) {
+          case ABIArg::Stack: {
+            Address src(masm.getStackPointer(), offsetToCallerStackArgs + i->offsetFromArgBase());
+            if (toValue) {
+                if (type == MIRType::Int32) {
+                    masm.load32(src, scratch);
+                    masm.storeValue(JSVAL_TYPE_INT32, scratch, dst);
+                } else if (type == MIRType::Int64) {
+                    // We can't box int64 into Values (yet).
                     masm.breakpoint();
                 } else {
-                    Address src(masm.getStackPointer(), offsetToCallerStackArgs + i->offsetFromArgBase());
-#if JS_BITS_PER_WORD == 32
-                    masm.load32(Address(src.base, src.offset + INT64LOW_OFFSET), scratch);
-                    masm.store32(scratch, Address(dstAddr.base, dstAddr.offset + INT64LOW_OFFSET));
-                    masm.load32(Address(src.base, src.offset + INT64HIGH_OFFSET), scratch);
-                    masm.store32(scratch, Address(dstAddr.base, dstAddr.offset + INT64HIGH_OFFSET));
-#else
-                    Register64 scratch64(scratch);
-                    masm.load64(src, scratch64);
-                    masm.store64(scratch64, dstAddr);
-#endif
-                }
-            } else {
-                MOZ_ASSERT(IsFloatingPointType(type));
-                Address src(masm.getStackPointer(), offsetToCallerStackArgs + i->offsetFromArgBase());
-                if (toValue) {
+                    MOZ_ASSERT(IsFloatingPointType(type));
                     if (type == MIRType::Float32) {
                         masm.loadFloat32(src, ScratchFloat32Reg);
                         masm.convertFloat32ToDouble(ScratchFloat32Reg, ScratchDoubleReg);
                     } else {
                         masm.loadDouble(src, ScratchDoubleReg);
                     }
                     masm.canonicalizeDouble(ScratchDoubleReg);
-                } else {
-                    masm.loadDouble(src, ScratchDoubleReg);
+                    masm.storeDouble(ScratchDoubleReg, dst);
                 }
-                masm.storeDouble(ScratchDoubleReg, dstAddr);
+            } else {
+                StackCopy(masm, type, scratch, src, dst);
             }
             break;
+          }
         }
     }
 }
 
+// Generate a wrapper function with the standard intra-wasm call ABI which simply
+// calls an import. This wrapper function allows any import to be treated like a
+// normal wasm function for the purposes of exports and table calls. In
+// particular, the wrapper function provides:
+//  - a table entry, so JS imports can be put into tables
+//  - normal (non-)profiling entries, so that, if the import is re-exported,
+//    an entry stub can be generated and called without any special cases
+FuncOffsets
+wasm::GenerateImportFunction(jit::MacroAssembler& masm, const FuncImport& fi, SigIdDesc sigId)
+{
+    masm.setFramePushed(0);
+
+    unsigned tlsBytes = sizeof(void*);
+    unsigned framePushed = StackDecrementForCall(masm, WasmStackAlignment, fi.sig().args(), tlsBytes);
+
+    FuncOffsets offsets;
+    GenerateFunctionPrologue(masm, framePushed, sigId, &offsets);
+
+    // The argument register state is already setup by our caller. We just need
+    // to be sure not to clobber it before the call.
+    Register scratch = ABINonArgReg0;
+
+    // Copy our frame's stack arguments to the callee frame's stack argument.
+    unsigned offsetToCallerStackArgs = sizeof(Frame) + masm.framePushed();
+    ABIArgValTypeIter i(fi.sig().args());
+    for (; !i.done(); i++) {
+        if (i->kind() != ABIArg::Stack)
+            continue;
+
+        Address src(masm.getStackPointer(), offsetToCallerStackArgs + i->offsetFromArgBase());
+        Address dst(masm.getStackPointer(), i->offsetFromArgBase());
+        StackCopy(masm, i.mirType(), scratch, src, dst);
+    }
+
+    // Save the TLS register so it can be restored later.
+    uint32_t tlsStackOffset = i.stackBytesConsumedSoFar();
+    masm.storePtr(WasmTlsReg, Address(masm.getStackPointer(), tlsStackOffset));
+
+    // Call the import exit stub.
+    CallSiteDesc desc(CallSiteDesc::Dynamic);
+    masm.wasmCallImport(desc, CalleeDesc::import(fi.tlsDataOffset()));
+
+    // Restore the TLS register and pinned regs, per wasm function ABI.
+    masm.loadPtr(Address(masm.getStackPointer(), tlsStackOffset), WasmTlsReg);
+    masm.loadWasmPinnedRegsFromTls();
+
+    GenerateFunctionEpilogue(masm, framePushed, &offsets);
+    offsets.end = masm.currentOffset();
+    return offsets;
+}
+
 // Generate a stub that is called via the internal ABI derived from the
 // signature of the import and calls into an appropriate callImport C++
 // function, having boxed all the ABI arguments into a homogeneous Value array.
 ProfilingOffsets
-wasm::GenerateInterpExit(MacroAssembler& masm, const FuncImport& fi, uint32_t funcImportIndex,
-                         Label* throwLabel)
+wasm::GenerateImportInterpExit(MacroAssembler& masm, const FuncImport& fi, uint32_t funcImportIndex,
+                               Label* throwLabel)
 {
     masm.setFramePushed(0);
 
     // Argument types for Module::callImport_*:
     static const MIRType typeArray[] = { MIRType::Pointer,   // Instance*
                                          MIRType::Pointer,   // funcImportIndex
                                          MIRType::Int32,     // argc
                                          MIRType::Pointer }; // argv
@@ -562,17 +620,17 @@ wasm::GenerateInterpExit(MacroAssembler&
 }
 
 static const unsigned SavedTlsReg = sizeof(void*);
 
 // Generate a stub that is called via the internal ABI derived from the
 // signature of the import and calls into a compatible JIT function,
 // having boxed all the ABI arguments into the JIT stack frame layout.
 ProfilingOffsets
-wasm::GenerateJitExit(MacroAssembler& masm, const FuncImport& fi, Label* throwLabel)
+wasm::GenerateImportJitExit(MacroAssembler& masm, const FuncImport& fi, Label* throwLabel)
 {
     masm.setFramePushed(0);
 
     // JIT calls use the following stack layout (sp grows to the left):
     //   | retaddr | descriptor | callee | argc | this | arg1..N |
     // After the JIT frame, the global register (if present) is saved since the
     // JIT's ABI does not preserve non-volatile regs. Also, unlike most ABIs,
     // the JIT ABI requires that sp be JitStackAlignment-aligned *after* pushing
--- a/js/src/wasm/WasmStubs.h
+++ b/js/src/wasm/WasmStubs.h
@@ -28,22 +28,25 @@ namespace jit { class MacroAssembler; cl
 namespace wasm {
 
 class FuncExport;
 class FuncImport;
 
 extern Offsets
 GenerateEntry(jit::MacroAssembler& masm, const FuncExport& fe);
 
-extern ProfilingOffsets
-GenerateInterpExit(jit::MacroAssembler& masm, const FuncImport& fi, uint32_t funcImportIndex,
-                   jit::Label* throwLabel);
+extern FuncOffsets
+GenerateImportFunction(jit::MacroAssembler& masm, const FuncImport& fi, SigIdDesc sigId);
 
 extern ProfilingOffsets
-GenerateJitExit(jit::MacroAssembler& masm, const FuncImport& fi, jit::Label* throwLabel);
+GenerateImportInterpExit(jit::MacroAssembler& masm, const FuncImport& fi, uint32_t funcImportIndex,
+                         jit::Label* throwLabel);
+
+extern ProfilingOffsets
+GenerateImportJitExit(jit::MacroAssembler& masm, const FuncImport& fi, jit::Label* throwLabel);
 
 extern ProfilingOffsets
 GenerateTrapExit(jit::MacroAssembler& masm, Trap trap, jit::Label* throwLabel);
 
 extern Offsets
 GenerateOutOfBoundsExit(jit::MacroAssembler& masm, jit::Label* throwLabel);
 
 extern Offsets
--- a/js/src/wasm/WasmTextToBinary.cpp
+++ b/js/src/wasm/WasmTextToBinary.cpp
@@ -1583,17 +1583,17 @@ ParseBlock(WasmParseContext& c, Expr exp
     ExprType type;
     if (!ParseBlockSignature(c, &type))
         return nullptr;
 
     if (!ParseExprList(c, &exprs, inParens))
         return nullptr;
 
     if (!inParens) {
-        if (!c.ts.getIf(WasmToken::End))
+        if (!c.ts.match(WasmToken::End, c.error))
             return nullptr;
     }
 
     AstBlock* result = new(c.lifo) AstBlock(expr, type, name, Move(exprs));
 
     if (expr == Expr::Loop && !otherName.empty()) {
         if (!exprs.append(result))
             return nullptr;
@@ -1678,17 +1678,21 @@ ParseCallIndirect(WasmParseContext& c, b
     if (!c.ts.matchRef(&sig, c.error))
         return nullptr;
 
     AstExprVector args(c.lifo);
     AstExpr* index;
     if (inParens) {
         if (!ParseArgs(c, &args))
             return nullptr;
-        index = args.popCopy();
+
+        if (args.empty())
+            index = new(c.lifo) AstPop();
+        else
+            index = args.popCopy();
     } else {
         index = new(c.lifo) AstPop();
     }
 
     return new(c.lifo) AstCallIndirect(sig, ExprType::Void, Move(args), index);
 }
 
 static uint_fast8_t