/*
 * Copyright 2015 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 <chrono>
#include <sstream>

#ifdef __linux__
#include <unistd.h>
#endif

#include "ir/hashed.h"
#include "ir/module-utils.h"
#include "pass.h"
#include "passes/passes.h"
#include "support/colors.h"
#include "wasm-debug.h"
#include "wasm-io.h"
#include "wasm-validator.h"

namespace wasm {

// PassRegistry

PassRegistry::PassRegistry() { registerPasses(); }

static PassRegistry singleton;

PassRegistry* PassRegistry::get() { return &singleton; }

void PassRegistry::registerPass(const char* name,
                                const char* description,
                                Creator create) {
  assert(passInfos.find(name) == passInfos.end());
  passInfos[name] = PassInfo(description, create);
}

std::unique_ptr<Pass> PassRegistry::createPass(std::string name) {
  if (passInfos.find(name) == passInfos.end()) {
    return nullptr;
  }
  std::unique_ptr<Pass> ret;
  ret.reset(passInfos[name].create());
  ret->name = name;
  return ret;
}

std::vector<std::string> PassRegistry::getRegisteredNames() {
  std::vector<std::string> ret;
  for (auto pair : passInfos) {
    ret.push_back(pair.first);
  }
  return ret;
}

std::string PassRegistry::getPassDescription(std::string name) {
  assert(passInfos.find(name) != passInfos.end());
  return passInfos[name].description;
}

// PassRunner

void PassRegistry::registerPasses() {
  registerPass("alignment-lowering",
               "lower unaligned loads and stores to smaller aligned ones",
               createAlignmentLoweringPass);
  registerPass("asyncify",
               "async/await style transform, allowing pausing and resuming",
               createAsyncifyPass);
  registerPass("avoid-reinterprets",
               "Tries to avoid reinterpret operations via more loads",
               createAvoidReinterpretsPass);
  registerPass(
    "dae", "removes arguments to calls in an lto-like manner", createDAEPass);
  registerPass("dae-optimizing",
               "removes arguments to calls in an lto-like manner, and "
               "optimizes where we removed",
               createDAEOptimizingPass);
  registerPass("coalesce-locals",
               "reduce # of locals by coalescing",
               createCoalesceLocalsPass);
  registerPass("coalesce-locals-learning",
               "reduce # of locals by coalescing and learning",
               createCoalesceLocalsWithLearningPass);
  registerPass("code-pushing",
               "push code forward, potentially making it not always execute",
               createCodePushingPass);
  registerPass(
    "code-folding", "fold code, merging duplicates", createCodeFoldingPass);
  registerPass("const-hoisting",
               "hoist repeated constants to a local",
               createConstHoistingPass);
  registerPass(
    "dce", "removes unreachable code", createDeadCodeEliminationPass);
  registerPass(
    "directize", "turns indirect calls into direct ones", createDirectizePass);
  registerPass(
    "dfo", "optimizes using the DataFlow SSA IR", createDataFlowOptsPass);
  registerPass("dwarfdump",
               "dump DWARF debug info sections from the read binary",
               createDWARFDumpPass);
  registerPass("duplicate-import-elimination",
               "removes duplicate imports",
               createDuplicateImportEliminationPass);
  registerPass("duplicate-function-elimination",
               "removes duplicate functions",
               createDuplicateFunctionEliminationPass);
  registerPass("emit-target-features",
               "emit the target features section in the output",
               createEmitTargetFeaturesPass);
  registerPass("extract-function",
               "leaves just one function (useful for debugging)",
               createExtractFunctionPass);
  registerPass(
    "flatten", "flattens out code, removing nesting", createFlattenPass);
  registerPass("fpcast-emu",
               "emulates function pointer casts, allowing incorrect indirect "
               "calls to (sometimes) work",
               createFuncCastEmulationPass);
  registerPass(
    "func-metrics", "reports function metrics", createFunctionMetricsPass);
  registerPass(
    "generate-stack-ir", "generate Stack IR", createGenerateStackIRPass);
  registerPass(
    "inline-main", "inline __original_main into main", createInlineMainPass);
  registerPass("inlining",
               "inline functions (you probably want inlining-optimizing)",
               createInliningPass);
  registerPass("inlining-optimizing",
               "inline functions and optimizes where we inlined",
               createInliningOptimizingPass);
  registerPass("legalize-js-interface",
               "legalizes i64 types on the import/export boundary",
               createLegalizeJSInterfacePass);
  registerPass("legalize-js-interface-minimally",
               "legalizes i64 types on the import/export boundary in a minimal "
               "manner, only on things only JS will call",
               createLegalizeJSInterfaceMinimallyPass);
  registerPass("local-cse",
               "common subexpression elimination inside basic blocks",
               createLocalCSEPass);
  registerPass("log-execution",
               "instrument the build with logging of where execution goes",
               createLogExecutionPass);
  registerPass("i64-to-i32-lowering",
               "lower all uses of i64s to use i32s instead",
               createI64ToI32LoweringPass);
  registerPass(
    "instrument-locals",
    "instrument the build with code to intercept all loads and stores",
    createInstrumentLocalsPass);
  registerPass(
    "instrument-memory",
    "instrument the build with code to intercept all loads and stores",
    createInstrumentMemoryPass);
  registerPass(
    "licm", "loop invariant code motion", createLoopInvariantCodeMotionPass);
  registerPass("limit-segments",
               "attempt to merge segments to fit within web limits",
               createLimitSegmentsPass);
  registerPass("memory-packing",
               "packs memory into separate segments, skipping zeros",
               createMemoryPackingPass);
  registerPass(
    "merge-blocks", "merges blocks to their parents", createMergeBlocksPass);
  registerPass(
    "merge-locals", "merges locals when beneficial", createMergeLocalsPass);
  registerPass("metrics", "reports metrics", createMetricsPass);
  registerPass("minify-imports",
               "minifies import names (only those, and not export names), and "
               "emits a mapping to the minified ones",
               createMinifyImportsPass);
  registerPass("minify-imports-and-exports",
               "minifies both import and export names, and emits a mapping to "
               "the minified ones",
               createMinifyImportsAndExportsPass);
  registerPass("minify-imports-and-exports-and-modules",
               "minifies both import and export names, and emits a mapping to "
               "the minified ones, and minifies the modules as well",
               createMinifyImportsAndExportsAndModulesPass);
  registerPass("mod-asyncify-always-and-only-unwind",
               "apply the assumption that asyncify imports always unwind, "
               "and we never rewind",
               createModAsyncifyAlwaysOnlyUnwindPass);
  registerPass("mod-asyncify-never-unwind",
               "apply the assumption that asyncify never unwinds",
               createModAsyncifyNeverUnwindPass);
  registerPass("nm", "name list", createNameListPass);
  registerPass("no-exit-runtime",
               "removes calls to atexit(), which is valid if the C runtime "
               "will never be exited",
               createNoExitRuntimePass);
  registerPass("optimize-added-constants",
               "optimizes added constants into load/store offsets",
               createOptimizeAddedConstantsPass);
  registerPass("optimize-added-constants-propagate",
               "optimizes added constants into load/store offsets, propagating "
               "them across locals too",
               createOptimizeAddedConstantsPropagatePass);
  registerPass("optimize-instructions",
               "optimizes instruction combinations",
               createOptimizeInstructionsPass);
  registerPass(
    "optimize-stack-ir", "optimize Stack IR", createOptimizeStackIRPass);
  registerPass("pick-load-signs",
               "pick load signs based on their uses",
               createPickLoadSignsPass);
  registerPass("post-assemblyscript",
               "eliminates redundant ARC patterns in AssemblyScript output",
               createPostAssemblyScriptPass);
  registerPass("post-assemblyscript-finalize",
               "eliminates collapsed ARC patterns after other optimizations",
               createPostAssemblyScriptFinalizePass);
  registerPass("post-emscripten",
               "miscellaneous optimizations for Emscripten-generated code",
               createPostEmscriptenPass);
  registerPass("precompute",
               "computes compile-time evaluatable expressions",
               createPrecomputePass);
  registerPass("precompute-propagate",
               "computes compile-time evaluatable expressions and propagates "
               "them through locals",
               createPrecomputePropagatePass);
  registerPass("print", "print in s-expression format", createPrinterPass);
  registerPass("print-minified",
               "print in minified s-expression format",
               createMinifiedPrinterPass);
  registerPass("print-features",
               "print options for enabled features",
               createPrintFeaturesPass);
  registerPass(
    "print-full", "print in full s-expression format", createFullPrinterPass);
  registerPass(
    "print-call-graph", "print call graph", createPrintCallGraphPass);
  registerPass("print-function-map",
               "print a map of function indexes to names",
               createPrintFunctionMapPass);
  registerPass("print-stack-ir",
               "print out Stack IR (useful for internal debugging)",
               createPrintStackIRPass);
  registerPass("relooper-jump-threading",
               "thread relooper jumps (fastcomp output only)",
               createRelooperJumpThreadingPass);
  registerPass("remove-non-js-ops",
               "removes operations incompatible with js",
               createRemoveNonJSOpsPass);
  registerPass("remove-imports",
               "removes imports and replaces them with nops",
               createRemoveImportsPass);
  registerPass(
    "remove-memory", "removes memory segments", createRemoveMemoryPass);
  registerPass("remove-unused-brs",
               "removes breaks from locations that are not needed",
               createRemoveUnusedBrsPass);
  registerPass("remove-unused-module-elements",
               "removes unused module elements",
               createRemoveUnusedModuleElementsPass);
  registerPass("remove-unused-nonfunction-module-elements",
               "removes unused module elements that are not functions",
               createRemoveUnusedNonFunctionModuleElementsPass);
  registerPass("remove-unused-names",
               "removes names from locations that are never branched to",
               createRemoveUnusedNamesPass);
  registerPass("reorder-functions",
               "sorts functions by access frequency",
               createReorderFunctionsPass);
  registerPass("reorder-locals",
               "sorts locals by access frequency",
               createReorderLocalsPass);
  registerPass("rereloop",
               "re-optimize control flow using the relooper algorithm",
               createReReloopPass);
  registerPass(
    "rse", "remove redundant local.sets", createRedundantSetEliminationPass);
  registerPass("roundtrip",
               "write the module to binary, then read it",
               createRoundTripPass);
  registerPass("safe-heap",
               "instrument loads and stores to check for invalid behavior",
               createSafeHeapPass);
  registerPass("simplify-globals",
               "miscellaneous globals-related optimizations",
               createSimplifyGlobalsPass);
  registerPass("simplify-globals-optimizing",
               "miscellaneous globals-related optimizations, and optimizes "
               "where we replaced global.gets with constants",
               createSimplifyGlobalsOptimizingPass);
  registerPass("simplify-locals",
               "miscellaneous locals-related optimizations",
               createSimplifyLocalsPass);
  registerPass("simplify-locals-nonesting",
               "miscellaneous locals-related optimizations (no nesting at all; "
               "preserves flatness)",
               createSimplifyLocalsNoNestingPass);
  registerPass("simplify-locals-notee",
               "miscellaneous locals-related optimizations (no tees)",
               createSimplifyLocalsNoTeePass);
  registerPass("simplify-locals-nostructure",
               "miscellaneous locals-related optimizations (no structure)",
               createSimplifyLocalsNoStructurePass);
  registerPass(
    "simplify-locals-notee-nostructure",
    "miscellaneous locals-related optimizations (no tees or structure)",
    createSimplifyLocalsNoTeeNoStructurePass);
  registerPass("souperify", "emit Souper IR in text form", createSouperifyPass);
  registerPass("souperify-single-use",
               "emit Souper IR in text form (single-use nodes only)",
               createSouperifySingleUsePass);
  registerPass("spill-pointers",
               "spill pointers to the C stack (useful for Boehm-style GC)",
               createSpillPointersPass);
  registerPass("ssa",
               "ssa-ify variables so that they have a single assignment",
               createSSAifyPass);
  registerPass(
    "ssa-nomerge",
    "ssa-ify variables so that they have a single assignment, ignoring merges",
    createSSAifyNoMergePass);
  registerPass(
    "strip", "deprecated; same as strip-debug", createStripDebugPass);
  registerPass("strip-debug",
               "strip debug info (including the names section)",
               createStripDebugPass);
  registerPass("strip-dwarf", "strip dwarf debug info", createStripDWARFPass);
  registerPass("strip-producers",
               "strip the wasm producers section",
               createStripProducersPass);
  registerPass("strip-target-features",
               "strip the wasm target features section",
               createStripTargetFeaturesPass);
  registerPass("trap-mode-clamp",
               "replace trapping operations with clamping semantics",
               createTrapModeClamp);
  registerPass("trap-mode-js",
               "replace trapping operations with js semantics",
               createTrapModeJS);
  registerPass("untee",
               "removes local.tees, replacing them with sets and gets",
               createUnteePass);
  registerPass("vacuum", "removes obviously unneeded code", createVacuumPass);
  // registerPass(
  //   "lower-i64", "lowers i64 into pairs of i32s", createLowerInt64Pass);
}

void PassRunner::addDefaultOptimizationPasses() {
  addDefaultGlobalOptimizationPrePasses();
  addDefaultFunctionOptimizationPasses();
  addDefaultGlobalOptimizationPostPasses();
}

// Check whether we should preserve valid DWARF while optimizing. If so, we
// disable optimizations that currently cause issues with debug info.
static bool shouldPreserveDWARF(PassOptions& options, Module& wasm) {
  return options.debugInfo && Debug::hasDWARFSections(wasm);
}

void PassRunner::addDefaultFunctionOptimizationPasses() {
  auto preserveDWARF = shouldPreserveDWARF(options, *wasm);
  // Untangling to semi-ssa form is helpful (but best to ignore merges
  // so as to not introduce new copies).
  // FIXME DWARF updating does not handle local changes yet.
  if (!preserveDWARF &&
      (options.optimizeLevel >= 3 || options.shrinkLevel >= 1)) {
    add("ssa-nomerge");
  }
  // if we are willing to work very very hard, flatten the IR and do opts
  // that depend on flat IR
  // FIXME DWARF updating does not handle local changes yet.
  if (!preserveDWARF && options.optimizeLevel >= 4) {
    add("flatten");
    add("local-cse");
  }
  add("dce");
  add("remove-unused-brs");
  add("remove-unused-names");
  add("optimize-instructions");
  if (options.optimizeLevel >= 2 || options.shrinkLevel >= 2) {
    add("pick-load-signs");
  }
  // early propagation
  if (options.optimizeLevel >= 3 || options.shrinkLevel >= 2) {
    add("precompute-propagate");
  } else {
    add("precompute");
  }
  if (options.lowMemoryUnused) {
    if (options.optimizeLevel >= 3 || options.shrinkLevel >= 1) {
      add("optimize-added-constants-propagate");
    } else {
      add("optimize-added-constants");
    }
  }
  if (options.optimizeLevel >= 2 || options.shrinkLevel >= 2) {
    add("code-pushing");
  }
  // don't create if/block return values yet, as coalesce can remove copies that
  // that could inhibit
  add("simplify-locals-nostructure");
  add("vacuum"); // previous pass creates garbage
  add("reorder-locals");
  // simplify-locals opens opportunities for optimizations
  add("remove-unused-brs");
  // if we are willing to work hard, also optimize copies before coalescing
  // FIXME DWARF updating does not handle local changes yet.
  if (!preserveDWARF &&
      (options.optimizeLevel >= 3 || options.shrinkLevel >= 2)) {
    add("merge-locals"); // very slow on e.g. sqlite
  }
  // FIXME DWARF updating does not handle local changes yet.
  if (!preserveDWARF) {
    add("coalesce-locals");
  }
  add("simplify-locals");
  add("vacuum");
  add("reorder-locals");
  // FIXME DWARF updating does not handle local changes yet.
  if (!preserveDWARF) {
    add("coalesce-locals");
    add("reorder-locals");
  }
  add("vacuum");
  if (options.optimizeLevel >= 3 || options.shrinkLevel >= 1) {
    add("code-folding");
  }
  add("merge-blocks");        // makes remove-unused-brs more effective
  add("remove-unused-brs");   // coalesce-locals opens opportunities
  add("remove-unused-names"); // remove-unused-brs opens opportunities
  add("merge-blocks");        // clean up remove-unused-brs new blocks
  // late propagation
  if (options.optimizeLevel >= 3 || options.shrinkLevel >= 2) {
    add("precompute-propagate");
  } else {
    add("precompute");
  }
  add("optimize-instructions");
  if (options.optimizeLevel >= 2 || options.shrinkLevel >= 1) {
    add("rse"); // after all coalesce-locals, and before a final vacuum
  }
  add("vacuum"); // just to be safe
}

void PassRunner::addDefaultGlobalOptimizationPrePasses() {
  // FIXME DWARF updating does not handle merging debug info with merged code.
  if (!shouldPreserveDWARF(options, *wasm)) {
    add("duplicate-function-elimination");
  }
  add("memory-packing");
}

void PassRunner::addDefaultGlobalOptimizationPostPasses() {
  auto preserveDWARF = shouldPreserveDWARF(options, *wasm);
  // FIXME DWARF may be badly affected currently as DAE changes function
  // signatures and hence params and locals.
  if (!preserveDWARF &&
      (options.optimizeLevel >= 2 || options.shrinkLevel >= 1)) {
    add("dae-optimizing");
  }
  // FIXME DWARF updating does not handle inlining yet.
  if (!preserveDWARF &&
      (options.optimizeLevel >= 2 || options.shrinkLevel >= 2)) {
    add("inlining-optimizing");
  }
  // Optimizations show more functions as duplicate, so run this here in Post.
  // FIXME DWARF updating does not handle merging debug info with merged code.
  if (!preserveDWARF) {
    add("duplicate-function-elimination");
  }
  add("duplicate-import-elimination");
  if (options.optimizeLevel >= 2 || options.shrinkLevel >= 2) {
    add("simplify-globals-optimizing");
  } else {
    add("simplify-globals");
  }
  add("remove-unused-module-elements");
  // may allow more inlining/dae/etc., need --converge for that
  add("directize");
  // perform Stack IR optimizations here, at the very end of the
  // optimization pipeline
  if (options.optimizeLevel >= 2 || options.shrinkLevel >= 1) {
    add("generate-stack-ir");
    add("optimize-stack-ir");
  }
}

static void dumpWast(Name name, Module* wasm) {
  // write out the wat
  static int counter = 0;
  std::string numstr = std::to_string(counter++);
  while (numstr.size() < 3) {
    numstr = '0' + numstr;
  }
  auto fullName = std::string("byn-");
#ifdef __linux__
  // TODO: use _getpid() on windows, elsewhere?
  fullName += std::to_string(getpid()) + '-';
#endif
  fullName += numstr + "-" + name.str;
  Colors::setEnabled(false);
  ModuleWriter writer;
  writer.writeText(*wasm, fullName + ".wast");
  writer.writeBinary(*wasm, fullName + ".wasm");
}

void PassRunner::run() {
  static const int passDebug = getPassDebug();
  if (!isNested && (options.debug || passDebug)) {
    // for debug logging purposes, run each pass in full before running the
    // other
    auto totalTime = std::chrono::duration<double>(0);
    size_t padding = 0;
    WasmValidator::Flags validationFlags = WasmValidator::Minimal;
    if (options.validateGlobally) {
      validationFlags = validationFlags | WasmValidator::Globally;
    }
    std::cerr << "[PassRunner] running passes..." << std::endl;
    for (auto& pass : passes) {
      padding = std::max(padding, pass->name.size());
    }
    if (passDebug >= 3) {
      dumpWast("before", wasm);
    }
    for (auto& pass : passes) {
      // ignoring the time, save a printout of the module before, in case this
      // pass breaks it, so we can print the before and after
      std::stringstream moduleBefore;
      if (passDebug == 2) {
        WasmPrinter::printModule(wasm, moduleBefore);
      }
      // prepare to run
      std::cerr << "[PassRunner]   running pass: " << pass->name << "... ";
      for (size_t i = 0; i < padding - pass->name.size(); i++) {
        std::cerr << ' ';
      }
      auto before = std::chrono::steady_clock::now();
      if (pass->isFunctionParallel()) {
        // function-parallel passes should get a new instance per function
        ModuleUtils::iterDefinedFunctions(
          *wasm, [&](Function* func) { runPassOnFunction(pass.get(), func); });
      } else {
        runPass(pass.get());
      }
      auto after = std::chrono::steady_clock::now();
      std::chrono::duration<double> diff = after - before;
      std::cerr << diff.count() << " seconds." << std::endl;
      totalTime += diff;
      if (options.validate) {
        // validate, ignoring the time
        std::cerr << "[PassRunner]   (validating)\n";
        if (!WasmValidator().validate(*wasm, validationFlags)) {
          WasmPrinter::printModule(wasm);
          if (passDebug >= 2) {
            std::cerr << "Last pass (" << pass->name
                      << ") broke validation. Here is the module before: \n"
                      << moduleBefore.str() << "\n";
          } else {
            std::cerr << "Last pass (" << pass->name
                      << ") broke validation. Run with BINARYEN_PASS_DEBUG=2 "
                         "in the env to see the earlier state, or 3 to dump "
                         "byn-* files for each pass\n";
          }
          abort();
        }
      }
      if (passDebug >= 3) {
        dumpWast(pass->name, wasm);
      }
    }
    std::cerr << "[PassRunner] passes took " << totalTime.count() << " seconds."
              << std::endl;
    if (options.validate) {
      std::cerr << "[PassRunner] (final validation)\n";
      if (!WasmValidator().validate(*wasm, validationFlags)) {
        WasmPrinter::printModule(wasm);
        std::cerr << "final module does not validate\n";
        abort();
      }
    }
  } else {
    // non-debug normal mode, run them in an optimal manner - for locality it is
    // better to run as many passes as possible on a single function before
    // moving to the next
    std::vector<Pass*> stack;
    auto flush = [&]() {
      if (stack.size() > 0) {
        // run the stack of passes on all the functions, in parallel
        size_t num = ThreadPool::get()->size();
        std::vector<std::function<ThreadWorkState()>> doWorkers;
        std::atomic<size_t> nextFunction;
        nextFunction.store(0);
        size_t numFunctions = wasm->functions.size();
        for (size_t i = 0; i < num; i++) {
          doWorkers.push_back([&]() {
            auto index = nextFunction.fetch_add(1);
            // get the next task, if there is one
            if (index >= numFunctions) {
              return ThreadWorkState::Finished; // nothing left
            }
            Function* func = this->wasm->functions[index].get();
            if (!func->imported()) {
              // do the current task: run all passes on this function
              for (auto* pass : stack) {
                runPassOnFunction(pass, func);
              }
            }
            if (index + 1 == numFunctions) {
              return ThreadWorkState::Finished; // we did the last one
            }
            return ThreadWorkState::More;
          });
        }
        ThreadPool::get()->work(doWorkers);
      }
      stack.clear();
    };
    for (auto& pass : passes) {
      if (pass->isFunctionParallel()) {
        stack.push_back(pass.get());
      } else {
        flush();
        runPass(pass.get());
      }
    }
    flush();
  }
}

void PassRunner::runOnFunction(Function* func) {
  if (options.debug) {
    std::cerr << "[PassRunner] running passes on function " << func->name
              << std::endl;
  }
  for (auto& pass : passes) {
    runPassOnFunction(pass.get(), func);
  }
}

void PassRunner::doAdd(std::unique_ptr<Pass> pass) {
  pass->prepareToRun(this, wasm);
  passes.emplace_back(std::move(pass));
}

// Checks that the state is valid before and after a
// pass runs on a function. We run these extra checks when
// pass-debug mode is enabled.
struct AfterEffectFunctionChecker {
  Function* func;
  Name name;

  // Check Stack IR state: if the main IR changes, there should be no
  // stack IR, as the stack IR would be wrong.
  bool beganWithStackIR;
  HashType originalFunctionHash;

  // In the creator we can scan the state of the module and function before the
  // pass runs.
  AfterEffectFunctionChecker(Function* func) : func(func), name(func->name) {
    beganWithStackIR = func->stackIR != nullptr;
    if (beganWithStackIR) {
      originalFunctionHash = FunctionHasher::hashFunction(func);
    }
  }

  // This is called after the pass is run, at which time we can check things.
  void check() {
    assert(func->name == name); // no global module changes should have occurred
    if (beganWithStackIR && func->stackIR) {
      auto after = FunctionHasher::hashFunction(func);
      if (after != originalFunctionHash) {
        Fatal() << "[PassRunner] PASS_DEBUG check failed: had Stack IR before "
                   "and after the pass ran, and the pass modified the main IR, "
                   "which invalidates Stack IR - pass should have been marked "
                   "'modifiesBinaryenIR'";
      }
    }
  }
};

// Runs checks on the entire module, in a non-function-parallel pass.
// In particular, in such a pass functions may be removed or renamed, track
// that.
struct AfterEffectModuleChecker {
  Module* module;

  std::vector<AfterEffectFunctionChecker> checkers;

  bool beganWithAnyStackIR;

  AfterEffectModuleChecker(Module* module) : module(module) {
    for (auto& func : module->functions) {
      checkers.emplace_back(func.get());
    }
    beganWithAnyStackIR = hasAnyStackIR();
  }

  void check() {
    if (beganWithAnyStackIR && hasAnyStackIR()) {
      // If anything changed to the functions, that's not good.
      if (checkers.size() != module->functions.size()) {
        error();
      }
      for (Index i = 0; i < checkers.size(); i++) {
        // Did a pointer change? (a deallocated function could cause that)
        if (module->functions[i].get() != checkers[i].func ||
            module->functions[i]->body != checkers[i].func->body) {
          error();
        }
        // Did a name change?
        if (module->functions[i]->name != checkers[i].name) {
          error();
        }
      }
      // Global function state appears to not have been changed: the same
      // functions are there. Look into their contents.
      for (auto& checker : checkers) {
        checker.check();
      }
    }
  }

  void error() {
    Fatal() << "[PassRunner] PASS_DEBUG check failed: had Stack IR before and "
               "after the pass ran, and the pass modified global function "
               "state - pass should have been marked 'modifiesBinaryenIR'";
  }

  bool hasAnyStackIR() {
    for (auto& func : module->functions) {
      if (func->stackIR) {
        return true;
      }
    }
    return false;
  }
};

void PassRunner::runPass(Pass* pass) {
  std::unique_ptr<AfterEffectModuleChecker> checker;
  if (getPassDebug()) {
    checker = std::unique_ptr<AfterEffectModuleChecker>(
      new AfterEffectModuleChecker(wasm));
  }
  pass->run(this, wasm);
  handleAfterEffects(pass);
  if (getPassDebug()) {
    checker->check();
  }
}

void PassRunner::runPassOnFunction(Pass* pass, Function* func) {
  assert(pass->isFunctionParallel());
  // function-parallel passes get a new instance per function
  auto instance = std::unique_ptr<Pass>(pass->create());
  std::unique_ptr<AfterEffectFunctionChecker> checker;
  if (getPassDebug()) {
    checker = std::unique_ptr<AfterEffectFunctionChecker>(
      new AfterEffectFunctionChecker(func));
  }
  instance->runOnFunction(this, wasm, func);
  handleAfterEffects(pass, func);
  if (getPassDebug()) {
    checker->check();
  }
}

void PassRunner::handleAfterEffects(Pass* pass, Function* func) {
  if (pass->modifiesBinaryenIR()) {
    // If Binaryen IR is modified, Stack IR must be cleared - it would
    // be out of sync in a potentially dangerous way.
    if (func) {
      func->stackIR.reset(nullptr);
    } else {
      for (auto& func : wasm->functions) {
        func->stackIR.reset(nullptr);
      }
    }
  }
}

int PassRunner::getPassDebug() {
  static const int passDebug =
    getenv("BINARYEN_PASS_DEBUG") ? atoi(getenv("BINARYEN_PASS_DEBUG")) : 0;
  return passDebug;
}

} // namespace wasm
