Bug 1528983 - Centralise instance-function arg/return type information. r=lhansen.
authorJulian Seward <jseward@acm.org>
Tue, 26 Feb 2019 14:25:54 +0100
changeset 519260 d4f2be92057edcbd88c6fef4fe68b7756e175973
parent 519259 62b4b842eda6a8e9eb6d04252c94cee31e4047a2
child 519261 b9fe4c97d93b6df3a0f07380fde86387e3a87591
push id10862
push userffxbld-merge
push dateMon, 11 Mar 2019 13:01:11 +0000
treeherdermozilla-beta@a2e7f5c935da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerslhansen
bugs1528983, 1528240
milestone67.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 1528983 - Centralise instance-function arg/return type information. r=lhansen. The wasm baseline and via-Ion compilers both generate numerous calls to supporting functions ("instance functions"). To generate correct code, and, shortly, correct stackmaps too, the compilers need to know argument and return types for these functions. Currently that information is scattered around somewhat: * In baseline, emitInstanceCall() specifies both arg and return types * In via-Ion, builtinInstanceMethodCall specifies the return type. Arg types aren't (obviously) available, but will be needed for upcoming stack map generation. This bug makes the following main changes: * Adds a new type, SymbolicAddressSignature, related to the existing SymbolicAddress type. A SymbolicAddressSignature specifies the arity, argument and return types for a SymbolicAddress. * For all the SymbolicAddresses referred to in the baseline and via-Ion compilers, a static SymbolicAddressSignature is provided, as readonly data. * The compilers then pass around |const SymbolicAddressSignature&| where before they just passed around a SymbolicAddress, to the extent possible. * All ad-hoc specification of arg and return types in emitInstanceCall() [baseline] and builtinInstanceMethodCall() [via Ion] have been removed. * Not all SymbolicAddress values have an associated SymbolicAddressSignature -- only those referred to in calls to emitInstanceCall() and builtinInstanceMethodCall() do. Smaller changes: * WasmBaselineCompile.cpp: - removed MIRTypeVector and SigP*_. - pushReturnValueOfCall: overloaded so as to accept both ExprType and MIRType. - emitInstanceCall now accepts a |const SymbolicAddressSignature&| and pulls type info out of that. All uses changed accordingly. - [drive-by fix]: emitInstanceCall: added a comment update that should have been in bug 1528240 ("Fix inconsistent return type use in BaseCompiler::emitInstanceCall") * WasmIonCompile.cpp: - passArg: overloaded so as to accept both MIRType and ValType for the node type - builtinInstanceMethodCall now accepts a |const SymbolicAddressSignature&| and pulls type info out of that. All uses changed accordingly. * WasmBuiltins.{cpp, h}: add the static SymbolicAddressSignature bundles. * WasmGC.h: add an overload of StackArgAreaSizeUnaligned that can take a Symbolic AddressSignature directly.
js/src/wasm/WasmBaselineCompile.cpp
js/src/wasm/WasmBuiltins.cpp
js/src/wasm/WasmBuiltins.h
js/src/wasm/WasmGC.h
js/src/wasm/WasmIonCompile.cpp
js/src/wasm/WasmTypes.h
--- a/js/src/wasm/WasmBaselineCompile.cpp
+++ b/js/src/wasm/WasmBaselineCompile.cpp
@@ -2420,17 +2420,16 @@ struct StackMapGenerator {
   }
 };
 
 // The baseline compiler proper.
 
 class BaseCompiler final : public BaseCompilerInterface {
   using Local = BaseStackFrame::Local;
   using LabelVector = Vector<NonAssertingLabel, 8, SystemAllocPolicy>;
-  using MIRTypeVector = Vector<MIRType, 8, SystemAllocPolicy>;
 
   // Bit set used for simple bounds check elimination.  Capping this at 64
   // locals makes sense; even 32 locals would probably be OK in practice.
   //
   // For more information about BCE, see the block comment above
   // popMemoryAccess(), below.
 
   using BCESet = uint64_t;
@@ -2541,27 +2540,16 @@ class BaseCompiler final : public BaseCo
   size_t lastReadCallSite_;
   TempAllocator& alloc_;
   const ValTypeVector& locals_;  // Types of parameters and locals
   bool deadCode_;  // Flag indicating we should decode & discard the opcode
   BCESet
       bceSafe_;  // Locals that have been bounds checked and not updated since
   ValTypeVector SigD_;
   ValTypeVector SigF_;
-  MIRTypeVector SigP_;
-  MIRTypeVector SigPI_;
-  MIRTypeVector SigPL_;
-  MIRTypeVector SigPII_;
-  MIRTypeVector SigPIRI_;
-  MIRTypeVector SigPIII_;
-  MIRTypeVector SigPIIL_;
-  MIRTypeVector SigPIIR_;
-  MIRTypeVector SigPILL_;
-  MIRTypeVector SigPIIII_;
-  MIRTypeVector SigPIIIII_;
   NonAssertingLabel returnLabel_;
 
   LatentOp latentOp_;   // Latent operation for branch (seen next)
   ValType latentType_;  // Operand type, if latentOp_ is true
   Assembler::Condition
       latentIntCmp_;  // Comparison operator, if latentOp_ == Compare, int types
   Assembler::DoubleCondition
       latentDoubleCmp_;  // Comparison operator, if latentOp_ == Compare, float
@@ -6473,29 +6461,23 @@ class BaseCompiler final : public BaseCo
   MOZ_MUST_USE bool emitPostBarrierCall(RegPtr valueAddr) {
     uint32_t bytecodeOffset = iter_.lastOpcodeOffset();
 
     // The `valueAddr` is a raw pointer to the cell within some GC object or
     // TLS area, and we guarantee that the GC will not run while the
     // postbarrier call is active, so push a uintptr_t value.
 #ifdef JS_64BIT
     pushI64(RegI64(Register64(valueAddr)));
-    if (!emitInstanceCall(bytecodeOffset, SigPL_, ExprType::Void,
-                          SymbolicAddress::PostBarrier,
-                          /*pushReturnedValue=*/false)) {
-      return false;
-    }
 #else
     pushI32(RegI32(valueAddr));
-    if (!emitInstanceCall(bytecodeOffset, SigPI_, ExprType::Void,
-                          SymbolicAddress::PostBarrier,
+#endif
+    if (!emitInstanceCall(bytecodeOffset, SASigPostBarrier,
                           /*pushReturnedValue=*/false)) {
       return false;
     }
-#endif
     return true;
   }
 
   MOZ_MUST_USE bool emitBarrieredStore(const Maybe<RegPtr>& object,
                                        RegPtr valueAddr, RegPtr value,
                                        ValType valueType) {
     // TODO/AnyRef-boxing: With boxed immediates and strings, the write
     // barrier is going to have to be more complicated.
@@ -6703,16 +6685,17 @@ class BaseCompiler final : public BaseCo
 
   void endBlock(ExprType type);
   void endLoop(ExprType type);
   void endIfThen();
   void endIfThenElse(ExprType type);
 
   void doReturn(ExprType returnType, bool popStack);
   void pushReturnValueOfCall(const FunctionCall& call, ExprType type);
+  void pushReturnValueOfCall(const FunctionCall& call, MIRType type);
 
   void emitCompareI32(Assembler::Condition compareOp, ValType compareType);
   void emitCompareI64(Assembler::Condition compareOp, ValType compareType);
   void emitCompareF32(Assembler::DoubleCondition compareOp,
                       ValType compareType);
   void emitCompareF64(Assembler::DoubleCondition compareOp,
                       ValType compareType);
   void emitCompareRef(Assembler::Condition compareOp, ValType compareType);
@@ -6819,18 +6802,17 @@ class BaseCompiler final : public BaseCo
   void emitConvertU64ToF32();
   void emitConvertI64ToF64();
   void emitConvertU64ToF64();
 #endif
   void emitReinterpretI32AsF32();
   void emitReinterpretI64AsF64();
   void emitRound(RoundingMode roundingMode, ValType operandType);
   MOZ_MUST_USE bool emitInstanceCall(uint32_t lineOrBytecode,
-                                     const MIRTypeVector& sig, ExprType retType,
-                                     SymbolicAddress builtin,
+                                     const SymbolicAddressSignature& builtin,
                                      bool pushReturnedValue = true);
   MOZ_MUST_USE bool emitMemoryGrow();
   MOZ_MUST_USE bool emitMemorySize();
 
   MOZ_MUST_USE bool emitRefNull();
   void emitRefIsNull();
 
   MOZ_MUST_USE bool emitAtomicCmpXchg(ValType type, Scalar::Type viewType);
@@ -8582,53 +8564,56 @@ bool BaseCompiler::emitCallArgs(const Va
     passArg(argTypes[i], peek(numArgs - 1 - i), baselineCall);
   }
 
   masm.loadWasmTlsRegFromFrame();
   return true;
 }
 
 void BaseCompiler::pushReturnValueOfCall(const FunctionCall& call,
-                                         ExprType type) {
-  switch (type.code()) {
-    case ExprType::I32: {
+                                         MIRType type) {
+  switch (type) {
+    case MIRType::Int32: {
       RegI32 rv = captureReturnedI32();
       pushI32(rv);
       break;
     }
-    case ExprType::I64: {
+    case MIRType::Int64: {
       RegI64 rv = captureReturnedI64();
       pushI64(rv);
       break;
     }
-    case ExprType::F32: {
+    case MIRType::Float32: {
       RegF32 rv = captureReturnedF32(call);
       pushF32(rv);
       break;
     }
-    case ExprType::F64: {
+    case MIRType::Double: {
       RegF64 rv = captureReturnedF64(call);
       pushF64(rv);
       break;
     }
-    case ExprType::Ref:
-    case ExprType::AnyRef: {
+    case MIRType::RefOrNull: {
       RegPtr rv = captureReturnedRef();
       pushRef(rv);
       break;
     }
-    case ExprType::NullRef:
-      MOZ_CRASH("NullRef not expressible");
     default:
-      // In particular, passing |type| == ExprType::Void to this function is
-      // an error.
+      // In particular, passing |type| as MIRType::Void or MIRType::Pointer to
+      // this function is an error.
       MOZ_CRASH("Function return type");
   }
 }
 
+void BaseCompiler::pushReturnValueOfCall(const FunctionCall& call,
+                                         ExprType type) {
+  MOZ_ASSERT(type.code() != ExprType::NullRef);
+  pushReturnValueOfCall(call, ToMIRType(type));
+}
+
 // For now, always sync() at the beginning of the call to easily save live
 // values.
 //
 // TODO / OPTIMIZE (Bug 1316806): We may be able to avoid a full sync(), since
 // all we want is to save live registers that won't be saved by the callee or
 // that we need for outgoing args - we don't need to sync the locals.  We can
 // just push the necessary registers, it'll be like a lightweight sync.
 //
@@ -9725,105 +9710,108 @@ void BaseCompiler::emitCompareRef(Assemb
   RegI32 rd = needI32();
   masm.cmpPtrSet(compareOp, rs1, rs2, rd);
   freeRef(rs1);
   freeRef(rs2);
   pushI32(rd);
 }
 
 bool BaseCompiler::emitInstanceCall(uint32_t lineOrBytecode,
-                                    const MIRTypeVector& sig, ExprType retType,
-                                    SymbolicAddress builtin,
+                                    const SymbolicAddressSignature& builtin,
                                     bool pushReturnedValue /*=true*/) {
-  MOZ_ASSERT(sig[0] == MIRType::Pointer);
+  const MIRType* argTypes = builtin.argTypes;
+  MOZ_ASSERT(argTypes[0] == MIRType::Pointer);
 
   sync();
 
-  uint32_t numArgs = sig.length() - 1 /* instance */;
-  size_t stackSpace = stackConsumed(numArgs);
+  uint32_t numNonInstanceArgs = builtin.numArgs - 1 /* instance */;
+  size_t stackSpace = stackConsumed(numNonInstanceArgs);
 
   FunctionCall baselineCall(lineOrBytecode);
   beginCall(baselineCall, UseABI::System, InterModule::True);
 
   ABIArg instanceArg = reservePointerArgument(&baselineCall);
 
-  startCallArgs(StackArgAreaSizeUnaligned(sig), &baselineCall);
-  for (uint32_t i = 1; i < sig.length(); i++) {
+  startCallArgs(StackArgAreaSizeUnaligned(builtin), &baselineCall);
+  for (uint32_t i = 1; i < builtin.numArgs; i++) {
     ValType t;
-    switch (sig[i]) {
+    switch (argTypes[i]) {
       case MIRType::Int32:
         t = ValType::I32;
         break;
       case MIRType::Int64:
         t = ValType::I64;
         break;
       case MIRType::RefOrNull:
         t = ValType::AnyRef;
         break;
+      case MIRType::Pointer:
+        // Instance function args can now be uninterpreted pointers (eg, for
+        // the cases PostBarrier and PostBarrierFilter) so we simply treat
+        // them like the equivalently sized integer.
+        t = sizeof(void*) == 4 ? ValType::I32 : ValType::I64;
+        break;
       default:
         MOZ_CRASH("Unexpected type");
     }
-    passArg(t, peek(numArgs - i), &baselineCall);
+    passArg(t, peek(numNonInstanceArgs - i), &baselineCall);
   }
   CodeOffset raOffset =
-      builtinInstanceMethodCall(builtin, instanceArg, baselineCall);
+      builtinInstanceMethodCall(builtin.identity, instanceArg, baselineCall);
   if (!createStackMap("emitInstanceCall", raOffset)) {
     return false;
   }
 
   endCall(baselineCall, stackSpace);
 
-  popValueStackBy(numArgs);
+  popValueStackBy(numNonInstanceArgs);
 
   // Note, many clients of emitInstanceCall currently assume that pushing the
   // result here does not destroy ReturnReg.
   //
-  // Furthermore, clients assume that even if retType == ExprType::Void, the
-  // callee may have returned a status result and left it in ReturnReg for us
-  // to find, and that that register will not be destroyed here (or above).
-  // In this case the callee will have a C++ declaration stating that there is
-  // a return value.  Examples include memory and table operations that are
-  // implemented as callouts.
+  // Furthermore, clients assume that if builtin.retType != MIRType::None, the
+  // callee will have returned a result and left it in ReturnReg for us to
+  // find, and that that register will not be destroyed here (or above).
 
   if (pushReturnedValue) {
-    MOZ_ASSERT(retType != ExprType::Void);
-    pushReturnValueOfCall(baselineCall, retType);
+    // For the return type only, MIRType::None is used to indicate that the
+    // call doesn't return a result, that is, returns a C/C++ "void".
+    MOZ_ASSERT(builtin.retType != MIRType::None);
+    pushReturnValueOfCall(baselineCall, builtin.retType);
   }
   return true;
 }
 
 bool BaseCompiler::emitMemoryGrow() {
   uint32_t lineOrBytecode = readCallSiteLineOrBytecode();
 
   Nothing arg;
   if (!iter_.readMemoryGrow(&arg)) {
     return false;
   }
 
   if (deadCode_) {
     return true;
   }
 
-  return emitInstanceCall(lineOrBytecode, SigPI_, ExprType::I32,
-                          SymbolicAddress::MemoryGrow);
+  return emitInstanceCall(lineOrBytecode, SASigMemoryGrow);
 }
 
 bool BaseCompiler::emitMemorySize() {
   uint32_t lineOrBytecode = readCallSiteLineOrBytecode();
 
   if (!iter_.readMemorySize()) {
     return false;
   }
 
   if (deadCode_) {
     return true;
   }
 
-  return emitInstanceCall(lineOrBytecode, SigP_, ExprType::I32,
-                          SymbolicAddress::MemorySize);
+  return emitInstanceCall(lineOrBytecode, SASigMemorySize);
 }
 
 bool BaseCompiler::emitRefNull() {
   if (!iter_.readRefNull()) {
     return false;
   }
 
   if (deadCode_) {
@@ -10127,24 +10115,22 @@ bool BaseCompiler::emitWait(ValType type
 
   if (deadCode_) {
     return true;
   }
 
   // Returns -1 on trap, otherwise nonnegative result.
   switch (type.code()) {
     case ValType::I32:
-      if (!emitInstanceCall(lineOrBytecode, SigPIIL_, ExprType::I32,
-                            SymbolicAddress::WaitI32)) {
+      if (!emitInstanceCall(lineOrBytecode, SASigWaitI32)) {
         return false;
       }
       break;
     case ValType::I64:
-      if (!emitInstanceCall(lineOrBytecode, SigPILL_, ExprType::I32,
-                            SymbolicAddress::WaitI64)) {
+      if (!emitInstanceCall(lineOrBytecode, SASigWaitI64)) {
         return false;
       }
       break;
     default:
       MOZ_CRASH();
   }
 
   Label ok;
@@ -10164,18 +10150,17 @@ bool BaseCompiler::emitWake() {
     return false;
   }
 
   if (deadCode_) {
     return true;
   }
 
   // Returns -1 on trap, otherwise nonnegative result.
-  if (!emitInstanceCall(lineOrBytecode, SigPII_, ExprType::I32,
-                        SymbolicAddress::Wake)) {
+  if (!emitInstanceCall(lineOrBytecode, SASigWake)) {
     return false;
   }
 
   Label ok;
   masm.branchTest32(Assembler::NotSigned, ReturnReg, ReturnReg, &ok);
   trap(Trap::ThrowReported);
   masm.bind(&ok);
 
@@ -10197,26 +10182,24 @@ bool BaseCompiler::emitMemOrTableCopy(bo
   if (deadCode_) {
     return true;
   }
 
   // Returns -1 on trap, otherwise 0.
   if (isMem) {
     MOZ_ASSERT(srcMemOrTableIndex == 0);
     MOZ_ASSERT(dstMemOrTableIndex == 0);
-    if (!emitInstanceCall(lineOrBytecode, SigPIII_, ExprType::I32,
-                          SymbolicAddress::MemCopy,
+    if (!emitInstanceCall(lineOrBytecode, SASigMemCopy,
                           /*pushReturnedValue=*/false)) {
       return false;
     }
   } else {
     pushI32(dstMemOrTableIndex);
     pushI32(srcMemOrTableIndex);
-    if (!emitInstanceCall(lineOrBytecode, SigPIIIII_, ExprType::I32,
-                          SymbolicAddress::TableCopy,
+    if (!emitInstanceCall(lineOrBytecode, SASigTableCopy,
                           /*pushReturnedValue=*/false)) {
       return false;
     }
   }
 
   Label ok;
   masm.branchTest32(Assembler::NotSigned, ReturnReg, ReturnReg, &ok);
   trap(Trap::ThrowReported);
@@ -10236,20 +10219,19 @@ bool BaseCompiler::emitDataOrElemDrop(bo
   if (deadCode_) {
     return true;
   }
 
   // Despite the cast to int32_t, the callee regards the value as unsigned.
   //
   // Returns -1 on trap, otherwise 0.
   pushI32(int32_t(segIndex));
-  SymbolicAddress callee =
-      isData ? SymbolicAddress::DataDrop : SymbolicAddress::ElemDrop;
-  if (!emitInstanceCall(lineOrBytecode, SigPI_, ExprType::Void, callee,
-                        /*pushReturnedValue=*/false)) {
+  const SymbolicAddressSignature& callee =
+      isData ? SASigDataDrop : SASigElemDrop;
+  if (!emitInstanceCall(lineOrBytecode, callee, /*pushReturnedValue=*/false)) {
     return false;
   }
 
   Label ok;
   masm.branchTest32(Assembler::NotSigned, ReturnReg, ReturnReg, &ok);
   trap(Trap::ThrowReported);
   masm.bind(&ok);
 
@@ -10264,18 +10246,17 @@ bool BaseCompiler::emitMemFill() {
     return false;
   }
 
   if (deadCode_) {
     return true;
   }
 
   // Returns -1 on trap, otherwise 0.
-  if (!emitInstanceCall(lineOrBytecode, SigPIII_, ExprType::Void,
-                        SymbolicAddress::MemFill,
+  if (!emitInstanceCall(lineOrBytecode, SASigMemFill,
                         /*pushReturnedValue=*/false)) {
     return false;
   }
 
   Label ok;
   masm.branchTest32(Assembler::NotSigned, ReturnReg, ReturnReg, &ok);
   trap(Trap::ThrowReported);
   masm.bind(&ok);
@@ -10296,25 +10277,23 @@ bool BaseCompiler::emitMemOrTableInit(bo
 
   if (deadCode_) {
     return true;
   }
 
   // Returns -1 on trap, otherwise 0.
   pushI32(int32_t(segIndex));
   if (isMem) {
-    if (!emitInstanceCall(lineOrBytecode, SigPIIII_, ExprType::Void,
-                          SymbolicAddress::MemInit,
+    if (!emitInstanceCall(lineOrBytecode, SASigMemInit,
                           /*pushReturnedValue=*/false)) {
       return false;
     }
   } else {
     pushI32(dstTableIndex);
-    if (!emitInstanceCall(lineOrBytecode, SigPIIIII_, ExprType::Void,
-                          SymbolicAddress::TableInit,
+    if (!emitInstanceCall(lineOrBytecode, SASigTableInit,
                           /*pushReturnedValue=*/false)) {
       return false;
     }
   }
 
   Label ok;
   masm.branchTest32(Assembler::NotSigned, ReturnReg, ReturnReg, &ok);
   trap(Trap::ThrowReported);
@@ -10335,26 +10314,33 @@ bool BaseCompiler::emitTableGet() {
   if (deadCode_) {
     return true;
   }
   // get(index:u32, table:u32) -> void*
   //
   // Returns nullptr for error, otherwise a pointer to a nonmoveable memory
   // location that holds the anyref value.
   pushI32(tableIndex);
-  if (!emitInstanceCall(lineOrBytecode, SigPII_, ExprType::AnyRef,
-                        SymbolicAddress::TableGet)) {
+  if (!emitInstanceCall(lineOrBytecode, SASigTableGet,
+                        /*pushReturnedValue=*/false)) {
     return false;
   }
   Label noTrap;
   masm.branchTestPtr(Assembler::NonZero, ReturnReg, ReturnReg, &noTrap);
   trap(Trap::ThrowReported);
   masm.bind(&noTrap);
+
   masm.loadPtr(Address(ReturnReg, 0), ReturnReg);
 
+  // Push the resulting anyref back on the eval stack.  NOTE: needRef() must
+  // not kill the value in the register.
+  RegPtr r = RegPtr(ReturnReg);
+  needRef(r);
+  pushRef(r);
+
   return true;
 }
 
 MOZ_MUST_USE
 bool BaseCompiler::emitTableGrow() {
   uint32_t lineOrBytecode = readCallSiteLineOrBytecode();
   Nothing delta;
   Nothing initValue;
@@ -10364,18 +10350,17 @@ bool BaseCompiler::emitTableGrow() {
   }
   if (deadCode_) {
     return true;
   }
   // grow(delta:u32, initValue:anyref, table:u32) -> u32
   //
   // infallible.
   pushI32(tableIndex);
-  return emitInstanceCall(lineOrBytecode, SigPIRI_, ExprType::I32,
-                          SymbolicAddress::TableGrow);
+  return emitInstanceCall(lineOrBytecode, SASigTableGrow);
 }
 
 MOZ_MUST_USE
 bool BaseCompiler::emitTableSet() {
   uint32_t lineOrBytecode = readCallSiteLineOrBytecode();
   Nothing index, value;
   uint32_t tableIndex;
   if (!iter_.readTableSet(&tableIndex, &index, &value)) {
@@ -10383,18 +10368,17 @@ bool BaseCompiler::emitTableSet() {
   }
   if (deadCode_) {
     return true;
   }
   // set(index:u32, value:ref, table:u32) -> i32
   //
   // Returns -1 on range error, otherwise 0 (which is then ignored).
   pushI32(tableIndex);
-  if (!emitInstanceCall(lineOrBytecode, SigPIRI_, ExprType::I32,
-                        SymbolicAddress::TableSet,
+  if (!emitInstanceCall(lineOrBytecode, SASigTableSet,
                         /*pushReturnedValue=*/false)) {
     return false;
   }
   Label noTrap;
   masm.branchTest32(Assembler::NotSigned, ReturnReg, ReturnReg, &noTrap);
   trap(Trap::ThrowReported);
   masm.bind(&noTrap);
   return true;
@@ -10409,18 +10393,17 @@ bool BaseCompiler::emitTableSize() {
   }
   if (deadCode_) {
     return true;
   }
   // size(table:u32) -> u32
   //
   // infallible.
   pushI32(tableIndex);
-  return emitInstanceCall(lineOrBytecode, SigPI_, ExprType::I32,
-                          SymbolicAddress::TableSize);
+  return emitInstanceCall(lineOrBytecode, SASigTableSize);
 }
 
 bool BaseCompiler::emitStructNew() {
   uint32_t lineOrBytecode = readCallSiteLineOrBytecode();
 
   uint32_t typeIndex;
   BaseOpIter::ValueVector args;
   if (!iter_.readStructNew(&typeIndex, &args)) {
@@ -10434,18 +10417,17 @@ bool BaseCompiler::emitStructNew() {
   // Allocate zeroed storage.  The parameter to StructNew is an index into a
   // descriptor table that the instance has.
   //
   // Returns null on OOM.
 
   const StructType& structType = env_.types[typeIndex].structType();
 
   pushI32(structType.moduleIndex_);
-  if (!emitInstanceCall(lineOrBytecode, SigPI_, ExprType::AnyRef,
-                        SymbolicAddress::StructNew)) {
+  if (!emitInstanceCall(lineOrBytecode, SASigStructNew)) {
     return false;
   }
 
   // Null pointer check.
 
   Label ok;
   masm.branchTestPtr(Assembler::NonZero, ReturnReg, ReturnReg, &ok);
   trap(Trap::ThrowReported);
@@ -10768,18 +10750,17 @@ bool BaseCompiler::emitStructNarrow() {
   //
   // Infallible.
   const StructType& outputStruct =
       env_.types[outputType.refTypeIndex()].structType();
 
   pushI32(mustUnboxAnyref);
   pushI32(outputStruct.moduleIndex_);
   pushRef(rp);
-  return emitInstanceCall(lineOrBytecode, SigPIIR_, ExprType::AnyRef,
-                          SymbolicAddress::StructNarrow);
+  return emitInstanceCall(lineOrBytecode, SASigStructNarrow);
 }
 
 bool BaseCompiler::emitBody() {
   MOZ_ASSERT(stackMapGenerator_.framePushedAtEntryToBody.isSome());
 
   if (!iter_.readFunctionStart(funcType().ret())) {
     return false;
   }
@@ -11847,64 +11828,16 @@ BaseCompiler::BaseCompiler(const ModuleE
 
 bool BaseCompiler::init() {
   if (!SigD_.append(ValType::F64)) {
     return false;
   }
   if (!SigF_.append(ValType::F32)) {
     return false;
   }
-  if (!SigP_.append(MIRType::Pointer)) {
-    return false;
-  }
-  if (!SigPI_.append(MIRType::Pointer) || !SigPI_.append(MIRType::Int32)) {
-    return false;
-  }
-  if (!SigPL_.append(MIRType::Pointer) || !SigPL_.append(MIRType::Int64)) {
-    return false;
-  }
-  if (!SigPII_.append(MIRType::Pointer) || !SigPII_.append(MIRType::Int32) ||
-      !SigPII_.append(MIRType::Int32)) {
-    return false;
-  }
-  if (!SigPIRI_.append(MIRType::Pointer) || !SigPIRI_.append(MIRType::Int32) ||
-      !SigPIRI_.append(MIRType::RefOrNull) ||
-      !SigPIRI_.append(MIRType::Int32)) {
-    return false;
-  }
-  if (!SigPIII_.append(MIRType::Pointer) || !SigPIII_.append(MIRType::Int32) ||
-      !SigPIII_.append(MIRType::Int32) || !SigPIII_.append(MIRType::Int32)) {
-    return false;
-  }
-  if (!SigPIIL_.append(MIRType::Pointer) || !SigPIIL_.append(MIRType::Int32) ||
-      !SigPIIL_.append(MIRType::Int32) || !SigPIIL_.append(MIRType::Int64)) {
-    return false;
-  }
-  if (!SigPIIR_.append(MIRType::Pointer) || !SigPIIR_.append(MIRType::Int32) ||
-      !SigPIIR_.append(MIRType::Int32) ||
-      !SigPIIR_.append(MIRType::RefOrNull)) {
-    return false;
-  }
-  if (!SigPILL_.append(MIRType::Pointer) || !SigPILL_.append(MIRType::Int32) ||
-      !SigPILL_.append(MIRType::Int64) || !SigPILL_.append(MIRType::Int64)) {
-    return false;
-  }
-  if (!SigPIIII_.append(MIRType::Pointer) ||
-      !SigPIIII_.append(MIRType::Int32) || !SigPIIII_.append(MIRType::Int32) ||
-      !SigPIIII_.append(MIRType::Int32) || !SigPIIII_.append(MIRType::Int32)) {
-    return false;
-  }
-  if (!SigPIIIII_.append(MIRType::Pointer) ||
-      !SigPIIIII_.append(MIRType::Int32) ||
-      !SigPIIIII_.append(MIRType::Int32) ||
-      !SigPIIIII_.append(MIRType::Int32) ||
-      !SigPIIIII_.append(MIRType::Int32) ||
-      !SigPIIIII_.append(MIRType::Int32)) {
-    return false;
-  }
 
   if (!fr.setupLocals(locals_, funcType().args(), env_.debugEnabled(),
                       &localInfo_)) {
     return false;
   }
 
   return true;
 }
--- a/js/src/wasm/WasmBuiltins.cpp
+++ b/js/src/wasm/WasmBuiltins.cpp
@@ -40,17 +40,163 @@ using namespace wasm;
 using mozilla::HashGeneric;
 using mozilla::IsNaN;
 using mozilla::MakeEnumeratedRange;
 
 static const unsigned BUILTIN_THUNK_LIFO_SIZE = 64 * 1024;
 
 // ============================================================================
 // WebAssembly builtin C++ functions called from wasm code to implement internal
-// wasm operations.
+// wasm operations: type descriptions.
+
+// Some abbreviations, for the sake of conciseness.
+#define _F64  MIRType::Double
+#define _F32  MIRType::Float32
+#define _I32  MIRType::Int32
+#define _I64  MIRType::Int64
+#define _PTR  MIRType::Pointer
+#define _RoN  MIRType::RefOrNull
+#define _VOID MIRType::None
+#define _END  MIRType::None
+
+namespace js {
+namespace wasm {
+
+const SymbolicAddressSignature SASigSinD = {
+  SymbolicAddress::SinD, _F64, 1, { _F64, _END }
+};
+const SymbolicAddressSignature SASigCosD = {
+  SymbolicAddress::CosD, _F64, 1, { _F64, _END }
+};
+const SymbolicAddressSignature SASigTanD = {
+  SymbolicAddress::TanD, _F64, 1, { _F64, _END }
+};
+const SymbolicAddressSignature SASigASinD = {
+  SymbolicAddress::ASinD, _F64, 1, { _F64, _END }
+};
+const SymbolicAddressSignature SASigACosD = {
+  SymbolicAddress::ACosD, _F64, 1, { _F64, _END }
+};
+const SymbolicAddressSignature SASigATanD = {
+  SymbolicAddress::ATanD, _F64, 1, { _F64, _END }
+};
+const SymbolicAddressSignature SASigCeilD = {
+  SymbolicAddress::CeilD, _F64, 1, { _F64, _END }
+};
+const SymbolicAddressSignature SASigCeilF = {
+  SymbolicAddress::CeilF, _F32, 1, { _F32, _END }
+};
+const SymbolicAddressSignature SASigFloorD = {
+  SymbolicAddress::FloorD, _F64, 1, { _F64, _END }
+};
+const SymbolicAddressSignature SASigFloorF = {
+  SymbolicAddress::FloorF, _F32, 1, { _F32, _END }
+};
+const SymbolicAddressSignature SASigTruncD = {
+  SymbolicAddress::TruncD, _F64, 1, { _F64, _END }
+};
+const SymbolicAddressSignature SASigTruncF = {
+  SymbolicAddress::TruncF, _F32, 1, { _F32, _END }
+};
+const SymbolicAddressSignature SASigNearbyIntD = {
+  SymbolicAddress::NearbyIntD, _F64, 1, { _F64, _END }
+};
+const SymbolicAddressSignature SASigNearbyIntF = {
+  SymbolicAddress::NearbyIntF, _F32, 1, { _F32, _END }
+};
+const SymbolicAddressSignature SASigExpD = {
+  SymbolicAddress::ExpD, _F64, 1, { _F64, _END }
+};
+const SymbolicAddressSignature SASigLogD = {
+  SymbolicAddress::LogD, _F64, 1, { _F64, _END }
+};
+const SymbolicAddressSignature SASigPowD = {
+  SymbolicAddress::PowD, _F64, 2, { _F64, _F64, _END }
+};
+const SymbolicAddressSignature SASigATan2D = {
+  SymbolicAddress::ATan2D, _F64, 2, { _F64, _F64, _END }
+};
+const SymbolicAddressSignature SASigMemoryGrow = {
+  SymbolicAddress::MemoryGrow, _I32, 2, { _PTR, _I32, _END }
+};
+const SymbolicAddressSignature SASigMemorySize = {
+  SymbolicAddress::MemorySize, _I32, 1, { _PTR, _END }
+};
+const SymbolicAddressSignature SASigWaitI32 = {
+  SymbolicAddress::WaitI32, _I32, 4, { _PTR, _I32, _I32, _I64, _END }
+};
+const SymbolicAddressSignature SASigWaitI64 = {
+  SymbolicAddress::WaitI64, _I32, 4, { _PTR, _I32, _I64, _I64, _END }
+};
+const SymbolicAddressSignature SASigWake = {
+  SymbolicAddress::Wake, _I32, 3, { _PTR, _I32, _I32, _END }
+};
+const SymbolicAddressSignature SASigMemCopy = {
+  SymbolicAddress::MemCopy, _I32, 4, { _PTR, _I32, _I32, _I32, _END }
+};
+const SymbolicAddressSignature SASigDataDrop = {
+  SymbolicAddress::DataDrop, _I32, 2, { _PTR, _I32, _END }
+};
+const SymbolicAddressSignature SASigMemFill = {
+  SymbolicAddress::MemFill, _I32, 4, { _PTR, _I32, _I32, _I32, _END }
+};
+const SymbolicAddressSignature SASigMemInit = {
+  SymbolicAddress::MemInit, _I32, 5, { _PTR, _I32, _I32, _I32, _I32, _END }
+};
+const SymbolicAddressSignature SASigTableCopy = {
+  SymbolicAddress::TableCopy,
+  _I32, 6, { _PTR, _I32, _I32, _I32, _I32, _I32, _END }
+};
+const SymbolicAddressSignature SASigElemDrop = {
+  SymbolicAddress::ElemDrop, _I32, 2, { _PTR, _I32, _END }
+};
+const SymbolicAddressSignature SASigTableGet = {
+  SymbolicAddress::TableGet, _PTR, 3, { _PTR, _I32, _I32, _END }
+};
+const SymbolicAddressSignature SASigTableGrow = {
+  SymbolicAddress::TableGrow, _I32, 4, { _PTR, _I32, _RoN, _I32, _END }
+};
+const SymbolicAddressSignature SASigTableInit = {
+  SymbolicAddress::TableInit,
+  _I32, 6, { _PTR, _I32, _I32, _I32, _I32, _I32, _END }
+};
+const SymbolicAddressSignature SASigTableSet = {
+  SymbolicAddress::TableSet, _I32, 4, { _PTR, _I32, _RoN, _I32, _END }
+};
+const SymbolicAddressSignature SASigTableSize = {
+  SymbolicAddress::TableSize, _I32, 2, { _PTR, _I32, _END }
+};
+const SymbolicAddressSignature SASigPostBarrier = {
+  SymbolicAddress::PostBarrier, _VOID, 2, { _PTR, _PTR, _END }
+};
+const SymbolicAddressSignature SASigPostBarrierFiltering = {
+  SymbolicAddress::PostBarrierFiltering, _VOID, 2, { _PTR, _PTR, _END }
+};
+const SymbolicAddressSignature SASigStructNew = {
+  SymbolicAddress::StructNew, _RoN, 2, { _PTR, _I32, _END }
+};
+const SymbolicAddressSignature SASigStructNarrow = {
+  SymbolicAddress::StructNarrow, _RoN, 4, { _PTR, _I32, _I32, _RoN, _END }
+};
+
+} // namespace wasm
+} // namespace js
+
+#undef _F64
+#undef _F32
+#undef _I32
+#undef _I64
+#undef _PTR
+#undef _RoN
+#undef _VOID
+#undef _END
+
+// ============================================================================
+// WebAssembly builtin C++ functions called from wasm code to implement internal
+// wasm operations: implementations.
 
 #if defined(JS_CODEGEN_ARM)
 extern "C" {
 
 extern MOZ_EXPORT int64_t __aeabi_idivmod(int, int);
 
 extern MOZ_EXPORT int64_t __aeabi_uidivmod(int, int);
 }
--- a/js/src/wasm/WasmBuiltins.h
+++ b/js/src/wasm/WasmBuiltins.h
@@ -21,16 +21,58 @@
 
 #include "wasm/WasmTypes.h"
 
 namespace js {
 namespace wasm {
 
 class WasmFrameIter;
 
+// These provide argument type information for a subset of the SymbolicAddress
+// targets, for which type info is needed to generate correct stackmaps.
+
+extern const SymbolicAddressSignature SASigSinD;
+extern const SymbolicAddressSignature SASigCosD;
+extern const SymbolicAddressSignature SASigTanD;
+extern const SymbolicAddressSignature SASigASinD;
+extern const SymbolicAddressSignature SASigACosD;
+extern const SymbolicAddressSignature SASigATanD;
+extern const SymbolicAddressSignature SASigCeilD;
+extern const SymbolicAddressSignature SASigCeilF;
+extern const SymbolicAddressSignature SASigFloorD;
+extern const SymbolicAddressSignature SASigFloorF;
+extern const SymbolicAddressSignature SASigTruncD;
+extern const SymbolicAddressSignature SASigTruncF;
+extern const SymbolicAddressSignature SASigNearbyIntD;
+extern const SymbolicAddressSignature SASigNearbyIntF;
+extern const SymbolicAddressSignature SASigExpD;
+extern const SymbolicAddressSignature SASigLogD;
+extern const SymbolicAddressSignature SASigPowD;
+extern const SymbolicAddressSignature SASigATan2D;
+extern const SymbolicAddressSignature SASigMemoryGrow;
+extern const SymbolicAddressSignature SASigMemorySize;
+extern const SymbolicAddressSignature SASigWaitI32;
+extern const SymbolicAddressSignature SASigWaitI64;
+extern const SymbolicAddressSignature SASigWake;
+extern const SymbolicAddressSignature SASigMemCopy;
+extern const SymbolicAddressSignature SASigDataDrop;
+extern const SymbolicAddressSignature SASigMemFill;
+extern const SymbolicAddressSignature SASigMemInit;
+extern const SymbolicAddressSignature SASigTableCopy;
+extern const SymbolicAddressSignature SASigElemDrop;
+extern const SymbolicAddressSignature SASigTableGet;
+extern const SymbolicAddressSignature SASigTableGrow;
+extern const SymbolicAddressSignature SASigTableInit;
+extern const SymbolicAddressSignature SASigTableSet;
+extern const SymbolicAddressSignature SASigTableSize;
+extern const SymbolicAddressSignature SASigPostBarrier;
+extern const SymbolicAddressSignature SASigPostBarrierFiltering;
+extern const SymbolicAddressSignature SASigStructNew;
+extern const SymbolicAddressSignature SASigStructNarrow;
+
 // A SymbolicAddress that NeedsBuiltinThunk() will call through a thunk to the
 // C++ function. This will be true for all normal calls from normal wasm
 // function code. Only calls to C++ from other exits/thunks do not need a thunk.
 
 bool NeedsBuiltinThunk(SymbolicAddress sym);
 
 // This function queries whether pc is in one of the process's builtin thunks
 // and, if so, returns the CodeRange and pointer to the code segment that the
--- a/js/src/wasm/WasmGC.h
+++ b/js/src/wasm/WasmGC.h
@@ -42,16 +42,47 @@ template <class T>
 static inline size_t StackArgAreaSizeUnaligned(const T& argTypes) {
   ABIArgIter<const T> i(argTypes);
   while (!i.done()) {
     i++;
   }
   return i.stackBytesConsumedSoFar();
 }
 
+static inline size_t StackArgAreaSizeUnaligned(
+                         const SymbolicAddressSignature& saSig) {
+  // ABIArgIter::ABIArgIter wants the items to be iterated over to be
+  // presented in some type that has methods length() and operator[].  So we
+  // have to wrap up |saSig|'s array of types in this API-matching class.
+  class MOZ_STACK_CLASS ItemsAndLength {
+    const MIRType* items_;
+    size_t length_;
+   public:
+    ItemsAndLength(const MIRType* items, size_t length)
+      : items_(items), length_(length)
+    {}
+    size_t length() const { return length_; }
+    MIRType operator[](size_t i) const { return items_[i]; }
+  };
+
+  // Assert, at least crudely, that we're not accidentally going to run off
+  // the end of the array of types, nor into undefined parts of it, while
+  // iterating.
+  MOZ_ASSERT(saSig.numArgs < sizeof(saSig.argTypes) / sizeof(saSig.argTypes[0]));
+  MOZ_ASSERT(saSig.argTypes[saSig.numArgs] == MIRType::None/*the end marker*/);
+
+  ItemsAndLength itemsAndLength(saSig.argTypes, saSig.numArgs);
+
+  ABIArgIter<ItemsAndLength> i(itemsAndLength);
+  while (!i.done()) {
+     i++;
+  }
+  return i.stackBytesConsumedSoFar();
+}
+
 static inline size_t AlignStackArgAreaSize(size_t unalignedSize) {
   return AlignBytes(unalignedSize, 16u);
 }
 
 template <class T>
 static inline size_t StackArgAreaSizeAligned(const T& argTypes) {
   return AlignStackArgAreaSize(StackArgAreaSizeUnaligned(argTypes));
 }
--- a/js/src/wasm/WasmIonCompile.cpp
+++ b/js/src/wasm/WasmIonCompile.cpp
@@ -18,16 +18,17 @@
 
 #include "wasm/WasmIonCompile.h"
 
 #include "mozilla/MathAlgorithms.h"
 
 #include "jit/CodeGenerator.h"
 
 #include "wasm/WasmBaselineCompile.h"
+#include "wasm/WasmBuiltins.h"
 #include "wasm/WasmGenerator.h"
 #include "wasm/WasmOpIter.h"
 #include "wasm/WasmSignalHandlers.h"
 #include "wasm/WasmValidate.h"
 
 using namespace js;
 using namespace js::jit;
 using namespace js::wasm;
@@ -981,22 +982,19 @@ class FunctionCompiler {
     }
 
     // Should only pass an instance once.
     MOZ_ASSERT(args->instanceArg_ == ABIArg());
     args->instanceArg_ = args->abi_.next(MIRType::Pointer);
     return true;
   }
 
-  bool passArg(MDefinition* argDef, ValType type, CallCompileState* call) {
-    if (inDeadCode()) {
-      return true;
-    }
-
-    ABIArg arg = call->abi_.next(ToMIRType(type));
+  // Do not call this directly.  Call one of the passArg() variants instead.
+  bool passArgWorker(MDefinition* argDef, MIRType type, CallCompileState* call) {
+    ABIArg arg = call->abi_.next(type);
     switch (arg.kind()) {
 #ifdef JS_CODEGEN_REGISTER_PAIR
       case ABIArg::GPR_PAIR: {
         auto mirLow =
             MWrapInt64ToInt32::New(alloc(), argDef, /* bottomHalf = */ true);
         curBlock_->add(mirLow);
         auto mirHigh =
             MWrapInt64ToInt32::New(alloc(), argDef, /* bottomHalf = */ false);
@@ -1017,16 +1015,30 @@ class FunctionCompiler {
         return true;
       }
       case ABIArg::Uninitialized:
         MOZ_ASSERT_UNREACHABLE("Uninitialized ABIArg kind");
     }
     MOZ_CRASH("Unknown ABIArg kind.");
   }
 
+  bool passArg(MDefinition* argDef, MIRType type, CallCompileState* call) {
+    if (inDeadCode()) {
+      return true;
+    }
+    return passArgWorker(argDef, type, call);
+  }
+
+  bool passArg(MDefinition* argDef, ValType type, CallCompileState* call) {
+    if (inDeadCode()) {
+      return true;
+    }
+    return passArgWorker(argDef, ToMIRType(type), call);
+  }
+
   bool finishCall(CallCompileState* call) {
     if (inDeadCode()) {
       return true;
     }
 
     if (!call->regArgs_.append(
             MWasmCall::Arg(AnyRegister(WasmTlsReg), tlsPointer_))) {
       return false;
@@ -1121,66 +1133,59 @@ class FunctionCompiler {
       return false;
     }
 
     curBlock_->add(ins);
     *def = ins;
     return true;
   }
 
-  bool builtinCall(SymbolicAddress builtin, uint32_t lineOrBytecode,
-                   const CallCompileState& call, ValType ret,
+  bool builtinCall(const SymbolicAddressSignature& builtin,
+                   uint32_t lineOrBytecode, const CallCompileState& call,
                    MDefinition** def) {
     if (inDeadCode()) {
       *def = nullptr;
       return true;
     }
 
     CallSiteDesc desc(lineOrBytecode, CallSiteDesc::Symbolic);
-    auto callee = CalleeDesc::builtin(builtin);
+    auto callee = CalleeDesc::builtin(builtin.identity);
     auto* ins =
-        MWasmCall::New(alloc(), desc, callee, call.regArgs_, ToMIRType(ret));
+        MWasmCall::New(alloc(), desc, callee, call.regArgs_, builtin.retType);
     if (!ins) {
       return false;
     }
 
     curBlock_->add(ins);
     *def = ins;
     return true;
   }
 
-  bool builtinInstanceMethodCall(SymbolicAddress builtin,
+  bool builtinInstanceMethodCall(const SymbolicAddressSignature& builtin,
                                  uint32_t lineOrBytecode,
-                                 const CallCompileState& call, MIRType ret,
+                                 const CallCompileState& call,
                                  MDefinition** def) {
     if (inDeadCode()) {
       *def = nullptr;
       return true;
     }
 
     CallSiteDesc desc(lineOrBytecode, CallSiteDesc::Symbolic);
     auto* ins = MWasmCall::NewBuiltinInstanceMethodCall(
-        alloc(), desc, builtin, call.instanceArg_, call.regArgs_, ret);
+        alloc(), desc, builtin.identity, call.instanceArg_, call.regArgs_,
+        builtin.retType);
     if (!ins) {
       return false;
     }
 
     curBlock_->add(ins);
     *def = ins;
     return true;
   }
 
-  bool builtinInstanceMethodCall(SymbolicAddress builtin,
-                                 uint32_t lineOrBytecode,
-                                 const CallCompileState& call, ValType ret,
-                                 MDefinition** def) {
-    return builtinInstanceMethodCall(builtin, lineOrBytecode, call,
-                                     ToMIRType(ret), def);
-  }
-
   /*********************************************** Control flow generation */
 
   inline bool inDeadCode() const { return curBlock_ == nullptr; }
 
   void returnExpr(MDefinition* operand) {
     if (inDeadCode()) {
       return;
     }
@@ -2196,27 +2201,23 @@ static bool EmitSetGlobal(FunctionCompil
   // the nursery, and the value stored will very frequently be in the nursery.
   // The C++ postbarrier performs any necessary filtering.
 
   if (barrierAddr) {
     CallCompileState args;
     if (!f.passInstance(&args)) {
       return false;
     }
-    // TODO: The argument type here is MIRType::Pointer, Julian's fix will take
-    // care of this.
     if (!f.passArg(barrierAddr, ValType::AnyRef, &args)) {
         return false;
     }
     f.finishCall(&args);
     MDefinition* ret;
-    // TODO: The return type here is void (ExprType::Void or MIRType::None),
-    // Julian's fix will take care of this.
-    if (!f.builtinInstanceMethodCall(SymbolicAddress::PostBarrierFiltering,
-                                     lineOrBytecode, args, ValType::I32, &ret)) {
+    if (!f.builtinInstanceMethodCall(SASigPostBarrierFiltering, lineOrBytecode,
+                                     args, &ret)) {
       return false;
     }
   }
 
   return true;
 }
 
 static bool EmitTeeGlobal(FunctionCompiler& f) {
@@ -2586,73 +2587,77 @@ static bool TryInlineUnaryBuiltin(Functi
     return false;
   }
 
   f.iter().setResult(f.nearbyInt(input, mode));
   return true;
 }
 
 static bool EmitUnaryMathBuiltinCall(FunctionCompiler& f,
-                                     SymbolicAddress callee,
-                                     ValType operandType) {
+                                     const SymbolicAddressSignature& callee) {
+  MOZ_ASSERT(callee.numArgs == 1);
+
   uint32_t lineOrBytecode = f.readCallSiteLineOrBytecode();
 
   MDefinition* input;
-  if (!f.iter().readUnary(operandType, &input)) {
+  if (!f.iter().readUnary(ValType(callee.argTypes[0]), &input)) {
     return false;
   }
 
-  if (TryInlineUnaryBuiltin(f, callee, input)) {
+  if (TryInlineUnaryBuiltin(f, callee.identity, input)) {
     return true;
   }
 
   CallCompileState call;
-  if (!f.passArg(input, operandType, &call)) {
+  if (!f.passArg(input, callee.argTypes[0], &call)) {
     return false;
   }
 
   if (!f.finishCall(&call)) {
     return false;
   }
 
   MDefinition* def;
-  if (!f.builtinCall(callee, lineOrBytecode, call, operandType, &def)) {
+  if (!f.builtinCall(callee, lineOrBytecode, call, &def)) {
     return false;
   }
 
   f.iter().setResult(def);
   return true;
 }
 
 static bool EmitBinaryMathBuiltinCall(FunctionCompiler& f,
-                                      SymbolicAddress callee,
-                                      ValType operandType) {
+                                      const SymbolicAddressSignature& callee) {
+  MOZ_ASSERT(callee.numArgs == 2);
+  MOZ_ASSERT(callee.argTypes[0] == callee.argTypes[1]);
+
   uint32_t lineOrBytecode = f.readCallSiteLineOrBytecode();
 
   CallCompileState call;
   MDefinition* lhs;
   MDefinition* rhs;
-  if (!f.iter().readBinary(operandType, &lhs, &rhs)) {
+  // This call to readBinary assumes both operands have the same type.
+  if (!f.iter().readBinary(ValType(callee.argTypes[0]), &lhs, &rhs)) {
     return false;
   }
 
-  if (!f.passArg(lhs, operandType, &call)) {
+  if (!f.passArg(lhs, callee.argTypes[0], &call)) {
     return false;
   }
 
-  if (!f.passArg(rhs, operandType, &call)) {
+  if (!f.passArg(rhs, callee.argTypes[1], &call)) {
     return false;
   }
 
   if (!f.finishCall(&call)) {
     return false;
   }
 
   MDefinition* def;
-  if (!f.builtinCall(callee, lineOrBytecode, call, operandType, &def)) {
+  if (!f.builtinCall(callee, lineOrBytecode, call, &def)) {
     return false;
   }
 
   f.iter().setResult(def);
   return true;
 }
 
 static bool EmitMemoryGrow(FunctionCompiler& f) {
@@ -2670,18 +2675,18 @@ static bool EmitMemoryGrow(FunctionCompi
 
   if (!f.passArg(delta, ValType::I32, &args)) {
     return false;
   }
 
   f.finishCall(&args);
 
   MDefinition* ret;
-  if (!f.builtinInstanceMethodCall(SymbolicAddress::MemoryGrow, lineOrBytecode,
-                                   args, ValType::I32, &ret)) {
+  if (!f.builtinInstanceMethodCall(SASigMemoryGrow, lineOrBytecode, args,
+                                   &ret)) {
     return false;
   }
 
   f.iter().setResult(ret);
   return true;
 }
 
 static bool EmitMemorySize(FunctionCompiler& f) {
@@ -2695,18 +2700,18 @@ static bool EmitMemorySize(FunctionCompi
 
   if (!f.passInstance(&args)) {
     return false;
   }
 
   f.finishCall(&args);
 
   MDefinition* ret;
-  if (!f.builtinInstanceMethodCall(SymbolicAddress::MemorySize,
-                                   lineOrBytecode, args, ValType::I32, &ret)) {
+  if (!f.builtinInstanceMethodCall(SASigMemorySize, lineOrBytecode, args,
+                                   &ret)) {
     return false;
   }
 
   f.iter().setResult(ret);
   return true;
 }
 
 static bool EmitAtomicCmpXchg(FunctionCompiler& f, ValType type,
@@ -2815,21 +2820,20 @@ static bool EmitWait(FunctionCompiler& f
   if (!f.passArg(timeout, ValType::I64, &args)) {
     return false;
   }
 
   if (!f.finishCall(&args)) {
     return false;
   }
 
-  SymbolicAddress callee = type == ValType::I32 ? SymbolicAddress::WaitI32
-                                                : SymbolicAddress::WaitI64;
+  const SymbolicAddressSignature& callee =
+      type == ValType::I32 ? SASigWaitI32 : SASigWaitI64;
   MDefinition* ret;
-  if (!f.builtinInstanceMethodCall(callee, lineOrBytecode, args, ValType::I32,
-                                   &ret)) {
+  if (!f.builtinInstanceMethodCall(callee, lineOrBytecode, args, &ret)) {
     return false;
   }
 
   if (!f.checkI32NegativeMeansFailedResult(ret)) {
     return false;
   }
 
   f.iter().setResult(ret);
@@ -2865,18 +2869,17 @@ static bool EmitWake(FunctionCompiler& f
     return false;
   }
 
   if (!f.finishCall(&args)) {
     return false;
   }
 
   MDefinition* ret;
-  if (!f.builtinInstanceMethodCall(SymbolicAddress::Wake, lineOrBytecode, args,
-                                   ValType::I32, &ret)) {
+  if (!f.builtinInstanceMethodCall(SASigWake, lineOrBytecode, args, &ret)) {
     return false;
   }
 
   if (!f.checkI32NegativeMeansFailedResult(ret)) {
     return false;
   }
 
   f.iter().setResult(ret);
@@ -2947,21 +2950,19 @@ static bool EmitMemOrTableCopy(FunctionC
     if (!f.passArg(sti, ValType::I32, &args)) {
       return false;
     }
   }
   if (!f.finishCall(&args)) {
     return false;
   }
 
-  SymbolicAddress callee =
-      isMem ? SymbolicAddress::MemCopy : SymbolicAddress::TableCopy;
+  const SymbolicAddressSignature& callee = isMem ? SASigMemCopy : SASigTableCopy;
   MDefinition* ret;
-  if (!f.builtinInstanceMethodCall(callee, lineOrBytecode, args, ValType::I32,
-                                   &ret)) {
+  if (!f.builtinInstanceMethodCall(callee, lineOrBytecode, args, &ret)) {
     return false;
   }
 
   if (!f.checkI32NegativeMeansFailedResult(ret)) {
     return false;
   }
 
   return true;
@@ -2989,21 +2990,20 @@ static bool EmitDataOrElemDrop(FunctionC
   if (!f.passArg(segIndex, ValType::I32, &args)) {
     return false;
   }
 
   if (!f.finishCall(&args)) {
     return false;
   }
 
-  SymbolicAddress callee =
-      isData ? SymbolicAddress::DataDrop : SymbolicAddress::ElemDrop;
+  const SymbolicAddressSignature& callee =
+      isData ? SASigDataDrop : SASigElemDrop;
   MDefinition* ret;
-  if (!f.builtinInstanceMethodCall(callee, lineOrBytecode, args, ValType::I32,
-                                   &ret)) {
+  if (!f.builtinInstanceMethodCall(callee, lineOrBytecode, args, &ret)) {
     return false;
   }
 
   if (!f.checkI32NegativeMeansFailedResult(ret)) {
     return false;
   }
 
   return true;
@@ -3036,18 +3036,17 @@ static bool EmitMemFill(FunctionCompiler
     return false;
   }
 
   if (!f.finishCall(&args)) {
     return false;
   }
 
   MDefinition* ret;
-  if (!f.builtinInstanceMethodCall(SymbolicAddress::MemFill, lineOrBytecode,
-                                   args, ValType::I32, &ret)) {
+  if (!f.builtinInstanceMethodCall(SASigMemFill, lineOrBytecode, args, &ret)) {
     return false;
   }
 
   if (!f.checkI32NegativeMeansFailedResult(ret)) {
     return false;
   }
 
   return true;
@@ -3095,21 +3094,19 @@ static bool EmitMemOrTableInit(FunctionC
     if (!f.passArg(dti, ValType::I32, &args)) {
       return false;
     }
   }
   if (!f.finishCall(&args)) {
     return false;
   }
 
-  SymbolicAddress callee =
-      isMem ? SymbolicAddress::MemInit : SymbolicAddress::TableInit;
+  const SymbolicAddressSignature& callee = isMem ? SASigMemInit : SASigTableInit;
   MDefinition* ret;
-  if (!f.builtinInstanceMethodCall(callee, lineOrBytecode, args, ValType::I32,
-                                   &ret)) {
+  if (!f.builtinInstanceMethodCall(callee, lineOrBytecode, args, &ret)) {
     return false;
   }
 
   if (!f.checkI32NegativeMeansFailedResult(ret)) {
     return false;
   }
 
   return true;
@@ -3153,18 +3150,18 @@ static bool EmitTableGet(FunctionCompile
 
   if (!f.finishCall(&args)) {
     return false;
   }
 
   // The return value here is either null, denoting an error, or a pointer to an
   // unmovable location containing a possibly-null ref.
   MDefinition* result;
-  if (!f.builtinInstanceMethodCall(SymbolicAddress::TableGet, lineOrBytecode,
-                                   args, MIRType::Pointer, &result)) {
+  if (!f.builtinInstanceMethodCall(SASigTableGet, lineOrBytecode, args,
+                                   &result)) {
     return false;
   }
   if (!f.checkPointerNullMeansFailedResult(result)) {
     return false;
   }
 
   MDefinition* ret = f.derefTableElementPointer(result);
   if (!ret) {
@@ -3211,18 +3208,18 @@ static bool EmitTableGrow(FunctionCompil
     return false;
   }
 
   if (!f.finishCall(&args)) {
     return false;
   }
 
   MDefinition* ret;
-  if (!f.builtinInstanceMethodCall(SymbolicAddress::TableGrow, lineOrBytecode,
-                                   args, ValType::I32, &ret)) {
+  if (!f.builtinInstanceMethodCall(SASigTableGrow, lineOrBytecode, args,
+                                   &ret)) {
     return false;
   }
 
   f.iter().setResult(ret);
   return true;
 }
 
 static bool EmitTableSet(FunctionCompiler& f) {
@@ -3261,18 +3258,17 @@ static bool EmitTableSet(FunctionCompile
     return false;
   }
 
   if (!f.finishCall(&args)) {
     return false;
   }
 
   MDefinition* ret;
-  if (!f.builtinInstanceMethodCall(SymbolicAddress::TableSet, lineOrBytecode,
-                                   args, ValType::I32, &ret)) {
+  if (!f.builtinInstanceMethodCall(SASigTableSet, lineOrBytecode, args, &ret)) {
     return false;
   }
   if (!f.checkI32NegativeMeansFailedResult(ret)) {
     return false;
   }
   return true;
 }
 
@@ -3302,18 +3298,18 @@ static bool EmitTableSize(FunctionCompil
     return false;
   }
 
   if (!f.finishCall(&args)) {
     return false;
   }
 
   MDefinition* ret;
-  if (!f.builtinInstanceMethodCall(SymbolicAddress::TableSize, lineOrBytecode,
-                                   args, ValType::I32, &ret)) {
+  if (!f.builtinInstanceMethodCall(SASigTableSize, lineOrBytecode, args,
+                                   &ret)) {
     return false;
   }
 
   f.iter().setResult(ret);
   return true;
 }
 #endif  // ENABLE_WASM_GENERALIZED_TABLES
 
@@ -3671,27 +3667,23 @@ static bool EmitBodyExprs(FunctionCompil
       case uint16_t(Op::I64Rotl):
       case uint16_t(Op::I64Rotr):
         CHECK(EmitRotate(f, ValType::I64, Op(op.b0) == Op::I64Rotl));
       case uint16_t(Op::F32Abs):
         CHECK(EmitUnaryWithType<MAbs>(f, ValType::F32, MIRType::Float32));
       case uint16_t(Op::F32Neg):
         CHECK(EmitUnaryWithType<MWasmNeg>(f, ValType::F32, MIRType::Float32));
       case uint16_t(Op::F32Ceil):
-        CHECK(
-            EmitUnaryMathBuiltinCall(f, SymbolicAddress::CeilF, ValType::F32));
+        CHECK(EmitUnaryMathBuiltinCall(f, SASigCeilF));
       case uint16_t(Op::F32Floor):
-        CHECK(
-            EmitUnaryMathBuiltinCall(f, SymbolicAddress::FloorF, ValType::F32));
+        CHECK(EmitUnaryMathBuiltinCall(f, SASigFloorF));
       case uint16_t(Op::F32Trunc):
-        CHECK(
-            EmitUnaryMathBuiltinCall(f, SymbolicAddress::TruncF, ValType::F32));
+        CHECK(EmitUnaryMathBuiltinCall(f, SASigTruncF));
       case uint16_t(Op::F32Nearest):
-        CHECK(EmitUnaryMathBuiltinCall(f, SymbolicAddress::NearbyIntF,
-                                       ValType::F32));
+        CHECK(EmitUnaryMathBuiltinCall(f, SASigNearbyIntF));
       case uint16_t(Op::F32Sqrt):
         CHECK(EmitUnaryWithType<MSqrt>(f, ValType::F32, MIRType::Float32));
       case uint16_t(Op::F32Add):
         CHECK(EmitAdd(f, ValType::F32, MIRType::Float32));
       case uint16_t(Op::F32Sub):
         CHECK(EmitSub(f, ValType::F32, MIRType::Float32));
       case uint16_t(Op::F32Mul):
         CHECK(EmitMul(f, ValType::F32, MIRType::Float32));
@@ -3704,27 +3696,23 @@ static bool EmitBodyExprs(FunctionCompil
                          Op(op.b0) == Op::F32Max));
       case uint16_t(Op::F32CopySign):
         CHECK(EmitCopySign(f, ValType::F32));
       case uint16_t(Op::F64Abs):
         CHECK(EmitUnaryWithType<MAbs>(f, ValType::F64, MIRType::Double));
       case uint16_t(Op::F64Neg):
         CHECK(EmitUnaryWithType<MWasmNeg>(f, ValType::F64, MIRType::Double));
       case uint16_t(Op::F64Ceil):
-        CHECK(
-            EmitUnaryMathBuiltinCall(f, SymbolicAddress::CeilD, ValType::F64));
+        CHECK(EmitUnaryMathBuiltinCall(f, SASigCeilD));
       case uint16_t(Op::F64Floor):
-        CHECK(
-            EmitUnaryMathBuiltinCall(f, SymbolicAddress::FloorD, ValType::F64));
+        CHECK(EmitUnaryMathBuiltinCall(f, SASigFloorD));
       case uint16_t(Op::F64Trunc):
-        CHECK(
-            EmitUnaryMathBuiltinCall(f, SymbolicAddress::TruncD, ValType::F64));
+        CHECK(EmitUnaryMathBuiltinCall(f, SASigTruncD));
       case uint16_t(Op::F64Nearest):
-        CHECK(EmitUnaryMathBuiltinCall(f, SymbolicAddress::NearbyIntD,
-                                       ValType::F64));
+        CHECK(EmitUnaryMathBuiltinCall(f, SASigNearbyIntD));
       case uint16_t(Op::F64Sqrt):
         CHECK(EmitUnaryWithType<MSqrt>(f, ValType::F64, MIRType::Double));
       case uint16_t(Op::F64Add):
         CHECK(EmitAdd(f, ValType::F64, MIRType::Double));
       case uint16_t(Op::F64Sub):
         CHECK(EmitSub(f, ValType::F64, MIRType::Double));
       case uint16_t(Op::F64Mul):
         CHECK(EmitMul(f, ValType::F64, MIRType::Double));
@@ -4107,45 +4095,35 @@ static bool EmitBodyExprs(FunctionCompil
           case uint16_t(MozOp::F32TeeStore):
             CHECK(EmitTeeStore(f, ValType::F32, Scalar::Float32));
           case uint16_t(MozOp::F64TeeStore):
             CHECK(EmitTeeStore(f, ValType::F64, Scalar::Float64));
           case uint16_t(MozOp::F64Mod):
             CHECK(EmitRem(f, ValType::F64, MIRType::Double,
                           /* isUnsigned = */ false));
           case uint16_t(MozOp::F64Sin):
-            CHECK(EmitUnaryMathBuiltinCall(f, SymbolicAddress::SinD,
-                                           ValType::F64));
+            CHECK(EmitUnaryMathBuiltinCall(f, SASigSinD));
           case uint16_t(MozOp::F64Cos):
-            CHECK(EmitUnaryMathBuiltinCall(f, SymbolicAddress::CosD,
-                                           ValType::F64));
+            CHECK(EmitUnaryMathBuiltinCall(f, SASigCosD));
           case uint16_t(MozOp::F64Tan):
-            CHECK(EmitUnaryMathBuiltinCall(f, SymbolicAddress::TanD,
-                                           ValType::F64));
+            CHECK(EmitUnaryMathBuiltinCall(f, SASigTanD));
           case uint16_t(MozOp::F64Asin):
-            CHECK(EmitUnaryMathBuiltinCall(f, SymbolicAddress::ASinD,
-                                           ValType::F64));
+            CHECK(EmitUnaryMathBuiltinCall(f, SASigASinD));
           case uint16_t(MozOp::F64Acos):
-            CHECK(EmitUnaryMathBuiltinCall(f, SymbolicAddress::ACosD,
-                                           ValType::F64));
+            CHECK(EmitUnaryMathBuiltinCall(f, SASigACosD));
           case uint16_t(MozOp::F64Atan):
-            CHECK(EmitUnaryMathBuiltinCall(f, SymbolicAddress::ATanD,
-                                           ValType::F64));
+            CHECK(EmitUnaryMathBuiltinCall(f, SASigATanD));
           case uint16_t(MozOp::F64Exp):
-            CHECK(EmitUnaryMathBuiltinCall(f, SymbolicAddress::ExpD,
-                                           ValType::F64));
+            CHECK(EmitUnaryMathBuiltinCall(f, SASigExpD));
           case uint16_t(MozOp::F64Log):
-            CHECK(EmitUnaryMathBuiltinCall(f, SymbolicAddress::LogD,
-                                           ValType::F64));
+            CHECK(EmitUnaryMathBuiltinCall(f, SASigLogD));
           case uint16_t(MozOp::F64Pow):
-            CHECK(EmitBinaryMathBuiltinCall(f, SymbolicAddress::PowD,
-                                            ValType::F64));
+            CHECK(EmitBinaryMathBuiltinCall(f, SASigPowD));
           case uint16_t(MozOp::F64Atan2):
-            CHECK(EmitBinaryMathBuiltinCall(f, SymbolicAddress::ATan2D,
-                                            ValType::F64));
+            CHECK(EmitBinaryMathBuiltinCall(f, SASigATan2D));
           case uint16_t(MozOp::OldCallDirect):
             CHECK(EmitCall(f, /* asmJSFuncDef = */ true));
           case uint16_t(MozOp::OldCallIndirect):
             CHECK(EmitCallIndirect(f, /* oldStyle = */ true));
 
           default:
             return f.iter().unrecognizedOpcode(&op);
         }
--- a/js/src/wasm/WasmTypes.h
+++ b/js/src/wasm/WasmTypes.h
@@ -391,16 +391,26 @@ class ValType {
   }
 
   explicit ValType(const ExprType& t) : tc_(t.packed()) {
     MOZ_ASSERT(isValidCode());
   }
 
   explicit ValType(PackedTypeCode ptc) : tc_(ptc) { MOZ_ASSERT(isValidCode()); }
 
+  explicit ValType(jit::MIRType mty) {
+    switch (mty) {
+      case jit::MIRType::Int32: tc_ = PackTypeCode(TypeCode::I32); break;
+      case jit::MIRType::Int64: tc_ = PackTypeCode(TypeCode::I64); break;
+      case jit::MIRType::Float32: tc_ = PackTypeCode(TypeCode::F32); break;
+      case jit::MIRType::Double: tc_ = PackTypeCode(TypeCode::F64); break;
+      default: MOZ_CRASH("ValType(MIRType): unexpected type");
+    }
+  }
+
   static ValType fromBitsUnsafe(uint32_t bits) {
     return ValType(PackedTypeCodeFromBits(bits));
   }
 
   PackedTypeCode packed() const { return tc_; }
 
   uint32_t bitsUnsafe() const { return PackedTypeCodeToBits(tc_); }
 
@@ -2086,16 +2096,50 @@ enum class SymbolicAddress {
   PrintPtr,
   PrintF32,
   PrintF64,
   PrintText,
 #endif
   Limit
 };
 
+// SymbolicAddressSignature carries type information for a function referred
+// to by a SymbolicAddress.  In order that |argTypes| can be written out as a
+// static initialiser, it has to have fixed length.  At present
+// SymbolicAddressType is used to describe functions with at most 6 arguments,
+// so |argTypes| has 7 entries in order to allow the last value to be
+// MIRType::None, in the hope of catching any accidental overruns of the
+// defined section of the array.
+
+static constexpr size_t SymbolicAddressSignatureMaxArgs = 6;
+
+struct SymbolicAddressSignature {
+  // The SymbolicAddress that is described.
+  const SymbolicAddress identity;
+  // The return type, or MIRType::None to denote 'void'.
+  const jit::MIRType retType;
+  // The number of arguments, 0 .. SymbolicAddressSignatureMaxArgs only.
+  const uint8_t numArgs;
+  // The argument types; SymbolicAddressSignatureMaxArgs + 1 guard, which
+  // should be MIRType::None.
+  const jit::MIRType argTypes[SymbolicAddressSignatureMaxArgs + 1];
+};
+
+// The 16 in this assertion is derived as follows: SymbolicAddress is probably
+// size-4 aligned-4, but it's at the start of the struct, so there's no
+// alignment hole before it.  All other components (MIRType and uint8_t) are
+// size-1 aligned-1, and there are 8 in total, so it is reasonable to assume
+// that they also don't create any alignment holes.  Hence it is also
+// reasonable to assume that the actual size is 1 * 4 + 8 * 1 == 12.  The
+// worst-plausible-case rounding will take that up to 16.  Hence, the
+// assertion uses 16.
+
+static_assert(sizeof(SymbolicAddressSignature) <= 16,
+              "SymbolicAddressSignature unexpectedly large");
+
 bool IsRoundingFunction(SymbolicAddress callee, jit::RoundingMode* mode);
 
 // Represents the resizable limits of memories and tables.
 
 struct Limits {
   uint32_t initial;
   Maybe<uint32_t> maximum;