Bug 1288222 - Baldr: match signature types structurally (r=bbouvier)
authorLuke Wagner <luke@mozilla.com>
Thu, 21 Jul 2016 21:19:49 -0500
changeset 346416 d55c09896ad7dc94eab55b9d81936618eda5271f
parent 346415 32e22104e1aa1501a5d26a636aa458722a2b91c4
child 346417 eff16f4a8cc1972124af8ce3f20d9aaa18b4b158
push id6389
push userraliiev@mozilla.com
push dateMon, 19 Sep 2016 13:38:22 +0000
treeherdermozilla-beta@01d67bfe6c81 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbbouvier
bugs1288222
milestone50.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1288222 - Baldr: match signature types structurally (r=bbouvier) MozReview-Commit-ID: 7Noq2TBkKmB
js/src/asmjs/AsmJS.cpp
js/src/asmjs/WasmBaselineCompile.cpp
js/src/asmjs/WasmCode.cpp
js/src/asmjs/WasmCode.h
js/src/asmjs/WasmCompile.cpp
js/src/asmjs/WasmFrameIterator.cpp
js/src/asmjs/WasmFrameIterator.h
js/src/asmjs/WasmGenerator.cpp
js/src/asmjs/WasmGenerator.h
js/src/asmjs/WasmInstance.cpp
js/src/asmjs/WasmInstance.h
js/src/asmjs/WasmIonCompile.cpp
js/src/asmjs/WasmIonCompile.h
js/src/asmjs/WasmModule.cpp
js/src/asmjs/WasmModule.h
js/src/asmjs/WasmTypes.cpp
js/src/asmjs/WasmTypes.h
js/src/jit-test/tests/wasm/spec/func_ptrs.wast
js/src/jit-test/tests/wasm/table-gc.js
js/src/jit-test/tests/wasm/tables.js
js/src/jit/CodeGenerator.cpp
js/src/jit/CodeGenerator.h
js/src/jit/MIR.h
js/src/jit/MacroAssembler.h
js/src/jit/arm/MacroAssembler-arm-inl.h
js/src/jit/arm64/MacroAssembler-arm64-inl.h
js/src/jit/mips-shared/MacroAssembler-mips-shared-inl.h
js/src/jit/shared/CodeGenerator-shared.cpp
js/src/jit/x86-shared/MacroAssembler-x86-shared-inl.h
js/src/vm/Runtime.h
--- a/js/src/asmjs/AsmJS.cpp
+++ b/js/src/asmjs/AsmJS.cpp
@@ -1538,20 +1538,20 @@ class MOZ_STACK_CLASS ModuleValidator
         PropertyName* name;
         Scalar::Type type;
     };
 
   private:
     class NamedSig
     {
         PropertyName* name_;
-        const DeclaredSig* sig_;
+        const SigWithId* sig_;
 
       public:
-        NamedSig(PropertyName* name, const DeclaredSig& sig)
+        NamedSig(PropertyName* name, const SigWithId& sig)
           : name_(name), sig_(&sig)
         {}
         PropertyName* name() const {
             return name_;
         }
         const Sig& sig() const {
             return *sig_;
         }
@@ -1565,17 +1565,17 @@ class MOZ_STACK_CLASS ModuleValidator
         static HashNumber hash(Lookup l) {
             return HashGeneric(l.name, l.sig.hash());
         }
         static bool match(NamedSig lhs, Lookup rhs) {
             return lhs.name_ == rhs.name && *lhs.sig_ == rhs.sig;
         }
     };
     typedef HashMap<NamedSig, uint32_t, NamedSig> ImportMap;
-    typedef HashMap<const DeclaredSig*, uint32_t, SigHashPolicy> SigMap;
+    typedef HashMap<const SigWithId*, uint32_t, SigHashPolicy> SigMap;
     typedef HashMap<PropertyName*, Global*> GlobalMap;
     typedef HashMap<PropertyName*, MathBuiltin> MathNameMap;
     typedef HashMap<PropertyName*, AsmJSAtomicsBuiltinFunction> AtomicsNameMap;
     typedef HashMap<PropertyName*, SimdOperation> SimdOperationNameMap;
     typedef Vector<ArrayView> ArrayViewVector;
 
     ExclusiveContext*     cx_;
     AsmJSParser&          parser_;
--- a/js/src/asmjs/WasmBaselineCompile.cpp
+++ b/js/src/asmjs/WasmBaselineCompile.cpp
@@ -1732,17 +1732,17 @@ class BaseCompiler
 
     //////////////////////////////////////////////////////////////////////
     //
     // Function prologue and epilogue.
 
     void beginFunction() {
         JitSpew(JitSpew_Codegen, "# Emitting wasm baseline code");
 
-        wasm::GenerateFunctionPrologue(masm, localSize_, mg_.funcSigIndex(func_.index()),
+        wasm::GenerateFunctionPrologue(masm, localSize_, mg_.funcSigs[func_.index()]->id,
                                        &compileResults_.offsets());
 
         MOZ_ASSERT(masm.framePushed() == uint32_t(localSize_));
 
         maxFramePushed_ = localSize_;
 
         // We won't know until after we've generated code how big the
         // frame will be (we may need arbitrary spill slots and
@@ -2036,30 +2036,40 @@ class BaseCompiler
 
     void callSymbolic(wasm::SymbolicAddress callee, const FunctionCall& call) {
         CallSiteDesc desc(call.lineOrBytecode_, CallSiteDesc::Register);
         masm.call(callee);
     }
 
     // Precondition: sync()
 
-    void funcPtrCall(const Sig& sig, uint32_t sigIndex, uint32_t length, uint32_t globalDataOffset,
+    void funcPtrCall(const SigWithId& sig, uint32_t length, uint32_t globalDataOffset,
                      Stk& indexVal, const FunctionCall& call)
     {
         Register ptrReg = WasmTableCallPtrReg;
 
         loadI32(ptrReg, indexVal);
 
         if (isCompilingAsmJS()) {
             MOZ_ASSERT(IsPowerOfTwo(length));
             masm.andPtr(Imm32((length - 1)), ptrReg);
         } else {
             masm.branch32(Assembler::Condition::AboveOrEqual, ptrReg, Imm32(length),
                           wasm::JumpTarget::OutOfBounds);
-            masm.move32(Imm32(sigIndex), WasmTableCallSigReg);
+        }
+
+        switch (sig.id.kind()) {
+          case SigIdDesc::Kind::Global:
+            masm.loadWasmGlobalPtr(sig.id.globalDataOffset(), WasmTableCallSigReg);
+            break;
+          case SigIdDesc::Kind::Immediate:
+            masm.move32(Imm32(sig.id.immediate()), WasmTableCallSigReg);
+            break;
+          case SigIdDesc::Kind::None:
+            break;
         }
 
         {
             ScratchI32 scratch(*this);
             masm.loadWasmGlobalPtr(globalDataOffset, scratch);
             masm.loadPtr(BaseIndex(scratch, ptrReg, ScalePointer, 0), ptrReg);
         }
 
@@ -5138,17 +5148,17 @@ BaseCompiler::emitCallIndirect(uint32_t 
 
     uint32_t sigIndex;
     uint32_t arity;
     if (!iter_.readCallIndirect(&sigIndex, &arity))
         return false;
 
     Nothing callee_;
 
-    const Sig& sig = mg_.sigs[sigIndex];
+    const SigWithId& sig = mg_.sigs[sigIndex];
 
     if (deadCode_) {
         return skipCall(sig.args()) && iter_.readCallIndirectCallee(&callee_) &&
                iter_.readCallReturn(sig.ret());
     }
 
     sync();
 
@@ -5170,17 +5180,17 @@ BaseCompiler::emitCallIndirect(uint32_t 
         return false;
 
     Stk& callee = peek(numArgs);
 
     const TableDesc& table = isCompilingAsmJS()
                              ? mg_.tables[mg_.asmJSSigToTableIndex[sigIndex]]
                              : mg_.tables[0];
 
-    funcPtrCall(sig, sigIndex, table.initial, table.globalDataOffset, callee, baselineCall);
+    funcPtrCall(sig, table.initial, table.globalDataOffset, callee, baselineCall);
 
     endCall(baselineCall);
 
     // TODO / OPTIMIZE: It would be better to merge this freeStack()
     // into the one in endCall, if we can.
 
     popValueStackBy(numArgs+1);
     masm.freeStack(stackSpace);
--- a/js/src/asmjs/WasmCode.cpp
+++ b/js/src/asmjs/WasmCode.cpp
@@ -244,108 +244,72 @@ CodeSegment::~CodeSegment()
 
     MOZ_ASSERT(wasmCodeAllocations > 0);
     wasmCodeAllocations--;
 
     MOZ_ASSERT(totalLength() > 0);
     DeallocateExecutableMemory(bytes_, totalLength(), gc::SystemPageSize());
 }
 
-static size_t
-SerializedSigSize(const Sig& sig)
-{
-    return sizeof(ExprType) +
-           SerializedPodVectorSize(sig.args());
-}
-
-static uint8_t*
-SerializeSig(uint8_t* cursor, const Sig& sig)
-{
-    cursor = WriteScalar<ExprType>(cursor, sig.ret());
-    cursor = SerializePodVector(cursor, sig.args());
-    return cursor;
-}
-
-static const uint8_t*
-DeserializeSig(const uint8_t* cursor, Sig* sig)
-{
-    ExprType ret;
-    cursor = ReadScalar<ExprType>(cursor, &ret);
-
-    ValTypeVector args;
-    cursor = DeserializePodVector(cursor, &args);
-    if (!cursor)
-        return nullptr;
-
-    *sig = Sig(Move(args), ret);
-    return cursor;
-}
-
-static size_t
-SizeOfSigExcludingThis(const Sig& sig, MallocSizeOf mallocSizeOf)
-{
-    return sig.args().sizeOfExcludingThis(mallocSizeOf);
-}
-
 size_t
 FuncExport::serializedSize() const
 {
-    return SerializedSigSize(sig_) +
+    return sig_.serializedSize() +
            sizeof(pod);
 }
 
 uint8_t*
 FuncExport::serialize(uint8_t* cursor) const
 {
-    cursor = SerializeSig(cursor, sig_);
+    cursor = sig_.serialize(cursor);
     cursor = WriteBytes(cursor, &pod, sizeof(pod));
     return cursor;
 }
 
 const uint8_t*
 FuncExport::deserialize(const uint8_t* cursor)
 {
-    (cursor = DeserializeSig(cursor, &sig_)) &&
+    (cursor = sig_.deserialize(cursor)) &&
     (cursor = ReadBytes(cursor, &pod, sizeof(pod)));
     return cursor;
 }
 
 size_t
 FuncExport::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const
 {
-    return SizeOfSigExcludingThis(sig_, mallocSizeOf);
+    return sig_.sizeOfExcludingThis(mallocSizeOf);
 }
 
 size_t
 FuncImport::serializedSize() const
 {
-    return SerializedSigSize(sig_) +
+    return sig_.serializedSize() +
            sizeof(pod);
 }
 
 uint8_t*
 FuncImport::serialize(uint8_t* cursor) const
 {
-    cursor = SerializeSig(cursor, sig_);
+    cursor = sig_.serialize(cursor);
     cursor = WriteBytes(cursor, &pod, sizeof(pod));
     return cursor;
 }
 
 const uint8_t*
 FuncImport::deserialize(const uint8_t* cursor)
 {
-    (cursor = DeserializeSig(cursor, &sig_)) &&
+    (cursor = sig_.deserialize(cursor)) &&
     (cursor = ReadBytes(cursor, &pod, sizeof(pod)));
     return cursor;
 }
 
 size_t
 FuncImport::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const
 {
-    return SizeOfSigExcludingThis(sig_, mallocSizeOf);
+    return sig_.sizeOfExcludingThis(mallocSizeOf);
 }
 
 CodeRange::CodeRange(Kind kind, Offsets offsets)
   : begin_(offsets.begin),
     profilingReturn_(0),
     end_(offsets.end),
     funcIndex_(0),
     funcLineOrBytecode_(0),
@@ -447,16 +411,17 @@ CacheableChars::sizeOfExcludingThis(Mall
 }
 
 size_t
 Metadata::serializedSize() const
 {
     return sizeof(pod()) +
            SerializedVectorSize(funcImports) +
            SerializedVectorSize(funcExports) +
+           SerializedVectorSize(sigIds) +
            SerializedPodVectorSize(tables) +
            SerializedPodVectorSize(memoryAccesses) +
            SerializedPodVectorSize(boundsChecks) +
            SerializedPodVectorSize(codeRanges) +
            SerializedPodVectorSize(callSites) +
            SerializedPodVectorSize(callThunks) +
            SerializedPodVectorSize(funcNames) +
            filename.serializedSize() +
@@ -464,16 +429,17 @@ Metadata::serializedSize() const
 }
 
 uint8_t*
 Metadata::serialize(uint8_t* cursor) const
 {
     cursor = WriteBytes(cursor, &pod(), sizeof(pod()));
     cursor = SerializeVector(cursor, funcImports);
     cursor = SerializeVector(cursor, funcExports);
+    cursor = SerializeVector(cursor, sigIds);
     cursor = SerializePodVector(cursor, tables);
     cursor = SerializePodVector(cursor, memoryAccesses);
     cursor = SerializePodVector(cursor, boundsChecks);
     cursor = SerializePodVector(cursor, codeRanges);
     cursor = SerializePodVector(cursor, callSites);
     cursor = SerializePodVector(cursor, callThunks);
     cursor = SerializePodVector(cursor, funcNames);
     cursor = filename.serialize(cursor);
@@ -482,16 +448,17 @@ Metadata::serialize(uint8_t* cursor) con
 }
 
 /* static */ const uint8_t*
 Metadata::deserialize(const uint8_t* cursor)
 {
     (cursor = ReadBytes(cursor, &pod(), sizeof(pod()))) &&
     (cursor = DeserializeVector(cursor, &funcImports)) &&
     (cursor = DeserializeVector(cursor, &funcExports)) &&
+    (cursor = DeserializeVector(cursor, &sigIds)) &&
     (cursor = DeserializePodVector(cursor, &tables)) &&
     (cursor = DeserializePodVector(cursor, &memoryAccesses)) &&
     (cursor = DeserializePodVector(cursor, &boundsChecks)) &&
     (cursor = DeserializePodVector(cursor, &codeRanges)) &&
     (cursor = DeserializePodVector(cursor, &callSites)) &&
     (cursor = DeserializePodVector(cursor, &callThunks)) &&
     (cursor = DeserializePodVector(cursor, &funcNames)) &&
     (cursor = filename.deserialize(cursor)) &&
@@ -499,16 +466,17 @@ Metadata::deserialize(const uint8_t* cur
     return cursor;
 }
 
 size_t
 Metadata::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const
 {
     return SizeOfVectorExcludingThis(funcImports, mallocSizeOf) +
            SizeOfVectorExcludingThis(funcExports, mallocSizeOf) +
+           SizeOfVectorExcludingThis(sigIds, mallocSizeOf) +
            tables.sizeOfExcludingThis(mallocSizeOf) +
            memoryAccesses.sizeOfExcludingThis(mallocSizeOf) +
            boundsChecks.sizeOfExcludingThis(mallocSizeOf) +
            codeRanges.sizeOfExcludingThis(mallocSizeOf) +
            callSites.sizeOfExcludingThis(mallocSizeOf) +
            callThunks.sizeOfExcludingThis(mallocSizeOf) +
            funcNames.sizeOfExcludingThis(mallocSizeOf) +
            filename.sizeOfExcludingThis(mallocSizeOf) +
--- a/js/src/asmjs/WasmCode.h
+++ b/js/src/asmjs/WasmCode.h
@@ -461,16 +461,17 @@ struct Metadata : ShareableBase<Metadata
 {
     virtual ~Metadata() {}
 
     MetadataCacheablePod& pod() { return *this; }
     const MetadataCacheablePod& pod() const { return *this; }
 
     FuncImportVector      funcImports;
     FuncExportVector      funcExports;
+    SigWithIdVector       sigIds;
     TableDescVector       tables;
     MemoryAccessVector    memoryAccesses;
     BoundsCheckVector     boundsChecks;
     CodeRangeVector       codeRanges;
     CallSiteVector        callSites;
     CallThunkVector       callThunks;
     NameInBytecodeVector  funcNames;
     CacheableChars        filename;
--- a/js/src/asmjs/WasmCompile.cpp
+++ b/js/src/asmjs/WasmCompile.cpp
@@ -545,17 +545,17 @@ DecodeTypeSection(Decoder& d, ModuleGene
 
     if (!d.finishSection(sectionStart, sectionSize))
         return Fail(d, "decls section byte size mismatch");
 
     return true;
 }
 
 static bool
-DecodeSignatureIndex(Decoder& d, const ModuleGeneratorData& init, const DeclaredSig** sig)
+DecodeSignatureIndex(Decoder& d, const ModuleGeneratorData& init, const SigWithId** sig)
 {
     uint32_t sigIndex;
     if (!d.readVarU32(&sigIndex))
         return Fail(d, "expected signature index");
 
     if (sigIndex >= init.sigs.length())
         return Fail(d, "signature index out of range");
 
@@ -718,17 +718,17 @@ DecodeResizableTable(Decoder& d, ModuleG
     table.maximum = resizable.maximum ? *resizable.maximum : UINT32_MAX;
     return init->tables.append(table);
 }
 
 static bool
 DecodeImport(Decoder& d, bool newFormat, ModuleGeneratorData* init, ImportVector* imports)
 {
     if (!newFormat) {
-        const DeclaredSig* sig = nullptr;
+        const SigWithId* sig = nullptr;
         if (!DecodeSignatureIndex(d, *init, &sig))
             return false;
 
         if (!CheckTypeForJS(d, *sig))
             return false;
 
         if (!init->funcImports.emplaceBack(sig))
             return false;
@@ -759,17 +759,17 @@ DecodeImport(Decoder& d, bool newFormat,
         return Fail(d, "expected valid import func name");
 
     uint32_t importKind;
     if (!d.readVarU32(&importKind))
         return Fail(d, "failed to read import kind");
 
     switch (DefinitionKind(importKind)) {
       case DefinitionKind::Function: {
-        const DeclaredSig* sig = nullptr;
+        const SigWithId* sig = nullptr;
         if (!DecodeSignatureIndex(d, *init, &sig))
             return false;
         if (!CheckTypeForJS(d, *sig))
             return false;
         if (!init->funcImports.emplaceBack(sig))
             return false;
         break;
       }
@@ -1062,17 +1062,17 @@ DecodeFunctionBody(Decoder& d, ModuleGen
     const uint8_t* bodyBegin = d.currentPosition();
     const uint8_t* bodyEnd = bodyBegin + bodySize;
 
     FunctionGenerator fg;
     if (!mg.startFuncDef(d.currentOffset(), &fg))
         return false;
 
     ValTypeVector locals;
-    const DeclaredSig& sig = mg.funcSig(funcIndex);
+    const Sig& sig = mg.funcSig(funcIndex);
     if (!locals.appendAll(sig.args()))
         return false;
 
     if (!DecodeLocalEntries(d, &locals))
         return Fail(d, "failed decoding local entries");
 
     for (ValType type : locals) {
         if (!CheckValType(d, type))
@@ -1115,17 +1115,17 @@ DecodeStartSection(Decoder& d, ModuleGen
 
     uint32_t startFuncIndex;
     if (!d.readVarU32(&startFuncIndex))
         return Fail(d, "failed to read start func index");
 
     if (startFuncIndex >= mg.numFuncSigs())
         return Fail(d, "unknown start function");
 
-    const DeclaredSig& sig = mg.funcSig(startFuncIndex);
+    const Sig& sig = mg.funcSig(startFuncIndex);
     if (sig.ret() != ExprType::Void)
         return Fail(d, "start function must not return anything");
 
     if (sig.args().length())
         return Fail(d, "start function must be nullary");
 
     if (!mg.setStartFunction(startFuncIndex))
         return false;
--- a/js/src/asmjs/WasmFrameIterator.cpp
+++ b/js/src/asmjs/WasmFrameIterator.cpp
@@ -326,17 +326,17 @@ 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, ToggleProfiling patches all callsites to either call the
 // profiling or non-profiling entry point.
 void
-wasm::GenerateFunctionPrologue(MacroAssembler& masm, unsigned framePushed, uint32_t sigIndex,
+wasm::GenerateFunctionPrologue(MacroAssembler& masm, unsigned framePushed, const SigIdDesc& sigId,
                                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
 
@@ -344,18 +344,31 @@ wasm::GenerateFunctionPrologue(MacroAsse
 
     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);
+    switch (sigId.kind()) {
+      case SigIdDesc::Kind::Global: {
+        Register scratch = WasmTableCallPtrReg; // clobbered by the indirect call
+        masm.loadWasmGlobalPtr(sigId.globalDataOffset(), scratch);
+        masm.branch32(Assembler::Condition::NotEqual, WasmTableCallSigReg, scratch,
+                      JumpTarget::BadIndirectCall);
+        break;
+      }
+      case SigIdDesc::Kind::Immediate:
+        masm.branch32(Assembler::Condition::NotEqual, WasmTableCallSigReg, Imm32(sigId.immediate()),
+                      JumpTarget::BadIndirectCall);
+        break;
+      case SigIdDesc::Kind::None:
+        break;
+    }
     offsets->tableProfilingJump = masm.nopPatchableToNearJump().offset();
 
     // Generate normal prologue:
     masm.nopAlign(CodeAlignment);
     offsets->nonProfilingEntry = masm.currentOffset();
     PushRetAddr(masm);
     masm.subFromStackPtr(Imm32(framePushed + AsmJSFrameBytesAfterReturnAddress));
 
--- a/js/src/asmjs/WasmFrameIterator.h
+++ b/js/src/asmjs/WasmFrameIterator.h
@@ -28,16 +28,17 @@ namespace js {
 class WasmActivation;
 namespace jit { class MacroAssembler; class Label; }
 
 namespace wasm {
 
 class CallSite;
 class CodeRange;
 class Instance;
+class SigIdDesc;
 struct CallThunk;
 struct FuncOffsets;
 struct Metadata;
 struct ProfilingOffsets;
 
 // Iterates over the frames of a single WasmActivation, called synchronously
 // from C++ in the thread of the asm.js.
 //
@@ -106,17 +107,17 @@ class ProfilingFrameIterator
 
 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, uint32_t sigIndex,
+GenerateFunctionPrologue(jit::MacroAssembler& masm, unsigned framePushed, const SigIdDesc& sigId,
                          FuncOffsets* offsets);
 void
 GenerateFunctionEpilogue(jit::MacroAssembler& masm, unsigned framePushed, FuncOffsets* offsets);
 
 // Runtime patching to enable/disable profiling
 
 void
 ToggleProfiling(const Instance& instance, const CallSite& callSite, bool enabled);
--- a/js/src/asmjs/WasmGenerator.cpp
+++ b/js/src/asmjs/WasmGenerator.cpp
@@ -147,16 +147,36 @@ ModuleGenerator::init(UniqueModuleGenera
             if (!addFuncImport(*funcImport.sig, funcImport.globalDataOffset))
                 return false;
         }
 
         for (TableDesc& table : shared_->tables) {
             if (!allocateGlobalBytes(sizeof(void*), sizeof(void*), &table.globalDataOffset))
                 return false;
         }
+
+        for (uint32_t i = 0; i < numSigs_; i++) {
+            SigWithId& sig = shared_->sigs[i];
+            if (SigIdDesc::isGlobal(sig)) {
+                uint32_t globalDataOffset;
+                if (!allocateGlobalBytes(sizeof(void*), sizeof(void*), &globalDataOffset))
+                    return false;
+
+                sig.id = SigIdDesc::global(sig, globalDataOffset);
+
+                Sig copy;
+                if (!copy.clone(sig))
+                    return false;
+
+                if (!metadata_->sigIds.emplaceBack(Move(copy), sig.id))
+                    return false;
+            } else {
+                sig.id = SigIdDesc::immediate(sig);
+            }
+        }
     } else {
         MOZ_ASSERT(shared_->sigs.length() == MaxSigs);
         MOZ_ASSERT(shared_->tables.length() == MaxTables);
         MOZ_ASSERT(shared_->asmJSSigToTableIndex.length() == MaxSigs);
     }
 
     return true;
 }
@@ -336,19 +356,16 @@ ModuleGenerator::finishTask(IonCompileTa
     if (!masm_.asmMergeWith(results.masm()))
         return false;
     MOZ_ASSERT(masm_.size() == offsetInWhole + results.masm().size());
 
     freeTasks_.infallibleAppend(task);
     return true;
 }
 
-typedef Vector<Offsets, 0, SystemAllocPolicy> OffsetVector;
-typedef Vector<ProfilingOffsets, 0, SystemAllocPolicy> ProfilingOffsetVector;
-
 bool
 ModuleGenerator::finishFuncExports()
 {
     // ModuleGenerator::exportedFuncs_ is an unordered HashSet. The
     // FuncExportVector stored in Metadata needs to be stored sorted by
     // function index to allow O(log(n)) lookup at runtime.
 
     Uint32Vector funcIndices;
@@ -371,16 +388,19 @@ ModuleGenerator::finishFuncExports()
 
         uint32_t tableEntry = funcCodeRange(funcIndex).funcTableEntry();
         metadata_->funcExports.infallibleEmplaceBack(Move(sig), funcIndex, tableEntry);
     }
 
     return true;
 }
 
+typedef Vector<Offsets, 0, SystemAllocPolicy> OffsetVector;
+typedef Vector<ProfilingOffsets, 0, SystemAllocPolicy> ProfilingOffsetVector;
+
 bool
 ModuleGenerator::finishCodegen()
 {
     uint32_t offsetInWhole = masm_.size();
 
     uint32_t numFuncExports = metadata_->funcExports.length();
     MOZ_ASSERT(numFuncExports == exportedFuncs_.count());
 
@@ -611,17 +631,17 @@ ModuleGenerator::initSig(uint32_t sigInd
     MOZ_ASSERT(isAsmJS());
     MOZ_ASSERT(sigIndex == numSigs_);
     numSigs_++;
 
     MOZ_ASSERT(shared_->sigs[sigIndex] == Sig());
     shared_->sigs[sigIndex] = Move(sig);
 }
 
-const DeclaredSig&
+const SigWithId&
 ModuleGenerator::sig(uint32_t index) const
 {
     MOZ_ASSERT(index < numSigs_);
     return shared_->sigs[index];
 }
 
 void
 ModuleGenerator::initFuncSig(uint32_t funcIndex, uint32_t sigIndex)
@@ -645,17 +665,17 @@ void
 ModuleGenerator::bumpMinMemoryLength(uint32_t newMinMemoryLength)
 {
     MOZ_ASSERT(isAsmJS());
     MOZ_ASSERT(newMinMemoryLength >= shared_->minMemoryLength);
 
     shared_->minMemoryLength = newMinMemoryLength;
 }
 
-const DeclaredSig&
+const SigWithId&
 ModuleGenerator::funcSig(uint32_t funcIndex) const
 {
     MOZ_ASSERT(shared_->funcSigs[funcIndex]);
     return *shared_->funcSigs[funcIndex];
 }
 
 bool
 ModuleGenerator::initImport(uint32_t funcImportIndex, uint32_t sigIndex)
--- a/js/src/asmjs/WasmGenerator.h
+++ b/js/src/asmjs/WasmGenerator.h
@@ -33,44 +33,40 @@ class FunctionGenerator;
 // ModuleGenerator thread and background compile threads. The background
 // threads are given a read-only view of the ModuleGeneratorData and the
 // ModuleGenerator is careful to initialize, and never subsequently mutate,
 // any given datum before being read by a background thread. In particular,
 // once created, the Vectors are never resized.
 
 struct FuncImportGenDesc
 {
-    const DeclaredSig* sig;
+    const SigWithId* sig;
     uint32_t globalDataOffset;
 
     FuncImportGenDesc() : sig(nullptr), globalDataOffset(0) {}
-    explicit FuncImportGenDesc(const DeclaredSig* sig) : sig(sig), globalDataOffset(0) {}
+    explicit FuncImportGenDesc(const SigWithId* sig) : sig(sig), globalDataOffset(0) {}
 };
 
 typedef Vector<FuncImportGenDesc, 0, SystemAllocPolicy> FuncImportGenDescVector;
 
 struct ModuleGeneratorData
 {
     ModuleKind                kind;
     SignalUsage               usesSignal;
     MemoryUsage               memoryUsage;
     mozilla::Atomic<uint32_t> minMemoryLength;
     uint32_t                  maxMemoryLength;
 
-    DeclaredSigVector         sigs;
-    DeclaredSigPtrVector      funcSigs;
+    SigWithIdVector           sigs;
+    SigWithIdPtrVector        funcSigs;
     FuncImportGenDescVector   funcImports;
     GlobalDescVector          globals;
     TableDescVector           tables;
     Uint32Vector              asmJSSigToTableIndex;
 
-    uint32_t funcSigIndex(uint32_t funcIndex) const {
-        return funcSigs[funcIndex] - sigs.begin();
-    }
-
     explicit ModuleGeneratorData(SignalUsage usesSignal, ModuleKind kind = ModuleKind::Wasm)
       : kind(kind),
         usesSignal(usesSignal),
         memoryUsage(MemoryUsage::None),
         minMemoryLength(0),
         maxMemoryLength(UINT32_MAX)
     {}
 
@@ -156,21 +152,21 @@ class MOZ_STACK_CLASS ModuleGenerator
     bool usesMemory() const { return UsesMemory(shared_->memoryUsage); }
     uint32_t minMemoryLength() const { return shared_->minMemoryLength; }
 
     // Tables:
     const TableDescVector& tables() const { return shared_->tables; }
 
     // Signatures:
     uint32_t numSigs() const { return numSigs_; }
-    const DeclaredSig& sig(uint32_t sigIndex) const;
+    const SigWithId& sig(uint32_t sigIndex) const;
 
     // Function declarations:
     uint32_t numFuncSigs() const { return shared_->funcSigs.length(); }
-    const DeclaredSig& funcSig(uint32_t funcIndex) const;
+    const SigWithId& funcSig(uint32_t funcIndex) const;
 
     // Globals:
     MOZ_MUST_USE bool allocateGlobal(ValType type, bool isConst, uint32_t* index);
     const GlobalDesc& global(unsigned index) const { return shared_->globals[index]; }
 
     // Imports:
     uint32_t numFuncImports() const;
     const FuncImportGenDesc& funcImport(uint32_t funcImportIndex) const;
--- a/js/src/asmjs/WasmInstance.cpp
+++ b/js/src/asmjs/WasmInstance.cpp
@@ -36,29 +36,89 @@
 #include "vm/ArrayBufferObject-inl.h"
 
 using namespace js;
 using namespace js::jit;
 using namespace js::wasm;
 using mozilla::BinarySearch;
 using mozilla::Swap;
 
+class SigIdSet
+{
+    typedef HashMap<const Sig*, uint32_t, SigHashPolicy, SystemAllocPolicy> Map;
+    Map map_;
+
+  public:
+    ~SigIdSet() {
+        MOZ_ASSERT_IF(!JSRuntime::hasLiveRuntimes(), !map_.initialized() || map_.empty());
+    }
+
+    bool ensureInitialized(JSContext* cx) {
+        if (!map_.initialized() && !map_.init()) {
+            ReportOutOfMemory(cx);
+            return false;
+        }
+
+        return true;
+    }
+
+    bool allocateSigId(JSContext* cx, const Sig& sig, const void** sigId) {
+        Map::AddPtr p = map_.lookupForAdd(sig);
+        if (p) {
+            MOZ_ASSERT(p->value() > 0);
+            p->value()++;
+            *sigId = p->key();
+            return true;
+        }
+
+        UniquePtr<Sig> clone = MakeUnique<Sig>();
+        if (!clone || !clone->clone(sig) || !map_.add(p, clone.get(), 1)) {
+            ReportOutOfMemory(cx);
+            return false;
+        }
+
+        *sigId = clone.release();
+        MOZ_ASSERT(!(uintptr_t(*sigId) & SigIdDesc::ImmediateBit));
+        return true;
+    }
+
+    void deallocateSigId(const Sig& sig, const void* sigId) {
+        Map::Ptr p = map_.lookup(sig);
+        MOZ_RELEASE_ASSERT(p && p->key() == sigId && p->value() > 0);
+
+        p->value()--;
+        if (!p->value()) {
+            js_delete(p->key());
+            map_.remove(p);
+        }
+    }
+};
+
+ExclusiveData<SigIdSet> sigIdSet;
+
 uint8_t**
 Instance::addressOfMemoryBase() const
 {
     return (uint8_t**)(codeSegment_->globalData() + HeapGlobalDataOffset);
 }
 
 void**
 Instance::addressOfTableBase(size_t tableIndex) const
 {
     MOZ_ASSERT(metadata_->tables[tableIndex].globalDataOffset >= InitialGlobalDataBytes);
     return (void**)(codeSegment_->globalData() + metadata_->tables[tableIndex].globalDataOffset);
 }
 
+const void**
+Instance::addressOfSigId(const SigIdDesc& sigId) const
+{
+    MOZ_ASSERT(sigId.globalDataOffset() >= InitialGlobalDataBytes);
+    return (const void**)(codeSegment_->globalData() + sigId.globalDataOffset());
+}
+
 FuncImportExit&
 Instance::funcImportToExit(const FuncImport& fi)
 {
     MOZ_ASSERT(fi.exitGlobalDataOffset() >= InitialGlobalDataBytes);
     return *(FuncImportExit*)(codeSegment_->globalData() + fi.exitGlobalDataOffset());
 }
 
 WasmActivation*&
@@ -383,23 +443,53 @@ Instance::Instance(UniqueCodeSegment cod
 
     if (memory)
         *addressOfMemoryBase() = memory->buffer().dataPointerEither().unwrap();
 
     for (size_t i = 0; i < tables_.length(); i++)
         *addressOfTableBase(i) = tables_[i]->array();
 }
 
+bool
+Instance::init(JSContext* cx)
+{
+    if (!metadata_->sigIds.empty()) {
+        ExclusiveData<SigIdSet>::Guard lockedSigIdSet = sigIdSet.lock();
+
+        if (!lockedSigIdSet->ensureInitialized(cx))
+            return false;
+
+        for (const SigWithId& sig : metadata_->sigIds) {
+            const void* sigId;
+            if (!lockedSigIdSet->allocateSigId(cx, sig, &sigId))
+                return false;
+
+            *addressOfSigId(sig.id) = sigId;
+        }
+    }
+
+    return true;
+}
+
 Instance::~Instance()
 {
     for (unsigned i = 0; i < metadata_->funcImports.length(); i++) {
         FuncImportExit& exit = funcImportToExit(metadata_->funcImports[i]);
         if (exit.baselineScript)
             exit.baselineScript->removeDependentWasmImport(*this, i);
     }
+
+    if (!metadata_->sigIds.empty()) {
+        ExclusiveData<SigIdSet>::Guard lockedSigIdSet = sigIdSet.lock();
+
+        for (const SigWithId& sig : metadata_->sigIds) {
+            if (const void* sigId = *addressOfSigId(sig.id))
+                lockedSigIdSet->deallocateSigId(sig, sigId);
+        }
+    }
 }
 
 void
 Instance::trace(JSTracer* trc)
 {
     for (const FuncImport& fi : metadata_->funcImports)
         TraceNullableEdge(trc, &funcImportToExit(fi).fun, "wasm function import");
     TraceNullableEdge(trc, &memory_, "wasm buffer");
--- a/js/src/asmjs/WasmInstance.h
+++ b/js/src/asmjs/WasmInstance.h
@@ -50,16 +50,17 @@ class Instance
     bool                                 profilingEnabled_;
     CacheableCharsVector                 funcLabels_;
 
     UniquePtr<GeneratedSourceMap>        maybeSourceMap_;
 
     // Internal helpers:
     uint8_t** addressOfMemoryBase() const;
     void** addressOfTableBase(size_t tableIndex) const;
+    const void** addressOfSigId(const SigIdDesc& sigId) const;
     FuncImportExit& funcImportToExit(const FuncImport& fi);
     MOZ_MUST_USE bool toggleProfiling(JSContext* cx);
 
     // An instance keeps track of its innermost WasmActivation. A WasmActivation
     // is pushed for the duration of each call of an export.
     friend class js::WasmActivation;
     WasmActivation*& activation();
 
@@ -75,16 +76,17 @@ class Instance
   public:
     Instance(UniqueCodeSegment codeSegment,
              const Metadata& metadata,
              const ShareableBytes* maybeBytecode,
              HandleWasmMemoryObject memory,
              SharedTableVector&& tables,
              Handle<FunctionVector> funcImports);
     ~Instance();
+    bool init(JSContext* cx);
     void trace(JSTracer* trc);
 
     const CodeSegment& codeSegment() const { return *codeSegment_; }
     const Metadata& metadata() const { return *metadata_; }
     const SharedTableVector& tables() const { return tables_; }
     SharedMem<uint8_t*> memoryBase() const;
     size_t memoryLength() const;
 
--- a/js/src/asmjs/WasmIonCompile.cpp
+++ b/js/src/asmjs/WasmIonCompile.cpp
@@ -896,20 +896,21 @@ class FunctionCompiler
             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);
             MInstruction* ptrFun = MAsmJSLoadFuncPtr::New(alloc(), maskedIndex, globalDataOffset);
             curBlock_->add(ptrFun);
             callee = MAsmJSCall::Callee(ptrFun);
+            MOZ_ASSERT(mg_.sigs[sigIndex].id.kind() == SigIdDesc::Kind::None);
         } else {
             MInstruction* ptrFun = MAsmJSLoadFuncPtr::New(alloc(), index, length, globalDataOffset);
             curBlock_->add(ptrFun);
-            callee = MAsmJSCall::Callee(ptrFun, sigIndex);
+            callee = MAsmJSCall::Callee(ptrFun, mg_.sigs[sigIndex].id);
         }
 
         return callPrivate(callee, args, mg_.sigs[sigIndex].ret(), def);
     }
 
     bool ffiCall(unsigned globalDataOffset, const CallArgs& args, ExprType ret, MDefinition** def)
     {
         if (inDeadCode()) {
@@ -3379,20 +3380,20 @@ wasm::IonCompileFunction(IonCompileTask*
 
         if (!OptimizeMIR(&mir))
             return false;
 
         LIRGraph* lir = GenerateLIR(&mir);
         if (!lir)
             return false;
 
-        uint32_t sigIndex = task->mg().funcSigIndex(func.index());
+        SigIdDesc sigId = task->mg().funcSigs[func.index()]->id;
 
         CodeGenerator codegen(&mir, lir, &results.masm());
-        if (!codegen.generateWasm(sigIndex, &results.offsets()))
+        if (!codegen.generateWasm(sigId, &results.offsets()))
             return false;
     }
 
     return true;
 }
 
 bool
 wasm::CompileFunction(IonCompileTask* task)
--- a/js/src/asmjs/WasmIonCompile.h
+++ b/js/src/asmjs/WasmIonCompile.h
@@ -32,39 +32,39 @@ typedef jit::ABIArgIter<MIRTypeVector> A
 typedef jit::ABIArgIter<ValTypeVector> ABIArgValTypeIter;
 
 // The FuncBytes class represents a single, concurrently-compilable function.
 // A FuncBytes object is composed of the wasm function body bytes along with the
 // ambient metadata describing the function necessary to compile it.
 
 class FuncBytes
 {
-    Bytes              bytes_;
-    uint32_t           index_;
-    const DeclaredSig& sig_;
-    uint32_t           lineOrBytecode_;
-    Uint32Vector       callSiteLineNums_;
+    Bytes            bytes_;
+    uint32_t         index_;
+    const SigWithId& sig_;
+    uint32_t         lineOrBytecode_;
+    Uint32Vector     callSiteLineNums_;
 
   public:
     FuncBytes(Bytes&& bytes,
               uint32_t index,
-              const DeclaredSig& sig,
+              const SigWithId& sig,
               uint32_t lineOrBytecode,
               Uint32Vector&& callSiteLineNums)
       : bytes_(Move(bytes)),
         index_(index),
         sig_(sig),
         lineOrBytecode_(lineOrBytecode),
         callSiteLineNums_(Move(callSiteLineNums))
     {}
 
     Bytes& bytes() { return bytes_; }
     const Bytes& bytes() const { return bytes_; }
     uint32_t index() const { return index_; }
-    const DeclaredSig& sig() const { return sig_; }
+    const SigWithId& sig() const { return sig_; }
     uint32_t lineOrBytecode() const { return lineOrBytecode_; }
     const Uint32Vector& callSiteLineNums() const { return callSiteLineNums_; }
 };
 
 typedef UniquePtr<FuncBytes> UniqueFuncBytes;
 
 // The FuncCompileResults class contains the results of compiling a single
 // function body, ready to be merged into the whole-module MacroAssembler.
--- a/js/src/asmjs/WasmModule.cpp
+++ b/js/src/asmjs/WasmModule.cpp
@@ -437,18 +437,18 @@ Module::instantiateMemory(JSContext* cx,
     uint8_t* memoryBase = memory->buffer().dataPointerEither().unwrap(/* memcpy */);
     for (const DataSegment& seg : dataSegments_)
         memcpy(memoryBase + seg.memoryOffset, bytecode_->begin() + seg.bytecodeOffset, seg.length);
 
     return true;
 }
 
 bool
-Module::instantiateTable(JSContext* cx, const CodeSegment& codeSegment,
-                         HandleWasmTableObject tableImport, SharedTableVector* tables) const
+Module::instantiateTable(JSContext* cx, HandleWasmTableObject tableImport,
+                         SharedTableVector* tables) const
 {
     for (const TableDesc& tableDesc : metadata_->tables) {
         SharedTable table;
         if (tableImport) {
             table = &tableImport->table();
             if (table->length() < tableDesc.initial || table->length() > tableDesc.maximum) {
                 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_IMP_SIZE, "Table");
                 return false;
@@ -546,17 +546,17 @@ Module::instantiate(JSContext* cx,
     if (!instantiateMemory(cx, &memory))
         return false;
 
     auto codeSegment = CodeSegment::create(cx, code_, linkData_, *metadata_, memory);
     if (!codeSegment)
         return false;
 
     SharedTableVector tables;
-    if (!instantiateTable(cx, *codeSegment, tableImport, &tables))
+    if (!instantiateTable(cx, tableImport, &tables))
         return false;
 
     // To support viewing the source of an instance (Instance::createText), the
     // instance must hold onto a ref of the bytecode (keeping it alive). This
     // wastes memory for most users, so we try to only save the source when a
     // developer actually cares: when the compartment is debuggable (which is
     // true when the web console is open) or a names section is present (since
     // this going to be stripped for non-developer builds).
@@ -581,16 +581,19 @@ Module::instantiate(JSContext* cx,
                                                   Move(tables),
                                                   funcImports);
         if (!instance)
             return false;
 
         instanceObj->init(Move(instance));
     }
 
+    if (!instanceObj->instance().init(cx))
+        return false;
+
     // Create the export object.
 
     RootedObject exportObj(cx);
     RootedWasmTableObject table(cx, tableImport);
     if (!CreateExportObject(cx, instanceObj, &table, memory, exports_, &exportObj))
         return false;
 
     JSAtom* atom = Atomize(cx, InstanceExportField, strlen(InstanceExportField));
--- a/js/src/asmjs/WasmModule.h
+++ b/js/src/asmjs/WasmModule.h
@@ -181,18 +181,18 @@ class Module : public RefCounted<Module>
     const ImportVector      imports_;
     const ExportVector      exports_;
     const DataSegmentVector dataSegments_;
     const ElemSegmentVector elemSegments_;
     const SharedMetadata    metadata_;
     const SharedBytes       bytecode_;
 
     bool instantiateMemory(JSContext* cx, MutableHandleWasmMemoryObject memory) const;
-    bool instantiateTable(JSContext* cx, const CodeSegment& codeSegment,
-                          HandleWasmTableObject tableImport, SharedTableVector* tables) const;
+    bool instantiateTable(JSContext* cx, HandleWasmTableObject tableImport,
+                          SharedTableVector* tables) const;
     bool initElems(JSContext* cx, HandleWasmInstanceObject instanceObj,
                    HandleWasmTableObject tableObj) const;
 
   public:
     Module(Bytes&& code,
            LinkData&& linkData,
            ImportVector&& imports,
            ExportVector&& exports,
--- a/js/src/asmjs/WasmTypes.cpp
+++ b/js/src/asmjs/WasmTypes.cpp
@@ -325,16 +325,164 @@ GetCPUID()
     return MIPS64 | (jit::GetMIPSFlags() << ARCH_BITS);
 #elif defined(JS_CODEGEN_NONE)
     return 0;
 #else
 # error "unknown architecture"
 #endif
 }
 
+typedef uint32_t ImmediateType;  // for 32/64 consistency
+static const unsigned sImmediateBits = sizeof(ImmediateType) * 8 - 1;  // -1 for ImmediateBit
+static const unsigned sReturnBit = 1;
+static const unsigned sLengthBits = 4;
+static const unsigned sTypeBits = 2;
+static const unsigned sMaxTypes = (sImmediateBits - sReturnBit - sLengthBits) / sTypeBits;
+
+static bool
+IsImmediateType(ValType vt)
+{
+    MOZ_ASSERT(uint32_t(vt) > 0);
+    return (uint32_t(vt) - 1) < (1 << sTypeBits);
+}
+
+static bool
+IsImmediateType(ExprType et)
+{
+    return et == ExprType::Void || IsImmediateType(NonVoidToValType(et));
+}
+
+/* static */ bool
+SigIdDesc::isGlobal(const Sig& sig)
+{
+    unsigned numTypes = (sig.ret() == ExprType::Void ? 0 : 1) +
+                        (sig.args().length());
+    if (numTypes > sMaxTypes)
+        return true;
+
+    if (!IsImmediateType(sig.ret()))
+        return true;
+
+    for (ValType v : sig.args()) {
+        if (!IsImmediateType(v))
+            return true;
+    }
+
+    return false;
+}
+
+/* static */ SigIdDesc
+SigIdDesc::global(const Sig& sig, uint32_t globalDataOffset)
+{
+    MOZ_ASSERT(isGlobal(sig));
+    return SigIdDesc(Kind::Global, globalDataOffset);
+}
+
+static ImmediateType
+LengthToBits(uint32_t length)
+{
+    static_assert(sMaxTypes <= ((1 << sLengthBits) - 1), "fits");
+    MOZ_ASSERT(length <= sMaxTypes);
+    return length;
+}
+
+static ImmediateType
+TypeToBits(ValType type)
+{
+    static_assert(3 <= ((1 << sTypeBits) - 1), "fits");
+    MOZ_ASSERT(uint32_t(type) >= 1 && uint32_t(type) <= 4);
+    return uint32_t(type) - 1;
+}
+
+size_t
+Sig::serializedSize() const
+{
+    return sizeof(ret_) +
+           SerializedPodVectorSize(args_);
+}
+
+uint8_t*
+Sig::serialize(uint8_t* cursor) const
+{
+    cursor = WriteScalar<ExprType>(cursor, ret_);
+    cursor = SerializePodVector(cursor, args_);
+    return cursor;
+}
+
+const uint8_t*
+Sig::deserialize(const uint8_t* cursor)
+{
+    (cursor = ReadScalar<ExprType>(cursor, &ret_)) &&
+    (cursor = DeserializePodVector(cursor, &args_));
+    return cursor;
+}
+
+size_t
+Sig::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const
+{
+    return args_.sizeOfExcludingThis(mallocSizeOf);
+}
+
+/* static */ SigIdDesc
+SigIdDesc::immediate(const Sig& sig)
+{
+    ImmediateType immediate = ImmediateBit;
+    uint32_t shift = 1;
+
+    if (sig.ret() != ExprType::Void) {
+        immediate |= (1 << shift);
+        shift += sReturnBit;
+
+        immediate |= TypeToBits(NonVoidToValType(sig.ret())) << shift;
+        shift += sTypeBits;
+    } else {
+        shift += sReturnBit;
+    }
+
+    immediate |= LengthToBits(sig.args().length()) << shift;
+    shift += sLengthBits;
+
+    for (ValType argType : sig.args()) {
+        immediate |= TypeToBits(argType) << shift;
+        shift += sTypeBits;
+    }
+
+    MOZ_ASSERT(shift <= sImmediateBits);
+    return SigIdDesc(Kind::Immediate, immediate);
+}
+
+size_t
+SigWithId::serializedSize() const
+{
+    return Sig::serializedSize() +
+           sizeof(id);
+}
+
+uint8_t*
+SigWithId::serialize(uint8_t* cursor) const
+{
+    cursor = Sig::serialize(cursor);
+    cursor = WriteBytes(cursor, &id, sizeof(id));
+    return cursor;
+}
+
+const uint8_t*
+SigWithId::deserialize(const uint8_t* cursor)
+{
+    (cursor = Sig::deserialize(cursor)) &&
+    (cursor = ReadBytes(cursor, &id, sizeof(id)));
+    return cursor;
+}
+
+size_t
+SigWithId::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const
+{
+    return Sig::sizeOfExcludingThis(mallocSizeOf);
+}
+
 Assumptions::Assumptions(JS::BuildIdCharVector&& buildId)
   : usesSignal(),
     cpuId(GetCPUID()),
     buildId(Move(buildId)),
     newFormat(false)
 {}
 
 Assumptions::Assumptions()
--- a/js/src/asmjs/WasmTypes.h
+++ b/js/src/asmjs/WasmTypes.h
@@ -397,56 +397,97 @@ class Sig
         return AddContainerToHash(args_, HashNumber(ret_));
     }
     bool operator==(const Sig& rhs) const {
         return ret() == rhs.ret() && EqualContainers(args(), rhs.args());
     }
     bool operator!=(const Sig& rhs) const {
         return !(*this == rhs);
     }
+
+    WASM_DECLARE_SERIALIZABLE(Sig)
 };
 
 struct SigHashPolicy
 {
     typedef const Sig& Lookup;
     static HashNumber hash(Lookup sig) { return sig.hash(); }
     static bool match(const Sig* lhs, Lookup rhs) { return *lhs == rhs; }
 };
 
+// SigIdDesc describes a signature id that can be used by call_indirect and
+// table-entry prologues to structurally compare whether the caller and callee's
+// signatures *structurally* match. To handle the general case, a Sig is
+// allocated and stored in a process-wide hash table, so that pointer equality
+// implies structural equality. As an optimization for the 99% case where the
+// Sig has a small number of parameters, the Sig is bit-packed into a uint32
+// immediate value so that integer equality implies structural equality. Both
+// cases can be handled with a single comparison by always setting the LSB for
+// the immediates (the LSB is necessarily 0 for allocated Sig pointers due to
+// alignment).
+
+class SigIdDesc
+{
+  public:
+    enum class Kind { None, Immediate, Global };
+    static const uintptr_t ImmediateBit = 0x1;
+
+  private:
+    Kind kind_;
+    size_t bits_;
+
+    SigIdDesc(Kind kind, size_t bits) : kind_(kind), bits_(bits) {}
+
+  public:
+    Kind kind() const { return kind_; }
+    static bool isGlobal(const Sig& sig);
+
+    SigIdDesc() : kind_(Kind::None), bits_(0) {}
+    static SigIdDesc global(const Sig& sig, uint32_t globalDataOffset);
+    static SigIdDesc immediate(const Sig& sig);
+
+    bool isGlobal() const { return kind_ == Kind::Global; }
+
+    size_t immediate() const { MOZ_ASSERT(kind_ == Kind::Immediate); return bits_; }
+    uint32_t globalDataOffset() const { MOZ_ASSERT(kind_ == Kind::Global); return bits_; }
+};
+
+// SigWithId pairs a Sig with SigIdDesc, describing either how to compile code
+// that compares this signature's id or, at instantiation what signature ids to
+// allocate in the global hash and where to put them.
+
+struct SigWithId : Sig
+{
+    SigIdDesc id;
+
+    SigWithId() = default;
+    explicit SigWithId(Sig&& sig, SigIdDesc id) : Sig(Move(sig)), id(id) {}
+    void operator=(Sig&& rhs) { Sig::operator=(Move(rhs)); }
+
+    WASM_DECLARE_SERIALIZABLE(SigWithId)
+};
+
+typedef Vector<SigWithId, 0, SystemAllocPolicy> SigWithIdVector;
+typedef Vector<const SigWithId*, 0, SystemAllocPolicy> SigWithIdPtrVector;
+
 // A GlobalDesc describes a single global variable. Currently, globals are only
 // exposed through asm.js.
 
 struct GlobalDesc
 {
     ValType type;
     unsigned globalDataOffset;
     bool isConst;
     GlobalDesc(ValType type, unsigned offset, bool isConst)
       : type(type), globalDataOffset(offset), isConst(isConst)
     {}
 };
 
 typedef Vector<GlobalDesc, 0, SystemAllocPolicy> GlobalDescVector;
 
-// A "declared" signature is a Sig object that is created and owned by the
-// ModuleGenerator. These signature objects are read-only and have the same
-// lifetime as the ModuleGenerator. This type is useful since some uses of Sig
-// need this extended lifetime and want to statically distinguish from the
-// common stack-allocated Sig objects that get passed around.
-
-struct DeclaredSig : Sig
-{
-    DeclaredSig() = default;
-    explicit DeclaredSig(Sig&& sig) : Sig(Move(sig)) {}
-    void operator=(Sig&& rhs) { Sig::operator=(Move(rhs)); }
-};
-
-typedef Vector<DeclaredSig, 0, SystemAllocPolicy> DeclaredSigVector;
-typedef Vector<const DeclaredSig*, 0, SystemAllocPolicy> DeclaredSigPtrVector;
-
 // The (,Profiling,Func)Offsets classes are used to record the offsets of
 // different key points in a CodeRange during compilation.
 
 struct Offsets
 {
     explicit Offsets(uint32_t begin = 0, uint32_t end = 0)
       : begin(begin), end(end)
     {}
--- a/js/src/jit-test/tests/wasm/spec/func_ptrs.wast
+++ b/js/src/jit-test/tests/wasm/spec/func_ptrs.wast
@@ -53,31 +53,31 @@
         (call_indirect $U (get_local $i))
     )
     (export "callu" $callu)
 )
 
 (assert_return (invoke "callt" (i32.const 0)) (i32.const 1))
 (assert_return (invoke "callt" (i32.const 1)) (i32.const 2))
 (assert_return (invoke "callt" (i32.const 2)) (i32.const 3))
-(assert_trap   (invoke "callt" (i32.const 3)) "indirect call signature mismatch")
-(assert_trap   (invoke "callt" (i32.const 4)) "indirect call signature mismatch")
+(assert_return (invoke "callt" (i32.const 3)) (i32.const 4))
+(assert_return (invoke "callt" (i32.const 4)) (i32.const 5))
 (assert_return (invoke "callt" (i32.const 5)) (i32.const 1))
 (assert_return (invoke "callt" (i32.const 6)) (i32.const 3))
 (assert_trap   (invoke "callt" (i32.const 7)) "undefined table index 7")
 (assert_trap   (invoke "callt" (i32.const 100)) "undefined table index 100")
 (assert_trap   (invoke "callt" (i32.const -1)) "undefined table index -1")
 
-(assert_trap   (invoke "callu" (i32.const 0)) "indirect call signature mismatch")
-(assert_trap   (invoke "callu" (i32.const 1)) "indirect call signature mismatch")
-(assert_trap   (invoke "callu" (i32.const 2)) "indirect call signature mismatch")
+(assert_return (invoke "callu" (i32.const 0)) (i32.const 1))
+(assert_return (invoke "callu" (i32.const 1)) (i32.const 2))
+(assert_return (invoke "callu" (i32.const 2)) (i32.const 3))
 (assert_return (invoke "callu" (i32.const 3)) (i32.const 4))
 (assert_return (invoke "callu" (i32.const 4)) (i32.const 5))
-(assert_trap   (invoke "callu" (i32.const 5)) "indirect call signature mismatch")
-(assert_trap   (invoke "callu" (i32.const 6)) "indirect call signature mismatch")
+(assert_return (invoke "callu" (i32.const 5)) (i32.const 1))
+(assert_return (invoke "callu" (i32.const 6)) (i32.const 3))
 (assert_trap   (invoke "callu" (i32.const 7)) "undefined table index 7")
 (assert_trap   (invoke "callu" (i32.const -1)) "undefined table index -1")
 
 (module
     (type $T (func (result i32)))
     (table 0 1)
 
     (import $print_i32 "spectest" "print" (param i32))
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/table-gc.js
@@ -0,0 +1,154 @@
+// |jit-test| --no-baseline
+// Turn off baseline and since it messes up the GC finalization assertions by
+// adding spurious edges to the GC graph.
+
+load(libdir + 'wasm.js');
+load(libdir + 'asserts.js');
+
+const Module = WebAssembly.Module;
+const Instance = WebAssembly.Instance;
+const Table = WebAssembly.Table;
+
+// Explicitly opt into the new binary format for imports and exports until it
+// is used by default everywhere.
+const textToBinary = str => wasmTextToBinary(str, 'new-format');
+const evalText = (str, imports) => new Instance(new Module(textToBinary(str)), imports);
+
+var caller = `(type $v2i (func (result i32))) (func $call (param $i i32) (result i32) (call_indirect $v2i (get_local $i))) (export "call" $call)`
+var callee = i => `(func $f${i} (type $v2i) (result i32) (i32.const ${i}))`;
+
+// A table should not hold exported functions alive and exported functions
+// should not hold their originating table alive. Live exported functions should
+// hold instances alive. Nothing should hold the export object alive.
+resetFinalizeCount();
+var i = evalText(`(module (table (resizable 2)) (export "tbl" table) (elem 0 $f0) ${callee(0)} ${caller})`);
+var e = i.exports;
+var t = e.tbl;
+var f = t.get(0);
+assertEq(f(), e.call(0));
+assertErrorMessage(() => e.call(1), Error, /bad wasm indirect call/);
+assertErrorMessage(() => e.call(2), Error, /out-of-range/);
+assertEq(finalizeCount(), 0);
+i.edge = makeFinalizeObserver();
+e.edge = makeFinalizeObserver();
+t.edge = makeFinalizeObserver();
+f.edge = makeFinalizeObserver();
+gc();
+assertEq(finalizeCount(), 0);
+f = null;
+gc();
+assertEq(finalizeCount(), 1);
+f = t.get(0);
+f.edge = makeFinalizeObserver();
+gc();
+assertEq(finalizeCount(), 1);
+i.exports = null;
+e = null;
+gc();
+assertEq(finalizeCount(), 2);
+t = null;
+gc();
+assertEq(finalizeCount(), 3);
+i = null;
+gc();
+assertEq(finalizeCount(), 3);
+assertEq(f(), 0);
+f = null;
+gc();
+assertEq(finalizeCount(), 5);
+
+// A table should hold the instance of any of its elements alive.
+resetFinalizeCount();
+var i = evalText(`(module (table (resizable 1)) (export "tbl" table) (elem 0 $f0) ${callee(0)} ${caller})`);
+var e = i.exports;
+var t = e.tbl;
+var f = t.get(0);
+i.edge = makeFinalizeObserver();
+e.edge = makeFinalizeObserver();
+t.edge = makeFinalizeObserver();
+f.edge = makeFinalizeObserver();
+gc();
+assertEq(finalizeCount(), 0);
+i.exports = null;
+e = null;
+gc();
+assertEq(finalizeCount(), 1);
+f = null;
+gc();
+assertEq(finalizeCount(), 2);
+i = null;
+gc();
+assertEq(finalizeCount(), 2);
+t = null;
+gc();
+assertEq(finalizeCount(), 4);
+
+// The bad-indirect-call stub should (currently, could be changed later) keep
+// the instance containing that stub alive.
+resetFinalizeCount();
+var i = evalText(`(module (table (resizable 2)) (export "tbl" table) ${caller})`);
+var e = i.exports;
+var t = e.tbl;
+i.edge = makeFinalizeObserver();
+e.edge = makeFinalizeObserver();
+t.edge = makeFinalizeObserver();
+gc();
+assertEq(finalizeCount(), 0);
+i.exports = null;
+e = null;
+gc();
+assertEq(finalizeCount(), 1);
+i = null;
+gc();
+assertEq(finalizeCount(), 1);
+t = null;
+gc();
+assertEq(finalizeCount(), 3);
+
+// Before initialization, a table is not bound to any instance.
+resetFinalizeCount();
+var i = evalText(`(module (func $f0 (result i32) (i32.const 0)) (export "f0" $f0))`);
+var t = new Table({initial:4});
+i.edge = makeFinalizeObserver();
+t.edge = makeFinalizeObserver();
+gc();
+assertEq(finalizeCount(), 0);
+i = null;
+gc();
+assertEq(finalizeCount(), 1);
+t = null;
+gc();
+assertEq(finalizeCount(), 2);
+
+// When a Table is created (uninitialized) and then first assigned, it keeps the
+// first element's Instance alive (as above).
+resetFinalizeCount();
+var i = evalText(`(module (func $f (result i32) (i32.const 42)) (export "f" $f))`);
+var f = i.exports.f;
+var t = new Table({initial:1});
+i.edge = makeFinalizeObserver();
+f.edge = makeFinalizeObserver();
+t.edge = makeFinalizeObserver();
+t.set(0, f);
+assertEq(t.get(0), f);
+assertEq(t.get(0)(), 42);
+gc();
+assertEq(finalizeCount(), 0);
+f = null;
+i.exports = null;
+gc();
+assertEq(finalizeCount(), 1);
+assertEq(t.get(0)(), 42);
+t.get(0).edge = makeFinalizeObserver();
+gc();
+assertEq(finalizeCount(), 2);
+i = null;
+gc();
+assertEq(finalizeCount(), 2);
+t.set(0, null);
+assertEq(t.get(0), null);
+gc();
+assertEq(finalizeCount(), 2);
+t = null;
+gc();
+assertEq(finalizeCount(), 4);
--- a/js/src/jit-test/tests/wasm/tables.js
+++ b/js/src/jit-test/tests/wasm/tables.js
@@ -1,13 +1,9 @@
-// |jit-test| --no-baseline
-
-// Turn off baseline and since it messes up the GC finalization assertions by
-// adding spurious edges to the GC graph.
-
+// |jit-test| test-also-wasm-baseline
 load(libdir + 'wasm.js');
 load(libdir + 'asserts.js');
 
 const Module = WebAssembly.Module;
 const Instance = WebAssembly.Instance;
 const Table = WebAssembly.Table;
 
 // Explicitly opt into the new binary format for imports and exports until it
@@ -55,143 +51,24 @@ assertErrorMessage(() => call(10), Error
 
 var tbl = new Table({initial:3});
 var call = evalText(`(module (import "a" "b" (table 2)) (export "tbl" table) (elem 0 $f0 $f1) ${callee(0)} ${callee(1)} ${caller})`, {a:{b:tbl}}).exports.call;
 assertEq(call(0), 0);
 assertEq(call(1), 1);
 assertEq(tbl.get(0)(), 0);
 assertEq(tbl.get(1)(), 1);
 
-// A table should not hold exported functions alive and exported functions
-// should not hold their originating table alive. Live exported functions should
-// hold instances alive. Nothing should hold the export object alive.
-resetFinalizeCount();
-var i = evalText(`(module (table (resizable 2)) (export "tbl" table) (elem 0 $f0) ${callee(0)} ${caller})`);
-var e = i.exports;
-var t = e.tbl;
-var f = t.get(0);
-assertEq(f(), e.call(0));
-assertErrorMessage(() => e.call(1), Error, /bad wasm indirect call/);
-assertErrorMessage(() => e.call(2), Error, /out-of-range/);
-assertEq(finalizeCount(), 0);
-i.edge = makeFinalizeObserver();
-e.edge = makeFinalizeObserver();
-t.edge = makeFinalizeObserver();
-f.edge = makeFinalizeObserver();
-gc();
-assertEq(finalizeCount(), 0);
-f = null;
-gc();
-assertEq(finalizeCount(), 1);
-f = t.get(0);
-f.edge = makeFinalizeObserver();
-gc();
-assertEq(finalizeCount(), 1);
-i.exports = null;
-e = null;
-gc();
-assertEq(finalizeCount(), 2);
-t = null;
-gc();
-assertEq(finalizeCount(), 3);
-i = null;
-gc();
-assertEq(finalizeCount(), 3);
-assertEq(f(), 0);
-f = null;
-gc();
-assertEq(finalizeCount(), 5);
-
-// A table should hold the instance of any of its elements alive.
-resetFinalizeCount();
-var i = evalText(`(module (table (resizable 1)) (export "tbl" table) (elem 0 $f0) ${callee(0)} ${caller})`);
-var e = i.exports;
-var t = e.tbl;
-var f = t.get(0);
-i.edge = makeFinalizeObserver();
-e.edge = makeFinalizeObserver();
-t.edge = makeFinalizeObserver();
-f.edge = makeFinalizeObserver();
-gc();
-assertEq(finalizeCount(), 0);
-i.exports = null;
-e = null;
-gc();
-assertEq(finalizeCount(), 1);
-f = null;
-gc();
-assertEq(finalizeCount(), 2);
-i = null;
-gc();
-assertEq(finalizeCount(), 2);
-t = null;
-gc();
-assertEq(finalizeCount(), 4);
+// Call signatures are matched structurally:
 
-// The bad-indirect-call stub should (currently, could be changed later) keep
-// the instance containing that stub alive.
-resetFinalizeCount();
-var i = evalText(`(module (table (resizable 2)) (export "tbl" table) ${caller})`);
-var e = i.exports;
-var t = e.tbl;
-i.edge = makeFinalizeObserver();
-e.edge = makeFinalizeObserver();
-t.edge = makeFinalizeObserver();
-gc();
-assertEq(finalizeCount(), 0);
-i.exports = null;
-e = null;
-gc();
-assertEq(finalizeCount(), 1);
-i = null;
-gc();
-assertEq(finalizeCount(), 1);
-t = null;
-gc();
-assertEq(finalizeCount(), 3);
-
-// Before initialization, a table is not bound to any instance.
-resetFinalizeCount();
-var i = evalText(`(module (func $f0 (result i32) (i32.const 0)) (export "f0" $f0))`);
-var t = new Table({initial:4});
-i.edge = makeFinalizeObserver();
-t.edge = makeFinalizeObserver();
-gc();
-assertEq(finalizeCount(), 0);
-i = null;
-gc();
-assertEq(finalizeCount(), 1);
-t = null;
-gc();
-assertEq(finalizeCount(), 2);
-
-// When a Table is created (uninitialized) and then first assigned, it keeps the
-// first element's Instance alive (as above).
-resetFinalizeCount();
-var i = evalText(`(module (func $f (result i32) (i32.const 42)) (export "f" $f))`);
-var f = i.exports.f;
-var t = new Table({initial:1});
-i.edge = makeFinalizeObserver();
-f.edge = makeFinalizeObserver();
-t.edge = makeFinalizeObserver();
-t.set(0, f);
-assertEq(t.get(0), f);
-assertEq(t.get(0)(), 42);
-gc();
-assertEq(finalizeCount(), 0);
-f = null;
-i.exports = null;
-gc();
-assertEq(finalizeCount(), 1);
-assertEq(t.get(0)(), 42);
-t.get(0).edge = makeFinalizeObserver();
-gc();
-assertEq(finalizeCount(), 2);
-i = null;
-gc();
-assertEq(finalizeCount(), 2);
-t.set(0, null);
-assertEq(t.get(0), null);
-gc();
-assertEq(finalizeCount(), 2);
-t = null;
-gc();
-assertEq(finalizeCount(), 4);
+var call = evalText(`(module
+    (type $v2i1 (func (result i32)))
+    (type $v2i2 (func (result i32)))
+    (type $i2v (func (param i32)))
+    (table $a $b $c)
+    (func $a (type $v2i1) (result i32) (i32.const 0))
+    (func $b (type $v2i2) (result i32) (i32.const 1))
+    (func $c (type $i2v) (param i32))
+    (func $call (param i32) (result i32) (call_indirect $v2i1 (get_local 0)))
+    (export "call" $call)
+)`).exports.call;
+assertEq(call(0), 0);
+assertEq(call(1), 1);
+assertErrorMessage(() => call(2), Error, /bad wasm indirect call/);
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -8967,21 +8967,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::generateWasm(uint32_t sigIndex, wasm::FuncOffsets* offsets)
+CodeGenerator::generateWasm(wasm::SigIdDesc sigId, wasm::FuncOffsets* offsets)
 {
     JitSpew(JitSpew_Codegen, "# Emitting asm.js code");
 
-    wasm::GenerateFunctionPrologue(masm, frameSize(), sigIndex, offsets);
+    wasm::GenerateFunctionPrologue(masm, frameSize(), sigId, 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 final : public CodeG
     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 generateWasm(uint32_t sigIndex, wasm::FuncOffsets *offsets);
+    MOZ_MUST_USE bool generateWasm(wasm::SigIdDesc sigId, 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/MIR.h
+++ b/js/src/jit/MIR.h
@@ -13470,57 +13470,55 @@ 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 {
+        union U {
+            U() {}
             uint32_t internal_;
             struct {
                 MDefinition* callee_;
-                uint32_t sigIndex_;
+                wasm::SigIdDesc sigId_;
             } dynamic;
             wasm::SymbolicAddress builtin_;
         } u;
       public:
         Callee() {}
         explicit Callee(uint32_t callee) : which_(Internal) {
             u.internal_ = callee;
         }
-        explicit Callee(MDefinition* callee, uint32_t sigIndex = NoSigIndex) : which_(Dynamic) {
+        explicit Callee(MDefinition* callee, wasm::SigIdDesc sigId = wasm::SigIdDesc())
+          : which_(Dynamic)
+        {
             u.dynamic.callee_ = callee;
-            u.dynamic.sigIndex_ = sigIndex;
+            u.dynamic.sigId_ = sigId;
         }
         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 {
+        wasm::SigIdDesc dynamicSigId() const {
             MOZ_ASSERT(which_ == Dynamic);
-            return u.dynamic.sigIndex_ != NoSigIndex;
-        }
-        uint32_t dynamicSigIndex() const {
-            MOZ_ASSERT(dynamicHasSigIndex());
-            return u.dynamic.sigIndex_;
+            return u.dynamic.sigId_;
         }
         wasm::SymbolicAddress builtin() const {
             MOZ_ASSERT(which_ == Builtin);
             return u.builtin_;
         }
     };
 
   private:
--- a/js/src/jit/MacroAssembler.h
+++ b/js/src/jit/MacroAssembler.h
@@ -878,17 +878,18 @@ class MacroAssembler : public MacroAssem
     inline void popcnt32(Register src, Register dest, Register temp) DEFINED_ON(arm, x86_shared);
 
     // temp may be invalid only if the chip has the POPCNT instruction.
     inline void popcnt64(Register64 src, Register64 dest, Register64 temp) DEFINED_ON(x64);
 
     // ===============================================================
     // Branch functions
 
-    inline void branch32(Condition cond, Register lhs, Register rhs, Label* label) PER_SHARED_ARCH;
+    template <class L>
+    inline void branch32(Condition cond, Register lhs, Register rhs, L label) PER_SHARED_ARCH;
     template <class L>
     inline void branch32(Condition cond, Register lhs, Imm32 rhs, L label) PER_SHARED_ARCH;
     inline void branch32(Condition cond, Register length, const RegisterOrInt32Constant& key,
                          Label* label);
 
     inline void branch32(Condition cond, const Address& lhs, Register rhs, Label* label) PER_SHARED_ARCH;
     inline void branch32(Condition cond, const Address& lhs, Imm32 rhs, Label* label) PER_SHARED_ARCH;
     inline void branch32(Condition cond, const Address& length, const RegisterOrInt32Constant& key,
--- a/js/src/jit/arm/MacroAssembler-arm-inl.h
+++ b/js/src/jit/arm/MacroAssembler-arm-inl.h
@@ -647,18 +647,19 @@ MacroAssembler::popcnt32(Register input,
     as_add(output, output, lsl(output, 8));
     as_add(output, output, lsl(output, 16));
     as_mov(output, asr(output, 24));
 }
 
 // ===============================================================
 // Branch functions
 
+template <class L>
 void
-MacroAssembler::branch32(Condition cond, Register lhs, Register rhs, Label* label)
+MacroAssembler::branch32(Condition cond, Register lhs, Register rhs, L label)
 {
     ma_cmp(lhs, rhs);
     ma_b(label, cond);
 }
 
 template <class L>
 void
 MacroAssembler::branch32(Condition cond, Register lhs, Imm32 rhs, L label)
--- a/js/src/jit/arm64/MacroAssembler-arm64-inl.h
+++ b/js/src/jit/arm64/MacroAssembler-arm64-inl.h
@@ -646,18 +646,19 @@ void
 MacroAssembler::ctz32(Register src, Register dest, bool knownNotZero)
 {
     MOZ_CRASH("NYI: ctz32");
 }
 
 // ===============================================================
 // Branch functions
 
+template <class L>
 void
-MacroAssembler::branch32(Condition cond, Register lhs, Register rhs, Label* label)
+MacroAssembler::branch32(Condition cond, Register lhs, Register rhs, L label)
 {
     cmp32(lhs, rhs);
     B(label, cond);
 }
 
 template <class L>
 void
 MacroAssembler::branch32(Condition cond, Register lhs, Imm32 imm, L label)
--- a/js/src/jit/mips-shared/MacroAssembler-mips-shared-inl.h
+++ b/js/src/jit/mips-shared/MacroAssembler-mips-shared-inl.h
@@ -395,18 +395,19 @@ void
 MacroAssembler::ctz32(Register src, Register dest, bool knownNotZero)
 {
     ma_ctz(dest, src);
 }
 
 // ===============================================================
 // Branch functions
 
+template <class L>
 void
-MacroAssembler::branch32(Condition cond, Register lhs, Register rhs, Label* label)
+MacroAssembler::branch32(Condition cond, Register lhs, Register rhs, L label)
 {
     ma_b(lhs, rhs, label, cond);
 }
 
 template <class L>
 void
 MacroAssembler::branch32(Condition cond, Register lhs, Imm32 imm, L label)
 {
--- a/js/src/jit/shared/CodeGenerator-shared.cpp
+++ b/js/src/jit/shared/CodeGenerator-shared.cpp
@@ -1505,29 +1505,40 @@ CodeGeneratorShared::emitAsmJSCall(LAsmJ
     Label ok;
     masm.branchTestStackPtr(Assembler::Zero, Imm32(AsmJSStackAlignment - 1), &ok);
     masm.breakpoint();
     masm.bind(&ok);
 #endif
 
     MAsmJSCall::Callee callee = mir->callee();
     switch (callee.which()) {
-      case MAsmJSCall::Callee::Internal:
+      case MAsmJSCall::Callee::Internal: {
         masm.call(mir->desc(), callee.internal());
         break;
+      }
       case MAsmJSCall::Callee::Dynamic: {
-        if (callee.dynamicHasSigIndex())
-            masm.move32(Imm32(callee.dynamicSigIndex()), WasmTableCallSigReg);
+        wasm::SigIdDesc sigId = callee.dynamicSigId();
+        switch (sigId.kind()) {
+          case wasm::SigIdDesc::Kind::Global:
+            masm.loadWasmGlobalPtr(sigId.globalDataOffset(), WasmTableCallSigReg);
+            break;
+          case wasm::SigIdDesc::Kind::Immediate:
+            masm.move32(Imm32(sigId.immediate()), WasmTableCallSigReg);
+            break;
+          case wasm::SigIdDesc::Kind::None:
+            break;
+        }
         MOZ_ASSERT(WasmTableCallPtrReg == ToRegister(ins->getOperand(mir->dynamicCalleeOperandIndex())));
         masm.call(mir->desc(), WasmTableCallPtrReg);
         break;
       }
-      case MAsmJSCall::Callee::Builtin:
+      case MAsmJSCall::Callee::Builtin: {
         masm.call(callee.builtin());
         break;
+      }
     }
 
     if (mir->spIncrement())
         masm.reserveStack(mir->spIncrement());
 }
 
 void
 CodeGeneratorShared::emitPreBarrier(Register base, const LAllocation* index, int32_t offsetAdjustment)
--- a/js/src/jit/x86-shared/MacroAssembler-x86-shared-inl.h
+++ b/js/src/jit/x86-shared/MacroAssembler-x86-shared-inl.h
@@ -437,18 +437,19 @@ void
 MacroAssembler::rshift32Arithmetic(Imm32 shift, Register srcDest)
 {
     sarl(shift, srcDest);
 }
 
 // ===============================================================
 // Branch instructions
 
+template <class L>
 void
-MacroAssembler::branch32(Condition cond, Register lhs, Register rhs, Label* label)
+MacroAssembler::branch32(Condition cond, Register lhs, Register rhs, L label)
 {
     cmp32(lhs, rhs);
     j(cond, label);
 }
 
 template <class L>
 void
 MacroAssembler::branch32(Condition cond, Register lhs, Imm32 rhs, L label)
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -101,20 +101,16 @@ class CompileRuntime;
 
 #ifdef JS_SIMULATOR_ARM64
 typedef vixl::Simulator Simulator;
 #elif defined(JS_SIMULATOR)
 class Simulator;
 #endif
 } // namespace jit
 
-namespace wasm {
-class Module;
-} // namespace wasm
-
 /*
  * A FreeOp can do one thing: free memory. For convenience, it has delete_
  * convenience methods that also call destructors.
  *
  * FreeOp is passed to finalizers and other sweep-phase hooks so that we do not
  * need to pass a JSContext to those hooks.
  */
 class FreeOp : public JSFreeOp