/*
 * Copyright 2017 WebAssembly Community Group participants
 *
 * 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 <mutex>
#include <set>
#include <sstream>
#include <unordered_set>

#include "ir/eh-utils.h"
#include "ir/features.h"
#include "ir/find_all.h"
#include "ir/gc-type-utils.h"
#include "ir/global-utils.h"
#include "ir/intrinsics.h"
#include "ir/local-graph.h"
#include "ir/local-structural-dominance.h"
#include "ir/module-utils.h"
#include "ir/stack-utils.h"
#include "ir/utils.h"
#include "support/colors.h"
#include "wasm-validator.h"
#include "wasm.h"

namespace wasm {

// Print anything that can be streamed to an ostream
template<typename T,
         typename std::enable_if<!std::is_base_of<
           Expression,
           typename std::remove_pointer<T>::type>::value>::type* = nullptr>
inline std::ostream&
printModuleComponent(T curr, std::ostream& stream, Module& wasm) {
  stream << curr << std::endl;
  return stream;
}

// Extra overload for Expressions, to print their contents.
inline std::ostream&
printModuleComponent(Expression* curr, std::ostream& stream, Module& wasm) {
  if (curr) {
    stream << ModuleExpression(wasm, curr) << '\n';
  }
  return stream;
}

// For parallel validation, we have a helper struct for coordination
struct ValidationInfo {
  Module& wasm;

  bool validateWeb;
  bool validateGlobally;
  bool quiet;
  bool closedWorld;

  std::atomic<bool> valid;

  // a stream of error test for each function. we print in the right order at
  // the end, for deterministic output
  // note errors are rare/unexpected, so it's ok to use a slow mutex here
  std::mutex mutex;
  std::unordered_map<Function*, std::unique_ptr<std::ostringstream>> outputs;

  ValidationInfo(Module& wasm) : wasm(wasm) { valid.store(true); }

  std::ostringstream& getStream(Function* func) {
    std::unique_lock<std::mutex> lock(mutex);
    auto iter = outputs.find(func);
    if (iter != outputs.end()) {
      return *(iter->second.get());
    }
    auto& ret = outputs[func] = std::make_unique<std::ostringstream>();
    return *ret.get();
  }

  // printing and error handling support

  template<typename T, typename S>
  std::ostream& fail(S text, T curr, Function* func) {
    valid.store(false);
    auto& stream = getStream(func);
    if (quiet) {
      return stream;
    }
    auto& ret = printFailureHeader(func);
    ret << text << ", on \n";
    return printModuleComponent(curr, ret, wasm);
  }

  std::ostream& printFailureHeader(Function* func) {
    auto& stream = getStream(func);
    if (quiet) {
      return stream;
    }
    Colors::red(stream);
    if (func) {
      stream << "[wasm-validator error in function ";
      Colors::green(stream);
      stream << func->name;
      Colors::red(stream);
      stream << "] ";
    } else {
      stream << "[wasm-validator error in module] ";
    }
    Colors::normal(stream);
    return stream;
  }

  // Checking utilities.

  // Returns whether the result was in fact true.
  template<typename T>
  bool shouldBeTrue(bool result,
                    T curr,
                    const char* text,
                    Function* func = nullptr) {
    if (!result) {
      fail("unexpected false: " + std::string(text), curr, func);
      return false;
    }
    return true;
  }

  // Returns whether the result was in fact false.
  template<typename T>
  bool shouldBeFalse(bool result,
                     T curr,
                     const char* text,
                     Function* func = nullptr) {
    if (result) {
      fail("unexpected true: " + std::string(text), curr, func);
      return false;
    }
    return true;
  }

  template<typename T, typename S>
  bool shouldBeEqual(
    S left, S right, T curr, const char* text, Function* func = nullptr) {
    if (left != right) {
      std::ostringstream ss;
      ss << left << " != " << right << ": " << text;
      fail(ss.str(), curr, func);
      return false;
    }
    return true;
  }

  template<typename T, typename S>
  bool shouldBeEqualOrFirstIsUnreachable(
    S left, S right, T curr, const char* text, Function* func = nullptr) {
    if (left != Type::unreachable && left != right) {
      std::ostringstream ss;
      ss << left << " != " << right << ": " << text;
      fail(ss.str(), curr, func);
      return false;
    }
    return true;
  }

  template<typename T, typename S>
  bool shouldBeUnequal(
    S left, S right, T curr, const char* text, Function* func = nullptr) {
    if (left == right) {
      std::ostringstream ss;
      ss << left << " == " << right << ": " << text;
      fail(ss.str(), curr, func);
      return false;
    }
    return true;
  }

  void shouldBeIntOrUnreachable(Type ty,
                                Expression* curr,
                                const char* text,
                                Function* func = nullptr) {
    switch (ty.getBasic()) {
      case Type::i32:
      case Type::i64:
      case Type::unreachable: {
        break;
      }
      default:
        fail(text, curr, func);
    }
  }

  // Type 'left' should be a subtype of 'right'.
  bool shouldBeSubType(Type left,
                       Type right,
                       Expression* curr,
                       const char* text,
                       Function* func = nullptr) {
    if (Type::isSubType(left, right)) {
      return true;
    }
    fail(text, curr, func);
    return false;
  }

  bool shouldBeSubTypeIgnoringShared(Type left,
                                     Type right,
                                     Expression* curr,
                                     const char* text,
                                     Function* func = nullptr) {
    assert(right.isRef() && right.getHeapType().isBasic());
    auto share = left.isRef() ? left.getHeapType().getShared() : Unshared;
    auto ht = right.getHeapType();
    auto matchedRight = Type(ht.getBasic(share), right.getNullability());
    return shouldBeSubType(left, matchedRight, curr, text, func);
  }
};

std::string getMissingFeaturesList(Module& wasm, FeatureSet feats) {
  std::stringstream ss;
  bool first = true;
  ss << '[';
  (feats - wasm.features).iterFeatures([&](FeatureSet feat) {
    if (first) {
      first = false;
    } else {
      ss << " ";
    }
    ss << "--enable-" << feat.toString();
  });
  ss << ']';
  return ss.str();
}

struct FunctionValidator : public WalkerPass<PostWalker<FunctionValidator>> {
  bool isFunctionParallel() override { return true; }

  std::unique_ptr<Pass> create() override {
    return std::make_unique<FunctionValidator>(*getModule(), &info);
  }

  bool modifiesBinaryenIR() override { return false; }

  ValidationInfo& info;

  FunctionValidator(Module& wasm, ValidationInfo* info) : info(*info) {
    setModule(&wasm);
  }

  // Validate the entire module.
  void validate(PassRunner* runner) { run(runner, getModule()); }

  // Validate a specific expression.
  void validate(Expression* curr) { walk(curr); }

  // Validate a function.
  void validate(Function* func) { walkFunction(func); }

  std::unordered_map<Name, std::unordered_set<Type>> breakTypes;
  std::unordered_set<Name> delegateTargetNames;
  std::unordered_set<Name> rethrowTargetNames;

  // Binaryen IR requires that label names must be unique - IR generators must
  // ensure that
  std::unordered_set<Name> labelNames;

  void noteLabelName(Name name);

public:
  // visitors

  void validatePoppyExpression(Expression* curr);

  static void visitPoppyExpression(FunctionValidator* self,
                                   Expression** currp) {
    self->validatePoppyExpression(*currp);
  }

  static void visitPreBlock(FunctionValidator* self, Expression** currp) {
    auto* curr = (*currp)->cast<Block>();
    if (curr->name.is()) {
      self->breakTypes[curr->name];
    }
  }

  void visitBlock(Block* curr);
  void validateNormalBlockElements(Block* curr);
  void validatePoppyBlockElements(Block* curr);

  static void visitPreLoop(FunctionValidator* self, Expression** currp) {
    auto* curr = (*currp)->cast<Loop>();
    if (curr->name.is()) {
      self->breakTypes[curr->name];
    }
  }

  void visitLoop(Loop* curr);
  void visitIf(If* curr);

  static void visitPreTry(FunctionValidator* self, Expression** currp) {
    auto* curr = (*currp)->cast<Try>();
    if (curr->name.is()) {
      self->delegateTargetNames.insert(curr->name);
    }
  }

  // We remove try's label before proceeding to verify catch bodies because the
  // following is a validation failure:
  // (try $l0
  //   (do ... )
  //   (catch $e
  //     (try
  //       (do ...)
  //       (delegate $l0) ;; validation failure
  //     )
  //   )
  // )
  // Unlike branches, if delegate's target 'catch' is located above the
  // delegate, it is a validation failure.
  static void visitPreCatch(FunctionValidator* self, Expression** currp) {
    auto* curr = (*currp)->cast<Try>();
    if (curr->name.is()) {
      self->delegateTargetNames.erase(curr->name);
      self->rethrowTargetNames.insert(curr->name);
    }
  }

  // override scan to add a pre and a post check task to all nodes
  static void scan(FunctionValidator* self, Expression** currp) {
    auto* curr = *currp;
    // Treat 'Try' specially because we need to run visitPreCatch between the
    // try body and catch bodies
    if (curr->is<Try>()) {
      self->pushTask(doVisitTry, currp);
      auto& list = curr->cast<Try>()->catchBodies;
      for (int i = int(list.size()) - 1; i >= 0; i--) {
        self->pushTask(scan, &list[i]);
      }
      self->pushTask(visitPreCatch, currp);
      self->pushTask(scan, &curr->cast<Try>()->body);
      self->pushTask(visitPreTry, currp);
      return;
    }

    PostWalker<FunctionValidator>::scan(self, currp);

    if (curr->is<Block>()) {
      self->pushTask(visitPreBlock, currp);
    }
    if (curr->is<Loop>()) {
      self->pushTask(visitPreLoop, currp);
    }
    if (auto* func = self->getFunction()) {
      if (func->profile == IRProfile::Poppy) {
        self->pushTask(visitPoppyExpression, currp);
      }
    }

    // Also verify that only allowed expressions end up in the situation where
    // the expression has type unreachable but there is no unreachable child.
    // For example a Call with no unreachable child cannot be unreachable, but a
    // Break can be.
    if (curr->type == Type::unreachable) {
      switch (curr->_id) {
        case Expression::BreakId: {
          // If there is a condition, that is already validated fully in
          // visitBreak(). If there isn't a condition, then this is allowed to
          // be unreachable even without an unreachable child. Either way, we
          // can leave.
          return;
        }
        case Expression::SwitchId:
        case Expression::ReturnId:
        case Expression::UnreachableId:
        case Expression::ThrowId:
        case Expression::RethrowId:
        case Expression::ThrowRefId: {
          // These can all be unreachable without an unreachable child.
          return;
        }
        case Expression::CallId: {
          if (curr->cast<Call>()->isReturn) {
            return;
          }
          break;
        }
        case Expression::CallIndirectId: {
          if (curr->cast<CallIndirect>()->isReturn) {
            return;
          }
          break;
        }
        case Expression::CallRefId: {
          if (curr->cast<CallRef>()->isReturn) {
            return;
          }
          break;
        }
        default: {
          break;
        }
      }

      // If we reach here, then we must have an unreachable child.
      bool hasUnreachableChild = false;
      for (auto* child : ChildIterator(curr)) {
        if (child->type == Type::unreachable) {
          hasUnreachableChild = true;
          break;
        }
      }
      self->shouldBeTrue(hasUnreachableChild,
                         curr,
                         "unreachable instruction must have unreachable child");
    }
  }

  void noteBreak(Name name, Expression* value, Expression* curr);
  void noteBreak(Name name, Type valueType, Expression* curr);
  void visitBreak(Break* curr);
  void visitSwitch(Switch* curr);
  void visitCall(Call* curr);
  void visitCallIndirect(CallIndirect* curr);
  void visitConst(Const* curr);
  void visitLocalGet(LocalGet* curr);
  void visitLocalSet(LocalSet* curr);
  void visitGlobalGet(GlobalGet* curr);
  void visitGlobalSet(GlobalSet* curr);
  void visitLoad(Load* curr);
  void visitStore(Store* curr);
  void visitAtomicRMW(AtomicRMW* curr);
  void visitAtomicCmpxchg(AtomicCmpxchg* curr);
  void visitAtomicWait(AtomicWait* curr);
  void visitAtomicNotify(AtomicNotify* curr);
  void visitAtomicFence(AtomicFence* curr);
  void visitSIMDExtract(SIMDExtract* curr);
  void visitSIMDReplace(SIMDReplace* curr);
  void visitSIMDShuffle(SIMDShuffle* curr);
  void visitSIMDTernary(SIMDTernary* curr);
  void visitSIMDShift(SIMDShift* curr);
  void visitSIMDLoad(SIMDLoad* curr);
  void visitSIMDLoadStoreLane(SIMDLoadStoreLane* curr);
  void visitMemoryInit(MemoryInit* curr);
  void visitDataDrop(DataDrop* curr);
  void visitMemoryCopy(MemoryCopy* curr);
  void visitMemoryFill(MemoryFill* curr);
  void visitBinary(Binary* curr);
  void visitUnary(Unary* curr);
  void visitSelect(Select* curr);
  void visitDrop(Drop* curr);
  void visitReturn(Return* curr);
  void visitMemorySize(MemorySize* curr);
  void visitMemoryGrow(MemoryGrow* curr);
  void visitRefNull(RefNull* curr);
  void visitRefIsNull(RefIsNull* curr);
  void visitRefAs(RefAs* curr);
  void visitRefFunc(RefFunc* curr);
  void visitRefEq(RefEq* curr);
  void visitTableGet(TableGet* curr);
  void visitTableSet(TableSet* curr);
  void visitTableSize(TableSize* curr);
  void visitTableGrow(TableGrow* curr);
  void visitTableFill(TableFill* curr);
  void visitTableCopy(TableCopy* curr);
  void noteDelegate(Name name, Expression* curr);
  void noteRethrow(Name name, Expression* curr);
  void visitTry(Try* curr);
  void visitTryTable(TryTable* curr);
  void visitThrow(Throw* curr);
  void visitRethrow(Rethrow* curr);
  void visitThrowRef(ThrowRef* curr);
  void visitTupleMake(TupleMake* curr);
  void visitTupleExtract(TupleExtract* curr);
  void visitCallRef(CallRef* curr);
  void visitRefI31(RefI31* curr);
  void visitI31Get(I31Get* curr);
  void visitRefTest(RefTest* curr);
  void visitRefCast(RefCast* curr);
  void visitBrOn(BrOn* curr);
  void visitStructNew(StructNew* curr);
  void visitStructGet(StructGet* curr);
  void visitStructSet(StructSet* curr);
  void visitArrayNew(ArrayNew* curr);
  template<typename ArrayNew> void visitArrayNew(ArrayNew* curr);
  void visitArrayNewData(ArrayNewData* curr);
  void visitArrayNewElem(ArrayNewElem* curr);
  void visitArrayNewFixed(ArrayNewFixed* curr);
  void visitArrayGet(ArrayGet* curr);
  void visitArraySet(ArraySet* curr);
  void visitArrayLen(ArrayLen* curr);
  void visitArrayCopy(ArrayCopy* curr);
  void visitArrayFill(ArrayFill* curr);
  template<typename ArrayInit> void visitArrayInit(ArrayInit* curr);
  void visitArrayInitData(ArrayInitData* curr);
  void visitArrayInitElem(ArrayInitElem* curr);
  void visitStringNew(StringNew* curr);
  void visitStringConst(StringConst* curr);
  void visitStringMeasure(StringMeasure* curr);
  void visitStringEncode(StringEncode* curr);
  void visitStringConcat(StringConcat* curr);
  void visitStringEq(StringEq* curr);
  void visitStringWTF16Get(StringWTF16Get* curr);
  void visitStringSliceWTF(StringSliceWTF* curr);
  void visitContBind(ContBind* curr);
  void visitContNew(ContNew* curr);
  void visitResume(Resume* curr);
  void visitSuspend(Suspend* curr);

  void visitFunction(Function* curr);

  // helpers
private:
  std::ostream& getStream() { return info.getStream(getFunction()); }

  template<typename T>
  bool shouldBeTrue(bool result, T curr, const char* text) {
    return info.shouldBeTrue(result, curr, text, getFunction());
  }
  template<typename T>
  bool shouldBeFalse(bool result, T curr, const char* text) {
    return info.shouldBeFalse(result, curr, text, getFunction());
  }

  template<typename T, typename S>
  bool shouldBeEqual(S left, S right, T curr, const char* text) {
    return info.shouldBeEqual(left, right, curr, text, getFunction());
  }

  template<typename T, typename S>
  bool
  shouldBeEqualOrFirstIsUnreachable(S left, S right, T curr, const char* text) {
    return info.shouldBeEqualOrFirstIsUnreachable(
      left, right, curr, text, getFunction());
  }

  template<typename T, typename S>
  bool shouldBeUnequal(S left, S right, T curr, const char* text) {
    return info.shouldBeUnequal(left, right, curr, text, getFunction());
  }

  void shouldBeIntOrUnreachable(Type ty, Expression* curr, const char* text) {
    return info.shouldBeIntOrUnreachable(ty, curr, text, getFunction());
  }

  bool
  shouldBeSubType(Type left, Type right, Expression* curr, const char* text) {
    return info.shouldBeSubType(left, right, curr, text, getFunction());
  }

  bool shouldBeSubTypeIgnoringShared(Type left,
                                     Type right,
                                     Expression* curr,
                                     const char* text) {
    return info.shouldBeSubTypeIgnoringShared(left, right, curr, text);
  }

  void validateOffset(Address offset, Memory* mem, Expression* curr);
  void validateAlignment(
    size_t align, Type type, Index bytes, bool isAtomic, Expression* curr);
  void validateMemBytes(uint8_t bytes, Type type, Expression* curr);

  template<typename T> void validateReturnCall(T* curr) {
    shouldBeTrue(!curr->isReturn || getModule()->features.hasTailCall(),
                 curr,
                 "return_call* requires tail calls [--enable-tail-call]");
  }

  // |printable| is the expression to print in case of an error. That may differ
  // from |curr| which we are validating.
  template<typename T>
  void validateCallParamsAndResult(T* curr,
                                   HeapType sigType,
                                   Expression* printable) {
    if (!shouldBeTrue(sigType.isSignature(),
                      printable,
                      "Heap type must be a signature type")) {
      return;
    }
    auto sig = sigType.getSignature();
    if (!shouldBeTrue(curr->operands.size() == sig.params.size(),
                      printable,
                      "call* param number must match")) {
      return;
    }
    size_t i = 0;
    for (const auto& param : sig.params) {
      if (!shouldBeSubType(curr->operands[i]->type,
                           param,
                           printable,
                           "call param types must match") &&
          !info.quiet) {
        getStream() << "(on argument " << i << ")\n";
      }
      ++i;
    }
    if (curr->isReturn) {
      shouldBeEqual(curr->type,
                    Type(Type::unreachable),
                    printable,
                    "return_call* should have unreachable type");
      shouldBeSubType(
        sig.results,
        getFunction()->getResults(),
        printable,
        "return_call* callee return type must match caller return type");
    } else {
      shouldBeEqualOrFirstIsUnreachable(
        curr->type,
        sig.results,
        printable,
        "call* type must match callee return type");
    }
  }

  // In the common case, we use |curr| as |printable|.
  template<typename T>
  void validateCallParamsAndResult(T* curr, HeapType sigType) {
    validateCallParamsAndResult(curr, sigType, curr);
  }
};

void FunctionValidator::noteLabelName(Name name) {
  if (!name.is()) {
    return;
  }
  auto [_, inserted] = labelNames.insert(name);
  shouldBeTrue(
    inserted,
    name,
    "names in Binaryen IR must be unique - IR generators must ensure that");
}

void FunctionValidator::validatePoppyExpression(Expression* curr) {
  if (curr->type == Type::unreachable) {
    shouldBeTrue(StackUtils::mayBeUnreachable(curr),
                 curr,
                 "Only control flow structures and unreachable polymorphic"
                 " instructions may be unreachable in Poppy IR");
  }
  if (Properties::isControlFlowStructure(curr)) {
    // Check that control flow children (except If conditions) are blocks
    if (auto* if_ = curr->dynCast<If>()) {
      shouldBeTrue(
        if_->condition->is<Pop>(), curr, "Expected condition to be a Pop");
      shouldBeTrue(if_->ifTrue->is<Block>(),
                   curr,
                   "Expected control flow child to be a block");
      shouldBeTrue(!if_->ifFalse || if_->ifFalse->is<Block>(),
                   curr,
                   "Expected control flow child to be a block");
    } else if (!curr->is<Block>()) {
      for (auto* child : ChildIterator(curr)) {
        shouldBeTrue(child->is<Block>(),
                     curr,
                     "Expected control flow child to be a block");
      }
    }
  } else {
    // Check that all children are Pops
    for (auto* child : ChildIterator(curr)) {
      shouldBeTrue(child->is<Pop>(), curr, "Unexpected non-Pop child");
    }
  }
}

void FunctionValidator::visitBlock(Block* curr) {
  if (!getModule()->features.hasMultivalue()) {
    shouldBeTrue(
      !curr->type.isTuple(),
      curr,
      "Multivalue block type require multivalue [--enable-multivalue]");
  }
  // if we are break'ed to, then the value must be right for us
  if (curr->name.is()) {
    noteLabelName(curr->name);
    auto iter = breakTypes.find(curr->name);
    assert(iter != breakTypes.end()); // we set it ourselves
    for (Type breakType : iter->second) {
      if (breakType == Type::none && curr->type == Type::unreachable) {
        // We allow empty breaks to unreachable blocks.
        continue;
      }

      shouldBeSubType(breakType,
                      curr->type,
                      curr,
                      "break type must be a subtype of the target block type");
    }
    breakTypes.erase(iter);
  }
  switch (getFunction()->profile) {
    case IRProfile::Normal:
      validateNormalBlockElements(curr);
      break;
    case IRProfile::Poppy:
      validatePoppyBlockElements(curr);
      break;
  }
}

void FunctionValidator::validateNormalBlockElements(Block* curr) {
  if (curr->list.size() > 1) {
    for (Index i = 0; i < curr->list.size() - 1; i++) {
      if (!shouldBeTrue(
            !curr->list[i]->type.isConcrete(),
            curr,
            "non-final block elements returning a value must be drop()ed "
            "(binaryen's autodrop option might help you)") &&
          !info.quiet) {
        getStream() << "(on index " << i << ":\n"
                    << curr->list[i] << "\n), type: " << curr->list[i]->type
                    << "\n";
      }
    }
  }
  if (curr->list.size() > 0) {
    auto backType = curr->list.back()->type;
    if (!curr->type.isConcrete()) {
      shouldBeFalse(backType.isConcrete(),
                    curr,
                    "if block is not returning a value, final element should "
                    "not flow out a value");
    } else {
      if (backType.isConcrete()) {
        shouldBeSubType(
          backType,
          curr->type,
          curr,
          "block with value and last element with value must match types");
      } else {
        shouldBeUnequal(
          backType,
          Type(Type::none),
          curr,
          "block with value must not have last element that is none");
      }
    }
  }
  if (curr->type.isConcrete()) {
    shouldBeTrue(
      curr->list.size() > 0, curr, "block with a value must not be empty");
  }
}

void FunctionValidator::validatePoppyBlockElements(Block* curr) {
  StackSignature blockSig;
  for (size_t i = 0; i < curr->list.size(); ++i) {
    Expression* expr = curr->list[i];
    if (!shouldBeTrue(
          !expr->is<Pop>(), expr, "Unexpected top-level pop in block")) {
      return;
    }
    StackSignature sig(expr);
    if (!shouldBeTrue(blockSig.composes(sig),
                      curr,
                      "block element has incompatible type") &&
        !info.quiet) {
      getStream() << "(on index " << i << ":\n"
                  << expr << "\n), required: " << sig.params << ", available: ";
      if (blockSig.kind == StackSignature::Polymorphic) {
        getStream() << "polymorphic, ";
      }
      getStream() << blockSig.results << "\n";
      return;
    }
    blockSig += sig;
  }
  if (curr->type == Type::unreachable) {
    shouldBeTrue(blockSig.kind == StackSignature::Polymorphic,
                 curr,
                 "unreachable block should have unreachable element");
  } else {
    if (!shouldBeTrue(
          StackSignature::isSubType(
            blockSig,
            StackSignature(Type::none, curr->type, StackSignature::Fixed)),
          curr,
          "block contents should satisfy block type") &&
        !info.quiet) {
      getStream() << "contents: " << blockSig.results
                  << (blockSig.kind == StackSignature::Polymorphic
                        ? " [polymorphic]"
                        : "")
                  << "\n"
                  << "expected: " << curr->type << "\n";
    }
  }
}

void FunctionValidator::visitLoop(Loop* curr) {
  if (curr->name.is()) {
    noteLabelName(curr->name);
    auto iter = breakTypes.find(curr->name);
    assert(iter != breakTypes.end()); // we set it ourselves
    for (Type breakType : iter->second) {
      shouldBeEqual(breakType,
                    Type(Type::none),
                    curr,
                    "breaks to a loop cannot pass a value");
    }
    breakTypes.erase(iter);
  }
  if (curr->type == Type::none) {
    shouldBeFalse(curr->body->type.isConcrete(),
                  curr,
                  "bad body for a loop that has no value");
  }

  // When there are multiple instructions within a loop, they are wrapped in a
  // Block internally, so visitBlock can take care of verification. Here we
  // check cases when there is only one instruction in a Loop.
  if (!curr->body->is<Block>()) {
    if (!curr->type.isConcrete()) {
      shouldBeFalse(curr->body->type.isConcrete(),
                    curr,
                    "if loop is not returning a value, final element should "
                    "not flow out a value");
    } else {
      shouldBeSubType(curr->body->type,
                      curr->type,
                      curr,
                      "loop with value and body must match types");
    }
  }
}

void FunctionValidator::visitIf(If* curr) {
  shouldBeTrue(curr->condition->type == Type::unreachable ||
                 curr->condition->type == Type::i32,
               curr,
               "if condition must be valid");
  if (!curr->ifFalse) {
    shouldBeFalse(curr->ifTrue->type.isConcrete(),
                  curr,
                  "if without else must not return a value in body");
    if (curr->condition->type != Type::unreachable) {
      shouldBeEqual(curr->type,
                    Type(Type::none),
                    curr,
                    "if without else and reachable condition must be none");
    }
  } else {
    if (curr->type != Type::unreachable) {
      shouldBeSubType(curr->ifTrue->type,
                      curr->type,
                      curr,
                      "returning if-else's true must have right type");
      shouldBeSubType(curr->ifFalse->type,
                      curr->type,
                      curr,
                      "returning if-else's false must have right type");
    } else {
      if (curr->condition->type != Type::unreachable) {
        shouldBeEqual(curr->ifTrue->type,
                      Type(Type::unreachable),
                      curr,
                      "unreachable if-else must have unreachable true");
        shouldBeEqual(curr->ifFalse->type,
                      Type(Type::unreachable),
                      curr,
                      "unreachable if-else must have unreachable false");
      }
    }
    if (curr->ifTrue->type.isConcrete()) {
      shouldBeSubType(curr->ifTrue->type,
                      curr->type,
                      curr,
                      "if type must match concrete ifTrue");
    }
    if (curr->ifFalse->type.isConcrete()) {
      shouldBeSubType(curr->ifFalse->type,
                      curr->type,
                      curr,
                      "if type must match concrete ifFalse");
    }
  }
}

void FunctionValidator::noteBreak(Name name,
                                  Expression* value,
                                  Expression* curr) {
  if (value) {
    shouldBeUnequal(
      value->type, Type(Type::none), curr, "breaks must have a valid value");
  }
  noteBreak(name, value ? value->type : Type::none, curr);
}

void FunctionValidator::noteBreak(Name name, Type valueType, Expression* curr) {
  auto iter = breakTypes.find(name);
  if (!shouldBeTrue(
        iter != breakTypes.end(), curr, "all break targets must be valid")) {
    return;
  }
  iter->second.insert(valueType);
}

void FunctionValidator::visitBreak(Break* curr) {
  noteBreak(curr->name, curr->value, curr);
  if (curr->value) {
    shouldBeTrue(curr->value->type != Type::none,
                 curr,
                 "break value must not have none type");
  }
  if (curr->condition) {
    shouldBeTrue(curr->condition->type == Type::unreachable ||
                   curr->condition->type == Type::i32,
                 curr,
                 "break condition must be i32");
  }
}

void FunctionValidator::visitSwitch(Switch* curr) {
  for (auto& target : curr->targets) {
    noteBreak(target, curr->value, curr);
  }
  noteBreak(curr->default_, curr->value, curr);
  shouldBeTrue(curr->condition->type == Type::unreachable ||
                 curr->condition->type == Type::i32,
               curr,
               "br_table condition must be i32");
}

void FunctionValidator::visitCall(Call* curr) {
  validateReturnCall(curr);
  if (!info.validateGlobally) {
    return;
  }
  auto* target = getModule()->getFunctionOrNull(curr->target);
  if (!shouldBeTrue(!!target, curr, "call target must exist")) {
    return;
  }
  validateCallParamsAndResult(curr, target->type);

  if (Intrinsics(*getModule()).isCallWithoutEffects(curr)) {
    // call.without.effects has the specific form of the last argument being a
    // function reference, which will be called with all the previous arguments.
    // The type must be consistent with that. This, for example, is not:
    //
    //  (call $call.without.effects
    //    (i32.const 1)
    //    (.. some function reference that expects an f64 param and not i32 ..)
    //  )
    if (shouldBeTrue(!curr->operands.empty(),
                     curr,
                     "call.without.effects must have a target operand")) {
      auto* target = curr->operands.back();
      // Validate only in the case that the target is a function. If it isn't,
      // it might be unreachable (which is fine, and we can ignore this), or if
      // the call.without.effects import doesn't have a function as the last
      // parameter, then validateImports() will handle that later (and it's
      // better to emit a single error there than one per callsite here).
      if (target->type.isFunction()) {
        // Copy the original call and remove the reference. It must then match
        // the expected signature.
        struct Copy {
          std::vector<Expression*> operands;
          bool isReturn;
          Type type;
        } copy;
        for (Index i = 0; i < curr->operands.size() - 1; i++) {
          copy.operands.push_back(curr->operands[i]);
        }
        copy.isReturn = curr->isReturn;
        copy.type = curr->type;
        validateCallParamsAndResult(&copy, target->type.getHeapType(), curr);
      }
    }
  }
}

void FunctionValidator::visitCallIndirect(CallIndirect* curr) {
  validateReturnCall(curr);

  if (curr->target->type != Type::unreachable) {
    auto* table = getModule()->getTableOrNull(curr->table);
    if (shouldBeTrue(!!table, curr, "call-indirect table must exist")) {
      shouldBeEqualOrFirstIsUnreachable(
        curr->target->type,
        table->indexType,
        curr,
        "call-indirect call target must match the table index type");
      shouldBeTrue(!!table, curr, "call-indirect table must exist");
      shouldBeTrue(table->type.isFunction(),
                   curr,
                   "call-indirect table must be of function type.");
    }
  }

  validateCallParamsAndResult(curr, curr->heapType);
}

void FunctionValidator::visitConst(Const* curr) {
  shouldBeTrue(curr->type.getFeatures() <= getModule()->features,
               curr,
               "all used features should be allowed");
}

void FunctionValidator::visitLocalGet(LocalGet* curr) {
  shouldBeTrue(curr->type.isConcrete(),
               curr,
               "local.get must have a valid type - check what you provided "
               "when you constructed the node");
  if (shouldBeTrue(curr->index < getFunction()->getNumLocals(),
                   curr,
                   "local.get index must be small enough")) {
    shouldBeTrue(curr->type == getFunction()->getLocalType(curr->index),
                 curr,
                 "local.get must have proper type");
  }
}

void FunctionValidator::visitLocalSet(LocalSet* curr) {
  if (shouldBeTrue(curr->index < getFunction()->getNumLocals(),
                   curr,
                   "local.set index must be small enough")) {
    if (curr->value->type != Type::unreachable) {
      if (curr->type != Type::none) { // tee is ok anyhow
        shouldBeEqual(getFunction()->getLocalType(curr->index),
                      curr->type,
                      curr,
                      "local.set type must be correct");
      }
      shouldBeSubType(curr->value->type,
                      getFunction()->getLocalType(curr->index),
                      curr,
                      "local.set's value type must be correct");
    }
  }
}

void FunctionValidator::visitGlobalGet(GlobalGet* curr) {
  if (!info.validateGlobally) {
    return;
  }
  auto* global = getModule()->getGlobalOrNull(curr->name);
  if (shouldBeTrue(global, curr, "global.get name must be valid")) {
    shouldBeEqual(
      curr->type, global->type, curr, "global.get must have right type");
  }
}

void FunctionValidator::visitGlobalSet(GlobalSet* curr) {
  if (!info.validateGlobally) {
    return;
  }
  auto* global = getModule()->getGlobalOrNull(curr->name);
  if (shouldBeTrue(global,
                   curr,
                   "global.set name must be valid (and not an import; imports "
                   "can't be modified)")) {
    shouldBeTrue(global->mutable_, curr, "global.set global must be mutable");
    shouldBeSubType(curr->value->type,
                    global->type,
                    curr,
                    "global.set value must have right type");
  }
}

void FunctionValidator::visitLoad(Load* curr) {
  auto* memory = getModule()->getMemoryOrNull(curr->memory);
  shouldBeTrue(!!memory, curr, "memory.load memory must exist");
  if (curr->isAtomic) {
    shouldBeTrue(getModule()->features.hasAtomics(),
                 curr,
                 "Atomic operations require threads [--enable-threads]");
    shouldBeTrue(curr->type == Type::i32 || curr->type == Type::i64 ||
                   curr->type == Type::unreachable,
                 curr,
                 "Atomic load should be i32 or i64");
  }
  if (curr->type == Type::v128) {
    shouldBeTrue(getModule()->features.hasSIMD(),
                 curr,
                 "SIMD operations require SIMD [--enable-simd]");
  }
  validateMemBytes(curr->bytes, curr->type, curr);
  validateOffset(curr->offset, memory, curr);
  validateAlignment(curr->align, curr->type, curr->bytes, curr->isAtomic, curr);
  shouldBeEqualOrFirstIsUnreachable(
    curr->ptr->type,
    memory->indexType,
    curr,
    "load pointer type must match memory index type");
  if (curr->isAtomic) {
    shouldBeFalse(curr->signed_, curr, "atomic loads must be unsigned");
    shouldBeIntOrUnreachable(
      curr->type, curr, "atomic loads must be of integers");
  }
}

void FunctionValidator::visitStore(Store* curr) {
  auto* memory = getModule()->getMemoryOrNull(curr->memory);
  shouldBeTrue(!!memory, curr, "memory.store memory must exist");
  if (curr->isAtomic) {
    shouldBeTrue(getModule()->features.hasAtomics(),
                 curr,
                 "Atomic operations require threads [--enable-threads]");
    shouldBeTrue(curr->valueType == Type::i32 || curr->valueType == Type::i64 ||
                   curr->valueType == Type::unreachable,
                 curr,
                 "Atomic store should be i32 or i64");
  }
  if (curr->valueType == Type::v128) {
    shouldBeTrue(getModule()->features.hasSIMD(),
                 curr,
                 "SIMD operations require SIMD [--enable-simd]");
  }
  validateMemBytes(curr->bytes, curr->valueType, curr);
  validateOffset(curr->offset, memory, curr);
  validateAlignment(
    curr->align, curr->valueType, curr->bytes, curr->isAtomic, curr);
  shouldBeEqualOrFirstIsUnreachable(
    curr->ptr->type,
    memory->indexType,
    curr,
    "store pointer must match memory index type");
  shouldBeUnequal(curr->value->type,
                  Type(Type::none),
                  curr,
                  "store value type must not be none");
  shouldBeEqualOrFirstIsUnreachable(
    curr->value->type, curr->valueType, curr, "store value type must match");
  if (curr->isAtomic) {
    shouldBeIntOrUnreachable(
      curr->valueType, curr, "atomic stores must be of integers");
  }
}

void FunctionValidator::visitAtomicRMW(AtomicRMW* curr) {
  auto* memory = getModule()->getMemoryOrNull(curr->memory);
  shouldBeTrue(!!memory, curr, "memory.atomicRMW memory must exist");
  shouldBeTrue(getModule()->features.hasAtomics(),
               curr,
               "Atomic operations require threads [--enable-threads]");
  validateMemBytes(curr->bytes, curr->type, curr);
  shouldBeEqualOrFirstIsUnreachable(
    curr->ptr->type,
    memory->indexType,
    curr,
    "AtomicRMW pointer type must match memory index type");
  shouldBeEqualOrFirstIsUnreachable(curr->type,
                                    curr->value->type,
                                    curr,
                                    "AtomicRMW result type must match operand");
  shouldBeIntOrUnreachable(
    curr->type, curr, "Atomic operations are only valid on int types");
}

void FunctionValidator::visitAtomicCmpxchg(AtomicCmpxchg* curr) {
  auto* memory = getModule()->getMemoryOrNull(curr->memory);
  shouldBeTrue(!!memory, curr, "memory.atomicCmpxchg memory must exist");
  shouldBeTrue(getModule()->features.hasAtomics(),
               curr,
               "Atomic operations require threads [--enable-threads]");
  validateMemBytes(curr->bytes, curr->type, curr);
  shouldBeEqualOrFirstIsUnreachable(
    curr->ptr->type,
    memory->indexType,
    curr,
    "cmpxchg pointer must match memory index type");
  if (curr->expected->type != Type::unreachable &&
      curr->replacement->type != Type::unreachable) {
    shouldBeEqual(curr->expected->type,
                  curr->replacement->type,
                  curr,
                  "cmpxchg operand types must match");
  }
  shouldBeEqualOrFirstIsUnreachable(curr->type,
                                    curr->expected->type,
                                    curr,
                                    "Cmpxchg result type must match expected");
  shouldBeEqualOrFirstIsUnreachable(
    curr->type,
    curr->replacement->type,
    curr,
    "Cmpxchg result type must match replacement");
  shouldBeIntOrUnreachable(curr->expected->type,
                           curr,
                           "Atomic operations are only valid on int types");
}

void FunctionValidator::visitAtomicWait(AtomicWait* curr) {
  auto* memory = getModule()->getMemoryOrNull(curr->memory);
  shouldBeTrue(!!memory, curr, "memory.atomicWait memory must exist");
  shouldBeTrue(getModule()->features.hasAtomics(),
               curr,
               "Atomic operations require threads [--enable-threads]");
  shouldBeEqualOrFirstIsUnreachable(
    curr->type, Type(Type::i32), curr, "AtomicWait must have type i32");
  shouldBeEqualOrFirstIsUnreachable(
    curr->ptr->type,
    memory->indexType,
    curr,
    "AtomicWait pointer must match memory index type");
  shouldBeIntOrUnreachable(
    curr->expected->type, curr, "AtomicWait expected type must be int");
  shouldBeEqualOrFirstIsUnreachable(
    curr->expected->type,
    curr->expectedType,
    curr,
    "AtomicWait expected type must match operand");
  shouldBeEqualOrFirstIsUnreachable(curr->timeout->type,
                                    Type(Type::i64),
                                    curr,
                                    "AtomicWait timeout type must be i64");
}

void FunctionValidator::visitAtomicNotify(AtomicNotify* curr) {
  auto* memory = getModule()->getMemoryOrNull(curr->memory);
  shouldBeTrue(!!memory, curr, "memory.atomicNotify memory must exist");
  shouldBeTrue(getModule()->features.hasAtomics(),
               curr,
               "Atomic operations require threads [--enable-threads]");
  shouldBeEqualOrFirstIsUnreachable(
    curr->type, Type(Type::i32), curr, "AtomicNotify must have type i32");
  shouldBeEqualOrFirstIsUnreachable(
    curr->ptr->type,
    memory->indexType,
    curr,
    "AtomicNotify pointer must match memory index type");
  shouldBeEqualOrFirstIsUnreachable(
    curr->notifyCount->type,
    Type(Type::i32),
    curr,
    "AtomicNotify notifyCount type must be i32");
}

void FunctionValidator::visitAtomicFence(AtomicFence* curr) {
  shouldBeTrue(getModule()->features.hasAtomics(),
               curr,
               "Atomic operations require threads [--enable-threads]");
  shouldBeTrue(curr->order == 0,
               curr,
               "Currently only sequentially consistent atomics are supported, "
               "so AtomicFence's order should be 0");
}

void FunctionValidator::visitSIMDExtract(SIMDExtract* curr) {
  shouldBeTrue(getModule()->features.hasSIMD(),
               curr,
               "SIMD operations require SIMD [--enable-simd]");
  shouldBeEqualOrFirstIsUnreachable(curr->vec->type,
                                    Type(Type::v128),
                                    curr,
                                    "extract_lane must operate on a v128");
  Type lane_t = Type::none;
  size_t lanes = 0;
  switch (curr->op) {
    case ExtractLaneSVecI8x16:
    case ExtractLaneUVecI8x16:
      lane_t = Type::i32;
      lanes = 16;
      break;
    case ExtractLaneSVecI16x8:
    case ExtractLaneUVecI16x8:
      lane_t = Type::i32;
      lanes = 8;
      break;
    case ExtractLaneVecI32x4:
      lane_t = Type::i32;
      lanes = 4;
      break;
    case ExtractLaneVecI64x2:
      lane_t = Type::i64;
      lanes = 2;
      break;
    case ExtractLaneVecF32x4:
      lane_t = Type::f32;
      lanes = 4;
      break;
    case ExtractLaneVecF64x2:
      lane_t = Type::f64;
      lanes = 2;
      break;
  }
  shouldBeEqualOrFirstIsUnreachable(
    curr->type,
    lane_t,
    curr,
    "extract_lane must have same type as vector lane");
  shouldBeTrue(curr->index < lanes, curr, "invalid lane index");
}

void FunctionValidator::visitSIMDReplace(SIMDReplace* curr) {
  shouldBeTrue(getModule()->features.hasSIMD(),
               curr,
               "SIMD operations require SIMD [--enable-simd]");
  shouldBeEqualOrFirstIsUnreachable(
    curr->type, Type(Type::v128), curr, "replace_lane must have type v128");
  shouldBeEqualOrFirstIsUnreachable(curr->vec->type,
                                    Type(Type::v128),
                                    curr,
                                    "replace_lane must operate on a v128");
  Type lane_t = Type::none;
  size_t lanes = 0;
  switch (curr->op) {
    case ReplaceLaneVecI8x16:
      lane_t = Type::i32;
      lanes = 16;
      break;
    case ReplaceLaneVecI16x8:
      lane_t = Type::i32;
      lanes = 8;
      break;
    case ReplaceLaneVecI32x4:
      lane_t = Type::i32;
      lanes = 4;
      break;
    case ReplaceLaneVecI64x2:
      lane_t = Type::i64;
      lanes = 2;
      break;
    case ReplaceLaneVecF32x4:
      lane_t = Type::f32;
      lanes = 4;
      break;
    case ReplaceLaneVecF64x2:
      lane_t = Type::f64;
      lanes = 2;
      break;
  }
  shouldBeEqualOrFirstIsUnreachable(
    curr->value->type, lane_t, curr, "unexpected value type");
  shouldBeTrue(curr->index < lanes, curr, "invalid lane index");
}

void FunctionValidator::visitSIMDShuffle(SIMDShuffle* curr) {
  shouldBeTrue(getModule()->features.hasSIMD(),
               curr,
               "SIMD operations require SIMD [--enable-simd]");
  shouldBeEqualOrFirstIsUnreachable(
    curr->type, Type(Type::v128), curr, "i8x16.shuffle must have type v128");
  shouldBeEqualOrFirstIsUnreachable(
    curr->left->type, Type(Type::v128), curr, "expected operand of type v128");
  shouldBeEqualOrFirstIsUnreachable(
    curr->right->type, Type(Type::v128), curr, "expected operand of type v128");
  for (uint8_t index : curr->mask) {
    shouldBeTrue(index < 32, curr, "Invalid lane index in mask");
  }
}

void FunctionValidator::visitSIMDTernary(SIMDTernary* curr) {
  shouldBeTrue(getModule()->features.hasSIMD(),
               curr,
               "SIMD operations require SIMD [--enable-simd]");
  shouldBeEqualOrFirstIsUnreachable(
    curr->type, Type(Type::v128), curr, "SIMD ternary must have type v128");
  shouldBeEqualOrFirstIsUnreachable(
    curr->a->type, Type(Type::v128), curr, "expected operand of type v128");
  shouldBeEqualOrFirstIsUnreachable(
    curr->b->type, Type(Type::v128), curr, "expected operand of type v128");
  shouldBeEqualOrFirstIsUnreachable(
    curr->c->type, Type(Type::v128), curr, "expected operand of type v128");
}

void FunctionValidator::visitSIMDShift(SIMDShift* curr) {
  shouldBeTrue(getModule()->features.hasSIMD(),
               curr,
               "SIMD operations require SIMD [--enable-simd]");
  shouldBeEqualOrFirstIsUnreachable(
    curr->type, Type(Type::v128), curr, "vector shift must have type v128");
  shouldBeEqualOrFirstIsUnreachable(
    curr->vec->type, Type(Type::v128), curr, "expected operand of type v128");
  shouldBeEqualOrFirstIsUnreachable(curr->shift->type,
                                    Type(Type::i32),
                                    curr,
                                    "expected shift amount to have type i32");
}

void FunctionValidator::visitSIMDLoad(SIMDLoad* curr) {
  auto* memory = getModule()->getMemoryOrNull(curr->memory);
  shouldBeTrue(!!memory, curr, "memory.SIMDLoad memory must exist");
  shouldBeTrue(getModule()->features.hasSIMD(),
               curr,
               "SIMD operations require SIMD [--enable-simd]");
  shouldBeEqualOrFirstIsUnreachable(
    curr->type, Type(Type::v128), curr, "load_splat must have type v128");
  shouldBeEqualOrFirstIsUnreachable(
    curr->ptr->type,
    memory->indexType,
    curr,
    "load_splat address must match memory index type");
  Type memAlignType = Type::none;
  switch (curr->op) {
    case Load8SplatVec128:
    case Load16SplatVec128:
    case Load32SplatVec128:
    case Load32ZeroVec128:
      memAlignType = Type::i32;
      break;
    case Load64SplatVec128:
    case Load8x8SVec128:
    case Load8x8UVec128:
    case Load16x4SVec128:
    case Load16x4UVec128:
    case Load32x2SVec128:
    case Load32x2UVec128:
    case Load64ZeroVec128:
      memAlignType = Type::i64;
      break;
  }
  Index bytes = curr->getMemBytes();
  validateOffset(curr->offset, memory, curr);
  validateAlignment(curr->align, memAlignType, bytes, /*isAtomic=*/false, curr);
}

void FunctionValidator::visitSIMDLoadStoreLane(SIMDLoadStoreLane* curr) {
  auto* memory = getModule()->getMemoryOrNull(curr->memory);
  shouldBeTrue(!!memory, curr, "memory.SIMDLoadStoreLane memory must exist");
  shouldBeTrue(getModule()->features.hasSIMD(),
               curr,
               "SIMD operations require SIMD [--enable-simd]");
  if (curr->isLoad()) {
    shouldBeEqualOrFirstIsUnreachable(
      curr->type, Type(Type::v128), curr, "loadX_lane must have type v128");
  } else {
    shouldBeEqualOrFirstIsUnreachable(
      curr->type, Type(Type::none), curr, "storeX_lane must have type none");
  }
  shouldBeEqualOrFirstIsUnreachable(
    curr->ptr->type,
    memory->indexType,
    curr,
    "loadX_lane or storeX_lane address must match memory index type");
  shouldBeEqualOrFirstIsUnreachable(
    curr->vec->type,
    Type(Type::v128),
    curr,
    "loadX_lane or storeX_lane vector argument must have type v128");
  size_t lanes;
  Type memAlignType = Type::none;
  switch (curr->op) {
    case Load8LaneVec128:
    case Store8LaneVec128:
      lanes = 16;
      memAlignType = Type::i32;
      break;
    case Load16LaneVec128:
    case Store16LaneVec128:
      lanes = 8;
      memAlignType = Type::i32;
      break;
    case Load32LaneVec128:
    case Store32LaneVec128:
      lanes = 4;
      memAlignType = Type::i32;
      break;
    case Load64LaneVec128:
    case Store64LaneVec128:
      lanes = 2;
      memAlignType = Type::i64;
      break;
    default:
      WASM_UNREACHABLE("Unexpected SIMDLoadStoreLane op");
  }
  Index bytes = curr->getMemBytes();
  validateOffset(curr->offset, memory, curr);
  validateAlignment(curr->align, memAlignType, bytes, /*isAtomic=*/false, curr);
  shouldBeTrue(curr->index < lanes, curr, "invalid lane index");
}

void FunctionValidator::visitMemoryInit(MemoryInit* curr) {
  auto* memory = getModule()->getMemoryOrNull(curr->memory);
  shouldBeTrue(
    getModule()->features.hasBulkMemory(),
    curr,
    "Bulk memory operations require bulk memory [--enable-bulk-memory]");
  shouldBeEqualOrFirstIsUnreachable(
    curr->type, Type(Type::none), curr, "memory.init must have type none");
  shouldBeEqualOrFirstIsUnreachable(
    curr->dest->type,
    memory->indexType,
    curr,
    "memory.init dest must match memory index type");
  shouldBeEqualOrFirstIsUnreachable(curr->offset->type,
                                    Type(Type::i32),
                                    curr,
                                    "memory.init offset must be an i32");
  shouldBeEqualOrFirstIsUnreachable(
    curr->size->type, Type(Type::i32), curr, "memory.init size must be an i32");
  if (!shouldBeTrue(!!memory, curr, "memory.init memory must exist")) {
    return;
  }
  shouldBeTrue(getModule()->getDataSegmentOrNull(curr->segment),
               curr,
               "memory.init segment should exist");
}

void FunctionValidator::visitDataDrop(DataDrop* curr) {
  shouldBeTrue(
    getModule()->features.hasBulkMemory(),
    curr,
    "Bulk memory operations require bulk memory [--enable-bulk-memory]");
  shouldBeEqualOrFirstIsUnreachable(
    curr->type, Type(Type::none), curr, "data.drop must have type none");
  shouldBeTrue(getModule()->getDataSegmentOrNull(curr->segment),
               curr,
               "data.drop segment should exist");
}

void FunctionValidator::visitMemoryCopy(MemoryCopy* curr) {
  shouldBeTrue(
    getModule()->features.hasBulkMemory(),
    curr,
    "Bulk memory operations require bulk memory [--enable-bulk-memory]");
  shouldBeEqualOrFirstIsUnreachable(
    curr->type, Type(Type::none), curr, "memory.copy must have type none");
  auto* destMemory = getModule()->getMemoryOrNull(curr->destMemory);
  shouldBeTrue(!!destMemory, curr, "memory.copy destMemory must exist");
  auto* sourceMemory = getModule()->getMemoryOrNull(curr->sourceMemory);
  shouldBeTrue(!!sourceMemory, curr, "memory.copy sourceMemory must exist");
  shouldBeEqualOrFirstIsUnreachable(
    curr->dest->type,
    destMemory->indexType,
    curr,
    "memory.copy dest must match destMemory index type");
  shouldBeEqualOrFirstIsUnreachable(
    curr->source->type,
    sourceMemory->indexType,
    curr,
    "memory.copy source must match sourceMemory index type");
  shouldBeEqualOrFirstIsUnreachable(
    curr->size->type,
    destMemory->indexType,
    curr,
    "memory.copy size must match destMemory index type");
  shouldBeEqualOrFirstIsUnreachable(
    curr->size->type,
    sourceMemory->indexType,
    curr,
    "memory.copy size must match destMemory index type");
}

void FunctionValidator::visitMemoryFill(MemoryFill* curr) {
  auto* memory = getModule()->getMemoryOrNull(curr->memory);
  shouldBeTrue(
    getModule()->features.hasBulkMemory(),
    curr,
    "Bulk memory operations require bulk memory [--enable-bulk-memory]");
  shouldBeEqualOrFirstIsUnreachable(
    curr->type, Type(Type::none), curr, "memory.fill must have type none");
  shouldBeEqualOrFirstIsUnreachable(
    curr->dest->type,
    memory->indexType,
    curr,
    "memory.fill dest must match memory index type");
  shouldBeEqualOrFirstIsUnreachable(curr->value->type,
                                    Type(Type::i32),
                                    curr,
                                    "memory.fill value must be an i32");
  shouldBeEqualOrFirstIsUnreachable(
    curr->size->type,
    memory->indexType,
    curr,
    "memory.fill size must match memory index type");
  shouldBeTrue(!!memory, curr, "memory.fill memory must exist");
}

void FunctionValidator::validateMemBytes(uint8_t bytes,
                                         Type type,
                                         Expression* curr) {
  switch (type.getBasic()) {
    case Type::i32:
      shouldBeTrue(bytes == 1 || bytes == 2 || bytes == 4,
                   curr,
                   "expected i32 operation to touch 1, 2, or 4 bytes");
      break;
    case Type::i64:
      shouldBeTrue(bytes == 1 || bytes == 2 || bytes == 4 || bytes == 8,
                   curr,
                   "expected i64 operation to touch 1, 2, 4, or 8 bytes");
      break;
    case Type::f32:
      shouldBeEqual(
        bytes, uint8_t(4), curr, "expected f32 operation to touch 4 bytes");
      break;
    case Type::f64:
      shouldBeEqual(
        bytes, uint8_t(8), curr, "expected f64 operation to touch 8 bytes");
      break;
    case Type::v128:
      shouldBeEqual(
        bytes, uint8_t(16), curr, "expected v128 operation to touch 16 bytes");
      break;
    case Type::unreachable:
      break;
    case Type::none:
      WASM_UNREACHABLE("unexpected type");
  }
}

void FunctionValidator::visitBinary(Binary* curr) {
  if (curr->left->type != Type::unreachable &&
      curr->right->type != Type::unreachable) {
    shouldBeEqual(curr->left->type,
                  curr->right->type,
                  curr,
                  "binary child types must be equal");
  }
  switch (curr->op) {
    case AddInt32:
    case SubInt32:
    case MulInt32:
    case DivSInt32:
    case DivUInt32:
    case RemSInt32:
    case RemUInt32:
    case AndInt32:
    case OrInt32:
    case XorInt32:
    case ShlInt32:
    case ShrUInt32:
    case ShrSInt32:
    case RotLInt32:
    case RotRInt32:
    case EqInt32:
    case NeInt32:
    case LtSInt32:
    case LtUInt32:
    case LeSInt32:
    case LeUInt32:
    case GtSInt32:
    case GtUInt32:
    case GeSInt32:
    case GeUInt32: {
      shouldBeEqualOrFirstIsUnreachable(
        curr->left->type, Type(Type::i32), curr, "i32 op");
      break;
    }
    case AddInt64:
    case SubInt64:
    case MulInt64:
    case DivSInt64:
    case DivUInt64:
    case RemSInt64:
    case RemUInt64:
    case AndInt64:
    case OrInt64:
    case XorInt64:
    case ShlInt64:
    case ShrUInt64:
    case ShrSInt64:
    case RotLInt64:
    case RotRInt64:
    case EqInt64:
    case NeInt64:
    case LtSInt64:
    case LtUInt64:
    case LeSInt64:
    case LeUInt64:
    case GtSInt64:
    case GtUInt64:
    case GeSInt64:
    case GeUInt64: {
      shouldBeEqualOrFirstIsUnreachable(
        curr->left->type, Type(Type::i64), curr, "i64 op");
      break;
    }
    case AddFloat32:
    case SubFloat32:
    case MulFloat32:
    case DivFloat32:
    case CopySignFloat32:
    case MinFloat32:
    case MaxFloat32:
    case EqFloat32:
    case NeFloat32:
    case LtFloat32:
    case LeFloat32:
    case GtFloat32:
    case GeFloat32: {
      shouldBeEqualOrFirstIsUnreachable(
        curr->left->type, Type(Type::f32), curr, "f32 op");
      break;
    }
    case AddFloat64:
    case SubFloat64:
    case MulFloat64:
    case DivFloat64:
    case CopySignFloat64:
    case MinFloat64:
    case MaxFloat64:
    case EqFloat64:
    case NeFloat64:
    case LtFloat64:
    case LeFloat64:
    case GtFloat64:
    case GeFloat64: {
      shouldBeEqualOrFirstIsUnreachable(
        curr->left->type, Type(Type::f64), curr, "f64 op");
      break;
    }
    case EqVecI8x16:
    case NeVecI8x16:
    case LtSVecI8x16:
    case LtUVecI8x16:
    case LeSVecI8x16:
    case LeUVecI8x16:
    case GtSVecI8x16:
    case GtUVecI8x16:
    case GeSVecI8x16:
    case GeUVecI8x16:
    case EqVecI16x8:
    case NeVecI16x8:
    case LtSVecI16x8:
    case LtUVecI16x8:
    case LeSVecI16x8:
    case LeUVecI16x8:
    case GtSVecI16x8:
    case GtUVecI16x8:
    case GeSVecI16x8:
    case GeUVecI16x8:
    case EqVecI32x4:
    case NeVecI32x4:
    case LtSVecI32x4:
    case LtUVecI32x4:
    case LeSVecI32x4:
    case LeUVecI32x4:
    case GtSVecI32x4:
    case GtUVecI32x4:
    case GeSVecI32x4:
    case GeUVecI32x4:
    case EqVecI64x2:
    case NeVecI64x2:
    case LtSVecI64x2:
    case LeSVecI64x2:
    case GtSVecI64x2:
    case GeSVecI64x2:
    case EqVecF32x4:
    case NeVecF32x4:
    case LtVecF32x4:
    case LeVecF32x4:
    case GtVecF32x4:
    case GeVecF32x4:
    case EqVecF64x2:
    case NeVecF64x2:
    case LtVecF64x2:
    case LeVecF64x2:
    case GtVecF64x2:
    case GeVecF64x2:
    case AndVec128:
    case OrVec128:
    case XorVec128:
    case AndNotVec128:
    case AddVecI8x16:
    case AddSatSVecI8x16:
    case AddSatUVecI8x16:
    case SubVecI8x16:
    case SubSatSVecI8x16:
    case SubSatUVecI8x16:
    case MinSVecI8x16:
    case MinUVecI8x16:
    case MaxSVecI8x16:
    case MaxUVecI8x16:
    case AvgrUVecI8x16:
    case Q15MulrSatSVecI16x8:
    case ExtMulLowSVecI16x8:
    case ExtMulHighSVecI16x8:
    case ExtMulLowUVecI16x8:
    case ExtMulHighUVecI16x8:
    case AddVecI16x8:
    case AddSatSVecI16x8:
    case AddSatUVecI16x8:
    case SubVecI16x8:
    case SubSatSVecI16x8:
    case SubSatUVecI16x8:
    case MulVecI16x8:
    case MinSVecI16x8:
    case MinUVecI16x8:
    case MaxSVecI16x8:
    case MaxUVecI16x8:
    case AvgrUVecI16x8:
    case AddVecI32x4:
    case SubVecI32x4:
    case MulVecI32x4:
    case MinSVecI32x4:
    case MinUVecI32x4:
    case MaxSVecI32x4:
    case MaxUVecI32x4:
    case DotSVecI16x8ToVecI32x4:
    case ExtMulLowSVecI32x4:
    case ExtMulHighSVecI32x4:
    case ExtMulLowUVecI32x4:
    case ExtMulHighUVecI32x4:
    case AddVecI64x2:
    case SubVecI64x2:
    case MulVecI64x2:
    case ExtMulLowSVecI64x2:
    case ExtMulHighSVecI64x2:
    case ExtMulLowUVecI64x2:
    case ExtMulHighUVecI64x2:
    case AddVecF32x4:
    case SubVecF32x4:
    case MulVecF32x4:
    case DivVecF32x4:
    case MinVecF32x4:
    case MaxVecF32x4:
    case PMinVecF32x4:
    case PMaxVecF32x4:
    case RelaxedMinVecF32x4:
    case RelaxedMaxVecF32x4:
    case AddVecF64x2:
    case SubVecF64x2:
    case MulVecF64x2:
    case DivVecF64x2:
    case MinVecF64x2:
    case MaxVecF64x2:
    case PMinVecF64x2:
    case PMaxVecF64x2:
    case RelaxedMinVecF64x2:
    case RelaxedMaxVecF64x2:
    case NarrowSVecI16x8ToVecI8x16:
    case NarrowUVecI16x8ToVecI8x16:
    case NarrowSVecI32x4ToVecI16x8:
    case NarrowUVecI32x4ToVecI16x8:
    case SwizzleVecI8x16:
    case RelaxedSwizzleVecI8x16:
    case RelaxedQ15MulrSVecI16x8:
    case DotI8x16I7x16SToVecI16x8: {
      shouldBeEqualOrFirstIsUnreachable(
        curr->left->type, Type(Type::v128), curr, "v128 op");
      shouldBeEqualOrFirstIsUnreachable(
        curr->right->type, Type(Type::v128), curr, "v128 op");
      break;
    }
    case InvalidBinary:
      WASM_UNREACHABLE("invliad binary op");
  }
  shouldBeTrue(Features::get(curr->op) <= getModule()->features,
               curr,
               "all used features should be allowed");
}

void FunctionValidator::visitUnary(Unary* curr) {
  shouldBeUnequal(curr->value->type,
                  Type(Type::none),
                  curr,
                  "unaries must not receive a none as their input");
  if (curr->value->type == Type::unreachable) {
    return; // nothing to check
  }
  switch (curr->op) {
    case ClzInt32:
    case CtzInt32:
    case PopcntInt32: {
      shouldBeEqual(curr->value->type,
                    Type(Type::i32),
                    curr,
                    "i32 unary value type must be correct");
      break;
    }
    case ClzInt64:
    case CtzInt64:
    case PopcntInt64: {
      shouldBeEqual(curr->value->type,
                    Type(Type::i64),
                    curr,
                    "i64 unary value type must be correct");
      break;
    }
    case NegFloat32:
    case AbsFloat32:
    case CeilFloat32:
    case FloorFloat32:
    case TruncFloat32:
    case NearestFloat32:
    case SqrtFloat32: {
      shouldBeEqual(curr->value->type,
                    Type(Type::f32),
                    curr,
                    "f32 unary value type must be correct");
      break;
    }
    case NegFloat64:
    case AbsFloat64:
    case CeilFloat64:
    case FloorFloat64:
    case TruncFloat64:
    case NearestFloat64:
    case SqrtFloat64: {
      shouldBeEqual(curr->value->type,
                    Type(Type::f64),
                    curr,
                    "f64 unary value type must be correct");
      break;
    }
    case EqZInt32: {
      shouldBeTrue(
        curr->value->type == Type::i32, curr, "i32.eqz input must be i32");
      break;
    }
    case EqZInt64: {
      shouldBeTrue(curr->value->type == Type(Type::i64),
                   curr,
                   "i64.eqz input must be i64");
      break;
    }
    case ExtendSInt32:
    case ExtendUInt32:
    case ExtendS8Int32:
    case ExtendS16Int32: {
      shouldBeEqual(curr->value->type,
                    Type(Type::i32),
                    curr,
                    "extend type must be correct");
      break;
    }
    case ExtendS8Int64:
    case ExtendS16Int64:
    case ExtendS32Int64: {
      shouldBeEqual(curr->value->type,
                    Type(Type::i64),
                    curr,
                    "extend type must be correct");
      break;
    }
    case WrapInt64: {
      shouldBeEqual(
        curr->value->type, Type(Type::i64), curr, "wrap type must be correct");
      break;
    }
    case TruncSFloat32ToInt32:
    case TruncSFloat32ToInt64:
    case TruncUFloat32ToInt32:
    case TruncUFloat32ToInt64: {
      shouldBeEqual(
        curr->value->type, Type(Type::f32), curr, "trunc type must be correct");
      break;
    }
    case TruncSatSFloat32ToInt32:
    case TruncSatSFloat32ToInt64:
    case TruncSatUFloat32ToInt32:
    case TruncSatUFloat32ToInt64: {
      shouldBeEqual(
        curr->value->type, Type(Type::f32), curr, "trunc type must be correct");
      break;
    }
    case TruncSFloat64ToInt32:
    case TruncSFloat64ToInt64:
    case TruncUFloat64ToInt32:
    case TruncUFloat64ToInt64: {
      shouldBeEqual(
        curr->value->type, Type(Type::f64), curr, "trunc type must be correct");
      break;
    }
    case TruncSatSFloat64ToInt32:
    case TruncSatSFloat64ToInt64:
    case TruncSatUFloat64ToInt32:
    case TruncSatUFloat64ToInt64: {
      shouldBeEqual(
        curr->value->type, Type(Type::f64), curr, "trunc type must be correct");
      break;
    }
    case ReinterpretFloat32: {
      shouldBeEqual(curr->value->type,
                    Type(Type::f32),
                    curr,
                    "reinterpret/f32 type must be correct");
      break;
    }
    case ReinterpretFloat64: {
      shouldBeEqual(curr->value->type,
                    Type(Type::f64),
                    curr,
                    "reinterpret/f64 type must be correct");
      break;
    }
    case ConvertUInt32ToFloat32:
    case ConvertUInt32ToFloat64:
    case ConvertSInt32ToFloat32:
    case ConvertSInt32ToFloat64: {
      shouldBeEqual(curr->value->type,
                    Type(Type::i32),
                    curr,
                    "convert type must be correct");
      break;
    }
    case ConvertUInt64ToFloat32:
    case ConvertUInt64ToFloat64:
    case ConvertSInt64ToFloat32:
    case ConvertSInt64ToFloat64: {
      shouldBeEqual(curr->value->type,
                    Type(Type::i64),
                    curr,
                    "convert type must be correct");
      break;
    }
    case PromoteFloat32: {
      shouldBeEqual(curr->value->type,
                    Type(Type::f32),
                    curr,
                    "promote type must be correct");
      break;
    }
    case DemoteFloat64: {
      shouldBeEqual(curr->value->type,
                    Type(Type::f64),
                    curr,
                    "demote type must be correct");
      break;
    }
    case ReinterpretInt32: {
      shouldBeEqual(curr->value->type,
                    Type(Type::i32),
                    curr,
                    "reinterpret/i32 type must be correct");
      break;
    }
    case ReinterpretInt64: {
      shouldBeEqual(curr->value->type,
                    Type(Type::i64),
                    curr,
                    "reinterpret/i64 type must be correct");
      break;
    }
    case SplatVecI8x16:
    case SplatVecI16x8:
    case SplatVecI32x4:
      shouldBeEqual(
        curr->type, Type(Type::v128), curr, "expected splat to have v128 type");
      shouldBeEqual(
        curr->value->type, Type(Type::i32), curr, "expected i32 splat value");
      break;
    case SplatVecI64x2:
      shouldBeEqual(
        curr->type, Type(Type::v128), curr, "expected splat to have v128 type");
      shouldBeEqual(
        curr->value->type, Type(Type::i64), curr, "expected i64 splat value");
      break;
    case SplatVecF32x4:
      shouldBeEqual(
        curr->type, Type(Type::v128), curr, "expected splat to have v128 type");
      shouldBeEqual(
        curr->value->type, Type(Type::f32), curr, "expected f32 splat value");
      break;
    case SplatVecF64x2:
      shouldBeEqual(
        curr->type, Type(Type::v128), curr, "expected splat to have v128 type");
      shouldBeEqual(
        curr->value->type, Type(Type::f64), curr, "expected f64 splat value");
      break;
    case NotVec128:
    case PopcntVecI8x16:
    case AbsVecI8x16:
    case AbsVecI16x8:
    case AbsVecI32x4:
    case AbsVecI64x2:
    case NegVecI8x16:
    case NegVecI16x8:
    case NegVecI32x4:
    case NegVecI64x2:
    case AbsVecF32x4:
    case NegVecF32x4:
    case SqrtVecF32x4:
    case CeilVecF32x4:
    case FloorVecF32x4:
    case TruncVecF32x4:
    case NearestVecF32x4:
    case AbsVecF64x2:
    case NegVecF64x2:
    case SqrtVecF64x2:
    case CeilVecF64x2:
    case FloorVecF64x2:
    case TruncVecF64x2:
    case NearestVecF64x2:
    case ExtAddPairwiseSVecI8x16ToI16x8:
    case ExtAddPairwiseUVecI8x16ToI16x8:
    case ExtAddPairwiseSVecI16x8ToI32x4:
    case ExtAddPairwiseUVecI16x8ToI32x4:
    case TruncSatSVecF32x4ToVecI32x4:
    case TruncSatUVecF32x4ToVecI32x4:
    case ConvertSVecI32x4ToVecF32x4:
    case ConvertUVecI32x4ToVecF32x4:
    case ExtendLowSVecI8x16ToVecI16x8:
    case ExtendHighSVecI8x16ToVecI16x8:
    case ExtendLowUVecI8x16ToVecI16x8:
    case ExtendHighUVecI8x16ToVecI16x8:
    case ExtendLowSVecI16x8ToVecI32x4:
    case ExtendHighSVecI16x8ToVecI32x4:
    case ExtendLowUVecI16x8ToVecI32x4:
    case ExtendHighUVecI16x8ToVecI32x4:
    case ExtendLowSVecI32x4ToVecI64x2:
    case ExtendHighSVecI32x4ToVecI64x2:
    case ExtendLowUVecI32x4ToVecI64x2:
    case ExtendHighUVecI32x4ToVecI64x2:
    case ConvertLowSVecI32x4ToVecF64x2:
    case ConvertLowUVecI32x4ToVecF64x2:
    case TruncSatZeroSVecF64x2ToVecI32x4:
    case TruncSatZeroUVecF64x2ToVecI32x4:
    case DemoteZeroVecF64x2ToVecF32x4:
    case PromoteLowVecF32x4ToVecF64x2:
    case RelaxedTruncSVecF32x4ToVecI32x4:
    case RelaxedTruncUVecF32x4ToVecI32x4:
    case RelaxedTruncZeroSVecF64x2ToVecI32x4:
    case RelaxedTruncZeroUVecF64x2ToVecI32x4:
      shouldBeEqual(curr->type, Type(Type::v128), curr, "expected v128 type");
      shouldBeEqual(
        curr->value->type, Type(Type::v128), curr, "expected v128 operand");
      break;
    case AnyTrueVec128:
    case AllTrueVecI8x16:
    case AllTrueVecI16x8:
    case AllTrueVecI32x4:
    case AllTrueVecI64x2:
    case BitmaskVecI8x16:
    case BitmaskVecI16x8:
    case BitmaskVecI32x4:
    case BitmaskVecI64x2:
      shouldBeEqual(curr->type, Type(Type::i32), curr, "expected i32 type");
      shouldBeEqual(
        curr->value->type, Type(Type::v128), curr, "expected v128 operand");
      break;
    case InvalidUnary:
      WASM_UNREACHABLE("invalid unary op");
  }
  shouldBeTrue(Features::get(curr->op) <= getModule()->features,
               curr,
               "all used features should be allowed");
}

void FunctionValidator::visitSelect(Select* curr) {
  shouldBeUnequal(
    curr->ifFalse->type, Type(Type::none), curr, "select right must be valid");
  shouldBeUnequal(
    curr->type, Type(Type::none), curr, "select type must be valid");
  shouldBeTrue(curr->condition->type == Type::unreachable ||
                 curr->condition->type == Type::i32,
               curr,
               "select condition must be valid");
  if (curr->ifTrue->type != Type::unreachable) {
    shouldBeFalse(
      curr->ifTrue->type.isTuple(), curr, "select value may not be a tuple");
  }
  if (curr->ifFalse->type != Type::unreachable) {
    shouldBeFalse(
      curr->ifFalse->type.isTuple(), curr, "select value may not be a tuple");
  }
  if (curr->type != Type::unreachable) {
    shouldBeTrue(Type::isSubType(curr->ifTrue->type, curr->type),
                 curr,
                 "select's left expression must be subtype of select's type");
    shouldBeTrue(Type::isSubType(curr->ifFalse->type, curr->type),
                 curr,
                 "select's right expression must be subtype of select's type");
  }
}

void FunctionValidator::visitDrop(Drop* curr) {
  shouldBeTrue(curr->value->type.isConcrete() ||
                 curr->value->type == Type::unreachable,
               curr,
               "can only drop a valid value");
  if (curr->value->type.isTuple()) {
    shouldBeTrue(getModule()->features.hasMultivalue(),
                 curr,
                 "Tuples drops are not allowed unless multivalue is enabled");
  }
}

void FunctionValidator::visitReturn(Return* curr) {
  auto* func = getFunction();
  if (!shouldBeTrue(!!func, curr, "return must be within a function")) {
    return;
  }
  auto results = func->getResults();
  if (results.isConcrete()) {
    if (!shouldBeTrue(
          curr->value, curr, "concrete return should have a value")) {
      return;
    }
    shouldBeSubType(
      curr->value->type,
      results,
      curr,
      "return value should be a subtype of the function result type");
  } else {
    shouldBeTrue(!curr->value, curr, "return should not have a value");
  }
}

void FunctionValidator::visitMemorySize(MemorySize* curr) {
  auto* memory = getModule()->getMemoryOrNull(curr->memory);
  shouldBeTrue(!!memory, curr, "memory.size memory must exist");
}

void FunctionValidator::visitMemoryGrow(MemoryGrow* curr) {
  auto* memory = getModule()->getMemoryOrNull(curr->memory);
  shouldBeTrue(!!memory, curr, "memory.grow memory must exist");
  shouldBeEqualOrFirstIsUnreachable(curr->delta->type,
                                    memory->indexType,
                                    curr,
                                    "memory.grow must match memory index type");
}

void FunctionValidator::visitRefNull(RefNull* curr) {
  // If we are not in a function, this is a global location like a table. We
  // allow RefNull there as we represent tables that way regardless of what
  // features are enabled.
  auto feats = curr->type.getFeatures();
  if (!shouldBeTrue(!getFunction() || feats <= getModule()->features,
                    curr,
                    "ref.null requires additional features")) {
    getStream() << getMissingFeaturesList(*getModule(), feats) << '\n';
  }
  if (!shouldBeTrue(
        curr->type.isNullable(), curr, "ref.null types must be nullable")) {
    return;
  }
  shouldBeTrue(
    curr->type.isNull(), curr, "ref.null must have a bottom heap type");
}

void FunctionValidator::visitRefIsNull(RefIsNull* curr) {
  shouldBeTrue(
    getModule()->features.hasReferenceTypes(),
    curr,
    "ref.is_null requires reference-types [--enable-reference-types]");
  shouldBeTrue(curr->value->type == Type::unreachable ||
                 curr->value->type.isRef(),
               curr->value,
               "ref.is_null's argument should be a reference type");
}

void FunctionValidator::visitRefAs(RefAs* curr) {
  switch (curr->op) {
    default:
      // TODO: validate all the other ref.as_*
      break;
    case AnyConvertExtern: {
      shouldBeTrue(getModule()->features.hasGC(),
                   curr,
                   "any.convert_extern requries GC [--enable-gc]");
      if (curr->type == Type::unreachable) {
        return;
      }
      shouldBeSubTypeIgnoringShared(
        curr->value->type,
        Type(HeapType::ext, Nullable),
        curr->value,
        "any.convert_extern value should be an externref");
      break;
    }
    case ExternConvertAny: {
      shouldBeTrue(getModule()->features.hasGC(),
                   curr,
                   "extern.convert_any requries GC [--enable-gc]");
      if (curr->type == Type::unreachable) {
        return;
      }
      shouldBeSubTypeIgnoringShared(
        curr->value->type,
        Type(HeapType::any, Nullable),
        curr->value,
        "extern.convert_any value should be an anyref");
      break;
    }
  }
}

void FunctionValidator::visitRefFunc(RefFunc* curr) {
  // If we are not in a function, this is a global location like a table. We
  // allow RefFunc there as we represent tables that way regardless of what
  // features are enabled.
  shouldBeTrue(!getFunction() || getModule()->features.hasReferenceTypes(),
               curr,
               "ref.func requires reference-types [--enable-reference-types]");
  if (!info.validateGlobally) {
    return;
  }
  auto* func = getModule()->getFunctionOrNull(curr->func);
  shouldBeTrue(!!func, curr, "function argument of ref.func must exist");
  shouldBeTrue(curr->type.isFunction(),
               curr,
               "ref.func must have a function reference type");
  shouldBeTrue(
    !curr->type.isNullable(), curr, "ref.func must have non-nullable type");
  // TODO: verify it also has a typed function references type, and the right
  // one,
  //   curr->type.getHeapType().getSignature()
  // That is blocked on having the ability to create signature types in the C
  // API (for now those users create the type with funcref). This also needs to
  // be fixed in LegalizeJSInterface and FuncCastEmulation and other places that
  // update function types.
  // TODO: check for non-nullability
}

void FunctionValidator::visitRefEq(RefEq* curr) {
  Type eqref = Type(HeapType::eq, Nullable);
  shouldBeTrue(
    getModule()->features.hasGC(), curr, "ref.eq requires gc [--enable-gc]");
  shouldBeSubTypeIgnoringShared(
    curr->left->type,
    eqref,
    curr->left,
    "ref.eq's left argument should be a subtype of eqref");
  shouldBeSubTypeIgnoringShared(
    curr->right->type,
    eqref,
    curr->right,
    "ref.eq's right argument should be a subtype of eqref");
}

void FunctionValidator::visitTableGet(TableGet* curr) {
  shouldBeTrue(getModule()->features.hasReferenceTypes(),
               curr,
               "table.get requires reference types [--enable-reference-types]");
  auto* table = getModule()->getTableOrNull(curr->table);
  if (shouldBeTrue(!!table, curr, "table.get table must exist")) {
    if (curr->type != Type::unreachable) {
      shouldBeEqual(curr->type,
                    table->type,
                    curr,
                    "table.get must have same type as table.");
    }
    shouldBeEqualOrFirstIsUnreachable(
      curr->index->type,
      table->indexType,
      curr,
      "table.get index must match the table index type.");
  }
}

void FunctionValidator::visitTableSet(TableSet* curr) {
  shouldBeTrue(getModule()->features.hasReferenceTypes(),
               curr,
               "table.set requires reference types [--enable-reference-types]");
  auto* table = getModule()->getTableOrNull(curr->table);
  if (shouldBeTrue(!!table, curr, "table.set table must exist")) {
    if (curr->type != Type::unreachable) {
      shouldBeSubType(curr->value->type,
                      table->type,
                      curr,
                      "table.set value must have right type");
    }
    shouldBeEqualOrFirstIsUnreachable(
      curr->index->type,
      table->indexType,
      curr,
      "table.set index must match the table index type.");
  }
}

void FunctionValidator::visitTableSize(TableSize* curr) {
  shouldBeTrue(
    getModule()->features.hasReferenceTypes(),
    curr,
    "table.size requires reference types [--enable-reference-types]");
  auto* table = getModule()->getTableOrNull(curr->table);
  shouldBeTrue(!!table, curr, "table.size table must exist");
}

void FunctionValidator::visitTableGrow(TableGrow* curr) {
  shouldBeTrue(
    getModule()->features.hasReferenceTypes(),
    curr,
    "table.grow requires reference types [--enable-reference-types]");
  auto* table = getModule()->getTableOrNull(curr->table);
  if (shouldBeTrue(!!table, curr, "table.grow table must exist") &&
      curr->type != Type::unreachable) {
    shouldBeSubType(curr->value->type,
                    table->type,
                    curr,
                    "table.grow value must have right type");
    shouldBeEqual(curr->delta->type,
                  table->indexType,
                  curr,
                  "table.grow must match table index type");
  }
}

void FunctionValidator::visitTableFill(TableFill* curr) {
  shouldBeTrue(getModule()->features.hasBulkMemory(),
               curr,
               "table.fill requires bulk-memory [--enable-bulk-memory]");
  auto* table = getModule()->getTableOrNull(curr->table);
  if (shouldBeTrue(!!table, curr, "table.fill table must exist")) {
    shouldBeSubType(curr->value->type,
                    table->type,
                    curr,
                    "table.fill value must have right type");
    shouldBeEqualOrFirstIsUnreachable(
      curr->dest->type,
      table->indexType,
      curr,
      "table.fill dest must match table index type");
    shouldBeEqualOrFirstIsUnreachable(
      curr->size->type,
      table->indexType,
      curr,
      "table.fill size must match table index type");
  }
}

void FunctionValidator::visitTableCopy(TableCopy* curr) {
  shouldBeTrue(getModule()->features.hasBulkMemory(),
               curr,
               "table.copy requires bulk-memory [--enable-bulk-memory]");
  auto* sourceTable = getModule()->getTableOrNull(curr->sourceTable);
  auto* destTable = getModule()->getTableOrNull(curr->destTable);
  if (shouldBeTrue(!!sourceTable, curr, "table.copy source table must exist") &&
      shouldBeTrue(!!destTable, curr, "table.copy dest table must exist")) {
    shouldBeSubType(sourceTable->type,
                    destTable->type,
                    curr,
                    "table.copy source must have right type for dest");
  }
  shouldBeEqualOrFirstIsUnreachable(
    curr->dest->type, Type(Type::i32), curr, "table.copy dest must be i32");
  shouldBeEqualOrFirstIsUnreachable(
    curr->source->type, Type(Type::i32), curr, "table.copy source must be i32");
  shouldBeEqualOrFirstIsUnreachable(
    curr->size->type, Type(Type::i32), curr, "table.copy size must be i32");
}

void FunctionValidator::noteDelegate(Name name, Expression* curr) {
  if (name != DELEGATE_CALLER_TARGET) {
    shouldBeTrue(delegateTargetNames.count(name) != 0,
                 curr,
                 "all delegate targets must be valid");
  }
}

void FunctionValidator::noteRethrow(Name name, Expression* curr) {
  shouldBeTrue(rethrowTargetNames.count(name) != 0,
               curr,
               "all rethrow targets must be valid");
}

void FunctionValidator::visitTry(Try* curr) {
  shouldBeTrue(getModule()->features.hasExceptionHandling(),
               curr,
               "try requires exception-handling [--enable-exception-handling]");
  if (curr->name.is()) {
    noteLabelName(curr->name);
  }
  if (curr->type != Type::unreachable) {
    shouldBeSubType(curr->body->type,
                    curr->type,
                    curr->body,
                    "try's type does not match try body's type");
    for (auto catchBody : curr->catchBodies) {
      shouldBeSubType(catchBody->type,
                      curr->type,
                      catchBody,
                      "try's type does not match catch's body type");
    }
  } else {
    shouldBeEqual(curr->body->type,
                  Type(Type::unreachable),
                  curr,
                  "unreachable try-catch must have unreachable try body");
    for (auto catchBody : curr->catchBodies) {
      shouldBeEqual(catchBody->type,
                    Type(Type::unreachable),
                    curr,
                    "unreachable try-catch must have unreachable catch body");
    }
  }
  shouldBeTrue(curr->catchBodies.size() - curr->catchTags.size() <= 1,
               curr,
               "the number of catch blocks and tags do not match");

  shouldBeFalse(curr->isCatch() && curr->isDelegate(),
                curr,
                "try cannot have both catch and delegate at the same time");

  for (Index i = 0; i < curr->catchTags.size(); i++) {
    Name tagName = curr->catchTags[i];
    auto* tag = getModule()->getTagOrNull(tagName);
    if (!shouldBeTrue(tag != nullptr, curr, "")) {
      getStream() << "tag name is invalid: " << tagName << "\n";
    } else if (!shouldBeEqual(tag->sig.results, Type(Type::none), curr, "")) {
      getStream()
        << "catch's tag (" << tagName
        << ") has result values, which is not allowed for exception handling";
    } else {
      auto* catchBody = curr->catchBodies[i];
      auto pops = EHUtils::findPops(catchBody);
      if (tag->sig.params == Type::none) {
        if (!shouldBeTrue(pops.empty(), curr, "")) {
          getStream() << "catch's tag (" << tagName
                      << ") doesn't have any params, but there are pops";
        }
      } else {
        if (shouldBeTrue(pops.size() == 1, curr, "")) {
          auto* pop = *pops.begin();
          if (!shouldBeSubType(tag->sig.params, pop->type, curr, "")) {
            getStream()
              << "catch's tag (" << tagName
              << ")'s pop doesn't have the same type as the tag's params";
          }
          if (!shouldBeTrue(
                EHUtils::containsValidDanglingPop(catchBody), curr, "")) {
            getStream() << "catch's body (" << tagName
                        << ")'s pop's location is not valid";
          }
        } else {
          getStream() << "catch's tag (" << tagName
                      << ") has params, so there should be a single pop within "
                         "the catch body";
        }
      }
    }
  }

  if (curr->hasCatchAll()) {
    auto* catchAllBody = curr->catchBodies.back();
    shouldBeTrue(EHUtils::findPops(catchAllBody).empty(),
                 curr,
                 "catch_all's body should not have pops");
  }

  if (curr->isDelegate()) {
    noteDelegate(curr->delegateTarget, curr);
  }

  rethrowTargetNames.erase(curr->name);
}

void FunctionValidator::visitTryTable(TryTable* curr) {
  shouldBeTrue(
    getModule()->features.hasExceptionHandling(),
    curr,
    "try_table requires exception-handling [--enable-exception-handling]");
  if (curr->type != Type::unreachable) {
    shouldBeSubType(curr->body->type,
                    curr->type,
                    curr->body,
                    "try_table's type does not match try_table body's type");
  }

  shouldBeEqual(curr->catchTags.size(),
                curr->catchDests.size(),
                curr,
                "the number of catch tags and catch destinations do not match");
  shouldBeEqual(curr->catchTags.size(),
                curr->catchRefs.size(),
                curr,
                "the number of catch tags and catch refs do not match");
  shouldBeEqual(curr->catchTags.size(),
                curr->sentTypes.size(),
                curr,
                "the number of catch tags and sent types do not match");

  const char* invalidSentTypeMsg = "invalid catch sent type information";
  Type exnref = Type(HeapType::exn, Nullable);
  for (Index i = 0; i < curr->catchTags.size(); i++) {
    auto sentType = curr->sentTypes[i];
    size_t tagTypeSize;

    Name tagName = curr->catchTags[i];
    if (!tagName) { // catch_all or catch_all_ref
      tagTypeSize = 0;
    } else { // catch or catch_ref
      // Check tag validity
      auto* tag = getModule()->getTagOrNull(tagName);
      if (!shouldBeTrue(tag != nullptr, curr, "")) {
        getStream() << "catch's tag name is invalid: " << tagName << "\n";
      } else if (!shouldBeEqual(tag->sig.results, Type(Type::none), curr, "")) {
        getStream()
          << "catch's tag (" << tagName
          << ") has result values, which is not allowed for exception handling";
      }

      // tagType and sentType should be the same (except for the possible exnref
      // at the end of sentType)
      auto tagType = tag->sig.params;
      tagTypeSize = tagType.size();
      for (Index j = 0; j < tagType.size(); j++) {
        shouldBeEqual(tagType[j], sentType[j], curr, invalidSentTypeMsg);
      }
    }

    // If this is catch_ref or catch_all_ref, sentType.size() should be
    // tagType.size() + 1 because there is an exrnef tacked at the end. If
    // this is catch/catch_all, the two sizes should be the same.
    if (curr->catchRefs[i]) {
      if (shouldBeTrue(
            sentType.size() == tagTypeSize + 1, curr, invalidSentTypeMsg)) {
        shouldBeEqual(
          sentType[sentType.size() - 1], exnref, curr, invalidSentTypeMsg);
      }
    } else {
      shouldBeTrue(sentType.size() == tagTypeSize, curr, invalidSentTypeMsg);
    }

    // Note catch destinations with sent types
    noteBreak(curr->catchDests[i], curr->sentTypes[i], curr);
  }
}

void FunctionValidator::visitThrow(Throw* curr) {
  shouldBeTrue(
    getModule()->features.hasExceptionHandling(),
    curr,
    "throw requires exception-handling [--enable-exception-handling]");
  shouldBeEqual(curr->type,
                Type(Type::unreachable),
                curr,
                "throw's type must be unreachable");
  if (!info.validateGlobally) {
    return;
  }
  auto* tag = getModule()->getTagOrNull(curr->tag);
  if (!shouldBeTrue(!!tag, curr, "throw's tag must exist")) {
    return;
  }
  shouldBeEqual(
    tag->sig.results,
    Type(Type::none),
    curr,
    "tags with result types must not be used for exception handling");
  if (!shouldBeEqual(curr->operands.size(),
                     tag->sig.params.size(),
                     curr,
                     "tag's param numbers must match")) {
    return;
  }
  size_t i = 0;
  for (const auto& param : tag->sig.params) {
    if (!shouldBeSubType(curr->operands[i]->type,
                         param,
                         curr->operands[i],
                         "tag param types must match") &&
        !info.quiet) {
      getStream() << "(on argument " << i << ")\n";
    }
    ++i;
  }
}

void FunctionValidator::visitRethrow(Rethrow* curr) {
  shouldBeTrue(
    getModule()->features.hasExceptionHandling(),
    curr,
    "rethrow requires exception-handling [--enable-exception-handling]");
  shouldBeEqual(curr->type,
                Type(Type::unreachable),
                curr,
                "rethrow's type must be unreachable");
  noteRethrow(curr->target, curr);
}

void FunctionValidator::visitTupleMake(TupleMake* curr) {
  shouldBeTrue(getModule()->features.hasMultivalue(),
               curr,
               "Tuples are not allowed unless multivalue is enabled");
  shouldBeTrue(
    curr->operands.size() > 1, curr, "tuple.make must have multiple operands");
  std::vector<Type> types;
  for (auto* op : curr->operands) {
    if (op->type == Type::unreachable) {
      shouldBeTrue(
        curr->type == Type::unreachable,
        curr,
        "If tuple.make has an unreachable operand, it must be unreachable");
      return;
    }
    types.push_back(op->type);
  }
  shouldBeSubType(Type(types),
                  curr->type,
                  curr,
                  "Type of tuple.make does not match types of its operands");
}

void FunctionValidator::visitThrowRef(ThrowRef* curr) {
  Type exnref = Type(HeapType::exn, Nullable);
  shouldBeSubType(curr->exnref->type,
                  exnref,
                  curr,
                  "throw_ref's argument should be a subtype of exnref");
}

void FunctionValidator::visitTupleExtract(TupleExtract* curr) {
  shouldBeTrue(getModule()->features.hasMultivalue(),
               curr,
               "Tuples are not allowed unless multivalue is enabled");
  if (curr->tuple->type == Type::unreachable) {
    shouldBeTrue(
      curr->type == Type::unreachable,
      curr,
      "If tuple.extract has an unreachable operand, it must be unreachable");
  } else {
    bool inBounds = curr->index < curr->tuple->type.size();
    shouldBeTrue(inBounds, curr, "tuple.extract index out of bounds");
    if (inBounds) {
      shouldBeSubType(
        curr->tuple->type[curr->index],
        curr->type,
        curr,
        "tuple.extract type does not match the type of the extracted element");
    }
  }
}

void FunctionValidator::visitCallRef(CallRef* curr) {
  validateReturnCall(curr);
  shouldBeTrue(
    getModule()->features.hasGC(), curr, "call_ref requires gc [--enable-gc]");
  if (curr->target->type == Type::unreachable ||
      (curr->target->type.isRef() &&
       curr->target->type.getHeapType() == HeapType::nofunc)) {
    return;
  }
  if (shouldBeTrue(curr->target->type.isFunction(),
                   curr,
                   "call_ref target must be a function reference")) {
    validateCallParamsAndResult(curr, curr->target->type.getHeapType());
  }
}

void FunctionValidator::visitRefI31(RefI31* curr) {
  shouldBeTrue(
    getModule()->features.hasGC(), curr, "ref.i31 requires gc [--enable-gc]");
  shouldBeSubType(curr->value->type,
                  Type::i32,
                  curr->value,
                  "ref.i31's argument should be i32");
}

void FunctionValidator::visitI31Get(I31Get* curr) {
  shouldBeTrue(getModule()->features.hasGC(),
               curr,
               "i31.get_s/u requires gc [--enable-gc]");
  shouldBeSubTypeIgnoringShared(curr->i31->type,
                                Type(HeapType::i31, Nullable),
                                curr->i31,
                                "i31.get_s/u's argument should be i31ref");
}

void FunctionValidator::visitRefTest(RefTest* curr) {
  shouldBeTrue(
    getModule()->features.hasGC(), curr, "ref.test requires gc [--enable-gc]");
  if (curr->ref->type == Type::unreachable) {
    return;
  }
  if (!shouldBeTrue(
        curr->ref->type.isRef(), curr, "ref.test ref must have ref type")) {
    return;
  }
  if (!shouldBeTrue(
        curr->castType.isRef(), curr, "ref.test target must have ref type")) {
    return;
  }
  shouldBeEqual(
    curr->castType.getHeapType().getBottom(),
    curr->ref->type.getHeapType().getBottom(),
    curr,
    "ref.test target type and ref type must have a common supertype");
}

void FunctionValidator::visitRefCast(RefCast* curr) {
  shouldBeTrue(
    getModule()->features.hasGC(), curr, "ref.cast requires gc [--enable-gc]");
  if (curr->ref->type == Type::unreachable) {
    return;
  }
  if (!shouldBeTrue(
        curr->ref->type.isRef(), curr, "ref.cast ref must have ref type")) {
    return;
  }
  shouldBeEqual(
    curr->type.getHeapType().getBottom(),
    curr->ref->type.getHeapType().getBottom(),
    curr,
    "ref.cast target type and ref type must have a common supertype");

  // We should never have a nullable cast of a non-nullable reference, since
  // that unnecessarily loses type information.
  shouldBeTrue(curr->ref->type.isNullable() || curr->type.isNonNullable(),
               curr,
               "ref.cast null of non-nullable references are not allowed");
}

void FunctionValidator::visitBrOn(BrOn* curr) {
  shouldBeTrue(getModule()->features.hasGC(),
               curr,
               "br_on_cast requires gc [--enable-gc]");
  if (curr->ref->type == Type::unreachable) {
    return;
  }
  if (!shouldBeTrue(
        curr->ref->type.isRef(), curr, "br_on_cast ref must have ref type")) {
    return;
  }
  if (curr->op == BrOnCast || curr->op == BrOnCastFail) {
    if (!shouldBeTrue(curr->castType.isRef(),
                      curr,
                      "br_on_cast must have reference cast type")) {
      return;
    }
    shouldBeEqual(
      curr->castType.getHeapType().getBottom(),
      curr->ref->type.getHeapType().getBottom(),
      curr,
      "br_on_cast* target type and ref type must have a common supertype");
    shouldBeSubType(
      curr->castType,
      curr->ref->type,
      curr,
      "br_on_cast* target type must be a subtype of its input type");
  } else {
    shouldBeEqual(curr->castType,
                  Type(Type::none),
                  curr,
                  "non-cast br_on* must not set intendedType field");
  }
  noteBreak(curr->name, curr->getSentType(), curr);
}

void FunctionValidator::visitStructNew(StructNew* curr) {
  shouldBeTrue(getModule()->features.hasGC(),
               curr,
               "struct.new requires gc [--enable-gc]");
  if (curr->type == Type::unreachable) {
    return;
  }
  auto heapType = curr->type.getHeapType();
  if (!shouldBeTrue(
        heapType.isStruct(), curr, "struct.new heap type must be struct")) {
    return;
  }
  const auto& fields = heapType.getStruct().fields;
  if (curr->isWithDefault()) {
    shouldBeTrue(curr->operands.empty(),
                 curr,
                 "struct.new_with_default should have no operands");
    // All the fields must be defaultable.
    for (const auto& field : fields) {
      shouldBeTrue(field.type.isDefaultable(),
                   field,
                   "struct.new_with_default value type must be defaultable");
    }
  } else {
    if (shouldBeEqual(curr->operands.size(),
                      fields.size(),
                      curr,
                      "struct.new must have the right number of operands")) {
      // All the fields must have the proper type.
      for (Index i = 0; i < fields.size(); i++) {
        if (!Type::isSubType(curr->operands[i]->type, fields[i].type)) {
          info.fail("struct.new operand " + std::to_string(i) +
                      " must have proper type",
                    curr,
                    getFunction());
        }
      }
    }
  }
}

void FunctionValidator::visitStructGet(StructGet* curr) {
  shouldBeTrue(getModule()->features.hasGC(),
               curr,
               "struct.get requires gc [--enable-gc]");
  if (curr->type == Type::unreachable || curr->ref->type.isNull()) {
    return;
  }
  if (!shouldBeTrue(curr->ref->type.isStruct(),
                    curr->ref,
                    "struct.get ref must be a struct")) {
    return;
  }
  const auto& fields = curr->ref->type.getHeapType().getStruct().fields;
  shouldBeTrue(curr->index < fields.size(), curr, "bad struct.get field");
  auto field = fields[curr->index];
  // If the type is not packed, it must be marked internally as unsigned, by
  // convention.
  if (field.type != Type::i32 || field.packedType == Field::not_packed) {
    shouldBeFalse(curr->signed_, curr, "non-packed get cannot be signed");
  }
  if (curr->ref->type == Type::unreachable) {
    return;
  }
  shouldBeEqual(
    curr->type, field.type, curr, "struct.get must have the proper type");
}

void FunctionValidator::visitStructSet(StructSet* curr) {
  shouldBeTrue(getModule()->features.hasGC(),
               curr,
               "struct.set requires gc [--enable-gc]");
  if (curr->ref->type == Type::unreachable) {
    return;
  }
  if (!shouldBeTrue(curr->ref->type.isRef(),
                    curr->ref,
                    "struct.set ref must be a reference type")) {
    return;
  }
  auto type = curr->ref->type.getHeapType();
  if (type.isBasic() && type.getBasic(Unshared) == HeapType::none) {
    return;
  }
  if (!shouldBeTrue(
        type.isStruct(), curr->ref, "struct.set ref must be a struct")) {
    return;
  }
  const auto& fields = type.getStruct().fields;
  shouldBeTrue(curr->index < fields.size(), curr, "bad struct.get field");
  auto& field = fields[curr->index];
  shouldBeSubType(curr->value->type,
                  field.type,
                  curr,
                  "struct.set must have the proper type");
  shouldBeEqual(
    field.mutable_, Mutable, curr, "struct.set field must be mutable");
}

void FunctionValidator::visitArrayNew(ArrayNew* curr) {
  shouldBeTrue(
    getModule()->features.hasGC(), curr, "array.new requires gc [--enable-gc]");
  shouldBeEqualOrFirstIsUnreachable(
    curr->size->type, Type(Type::i32), curr, "array.new size must be an i32");
  if (curr->type == Type::unreachable) {
    return;
  }
  auto heapType = curr->type.getHeapType();
  if (!shouldBeTrue(
        heapType.isArray(), curr, "array.new heap type must be array")) {
    return;
  }
  const auto& element = heapType.getArray().element;
  if (curr->isWithDefault()) {
    shouldBeTrue(
      !curr->init, curr, "array.new_with_default should have no init");
    // The element must be defaultable.
    shouldBeTrue(element.type.isDefaultable(),
                 element,
                 "array.new_with_default value type must be defaultable");
  } else {
    shouldBeTrue(!!curr->init, curr, "array.new should have an init");
    // The inits must have the proper type.
    shouldBeSubType(curr->init->type,
                    element.type,
                    curr,
                    "array.new init must have proper type");
  }
}

template<typename ArrayNew>
void FunctionValidator::visitArrayNew(ArrayNew* curr) {
  shouldBeTrue(getModule()->features.hasGC(),
               curr,
               "array.new_{data, elem} requires gc [--enable-gc]");
  shouldBeEqualOrFirstIsUnreachable(
    curr->offset->type,
    Type(Type::i32),
    curr,
    "array.new_{data, elem} offset must be an i32");
  shouldBeEqualOrFirstIsUnreachable(
    curr->size->type,
    Type(Type::i32),
    curr,
    "array.new_{data, elem} size must be an i32");
  if (curr->type == Type::unreachable) {
    return;
  }
  if (!shouldBeTrue(
        curr->type.isRef(),
        curr,
        "array.new_{data, elem} type should be an array reference")) {
    return;
  }
  auto heapType = curr->type.getHeapType();
  if (!shouldBeTrue(
        heapType.isArray(),
        curr,
        "array.new_{data, elem} type should be an array reference")) {
    return;
  }
}

void FunctionValidator::visitArrayNewData(ArrayNewData* curr) {
  visitArrayNew(curr);

  shouldBeTrue(
    getModule()->features.hasBulkMemory(),
    curr,
    "Data segment operations require bulk memory [--enable-bulk-memory]");
  if (!shouldBeTrue(getModule()->getDataSegment(curr->segment),
                    curr,
                    "array.new_data segment should exist")) {
    return;
  }

  auto field = GCTypeUtils::getField(curr->type);
  if (!field) {
    // A bottom type, or unreachable.
    return;
  }
  shouldBeTrue(field->type.isNumber(),
               curr,
               "array.new_data result element type should be numeric");
}

void FunctionValidator::visitArrayNewElem(ArrayNewElem* curr) {
  visitArrayNew(curr);

  if (!shouldBeTrue(getModule()->getElementSegment(curr->segment),
                    curr,
                    "array.new_elem segment should exist")) {
    return;
  }

  auto field = GCTypeUtils::getField(curr->type);
  if (!field) {
    // A bottom type, or unreachable.
    return;
  }
  shouldBeSubType(getModule()->getElementSegment(curr->segment)->type,
                  field->type,
                  curr,
                  "array.new_elem segment type should be a subtype of the "
                  "result element type");
}

void FunctionValidator::visitArrayNewFixed(ArrayNewFixed* curr) {
  shouldBeTrue(getModule()->features.hasGC(),
               curr,
               "array.init requires gc [--enable-gc]");
  if (curr->type == Type::unreachable) {
    return;
  }
  auto heapType = curr->type.getHeapType();
  if (!shouldBeTrue(
        heapType.isArray(), curr, "array.init heap type must be array")) {
    return;
  }
  const auto& element = heapType.getArray().element;
  for (auto* value : curr->values) {
    shouldBeSubType(value->type,
                    element.type,
                    curr,
                    "array.init value must have proper type");
  }
}

void FunctionValidator::visitArrayGet(ArrayGet* curr) {
  shouldBeTrue(
    getModule()->features.hasGC(), curr, "array.get requires gc [--enable-gc]");
  shouldBeEqualOrFirstIsUnreachable(
    curr->index->type, Type(Type::i32), curr, "array.get index must be an i32");
  const char* mustBeArray =
    "array.get target should be a specific array reference";
  if (curr->type == Type::unreachable ||
      !shouldBeTrue(curr->ref->type.isRef(), curr, mustBeArray) ||
      curr->ref->type.getHeapType().isBottom() ||
      !shouldBeTrue(curr->ref->type.isArray(), curr, mustBeArray)) {
    return;
  }
  auto heapType = curr->ref->type.getHeapType();
  const auto& element = heapType.getArray().element;
  // If the type is not packed, it must be marked internally as unsigned, by
  // convention.
  if (element.type != Type::i32 || element.packedType == Field::not_packed) {
    shouldBeFalse(curr->signed_, curr, "non-packed get cannot be signed");
  }
  shouldBeEqual(
    curr->type, element.type, curr, "array.get must have the proper type");
}

void FunctionValidator::visitArraySet(ArraySet* curr) {
  shouldBeTrue(
    getModule()->features.hasGC(), curr, "array.set requires gc [--enable-gc]");
  shouldBeEqualOrFirstIsUnreachable(
    curr->index->type, Type(Type::i32), curr, "array.set index must be an i32");
  if (curr->type == Type::unreachable) {
    return;
  }
  const char* mustBeArray = "array.set target should be an array reference";
  if (curr->type == Type::unreachable ||
      !shouldBeTrue(curr->ref->type.isRef(), curr, mustBeArray) ||
      curr->ref->type.getHeapType().isBottom() ||
      !shouldBeTrue(curr->ref->type.isArray(), curr, mustBeArray)) {
    return;
  }
  const auto& element = curr->ref->type.getHeapType().getArray().element;
  shouldBeSubType(curr->value->type,
                  element.type,
                  curr,
                  "array.set must have the proper type");
  shouldBeTrue(element.mutable_, curr, "array.set type must be mutable");
}

void FunctionValidator::visitArrayLen(ArrayLen* curr) {
  shouldBeTrue(
    getModule()->features.hasGC(), curr, "array.len requires gc [--enable-gc]");
  shouldBeEqualOrFirstIsUnreachable(
    curr->type, Type(Type::i32), curr, "array.len result must be an i32");
  shouldBeSubTypeIgnoringShared(
    curr->ref->type,
    Type(HeapType::array, Nullable),
    curr,
    "array.len argument must be an array reference");
}

void FunctionValidator::visitArrayCopy(ArrayCopy* curr) {
  shouldBeTrue(getModule()->features.hasGC(),
               curr,
               "array.copy requires gc [--enable-gc]");
  shouldBeEqualOrFirstIsUnreachable(curr->srcIndex->type,
                                    Type(Type::i32),
                                    curr,
                                    "array.copy src index must be an i32");
  shouldBeEqualOrFirstIsUnreachable(curr->destIndex->type,
                                    Type(Type::i32),
                                    curr,
                                    "array.copy dest index must be an i32");
  if (curr->type == Type::unreachable) {
    return;
  }
  if (!shouldBeTrue(curr->srcRef->type.isRef(),
                    curr,
                    "array.copy source should be a reference")) {
    return;
  }
  if (!shouldBeTrue(curr->destRef->type.isRef(),
                    curr,
                    "array.copy destination should be a reference")) {
    return;
  }
  auto srcHeapType = curr->srcRef->type.getHeapType();
  auto destHeapType = curr->destRef->type.getHeapType();
  // Normally both types need to be references to specific arrays, but if either
  // of the types are bottom, we don't further constrain the other at all
  // because this will be emitted as an unreachable.
  if (srcHeapType.isBottom() || destHeapType.isBottom()) {
    return;
  }
  if (!shouldBeTrue(srcHeapType.isArray(),
                    curr,
                    "array.copy source should be an array reference")) {
    return;
  }
  if (!shouldBeTrue(destHeapType.isArray(),
                    curr,
                    "array.copy destination should be an array reference")) {
    return;
  }
  const auto& srcElement = srcHeapType.getArray().element;
  const auto& destElement = destHeapType.getArray().element;
  shouldBeSubType(srcElement.type,
                  destElement.type,
                  curr,
                  "array.copy must have the proper types");
  shouldBeEqual(srcElement.packedType,
                destElement.packedType,
                curr,
                "array.copy types must match");
  shouldBeTrue(
    destElement.mutable_, curr, "array.copy destination must be mutable");
}

void FunctionValidator::visitArrayFill(ArrayFill* curr) {
  shouldBeTrue(getModule()->features.hasGC(),
               curr,
               "array.fill requires gc [--enable-gc]");
  shouldBeEqualOrFirstIsUnreachable(curr->index->type,
                                    Type(Type::i32),
                                    curr,
                                    "array.fill index must be an i32");
  shouldBeEqualOrFirstIsUnreachable(
    curr->size->type, Type(Type::i32), curr, "array.fill size must be an i32");
  const char* mustBeArray =
    "array.fill destination should be an array reference";
  if (curr->type == Type::unreachable ||
      !shouldBeTrue(curr->ref->type.isRef(), curr, mustBeArray) ||
      curr->ref->type.getHeapType().isBottom() ||
      !shouldBeTrue(curr->ref->type.isArray(), curr, mustBeArray)) {
    return;
  }
  auto heapType = curr->ref->type.getHeapType();
  auto element = heapType.getArray().element;
  shouldBeSubType(curr->value->type,
                  element.type,
                  curr,
                  "array.fill value must match destination element type");
  shouldBeTrue(
    element.mutable_, curr, "array.fill destination must be mutable");
}

template<typename ArrayInit>
void FunctionValidator::visitArrayInit(ArrayInit* curr) {
  shouldBeTrue(getModule()->features.hasGC(),
               curr,
               "array.init_* requires gc [--enable-gc]");
  shouldBeEqualOrFirstIsUnreachable(curr->index->type,
                                    Type(Type::i32),
                                    curr,
                                    "array.init_* index must be an i32");
  shouldBeEqualOrFirstIsUnreachable(curr->offset->type,
                                    Type(Type::i32),
                                    curr,
                                    "array.init_* offset must be an i32");
  shouldBeEqualOrFirstIsUnreachable(curr->size->type,
                                    Type(Type::i32),
                                    curr,
                                    "array.init_* size must be an i32");
  const char* mustBeArray =
    "array.init_* destination must be an array reference";
  if (curr->type == Type::unreachable ||
      !shouldBeTrue(curr->ref->type.isRef(), curr, mustBeArray) ||
      curr->ref->type.getHeapType().isBottom() ||
      !shouldBeTrue(curr->ref->type.isArray(), curr, mustBeArray)) {
    return;
  }
  auto heapType = curr->ref->type.getHeapType();
  auto element = heapType.getArray().element;
  shouldBeTrue(
    element.mutable_, curr, "array.init_* destination must be mutable");
}

void FunctionValidator::visitArrayInitData(ArrayInitData* curr) {
  visitArrayInit(curr);

  shouldBeTrue(
    getModule()->features.hasBulkMemory(),
    curr,
    "Data segment operations require bulk memory [--enable-bulk-memory]");
  shouldBeTrue(getModule()->getDataSegmentOrNull(curr->segment),
               curr,
               "array.init_data segment must exist");

  auto field = GCTypeUtils::getField(curr->ref->type);
  if (!field) {
    // A bottom type, or unreachable.
    return;
  }
  shouldBeTrue(field->type.isNumber(),
               curr,
               "array.init_data destination must be numeric");
}

void FunctionValidator::visitArrayInitElem(ArrayInitElem* curr) {
  visitArrayInit(curr);

  auto* seg = getModule()->getElementSegmentOrNull(curr->segment);
  if (!shouldBeTrue(seg, curr, "array.init_elem segment must exist")) {
    return;
  }

  auto field = GCTypeUtils::getField(curr->ref->type);
  if (!field) {
    // A bottom type, or unreachable.
    return;
  }

  shouldBeSubType(seg->type,
                  field->type,
                  curr,
                  "array.init_elem segment type must match destination type");
}

void FunctionValidator::visitStringNew(StringNew* curr) {
  shouldBeTrue(!getModule() || getModule()->features.hasStrings(),
               curr,
               "string operations require reference-types [--enable-strings]");

  switch (curr->op) {
    case StringNewLossyUTF8Array:
    case StringNewWTF16Array: {
      auto refType = curr->ref->type;
      if (refType == Type::unreachable) {
        return;
      }
      if (!shouldBeTrue(
            refType.isRef(), curr, "string.new input must have array type")) {
        return;
      }
      auto heapType = refType.getHeapType();
      if (!shouldBeTrue(heapType.isBottom() || heapType.isArray(),
                        curr,
                        "string.new input must have array type")) {
        return;
      }
      shouldBeEqualOrFirstIsUnreachable(curr->start->type,
                                        Type(Type::i32),
                                        curr,
                                        "string.new start must be i32");
      shouldBeEqualOrFirstIsUnreachable(
        curr->end->type, Type(Type::i32), curr, "string.new end must be i32");
      return;
    }
    case StringNewFromCodePoint:
      shouldBeEqualOrFirstIsUnreachable(
        curr->ref->type,
        Type(Type::i32),
        curr,
        "string.from_code_point code point must be i32");
      shouldBeTrue(
        !curr->start, curr, "string.from_code_point should not have start");
      shouldBeTrue(
        !curr->end, curr, "string.from_code_point should not have end");
      return;
  }
  WASM_UNREACHABLE("unexpected op");
}

void FunctionValidator::visitStringConst(StringConst* curr) {
  shouldBeTrue(!getModule() || getModule()->features.hasStrings(),
               curr,
               "string operations require reference-types [--enable-strings]");
}

void FunctionValidator::visitStringMeasure(StringMeasure* curr) {
  shouldBeTrue(!getModule() || getModule()->features.hasStrings(),
               curr,
               "string operations require reference-types [--enable-strings]");
}

void FunctionValidator::visitStringEncode(StringEncode* curr) {
  shouldBeTrue(!getModule() || getModule()->features.hasStrings(),
               curr,
               "string operations require reference-types [--enable-strings]");
}

void FunctionValidator::visitStringConcat(StringConcat* curr) {
  shouldBeTrue(!getModule() || getModule()->features.hasStrings(),
               curr,
               "string operations require reference-types [--enable-strings]");
}

void FunctionValidator::visitStringEq(StringEq* curr) {
  shouldBeTrue(!getModule() || getModule()->features.hasStrings(),
               curr,
               "string operations require reference-types [--enable-strings]");
}

void FunctionValidator::visitStringWTF16Get(StringWTF16Get* curr) {
  shouldBeTrue(!getModule() || getModule()->features.hasStrings(),
               curr,
               "string operations require reference-types [--enable-strings]");
}

void FunctionValidator::visitStringSliceWTF(StringSliceWTF* curr) {
  shouldBeTrue(!getModule() || getModule()->features.hasStrings(),
               curr,
               "string operations require reference-types [--enable-strings]");
}

void FunctionValidator::visitContBind(ContBind* curr) {
  // TODO implement actual type-checking
  shouldBeTrue(
    !getModule() || getModule()->features.hasTypedContinuations(),
    curr,
    "cont.bind requires typed-continuatons [--enable-typed-continuations]");

  shouldBeTrue((curr->contTypeBefore.isContinuation() &&
                curr->contTypeBefore.getContinuation().type.isSignature()),
               curr,
               "invalid first type in ContBind expression");

  shouldBeTrue((curr->contTypeAfter.isContinuation() &&
                curr->contTypeAfter.getContinuation().type.isSignature()),
               curr,
               "invalid second type in ContBind expression");
}

void FunctionValidator::visitContNew(ContNew* curr) {
  // TODO implement actual type-checking
  shouldBeTrue(
    !getModule() || getModule()->features.hasTypedContinuations(),
    curr,
    "cont.new requires typed-continuatons [--enable-typed-continuations]");

  shouldBeTrue((curr->contType.isContinuation() &&
                curr->contType.getContinuation().type.isSignature()),
               curr,
               "invalid type in ContNew expression");
}

void FunctionValidator::visitResume(Resume* curr) {
  // TODO implement actual type-checking
  shouldBeTrue(
    !getModule() || getModule()->features.hasTypedContinuations(),
    curr,
    "resume requires typed-continuatons [--enable-typed-continuations]");

  shouldBeTrue(
    curr->sentTypes.size() == curr->handlerBlocks.size(),
    curr,
    "sentTypes cache in Resume instruction has not been initialized");

  shouldBeTrue((curr->contType.isContinuation() &&
                curr->contType.getContinuation().type.isSignature()),
               curr,
               "invalid type in Resume expression");
}

void FunctionValidator::visitSuspend(Suspend* curr) {
  // TODO implement actual type-checking
  shouldBeTrue(
    !getModule() || getModule()->features.hasTypedContinuations(),
    curr,
    "suspend requires typed-continuations [--enable-typed-continuations]");
}

void FunctionValidator::visitFunction(Function* curr) {
  FeatureSet features;
  // Check for things like having a rec group with GC enabled. The type we're
  // checking is a reference type even if this an MVP function type, so ignore
  // the reference types feature here.
  features |= (curr->type.getFeatures() & ~FeatureSet::ReferenceTypes);
  for (const auto& param : curr->getParams()) {
    features |= param.getFeatures();
    shouldBeTrue(param.isConcrete(), curr, "params must be concretely typed");
  }
  for (const auto& result : curr->getResults()) {
    features |= result.getFeatures();
    shouldBeTrue(result.isConcrete(), curr, "results must be concretely typed");
  }
  for (const auto& var : curr->vars) {
    features |= var.getFeatures();
  }
  shouldBeTrue(features <= getModule()->features,
               curr->name,
               "all used types should be allowed");

  // validate optional local names
  std::unordered_set<Name> seen;
  for (auto& pair : curr->localNames) {
    Name name = pair.second;
    shouldBeTrue(seen.insert(name).second, name, "local names must be unique");
  }

  if (curr->body) {
    if (curr->getResults().isTuple()) {
      shouldBeTrue(getModule()->features.hasMultivalue(),
                   curr->body,
                   "Multivalue function results (multivalue is not enabled)");
    }
    if (curr->profile == IRProfile::Poppy) {
      shouldBeTrue(
        curr->body->is<Block>(), curr->body, "Function body must be a block");
    }
    // if function has no result, it is ignored
    // if body is unreachable, it might be e.g. a return
    shouldBeSubType(curr->body->type,
                    curr->getResults(),
                    curr->body,
                    "function body type must match, if function returns");

    if (getModule()->features.hasGC()) {
      // If we have non-nullable locals, verify that local.get are valid.
      LocalStructuralDominance info(curr, *getModule());
      for (auto index : info.nonDominatingIndices) {
        auto localType = curr->getLocalType(index);
        for (auto type : localType) {
          shouldBeTrue(!type.isNonNullable(),
                       index,
                       "non-nullable local's sets must dominate gets");
        }
      }
    }

    // Assert that we finished with a clean state after processing the body's
    // expressions, and reset the state for next time. Note that we use some of
    // this state in the above validations, so this must appear last.
    assert(breakTypes.empty());
    assert(delegateTargetNames.empty());
    assert(rethrowTargetNames.empty());
    labelNames.clear();
  }
}

void FunctionValidator::validateOffset(Address offset,
                                       Memory* mem,
                                       Expression* curr) {
  shouldBeTrue(mem->is64() || offset <= std::numeric_limits<uint32_t>::max(),
               curr,
               "offset must be u32");
}

void FunctionValidator::validateAlignment(
  size_t align, Type type, Index bytes, bool isAtomic, Expression* curr) {
  if (isAtomic) {
    shouldBeEqual(align,
                  (size_t)bytes,
                  curr,
                  "atomic accesses must have natural alignment");
    return;
  }
  switch (align) {
    case 1:
    case 2:
    case 4:
    case 8:
    case 16:
      break;
    default: {
      info.fail("bad alignment: " + std::to_string(align), curr, getFunction());
      break;
    }
  }
  shouldBeTrue(align <= bytes, curr, "alignment must not exceed natural");
  TODO_SINGLE_COMPOUND(type);
  switch (type.getBasic()) {
    case Type::i32:
    case Type::f32: {
      shouldBeTrue(align <= 4, curr, "alignment must not exceed natural");
      break;
    }
    case Type::i64:
    case Type::f64: {
      shouldBeTrue(align <= 8, curr, "alignment must not exceed natural");
      break;
    }
    case Type::v128:
    case Type::unreachable:
      break;
    case Type::none:
      WASM_UNREACHABLE("invalid type");
  }
}

static void validateBinaryenIR(Module& wasm, ValidationInfo& info) {
  struct BinaryenIRValidator
    : public PostWalker<BinaryenIRValidator,
                        UnifiedExpressionVisitor<BinaryenIRValidator>> {
    ValidationInfo& info;

    std::unordered_set<Expression*> seen;

    BinaryenIRValidator(ValidationInfo& info) : info(info) {}

    void visitExpression(Expression* curr) {
      auto scope = getFunction() ? getFunction()->name : Name("(global scope)");
      // check if a node type is 'stale', i.e., we forgot to finalize() the
      // node.
      auto oldType = curr->type;
      ReFinalizeNode().visit(curr);
      auto newType = curr->type;
      if (newType != oldType) {
        // We accept concrete => undefined on control flow structures:
        // e.g.
        //
        //  (drop (block (result i32) (unreachable)))
        //
        // The block has a type annotated on it, which can make its unreachable
        // contents have a concrete type. Refinalize will make it unreachable,
        // so both are valid here.
        bool validControlFlowStructureChange =
          Properties::isControlFlowStructure(curr) && oldType.isConcrete() &&
          newType == Type::unreachable;
        // It's ok in general for types to get refined as long as they don't
        // become unreachable.
        bool validRefinement =
          Type::isSubType(newType, oldType) && newType != Type::unreachable;
        if (!validRefinement && !validControlFlowStructureChange) {
          std::ostringstream ss;
          ss << "stale type found in " << scope << " on " << curr
             << "\n(marked as " << oldType << ", should be " << newType
             << ")\n";
          info.fail(ss.str(), curr, getFunction());
        }
        curr->type = oldType;
      }
      // check if a node is a duplicate - expressions must not be seen more than
      // once
      if (!seen.insert(curr).second) {
        std::ostringstream ss;
        ss << "expression seen more than once in the tree in " << scope
           << " on " << curr << '\n';
        info.fail(ss.str(), curr, getFunction());
      }
    }
  };
  BinaryenIRValidator binaryenIRValidator(info);
  binaryenIRValidator.walkModule(&wasm);
}

// Main validator class

static void validateImports(Module& module, ValidationInfo& info) {
  ModuleUtils::iterImportedFunctions(module, [&](Function* curr) {
    if (curr->getResults().isTuple()) {
      info.shouldBeTrue(module.features.hasMultivalue(),
                        curr->name,
                        "Imported multivalue function requires multivalue "
                        "[--enable-multivalue]");
    }
    if (info.validateWeb) {
      for (const auto& param : curr->getParams()) {
        info.shouldBeUnequal(param,
                             Type(Type::i64),
                             curr->name,
                             "Imported function must not have i64 parameters");
      }
      for (const auto& result : curr->getResults()) {
        info.shouldBeUnequal(result,
                             Type(Type::i64),
                             curr->name,
                             "Imported function must not have i64 results");
      }
    }

    if (Intrinsics(module).isCallWithoutEffects(curr)) {
      auto lastParam = curr->getParams();
      if (lastParam.isTuple()) {
        lastParam = lastParam.getTuple().back();
      }
      info.shouldBeTrue(lastParam.isFunction(),
                        curr->name,
                        "call.if.used's last param must be a function");
    }
  });
  ModuleUtils::iterImportedGlobals(module, [&](Global* curr) {
    if (!module.features.hasMutableGlobals()) {
      info.shouldBeFalse(curr->mutable_,
                         curr->name,
                         "Imported mutable global requires mutable-globals "
                         "[--enable-mutable-globals]");
    }
    info.shouldBeFalse(
      curr->type.isTuple(), curr->name, "Imported global cannot be tuple");
  });
}

static void validateExports(Module& module, ValidationInfo& info) {
  for (auto& curr : module.exports) {
    if (curr->kind == ExternalKind::Function) {
      if (info.validateWeb) {
        Function* f = module.getFunction(curr->value);
        for (const auto& param : f->getParams()) {
          info.shouldBeUnequal(
            param,
            Type(Type::i64),
            f->name,
            "Exported function must not have i64 parameters");
        }
        for (const auto& result : f->getResults()) {
          info.shouldBeUnequal(result,
                               Type(Type::i64),
                               f->name,
                               "Exported function must not have i64 results");
        }
      }
    } else if (curr->kind == ExternalKind::Global) {
      if (Global* g = module.getGlobalOrNull(curr->value)) {
        if (!module.features.hasMutableGlobals()) {
          info.shouldBeFalse(g->mutable_,
                             g->name,
                             "Exported mutable global requires mutable-globals "
                             "[--enable-mutable-globals]");
        }
        info.shouldBeFalse(
          g->type.isTuple(), g->name, "Exported global cannot be tuple");
      }
    }
  }
  std::unordered_set<Name> exportNames;
  for (auto& exp : module.exports) {
    Name name = exp->value;
    if (exp->kind == ExternalKind::Function) {
      info.shouldBeTrue(module.getFunctionOrNull(name),
                        name,
                        "module function exports must be found");
    } else if (exp->kind == ExternalKind::Global) {
      info.shouldBeTrue(module.getGlobalOrNull(name),
                        name,
                        "module global exports must be found");
    } else if (exp->kind == ExternalKind::Table) {
      info.shouldBeTrue(module.getTableOrNull(name),
                        name,
                        "module table exports must be found");
    } else if (exp->kind == ExternalKind::Memory) {
      info.shouldBeTrue(module.getMemoryOrNull(name),
                        name,
                        "module memory exports must be found");
    } else if (exp->kind == ExternalKind::Tag) {
      info.shouldBeTrue(
        module.getTagOrNull(name), name, "module tag exports must be found");
    } else {
      WASM_UNREACHABLE("invalid ExternalKind");
    }
    Name exportName = exp->name;
    info.shouldBeFalse(exportNames.count(exportName) > 0,
                       exportName,
                       "module exports must be unique");
    exportNames.insert(exportName);
  }
}

static void validateGlobals(Module& module, ValidationInfo& info) {
  std::unordered_set<Global*> seen;
  ModuleUtils::iterDefinedGlobals(module, [&](Global* curr) {
    info.shouldBeTrue(curr->type.getFeatures() <= module.features,
                      curr->name,
                      "all used types should be allowed");
    info.shouldBeTrue(
      curr->init != nullptr, curr->name, "global init must be non-null");
    assert(curr->init);
    info.shouldBeTrue(GlobalUtils::canInitializeGlobal(module, curr->init),
                      curr->name,
                      "global init must be constant");

    if (!info.shouldBeSubType(curr->init->type,
                              curr->type,
                              curr->init,
                              "global init must have correct type") &&
        !info.quiet) {
      info.getStream(nullptr) << "(on global " << curr->name << ")\n";
    }
    FunctionValidator(module, &info).validate(curr->init);
    // If GC is enabled (which means globals can refer to other non-imported
    // globals), check that globals only refer to preceeding globals.
    if (module.features.hasGC() && curr->init) {
      for (auto* get : FindAll<GlobalGet>(curr->init).list) {
        auto* global = module.getGlobalOrNull(get->name);
        info.shouldBeTrue(
          global && (seen.count(global) || global->imported()),
          curr->init,
          "global initializer should only refer to previous globals");
      }
      seen.insert(curr);
    }
  });

  // Check that globals have allowed types.
  for (auto& g : module.globals) {
    auto globalFeats = g->type.getFeatures();
    if (!info.shouldBeTrue(globalFeats <= module.features, g->name, "")) {
      info.getStream(nullptr)
        << "global type requires additional features "
        << getMissingFeaturesList(module, globalFeats) << '\n';
    }
  }
}

static void validateMemories(Module& module, ValidationInfo& info) {
  if (module.memories.size() > 1) {
    info.shouldBeTrue(
      module.features.hasMultiMemory(),
      "memory",
      "multiple memories require multimemory [--enable-multimemory]");
  }
  for (auto& memory : module.memories) {
    if (memory->hasMax()) {
      info.shouldBeFalse(
        memory->initial > memory->max, "memory", "memory max >= initial");
    }
    if (memory->is64()) {
      info.shouldBeTrue(module.features.hasMemory64(),
                        "memory",
                        "64-bit memories require memory64 [--enable-memory64]");
    } else {
      info.shouldBeTrue(memory->initial <= Memory::kMaxSize32,
                        "memory",
                        "initial memory must be <= 4GB");
      info.shouldBeTrue(!memory->hasMax() || memory->max <= Memory::kMaxSize32,
                        "memory",
                        "max memory must be <= 4GB, or unlimited");
    }
    info.shouldBeTrue(!memory->shared || memory->hasMax(),
                      "memory",
                      "shared memory must have max size");
    if (memory->shared) {
      info.shouldBeTrue(module.features.hasAtomics(),
                        "memory",
                        "shared memory requires threads [--enable-threads]");
    }
  }
}

static void validateDataSegments(Module& module, ValidationInfo& info) {
  for (auto& segment : module.dataSegments) {
    if (segment->isPassive) {
      info.shouldBeTrue(
        module.features.hasBulkMemory(),
        segment->offset,
        "nonzero segment flags require bulk memory [--enable-bulk-memory]");
      info.shouldBeEqual(segment->offset,
                         (Expression*)nullptr,
                         segment->offset,
                         "passive segment should not have an offset");
    } else {
      auto memory = module.getMemoryOrNull(segment->memory);
      if (!info.shouldBeTrue(memory != nullptr,
                             "segment",
                             "active segment must have a valid memory name")) {
        continue;
      }
      info.shouldBeEqual(segment->offset->type,
                         memory->indexType,
                         segment->offset,
                         "segment offset must match memory index type");
      info.shouldBeTrue(
        Properties::isValidConstantExpression(module, segment->offset),
        segment->offset,
        "memory segment offset must be constant");
      FunctionValidator(module, &info).validate(segment->offset);
    }
  }
}

static void validateTables(Module& module, ValidationInfo& info) {
  FunctionValidator validator(module, &info);

  if (!module.features.hasReferenceTypes()) {
    info.shouldBeTrue(module.tables.size() <= 1,
                      "table",
                      "Only 1 table definition allowed in MVP (requires "
                      "--enable-reference-types)");
    if (!module.tables.empty()) {
      auto& table = module.tables.front();
      info.shouldBeTrue(table->type == Type(HeapType::func, Nullable),
                        "table",
                        "Only funcref is valid for table type (when reference "
                        "types are disabled)");
      for (auto& segment : module.elementSegments) {
        info.shouldBeTrue(segment->table == table->name,
                          "elem",
                          "all element segments should refer to a single table "
                          "in MVP.");
        for (auto* expr : segment->data) {
          info.shouldBeTrue(
            expr->is<RefFunc>(),
            expr,
            "all table elements must be non-null funcrefs in MVP.");
          validator.validate(expr);
        }
      }
    }
  }

  Type externref = Type(HeapType::ext, Nullable);
  Type funcref = Type(HeapType::func, Nullable);
  for (auto& table : module.tables) {
    info.shouldBeTrue(table->initial <= table->max,
                      "table",
                      "size minimum must not be greater than maximum");
    info.shouldBeTrue(
      table->type.isNullable(),
      "table",
      "Non-nullable reference types are not yet supported for tables");
    if (!module.features.hasGC()) {
      info.shouldBeTrue(table->type.isFunction() || table->type == externref,
                        "table",
                        "Only function reference types or externref are valid "
                        "for table type (when GC is disabled)");
    }
    if (!module.features.hasGC()) {
      info.shouldBeTrue(table->type == funcref || table->type == externref,
                        "table",
                        "Only funcref and externref are valid for table type "
                        "(when gc is disabled)");
    }
    if (table->is64()) {
      info.shouldBeTrue(module.features.hasMemory64(),
                        "memory",
                        "64-bit tables require memory64 [--enable-memory64]");
    }
  }

  for (auto& segment : module.elementSegments) {
    info.shouldBeTrue(segment->type.isRef(),
                      "elem",
                      "element segment type must be of reference type.");
    info.shouldBeTrue(
      segment->type.isNullable(),
      "elem",
      "Non-nullable reference types are not yet supported for tables");

    bool isPassive = !segment->table.is();
    if (isPassive) {
      info.shouldBeTrue(
        !segment->offset, "elem", "passive segment should not have an offset");
    } else {
      auto table = module.getTableOrNull(segment->table);
      info.shouldBeTrue(table != nullptr,
                        "elem",
                        "element segment must have a valid table name");
      info.shouldBeTrue(
        !!segment->offset, "elem", "table segment offset must have an offset");
      info.shouldBeEqual(segment->offset->type,
                         table->indexType,
                         segment->offset,
                         "element segment offset must match table index type");
      info.shouldBeTrue(
        Properties::isValidConstantExpression(module, segment->offset),
        segment->offset,
        "table segment offset must be constant");
      info.shouldBeTrue(
        Type::isSubType(segment->type, table->type),
        "elem",
        "element segment type must be a subtype of the table type");
      validator.validate(segment->offset);
    }
    for (auto* expr : segment->data) {
      info.shouldBeTrue(Properties::isValidConstantExpression(module, expr),
                        expr,
                        "element must be a constant expression");
      info.shouldBeSubType(expr->type,
                           segment->type,
                           expr,
                           "element must be a subtype of the segment type");
      validator.validate(expr);
    }
  }
}

static void validateTags(Module& module, ValidationInfo& info) {
  if (!module.tags.empty()) {
    info.shouldBeTrue(
      module.features.hasExceptionHandling(),
      module.tags[0]->name,
      "Tags require exception-handling [--enable-exception-handling]");
  }
  for (auto& curr : module.tags) {
    if (curr->sig.results != Type(Type::none)) {
      info.shouldBeTrue(module.features.hasTypedContinuations(),
                        curr->name,
                        "Tags with result types require typed continuations "
                        "feature [--enable-typed-continuations]");
    }
    if (curr->sig.params.isTuple()) {
      info.shouldBeTrue(
        module.features.hasMultivalue(),
        curr->name,
        "Multivalue tag type requires multivalue [--enable-multivalue]");
    }
    FeatureSet features;
    for (const auto& param : curr->sig.params) {
      features |= param.getFeatures();
      info.shouldBeTrue(param.isConcrete(),
                        curr->name,
                        "Values in a tag should have concrete types");
    }
    info.shouldBeTrue(features <= module.features,
                      curr->name,
                      "all param types in tags should be allowed");
  }
}

static void validateStart(Module& module, ValidationInfo& info) {
  // start
  if (module.start.is()) {
    auto func = module.getFunctionOrNull(module.start);
    if (info.shouldBeTrue(
          func != nullptr, module.start, "start must be found")) {
      info.shouldBeTrue(func->getParams() == Type::none,
                        module.start,
                        "start must have 0 params");
      info.shouldBeTrue(func->getResults() == Type::none,
                        module.start,
                        "start must not return a value");
    }
  }
}

namespace {
template<typename T, typename U>
void validateModuleMap(Module& module,
                       ValidationInfo& info,
                       T& list,
                       U getter,
                       const std::string& kind) {
  // Given a list of module elements (like exports or globals), see that we can
  // get the items using the getter (getExportorNull, etc.). The getter uses the
  // lookup map internally, so this validates that they contain all items in
  // the list.
  for (auto& item : list) {
    auto* ptr = (module.*getter)(item->name);
    if (!ptr) {
      info.fail(kind + " must be found (use updateMaps)", item->name, nullptr);
    } else {
      info.shouldBeEqual(item->name,
                         ptr->name,
                         item->name,
                         "getter must return the correct item");
    }
  }

  // TODO: Also check there is nothing extraneous in the map, but that would
  //       require inspecting private fields of Module.
}
} // anonymous namespace

static void validateModuleMaps(Module& module, ValidationInfo& info) {
  // Module maps should be up to date.
  validateModuleMap(
    module, info, module.exports, &Module::getExportOrNull, "Export");
  validateModuleMap(
    module, info, module.functions, &Module::getFunctionOrNull, "Function");
  validateModuleMap(
    module, info, module.globals, &Module::getGlobalOrNull, "Global");
  validateModuleMap(module, info, module.tags, &Module::getTagOrNull, "Tag");
  validateModuleMap(module,
                    info,
                    module.elementSegments,
                    &Module::getElementSegmentOrNull,
                    "ElementSegment");
  validateModuleMap(
    module, info, module.memories, &Module::getMemoryOrNull, "Memory");
  validateModuleMap(module,
                    info,
                    module.dataSegments,
                    &Module::getDataSegmentOrNull,
                    "DataSegment");
  validateModuleMap(
    module, info, module.tables, &Module::getTableOrNull, "Table");
}

static void validateFeatures(Module& module, ValidationInfo& info) {
  if (module.features.hasGC()) {
    info.shouldBeTrue(module.features.hasReferenceTypes(),
                      module.features,
                      "--enable-gc requires --enable-reference-types");
  }
}

static void validateClosedWorldInterface(Module& module, ValidationInfo& info) {
  // Error if there are any publicly exposed heap types beyond the types of
  // publicly exposed functions. Note that we must include all types in the rec
  // groups that are used, as if a type if public then all types in its rec
  // group are as well.
  std::unordered_set<RecGroup> publicRecGroups;
  ModuleUtils::iterImportedFunctions(module, [&](Function* func) {
    publicRecGroups.insert(func->type.getRecGroup());
  });
  for (auto& ex : module.exports) {
    if (ex->kind == ExternalKind::Function) {
      publicRecGroups.insert(module.getFunction(ex->value)->type.getRecGroup());
    }
  }

  std::unordered_set<HeapType> publicTypes;
  for (auto& group : publicRecGroups) {
    for (auto type : group) {
      publicTypes.insert(type);
    }
  }

  // Ignorable public types are public, but we can ignore them for purposes of
  // erroring here: It is always ok that they are public.
  auto ignorable = getIgnorablePublicTypes();

  for (auto type : ModuleUtils::getPublicHeapTypes(module)) {
    if (!publicTypes.count(type) && !ignorable.count(type)) {
      auto name = type.toString();
      if (auto it = module.typeNames.find(type); it != module.typeNames.end()) {
        name = it->second.name.toString();
      }
      info.fail("publicly exposed type disallowed with a closed world: $" +
                  name,
                type,
                nullptr);
    }
  }
}

// TODO: If we want the validator to be part of libwasm rather than libpasses,
// then Using PassRunner::getPassDebug causes a circular dependence. We should
// fix that, perhaps by moving some of the pass infrastructure into libsupport.
bool WasmValidator::validate(Module& module, Flags flags) {
  ValidationInfo info(module);
  info.validateWeb = (flags & Web) != 0;
  info.validateGlobally = (flags & Globally) != 0;
  info.quiet = (flags & Quiet) != 0;
  info.closedWorld = (flags & ClosedWorld) != 0;

  // Parallel function validation.
  PassRunner runner(&module);
  FunctionValidator functionValidator(module, &info);
  functionValidator.validate(&runner);

  // Also validate imports, which were not covered in the parallel traversal
  // since it is a function-parallel operation.
  for (auto& func : module.functions) {
    if (func->imported()) {
      functionValidator.visitFunction(func.get());
    }
  }

  // Validate globally.
  if (info.validateGlobally) {
    validateImports(module, info);
    validateExports(module, info);
    validateGlobals(module, info);
    validateMemories(module, info);
    validateDataSegments(module, info);
    validateTables(module, info);
    validateTags(module, info);
    validateStart(module, info);
    validateModuleMaps(module, info);
    validateFeatures(module, info);
    if (info.closedWorld) {
      validateClosedWorldInterface(module, info);
    }
  }

  // Validate additional internal IR details when in pass-debug mode.
  if (PassRunner::getPassDebug()) {
    validateBinaryenIR(module, info);
  }

  // Print all the data.
  if (!info.valid.load() && !info.quiet) {
    for (auto& func : module.functions) {
      std::cerr << info.getStream(func.get()).str();
    }
    std::cerr << info.getStream(nullptr).str();
  }
  return info.valid.load();
}

bool WasmValidator::validate(Module& module, const PassOptions& options) {
  Flags flags = options.validateGlobally ? Globally : Minimal;
  if (options.closedWorld) {
    flags |= ClosedWorld;
  }
  return validate(module, flags);
}

bool WasmValidator::validate(Function* func, Module& module, Flags flags) {
  ValidationInfo info(module);
  info.validateWeb = (flags & Web) != 0;
  info.validateGlobally = (flags & Globally) != 0;
  info.quiet = (flags & Quiet) != 0;
  FunctionValidator(module, &info).validate(func);
  // print all the data
  if (!info.valid.load() && !info.quiet) {
    std::cerr << info.getStream(func).str();
    std::cerr << info.getStream(nullptr).str();
  }
  return info.valid.load();
}

} // namespace wasm