Back out 4a8940073f8c and c7577c232591for some asm.js failures. r=bustage in a CLOSED TREE
authorJeff Walden <jwalden@mit.edu>
Fri, 04 Jan 2019 12:53:18 -0600
changeset 509712 0f6a3dd2c2f7
parent 509711 e6628922b7be
child 509713 1b508a44e699
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)
reviewersbustage
milestone66.0a1
backs out4a8940073f8c
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
Back out 4a8940073f8c and c7577c232591for some asm.js failures. r=bustage in a CLOSED TREE
js/src/wasm/AsmJS.cpp
js/src/wasm/AsmJS.h
--- a/js/src/wasm/AsmJS.cpp
+++ b/js/src/wasm/AsmJS.cpp
@@ -199,18 +199,16 @@ class AsmJSGlobal {
       struct {
         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);
@@ -642,52 +640,48 @@ static inline ParseNode* SkipEmptyStatem
   }
   return pn;
 }
 
 static inline ParseNode* NextNonEmptyStatement(ParseNode* pn) {
   return SkipEmptyStatements(pn->pn_next);
 }
 
-template <typename Unit>
-static bool GetToken(AsmJSParser<Unit>& parser, TokenKind* tkp) {
+static bool GetToken(AsmJSParser& 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;
 }
 
-template <typename Unit>
-static bool PeekToken(AsmJSParser<Unit>& parser, TokenKind* tkp) {
+static bool PeekToken(AsmJSParser& 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;
 }
 
-template <typename Unit>
-static bool ParseVarOrConstStatement(AsmJSParser<Unit>& parser,
-                                     ParseNode** var) {
+static bool ParseVarOrConstStatement(AsmJSParser& parser, ParseNode** var) {
   TokenKind tk;
   if (!PeekToken(parser, &tk)) {
     return false;
   }
   if (tk != TokenKind::Var && tk != TokenKind::Const) {
     *var = nullptr;
     return true;
   }
@@ -1039,17 +1033,31 @@ class Type {
         return "void";
     }
     MOZ_CRASH("Invalid Type");
   }
 };
 
 static const unsigned VALIDATION_LIFO_DEFAULT_CHUNK_SIZE = 4 * 1024;
 
-class MOZ_STACK_CLASS JS_HAZ_ROOTED ModuleValidatorShared {
+// The ModuleValidator encapsulates the entire validation of an asm.js module.
+// Its lifetime goes from the validation of the top components of an asm.js
+// module (all the globals), the emission of bytecode for all the functions in
+// the module and the validation of function's pointer tables. It also finishes
+// the compilation of all the module's stubs.
+//
+// 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!
+class MOZ_STACK_CLASS JS_HAZ_ROOTED ModuleValidator {
  public:
   class Func {
     PropertyName* name_;
     uint32_t sigIndex_;
     uint32_t firstUse_;
     uint32_t funcDefIndex_;
 
     bool defined_;
@@ -1196,18 +1204,16 @@ class MOZ_STACK_CLASS JS_HAZ_ROOTED Modu
       // |varOrConst|, through |varOrConst.literalValue_|, has a
       // 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 {
@@ -1270,17 +1276,17 @@ class MOZ_STACK_CLASS JS_HAZ_ROOTED Modu
 
   struct ArrayView {
     ArrayView(PropertyName* name, Scalar::Type type) : name(name), type(type) {}
 
     PropertyName* name;
     Scalar::Type type;
   };
 
- protected:
+ private:
   class HashableSig {
     uint32_t sigIndex_;
     const TypeDefVector& types_;
 
    public:
     HashableSig(uint32_t sigIndex, const TypeDefVector& types)
         : sigIndex_(sigIndex), types_(types) {}
     uint32_t sigIndex() const { return sigIndex_; }
@@ -1318,18 +1324,18 @@ class MOZ_STACK_CLASS JS_HAZ_ROOTED Modu
   };
 
   using SigSet = HashSet<HashableSig, HashableSig>;
   using FuncImportMap = HashMap<NamedSig, uint32_t, NamedSig>;
   using GlobalMap = HashMap<PropertyName*, Global*>;
   using MathNameMap = HashMap<PropertyName*, MathBuiltin>;
   using ArrayViewVector = Vector<ArrayView>;
 
- protected:
   JSContext* cx_;
+  AsmJSParser& parser_;
   CodeNode* moduleFunctionNode_;
   PropertyName* moduleFunctionName_;
   PropertyName* globalArgumentName_;
   PropertyName* importArgumentName_;
   PropertyName* bufferArgumentName_;
   MathNameMap standardLibraryMathNames_;
   RootedFunction dummyFunction_;
 
@@ -1347,19 +1353,60 @@ class MOZ_STACK_CLASS JS_HAZ_ROOTED Modu
   ModuleEnvironment env_;
   MutableAsmJSMetadata asmJSMetadata_;
 
   // Error reporting:
   UniqueChars errorString_;
   uint32_t errorOffset_;
   bool errorOverRecursed_;
 
- protected:
-  ModuleValidatorShared(JSContext* cx, CodeNode* moduleFunctionNode)
+  // Helpers:
+  bool addStandardLibraryMathName(const char* name,
+                                  AsmJSMathBuiltinFunction func) {
+    JSAtom* atom = Atomize(cx_, name, strlen(name));
+    if (!atom) {
+      return false;
+    }
+    MathBuiltin builtin(func);
+    return standardLibraryMathNames_.putNew(atom->asPropertyName(), builtin);
+  }
+  bool addStandardLibraryMathName(const char* name, double cst) {
+    JSAtom* atom = Atomize(cx_, name, strlen(name));
+    if (!atom) {
+      return false;
+    }
+    MathBuiltin builtin(cst);
+    return standardLibraryMathNames_.putNew(atom->asPropertyName(), builtin);
+  }
+  bool newSig(FuncType&& sig, uint32_t* sigIndex) {
+    if (env_.types.length() >= MaxTypes) {
+      return failCurrentOffset("too many signatures");
+    }
+
+    *sigIndex = env_.types.length();
+    return env_.types.append(std::move(sig));
+  }
+  bool declareSig(FuncType&& sig, uint32_t* sigIndex) {
+    SigSet::AddPtr p = sigSet_.lookupForAdd(sig);
+    if (p) {
+      *sigIndex = p->sigIndex();
+      MOZ_ASSERT(env_.types[*sigIndex].funcType() == sig);
+      return true;
+    }
+
+    return newSig(std::move(sig), sigIndex) &&
+           sigSet_.add(p, HashableSig(*sigIndex, env_.types));
+  }
+
+ public:
+  ModuleValidator(JSContext* cx, AsmJSParser& parser,
+                  CodeNode* moduleFunctionNode)
       : cx_(cx),
+        parser_(parser),
+        moduleFunctionNode_(moduleFunctionNode),
         moduleFunctionName_(FunctionName(moduleFunctionNode)),
         globalArgumentName_(nullptr),
         importArgumentName_(nullptr),
         bufferArgumentName_(nullptr),
         standardLibraryMathNames_(cx),
         dummyFunction_(cx),
         validationLifo_(VALIDATION_LIFO_DEFAULT_CHUNK_SIZE),
         funcDefs_(cx),
@@ -1374,104 +1421,123 @@ class MOZ_STACK_CLASS JS_HAZ_ROOTED Modu
              ModuleKind::AsmJS),
         errorString_(nullptr),
         errorOffset_(UINT32_MAX),
         errorOverRecursed_(false) {
     compilerEnv_.computeParameters(HasGcTypes::False);
     env_.minMemoryLength = RoundUpToNextValidAsmJSHeapLength(0);
   }
 
- protected:
-  MOZ_MUST_USE bool addStandardLibraryMathInfo() {
-    static constexpr struct {
-      const char* name;
-      AsmJSMathBuiltinFunction func;
-    } functions[] = {
-        {"sin", AsmJSMathBuiltin_sin},       {"cos", AsmJSMathBuiltin_cos},
-        {"tan", AsmJSMathBuiltin_tan},       {"asin", AsmJSMathBuiltin_asin},
-        {"acos", AsmJSMathBuiltin_acos},     {"atan", AsmJSMathBuiltin_atan},
-        {"ceil", AsmJSMathBuiltin_ceil},     {"floor", AsmJSMathBuiltin_floor},
-        {"exp", AsmJSMathBuiltin_exp},       {"log", AsmJSMathBuiltin_log},
-        {"pow", AsmJSMathBuiltin_pow},       {"sqrt", AsmJSMathBuiltin_sqrt},
-        {"abs", AsmJSMathBuiltin_abs},       {"atan2", AsmJSMathBuiltin_atan2},
-        {"imul", AsmJSMathBuiltin_imul},     {"clz32", AsmJSMathBuiltin_clz32},
-        {"fround", AsmJSMathBuiltin_fround}, {"min", AsmJSMathBuiltin_min},
-        {"max", AsmJSMathBuiltin_max},
-    };
-
-    auto AddMathFunction = [this](const char* name,
-                                  AsmJSMathBuiltinFunction func) {
-      JSAtom* atom = Atomize(cx_, name, strlen(name));
-      if (!atom) {
-        return false;
-      }
-      MathBuiltin builtin(func);
-      return this->standardLibraryMathNames_.putNew(atom->asPropertyName(),
-                                                    builtin);
-    };
-
-    for (const auto& info : functions) {
-      if (!AddMathFunction(info.name, info.func)) {
-        return false;
+  ~ModuleValidator() {
+    if (errorString_) {
+      MOZ_ASSERT(errorOffset_ != UINT32_MAX);
+      typeFailure(errorOffset_, errorString_.get());
+    }
+    if (errorOverRecursed_) {
+      ReportOverRecursed(cx_);
+    }
+  }
+
+ private:
+  void typeFailure(uint32_t offset, ...) {
+    va_list args;
+    va_start(args, offset);
+
+    auto& ts = tokenStream();
+    ErrorMetadata metadata;
+    if (ts.computeErrorMetadata(&metadata, offset)) {
+      if (ts.anyCharsAccess().options().throwOnAsmJSValidationFailureOption) {
+        ReportCompileError(cx_, std::move(metadata), nullptr, JSREPORT_ERROR,
+                           JSMSG_USE_ASM_TYPE_FAIL, args);
+      } else {
+        // asm.js type failure is indicated by calling one of the fail*
+        // functions below.  These functions always return false to
+        // halt asm.js parsing.  Whether normal parsing is attempted as
+        // fallback, depends whether an exception is also set.
+        //
+        // If warning succeeds, no exception is set.  If warning fails,
+        // an exception is set and execution will halt.  Thus it's safe
+        // and correct to ignore the return value here.
+        Unused << ts.anyCharsAccess().compileWarning(
+            std::move(metadata), nullptr, JSREPORT_WARNING,
+            JSMSG_USE_ASM_TYPE_FAIL, args);
       }
     }
 
-    static constexpr struct {
-      const char* name;
-      double value;
-    } constants[] = {
-        {"E", M_E},
-        {"LN10", M_LN10},
-        {"LN2", M_LN2},
-        {"LOG2E", M_LOG2E},
-        {"LOG10E", M_LOG10E},
-        {"PI", M_PI},
-        {"SQRT1_2", M_SQRT1_2},
-        {"SQRT2", M_SQRT2},
-    };
-
-    auto AddMathConstant = [this](const char* name, double cst) {
-      JSAtom* atom = Atomize(cx_, name, strlen(name));
-      if (!atom) {
-        return false;
-      }
-      MathBuiltin builtin(cst);
-      return this->standardLibraryMathNames_.putNew(atom->asPropertyName(),
-                                                    builtin);
-    };
-
-    for (const auto& info : constants) {
-      if (!AddMathConstant(info.name, info.value)) {
-        return false;
-      }
-    }
-
-    return true;
-  }
-
-  MOZ_MUST_USE bool initDummyFunction() {
+    va_end(args);
+  }
+
+ public:
+  bool init() {
+    asmJSMetadata_ = cx_->new_<AsmJSMetadata>();
+    if (!asmJSMetadata_) {
+      return false;
+    }
+
+    asmJSMetadata_->toStringStart =
+        moduleFunctionNode_->funbox()->toStringStart;
+    asmJSMetadata_->srcStart = moduleFunctionNode_->body()->pn_pos.begin;
+    asmJSMetadata_->strict =
+        parser_.pc->sc()->strict() && !parser_.pc->sc()->hasExplicitUseStrict();
+    asmJSMetadata_->scriptSource.reset(parser_.ss);
+
+    if (!addStandardLibraryMathName("sin", AsmJSMathBuiltin_sin) ||
+        !addStandardLibraryMathName("cos", AsmJSMathBuiltin_cos) ||
+        !addStandardLibraryMathName("tan", AsmJSMathBuiltin_tan) ||
+        !addStandardLibraryMathName("asin", AsmJSMathBuiltin_asin) ||
+        !addStandardLibraryMathName("acos", AsmJSMathBuiltin_acos) ||
+        !addStandardLibraryMathName("atan", AsmJSMathBuiltin_atan) ||
+        !addStandardLibraryMathName("ceil", AsmJSMathBuiltin_ceil) ||
+        !addStandardLibraryMathName("floor", AsmJSMathBuiltin_floor) ||
+        !addStandardLibraryMathName("exp", AsmJSMathBuiltin_exp) ||
+        !addStandardLibraryMathName("log", AsmJSMathBuiltin_log) ||
+        !addStandardLibraryMathName("pow", AsmJSMathBuiltin_pow) ||
+        !addStandardLibraryMathName("sqrt", AsmJSMathBuiltin_sqrt) ||
+        !addStandardLibraryMathName("abs", AsmJSMathBuiltin_abs) ||
+        !addStandardLibraryMathName("atan2", AsmJSMathBuiltin_atan2) ||
+        !addStandardLibraryMathName("imul", AsmJSMathBuiltin_imul) ||
+        !addStandardLibraryMathName("clz32", AsmJSMathBuiltin_clz32) ||
+        !addStandardLibraryMathName("fround", AsmJSMathBuiltin_fround) ||
+        !addStandardLibraryMathName("min", AsmJSMathBuiltin_min) ||
+        !addStandardLibraryMathName("max", AsmJSMathBuiltin_max) ||
+        !addStandardLibraryMathName("E", M_E) ||
+        !addStandardLibraryMathName("LN10", M_LN10) ||
+        !addStandardLibraryMathName("LN2", M_LN2) ||
+        !addStandardLibraryMathName("LOG2E", M_LOG2E) ||
+        !addStandardLibraryMathName("LOG10E", M_LOG10E) ||
+        !addStandardLibraryMathName("PI", M_PI) ||
+        !addStandardLibraryMathName("SQRT1_2", M_SQRT1_2) ||
+        !addStandardLibraryMathName("SQRT2", M_SQRT2)) {
+      return false;
+    }
+
     // This flows into FunctionBox, so must be tenured.
     dummyFunction_ = NewScriptedFunction(
         cx_, 0, JSFunction::INTERPRETED, nullptr,
         /* proto = */ nullptr, gc::AllocKind::FUNCTION, TenuredObject);
     if (!dummyFunction_) {
       return false;
     }
 
     return true;
   }
 
- public:
   JSContext* cx() const { return cx_; }
   PropertyName* moduleFunctionName() const { return moduleFunctionName_; }
   PropertyName* globalArgumentName() const { return globalArgumentName_; }
   PropertyName* importArgumentName() const { return importArgumentName_; }
   PropertyName* bufferArgumentName() const { return bufferArgumentName_; }
   const ModuleEnvironment& env() { return env_; }
 
+  AsmJSParser& parser() const { return parser_; }
+
+  auto tokenStream() const -> decltype(parser_.tokenStream)& {
+    return parser_.tokenStream;
+  }
+
   RootedFunction& dummyFunction() { return dummyFunction_; }
   uint32_t minMemoryLength() const { return env_.minMemoryLength; }
 
   void initModuleFunctionName(PropertyName* name) {
     MOZ_ASSERT(!moduleFunctionName_);
     moduleFunctionName_ = name;
   }
   MOZ_MUST_USE bool initGlobalArgumentName(PropertyName* n) {
@@ -1724,17 +1790,80 @@ class MOZ_STACK_CLASS JS_HAZ_ROOTED Modu
     }
 
     // The exported function might have already been exported in which case
     // the index will refer into the range of AsmJSExports.
     return asmJSMetadata_->asmJSExports.emplaceBack(
         funcIndex, func.srcBegin() - asmJSMetadata_->srcStart,
         func.srcEnd() - asmJSMetadata_->srcStart);
   }
-
+  bool addFuncDef(PropertyName* name, uint32_t firstUse, FuncType&& sig,
+                  Func** func) {
+    uint32_t sigIndex;
+    if (!declareSig(std::move(sig), &sigIndex)) {
+      return false;
+    }
+
+    uint32_t funcDefIndex = funcDefs_.length();
+    if (funcDefIndex >= MaxFuncs) {
+      return failCurrentOffset("too many functions");
+    }
+
+    Global* global = validationLifo_.new_<Global>(Global::Function);
+    if (!global) {
+      return false;
+    }
+    new (&global->u.funcDefIndex_) uint32_t(funcDefIndex);
+    if (!globalMap_.putNew(name, global)) {
+      return false;
+    }
+    if (!funcDefs_.emplaceBack(name, sigIndex, firstUse, funcDefIndex)) {
+      return false;
+    }
+    *func = &funcDefs_.back();
+    return true;
+  }
+  bool declareFuncPtrTable(FuncType&& sig, PropertyName* name,
+                           uint32_t firstUse, uint32_t mask,
+                           uint32_t* tableIndex) {
+    if (mask > MaxTableInitialLength) {
+      return failCurrentOffset("function pointer table too big");
+    }
+
+    MOZ_ASSERT(env_.tables.length() == tables_.length());
+    *tableIndex = env_.tables.length();
+
+    uint32_t sigIndex;
+    if (!newSig(std::move(sig), &sigIndex)) {
+      return false;
+    }
+
+    MOZ_ASSERT(sigIndex >= env_.asmJSSigToTableIndex.length());
+    if (!env_.asmJSSigToTableIndex.resize(sigIndex + 1)) {
+      return false;
+    }
+
+    env_.asmJSSigToTableIndex[sigIndex] = env_.tables.length();
+    if (!env_.tables.emplaceBack(TableKind::TypedFunction, Limits(mask + 1))) {
+      return false;
+    }
+
+    Global* global = validationLifo_.new_<Global>(Global::Table);
+    if (!global) {
+      return false;
+    }
+
+    new (&global->u.tableIndex_) uint32_t(*tableIndex);
+    if (!globalMap_.putNew(name, global)) {
+      return false;
+    }
+
+    Table* t = validationLifo_.new_<Table>(sigIndex, name, firstUse, mask);
+    return t && tables_.append(t);
+  }
   bool defineFuncPtrTable(uint32_t tableIndex, Uint32Vector&& elems) {
     Table& table = *tables_[tableIndex];
     if (table.defined()) {
       return false;
     }
 
     table.define();
 
@@ -1746,16 +1875,44 @@ class MOZ_STACK_CLASS JS_HAZ_ROOTED Modu
     if (!seg) {
       return false;
     }
     seg->tableIndex = tableIndex;
     seg->offsetIfActive = Some(InitExpr(LitVal(uint32_t(0))));
     seg->elemFuncIndices = std::move(elems);
     return env_.elemSegments.append(std::move(seg));
   }
+  bool declareImport(PropertyName* name, FuncType&& sig, unsigned ffiIndex,
+                     uint32_t* importIndex) {
+    FuncImportMap::AddPtr p =
+        funcImportMap_.lookupForAdd(NamedSig::Lookup(name, sig));
+    if (p) {
+      *importIndex = p->value();
+      return true;
+    }
+
+    *importIndex = funcImportMap_.count();
+    MOZ_ASSERT(*importIndex == asmJSMetadata_->asmJSImports.length());
+
+    if (*importIndex >= MaxImports) {
+      return failCurrentOffset("too many imports");
+    }
+
+    if (!asmJSMetadata_->asmJSImports.emplaceBack(ffiIndex)) {
+      return false;
+    }
+
+    uint32_t sigIndex;
+    if (!declareSig(std::move(sig), &sigIndex)) {
+      return false;
+    }
+
+    return funcImportMap_.add(p, NamedSig(name, sigIndex, env_.types),
+                              *importIndex);
+  }
 
   bool tryConstantAccess(uint64_t start, uint64_t width) {
     MOZ_ASSERT(UINT64_MAX - start > width);
     uint64_t len = start + width;
     if (len > uint64_t(INT32_MAX) + 1) {
       return false;
     }
     len = RoundUpToNextValidAsmJSHeapLength(len);
@@ -1772,16 +1929,21 @@ class MOZ_STACK_CLASS JS_HAZ_ROOTED Modu
     MOZ_ASSERT(!hasAlreadyFailed());
     MOZ_ASSERT(errorOffset_ == UINT32_MAX);
     MOZ_ASSERT(str);
     errorOffset_ = offset;
     errorString_ = DuplicateString(str);
     return false;
   }
 
+  bool failCurrentOffset(const char* str) {
+    return failOffset(tokenStream().anyCharsAccess().currentToken().pos.begin,
+                      str);
+  }
+
   bool fail(ParseNode* pn, const char* str) {
     return failOffset(pn->pn_pos.begin, str);
   }
 
   bool failfVAOffset(uint32_t offset, const char* fmt, va_list ap)
       MOZ_FORMAT_PRINTF(3, 0) {
     MOZ_ASSERT(!hasAlreadyFailed());
     MOZ_ASSERT(errorOffset_ == UINT32_MAX);
@@ -1862,235 +2024,16 @@ class MOZ_STACK_CLASS JS_HAZ_ROOTED Modu
   bool startFunctionBodies() {
     if (!arrayViews_.empty()) {
       env_.memoryUsage = MemoryUsage::Unshared;
     } else {
       env_.memoryUsage = MemoryUsage::None;
     }
     return true;
   }
-};
-
-// The ModuleValidator encapsulates the entire validation of an asm.js module.
-// Its lifetime goes from the validation of the top components of an asm.js
-// module (all the globals), the emission of bytecode for all the functions in
-// the module and the validation of function's pointer tables. It also finishes
-// the compilation of all the module's stubs.
-//
-// 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<Unit>& parser_;
-
- public:
-  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());
-    }
-    if (errorOverRecursed_) {
-      ReportOverRecursed(cx_);
-    }
-  }
-
- private:
-  // Helpers:
-  bool newSig(FuncType&& sig, uint32_t* sigIndex) {
-    if (env_.types.length() >= MaxTypes) {
-      return failCurrentOffset("too many signatures");
-    }
-
-    *sigIndex = env_.types.length();
-    return env_.types.append(std::move(sig));
-  }
-  bool declareSig(FuncType&& sig, uint32_t* sigIndex) {
-    SigSet::AddPtr p = sigSet_.lookupForAdd(sig);
-    if (p) {
-      *sigIndex = p->sigIndex();
-      MOZ_ASSERT(env_.types[*sigIndex].funcType() == sig);
-      return true;
-    }
-
-    return newSig(std::move(sig), sigIndex) &&
-           sigSet_.add(p, HashableSig(*sigIndex, env_.types));
-  }
-
- private:
-  void typeFailure(uint32_t offset, ...) {
-    va_list args;
-    va_start(args, offset);
-
-    auto& ts = tokenStream();
-    ErrorMetadata metadata;
-    if (ts.computeErrorMetadata(&metadata, offset)) {
-      if (ts.anyCharsAccess().options().throwOnAsmJSValidationFailureOption) {
-        ReportCompileError(cx_, std::move(metadata), nullptr, JSREPORT_ERROR,
-                           JSMSG_USE_ASM_TYPE_FAIL, args);
-      } else {
-        // asm.js type failure is indicated by calling one of the fail*
-        // functions below.  These functions always return false to
-        // halt asm.js parsing.  Whether normal parsing is attempted as
-        // fallback, depends whether an exception is also set.
-        //
-        // If warning succeeds, no exception is set.  If warning fails,
-        // an exception is set and execution will halt.  Thus it's safe
-        // and correct to ignore the return value here.
-        Unused << ts.anyCharsAccess().compileWarning(
-            std::move(metadata), nullptr, JSREPORT_WARNING,
-            JSMSG_USE_ASM_TYPE_FAIL, args);
-      }
-    }
-
-    va_end(args);
-  }
-
- public:
-  bool init() {
-    asmJSMetadata_ = cx_->new_<AsmJSMetadata>();
-    if (!asmJSMetadata_) {
-      return false;
-    }
-
-    asmJSMetadata_->toStringStart =
-        moduleFunctionNode_->funbox()->toStringStart;
-    asmJSMetadata_->srcStart = moduleFunctionNode_->body()->pn_pos.begin;
-    asmJSMetadata_->strict =
-        parser_.pc->sc()->strict() && !parser_.pc->sc()->hasExplicitUseStrict();
-    asmJSMetadata_->scriptSource.reset(parser_.ss);
-
-    if (!addStandardLibraryMathInfo()) {
-      return false;
-    }
-
-    if (!initDummyFunction()) {
-      return false;
-    }
-
-    return true;
-  }
-
-  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) {
-    uint32_t sigIndex;
-    if (!declareSig(std::move(sig), &sigIndex)) {
-      return false;
-    }
-
-    uint32_t funcDefIndex = funcDefs_.length();
-    if (funcDefIndex >= MaxFuncs) {
-      return failCurrentOffset("too many functions");
-    }
-
-    Global* global = validationLifo_.new_<Global>(Global::Function);
-    if (!global) {
-      return false;
-    }
-    new (&global->u.funcDefIndex_) uint32_t(funcDefIndex);
-    if (!globalMap_.putNew(name, global)) {
-      return false;
-    }
-    if (!funcDefs_.emplaceBack(name, sigIndex, firstUse, funcDefIndex)) {
-      return false;
-    }
-    *func = &funcDefs_.back();
-    return true;
-  }
-  bool declareFuncPtrTable(FuncType&& sig, PropertyName* name,
-                           uint32_t firstUse, uint32_t mask,
-                           uint32_t* tableIndex) {
-    if (mask > MaxTableInitialLength) {
-      return failCurrentOffset("function pointer table too big");
-    }
-
-    MOZ_ASSERT(env_.tables.length() == tables_.length());
-    *tableIndex = env_.tables.length();
-
-    uint32_t sigIndex;
-    if (!newSig(std::move(sig), &sigIndex)) {
-      return false;
-    }
-
-    MOZ_ASSERT(sigIndex >= env_.asmJSSigToTableIndex.length());
-    if (!env_.asmJSSigToTableIndex.resize(sigIndex + 1)) {
-      return false;
-    }
-
-    env_.asmJSSigToTableIndex[sigIndex] = env_.tables.length();
-    if (!env_.tables.emplaceBack(TableKind::TypedFunction, Limits(mask + 1))) {
-      return false;
-    }
-
-    Global* global = validationLifo_.new_<Global>(Global::Table);
-    if (!global) {
-      return false;
-    }
-
-    new (&global->u.tableIndex_) uint32_t(*tableIndex);
-    if (!globalMap_.putNew(name, global)) {
-      return false;
-    }
-
-    Table* t = validationLifo_.new_<Table>(sigIndex, name, firstUse, mask);
-    return t && tables_.append(t);
-  }
-  bool declareImport(PropertyName* name, FuncType&& sig, unsigned ffiIndex,
-                     uint32_t* importIndex) {
-    FuncImportMap::AddPtr p =
-        funcImportMap_.lookupForAdd(NamedSig::Lookup(name, sig));
-    if (p) {
-      *importIndex = p->value();
-      return true;
-    }
-
-    *importIndex = funcImportMap_.count();
-    MOZ_ASSERT(*importIndex == asmJSMetadata_->asmJSImports.length());
-
-    if (*importIndex >= MaxImports) {
-      return failCurrentOffset("too many imports");
-    }
-
-    if (!asmJSMetadata_->asmJSImports.emplaceBack(ffiIndex)) {
-      return false;
-    }
-
-    uint32_t sigIndex;
-    if (!declareSig(std::move(sig), &sigIndex)) {
-      return false;
-    }
-
-    return funcImportMap_.add(p, NamedSig(name, sigIndex, env_.types),
-                              *importIndex);
-  }
-
-  // Error handling.
-  bool failCurrentOffset(const char* str) {
-    return failOffset(tokenStream().anyCharsAccess().currentToken().pos.begin,
-                      str);
-  }
-
   SharedModule finish(UniqueLinkData* linkData) {
     MOZ_ASSERT(env_.funcTypes.empty());
     if (!env_.funcTypes.resize(funcImportMap_.count() + funcDefs_.length())) {
       return nullptr;
     }
     for (FuncImportMap::Range r = funcImportMap_.all(); !r.empty();
          r.popFront()) {
       uint32_t funcIndex = r.front().value();
@@ -2190,34 +2133,34 @@ class MOZ_STACK_CLASS JS_HAZ_ROOTED Modu
 static bool IsNumericNonFloatLiteral(ParseNode* pn) {
   // Note: '-' is never rolled into the number; numbers are always positive
   // and negations must be applied manually.
   return pn->isKind(ParseNodeKind::NumberExpr) ||
          (pn->isKind(ParseNodeKind::NegExpr) &&
           UnaryKid(pn)->isKind(ParseNodeKind::NumberExpr));
 }
 
-static bool IsCallToGlobal(ModuleValidatorShared& m, ParseNode* pn,
-                           const ModuleValidatorShared::Global** global) {
+static bool IsCallToGlobal(ModuleValidator& m, ParseNode* pn,
+                           const ModuleValidator::Global** global) {
   if (!pn->isKind(ParseNodeKind::CallExpr)) {
     return false;
   }
 
   ParseNode* callee = CallCallee(pn);
   if (!callee->isKind(ParseNodeKind::Name)) {
     return false;
   }
 
   *global = m.lookupGlobal(callee->as<NameNode>().name());
   return !!*global;
 }
 
-static bool IsCoercionCall(ModuleValidatorShared& m, ParseNode* pn,
-                           Type* coerceTo, ParseNode** coercedExpr) {
-  const ModuleValidatorShared::Global* global;
+static bool IsCoercionCall(ModuleValidator& m, ParseNode* pn, Type* coerceTo,
+                           ParseNode** coercedExpr) {
+  const ModuleValidator::Global* global;
   if (!IsCallToGlobal(m, pn, &global)) {
     return false;
   }
 
   if (CallArgListLength(pn) != 1) {
     return false;
   }
 
@@ -2229,30 +2172,37 @@ static bool IsCoercionCall(ModuleValidat
       global->mathBuiltinFunction() == AsmJSMathBuiltin_fround) {
     *coerceTo = Type::Float;
     return true;
   }
 
   return false;
 }
 
-static bool IsFloatLiteral(ModuleValidatorShared& m, ParseNode* pn) {
+static bool IsFloatLiteral(ModuleValidator& m, ParseNode* pn) {
   ParseNode* coercedExpr;
   Type coerceTo;
   if (!IsCoercionCall(m, pn, &coerceTo, &coercedExpr)) {
     return false;
   }
   // Don't fold into || to avoid clang/memcheck bug (bug 1077031).
   if (!coerceTo.isFloat()) {
     return false;
   }
   return IsNumericNonFloatLiteral(coercedExpr);
 }
 
-static bool IsNumericLiteral(ModuleValidatorShared& m, ParseNode* pn) {
+static bool IsNumericLiteral(ModuleValidator& m, ParseNode* pn);
+
+static NumLit ExtractNumericLiteral(ModuleValidator& m, ParseNode* pn);
+
+static inline bool IsLiteralInt(ModuleValidator& m, ParseNode* pn,
+                                uint32_t* u32);
+
+static bool IsNumericLiteral(ModuleValidator& m, ParseNode* pn) {
   return IsNumericNonFloatLiteral(pn) || IsFloatLiteral(m, pn);
 }
 
 // The JS grammar treats -42 as -(42) (i.e., with separate grammar
 // productions) for the unary - and literal 42). However, the asm.js spec
 // recognizes -42 (modulo parens, so -(42) and -((42))) as a single literal
 // so fold the two potential parse nodes into a single double value.
 static double ExtractNumericNonFloatValue(ParseNode* pn,
@@ -2265,17 +2215,17 @@ static double ExtractNumericNonFloatValu
       *out = pn;
     }
     return -NumberNodeValue(pn);
   }
 
   return NumberNodeValue(pn);
 }
 
-static NumLit ExtractNumericLiteral(ModuleValidatorShared& m, ParseNode* pn) {
+static NumLit ExtractNumericLiteral(ModuleValidator& m, ParseNode* pn) {
   MOZ_ASSERT(IsNumericLiteral(m, pn));
 
   if (pn->isKind(ParseNodeKind::CallExpr)) {
     // Float literals are explicitly coerced and thus the coerced literal may be
     // any valid (non-float) numeric literal.
     MOZ_ASSERT(CallArgListLength(pn) == 1);
     pn = CallArgList(pn);
     double d = ExtractNumericNonFloatValue(pn);
@@ -2326,88 +2276,79 @@ static inline bool IsLiteralInt(const Nu
     case NumLit::Double:
     case NumLit::Float:
     case NumLit::OutOfRangeInt:
       return false;
   }
   MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Bad literal type");
 }
 
-static inline bool IsLiteralInt(ModuleValidatorShared& m, ParseNode* pn,
+static inline bool IsLiteralInt(ModuleValidator& m, ParseNode* pn,
                                 uint32_t* u32) {
   return IsNumericLiteral(m, pn) &&
          IsLiteralInt(ExtractNumericLiteral(m, pn), u32);
 }
 
 /*****************************************************************************/
 
 namespace {
 
 typedef Vector<PropertyName*, 4, SystemAllocPolicy> LabelVector;
 
-class MOZ_STACK_CLASS FunctionValidatorShared {
+// 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.
+class MOZ_STACK_CLASS FunctionValidator {
  public:
   struct Local {
     Type type;
     unsigned slot;
     Local(Type t, unsigned slot) : type(t), slot(slot) {
       MOZ_ASSERT(type.isCanonicalValType());
     }
   };
 
- protected:
-  using LocalMap = HashMap<PropertyName*, Local>;
-  using LabelMap = HashMap<PropertyName*, uint32_t>;
-
-  // This is also a ModuleValidator<Unit>& after the appropriate static_cast<>.
-  ModuleValidatorShared& m_;
-
+ private:
+  typedef HashMap<PropertyName*, Local> LocalMap;
+  typedef HashMap<PropertyName*, uint32_t> LabelMap;
+
+  ModuleValidator& m_;
   ParseNode* fn_;
   Bytes bytes_;
   Encoder encoder_;
   Uint32Vector callSiteLineNums_;
   LocalMap locals_;
 
   // Labels
   LabelMap breakLabels_;
   LabelMap continueLabels_;
   Uint32Vector breakableStack_;
   Uint32Vector continuableStack_;
   uint32_t blockDepth_;
 
   bool hasAlreadyReturned_;
   ExprType ret_;
 
- private:
-  FunctionValidatorShared(ModuleValidatorShared& m, ParseNode* fn,
-                          JSContext* cx)
+ public:
+  FunctionValidator(ModuleValidator& m, ParseNode* fn)
       : m_(m),
         fn_(fn),
         encoder_(bytes_),
-        locals_(cx),
-        breakLabels_(cx),
-        continueLabels_(cx),
+        locals_(m.cx()),
+        breakLabels_(m.cx()),
+        continueLabels_(m.cx()),
         blockDepth_(0),
         hasAlreadyReturned_(false),
         ret_(ExprType::Limit) {}
 
- protected:
-  template <typename Unit>
-  FunctionValidatorShared(ModuleValidator<Unit>& m, ParseNode* fn,
-                          JSContext* cx)
-      : FunctionValidatorShared(static_cast<ModuleValidatorShared&>(m), fn,
-                                cx) {}
-
- public:
-  ModuleValidatorShared& m() const { return m_; }
-
+  ModuleValidator& m() const { return m_; }
   JSContext* cx() const { return m_.cx(); }
   ParseNode* fn() const { return fn_; }
 
-  void define(ModuleValidatorShared::Func* func, unsigned line) {
+  void define(ModuleValidator::Func* func, unsigned line) {
     MOZ_ASSERT(!blockDepth_);
     MOZ_ASSERT(breakableStack_.empty());
     MOZ_ASSERT(continuableStack_.empty());
     MOZ_ASSERT(breakLabels_.empty());
     MOZ_ASSERT(continueLabels_.empty());
     func->define(fn_, line, std::move(bytes_), std::move(callSiteLineNums_));
   }
 
@@ -2581,17 +2522,17 @@ class MOZ_STACK_CLASS FunctionValidatorS
 
   const Local* lookupLocal(PropertyName* name) const {
     if (auto p = locals_.lookup(name)) {
       return &p->value();
     }
     return nullptr;
   }
 
-  const ModuleValidatorShared::Global* lookupGlobal(PropertyName* name) const {
+  const ModuleValidator::Global* lookupGlobal(PropertyName* name) const {
     if (locals_.has(name)) {
       return nullptr;
     }
     return m_.lookupGlobal(name);
   }
 
   size_t numLocals() const { return locals_.count(); }
 
@@ -2614,32 +2555,16 @@ class MOZ_STACK_CLASS FunctionValidatorS
       case NumLit::Double:
         return encoder().writeOp(Op::F64Const) &&
                encoder().writeFixedF64(lit.toDouble());
       case NumLit::OutOfRangeInt:
         break;
     }
     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<Unit>& m, ParseNode* fn)
-      : FunctionValidatorShared(m, fn, m.cx()) {}
-
- public:
-  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);
   }
   MOZ_MUST_USE bool writeCall(ParseNode* pn, MozOp op) {
@@ -2662,53 +2587,53 @@ class MOZ_STACK_CLASS FunctionValidator 
   }
 };
 
 } /* anonymous namespace */
 
 /*****************************************************************************/
 // asm.js type-checking and code-generation algorithm
 
-static bool CheckIdentifier(ModuleValidatorShared& m, ParseNode* usepn,
+static bool CheckIdentifier(ModuleValidator& m, ParseNode* usepn,
                             PropertyName* name) {
   if (name == m.cx()->names().arguments || name == m.cx()->names().eval) {
     return m.failName(usepn, "'%s' is not an allowed identifier", name);
   }
   return true;
 }
 
-static bool CheckModuleLevelName(ModuleValidatorShared& m, ParseNode* usepn,
+static bool CheckModuleLevelName(ModuleValidator& m, ParseNode* usepn,
                                  PropertyName* name) {
   if (!CheckIdentifier(m, usepn, name)) {
     return false;
   }
 
   if (name == m.moduleFunctionName() || name == m.globalArgumentName() ||
       name == m.importArgumentName() || name == m.bufferArgumentName() ||
       m.lookupGlobal(name)) {
     return m.failName(usepn, "duplicate name '%s' not allowed", name);
   }
 
   return true;
 }
 
-static bool CheckFunctionHead(ModuleValidatorShared& m, CodeNode* funNode) {
+static bool CheckFunctionHead(ModuleValidator& m, CodeNode* funNode) {
   FunctionBox* funbox = funNode->funbox();
   MOZ_ASSERT(!funbox->hasExprBody());
 
   if (funbox->hasRest()) {
     return m.fail(funNode, "rest args not allowed");
   }
   if (funbox->hasDestructuringArgs) {
     return m.fail(funNode, "destructuring args not allowed");
   }
   return true;
 }
 
-static bool CheckArgument(ModuleValidatorShared& m, ParseNode* arg,
+static bool CheckArgument(ModuleValidator& m, ParseNode* arg,
                           PropertyName** name) {
   *name = nullptr;
 
   if (!arg->isKind(ParseNodeKind::Name)) {
     return m.fail(arg, "argument is not a plain name");
   }
 
   PropertyName* argName = arg->as<NameNode>().name();
@@ -2716,30 +2641,30 @@ static bool CheckArgument(ModuleValidato
   if (!CheckIdentifier(m, arg, argName)) {
     return false;
   }
 
   *name = argName;
   return true;
 }
 
-static bool CheckModuleArgument(ModuleValidatorShared& m, ParseNode* arg,
+static bool CheckModuleArgument(ModuleValidator& m, ParseNode* arg,
                                 PropertyName** name) {
   if (!CheckArgument(m, arg, name)) {
     return false;
   }
 
   if (!CheckModuleLevelName(m, arg, *name)) {
     return false;
   }
 
   return true;
 }
 
-static bool CheckModuleArguments(ModuleValidatorShared& m, CodeNode* funNode) {
+static bool CheckModuleArguments(ModuleValidator& m, CodeNode* funNode) {
   unsigned numFormals;
   ParseNode* arg1 = FunctionFormalParametersList(funNode, &numFormals);
   ParseNode* arg2 = arg1 ? NextNode(arg1) : nullptr;
   ParseNode* arg3 = arg2 ? NextNode(arg2) : nullptr;
 
   if (numFormals > 3) {
     return m.fail(funNode, "asm.js modules takes at most 3 argument");
   }
@@ -2766,49 +2691,48 @@ static bool CheckModuleArguments(ModuleV
   }
   if (!m.initBufferArgumentName(arg3Name)) {
     return false;
   }
 
   return true;
 }
 
-static bool CheckPrecedingStatements(ModuleValidatorShared& m,
-                                     ParseNode* stmtList) {
+static bool CheckPrecedingStatements(ModuleValidator& m, ParseNode* stmtList) {
   MOZ_ASSERT(stmtList->isKind(ParseNodeKind::StatementList));
 
   ParseNode* stmt = ListHead(stmtList);
   for (unsigned i = 0, n = ListLength(stmtList); i < n; i++) {
     if (!IsIgnoredDirective(m.cx(), stmt)) {
       return m.fail(stmt, "invalid asm.js statement");
     }
   }
 
   return true;
 }
 
-static bool CheckGlobalVariableInitConstant(ModuleValidatorShared& m,
+static bool CheckGlobalVariableInitConstant(ModuleValidator& m,
                                             PropertyName* varName,
                                             ParseNode* initNode, bool isConst) {
   NumLit lit = ExtractNumericLiteral(m, initNode);
   if (!lit.valid()) {
     return m.fail(initNode,
                   "global initializer is out of representable integer range");
   }
 
   Type canonicalType = Type::canonicalize(Type::lit(lit));
   if (!canonicalType.isGlobalVarType()) {
     return m.fail(initNode, "global variable type not allowed");
   }
 
   return m.addGlobalVarInit(varName, lit, canonicalType, isConst);
 }
 
-static bool CheckTypeAnnotation(ModuleValidatorShared& m,
-                                ParseNode* coercionNode, Type* coerceTo,
+static bool CheckTypeAnnotation(ModuleValidator& m, ParseNode* coercionNode,
+                                Type* coerceTo,
                                 ParseNode** coercedExpr = nullptr) {
   switch (coercionNode->getKind()) {
     case ParseNodeKind::BitOrExpr: {
       ParseNode* rhs = BitwiseRight(coercionNode);
       uint32_t i;
       if (!IsLiteralInt(m, rhs, &i) || i != 0) {
         return m.fail(rhs, "must use |0 for argument/return coercion");
       }
@@ -2832,17 +2756,17 @@ static bool CheckTypeAnnotation(ModuleVa
       break;
     }
     default:;
   }
 
   return m.fail(coercionNode, "must be of the form +x, x|0 or fround(x)");
 }
 
-static bool CheckGlobalVariableInitImport(ModuleValidatorShared& m,
+static bool CheckGlobalVariableInitImport(ModuleValidator& m,
                                           PropertyName* varName,
                                           ParseNode* initNode, bool isConst) {
   Type coerceTo;
   ParseNode* coercedExpr;
   if (!CheckTypeAnnotation(m, initNode, &coerceTo, &coercedExpr)) {
     return false;
   }
 
@@ -2866,17 +2790,17 @@ static bool CheckGlobalVariableInitImpor
   if (!IsUseOfName(base, importName)) {
     return m.failName(coercedExpr, "base of import expression must be '%s'",
                       importName);
   }
 
   return m.addGlobalVarImport(varName, field, coerceTo, isConst);
 }
 
-static bool IsArrayViewCtorName(ModuleValidatorShared& m, PropertyName* name,
+static bool IsArrayViewCtorName(ModuleValidator& m, PropertyName* name,
                                 Scalar::Type* type) {
   JSAtomState& names = m.cx()->names();
   if (name == names.Int8Array) {
     *type = Scalar::Int8;
   } else if (name == names.Uint8Array) {
     *type = Scalar::Uint8;
   } else if (name == names.Int16Array) {
     *type = Scalar::Int16;
@@ -2891,17 +2815,17 @@ static bool IsArrayViewCtorName(ModuleVa
   } else if (name == names.Float64Array) {
     *type = Scalar::Float64;
   } else {
     return false;
   }
   return true;
 }
 
-static bool CheckNewArrayViewArgs(ModuleValidatorShared& m, ParseNode* newExpr,
+static bool CheckNewArrayViewArgs(ModuleValidator& m, ParseNode* newExpr,
                                   PropertyName* bufferName) {
   ParseNode* ctorExpr = BinaryLeft(newExpr);
   ParseNode* ctorArgs = BinaryRight(newExpr);
   ParseNode* bufArg = ListHead(ctorArgs);
   if (!bufArg || NextNode(bufArg) != nullptr) {
     return m.fail(ctorExpr,
                   "array view constructor takes exactly one argument");
   }
@@ -2909,17 +2833,17 @@ static bool CheckNewArrayViewArgs(Module
   if (!IsUseOfName(bufArg, bufferName)) {
     return m.failName(bufArg, "argument to array view constructor must be '%s'",
                       bufferName);
   }
 
   return true;
 }
 
-static bool CheckNewArrayView(ModuleValidatorShared& m, PropertyName* varName,
+static bool CheckNewArrayView(ModuleValidator& m, PropertyName* varName,
                               ParseNode* newExpr) {
   PropertyName* globalName = m.globalArgumentName();
   if (!globalName) {
     return m.fail(
         newExpr, "cannot create array view without an asm.js global parameter");
   }
 
   PropertyName* bufferName = m.bufferArgumentName();
@@ -2945,60 +2869,60 @@ static bool CheckNewArrayView(ModuleVali
     }
   } else {
     if (!ctorExpr->isKind(ParseNodeKind::Name)) {
       return m.fail(ctorExpr,
                     "expecting name of imported array view constructor");
     }
 
     PropertyName* globalName = ctorExpr->as<NameNode>().name();
-    const ModuleValidatorShared::Global* global = m.lookupGlobal(globalName);
+    const ModuleValidator::Global* global = m.lookupGlobal(globalName);
     if (!global) {
       return m.failName(ctorExpr, "%s not found in module global scope",
                         globalName);
     }
 
-    if (global->which() != ModuleValidatorShared::Global::ArrayViewCtor) {
+    if (global->which() != ModuleValidator::Global::ArrayViewCtor) {
       return m.failName(ctorExpr,
                         "%s must be an imported array view constructor",
                         globalName);
     }
 
     field = nullptr;
     type = global->viewType();
   }
 
   if (!CheckNewArrayViewArgs(m, newExpr, bufferName)) {
     return false;
   }
 
   return m.addArrayView(varName, type, field);
 }
 
-static bool CheckGlobalMathImport(ModuleValidatorShared& m, ParseNode* initNode,
+static bool CheckGlobalMathImport(ModuleValidator& m, ParseNode* initNode,
                                   PropertyName* varName, PropertyName* field) {
   // Math builtin, with the form glob.Math.[[builtin]]
-  ModuleValidatorShared::MathBuiltin mathBuiltin;
+  ModuleValidator::MathBuiltin mathBuiltin;
   if (!m.lookupStandardLibraryMathName(field, &mathBuiltin)) {
     return m.failName(initNode, "'%s' is not a standard Math builtin", field);
   }
 
   switch (mathBuiltin.kind) {
-    case ModuleValidatorShared::MathBuiltin::Function:
+    case ModuleValidator::MathBuiltin::Function:
       return m.addMathBuiltinFunction(varName, mathBuiltin.u.func, field);
-    case ModuleValidatorShared::MathBuiltin::Constant:
+    case ModuleValidator::MathBuiltin::Constant:
       return m.addMathBuiltinConstant(varName, mathBuiltin.u.cst, field);
     default:
       break;
   }
   MOZ_CRASH("unexpected or uninitialized math builtin type");
 }
 
-static bool CheckGlobalDotImport(ModuleValidatorShared& m,
-                                 PropertyName* varName, ParseNode* initNode) {
+static bool CheckGlobalDotImport(ModuleValidator& m, PropertyName* varName,
+                                 ParseNode* initNode) {
   ParseNode* base = DotBase(initNode);
   PropertyName* field = DotMember(initNode);
 
   if (base->isKind(ParseNodeKind::DotExpr)) {
     ParseNode* global = DotBase(base);
     PropertyName* math = DotMember(base);
 
     PropertyName* globalName = m.globalArgumentName();
@@ -3047,17 +2971,17 @@ static bool CheckGlobalDotImport(ModuleV
 
   if (baseName != m.importArgumentName()) {
     return m.fail(base, "expected global or import name");
   }
 
   return m.addFFI(varName, field);
 }
 
-static bool CheckModuleGlobal(ModuleValidatorShared& m, ParseNode* var,
+static bool CheckModuleGlobal(ModuleValidator& m, ParseNode* var,
                               bool isConst) {
   if (!var->isKind(ParseNodeKind::Name)) {
     return m.fail(var, "import variable is not a plain name");
   }
 
   PropertyName* varName = var->as<NameNode>().name();
   if (!CheckModuleLevelName(m, var, varName)) {
     return false;
@@ -3084,18 +3008,17 @@ static bool CheckModuleGlobal(ModuleVali
 
   if (initNode->isKind(ParseNodeKind::DotExpr)) {
     return CheckGlobalDotImport(m, varName, initNode);
   }
 
   return m.fail(initNode, "unsupported import expression");
 }
 
-template <typename Unit>
-static bool CheckModuleProcessingDirectives(ModuleValidator<Unit>& m) {
+static bool CheckModuleProcessingDirectives(ModuleValidator& m) {
   auto& ts = m.parser().tokenStream;
   while (true) {
     bool matched;
     if (!ts.matchToken(&matched, TokenKind::String,
                        TokenStreamShared::Operand)) {
       return false;
     }
     if (!matched) {
@@ -3112,18 +3035,17 @@ static bool CheckModuleProcessingDirecti
       return false;
     }
     if (tt != TokenKind::Semi) {
       return m.failCurrentOffset("expected semicolon after string literal");
     }
   }
 }
 
-template <typename Unit>
-static bool CheckModuleGlobals(ModuleValidator<Unit>& m) {
+static bool CheckModuleGlobals(ModuleValidator& m) {
   while (true) {
     ParseNode* varStmt;
     if (!ParseVarOrConstStatement(m.parser(), &varStmt)) {
       return false;
     }
     if (!varStmt) {
       break;
     }
@@ -3133,25 +3055,25 @@ static bool CheckModuleGlobals(ModuleVal
         return false;
       }
     }
   }
 
   return true;
 }
 
-static bool ArgFail(FunctionValidatorShared& f, PropertyName* argName,
+static bool ArgFail(FunctionValidator& f, PropertyName* argName,
                     ParseNode* stmt) {
   return f.failName(stmt,
                     "expecting argument type declaration for '%s' of the "
                     "form 'arg = arg|0' or 'arg = +arg' or 'arg = fround(arg)'",
                     argName);
 }
 
-static bool CheckArgumentType(FunctionValidatorShared& f, ParseNode* stmt,
+static bool CheckArgumentType(FunctionValidator& f, ParseNode* stmt,
                               PropertyName* name, Type* type) {
   if (!stmt || !IsExpressionStatement(stmt)) {
     return ArgFail(f, name, stmt ? stmt : f.fn());
   }
 
   ParseNode* initNode = ExpressionStatementExpr(stmt);
   if (!initNode->isKind(ParseNodeKind::AssignExpr)) {
     return ArgFail(f, name, stmt);
@@ -3175,29 +3097,29 @@ static bool CheckArgumentType(FunctionVa
 
   if (!IsUseOfName(coercedExpr, name)) {
     return ArgFail(f, name, stmt);
   }
 
   return true;
 }
 
-static bool CheckProcessingDirectives(ModuleValidatorShared& m,
+static bool CheckProcessingDirectives(ModuleValidator& m,
                                       ParseNode** stmtIter) {
   ParseNode* stmt = *stmtIter;
 
   while (stmt && IsIgnoredDirective(m.cx(), stmt)) {
     stmt = NextNode(stmt);
   }
 
   *stmtIter = stmt;
   return true;
 }
 
-static bool CheckArguments(FunctionValidatorShared& f, ParseNode** stmtIter,
+static bool CheckArguments(FunctionValidator& f, ParseNode** stmtIter,
                            ValTypeVector* argTypes) {
   ParseNode* stmt = *stmtIter;
 
   unsigned numFormals;
   ParseNode* argpn = FunctionFormalParametersList(f.fn(), &numFormals);
 
   for (unsigned i = 0; i < numFormals;
        i++, argpn = NextNode(argpn), stmt = NextNode(stmt)) {
@@ -3219,39 +3141,38 @@ static bool CheckArguments(FunctionValid
       return false;
     }
   }
 
   *stmtIter = stmt;
   return true;
 }
 
-static bool IsLiteralOrConst(FunctionValidatorShared& f, ParseNode* pn,
-                             NumLit* lit) {
+static bool IsLiteralOrConst(FunctionValidator& f, ParseNode* pn, NumLit* lit) {
   if (pn->isKind(ParseNodeKind::Name)) {
-    const ModuleValidatorShared::Global* global =
+    const ModuleValidator::Global* global =
         f.lookupGlobal(pn->as<NameNode>().name());
     if (!global ||
-        global->which() != ModuleValidatorShared::Global::ConstantLiteral) {
+        global->which() != ModuleValidator::Global::ConstantLiteral) {
       return false;
     }
 
     *lit = global->constLiteralValue();
     return true;
   }
 
   if (!IsNumericLiteral(f.m(), pn)) {
     return false;
   }
 
   *lit = ExtractNumericLiteral(f.m(), pn);
   return true;
 }
 
-static bool CheckFinalReturn(FunctionValidatorShared& f,
+static bool CheckFinalReturn(FunctionValidator& f,
                              ParseNode* lastNonEmptyStmt) {
   if (!f.encoder().writeOp(Op::End)) {
     return false;
   }
 
   if (!f.hasAlreadyReturned()) {
     f.setReturnedType(ExprType::Void);
     return true;
@@ -3261,17 +3182,17 @@ static bool CheckFinalReturn(FunctionVal
       !IsVoid(f.returnedType())) {
     return f.fail(lastNonEmptyStmt,
                   "void incompatible with previous return type");
   }
 
   return true;
 }
 
-static bool CheckVariable(FunctionValidatorShared& f, ParseNode* var,
+static bool CheckVariable(FunctionValidator& f, ParseNode* var,
                           ValTypeVector* types, Vector<NumLit>* inits) {
   if (!var->isKind(ParseNodeKind::Name)) {
     return f.fail(var, "local variable is not a plain name");
   }
 
   PropertyName* name = var->as<NameNode>().name();
 
   if (!CheckIdentifier(f.m(), var, name)) {
@@ -3296,17 +3217,17 @@ static bool CheckVariable(FunctionValida
   }
 
   Type type = Type::canonicalize(Type::lit(lit));
 
   return f.addLocal(var, name, type) &&
          types->append(type.canonicalToValType()) && inits->append(lit);
 }
 
-static bool CheckVariables(FunctionValidatorShared& f, ParseNode** stmtIter) {
+static bool CheckVariables(FunctionValidator& f, ParseNode** stmtIter) {
   ParseNode* stmt = *stmtIter;
 
   uint32_t firstVar = f.numLocals();
 
   ValTypeVector types;
   Vector<NumLit> inits(f.cx());
 
   for (; stmt && stmt->isKind(ParseNodeKind::VarStmt);
@@ -3339,93 +3260,89 @@ static bool CheckVariables(FunctionValid
       return false;
     }
   }
 
   *stmtIter = stmt;
   return true;
 }
 
-template <typename Unit>
-static bool CheckExpr(FunctionValidator<Unit>& f, ParseNode* op, Type* type);
-
-template <typename Unit>
-static bool CheckNumericLiteral(FunctionValidator<Unit>& f, ParseNode* num,
+static bool CheckExpr(FunctionValidator& f, ParseNode* op, Type* type);
+
+static bool CheckNumericLiteral(FunctionValidator& 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);
 }
 
-static bool CheckVarRef(FunctionValidatorShared& f, ParseNode* varRef,
-                        Type* type) {
+static bool CheckVarRef(FunctionValidator& f, ParseNode* varRef, Type* type) {
   PropertyName* name = varRef->as<NameNode>().name();
 
-  if (const FunctionValidatorShared::Local* local = f.lookupLocal(name)) {
+  if (const FunctionValidator::Local* local = f.lookupLocal(name)) {
     if (!f.encoder().writeOp(Op::GetLocal)) {
       return false;
     }
     if (!f.encoder().writeVarU32(local->slot)) {
       return false;
     }
     *type = local->type;
     return true;
   }
 
-  if (const ModuleValidatorShared::Global* global = f.lookupGlobal(name)) {
+  if (const ModuleValidator::Global* global = f.lookupGlobal(name)) {
     switch (global->which()) {
-      case ModuleValidatorShared::Global::ConstantLiteral:
+      case ModuleValidator::Global::ConstantLiteral:
         *type = global->varOrConstType();
         return f.writeConstExpr(global->constLiteralValue());
-      case ModuleValidatorShared::Global::ConstantImport:
-      case ModuleValidatorShared::Global::Variable: {
+      case ModuleValidator::Global::ConstantImport:
+      case ModuleValidator::Global::Variable: {
         *type = global->varOrConstType();
         return f.encoder().writeOp(Op::GetGlobal) &&
                f.encoder().writeVarU32(global->varOrConstIndex());
       }
-      case ModuleValidatorShared::Global::Function:
-      case ModuleValidatorShared::Global::FFI:
-      case ModuleValidatorShared::Global::MathBuiltinFunction:
-      case ModuleValidatorShared::Global::Table:
-      case ModuleValidatorShared::Global::ArrayView:
-      case ModuleValidatorShared::Global::ArrayViewCtor:
+      case ModuleValidator::Global::Function:
+      case ModuleValidator::Global::FFI:
+      case ModuleValidator::Global::MathBuiltinFunction:
+      case ModuleValidator::Global::Table:
+      case ModuleValidator::Global::ArrayView:
+      case ModuleValidator::Global::ArrayViewCtor:
         break;
     }
     return f.failName(varRef,
                       "'%s' may not be accessed by ordinary expressions", name);
   }
 
   return f.failName(varRef, "'%s' not found in local or asm.js module scope",
                     name);
 }
 
-static inline bool IsLiteralOrConstInt(FunctionValidatorShared& f,
-                                       ParseNode* pn, uint32_t* u32) {
+static inline bool IsLiteralOrConstInt(FunctionValidator& f, ParseNode* pn,
+                                       uint32_t* u32) {
   NumLit lit;
   if (!IsLiteralOrConst(f, pn, &lit)) {
     return false;
   }
 
   return IsLiteralInt(lit, u32);
 }
 
 static const int32_t NoMask = -1;
 
-template <typename Unit>
-static bool CheckArrayAccess(FunctionValidator<Unit>& f, ParseNode* viewName,
+static bool CheckArrayAccess(FunctionValidator& 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 =
+  const ModuleValidator::Global* global =
       f.lookupGlobal(viewName->as<NameNode>().name());
   if (!global || !global->isAnyArrayView()) {
     return f.fail(viewName,
                   "base of array access must be a typed array view name");
   }
 
   *viewType = global->viewType();
 
@@ -3496,36 +3413,33 @@ static bool CheckArrayAccess(FunctionVal
   // for a shift of zero.
   if (mask != NoMask) {
     return f.writeInt32Lit(mask) && f.encoder().writeOp(Op::I32And);
   }
 
   return true;
 }
 
-static bool WriteArrayAccessFlags(FunctionValidatorShared& f,
-                                  Scalar::Type viewType) {
+static bool WriteArrayAccessFlags(FunctionValidator& f, Scalar::Type viewType) {
   // asm.js only has naturally-aligned accesses.
   size_t align = TypedArrayElemSize(viewType);
   MOZ_ASSERT(IsPowerOfTwo(align));
   if (!f.encoder().writeFixedU8(CeilingLog2(align))) {
     return false;
   }
 
   // asm.js doesn't have constant offsets, so just encode a 0.
   if (!f.encoder().writeVarU32(0)) {
     return false;
   }
 
   return true;
 }
 
-template <typename Unit>
-static bool CheckLoadArray(FunctionValidator<Unit>& f, ParseNode* elem,
-                           Type* type) {
+static bool CheckLoadArray(FunctionValidator& f, ParseNode* elem, Type* type) {
   Scalar::Type viewType;
 
   if (!CheckArrayAccess(f, ElemBase(elem), ElemIndex(elem), &viewType)) {
     return false;
   }
 
   switch (viewType) {
     case Scalar::Int8:
@@ -3575,18 +3489,17 @@ static bool CheckLoadArray(FunctionValid
 
   if (!WriteArrayAccessFlags(f, viewType)) {
     return false;
   }
 
   return true;
 }
 
-template <typename Unit>
-static bool CheckStoreArray(FunctionValidator<Unit>& f, ParseNode* lhs,
+static bool CheckStoreArray(FunctionValidator& 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)) {
@@ -3668,22 +3581,21 @@ static bool CheckStoreArray(FunctionVali
   if (!WriteArrayAccessFlags(f, viewType)) {
     return false;
   }
 
   *type = rhsType;
   return true;
 }
 
-template <typename Unit>
-static bool CheckAssignName(FunctionValidator<Unit>& f, ParseNode* lhs,
+static bool CheckAssignName(FunctionValidator& f, ParseNode* lhs,
                             ParseNode* rhs, Type* type) {
   RootedPropertyName name(f.cx(), lhs->as<NameNode>().name());
 
-  if (const FunctionValidatorShared::Local* lhsVar = f.lookupLocal(name)) {
+  if (const FunctionValidator::Local* lhsVar = f.lookupLocal(name)) {
     Type rhsType;
     if (!CheckExpr(f, rhs, &rhsType)) {
       return false;
     }
 
     if (!f.encoder().writeOp(Op::TeeLocal)) {
       return false;
     }
@@ -3694,18 +3606,18 @@ static bool CheckAssignName(FunctionVali
     if (!(rhsType <= lhsVar->type)) {
       return f.failf(lhs, "%s is not a subtype of %s", rhsType.toChars(),
                      lhsVar->type.toChars());
     }
     *type = rhsType;
     return true;
   }
 
-  if (const ModuleValidatorShared::Global* global = f.lookupGlobal(name)) {
-    if (global->which() != ModuleValidatorShared::Global::Variable) {
+  if (const ModuleValidator::Global* global = f.lookupGlobal(name)) {
+    if (global->which() != ModuleValidator::Global::Variable) {
       return f.failName(lhs, "'%s' is not a mutable variable", name);
     }
 
     Type rhsType;
     if (!CheckExpr(f, rhs, &rhsType)) {
       return false;
     }
 
@@ -3724,19 +3636,17 @@ static bool CheckAssignName(FunctionVali
     *type = rhsType;
     return true;
   }
 
   return f.failName(lhs, "'%s' not found in local or asm.js module scope",
                     name);
 }
 
-template <typename Unit>
-static bool CheckAssign(FunctionValidator<Unit>& f, ParseNode* assign,
-                        Type* type) {
+static bool CheckAssign(FunctionValidator& 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);
   }
@@ -3745,19 +3655,17 @@ 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");
 }
 
-template <typename Unit>
-static bool CheckMathIMul(FunctionValidator<Unit>& f, ParseNode* call,
-                          Type* type) {
+static bool CheckMathIMul(FunctionValidator& 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;
@@ -3776,19 +3684,17 @@ 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);
 }
 
-template <typename Unit>
-static bool CheckMathClz32(FunctionValidator<Unit>& f, ParseNode* call,
-                           Type* type) {
+static bool CheckMathClz32(FunctionValidator& 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)) {
@@ -3798,19 +3704,17 @@ 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);
 }
 
-template <typename Unit>
-static bool CheckMathAbs(FunctionValidator<Unit>& f, ParseNode* call,
-                         Type* type) {
+static bool CheckMathAbs(FunctionValidator& 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)) {
@@ -3831,19 +3735,17 @@ 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());
 }
 
-template <typename Unit>
-static bool CheckMathSqrt(FunctionValidator<Unit>& f, ParseNode* call,
-                          Type* type) {
+static bool CheckMathSqrt(FunctionValidator& 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)) {
@@ -3859,18 +3761,17 @@ 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());
 }
 
-template <typename Unit>
-static bool CheckMathMinMax(FunctionValidator<Unit>& f, ParseNode* callNode,
+static bool CheckMathMinMax(FunctionValidator& 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)) {
@@ -3917,21 +3818,21 @@ static bool CheckMathMinMax(FunctionVali
         return false;
       }
     }
   }
 
   return true;
 }
 
-using CheckArgType = bool (*)(FunctionValidatorShared& f, ParseNode* argNode,
+using CheckArgType = bool (*)(FunctionValidator& f, ParseNode* argNode,
                               Type type);
 
-template <CheckArgType checkArg, typename Unit>
-static bool CheckCallArgs(FunctionValidator<Unit>& f, ParseNode* callNode,
+template <CheckArgType checkArg>
+static bool CheckCallArgs(FunctionValidator& 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;
     }
@@ -3942,18 +3843,18 @@ static bool CheckCallArgs(FunctionValida
 
     if (!args->append(Type::canonicalize(type).canonicalToValType())) {
       return false;
     }
   }
   return true;
 }
 
-static bool CheckSignatureAgainstExisting(ModuleValidatorShared& m,
-                                          ParseNode* usepn, const FuncType& sig,
+static bool CheckSignatureAgainstExisting(ModuleValidator& m, ParseNode* usepn,
+                                          const FuncType& sig,
                                           const FuncType& existing) {
   if (sig.args().length() != existing.args().length()) {
     return m.failf(usepn,
                    "incompatible number of arguments (%zu"
                    " here vs. %zu before)",
                    sig.args().length(), existing.args().length());
   }
 
@@ -3969,25 +3870,24 @@ 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;
 }
 
-template <typename Unit>
-static bool CheckFunctionSignature(ModuleValidator<Unit>& m, ParseNode* usepn,
+static bool CheckFunctionSignature(ModuleValidator& m, ParseNode* usepn,
                                    FuncType&& sig, PropertyName* name,
-                                   ModuleValidatorShared::Func** func) {
+                                   ModuleValidator::Func** func) {
   if (sig.args().length() > MaxParams) {
     return m.failf(usepn, "too many parameters");
   }
 
-  ModuleValidatorShared::Func* existing = m.lookupFuncDef(name);
+  ModuleValidator::Func* existing = m.lookupFuncDef(name);
   if (!existing) {
     if (!CheckModuleLevelName(m, usepn, name)) {
       return false;
     }
     return m.addFuncDef(name, usepn->pn_pos.begin, std::move(sig), func);
   }
 
   const FuncTypeWithId& existingSig =
@@ -3996,38 +3896,37 @@ static bool CheckFunctionSignature(Modul
   if (!CheckSignatureAgainstExisting(m, usepn, sig, existingSig)) {
     return false;
   }
 
   *func = existing;
   return true;
 }
 
-static bool CheckIsArgType(FunctionValidatorShared& f, ParseNode* argNode,
+static bool CheckIsArgType(FunctionValidator& f, ParseNode* argNode,
                            Type type) {
   if (!type.isArgType()) {
     return f.failf(argNode, "%s is not a subtype of int, float, or double",
                    type.toChars());
   }
   return true;
 }
 
-template <typename Unit>
-static bool CheckInternalCall(FunctionValidator<Unit>& f, ParseNode* callNode,
+static bool CheckInternalCall(FunctionValidator& f, ParseNode* callNode,
                               PropertyName* calleeName, Type ret, Type* type) {
   MOZ_ASSERT(ret.isCanonical());
 
   ValTypeVector args;
   if (!CheckCallArgs<CheckIsArgType>(f, callNode, &args)) {
     return false;
   }
 
   FuncType sig(std::move(args), ret.canonicalToExprType());
 
-  ModuleValidatorShared::Func* callee;
+  ModuleValidator::Func* callee;
   if (!CheckFunctionSignature(f.m(), callNode, std::move(sig), calleeName,
                               &callee)) {
     return false;
   }
 
   if (!f.writeCall(callNode, MozOp::OldCallDirect)) {
     return false;
   }
@@ -4035,28 +3934,27 @@ static bool CheckInternalCall(FunctionVa
   if (!f.encoder().writeVarU32(callee->funcDefIndex())) {
     return false;
   }
 
   *type = Type::ret(ret);
   return true;
 }
 
-template <typename Unit>
-static bool CheckFuncPtrTableAgainstExisting(ModuleValidator<Unit>& m,
+static bool CheckFuncPtrTableAgainstExisting(ModuleValidator& 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) {
+  if (const ModuleValidator::Global* existing = m.lookupGlobal(name)) {
+    if (existing->which() != ModuleValidator::Global::Table) {
       return m.failName(usepn, "'%s' is not a function-pointer table", name);
     }
 
-    ModuleValidatorShared::Table& table = m.table(existing->tableIndex());
+    ModuleValidator::Table& table = m.table(existing->tableIndex());
     if (mask != table.mask()) {
       return m.failf(usepn, "mask does not match previous value (%u)",
                      table.mask());
     }
 
     if (!CheckSignatureAgainstExisting(
             m, usepn, sig, m.env().types[table.sigIndex()].funcType())) {
       return false;
@@ -4073,32 +3971,31 @@ static bool CheckFuncPtrTableAgainstExis
   if (!m.declareFuncPtrTable(std::move(sig), name, usepn->pn_pos.begin, mask,
                              tableIndex)) {
     return false;
   }
 
   return true;
 }
 
-template <typename Unit>
-static bool CheckFuncPtrCall(FunctionValidator<Unit>& f, ParseNode* callNode,
+static bool CheckFuncPtrCall(FunctionValidator& 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)) {
     return f.fail(tableNode, "expecting name of function-pointer array");
   }
 
   PropertyName* name = tableNode->as<NameNode>().name();
-  if (const ModuleValidatorShared::Global* existing = f.lookupGlobal(name)) {
-    if (existing->which() != ModuleValidatorShared::Global::Table) {
+  if (const ModuleValidator::Global* existing = f.lookupGlobal(name)) {
+    if (existing->which() != ModuleValidator::Global::Table) {
       return f.failName(
           tableNode, "'%s' is not the name of a function-pointer array", name);
     }
   }
 
   if (!indexExpr->isKind(ParseNodeKind::BitAndExpr)) {
     return f.fail(indexExpr,
                   "function-pointer table index expression needs & mask");
@@ -4146,26 +4043,25 @@ static bool CheckFuncPtrCall(FunctionVal
   if (!f.encoder().writeVarU32(f.m().table(tableIndex).sigIndex())) {
     return false;
   }
 
   *type = Type::ret(ret);
   return true;
 }
 
-static bool CheckIsExternType(FunctionValidatorShared& f, ParseNode* argNode,
+static bool CheckIsExternType(FunctionValidator& f, ParseNode* argNode,
                               Type type) {
   if (!type.isExtern()) {
     return f.failf(argNode, "%s is not a subtype of extern", type.toChars());
   }
   return true;
 }
 
-template <typename Unit>
-static bool CheckFFICall(FunctionValidator<Unit>& f, ParseNode* callNode,
+static bool CheckFFICall(FunctionValidator& 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");
   }
@@ -4190,18 +4086,18 @@ static bool CheckFFICall(FunctionValidat
   if (!f.encoder().writeVarU32(importIndex)) {
     return false;
   }
 
   *type = Type::ret(ret);
   return true;
 }
 
-static bool CheckFloatCoercionArg(FunctionValidatorShared& f,
-                                  ParseNode* inputNode, Type inputType) {
+static bool CheckFloatCoercionArg(FunctionValidator& f, ParseNode* inputNode,
+                                  Type inputType) {
   if (inputType.isMaybeDouble()) {
     return f.encoder().writeOp(Op::F32DemoteF64);
   }
   if (inputType.isSigned()) {
     return f.encoder().writeOp(Op::F32ConvertSI32);
   }
   if (inputType.isUnsigned()) {
     return f.encoder().writeOp(Op::F32ConvertUI32);
@@ -4210,22 +4106,20 @@ static bool CheckFloatCoercionArg(Functi
     return true;
   }
 
   return f.failf(inputNode,
                  "%s is not a subtype of signed, unsigned, double? or floatish",
                  inputType.toChars());
 }
 
-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,
+static bool CheckCoercedCall(FunctionValidator& f, ParseNode* call, Type ret,
+                             Type* type);
+
+static bool CheckCoercionArg(FunctionValidator& f, ParseNode* arg,
                              Type expected, Type* type) {
   MOZ_ASSERT(expected.isCanonicalValType());
 
   if (arg->isKind(ParseNodeKind::CallExpr)) {
     return CheckCoercedCall(f, arg, expected, type);
   }
 
   Type argType;
@@ -4240,37 +4134,34 @@ static bool CheckCoercionArg(FunctionVal
   } else {
     MOZ_CRASH("not call coercions");
   }
 
   *type = Type::ret(expected);
   return true;
 }
 
-template <typename Unit>
-static bool CheckMathFRound(FunctionValidator<Unit>& f, ParseNode* callNode,
+static bool CheckMathFRound(FunctionValidator& 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;
 }
 
-template <typename Unit>
-static bool CheckMathBuiltinCall(FunctionValidator<Unit>& f,
-                                 ParseNode* callNode,
+static bool CheckMathBuiltinCall(FunctionValidator& 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);
@@ -4411,35 +4302,34 @@ static bool CheckMathBuiltinCall(Functio
       return false;
     }
   }
 
   *type = opIsDouble ? Type::Double : Type::Floatish;
   return true;
 }
 
-template <typename Unit>
-static bool CheckUncoercedCall(FunctionValidator<Unit>& f, ParseNode* expr,
+static bool CheckUncoercedCall(FunctionValidator& f, ParseNode* expr,
                                Type* type) {
   MOZ_ASSERT(expr->isKind(ParseNodeKind::CallExpr));
 
-  const ModuleValidatorShared::Global* global;
+  const ModuleValidator::Global* global;
   if (IsCallToGlobal(f.m(), expr, &global) && global->isMathFunction()) {
     return CheckMathBuiltinCall(f, expr, global->mathBuiltinFunction(), type);
   }
 
   return f.fail(
       expr,
       "all function calls must be calls to standard lib math functions,"
       " ignored (via f(); or comma-expression), coerced to signed (via f()|0),"
       " coerced to float (via fround(f())), or coerced to double (via +f())");
 }
 
-static bool CoerceResult(FunctionValidatorShared& f, ParseNode* expr,
-                         Type expected, Type actual, Type* type) {
+static bool CoerceResult(FunctionValidator& f, ParseNode* expr, Type expected,
+                         Type actual, Type* type) {
   MOZ_ASSERT(expected.isCanonical());
 
   // At this point, the bytecode resembles this:
   //      | the thing we wanted to coerce | current position |>
   switch (expected.which()) {
     case Type::Void:
       if (!actual.isVoid()) {
         if (!f.encoder().writeOp(Op::Drop)) {
@@ -4481,31 +4371,29 @@ static bool CoerceResult(FunctionValidat
     default:
       MOZ_CRASH("unexpected uncoerced result type");
   }
 
   *type = Type::ret(expected);
   return true;
 }
 
-template <typename Unit>
-static bool CheckCoercedMathBuiltinCall(FunctionValidator<Unit>& f,
+static bool CheckCoercedMathBuiltinCall(FunctionValidator& 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);
 }
 
-template <typename Unit>
-static bool CheckCoercedCall(FunctionValidator<Unit>& f, ParseNode* call,
-                             Type ret, Type* type) {
+static bool CheckCoercedCall(FunctionValidator& 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);
@@ -4522,58 +4410,55 @@ static bool CheckCoercedCall(FunctionVal
   }
 
   if (!callee->isKind(ParseNodeKind::Name)) {
     return f.fail(callee, "unexpected callee expression type");
   }
 
   PropertyName* calleeName = callee->as<NameNode>().name();
 
-  if (const ModuleValidatorShared::Global* global =
-          f.lookupGlobal(calleeName)) {
+  if (const ModuleValidator::Global* global = f.lookupGlobal(calleeName)) {
     switch (global->which()) {
-      case ModuleValidatorShared::Global::FFI:
+      case ModuleValidator::Global::FFI:
         return CheckFFICall(f, call, global->ffiIndex(), ret, type);
-      case ModuleValidatorShared::Global::MathBuiltinFunction:
+      case ModuleValidator::Global::MathBuiltinFunction:
         return CheckCoercedMathBuiltinCall(
             f, call, global->mathBuiltinFunction(), ret, type);
-      case ModuleValidatorShared::Global::ConstantLiteral:
-      case ModuleValidatorShared::Global::ConstantImport:
-      case ModuleValidatorShared::Global::Variable:
-      case ModuleValidatorShared::Global::Table:
-      case ModuleValidatorShared::Global::ArrayView:
-      case ModuleValidatorShared::Global::ArrayViewCtor:
+      case ModuleValidator::Global::ConstantLiteral:
+      case ModuleValidator::Global::ConstantImport:
+      case ModuleValidator::Global::Variable:
+      case ModuleValidator::Global::Table:
+      case ModuleValidator::Global::ArrayView:
+      case ModuleValidator::Global::ArrayViewCtor:
         return f.failName(callee, "'%s' is not callable function", calleeName);
-      case ModuleValidatorShared::Global::Function:
+      case ModuleValidator::Global::Function:
         break;
     }
   }
 
   return CheckInternalCall(f, call, calleeName, ret, type);
 }
 
-template <typename Unit>
-static bool CheckPos(FunctionValidator<Unit>& f, ParseNode* pos, Type* type) {
+static bool CheckPos(FunctionValidator& 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);
 }
 
-template <typename Unit>
-static bool CheckNot(FunctionValidator<Unit>& f, ParseNode* expr, Type* type) {
+static bool CheckNot(FunctionValidator& f, ParseNode* expr, Type* type) {
   MOZ_ASSERT(expr->isKind(ParseNodeKind::NotExpr));
   ParseNode* operand = UnaryKid(expr);
 
   Type operandType;
   if (!CheckExpr(f, operand, &operandType)) {
     return false;
   }
 
@@ -4581,18 +4466,17 @@ static bool CheckNot(FunctionValidator<U
     return f.failf(operand, "%s is not a subtype of int",
                    operandType.toChars());
   }
 
   *type = Type::Int;
   return f.encoder().writeOp(Op::I32Eqz);
 }
 
-template <typename Unit>
-static bool CheckNeg(FunctionValidator<Unit>& f, ParseNode* expr, Type* type) {
+static bool CheckNeg(FunctionValidator& f, ParseNode* expr, Type* type) {
   MOZ_ASSERT(expr->isKind(ParseNodeKind::NegExpr));
   ParseNode* operand = UnaryKid(expr);
 
   Type operandType;
   if (!CheckExpr(f, operand, &operandType)) {
     return false;
   }
 
@@ -4610,18 +4494,17 @@ static bool CheckNeg(FunctionValidator<U
     *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());
 }
 
-template <typename Unit>
-static bool CheckCoerceToInt(FunctionValidator<Unit>& f, ParseNode* expr,
+static bool CheckCoerceToInt(FunctionValidator& f, ParseNode* expr,
                              Type* type) {
   MOZ_ASSERT(expr->isKind(ParseNodeKind::BitNotExpr));
   ParseNode* operand = UnaryKid(expr);
 
   Type operandType;
   if (!CheckExpr(f, operand, &operandType)) {
     return false;
   }
@@ -4637,19 +4520,17 @@ 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;
 }
 
-template <typename Unit>
-static bool CheckBitNot(FunctionValidator<Unit>& f, ParseNode* neg,
-                        Type* type) {
+static bool CheckBitNot(FunctionValidator& 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;
@@ -4665,23 +4546,19 @@ static bool CheckBitNot(FunctionValidato
   if (!f.encoder().writeOp(MozOp::I32BitNot)) {
     return false;
   }
 
   *type = Type::Signed;
   return true;
 }
 
-template <typename Unit>
-static bool CheckAsExprStatement(FunctionValidator<Unit>& f,
-                                 ParseNode* exprStmt);
-
-template <typename Unit>
-static bool CheckComma(FunctionValidator<Unit>& f, ParseNode* comma,
-                       Type* type) {
+static bool CheckAsExprStatement(FunctionValidator& f, ParseNode* exprStmt);
+
+static bool CheckComma(FunctionValidator& 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;
   }
@@ -4703,18 +4580,17 @@ static bool CheckComma(FunctionValidator
   }
 
   f.encoder().patchFixedU7(typeAt,
                            uint8_t(type->toWasmBlockSignatureType().code()));
 
   return f.encoder().writeOp(Op::End);
 }
 
-template <typename Unit>
-static bool CheckConditional(FunctionValidator<Unit>& f, ParseNode* ternary,
+static bool CheckConditional(FunctionValidator& 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;
@@ -4761,19 +4637,17 @@ static bool CheckConditional(FunctionVal
 
   if (!f.popIf(typeAt, type->toWasmBlockSignatureType())) {
     return false;
   }
 
   return true;
 }
 
-template <typename Unit>
-static bool IsValidIntMultiplyConstant(ModuleValidator<Unit>& m,
-                                       ParseNode* expr) {
+static bool IsValidIntMultiplyConstant(ModuleValidator& m, ParseNode* expr) {
   if (!IsNumericLiteral(m, expr)) {
     return false;
   }
 
   NumLit lit = ExtractNumericLiteral(m, expr);
   switch (lit.which()) {
     case NumLit::Fixnum:
     case NumLit::NegativeInt:
@@ -4786,19 +4660,17 @@ static bool IsValidIntMultiplyConstant(M
     case NumLit::Float:
     case NumLit::OutOfRangeInt:
       return false;
   }
 
   MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Bad literal");
 }
 
-template <typename Unit>
-static bool CheckMultiply(FunctionValidator<Unit>& f, ParseNode* star,
-                          Type* type) {
+static bool CheckMultiply(FunctionValidator& 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;
   }
@@ -4828,19 +4700,18 @@ 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?");
 }
 
-template <typename Unit>
-static bool CheckAddOrSub(FunctionValidator<Unit>& f, ParseNode* expr,
-                          Type* type, unsigned* numAddOrSubOut = nullptr) {
+static bool CheckAddOrSub(FunctionValidator& 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);
@@ -4909,19 +4780,17 @@ static bool CheckAddOrSub(FunctionValida
   }
 
   if (numAddOrSubOut) {
     *numAddOrSubOut = numAddOrSub;
   }
   return true;
 }
 
-template <typename Unit>
-static bool CheckDivOrMod(FunctionValidator<Unit>& f, ParseNode* expr,
-                          Type* type) {
+static bool CheckDivOrMod(FunctionValidator& 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)) {
@@ -4962,19 +4831,17 @@ 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());
 }
 
-template <typename Unit>
-static bool CheckComparison(FunctionValidator<Unit>& f, ParseNode* comp,
-                            Type* type) {
+static bool CheckComparison(FunctionValidator& 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);
@@ -5095,19 +4962,17 @@ static bool CheckComparison(FunctionVali
   } else {
     MOZ_CRASH("unexpected type");
   }
 
   *type = Type::Int;
   return f.encoder().writeOp(stmt);
 }
 
-template <typename Unit>
-static bool CheckBitwise(FunctionValidator<Unit>& f, ParseNode* bitwise,
-                         Type* type) {
+static bool CheckBitwise(FunctionValidator& 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;
@@ -5212,18 +5077,17 @@ static bool CheckBitwise(FunctionValidat
       break;
     default:
       MOZ_CRASH("not a bitwise op");
   }
 
   return true;
 }
 
-template <typename Unit>
-static bool CheckExpr(FunctionValidator<Unit>& f, ParseNode* expr, Type* type) {
+static bool CheckExpr(FunctionValidator& f, ParseNode* expr, Type* type) {
   if (!CheckRecursionLimitDontReport(f.cx())) {
     return f.m().failOverRecursed();
   }
 
   if (IsNumericLiteral(f.m(), expr)) {
     return CheckNumericLiteral(f, expr, type);
   }
 
@@ -5276,21 +5140,19 @@ static bool CheckExpr(FunctionValidator<
       return CheckBitwise(f, expr, type);
 
     default:;
   }
 
   return f.fail(expr, "unsupported expression");
 }
 
-template <typename Unit>
-static bool CheckStatement(FunctionValidator<Unit>& f, ParseNode* stmt);
-
-template <typename Unit>
-static bool CheckAsExprStatement(FunctionValidator<Unit>& f, ParseNode* expr) {
+static bool CheckStatement(FunctionValidator& f, ParseNode* stmt);
+
+static bool CheckAsExprStatement(FunctionValidator& 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;
@@ -5300,26 +5162,22 @@ static bool CheckAsExprStatement(Functio
     if (!f.encoder().writeOp(Op::Drop)) {
       return false;
     }
   }
 
   return true;
 }
 
-template <typename Unit>
-static bool CheckExprStatement(FunctionValidator<Unit>& f,
-                               ParseNode* exprStmt) {
+static bool CheckExprStatement(FunctionValidator& f, ParseNode* exprStmt) {
   MOZ_ASSERT(exprStmt->isKind(ParseNodeKind::ExpressionStmt));
   return CheckAsExprStatement(f, UnaryKid(exprStmt));
 }
 
-template <typename Unit>
-static bool CheckLoopConditionOnEntry(FunctionValidator<Unit>& f,
-                                      ParseNode* cond) {
+static bool CheckLoopConditionOnEntry(FunctionValidator& f, ParseNode* cond) {
   uint32_t maybeLit;
   if (IsLiteralInt(f.m(), cond, &maybeLit) && maybeLit) {
     return true;
   }
 
   Type condType;
   if (!CheckExpr(f, cond, &condType)) {
     return false;
@@ -5335,18 +5193,17 @@ static bool CheckLoopConditionOnEntry(Fu
   // brIf (i32.eqz $f) $out
   if (!f.writeBreakIf()) {
     return false;
   }
 
   return true;
 }
 
-template <typename Unit>
-static bool CheckWhile(FunctionValidator<Unit>& f, ParseNode* whileStmt,
+static bool CheckWhile(FunctionValidator& 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
@@ -5377,18 +5234,17 @@ static bool CheckWhile(FunctionValidator
     return false;
   }
   if (labels) {
     f.removeLabels(*labels);
   }
   return true;
 }
 
-template <typename Unit>
-static bool CheckFor(FunctionValidator<Unit>& f, ParseNode* forStmt,
+static bool CheckFor(FunctionValidator& 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");
   }
@@ -5463,18 +5319,17 @@ static bool CheckFor(FunctionValidator<U
 
   if (labels) {
     f.removeLabels(*labels);
   }
 
   return true;
 }
 
-template <typename Unit>
-static bool CheckDoWhile(FunctionValidator<Unit>& f, ParseNode* whileStmt,
+static bool CheckDoWhile(FunctionValidator& 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
@@ -5521,22 +5376,20 @@ static bool CheckDoWhile(FunctionValidat
     return false;
   }
   if (labels) {
     f.removeLabels(*labels);
   }
   return true;
 }
 
-template <typename Unit>
-static bool CheckStatementList(FunctionValidator<Unit>& f, ParseNode*,
+static bool CheckStatementList(FunctionValidator& f, ParseNode*,
                                const LabelVector* = nullptr);
 
-template <typename Unit>
-static bool CheckLabel(FunctionValidator<Unit>& f, ParseNode* labeledStmt) {
+static bool CheckLabel(FunctionValidator& f, ParseNode* labeledStmt) {
   MOZ_ASSERT(labeledStmt->isKind(ParseNodeKind::LabelStmt));
 
   LabelVector labels;
   ParseNode* innermost = labeledStmt;
   do {
     if (!labels.append(LabeledStatementLabel(innermost))) {
       return false;
     }
@@ -5565,18 +5418,17 @@ static bool CheckLabel(FunctionValidator
   }
 
   if (!f.popUnbreakableBlock(&labels)) {
     return false;
   }
   return true;
 }
 
-template <typename Unit>
-static bool CheckIf(FunctionValidator<Unit>& f, ParseNode* ifStmt) {
+static bool CheckIf(FunctionValidator& 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);
 
@@ -5621,17 +5473,17 @@ recurse:
     if (!f.popIf()) {
       return false;
     }
   }
 
   return true;
 }
 
-static bool CheckCaseExpr(FunctionValidatorShared& f, ParseNode* caseExpr,
+static bool CheckCaseExpr(FunctionValidator& f, ParseNode* caseExpr,
                           int32_t* value) {
   if (!IsNumericLiteral(f.m(), caseExpr)) {
     return f.fail(caseExpr,
                   "switch case expression must be an integer literal");
   }
 
   NumLit lit = ExtractNumericLiteral(f.m(), caseExpr);
   switch (lit.which()) {
@@ -5646,27 +5498,27 @@ static bool CheckCaseExpr(FunctionValida
     case NumLit::Float:
       return f.fail(caseExpr,
                     "switch case expression must be an integer literal");
   }
 
   return true;
 }
 
-static bool CheckDefaultAtEnd(FunctionValidatorShared& f, ParseNode* stmt) {
+static bool CheckDefaultAtEnd(FunctionValidator& f, ParseNode* stmt) {
   for (; stmt; stmt = NextNode(stmt)) {
     if (IsDefaultCase(stmt) && NextNode(stmt) != nullptr) {
       return f.fail(stmt, "default label must be at the end");
     }
   }
 
   return true;
 }
 
-static bool CheckSwitchRange(FunctionValidatorShared& f, ParseNode* stmt,
+static bool CheckSwitchRange(FunctionValidator& f, ParseNode* stmt,
                              int32_t* low, int32_t* high,
                              uint32_t* tableLength) {
   if (IsDefaultCase(stmt)) {
     *low = 0;
     *high = -1;
     *tableLength = 0;
     return true;
   }
@@ -5696,18 +5548,17 @@ static bool CheckSwitchRange(FunctionVal
         initialStmt,
         "all switch statements generate tables; this table would be too big");
   }
 
   *tableLength = uint32_t(i64);
   return true;
 }
 
-template <typename Unit>
-static bool CheckSwitchExpr(FunctionValidator<Unit>& f, ParseNode* switchExpr) {
+static bool CheckSwitchExpr(FunctionValidator& 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());
   }
@@ -5721,18 +5572,17 @@ 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.
-template <typename Unit>
-static bool CheckSwitch(FunctionValidator<Unit>& f, ParseNode* switchStmt) {
+static bool CheckSwitch(FunctionValidator& 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()) {
@@ -5869,33 +5719,31 @@ static bool CheckSwitch(FunctionValidato
 
   // Close the wrapping block.
   if (!f.popBreakableBlock()) {
     return false;
   }
   return true;
 }
 
-static bool CheckReturnType(FunctionValidatorShared& f, ParseNode* usepn,
-                            Type ret) {
+static bool CheckReturnType(FunctionValidator& f, ParseNode* usepn, Type ret) {
   if (!f.hasAlreadyReturned()) {
     f.setReturnedType(ret.canonicalToExprType());
     return true;
   }
 
   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;
 }
 
-template <typename Unit>
-static bool CheckReturn(FunctionValidator<Unit>& f, ParseNode* returnStmt) {
+static bool CheckReturn(FunctionValidator& f, ParseNode* returnStmt) {
   ParseNode* expr = ReturnExpr(returnStmt);
 
   if (!expr) {
     if (!CheckReturnType(f, returnStmt, Type::Void)) {
       return false;
     }
   } else {
     Type type;
@@ -5914,18 +5762,17 @@ static bool CheckReturn(FunctionValidato
 
   if (!f.encoder().writeOp(Op::Return)) {
     return false;
   }
 
   return true;
 }
 
-template <typename Unit>
-static bool CheckStatementList(FunctionValidator<Unit>& f, ParseNode* stmtList,
+static bool CheckStatementList(FunctionValidator& 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)) {
@@ -5935,36 +5782,34 @@ static bool CheckStatementList(FunctionV
   }
 
   if (!f.popUnbreakableBlock(labels)) {
     return false;
   }
   return true;
 }
 
-template <typename Unit>
-static bool CheckLexicalScope(FunctionValidator<Unit>& f, ParseNode* node) {
+static bool CheckLexicalScope(FunctionValidator& 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,
+static bool CheckBreakOrContinue(FunctionValidator& f, bool isBreak,
                                  ParseNode* stmt) {
   if (PropertyName* maybeLabel = LoopControlMaybeLabel(stmt)) {
     return f.writeLabeledBreakOrContinue(maybeLabel, isBreak);
   }
   return f.writeUnlabeledBreakOrContinue(isBreak);
 }
 
-template <typename Unit>
-static bool CheckStatement(FunctionValidator<Unit>& f, ParseNode* stmt) {
+static bool CheckStatement(FunctionValidator& f, ParseNode* stmt) {
   if (!CheckRecursionLimitDontReport(f.cx())) {
     return f.m().failOverRecursed();
   }
 
   switch (stmt->getKind()) {
     case ParseNodeKind::EmptyStmt:
       return true;
     case ParseNodeKind::ExpressionStmt:
@@ -5992,18 +5837,17 @@ static bool CheckStatement(FunctionValid
     case ParseNodeKind::LexicalScope:
       return CheckLexicalScope(f, stmt);
     default:;
   }
 
   return f.fail(stmt, "unexpected statement kind");
 }
 
-template <typename Unit>
-static bool ParseFunction(ModuleValidator<Unit>& m, CodeNode** funNodeOut,
+static bool ParseFunction(ModuleValidator& 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;
@@ -6062,36 +5906,35 @@ static bool ParseFunction(ModuleValidato
 
   MOZ_ASSERT(!anyChars.hadError());
   MOZ_ASSERT(directives == newDirectives);
 
   *funNodeOut = funNode;
   return true;
 }
 
-template <typename Unit>
-static bool CheckFunction(ModuleValidator<Unit>& m) {
+static bool CheckFunction(ModuleValidator& 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.
-  frontend::ParserBase::Mark mark = m.parser().mark();
+  AsmJSParser::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<Unit> f(m, funNode);
+  FunctionValidator f(m, funNode);
 
   ParseNode* stmtIter = ListHead(FunctionStatementList(funNode));
 
   if (!CheckProcessingDirectives(m, &stmtIter)) {
     return false;
   }
 
   ValTypeVector args;
@@ -6110,47 +5953,46 @@ static bool CheckFunction(ModuleValidato
       return false;
     }
   }
 
   if (!CheckFinalReturn(f, lastNonEmptyStmt)) {
     return false;
   }
 
-  ModuleValidatorShared::Func* func = nullptr;
+  ModuleValidator::Func* func = nullptr;
   if (!CheckFunctionSignature(m, funNode,
                               FuncType(std::move(args), f.returnedType()),
                               FunctionName(funNode), &func)) {
     return false;
   }
 
   if (func->defined()) {
     return m.failName(funNode, "function '%s' already defined",
                       FunctionName(funNode));
   }
 
   f.define(func, line);
 
   return true;
 }
 
-static bool CheckAllFunctionsDefined(ModuleValidatorShared& m) {
+static bool CheckAllFunctionsDefined(ModuleValidator& m) {
   for (unsigned i = 0; i < m.numFuncDefs(); i++) {
-    const ModuleValidatorShared::Func& f = m.funcDef(i);
+    const ModuleValidator::Func& f = m.funcDef(i);
     if (!f.defined()) {
       return m.failNameOffset(f.firstUse(), "missing definition of function %s",
                               f.name());
     }
   }
 
   return true;
 }
 
-template <typename Unit>
-static bool CheckFunctions(ModuleValidator<Unit>& m) {
+static bool CheckFunctions(ModuleValidator& m) {
   while (true) {
     TokenKind tk;
     if (!PeekToken(m.parser(), &tk)) {
       return false;
     }
 
     if (tk != TokenKind::Function) {
       break;
@@ -6159,18 +6001,17 @@ static bool CheckFunctions(ModuleValidat
     if (!CheckFunction(m)) {
       return false;
     }
   }
 
   return CheckAllFunctionsDefined(m);
 }
 
-template <typename Unit>
-static bool CheckFuncPtrTable(ModuleValidator<Unit>& m, ParseNode* var) {
+static bool CheckFuncPtrTable(ModuleValidator& 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");
@@ -6190,17 +6031,17 @@ static bool CheckFuncPtrTable(ModuleVali
   const FuncType* sig = nullptr;
   for (ParseNode* elem = ListHead(arrayLiteral); elem; elem = NextNode(elem)) {
     if (!elem->isKind(ParseNodeKind::Name)) {
       return m.fail(
           elem, "function-pointer table's elements must be names of functions");
     }
 
     PropertyName* funcName = elem->as<NameNode>().name();
-    const ModuleValidatorShared::Func* func = m.lookupFuncDef(funcName);
+    const ModuleValidator::Func* func = m.lookupFuncDef(funcName);
     if (!func) {
       return m.fail(
           elem, "function-pointer table's elements must be names of functions");
     }
 
     const FuncType& funcSig = m.env().types[func->sigIndex()].funcType();
     if (sig) {
       if (*sig != funcSig) {
@@ -6228,62 +6069,60 @@ static bool CheckFuncPtrTable(ModuleVali
 
   if (!m.defineFuncPtrTable(tableIndex, std::move(elemFuncDefIndices))) {
     return m.fail(var, "duplicate function-pointer definition");
   }
 
   return true;
 }
 
-template <typename Unit>
-static bool CheckFuncPtrTables(ModuleValidator<Unit>& m) {
+static bool CheckFuncPtrTables(ModuleValidator& m) {
   while (true) {
     ParseNode* varStmt;
     if (!ParseVarOrConstStatement(m.parser(), &varStmt)) {
       return false;
     }
     if (!varStmt) {
       break;
     }
     for (ParseNode* var = VarListHead(varStmt); var; var = NextNode(var)) {
       if (!CheckFuncPtrTable(m, var)) {
         return false;
       }
     }
   }
 
   for (unsigned i = 0; i < m.numFuncPtrTables(); i++) {
-    ModuleValidatorShared::Table& table = m.table(i);
+    ModuleValidator::Table& table = m.table(i);
     if (!table.defined()) {
       return m.failNameOffset(table.firstUse(),
                               "function-pointer table %s wasn't defined",
                               table.name());
     }
   }
 
   return true;
 }
 
-static bool CheckModuleExportFunction(ModuleValidatorShared& m, ParseNode* pn,
+static bool CheckModuleExportFunction(ModuleValidator& m, ParseNode* pn,
                                       PropertyName* maybeFieldName = nullptr) {
   if (!pn->isKind(ParseNodeKind::Name)) {
     return m.fail(pn, "expected name of exported function");
   }
 
   PropertyName* funcName = pn->as<NameNode>().name();
-  const ModuleValidatorShared::Func* func = m.lookupFuncDef(funcName);
+  const ModuleValidator::Func* func = m.lookupFuncDef(funcName);
   if (!func) {
     return m.failName(pn, "function '%s' not found", funcName);
   }
 
   return m.addExportField(*func, maybeFieldName);
 }
 
-static bool CheckModuleExportObject(ModuleValidatorShared& m,
-                                    ParseNode* object) {
+static bool CheckModuleExportObject(ModuleValidator& m, ParseNode* object) {
   MOZ_ASSERT(object->isKind(ParseNodeKind::ObjectExpr));
 
   for (ParseNode* pn = ListHead(object); pn; pn = NextNode(pn)) {
     if (!IsNormalObjectField(pn)) {
       return m.fail(pn,
                     "only normal object properties may be used in the export "
                     "object literal");
     }
@@ -6300,18 +6139,17 @@ static bool CheckModuleExportObject(Modu
     if (!CheckModuleExportFunction(m, initNode, fieldName)) {
       return false;
     }
   }
 
   return true;
 }
 
-template <typename Unit>
-static bool CheckModuleReturn(ModuleValidator<Unit>& m) {
+static bool CheckModuleReturn(ModuleValidator& 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)
@@ -6338,41 +6176,39 @@ static bool CheckModuleReturn(ModuleVali
     if (!CheckModuleExportFunction(m, returnExpr)) {
       return false;
     }
   }
 
   return true;
 }
 
-template <typename Unit>
-static bool CheckModuleEnd(ModuleValidator<Unit>& m) {
+static bool CheckModuleEnd(ModuleValidator& 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;
 }
 
-template <typename Unit>
-static SharedModule CheckModule(JSContext* cx, AsmJSParser<Unit>& parser,
+static SharedModule CheckModule(JSContext* cx, AsmJSParser& parser,
                                 ParseNode* stmtList, UniqueLinkData* linkData,
                                 unsigned* time) {
   int64_t before = PRMJ_Now();
 
   CodeNode* moduleFunctionNode = parser.pc->functionBox()->functionNode;
 
-  ModuleValidator<Unit> m(cx, parser, moduleFunctionNode);
+  ModuleValidator m(cx, parser, moduleFunctionNode);
   if (!m.init()) {
     return nullptr;
   }
 
   if (!CheckFunctionHead(m, moduleFunctionNode)) {
     return nullptr;
   }
 
@@ -7119,35 +6955,35 @@ const uint8_t* Assumptions::deserialize(
 }
 
 class ModuleChars {
  protected:
   uint32_t isFunCtor_;
   Vector<CacheableChars, 0, SystemAllocPolicy> funCtorArgs_;
 
  public:
-  static uint32_t beginOffset(AsmJSParser<char16_t>& parser) {
+  static uint32_t beginOffset(AsmJSParser& parser) {
     return parser.pc->functionBox()->functionNode->pn_pos.begin;
   }
 
-  static uint32_t endOffset(AsmJSParser<char16_t>& parser) {
+  static uint32_t endOffset(AsmJSParser& 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<char16_t>& parser) {
+  bool init(AsmJSParser& 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;
     }
@@ -7240,17 +7076,17 @@ class ModuleCharsForLookup : ModuleChars
     cursor = ReadScalar<uint32_t>(cursor, &isFunCtor_);
     if (isFunCtor_) {
       cursor = DeserializeVector(cursor, &funCtorArgs_);
     }
 
     return cursor;
   }
 
-  bool match(AsmJSParser<char16_t>& parser) const {
+  bool match(AsmJSParser& 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())) {
@@ -7317,19 +7153,20 @@ struct ScopedCacheEntryOpenedForRead {
     if (memory) {
       cx->asmJSCacheOps().closeEntryForRead(serializedSize, memory, handle);
     }
   }
 };
 
 }  // unnamed namespace
 
-static JS::AsmJSCacheResult StoreAsmJSModuleInCache(
-    AsmJSParser<char16_t>& parser, const Module& module,
-    const LinkData& linkData, JSContext* cx) {
+static JS::AsmJSCacheResult StoreAsmJSModuleInCache(AsmJSParser& 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);
 
@@ -7370,18 +7207,17 @@ 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<char16_t>& parser,
+static bool LookupAsmJSModuleInCache(JSContext* cx, AsmJSParser& parser,
                                      bool* loadedFromCache,
                                      SharedModule* module,
                                      UniqueChars* compilationTimeReport) {
   int64_t before = PRMJ_Now();
 
   *loadedFromCache = false;
 
   JS::OpenAsmJSCacheEntryForReadOp open = cx->asmJSCacheOps().openEntryForRead;
@@ -7473,36 +7309,34 @@ static bool LookupAsmJSModuleInCache(JSC
 
 /*****************************************************************************/
 // Top-level js::CompileAsmJS
 
 static bool NoExceptionPending(JSContext* cx) {
   return cx->helperThread() || !cx->isExceptionPending();
 }
 
-static bool SuccessfulValidation(frontend::ParserBase& parser,
-                                 UniqueChars str) {
+static bool SuccessfulValidation(AsmJSParser& parser, UniqueChars str) {
   return parser.warningNoOffset(JSMSG_USE_ASM_TYPE_OK, str.get());
 }
 
-static bool TypeFailureWarning(frontend::ParserBase& parser, const char* str) {
+static bool TypeFailureWarning(AsmJSParser& 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,
-                                   frontend::ParserBase& parser) {
+static bool EstablishPreconditions(JSContext* cx, AsmJSParser& 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");
@@ -7579,18 +7413,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<char16_t>& parser,
-                      ParseNode* stmtList, bool* validated) {
+bool js::CompileAsmJS(JSContext* cx, AsmJSParser& 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,28 +45,26 @@ class ParseContext;
 class ParseNode;
 
 template <class ParseHandler, typename CharT>
 class Parser;
 class FullParseHandler;
 
 }  // namespace frontend
 
-template <typename Unit>
-using AsmJSParser = frontend::Parser<frontend::FullParseHandler, Unit>;
+using AsmJSParser = frontend::Parser<frontend::FullParseHandler, char16_t>;
 
 // 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<char16_t>& parser,
+extern MOZ_MUST_USE bool CompileAsmJS(JSContext* cx, AsmJSParser& parser,
                                       frontend::ParseNode* stmtList,
                                       bool* validated);
 
 // asm.js module/export queries:
 
 extern bool IsAsmJSModuleNative(JSNative native);
 
 extern bool IsAsmJSModule(JSFunction* fun);