/*
 * 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.
 */

//
// Loads wasm plus a list of functions that are global ctors, i.e.,
// are to be executed. It then executes as many of them as it can,
// applying their changes to memory etc as needed, then writes it. In
// other words, this executes code at compile time to speed up
// startup later.
//

#include <memory>

#include "asmjs/shared-constants.h"
#include "ir/global-utils.h"
#include "ir/import-utils.h"
#include "ir/literal-utils.h"
#include "ir/memory-utils.h"
#include "ir/names.h"
#include "pass.h"
#include "support/colors.h"
#include "support/file.h"
#include "support/small_set.h"
#include "support/string.h"
#include "tool-options.h"
#include "wasm-builder.h"
#include "wasm-interpreter.h"
#include "wasm-io.h"
#include "wasm-validator.h"

using namespace wasm;

namespace {

struct FailToEvalException {
  std::string why;
  FailToEvalException(std::string why) : why(why) {}
};

// The prefix for a recommendation, so it is aligned properly with the rest of
// the output.
#define RECOMMENDATION "\n       recommendation: "

class EvallingModuleRunner : public ModuleRunnerBase<EvallingModuleRunner> {
public:
  EvallingModuleRunner(
    Module& wasm,
    ExternalInterface* externalInterface,
    std::map<Name, std::shared_ptr<EvallingModuleRunner>> linkedInstances_ = {})
    : ModuleRunnerBase(wasm, externalInterface, linkedInstances_) {}

  Flow visitGlobalGet(GlobalGet* curr) {
    // Error on reads of imported globals.
    auto* global = wasm.getGlobal(curr->name);
    if (global->imported()) {
      throw FailToEvalException(std::string("read from imported global ") +
                                global->module.str + "." + global->base.str);
    }

    return ModuleRunnerBase<EvallingModuleRunner>::visitGlobalGet(curr);
  }
};

// Build an artificial `env` module based on a module's imports, so that the
// interpreter can use correct object instances. It initializes usable global
// imports, and fills the rest with fake values since those are dangerous to
// use. we will fail if dangerous globals are used.
std::unique_ptr<Module> buildEnvModule(Module& wasm) {
  auto env = std::make_unique<Module>();
  env->name = "env";

  // create empty functions with similar signature
  ModuleUtils::iterImportedFunctions(wasm, [&](Function* func) {
    if (func->module == env->name) {
      Builder builder(*env);
      auto* copied = ModuleUtils::copyFunction(func, *env);
      copied->module = Name();
      copied->base = Name();
      copied->body = builder.makeUnreachable();
      env->addExport(
        builder.makeExport(func->base, copied->name, ExternalKind::Function));
    }
  });

  // create tables with similar initial and max values
  ModuleUtils::iterImportedTables(wasm, [&](Table* table) {
    if (table->module == env->name) {
      auto* copied = ModuleUtils::copyTable(table, *env);
      copied->module = Name();
      copied->base = Name();
      env->addExport(Builder(*env).makeExport(
        table->base, copied->name, ExternalKind::Table));
    }
  });

  ModuleUtils::iterImportedGlobals(wasm, [&](Global* global) {
    if (global->module == env->name) {
      auto* copied = ModuleUtils::copyGlobal(global, *env);
      copied->module = Name();
      copied->base = Name();

      Builder builder(*env);
      copied->init = builder.makeConst(Literal::makeZero(global->type));
      env->addExport(
        builder.makeExport(global->base, copied->name, ExternalKind::Global));
    }
  });

  // create an exported memory with the same initial and max size
  ModuleUtils::iterImportedMemories(wasm, [&](Memory* memory) {
    if (memory->module == env->name) {
      env->memory.name = wasm.memory.name;
      env->memory.exists = true;
      env->memory.initial = memory->initial;
      env->memory.max = memory->max;
      env->memory.shared = memory->shared;
      env->memory.indexType = memory->indexType;
      env->addExport(Builder(*env).makeExport(
        wasm.memory.base, wasm.memory.name, ExternalKind::Memory));
    }
  });

  return env;
}

// Whether to ignore external input to the program as it runs. If set, we will
// assume that stdin is empty, that any env vars we try to read are not set,
// that there are not arguments passed to main, etc.
static bool ignoreExternalInput = false;

struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface {
  Module* wasm;
  EvallingModuleRunner* instance;
  std::map<Name, std::shared_ptr<EvallingModuleRunner>> linkedInstances;

  // A representation of the contents of wasm memory as we execute.
  std::vector<char> memory;

  CtorEvalExternalInterface(
    std::map<Name, std::shared_ptr<EvallingModuleRunner>> linkedInstances_ =
      {}) {
    linkedInstances.swap(linkedInstances_);
  }

  // Called when we want to apply the current state of execution to the Module.
  // Until this is called the Module is never changed.
  void applyToModule() {
    clearApplyState();

    // If nothing was ever written to memory then there is nothing to update.
    if (!memory.empty()) {
      applyMemoryToModule();
    }

    applyGlobalsToModule();
  }

  void init(Module& wasm_, EvallingModuleRunner& instance_) override {
    wasm = &wasm_;
    instance = &instance_;
  }

  void importGlobals(GlobalValueSet& globals, Module& wasm_) override {
    ModuleUtils::iterImportedGlobals(wasm_, [&](Global* global) {
      auto it = linkedInstances.find(global->module);
      if (it != linkedInstances.end()) {
        auto* inst = it->second.get();
        auto* globalExport = inst->wasm.getExportOrNull(global->base);
        if (!globalExport) {
          throw FailToEvalException(std::string("importGlobals: ") +
                                    global->module.str + "." +
                                    global->base.str);
        }
        globals[global->name] = inst->globals[globalExport->value];
      } else {
        throw FailToEvalException(std::string("importGlobals: ") +
                                  global->module.str + "." + global->base.str);
      }
    });
  }

  Literals callImport(Function* import, Literals& arguments) override {
    Name WASI("wasi_snapshot_preview1");

    if (ignoreExternalInput) {
      if (import->module == WASI) {
        if (import->base == "environ_sizes_get") {
          if (arguments.size() != 2 || arguments[0].type != Type::i32 ||
              import->getResults() != Type::i32) {
            throw FailToEvalException("wasi environ_sizes_get has wrong sig");
          }

          // Write out a count of i32(0) and return __WASI_ERRNO_SUCCESS (0).
          store32(arguments[0].geti32(), 0);
          return {Literal(int32_t(0))};
        }

        if (import->base == "environ_get") {
          if (arguments.size() != 2 || arguments[0].type != Type::i32 ||
              import->getResults() != Type::i32) {
            throw FailToEvalException("wasi environ_get has wrong sig");
          }

          // Just return __WASI_ERRNO_SUCCESS (0).
          return {Literal(int32_t(0))};
        }

        if (import->base == "args_sizes_get") {
          if (arguments.size() != 2 || arguments[0].type != Type::i32 ||
              import->getResults() != Type::i32) {
            throw FailToEvalException("wasi args_sizes_get has wrong sig");
          }

          // Write out an argc of i32(0) and return a __WASI_ERRNO_SUCCESS (0).
          store32(arguments[0].geti32(), 0);
          return {Literal(int32_t(0))};
        }

        if (import->base == "args_get") {
          if (arguments.size() != 2 || arguments[0].type != Type::i32 ||
              import->getResults() != Type::i32) {
            throw FailToEvalException("wasi args_get has wrong sig");
          }

          // Just return __WASI_ERRNO_SUCCESS (0).
          return {Literal(int32_t(0))};
        }

        // Otherwise, we don't recognize this import; continue normally to
        // error.
      }
    }

    std::string extra;
    if (import->module == ENV && import->base == "___cxa_atexit") {
      extra = RECOMMENDATION "build with -s NO_EXIT_RUNTIME=1 so that calls "
                             "to atexit are not emitted";
    } else if (import->module == WASI && !ignoreExternalInput) {
      extra = RECOMMENDATION "consider --ignore-external-input";
    }
    throw FailToEvalException(std::string("call import: ") +
                              import->module.str + "." + import->base.str +
                              extra);
  }

  // We assume the table is not modified FIXME
  Literals callTable(Name tableName,
                     Index index,
                     HeapType sig,
                     Literals& arguments,
                     Type result,
                     EvallingModuleRunner& instance) override {

    std::unordered_map<wasm::Name, std::vector<wasm::Name>>::iterator it;

    auto* table = wasm->getTableOrNull(tableName);
    if (!table) {
      throw FailToEvalException("callTable on non-existing table");
    }

    // Look through the segments and find the function. Segments can overlap,
    // so we want the last one.
    Name targetFunc;
    for (auto& segment : wasm->elementSegments) {
      if (segment->table != tableName) {
        continue;
      }

      Index start;
      // look for the index in this segment. if it has a constant offset, we
      // look in the proper range. if it instead gets a global, we rely on the
      // fact that when not dynamically linking then the table is loaded at
      // offset 0.
      if (auto* c = segment->offset->dynCast<Const>()) {
        start = c->value.getInteger();
      } else if (segment->offset->is<GlobalGet>()) {
        start = 0;
      } else {
        // wasm spec only allows const and global.get there
        WASM_UNREACHABLE("invalid expr type");
      }
      auto end = start + segment->data.size();
      if (start <= index && index < end) {
        auto entry = segment->data[index - start];
        if (auto* get = entry->dynCast<RefFunc>()) {
          targetFunc = get->func;
        } else {
          throw FailToEvalException(
            std::string("callTable on uninitialized entry"));
        }
      }
    }

    if (!targetFunc.is()) {
      throw FailToEvalException(
        std::string("callTable on index not found in static segments: ") +
        std::to_string(index));
    }

    // If this is one of our functions, we can call it; if it was
    // imported, fail.
    auto* func = wasm->getFunction(targetFunc);
    if (func->type != sig) {
      throw FailToEvalException(std::string("callTable signature mismatch: ") +
                                targetFunc.str);
    }
    if (!func->imported()) {
      return instance.callFunctionInternal(targetFunc, arguments);
    } else {
      throw FailToEvalException(
        std::string("callTable on imported function: ") + targetFunc.str);
    }
  }

  Index tableSize(Name tableName) override {
    throw FailToEvalException("table size");
  }

  Literal tableLoad(Name tableName, Index index) override {
    throw FailToEvalException("table.get: TODO");
  }

  // called during initialization
  void tableStore(Name tableName, Index index, const Literal& value) override {}

  int8_t load8s(Address addr) override { return doLoad<int8_t>(addr); }
  uint8_t load8u(Address addr) override { return doLoad<uint8_t>(addr); }
  int16_t load16s(Address addr) override { return doLoad<int16_t>(addr); }
  uint16_t load16u(Address addr) override { return doLoad<uint16_t>(addr); }
  int32_t load32s(Address addr) override { return doLoad<int32_t>(addr); }
  uint32_t load32u(Address addr) override { return doLoad<uint32_t>(addr); }
  int64_t load64s(Address addr) override { return doLoad<int64_t>(addr); }
  uint64_t load64u(Address addr) override { return doLoad<uint64_t>(addr); }

  void store8(Address addr, int8_t value) override {
    doStore<int8_t>(addr, value);
  }
  void store16(Address addr, int16_t value) override {
    doStore<int16_t>(addr, value);
  }
  void store32(Address addr, int32_t value) override {
    doStore<int32_t>(addr, value);
  }
  void store64(Address addr, int64_t value) override {
    doStore<int64_t>(addr, value);
  }

  bool growMemory(Address /*oldSize*/, Address /*newSize*/) override {
    throw FailToEvalException("grow memory");
  }

  bool growTable(Name /*name*/,
                 const Literal& /*value*/,
                 Index /*oldSize*/,
                 Index /*newSize*/) override {
    throw FailToEvalException("grow table");
  }

  void trap(const char* why) override {
    throw FailToEvalException(std::string("trap: ") + why);
  }

  void hostLimit(const char* why) override {
    throw FailToEvalException(std::string("trap: ") + why);
  }

  void throwException(const WasmException& exn) override {
    std::stringstream ss;
    ss << "exception thrown: " << exn;
    throw FailToEvalException(ss.str());
  }

private:
  // TODO: handle unaligned too, see shell-interface

  template<typename T> T* getMemory(Address address) {
    // resize the memory buffer as needed.
    auto max = address + sizeof(T);
    if (max > memory.size()) {
      memory.resize(max);
    }
    return (T*)(&memory[address]);
  }

  template<typename T> void doStore(Address address, T value) {
    // do a memcpy to avoid undefined behavior if unaligned
    memcpy(getMemory<T>(address), &value, sizeof(T));
  }

  template<typename T> T doLoad(Address address) {
    // do a memcpy to avoid undefined behavior if unaligned
    T ret;
    memcpy(&ret, getMemory<T>(address), sizeof(T));
    return ret;
  }

  // Clear the state of the operation of applying the interpreter's runtime
  // information into the module.
  //
  // This happens each time we apply contents to the module, which is basically
  // once per ctor function, but can be more fine-grained also if we execute a
  // line at a time.
  void clearApplyState() {
    // The process of allocating "defining globals" begins here, from scratch
    // each time (things live before may no longer be).
    definingGlobals.clear();

    // When we start to apply the state there should be no previous state left
    // over.
    assert(seenDataStack.empty());
  }

  void applyMemoryToModule() {
    // Memory must have already been flattened into the standard form: one
    // segment at offset 0, or none.
    if (wasm->memory.segments.empty()) {
      Builder builder(*wasm);
      std::vector<char> empty;
      wasm->memory.segments.push_back(
        Memory::Segment(builder.makeConst(int32_t(0)), empty));
    }
    auto& segment = wasm->memory.segments[0];
    assert(segment.offset->cast<Const>()->value.getInteger() == 0);

    // Copy the current memory contents after execution into the Module's
    // memory.
    segment.data = memory;
  }

  // Serializing GC data requires more work than linear memory, because
  // allocations have an identity, and they are created using struct.new /
  // array.new, which we must emit in a proper location in the wasm. This
  // affects how we serialize globals, which can contain GC data, and also, we
  // use globals to store GC data, so overall the process of computing the
  // globals is where most of the GC logic ends up.
  //
  // The general idea for handling GC data is as follows: After evaluating the
  // code, we end up with some live allocations in the interpreter, which we
  // need to somehow serialize into the wasm module. We will put each such live
  // GC data item into its own "defining global", a global whose purpose is to
  // create and store that data. Each such global is immutable, and has the
  // exact type of the data, for simplicity. Every other reference to that GC
  // data in the interpreter's memory can then be serialized by simply emitting
  // a global.get of that defining global.
  void applyGlobalsToModule() {
    Builder builder(*wasm);

    if (!wasm->features.hasGC()) {
      // Without GC, we can simply serialize the globals in place as they are.
      for (const auto& [name, values] : instance->globals) {
        wasm->getGlobal(name)->init = getSerialization(values);
      }
      return;
    }

    // We need to emit the "defining globals" of GC data before the existing
    // globals, as the normal ones may refer to them. We do this by removing all
    // the existing globals, and then adding them one by one, during which time
    // we call getSerialization() for their init expressions. If their init
    // refes to GC data, then we will allocate a defining global for that data,
    // and refer to it. Put another way, we place the existing globals back into
    // the module one at a time, adding their dependencies as we go.
    auto oldGlobals = std::move(wasm->globals);
    wasm->updateMaps();

    for (auto& oldGlobal : oldGlobals) {
      // Serialize the global's value. While doing so, pass in the name of this
      // global, as we may be able to reuse the global as the defining global
      // for the value. See getSerialization() for more details.
      Name name;
      if (!oldGlobal->mutable_ && oldGlobal->type == oldGlobal->init->type) {
        // This has the properties we need of a defining global - immutable and
        // of the precise type - so use it.
        name = oldGlobal->name;
      }

      // If there is a value here to serialize, do so. (If there is no value,
      // then this global was added after the interpreter initialized the
      // module, which means it is a new global we've added since; we don't need
      // to do anything for such a global - if it is needed it will show up as a
      // dependency of something, and be emitted at the right time and place.)
      auto iter = instance->globals.find(oldGlobal->name);
      if (iter != instance->globals.end()) {
        oldGlobal->init = getSerialization(iter->second, name);
        wasm->addGlobal(std::move(oldGlobal));
      }
    }
  }

public:
  // Maps each GC data in the interpreter to its defining global: the global in
  // which it is created, and then all other users of it can just global.get
  // that.
  std::unordered_map<GCData*, Name> definingGlobals;

  // The data we have seen so far on the stack. This is used to guard against
  // infinite recursion, which would otherwise happen if there is a cycle among
  // the live objects, which we don't handle yet.
  //
  // Pick a constant of 2 here to handle the common case of an object with a
  // reference to another object that is already in a defining global.
  SmallSet<GCData*, 2> seenDataStack;

  // If |possibleDefiningGlobal| is provided, it is the name of a global that we
  // are in the init expression of, and which can be reused as defining global,
  // if the other conditions are suitable.
  Expression* getSerialization(const Literal& value,
                               Name possibleDefiningGlobal = Name()) {
    Builder builder(*wasm);

    if (!value.isData()) {
      // This can be handled normally.
      return builder.makeConstantExpression(value);
    }

    // This is GC data, which we must handle in a more careful way.
    auto* data = value.getGCData().get();
    if (!data) {
      // This is a null, so simply emit one.
      return builder.makeRefNull(value.type);
    }

    // There was actual GC data allocated here.
    auto type = value.type;
    auto& definingGlobal = definingGlobals[data];
    if (!definingGlobal.is()) {
      // This is the first usage of this allocation. Generate a struct.new /
      // array.new for it.
      auto& values = value.getGCData()->values;
      std::vector<Expression*> args;

      // The initial values for this allocation may themselves be GC
      // allocations. Recurse and add globals as necessary.
      // TODO: Handle cycles. That will require code in the start function. For
      //       now, just error if we detect an infinite recursion.
      if (seenDataStack.count(data)) {
        Fatal() << "Cycle in live GC data, which we cannot serialize yet.";
      }
      seenDataStack.insert(data);
      for (auto& value : values) {
        args.push_back(getSerialization(value));
      }
      seenDataStack.erase(data);

      Expression* init;
      auto heapType = type.getHeapType();
      // TODO: handle rtts if we need them
      if (heapType.isStruct()) {
        init = builder.makeStructNew(heapType, args);
      } else if (heapType.isArray()) {
        // TODO: for repeated identical values, can use ArrayNew
        init = builder.makeArrayInit(heapType, args);
      } else {
        WASM_UNREACHABLE("bad gc type");
      }

      if (possibleDefiningGlobal.is()) {
        // No need to allocate a new global, as we are in the definition of
        // one. Just return the initialization expression, which will be
        // placed in that global's |init| field, and first note this as the
        // defining global.
        definingGlobal = possibleDefiningGlobal;
        return init;
      }

      // Allocate a new defining global.
      auto name = Names::getValidGlobalName(*wasm, "ctor-eval$global");
      wasm->addGlobal(builder.makeGlobal(name, type, init, Builder::Immutable));
      definingGlobal = name;
    }

    // Refer to this GC allocation by reading from the global that is
    // designated to contain it.
    return builder.makeGlobalGet(definingGlobal, value.type);
  }

  Expression* getSerialization(const Literals& values,
                               Name possibleDefiningGlobal = Name()) {
    assert(values.size() == 1);
    return getSerialization(values[0], possibleDefiningGlobal);
  }
};

// The outcome of evalling a ctor is one of three states:
//
// 1. We failed to eval it completely (but perhaps we succeeded partially). In
//    that case the std::optional here contains nothing.
// 2. We evalled it completely, and it is a function with no return value, so
//    it contains an empty Literals.
// 3. We evalled it completely, and it is a function with a return value, so
//    it contains Literals with those results.
using EvalCtorOutcome = std::optional<Literals>;

// Eval a single ctor function. Returns whether we succeeded to completely
// evaluate the ctor (which means that the caller can proceed to try to eval
// further ctors if there are any), and if we did, the results if the function
// returns any.
EvalCtorOutcome evalCtor(EvallingModuleRunner& instance,
                         CtorEvalExternalInterface& interface,
                         Name funcName,
                         Name exportName) {
  auto& wasm = instance.wasm;
  auto* func = wasm.getFunction(funcName);

  // We don't know the values of parameters, so give up if there are any, unless
  // we are ignoring them.
  if (func->getNumParams() > 0 && !ignoreExternalInput) {
    std::cout << "  ...stopping due to params\n";
    std::cout << RECOMMENDATION "consider --ignore-external-input";
    return EvalCtorOutcome();
  }

  // If there are params, we are ignoring them (or we would have quit earlier);
  // set those up with zeros.
  // TODO: Have a safer option here, either
  //        1. Statically or dynamically stop evalling when a param is actually
  //           used, or
  //        2. Split out --ignore-external-input into separate flags.
  Literals params;
  for (Index i = 0; i < func->getNumParams(); i++) {
    auto type = func->getLocalType(i);
    if (!LiteralUtils::canMakeZero(type)) {
      std::cout << "  ...stopping due to non-zeroable param\n";
      return EvalCtorOutcome();
    }
    params.push_back(Literal::makeZero(type));
  }

  // We want to handle the form of the global constructor function in LLVM. That
  // looks like this:
  //
  //    (func $__wasm_call_ctors
  //      (call $ctor.1)
  //      (call $ctor.2)
  //      (call $ctor.3)
  //    )
  //
  // Some of those ctors may be inlined, however, which would mean that the
  // function could have locals, control flow, etc. However, we assume for now
  // that it does not have parameters at least (whose values we can't tell).
  // And for now we look for a toplevel block and process its children one at a
  // time. This allows us to eval some of the $ctor.* functions (or their
  // inlined contents) even if not all.
  //
  // TODO: Support complete partial evalling, that is, evaluate parts of an
  //       arbitrary function, and not just a sequence in a single toplevel
  //       block.

  if (auto* block = func->body->dynCast<Block>()) {
    // Go through the items in the block and try to execute them. We do all this
    // in a single function scope for all the executions.
    EvallingModuleRunner::FunctionScope scope(func, params, instance);

    // After we successfully eval a line we will apply the changes here. This is
    // the same idea as applyToModule() - we must only do it after an entire
    // atomic "chunk" has been processed, we do not want partial updates from
    // an item in the block that we only partially evalled.
    std::vector<Literals> appliedLocals;

    Literals results;
    Index successes = 0;
    for (auto* curr : block->list) {
      Flow flow;
      try {
        flow = instance.visit(curr);
      } catch (FailToEvalException& fail) {
        if (successes == 0) {
          std::cout << "  ...stopping (in block) since could not eval: "
                    << fail.why << "\n";
        } else {
          std::cout << "  ...partial evalling successful, but stopping since "
                       "could not eval: "
                    << fail.why << "\n";
        }
        break;
      }

      // So far so good! Apply the results.
      interface.applyToModule();
      appliedLocals = scope.locals;
      successes++;

      // Note the values here, if any. If we are exiting the function now then
      // these will be returned.
      results = flow.values;

      if (flow.breaking()) {
        // We are returning out of the function (either via a return, or via a
        // break to |block|, which has the same outcome. That means we don't
        // need to execute any more lines, and can consider them to be executed.
        std::cout << "  ...stopping in block due to break\n";

        // Mark us as having succeeded on the entire block, since we have: we
        // are skipping the rest, which means there is no problem there. We must
        // set this here so that lower down we realize that we've evalled
        // everything.
        successes = block->list.size();
        break;
      }
    }

    if (successes > 0 && successes < block->list.size()) {
      // We managed to eval some but not all. That means we can't just remove
      // the entire function, but need to keep parts of it - the parts we have
      // not evalled - around. To do so, we create a copy of the function with
      // the partially-evalled contents and make the export use that (as the
      // function may be used in other places than the export, which we do not
      // want to affect).
      auto copyName = Names::getValidFunctionName(wasm, funcName);
      auto* copyFunc = ModuleUtils::copyFunction(func, wasm, copyName);
      wasm.getExport(exportName)->value = copyName;

      // Remove the items we've evalled.
      Builder builder(wasm);
      auto* copyBlock = copyFunc->body->cast<Block>();
      for (Index i = 0; i < successes; i++) {
        copyBlock->list[i] = builder.makeNop();
      }

      // Write out the values of locals, that is the local state after evalling
      // the things we've just nopped. For simplicity we just write out all of
      // locals, and leave it to the optimizer to remove redundant or
      // unnecessary operations.
      std::vector<Expression*> localSets;
      for (Index i = 0; i < copyFunc->getNumLocals(); i++) {
        auto value = appliedLocals[i];
        localSets.push_back(
          builder.makeLocalSet(i, interface.getSerialization(value)));
      }

      // Put the local sets at the front of the block. We know there must be a
      // nop in that position (since we've evalled at least one item in the
      // block, and replaced it with a nop), so we can overwrite it.
      copyBlock->list[0] = builder.makeBlock(localSets);

      // Interesting optimizations may be possible both due to removing some but
      // not all of the code, and due to the locals we just added.
      PassRunner passRunner(&wasm,
                            PassOptions::getWithDefaultOptimizationOptions());
      passRunner.addDefaultFunctionOptimizationPasses();
      passRunner.runOnFunction(copyFunc);
    }

    // Return true if we evalled the entire block. Otherwise, even if we evalled
    // some of it, the caller must stop trying to eval further things.
    if (successes == block->list.size()) {
      return EvalCtorOutcome(results);
    } else {
      return EvalCtorOutcome();
    }
  }

  // Otherwise, we don't recognize a pattern that allows us to do partial
  // evalling. So simply call the entire function at once and see if we can
  // optimize that.

  Literals results;
  try {
    results = instance.callFunction(funcName, params);
  } catch (FailToEvalException& fail) {
    std::cout << "  ...stopping since could not eval: " << fail.why << "\n";
    return EvalCtorOutcome();
  }

  // Success! Apply the results.
  interface.applyToModule();
  return EvalCtorOutcome(results);
}

// Eval all ctors in a module.
void evalCtors(Module& wasm,
               std::vector<std::string>& ctors,
               std::vector<std::string>& keptExports) {
  std::unordered_set<std::string> keptExportsSet(keptExports.begin(),
                                                 keptExports.end());

  std::map<Name, std::shared_ptr<EvallingModuleRunner>> linkedInstances;

  // build and link the env module
  auto envModule = buildEnvModule(wasm);
  CtorEvalExternalInterface envInterface;
  auto envInstance =
    std::make_shared<EvallingModuleRunner>(*envModule, &envInterface);
  linkedInstances[envModule->name] = envInstance;

  CtorEvalExternalInterface interface(linkedInstances);
  try {
    // create an instance for evalling
    EvallingModuleRunner instance(wasm, &interface, linkedInstances);
    // go one by one, in order, until we fail
    // TODO: if we knew priorities, we could reorder?
    for (auto& ctor : ctors) {
      std::cout << "trying to eval " << ctor << '\n';
      Export* ex = wasm.getExportOrNull(ctor);
      if (!ex) {
        Fatal() << "export not found: " << ctor;
      }
      auto funcName = ex->value;
      auto outcome = evalCtor(instance, interface, funcName, ctor);
      if (!outcome) {
        std::cout << "  ...stopping\n";
        return;
      }

      // Success! And we can continue to try more.
      std::cout << "  ...success on " << ctor << ".\n";

      // Remove the export if we should.
      auto* exp = wasm.getExport(ctor);
      if (!keptExportsSet.count(ctor)) {
        wasm.removeExport(exp->name);
      } else {
        // We are keeping around the export, which should now refer to an
        // empty function since calling the export should do nothing.
        auto* func = wasm.getFunction(exp->value);
        auto copyName = Names::getValidFunctionName(wasm, func->name);
        auto* copyFunc = ModuleUtils::copyFunction(func, wasm, copyName);
        if (func->getResults() == Type::none) {
          copyFunc->body = Builder(wasm).makeNop();
        } else {
          copyFunc->body = interface.getSerialization(*outcome);
        }
        wasm.getExport(exp->name)->value = copyName;
      }
    }
  } catch (FailToEvalException& fail) {
    // that's it, we failed to even create the instance
    std::cout << "  ...stopping since could not create module instance: "
              << fail.why << "\n";
    return;
  }
}

static bool canEval(Module& wasm) {
  // Check if we can flatten memory. We need to do so currently because of how
  // we assume memory is simple and flat. TODO
  if (!MemoryUtils::flatten(wasm)) {
    std::cout << "  ...stopping since could not flatten memory\n";
    return false;
  }
  return true;
}

} // anonymous namespace

//
// main
//

int main(int argc, const char* argv[]) {
  Name entry;
  std::vector<std::string> passes;
  bool emitBinary = true;
  bool debugInfo = false;
  String::Split ctors;
  String::Split keptExports;

  const std::string WasmCtorEvalOption = "wasm-ctor-eval options";

  ToolOptions options("wasm-ctor-eval", "Execute code at compile time");
  options
    .add("--output",
         "-o",
         "Output file (stdout if not specified)",
         WasmCtorEvalOption,
         Options::Arguments::One,
         [](Options* o, const std::string& argument) {
           o->extra["output"] = argument;
           Colors::setEnabled(false);
         })
    .add("--emit-text",
         "-S",
         "Emit text instead of binary for the output file",
         WasmCtorEvalOption,
         Options::Arguments::Zero,
         [&](Options* o, const std::string& argument) { emitBinary = false; })
    .add("--debuginfo",
         "-g",
         "Emit names section and debug info",
         WasmCtorEvalOption,
         Options::Arguments::Zero,
         [&](Options* o, const std::string& arguments) { debugInfo = true; })
    .add("--ctors",
         "-c",
         "Comma-separated list of global constructor functions to evaluate",
         WasmCtorEvalOption,
         Options::Arguments::One,
         [&](Options* o, const std::string& argument) {
           ctors = String::Split(argument, ",");
         })
    .add(
      "--kept-exports",
      "-ke",
      "Comma-separated list of ctors whose exports we keep around even if we "
      "eval those ctors",
      WasmCtorEvalOption,
      Options::Arguments::One,
      [&](Options* o, const std::string& argument) {
        keptExports = String::Split(argument, ",");
      })
    .add("--ignore-external-input",
         "-ipi",
         "Assumes no env vars are to be read, stdin is empty, etc.",
         WasmCtorEvalOption,
         Options::Arguments::Zero,
         [&](Options* o, const std::string& argument) {
           ignoreExternalInput = true;
         })
    .add_positional("INFILE",
                    Options::Arguments::One,
                    [](Options* o, const std::string& argument) {
                      o->extra["infile"] = argument;
                    });
  options.parse(argc, argv);

  auto input(read_file<std::string>(options.extra["infile"], Flags::Text));

  Module wasm;
  options.applyFeatures(wasm);

  {
    if (options.debug) {
      std::cout << "reading...\n";
    }
    ModuleReader reader;
    try {
      reader.read(options.extra["infile"], wasm);
    } catch (ParseException& p) {
      p.dump(std::cout);
      Fatal() << "error in parsing input";
    }
  }

  if (!WasmValidator().validate(wasm)) {
    std::cout << wasm << '\n';
    Fatal() << "error in validating input";
  }

  if (canEval(wasm)) {
    evalCtors(wasm, ctors, keptExports);

    // Do some useful optimizations after the evalling
    {
      PassRunner passRunner(&wasm);
      passRunner.add("memory-packing"); // we flattened it, so re-optimize
      // TODO: just do -Os for the one function
      passRunner.add("remove-unused-names");
      passRunner.add("dce");
      passRunner.add("merge-blocks");
      passRunner.add("vacuum");
      passRunner.add("remove-unused-module-elements");
      passRunner.run();
    }
  }

  if (options.extra.count("output") > 0) {
    if (options.debug) {
      std::cout << "writing..." << std::endl;
    }
    ModuleWriter writer;
    writer.setBinary(emitBinary);
    writer.setDebugInfo(debugInfo);
    writer.write(wasm, options.extra["output"]);
  }
}
