Bug 1271010 - Baldr: add real heterogeneous function table (r=bbouvier)
authorLuke Wagner <luke@mozilla.com>
Mon, 30 May 2016 10:09:53 -0500
changeset 340603 d21a912dfd85657ed906dcc7ec46b4d0a1eacca9
parent 340602 d9a0b729a7be40ae0f8d1f8ce4bd15415fba0914
child 340604 02c6457de3d88f72f3436987814ba83d4c08710a
push id1183
push userraliiev@mozilla.com
push dateMon, 05 Sep 2016 20:01:49 +0000
treeherdermozilla-release@3148731bed45 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbbouvier
bugs1271010
milestone49.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 1271010 - Baldr: add real heterogeneous function table (r=bbouvier) MozReview-Commit-ID: BuZZzes6ZeL
js/src/asmjs/AsmJS.cpp
js/src/asmjs/Wasm.cpp
js/src/asmjs/WasmFrameIterator.cpp
js/src/asmjs/WasmFrameIterator.h
js/src/asmjs/WasmGenerator.cpp
js/src/asmjs/WasmGenerator.h
js/src/asmjs/WasmIonCompile.cpp
js/src/asmjs/WasmModule.cpp
js/src/asmjs/WasmModule.h
js/src/asmjs/WasmTypes.h
js/src/jit-test/tests/wasm/basic.js
js/src/jit-test/tests/wasm/profiling.js
js/src/jit/CodeGenerator.cpp
js/src/jit/CodeGenerator.h
js/src/jit/Lowering.cpp
js/src/jit/MIR.cpp
js/src/jit/MIR.h
js/src/jit/arm/Assembler-arm.h
js/src/jit/arm/CodeGenerator-arm.cpp
js/src/jit/arm64/Assembler-arm64.h
js/src/jit/arm64/MacroAssembler-arm64.h
js/src/jit/mips-shared/CodeGenerator-mips-shared.cpp
js/src/jit/mips32/Assembler-mips32.h
js/src/jit/mips64/Assembler-mips64.h
js/src/jit/none/MacroAssembler-none.h
js/src/jit/shared/CodeGenerator-shared.cpp
js/src/jit/x64/Assembler-x64.h
js/src/jit/x64/CodeGenerator-x64.cpp
js/src/jit/x86/Assembler-x86.h
js/src/jit/x86/CodeGenerator-x86.cpp
--- a/js/src/asmjs/AsmJS.cpp
+++ b/js/src/asmjs/AsmJS.cpp
@@ -1851,17 +1851,17 @@ class MOZ_STACK_CLASS ModuleValidator
         if (!dummyFunction_)
             return false;
 
         UniqueModuleGeneratorData genData = MakeUnique<ModuleGeneratorData>(cx_, ModuleKind::AsmJS);
         if (!genData ||
             !genData->sigs.resize(MaxSigs) ||
             !genData->funcSigs.resize(MaxFuncs) ||
             !genData->imports.resize(MaxImports) ||
-            !genData->sigToTable.resize(MaxSigs))
+            !genData->asmJSSigToTable.resize(MaxTables))
         {
             return false;
         }
 
         CacheableChars filename;
         if (parser_.ss->filename()) {
             filename = DuplicateString(parser_.ss->filename());
             if (!filename)
--- a/js/src/asmjs/Wasm.cpp
+++ b/js/src/asmjs/Wasm.cpp
@@ -514,18 +514,16 @@ DecodeTypeSection(JSContext* cx, Decoder
     if (!d.readVarU32(&numSigs))
         return Fail(cx, d, "expected number of signatures");
 
     if (numSigs > MaxSigs)
         return Fail(cx, d, "too many signatures");
 
     if (!init->sigs.resize(numSigs))
         return false;
-    if (!init->sigToTable.resize(numSigs))
-        return false;
 
     for (uint32_t sigIndex = 0; sigIndex < numSigs; sigIndex++) {
         uint32_t form;
         if (!d.readVarU32(&form) || form != uint32_t(TypeConstructor::Function))
             return Fail(cx, d, "expected function form");
 
         uint32_t numArgs;
         if (!d.readVarU32(&numArgs))
@@ -624,63 +622,39 @@ static bool
 DecodeTableSection(JSContext* cx, Decoder& d, ModuleGeneratorData* init)
 {
     uint32_t sectionStart, sectionSize;
     if (!d.startSection(TableSectionId, &sectionStart, &sectionSize))
         return Fail(cx, d, "failed to start section");
     if (sectionStart == Decoder::NotStarted)
         return true;
 
-    if (!d.readVarU32(&init->numTableElems))
+    if (!d.readVarU32(&init->wasmTable.numElems))
         return Fail(cx, d, "expected number of table elems");
 
-    if (init->numTableElems > MaxTableElems)
+    if (init->wasmTable.numElems > MaxTableElems)
         return Fail(cx, d, "too many table elements");
 
-    Uint32Vector elems;
-    if (!elems.resize(init->numTableElems))
+    if (!init->wasmTable.elemFuncIndices.resize(init->wasmTable.numElems))
         return false;
 
-    for (uint32_t i = 0; i < init->numTableElems; i++) {
+    for (uint32_t i = 0; i < init->wasmTable.numElems; i++) {
         uint32_t funcIndex;
         if (!d.readVarU32(&funcIndex))
             return Fail(cx, d, "expected table element");
 
         if (funcIndex >= init->funcSigs.length())
             return Fail(cx, d, "table element out of range");
 
-        elems[i] = funcIndex;
+        init->wasmTable.elemFuncIndices[i] = funcIndex;
     }
 
     if (!d.finishSection(sectionStart, sectionSize))
         return Fail(cx, d, "table section byte size mismatch");
 
-    // Convert the single (heterogeneous) indirect function table into an
-    // internal set of asm.js-like homogeneous tables indexed by signature.
-    // Every element in the heterogeneous table is present in only one
-    // homogeneous table (as determined by its signature). An element's index in
-    // the heterogeneous table is the same as its index in its homogeneous table
-    // and all other homogeneous tables are given an entry that will fault if
-    // called for at that element's index.
-
-    for (uint32_t elemIndex = 0; elemIndex < elems.length(); elemIndex++) {
-        uint32_t funcIndex = elems[elemIndex];
-        TableModuleGeneratorData& table = init->sigToTable[init->funcSigIndex(funcIndex)];
-        if (table.numElems == 0) {
-            table.numElems = elems.length();
-            if (!table.elemFuncIndices.appendN(ModuleGenerator::BadIndirectCall, elems.length()))
-                return false;
-        }
-    }
-
-    for (uint32_t elemIndex = 0; elemIndex < elems.length(); elemIndex++) {
-        uint32_t funcIndex = elems[elemIndex];
-        init->sigToTable[init->funcSigIndex(funcIndex)].elemFuncIndices[elemIndex] = funcIndex;
-    }
-
     return true;
 }
 
 static bool
 CheckTypeForJS(JSContext* cx, Decoder& d, const Sig& sig)
 {
     bool allowI64 = IsI64Implemented() && JitOptions.wasmTestMode;
 
--- a/js/src/asmjs/WasmFrameIterator.cpp
+++ b/js/src/asmjs/WasmFrameIterator.cpp
@@ -279,32 +279,40 @@ GenerateProfilingEpilogue(MacroAssembler
 // In profiling mode, we need to maintain fp so that we can unwind the stack at
 // any pc. In non-profiling mode, the only way to observe WasmActivation::fp is
 // to call out to C++ so, as an optimization, we don't update fp. To avoid
 // recompilation when the profiling mode is toggled, we generate both prologues
 // a priori and switch between prologues when the profiling mode is toggled.
 // Specifically, Module::setProfilingEnabled patches all callsites to
 // either call the profiling or non-profiling entry point.
 void
-wasm::GenerateFunctionPrologue(MacroAssembler& masm, unsigned framePushed, FuncOffsets* offsets)
+wasm::GenerateFunctionPrologue(MacroAssembler& masm, unsigned framePushed, uint32_t sigIndex,
+                               FuncOffsets* offsets)
 {
 #if defined(JS_CODEGEN_ARM)
     // Flush pending pools so they do not get dumped between the 'begin' and
     // 'entry' offsets since the difference must be less than UINT8_MAX.
     masm.flushBuffer();
 #endif
 
     masm.haltingAlign(CodeAlignment);
 
     GenerateProfilingPrologue(masm, framePushed, ExitReason::None, offsets);
     Label body;
     masm.jump(&body);
 
+    // Generate table entry thunk:
+    masm.haltingAlign(CodeAlignment);
+    offsets->tableEntry = masm.currentOffset();
+    masm.branch32(Assembler::Condition::NotEqual, WasmTableCallSigReg, Imm32(sigIndex),
+                  JumpTarget::BadIndirectCall);
+    offsets->tableProfilingJump = masm.nopPatchableToNearJump().offset();
+
     // Generate normal prologue:
-    masm.haltingAlign(CodeAlignment);
+    masm.nopAlign(CodeAlignment);
     offsets->nonProfilingEntry = masm.currentOffset();
     PushRetAddr(masm);
     masm.subFromStackPtr(Imm32(framePushed + AsmJSFrameBytesAfterReturnAddress));
 
     // Prologue join point, body begin:
     masm.bind(&body);
     masm.setFramePushed(framePushed);
 }
@@ -472,16 +480,27 @@ ProfilingFrameIterator::initFromFP(const
     if (exitReason_ == ExitReason::None)
         exitReason_ = ExitReason::Native;
 
     MOZ_ASSERT(!done());
 }
 
 typedef JS::ProfilingFrameIterator::RegisterState RegisterState;
 
+static bool
+InThunk(const CodeRange& codeRange, uint32_t offsetInModule)
+{
+    if (codeRange.kind() == CodeRange::CallThunk)
+        return true;
+
+    return codeRange.isFunction() &&
+           offsetInModule >= codeRange.funcTableEntry() &&
+           offsetInModule < codeRange.funcNonProfilingEntry();
+}
+
 ProfilingFrameIterator::ProfilingFrameIterator(const WasmActivation& activation,
                                                const RegisterState& state)
   : module_(&activation.module()),
     codeRange_(nullptr),
     callerFP_(nullptr),
     callerPC_(nullptr),
     stackAddress_(nullptr),
     exitReason_(ExitReason::None)
@@ -521,32 +540,32 @@ ProfilingFrameIterator::ProfilingFrameIt
         // the code in the prologue and epilogue to do the Right Thing.
         MOZ_ASSERT(module_->containsCodePC(state.pc));
         uint32_t offsetInModule = (uint8_t*)state.pc - module_->code();
         MOZ_ASSERT(offsetInModule >= codeRange->begin());
         MOZ_ASSERT(offsetInModule < codeRange->end());
         uint32_t offsetInCodeRange = offsetInModule - codeRange->begin();
         void** sp = (void**)state.sp;
 #if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
-        if (offsetInCodeRange < PushedRetAddr || codeRange->kind() == CodeRange::CallThunk) {
+        if (offsetInCodeRange < PushedRetAddr || InThunk(*codeRange, offsetInModule)) {
             // First instruction of the ARM/MIPS function; the return address is
             // still in lr and fp still holds the caller's fp.
             callerPC_ = state.lr;
             callerFP_ = fp;
             AssertMatchesCallSite(*module_, callerPC_, callerFP_, sp - 2);
         } else if (offsetInModule == codeRange->profilingReturn() - PostStorePrePopFP) {
             // Second-to-last instruction of the ARM/MIPS function; fp points to
             // the caller's fp; have not yet popped AsmJSFrame.
             callerPC_ = ReturnAddressFromFP(sp);
             callerFP_ = CallerFPFromFP(sp);
             AssertMatchesCallSite(*module_, callerPC_, callerFP_, sp);
         } else
 #endif
         if (offsetInCodeRange < PushedFP || offsetInModule == codeRange->profilingReturn() ||
-            codeRange->kind() == CodeRange::CallThunk)
+            InThunk(*codeRange, offsetInModule))
         {
             // The return address has been pushed on the stack but not fp; fp
             // still points to the caller's fp.
             callerPC_ = *sp;
             callerFP_ = fp;
             AssertMatchesCallSite(*module_, callerPC_, callerFP_, sp - 1);
         } else if (offsetInCodeRange < StoredFP) {
             // The full AsmJSFrame has been pushed; fp still points to the
@@ -675,20 +694,18 @@ ProfilingFrameIterator::label() const
     }
 
     MOZ_CRASH("bad code range kind");
 }
 
 /*****************************************************************************/
 // Runtime patching to enable/disable profiling
 
-// Patch all internal (asm.js->asm.js) callsites to call the profiling
-// prologues:
 void
-wasm::EnableProfilingPrologue(const Module& module, const CallSite& callSite, bool enabled)
+wasm::ToggleProfiling(const Module& module, const CallSite& callSite, bool enabled)
 {
     if (callSite.kind() != CallSite::Relative)
         return;
 
     uint8_t* callerRetAddr = module.code() + callSite.returnAddressOffset();
 
 #if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
     void* callee = X86Encoding::GetRel32Target(callerRetAddr);
@@ -738,31 +755,34 @@ wasm::EnableProfilingPrologue(const Modu
 #elif defined(JS_CODEGEN_NONE)
     MOZ_CRASH();
 #else
 # error "Missing architecture"
 #endif
 }
 
 void
-wasm::EnableProfilingThunk(const Module& module, const CallThunk& callThunk, bool enabled)
+wasm::ToggleProfiling(const Module& module, const CallThunk& callThunk, bool enabled)
 {
     const CodeRange& cr = module.codeRanges()[callThunk.u.codeRangeIndex];
     uint32_t calleeOffset = enabled ? cr.funcProfilingEntry() : cr.funcNonProfilingEntry();
     MacroAssembler::repatchThunk(module.code(), callThunk.offset, calleeOffset);
 }
 
-// Replace all the nops in all the epilogues of asm.js functions with jumps
-// to the profiling epilogues.
 void
-wasm::EnableProfilingEpilogue(const Module& module, const CodeRange& codeRange, bool enabled)
+wasm::ToggleProfiling(const Module& module, const CodeRange& codeRange, bool enabled)
 {
     if (!codeRange.isFunction())
         return;
 
-    uint8_t* profilingJump = module.code() + codeRange.funcProfilingJump();
-    uint8_t* profilingEpilogue = module.code() + codeRange.funcProfilingEpilogue();
+    uint8_t* profilingEntry     = module.code() + codeRange.funcProfilingEntry();
+    uint8_t* tableProfilingJump = module.code() + codeRange.funcTableProfilingJump();
+    uint8_t* profilingJump      = module.code() + codeRange.funcProfilingJump();
+    uint8_t* profilingEpilogue  = module.code() + codeRange.funcProfilingEpilogue();
 
-    if (enabled)
+    if (enabled) {
+        MacroAssembler::patchNopToNearJump(tableProfilingJump, profilingEntry);
         MacroAssembler::patchNopToNearJump(profilingJump, profilingEpilogue);
-    else
+    } else {
+        MacroAssembler::patchNearJumpToNop(tableProfilingJump);
         MacroAssembler::patchNearJumpToNop(profilingJump);
+    }
 }
--- a/js/src/asmjs/WasmFrameIterator.h
+++ b/js/src/asmjs/WasmFrameIterator.h
@@ -93,34 +93,36 @@ class ProfilingFrameIterator
     void operator++();
     bool done() const { return !codeRange_; }
 
     void* stackAddress() const { MOZ_ASSERT(!done()); return stackAddress_; }
     const char* label() const;
 };
 
 // Prologue/epilogue code generation
+
 void
 GenerateExitPrologue(jit::MacroAssembler& masm, unsigned framePushed, ExitReason reason,
                      ProfilingOffsets* offsets);
 void
 GenerateExitEpilogue(jit::MacroAssembler& masm, unsigned framePushed, ExitReason reason,
                      ProfilingOffsets* offsets);
 void
-GenerateFunctionPrologue(jit::MacroAssembler& masm, unsigned framePushed, FuncOffsets* offsets);
+GenerateFunctionPrologue(jit::MacroAssembler& masm, unsigned framePushed, uint32_t sigIndex,
+                         FuncOffsets* offsets);
 void
 GenerateFunctionEpilogue(jit::MacroAssembler& masm, unsigned framePushed, FuncOffsets* offsets);
 
 // Runtime patching to enable/disable profiling
 
 void
-EnableProfilingPrologue(const Module& module, const CallSite& callSite, bool enabled);
+ToggleProfiling(const Module& module, const CallSite& callSite, bool enabled);
 
 void
-EnableProfilingThunk(const Module& module, const CallThunk& callThunk, bool enabled);
+ToggleProfiling(const Module& module, const CallThunk& callThunk, bool enabled);
 
 void
-EnableProfilingEpilogue(const Module& module, const CodeRange& codeRange, bool enabled);
+ToggleProfiling(const Module& module, const CodeRange& codeRange, bool enabled);
 
 } // namespace wasm
 } // namespace js
 
 #endif // wasm_frame_iterator_h
--- a/js/src/asmjs/WasmGenerator.cpp
+++ b/js/src/asmjs/WasmGenerator.cpp
@@ -32,18 +32,16 @@ using namespace js::wasm;
 using mozilla::MakeEnumeratedRange;
 
 // ****************************************************************************
 // ModuleGenerator
 
 static const unsigned GENERATOR_LIFO_DEFAULT_CHUNK_SIZE = 4 * 1024;
 static const unsigned COMPILATION_LIFO_DEFAULT_CHUNK_SIZE = 64 * 1024;
 
-const unsigned ModuleGenerator::BadIndirectCall;
-
 ModuleGenerator::ModuleGenerator(ExclusiveContext* cx)
   : cx_(cx),
     jcx_(CompileRuntime::get(cx->compartment()->runtimeFromAnyThread())),
     slowFuncs_(cx),
     numSigs_(0),
     lifo_(GENERATOR_LIFO_DEFAULT_CHUNK_SIZE),
     alloc_(&lifo_),
     masm_(MacroAssembler::AsmJSToken(), alloc_),
@@ -147,25 +145,21 @@ ModuleGenerator::init(UniqueModuleGenera
             MOZ_ASSERT(!import.globalDataOffset);
             import.globalDataOffset = module_->globalBytes;
             module_->globalBytes += Module::SizeOfImportExit;
             if (!addImport(*import.sig, import.globalDataOffset))
                 return false;
         }
 
         MOZ_ASSERT(module_->globalBytes % sizeof(void*) == 0);
-
-        for (TableModuleGeneratorData& table : shared_->sigToTable) {
-            MOZ_ASSERT(table.numElems == table.elemFuncIndices.length());
-            if (!table.numElems)
-                continue;
-            MOZ_ASSERT(!table.globalDataOffset);
-            table.globalDataOffset = module_->globalBytes;
-            module_->globalBytes += table.numElems * sizeof(void*);
-        }
+        MOZ_ASSERT(shared_->asmJSSigToTable.empty());
+        MOZ_ASSERT(shared_->wasmTable.numElems == shared_->wasmTable.elemFuncIndices.length());
+        MOZ_ASSERT(!shared_->wasmTable.globalDataOffset);
+        shared_->wasmTable.globalDataOffset = module_->globalBytes;
+        module_->globalBytes += shared_->wasmTable.numElems * sizeof(void*);
     }
 
     return true;
 }
 
 bool
 ModuleGenerator::finishOutstandingTask()
 {
@@ -197,21 +191,23 @@ static const uint32_t BadCodeRange = UIN
 
 bool
 ModuleGenerator::funcIsDefined(uint32_t funcIndex) const
 {
     return funcIndex < funcIndexToCodeRange_.length() &&
            funcIndexToCodeRange_[funcIndex] != BadCodeRange;
 }
 
-uint32_t
-ModuleGenerator::funcEntry(uint32_t funcIndex) const
+const CodeRange&
+ModuleGenerator::funcCodeRange(uint32_t funcIndex) const
 {
     MOZ_ASSERT(funcIsDefined(funcIndex));
-    return module_->codeRanges[funcIndexToCodeRange_[funcIndex]].funcNonProfilingEntry();
+    const CodeRange& cr = module_->codeRanges[funcIndexToCodeRange_[funcIndex]];
+    MOZ_ASSERT(cr.isFunction());
+    return cr;
 }
 
 static uint32_t
 JumpRange()
 {
     return Min(JitOptions.jumpThreshold, JumpImmediateRange);
 }
 
@@ -233,17 +229,17 @@ ModuleGenerator::convertOutOfRangeBranch
         const CallSiteAndTarget& cs = masm_.callSites()[lastPatchedCallsite_];
         if (!cs.isInternal())
             continue;
 
         uint32_t callerOffset = cs.returnAddressOffset();
         MOZ_RELEASE_ASSERT(callerOffset < INT32_MAX);
 
         if (funcIsDefined(cs.targetIndex())) {
-            uint32_t calleeOffset = funcEntry(cs.targetIndex());
+            uint32_t calleeOffset = funcCodeRange(cs.targetIndex()).funcNonProfilingEntry();
             MOZ_RELEASE_ASSERT(calleeOffset < INT32_MAX);
 
             if (uint32_t(abs(int32_t(calleeOffset) - int32_t(callerOffset))) < JumpRange()) {
                 masm_.patchCall(callerOffset, calleeOffset);
                 continue;
             }
         }
 
@@ -428,51 +424,28 @@ ModuleGenerator::finishCodegen(StaticLin
     if (!module_->codeRanges.emplaceBack(CodeRange::Inline, interruptExit))
         return false;
 
     // Fill in StaticLinkData with the offsets of these stubs.
 
     link->pod.outOfBoundsOffset = jumpTargets[JumpTarget::OutOfBounds].begin;
     link->pod.interruptOffset = interruptExit.begin;
 
-    Offsets badIndirectCallExit = jumpTargets[JumpTarget::BadIndirectCall];
-
-    for (uint32_t sigIndex = 0; sigIndex < numSigs_; sigIndex++) {
-        const TableModuleGeneratorData& table = shared_->sigToTable[sigIndex];
-        if (table.elemFuncIndices.empty())
-            continue;
-
-        Uint32Vector elemOffsets;
-        if (!elemOffsets.resize(table.elemFuncIndices.length()))
-            return false;
-
-        for (size_t i = 0; i < table.elemFuncIndices.length(); i++) {
-            uint32_t funcIndex = table.elemFuncIndices[i];
-            if (funcIndex == BadIndirectCall)
-                elemOffsets[i] = badIndirectCallExit.begin;
-            else
-                elemOffsets[i] = funcEntry(funcIndex);
-        }
-
-        if (!link->funcPtrTables.emplaceBack(table.globalDataOffset, Move(elemOffsets)))
-            return false;
-    }
-
     // Only call convertOutOfRangeBranchesToThunks after all other codegen that may
     // emit new jumps to JumpTargets has finished.
 
     if (!convertOutOfRangeBranchesToThunks())
         return false;
 
     // Now that all thunks have been generated, patch all the thunks.
 
     for (CallThunk& callThunk : module_->callThunks) {
         uint32_t funcIndex = callThunk.u.funcIndex;
         callThunk.u.codeRangeIndex = funcIndexToCodeRange_[funcIndex];
-        masm_.patchThunk(callThunk.offset, funcEntry(funcIndex));
+        masm_.patchThunk(callThunk.offset, funcCodeRange(funcIndex).funcNonProfilingEntry());
     }
 
     for (JumpTarget target : MakeEnumeratedRange(JumpTarget::Limit)) {
         for (uint32_t thunkOffset : jumpThunks_[target])
             masm_.patchThunk(thunkOffset, jumpTargets[target].begin);
     }
 
     // Code-generation is complete!
@@ -525,16 +498,45 @@ ModuleGenerator::finishStaticLinkData(ui
     // not need patching after deserialization.
     uint8_t* globalData = code + codeBytes;
     for (size_t i = 0; i < masm_.numAsmJSGlobalAccesses(); i++) {
         AsmJSGlobalAccess a = masm_.asmJSGlobalAccess(i);
         masm_.patchAsmJSGlobalAccess(a.patchAt, code, globalData, a.globalDataOffset);
     }
 #endif
 
+    // Function pointer table elements
+
+    if (shared_->wasmTable.numElems > 0) {
+        const TableModuleGeneratorData& table = shared_->wasmTable;
+
+        Uint32Vector elemOffsets;
+        for (size_t i = 0; i < table.elemFuncIndices.length(); i++) {
+            if (!elemOffsets.append(funcCodeRange(table.elemFuncIndices[i]).funcTableEntry()))
+                return false;
+        }
+
+        if (!link->funcPtrTables.emplaceBack(table.globalDataOffset, Move(elemOffsets)))
+            return false;
+    }
+
+    for (const TableModuleGeneratorData& table : shared_->asmJSSigToTable) {
+        if (table.elemFuncIndices.empty())
+            continue;
+
+        Uint32Vector elemOffsets;
+        for (size_t i = 0; i < table.elemFuncIndices.length(); i++) {
+            if (!elemOffsets.append(funcCodeRange(table.elemFuncIndices[i]).funcNonProfilingEntry()))
+                return false;
+        }
+
+        if (!link->funcPtrTables.emplaceBack(table.globalDataOffset, Move(elemOffsets)))
+            return false;
+    }
+
     return true;
 }
 
 bool
 ModuleGenerator::addImport(const Sig& sig, uint32_t globalDataOffset)
 {
     Sig copy;
     if (!copy.clone(sig))
@@ -858,30 +860,30 @@ ModuleGenerator::initSigTableLength(uint
     MOZ_ASSERT(isAsmJS());
     MOZ_ASSERT(numElems != 0);
     MOZ_ASSERT(numElems <= MaxTableElems);
 
     uint32_t globalDataOffset;
     if (!allocateGlobalBytes(numElems * sizeof(void*), sizeof(void*), &globalDataOffset))
         return false;
 
-    TableModuleGeneratorData& table = shared_->sigToTable[sigIndex];
+    TableModuleGeneratorData& table = shared_->asmJSSigToTable[sigIndex];
     MOZ_ASSERT(table.numElems == 0);
     table.numElems = numElems;
     table.globalDataOffset = globalDataOffset;
     return true;
 }
 
 void
 ModuleGenerator::initSigTableElems(uint32_t sigIndex, Uint32Vector&& elemFuncIndices)
 {
     MOZ_ASSERT(isAsmJS());
     MOZ_ASSERT(!elemFuncIndices.empty());
 
-    TableModuleGeneratorData& table = shared_->sigToTable[sigIndex];
+    TableModuleGeneratorData& table = shared_->asmJSSigToTable[sigIndex];
     MOZ_ASSERT(table.numElems == elemFuncIndices.length());
 
     MOZ_ASSERT(table.elemFuncIndices.empty());
     table.elemFuncIndices = Move(elemFuncIndices);
 }
 
 bool
 ModuleGenerator::finish(CacheableCharsVector&& prettyFuncNames,
--- a/js/src/asmjs/WasmGenerator.h
+++ b/js/src/asmjs/WasmGenerator.h
@@ -79,31 +79,32 @@ struct ImportModuleGeneratorData
 };
 
 typedef Vector<ImportModuleGeneratorData, 0, SystemAllocPolicy> ImportModuleGeneratorDataVector;
 
 struct ModuleGeneratorData
 {
     CompileArgs                     args;
     ModuleKind                      kind;
-    uint32_t                        numTableElems;
     mozilla::Atomic<uint32_t>       minHeapLength;
 
     DeclaredSigVector               sigs;
-    TableModuleGeneratorDataVector  sigToTable;
     DeclaredSigPtrVector            funcSigs;
     ImportModuleGeneratorDataVector imports;
     GlobalDescVector                globals;
 
+    TableModuleGeneratorData        wasmTable;
+    TableModuleGeneratorDataVector  asmJSSigToTable;
+
     uint32_t funcSigIndex(uint32_t funcIndex) const {
         return funcSigs[funcIndex] - sigs.begin();
     }
 
     explicit ModuleGeneratorData(ExclusiveContext* cx, ModuleKind kind = ModuleKind::Wasm)
-      : args(cx), kind(kind), numTableElems(0), minHeapLength(0)
+      : args(cx), kind(kind), minHeapLength(0)
     {}
 };
 
 typedef UniquePtr<ModuleGeneratorData> UniqueModuleGeneratorData;
 
 // A ModuleGenerator encapsulates the creation of a wasm module. During the
 // lifetime of a ModuleGenerator, a sequence of FunctionGenerators are created
 // and destroyed to compile the individual function bodies. After generating all
@@ -142,24 +143,23 @@ class MOZ_STACK_CLASS ModuleGenerator
 
     // Assertions
     DebugOnly<FunctionGenerator*>   activeFunc_;
     DebugOnly<bool>                 startedFuncDefs_;
     DebugOnly<bool>                 finishedFuncDefs_;
 
     MOZ_MUST_USE bool finishOutstandingTask();
     bool funcIsDefined(uint32_t funcIndex) const;
-    uint32_t funcEntry(uint32_t funcIndex) const;
+    const CodeRange& funcCodeRange(uint32_t funcIndex) const;
     MOZ_MUST_USE bool convertOutOfRangeBranchesToThunks();
     MOZ_MUST_USE bool finishTask(IonCompileTask* task);
     MOZ_MUST_USE bool finishCodegen(StaticLinkData* link);
     MOZ_MUST_USE bool finishStaticLinkData(uint8_t* code, uint32_t codeBytes, StaticLinkData* link);
     MOZ_MUST_USE bool addImport(const Sig& sig, uint32_t globalDataOffset);
-    MOZ_MUST_USE bool allocateGlobalBytes(uint32_t bytes, uint32_t align,
-                                          uint32_t* globalDataOffset);
+    MOZ_MUST_USE bool allocateGlobalBytes(uint32_t bytes, uint32_t align, uint32_t* globalDataOff);
 
   public:
     explicit ModuleGenerator(ExclusiveContext* cx);
     ~ModuleGenerator();
 
     MOZ_MUST_USE bool init(UniqueModuleGeneratorData shared, UniqueChars filename);
 
     bool isAsmJS() const { return module_->kind == ModuleKind::AsmJS; }
@@ -194,19 +194,16 @@ class MOZ_STACK_CLASS ModuleGenerator
 
     // Function definitions:
     MOZ_MUST_USE bool startFuncDefs();
     MOZ_MUST_USE bool startFuncDef(uint32_t lineOrBytecode, FunctionGenerator* fg);
     MOZ_MUST_USE bool finishFuncDef(uint32_t funcIndex, unsigned generateTime,
                                     FunctionGenerator* fg);
     MOZ_MUST_USE bool finishFuncDefs();
 
-    // Function-pointer tables:
-    static const uint32_t BadIndirectCall = UINT32_MAX;
-
     // asm.js lazy initialization:
     void initSig(uint32_t sigIndex, Sig&& sig);
     void initFuncSig(uint32_t funcIndex, uint32_t sigIndex);
     MOZ_MUST_USE bool initImport(uint32_t importIndex, uint32_t sigIndex);
     MOZ_MUST_USE bool initSigTableLength(uint32_t sigIndex, uint32_t numElems);
     void initSigTableElems(uint32_t sigIndex, Uint32Vector&& elemFuncIndices);
     void bumpMinHeapLength(uint32_t newMinHeapLength);
 
--- a/js/src/asmjs/WasmIonCompile.cpp
+++ b/js/src/asmjs/WasmIonCompile.cpp
@@ -871,48 +871,41 @@ class FunctionCompiler
     }
 
   public:
     bool internalCall(const Sig& sig, uint32_t funcIndex, const CallArgs& args, MDefinition** def)
     {
         return callPrivate(MAsmJSCall::Callee(funcIndex), args, sig.ret(), def);
     }
 
-    bool funcPtrCall(const Sig& sig, uint32_t length, uint32_t globalDataOffset, MDefinition* index,
-                     const CallArgs& args, MDefinition** def)
+    bool funcPtrCall(uint32_t sigIndex, uint32_t length, uint32_t globalDataOffset,
+                     MDefinition* index, const CallArgs& args, MDefinition** def)
     {
         if (inDeadCode()) {
             *def = nullptr;
             return true;
         }
 
-        MAsmJSLoadFuncPtr* ptrFun;
+        MAsmJSCall::Callee callee;
         if (mg().kind == ModuleKind::AsmJS) {
             MOZ_ASSERT(IsPowerOfTwo(length));
             MConstant* mask = MConstant::New(alloc(), Int32Value(length - 1));
             curBlock_->add(mask);
             MBitAnd* maskedIndex = MBitAnd::NewAsmJS(alloc(), index, mask, MIRType::Int32);
             curBlock_->add(maskedIndex);
-            ptrFun = MAsmJSLoadFuncPtr::New(alloc(), maskedIndex, globalDataOffset);
+            MInstruction* ptrFun = MAsmJSLoadFuncPtr::New(alloc(), maskedIndex, globalDataOffset);
             curBlock_->add(ptrFun);
+            callee = MAsmJSCall::Callee(ptrFun);
         } else {
-            // For wasm code, as a space optimization, the ModuleGenerator does not allocate a
-            // table for signatures which do not contain any indirectly-callable functions.
-            // However, these signatures may still be called (it is not a validation error)
-            // so we instead have a flag alwaysThrow which throws an exception instead of loading
-            // the function pointer from the (non-existant) array.
-            MOZ_ASSERT(!length || length == mg_.numTableElems);
-            bool alwaysThrow = !length;
-
-            ptrFun = MAsmJSLoadFuncPtr::New(alloc(), index, mg_.numTableElems, alwaysThrow,
-                                            globalDataOffset);
+            MInstruction* ptrFun = MAsmJSLoadFuncPtr::New(alloc(), index, length, globalDataOffset);
             curBlock_->add(ptrFun);
+            callee = MAsmJSCall::Callee(ptrFun, sigIndex);
         }
 
-        return callPrivate(MAsmJSCall::Callee(ptrFun), args, sig.ret(), def);
+        return callPrivate(callee, args, mg_.sigs[sigIndex].ret(), def);
     }
 
     bool ffiCall(unsigned globalDataOffset, const CallArgs& args, ExprType ret, MDefinition** def)
     {
         if (inDeadCode()) {
             *def = nullptr;
             return true;
         }
@@ -1727,19 +1720,22 @@ EmitCallIndirect(FunctionCompiler& f, ui
 
     MDefinition* callee;
     if (!f.iter().readCallIndirectCallee(&callee))
         return false;
 
     if (!f.iter().readCallReturn(sig.ret()))
         return false;
 
+    const TableModuleGeneratorData& table = f.mg().kind == ModuleKind::AsmJS
+                                            ? f.mg().asmJSSigToTable[sigIndex]
+                                            : f.mg().wasmTable;
+
     MDefinition* def;
-    const TableModuleGeneratorData& table = f.mg().sigToTable[sigIndex];
-    if (!f.funcPtrCall(sig, table.numElems, table.globalDataOffset, callee, args, &def))
+    if (!f.funcPtrCall(sigIndex, table.numElems, table.globalDataOffset, callee, args, &def))
         return false;
 
     if (IsVoid(sig.ret()))
         return true;
 
     f.iter().setResult(def);
     return true;
 }
@@ -3451,16 +3447,18 @@ wasm::IonCompileFunction(IonCompileTask*
 
         if (!OptimizeMIR(&mir))
             return false;
 
         LIRGraph* lir = GenerateLIR(&mir);
         if (!lir)
             return false;
 
+        uint32_t sigIndex = task->mg().funcSigIndex(func.index());
+
         CodeGenerator codegen(&mir, lir, &results.masm());
-        if (!codegen.generateAsmJS(&results.offsets()))
+        if (!codegen.generateWasm(sigIndex, &results.offsets()))
             return false;
     }
 
     results.setCompileTime((PRMJ_Now() - before) / PRMJ_USEC_PER_MSEC);
     return true;
 }
--- a/js/src/asmjs/WasmModule.cpp
+++ b/js/src/asmjs/WasmModule.cpp
@@ -359,54 +359,62 @@ Import::sizeOfExcludingThis(MallocSizeOf
 }
 
 CodeRange::CodeRange(Kind kind, Offsets offsets)
   : begin_(offsets.begin),
     profilingReturn_(0),
     end_(offsets.end),
     funcIndex_(0),
     funcLineOrBytecode_(0),
+    funcBeginToTableEntry_(0),
+    funcBeginToTableProfilingJump_(0),
     funcBeginToNonProfilingEntry_(0),
     funcProfilingJumpToProfilingReturn_(0),
     funcProfilingEpilogueToProfilingReturn_(0),
     kind_(kind)
 {
     MOZ_ASSERT(begin_ <= end_);
     MOZ_ASSERT(kind_ == Entry || kind_ == Inline || kind_ == CallThunk);
 }
 
 CodeRange::CodeRange(Kind kind, ProfilingOffsets offsets)
   : begin_(offsets.begin),
     profilingReturn_(offsets.profilingReturn),
     end_(offsets.end),
     funcIndex_(0),
     funcLineOrBytecode_(0),
+    funcBeginToTableEntry_(0),
+    funcBeginToTableProfilingJump_(0),
     funcBeginToNonProfilingEntry_(0),
     funcProfilingJumpToProfilingReturn_(0),
     funcProfilingEpilogueToProfilingReturn_(0),
     kind_(kind)
 {
     MOZ_ASSERT(begin_ < profilingReturn_);
     MOZ_ASSERT(profilingReturn_ < end_);
     MOZ_ASSERT(kind_ == ImportJitExit || kind_ == ImportInterpExit);
 }
 
 CodeRange::CodeRange(uint32_t funcIndex, uint32_t funcLineOrBytecode, FuncOffsets offsets)
   : begin_(offsets.begin),
     profilingReturn_(offsets.profilingReturn),
     end_(offsets.end),
     funcIndex_(funcIndex),
     funcLineOrBytecode_(funcLineOrBytecode),
+    funcBeginToTableEntry_(offsets.tableEntry - begin_),
+    funcBeginToTableProfilingJump_(offsets.tableProfilingJump - begin_),
     funcBeginToNonProfilingEntry_(offsets.nonProfilingEntry - begin_),
     funcProfilingJumpToProfilingReturn_(profilingReturn_ - offsets.profilingJump),
     funcProfilingEpilogueToProfilingReturn_(profilingReturn_ - offsets.profilingEpilogue),
     kind_(Function)
 {
     MOZ_ASSERT(begin_ < profilingReturn_);
     MOZ_ASSERT(profilingReturn_ < end_);
+    MOZ_ASSERT(funcBeginToTableEntry_ == offsets.tableEntry - begin_);
+    MOZ_ASSERT(funcBeginToTableProfilingJump_ == offsets.tableProfilingJump - begin_);
     MOZ_ASSERT(funcBeginToNonProfilingEntry_ == offsets.nonProfilingEntry - begin_);
     MOZ_ASSERT(funcProfilingJumpToProfilingReturn_ == profilingReturn_ - offsets.profilingJump);
     MOZ_ASSERT(funcProfilingEpilogueToProfilingReturn_ == profilingReturn_ - offsets.profilingEpilogue);
 }
 
 static size_t
 NullableStringLength(const char* chars)
 {
@@ -797,39 +805,41 @@ Module::setProfilingEnabled(JSContext* c
 
     // Patch callsites and returns to execute profiling prologues/epilogues.
     {
         AutoWritableJitCode awjc(cx->runtime(), code(), codeBytes());
         AutoFlushICache afc("Module::setProfilingEnabled");
         AutoFlushICache::setRange(uintptr_t(code()), codeBytes());
 
         for (const CallSite& callSite : module_->callSites)
-            EnableProfilingPrologue(*this, callSite, enabled);
+            ToggleProfiling(*this, callSite, enabled);
 
         for (const CallThunk& callThunk : module_->callThunks)
-            EnableProfilingThunk(*this, callThunk, enabled);
+            ToggleProfiling(*this, callThunk, enabled);
 
         for (const CodeRange& codeRange : module_->codeRanges)
-            EnableProfilingEpilogue(*this, codeRange, enabled);
+            ToggleProfiling(*this, codeRange, enabled);
     }
 
-    // Update the function-pointer tables to point to profiling prologues.
-    for (FuncPtrTable& table : funcPtrTables_) {
-        auto array = reinterpret_cast<void**>(globalData() + table.globalDataOffset);
-        for (size_t i = 0; i < table.numElems; i++) {
-            const CodeRange* codeRange = lookupCodeRange(array[i]);
-            // Don't update entries for the BadIndirectCall exit.
-            if (codeRange->isInline())
-                continue;
-            void* from = code() + codeRange->funcNonProfilingEntry();
-            void* to = code() + codeRange->funcProfilingEntry();
-            if (!enabled)
-                Swap(from, to);
-            MOZ_ASSERT(array[i] == from);
-            array[i] = to;
+    // In asm.js, table elements point directly to the prologue and must be
+    // updated to reflect the profiling mode. In wasm, table elements point to
+    // the (one) table entry which checks signature before jumping to the
+    // appropriate prologue (which is patched by ToggleProfiling).
+    if (isAsmJS()) {
+        for (FuncPtrTable& table : funcPtrTables_) {
+            auto array = reinterpret_cast<void**>(globalData() + table.globalDataOffset);
+            for (size_t i = 0; i < table.numElems; i++) {
+                const CodeRange* codeRange = lookupCodeRange(array[i]);
+                void* from = code() + codeRange->funcNonProfilingEntry();
+                void* to = code() + codeRange->funcProfilingEntry();
+                if (!enabled)
+                    Swap(from, to);
+                MOZ_ASSERT(array[i] == from);
+                array[i] = to;
+            }
         }
     }
 
     profilingEnabled_ = enabled;
     return true;
 }
 
 Module::ImportExit&
--- a/js/src/asmjs/WasmModule.h
+++ b/js/src/asmjs/WasmModule.h
@@ -200,16 +200,18 @@ class CodeRange
 
   private:
     // All fields are treated as cacheable POD:
     uint32_t begin_;
     uint32_t profilingReturn_;
     uint32_t end_;
     uint32_t funcIndex_;
     uint32_t funcLineOrBytecode_;
+    uint8_t funcBeginToTableEntry_;
+    uint8_t funcBeginToTableProfilingJump_;
     uint8_t funcBeginToNonProfilingEntry_;
     uint8_t funcProfilingJumpToProfilingReturn_;
     uint8_t funcProfilingEpilogueToProfilingReturn_;
     Kind kind_ : 8;
 
   public:
     CodeRange() = default;
     CodeRange(Kind kind, Offsets offsets);
@@ -251,16 +253,24 @@ class CodeRange
 
     // Functions have offsets which allow patching to selectively execute
     // profiling prologues/epilogues.
 
     uint32_t funcProfilingEntry() const {
         MOZ_ASSERT(isFunction());
         return begin();
     }
+    uint32_t funcTableEntry() const {
+        MOZ_ASSERT(isFunction());
+        return begin_ + funcBeginToTableEntry_;
+    }
+    uint32_t funcTableProfilingJump() const {
+        MOZ_ASSERT(isFunction());
+        return begin_ + funcBeginToTableProfilingJump_;
+    }
     uint32_t funcNonProfilingEntry() const {
         MOZ_ASSERT(isFunction());
         return begin_ + funcBeginToNonProfilingEntry_;
     }
     uint32_t funcProfilingJump() const {
         MOZ_ASSERT(isFunction());
         return profilingReturn_ - funcProfilingJumpToProfilingReturn_;
     }
--- a/js/src/asmjs/WasmTypes.h
+++ b/js/src/asmjs/WasmTypes.h
@@ -419,37 +419,46 @@ struct ProfilingOffsets : Offsets
     void offsetBy(uint32_t offset) {
         Offsets::offsetBy(offset);
         profilingReturn += offset;
     }
 };
 
 struct FuncOffsets : ProfilingOffsets
 {
-    MOZ_IMPLICIT FuncOffsets(uint32_t nonProfilingEntry = 0,
-                             uint32_t profilingJump = 0,
-                             uint32_t profilingEpilogue = 0)
+    MOZ_IMPLICIT FuncOffsets()
       : ProfilingOffsets(),
-        nonProfilingEntry(nonProfilingEntry),
-        profilingJump(profilingJump),
-        profilingEpilogue(profilingEpilogue)
+        tableEntry(0),
+        tableProfilingJump(0),
+        nonProfilingEntry(0),
+        profilingJump(0),
+        profilingEpilogue(0)
     {}
 
+    // Function CodeRanges have a table entry which takes an extra signature
+    // argument which is checked against the callee's signature before falling
+    // through to the normal prologue. When profiling is enabled, a nop on the
+    // fallthrough is patched to instead jump to the profiling epilogue.
+    uint32_t tableEntry;
+    uint32_t tableProfilingJump;
+
     // Function CodeRanges have an additional non-profiling entry that comes
     // after the profiling entry and a non-profiling epilogue that comes before
     // the profiling epilogue.
     uint32_t nonProfilingEntry;
 
     // When profiling is enabled, the 'nop' at offset 'profilingJump' is
     // overwritten to be a jump to 'profilingEpilogue'.
     uint32_t profilingJump;
     uint32_t profilingEpilogue;
 
     void offsetBy(uint32_t offset) {
         ProfilingOffsets::offsetBy(offset);
+        tableEntry += offset;
+        tableProfilingJump += offset;
         nonProfilingEntry += offset;
         profilingJump += offset;
         profilingEpilogue += offset;
     }
 };
 
 // While the frame-pointer chain allows the stack to be unwound without
 // metadata, Error.stack still needs to know the line/column of every call in
@@ -781,16 +790,17 @@ static const unsigned NaN64GlobalDataOff
 static const unsigned NaN32GlobalDataOffset      = NaN64GlobalDataOffset + sizeof(double);
 static const unsigned InitialGlobalDataBytes     = NaN32GlobalDataOffset + sizeof(float);
 
 static const unsigned MaxSigs                    =        4 * 1024;
 static const unsigned MaxFuncs                   =      512 * 1024;
 static const unsigned MaxLocals                  =       64 * 1024;
 static const unsigned MaxImports                 =       64 * 1024;
 static const unsigned MaxExports                 =       64 * 1024;
+static const unsigned MaxTables                  =        4 * 1024;
 static const unsigned MaxTableElems              =      128 * 1024;
 static const unsigned MaxArgsPerFunc             =        4 * 1024;
 static const unsigned MaxBrTableElems            = 4 * 1024 * 1024;
 
 } // namespace wasm
 } // namespace js
 
 #endif // wasm_types_h
--- a/js/src/jit-test/tests/wasm/basic.js
+++ b/js/src/jit-test/tests/wasm/basic.js
@@ -147,17 +147,16 @@ wasmEvalText('(module (import $foo "a" "
 wasmEvalText('(module (memory 0))');
 wasmEvalText('(module (memory 1))');
 assertErrorMessage(() => wasmEvalText('(module (memory 65536))'), TypeError, /initial memory size too big/);
 
 // May OOM, but must not crash:
 try {
     wasmEvalText('(module (memory 65535))');
 } catch (e) {
-    print(e);
     assertEq(String(e).indexOf("out of memory") != -1, true);
 }
 
 // Tests to reinstate pending a switch back to "real" memory exports:
 //
 //assertErrorMessage(() => wasmEvalText('(module (export "" memory))'), TypeError, /no memory section/);
 //
 //var buf = wasmEvalText('(module (memory 1) (export "" memory))');
@@ -295,32 +294,30 @@ assertErrorMessage(() => wasmEvalText('(
 assertEq(wasmEvalText('(module (func (result i32) (block (i32.const 13) (block (i32.const 42)))) (export "" 0))')(), 42);
 assertErrorMessage(() => wasmEvalText('(module (func (result f32) (param f32) (block (get_local 0) (i32.const 0))))'), TypeError, mismatchError("i32", "f32"));
 
 assertEq(wasmEvalText('(module (func (result i32) (local i32) (set_local 0 (i32.const 42)) (get_local 0)) (export "" 0))')(), 42);
 
 // ----------------------------------------------------------------------------
 // calls
 
-// TODO: Reenable when syntactic arities are added for calls
-//assertThrowsInstanceOf(() => wasmEvalText('(module (func (nop)) (func (call 0 (i32.const 0))))'), TypeError);
+assertThrowsInstanceOf(() => wasmEvalText('(module (func (nop)) (func (call 0 (i32.const 0))))'), TypeError);
 
 assertThrowsInstanceOf(() => wasmEvalText('(module (func (param i32) (nop)) (func (call 0)))'), TypeError);
 assertThrowsInstanceOf(() => wasmEvalText('(module (func (param f32) (nop)) (func (call 0 (i32.const 0))))'), TypeError);
 assertErrorMessage(() => wasmEvalText('(module (func (nop)) (func (call 3)))'), TypeError, /callee index out of range/);
 wasmEvalText('(module (func (nop)) (func (call 0)))');
 wasmEvalText('(module (func (param i32) (nop)) (func (call 0 (i32.const 0))))');
 assertEq(wasmEvalText('(module (func (result i32) (i32.const 42)) (func (result i32) (call 0)) (export "" 1))')(), 42);
 assertThrowsInstanceOf(() => wasmEvalText('(module (func (call 0)) (export "" 0))')(), InternalError);
 assertThrowsInstanceOf(() => wasmEvalText('(module (func (call 1)) (func (call 0)) (export "" 0))')(), InternalError);
 wasmEvalText('(module (func (param i32 f32)) (func (call 0 (i32.const 0) (f32.const nan))))');
 assertErrorMessage(() => wasmEvalText('(module (func (param i32 f32)) (func (call 0 (i32.const 0) (i32.const 0))))'), TypeError, mismatchError("i32", "f32"));
 
-// TODO: Reenable when syntactic arities are added for calls
-//assertThrowsInstanceOf(() => wasmEvalText('(module (import "a" "") (func (call_import 0 (i32.const 0))))', {a:()=>{}}), TypeError);
+assertThrowsInstanceOf(() => wasmEvalText('(module (import "a" "") (func (call_import 0 (i32.const 0))))', {a:()=>{}}), TypeError);
 
 assertThrowsInstanceOf(() => wasmEvalText('(module (import "a" "" (param i32)) (func (call_import 0)))', {a:()=>{}}), TypeError);
 assertThrowsInstanceOf(() => wasmEvalText('(module (import "a" "" (param f32)) (func (call_import 0 (i32.const 0))))', {a:()=>{}}), TypeError);
 assertErrorMessage(() => wasmEvalText('(module (import "a" "") (func (call_import 1)))'), TypeError, /import index out of range/);
 wasmEvalText('(module (import "a" "") (func (call_import 0)))', {a:()=>{}});
 wasmEvalText('(module (import "a" "" (param i32)) (func (call_import 0 (i32.const 0))))', {a:()=>{}});
 
 function checkF32CallImport(v) {
@@ -460,51 +457,46 @@ assertErrorMessage(() => i2v(0), Error, 
 assertErrorMessage(() => i2v(1), Error, badIndirectCall);
 assertErrorMessage(() => i2v(2), Error, badIndirectCall);
 assertErrorMessage(() => i2v(3), Error, badIndirectCall);
 assertErrorMessage(() => i2v(4), Error, badIndirectCall);
 assertErrorMessage(() => i2v(5), Error, badIndirectCall);
 
 {
     enableSPSProfiling();
-    wasmEvalText(`(
-        module
-        (func (result i32) (i32.const 0))
-        (func)
-        (table 1 0)
-        (export "" 0)
-    )`)();
+
+    var stack;
+    assertEq(wasmEvalText(
+        `(module
+            (type $v2v (func))
+            (import $foo "f" "")
+            (func (call_import $foo))
+            (func (result i32) (i32.const 0))
+            (table 0 1)
+            (func $bar (call_indirect $v2v (i32.const 0)))
+            (export "" $bar)
+        )`,
+        {f:() => { stack = new Error().stack }}
+    )(), undefined);
+
     disableSPSProfiling();
+
+    var inner = stack.indexOf("wasm-function[0]");
+    var outer = stack.indexOf("wasm-function[2]");
+    assertEq(inner === -1, false);
+    assertEq(outer === -1, false);
+    assertEq(inner < outer, true);
 }
 
 for (bad of [6, 7, 100, Math.pow(2,31)-1, Math.pow(2,31), Math.pow(2,31)+1, Math.pow(2,32)-2, Math.pow(2,32)-1]) {
     assertThrowsInstanceOf(() => v2i(bad), RangeError);
     assertThrowsInstanceOf(() => i2i(bad, 0), RangeError);
     assertThrowsInstanceOf(() => i2v(bad, 0), RangeError);
 }
 
-var {v2i, i2i, i2v} = wasmEvalText(`(module
-    (type $a (func (result i32)))
-    (type $b (func (param i32) (result i32)))
-    (type $c (func (param i32)))
-    (func $a (type $a) (i32.const 13))
-    (func $b (type $a) (i32.const 42))
-    (func $c (type $b) (i32.add (get_local 0) (i32.const 1)))
-    (func $d (type $b) (i32.add (get_local 0) (i32.const 2)))
-    (func $e (type $b) (i32.add (get_local 0) (i32.const 3)))
-    (func $f (type $b) (i32.add (get_local 0) (i32.const 4)))
-    (table $a $b $c $d $e $f)
-    (func (param i32) (result i32) (call_indirect $a (get_local 0)))
-    (func (param i32) (param i32) (result i32) (call_indirect $b (get_local 0) (get_local 1)))
-    (func (param i32) (call_indirect $c (get_local 0) (i32.const 0)))
-    (export "v2i" 6)
-    (export "i2i" 7)
-    (export "i2v" 8)
-)`);
-
 wasmEvalText('(module (func $foo (nop)) (func (call $foo)))');
 wasmEvalText('(module (func (call $foo)) (func $foo (nop)))');
 wasmEvalText('(module (import $bar "a" "") (func (call_import $bar)) (func $foo (nop)))', {a:()=>{}});
 
 
 // ----------------------------------------------------------------------------
 // select
 
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/profiling.js
@@ -0,0 +1,104 @@
+load(libdir + "wasm.js");
+load(libdir + "asserts.js");
+
+// Single-step profiling currently only works in the ARM simulator
+if (!getBuildConfiguration()["arm-simulator"])
+    quit();
+
+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:""}
+    ];
+
+    var framesIn = stack.split(',');
+    var framesOut = [];
+    for (let frame of framesIn) {
+        for (let {re, sub} of wasmFrameTypes) {
+            if (re.test(frame)) {
+                framesOut.push(frame.replace(re, sub));
+                break;
+            }
+        }
+    }
+
+    return framesOut.join(',');
+}
+
+function assertEqStacks(got, expect)
+{
+    for (let i = 0; i < got.length; i++)
+        got[i] = normalize(got[i]);
+
+    if (got.length != expect.length) {
+        print(`Got:\n${got.toSource()}\nExpect:\n${expect.toSource()}`);
+        assertEq(got.length, expect.length);
+    }
+
+    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)
+{
+    enableSPSProfiling();
+
+    var f = wasmEvalText(code);
+    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 $bar)
+    (export "" $foo)
+)`,
+["", ">", "0,>", "1,0,>", "0,>", ">", ""]);
+
+function testError(code, error)
+{
+    enableSPSProfiling();
+    var f = wasmEvalText(code);
+    enableSingleStepProfiling();
+    assertThrowsInstanceOf(f, error);
+    disableSingleStepProfiling();
+    disableSPSProfiling();
+}
+
+testError(
+`(module
+    (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);
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -8783,21 +8783,21 @@ CodeGenerator::visitRest(LRest* lir)
         masm.movePtr(ImmPtr(nullptr), temp2);
     }
     masm.bind(&joinAlloc);
 
     emitRest(lir, temp2, numActuals, temp0, temp1, numFormals, templateObject, false, ToRegister(lir->output()));
 }
 
 bool
-CodeGenerator::generateAsmJS(wasm::FuncOffsets* offsets)
+CodeGenerator::generateWasm(uint32_t sigIndex, wasm::FuncOffsets* offsets)
 {
     JitSpew(JitSpew_Codegen, "# Emitting asm.js code");
 
-    wasm::GenerateFunctionPrologue(masm, frameSize(), offsets);
+    wasm::GenerateFunctionPrologue(masm, frameSize(), sigIndex, offsets);
 
     // Overflow checks are omitted by CodeGenerator in some cases (leaf
     // functions with small framePushed). Perform overflow-checking after
     // pushing framePushed to catch cases with really large frames.
     Label onOverflow;
     if (!omitOverRecursedCheck()) {
         masm.branchPtr(Assembler::AboveOrEqual,
                        wasm::SymbolicAddress::StackLimit,
--- a/js/src/jit/CodeGenerator.h
+++ b/js/src/jit/CodeGenerator.h
@@ -61,17 +61,17 @@ class CodeGenerator : public CodeGenerat
     ConstantOrRegister toConstantOrRegister(LInstruction* lir, size_t n, MIRType type);
 
   public:
     CodeGenerator(MIRGenerator* gen, LIRGraph* graph, MacroAssembler* masm = nullptr);
     ~CodeGenerator();
 
   public:
     MOZ_MUST_USE bool generate();
-    MOZ_MUST_USE bool generateAsmJS(wasm::FuncOffsets *offsets);
+    MOZ_MUST_USE bool generateWasm(uint32_t sigIndex, wasm::FuncOffsets *offsets);
     MOZ_MUST_USE bool link(JSContext* cx, CompilerConstraintList* constraints);
     MOZ_MUST_USE bool linkSharedStubs(JSContext* cx);
 
     void visitOsiPoint(LOsiPoint* lir);
     void visitGoto(LGoto* lir);
     void visitTableSwitch(LTableSwitch* ins);
     void visitTableSwitchV(LTableSwitchV* ins);
     void visitCloneLiteral(LCloneLiteral* lir);
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -4202,17 +4202,17 @@ LIRGenerator::visitAsmJSCall(MAsmJSCall*
         return;
     }
 
     for (unsigned i = 0; i < ins->numArgs(); i++)
         args[i] = useFixed(ins->getOperand(i), ins->registerForArg(i));
 
     if (ins->callee().which() == MAsmJSCall::Callee::Dynamic) {
         args[ins->dynamicCalleeOperandIndex()] =
-            useFixed(ins->callee().dynamic(), ABINonArgReg0);
+            useFixed(ins->callee().dynamicPtr(), WasmTableCallPtrReg);
     }
 
     LInstruction* lir = new(alloc()) LAsmJSCall(args, ins->numOperands());
     if (ins->type() == MIRType::None)
         add(lir, ins);
     else
         defineReturn(lir, ins);
 }
--- a/js/src/jit/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -4734,31 +4734,27 @@ MAsmJSLoadGlobalVar::foldsTo(TempAllocat
 
     return store->value();
 }
 
 HashNumber
 MAsmJSLoadFuncPtr::valueHash() const
 {
     HashNumber hash = MDefinition::valueHash();
-    hash = addU32ToHash(hash, hasLimit_);
     hash = addU32ToHash(hash, limit_);
-    hash = addU32ToHash(hash, alwaysThrow_);
     hash = addU32ToHash(hash, globalDataOffset_);
     return hash;
 }
 
 bool
 MAsmJSLoadFuncPtr::congruentTo(const MDefinition* ins) const
 {
     if (ins->isAsmJSLoadFuncPtr()) {
         const MAsmJSLoadFuncPtr* load = ins->toAsmJSLoadFuncPtr();
-        return hasLimit_ == load->hasLimit_ &&
-               limit_ == load->limit_ &&
-               alwaysThrow_ == load->alwaysThrow_ &&
+        return limit_ == load->limit_ &&
                globalDataOffset_ == load->globalDataOffset_;
     }
     return false;
 }
 
 HashNumber
 MAsmJSLoadFFIFunc::valueHash() const
 {
@@ -5101,17 +5097,17 @@ MAsmJSCall::New(TempAllocator& alloc, co
         call->argRegs_[i] = args[i].reg;
 
     if (!call->init(alloc, call->argRegs_.length() + (callee.which() == Callee::Dynamic ? 1 : 0)))
         return nullptr;
     // FixedList doesn't initialize its elements, so do an unchecked init.
     for (size_t i = 0; i < call->argRegs_.length(); i++)
         call->initOperand(i, args[i].def);
     if (callee.which() == Callee::Dynamic)
-        call->initOperand(call->argRegs_.length(), callee.dynamic());
+        call->initOperand(call->argRegs_.length(), callee.dynamicPtr());
 
     return call;
 }
 
 void
 MSqrt::trySpecializeFloat32(TempAllocator& alloc) {
     if (!input()->canProduceFloat32() || !CheckUsesAreFloat32Consumers(this)) {
         if (input()->type() == MIRType::Float32)
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -14420,48 +14420,46 @@ class MAsmJSStoreGlobalVar
         return AliasSet::Store(AliasSet::AsmJSGlobalVar);
     }
 };
 
 class MAsmJSLoadFuncPtr
   : public MUnaryInstruction,
     public NoTypePolicy::Data
 {
-    MAsmJSLoadFuncPtr(MDefinition* index, bool hasLimit, uint32_t limit, bool alwaysThrow,
-                      unsigned globalDataOffset)
-      : MUnaryInstruction(index), hasLimit_(hasLimit), limit_(limit), alwaysThrow_(alwaysThrow),
-        globalDataOffset_(globalDataOffset)
+    MAsmJSLoadFuncPtr(MDefinition* index, uint32_t limit, unsigned globalDataOffset)
+      : MUnaryInstruction(index), limit_(limit), globalDataOffset_(globalDataOffset)
     {
         setResultType(MIRType::Pointer);
     }
 
-    bool hasLimit_;
     uint32_t limit_;
-    bool alwaysThrow_;
     unsigned globalDataOffset_;
 
   public:
     INSTRUCTION_HEADER(AsmJSLoadFuncPtr)
 
+    static const uint32_t NoLimit = UINT32_MAX;
+
     static MAsmJSLoadFuncPtr* New(TempAllocator& alloc, MDefinition* index, uint32_t limit,
-                                  bool alwaysThrow, unsigned globalDataOffset)
-    {
-        return new(alloc) MAsmJSLoadFuncPtr(index, true, limit, alwaysThrow, globalDataOffset);
+                                  unsigned globalDataOffset)
+    {
+        MOZ_ASSERT(limit != NoLimit);
+        return new(alloc) MAsmJSLoadFuncPtr(index, limit, globalDataOffset);
     }
 
     static MAsmJSLoadFuncPtr* New(TempAllocator& alloc, MDefinition* index,
                                   unsigned globalDataOffset)
     {
-        return new(alloc) MAsmJSLoadFuncPtr(index, false, 0, false, globalDataOffset);
+        return new(alloc) MAsmJSLoadFuncPtr(index, NoLimit, globalDataOffset);
     }
 
     MDefinition* index() const { return getOperand(0); }
-    bool hasLimit() const { return hasLimit_; }
-    uint32_t limit() const { MOZ_ASSERT(hasLimit_); return limit_; }
-    bool alwaysThrow() const { return alwaysThrow_; }
+    bool hasLimit() const { return limit_ != NoLimit; }
+    uint32_t limit() const { MOZ_ASSERT(hasLimit()); return limit_; }
     unsigned globalDataOffset() const { return globalDataOffset_; }
 
     HashNumber valueHash() const override;
     bool congruentTo(const MDefinition* ins) const override;
 };
 
 class MAsmJSLoadFFIFunc : public MNullaryInstruction
 {
@@ -14563,32 +14561,62 @@ class MAsmJSPassStackArg
 class MAsmJSCall final
   : public MVariadicInstruction,
     public NoTypePolicy::Data
 {
   public:
     class Callee {
       public:
         enum Which { Internal, Dynamic, Builtin };
+        static const uint32_t NoSigIndex = UINT32_MAX;
       private:
         Which which_;
         union {
             uint32_t internal_;
-            MDefinition* dynamic_;
+            struct {
+                MDefinition* callee_;
+                uint32_t sigIndex_;
+            } dynamic;
             wasm::SymbolicAddress builtin_;
         } u;
       public:
         Callee() {}
-        explicit Callee(uint32_t callee) : which_(Internal) { u.internal_ = callee; }
-        explicit Callee(MDefinition* callee) : which_(Dynamic) { u.dynamic_ = callee; }
-        explicit Callee(wasm::SymbolicAddress callee) : which_(Builtin) { u.builtin_ = callee; }
-        Which which() const { return which_; }
-        uint32_t internal() const { MOZ_ASSERT(which_ == Internal); return u.internal_; }
-        MDefinition* dynamic() const { MOZ_ASSERT(which_ == Dynamic); return u.dynamic_; }
-        wasm::SymbolicAddress builtin() const { MOZ_ASSERT(which_ == Builtin); return u.builtin_; }
+        explicit Callee(uint32_t callee) : which_(Internal) {
+            u.internal_ = callee;
+        }
+        explicit Callee(MDefinition* callee, uint32_t sigIndex = NoSigIndex) : which_(Dynamic) {
+            u.dynamic.callee_ = callee;
+            u.dynamic.sigIndex_ = sigIndex;
+        }
+        explicit Callee(wasm::SymbolicAddress callee) : which_(Builtin) {
+            u.builtin_ = callee;
+        }
+        Which which() const {
+            return which_;
+        }
+        uint32_t internal() const {
+            MOZ_ASSERT(which_ == Internal);
+            return u.internal_;
+        }
+        MDefinition* dynamicPtr() const {
+            MOZ_ASSERT(which_ == Dynamic);
+            return u.dynamic.callee_;
+        }
+        bool dynamicHasSigIndex() const {
+            MOZ_ASSERT(which_ == Dynamic);
+            return u.dynamic.sigIndex_ != NoSigIndex;
+        }
+        uint32_t dynamicSigIndex() const {
+            MOZ_ASSERT(dynamicHasSigIndex());
+            return u.dynamic.sigIndex_;
+        }
+        wasm::SymbolicAddress builtin() const {
+            MOZ_ASSERT(which_ == Builtin);
+            return u.builtin_;
+        }
     };
 
   private:
     wasm::CallSiteDesc desc_;
     Callee callee_;
     FixedList<AnyRegister> argRegs_;
     size_t spIncrement_;
 
--- a/js/src/jit/arm/Assembler-arm.h
+++ b/js/src/jit/arm/Assembler-arm.h
@@ -103,16 +103,21 @@ class ABIArgGenerator
     uint32_t stackBytesConsumedSoFar() const { return stackOffset_; }
 };
 
 static MOZ_CONSTEXPR_VAR Register ABINonArgReg0 = r4;
 static MOZ_CONSTEXPR_VAR Register ABINonArgReg1 = r5;
 static MOZ_CONSTEXPR_VAR Register ABINonArgReturnReg0 = r4;
 static MOZ_CONSTEXPR_VAR Register ABINonArgReturnReg1 = r5;
 
+// Registers used for asm.js/wasm table calls. These registers must be disjoint
+// from the ABI argument registers and from each other.
+static MOZ_CONSTEXPR_VAR Register WasmTableCallPtrReg = ABINonArgReg0;
+static MOZ_CONSTEXPR_VAR Register WasmTableCallSigReg = ABINonArgReg1;
+
 static MOZ_CONSTEXPR_VAR Register PreBarrierReg = r1;
 
 static MOZ_CONSTEXPR_VAR Register InvalidReg = { Registers::invalid_reg };
 static MOZ_CONSTEXPR_VAR FloatRegister InvalidFloatReg;
 
 static MOZ_CONSTEXPR_VAR Register JSReturnReg_Type = r3;
 static MOZ_CONSTEXPR_VAR Register JSReturnReg_Data = r2;
 static MOZ_CONSTEXPR_VAR Register StackPointer = sp;
--- a/js/src/jit/arm/CodeGenerator-arm.cpp
+++ b/js/src/jit/arm/CodeGenerator-arm.cpp
@@ -2776,24 +2776,20 @@ CodeGeneratorARM::visitAsmJSLoadFuncPtr(
     Register tmp = ToRegister(ins->temp());
     Register out = ToRegister(ins->output());
 
     if (mir->hasLimit()) {
         masm.branch32(Assembler::Condition::AboveOrEqual, index, Imm32(mir->limit()),
                       wasm::JumpTarget::OutOfBounds);
     }
 
-    if (mir->alwaysThrow()) {
-        masm.jump(wasm::JumpTarget::BadIndirectCall);
-    } else {
-        unsigned addr = mir->globalDataOffset();
-        masm.ma_mov(Imm32(addr - AsmJSGlobalRegBias), tmp);
-        masm.as_add(tmp, tmp, lsl(index, 2));
-        masm.ma_ldr(DTRAddr(GlobalReg, DtrRegImmShift(tmp, LSL, 0)), out);
-    }
+    unsigned addr = mir->globalDataOffset();
+    masm.ma_mov(Imm32(addr - AsmJSGlobalRegBias), tmp);
+    masm.as_add(tmp, tmp, lsl(index, 2));
+    masm.ma_ldr(DTRAddr(GlobalReg, DtrRegImmShift(tmp, LSL, 0)), out);
 }
 
 void
 CodeGeneratorARM::visitAsmJSLoadFFIFunc(LAsmJSLoadFFIFunc* ins)
 {
     const MAsmJSLoadFFIFunc* mir = ins->mir();
 
     masm.ma_ldr(Address(GlobalReg, mir->globalDataOffset() - AsmJSGlobalRegBias),
--- a/js/src/jit/arm64/Assembler-arm64.h
+++ b/js/src/jit/arm64/Assembler-arm64.h
@@ -455,16 +455,21 @@ class ABIArgGenerator
     ABIArg current_;
 };
 
 static MOZ_CONSTEXPR_VAR Register ABINonArgReg0 = r8;
 static MOZ_CONSTEXPR_VAR Register ABINonArgReg1 = r9;
 static MOZ_CONSTEXPR_VAR Register ABINonArgReturnReg0 = r8;
 static MOZ_CONSTEXPR_VAR Register ABINonArgReturnReg1 = r9;
 
+// Registers used for asm.js/wasm table calls. These registers must be disjoint
+// from the ABI argument registers and from each other.
+static MOZ_CONSTEXPR_VAR Register WasmTableCallPtrReg = ABINonArgReg0;
+static MOZ_CONSTEXPR_VAR Register WasmTableCallSigReg = ABINonArgReg1;
+
 static inline bool
 GetIntArgReg(uint32_t usedIntArgs, uint32_t usedFloatArgs, Register* out)
 {
     if (usedIntArgs >= NumIntArgRegs)
         return false;
     *out = Register::FromCode(usedIntArgs);
     return true;
 }
--- a/js/src/jit/arm64/MacroAssembler-arm64.h
+++ b/js/src/jit/arm64/MacroAssembler-arm64.h
@@ -721,16 +721,19 @@ class MacroAssemblerCompat : public vixl
         armbuffer_.align(alignment);
     }
 
     void haltingAlign(int alignment) {
         // TODO: Implement a proper halting align.
         // ARM doesn't have one either.
         armbuffer_.align(alignment);
     }
+    void nopAlign(int alignment) {
+        MOZ_CRASH("NYI");
+    }
 
     void movePtr(Register src, Register dest) {
         Mov(ARMRegister(dest, 64), ARMRegister(src, 64));
     }
     void movePtr(ImmWord imm, Register dest) {
         Mov(ARMRegister(dest, 64), int64_t(imm.value));
     }
     void movePtr(ImmPtr imm, Register dest) {
--- a/js/src/jit/mips-shared/CodeGenerator-mips-shared.cpp
+++ b/js/src/jit/mips-shared/CodeGenerator-mips-shared.cpp
@@ -2171,22 +2171,18 @@ CodeGeneratorMIPSShared::visitAsmJSLoadF
     Register out = ToRegister(ins->output());
     unsigned addr = mir->globalDataOffset() - AsmJSGlobalRegBias;
 
     if (mir->hasLimit()) {
         masm.branch32(Assembler::Condition::AboveOrEqual, index, Imm32(mir->limit()),
                       wasm::JumpTarget::OutOfBounds);
     }
 
-    if (mir->alwaysThrow()) {
-        masm.jump(wasm::JumpTarget::BadIndirectCall);
-    } else {
-        BaseIndex source(GlobalReg, index, ScalePointer, addr);
-        masm.loadPtr(source, out);
-    }
+    BaseIndex source(GlobalReg, index, ScalePointer, addr);
+    masm.loadPtr(source, out);
 }
 
 void
 CodeGeneratorMIPSShared::visitAsmJSLoadFFIFunc(LAsmJSLoadFFIFunc* ins)
 {
     const MAsmJSLoadFFIFunc* mir = ins->mir();
     masm.loadPtr(Address(GlobalReg, mir->globalDataOffset() - AsmJSGlobalRegBias),
                  ToRegister(ins->output()));
--- a/js/src/jit/mips32/Assembler-mips32.h
+++ b/js/src/jit/mips32/Assembler-mips32.h
@@ -46,16 +46,21 @@ class ABIArgGenerator
     }
 };
 
 static MOZ_CONSTEXPR_VAR Register ABINonArgReg0 = t0;
 static MOZ_CONSTEXPR_VAR Register ABINonArgReg1 = t1;
 static MOZ_CONSTEXPR_VAR Register ABINonArgReturnReg0 = t0;
 static MOZ_CONSTEXPR_VAR Register ABINonArgReturnReg1 = t1;
 
+// Registers used for asm.js/wasm table calls. These registers must be disjoint
+// from the ABI argument registers and from each other.
+static MOZ_CONSTEXPR_VAR Register WasmTableCallPtrReg = ABINonArgReg0;
+static MOZ_CONSTEXPR_VAR Register WasmTableCallSigReg = ABINonArgReg1;
+
 static MOZ_CONSTEXPR_VAR Register JSReturnReg_Type = a3;
 static MOZ_CONSTEXPR_VAR Register JSReturnReg_Data = a2;
 static MOZ_CONSTEXPR_VAR Register64 ReturnReg64(InvalidReg, InvalidReg);
 static MOZ_CONSTEXPR_VAR FloatRegister ReturnFloat32Reg = { FloatRegisters::f0, FloatRegister::Single };
 static MOZ_CONSTEXPR_VAR FloatRegister ReturnDoubleReg = { FloatRegisters::f0, FloatRegister::Double };
 static MOZ_CONSTEXPR_VAR FloatRegister ScratchFloat32Reg = { FloatRegisters::f18, FloatRegister::Single };
 static MOZ_CONSTEXPR_VAR FloatRegister ScratchDoubleReg = { FloatRegisters::f18, FloatRegister::Double };
 static MOZ_CONSTEXPR_VAR FloatRegister SecondScratchFloat32Reg = { FloatRegisters::f16, FloatRegister::Single };
--- a/js/src/jit/mips64/Assembler-mips64.h
+++ b/js/src/jit/mips64/Assembler-mips64.h
@@ -39,16 +39,21 @@ class ABIArgGenerator
     }
 };
 
 static MOZ_CONSTEXPR_VAR Register ABINonArgReg0 = t0;
 static MOZ_CONSTEXPR_VAR Register ABINonArgReg1 = t1;
 static MOZ_CONSTEXPR_VAR Register ABINonArgReturnReg0 = t0;
 static MOZ_CONSTEXPR_VAR Register ABINonArgReturnReg1 = t1;
 
+// Registers used for asm.js/wasm table calls. These registers must be disjoint
+// from the ABI argument registers and from each other.
+static MOZ_CONSTEXPR_VAR Register WasmTableCallPtrReg = ABINonArgReg0;
+static MOZ_CONSTEXPR_VAR Register WasmTableCallSigReg = ABINonArgReg1;
+
 static MOZ_CONSTEXPR_VAR Register JSReturnReg = v1;
 static MOZ_CONSTEXPR_VAR Register JSReturnReg_Type = JSReturnReg;
 static MOZ_CONSTEXPR_VAR Register JSReturnReg_Data = JSReturnReg;
 static MOZ_CONSTEXPR_VAR Register64 ReturnReg64(ReturnReg);
 static MOZ_CONSTEXPR_VAR FloatRegister ReturnFloat32Reg = { FloatRegisters::f0, FloatRegisters::Single };
 static MOZ_CONSTEXPR_VAR FloatRegister ReturnDoubleReg = { FloatRegisters::f0, FloatRegisters::Double };
 static MOZ_CONSTEXPR_VAR FloatRegister ScratchFloat32Reg = { FloatRegisters::f23, FloatRegisters::Single };
 static MOZ_CONSTEXPR_VAR FloatRegister ScratchDoubleReg = { FloatRegisters::f23, FloatRegisters::Double };
--- a/js/src/jit/none/MacroAssembler-none.h
+++ b/js/src/jit/none/MacroAssembler-none.h
@@ -79,16 +79,19 @@ static MOZ_CONSTEXPR_VAR Register64 Retu
 #error "Bad architecture"
 #endif
 
 static MOZ_CONSTEXPR_VAR Register ABINonArgReg0 = { Registers::invalid_reg };
 static MOZ_CONSTEXPR_VAR Register ABINonArgReg1 = { Registers::invalid_reg };
 static MOZ_CONSTEXPR_VAR Register ABINonArgReturnReg0 = { Registers::invalid_reg };
 static MOZ_CONSTEXPR_VAR Register ABINonArgReturnReg1 = { Registers::invalid_reg };
 
+static MOZ_CONSTEXPR_VAR Register WasmTableCallPtrReg = { Register::invalid_reg };
+static MOZ_CONSTEXPR_VAR Register WasmTableCallSigReg = { Register::invalid_reg };
+
 static MOZ_CONSTEXPR_VAR uint32_t ABIStackAlignment = 4;
 static MOZ_CONSTEXPR_VAR uint32_t CodeAlignment = 4;
 static MOZ_CONSTEXPR_VAR uint32_t JitStackAlignment = 8;
 static MOZ_CONSTEXPR_VAR uint32_t JitStackValueAlignment = JitStackAlignment / sizeof(Value);
 
 static const Scale ScalePointer = TimesOne;
 
 class Assembler : public AssemblerShared
--- a/js/src/jit/shared/CodeGenerator-shared.cpp
+++ b/js/src/jit/shared/CodeGenerator-shared.cpp
@@ -1513,19 +1513,23 @@ CodeGeneratorShared::emitAsmJSCall(LAsmJ
     masm.bind(&ok);
 #endif
 
     MAsmJSCall::Callee callee = mir->callee();
     switch (callee.which()) {
       case MAsmJSCall::Callee::Internal:
         masm.call(mir->desc(), callee.internal());
         break;
-      case MAsmJSCall::Callee::Dynamic:
-        masm.call(mir->desc(), ToRegister(ins->getOperand(mir->dynamicCalleeOperandIndex())));
+      case MAsmJSCall::Callee::Dynamic: {
+        if (callee.dynamicHasSigIndex())
+            masm.move32(Imm32(callee.dynamicSigIndex()), WasmTableCallSigReg);
+        MOZ_ASSERT(WasmTableCallPtrReg == ToRegister(ins->getOperand(mir->dynamicCalleeOperandIndex())));
+        masm.call(mir->desc(), WasmTableCallPtrReg);
         break;
+      }
       case MAsmJSCall::Callee::Builtin:
         masm.call(callee.builtin());
         break;
     }
 
     if (mir->spIncrement())
         masm.reserveStack(mir->spIncrement());
 }
--- a/js/src/jit/x64/Assembler-x64.h
+++ b/js/src/jit/x64/Assembler-x64.h
@@ -195,16 +195,21 @@ class ABIArgGenerator
 static MOZ_CONSTEXPR_VAR Register ABINonArgReg0 = rax;
 static MOZ_CONSTEXPR_VAR Register ABINonArgReg1 = rbx;
 
 // Note: these three registers are all guaranteed to be different
 static MOZ_CONSTEXPR_VAR Register ABINonArgReturnReg0 = r10;
 static MOZ_CONSTEXPR_VAR Register ABINonArgReturnReg1 = r12;
 static MOZ_CONSTEXPR_VAR Register ABINonVolatileReg = r13;
 
+// Registers used for asm.js/wasm table calls. These registers must be disjoint
+// from the ABI argument registers and from each other.
+static MOZ_CONSTEXPR_VAR Register WasmTableCallPtrReg = ABINonArgReg0;
+static MOZ_CONSTEXPR_VAR Register WasmTableCallSigReg = ABINonArgReg1;
+
 static MOZ_CONSTEXPR_VAR Register OsrFrameReg = IntArgReg3;
 
 static MOZ_CONSTEXPR_VAR Register PreBarrierReg = rdx;
 
 static MOZ_CONSTEXPR_VAR uint32_t ABIStackAlignment = 16;
 static MOZ_CONSTEXPR_VAR uint32_t CodeAlignment = 16;
 static MOZ_CONSTEXPR_VAR uint32_t JitStackAlignment = 16;
 
--- a/js/src/jit/x64/CodeGenerator-x64.cpp
+++ b/js/src/jit/x64/CodeGenerator-x64.cpp
@@ -1102,23 +1102,19 @@ CodeGeneratorX64::visitAsmJSLoadFuncPtr(
     Register tmp = ToRegister(ins->temp());
     Register out = ToRegister(ins->output());
 
     if (mir->hasLimit()) {
         masm.branch32(Assembler::Condition::AboveOrEqual, index, Imm32(mir->limit()),
                       wasm::JumpTarget::OutOfBounds);
     }
 
-    if (mir->alwaysThrow()) {
-        masm.jump(wasm::JumpTarget::BadIndirectCall);
-    } else {
-        CodeOffset label = masm.leaRipRelative(tmp);
-        masm.loadPtr(Operand(tmp, index, TimesEight, 0), out);
-        masm.append(AsmJSGlobalAccess(label, mir->globalDataOffset()));
-    }
+    CodeOffset label = masm.leaRipRelative(tmp);
+    masm.loadPtr(Operand(tmp, index, ScalePointer, 0), out);
+    masm.append(AsmJSGlobalAccess(label, mir->globalDataOffset()));
 }
 
 void
 CodeGeneratorX64::visitAsmJSLoadFFIFunc(LAsmJSLoadFFIFunc* ins)
 {
     MAsmJSLoadFFIFunc* mir = ins->mir();
 
     CodeOffset label = masm.loadRipRelativeInt64(ToRegister(ins->output()));
--- a/js/src/jit/x86/Assembler-x86.h
+++ b/js/src/jit/x86/Assembler-x86.h
@@ -84,16 +84,21 @@ class ABIArgGenerator
 static MOZ_CONSTEXPR_VAR Register ABINonArgReg0 = eax;
 static MOZ_CONSTEXPR_VAR Register ABINonArgReg1 = ecx;
 
 // Note: these three registers are all guaranteed to be different
 static MOZ_CONSTEXPR_VAR Register ABINonArgReturnReg0 = ecx;
 static MOZ_CONSTEXPR_VAR Register ABINonArgReturnReg1 = edx;
 static MOZ_CONSTEXPR_VAR Register ABINonVolatileReg = ebx;
 
+// Registers used for asm.js/wasm table calls. These registers must be disjoint
+// from the ABI argument registers and from each other.
+static MOZ_CONSTEXPR_VAR Register WasmTableCallPtrReg = ABINonArgReg0;
+static MOZ_CONSTEXPR_VAR Register WasmTableCallSigReg = ABINonArgReg1;
+
 static MOZ_CONSTEXPR_VAR Register OsrFrameReg = edx;
 static MOZ_CONSTEXPR_VAR Register PreBarrierReg = edx;
 
 // Registers used in the GenerateFFIIonExit Enable Activation block.
 static MOZ_CONSTEXPR_VAR Register AsmJSIonExitRegCallee = ecx;
 static MOZ_CONSTEXPR_VAR Register AsmJSIonExitRegE0 = edi;
 static MOZ_CONSTEXPR_VAR Register AsmJSIonExitRegE1 = eax;
 static MOZ_CONSTEXPR_VAR Register AsmJSIonExitRegE2 = ebx;
--- a/js/src/jit/x86/CodeGenerator-x86.cpp
+++ b/js/src/jit/x86/CodeGenerator-x86.cpp
@@ -829,22 +829,18 @@ CodeGeneratorX86::visitAsmJSLoadFuncPtr(
     Register index = ToRegister(ins->index());
     Register out = ToRegister(ins->output());
 
     if (mir->hasLimit()) {
         masm.branch32(Assembler::Condition::AboveOrEqual, index, Imm32(mir->limit()),
                       wasm::JumpTarget::OutOfBounds);
     }
 
-    if (mir->alwaysThrow()) {
-        masm.jump(wasm::JumpTarget::BadIndirectCall);
-    } else {
-        CodeOffset label = masm.movlWithPatch(PatchedAbsoluteAddress(), index, TimesFour, out);
-        masm.append(AsmJSGlobalAccess(label, mir->globalDataOffset()));
-    }
+    CodeOffset label = masm.movlWithPatch(PatchedAbsoluteAddress(), index, ScalePointer, out);
+    masm.append(AsmJSGlobalAccess(label, mir->globalDataOffset()));
 }
 
 void
 CodeGeneratorX86::visitAsmJSLoadFFIFunc(LAsmJSLoadFFIFunc* ins)
 {
     MAsmJSLoadFFIFunc* mir = ins->mir();
 
     Register out = ToRegister(ins->output());