Bug 1516697 - Make AsmJSParser a template typedef and {Module,Function}Validator template classes. r=arai
☠☠ backed out by f8a29b8bb211 ☠ ☠
authorJeff Walden <jwalden@mit.edu>
Fri, 28 Dec 2018 21:00:57 -0600
changeset 509716 d1267711aef0a8031752776607483d6ac5e31dde
parent 509715 df1b7a886a9d92c035cd11eb816c797e97a1dd1d
child 509717 3fd283d8fe546b14c2f6eb49d68527eb3b330898
push id10547
push userffxbld-merge
push dateMon, 21 Jan 2019 13:03:58 +0000
treeherdermozilla-beta@24ec1916bffe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersarai
bugs1516697
milestone66.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 1516697 - Make AsmJSParser a template typedef and {Module,Function}Validator template classes. r=arai
js/src/wasm/AsmJS.cpp
js/src/wasm/AsmJS.h
--- a/js/src/wasm/AsmJS.cpp
+++ b/js/src/wasm/AsmJS.cpp
@@ -200,16 +200,17 @@ class AsmJSGlobal {
         ConstantKind kind_;
         double value_;
       } constant;
     } u;
   } pod;
   CacheableChars field_;
 
   friend class ModuleValidatorShared;
+  template <typename Unit>
   friend class ModuleValidator;
 
  public:
   AsmJSGlobal() = default;
   AsmJSGlobal(Which which, UniqueChars field) {
     mozilla::PodZero(&pod);  // zero padding for Valgrind
     pod.which_ = which;
     field_ = std::move(field);
@@ -641,48 +642,52 @@ static inline ParseNode* SkipEmptyStatem
   }
   return pn;
 }
 
 static inline ParseNode* NextNonEmptyStatement(ParseNode* pn) {
   return SkipEmptyStatements(pn->pn_next);
 }
 
-static bool GetToken(AsmJSParser& parser, TokenKind* tkp) {
+template <typename Unit>
+static bool GetToken(AsmJSParser<Unit>& parser, TokenKind* tkp) {
   auto& ts = parser.tokenStream;
   TokenKind tk;
   while (true) {
     if (!ts.getToken(&tk, TokenStreamShared::Operand)) {
       return false;
     }
     if (tk != TokenKind::Semi) {
       break;
     }
   }
   *tkp = tk;
   return true;
 }
 
-static bool PeekToken(AsmJSParser& parser, TokenKind* tkp) {
+template <typename Unit>
+static bool PeekToken(AsmJSParser<Unit>& parser, TokenKind* tkp) {
   auto& ts = parser.tokenStream;
   TokenKind tk;
   while (true) {
     if (!ts.peekToken(&tk, TokenStream::Operand)) {
       return false;
     }
     if (tk != TokenKind::Semi) {
       break;
     }
     ts.consumeKnownToken(TokenKind::Semi, TokenStreamShared::Operand);
   }
   *tkp = tk;
   return true;
 }
 
-static bool ParseVarOrConstStatement(AsmJSParser& parser, ParseNode** var) {
+template <typename Unit>
+static bool ParseVarOrConstStatement(AsmJSParser<Unit>& parser,
+                                     ParseNode** var) {
   TokenKind tk;
   if (!PeekToken(parser, &tk)) {
     return false;
   }
   if (tk != TokenKind::Var && tk != TokenKind::Const) {
     *var = nullptr;
     return true;
   }
@@ -1192,16 +1197,17 @@ class MOZ_STACK_CLASS JS_HAZ_ROOTED Modu
       // non-trivial constructor and therefore MUST be placement-new'd
       // into existence.
       MOZ_PUSH_DISABLE_NONTRIVIAL_UNION_WARNINGS
       U() : funcDefIndex_(0) {}
       MOZ_POP_DISABLE_NONTRIVIAL_UNION_WARNINGS
     } u;
 
     friend class ModuleValidatorShared;
+    template <typename Unit>
     friend class ModuleValidator;
     friend class js::LifoAlloc;
 
     explicit Global(Which which) : which_(which) {}
 
    public:
     Which which() const { return which_; }
     Type varOrConstType() const {
@@ -1867,23 +1873,24 @@ protected:
 // Rooting note: ModuleValidator is a stack class that contains unrooted
 // PropertyName (JSAtom) pointers.  This is safe because it cannot be
 // constructed without a TokenStream reference.  TokenStream is itself a stack
 // class that cannot be constructed without an AutoKeepAtoms being live on the
 // stack, which prevents collection of atoms.
 //
 // ModuleValidator is marked as rooted in the rooting analysis.  Don't add
 // non-JSAtom pointers, or this will break!
+template <typename Unit>
 class MOZ_STACK_CLASS JS_HAZ_ROOTED ModuleValidator
     : public ModuleValidatorShared {
  private:
-  AsmJSParser& parser_;
+  AsmJSParser<Unit>& parser_;
 
  public:
-  ModuleValidator(JSContext* cx, AsmJSParser& parser,
+  ModuleValidator(JSContext* cx, AsmJSParser<Unit>& parser,
                   CodeNode* moduleFunctionNode)
       : ModuleValidatorShared(cx, moduleFunctionNode), parser_(parser) {}
 
   ~ModuleValidator() {
     if (errorString_) {
       MOZ_ASSERT(errorOffset_ != UINT32_MAX);
       typeFailure(errorOffset_, errorString_.get());
     }
@@ -1963,17 +1970,17 @@ class MOZ_STACK_CLASS JS_HAZ_ROOTED Modu
 
     if (!initDummyFunction()) {
       return false;
     }
 
     return true;
   }
 
-  AsmJSParser& parser() const { return parser_; }
+  AsmJSParser<Unit>& parser() const { return parser_; }
 
   auto tokenStream() const -> decltype(parser_.tokenStream)& {
     return parser_.tokenStream;
   }
 
  public:
   bool addFuncDef(PropertyName* name, uint32_t firstUse, FuncType&& sig,
                   Func** func) {
@@ -2340,17 +2347,17 @@ class MOZ_STACK_CLASS FunctionValidatorS
       MOZ_ASSERT(type.isCanonicalValType());
     }
   };
 
  protected:
   using LocalMap = HashMap<PropertyName*, Local>;
   using LabelMap = HashMap<PropertyName*, uint32_t>;
 
-  // This is also a ModuleValidator& after the appropriate static_cast<>.
+  // This is also a ModuleValidator<Unit>& after the appropriate static_cast<>.
   ModuleValidatorShared& m_;
 
   ParseNode* fn_;
   Bytes bytes_;
   Encoder encoder_;
   Uint32Vector callSiteLineNums_;
   LocalMap locals_;
 
@@ -2373,17 +2380,19 @@ class MOZ_STACK_CLASS FunctionValidatorS
         locals_(cx),
         breakLabels_(cx),
         continueLabels_(cx),
         blockDepth_(0),
         hasAlreadyReturned_(false),
         ret_(ExprType::Limit) {}
 
  protected:
-  FunctionValidatorShared(ModuleValidator& m, ParseNode* fn, JSContext* cx)
+  template <typename Unit>
+  FunctionValidatorShared(ModuleValidator<Unit>& m, ParseNode* fn,
+                          JSContext* cx)
       : FunctionValidatorShared(static_cast<ModuleValidatorShared&>(m), fn,
                                 cx) {}
 
  public:
   ModuleValidatorShared& m() const { return m_; }
 
   JSContext* cx() const { return m_.cx(); }
   ParseNode* fn() const { return fn_; }
@@ -2605,24 +2614,25 @@ class MOZ_STACK_CLASS FunctionValidatorS
     }
     MOZ_CRASH("unexpected literal type");
   }
 };
 
 // Encapsulates the building of an asm bytecode function from an asm.js function
 // source code, packing the asm.js code into the asm bytecode form that can
 // be decoded and compiled with a FunctionCompiler.
+template <typename Unit>
 class MOZ_STACK_CLASS FunctionValidator : public FunctionValidatorShared {
  public:
-  FunctionValidator(ModuleValidator& m, ParseNode* fn)
+  FunctionValidator(ModuleValidator<Unit>& m, ParseNode* fn)
       : FunctionValidatorShared(m, fn, m.cx()) {}
 
  public:
-  ModuleValidator& m() const {
-    return static_cast<ModuleValidator&>(FunctionValidatorShared::m());
+  ModuleValidator<Unit>& m() const {
+    return static_cast<ModuleValidator<Unit>&>(FunctionValidatorShared::m());
   }
 
   MOZ_MUST_USE bool writeCall(ParseNode* pn, Op op) {
     if (!encoder().writeOp(op)) {
       return false;
     }
 
     return appendCallSiteLineNumber(pn);
@@ -3069,17 +3079,18 @@ static bool CheckModuleGlobal(ModuleVali
 
   if (initNode->isKind(ParseNodeKind::DotExpr)) {
     return CheckGlobalDotImport(m, varName, initNode);
   }
 
   return m.fail(initNode, "unsupported import expression");
 }
 
-static bool CheckModuleProcessingDirectives(ModuleValidator& m) {
+template <typename Unit>
+static bool CheckModuleProcessingDirectives(ModuleValidator<Unit>& m) {
   auto& ts = m.parser().tokenStream;
   while (true) {
     bool matched;
     if (!ts.matchToken(&matched, TokenKind::String,
                        TokenStreamShared::Operand)) {
       return false;
     }
     if (!matched) {
@@ -3096,17 +3107,18 @@ static bool CheckModuleProcessingDirecti
       return false;
     }
     if (tt != TokenKind::Semi) {
       return m.failCurrentOffset("expected semicolon after string literal");
     }
   }
 }
 
-static bool CheckModuleGlobals(ModuleValidator& m) {
+template <typename Unit>
+static bool CheckModuleGlobals(ModuleValidator<Unit>& m) {
   while (true) {
     ParseNode* varStmt;
     if (!ParseVarOrConstStatement(m.parser(), &varStmt)) {
       return false;
     }
     if (!varStmt) {
       break;
     }
@@ -3322,19 +3334,21 @@ static bool CheckVariables(FunctionValid
       return false;
     }
   }
 
   *stmtIter = stmt;
   return true;
 }
 
-static bool CheckExpr(FunctionValidator& f, ParseNode* op, Type* type);
-
-static bool CheckNumericLiteral(FunctionValidator& f, ParseNode* num,
+template <typename Unit>
+static bool CheckExpr(FunctionValidator<Unit>& f, ParseNode* op, Type* type);
+
+template <typename Unit>
+static bool CheckNumericLiteral(FunctionValidator<Unit>& f, ParseNode* num,
                                 Type* type) {
   NumLit lit = ExtractNumericLiteral(f.m(), num);
   if (!lit.valid()) {
     return f.fail(num, "numeric literal out of representable integer range");
   }
   *type = Type::lit(lit);
   return f.writeConstExpr(lit);
 }
@@ -3388,17 +3402,18 @@ static inline bool IsLiteralOrConstInt(F
     return false;
   }
 
   return IsLiteralInt(lit, u32);
 }
 
 static const int32_t NoMask = -1;
 
-static bool CheckArrayAccess(FunctionValidator& f, ParseNode* viewName,
+template <typename Unit>
+static bool CheckArrayAccess(FunctionValidator<Unit>& f, ParseNode* viewName,
                              ParseNode* indexExpr, Scalar::Type* viewType) {
   if (!viewName->isKind(ParseNodeKind::Name)) {
     return f.fail(viewName,
                   "base of array access must be a typed array view name");
   }
 
   const ModuleValidatorShared::Global* global =
       f.lookupGlobal(viewName->as<NameNode>().name());
@@ -3493,17 +3508,19 @@ static bool WriteArrayAccessFlags(Functi
   // asm.js doesn't have constant offsets, so just encode a 0.
   if (!f.encoder().writeVarU32(0)) {
     return false;
   }
 
   return true;
 }
 
-static bool CheckLoadArray(FunctionValidator& f, ParseNode* elem, Type* type) {
+template <typename Unit>
+static bool CheckLoadArray(FunctionValidator<Unit>& f, ParseNode* elem,
+                           Type* type) {
   Scalar::Type viewType;
 
   if (!CheckArrayAccess(f, ElemBase(elem), ElemIndex(elem), &viewType)) {
     return false;
   }
 
   switch (viewType) {
     case Scalar::Int8:
@@ -3553,17 +3570,18 @@ static bool CheckLoadArray(FunctionValid
 
   if (!WriteArrayAccessFlags(f, viewType)) {
     return false;
   }
 
   return true;
 }
 
-static bool CheckStoreArray(FunctionValidator& f, ParseNode* lhs,
+template <typename Unit>
+static bool CheckStoreArray(FunctionValidator<Unit>& f, ParseNode* lhs,
                             ParseNode* rhs, Type* type) {
   Scalar::Type viewType;
   if (!CheckArrayAccess(f, ElemBase(lhs), ElemIndex(lhs), &viewType)) {
     return false;
   }
 
   Type rhsType;
   if (!CheckExpr(f, rhs, &rhsType)) {
@@ -3645,17 +3663,18 @@ static bool CheckStoreArray(FunctionVali
   if (!WriteArrayAccessFlags(f, viewType)) {
     return false;
   }
 
   *type = rhsType;
   return true;
 }
 
-static bool CheckAssignName(FunctionValidator& f, ParseNode* lhs,
+template <typename Unit>
+static bool CheckAssignName(FunctionValidator<Unit>& f, ParseNode* lhs,
                             ParseNode* rhs, Type* type) {
   RootedPropertyName name(f.cx(), lhs->as<NameNode>().name());
 
   if (const FunctionValidatorShared::Local* lhsVar = f.lookupLocal(name)) {
     Type rhsType;
     if (!CheckExpr(f, rhs, &rhsType)) {
       return false;
     }
@@ -3700,17 +3719,19 @@ static bool CheckAssignName(FunctionVali
     *type = rhsType;
     return true;
   }
 
   return f.failName(lhs, "'%s' not found in local or asm.js module scope",
                     name);
 }
 
-static bool CheckAssign(FunctionValidator& f, ParseNode* assign, Type* type) {
+template <typename Unit>
+static bool CheckAssign(FunctionValidator<Unit>& f, ParseNode* assign,
+                        Type* type) {
   MOZ_ASSERT(assign->isKind(ParseNodeKind::AssignExpr));
 
   ParseNode* lhs = BinaryLeft(assign);
   ParseNode* rhs = BinaryRight(assign);
 
   if (lhs->getKind() == ParseNodeKind::ElemExpr) {
     return CheckStoreArray(f, lhs, rhs, type);
   }
@@ -3719,17 +3740,19 @@ static bool CheckAssign(FunctionValidato
     return CheckAssignName(f, lhs, rhs, type);
   }
 
   return f.fail(
       assign,
       "left-hand side of assignment must be a variable or array access");
 }
 
-static bool CheckMathIMul(FunctionValidator& f, ParseNode* call, Type* type) {
+template <typename Unit>
+static bool CheckMathIMul(FunctionValidator<Unit>& f, ParseNode* call,
+                          Type* type) {
   if (CallArgListLength(call) != 2) {
     return f.fail(call, "Math.imul must be passed 2 arguments");
   }
 
   ParseNode* lhs = CallArgList(call);
   ParseNode* rhs = NextNode(lhs);
 
   Type lhsType;
@@ -3748,17 +3771,19 @@ static bool CheckMathIMul(FunctionValida
   if (!rhsType.isIntish()) {
     return f.failf(rhs, "%s is not a subtype of intish", rhsType.toChars());
   }
 
   *type = Type::Signed;
   return f.encoder().writeOp(Op::I32Mul);
 }
 
-static bool CheckMathClz32(FunctionValidator& f, ParseNode* call, Type* type) {
+template <typename Unit>
+static bool CheckMathClz32(FunctionValidator<Unit>& f, ParseNode* call,
+                           Type* type) {
   if (CallArgListLength(call) != 1) {
     return f.fail(call, "Math.clz32 must be passed 1 argument");
   }
 
   ParseNode* arg = CallArgList(call);
 
   Type argType;
   if (!CheckExpr(f, arg, &argType)) {
@@ -3768,17 +3793,19 @@ static bool CheckMathClz32(FunctionValid
   if (!argType.isIntish()) {
     return f.failf(arg, "%s is not a subtype of intish", argType.toChars());
   }
 
   *type = Type::Fixnum;
   return f.encoder().writeOp(Op::I32Clz);
 }
 
-static bool CheckMathAbs(FunctionValidator& f, ParseNode* call, Type* type) {
+template <typename Unit>
+static bool CheckMathAbs(FunctionValidator<Unit>& f, ParseNode* call,
+                         Type* type) {
   if (CallArgListLength(call) != 1) {
     return f.fail(call, "Math.abs must be passed 1 argument");
   }
 
   ParseNode* arg = CallArgList(call);
 
   Type argType;
   if (!CheckExpr(f, arg, &argType)) {
@@ -3799,17 +3826,19 @@ static bool CheckMathAbs(FunctionValidat
     *type = Type::Floatish;
     return f.encoder().writeOp(Op::F32Abs);
   }
 
   return f.failf(call, "%s is not a subtype of signed, float? or double?",
                  argType.toChars());
 }
 
-static bool CheckMathSqrt(FunctionValidator& f, ParseNode* call, Type* type) {
+template <typename Unit>
+static bool CheckMathSqrt(FunctionValidator<Unit>& f, ParseNode* call,
+                          Type* type) {
   if (CallArgListLength(call) != 1) {
     return f.fail(call, "Math.sqrt must be passed 1 argument");
   }
 
   ParseNode* arg = CallArgList(call);
 
   Type argType;
   if (!CheckExpr(f, arg, &argType)) {
@@ -3825,17 +3854,18 @@ static bool CheckMathSqrt(FunctionValida
     *type = Type::Floatish;
     return f.encoder().writeOp(Op::F32Sqrt);
   }
 
   return f.failf(call, "%s is neither a subtype of double? nor float?",
                  argType.toChars());
 }
 
-static bool CheckMathMinMax(FunctionValidator& f, ParseNode* callNode,
+template <typename Unit>
+static bool CheckMathMinMax(FunctionValidator<Unit>& f, ParseNode* callNode,
                             bool isMax, Type* type) {
   if (CallArgListLength(callNode) < 2) {
     return f.fail(callNode, "Math.min/max must be passed at least 2 arguments");
   }
 
   ParseNode* firstArg = CallArgList(callNode);
   Type firstType;
   if (!CheckExpr(f, firstArg, &firstType)) {
@@ -3885,18 +3915,18 @@ static bool CheckMathMinMax(FunctionVali
   }
 
   return true;
 }
 
 using CheckArgType = bool (*)(FunctionValidatorShared& f, ParseNode* argNode,
                               Type type);
 
-template <CheckArgType checkArg>
-static bool CheckCallArgs(FunctionValidator& f, ParseNode* callNode,
+template <CheckArgType checkArg, typename Unit>
+static bool CheckCallArgs(FunctionValidator<Unit>& f, ParseNode* callNode,
                           ValTypeVector* args) {
   ParseNode* argNode = CallArgList(callNode);
   for (unsigned i = 0; i < CallArgListLength(callNode);
        i++, argNode = NextNode(argNode)) {
     Type type;
     if (!CheckExpr(f, argNode, &type)) {
       return false;
     }
@@ -3934,17 +3964,18 @@ static bool CheckSignatureAgainstExistin
     return m.failf(usepn, "%s incompatible with previous return of type %s",
                    ToCString(sig.ret()), ToCString(existing.ret()));
   }
 
   MOZ_ASSERT(sig == existing);
   return true;
 }
 
-static bool CheckFunctionSignature(ModuleValidator& m, ParseNode* usepn,
+template <typename Unit>
+static bool CheckFunctionSignature(ModuleValidator<Unit>& m, ParseNode* usepn,
                                    FuncType&& sig, PropertyName* name,
                                    ModuleValidatorShared::Func** func) {
   if (sig.args().length() > MaxParams) {
     return m.failf(usepn, "too many parameters");
   }
 
   ModuleValidatorShared::Func* existing = m.lookupFuncDef(name);
   if (!existing) {
@@ -3969,17 +4000,18 @@ static bool CheckIsArgType(FunctionValid
                            Type type) {
   if (!type.isArgType()) {
     return f.failf(argNode, "%s is not a subtype of int, float, or double",
                    type.toChars());
   }
   return true;
 }
 
-static bool CheckInternalCall(FunctionValidator& f, ParseNode* callNode,
+template <typename Unit>
+static bool CheckInternalCall(FunctionValidator<Unit>& f, ParseNode* callNode,
                               PropertyName* calleeName, Type ret, Type* type) {
   MOZ_ASSERT(ret.isCanonical());
 
   ValTypeVector args;
   if (!CheckCallArgs<CheckIsArgType>(f, callNode, &args)) {
     return false;
   }
 
@@ -3998,17 +4030,18 @@ static bool CheckInternalCall(FunctionVa
   if (!f.encoder().writeVarU32(callee->funcDefIndex())) {
     return false;
   }
 
   *type = Type::ret(ret);
   return true;
 }
 
-static bool CheckFuncPtrTableAgainstExisting(ModuleValidator& m,
+template <typename Unit>
+static bool CheckFuncPtrTableAgainstExisting(ModuleValidator<Unit>& m,
                                              ParseNode* usepn,
                                              PropertyName* name, FuncType&& sig,
                                              unsigned mask,
                                              uint32_t* tableIndex) {
   if (const ModuleValidatorShared::Global* existing = m.lookupGlobal(name)) {
     if (existing->which() != ModuleValidatorShared::Global::Table) {
       return m.failName(usepn, "'%s' is not a function-pointer table", name);
     }
@@ -4035,17 +4068,18 @@ static bool CheckFuncPtrTableAgainstExis
   if (!m.declareFuncPtrTable(std::move(sig), name, usepn->pn_pos.begin, mask,
                              tableIndex)) {
     return false;
   }
 
   return true;
 }
 
-static bool CheckFuncPtrCall(FunctionValidator& f, ParseNode* callNode,
+template <typename Unit>
+static bool CheckFuncPtrCall(FunctionValidator<Unit>& f, ParseNode* callNode,
                              Type ret, Type* type) {
   MOZ_ASSERT(ret.isCanonical());
 
   ParseNode* callee = CallCallee(callNode);
   ParseNode* tableNode = ElemBase(callee);
   ParseNode* indexExpr = ElemIndex(callee);
 
   if (!tableNode->isKind(ParseNodeKind::Name)) {
@@ -4115,17 +4149,18 @@ static bool CheckFuncPtrCall(FunctionVal
 static bool CheckIsExternType(FunctionValidatorShared& f, ParseNode* argNode,
                               Type type) {
   if (!type.isExtern()) {
     return f.failf(argNode, "%s is not a subtype of extern", type.toChars());
   }
   return true;
 }
 
-static bool CheckFFICall(FunctionValidator& f, ParseNode* callNode,
+template <typename Unit>
+static bool CheckFFICall(FunctionValidator<Unit>& f, ParseNode* callNode,
                          unsigned ffiIndex, Type ret, Type* type) {
   MOZ_ASSERT(ret.isCanonical());
 
   PropertyName* calleeName = CallCallee(callNode)->as<NameNode>().name();
 
   if (ret.isFloat()) {
     return f.fail(callNode, "FFI calls can't return float");
   }
@@ -4170,20 +4205,22 @@ static bool CheckFloatCoercionArg(Functi
     return true;
   }
 
   return f.failf(inputNode,
                  "%s is not a subtype of signed, unsigned, double? or floatish",
                  inputType.toChars());
 }
 
-static bool CheckCoercedCall(FunctionValidator& f, ParseNode* call, Type ret,
-                             Type* type);
-
-static bool CheckCoercionArg(FunctionValidator& f, ParseNode* arg,
+template <typename Unit>
+static bool CheckCoercedCall(FunctionValidator<Unit>& f, ParseNode* call,
+                             Type ret, Type* type);
+
+template <typename Unit>
+static bool CheckCoercionArg(FunctionValidator<Unit>& f, ParseNode* arg,
                              Type expected, Type* type) {
   MOZ_ASSERT(expected.isCanonicalValType());
 
   if (arg->isKind(ParseNodeKind::CallExpr)) {
     return CheckCoercedCall(f, arg, expected, type);
   }
 
   Type argType;
@@ -4198,34 +4235,37 @@ static bool CheckCoercionArg(FunctionVal
   } else {
     MOZ_CRASH("not call coercions");
   }
 
   *type = Type::ret(expected);
   return true;
 }
 
-static bool CheckMathFRound(FunctionValidator& f, ParseNode* callNode,
+template <typename Unit>
+static bool CheckMathFRound(FunctionValidator<Unit>& f, ParseNode* callNode,
                             Type* type) {
   if (CallArgListLength(callNode) != 1) {
     return f.fail(callNode, "Math.fround must be passed 1 argument");
   }
 
   ParseNode* argNode = CallArgList(callNode);
   Type argType;
   if (!CheckCoercionArg(f, argNode, Type::Float, &argType)) {
     return false;
   }
 
   MOZ_ASSERT(argType == Type::Float);
   *type = Type::Float;
   return true;
 }
 
-static bool CheckMathBuiltinCall(FunctionValidator& f, ParseNode* callNode,
+template <typename Unit>
+static bool CheckMathBuiltinCall(FunctionValidator<Unit>& f,
+                                 ParseNode* callNode,
                                  AsmJSMathBuiltinFunction func, Type* type) {
   unsigned arity = 0;
   Op f32 = Op::Limit;
   Op f64 = Op::Limit;
   MozOp mozf64 = MozOp::Limit;
   switch (func) {
     case AsmJSMathBuiltin_imul:
       return CheckMathIMul(f, callNode, type);
@@ -4366,17 +4406,18 @@ static bool CheckMathBuiltinCall(Functio
       return false;
     }
   }
 
   *type = opIsDouble ? Type::Double : Type::Floatish;
   return true;
 }
 
-static bool CheckUncoercedCall(FunctionValidator& f, ParseNode* expr,
+template <typename Unit>
+static bool CheckUncoercedCall(FunctionValidator<Unit>& f, ParseNode* expr,
                                Type* type) {
   MOZ_ASSERT(expr->isKind(ParseNodeKind::CallExpr));
 
   const ModuleValidatorShared::Global* global;
   if (IsCallToGlobal(f.m(), expr, &global) && global->isMathFunction()) {
     return CheckMathBuiltinCall(f, expr, global->mathBuiltinFunction(), type);
   }
 
@@ -4435,29 +4476,31 @@ static bool CoerceResult(FunctionValidat
     default:
       MOZ_CRASH("unexpected uncoerced result type");
   }
 
   *type = Type::ret(expected);
   return true;
 }
 
-static bool CheckCoercedMathBuiltinCall(FunctionValidator& f,
+template <typename Unit>
+static bool CheckCoercedMathBuiltinCall(FunctionValidator<Unit>& f,
                                         ParseNode* callNode,
                                         AsmJSMathBuiltinFunction func, Type ret,
                                         Type* type) {
   Type actual;
   if (!CheckMathBuiltinCall(f, callNode, func, &actual)) {
     return false;
   }
   return CoerceResult(f, callNode, ret, actual, type);
 }
 
-static bool CheckCoercedCall(FunctionValidator& f, ParseNode* call, Type ret,
-                             Type* type) {
+template <typename Unit>
+static bool CheckCoercedCall(FunctionValidator<Unit>& f, ParseNode* call,
+                             Type ret, Type* type) {
   MOZ_ASSERT(ret.isCanonical());
 
   if (!CheckRecursionLimitDontReport(f.cx())) {
     return f.m().failOverRecursed();
   }
 
   if (IsNumericLiteral(f.m(), call)) {
     NumLit lit = ExtractNumericLiteral(f.m(), call);
@@ -4497,33 +4540,35 @@ static bool CheckCoercedCall(FunctionVal
       case ModuleValidatorShared::Global::Function:
         break;
     }
   }
 
   return CheckInternalCall(f, call, calleeName, ret, type);
 }
 
-static bool CheckPos(FunctionValidator& f, ParseNode* pos, Type* type) {
+template <typename Unit>
+static bool CheckPos(FunctionValidator<Unit>& f, ParseNode* pos, Type* type) {
   MOZ_ASSERT(pos->isKind(ParseNodeKind::PosExpr));
   ParseNode* operand = UnaryKid(pos);
 
   if (operand->isKind(ParseNodeKind::CallExpr)) {
     return CheckCoercedCall(f, operand, Type::Double, type);
   }
 
   Type actual;
   if (!CheckExpr(f, operand, &actual)) {
     return false;
   }
 
   return CoerceResult(f, operand, Type::Double, actual, type);
 }
 
-static bool CheckNot(FunctionValidator& f, ParseNode* expr, Type* type) {
+template <typename Unit>
+static bool CheckNot(FunctionValidator<Unit>& f, ParseNode* expr, Type* type) {
   MOZ_ASSERT(expr->isKind(ParseNodeKind::NotExpr));
   ParseNode* operand = UnaryKid(expr);
 
   Type operandType;
   if (!CheckExpr(f, operand, &operandType)) {
     return false;
   }
 
@@ -4531,17 +4576,18 @@ static bool CheckNot(FunctionValidator& 
     return f.failf(operand, "%s is not a subtype of int",
                    operandType.toChars());
   }
 
   *type = Type::Int;
   return f.encoder().writeOp(Op::I32Eqz);
 }
 
-static bool CheckNeg(FunctionValidator& f, ParseNode* expr, Type* type) {
+template <typename Unit>
+static bool CheckNeg(FunctionValidator<Unit>& f, ParseNode* expr, Type* type) {
   MOZ_ASSERT(expr->isKind(ParseNodeKind::NegExpr));
   ParseNode* operand = UnaryKid(expr);
 
   Type operandType;
   if (!CheckExpr(f, operand, &operandType)) {
     return false;
   }
 
@@ -4559,17 +4605,18 @@ static bool CheckNeg(FunctionValidator& 
     *type = Type::Floatish;
     return f.encoder().writeOp(Op::F32Neg);
   }
 
   return f.failf(operand, "%s is not a subtype of int, float? or double?",
                  operandType.toChars());
 }
 
-static bool CheckCoerceToInt(FunctionValidator& f, ParseNode* expr,
+template <typename Unit>
+static bool CheckCoerceToInt(FunctionValidator<Unit>& f, ParseNode* expr,
                              Type* type) {
   MOZ_ASSERT(expr->isKind(ParseNodeKind::BitNotExpr));
   ParseNode* operand = UnaryKid(expr);
 
   Type operandType;
   if (!CheckExpr(f, operand, &operandType)) {
     return false;
   }
@@ -4585,17 +4632,19 @@ static bool CheckCoerceToInt(FunctionVal
     return f.failf(operand, "%s is not a subtype of double?, float? or intish",
                    operandType.toChars());
   }
 
   *type = Type::Signed;
   return true;
 }
 
-static bool CheckBitNot(FunctionValidator& f, ParseNode* neg, Type* type) {
+template <typename Unit>
+static bool CheckBitNot(FunctionValidator<Unit>& f, ParseNode* neg,
+                        Type* type) {
   MOZ_ASSERT(neg->isKind(ParseNodeKind::BitNotExpr));
   ParseNode* operand = UnaryKid(neg);
 
   if (operand->isKind(ParseNodeKind::BitNotExpr)) {
     return CheckCoerceToInt(f, operand, type);
   }
 
   Type operandType;
@@ -4611,19 +4660,23 @@ static bool CheckBitNot(FunctionValidato
   if (!f.encoder().writeOp(MozOp::I32BitNot)) {
     return false;
   }
 
   *type = Type::Signed;
   return true;
 }
 
-static bool CheckAsExprStatement(FunctionValidator& f, ParseNode* exprStmt);
-
-static bool CheckComma(FunctionValidator& f, ParseNode* comma, Type* type) {
+template <typename Unit>
+static bool CheckAsExprStatement(FunctionValidator<Unit>& f,
+                                 ParseNode* exprStmt);
+
+template <typename Unit>
+static bool CheckComma(FunctionValidator<Unit>& f, ParseNode* comma,
+                       Type* type) {
   MOZ_ASSERT(comma->isKind(ParseNodeKind::CommaExpr));
   ParseNode* operands = ListHead(comma);
 
   // The block depth isn't taken into account here, because a comma list can't
   // contain breaks and continues and nested control flow structures.
   if (!f.encoder().writeOp(Op::Block)) {
     return false;
   }
@@ -4645,17 +4698,18 @@ static bool CheckComma(FunctionValidator
   }
 
   f.encoder().patchFixedU7(typeAt,
                            uint8_t(type->toWasmBlockSignatureType().code()));
 
   return f.encoder().writeOp(Op::End);
 }
 
-static bool CheckConditional(FunctionValidator& f, ParseNode* ternary,
+template <typename Unit>
+static bool CheckConditional(FunctionValidator<Unit>& f, ParseNode* ternary,
                              Type* type) {
   MOZ_ASSERT(ternary->isKind(ParseNodeKind::ConditionalExpr));
 
   ParseNode* cond = TernaryKid1(ternary);
   ParseNode* thenExpr = TernaryKid2(ternary);
   ParseNode* elseExpr = TernaryKid3(ternary);
 
   Type condType;
@@ -4702,17 +4756,19 @@ static bool CheckConditional(FunctionVal
 
   if (!f.popIf(typeAt, type->toWasmBlockSignatureType())) {
     return false;
   }
 
   return true;
 }
 
-static bool IsValidIntMultiplyConstant(ModuleValidator& m, ParseNode* expr) {
+template <typename Unit>
+static bool IsValidIntMultiplyConstant(ModuleValidator<Unit>& m,
+                                       ParseNode* expr) {
   if (!IsNumericLiteral(m, expr)) {
     return false;
   }
 
   NumLit lit = ExtractNumericLiteral(m, expr);
   switch (lit.which()) {
     case NumLit::Fixnum:
     case NumLit::NegativeInt:
@@ -4725,17 +4781,19 @@ static bool IsValidIntMultiplyConstant(M
     case NumLit::Float:
     case NumLit::OutOfRangeInt:
       return false;
   }
 
   MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Bad literal");
 }
 
-static bool CheckMultiply(FunctionValidator& f, ParseNode* star, Type* type) {
+template <typename Unit>
+static bool CheckMultiply(FunctionValidator<Unit>& f, ParseNode* star,
+                          Type* type) {
   MOZ_ASSERT(star->isKind(ParseNodeKind::MulExpr));
   ParseNode* lhs = MultiplyLeft(star);
   ParseNode* rhs = MultiplyRight(star);
 
   Type lhsType;
   if (!CheckExpr(f, lhs, &lhsType)) {
     return false;
   }
@@ -4765,18 +4823,19 @@ static bool CheckMultiply(FunctionValida
     *type = Type::Floatish;
     return f.encoder().writeOp(Op::F32Mul);
   }
 
   return f.fail(
       star, "multiply operands must be both int, both double? or both float?");
 }
 
-static bool CheckAddOrSub(FunctionValidator& f, ParseNode* expr, Type* type,
-                          unsigned* numAddOrSubOut = nullptr) {
+template <typename Unit>
+static bool CheckAddOrSub(FunctionValidator<Unit>& f, ParseNode* expr,
+                          Type* type, unsigned* numAddOrSubOut = nullptr) {
   if (!CheckRecursionLimitDontReport(f.cx())) {
     return f.m().failOverRecursed();
   }
 
   MOZ_ASSERT(expr->isKind(ParseNodeKind::AddExpr) ||
              expr->isKind(ParseNodeKind::SubExpr));
   ParseNode* lhs = AddSubLeft(expr);
   ParseNode* rhs = AddSubRight(expr);
@@ -4845,17 +4904,19 @@ static bool CheckAddOrSub(FunctionValida
   }
 
   if (numAddOrSubOut) {
     *numAddOrSubOut = numAddOrSub;
   }
   return true;
 }
 
-static bool CheckDivOrMod(FunctionValidator& f, ParseNode* expr, Type* type) {
+template <typename Unit>
+static bool CheckDivOrMod(FunctionValidator<Unit>& f, ParseNode* expr,
+                          Type* type) {
   MOZ_ASSERT(expr->isKind(ParseNodeKind::DivExpr) ||
              expr->isKind(ParseNodeKind::ModExpr));
 
   ParseNode* lhs = DivOrModLeft(expr);
   ParseNode* rhs = DivOrModRight(expr);
 
   Type lhsType, rhsType;
   if (!CheckExpr(f, lhs, &lhsType)) {
@@ -4896,17 +4957,19 @@ static bool CheckDivOrMod(FunctionValida
 
   return f.failf(
       expr,
       "arguments to / or %% must both be double?, float?, signed, or unsigned; "
       "%s and %s are given",
       lhsType.toChars(), rhsType.toChars());
 }
 
-static bool CheckComparison(FunctionValidator& f, ParseNode* comp, Type* type) {
+template <typename Unit>
+static bool CheckComparison(FunctionValidator<Unit>& f, ParseNode* comp,
+                            Type* type) {
   MOZ_ASSERT(comp->isKind(ParseNodeKind::LtExpr) ||
              comp->isKind(ParseNodeKind::LeExpr) ||
              comp->isKind(ParseNodeKind::GtExpr) ||
              comp->isKind(ParseNodeKind::GeExpr) ||
              comp->isKind(ParseNodeKind::EqExpr) ||
              comp->isKind(ParseNodeKind::NeExpr));
 
   ParseNode* lhs = ComparisonLeft(comp);
@@ -5027,17 +5090,19 @@ static bool CheckComparison(FunctionVali
   } else {
     MOZ_CRASH("unexpected type");
   }
 
   *type = Type::Int;
   return f.encoder().writeOp(stmt);
 }
 
-static bool CheckBitwise(FunctionValidator& f, ParseNode* bitwise, Type* type) {
+template <typename Unit>
+static bool CheckBitwise(FunctionValidator<Unit>& f, ParseNode* bitwise,
+                         Type* type) {
   ParseNode* lhs = BitwiseLeft(bitwise);
   ParseNode* rhs = BitwiseRight(bitwise);
 
   int32_t identityElement;
   bool onlyOnRight;
   switch (bitwise->getKind()) {
     case ParseNodeKind::BitOrExpr:
       identityElement = 0;
@@ -5142,17 +5207,18 @@ static bool CheckBitwise(FunctionValidat
       break;
     default:
       MOZ_CRASH("not a bitwise op");
   }
 
   return true;
 }
 
-static bool CheckExpr(FunctionValidator& f, ParseNode* expr, Type* type) {
+template <typename Unit>
+static bool CheckExpr(FunctionValidator<Unit>& f, ParseNode* expr, Type* type) {
   if (!CheckRecursionLimitDontReport(f.cx())) {
     return f.m().failOverRecursed();
   }
 
   if (IsNumericLiteral(f.m(), expr)) {
     return CheckNumericLiteral(f, expr, type);
   }
 
@@ -5205,19 +5271,21 @@ static bool CheckExpr(FunctionValidator&
       return CheckBitwise(f, expr, type);
 
     default:;
   }
 
   return f.fail(expr, "unsupported expression");
 }
 
-static bool CheckStatement(FunctionValidator& f, ParseNode* stmt);
-
-static bool CheckAsExprStatement(FunctionValidator& f, ParseNode* expr) {
+template <typename Unit>
+static bool CheckStatement(FunctionValidator<Unit>& f, ParseNode* stmt);
+
+template <typename Unit>
+static bool CheckAsExprStatement(FunctionValidator<Unit>& f, ParseNode* expr) {
   if (expr->isKind(ParseNodeKind::CallExpr)) {
     Type ignored;
     return CheckCoercedCall(f, expr, Type::Void, &ignored);
   }
 
   Type resultType;
   if (!CheckExpr(f, expr, &resultType)) {
     return false;
@@ -5227,22 +5295,26 @@ static bool CheckAsExprStatement(Functio
     if (!f.encoder().writeOp(Op::Drop)) {
       return false;
     }
   }
 
   return true;
 }
 
-static bool CheckExprStatement(FunctionValidator& f, ParseNode* exprStmt) {
+template <typename Unit>
+static bool CheckExprStatement(FunctionValidator<Unit>& f,
+                               ParseNode* exprStmt) {
   MOZ_ASSERT(exprStmt->isKind(ParseNodeKind::ExpressionStmt));
   return CheckAsExprStatement(f, UnaryKid(exprStmt));
 }
 
-static bool CheckLoopConditionOnEntry(FunctionValidator& f, ParseNode* cond) {
+template <typename Unit>
+static bool CheckLoopConditionOnEntry(FunctionValidator<Unit>& f,
+                                      ParseNode* cond) {
   uint32_t maybeLit;
   if (IsLiteralInt(f.m(), cond, &maybeLit) && maybeLit) {
     return true;
   }
 
   Type condType;
   if (!CheckExpr(f, cond, &condType)) {
     return false;
@@ -5258,17 +5330,18 @@ static bool CheckLoopConditionOnEntry(Fu
   // brIf (i32.eqz $f) $out
   if (!f.writeBreakIf()) {
     return false;
   }
 
   return true;
 }
 
-static bool CheckWhile(FunctionValidator& f, ParseNode* whileStmt,
+template <typename Unit>
+static bool CheckWhile(FunctionValidator<Unit>& f, ParseNode* whileStmt,
                        const LabelVector* labels = nullptr) {
   MOZ_ASSERT(whileStmt->isKind(ParseNodeKind::WhileStmt));
   ParseNode* cond = BinaryLeft(whileStmt);
   ParseNode* body = BinaryRight(whileStmt);
 
   // A while loop `while(#cond) #body` is equivalent to:
   // (block $after_loop
   //    (loop $top
@@ -5299,17 +5372,18 @@ static bool CheckWhile(FunctionValidator
     return false;
   }
   if (labels) {
     f.removeLabels(*labels);
   }
   return true;
 }
 
-static bool CheckFor(FunctionValidator& f, ParseNode* forStmt,
+template <typename Unit>
+static bool CheckFor(FunctionValidator<Unit>& f, ParseNode* forStmt,
                      const LabelVector* labels = nullptr) {
   MOZ_ASSERT(forStmt->isKind(ParseNodeKind::ForStmt));
   ParseNode* forHead = BinaryLeft(forStmt);
   ParseNode* body = BinaryRight(forStmt);
 
   if (!forHead->isKind(ParseNodeKind::ForHead)) {
     return f.fail(forHead, "unsupported for-loop statement");
   }
@@ -5384,17 +5458,18 @@ static bool CheckFor(FunctionValidator& 
 
   if (labels) {
     f.removeLabels(*labels);
   }
 
   return true;
 }
 
-static bool CheckDoWhile(FunctionValidator& f, ParseNode* whileStmt,
+template <typename Unit>
+static bool CheckDoWhile(FunctionValidator<Unit>& f, ParseNode* whileStmt,
                          const LabelVector* labels = nullptr) {
   MOZ_ASSERT(whileStmt->isKind(ParseNodeKind::DoWhileStmt));
   ParseNode* body = BinaryLeft(whileStmt);
   ParseNode* cond = BinaryRight(whileStmt);
 
   // A do-while loop `do { #body } while (#cond)` is equivalent to:
   // (block $after_loop           // depth X
   //   (loop $top                 // depth X+1
@@ -5441,20 +5516,22 @@ static bool CheckDoWhile(FunctionValidat
     return false;
   }
   if (labels) {
     f.removeLabels(*labels);
   }
   return true;
 }
 
-static bool CheckStatementList(FunctionValidator& f, ParseNode*,
+template <typename Unit>
+static bool CheckStatementList(FunctionValidator<Unit>& f, ParseNode*,
                                const LabelVector* = nullptr);
 
-static bool CheckLabel(FunctionValidator& f, ParseNode* labeledStmt) {
+template <typename Unit>
+static bool CheckLabel(FunctionValidator<Unit>& f, ParseNode* labeledStmt) {
   MOZ_ASSERT(labeledStmt->isKind(ParseNodeKind::LabelStmt));
 
   LabelVector labels;
   ParseNode* innermost = labeledStmt;
   do {
     if (!labels.append(LabeledStatementLabel(innermost))) {
       return false;
     }
@@ -5483,17 +5560,18 @@ static bool CheckLabel(FunctionValidator
   }
 
   if (!f.popUnbreakableBlock(&labels)) {
     return false;
   }
   return true;
 }
 
-static bool CheckIf(FunctionValidator& f, ParseNode* ifStmt) {
+template <typename Unit>
+static bool CheckIf(FunctionValidator<Unit>& f, ParseNode* ifStmt) {
   uint32_t numIfEnd = 1;
 
 recurse:
   MOZ_ASSERT(ifStmt->isKind(ParseNodeKind::IfStmt));
   ParseNode* cond = TernaryKid1(ifStmt);
   ParseNode* thenStmt = TernaryKid2(ifStmt);
   ParseNode* elseStmt = TernaryKid3(ifStmt);
 
@@ -5613,17 +5691,18 @@ static bool CheckSwitchRange(FunctionVal
         initialStmt,
         "all switch statements generate tables; this table would be too big");
   }
 
   *tableLength = uint32_t(i64);
   return true;
 }
 
-static bool CheckSwitchExpr(FunctionValidator& f, ParseNode* switchExpr) {
+template <typename Unit>
+static bool CheckSwitchExpr(FunctionValidator<Unit>& f, ParseNode* switchExpr) {
   Type exprType;
   if (!CheckExpr(f, switchExpr, &exprType)) {
     return false;
   }
   if (!exprType.isSigned()) {
     return f.failf(switchExpr, "%s is not a subtype of signed",
                    exprType.toChars());
   }
@@ -5637,17 +5716,18 @@ static bool CheckSwitchExpr(FunctionVali
 // the end, so the default block always encloses all the cases blocks.
 // - one block per case between low and high; undefined cases just jump to the
 // default case. Each of these blocks contain two statements: the next case's
 // block and the possibly empty statement list comprising the case body. The
 // last block pushed is the first case so the (relative) branch target therefore
 // matches the sequential order of cases.
 // - one block for the br_table, so that the first break goes to the first
 // case's block.
-static bool CheckSwitch(FunctionValidator& f, ParseNode* switchStmt) {
+template <typename Unit>
+static bool CheckSwitch(FunctionValidator<Unit>& f, ParseNode* switchStmt) {
   MOZ_ASSERT(switchStmt->isKind(ParseNodeKind::SwitchStmt));
 
   ParseNode* switchExpr = BinaryLeft(switchStmt);
   ParseNode* switchBody = BinaryRight(switchStmt);
 
   if (switchBody->is<LexicalScopeNode>()) {
     LexicalScopeNode* scope = &switchBody->as<LexicalScopeNode>();
     if (!scope->isEmptyScope()) {
@@ -5799,17 +5879,18 @@ static bool CheckReturnType(FunctionVali
   if (f.returnedType() != ret.canonicalToExprType()) {
     return f.failf(usepn, "%s incompatible with previous return of type %s",
                    Type::ret(ret).toChars(), ToCString(f.returnedType()));
   }
 
   return true;
 }
 
-static bool CheckReturn(FunctionValidator& f, ParseNode* returnStmt) {
+template <typename Unit>
+static bool CheckReturn(FunctionValidator<Unit>& f, ParseNode* returnStmt) {
   ParseNode* expr = ReturnExpr(returnStmt);
 
   if (!expr) {
     if (!CheckReturnType(f, returnStmt, Type::Void)) {
       return false;
     }
   } else {
     Type type;
@@ -5828,17 +5909,18 @@ static bool CheckReturn(FunctionValidato
 
   if (!f.encoder().writeOp(Op::Return)) {
     return false;
   }
 
   return true;
 }
 
-static bool CheckStatementList(FunctionValidator& f, ParseNode* stmtList,
+template <typename Unit>
+static bool CheckStatementList(FunctionValidator<Unit>& f, ParseNode* stmtList,
                                const LabelVector* labels /*= nullptr */) {
   MOZ_ASSERT(stmtList->isKind(ParseNodeKind::StatementList));
 
   if (!f.pushUnbreakableBlock(labels)) {
     return false;
   }
 
   for (ParseNode* stmt = ListHead(stmtList); stmt; stmt = NextNode(stmt)) {
@@ -5848,34 +5930,36 @@ static bool CheckStatementList(FunctionV
   }
 
   if (!f.popUnbreakableBlock(labels)) {
     return false;
   }
   return true;
 }
 
-static bool CheckLexicalScope(FunctionValidator& f, ParseNode* node) {
+template <typename Unit>
+static bool CheckLexicalScope(FunctionValidator<Unit>& f, ParseNode* node) {
   LexicalScopeNode* lexicalScope = &node->as<LexicalScopeNode>();
   if (!lexicalScope->isEmptyScope()) {
     return f.fail(lexicalScope, "cannot have 'let' or 'const' declarations");
   }
 
   return CheckStatement(f, lexicalScope->scopeBody());
 }
 
 static bool CheckBreakOrContinue(FunctionValidatorShared& f, bool isBreak,
                                  ParseNode* stmt) {
   if (PropertyName* maybeLabel = LoopControlMaybeLabel(stmt)) {
     return f.writeLabeledBreakOrContinue(maybeLabel, isBreak);
   }
   return f.writeUnlabeledBreakOrContinue(isBreak);
 }
 
-static bool CheckStatement(FunctionValidator& f, ParseNode* stmt) {
+template <typename Unit>
+static bool CheckStatement(FunctionValidator<Unit>& f, ParseNode* stmt) {
   if (!CheckRecursionLimitDontReport(f.cx())) {
     return f.m().failOverRecursed();
   }
 
   switch (stmt->getKind()) {
     case ParseNodeKind::EmptyStmt:
       return true;
     case ParseNodeKind::ExpressionStmt:
@@ -5903,17 +5987,18 @@ static bool CheckStatement(FunctionValid
     case ParseNodeKind::LexicalScope:
       return CheckLexicalScope(f, stmt);
     default:;
   }
 
   return f.fail(stmt, "unexpected statement kind");
 }
 
-static bool ParseFunction(ModuleValidator& m, CodeNode** funNodeOut,
+template <typename Unit>
+static bool ParseFunction(ModuleValidator<Unit>& m, CodeNode** funNodeOut,
                           unsigned* line) {
   auto& tokenStream = m.tokenStream();
 
   tokenStream.consumeKnownToken(TokenKind::Function,
                                 TokenStreamShared::Operand);
 
   auto& anyChars = tokenStream.anyCharsAccess();
   uint32_t toStringStart = anyChars.currentToken().pos.begin;
@@ -5972,35 +6057,36 @@ static bool ParseFunction(ModuleValidato
 
   MOZ_ASSERT(!anyChars.hadError());
   MOZ_ASSERT(directives == newDirectives);
 
   *funNodeOut = funNode;
   return true;
 }
 
-static bool CheckFunction(ModuleValidator& m) {
+template <typename Unit>
+static bool CheckFunction(ModuleValidator<Unit>& m) {
   // asm.js modules can be quite large when represented as parse trees so pop
   // the backing LifoAlloc after parsing/compiling each function. Release the
   // parser's lifo memory after the last use of a parse node.
-  AsmJSParser::Mark mark = m.parser().mark();
+  frontend::ParserBase::Mark mark = m.parser().mark();
   auto releaseMark =
       mozilla::MakeScopeExit([&m, &mark] { m.parser().release(mark); });
 
   CodeNode* funNode = nullptr;
   unsigned line = 0;
   if (!ParseFunction(m, &funNode, &line)) {
     return false;
   }
 
   if (!CheckFunctionHead(m, funNode)) {
     return false;
   }
 
-  FunctionValidator f(m, funNode);
+  FunctionValidator<Unit> f(m, funNode);
 
   ParseNode* stmtIter = ListHead(FunctionStatementList(funNode));
 
   if (!CheckProcessingDirectives(m, &stmtIter)) {
     return false;
   }
 
   ValTypeVector args;
@@ -6048,17 +6134,18 @@ static bool CheckAllFunctionsDefined(Mod
       return m.failNameOffset(f.firstUse(), "missing definition of function %s",
                               f.name());
     }
   }
 
   return true;
 }
 
-static bool CheckFunctions(ModuleValidator& m) {
+template <typename Unit>
+static bool CheckFunctions(ModuleValidator<Unit>& m) {
   while (true) {
     TokenKind tk;
     if (!PeekToken(m.parser(), &tk)) {
       return false;
     }
 
     if (tk != TokenKind::Function) {
       break;
@@ -6067,17 +6154,18 @@ static bool CheckFunctions(ModuleValidat
     if (!CheckFunction(m)) {
       return false;
     }
   }
 
   return CheckAllFunctionsDefined(m);
 }
 
-static bool CheckFuncPtrTable(ModuleValidator& m, ParseNode* var) {
+template <typename Unit>
+static bool CheckFuncPtrTable(ModuleValidator<Unit>& m, ParseNode* var) {
   if (!var->isKind(ParseNodeKind::Name)) {
     return m.fail(var, "function-pointer table name is not a plain name");
   }
 
   ParseNode* arrayLiteral = MaybeInitializer(var);
   if (!arrayLiteral || !arrayLiteral->isKind(ParseNodeKind::ArrayExpr)) {
     return m.fail(
         var, "function-pointer table's initializer must be an array literal");
@@ -6135,17 +6223,18 @@ static bool CheckFuncPtrTable(ModuleVali
 
   if (!m.defineFuncPtrTable(tableIndex, std::move(elemFuncDefIndices))) {
     return m.fail(var, "duplicate function-pointer definition");
   }
 
   return true;
 }
 
-static bool CheckFuncPtrTables(ModuleValidator& m) {
+template <typename Unit>
+static bool CheckFuncPtrTables(ModuleValidator<Unit>& m) {
   while (true) {
     ParseNode* varStmt;
     if (!ParseVarOrConstStatement(m.parser(), &varStmt)) {
       return false;
     }
     if (!varStmt) {
       break;
     }
@@ -6206,17 +6295,18 @@ static bool CheckModuleExportObject(Modu
     if (!CheckModuleExportFunction(m, initNode, fieldName)) {
       return false;
     }
   }
 
   return true;
 }
 
-static bool CheckModuleReturn(ModuleValidator& m) {
+template <typename Unit>
+static bool CheckModuleReturn(ModuleValidator<Unit>& m) {
   TokenKind tk;
   if (!GetToken(m.parser(), &tk)) {
     return false;
   }
   auto& ts = m.parser().tokenStream;
   if (tk != TokenKind::Return) {
     return m.failCurrentOffset(
         (tk == TokenKind::RightCurly || tk == TokenKind::Eof)
@@ -6243,39 +6333,41 @@ static bool CheckModuleReturn(ModuleVali
     if (!CheckModuleExportFunction(m, returnExpr)) {
       return false;
     }
   }
 
   return true;
 }
 
-static bool CheckModuleEnd(ModuleValidator& m) {
+template <typename Unit>
+static bool CheckModuleEnd(ModuleValidator<Unit>& m) {
   TokenKind tk;
   if (!GetToken(m.parser(), &tk)) {
     return false;
   }
 
   if (tk != TokenKind::Eof && tk != TokenKind::RightCurly) {
     return m.failCurrentOffset(
         "top-level export (return) must be the last statement");
   }
 
   m.parser().tokenStream.anyCharsAccess().ungetToken();
   return true;
 }
 
-static SharedModule CheckModule(JSContext* cx, AsmJSParser& parser,
+template <typename Unit>
+static SharedModule CheckModule(JSContext* cx, AsmJSParser<Unit>& parser,
                                 ParseNode* stmtList, UniqueLinkData* linkData,
                                 unsigned* time) {
   int64_t before = PRMJ_Now();
 
   CodeNode* moduleFunctionNode = parser.pc->functionBox()->functionNode;
 
-  ModuleValidator m(cx, parser, moduleFunctionNode);
+  ModuleValidator<Unit> m(cx, parser, moduleFunctionNode);
   if (!m.init()) {
     return nullptr;
   }
 
   if (!CheckFunctionHead(m, moduleFunctionNode)) {
     return nullptr;
   }
 
@@ -7022,35 +7114,35 @@ const uint8_t* Assumptions::deserialize(
 }
 
 class ModuleChars {
  protected:
   uint32_t isFunCtor_;
   Vector<CacheableChars, 0, SystemAllocPolicy> funCtorArgs_;
 
  public:
-  static uint32_t beginOffset(AsmJSParser& parser) {
+  static uint32_t beginOffset(AsmJSParser<char16_t>& parser) {
     return parser.pc->functionBox()->functionNode->pn_pos.begin;
   }
 
-  static uint32_t endOffset(AsmJSParser& parser) {
+  static uint32_t endOffset(AsmJSParser<char16_t>& parser) {
     TokenPos pos(0, 0);  // initialize to silence GCC warning
     MOZ_ALWAYS_TRUE(
         parser.tokenStream.peekTokenPos(&pos, TokenStreamShared::Operand));
     return pos.end;
   }
 };
 
 class ModuleCharsForStore : ModuleChars {
   uint32_t uncompressedSize_;
   uint32_t compressedSize_;
   Vector<char, 0, SystemAllocPolicy> compressedBuffer_;
 
  public:
-  bool init(AsmJSParser& parser) {
+  bool init(AsmJSParser<char16_t>& parser) {
     MOZ_ASSERT(beginOffset(parser) < endOffset(parser));
 
     uncompressedSize_ =
         (endOffset(parser) - beginOffset(parser)) * sizeof(char16_t);
     size_t maxCompressedSize = LZ4::maxCompressedSize(uncompressedSize_);
     if (maxCompressedSize < uncompressedSize_) {
       return false;
     }
@@ -7143,17 +7235,17 @@ class ModuleCharsForLookup : ModuleChars
     cursor = ReadScalar<uint32_t>(cursor, &isFunCtor_);
     if (isFunCtor_) {
       cursor = DeserializeVector(cursor, &funCtorArgs_);
     }
 
     return cursor;
   }
 
-  bool match(AsmJSParser& parser) const {
+  bool match(AsmJSParser<char16_t>& parser) const {
     const char16_t* parseBegin =
         parser.tokenStream.codeUnitPtrAt(beginOffset(parser));
     const char16_t* parseLimit = parser.tokenStream.rawLimit();
     MOZ_ASSERT(parseLimit >= parseBegin);
     if (uint32_t(parseLimit - parseBegin) < chars_.length()) {
       return false;
     }
     if (!ArrayEqual(chars_.begin(), parseBegin, chars_.length())) {
@@ -7220,20 +7312,19 @@ struct ScopedCacheEntryOpenedForRead {
     if (memory) {
       cx->asmJSCacheOps().closeEntryForRead(serializedSize, memory, handle);
     }
   }
 };
 
 }  // unnamed namespace
 
-static JS::AsmJSCacheResult StoreAsmJSModuleInCache(AsmJSParser& parser,
-                                                    const Module& module,
-                                                    const LinkData& linkData,
-                                                    JSContext* cx) {
+static JS::AsmJSCacheResult StoreAsmJSModuleInCache(
+    AsmJSParser<char16_t>& parser, const Module& module,
+    const LinkData& linkData, JSContext* cx) {
   ModuleCharsForStore moduleChars;
   if (!moduleChars.init(parser)) {
     return JS::AsmJSCache_InternalError;
   }
 
   size_t moduleSize = module.serializedSize(linkData);
   MOZ_RELEASE_ASSERT(moduleSize <= UINT32_MAX);
 
@@ -7274,17 +7365,18 @@ static JS::AsmJSCacheResult StoreAsmJSMo
 
   cursor = moduleChars.serialize(cursor);
 
   MOZ_RELEASE_ASSERT(cursor == entry.memory + serializedSize);
 
   return JS::AsmJSCache_Success;
 }
 
-static bool LookupAsmJSModuleInCache(JSContext* cx, AsmJSParser& parser,
+static bool LookupAsmJSModuleInCache(JSContext* cx,
+                                     AsmJSParser<char16_t>& parser,
                                      bool* loadedFromCache,
                                      SharedModule* module,
                                      UniqueChars* compilationTimeReport) {
   int64_t before = PRMJ_Now();
 
   *loadedFromCache = false;
 
   JS::OpenAsmJSCacheEntryForReadOp open = cx->asmJSCacheOps().openEntryForRead;
@@ -7376,34 +7468,36 @@ static bool LookupAsmJSModuleInCache(JSC
 
 /*****************************************************************************/
 // Top-level js::CompileAsmJS
 
 static bool NoExceptionPending(JSContext* cx) {
   return cx->helperThread() || !cx->isExceptionPending();
 }
 
-static bool SuccessfulValidation(AsmJSParser& parser, UniqueChars str) {
+static bool SuccessfulValidation(frontend::ParserBase& parser,
+                                 UniqueChars str) {
   return parser.warningNoOffset(JSMSG_USE_ASM_TYPE_OK, str.get());
 }
 
-static bool TypeFailureWarning(AsmJSParser& parser, const char* str) {
+static bool TypeFailureWarning(frontend::ParserBase& parser, const char* str) {
   if (parser.options().throwOnAsmJSValidationFailureOption) {
     parser.errorNoOffset(JSMSG_USE_ASM_TYPE_FAIL, str ? str : "");
     return false;
   }
 
   // Per the asm.js standard convention, whether failure sets a pending
   // exception determines whether to attempt non-asm.js reparsing, so ignore
   // the return value below.
   Unused << parser.warningNoOffset(JSMSG_USE_ASM_TYPE_FAIL, str ? str : "");
   return false;
 }
 
-static bool EstablishPreconditions(JSContext* cx, AsmJSParser& parser) {
+static bool EstablishPreconditions(JSContext* cx,
+                                   frontend::ParserBase& parser) {
   // asm.js requires Ion.
   if (!HasCompilerSupport(cx) || !IonCanCompile()) {
     return TypeFailureWarning(parser, "Disabled by lack of compiler support");
   }
 
   switch (parser.options().asmJSOption) {
     case AsmJSOption::Disabled:
       return TypeFailureWarning(parser, "Disabled by 'asmjs' runtime option");
@@ -7480,18 +7574,18 @@ static UniqueChars BuildConsoleMessage(u
   }
 
   return JS_smprintf("total compilation time %dms; %s", time, cacheString);
 #else
   return DuplicateString("");
 #endif
 }
 
-bool js::CompileAsmJS(JSContext* cx, AsmJSParser& parser, ParseNode* stmtList,
-                      bool* validated) {
+bool js::CompileAsmJS(JSContext* cx, AsmJSParser<char16_t>& parser,
+                      ParseNode* stmtList, bool* validated) {
   *validated = false;
 
   // Various conditions disable asm.js optimizations.
   if (!EstablishPreconditions(cx, parser)) {
     return NoExceptionPending(cx);
   }
 
   // Before spending any time parsing the module, try to look it up in the
--- a/js/src/wasm/AsmJS.h
+++ b/js/src/wasm/AsmJS.h
@@ -45,26 +45,28 @@ class ParseContext;
 class ParseNode;
 
 template <class ParseHandler, typename CharT>
 class Parser;
 class FullParseHandler;
 
 }  // namespace frontend
 
-using AsmJSParser = frontend::Parser<frontend::FullParseHandler, char16_t>;
+template <typename Unit>
+using AsmJSParser = frontend::Parser<frontend::FullParseHandler, Unit>;
 
 // This function takes over parsing of a function starting with "use asm". The
 // return value indicates whether an error was reported which the caller should
 // propagate. If no error was reported, the function may still fail to validate
 // as asm.js. In this case, the parser.tokenStream has been advanced an
 // indeterminate amount and the entire function should be reparsed from the
 // beginning.
 
-extern MOZ_MUST_USE bool CompileAsmJS(JSContext* cx, AsmJSParser& parser,
+extern MOZ_MUST_USE bool CompileAsmJS(JSContext* cx,
+                                      AsmJSParser<char16_t>& parser,
                                       frontend::ParseNode* stmtList,
                                       bool* validated);
 
 // asm.js module/export queries:
 
 extern bool IsAsmJSModuleNative(JSNative native);
 
 extern bool IsAsmJSModule(JSFunction* fun);