/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * vim: set ts=8 sts=2 et sw=2 tw=80:
 *
 * Copyright 2014 Mozilla Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "wasm/AsmJS.h"

#include "mozilla/Attributes.h"
#include "mozilla/Compression.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/Maybe.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/Sprintf.h"  // SprintfLiteral
#include "mozilla/Try.h"      // MOZ_TRY*
#include "mozilla/Utf8.h"     // mozilla::Utf8Unit
#include "mozilla/Variant.h"

#include <algorithm>
#include <new>

#include "jsmath.h"

#include "frontend/BytecodeCompiler.h"    // CompileStandaloneFunction
#include "frontend/FrontendContext.h"     // js::FrontendContext
#include "frontend/FunctionSyntaxKind.h"  // FunctionSyntaxKind
#include "frontend/ParseNode.h"
#include "frontend/Parser-macros.h"  // MOZ_TRY_*
#include "frontend/Parser.h"
#include "frontend/ParserAtom.h"     // ParserAtomsTable, TaggedParserAtomIndex
#include "frontend/SharedContext.h"  // TopLevelFunction
#include "frontend/TaggedParserAtomIndexHasher.h"  // TaggedParserAtomIndexHasher
#include "gc/GC.h"
#include "gc/Policy.h"
#include "jit/InlinableNatives.h"
#include "js/BuildId.h"  // JS::BuildIdCharVector
#include "js/experimental/JitInfo.h"
#include "js/friend/ErrorMessages.h"  // JSMSG_*
#include "js/MemoryMetrics.h"
#include "js/Printf.h"
#include "js/ScalarType.h"  // js::Scalar::Type
#include "js/SourceText.h"
#include "js/StableStringChars.h"
#include "js/Wrapper.h"
#include "util/DifferentialTesting.h"
#include "util/StringBuilder.h"
#include "util/Text.h"
#include "vm/ErrorReporting.h"
#include "vm/FunctionFlags.h"          // js::FunctionFlags
#include "vm/GeneratorAndAsyncKind.h"  // js::GeneratorKind, js::FunctionAsyncKind
#include "vm/Interpreter.h"
#include "vm/SelfHosting.h"
#include "vm/Time.h"
#include "vm/TypedArrayObject.h"
#include "vm/Warnings.h"  // js::WarnNumberASCII
#include "wasm/WasmCompile.h"
#include "wasm/WasmFeatures.h"
#include "wasm/WasmGenerator.h"
#include "wasm/WasmInstance.h"
#include "wasm/WasmIonCompile.h"
#include "wasm/WasmJS.h"
#include "wasm/WasmModuleTypes.h"
#include "wasm/WasmSerialize.h"
#include "wasm/WasmSignalHandlers.h"
#include "wasm/WasmValidate.h"

#include "frontend/SharedContext-inl.h"
#include "vm/ArrayBufferObject-inl.h"
#include "vm/JSObject-inl.h"
#include "wasm/WasmInstance-inl.h"

using namespace js;
using namespace js::frontend;
using namespace js::jit;
using namespace js::wasm;

using JS::AsmJSOption;
using JS::AutoStableStringChars;
using JS::GenericNaN;
using JS::SourceText;
using mozilla::Abs;
using mozilla::AsVariant;
using mozilla::CeilingLog2;
using mozilla::HashGeneric;
using mozilla::IsNegativeZero;
using mozilla::IsPositiveZero;
using mozilla::IsPowerOfTwo;
using mozilla::Maybe;
using mozilla::Nothing;
using mozilla::PodZero;
using mozilla::PositiveInfinity;
using mozilla::Some;
using mozilla::Utf8Unit;
using mozilla::Compression::LZ4;

using FunctionVector = JS::GCVector<JSFunction*>;

/*****************************************************************************/

// A wasm module can either use no memory, a unshared memory (ArrayBuffer) or
// shared memory (SharedArrayBuffer).

enum class MemoryUsage { None = false, Unshared = 1, Shared = 2 };

// The asm.js valid heap lengths are precisely the WASM valid heap lengths for
// ARM greater or equal to MinHeapLength
static const size_t MinHeapLength = PageSize;
// An asm.js heap can in principle be up to INT32_MAX bytes but requirements
// on the format restrict it further to the largest pseudo-ARM-immediate.
// See IsValidAsmJSHeapLength().
static const uint64_t MaxHeapLength = 0x7f000000;

static uint64_t RoundUpToNextValidAsmJSHeapLength(uint64_t length) {
  if (length <= MinHeapLength) {
    return MinHeapLength;
  }

  return wasm::RoundUpToNextValidARMImmediate(length);
}

static uint64_t DivideRoundingUp(uint64_t a, uint64_t b) {
  return (a + (b - 1)) / b;
}

/*****************************************************************************/
// asm.js module object

// The asm.js spec recognizes this set of builtin Math functions.
enum AsmJSMathBuiltinFunction {
  AsmJSMathBuiltin_sin,
  AsmJSMathBuiltin_cos,
  AsmJSMathBuiltin_tan,
  AsmJSMathBuiltin_asin,
  AsmJSMathBuiltin_acos,
  AsmJSMathBuiltin_atan,
  AsmJSMathBuiltin_ceil,
  AsmJSMathBuiltin_floor,
  AsmJSMathBuiltin_exp,
  AsmJSMathBuiltin_log,
  AsmJSMathBuiltin_pow,
  AsmJSMathBuiltin_sqrt,
  AsmJSMathBuiltin_abs,
  AsmJSMathBuiltin_atan2,
  AsmJSMathBuiltin_imul,
  AsmJSMathBuiltin_fround,
  AsmJSMathBuiltin_min,
  AsmJSMathBuiltin_max,
  AsmJSMathBuiltin_clz32
};

// LitValPOD is a restricted version of LitVal suitable for asm.js that is
// always POD.

struct LitValPOD {
  PackedTypeCode valType_;
  union U {
    uint32_t u32_;
    uint64_t u64_;
    float f32_;
    double f64_;
  } u;

  LitValPOD() = default;

  explicit LitValPOD(uint32_t u32) : valType_(ValType(ValType::I32).packed()) {
    u.u32_ = u32;
  }
  explicit LitValPOD(uint64_t u64) : valType_(ValType(ValType::I64).packed()) {
    u.u64_ = u64;
  }

  explicit LitValPOD(float f32) : valType_(ValType(ValType::F32).packed()) {
    u.f32_ = f32;
  }
  explicit LitValPOD(double f64) : valType_(ValType(ValType::F64).packed()) {
    u.f64_ = f64;
  }

  LitVal asLitVal() const {
    switch (valType_.typeCode()) {
      case TypeCode::I32:
        return LitVal(u.u32_);
      case TypeCode::I64:
        return LitVal(u.u64_);
      case TypeCode::F32:
        return LitVal(u.f32_);
      case TypeCode::F64:
        return LitVal(u.f64_);
      default:
        MOZ_CRASH("Can't happen");
    }
  }
};

static_assert(std::is_pod_v<LitValPOD>,
              "must be POD to be simply serialized/deserialized");

// An AsmJSGlobal represents a JS global variable in the asm.js module function.
class AsmJSGlobal {
 public:
  enum Which {
    Variable,
    FFI,
    ArrayView,
    ArrayViewCtor,
    MathBuiltinFunction,
    Constant
  };
  enum VarInitKind { InitConstant, InitImport };
  enum ConstantKind { GlobalConstant, MathConstant };

 private:
  struct CacheablePod {
    Which which_;
    union V {
      struct {
        VarInitKind initKind_;
        union U {
          PackedTypeCode importValType_;
          LitValPOD val_;
        } u;
      } var;
      uint32_t ffiIndex_;
      Scalar::Type viewType_;
      AsmJSMathBuiltinFunction mathBuiltinFunc_;
      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);
  }
  const char* field() const { return field_.get(); }
  Which which() const { return pod.which_; }
  VarInitKind varInitKind() const {
    MOZ_ASSERT(pod.which_ == Variable);
    return pod.u.var.initKind_;
  }
  LitValPOD varInitVal() const {
    MOZ_ASSERT(pod.which_ == Variable);
    MOZ_ASSERT(pod.u.var.initKind_ == InitConstant);
    return pod.u.var.u.val_;
  }
  ValType varInitImportType() const {
    MOZ_ASSERT(pod.which_ == Variable);
    MOZ_ASSERT(pod.u.var.initKind_ == InitImport);
    return ValType(pod.u.var.u.importValType_);
  }
  uint32_t ffiIndex() const {
    MOZ_ASSERT(pod.which_ == FFI);
    return pod.u.ffiIndex_;
  }
  // When a view is created from an imported constructor:
  //   var I32 = stdlib.Int32Array;
  //   var i32 = new I32(buffer);
  // the second import has nothing to validate and thus has a null field.
  Scalar::Type viewType() const {
    MOZ_ASSERT(pod.which_ == ArrayView || pod.which_ == ArrayViewCtor);
    return pod.u.viewType_;
  }
  AsmJSMathBuiltinFunction mathBuiltinFunction() const {
    MOZ_ASSERT(pod.which_ == MathBuiltinFunction);
    return pod.u.mathBuiltinFunc_;
  }
  ConstantKind constantKind() const {
    MOZ_ASSERT(pod.which_ == Constant);
    return pod.u.constant.kind_;
  }
  double constantValue() const {
    MOZ_ASSERT(pod.which_ == Constant);
    return pod.u.constant.value_;
  }
};

using AsmJSGlobalVector = Vector<AsmJSGlobal, 0, SystemAllocPolicy>;

// An AsmJSImport is slightly different than an asm.js FFI function: a single
// asm.js FFI function can be called with many different signatures. When
// compiled to wasm, each unique FFI function paired with signature generates a
// wasm import.
class AsmJSImport {
  uint32_t ffiIndex_;

 public:
  AsmJSImport() = default;
  explicit AsmJSImport(uint32_t ffiIndex) : ffiIndex_(ffiIndex) {}
  uint32_t ffiIndex() const { return ffiIndex_; }
};

using AsmJSImportVector = Vector<AsmJSImport, 0, SystemAllocPolicy>;

// An AsmJSExport logically extends Export with the extra information needed for
// an asm.js exported function, viz., the offsets in module's source chars in
// case the function is toString()ed.
class AsmJSExport {
  uint32_t funcIndex_ = 0;

  // All fields are treated as cacheable POD:
  uint32_t startOffsetInModule_ = 0;  // Store module-start-relative offsets
  uint32_t endOffsetInModule_ = 0;    // so preserved by serialization.

 public:
  AsmJSExport() = default;
  AsmJSExport(uint32_t funcIndex, uint32_t startOffsetInModule,
              uint32_t endOffsetInModule)
      : funcIndex_(funcIndex),
        startOffsetInModule_(startOffsetInModule),
        endOffsetInModule_(endOffsetInModule) {}
  uint32_t funcIndex() const { return funcIndex_; }
  uint32_t startOffsetInModule() const { return startOffsetInModule_; }
  uint32_t endOffsetInModule() const { return endOffsetInModule_; }
};

using AsmJSExportVector = Vector<AsmJSExport, 0, SystemAllocPolicy>;

// Holds the immutable guts of an AsmJSModule.
//
// CodeMetadataForAsmJSImpl is built incrementally by ModuleValidator and then
// shared immutably between AsmJSModules.

struct js::CodeMetadataForAsmJSImpl : CodeMetadataForAsmJS {
  uint32_t numFFIs = 0;
  uint32_t srcLength = 0;
  uint32_t srcLengthWithRightBrace = 0;
  AsmJSGlobalVector asmJSGlobals;
  AsmJSImportVector asmJSImports;
  AsmJSExportVector asmJSExports;
  CacheableCharsVector asmJSFuncNames;
  CacheableChars globalArgumentName;
  CacheableChars importArgumentName;
  CacheableChars bufferArgumentName;

  // These values are not serialized since they are relative to the
  // containing script which can be different between serialization and
  // deserialization contexts. Thus, they must be set explicitly using the
  // ambient Parser/ScriptSource after deserialization.
  //
  // srcStart refers to the offset in the ScriptSource to the beginning of
  // the asm.js module function. If the function has been created with the
  // Function constructor, this will be the first character in the function
  // source. Otherwise, it will be the opening parenthesis of the arguments
  // list.
  uint32_t toStringStart;
  uint32_t srcStart;
  bool strict;
  bool alwaysUseFdlibm = false;
  RefPtr<ScriptSource> source;

  uint32_t srcEndBeforeCurly() const { return srcStart + srcLength; }
  uint32_t srcEndAfterCurly() const {
    return srcStart + srcLengthWithRightBrace;
  }

  CodeMetadataForAsmJSImpl() : toStringStart(0), srcStart(0), strict(false) {}
  ~CodeMetadataForAsmJSImpl() = default;

  const CodeMetadataForAsmJSImpl& asAsmJS() const { return *this; }

  const AsmJSExport& lookupAsmJSExport(uint32_t funcIndex) const {
    // The AsmJSExportVector isn't stored in sorted order so do a linear
    // search. This is for the super-cold and already-expensive toString()
    // path and the number of exports is generally small.
    for (const AsmJSExport& exp : asmJSExports) {
      if (exp.funcIndex() == funcIndex) {
        return exp;
      }
    }
    MOZ_CRASH("missing asm.js func export");
  }

  bool mutedErrors() const { return source->mutedErrors(); }
  const char16_t* displayURL() const {
    return source->hasDisplayURL() ? source->displayURL() : nullptr;
  }
  ScriptSource* maybeScriptSource() const { return source.get(); }
  bool getFuncNameForAsmJS(uint32_t funcIndex, UTF8Bytes* name) const {
    const char* p = asmJSFuncNames[funcIndex].get();
    if (!p) {
      return true;
    }
    return name->append(p, strlen(p));
  }

  size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
    return asmJSGlobals.sizeOfExcludingThis(mallocSizeOf) +
           asmJSImports.sizeOfExcludingThis(mallocSizeOf) +
           asmJSExports.sizeOfExcludingThis(mallocSizeOf) +
           asmJSFuncNames.sizeOfExcludingThis(mallocSizeOf) +
           globalArgumentName.sizeOfExcludingThis(mallocSizeOf) +
           importArgumentName.sizeOfExcludingThis(mallocSizeOf) +
           bufferArgumentName.sizeOfExcludingThis(mallocSizeOf);
  }
};

using MutableCodeMetadataForAsmJSImpl = RefPtr<CodeMetadataForAsmJSImpl>;

/*****************************************************************************/
// ParseNode utilities

static inline ParseNode* NextNode(ParseNode* pn) { return pn->pn_next; }

static inline ParseNode* UnaryKid(ParseNode* pn) {
  return pn->as<UnaryNode>().kid();
}

static inline ParseNode* BinaryRight(ParseNode* pn) {
  return pn->as<BinaryNode>().right();
}

static inline ParseNode* BinaryLeft(ParseNode* pn) {
  return pn->as<BinaryNode>().left();
}

static inline ParseNode* ReturnExpr(ParseNode* pn) {
  MOZ_ASSERT(pn->isKind(ParseNodeKind::ReturnStmt));
  return UnaryKid(pn);
}

static inline ParseNode* TernaryKid1(ParseNode* pn) {
  return pn->as<TernaryNode>().kid1();
}

static inline ParseNode* TernaryKid2(ParseNode* pn) {
  return pn->as<TernaryNode>().kid2();
}

static inline ParseNode* TernaryKid3(ParseNode* pn) {
  return pn->as<TernaryNode>().kid3();
}

static inline ParseNode* ListHead(ParseNode* pn) {
  return pn->as<ListNode>().head();
}

static inline unsigned ListLength(ParseNode* pn) {
  return pn->as<ListNode>().count();
}

static inline ParseNode* CallCallee(ParseNode* pn) {
  MOZ_ASSERT(pn->isKind(ParseNodeKind::CallExpr));
  return BinaryLeft(pn);
}

static inline unsigned CallArgListLength(ParseNode* pn) {
  MOZ_ASSERT(pn->isKind(ParseNodeKind::CallExpr));
  return ListLength(BinaryRight(pn));
}

static inline ParseNode* CallArgList(ParseNode* pn) {
  MOZ_ASSERT(pn->isKind(ParseNodeKind::CallExpr));
  return ListHead(BinaryRight(pn));
}

static inline ParseNode* VarListHead(ParseNode* pn) {
  MOZ_ASSERT(pn->isKind(ParseNodeKind::VarStmt) ||
             pn->isKind(ParseNodeKind::ConstDecl));
  return ListHead(pn);
}

static inline bool IsDefaultCase(ParseNode* pn) {
  return pn->as<CaseClause>().isDefault();
}

static inline ParseNode* CaseExpr(ParseNode* pn) {
  return pn->as<CaseClause>().caseExpression();
}

static inline ParseNode* CaseBody(ParseNode* pn) {
  return pn->as<CaseClause>().statementList();
}

static inline ParseNode* BinaryOpLeft(ParseNode* pn) {
  MOZ_ASSERT(pn->isBinaryOperation());
  MOZ_ASSERT(pn->as<ListNode>().count() == 2);
  return ListHead(pn);
}

static inline ParseNode* BinaryOpRight(ParseNode* pn) {
  MOZ_ASSERT(pn->isBinaryOperation());
  MOZ_ASSERT(pn->as<ListNode>().count() == 2);
  return NextNode(ListHead(pn));
}

static inline ParseNode* BitwiseLeft(ParseNode* pn) { return BinaryOpLeft(pn); }

static inline ParseNode* BitwiseRight(ParseNode* pn) {
  return BinaryOpRight(pn);
}

static inline ParseNode* MultiplyLeft(ParseNode* pn) {
  MOZ_ASSERT(pn->isKind(ParseNodeKind::MulExpr));
  return BinaryOpLeft(pn);
}

static inline ParseNode* MultiplyRight(ParseNode* pn) {
  MOZ_ASSERT(pn->isKind(ParseNodeKind::MulExpr));
  return BinaryOpRight(pn);
}

static inline ParseNode* AddSubLeft(ParseNode* pn) {
  MOZ_ASSERT(pn->isKind(ParseNodeKind::AddExpr) ||
             pn->isKind(ParseNodeKind::SubExpr));
  return BinaryOpLeft(pn);
}

static inline ParseNode* AddSubRight(ParseNode* pn) {
  MOZ_ASSERT(pn->isKind(ParseNodeKind::AddExpr) ||
             pn->isKind(ParseNodeKind::SubExpr));
  return BinaryOpRight(pn);
}

static inline ParseNode* DivOrModLeft(ParseNode* pn) {
  MOZ_ASSERT(pn->isKind(ParseNodeKind::DivExpr) ||
             pn->isKind(ParseNodeKind::ModExpr));
  return BinaryOpLeft(pn);
}

static inline ParseNode* DivOrModRight(ParseNode* pn) {
  MOZ_ASSERT(pn->isKind(ParseNodeKind::DivExpr) ||
             pn->isKind(ParseNodeKind::ModExpr));
  return BinaryOpRight(pn);
}

static inline ParseNode* ComparisonLeft(ParseNode* pn) {
  return BinaryOpLeft(pn);
}

static inline ParseNode* ComparisonRight(ParseNode* pn) {
  return BinaryOpRight(pn);
}

static inline bool IsExpressionStatement(ParseNode* pn) {
  return pn->isKind(ParseNodeKind::ExpressionStmt);
}

static inline ParseNode* ExpressionStatementExpr(ParseNode* pn) {
  MOZ_ASSERT(pn->isKind(ParseNodeKind::ExpressionStmt));
  return UnaryKid(pn);
}

static inline TaggedParserAtomIndex LoopControlMaybeLabel(ParseNode* pn) {
  MOZ_ASSERT(pn->isKind(ParseNodeKind::BreakStmt) ||
             pn->isKind(ParseNodeKind::ContinueStmt));
  return pn->as<LoopControlStatement>().label();
}

static inline TaggedParserAtomIndex LabeledStatementLabel(ParseNode* pn) {
  return pn->as<LabeledStatement>().label();
}

static inline ParseNode* LabeledStatementStatement(ParseNode* pn) {
  return pn->as<LabeledStatement>().statement();
}

static double NumberNodeValue(ParseNode* pn) {
  return pn->as<NumericLiteral>().value();
}

static bool NumberNodeHasFrac(ParseNode* pn) {
  return pn->as<NumericLiteral>().decimalPoint() == HasDecimal;
}

static ParseNode* DotBase(ParseNode* pn) {
  return &pn->as<PropertyAccess>().expression();
}

static TaggedParserAtomIndex DotMember(ParseNode* pn) {
  return pn->as<PropertyAccess>().name();
}

static ParseNode* ElemBase(ParseNode* pn) {
  return &pn->as<PropertyByValue>().expression();
}

static ParseNode* ElemIndex(ParseNode* pn) {
  return &pn->as<PropertyByValue>().key();
}

static inline TaggedParserAtomIndex FunctionName(FunctionNode* funNode) {
  if (auto name = funNode->funbox()->explicitName()) {
    return name;
  }
  return TaggedParserAtomIndex::null();
}

static inline ParseNode* FunctionFormalParametersList(FunctionNode* fn,
                                                      unsigned* numFormals) {
  ParamsBodyNode* argsBody = fn->body();

  // The number of formals is equal to the number of parameters (excluding the
  // trailing lexical scope). There are no destructuring or rest parameters for
  // asm.js functions.
  *numFormals = argsBody->count();

  // If the function has been fully parsed, the trailing function body node is a
  // lexical scope. If we've only parsed the function parameters, the last node
  // is the last parameter.
  if (*numFormals > 0 && argsBody->last()->is<LexicalScopeNode>()) {
    MOZ_ASSERT(argsBody->last()->as<LexicalScopeNode>().scopeBody()->isKind(
        ParseNodeKind::StatementList));
    (*numFormals)--;
  }

  return argsBody->head();
}

static inline ParseNode* FunctionStatementList(FunctionNode* funNode) {
  LexicalScopeNode* last = funNode->body()->body();
  MOZ_ASSERT(last->isEmptyScope());
  ParseNode* body = last->scopeBody();
  MOZ_ASSERT(body->isKind(ParseNodeKind::StatementList));
  return body;
}

static inline bool IsNormalObjectField(ParseNode* pn) {
  return pn->isKind(ParseNodeKind::PropertyDefinition) &&
         pn->as<PropertyDefinition>().accessorType() == AccessorType::None &&
         BinaryLeft(pn)->isKind(ParseNodeKind::ObjectPropertyName);
}

static inline TaggedParserAtomIndex ObjectNormalFieldName(ParseNode* pn) {
  MOZ_ASSERT(IsNormalObjectField(pn));
  MOZ_ASSERT(BinaryLeft(pn)->isKind(ParseNodeKind::ObjectPropertyName));
  return BinaryLeft(pn)->as<NameNode>().atom();
}

static inline ParseNode* ObjectNormalFieldInitializer(ParseNode* pn) {
  MOZ_ASSERT(IsNormalObjectField(pn));
  return BinaryRight(pn);
}

static inline bool IsUseOfName(ParseNode* pn, TaggedParserAtomIndex name) {
  return pn->isName(name);
}

static inline bool IsIgnoredDirectiveName(TaggedParserAtomIndex atom) {
  return atom != TaggedParserAtomIndex::WellKnown::use_strict_();
}

static inline bool IsIgnoredDirective(ParseNode* pn) {
  return pn->isKind(ParseNodeKind::ExpressionStmt) &&
         UnaryKid(pn)->isKind(ParseNodeKind::StringExpr) &&
         IsIgnoredDirectiveName(UnaryKid(pn)->as<NameNode>().atom());
}

static inline bool IsEmptyStatement(ParseNode* pn) {
  return pn->isKind(ParseNodeKind::EmptyStmt);
}

static inline ParseNode* SkipEmptyStatements(ParseNode* pn) {
  while (pn && IsEmptyStatement(pn)) {
    pn = pn->pn_next;
  }
  return pn;
}

static inline ParseNode* NextNonEmptyStatement(ParseNode* pn) {
  return SkipEmptyStatements(pn->pn_next);
}

template <typename Unit>
static bool GetToken(AsmJSParser<Unit>& parser, TokenKind* tkp) {
  auto& ts = parser.tokenStream;
  TokenKind tk;
  while (true) {
    if (!ts.getToken(&tk, TokenStreamShared::SlashIsRegExp)) {
      return false;
    }
    if (tk != TokenKind::Semi) {
      break;
    }
  }
  *tkp = tk;
  return true;
}

template <typename Unit>
static bool PeekToken(AsmJSParser<Unit>& parser, TokenKind* tkp) {
  auto& ts = parser.tokenStream;
  TokenKind tk;
  while (true) {
    if (!ts.peekToken(&tk, TokenStream::SlashIsRegExp)) {
      return false;
    }
    if (tk != TokenKind::Semi) {
      break;
    }
    ts.consumeKnownToken(TokenKind::Semi, TokenStreamShared::SlashIsRegExp);
  }
  *tkp = tk;
  return true;
}

template <typename Unit>
static bool ParseVarOrConstStatement(AsmJSParser<Unit>& parser,
                                     ParseNode** var) {
  TokenKind tk;
  if (!PeekToken(parser, &tk)) {
    return false;
  }
  if (tk != TokenKind::Var && tk != TokenKind::Const) {
    *var = nullptr;
    return true;
  }

  MOZ_TRY_VAR_OR_RETURN(*var, parser.statementListItem(YieldIsName), false);

  MOZ_ASSERT((*var)->isKind(ParseNodeKind::VarStmt) ||
             (*var)->isKind(ParseNodeKind::ConstDecl));
  return true;
}

/*****************************************************************************/

// Represents the type and value of an asm.js numeric literal.
//
// A literal is a double iff the literal contains a decimal point (even if the
// fractional part is 0). Otherwise, integers may be classified:
//  fixnum: [0, 2^31)
//  negative int: [-2^31, 0)
//  big unsigned: [2^31, 2^32)
//  out of range: otherwise
// Lastly, a literal may be a float literal which is any double or integer
// literal coerced with Math.fround.
class NumLit {
 public:
  enum Which {
    Fixnum,
    NegativeInt,
    BigUnsigned,
    Double,
    Float,
    OutOfRangeInt = -1
  };

 private:
  Which which_;
  JS::Value value_;

 public:
  NumLit() = default;

  NumLit(Which w, const Value& v) : which_(w), value_(v) {}

  Which which() const { return which_; }

  int32_t toInt32() const {
    MOZ_ASSERT(which_ == Fixnum || which_ == NegativeInt ||
               which_ == BigUnsigned);
    return value_.toInt32();
  }

  uint32_t toUint32() const { return (uint32_t)toInt32(); }

  double toDouble() const {
    MOZ_ASSERT(which_ == Double);
    return value_.toDouble();
  }

  float toFloat() const {
    MOZ_ASSERT(which_ == Float);
    return float(value_.toDouble());
  }

  Value scalarValue() const {
    MOZ_ASSERT(which_ != OutOfRangeInt);
    return value_;
  }

  bool valid() const { return which_ != OutOfRangeInt; }

  bool isZeroBits() const {
    MOZ_ASSERT(valid());
    switch (which()) {
      case NumLit::Fixnum:
      case NumLit::NegativeInt:
      case NumLit::BigUnsigned:
        return toInt32() == 0;
      case NumLit::Double:
        return IsPositiveZero(toDouble());
      case NumLit::Float:
        return IsPositiveZero(toFloat());
      case NumLit::OutOfRangeInt:
        MOZ_CRASH("can't be here because of valid() check above");
    }
    return false;
  }

  LitValPOD value() const {
    switch (which_) {
      case NumLit::Fixnum:
      case NumLit::NegativeInt:
      case NumLit::BigUnsigned:
        return LitValPOD(toUint32());
      case NumLit::Float:
        return LitValPOD(toFloat());
      case NumLit::Double:
        return LitValPOD(toDouble());
      case NumLit::OutOfRangeInt:;
    }
    MOZ_CRASH("bad literal");
  }
};

// Represents the type of a general asm.js expression.
//
// A canonical subset of types representing the coercion targets: Int, Float,
// Double.
//
// Void is also part of the canonical subset.

class Type {
 public:
  enum Which {
    Fixnum = NumLit::Fixnum,
    Signed = NumLit::NegativeInt,
    Unsigned = NumLit::BigUnsigned,
    DoubleLit = NumLit::Double,
    Float = NumLit::Float,
    Double,
    MaybeDouble,
    MaybeFloat,
    Floatish,
    Int,
    Intish,
    Void
  };

 private:
  Which which_;

 public:
  Type() = default;
  MOZ_IMPLICIT Type(Which w) : which_(w) {}

  // Map an already canonicalized Type to the return type of a function call.
  static Type ret(Type t) {
    MOZ_ASSERT(t.isCanonical());
    // The 32-bit external type is Signed, not Int.
    return t.isInt() ? Signed : t;
  }

  static Type lit(const NumLit& lit) {
    MOZ_ASSERT(lit.valid());
    Which which = Type::Which(lit.which());
    MOZ_ASSERT(which >= Fixnum && which <= Float);
    Type t;
    t.which_ = which;
    return t;
  }

  // Map |t| to one of the canonical vartype representations of a
  // wasm::ValType.
  static Type canonicalize(Type t) {
    switch (t.which()) {
      case Fixnum:
      case Signed:
      case Unsigned:
      case Int:
        return Int;

      case Float:
        return Float;

      case DoubleLit:
      case Double:
        return Double;

      case Void:
        return Void;

      case MaybeDouble:
      case MaybeFloat:
      case Floatish:
      case Intish:
        // These types need some kind of coercion, they can't be mapped
        // to an VarType.
        break;
    }
    MOZ_CRASH("Invalid vartype");
  }

  Which which() const { return which_; }

  bool operator==(Type rhs) const { return which_ == rhs.which_; }
  bool operator!=(Type rhs) const { return which_ != rhs.which_; }

  bool operator<=(Type rhs) const {
    switch (rhs.which_) {
      case Signed:
        return isSigned();
      case Unsigned:
        return isUnsigned();
      case DoubleLit:
        return isDoubleLit();
      case Double:
        return isDouble();
      case Float:
        return isFloat();
      case MaybeDouble:
        return isMaybeDouble();
      case MaybeFloat:
        return isMaybeFloat();
      case Floatish:
        return isFloatish();
      case Int:
        return isInt();
      case Intish:
        return isIntish();
      case Fixnum:
        return isFixnum();
      case Void:
        return isVoid();
    }
    MOZ_CRASH("unexpected rhs type");
  }

  bool isFixnum() const { return which_ == Fixnum; }

  bool isSigned() const { return which_ == Signed || which_ == Fixnum; }

  bool isUnsigned() const { return which_ == Unsigned || which_ == Fixnum; }

  bool isInt() const { return isSigned() || isUnsigned() || which_ == Int; }

  bool isIntish() const { return isInt() || which_ == Intish; }

  bool isDoubleLit() const { return which_ == DoubleLit; }

  bool isDouble() const { return isDoubleLit() || which_ == Double; }

  bool isMaybeDouble() const { return isDouble() || which_ == MaybeDouble; }

  bool isFloat() const { return which_ == Float; }

  bool isMaybeFloat() const { return isFloat() || which_ == MaybeFloat; }

  bool isFloatish() const { return isMaybeFloat() || which_ == Floatish; }

  bool isVoid() const { return which_ == Void; }

  bool isExtern() const { return isDouble() || isSigned(); }

  // Check if this is one of the valid types for a function argument.
  bool isArgType() const { return isInt() || isFloat() || isDouble(); }

  // Check if this is one of the valid types for a function return value.
  bool isReturnType() const {
    return isSigned() || isFloat() || isDouble() || isVoid();
  }

  // Check if this is one of the valid types for a global variable.
  bool isGlobalVarType() const { return isArgType(); }

  // Check if this is one of the canonical vartype representations of a
  // wasm::ValType, or is void. See Type::canonicalize().
  bool isCanonical() const {
    switch (which()) {
      case Int:
      case Float:
      case Double:
      case Void:
        return true;
      default:
        return false;
    }
  }

  // Check if this is a canonical representation of a wasm::ValType.
  bool isCanonicalValType() const { return !isVoid() && isCanonical(); }

  // Convert this canonical type to a wasm::ValType.
  ValType canonicalToValType() const {
    switch (which()) {
      case Int:
        return ValType::I32;
      case Float:
        return ValType::F32;
      case Double:
        return ValType::F64;
      default:
        MOZ_CRASH("Need canonical type");
    }
  }

  Maybe<ValType> canonicalToReturnType() const {
    return isVoid() ? Nothing() : Some(canonicalToValType());
  }

  // Convert this type to a wasm::TypeCode for use in a wasm
  // block signature. This works for all types, including non-canonical
  // ones. Consequently, the type isn't valid for subsequent asm.js
  // validation; it's only valid for use in producing wasm.
  TypeCode toWasmBlockSignatureType() const {
    switch (which()) {
      case Fixnum:
      case Signed:
      case Unsigned:
      case Int:
      case Intish:
        return TypeCode::I32;

      case Float:
      case MaybeFloat:
      case Floatish:
        return TypeCode::F32;

      case DoubleLit:
      case Double:
      case MaybeDouble:
        return TypeCode::F64;

      case Void:
        return TypeCode::BlockVoid;
    }
    MOZ_CRASH("Invalid Type");
  }

  const char* toChars() const {
    switch (which_) {
      case Double:
        return "double";
      case DoubleLit:
        return "doublelit";
      case MaybeDouble:
        return "double?";
      case Float:
        return "float";
      case Floatish:
        return "floatish";
      case MaybeFloat:
        return "float?";
      case Fixnum:
        return "fixnum";
      case Int:
        return "int";
      case Signed:
        return "signed";
      case Unsigned:
        return "unsigned";
      case Intish:
        return "intish";
      case Void:
        return "void";
    }
    MOZ_CRASH("Invalid Type");
  }
};

static const unsigned VALIDATION_LIFO_DEFAULT_CHUNK_SIZE = 4 * 1024;

class MOZ_STACK_CLASS ModuleValidatorShared {
 public:
  struct Memory {
    MemoryUsage usage;
    uint64_t minLength;

    uint64_t minPages() const { return DivideRoundingUp(minLength, PageSize); }

    Memory() = default;
  };

  class Func {
    TaggedParserAtomIndex name_;
    uint32_t sigIndex_;
    uint32_t firstUse_;
    uint32_t funcDefIndex_;

    bool defined_;

    // Available when defined:
    uint32_t srcBegin_;
    uint32_t srcEnd_;
    uint32_t line_;
    Bytes bytes_;
    Uint32Vector callSiteLineNums_;

   public:
    Func(TaggedParserAtomIndex name, uint32_t sigIndex, uint32_t firstUse,
         uint32_t funcDefIndex)
        : name_(name),
          sigIndex_(sigIndex),
          firstUse_(firstUse),
          funcDefIndex_(funcDefIndex),
          defined_(false),
          srcBegin_(0),
          srcEnd_(0),
          line_(0) {}

    TaggedParserAtomIndex name() const { return name_; }
    uint32_t sigIndex() const { return sigIndex_; }
    uint32_t firstUse() const { return firstUse_; }
    bool defined() const { return defined_; }
    uint32_t funcDefIndex() const { return funcDefIndex_; }

    void define(ParseNode* fn, uint32_t line, Bytes&& bytes,
                Uint32Vector&& callSiteLineNums) {
      MOZ_ASSERT(!defined_);
      defined_ = true;
      srcBegin_ = fn->pn_pos.begin;
      srcEnd_ = fn->pn_pos.end;
      line_ = line;
      bytes_ = std::move(bytes);
      callSiteLineNums_ = std::move(callSiteLineNums);
    }

    uint32_t srcBegin() const {
      MOZ_ASSERT(defined_);
      return srcBegin_;
    }
    uint32_t srcEnd() const {
      MOZ_ASSERT(defined_);
      return srcEnd_;
    }
    uint32_t line() const {
      MOZ_ASSERT(defined_);
      return line_;
    }
    const Bytes& bytes() const {
      MOZ_ASSERT(defined_);
      return bytes_;
    }
    Uint32Vector& callSiteLineNums() {
      MOZ_ASSERT(defined_);
      return callSiteLineNums_;
    }
  };

  using ConstFuncVector = Vector<const Func*>;
  using FuncVector = Vector<Func>;

  class Table {
    uint32_t sigIndex_;
    TaggedParserAtomIndex name_;
    uint32_t firstUse_;
    uint32_t mask_;
    bool defined_;

   public:
    Table(uint32_t sigIndex, TaggedParserAtomIndex name, uint32_t firstUse,
          uint32_t mask)
        : sigIndex_(sigIndex),
          name_(name),
          firstUse_(firstUse),
          mask_(mask),
          defined_(false) {}

    Table(Table&& rhs) = delete;

    uint32_t sigIndex() const { return sigIndex_; }
    TaggedParserAtomIndex name() const { return name_; }
    uint32_t firstUse() const { return firstUse_; }
    unsigned mask() const { return mask_; }
    bool defined() const { return defined_; }
    void define() {
      MOZ_ASSERT(!defined_);
      defined_ = true;
    }
  };

  using TableVector = Vector<Table*>;

  class Global {
   public:
    enum Which {
      Variable,
      ConstantLiteral,
      ConstantImport,
      Function,
      Table,
      FFI,
      ArrayView,
      ArrayViewCtor,
      MathBuiltinFunction
    };

   private:
    Which which_;
    union U {
      struct VarOrConst {
        Type::Which type_;
        unsigned index_;
        NumLit literalValue_;

        VarOrConst(unsigned index, const NumLit& lit)
            : type_(Type::lit(lit).which()),
              index_(index),
              literalValue_(lit)  // copies |lit|
        {}

        VarOrConst(unsigned index, Type::Which which)
            : type_(which), index_(index) {
          // The |literalValue_| field remains unused and
          // uninitialized for non-constant variables.
        }

        explicit VarOrConst(double constant)
            : type_(Type::Double),
              literalValue_(NumLit::Double, DoubleValue(constant)) {
          // The index_ field is unused and uninitialized for
          // constant doubles.
        }
      } varOrConst;
      uint32_t funcDefIndex_;
      uint32_t tableIndex_;
      uint32_t ffiIndex_;
      Scalar::Type viewType_;
      AsmJSMathBuiltinFunction mathBuiltinFunc_;

      // |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 {
      MOZ_ASSERT(which_ == Variable || which_ == ConstantLiteral ||
                 which_ == ConstantImport);
      return u.varOrConst.type_;
    }
    unsigned varOrConstIndex() const {
      MOZ_ASSERT(which_ == Variable || which_ == ConstantImport);
      return u.varOrConst.index_;
    }
    bool isConst() const {
      return which_ == ConstantLiteral || which_ == ConstantImport;
    }
    NumLit constLiteralValue() const {
      MOZ_ASSERT(which_ == ConstantLiteral);
      return u.varOrConst.literalValue_;
    }
    uint32_t funcDefIndex() const {
      MOZ_ASSERT(which_ == Function);
      return u.funcDefIndex_;
    }
    uint32_t tableIndex() const {
      MOZ_ASSERT(which_ == Table);
      return u.tableIndex_;
    }
    unsigned ffiIndex() const {
      MOZ_ASSERT(which_ == FFI);
      return u.ffiIndex_;
    }
    Scalar::Type viewType() const {
      MOZ_ASSERT(which_ == ArrayView || which_ == ArrayViewCtor);
      return u.viewType_;
    }
    bool isMathFunction() const { return which_ == MathBuiltinFunction; }
    AsmJSMathBuiltinFunction mathBuiltinFunction() const {
      MOZ_ASSERT(which_ == MathBuiltinFunction);
      return u.mathBuiltinFunc_;
    }
  };

  struct MathBuiltin {
    enum Kind { Function, Constant };
    Kind kind;

    union {
      double cst;
      AsmJSMathBuiltinFunction func;
    } u;

    MathBuiltin() : kind(Kind(-1)), u{} {}
    explicit MathBuiltin(double cst) : kind(Constant) { u.cst = cst; }
    explicit MathBuiltin(AsmJSMathBuiltinFunction func) : kind(Function) {
      u.func = func;
    }
  };

  struct ArrayView {
    ArrayView(TaggedParserAtomIndex name, Scalar::Type type)
        : name(name), type(type) {}

    TaggedParserAtomIndex name;
    Scalar::Type type;
  };

 protected:
  class HashableSig {
    uint32_t sigIndex_;
    const TypeContext& types_;

   public:
    HashableSig(uint32_t sigIndex, const TypeContext& types)
        : sigIndex_(sigIndex), types_(types) {}
    uint32_t sigIndex() const { return sigIndex_; }
    const FuncType& funcType() const { return types_[sigIndex_].funcType(); }

    // Implement HashPolicy:
    using Lookup = const FuncType&;
    static HashNumber hash(Lookup l) { return l.hash(nullptr); }
    static bool match(HashableSig lhs, Lookup rhs) {
      return FuncType::strictlyEquals(lhs.funcType(), rhs);
    }
  };

  class NamedSig : public HashableSig {
    TaggedParserAtomIndex name_;

   public:
    NamedSig(TaggedParserAtomIndex name, uint32_t sigIndex,
             const TypeContext& types)
        : HashableSig(sigIndex, types), name_(name) {}
    TaggedParserAtomIndex name() const { return name_; }

    // Implement HashPolicy:
    struct Lookup {
      TaggedParserAtomIndex name;
      const FuncType& funcType;
      Lookup(TaggedParserAtomIndex name, const FuncType& funcType)
          : name(name), funcType(funcType) {}
    };
    static HashNumber hash(Lookup l) {
      return HashGeneric(TaggedParserAtomIndexHasher::hash(l.name),
                         l.funcType.hash(nullptr));
    }
    static bool match(NamedSig lhs, Lookup rhs) {
      return lhs.name() == rhs.name &&
             FuncType::strictlyEquals(lhs.funcType(), rhs.funcType);
    }
  };

  using SigSet = HashSet<HashableSig, HashableSig>;
  using FuncImportMap = HashMap<NamedSig, uint32_t, NamedSig>;
  using GlobalMap =
      HashMap<TaggedParserAtomIndex, Global*, TaggedParserAtomIndexHasher>;
  using MathNameMap =
      HashMap<TaggedParserAtomIndex, MathBuiltin, TaggedParserAtomIndexHasher>;
  using ArrayViewVector = Vector<ArrayView>;

 protected:
  FrontendContext* fc_;
  ParserAtomsTable& parserAtoms_;
  FunctionNode* moduleFunctionNode_;
  TaggedParserAtomIndex moduleFunctionName_;
  TaggedParserAtomIndex globalArgumentName_;
  TaggedParserAtomIndex importArgumentName_;
  TaggedParserAtomIndex bufferArgumentName_;
  MathNameMap standardLibraryMathNames_;

  // Validation-internal state:
  LifoAlloc validationLifo_;
  Memory memory_;
  FuncVector funcDefs_;
  TableVector tables_;
  GlobalMap globalMap_;
  SigSet sigSet_;
  FuncImportMap funcImportMap_;
  ArrayViewVector arrayViews_;

  // State used to build the AsmJSModule in finish():
  CompilerEnvironment compilerEnv_;
  MutableModuleMetadata moduleMeta_;
  MutableCodeMetadata codeMeta_;
  MutableCodeMetadataForAsmJSImpl codeMetaForAsmJS_;

  // Error reporting:
  UniqueChars errorString_ = nullptr;
  uint32_t errorOffset_ = UINT32_MAX;
  bool errorOverRecursed_ = false;

 protected:
  ModuleValidatorShared(FrontendContext* fc, ParserAtomsTable& parserAtoms,
                        MutableModuleMetadata moduleMeta,
                        MutableCodeMetadata codeMeta,
                        FunctionNode* moduleFunctionNode)
      : fc_(fc),
        parserAtoms_(parserAtoms),
        moduleFunctionNode_(moduleFunctionNode),
        moduleFunctionName_(FunctionName(moduleFunctionNode)),
        standardLibraryMathNames_(fc),
        validationLifo_(VALIDATION_LIFO_DEFAULT_CHUNK_SIZE, js::MallocArena),
        funcDefs_(fc),
        tables_(fc),
        globalMap_(fc),
        sigSet_(fc),
        funcImportMap_(fc),
        arrayViews_(fc),
        compilerEnv_(CompileMode::Once, Tier::Optimized, DebugEnabled::False),
        moduleMeta_(moduleMeta),
        codeMeta_(codeMeta) {
    compilerEnv_.computeParameters();
    memory_.minLength = RoundUpToNextValidAsmJSHeapLength(0);
  }

 protected:
  [[nodiscard]] 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) {
      auto atom = parserAtoms_.internAscii(fc_, name, strlen(name));
      if (!atom) {
        return false;
      }
      MathBuiltin builtin(func);
      return this->standardLibraryMathNames_.putNew(atom, builtin);
    };

    for (const auto& info : functions) {
      if (!AddMathFunction(info.name, info.func)) {
        return false;
      }
    }

    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) {
      auto atom = parserAtoms_.internAscii(fc_, name, strlen(name));
      if (!atom) {
        return false;
      }
      MathBuiltin builtin(cst);
      return this->standardLibraryMathNames_.putNew(atom, builtin);
    };

    for (const auto& info : constants) {
      if (!AddMathConstant(info.name, info.value)) {
        return false;
      }
    }

    return true;
  }

 public:
  FrontendContext* fc() const { return fc_; }
  TaggedParserAtomIndex moduleFunctionName() const {
    return moduleFunctionName_;
  }
  TaggedParserAtomIndex globalArgumentName() const {
    return globalArgumentName_;
  }
  TaggedParserAtomIndex importArgumentName() const {
    return importArgumentName_;
  }
  TaggedParserAtomIndex bufferArgumentName() const {
    return bufferArgumentName_;
  }
  const CodeMetadata* codeMeta() { return codeMeta_; }
  const ModuleMetadata* moduleMeta() { return moduleMeta_; }

  void initModuleFunctionName(TaggedParserAtomIndex name) {
    MOZ_ASSERT(!moduleFunctionName_);
    moduleFunctionName_ = name;
  }
  [[nodiscard]] bool initGlobalArgumentName(TaggedParserAtomIndex n) {
    globalArgumentName_ = n;
    if (n) {
      codeMetaForAsmJS_->globalArgumentName =
          parserAtoms_.toNewUTF8CharsZ(fc_, n);
      if (!codeMetaForAsmJS_->globalArgumentName) {
        return false;
      }
    }
    return true;
  }
  [[nodiscard]] bool initImportArgumentName(TaggedParserAtomIndex n) {
    importArgumentName_ = n;
    if (n) {
      codeMetaForAsmJS_->importArgumentName =
          parserAtoms_.toNewUTF8CharsZ(fc_, n);
      if (!codeMetaForAsmJS_->importArgumentName) {
        return false;
      }
    }
    return true;
  }
  [[nodiscard]] bool initBufferArgumentName(TaggedParserAtomIndex n) {
    bufferArgumentName_ = n;
    if (n) {
      codeMetaForAsmJS_->bufferArgumentName =
          parserAtoms_.toNewUTF8CharsZ(fc_, n);
      if (!codeMetaForAsmJS_->bufferArgumentName) {
        return false;
      }
    }
    return true;
  }
  bool addGlobalVarInit(TaggedParserAtomIndex var, const NumLit& lit, Type type,
                        bool isConst) {
    MOZ_ASSERT(type.isGlobalVarType());
    MOZ_ASSERT(type == Type::canonicalize(Type::lit(lit)));

    uint32_t index = codeMeta_->globals.length();
    if (!codeMeta_->globals.emplaceBack(type.canonicalToValType(), !isConst,
                                        index, ModuleKind::AsmJS)) {
      return false;
    }

    Global::Which which = isConst ? Global::ConstantLiteral : Global::Variable;
    Global* global = validationLifo_.new_<Global>(which);
    if (!global) {
      return false;
    }
    if (isConst) {
      new (&global->u.varOrConst) Global::U::VarOrConst(index, lit);
    } else {
      new (&global->u.varOrConst) Global::U::VarOrConst(index, type.which());
    }
    if (!globalMap_.putNew(var, global)) {
      return false;
    }

    AsmJSGlobal g(AsmJSGlobal::Variable, nullptr);
    g.pod.u.var.initKind_ = AsmJSGlobal::InitConstant;
    g.pod.u.var.u.val_ = lit.value();
    return codeMetaForAsmJS_->asmJSGlobals.append(std::move(g));
  }
  bool addGlobalVarImport(TaggedParserAtomIndex var,
                          TaggedParserAtomIndex field, Type type,
                          bool isConst) {
    MOZ_ASSERT(type.isGlobalVarType());

    UniqueChars fieldChars = parserAtoms_.toNewUTF8CharsZ(fc_, field);
    if (!fieldChars) {
      return false;
    }

    uint32_t index = codeMeta_->globals.length();
    ValType valType = type.canonicalToValType();
    if (!codeMeta_->globals.emplaceBack(valType, !isConst, index,
                                        ModuleKind::AsmJS)) {
      return false;
    }

    Global::Which which = isConst ? Global::ConstantImport : Global::Variable;
    Global* global = validationLifo_.new_<Global>(which);
    if (!global) {
      return false;
    }
    new (&global->u.varOrConst) Global::U::VarOrConst(index, type.which());
    if (!globalMap_.putNew(var, global)) {
      return false;
    }

    AsmJSGlobal g(AsmJSGlobal::Variable, std::move(fieldChars));
    g.pod.u.var.initKind_ = AsmJSGlobal::InitImport;
    g.pod.u.var.u.importValType_ = valType.packed();
    return codeMetaForAsmJS_->asmJSGlobals.append(std::move(g));
  }
  bool addArrayView(TaggedParserAtomIndex var, Scalar::Type vt,
                    TaggedParserAtomIndex maybeField) {
    UniqueChars fieldChars;
    if (maybeField) {
      fieldChars = parserAtoms_.toNewUTF8CharsZ(fc_, maybeField);
      if (!fieldChars) {
        return false;
      }
    }

    if (!arrayViews_.append(ArrayView(var, vt))) {
      return false;
    }

    Global* global = validationLifo_.new_<Global>(Global::ArrayView);
    if (!global) {
      return false;
    }
    new (&global->u.viewType_) Scalar::Type(vt);
    if (!globalMap_.putNew(var, global)) {
      return false;
    }

    AsmJSGlobal g(AsmJSGlobal::ArrayView, std::move(fieldChars));
    g.pod.u.viewType_ = vt;
    return codeMetaForAsmJS_->asmJSGlobals.append(std::move(g));
  }
  bool addMathBuiltinFunction(TaggedParserAtomIndex var,
                              AsmJSMathBuiltinFunction func,
                              TaggedParserAtomIndex field) {
    UniqueChars fieldChars = parserAtoms_.toNewUTF8CharsZ(fc_, field);
    if (!fieldChars) {
      return false;
    }

    Global* global = validationLifo_.new_<Global>(Global::MathBuiltinFunction);
    if (!global) {
      return false;
    }
    new (&global->u.mathBuiltinFunc_) AsmJSMathBuiltinFunction(func);
    if (!globalMap_.putNew(var, global)) {
      return false;
    }

    AsmJSGlobal g(AsmJSGlobal::MathBuiltinFunction, std::move(fieldChars));
    g.pod.u.mathBuiltinFunc_ = func;
    return codeMetaForAsmJS_->asmJSGlobals.append(std::move(g));
  }

 private:
  bool addGlobalDoubleConstant(TaggedParserAtomIndex var, double constant) {
    Global* global = validationLifo_.new_<Global>(Global::ConstantLiteral);
    if (!global) {
      return false;
    }
    new (&global->u.varOrConst) Global::U::VarOrConst(constant);
    return globalMap_.putNew(var, global);
  }

 public:
  bool addMathBuiltinConstant(TaggedParserAtomIndex var, double constant,
                              TaggedParserAtomIndex field) {
    UniqueChars fieldChars = parserAtoms_.toNewUTF8CharsZ(fc_, field);
    if (!fieldChars) {
      return false;
    }

    if (!addGlobalDoubleConstant(var, constant)) {
      return false;
    }

    AsmJSGlobal g(AsmJSGlobal::Constant, std::move(fieldChars));
    g.pod.u.constant.value_ = constant;
    g.pod.u.constant.kind_ = AsmJSGlobal::MathConstant;
    return codeMetaForAsmJS_->asmJSGlobals.append(std::move(g));
  }
  bool addGlobalConstant(TaggedParserAtomIndex var, double constant,
                         TaggedParserAtomIndex field) {
    UniqueChars fieldChars = parserAtoms_.toNewUTF8CharsZ(fc_, field);
    if (!fieldChars) {
      return false;
    }

    if (!addGlobalDoubleConstant(var, constant)) {
      return false;
    }

    AsmJSGlobal g(AsmJSGlobal::Constant, std::move(fieldChars));
    g.pod.u.constant.value_ = constant;
    g.pod.u.constant.kind_ = AsmJSGlobal::GlobalConstant;
    return codeMetaForAsmJS_->asmJSGlobals.append(std::move(g));
  }
  bool addArrayViewCtor(TaggedParserAtomIndex var, Scalar::Type vt,
                        TaggedParserAtomIndex field) {
    UniqueChars fieldChars = parserAtoms_.toNewUTF8CharsZ(fc_, field);
    if (!fieldChars) {
      return false;
    }

    Global* global = validationLifo_.new_<Global>(Global::ArrayViewCtor);
    if (!global) {
      return false;
    }
    new (&global->u.viewType_) Scalar::Type(vt);
    if (!globalMap_.putNew(var, global)) {
      return false;
    }

    AsmJSGlobal g(AsmJSGlobal::ArrayViewCtor, std::move(fieldChars));
    g.pod.u.viewType_ = vt;
    return codeMetaForAsmJS_->asmJSGlobals.append(std::move(g));
  }
  bool addFFI(TaggedParserAtomIndex var, TaggedParserAtomIndex field) {
    UniqueChars fieldChars = parserAtoms_.toNewUTF8CharsZ(fc_, field);
    if (!fieldChars) {
      return false;
    }

    if (codeMetaForAsmJS_->numFFIs == UINT32_MAX) {
      return false;
    }
    uint32_t ffiIndex = codeMetaForAsmJS_->numFFIs++;

    Global* global = validationLifo_.new_<Global>(Global::FFI);
    if (!global) {
      return false;
    }
    new (&global->u.ffiIndex_) uint32_t(ffiIndex);
    if (!globalMap_.putNew(var, global)) {
      return false;
    }

    AsmJSGlobal g(AsmJSGlobal::FFI, std::move(fieldChars));
    g.pod.u.ffiIndex_ = ffiIndex;
    return codeMetaForAsmJS_->asmJSGlobals.append(std::move(g));
  }
  bool addExportField(const Func& func, TaggedParserAtomIndex maybeField) {
    // Record the field name of this export.
    CacheableName fieldName;
    if (maybeField) {
      UniqueChars fieldChars = parserAtoms_.toNewUTF8CharsZ(fc_, maybeField);
      if (!fieldChars) {
        return false;
      }
      fieldName = CacheableName::fromUTF8Chars(std::move(fieldChars));
    }

    // Declare which function is exported which gives us an index into the
    // module ExportVector.
    uint32_t funcIndex = funcImportMap_.count() + func.funcDefIndex();
    if (!moduleMeta_->exports.emplaceBack(std::move(fieldName), funcIndex,
                                          DefinitionKind::Function)) {
      return false;
    }

    // The exported function might have already been exported in which case
    // the index will refer into the range of AsmJSExports.
    return codeMetaForAsmJS_->asmJSExports.emplaceBack(
        funcIndex, func.srcBegin() - codeMetaForAsmJS_->srcStart,
        func.srcEnd() - codeMetaForAsmJS_->srcStart);
  }

  bool defineFuncPtrTable(uint32_t tableIndex, Uint32Vector&& elems) {
    Table& table = *tables_[tableIndex];
    if (table.defined()) {
      return false;
    }

    table.define();

    for (uint32_t& index : elems) {
      index += funcImportMap_.count();
    }

    ModuleElemSegment seg = ModuleElemSegment();
    seg.elemType = RefType::func();
    seg.tableIndex = tableIndex;
    seg.offsetIfActive = Some(InitExpr(LitVal(uint32_t(0))));
    seg.encoding = ModuleElemSegment::Encoding::Indices;
    seg.elemIndices = std::move(elems);
    bool ok = codeMeta_->elemSegmentTypes.append(seg.elemType) &&
              moduleMeta_->elemSegments.append(std::move(seg));
    MOZ_ASSERT_IF(ok, codeMeta_->elemSegmentTypes.length() ==
                          moduleMeta_->elemSegments.length());
    return ok;
  }

  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);
    if (len > memory_.minLength) {
      memory_.minLength = len;
    }
    return true;
  }

  // Error handling.
  bool hasAlreadyFailed() const { return !!errorString_; }

  bool failOffset(uint32_t offset, const char* str) {
    MOZ_ASSERT(!hasAlreadyFailed());
    MOZ_ASSERT(errorOffset_ == UINT32_MAX);
    MOZ_ASSERT(str);
    errorOffset_ = offset;
    errorString_ = DuplicateString(str);
    return false;
  }

  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);
    MOZ_ASSERT(fmt);
    errorOffset_ = offset;
    errorString_ = JS_vsmprintf(fmt, ap);
    return false;
  }

  bool failfOffset(uint32_t offset, const char* fmt, ...)
      MOZ_FORMAT_PRINTF(3, 4) {
    va_list ap;
    va_start(ap, fmt);
    failfVAOffset(offset, fmt, ap);
    va_end(ap);
    return false;
  }

  bool failf(ParseNode* pn, const char* fmt, ...) MOZ_FORMAT_PRINTF(3, 4) {
    va_list ap;
    va_start(ap, fmt);
    failfVAOffset(pn->pn_pos.begin, fmt, ap);
    va_end(ap);
    return false;
  }

  bool failNameOffset(uint32_t offset, const char* fmt,
                      TaggedParserAtomIndex name) {
    // This function is invoked without the caller properly rooting its locals.
    if (UniqueChars bytes = parserAtoms_.toPrintableString(name)) {
      failfOffset(offset, fmt, bytes.get());
    } else {
      ReportOutOfMemory(fc_);
    }
    return false;
  }

  bool failName(ParseNode* pn, const char* fmt, TaggedParserAtomIndex name) {
    return failNameOffset(pn->pn_pos.begin, fmt, name);
  }

  bool failOverRecursed() {
    errorOverRecursed_ = true;
    return false;
  }

  unsigned numArrayViews() const { return arrayViews_.length(); }
  const ArrayView& arrayView(unsigned i) const { return arrayViews_[i]; }
  unsigned numFuncDefs() const { return funcDefs_.length(); }
  const Func& funcDef(unsigned i) const { return funcDefs_[i]; }
  unsigned numFuncPtrTables() const { return tables_.length(); }
  Table& table(unsigned i) const { return *tables_[i]; }

  const Global* lookupGlobal(TaggedParserAtomIndex name) const {
    if (GlobalMap::Ptr p = globalMap_.lookup(name)) {
      return p->value();
    }
    return nullptr;
  }

  Func* lookupFuncDef(TaggedParserAtomIndex name) {
    if (GlobalMap::Ptr p = globalMap_.lookup(name)) {
      Global* value = p->value();
      if (value->which() == Global::Function) {
        return &funcDefs_[value->funcDefIndex()];
      }
    }
    return nullptr;
  }

  bool lookupStandardLibraryMathName(TaggedParserAtomIndex name,
                                     MathBuiltin* mathBuiltin) const {
    if (MathNameMap::Ptr p = standardLibraryMathNames_.lookup(name)) {
      *mathBuiltin = p->value();
      return true;
    }
    return false;
  }

  bool startFunctionBodies() {
    if (!arrayViews_.empty()) {
      memory_.usage = MemoryUsage::Unshared;
    } else {
      memory_.usage = 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.
template <typename Unit>
class MOZ_STACK_CLASS ModuleValidator : public ModuleValidatorShared {
 private:
  AsmJSParser<Unit>& parser_;

 public:
  ModuleValidator(FrontendContext* fc, ParserAtomsTable& parserAtoms,
                  MutableModuleMetadata moduleMeta,
                  MutableCodeMetadata codeMeta, AsmJSParser<Unit>& parser,
                  FunctionNode* moduleFunctionNode)
      : ModuleValidatorShared(fc, parserAtoms, moduleMeta, codeMeta,
                              moduleFunctionNode),
        parser_(parser) {}

  ~ModuleValidator() {
    if (errorString_) {
      MOZ_ASSERT(errorOffset_ != UINT32_MAX);
      typeFailure(errorOffset_, errorString_.get());
    }
    if (errorOverRecursed_) {
      ReportOverRecursed(fc_);
    }
  }

 private:
  // Helpers:
  bool newSig(FuncType&& sig, uint32_t* sigIndex) {
    if (codeMeta_->types->length() >= MaxTypes) {
      return failCurrentOffset("too many signatures");
    }

    *sigIndex = codeMeta_->types->length();
    return codeMeta_->types->addType(std::move(sig));
  }
  bool declareSig(FuncType&& sig, uint32_t* sigIndex) {
    SigSet::AddPtr p = sigSet_.lookupForAdd(sig);
    if (p) {
      *sigIndex = p->sigIndex();
      MOZ_ASSERT(FuncType::strictlyEquals(
          codeMeta_->types->type(*sigIndex).funcType(), sig));
      return true;
    }

    return newSig(std::move(sig), sigIndex) &&
           sigSet_.add(p, HashableSig(*sigIndex, *codeMeta_->types));
  }

 private:
  void typeFailure(uint32_t offset, ...) {
    va_list args;
    va_start(args, offset);

    auto& ts = tokenStream();
    ErrorMetadata metadata;
    if (ts.computeErrorMetadata(&metadata, AsVariant(offset))) {
      if (ts.anyCharsAccess().options().throwOnAsmJSValidationFailure()) {
        ReportCompileErrorLatin1VA(fc_, std::move(metadata), nullptr,
                                   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.
        (void)ts.compileWarning(std::move(metadata), nullptr,
                                JSMSG_USE_ASM_TYPE_FAIL, &args);
      }
    }

    va_end(args);
  }

 public:
  bool init() {
    codeMetaForAsmJS_ = js_new<CodeMetadataForAsmJSImpl>();
    if (!codeMetaForAsmJS_) {
      ReportOutOfMemory(fc_);
      return false;
    }

    codeMetaForAsmJS_->toStringStart =
        moduleFunctionNode_->funbox()->extent().toStringStart;
    codeMetaForAsmJS_->srcStart = moduleFunctionNode_->body()->pn_pos.begin;
    codeMetaForAsmJS_->strict = parser_.pc_->sc()->strict() &&
                                !parser_.pc_->sc()->hasExplicitUseStrict();
    codeMetaForAsmJS_->alwaysUseFdlibm = parser_.options().alwaysUseFdlibm();
    codeMetaForAsmJS_->source = do_AddRef(parser_.ss);

    return addStandardLibraryMathInfo();
  }

  AsmJSParser<Unit>& parser() const { return parser_; }

  auto& tokenStream() const { return parser_.tokenStream; }

  bool alwaysUseFdlibm() const { return codeMetaForAsmJS_->alwaysUseFdlibm; }

 public:
  bool addFuncDef(TaggedParserAtomIndex 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, TaggedParserAtomIndex name,
                           uint32_t firstUse, uint32_t mask,
                           uint32_t* tableIndex) {
    if (mask > MaxTableElemsRuntime) {
      return failCurrentOffset("function pointer table too big");
    }

    MOZ_ASSERT(codeMeta_->tables.length() == tables_.length());
    *tableIndex = codeMeta_->tables.length();

    uint32_t sigIndex;
    if (!newSig(std::move(sig), &sigIndex)) {
      return false;
    }

    MOZ_ASSERT(sigIndex >= codeMeta_->asmJSSigToTableIndex.length());
    if (!codeMeta_->asmJSSigToTableIndex.resize(sigIndex + 1)) {
      return false;
    }

    Limits limits = Limits(mask + 1, Nothing(), Shareable::False);
    codeMeta_->asmJSSigToTableIndex[sigIndex] = codeMeta_->tables.length();
    if (!codeMeta_->tables.emplaceBack(limits, RefType::func(),
                                       /* initExpr */ Nothing(),
                                       /*isAsmJS*/ true)) {
      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(TaggedParserAtomIndex 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 == codeMetaForAsmJS_->asmJSImports.length());

    if (*importIndex >= MaxImports) {
      return failCurrentOffset("too many imports");
    }

    if (!codeMetaForAsmJS_->asmJSImports.emplaceBack(ffiIndex)) {
      return false;
    }

    uint32_t sigIndex;
    if (!declareSig(std::move(sig), &sigIndex)) {
      return false;
    }

    return funcImportMap_.add(p, NamedSig(name, sigIndex, *codeMeta_->types),
                              *importIndex);
  }

  // Error handling.
  bool failCurrentOffset(const char* str) {
    return failOffset(tokenStream().anyCharsAccess().currentToken().pos.begin,
                      str);
  }

  SharedModule finish() {
    MOZ_ASSERT(codeMeta_->numMemories() == 0);
    if (memory_.usage != MemoryUsage::None) {
      Limits limits;
      limits.shared = memory_.usage == MemoryUsage::Shared ? Shareable::True
                                                           : Shareable::False;
      limits.initial = memory_.minPages();
      limits.maximum = Nothing();
      limits.addressType = AddressType::I32;
      if (!codeMeta_->memories.append(MemoryDesc(limits))) {
        return nullptr;
      }
    }
    MOZ_ASSERT(codeMeta_->funcs.empty());
    if (!codeMeta_->funcs.resize(funcImportMap_.count() + funcDefs_.length())) {
      return nullptr;
    }
    for (FuncImportMap::Range r = funcImportMap_.all(); !r.empty();
         r.popFront()) {
      uint32_t funcIndex = r.front().value();
      uint32_t funcTypeIndex = r.front().key().sigIndex();
      codeMeta_->funcs[funcIndex] = FuncDesc(funcTypeIndex);
    }
    for (const Func& func : funcDefs_) {
      uint32_t funcIndex = funcImportMap_.count() + func.funcDefIndex();
      uint32_t funcTypeIndex = func.sigIndex();
      codeMeta_->funcs[funcIndex] = FuncDesc(funcTypeIndex);
    }
    for (const Export& exp : moduleMeta_->exports) {
      if (exp.kind() != DefinitionKind::Function) {
        continue;
      }
      uint32_t funcIndex = exp.funcIndex();
      codeMeta_->funcs[funcIndex].declareFuncExported(/* eager */ true,
                                                      /* canRefFunc */ false);
    }

    codeMeta_->numFuncImports = funcImportMap_.count();

    // All globals (inits and imports) are imports from Wasm point of view.
    codeMeta_->numGlobalImports = codeMeta_->globals.length();

    MOZ_ASSERT(codeMetaForAsmJS_->asmJSFuncNames.empty());
    if (!codeMetaForAsmJS_->asmJSFuncNames.resize(funcImportMap_.count())) {
      return nullptr;
    }
    for (const Func& func : funcDefs_) {
      CacheableChars funcName = parserAtoms_.toNewUTF8CharsZ(fc_, func.name());
      if (!funcName ||
          !codeMetaForAsmJS_->asmJSFuncNames.emplaceBack(std::move(funcName))) {
        return nullptr;
      }
    }

    uint32_t endBeforeCurly =
        tokenStream().anyCharsAccess().currentToken().pos.end;
    codeMetaForAsmJS_->srcLength = endBeforeCurly - codeMetaForAsmJS_->srcStart;

    TokenPos pos;
    MOZ_ALWAYS_TRUE(
        tokenStream().peekTokenPos(&pos, TokenStreamShared::SlashIsRegExp));
    uint32_t endAfterCurly = pos.end;
    codeMetaForAsmJS_->srcLengthWithRightBrace =
        endAfterCurly - codeMetaForAsmJS_->srcStart;

    uint32_t codeSectionSize = 0;
    for (const Func& func : funcDefs_) {
      codeSectionSize += func.bytes().length();
    }

    codeMeta_->codeSectionRange = Some(BytecodeRange(0, codeSectionSize));

    // asm.js does not have any wasm bytecode to save; view-source is
    // provided through the ScriptSource.
    BytecodeBufferOrSource bytecode;

    if (!moduleMeta_->prepareForCompile(compilerEnv_.mode())) {
      return nullptr;
    }

    ModuleGenerator mg(*codeMeta_, compilerEnv_, compilerEnv_.initialState(),
                       nullptr, nullptr, nullptr);
    if (!mg.initializeCompleteTier(codeMetaForAsmJS_.get())) {
      return nullptr;
    }

    for (Func& func : funcDefs_) {
      if (!mg.compileFuncDef(funcImportMap_.count() + func.funcDefIndex(),
                             func.line(), func.bytes().begin(),
                             func.bytes().end(),
                             std::move(func.callSiteLineNums()))) {
        return nullptr;
      }
    }

    if (!mg.finishFuncDefs()) {
      return nullptr;
    }

    return mg.finishModule(bytecode, *moduleMeta_,
                           /*maybeCompleteTier2Listener=*/nullptr);
  }
};

/*****************************************************************************/
// Numeric literal utilities

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) {
  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;
  if (!IsCallToGlobal(m, pn, &global)) {
    return false;
  }

  if (CallArgListLength(pn) != 1) {
    return false;
  }

  if (coercedExpr) {
    *coercedExpr = CallArgList(pn);
  }

  if (global->isMathFunction() &&
      global->mathBuiltinFunction() == AsmJSMathBuiltin_fround) {
    *coerceTo = Type::Float;
    return true;
  }

  return false;
}

static bool IsFloatLiteral(ModuleValidatorShared& 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) {
  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,
                                          ParseNode** out = nullptr) {
  MOZ_ASSERT(IsNumericNonFloatLiteral(pn));

  if (pn->isKind(ParseNodeKind::NegExpr)) {
    pn = UnaryKid(pn);
    if (out) {
      *out = pn;
    }
    return -NumberNodeValue(pn);
  }

  return NumberNodeValue(pn);
}

static NumLit ExtractNumericLiteral(ModuleValidatorShared& 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);
    return NumLit(NumLit::Float, DoubleValue(d));
  }

  double d = ExtractNumericNonFloatValue(pn, &pn);

  // The asm.js spec syntactically distinguishes any literal containing a
  // decimal point or the literal -0 as having double type.
  if (NumberNodeHasFrac(pn) || IsNegativeZero(d)) {
    return NumLit(NumLit::Double, DoubleValue(d));
  }

  // The syntactic checks above rule out these double values.
  MOZ_ASSERT(!IsNegativeZero(d));
  MOZ_ASSERT(!std::isnan(d));

  // Although doubles can only *precisely* represent 53-bit integers, they
  // can *imprecisely* represent integers much bigger than an int64_t.
  // Furthermore, d may be inf or -inf. In both cases, casting to an int64_t
  // is undefined, so test against the integer bounds using doubles.
  if (d < double(INT32_MIN) || d > double(UINT32_MAX)) {
    return NumLit(NumLit::OutOfRangeInt, UndefinedValue());
  }

  // With the above syntactic and range limitations, d is definitely an
  // integer in the range [INT32_MIN, UINT32_MAX] range.
  int64_t i64 = int64_t(d);
  if (i64 >= 0) {
    if (i64 <= INT32_MAX) {
      return NumLit(NumLit::Fixnum, Int32Value(i64));
    }
    MOZ_ASSERT(i64 <= UINT32_MAX);
    return NumLit(NumLit::BigUnsigned, Int32Value(uint32_t(i64)));
  }
  MOZ_ASSERT(i64 >= INT32_MIN);
  return NumLit(NumLit::NegativeInt, Int32Value(i64));
}

static inline bool IsLiteralInt(const NumLit& lit, uint32_t* u32) {
  switch (lit.which()) {
    case NumLit::Fixnum:
    case NumLit::BigUnsigned:
    case NumLit::NegativeInt:
      *u32 = lit.toUint32();
      return true;
    case NumLit::Double:
    case NumLit::Float:
    case NumLit::OutOfRangeInt:
      return false;
  }
  MOZ_CRASH("Bad literal type");
}

static inline bool IsLiteralInt(ModuleValidatorShared& m, ParseNode* pn,
                                uint32_t* u32) {
  return IsNumericLiteral(m, pn) &&
         IsLiteralInt(ExtractNumericLiteral(m, pn), u32);
}

/*****************************************************************************/

namespace {

using LabelVector = Vector<TaggedParserAtomIndex, 4, SystemAllocPolicy>;

class MOZ_STACK_CLASS FunctionValidatorShared {
 public:
  struct Local {
    Type type;
    unsigned slot;
    Local(Type t, unsigned slot) : type(t), slot(slot) {
      MOZ_ASSERT(type.isCanonicalValType());
    }
  };

 protected:
  using LocalMap =
      HashMap<TaggedParserAtomIndex, Local, TaggedParserAtomIndexHasher>;
  using LabelMap =
      HashMap<TaggedParserAtomIndex, uint32_t, TaggedParserAtomIndexHasher>;

  // This is also a ModuleValidator<Unit>& after the appropriate static_cast<>.
  ModuleValidatorShared& m_;

  FunctionNode* fn_;
  Bytes bytes_;
  Encoder encoder_;
  Uint32Vector callSiteLineNums_;
  LocalMap locals_;

  // Labels
  LabelMap breakLabels_;
  LabelMap continueLabels_;
  Uint32Vector breakableStack_;
  Uint32Vector continuableStack_;
  uint32_t blockDepth_;

  bool hasAlreadyReturned_;
  Maybe<ValType> ret_;

 private:
  FunctionValidatorShared(ModuleValidatorShared& m, FunctionNode* fn,
                          FrontendContext* fc)
      : m_(m),
        fn_(fn),
        encoder_(bytes_),
        locals_(fc),
        breakLabels_(fc),
        continueLabels_(fc),
        blockDepth_(0),
        hasAlreadyReturned_(false) {}

 protected:
  template <typename Unit>
  FunctionValidatorShared(ModuleValidator<Unit>& m, FunctionNode* fn,
                          FrontendContext* fc)
      : FunctionValidatorShared(static_cast<ModuleValidatorShared&>(m), fn,
                                fc) {}

 public:
  ModuleValidatorShared& m() const { return m_; }

  FrontendContext* fc() const { return m_.fc(); }
  FunctionNode* fn() const { return fn_; }

  void define(ModuleValidatorShared::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_));
  }

  bool fail(ParseNode* pn, const char* str) { return m_.fail(pn, str); }

  bool failf(ParseNode* pn, const char* fmt, ...) MOZ_FORMAT_PRINTF(3, 4) {
    va_list ap;
    va_start(ap, fmt);
    m_.failfVAOffset(pn->pn_pos.begin, fmt, ap);
    va_end(ap);
    return false;
  }

  bool failName(ParseNode* pn, const char* fmt, TaggedParserAtomIndex name) {
    return m_.failName(pn, fmt, name);
  }

  /***************************************************** Local scope setup */

  bool addLocal(ParseNode* pn, TaggedParserAtomIndex name, Type type) {
    LocalMap::AddPtr p = locals_.lookupForAdd(name);
    if (p) {
      return failName(pn, "duplicate local name '%s' not allowed", name);
    }
    return locals_.add(p, name, Local(type, locals_.count()));
  }

  /****************************** For consistency of returns in a function */

  bool hasAlreadyReturned() const { return hasAlreadyReturned_; }

  Maybe<ValType> returnedType() const { return ret_; }

  void setReturnedType(const Maybe<ValType>& ret) {
    MOZ_ASSERT(!hasAlreadyReturned_);
    ret_ = ret;
    hasAlreadyReturned_ = true;
  }

  /**************************************************************** Labels */
 private:
  bool writeBr(uint32_t absolute, Op op = Op::Br) {
    MOZ_ASSERT(op == Op::Br || op == Op::BrIf);
    MOZ_ASSERT(absolute < blockDepth_);
    return encoder().writeOp(op) &&
           encoder().writeVarU32(blockDepth_ - 1 - absolute);
  }
  void removeLabel(TaggedParserAtomIndex label, LabelMap* map) {
    LabelMap::Ptr p = map->lookup(label);
    MOZ_ASSERT(p);
    map->remove(p);
  }

 public:
  bool pushBreakableBlock() {
    return encoder().writeOp(Op::Block) &&
           encoder().writeFixedU8(uint8_t(TypeCode::BlockVoid)) &&
           breakableStack_.append(blockDepth_++);
  }
  bool popBreakableBlock() {
    MOZ_ALWAYS_TRUE(breakableStack_.popCopy() == --blockDepth_);
    return encoder().writeOp(Op::End);
  }

  bool pushUnbreakableBlock(const LabelVector* labels = nullptr) {
    if (labels) {
      for (TaggedParserAtomIndex label : *labels) {
        if (!breakLabels_.putNew(label, blockDepth_)) {
          return false;
        }
      }
    }
    blockDepth_++;
    return encoder().writeOp(Op::Block) &&
           encoder().writeFixedU8(uint8_t(TypeCode::BlockVoid));
  }
  bool popUnbreakableBlock(const LabelVector* labels = nullptr) {
    if (labels) {
      for (TaggedParserAtomIndex label : *labels) {
        removeLabel(label, &breakLabels_);
      }
    }
    --blockDepth_;
    return encoder().writeOp(Op::End);
  }

  bool pushContinuableBlock() {
    return encoder().writeOp(Op::Block) &&
           encoder().writeFixedU8(uint8_t(TypeCode::BlockVoid)) &&
           continuableStack_.append(blockDepth_++);
  }
  bool popContinuableBlock() {
    MOZ_ALWAYS_TRUE(continuableStack_.popCopy() == --blockDepth_);
    return encoder().writeOp(Op::End);
  }

  bool pushLoop() {
    return encoder().writeOp(Op::Block) &&
           encoder().writeFixedU8(uint8_t(TypeCode::BlockVoid)) &&
           encoder().writeOp(Op::Loop) &&
           encoder().writeFixedU8(uint8_t(TypeCode::BlockVoid)) &&
           breakableStack_.append(blockDepth_++) &&
           continuableStack_.append(blockDepth_++);
  }
  bool popLoop() {
    MOZ_ALWAYS_TRUE(continuableStack_.popCopy() == --blockDepth_);
    MOZ_ALWAYS_TRUE(breakableStack_.popCopy() == --blockDepth_);
    return encoder().writeOp(Op::End) && encoder().writeOp(Op::End);
  }

  bool pushIf(size_t* typeAt) {
    ++blockDepth_;
    return encoder().writeOp(Op::If) && encoder().writePatchableFixedU7(typeAt);
  }
  bool switchToElse() {
    MOZ_ASSERT(blockDepth_ > 0);
    return encoder().writeOp(Op::Else);
  }
  void setIfType(size_t typeAt, TypeCode type) {
    encoder().patchFixedU7(typeAt, uint8_t(type));
  }
  bool popIf() {
    MOZ_ASSERT(blockDepth_ > 0);
    --blockDepth_;
    return encoder().writeOp(Op::End);
  }
  bool popIf(size_t typeAt, TypeCode type) {
    MOZ_ASSERT(blockDepth_ > 0);
    --blockDepth_;
    if (!encoder().writeOp(Op::End)) {
      return false;
    }

    setIfType(typeAt, type);
    return true;
  }

  bool writeBreakIf() { return writeBr(breakableStack_.back(), Op::BrIf); }
  bool writeContinueIf() { return writeBr(continuableStack_.back(), Op::BrIf); }
  bool writeUnlabeledBreakOrContinue(bool isBreak) {
    return writeBr(isBreak ? breakableStack_.back() : continuableStack_.back());
  }
  bool writeContinue() { return writeBr(continuableStack_.back()); }

  bool addLabels(const LabelVector& labels, uint32_t relativeBreakDepth,
                 uint32_t relativeContinueDepth) {
    for (TaggedParserAtomIndex label : labels) {
      if (!breakLabels_.putNew(label, blockDepth_ + relativeBreakDepth)) {
        return false;
      }
      if (!continueLabels_.putNew(label, blockDepth_ + relativeContinueDepth)) {
        return false;
      }
    }
    return true;
  }
  void removeLabels(const LabelVector& labels) {
    for (TaggedParserAtomIndex label : labels) {
      removeLabel(label, &breakLabels_);
      removeLabel(label, &continueLabels_);
    }
  }
  bool writeLabeledBreakOrContinue(TaggedParserAtomIndex label, bool isBreak) {
    LabelMap& map = isBreak ? breakLabels_ : continueLabels_;
    if (LabelMap::Ptr p = map.lookup(label)) {
      return writeBr(p->value());
    }
    MOZ_CRASH("nonexistent label");
  }

  /*************************************************** Read-only interface */

  const Local* lookupLocal(TaggedParserAtomIndex name) const {
    if (auto p = locals_.lookup(name)) {
      return &p->value();
    }
    return nullptr;
  }

  const ModuleValidatorShared::Global* lookupGlobal(
      TaggedParserAtomIndex name) const {
    if (locals_.has(name)) {
      return nullptr;
    }
    return m_.lookupGlobal(name);
  }

  size_t numLocals() const { return locals_.count(); }

  /**************************************************** Encoding interface */

  Encoder& encoder() { return encoder_; }

  [[nodiscard]] bool writeInt32Lit(int32_t i32) {
    return encoder().writeOp(Op::I32Const) && encoder().writeVarS32(i32);
  }
  [[nodiscard]] bool writeConstExpr(const NumLit& lit) {
    switch (lit.which()) {
      case NumLit::Fixnum:
      case NumLit::NegativeInt:
      case NumLit::BigUnsigned:
        return writeInt32Lit(lit.toInt32());
      case NumLit::Float:
        return encoder().writeOp(Op::F32Const) &&
               encoder().writeFixedF32(lit.toFloat());
      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, FunctionNode* fn)
      : FunctionValidatorShared(m, fn, m.fc()) {}

 public:
  ModuleValidator<Unit>& m() const {
    return static_cast<ModuleValidator<Unit>&>(FunctionValidatorShared::m());
  }

  [[nodiscard]] bool writeCall(ParseNode* pn, Op op) {
    MOZ_ASSERT(op == Op::Call);
    if (!encoder().writeOp(op)) {
      return false;
    }

    return appendCallSiteLineNumber(pn);
  }
  [[nodiscard]] bool writeCall(ParseNode* pn, MozOp op) {
    MOZ_ASSERT(op == MozOp::OldCallDirect || op == MozOp::OldCallIndirect);
    if (!encoder().writeOp(op)) {
      return false;
    }

    return appendCallSiteLineNumber(pn);
  }
  [[nodiscard]] bool prepareCall(ParseNode* pn) {
    return appendCallSiteLineNumber(pn);
  }

 private:
  [[nodiscard]] bool appendCallSiteLineNumber(ParseNode* node) {
    const TokenStreamAnyChars& anyChars = m().tokenStream().anyCharsAccess();
    auto lineToken = anyChars.lineToken(node->pn_pos.begin);
    uint32_t lineNumber = anyChars.lineNumber(lineToken);
    if (lineNumber > CallSiteDesc::MAX_LINE_OR_BYTECODE_VALUE) {
      return fail(node, "line number exceeding implementation limits");
    }
    return callSiteLineNums_.append(lineNumber);
  }
};

} /* anonymous namespace */

/*****************************************************************************/
// asm.js type-checking and code-generation algorithm

static bool CheckIdentifier(ModuleValidatorShared& m, ParseNode* usepn,
                            TaggedParserAtomIndex name) {
  if (name == TaggedParserAtomIndex::WellKnown::arguments() ||
      name == TaggedParserAtomIndex::WellKnown::eval()) {
    return m.failName(usepn, "'%s' is not an allowed identifier", name);
  }
  return true;
}

static bool CheckModuleLevelName(ModuleValidatorShared& m, ParseNode* usepn,
                                 TaggedParserAtomIndex 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, FunctionNode* 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,
                          TaggedParserAtomIndex* name) {
  *name = TaggedParserAtomIndex::null();

  if (!arg->isKind(ParseNodeKind::Name)) {
    return m.fail(arg, "argument is not a plain name");
  }

  TaggedParserAtomIndex argName = arg->as<NameNode>().name();
  if (!CheckIdentifier(m, arg, argName)) {
    return false;
  }

  *name = argName;
  return true;
}

static bool CheckModuleArgument(ModuleValidatorShared& m, ParseNode* arg,
                                TaggedParserAtomIndex* name) {
  if (!CheckArgument(m, arg, name)) {
    return false;
  }

  return CheckModuleLevelName(m, arg, *name);
}

static bool CheckModuleArguments(ModuleValidatorShared& m,
                                 FunctionNode* 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");
  }

  TaggedParserAtomIndex arg1Name;
  if (arg1 && !CheckModuleArgument(m, arg1, &arg1Name)) {
    return false;
  }
  if (!m.initGlobalArgumentName(arg1Name)) {
    return false;
  }

  TaggedParserAtomIndex arg2Name;
  if (arg2 && !CheckModuleArgument(m, arg2, &arg2Name)) {
    return false;
  }
  if (!m.initImportArgumentName(arg2Name)) {
    return false;
  }

  TaggedParserAtomIndex arg3Name;
  if (arg3 && !CheckModuleArgument(m, arg3, &arg3Name)) {
    return false;
  }
  return m.initBufferArgumentName(arg3Name);
}

static bool CheckPrecedingStatements(ModuleValidatorShared& 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(stmt)) {
      return m.fail(stmt, "invalid asm.js statement");
    }
  }

  return true;
}

static bool CheckGlobalVariableInitConstant(ModuleValidatorShared& m,
                                            TaggedParserAtomIndex 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,
                                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");
      }
      *coerceTo = Type::Int;
      if (coercedExpr) {
        *coercedExpr = BitwiseLeft(coercionNode);
      }
      return true;
    }
    case ParseNodeKind::PosExpr: {
      *coerceTo = Type::Double;
      if (coercedExpr) {
        *coercedExpr = UnaryKid(coercionNode);
      }
      return true;
    }
    case ParseNodeKind::CallExpr: {
      if (IsCoercionCall(m, coercionNode, coerceTo, coercedExpr)) {
        return true;
      }
      break;
    }
    default:;
  }

  return m.fail(coercionNode, "must be of the form +x, x|0 or fround(x)");
}

static bool CheckGlobalVariableInitImport(ModuleValidatorShared& m,
                                          TaggedParserAtomIndex varName,
                                          ParseNode* initNode, bool isConst) {
  Type coerceTo;
  ParseNode* coercedExpr;
  if (!CheckTypeAnnotation(m, initNode, &coerceTo, &coercedExpr)) {
    return false;
  }

  if (!coercedExpr->isKind(ParseNodeKind::DotExpr)) {
    return m.failName(coercedExpr, "invalid import expression for global '%s'",
                      varName);
  }

  if (!coerceTo.isGlobalVarType()) {
    return m.fail(initNode, "global variable type not allowed");
  }

  ParseNode* base = DotBase(coercedExpr);
  TaggedParserAtomIndex field = DotMember(coercedExpr);

  TaggedParserAtomIndex importName = m.importArgumentName();
  if (!importName) {
    return m.fail(coercedExpr,
                  "cannot import without an asm.js foreign parameter");
  }
  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,
                                TaggedParserAtomIndex name,
                                Scalar::Type* type) {
  if (name == TaggedParserAtomIndex::WellKnown::Int8Array()) {
    *type = Scalar::Int8;
  } else if (name == TaggedParserAtomIndex::WellKnown::Uint8Array()) {
    *type = Scalar::Uint8;
  } else if (name == TaggedParserAtomIndex::WellKnown::Int16Array()) {
    *type = Scalar::Int16;
  } else if (name == TaggedParserAtomIndex::WellKnown::Uint16Array()) {
    *type = Scalar::Uint16;
  } else if (name == TaggedParserAtomIndex::WellKnown::Int32Array()) {
    *type = Scalar::Int32;
  } else if (name == TaggedParserAtomIndex::WellKnown::Uint32Array()) {
    *type = Scalar::Uint32;
  } else if (name == TaggedParserAtomIndex::WellKnown::Float32Array()) {
    *type = Scalar::Float32;
  } else if (name == TaggedParserAtomIndex::WellKnown::Float64Array()) {
    *type = Scalar::Float64;
  } else {
    return false;
  }
  return true;
}

static bool CheckNewArrayViewArgs(ModuleValidatorShared& m, ParseNode* newExpr,
                                  TaggedParserAtomIndex 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");
  }

  if (!IsUseOfName(bufArg, bufferName)) {
    return m.failName(bufArg, "argument to array view constructor must be '%s'",
                      bufferName);
  }

  return true;
}

static bool CheckNewArrayView(ModuleValidatorShared& m,
                              TaggedParserAtomIndex varName,
                              ParseNode* newExpr) {
  TaggedParserAtomIndex globalName = m.globalArgumentName();
  if (!globalName) {
    return m.fail(
        newExpr, "cannot create array view without an asm.js global parameter");
  }

  TaggedParserAtomIndex bufferName = m.bufferArgumentName();
  if (!bufferName) {
    return m.fail(newExpr,
                  "cannot create array view without an asm.js heap parameter");
  }

  ParseNode* ctorExpr = BinaryLeft(newExpr);

  TaggedParserAtomIndex field;
  Scalar::Type type;
  if (ctorExpr->isKind(ParseNodeKind::DotExpr)) {
    ParseNode* base = DotBase(ctorExpr);

    if (!IsUseOfName(base, globalName)) {
      return m.failName(base, "expecting '%s.*Array", globalName);
    }

    field = DotMember(ctorExpr);
    if (!IsArrayViewCtorName(m, field, &type)) {
      return m.fail(ctorExpr, "could not match typed array name");
    }
  } else {
    if (!ctorExpr->isKind(ParseNodeKind::Name)) {
      return m.fail(ctorExpr,
                    "expecting name of imported array view constructor");
    }

    TaggedParserAtomIndex globalName = ctorExpr->as<NameNode>().name();
    const ModuleValidatorShared::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) {
      return m.failName(ctorExpr,
                        "%s must be an imported array view constructor",
                        globalName);
    }

    type = global->viewType();
  }

  if (!CheckNewArrayViewArgs(m, newExpr, bufferName)) {
    return false;
  }

  return m.addArrayView(varName, type, field);
}

static bool CheckGlobalMathImport(ModuleValidatorShared& m, ParseNode* initNode,
                                  TaggedParserAtomIndex varName,
                                  TaggedParserAtomIndex field) {
  // Math builtin, with the form glob.Math.[[builtin]]
  ModuleValidatorShared::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:
      return m.addMathBuiltinFunction(varName, mathBuiltin.u.func, field);
    case ModuleValidatorShared::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,
                                 TaggedParserAtomIndex varName,
                                 ParseNode* initNode) {
  ParseNode* base = DotBase(initNode);
  TaggedParserAtomIndex field = DotMember(initNode);

  if (base->isKind(ParseNodeKind::DotExpr)) {
    ParseNode* global = DotBase(base);
    TaggedParserAtomIndex math = DotMember(base);

    TaggedParserAtomIndex globalName = m.globalArgumentName();
    if (!globalName) {
      return m.fail(
          base, "import statement requires the module have a stdlib parameter");
    }

    if (!IsUseOfName(global, globalName)) {
      if (global->isKind(ParseNodeKind::DotExpr)) {
        return m.failName(base,
                          "imports can have at most two dot accesses "
                          "(e.g. %s.Math.sin)",
                          globalName);
      }
      return m.failName(base, "expecting %s.*", globalName);
    }

    if (math == TaggedParserAtomIndex::WellKnown::Math()) {
      return CheckGlobalMathImport(m, initNode, varName, field);
    }
    return m.failName(base, "expecting %s.Math", globalName);
  }

  if (!base->isKind(ParseNodeKind::Name)) {
    return m.fail(base, "expected name of variable or parameter");
  }

  auto baseName = base->as<NameNode>().name();
  if (baseName == m.globalArgumentName()) {
    if (field == TaggedParserAtomIndex::WellKnown::NaN()) {
      return m.addGlobalConstant(varName, GenericNaN(), field);
    }
    if (field == TaggedParserAtomIndex::WellKnown::Infinity()) {
      return m.addGlobalConstant(varName, PositiveInfinity<double>(), field);
    }

    Scalar::Type type;
    if (IsArrayViewCtorName(m, field, &type)) {
      return m.addArrayViewCtor(varName, type, field);
    }

    return m.failName(
        initNode, "'%s' is not a standard constant or typed array name", field);
  }

  if (baseName != m.importArgumentName()) {
    return m.fail(base, "expected global or import name");
  }

  return m.addFFI(varName, field);
}

static bool CheckModuleGlobal(ModuleValidatorShared& m, ParseNode* decl,
                              bool isConst) {
  if (!decl->isKind(ParseNodeKind::AssignExpr)) {
    return m.fail(decl, "module import needs initializer");
  }
  AssignmentNode* assignNode = &decl->as<AssignmentNode>();

  ParseNode* var = assignNode->left();

  if (!var->isKind(ParseNodeKind::Name)) {
    return m.fail(var, "import variable is not a plain name");
  }

  TaggedParserAtomIndex varName = var->as<NameNode>().name();
  if (!CheckModuleLevelName(m, var, varName)) {
    return false;
  }

  ParseNode* initNode = assignNode->right();

  if (IsNumericLiteral(m, initNode)) {
    return CheckGlobalVariableInitConstant(m, varName, initNode, isConst);
  }

  if (initNode->isKind(ParseNodeKind::BitOrExpr) ||
      initNode->isKind(ParseNodeKind::PosExpr) ||
      initNode->isKind(ParseNodeKind::CallExpr)) {
    return CheckGlobalVariableInitImport(m, varName, initNode, isConst);
  }

  if (initNode->isKind(ParseNodeKind::NewExpr)) {
    return CheckNewArrayView(m, varName, initNode);
  }

  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) {
  auto& ts = m.parser().tokenStream;
  while (true) {
    bool matched;
    if (!ts.matchToken(&matched, TokenKind::String,
                       TokenStreamShared::SlashIsRegExp)) {
      return false;
    }
    if (!matched) {
      return true;
    }

    if (!IsIgnoredDirectiveName(ts.anyCharsAccess().currentToken().atom())) {
      return m.failCurrentOffset("unsupported processing directive");
    }

    TokenKind tt;
    if (!ts.getToken(&tt)) {
      return false;
    }
    if (tt != TokenKind::Semi) {
      return m.failCurrentOffset("expected semicolon after string literal");
    }
  }
}

template <typename Unit>
static bool CheckModuleGlobals(ModuleValidator<Unit>& 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 (!CheckModuleGlobal(m, var,
                             varStmt->isKind(ParseNodeKind::ConstDecl))) {
        return false;
      }
    }
  }

  return true;
}

static bool ArgFail(FunctionValidatorShared& f, TaggedParserAtomIndex 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,
                              TaggedParserAtomIndex 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);
  }

  ParseNode* argNode = BinaryLeft(initNode);
  ParseNode* coercionNode = BinaryRight(initNode);

  if (!IsUseOfName(argNode, name)) {
    return ArgFail(f, name, stmt);
  }

  ParseNode* coercedExpr;
  if (!CheckTypeAnnotation(f.m(), coercionNode, type, &coercedExpr)) {
    return false;
  }

  if (!type->isArgType()) {
    return f.failName(stmt, "invalid type for argument '%s'", name);
  }

  if (!IsUseOfName(coercedExpr, name)) {
    return ArgFail(f, name, stmt);
  }

  return true;
}

static bool CheckProcessingDirectives(ModuleValidatorShared& m,
                                      ParseNode** stmtIter) {
  ParseNode* stmt = *stmtIter;

  while (stmt && IsIgnoredDirective(stmt)) {
    stmt = NextNode(stmt);
  }

  *stmtIter = stmt;
  return true;
}

static bool CheckArguments(FunctionValidatorShared& 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)) {
    TaggedParserAtomIndex name;
    if (!CheckArgument(f.m(), argpn, &name)) {
      return false;
    }

    Type type;
    if (!CheckArgumentType(f, stmt, name, &type)) {
      return false;
    }

    if (!argTypes->append(type.canonicalToValType())) {
      return false;
    }

    if (!f.addLocal(argpn, name, type)) {
      return false;
    }
  }

  if (argTypes->length() > MaxParams) {
    return f.fail(stmt, "too many parameters");
  }

  *stmtIter = stmt;
  return true;
}

static bool IsLiteralOrConst(FunctionValidatorShared& f, ParseNode* pn,
                             NumLit* lit) {
  if (pn->isKind(ParseNodeKind::Name)) {
    const ModuleValidatorShared::Global* global =
        f.lookupGlobal(pn->as<NameNode>().name());
    if (!global ||
        global->which() != ModuleValidatorShared::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,
                             ParseNode* lastNonEmptyStmt) {
  if (!f.encoder().writeOp(Op::End)) {
    return false;
  }

  if (!f.hasAlreadyReturned()) {
    f.setReturnedType(Nothing());
    return true;
  }

  if (!lastNonEmptyStmt->isKind(ParseNodeKind::ReturnStmt) &&
      f.returnedType()) {
    return f.fail(lastNonEmptyStmt,
                  "void incompatible with previous return type");
  }

  return true;
}

static bool CheckVariable(FunctionValidatorShared& f, ParseNode* decl,
                          ValTypeVector* types, Vector<NumLit>* inits) {
  if (!decl->isKind(ParseNodeKind::AssignExpr)) {
    return f.failName(
        decl, "var '%s' needs explicit type declaration via an initial value",
        decl->as<NameNode>().name());
  }
  AssignmentNode* assignNode = &decl->as<AssignmentNode>();

  ParseNode* var = assignNode->left();

  if (!var->isKind(ParseNodeKind::Name)) {
    return f.fail(var, "local variable is not a plain name");
  }

  TaggedParserAtomIndex name = var->as<NameNode>().name();

  if (!CheckIdentifier(f.m(), var, name)) {
    return false;
  }

  ParseNode* initNode = assignNode->right();

  NumLit lit;
  if (!IsLiteralOrConst(f, initNode, &lit)) {
    return f.failName(
        var, "var '%s' initializer must be literal or const literal", name);
  }

  if (!lit.valid()) {
    return f.failName(var, "var '%s' initializer out of range", name);
  }

  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) {
  ParseNode* stmt = *stmtIter;

  uint32_t firstVar = f.numLocals();

  ValTypeVector types;
  Vector<NumLit> inits(f.fc());

  for (; stmt && stmt->isKind(ParseNodeKind::VarStmt);
       stmt = NextNonEmptyStatement(stmt)) {
    for (ParseNode* var = VarListHead(stmt); var; var = NextNode(var)) {
      if (!CheckVariable(f, var, &types, &inits)) {
        return false;
      }
    }
  }

  MOZ_ASSERT(f.encoder().empty());

  if (!EncodeLocalEntries(f.encoder(), types)) {
    return false;
  }

  for (uint32_t i = 0; i < inits.length(); i++) {
    NumLit lit = inits[i];
    if (lit.isZeroBits()) {
      continue;
    }
    if (!f.writeConstExpr(lit)) {
      return false;
    }
    if (!f.encoder().writeOp(Op::LocalSet)) {
      return false;
    }
    if (!f.encoder().writeVarU32(firstVar + i)) {
      return false;
    }
  }

  *stmtIter = stmt;
  return true;
}

template <typename Unit>
static bool CheckExpr(FunctionValidator<Unit>& f, ParseNode* expr, Type* type);

template <typename Unit>
static bool CheckNumericLiteral(FunctionValidator<Unit>& f, ParseNode* num,
                                Type* type) {
  NumLit lit = ExtractNumericLiteral(f.m(), num);
  if (!lit.valid()) {
    return f.fail(num, "numeric literal out of representable integer range");
  }
  *type = Type::lit(lit);
  return f.writeConstExpr(lit);
}

static bool CheckVarRef(FunctionValidatorShared& f, ParseNode* varRef,
                        Type* type) {
  TaggedParserAtomIndex name = varRef->as<NameNode>().name();

  if (const FunctionValidatorShared::Local* local = f.lookupLocal(name)) {
    if (!f.encoder().writeOp(Op::LocalGet)) {
      return false;
    }
    if (!f.encoder().writeVarU32(local->slot)) {
      return false;
    }
    *type = local->type;
    return true;
  }

  if (const ModuleValidatorShared::Global* global = f.lookupGlobal(name)) {
    switch (global->which()) {
      case ModuleValidatorShared::Global::ConstantLiteral:
        *type = global->varOrConstType();
        return f.writeConstExpr(global->constLiteralValue());
      case ModuleValidatorShared::Global::ConstantImport:
      case ModuleValidatorShared::Global::Variable: {
        *type = global->varOrConstType();
        return f.encoder().writeOp(Op::GlobalGet) &&
               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:
        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) {
  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,
                             ParseNode* indexExpr, Scalar::Type* viewType) {
  if (!viewName->isKind(ParseNodeKind::Name)) {
    return f.fail(viewName,
                  "base of array access must be a typed array view name");
  }

  const ModuleValidatorShared::Global* global =
      f.lookupGlobal(viewName->as<NameNode>().name());
  if (!global || global->which() != ModuleValidatorShared::Global::ArrayView) {
    return f.fail(viewName,
                  "base of array access must be a typed array view name");
  }

  *viewType = global->viewType();

  uint32_t index;
  if (IsLiteralOrConstInt(f, indexExpr, &index)) {
    uint64_t byteOffset = uint64_t(index) << TypedArrayShift(*viewType);
    uint64_t width = TypedArrayElemSize(*viewType);
    if (!f.m().tryConstantAccess(byteOffset, width)) {
      return f.fail(indexExpr, "constant index out of range");
    }

    return f.writeInt32Lit(byteOffset);
  }

  // Mask off the low bits to account for the clearing effect of a right shift
  // followed by the left shift implicit in the array access. E.g., H32[i>>2]
  // loses the low two bits.
  int32_t mask = ~(TypedArrayElemSize(*viewType) - 1);

  if (indexExpr->isKind(ParseNodeKind::RshExpr)) {
    ParseNode* shiftAmountNode = BitwiseRight(indexExpr);

    uint32_t shift;
    if (!IsLiteralInt(f.m(), shiftAmountNode, &shift)) {
      return f.failf(shiftAmountNode, "shift amount must be constant");
    }

    unsigned requiredShift = TypedArrayShift(*viewType);
    if (shift != requiredShift) {
      return f.failf(shiftAmountNode, "shift amount must be %u", requiredShift);
    }

    ParseNode* pointerNode = BitwiseLeft(indexExpr);

    Type pointerType;
    if (!CheckExpr(f, pointerNode, &pointerType)) {
      return false;
    }

    if (!pointerType.isIntish()) {
      return f.failf(pointerNode, "%s is not a subtype of int",
                     pointerType.toChars());
    }
  } else {
    // For legacy scalar access compatibility, accept Int8/Uint8 accesses
    // with no shift.
    if (TypedArrayShift(*viewType) != 0) {
      return f.fail(
          indexExpr,
          "index expression isn't shifted; must be an Int8/Uint8 access");
    }

    MOZ_ASSERT(mask == NoMask);

    ParseNode* pointerNode = indexExpr;

    Type pointerType;
    if (!CheckExpr(f, pointerNode, &pointerType)) {
      return false;
    }
    if (!pointerType.isInt()) {
      return f.failf(pointerNode, "%s is not a subtype of int",
                     pointerType.toChars());
    }
  }

  // Don't generate the mask op if there is no need for it which could happen
  // 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) {
  // 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.
  return f.encoder().writeVarU32(0);
}

template <typename Unit>
static bool CheckLoadArray(FunctionValidator<Unit>& f, ParseNode* elem,
                           Type* type) {
  Scalar::Type viewType;

  if (!CheckArrayAccess(f, ElemBase(elem), ElemIndex(elem), &viewType)) {
    return false;
  }

  switch (viewType) {
    case Scalar::Int8:
      if (!f.encoder().writeOp(Op::I32Load8S)) return false;
      break;
    case Scalar::Uint8:
      if (!f.encoder().writeOp(Op::I32Load8U)) return false;
      break;
    case Scalar::Int16:
      if (!f.encoder().writeOp(Op::I32Load16S)) return false;
      break;
    case Scalar::Uint16:
      if (!f.encoder().writeOp(Op::I32Load16U)) return false;
      break;
    case Scalar::Uint32:
    case Scalar::Int32:
      if (!f.encoder().writeOp(Op::I32Load)) return false;
      break;
    case Scalar::Float32:
      if (!f.encoder().writeOp(Op::F32Load)) return false;
      break;
    case Scalar::Float64:
      if (!f.encoder().writeOp(Op::F64Load)) return false;
      break;
    default:
      MOZ_CRASH("unexpected scalar type");
  }

  switch (viewType) {
    case Scalar::Int8:
    case Scalar::Int16:
    case Scalar::Int32:
    case Scalar::Uint8:
    case Scalar::Uint16:
    case Scalar::Uint32:
      *type = Type::Intish;
      break;
    case Scalar::Float32:
      *type = Type::MaybeFloat;
      break;
    case Scalar::Float64:
      *type = Type::MaybeDouble;
      break;
    default:
      MOZ_CRASH("Unexpected array type");
  }

  return WriteArrayAccessFlags(f, viewType);
}

template <typename Unit>
static bool CheckStoreArray(FunctionValidator<Unit>& f, ParseNode* lhs,
                            ParseNode* rhs, Type* type) {
  Scalar::Type viewType;
  if (!CheckArrayAccess(f, ElemBase(lhs), ElemIndex(lhs), &viewType)) {
    return false;
  }

  Type rhsType;
  if (!CheckExpr(f, rhs, &rhsType)) {
    return false;
  }

  switch (viewType) {
    case Scalar::Int8:
    case Scalar::Int16:
    case Scalar::Int32:
    case Scalar::Uint8:
    case Scalar::Uint16:
    case Scalar::Uint32:
      if (!rhsType.isIntish()) {
        return f.failf(lhs, "%s is not a subtype of intish", rhsType.toChars());
      }
      break;
    case Scalar::Float32:
      if (!rhsType.isMaybeDouble() && !rhsType.isFloatish()) {
        return f.failf(lhs, "%s is not a subtype of double? or floatish",
                       rhsType.toChars());
      }
      break;
    case Scalar::Float64:
      if (!rhsType.isMaybeFloat() && !rhsType.isMaybeDouble()) {
        return f.failf(lhs, "%s is not a subtype of float? or double?",
                       rhsType.toChars());
      }
      break;
    default:
      MOZ_CRASH("Unexpected view type");
  }

  switch (viewType) {
    case Scalar::Int8:
    case Scalar::Uint8:
      if (!f.encoder().writeOp(MozOp::I32TeeStore8)) {
        return false;
      }
      break;
    case Scalar::Int16:
    case Scalar::Uint16:
      if (!f.encoder().writeOp(MozOp::I32TeeStore16)) {
        return false;
      }
      break;
    case Scalar::Int32:
    case Scalar::Uint32:
      if (!f.encoder().writeOp(MozOp::I32TeeStore)) {
        return false;
      }
      break;
    case Scalar::Float32:
      if (rhsType.isFloatish()) {
        if (!f.encoder().writeOp(MozOp::F32TeeStore)) {
          return false;
        }
      } else {
        if (!f.encoder().writeOp(MozOp::F64TeeStoreF32)) {
          return false;
        }
      }
      break;
    case Scalar::Float64:
      if (rhsType.isFloatish()) {
        if (!f.encoder().writeOp(MozOp::F32TeeStoreF64)) {
          return false;
        }
      } else {
        if (!f.encoder().writeOp(MozOp::F64TeeStore)) {
          return false;
        }
      }
      break;
    default:
      MOZ_CRASH("unexpected scalar type");
  }

  if (!WriteArrayAccessFlags(f, viewType)) {
    return false;
  }

  *type = rhsType;
  return true;
}

template <typename Unit>
static bool CheckAssignName(FunctionValidator<Unit>& f, ParseNode* lhs,
                            ParseNode* rhs, Type* type) {
  TaggedParserAtomIndex name = lhs->as<NameNode>().name();

  if (const FunctionValidatorShared::Local* lhsVar = f.lookupLocal(name)) {
    Type rhsType;
    if (!CheckExpr(f, rhs, &rhsType)) {
      return false;
    }

    if (!f.encoder().writeOp(Op::LocalTee)) {
      return false;
    }
    if (!f.encoder().writeVarU32(lhsVar->slot)) {
      return false;
    }

    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) {
      return f.failName(lhs, "'%s' is not a mutable variable", name);
    }

    Type rhsType;
    if (!CheckExpr(f, rhs, &rhsType)) {
      return false;
    }

    Type globType = global->varOrConstType();
    if (!(rhsType <= globType)) {
      return f.failf(lhs, "%s is not a subtype of %s", rhsType.toChars(),
                     globType.toChars());
    }
    if (!f.encoder().writeOp(MozOp::TeeGlobal)) {
      return false;
    }
    if (!f.encoder().writeVarU32(global->varOrConstIndex())) {
      return false;
    }

    *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) {
  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);
  }

  if (lhs->getKind() == ParseNodeKind::Name) {
    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) {
  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;
  if (!CheckExpr(f, lhs, &lhsType)) {
    return false;
  }

  Type rhsType;
  if (!CheckExpr(f, rhs, &rhsType)) {
    return false;
  }

  if (!lhsType.isIntish()) {
    return f.failf(lhs, "%s is not a subtype of intish", lhsType.toChars());
  }
  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) {
  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)) {
    return false;
  }

  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) {
  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)) {
    return false;
  }

  if (argType.isSigned()) {
    *type = Type::Unsigned;
    return f.encoder().writeOp(MozOp::I32Abs);
  }

  if (argType.isMaybeDouble()) {
    *type = Type::Double;
    return f.encoder().writeOp(Op::F64Abs);
  }

  if (argType.isMaybeFloat()) {
    *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) {
  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)) {
    return false;
  }

  if (argType.isMaybeDouble()) {
    *type = Type::Double;
    return f.encoder().writeOp(Op::F64Sqrt);
  }

  if (argType.isMaybeFloat()) {
    *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,
                            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)) {
    return false;
  }

  Op op = Op::Limit;
  MozOp mozOp = MozOp::Limit;
  if (firstType.isMaybeDouble()) {
    *type = Type::Double;
    firstType = Type::MaybeDouble;
    op = isMax ? Op::F64Max : Op::F64Min;
  } else if (firstType.isMaybeFloat()) {
    *type = Type::Float;
    firstType = Type::MaybeFloat;
    op = isMax ? Op::F32Max : Op::F32Min;
  } else if (firstType.isSigned()) {
    *type = Type::Signed;
    firstType = Type::Signed;
    mozOp = isMax ? MozOp::I32Max : MozOp::I32Min;
  } else {
    return f.failf(firstArg, "%s is not a subtype of double?, float? or signed",
                   firstType.toChars());
  }

  unsigned numArgs = CallArgListLength(callNode);
  ParseNode* nextArg = NextNode(firstArg);
  for (unsigned i = 1; i < numArgs; i++, nextArg = NextNode(nextArg)) {
    Type nextType;
    if (!CheckExpr(f, nextArg, &nextType)) {
      return false;
    }
    if (!(nextType <= firstType)) {
      return f.failf(nextArg, "%s is not a subtype of %s", nextType.toChars(),
                     firstType.toChars());
    }

    if (op != Op::Limit) {
      if (!f.encoder().writeOp(op)) {
        return false;
      }
    } else {
      if (!f.encoder().writeOp(mozOp)) {
        return false;
      }
    }
  }

  return true;
}

using CheckArgType = bool (*)(FunctionValidatorShared& f, ParseNode* argNode,
                              Type type);

template <CheckArgType checkArg, typename Unit>
static bool CheckCallArgs(FunctionValidator<Unit>& f, ParseNode* callNode,
                          ValTypeVector* args) {
  ParseNode* argNode = CallArgList(callNode);
  for (unsigned i = 0; i < CallArgListLength(callNode);
       i++, argNode = NextNode(argNode)) {
    Type type;
    if (!CheckExpr(f, argNode, &type)) {
      return false;
    }

    if (!checkArg(f, argNode, type)) {
      return false;
    }

    if (!args->append(Type::canonicalize(type).canonicalToValType())) {
      return false;
    }
  }
  if (args->length() > MaxParams) {
    return f.fail(callNode, "too many parameters");
  }
  return true;
}

static bool CheckSignatureAgainstExisting(ModuleValidatorShared& m,
                                          ParseNode* usepn, const FuncType& sig,
                                          const FuncType& existing) {
  if (!FuncType::strictlyEquals(sig, existing)) {
    return m.failf(usepn, "incompatible argument types to function");
  }
  return true;
}

template <typename Unit>
static bool CheckFunctionSignature(ModuleValidator<Unit>& m, ParseNode* usepn,
                                   FuncType&& sig, TaggedParserAtomIndex name,
                                   ModuleValidatorShared::Func** func) {
  ModuleValidatorShared::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 FuncType& existingSig =
      m.codeMeta()->types->type(existing->sigIndex()).funcType();

  if (!CheckSignatureAgainstExisting(m, usepn, sig, existingSig)) {
    return false;
  }

  *func = existing;
  return true;
}

static bool CheckIsArgType(FunctionValidatorShared& 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,
                              TaggedParserAtomIndex calleeName, Type ret,
                              Type* type) {
  MOZ_ASSERT(ret.isCanonical());

  ValTypeVector args;
  if (!CheckCallArgs<CheckIsArgType>(f, callNode, &args)) {
    return false;
  }

  ValTypeVector results;
  Maybe<ValType> retType = ret.canonicalToReturnType();
  if (retType && !results.append(retType.ref())) {
    return false;
  }

  FuncType sig(std::move(args), std::move(results));

  ModuleValidatorShared::Func* callee;
  if (!CheckFunctionSignature(f.m(), callNode, std::move(sig), calleeName,
                              &callee)) {
    return false;
  }

  if (!f.writeCall(callNode, MozOp::OldCallDirect)) {
    return false;
  }

  if (!f.encoder().writeVarU32(callee->funcDefIndex())) {
    return false;
  }

  *type = Type::ret(ret);
  return true;
}

template <typename Unit>
static bool CheckFuncPtrTableAgainstExisting(ModuleValidator<Unit>& m,
                                             ParseNode* usepn,
                                             TaggedParserAtomIndex name,
                                             FuncType&& sig, unsigned mask,
                                             uint32_t* tableIndex) {
  if (const ModuleValidatorShared::Global* existing = m.lookupGlobal(name)) {
    if (existing->which() != ModuleValidatorShared::Global::Table) {
      return m.failName(usepn, "'%s' is not a function-pointer table", name);
    }

    ModuleValidatorShared::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.codeMeta()->types->type(table.sigIndex()).funcType())) {
      return false;
    }

    *tableIndex = existing->tableIndex();
    return true;
  }

  if (!CheckModuleLevelName(m, usepn, name)) {
    return false;
  }

  return m.declareFuncPtrTable(std::move(sig), name, usepn->pn_pos.begin, mask,
                               tableIndex);
}

template <typename Unit>
static bool CheckFuncPtrCall(FunctionValidator<Unit>& f, ParseNode* callNode,
                             Type ret, Type* type) {
  MOZ_ASSERT(ret.isCanonical());

  ParseNode* callee = CallCallee(callNode);
  ParseNode* tableNode = ElemBase(callee);
  ParseNode* indexExpr = ElemIndex(callee);

  if (!tableNode->isKind(ParseNodeKind::Name)) {
    return f.fail(tableNode, "expecting name of function-pointer array");
  }

  TaggedParserAtomIndex name = tableNode->as<NameNode>().name();
  if (const ModuleValidatorShared::Global* existing = f.lookupGlobal(name)) {
    if (existing->which() != ModuleValidatorShared::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");
  }

  ParseNode* indexNode = BitwiseLeft(indexExpr);
  ParseNode* maskNode = BitwiseRight(indexExpr);

  uint32_t mask;
  if (!IsLiteralInt(f.m(), maskNode, &mask) || mask == UINT32_MAX ||
      !IsPowerOfTwo(mask + 1)) {
    return f.fail(maskNode,
                  "function-pointer table index mask value must be a power of "
                  "two minus 1");
  }

  Type indexType;
  if (!CheckExpr(f, indexNode, &indexType)) {
    return false;
  }

  if (!indexType.isIntish()) {
    return f.failf(indexNode, "%s is not a subtype of intish",
                   indexType.toChars());
  }

  ValTypeVector args;
  if (!CheckCallArgs<CheckIsArgType>(f, callNode, &args)) {
    return false;
  }

  ValTypeVector results;
  Maybe<ValType> retType = ret.canonicalToReturnType();
  if (retType && !results.append(retType.ref())) {
    return false;
  }

  FuncType sig(std::move(args), std::move(results));

  uint32_t tableIndex;
  if (!CheckFuncPtrTableAgainstExisting(f.m(), tableNode, name, std::move(sig),
                                        mask, &tableIndex)) {
    return false;
  }

  if (!f.writeCall(callNode, MozOp::OldCallIndirect)) {
    return false;
  }

  // Call signature
  if (!f.encoder().writeVarU32(f.m().table(tableIndex).sigIndex())) {
    return false;
  }

  *type = Type::ret(ret);
  return true;
}

static bool CheckIsExternType(FunctionValidatorShared& f, ParseNode* argNode,
                              Type type) {
  if (!type.isExtern()) {
    return f.failf(argNode, "%s is not a subtype of extern", type.toChars());
  }
  return true;
}

template <typename Unit>
static bool CheckFFICall(FunctionValidator<Unit>& f, ParseNode* callNode,
                         unsigned ffiIndex, Type ret, Type* type) {
  MOZ_ASSERT(ret.isCanonical());

  TaggedParserAtomIndex calleeName =
      CallCallee(callNode)->as<NameNode>().name();

  if (ret.isFloat()) {
    return f.fail(callNode, "FFI calls can't return float");
  }

  ValTypeVector args;
  if (!CheckCallArgs<CheckIsExternType>(f, callNode, &args)) {
    return false;
  }

  ValTypeVector results;
  Maybe<ValType> retType = ret.canonicalToReturnType();
  if (retType && !results.append(retType.ref())) {
    return false;
  }

  FuncType sig(std::move(args), std::move(results));

  uint32_t importIndex;
  if (!f.m().declareImport(calleeName, std::move(sig), ffiIndex,
                           &importIndex)) {
    return false;
  }

  if (!f.writeCall(callNode, Op::Call)) {
    return false;
  }

  if (!f.encoder().writeVarU32(importIndex)) {
    return false;
  }

  *type = Type::ret(ret);
  return true;
}

static bool CheckFloatCoercionArg(FunctionValidatorShared& f,
                                  ParseNode* inputNode, Type inputType) {
  if (inputType.isMaybeDouble()) {
    return f.encoder().writeOp(Op::F32DemoteF64);
  }
  if (inputType.isSigned()) {
    return f.encoder().writeOp(Op::F32ConvertI32S);
  }
  if (inputType.isUnsigned()) {
    return f.encoder().writeOp(Op::F32ConvertI32U);
  }
  if (inputType.isFloatish()) {
    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,
                             Type expected, Type* type) {
  MOZ_ASSERT(expected.isCanonicalValType());

  if (arg->isKind(ParseNodeKind::CallExpr)) {
    return CheckCoercedCall(f, arg, expected, type);
  }

  Type argType;
  if (!CheckExpr(f, arg, &argType)) {
    return false;
  }

  if (expected.isFloat()) {
    if (!CheckFloatCoercionArg(f, arg, argType)) {
      return false;
    }
  } else {
    MOZ_CRASH("not call coercions");
  }

  *type = Type::ret(expected);
  return true;
}

template <typename Unit>
static bool CheckMathFRound(FunctionValidator<Unit>& f, ParseNode* callNode,
                            Type* type) {
  if (CallArgListLength(callNode) != 1) {
    return f.fail(callNode, "Math.fround must be passed 1 argument");
  }

  ParseNode* argNode = CallArgList(callNode);
  Type argType;
  if (!CheckCoercionArg(f, argNode, Type::Float, &argType)) {
    return false;
  }

  MOZ_ASSERT(argType == Type::Float);
  *type = Type::Float;
  return true;
}

template <typename Unit>
static bool CheckMathBuiltinCall(FunctionValidator<Unit>& f,
                                 ParseNode* callNode,
                                 AsmJSMathBuiltinFunction func, Type* type) {
  unsigned arity = 0;
  Op f32 = Op::Limit;
  Op f64 = Op::Limit;
  MozOp mozf64 = MozOp::Limit;
  switch (func) {
    case AsmJSMathBuiltin_imul:
      return CheckMathIMul(f, callNode, type);
    case AsmJSMathBuiltin_clz32:
      return CheckMathClz32(f, callNode, type);
    case AsmJSMathBuiltin_abs:
      return CheckMathAbs(f, callNode, type);
    case AsmJSMathBuiltin_sqrt:
      return CheckMathSqrt(f, callNode, type);
    case AsmJSMathBuiltin_fround:
      return CheckMathFRound(f, callNode, type);
    case AsmJSMathBuiltin_min:
      return CheckMathMinMax(f, callNode, /* isMax = */ false, type);
    case AsmJSMathBuiltin_max:
      return CheckMathMinMax(f, callNode, /* isMax = */ true, type);
    case AsmJSMathBuiltin_ceil:
      arity = 1;
      f64 = Op::F64Ceil;
      f32 = Op::F32Ceil;
      break;
    case AsmJSMathBuiltin_floor:
      arity = 1;
      f64 = Op::F64Floor;
      f32 = Op::F32Floor;
      break;
    case AsmJSMathBuiltin_sin:
      arity = 1;
      if (!f.m().alwaysUseFdlibm()) {
        mozf64 = MozOp::F64SinNative;
      } else {
        mozf64 = MozOp::F64SinFdlibm;
      }
      f32 = Op::Unreachable;
      break;
    case AsmJSMathBuiltin_cos:
      arity = 1;
      if (!f.m().alwaysUseFdlibm()) {
        mozf64 = MozOp::F64CosNative;
      } else {
        mozf64 = MozOp::F64CosFdlibm;
      }
      f32 = Op::Unreachable;
      break;
    case AsmJSMathBuiltin_tan:
      arity = 1;
      if (!f.m().alwaysUseFdlibm()) {
        mozf64 = MozOp::F64TanNative;
      } else {
        mozf64 = MozOp::F64TanFdlibm;
      }
      f32 = Op::Unreachable;
      break;
    case AsmJSMathBuiltin_asin:
      arity = 1;
      mozf64 = MozOp::F64Asin;
      f32 = Op::Unreachable;
      break;
    case AsmJSMathBuiltin_acos:
      arity = 1;
      mozf64 = MozOp::F64Acos;
      f32 = Op::Unreachable;
      break;
    case AsmJSMathBuiltin_atan:
      arity = 1;
      mozf64 = MozOp::F64Atan;
      f32 = Op::Unreachable;
      break;
    case AsmJSMathBuiltin_exp:
      arity = 1;
      mozf64 = MozOp::F64Exp;
      f32 = Op::Unreachable;
      break;
    case AsmJSMathBuiltin_log:
      arity = 1;
      mozf64 = MozOp::F64Log;
      f32 = Op::Unreachable;
      break;
    case AsmJSMathBuiltin_pow:
      arity = 2;
      mozf64 = MozOp::F64Pow;
      f32 = Op::Unreachable;
      break;
    case AsmJSMathBuiltin_atan2:
      arity = 2;
      mozf64 = MozOp::F64Atan2;
      f32 = Op::Unreachable;
      break;
    default:
      MOZ_CRASH("unexpected mathBuiltin function");
  }

  unsigned actualArity = CallArgListLength(callNode);
  if (actualArity != arity) {
    return f.failf(callNode, "call passed %u arguments, expected %u",
                   actualArity, arity);
  }

  if (!f.prepareCall(callNode)) {
    return false;
  }

  Type firstType;
  ParseNode* argNode = CallArgList(callNode);
  if (!CheckExpr(f, argNode, &firstType)) {
    return false;
  }

  if (!firstType.isMaybeFloat() && !firstType.isMaybeDouble()) {
    return f.fail(
        argNode,
        "arguments to math call should be a subtype of double? or float?");
  }

  bool opIsDouble = firstType.isMaybeDouble();
  if (!opIsDouble && f32 == Op::Unreachable) {
    return f.fail(callNode, "math builtin cannot be used as float");
  }

  if (arity == 2) {
    Type secondType;
    argNode = NextNode(argNode);
    if (!CheckExpr(f, argNode, &secondType)) {
      return false;
    }

    if (firstType.isMaybeDouble() && !secondType.isMaybeDouble()) {
      return f.fail(
          argNode,
          "both arguments to math builtin call should be the same type");
    }
    if (firstType.isMaybeFloat() && !secondType.isMaybeFloat()) {
      return f.fail(
          argNode,
          "both arguments to math builtin call should be the same type");
    }
  }

  if (opIsDouble) {
    if (f64 != Op::Limit) {
      if (!f.encoder().writeOp(f64)) {
        return false;
      }
    } else {
      if (!f.encoder().writeOp(mozf64)) {
        return false;
      }
    }
  } else {
    if (!f.encoder().writeOp(f32)) {
      return false;
    }
  }

  *type = opIsDouble ? Type::Double : Type::Floatish;
  return true;
}

template <typename Unit>
static bool CheckUncoercedCall(FunctionValidator<Unit>& f, ParseNode* expr,
                               Type* type) {
  MOZ_ASSERT(expr->isKind(ParseNodeKind::CallExpr));

  const ModuleValidatorShared::Global* global;
  if (IsCallToGlobal(f.m(), expr, &global) && global->isMathFunction()) {
    return CheckMathBuiltinCall(f, expr, global->mathBuiltinFunction(), type);
  }

  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) {
  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)) {
          return false;
        }
      }
      break;
    case Type::Int:
      if (!actual.isIntish()) {
        return f.failf(expr, "%s is not a subtype of intish", actual.toChars());
      }
      break;
    case Type::Float:
      if (!CheckFloatCoercionArg(f, expr, actual)) {
        return false;
      }
      break;
    case Type::Double:
      if (actual.isMaybeDouble()) {
        // No conversion necessary.
      } else if (actual.isMaybeFloat()) {
        if (!f.encoder().writeOp(Op::F64PromoteF32)) {
          return false;
        }
      } else if (actual.isSigned()) {
        if (!f.encoder().writeOp(Op::F64ConvertI32S)) {
          return false;
        }
      } else if (actual.isUnsigned()) {
        if (!f.encoder().writeOp(Op::F64ConvertI32U)) {
          return false;
        }
      } else {
        return f.failf(
            expr, "%s is not a subtype of double?, float?, signed or unsigned",
            actual.toChars());
      }
      break;
    default:
      MOZ_CRASH("unexpected uncoerced result type");
  }

  *type = Type::ret(expected);
  return true;
}

template <typename Unit>
static bool CheckCoercedMathBuiltinCall(FunctionValidator<Unit>& f,
                                        ParseNode* callNode,
                                        AsmJSMathBuiltinFunction func, Type ret,
                                        Type* type) {
  Type actual;
  if (!CheckMathBuiltinCall(f, callNode, func, &actual)) {
    return false;
  }
  return CoerceResult(f, callNode, ret, actual, type);
}

template <typename Unit>
static bool CheckCoercedCall(FunctionValidator<Unit>& f, ParseNode* call,
                             Type ret, Type* type) {
  MOZ_ASSERT(ret.isCanonical());

  AutoCheckRecursionLimit recursion(f.fc());
  if (!recursion.checkDontReport(f.fc())) {
    return f.m().failOverRecursed();
  }

  if (IsNumericLiteral(f.m(), call)) {
    NumLit lit = ExtractNumericLiteral(f.m(), call);
    if (!f.writeConstExpr(lit)) {
      return false;
    }
    return CoerceResult(f, call, ret, Type::lit(lit), type);
  }

  ParseNode* callee = CallCallee(call);

  if (callee->isKind(ParseNodeKind::ElemExpr)) {
    return CheckFuncPtrCall(f, call, ret, type);
  }

  if (!callee->isKind(ParseNodeKind::Name)) {
    return f.fail(callee, "unexpected callee expression type");
  }

  TaggedParserAtomIndex calleeName = callee->as<NameNode>().name();

  if (const ModuleValidatorShared::Global* global =
          f.lookupGlobal(calleeName)) {
    switch (global->which()) {
      case ModuleValidatorShared::Global::FFI:
        return CheckFFICall(f, call, global->ffiIndex(), ret, type);
      case ModuleValidatorShared::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:
        return f.failName(callee, "'%s' is not callable function", calleeName);
      case ModuleValidatorShared::Global::Function:
        break;
    }
  }

  return CheckInternalCall(f, call, calleeName, ret, type);
}

template <typename Unit>
static bool CheckPos(FunctionValidator<Unit>& f, ParseNode* pos, Type* type) {
  MOZ_ASSERT(pos->isKind(ParseNodeKind::PosExpr));
  ParseNode* operand = UnaryKid(pos);

  if (operand->isKind(ParseNodeKind::CallExpr)) {
    return CheckCoercedCall(f, operand, Type::Double, type);
  }

  Type actual;
  if (!CheckExpr(f, operand, &actual)) {
    return false;
  }

  return CoerceResult(f, operand, Type::Double, actual, type);
}

template <typename Unit>
static bool CheckNot(FunctionValidator<Unit>& f, ParseNode* expr, Type* type) {
  MOZ_ASSERT(expr->isKind(ParseNodeKind::NotExpr));
  ParseNode* operand = UnaryKid(expr);

  Type operandType;
  if (!CheckExpr(f, operand, &operandType)) {
    return false;
  }

  if (!operandType.isInt()) {
    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) {
  MOZ_ASSERT(expr->isKind(ParseNodeKind::NegExpr));
  ParseNode* operand = UnaryKid(expr);

  Type operandType;
  if (!CheckExpr(f, operand, &operandType)) {
    return false;
  }

  if (operandType.isInt()) {
    *type = Type::Intish;
    return f.encoder().writeOp(MozOp::I32Neg);
  }

  if (operandType.isMaybeDouble()) {
    *type = Type::Double;
    return f.encoder().writeOp(Op::F64Neg);
  }

  if (operandType.isMaybeFloat()) {
    *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,
                             Type* type) {
  MOZ_ASSERT(expr->isKind(ParseNodeKind::BitNotExpr));
  ParseNode* operand = UnaryKid(expr);

  Type operandType;
  if (!CheckExpr(f, operand, &operandType)) {
    return false;
  }

  if (operandType.isMaybeDouble() || operandType.isMaybeFloat()) {
    *type = Type::Signed;
    Op opcode =
        operandType.isMaybeDouble() ? Op::I32TruncF64S : Op::I32TruncF32S;
    if (!f.prepareCall(expr)) {
      return false;
    }
    return f.encoder().writeOp(opcode);
  }

  if (!operandType.isIntish()) {
    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) {
  MOZ_ASSERT(neg->isKind(ParseNodeKind::BitNotExpr));
  ParseNode* operand = UnaryKid(neg);

  if (operand->isKind(ParseNodeKind::BitNotExpr)) {
    return CheckCoerceToInt(f, operand, type);
  }

  Type operandType;
  if (!CheckExpr(f, operand, &operandType)) {
    return false;
  }

  if (!operandType.isIntish()) {
    return f.failf(operand, "%s is not a subtype of intish",
                   operandType.toChars());
  }

  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) {
  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;
  }

  size_t typeAt;
  if (!f.encoder().writePatchableFixedU7(&typeAt)) {
    return false;
  }

  ParseNode* pn = operands;
  for (; NextNode(pn); pn = NextNode(pn)) {
    if (!CheckAsExprStatement(f, pn)) {
      return false;
    }
  }

  if (!CheckExpr(f, pn, type)) {
    return false;
  }

  f.encoder().patchFixedU7(typeAt, uint8_t(type->toWasmBlockSignatureType()));

  return f.encoder().writeOp(Op::End);
}

template <typename Unit>
static bool CheckConditional(FunctionValidator<Unit>& f, ParseNode* ternary,
                             Type* type) {
  MOZ_ASSERT(ternary->isKind(ParseNodeKind::ConditionalExpr));

  ParseNode* cond = TernaryKid1(ternary);
  ParseNode* thenExpr = TernaryKid2(ternary);
  ParseNode* elseExpr = TernaryKid3(ternary);

  Type condType;
  if (!CheckExpr(f, cond, &condType)) {
    return false;
  }

  if (!condType.isInt()) {
    return f.failf(cond, "%s is not a subtype of int", condType.toChars());
  }

  size_t typeAt;
  if (!f.pushIf(&typeAt)) {
    return false;
  }

  Type thenType;
  if (!CheckExpr(f, thenExpr, &thenType)) {
    return false;
  }

  if (!f.switchToElse()) {
    return false;
  }

  Type elseType;
  if (!CheckExpr(f, elseExpr, &elseType)) {
    return false;
  }

  if (thenType.isInt() && elseType.isInt()) {
    *type = Type::Int;
  } else if (thenType.isDouble() && elseType.isDouble()) {
    *type = Type::Double;
  } else if (thenType.isFloat() && elseType.isFloat()) {
    *type = Type::Float;
  } else {
    return f.failf(
        ternary,
        "then/else branches of conditional must both produce int, float, "
        "double, current types are %s and %s",
        thenType.toChars(), elseType.toChars());
  }

  return f.popIf(typeAt, type->toWasmBlockSignatureType());
}

template <typename Unit>
static bool IsValidIntMultiplyConstant(ModuleValidator<Unit>& m,
                                       ParseNode* expr) {
  if (!IsNumericLiteral(m, expr)) {
    return false;
  }

  NumLit lit = ExtractNumericLiteral(m, expr);
  switch (lit.which()) {
    case NumLit::Fixnum:
    case NumLit::NegativeInt:
      if (Abs(lit.toInt32()) < (uint32_t(1) << 20)) {
        return true;
      }
      return false;
    case NumLit::BigUnsigned:
    case NumLit::Double:
    case NumLit::Float:
    case NumLit::OutOfRangeInt:
      return false;
  }

  MOZ_CRASH("Bad literal");
}

template <typename Unit>
static bool CheckMultiply(FunctionValidator<Unit>& f, ParseNode* star,
                          Type* type) {
  MOZ_ASSERT(star->isKind(ParseNodeKind::MulExpr));
  ParseNode* lhs = MultiplyLeft(star);
  ParseNode* rhs = MultiplyRight(star);

  Type lhsType;
  if (!CheckExpr(f, lhs, &lhsType)) {
    return false;
  }

  Type rhsType;
  if (!CheckExpr(f, rhs, &rhsType)) {
    return false;
  }

  if (lhsType.isInt() && rhsType.isInt()) {
    if (!IsValidIntMultiplyConstant(f.m(), lhs) &&
        !IsValidIntMultiplyConstant(f.m(), rhs)) {
      return f.fail(
          star,
          "one arg to int multiply must be a small (-2^20, 2^20) int literal");
    }
    *type = Type::Intish;
    return f.encoder().writeOp(Op::I32Mul);
  }

  if (lhsType.isMaybeDouble() && rhsType.isMaybeDouble()) {
    *type = Type::Double;
    return f.encoder().writeOp(Op::F64Mul);
  }

  if (lhsType.isMaybeFloat() && rhsType.isMaybeFloat()) {
    *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) {
  AutoCheckRecursionLimit recursion(f.fc());
  if (!recursion.checkDontReport(f.fc())) {
    return f.m().failOverRecursed();
  }

  MOZ_ASSERT(expr->isKind(ParseNodeKind::AddExpr) ||
             expr->isKind(ParseNodeKind::SubExpr));
  ParseNode* lhs = AddSubLeft(expr);
  ParseNode* rhs = AddSubRight(expr);

  Type lhsType, rhsType;
  unsigned lhsNumAddOrSub, rhsNumAddOrSub;

  if (lhs->isKind(ParseNodeKind::AddExpr) ||
      lhs->isKind(ParseNodeKind::SubExpr)) {
    if (!CheckAddOrSub(f, lhs, &lhsType, &lhsNumAddOrSub)) {
      return false;
    }
    if (lhsType == Type::Intish) {
      lhsType = Type::Int;
    }
  } else {
    if (!CheckExpr(f, lhs, &lhsType)) {
      return false;
    }
    lhsNumAddOrSub = 0;
  }

  if (rhs->isKind(ParseNodeKind::AddExpr) ||
      rhs->isKind(ParseNodeKind::SubExpr)) {
    if (!CheckAddOrSub(f, rhs, &rhsType, &rhsNumAddOrSub)) {
      return false;
    }
    if (rhsType == Type::Intish) {
      rhsType = Type::Int;
    }
  } else {
    if (!CheckExpr(f, rhs, &rhsType)) {
      return false;
    }
    rhsNumAddOrSub = 0;
  }

  unsigned numAddOrSub = lhsNumAddOrSub + rhsNumAddOrSub + 1;
  if (numAddOrSub > (1 << 20)) {
    return f.fail(expr, "too many + or - without intervening coercion");
  }

  if (lhsType.isInt() && rhsType.isInt()) {
    if (!f.encoder().writeOp(
            expr->isKind(ParseNodeKind::AddExpr) ? Op::I32Add : Op::I32Sub)) {
      return false;
    }
    *type = Type::Intish;
  } else if (lhsType.isMaybeDouble() && rhsType.isMaybeDouble()) {
    if (!f.encoder().writeOp(
            expr->isKind(ParseNodeKind::AddExpr) ? Op::F64Add : Op::F64Sub)) {
      return false;
    }
    *type = Type::Double;
  } else if (lhsType.isMaybeFloat() && rhsType.isMaybeFloat()) {
    if (!f.encoder().writeOp(
            expr->isKind(ParseNodeKind::AddExpr) ? Op::F32Add : Op::F32Sub)) {
      return false;
    }
    *type = Type::Floatish;
  } else {
    return f.failf(
        expr,
        "operands to + or - must both be int, float? or double?, got %s and %s",
        lhsType.toChars(), rhsType.toChars());
  }

  if (numAddOrSubOut) {
    *numAddOrSubOut = numAddOrSub;
  }
  return true;
}

template <typename Unit>
static bool CheckDivOrMod(FunctionValidator<Unit>& f, ParseNode* expr,
                          Type* type) {
  MOZ_ASSERT(expr->isKind(ParseNodeKind::DivExpr) ||
             expr->isKind(ParseNodeKind::ModExpr));

  ParseNode* lhs = DivOrModLeft(expr);
  ParseNode* rhs = DivOrModRight(expr);

  Type lhsType, rhsType;
  if (!CheckExpr(f, lhs, &lhsType)) {
    return false;
  }
  if (!CheckExpr(f, rhs, &rhsType)) {
    return false;
  }

  if (lhsType.isMaybeDouble() && rhsType.isMaybeDouble()) {
    *type = Type::Double;
    if (expr->isKind(ParseNodeKind::DivExpr)) {
      return f.encoder().writeOp(Op::F64Div);
    }
    return f.encoder().writeOp(MozOp::F64Mod);
  }

  if (lhsType.isMaybeFloat() && rhsType.isMaybeFloat()) {
    *type = Type::Floatish;
    if (expr->isKind(ParseNodeKind::DivExpr)) {
      return f.encoder().writeOp(Op::F32Div);
    }
    return f.fail(expr, "modulo cannot receive float arguments");
  }

  if (lhsType.isSigned() && rhsType.isSigned()) {
    *type = Type::Intish;
    return f.encoder().writeOp(
        expr->isKind(ParseNodeKind::DivExpr) ? Op::I32DivS : Op::I32RemS);
  }

  if (lhsType.isUnsigned() && rhsType.isUnsigned()) {
    *type = Type::Intish;
    return f.encoder().writeOp(
        expr->isKind(ParseNodeKind::DivExpr) ? Op::I32DivU : Op::I32RemU);
  }

  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) {
  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);
  ParseNode* rhs = ComparisonRight(comp);

  Type lhsType, rhsType;
  if (!CheckExpr(f, lhs, &lhsType)) {
    return false;
  }
  if (!CheckExpr(f, rhs, &rhsType)) {
    return false;
  }

  if (!(lhsType.isSigned() && rhsType.isSigned()) &&
      !(lhsType.isUnsigned() && rhsType.isUnsigned()) &&
      !(lhsType.isDouble() && rhsType.isDouble()) &&
      !(lhsType.isFloat() && rhsType.isFloat())) {
    return f.failf(comp,
                   "arguments to a comparison must both be signed, unsigned, "
                   "floats or doubles; "
                   "%s and %s are given",
                   lhsType.toChars(), rhsType.toChars());
  }

  Op stmt;
  if (lhsType.isSigned() && rhsType.isSigned()) {
    switch (comp->getKind()) {
      case ParseNodeKind::EqExpr:
        stmt = Op::I32Eq;
        break;
      case ParseNodeKind::NeExpr:
        stmt = Op::I32Ne;
        break;
      case ParseNodeKind::LtExpr:
        stmt = Op::I32LtS;
        break;
      case ParseNodeKind::LeExpr:
        stmt = Op::I32LeS;
        break;
      case ParseNodeKind::GtExpr:
        stmt = Op::I32GtS;
        break;
      case ParseNodeKind::GeExpr:
        stmt = Op::I32GeS;
        break;
      default:
        MOZ_CRASH("unexpected comparison op");
    }
  } else if (lhsType.isUnsigned() && rhsType.isUnsigned()) {
    switch (comp->getKind()) {
      case ParseNodeKind::EqExpr:
        stmt = Op::I32Eq;
        break;
      case ParseNodeKind::NeExpr:
        stmt = Op::I32Ne;
        break;
      case ParseNodeKind::LtExpr:
        stmt = Op::I32LtU;
        break;
      case ParseNodeKind::LeExpr:
        stmt = Op::I32LeU;
        break;
      case ParseNodeKind::GtExpr:
        stmt = Op::I32GtU;
        break;
      case ParseNodeKind::GeExpr:
        stmt = Op::I32GeU;
        break;
      default:
        MOZ_CRASH("unexpected comparison op");
    }
  } else if (lhsType.isDouble()) {
    switch (comp->getKind()) {
      case ParseNodeKind::EqExpr:
        stmt = Op::F64Eq;
        break;
      case ParseNodeKind::NeExpr:
        stmt = Op::F64Ne;
        break;
      case ParseNodeKind::LtExpr:
        stmt = Op::F64Lt;
        break;
      case ParseNodeKind::LeExpr:
        stmt = Op::F64Le;
        break;
      case ParseNodeKind::GtExpr:
        stmt = Op::F64Gt;
        break;
      case ParseNodeKind::GeExpr:
        stmt = Op::F64Ge;
        break;
      default:
        MOZ_CRASH("unexpected comparison op");
    }
  } else if (lhsType.isFloat()) {
    switch (comp->getKind()) {
      case ParseNodeKind::EqExpr:
        stmt = Op::F32Eq;
        break;
      case ParseNodeKind::NeExpr:
        stmt = Op::F32Ne;
        break;
      case ParseNodeKind::LtExpr:
        stmt = Op::F32Lt;
        break;
      case ParseNodeKind::LeExpr:
        stmt = Op::F32Le;
        break;
      case ParseNodeKind::GtExpr:
        stmt = Op::F32Gt;
        break;
      case ParseNodeKind::GeExpr:
        stmt = Op::F32Ge;
        break;
      default:
        MOZ_CRASH("unexpected comparison op");
    }
  } 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) {
  ParseNode* lhs = BitwiseLeft(bitwise);
  ParseNode* rhs = BitwiseRight(bitwise);

  int32_t identityElement;
  bool onlyOnRight;
  switch (bitwise->getKind()) {
    case ParseNodeKind::BitOrExpr:
      identityElement = 0;
      onlyOnRight = false;
      *type = Type::Signed;
      break;
    case ParseNodeKind::BitAndExpr:
      identityElement = -1;
      onlyOnRight = false;
      *type = Type::Signed;
      break;
    case ParseNodeKind::BitXorExpr:
      identityElement = 0;
      onlyOnRight = false;
      *type = Type::Signed;
      break;
    case ParseNodeKind::LshExpr:
      identityElement = 0;
      onlyOnRight = true;
      *type = Type::Signed;
      break;
    case ParseNodeKind::RshExpr:
      identityElement = 0;
      onlyOnRight = true;
      *type = Type::Signed;
      break;
    case ParseNodeKind::UrshExpr:
      identityElement = 0;
      onlyOnRight = true;
      *type = Type::Unsigned;
      break;
    default:
      MOZ_CRASH("not a bitwise op");
  }

  uint32_t i;
  if (!onlyOnRight && IsLiteralInt(f.m(), lhs, &i) &&
      i == uint32_t(identityElement)) {
    Type rhsType;
    if (!CheckExpr(f, rhs, &rhsType)) {
      return false;
    }
    if (!rhsType.isIntish()) {
      return f.failf(bitwise, "%s is not a subtype of intish",
                     rhsType.toChars());
    }
    return true;
  }

  if (IsLiteralInt(f.m(), rhs, &i) && i == uint32_t(identityElement)) {
    if (bitwise->isKind(ParseNodeKind::BitOrExpr) &&
        lhs->isKind(ParseNodeKind::CallExpr)) {
      return CheckCoercedCall(f, lhs, Type::Int, type);
    }

    Type lhsType;
    if (!CheckExpr(f, lhs, &lhsType)) {
      return false;
    }
    if (!lhsType.isIntish()) {
      return f.failf(bitwise, "%s is not a subtype of intish",
                     lhsType.toChars());
    }
    return true;
  }

  Type lhsType;
  if (!CheckExpr(f, lhs, &lhsType)) {
    return false;
  }

  Type rhsType;
  if (!CheckExpr(f, rhs, &rhsType)) {
    return false;
  }

  if (!lhsType.isIntish()) {
    return f.failf(lhs, "%s is not a subtype of intish", lhsType.toChars());
  }
  if (!rhsType.isIntish()) {
    return f.failf(rhs, "%s is not a subtype of intish", rhsType.toChars());
  }

  switch (bitwise->getKind()) {
    case ParseNodeKind::BitOrExpr:
      if (!f.encoder().writeOp(Op::I32Or)) return false;
      break;
    case ParseNodeKind::BitAndExpr:
      if (!f.encoder().writeOp(Op::I32And)) return false;
      break;
    case ParseNodeKind::BitXorExpr:
      if (!f.encoder().writeOp(Op::I32Xor)) return false;
      break;
    case ParseNodeKind::LshExpr:
      if (!f.encoder().writeOp(Op::I32Shl)) return false;
      break;
    case ParseNodeKind::RshExpr:
      if (!f.encoder().writeOp(Op::I32ShrS)) return false;
      break;
    case ParseNodeKind::UrshExpr:
      if (!f.encoder().writeOp(Op::I32ShrU)) return false;
      break;
    default:
      MOZ_CRASH("not a bitwise op");
  }

  return true;
}

template <typename Unit>
static bool CheckExpr(FunctionValidator<Unit>& f, ParseNode* expr, Type* type) {
  AutoCheckRecursionLimit recursion(f.fc());
  if (!recursion.checkDontReport(f.fc())) {
    return f.m().failOverRecursed();
  }

  if (IsNumericLiteral(f.m(), expr)) {
    return CheckNumericLiteral(f, expr, type);
  }

  switch (expr->getKind()) {
    case ParseNodeKind::Name:
      return CheckVarRef(f, expr, type);
    case ParseNodeKind::ElemExpr:
      return CheckLoadArray(f, expr, type);
    case ParseNodeKind::AssignExpr:
      return CheckAssign(f, expr, type);
    case ParseNodeKind::PosExpr:
      return CheckPos(f, expr, type);
    case ParseNodeKind::NotExpr:
      return CheckNot(f, expr, type);
    case ParseNodeKind::NegExpr:
      return CheckNeg(f, expr, type);
    case ParseNodeKind::BitNotExpr:
      return CheckBitNot(f, expr, type);
    case ParseNodeKind::CommaExpr:
      return CheckComma(f, expr, type);
    case ParseNodeKind::ConditionalExpr:
      return CheckConditional(f, expr, type);
    case ParseNodeKind::MulExpr:
      return CheckMultiply(f, expr, type);
    case ParseNodeKind::CallExpr:
      return CheckUncoercedCall(f, expr, type);

    case ParseNodeKind::AddExpr:
    case ParseNodeKind::SubExpr:
      return CheckAddOrSub(f, expr, type);

    case ParseNodeKind::DivExpr:
    case ParseNodeKind::ModExpr:
      return CheckDivOrMod(f, expr, type);

    case ParseNodeKind::LtExpr:
    case ParseNodeKind::LeExpr:
    case ParseNodeKind::GtExpr:
    case ParseNodeKind::GeExpr:
    case ParseNodeKind::EqExpr:
    case ParseNodeKind::NeExpr:
      return CheckComparison(f, expr, type);

    case ParseNodeKind::BitOrExpr:
    case ParseNodeKind::BitAndExpr:
    case ParseNodeKind::BitXorExpr:
    case ParseNodeKind::LshExpr:
    case ParseNodeKind::RshExpr:
    case ParseNodeKind::UrshExpr:
      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) {
  if (expr->isKind(ParseNodeKind::CallExpr)) {
    Type ignored;
    return CheckCoercedCall(f, expr, Type::Void, &ignored);
  }

  Type resultType;
  if (!CheckExpr(f, expr, &resultType)) {
    return false;
  }

  if (!resultType.isVoid()) {
    if (!f.encoder().writeOp(Op::Drop)) {
      return false;
    }
  }

  return true;
}

template <typename Unit>
static bool CheckExprStatement(FunctionValidator<Unit>& 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) {
  uint32_t maybeLit;
  if (IsLiteralInt(f.m(), cond, &maybeLit) && maybeLit) {
    return true;
  }

  Type condType;
  if (!CheckExpr(f, cond, &condType)) {
    return false;
  }
  if (!condType.isInt()) {
    return f.failf(cond, "%s is not a subtype of int", condType.toChars());
  }

  if (!f.encoder().writeOp(Op::I32Eqz)) {
    return false;
  }

  // brIf (i32.eqz $f) $out
  return f.writeBreakIf();
}

template <typename Unit>
static bool CheckWhile(FunctionValidator<Unit>& f, ParseNode* whileStmt,
                       const LabelVector* labels = nullptr) {
  MOZ_ASSERT(whileStmt->isKind(ParseNodeKind::WhileStmt));
  ParseNode* cond = BinaryLeft(whileStmt);
  ParseNode* body = BinaryRight(whileStmt);

  // A while loop `while(#cond) #body` is equivalent to:
  // (block $after_loop
  //    (loop $top
  //       (brIf $after_loop (i32.eq 0 #cond))
  //       #body
  //       (br $top)
  //    )
  // )
  if (labels && !f.addLabels(*labels, 0, 1)) {
    return false;
  }

  if (!f.pushLoop()) {
    return false;
  }

  if (!CheckLoopConditionOnEntry(f, cond)) {
    return false;
  }
  if (!CheckStatement(f, body)) {
    return false;
  }
  if (!f.writeContinue()) {
    return false;
  }

  if (!f.popLoop()) {
    return false;
  }
  if (labels) {
    f.removeLabels(*labels);
  }
  return true;
}

template <typename Unit>
static bool CheckFor(FunctionValidator<Unit>& f, ParseNode* forStmt,
                     const LabelVector* labels = nullptr) {
  MOZ_ASSERT(forStmt->isKind(ParseNodeKind::ForStmt));
  ParseNode* forHead = BinaryLeft(forStmt);
  ParseNode* body = BinaryRight(forStmt);

  if (!forHead->isKind(ParseNodeKind::ForHead)) {
    return f.fail(forHead, "unsupported for-loop statement");
  }

  ParseNode* maybeInit = TernaryKid1(forHead);
  ParseNode* maybeCond = TernaryKid2(forHead);
  ParseNode* maybeInc = TernaryKid3(forHead);

  // A for-loop `for (#init; #cond; #inc) #body` is equivalent to:
  // (block                                               // depth X
  //   (#init)
  //   (block $after_loop                                 // depth X+1 (block)
  //     (loop $loop_top                                  // depth X+2 (loop)
  //       (brIf $after (eq 0 #cond))
  //       (block $after_body #body)                      // depth X+3
  //       #inc
  //       (br $loop_top)
  //     )
  //   )
  // )
  // A break in the body should break out to $after_loop, i.e. depth + 1.
  // A continue in the body should break out to $after_body, i.e. depth + 3.
  if (labels && !f.addLabels(*labels, 1, 3)) {
    return false;
  }

  if (!f.pushUnbreakableBlock()) {
    return false;
  }

  if (maybeInit && !CheckAsExprStatement(f, maybeInit)) {
    return false;
  }

  {
    if (!f.pushLoop()) {
      return false;
    }

    if (maybeCond && !CheckLoopConditionOnEntry(f, maybeCond)) {
      return false;
    }

    {
      // Continuing in the body should just break out to the increment.
      if (!f.pushContinuableBlock()) {
        return false;
      }
      if (!CheckStatement(f, body)) {
        return false;
      }
      if (!f.popContinuableBlock()) {
        return false;
      }
    }

    if (maybeInc && !CheckAsExprStatement(f, maybeInc)) {
      return false;
    }

    if (!f.writeContinue()) {
      return false;
    }
    if (!f.popLoop()) {
      return false;
    }
  }

  if (!f.popUnbreakableBlock()) {
    return false;
  }

  if (labels) {
    f.removeLabels(*labels);
  }

  return true;
}

template <typename Unit>
static bool CheckDoWhile(FunctionValidator<Unit>& f, ParseNode* whileStmt,
                         const LabelVector* labels = nullptr) {
  MOZ_ASSERT(whileStmt->isKind(ParseNodeKind::DoWhileStmt));
  ParseNode* body = BinaryLeft(whileStmt);
  ParseNode* cond = BinaryRight(whileStmt);

  // A do-while loop `do { #body } while (#cond)` is equivalent to:
  // (block $after_loop           // depth X
  //   (loop $top                 // depth X+1
  //     (block #body)            // depth X+2
  //     (brIf #cond $top)
  //   )
  // )
  // A break should break out of the entire loop, i.e. at depth 0.
  // A continue should break out to the condition, i.e. at depth 2.
  if (labels && !f.addLabels(*labels, 0, 2)) {
    return false;
  }

  if (!f.pushLoop()) {
    return false;
  }

  {
    // An unlabeled continue in the body should break out to the condition.
    if (!f.pushContinuableBlock()) {
      return false;
    }
    if (!CheckStatement(f, body)) {
      return false;
    }
    if (!f.popContinuableBlock()) {
      return false;
    }
  }

  Type condType;
  if (!CheckExpr(f, cond, &condType)) {
    return false;
  }
  if (!condType.isInt()) {
    return f.failf(cond, "%s is not a subtype of int", condType.toChars());
  }

  if (!f.writeContinueIf()) {
    return false;
  }

  if (!f.popLoop()) {
    return false;
  }
  if (labels) {
    f.removeLabels(*labels);
  }
  return true;
}

template <typename Unit>
static bool CheckStatementList(FunctionValidator<Unit>& f, ParseNode*,
                               const LabelVector* = nullptr);

template <typename Unit>
static bool CheckLabel(FunctionValidator<Unit>& f, ParseNode* labeledStmt) {
  MOZ_ASSERT(labeledStmt->isKind(ParseNodeKind::LabelStmt));

  LabelVector labels;
  ParseNode* innermost = labeledStmt;
  do {
    if (!labels.append(LabeledStatementLabel(innermost))) {
      return false;
    }
    innermost = LabeledStatementStatement(innermost);
  } while (innermost->getKind() == ParseNodeKind::LabelStmt);

  switch (innermost->getKind()) {
    case ParseNodeKind::ForStmt:
      return CheckFor(f, innermost, &labels);
    case ParseNodeKind::DoWhileStmt:
      return CheckDoWhile(f, innermost, &labels);
    case ParseNodeKind::WhileStmt:
      return CheckWhile(f, innermost, &labels);
    case ParseNodeKind::StatementList:
      return CheckStatementList(f, innermost, &labels);
    default:
      break;
  }

  if (!f.pushUnbreakableBlock(&labels)) {
    return false;
  }

  if (!CheckStatement(f, innermost)) {
    return false;
  }

  return f.popUnbreakableBlock(&labels);
}

template <typename Unit>
static bool CheckIf(FunctionValidator<Unit>& f, ParseNode* ifStmt) {
  uint32_t numIfEnd = 1;

recurse:
  MOZ_ASSERT(ifStmt->isKind(ParseNodeKind::IfStmt));
  ParseNode* cond = TernaryKid1(ifStmt);
  ParseNode* thenStmt = TernaryKid2(ifStmt);
  ParseNode* elseStmt = TernaryKid3(ifStmt);

  Type condType;
  if (!CheckExpr(f, cond, &condType)) {
    return false;
  }
  if (!condType.isInt()) {
    return f.failf(cond, "%s is not a subtype of int", condType.toChars());
  }

  size_t typeAt;
  if (!f.pushIf(&typeAt)) {
    return false;
  }

  f.setIfType(typeAt, TypeCode::BlockVoid);

  if (!CheckStatement(f, thenStmt)) {
    return false;
  }

  if (elseStmt) {
    if (!f.switchToElse()) {
      return false;
    }

    if (elseStmt->isKind(ParseNodeKind::IfStmt)) {
      ifStmt = elseStmt;
      if (numIfEnd++ == UINT32_MAX) {
        return false;
      }
      goto recurse;
    }

    if (!CheckStatement(f, elseStmt)) {
      return false;
    }
  }

  for (uint32_t i = 0; i != numIfEnd; ++i) {
    if (!f.popIf()) {
      return false;
    }
  }

  return true;
}

static bool CheckCaseExpr(FunctionValidatorShared& 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()) {
    case NumLit::Fixnum:
    case NumLit::NegativeInt:
      *value = lit.toInt32();
      break;
    case NumLit::OutOfRangeInt:
    case NumLit::BigUnsigned:
      return f.fail(caseExpr, "switch case expression out of integer range");
    case NumLit::Double:
    case NumLit::Float:
      return f.fail(caseExpr,
                    "switch case expression must be an integer literal");
  }

  return true;
}

static bool CheckDefaultAtEnd(FunctionValidatorShared& 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,
                             int32_t* low, int32_t* high,
                             uint32_t* tableLength) {
  if (IsDefaultCase(stmt)) {
    *low = 0;
    *high = -1;
    *tableLength = 0;
    return true;
  }

  int32_t i = 0;
  if (!CheckCaseExpr(f, CaseExpr(stmt), &i)) {
    return false;
  }

  *low = *high = i;

  ParseNode* initialStmt = stmt;
  for (stmt = NextNode(stmt); stmt && !IsDefaultCase(stmt);
       stmt = NextNode(stmt)) {
    int32_t i = 0;
    if (!CheckCaseExpr(f, CaseExpr(stmt), &i)) {
      return false;
    }

    *low = std::min(*low, i);
    *high = std::max(*high, i);
  }

  int64_t i64 = (int64_t(*high) - int64_t(*low)) + 1;
  if (i64 > MaxBrTableElems) {
    return f.fail(
        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) {
  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());
  }
  return true;
}

// A switch will be constructed as:
// - the default block wrapping all the other blocks, to be able to break
// out of the switch with an unlabeled break statement. It has two statements
// (an inner block and the default expr). asm.js rules require default to be at
// 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) {
  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()) {
      return f.fail(scope, "switch body may not contain lexical declarations");
    }
    switchBody = scope->scopeBody();
  }

  ParseNode* stmt = ListHead(switchBody);
  if (!stmt) {
    if (!CheckSwitchExpr(f, switchExpr)) {
      return false;
    }
    return f.encoder().writeOp(Op::Drop);
  }

  if (!CheckDefaultAtEnd(f, stmt)) {
    return false;
  }

  int32_t low = 0, high = 0;
  uint32_t tableLength = 0;
  if (!CheckSwitchRange(f, stmt, &low, &high, &tableLength)) {
    return false;
  }

  static const uint32_t CASE_NOT_DEFINED = UINT32_MAX;

  Uint32Vector caseDepths;
  if (!caseDepths.appendN(CASE_NOT_DEFINED, tableLength)) {
    return false;
  }

  uint32_t numCases = 0;
  for (ParseNode* s = stmt; s && !IsDefaultCase(s); s = NextNode(s)) {
    int32_t caseValue = ExtractNumericLiteral(f.m(), CaseExpr(s)).toInt32();

    MOZ_ASSERT(caseValue >= low);
    unsigned i = caseValue - low;
    if (caseDepths[i] != CASE_NOT_DEFINED) {
      return f.fail(s, "no duplicate case labels");
    }

    MOZ_ASSERT(numCases != CASE_NOT_DEFINED);
    caseDepths[i] = numCases++;
  }

  // Open the wrapping breakable default block.
  if (!f.pushBreakableBlock()) {
    return false;
  }

  // Open all the case blocks.
  for (uint32_t i = 0; i < numCases; i++) {
    if (!f.pushUnbreakableBlock()) {
      return false;
    }
  }

  // Open the br_table block.
  if (!f.pushUnbreakableBlock()) {
    return false;
  }

  // The default block is the last one.
  uint32_t defaultDepth = numCases;

  // Subtract lowest case value, so that all the cases start from 0.
  if (low) {
    if (!CheckSwitchExpr(f, switchExpr)) {
      return false;
    }
    if (!f.writeInt32Lit(low)) {
      return false;
    }
    if (!f.encoder().writeOp(Op::I32Sub)) {
      return false;
    }
  } else {
    if (!CheckSwitchExpr(f, switchExpr)) {
      return false;
    }
  }

  // Start the br_table block.
  if (!f.encoder().writeOp(Op::BrTable)) {
    return false;
  }

  // Write the number of cases (tableLength - 1 + 1 (default)).
  // Write the number of cases (tableLength - 1 + 1 (default)).
  if (!f.encoder().writeVarU32(tableLength)) {
    return false;
  }

  // Each case value describes the relative depth to the actual block. When
  // a case is not explicitly defined, it goes to the default.
  for (size_t i = 0; i < tableLength; i++) {
    uint32_t target =
        caseDepths[i] == CASE_NOT_DEFINED ? defaultDepth : caseDepths[i];
    if (!f.encoder().writeVarU32(target)) {
      return false;
    }
  }

  // Write the default depth.
  if (!f.encoder().writeVarU32(defaultDepth)) {
    return false;
  }

  // Our br_table is done. Close its block, write the cases down in order.
  if (!f.popUnbreakableBlock()) {
    return false;
  }

  for (; stmt && !IsDefaultCase(stmt); stmt = NextNode(stmt)) {
    if (!CheckStatement(f, CaseBody(stmt))) {
      return false;
    }
    if (!f.popUnbreakableBlock()) {
      return false;
    }
  }

  // Write the default block.
  if (stmt && IsDefaultCase(stmt)) {
    if (!CheckStatement(f, CaseBody(stmt))) {
      return false;
    }
  }

  // Close the wrapping block.
  return f.popBreakableBlock();
}

static bool CheckReturnType(FunctionValidatorShared& f, ParseNode* usepn,
                            Type ret) {
  Maybe<ValType> type = ret.canonicalToReturnType();

  if (!f.hasAlreadyReturned()) {
    f.setReturnedType(type);
    return true;
  }

  if (f.returnedType() != type) {
    return f.failf(usepn, "%s incompatible with previous return of type %s",
                   ToString(type, nullptr).get(),
                   ToString(f.returnedType(), nullptr).get());
  }

  return true;
}

template <typename Unit>
static bool CheckReturn(FunctionValidator<Unit>& f, ParseNode* returnStmt) {
  ParseNode* expr = ReturnExpr(returnStmt);

  if (!expr) {
    if (!CheckReturnType(f, returnStmt, Type::Void)) {
      return false;
    }
  } else {
    Type type;
    if (!CheckExpr(f, expr, &type)) {
      return false;
    }

    if (!type.isReturnType()) {
      return f.failf(expr, "%s is not a valid return type", type.toChars());
    }

    if (!CheckReturnType(f, expr, Type::canonicalize(type))) {
      return false;
    }
  }

  return f.encoder().writeOp(Op::Return);
}

template <typename Unit>
static bool CheckStatementList(FunctionValidator<Unit>& f, ParseNode* stmtList,
                               const LabelVector* labels /*= nullptr */) {
  MOZ_ASSERT(stmtList->isKind(ParseNodeKind::StatementList));

  if (!f.pushUnbreakableBlock(labels)) {
    return false;
  }

  for (ParseNode* stmt = ListHead(stmtList); stmt; stmt = NextNode(stmt)) {
    if (!CheckStatement(f, stmt)) {
      return false;
    }
  }

  return f.popUnbreakableBlock(labels);
}

template <typename Unit>
static bool CheckLexicalScope(FunctionValidator<Unit>& f, ParseNode* node) {
  LexicalScopeNode* lexicalScope = &node->as<LexicalScopeNode>();
  if (!lexicalScope->isEmptyScope()) {
    return f.fail(lexicalScope, "cannot have 'let' or 'const' declarations");
  }

  return CheckStatement(f, lexicalScope->scopeBody());
}

static bool CheckBreakOrContinue(FunctionValidatorShared& f, bool isBreak,
                                 ParseNode* stmt) {
  if (TaggedParserAtomIndex maybeLabel = LoopControlMaybeLabel(stmt)) {
    return f.writeLabeledBreakOrContinue(maybeLabel, isBreak);
  }
  return f.writeUnlabeledBreakOrContinue(isBreak);
}

template <typename Unit>
static bool CheckStatement(FunctionValidator<Unit>& f, ParseNode* stmt) {
  AutoCheckRecursionLimit recursion(f.fc());
  if (!recursion.checkDontReport(f.fc())) {
    return f.m().failOverRecursed();
  }

  switch (stmt->getKind()) {
    case ParseNodeKind::EmptyStmt:
      return true;
    case ParseNodeKind::ExpressionStmt:
      return CheckExprStatement(f, stmt);
    case ParseNodeKind::WhileStmt:
      return CheckWhile(f, stmt);
    case ParseNodeKind::ForStmt:
      return CheckFor(f, stmt);
    case ParseNodeKind::DoWhileStmt:
      return CheckDoWhile(f, stmt);
    case ParseNodeKind::LabelStmt:
      return CheckLabel(f, stmt);
    case ParseNodeKind::IfStmt:
      return CheckIf(f, stmt);
    case ParseNodeKind::SwitchStmt:
      return CheckSwitch(f, stmt);
    case ParseNodeKind::ReturnStmt:
      return CheckReturn(f, stmt);
    case ParseNodeKind::StatementList:
      return CheckStatementList(f, stmt);
    case ParseNodeKind::BreakStmt:
      return CheckBreakOrContinue(f, true, stmt);
    case ParseNodeKind::ContinueStmt:
      return CheckBreakOrContinue(f, false, stmt);
    case ParseNodeKind::LexicalScope:
      return CheckLexicalScope(f, stmt);
    default:;
  }

  return f.fail(stmt, "unexpected statement kind");
}

template <typename Unit>
static bool ParseFunction(ModuleValidator<Unit>& m, FunctionNode** funNodeOut,
                          unsigned* line) {
  auto& tokenStream = m.tokenStream();

  tokenStream.consumeKnownToken(TokenKind::Function,
                                TokenStreamShared::SlashIsRegExp);

  auto& anyChars = tokenStream.anyCharsAccess();
  uint32_t toStringStart = anyChars.currentToken().pos.begin;
  *line = anyChars.lineNumber(anyChars.lineToken(toStringStart));

  TokenKind tk;
  if (!tokenStream.getToken(&tk, TokenStreamShared::SlashIsRegExp)) {
    return false;
  }
  if (tk == TokenKind::Mul) {
    return m.failCurrentOffset("unexpected generator function");
  }
  if (!TokenKindIsPossibleIdentifier(tk)) {
    return false;  // The regular parser will throw a SyntaxError, no need to
                   // m.fail.
  }

  TaggedParserAtomIndex name = m.parser().bindingIdentifier(YieldIsName);
  if (!name) {
    return false;
  }

  FunctionNode* funNode;
  MOZ_TRY_VAR_OR_RETURN(funNode,
                        m.parser().handler_.newFunction(
                            FunctionSyntaxKind::Statement, m.parser().pos()),
                        false);

  ParseContext* outerpc = m.parser().pc_;
  Directives directives(outerpc);
  FunctionFlags flags(FunctionFlags::INTERPRETED_NORMAL);
  FunctionBox* funbox = m.parser().newFunctionBox(
      funNode, name, flags, toStringStart, directives,
      GeneratorKind::NotGenerator, FunctionAsyncKind::SyncFunction);
  if (!funbox) {
    return false;
  }
  funbox->initWithEnclosingParseContext(outerpc, FunctionSyntaxKind::Statement);

  Directives newDirectives = directives;
  SourceParseContext funpc(&m.parser(), funbox, &newDirectives);
  if (!funpc.init()) {
    return false;
  }

  if (!m.parser().functionFormalParametersAndBody(
          InAllowed, YieldIsName, &funNode, FunctionSyntaxKind::Statement)) {
    if (anyChars.hadError() || directives == newDirectives) {
      return false;
    }

    return m.fail(funNode, "encountered new directive in function");
  }

  MOZ_ASSERT(!anyChars.hadError());
  MOZ_ASSERT(directives == newDirectives);

  *funNodeOut = funNode;
  return true;
}

template <typename Unit>
static bool CheckFunction(ModuleValidator<Unit>& m) {
  // asm.js modules can be quite large when represented as parse trees so pop
  // the backing LifoAlloc after parsing/compiling each function. Release the
  // parser's lifo memory after the last use of a parse node.
  frontend::ParserBase::Mark mark = m.parser().mark();
  auto releaseMark =
      mozilla::MakeScopeExit([&m, &mark] { m.parser().release(mark); });

  FunctionNode* funNode = nullptr;
  unsigned line = 0;
  if (!ParseFunction(m, &funNode, &line)) {
    return false;
  }

  if (!CheckFunctionHead(m, funNode)) {
    return false;
  }

  FunctionValidator<Unit> f(m, funNode);

  ParseNode* stmtIter = ListHead(FunctionStatementList(funNode));

  if (!CheckProcessingDirectives(m, &stmtIter)) {
    return false;
  }

  ValTypeVector args;
  if (!CheckArguments(f, &stmtIter, &args)) {
    return false;
  }

  if (!CheckVariables(f, &stmtIter)) {
    return false;
  }

  ParseNode* lastNonEmptyStmt = nullptr;
  for (; stmtIter; stmtIter = NextNonEmptyStatement(stmtIter)) {
    lastNonEmptyStmt = stmtIter;
    if (!CheckStatement(f, stmtIter)) {
      return false;
    }
  }

  if (!CheckFinalReturn(f, lastNonEmptyStmt)) {
    return false;
  }

  ValTypeVector results;
  if (f.returnedType()) {
    if (!results.append(f.returnedType().ref())) {
      return false;
    }
  }

  FuncType sig(std::move(args), std::move(results));

  ModuleValidatorShared::Func* func = nullptr;
  if (!CheckFunctionSignature(m, funNode, std::move(sig), 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) {
  for (unsigned i = 0; i < m.numFuncDefs(); i++) {
    const ModuleValidatorShared::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) {
  while (true) {
    TokenKind tk;
    if (!PeekToken(m.parser(), &tk)) {
      return false;
    }

    if (tk != TokenKind::Function) {
      break;
    }

    if (!CheckFunction(m)) {
      return false;
    }
  }

  return CheckAllFunctionsDefined(m);
}

template <typename Unit>
static bool CheckFuncPtrTable(ModuleValidator<Unit>& m, ParseNode* decl) {
  if (!decl->isKind(ParseNodeKind::AssignExpr)) {
    return m.fail(decl, "function-pointer table must have initializer");
  }
  AssignmentNode* assignNode = &decl->as<AssignmentNode>();

  ParseNode* var = assignNode->left();

  if (!var->isKind(ParseNodeKind::Name)) {
    return m.fail(var, "function-pointer table name is not a plain name");
  }

  ParseNode* arrayLiteral = assignNode->right();

  if (!arrayLiteral->isKind(ParseNodeKind::ArrayExpr)) {
    return m.fail(
        var, "function-pointer table's initializer must be an array literal");
  }

  unsigned length = ListLength(arrayLiteral);

  if (!IsPowerOfTwo(length)) {
    return m.failf(arrayLiteral,
                   "function-pointer table length must be a power of 2 (is %u)",
                   length);
  }

  unsigned mask = length - 1;

  Uint32Vector elemFuncDefIndices;
  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");
    }

    TaggedParserAtomIndex funcName = elem->as<NameNode>().name();
    const ModuleValidatorShared::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.codeMeta()->types->type(func->sigIndex()).funcType();
    if (sig) {
      if (!FuncType::strictlyEquals(*sig, funcSig)) {
        return m.fail(elem, "all functions in table must have same signature");
      }
    } else {
      sig = &funcSig;
    }

    if (!elemFuncDefIndices.append(func->funcDefIndex())) {
      return false;
    }
  }

  FuncType copy;
  if (!copy.clone(*sig)) {
    return false;
  }

  uint32_t tableIndex;
  if (!CheckFuncPtrTableAgainstExisting(m, var, var->as<NameNode>().name(),
                                        std::move(copy), mask, &tableIndex)) {
    return false;
  }

  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) {
  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);
    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,
    TaggedParserAtomIndex maybeFieldName = TaggedParserAtomIndex::null()) {
  if (!pn->isKind(ParseNodeKind::Name)) {
    return m.fail(pn, "expected name of exported function");
  }

  TaggedParserAtomIndex funcName = pn->as<NameNode>().name();
  const ModuleValidatorShared::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) {
  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");
    }

    TaggedParserAtomIndex fieldName = ObjectNormalFieldName(pn);

    ParseNode* initNode = ObjectNormalFieldInitializer(pn);
    if (!initNode->isKind(ParseNodeKind::Name)) {
      return m.fail(
          initNode,
          "initializer of exported object literal must be name of function");
    }

    if (!CheckModuleExportFunction(m, initNode, fieldName)) {
      return false;
    }
  }

  return true;
}

template <typename Unit>
static bool CheckModuleReturn(ModuleValidator<Unit>& m) {
  TokenKind tk;
  if (!GetToken(m.parser(), &tk)) {
    return false;
  }
  auto& ts = m.parser().tokenStream;
  if (tk != TokenKind::Return) {
    return m.failCurrentOffset(
        (tk == TokenKind::RightCurly || tk == TokenKind::Eof)
            ? "expecting return statement"
            : "invalid asm.js. statement");
  }
  ts.anyCharsAccess().ungetToken();

  ParseNode* returnStmt;
  MOZ_TRY_VAR_OR_RETURN(returnStmt, m.parser().statementListItem(YieldIsName),
                        false);

  ParseNode* returnExpr = ReturnExpr(returnStmt);
  if (!returnExpr) {
    return m.fail(returnStmt, "export statement must return something");
  }

  if (returnExpr->isKind(ParseNodeKind::ObjectExpr)) {
    if (!CheckModuleExportObject(m, returnExpr)) {
      return false;
    }
  } else {
    if (!CheckModuleExportFunction(m, returnExpr)) {
      return false;
    }
  }

  return true;
}

template <typename Unit>
static bool CheckModuleEnd(ModuleValidator<Unit>& m) {
  TokenKind tk;
  if (!GetToken(m.parser(), &tk)) {
    return false;
  }

  if (tk != TokenKind::Eof && tk != TokenKind::RightCurly) {
    return m.failCurrentOffset(
        "top-level export (return) must be the last statement");
  }

  m.parser().tokenStream.anyCharsAccess().ungetToken();
  return true;
}

template <typename Unit>
static SharedModule CheckModule(FrontendContext* fc,
                                ParserAtomsTable& parserAtoms,
                                AsmJSParser<Unit>& parser, ParseNode* stmtList,
                                unsigned* time) {
  int64_t before = PRMJ_Now();

  ScriptedCaller scriptedCaller;
  if (parser.ss->filename()) {
    scriptedCaller.line = 0;  // unused
    scriptedCaller.filename = DuplicateString(parser.ss->filename());
    if (!scriptedCaller.filename) {
      return nullptr;
    }
  }

  // The default options are fine for asm.js
  SharedCompileArgs args =
      CompileArgs::buildForAsmJS(std::move(scriptedCaller));
  if (!args) {
    ReportOutOfMemory(fc);
    return nullptr;
  }

  MutableModuleMetadata moduleMeta = js_new<ModuleMetadata>();
  if (!moduleMeta || !moduleMeta->init(*args, ModuleKind::AsmJS)) {
    return nullptr;
  }
  MutableCodeMetadata codeMeta = moduleMeta->codeMeta;

  FunctionNode* moduleFunctionNode = parser.pc_->functionBox()->functionNode;

  ModuleValidator<Unit> m(fc, parserAtoms, moduleMeta, codeMeta, parser,
                          moduleFunctionNode);
  if (!m.init()) {
    return nullptr;
  }

  if (!CheckFunctionHead(m, moduleFunctionNode)) {
    return nullptr;
  }

  if (!CheckModuleArguments(m, moduleFunctionNode)) {
    return nullptr;
  }

  if (!CheckPrecedingStatements(m, stmtList)) {
    return nullptr;
  }

  if (!CheckModuleProcessingDirectives(m)) {
    return nullptr;
  }

  if (!CheckModuleGlobals(m)) {
    return nullptr;
  }

  if (!m.startFunctionBodies()) {
    return nullptr;
  }

  if (!CheckFunctions(m)) {
    return nullptr;
  }

  if (!CheckFuncPtrTables(m)) {
    return nullptr;
  }

  if (!CheckModuleReturn(m)) {
    return nullptr;
  }

  if (!CheckModuleEnd(m)) {
    return nullptr;
  }

  SharedModule module = m.finish();
  if (!module) {
    return nullptr;
  }

  *time = (PRMJ_Now() - before) / PRMJ_USEC_PER_MSEC;
  return module;
}

/*****************************************************************************/
// Link-time validation

static bool LinkFail(JSContext* cx, const char* str) {
  WarnNumberASCII(cx, JSMSG_USE_ASM_LINK_FAIL, str);
  return false;
}

static bool IsMaybeWrappedScriptedProxy(JSObject* obj) {
  JSObject* unwrapped = UncheckedUnwrap(obj);
  return unwrapped && IsScriptedProxy(unwrapped);
}

static bool GetDataProperty(JSContext* cx, HandleValue objVal,
                            Handle<JSAtom*> field, MutableHandleValue v) {
  if (!objVal.isObject()) {
    return LinkFail(cx, "accessing property of non-object");
  }

  RootedObject obj(cx, &objVal.toObject());
  if (IsMaybeWrappedScriptedProxy(obj)) {
    return LinkFail(cx, "accessing property of a Proxy");
  }

  RootedId id(cx, AtomToId(field));
  Rooted<mozilla::Maybe<PropertyDescriptor>> desc(cx);
  RootedObject holder(cx);
  if (!GetPropertyDescriptor(cx, obj, id, &desc, &holder)) {
    return false;
  }

  if (!desc.isSome()) {
    return LinkFail(cx, "property not present on object");
  }

  if (!desc->isDataDescriptor()) {
    return LinkFail(cx, "property is not a data property");
  }

  v.set(desc->value());
  return true;
}

static bool GetDataProperty(JSContext* cx, HandleValue objVal,
                            const char* fieldChars, MutableHandleValue v) {
  Rooted<JSAtom*> field(cx,
                        AtomizeUTF8Chars(cx, fieldChars, strlen(fieldChars)));
  if (!field) {
    return false;
  }

  return GetDataProperty(cx, objVal, field, v);
}

static bool GetDataProperty(JSContext* cx, HandleValue objVal,
                            const ImmutableTenuredPtr<PropertyName*>& field,
                            MutableHandleValue v) {
  Handle<PropertyName*> fieldHandle = field;
  return GetDataProperty(cx, objVal, fieldHandle, v);
}

static bool HasObjectValueOfMethodPure(JSObject* obj, JSContext* cx) {
  Value v;
  if (!GetPropertyPure(cx, obj, NameToId(cx->names().valueOf), &v)) {
    return false;
  }

  JSFunction* fun;
  if (!IsFunctionObject(v, &fun)) {
    return false;
  }

  return IsSelfHostedFunctionWithName(fun, cx->names().Object_valueOf);
}

static bool HasPureCoercion(JSContext* cx, HandleValue v) {
  // Ideally, we'd reject all non-primitives, but Emscripten has a bug that
  // generates code that passes functions for some imports. To avoid breaking
  // all the code that contains this bug, we make an exception for functions
  // that don't have user-defined valueOf or toString, for their coercions
  // are not observable and coercion via ToNumber/ToInt32 definitely produces
  // NaN/0. We should remove this special case later once most apps have been
  // built with newer Emscripten.
  return v.toObject().is<JSFunction>() &&
         HasNoToPrimitiveMethodPure(&v.toObject(), cx) &&
         HasObjectValueOfMethodPure(&v.toObject(), cx) &&
         HasNativeMethodPure(&v.toObject(), cx->names().toString, fun_toString,
                             cx);
}

static bool ValidateGlobalVariable(JSContext* cx, const AsmJSGlobal& global,
                                   HandleValue importVal,
                                   Maybe<LitValPOD>* val) {
  switch (global.varInitKind()) {
    case AsmJSGlobal::InitConstant:
      val->emplace(global.varInitVal());
      return true;

    case AsmJSGlobal::InitImport: {
      RootedValue v(cx);
      if (!GetDataProperty(cx, importVal, global.field(), &v)) {
        return false;
      }

      if (!v.isPrimitive() && !HasPureCoercion(cx, v)) {
        return LinkFail(cx, "Imported values must be primitives");
      }

      switch (global.varInitImportType().kind()) {
        case ValType::I32: {
          int32_t i32;
          if (!ToInt32(cx, v, &i32)) {
            return false;
          }
          val->emplace(uint32_t(i32));
          return true;
        }
        case ValType::I64:
          MOZ_CRASH("int64");
        case ValType::V128:
          MOZ_CRASH("v128");
        case ValType::F32: {
          float f;
          if (!RoundFloat32(cx, v, &f)) {
            return false;
          }
          val->emplace(f);
          return true;
        }
        case ValType::F64: {
          double d;
          if (!ToNumber(cx, v, &d)) {
            return false;
          }
          val->emplace(d);
          return true;
        }
        case ValType::Ref: {
          MOZ_CRASH("not available in asm.js");
        }
      }
    }
  }

  MOZ_CRASH("unreachable");
}

static bool ValidateFFI(JSContext* cx, const AsmJSGlobal& global,
                        HandleValue importVal,
                        MutableHandle<FunctionVector> ffis) {
  RootedValue v(cx);
  if (!GetDataProperty(cx, importVal, global.field(), &v)) {
    return false;
  }

  if (!IsFunctionObject(v)) {
    return LinkFail(cx, "FFI imports must be functions");
  }

  ffis[global.ffiIndex()].set(&v.toObject().as<JSFunction>());
  return true;
}

static bool ValidateArrayView(JSContext* cx, const AsmJSGlobal& global,
                              HandleValue globalVal) {
  if (!global.field()) {
    return true;
  }

  if (Scalar::isBigIntType(global.viewType())) {
    return LinkFail(cx, "bad typed array constructor");
  }

  RootedValue v(cx);
  if (!GetDataProperty(cx, globalVal, global.field(), &v)) {
    return false;
  }

  bool tac = IsTypedArrayConstructor(v, global.viewType());
  if (!tac) {
    return LinkFail(cx, "bad typed array constructor");
  }

  return true;
}

static InlinableNative ToInlinableNative(AsmJSMathBuiltinFunction func) {
  switch (func) {
    case AsmJSMathBuiltin_sin:
      return InlinableNative::MathSin;
    case AsmJSMathBuiltin_cos:
      return InlinableNative::MathCos;
    case AsmJSMathBuiltin_tan:
      return InlinableNative::MathTan;
    case AsmJSMathBuiltin_asin:
      return InlinableNative::MathASin;
    case AsmJSMathBuiltin_acos:
      return InlinableNative::MathACos;
    case AsmJSMathBuiltin_atan:
      return InlinableNative::MathATan;
    case AsmJSMathBuiltin_ceil:
      return InlinableNative::MathCeil;
    case AsmJSMathBuiltin_floor:
      return InlinableNative::MathFloor;
    case AsmJSMathBuiltin_exp:
      return InlinableNative::MathExp;
    case AsmJSMathBuiltin_log:
      return InlinableNative::MathLog;
    case AsmJSMathBuiltin_pow:
      return InlinableNative::MathPow;
    case AsmJSMathBuiltin_sqrt:
      return InlinableNative::MathSqrt;
    case AsmJSMathBuiltin_abs:
      return InlinableNative::MathAbs;
    case AsmJSMathBuiltin_atan2:
      return InlinableNative::MathATan2;
    case AsmJSMathBuiltin_imul:
      return InlinableNative::MathImul;
    case AsmJSMathBuiltin_fround:
      return InlinableNative::MathFRound;
    case AsmJSMathBuiltin_min:
      return InlinableNative::MathMin;
    case AsmJSMathBuiltin_max:
      return InlinableNative::MathMax;
    case AsmJSMathBuiltin_clz32:
      return InlinableNative::MathClz32;
  }
  MOZ_CRASH("Invalid asm.js math builtin function");
}

static bool ValidateMathBuiltinFunction(
    JSContext* cx, const CodeMetadataForAsmJSImpl& codeMetaForAsmJS,
    const AsmJSGlobal& global, HandleValue globalVal) {
  RootedValue v(cx);
  if (!GetDataProperty(cx, globalVal, cx->names().Math, &v)) {
    return false;
  }

  if (!GetDataProperty(cx, v, global.field(), &v)) {
    return false;
  }

  InlinableNative native = ToInlinableNative(global.mathBuiltinFunction());

  JSFunction* fun;
  if (!IsFunctionObject(v, &fun) || !fun->hasJitInfo() ||
      fun->jitInfo()->type() != JSJitInfo::InlinableNative ||
      fun->jitInfo()->inlinableNative != native) {
    return LinkFail(cx, "bad Math.* builtin function");
  }
  if (fun->realm()->creationOptions().alwaysUseFdlibm() !=
      codeMetaForAsmJS.alwaysUseFdlibm) {
    return LinkFail(cx,
                    "Math.* builtin function and asm.js use different native"
                    " math implementations.");
  }

  return true;
}

static bool ValidateConstant(JSContext* cx, const AsmJSGlobal& global,
                             HandleValue globalVal) {
  RootedValue v(cx, globalVal);

  if (global.constantKind() == AsmJSGlobal::MathConstant) {
    if (!GetDataProperty(cx, v, cx->names().Math, &v)) {
      return false;
    }
  }

  if (!GetDataProperty(cx, v, global.field(), &v)) {
    return false;
  }

  if (!v.isNumber()) {
    return LinkFail(cx, "math / global constant value needs to be a number");
  }

  // NaN != NaN
  if (std::isnan(global.constantValue())) {
    if (!std::isnan(v.toNumber())) {
      return LinkFail(cx, "global constant value needs to be NaN");
    }
  } else {
    if (v.toNumber() != global.constantValue()) {
      return LinkFail(cx, "global constant value mismatch");
    }
  }

  return true;
}

static bool CheckBuffer(JSContext* cx, const CodeMetadata& codeMeta,
                        HandleValue bufferVal,
                        MutableHandle<ArrayBufferObject*> buffer) {
  if (!bufferVal.isObject()) {
    return LinkFail(cx, "buffer must be an object");
  }
  JSObject* bufferObj = &bufferVal.toObject();

  if (codeMeta.memories[0].isShared()) {
    if (!bufferObj->is<SharedArrayBufferObject>()) {
      return LinkFail(
          cx, "shared views can only be constructed onto SharedArrayBuffer");
    }
    return LinkFail(cx, "Unable to prepare SharedArrayBuffer for asm.js use");
  }

  if (!bufferObj->is<ArrayBufferObject>()) {
    return LinkFail(cx,
                    "unshared views can only be constructed onto ArrayBuffer");
  }

  buffer.set(&bufferObj->as<ArrayBufferObject>());

  size_t memoryLength = buffer->byteLength();

  if (!IsValidAsmJSHeapLength(memoryLength)) {
    UniqueChars msg;
    if (memoryLength > MaxHeapLength) {
      msg = JS_smprintf("ArrayBuffer byteLength 0x%" PRIx64
                        " is not a valid heap length - it is too long."
                        " The longest valid length is 0x%" PRIx64,
                        uint64_t(memoryLength), MaxHeapLength);
    } else {
      msg = JS_smprintf("ArrayBuffer byteLength 0x%" PRIx64
                        " is not a valid heap length. The next "
                        "valid length is 0x%" PRIx64,
                        uint64_t(memoryLength),
                        RoundUpToNextValidAsmJSHeapLength(memoryLength));
    }
    if (!msg) {
      return false;
    }
    return LinkFail(cx, msg.get());
  }

  // This check is sufficient without considering the size of the loaded datum
  // because heap loads and stores start on an aligned boundary and the heap
  // byteLength has larger alignment.
  uint64_t minMemoryLength = codeMeta.memories.length() != 0
                                 ? codeMeta.memories[0].initialLength32()
                                 : 0;
  MOZ_ASSERT((minMemoryLength - 1) <= INT32_MAX);
  if (memoryLength < minMemoryLength) {
    UniqueChars msg(JS_smprintf("ArrayBuffer byteLength of 0x%" PRIx64
                                " is less than 0x%" PRIx64 " (the "
                                "size implied "
                                "by const heap accesses).",
                                uint64_t(memoryLength), minMemoryLength));
    if (!msg) {
      return false;
    }
    return LinkFail(cx, msg.get());
  }

  // ArrayBuffer lengths in SpiderMonkey used to be restricted to <= INT32_MAX,
  // but that has since been relaxed for the benefit of wasm.  We keep the old
  // limit for asm.js so as to avoid having to worry about whether the asm.js
  // implementation is safe for larger heaps.
  if (memoryLength >= INT32_MAX) {
    UniqueChars msg(
        JS_smprintf("ArrayBuffer byteLength 0x%" PRIx64
                    " is too large for asm.js (implementation limit).",
                    uint64_t(memoryLength)));
    if (!msg) {
      return false;
    }
    return LinkFail(cx, msg.get());
  }

  if (buffer->isResizable()) {
    return LinkFail(cx,
                    "Unable to prepare resizable ArrayBuffer for asm.js use");
  }

  if (!buffer->prepareForAsmJS()) {
    return LinkFail(cx, "Unable to prepare ArrayBuffer for asm.js use");
  }

  MOZ_ASSERT(buffer->isPreparedForAsmJS());
  return true;
}

static bool GetImports(JSContext* cx,
                       const CodeMetadataForAsmJSImpl& codeMetaForAsmJS,
                       HandleValue globalVal, HandleValue importVal,
                       ImportValues* imports) {
  Rooted<FunctionVector> ffis(cx, FunctionVector(cx));
  if (!ffis.resize(codeMetaForAsmJS.numFFIs)) {
    return false;
  }

  for (const AsmJSGlobal& global : codeMetaForAsmJS.asmJSGlobals) {
    switch (global.which()) {
      case AsmJSGlobal::Variable: {
        Maybe<LitValPOD> litVal;
        if (!ValidateGlobalVariable(cx, global, importVal, &litVal)) {
          return false;
        }
        if (!imports->globalValues.append(Val(litVal->asLitVal()))) {
          return false;
        }
        break;
      }
      case AsmJSGlobal::FFI:
        if (!ValidateFFI(cx, global, importVal, &ffis)) {
          return false;
        }
        break;
      case AsmJSGlobal::ArrayView:
      case AsmJSGlobal::ArrayViewCtor:
        if (!ValidateArrayView(cx, global, globalVal)) {
          return false;
        }
        break;
      case AsmJSGlobal::MathBuiltinFunction:
        if (!ValidateMathBuiltinFunction(cx, codeMetaForAsmJS, global,
                                         globalVal)) {
          return false;
        }
        break;
      case AsmJSGlobal::Constant:
        if (!ValidateConstant(cx, global, globalVal)) {
          return false;
        }
        break;
    }
  }

  for (const AsmJSImport& import : codeMetaForAsmJS.asmJSImports) {
    if (!imports->funcs.append(ffis[import.ffiIndex()])) {
      return false;
    }
  }

  return true;
}

static bool TryInstantiate(JSContext* cx, const CallArgs& args,
                           const Module& module,
                           const CodeMetadataForAsmJSImpl& codeMetaForAsmJS,
                           MutableHandle<WasmInstanceObject*> instanceObj,
                           MutableHandleObject exportObj) {
  HandleValue globalVal = args.get(0);
  HandleValue importVal = args.get(1);
  HandleValue bufferVal = args.get(2);

  MOZ_RELEASE_ASSERT(HasPlatformSupport());

  if (!wasm::EnsureFullSignalHandlers(cx)) {
    return LinkFail(cx, "failed to install signal handlers");
  }

  Rooted<ImportValues> imports(cx);

  if (module.codeMeta().memories.length() != 0) {
    MOZ_ASSERT(module.codeMeta().memories.length() == 1);
    Rooted<ArrayBufferObject*> buffer(cx);
    if (!CheckBuffer(cx, module.codeMeta(), bufferVal, &buffer)) {
      return false;
    }

    Rooted<WasmMemoryObject*> memory(
        cx, WasmMemoryObject::create(cx, buffer, /* isHuge= */ false, nullptr));
    if (!memory || !imports.get().memories.append(memory)) {
      return false;
    }
  }

  if (!GetImports(cx, codeMetaForAsmJS, globalVal, importVal,
                  imports.address())) {
    return false;
  }

  if (!module.instantiate(cx, imports.get(), nullptr, instanceObj)) {
    return false;
  }

  exportObj.set(&instanceObj->exportsObj());
  return true;
}

static bool HandleInstantiationFailure(
    JSContext* cx, const CallArgs& args,
    const CodeMetadataForAsmJSImpl& codeMetaForAsmJS) {
  using js::frontend::FunctionSyntaxKind;

  Rooted<JSAtom*> name(cx, args.callee().as<JSFunction>().fullExplicitName());

  if (cx->isExceptionPending()) {
    return false;
  }

  ScriptSource* source = codeMetaForAsmJS.maybeScriptSource();

  // Source discarding is allowed to affect JS semantics because it is never
  // enabled for normal JS content.
  bool haveSource;
  if (!ScriptSource::loadSource(cx, source, &haveSource)) {
    return false;
  }
  if (!haveSource) {
    JS_ReportErrorASCII(cx,
                        "asm.js link failure with source discarding enabled");
    return false;
  }

  uint32_t begin = codeMetaForAsmJS.toStringStart;
  uint32_t end = codeMetaForAsmJS.srcEndAfterCurly();
  Rooted<JSLinearString*> src(cx, source->substringDontDeflate(cx, begin, end));
  if (!src) {
    return false;
  }

  JS::CompileOptions options(cx);
  options.setMutedErrors(source->mutedErrors())
      .setFile(source->filename())
      .setNoScriptRval(false);
  options.setAsmJSOption(AsmJSOption::DisabledByLinker);

  // The exported function inherits an implicit strict context if the module
  // also inherited it somehow.
  if (codeMetaForAsmJS.strict) {
    options.setForceStrictMode();
  }

  AutoStableStringChars linearChars(cx);
  if (!linearChars.initTwoByte(cx, src)) {
    return false;
  }

  SourceText<char16_t> srcBuf;
  if (!srcBuf.initMaybeBorrowed(cx, linearChars)) {
    return false;
  }

  FunctionSyntaxKind syntaxKind = FunctionSyntaxKind::Statement;

  RootedFunction fun(cx, frontend::CompileStandaloneFunction(
                             cx, options, srcBuf, Nothing(), syntaxKind));
  if (!fun) {
    return false;
  }

  fun->initEnvironment(&cx->global()->lexicalEnvironment());

  // Call the function we just recompiled.
  args.setCallee(ObjectValue(*fun));
  return InternalCallOrConstruct(
      cx, args, args.isConstructing() ? CONSTRUCT : NO_CONSTRUCT);
}

static const Module& AsmJSModuleFunctionToModule(JSFunction* fun) {
  MOZ_ASSERT(IsAsmJSModule(fun));
  const Value& v = fun->getExtendedSlot(FunctionExtended::ASMJS_MODULE_SLOT);
  return v.toObject().as<WasmModuleObject>().module();
}

// Implements the semantics of an asm.js module function that has been
// successfully validated.
bool js::InstantiateAsmJS(JSContext* cx, unsigned argc, JS::Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  JSFunction* callee = &args.callee().as<JSFunction>();
  const Module& module = AsmJSModuleFunctionToModule(callee);
  const CodeMetadataForAsmJSImpl& codeMetaForAsmJS =
      module.codeMetaForAsmJS()->asAsmJS();

  Rooted<WasmInstanceObject*> instanceObj(cx);
  RootedObject exportObj(cx);
  if (!TryInstantiate(cx, args, module, codeMetaForAsmJS, &instanceObj,
                      &exportObj)) {
    // Link-time validation checks failed, so reparse the entire asm.js
    // module from scratch to get normal interpreted bytecode which we can
    // simply Invoke. Very slow.
    return HandleInstantiationFailure(cx, args, codeMetaForAsmJS);
  }

  args.rval().set(ObjectValue(*exportObj));
  return true;
}

/*****************************************************************************/
// Top-level js::CompileAsmJS

static bool NoExceptionPending(FrontendContext* fc) { return !fc->hadErrors(); }

static bool SuccessfulValidation(frontend::ParserBase& parser,
                                 unsigned compilationTime) {
  unsigned errNum = js::SupportDifferentialTesting()
                        ? JSMSG_USE_ASM_TYPE_OK_NO_TIME
                        : JSMSG_USE_ASM_TYPE_OK;

  char timeChars[20];
  SprintfLiteral(timeChars, "%u", compilationTime);

  return parser.warningNoOffset(errNum, timeChars);
}

static bool TypeFailureWarning(frontend::ParserBase& parser, const char* str) {
  if (parser.options().throwOnAsmJSValidationFailure()) {
    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.
  (void)parser.warningNoOffset(JSMSG_USE_ASM_TYPE_FAIL, str ? str : "");
  return false;
}

// asm.js requires Ion to be available on the current hardware/OS and to be
// enabled for wasm, since asm.js compilation goes via wasm.
static bool IsAsmJSCompilerAvailable(JSContext* cx) {
  return HasPlatformSupport() && WasmCompilerForAsmJSAvailable(cx);
}

static bool EstablishPreconditions(frontend::ParserBase& parser) {
  switch (parser.options().asmJSOption()) {
    case AsmJSOption::DisabledByAsmJSPref:
      return TypeFailureWarning(
          parser, "Asm.js optimizer disabled by 'asmjs' runtime option");
    case AsmJSOption::DisabledByLinker:
      return TypeFailureWarning(
          parser,
          "Asm.js optimizer disabled by linker (instantiation failure)");
    case AsmJSOption::DisabledByNoWasmCompiler:
      return TypeFailureWarning(parser,
                                "Asm.js optimizer disabled because no suitable "
                                "wasm compiler is available");
    case AsmJSOption::DisabledByDebugger:
      return TypeFailureWarning(
          parser, "Asm.js optimizer disabled because debugger is active");
    case AsmJSOption::Enabled:
      break;
  }

  if (parser.pc_->isGenerator()) {
    return TypeFailureWarning(parser,
                              "Asm.js optimizer disabled in generator context");
  }

  if (parser.pc_->isAsync()) {
    return TypeFailureWarning(parser,
                              "Asm.js optimizer disabled in async context");
  }

  if (parser.pc_->isArrowFunction()) {
    return TypeFailureWarning(
        parser, "Asm.js optimizer disabled in arrow function context");
  }

  // Class constructors are also methods
  if (parser.pc_->isMethod() || parser.pc_->isGetterOrSetter()) {
    return TypeFailureWarning(
        parser,
        "Asm.js optimizer disabled in class constructor or method context");
  }

  return true;
}

template <typename Unit>
static bool DoCompileAsmJS(FrontendContext* fc, ParserAtomsTable& parserAtoms,
                           AsmJSParser<Unit>& parser, ParseNode* stmtList,
                           bool* validated) {
  *validated = false;

  // Various conditions disable asm.js optimizations.
  if (!EstablishPreconditions(parser)) {
    return NoExceptionPending(fc);
  }

  // "Checking" parses, validates and compiles, producing a fully compiled
  // WasmModuleObject as result.
  unsigned time;
  SharedModule module = CheckModule(fc, parserAtoms, parser, stmtList, &time);
  if (!module) {
    return NoExceptionPending(fc);
  }

  // Finished! Save the ref-counted module on the FunctionBox. When JSFunctions
  // are eventually allocated we will create an asm.js constructor for it.
  FunctionBox* funbox = parser.pc_->functionBox();
  MOZ_ASSERT(funbox->isInterpreted());
  if (!funbox->setAsmJSModule(module)) {
    return NoExceptionPending(fc);
  }

  // Success! Write to the console with a "warning" message indicating
  // total compilation time.
  *validated = true;
  SuccessfulValidation(parser, time);
  return NoExceptionPending(fc);
}

bool js::CompileAsmJS(FrontendContext* fc, ParserAtomsTable& parserAtoms,
                      AsmJSParser<char16_t>& parser, ParseNode* stmtList,
                      bool* validated) {
  return DoCompileAsmJS(fc, parserAtoms, parser, stmtList, validated);
}

bool js::CompileAsmJS(FrontendContext* fc, ParserAtomsTable& parserAtoms,
                      AsmJSParser<Utf8Unit>& parser, ParseNode* stmtList,
                      bool* validated) {
  return DoCompileAsmJS(fc, parserAtoms, parser, stmtList, validated);
}

/*****************************************************************************/
// asm.js testing functions

bool js::IsAsmJSModuleNative(Native native) {
  return native == InstantiateAsmJS;
}

bool js::IsAsmJSModule(JSFunction* fun) {
  return fun->maybeNative() == InstantiateAsmJS;
}

bool js::IsAsmJSFunction(JSFunction* fun) {
  return fun->kind() == FunctionFlags::AsmJS;
}

bool js::IsAsmJSStrictModeModuleOrFunction(JSFunction* fun) {
  if (IsAsmJSModule(fun)) {
    return AsmJSModuleFunctionToModule(fun)
        .codeMetaForAsmJS()
        ->asAsmJS()
        .strict;
  }

  if (IsAsmJSFunction(fun)) {
    return fun->wasmInstance().codeMetaForAsmJS()->asAsmJS().strict;
  }

  return false;
}

bool js::IsAsmJSCompilationAvailable(JSContext* cx) {
  return cx->options().asmJS() && IsAsmJSCompilerAvailable(cx);
}

bool js::IsAsmJSCompilationAvailable(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  bool available = IsAsmJSCompilationAvailable(cx);
  args.rval().set(BooleanValue(available));
  return true;
}

static JSFunction* MaybeWrappedNativeFunction(const Value& v) {
  if (!v.isObject()) {
    return nullptr;
  }

  return v.toObject().maybeUnwrapIf<JSFunction>();
}

bool js::IsAsmJSModule(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  bool rval = false;
  if (JSFunction* fun = MaybeWrappedNativeFunction(args.get(0))) {
    rval = IsAsmJSModule(fun);
  }

  args.rval().set(BooleanValue(rval));
  return true;
}

bool js::IsAsmJSFunction(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  bool rval = false;
  if (JSFunction* fun = MaybeWrappedNativeFunction(args.get(0))) {
    rval = IsAsmJSFunction(fun);
  }

  args.rval().set(BooleanValue(rval));
  return true;
}

/*****************************************************************************/
// asm.js toString/toSource support

JSString* js::AsmJSModuleToString(JSContext* cx, HandleFunction fun,
                                  bool isToSource) {
  MOZ_ASSERT(IsAsmJSModule(fun));

  const CodeMetadataForAsmJSImpl& codeMetaForAsmJS =
      AsmJSModuleFunctionToModule(fun).codeMetaForAsmJS()->asAsmJS();
  uint32_t begin = codeMetaForAsmJS.toStringStart;
  uint32_t end = codeMetaForAsmJS.srcEndAfterCurly();
  ScriptSource* source = codeMetaForAsmJS.maybeScriptSource();

  JSStringBuilder out(cx);

  if (isToSource && fun->isLambda() && !out.append("(")) {
    return nullptr;
  }

  bool haveSource;
  if (!ScriptSource::loadSource(cx, source, &haveSource)) {
    return nullptr;
  }

  if (!haveSource) {
    if (!out.append("function ")) {
      return nullptr;
    }
    if (fun->fullExplicitName() && !out.append(fun->fullExplicitName())) {
      return nullptr;
    }
    if (!out.append("() {\n    [native code]\n}")) {
      return nullptr;
    }
  } else {
    Rooted<JSLinearString*> src(cx, source->substring(cx, begin, end));
    if (!src) {
      return nullptr;
    }

    if (!out.append(src)) {
      return nullptr;
    }
  }

  if (isToSource && fun->isLambda() && !out.append(")")) {
    return nullptr;
  }

  return out.finishString();
}

JSString* js::AsmJSFunctionToString(JSContext* cx, HandleFunction fun) {
  MOZ_ASSERT(IsAsmJSFunction(fun));

  const CodeMetadataForAsmJSImpl& codeMetaForAsmJS =
      fun->wasmInstance().codeMetaForAsmJS()->asAsmJS();
  const AsmJSExport& f =
      codeMetaForAsmJS.lookupAsmJSExport(fun->wasmFuncIndex());

  uint32_t begin = codeMetaForAsmJS.srcStart + f.startOffsetInModule();
  uint32_t end = codeMetaForAsmJS.srcStart + f.endOffsetInModule();

  ScriptSource* source = codeMetaForAsmJS.maybeScriptSource();
  JSStringBuilder out(cx);

  if (!out.append("function ")) {
    return nullptr;
  }

  bool haveSource;
  if (!ScriptSource::loadSource(cx, source, &haveSource)) {
    return nullptr;
  }

  if (!haveSource) {
    // asm.js functions can't be anonymous
    MOZ_ASSERT(fun->fullExplicitName());
    if (!out.append(fun->fullExplicitName())) {
      return nullptr;
    }
    if (!out.append("() {\n    [native code]\n}")) {
      return nullptr;
    }
  } else {
    Rooted<JSLinearString*> src(cx, source->substring(cx, begin, end));
    if (!src) {
      return nullptr;
    }
    if (!out.append(src)) {
      return nullptr;
    }
  }

  return out.finishString();
}

bool js::IsValidAsmJSHeapLength(size_t length) {
  if (length < MinHeapLength) {
    return false;
  }

  // The heap length is limited by what a wasm memory32 can handle.
  if (length > MaxMemoryBytes(AddressType::I32)) {
    return false;
  }

  return wasm::IsValidARMImmediate(length);
}
