Bug 1313180 - Baldr: allow JS imports in Tables (r=bbouvier)
authorLuke Wagner <luke@mozilla.com>
Fri, 04 Nov 2016 17:05:57 -0500
changeset 351280 ad7523af20b9386d4770e7d124ba42ba8b061a70
parent 351279 a938e94dc04db48f56a39f4a798977d015a5050e
child 351281 0394048027e82fc887c9e8008b6bd468d4c4a49e
push id6795
push userjlund@mozilla.com
push dateMon, 23 Jan 2017 14:19:46 +0000
treeherdermozilla-esr52@76101b503191 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbbouvier
bugs1313180
milestone52.0a1
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