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

//
// WebAssembly-to-JS code translator. Converts wasm functions into
// valid JavaScript (with a somewhat asm.js-ish flavor).
//

#ifndef wasm_wasm2js_h
#define wasm_wasm2js_h

#include <cmath>
#include <numeric>

#include "abi/js.h"
#include "asm_v_wasm.h"
#include "asmjs/asmangle.h"
#include "asmjs/shared-constants.h"
#include "emscripten-optimizer/optimizer.h"
#include "ir/branch-utils.h"
#include "ir/effects.h"
#include "ir/element-utils.h"
#include "ir/find_all.h"
#include "ir/import-utils.h"
#include "ir/load-utils.h"
#include "ir/module-utils.h"
#include "ir/names.h"
#include "ir/table-utils.h"
#include "ir/utils.h"
#include "mixed_arena.h"
#include "passes/passes.h"
#include "support/base64.h"
#include "support/file.h"
#include "wasm-builder.h"
#include "wasm-io.h"
#include "wasm-validator.h"
#include "wasm.h"

namespace wasm {

using namespace cashew;

static IString importObject("imports");

// Appends extra to block, flattening out if extra is a block as well
void flattenAppend(Ref ast, Ref extra) {
  int index;
  if (ast[0] == BLOCK || ast[0] == TOPLEVEL) {
    index = 1;
  } else if (ast[0] == DEFUN) {
    index = 3;
  } else {
    abort();
  }
  if (extra->isArray() && extra[0] == BLOCK) {
    for (size_t i = 0; i < extra[1]->size(); i++) {
      ast[index]->push_back(extra[1][i]);
    }
  } else {
    ast[index]->push_back(extra);
  }
}

// Appends extra to a chain of sequence elements
void sequenceAppend(Ref& ast, Ref extra) {
  if (!ast.get()) {
    ast = extra;
    return;
  }
  ast = ValueBuilder::makeSeq(ast, extra);
}

bool isTableExported(Module& wasm) {
  if (wasm.tables.empty() || wasm.tables[0]->imported()) {
    return false;
  }
  for (auto& ex : wasm.exports) {
    if (ex->kind == ExternalKind::Table && ex->value == wasm.tables[0]->name) {
      return true;
    }
  }
  return false;
}

bool hasActiveSegments(Module& wasm) {
  for (Index i = 0; i < wasm.dataSegments.size(); i++) {
    if (!wasm.dataSegments[i]->isPassive) {
      return true;
    }
  }
  return false;
}

bool needsBufferView(Module& wasm) {
  if (wasm.memories.empty()) {
    return false;
  }

  // If there are any active segments, initActiveSegments needs access
  // to bufferView.
  if (hasActiveSegments(wasm)) {
    return true;
  }

  // The special support functions are emitted as part of the JS glue, if we
  // need them.
  bool need = false;
  ModuleUtils::iterImportedFunctions(wasm, [&](Function* import) {
    if (ABI::wasm2js::isHelper(import->base)) {
      need = true;
    }
  });
  return need;
}

IString stringToIString(std::string str) { return IString(str.c_str(), false); }

// Used when taking a wasm name and generating a JS identifier. Each scope here
// is used to ensure that all names have a unique name but the same wasm name
// within a scope always resolves to the same symbol.
//
// Export: Export names
// Top: The main scope which contains functions and globals
// Local: Local variables in a function.
// Label: Label identifiers in a function
enum class NameScope {
  Export,
  Top,
  Local,
  Label,
  Max,
};

//
// Wasm2JSBuilder - converts a WebAssembly module's functions into JS
//
// Wasm-to-JS is tricky because wasm doesn't distinguish
// statements and expressions, or in other words, things like `break` and `if`
// can show up in places where JS can't handle them, like inside an a loop's
// condition check. For that reason we use flat IR here.
// We do optimize it later, to allow some nesting, but we avoid
// non-JS-compatible nesting like block return values control flow in an if
// condition, etc.
//

class Wasm2JSBuilder {
public:
  struct Flags {
    // see wasm2js.cpp for details
    bool debug = false;
    bool pedantic = false;
    bool allowAsserts = false;
    bool emscripten = false;
    bool deterministic = false;
    std::string symbolsFile;
  };

  // Map data segment names to indices.
  std::unordered_map<Name, Index> dataIndices;

  Wasm2JSBuilder(Flags f, PassOptions options_) : flags(f), options(options_) {
    // We don't try to model wasm's trapping precisely - if we did, each load
    // and store would need to do a check. Given that, we can just ignore
    // implicit traps like those when optimizing. (When not optimizing, it's
    // nice to see codegen that matches wasm more precisely.)
    // It is also important to prevent the optimizer from adding new things that
    // require additional lowering, as we could hit a cycle.
    if (options.optimizeLevel > 0) {
      options.ignoreImplicitTraps = true;
      options.targetJS = true;
    }
  }

  Ref processWasm(Module* wasm, Name funcName = ASM_FUNC);
  Ref processFunction(Module* wasm, Function* func, bool standalone = false);
  Ref processStandaloneFunction(Module* wasm, Function* func) {
    return processFunction(wasm, func, true);
  }

  // The second pass on an expression: process it fully, generating
  // JS
  Ref processExpression(Expression* curr,
                        Module* m,
                        Function* func = nullptr,
                        bool standalone = false);

  Index getDataIndex(Name segment) {
    auto it = dataIndices.find(segment);
    assert(it != dataIndices.end());
    return it->second;
  }

  // Get a temp var.
  IString getTemp(Type type, Function* func) {
    IString ret;
    // TODO: handle tuples
    assert(!type.isTuple() && "Unexpected tuple type");
    if (frees[type].size() > 0) {
      ret = frees[type].back();
      frees[type].pop_back();
    } else {
      auto index = temps[type]++;
      ret = IString((std::string("wasm2js_") + type.toString() + "$" +
                     std::to_string(index))
                      .c_str(),
                    false);
      ret = fromName(ret, NameScope::Local);
    }
    if (func->localIndices.find(ret) == func->localIndices.end()) {
      Builder::addVar(func, ret, type);
    }
    return ret;
  }

  // Free a temp var.
  void freeTemp(Type type, IString temp) {
    // TODO: handle tuples
    assert(!type.isTuple() && "Unexpected tuple type");
    frees[type].push_back(temp);
  }

  // Generates a mangled name from `name` within the specified scope.
  //
  // The goal of this function is to ensure that all identifiers in JS ar
  // unique. Otherwise there can be clashes with locals and functions and cause
  // unwanted name shadowing.
  //
  // The returned string from this function is constant for a particular `name`
  // within a `scope`. Or in other words, the same `name` and `scope` pair will
  // always return the same result. If `scope` changes, however, the return
  // value may differ even if the same `name` is passed in.
  IString fromName(Name name, NameScope scope) {
    // TODO: checking names do not collide after mangling

    // First up check our cached of mangled names to avoid doing extra work
    // below
    auto& map = wasmNameToMangledName[(int)scope];
    auto it = map.find(name.str.data());
    if (it != map.end()) {
      return it->second;
    }
    // The mangled names in our scope.
    auto& scopeMangledNames = mangledNames[(int)scope];
    // In some cases (see below) we need to also check the Top scope.
    auto& topMangledNames = mangledNames[int(NameScope::Top)];

    // This is the first time we've seen the `name` and `scope` pair. Generate a
    // globally unique name based on `name` and then register that in our cache
    // and return it.
    //
    // Identifiers here generated are of the form `${name}_${n}` where `_${n}`
    // is omitted if `n==0` and otherwise `n` is just looped over to find the
    // next unused identifier.
    IString ret;
    for (int i = 0;; i++) {
      std::ostringstream out;
      out << name;
      if (i > 0) {
        out << "_" << i;
      }
      auto mangled = asmangle(out.str());
      ret = stringToIString(mangled);
      if (scopeMangledNames.count(ret)) {
        // When export names collide things may be confusing, as this is
        // observable externally by the person using the JS. Report a warning.
        if (scope == NameScope::Export) {
          std::cerr << "wasm2js: warning: export names colliding: " << mangled
                    << '\n';
        }
        continue;
      }
      // The Local scope is special: a Local name must not collide with a Top
      // name, as they are in a single namespace in JS and can conflict:
      //
      // function foo(bar) {
      //   var bar = 0;
      // }
      // function bar() { ..
      if (scope == NameScope::Local && topMangledNames.count(ret)) {
        continue;
      }
      // We found a good name, use it.
      scopeMangledNames.insert(ret);
      map[name.str.data()] = ret;
      return ret;
    }
  }

private:
  Flags flags;
  PassOptions options;

  // How many temp vars we need for each type (type => num).
  std::unordered_map<Type, Index> temps;
  // Which temp vars are currently free to use for each type (type => freelist).
  std::unordered_map<Type, std::vector<IString>> frees;

  // Mangled names cache by interned names.
  // Utilizes the usually reused underlying cstring's pointer as the key.
  std::unordered_map<const void*, IString>
    wasmNameToMangledName[(int)NameScope::Max];
  // Set of all mangled names in each scope.
  std::unordered_set<IString> mangledNames[(int)NameScope::Max];
  std::unordered_set<IString> seenModuleImports;

  // If a function is callable from outside, we'll need to cast the inputs
  // and our return value. Otherwise, internally, casts are only needed
  // on operations.
  std::unordered_set<Name> functionsCallableFromOutside;

  void ensureModuleVar(Ref ast, const Importable& imp);
  Ref getImportName(const Importable& imp);
  void addBasics(Ref ast, Module* wasm);
  void addFunctionImport(Ref ast, Function* import);
  void addGlobalImport(Ref ast, Global* import);
  void addTable(Ref ast, Module* wasm);
  void addStart(Ref ast, Module* wasm);
  void addExports(Ref ast, Module* wasm);
  void addGlobal(Ref ast, Global* global, Module* module);
  void addMemoryFuncs(Ref ast, Module* wasm);
  void addMemoryGrowFunc(Ref ast, Module* wasm);

  Wasm2JSBuilder() = delete;
  Wasm2JSBuilder(const Wasm2JSBuilder&) = delete;
  Wasm2JSBuilder& operator=(const Wasm2JSBuilder&) = delete;
};

Ref Wasm2JSBuilder::processWasm(Module* wasm, Name funcName) {
  // Scan the wasm for important things.
  for (auto& exp : wasm->exports) {
    if (exp->kind == ExternalKind::Function) {
      functionsCallableFromOutside.insert(exp->value);
    }
  }
  ElementUtils::iterAllElementFunctionNames(
    wasm, [&](Name name) { functionsCallableFromOutside.insert(name); });

  // Collect passive data segment indices.
  for (Index i = 0; i < wasm->dataSegments.size(); ++i) {
    dataIndices[wasm->dataSegments[i]->name] = i;
  }

  // Ensure the scratch memory helpers.
  // If later on they aren't needed, we'll clean them up.
  ABI::wasm2js::ensureHelpers(wasm);

  // Process the code, and optimize if relevant.
  // First, do the lowering to a JS-friendly subset.
  {
    PassRunner runner(wasm, options);
    runner.add(std::make_unique<AutoDrop>());
    // TODO: only legalize if necessary - emscripten would already do so, and
    //       likely other toolchains. but spec test suite needs that.
    runner.add("legalize-js-interface");
    // Before lowering non-JS operations we can optimize some instructions which
    // may simplify next passes
    if (options.optimizeLevel > 0) {
      runner.add("optimize-for-js");
    }
    // First up remove as many non-JS operations we can, including things like
    // 64-bit integer multiplication/division, `f32.nearest` instructions, etc.
    // This may inject intrinsics which use i64 so it needs to be run before the
    // i64-to-i32 lowering pass.
    runner.add("remove-non-js-ops");
    // Currently the i64-to-32 lowering pass requires that `flatten` be run
    // before it to produce correct code. For some more details about this see
    // #1480
    runner.add("flatten");
    runner.add("i64-to-i32-lowering");
    runner.add("alignment-lowering");
    // Next, optimize that as best we can. This should not generate
    // non-JS-friendly things.
    if (options.optimizeLevel > 0) {
      // It is especially import to propagate constants after the lowering.
      // However, this can be a slow operation, especially after flattening;
      // some local simplification helps.
      if (options.optimizeLevel >= 3 || options.shrinkLevel >= 1) {
        runner.add("simplify-locals-nonesting");
        runner.add("precompute-propagate");
        // Avoiding reinterpretation is helped by propagation. We also run
        // it later down as default optimizations help as well.
        runner.add("avoid-reinterprets");
      }
      runner.addDefaultOptimizationPasses();
      runner.add("avoid-reinterprets");
    }
    // Finally, get the code into the flat form we need for wasm2js itself, and
    // optimize that a little in a way that keeps that property.
    runner.add("flatten");
    // Regardless of optimization level, run some simple optimizations to undo
    // some of the effects of flattening.
    runner.add("simplify-locals-notee-nostructure");
    // Some operations can be very slow if we didn't run full optimizations
    // earlier, so don't run them automatically.
    if (options.optimizeLevel > 0) {
      runner.add("remove-unused-names");
      runner.add("merge-blocks");
      runner.add("reorder-locals");
      runner.add("coalesce-locals");
    }
    runner.add("reorder-locals");
    runner.add("vacuum");
    runner.add("remove-unused-module-elements");
    // DCE at the end to make sure all IR nodes have valid types for conversion
    // to JS, and not unreachable.
    runner.add("dce");
    runner.setDebug(flags.debug);
    runner.run();
  }

  if (flags.symbolsFile.size() > 0) {
    Output out(flags.symbolsFile, wasm::Flags::Text);
    Index i = 0;
    for (auto& func : wasm->functions) {
      out.getStream() << i++ << ':' << func->name.str << '\n';
    }
  }

#ifndef NDEBUG
  if (!WasmValidator().validate(*wasm)) {
    std::cout << *wasm << '\n';
    Fatal() << "error in validating wasm2js output";
  }
#endif

  Ref ret = ValueBuilder::makeToplevel();
  Ref asmFunc = ValueBuilder::makeFunction(funcName);
  ret[1]->push_back(asmFunc);
  ValueBuilder::appendArgumentToFunction(asmFunc, importObject);

  // add memory import
  if (!wasm->memories.empty()) {
    if (wasm->memories[0]->imported()) {
      ensureModuleVar(asmFunc[3], *wasm->memories[0]);

      // find memory and buffer in imports
      Ref theVar = ValueBuilder::makeVar();
      asmFunc[3]->push_back(theVar);
      ValueBuilder::appendToVar(
        theVar, "memory", getImportName(*wasm->memories[0]));

      // Assign `buffer = memory.buffer`
      Ref buf = ValueBuilder::makeVar();
      asmFunc[3]->push_back(buf);
      ValueBuilder::appendToVar(
        buf,
        BUFFER,
        ValueBuilder::makeDot(ValueBuilder::makeName("memory"),
                              ValueBuilder::makeName("buffer")));

      // If memory is growable, override the imported memory's grow method to
      // ensure so that when grow is called from the output it works as expected
      if (wasm->memories[0]->max > wasm->memories[0]->initial) {
        asmFunc[3]->push_back(
          ValueBuilder::makeStatement(ValueBuilder::makeBinary(
            ValueBuilder::makeDot(ValueBuilder::makeName("memory"),
                                  ValueBuilder::makeName("grow")),
            SET,
            ValueBuilder::makeName(WASM_MEMORY_GROW))));
      }
    } else {
      Ref theVar = ValueBuilder::makeVar();
      asmFunc[3]->push_back(theVar);
      ValueBuilder::appendToVar(
        theVar,
        BUFFER,
        ValueBuilder::makeNew(ValueBuilder::makeCall(
          ValueBuilder::makeName("ArrayBuffer"),
          ValueBuilder::makeInt(Address::address32_t(
            wasm->memories[0]->initial.addr * Memory::kPageSize)))));
    }
  }

  // add imported tables
  ModuleUtils::iterImportedTables(*wasm, [&](Table* table) {
    ensureModuleVar(asmFunc[3], *table);
    Ref theVar = ValueBuilder::makeVar();
    asmFunc[3]->push_back(theVar);
    ValueBuilder::appendToVar(theVar, FUNCTION_TABLE, getImportName(*table));
  });

  // create heaps, etc
  addBasics(asmFunc[3], wasm);
  ModuleUtils::iterImportedFunctions(
    *wasm, [&](Function* import) { addFunctionImport(asmFunc[3], import); });
  ModuleUtils::iterImportedGlobals(
    *wasm, [&](Global* import) { addGlobalImport(asmFunc[3], import); });

  // Note the names of functions. We need to do this here as when generating
  // mangled local names we need them not to conflict with these (see fromName)
  // so we can't wait until we parse each function to note its name.
  for (auto& f : wasm->functions) {
    fromName(f->name, NameScope::Top);
  }

  // globals
  bool generateFetchHighBits = false;
  ModuleUtils::iterDefinedGlobals(*wasm, [&](Global* global) {
    addGlobal(asmFunc[3], global, wasm);
    if (flags.allowAsserts && global->name == INT64_TO_32_HIGH_BITS) {
      generateFetchHighBits = true;
    }
  });
  if (flags.emscripten) {
    asmFunc[3]->push_back(
      ValueBuilder::makeName("// EMSCRIPTEN_START_FUNCS\n"));
  }
  // functions
  ModuleUtils::iterDefinedFunctions(*wasm, [&](Function* func) {
    asmFunc[3]->push_back(processFunction(wasm, func));
  });
  if (generateFetchHighBits) {
    Builder builder(*wasm);
    asmFunc[3]->push_back(
      processFunction(wasm,
                      wasm->addFunction(builder.makeFunction(
                        WASM_FETCH_HIGH_BITS,
                        Signature(Type::none, Type::i32),
                        {},
                        builder.makeReturn(builder.makeGlobalGet(
                          INT64_TO_32_HIGH_BITS, Type::i32))))));
    auto e = new Export();
    e->name = WASM_FETCH_HIGH_BITS;
    e->value = WASM_FETCH_HIGH_BITS;
    e->kind = ExternalKind::Function;
    wasm->addExport(e);
  }
  if (flags.emscripten) {
    asmFunc[3]->push_back(ValueBuilder::makeName("// EMSCRIPTEN_END_FUNCS\n"));
  }

  if (needsBufferView(*wasm)) {
    asmFunc[3]->push_back(
      ValueBuilder::makeBinary(ValueBuilder::makeName("bufferView"),
                               SET,
                               ValueBuilder::makeName(HEAPU8)));
  }
  if (hasActiveSegments(*wasm)) {
    asmFunc[3]->push_back(
      ValueBuilder::makeCall(ValueBuilder::makeName("initActiveSegments"),
                             ValueBuilder::makeName(importObject)));
  }

  addTable(asmFunc[3], wasm);
  addStart(asmFunc[3], wasm);
  addExports(asmFunc[3], wasm);
  return ret;
}

void Wasm2JSBuilder::addBasics(Ref ast, Module* wasm) {
  if (!wasm->memories.empty()) {
    // heaps, var HEAP8 = new global.Int8Array(buffer); etc
    auto addHeap = [&](IString name, IString view) {
      Ref theVar = ValueBuilder::makeVar();
      ast->push_back(theVar);
      ValueBuilder::appendToVar(theVar,
                                name,
                                ValueBuilder::makeNew(ValueBuilder::makeCall(
                                  view, ValueBuilder::makeName(BUFFER))));
    };
    addHeap(HEAP8, INT8ARRAY);
    addHeap(HEAP16, INT16ARRAY);
    addHeap(HEAP32, INT32ARRAY);
    addHeap(HEAPU8, UINT8ARRAY);
    addHeap(HEAPU16, UINT16ARRAY);
    addHeap(HEAPU32, UINT32ARRAY);
    addHeap(HEAPF32, FLOAT32ARRAY);
    addHeap(HEAPF64, FLOAT64ARRAY);
  }
  // core asm.js imports
  auto addMath = [&](IString name, IString base) {
    Ref theVar = ValueBuilder::makeVar();
    ast->push_back(theVar);
    ValueBuilder::appendToVar(
      theVar, name, ValueBuilder::makeDot(ValueBuilder::makeName(MATH), base));
  };
  addMath(MATH_IMUL, IMUL);
  addMath(MATH_FROUND, FROUND);
  addMath(MATH_ABS, ABS);
  addMath(MATH_CLZ32, CLZ32);
  addMath(MATH_MIN, MIN);
  addMath(MATH_MAX, MAX);
  addMath(MATH_FLOOR, FLOOR);
  addMath(MATH_CEIL, CEIL);
  addMath(MATH_TRUNC, TRUNC);
  addMath(MATH_SQRT, SQRT);
}

static bool needsQuoting(Name name) {
  auto mangled = asmangle(name.toString());
  return mangled != name.str;
}

void Wasm2JSBuilder::ensureModuleVar(Ref ast, const Importable& imp) {
  if (seenModuleImports.count(imp.module) > 0) {
    return;
  }
  Ref theVar = ValueBuilder::makeVar();
  ast->push_back(theVar);
  Ref rhs;
  if (needsQuoting(imp.module)) {
    rhs = ValueBuilder::makeSub(ValueBuilder::makeName(importObject),
                                ValueBuilder::makeString(imp.module));
  } else {
    rhs = ValueBuilder::makeDot(ValueBuilder::makeName(importObject),
                                ValueBuilder::makeName(imp.module));
  }

  ValueBuilder::appendToVar(theVar, fromName(imp.module, NameScope::Top), rhs);
  seenModuleImports.insert(imp.module);
}

Ref Wasm2JSBuilder::getImportName(const Importable& imp) {
  if (needsQuoting(imp.base)) {
    return ValueBuilder::makeSub(
      ValueBuilder::makeName(fromName(imp.module, NameScope::Top)),
      ValueBuilder::makeString(imp.base));
  } else {
    return ValueBuilder::makeDot(
      ValueBuilder::makeName(fromName(imp.module, NameScope::Top)),
      ValueBuilder::makeName(imp.base));
  }
}

void Wasm2JSBuilder::addFunctionImport(Ref ast, Function* import) {
  // The scratch memory helpers are emitted in the glue, see code and comments
  // below.
  if (ABI::wasm2js::isHelper(import->base)) {
    return;
  }
  ensureModuleVar(ast, *import);
  Ref theVar = ValueBuilder::makeVar();
  ast->push_back(theVar);
  ValueBuilder::appendToVar(
    theVar, fromName(import->name, NameScope::Top), getImportName(*import));
}

void Wasm2JSBuilder::addGlobalImport(Ref ast, Global* import) {
  ensureModuleVar(ast, *import);
  Ref theVar = ValueBuilder::makeVar();
  ast->push_back(theVar);
  Ref value = getImportName(*import);
  if (import->type == Type::i32) {
    value = makeJsCoercion(value, JS_INT);
  }
  ValueBuilder::appendToVar(
    theVar, fromName(import->name, NameScope::Top), value);
}

void Wasm2JSBuilder::addTable(Ref ast, Module* wasm) {
  if (wasm->tables.size() == 0) {
    return;
  }

  bool perElementInit = false;

  // Emit a simple flat table as a JS array literal. Otherwise,
  // emit assignments separately for each index.
  Ref theArray = ValueBuilder::makeArray();
  for (auto& table : wasm->tables) {
    if (!table->type.isFunction()) {
      Fatal() << "wasm2js doesn't support non-function tables\n";
    }

    if (!table->imported()) {
      TableUtils::FlatTable flat(*wasm, *table);
      if (flat.valid) {
        for (auto& name : flat.names) {
          if (name.is()) {
            name = fromName(name, NameScope::Top);
          } else {
            name = NULL_;
          }
          ValueBuilder::appendToArray(theArray, ValueBuilder::makeName(name));
        }
      } else {
        perElementInit = true;
        Ref initial =
          ValueBuilder::makeInt(Address::address32_t(table->initial.addr));
        theArray = ValueBuilder::makeNew(
          ValueBuilder::makeCall(IString("Array"), initial));
      }
    } else {
      perElementInit = true;
    }

    if (isTableExported(*wasm)) {
      // If the table is exported use a fake WebAssembly.Table object
      // We don't handle the case where a table is both imported and exported.
      if (table->imported()) {
        Fatal() << "wasm2js doesn't support a table that is both imported and "
                   "exported\n";
      }
      Ref theVar = ValueBuilder::makeVar();
      ast->push_back(theVar);

      Ref table = ValueBuilder::makeCall(IString("Table"), theArray);
      ValueBuilder::appendToVar(theVar, FUNCTION_TABLE, table);
    } else if (!table->imported()) {
      // Otherwise if the table is internal (neither imported not exported).
      // Just use a plain array in this case, avoiding the Table.
      Ref theVar = ValueBuilder::makeVar();
      ast->push_back(theVar);
      ValueBuilder::appendToVar(theVar, FUNCTION_TABLE, theArray);
    }

    if (perElementInit) {
      // TODO: optimize for size
      ModuleUtils::iterTableSegments(
        *wasm, table->name, [&](ElementSegment* segment) {
          auto offset = segment->offset;
          ElementUtils::iterElementSegmentFunctionNames(
            segment, [&](Name entry, Index i) {
              Ref index;
              if (auto* c = offset->dynCast<Const>()) {
                index = ValueBuilder::makeInt(c->value.geti32() + i);
              } else if (auto* get = offset->dynCast<GlobalGet>()) {
                index = ValueBuilder::makeBinary(
                  ValueBuilder::makeName(
                    stringToIString(asmangle(get->name.toString()))),
                  PLUS,
                  ValueBuilder::makeNum(i));
              } else {
                WASM_UNREACHABLE("unexpected expr type");
              }
              ast->push_back(
                ValueBuilder::makeStatement(ValueBuilder::makeBinary(
                  ValueBuilder::makeSub(ValueBuilder::makeName(FUNCTION_TABLE),
                                        index),
                  SET,
                  ValueBuilder::makeName(fromName(entry, NameScope::Top)))));
            });
        });
    }
  }
}

void Wasm2JSBuilder::addStart(Ref ast, Module* wasm) {
  if (wasm->start.is()) {
    ast->push_back(
      ValueBuilder::makeCall(fromName(wasm->start, NameScope::Top)));
  }
}

void Wasm2JSBuilder::addExports(Ref ast, Module* wasm) {
  Ref exports = ValueBuilder::makeObject();
  for (auto& export_ : wasm->exports) {
    switch (export_->kind) {
      case ExternalKind::Function: {
        ValueBuilder::appendToObjectWithQuotes(
          exports,
          fromName(export_->name, NameScope::Export),
          ValueBuilder::makeName(fromName(export_->value, NameScope::Top)));
        break;
      }
      case ExternalKind::Memory: {
        Ref descs = ValueBuilder::makeObject();
        Ref growDesc = ValueBuilder::makeObject();
        ValueBuilder::appendToObjectWithQuotes(
          descs, IString("grow"), growDesc);
        if (wasm->memories[0]->max > wasm->memories[0]->initial) {
          ValueBuilder::appendToObjectWithQuotes(
            growDesc,
            IString("value"),
            ValueBuilder::makeName(WASM_MEMORY_GROW));
        }
        Ref bufferDesc = ValueBuilder::makeObject();
        Ref bufferGetter = ValueBuilder::makeFunction(IString(""));
        bufferGetter[3]->push_back(
          ValueBuilder::makeReturn(ValueBuilder::makeName(BUFFER)));
        ValueBuilder::appendToObjectWithQuotes(
          bufferDesc, IString("get"), bufferGetter);
        ValueBuilder::appendToObjectWithQuotes(
          descs, IString("buffer"), bufferDesc);
        Ref memory = ValueBuilder::makeCall(
          ValueBuilder::makeDot(ValueBuilder::makeName(IString("Object")),
                                IString("create")),
          ValueBuilder::makeDot(ValueBuilder::makeName(IString("Object")),
                                IString("prototype")));
        ValueBuilder::appendToCall(memory, descs);
        ValueBuilder::appendToObjectWithQuotes(
          exports, fromName(export_->name, NameScope::Export), memory);
        break;
      }
      case ExternalKind::Table: {
        ValueBuilder::appendToObjectWithQuotes(
          exports,
          fromName(export_->name, NameScope::Export),
          ValueBuilder::makeName(FUNCTION_TABLE));
        break;
      }
      case ExternalKind::Global: {
        Ref object = ValueBuilder::makeObject();

        IString identName = fromName(export_->value, NameScope::Top);

        // getter
        {
          Ref block = ValueBuilder::makeBlock();

          block[1]->push_back(
            ValueBuilder::makeReturn(ValueBuilder::makeName(identName)));

          ValueBuilder::appendToObjectAsGetter(object, IString("value"), block);
        }

        // setter
        {
          std::ostringstream buffer;
          buffer << '_' << identName;
          auto setterParam = stringToIString(buffer.str());

          auto block = ValueBuilder::makeBlock();

          block[1]->push_back(
            ValueBuilder::makeBinary(ValueBuilder::makeName(identName),
                                     SET,
                                     ValueBuilder::makeName(setterParam)));

          ValueBuilder::appendToObjectAsSetter(
            object, IString("value"), setterParam, block);
        }

        ValueBuilder::appendToObjectWithQuotes(
          exports, fromName(export_->name, NameScope::Export), object);

        break;
      }
      case ExternalKind::Tag:
      case ExternalKind::Invalid:
        Fatal() << "unsupported export type: " << export_->name << "\n";
    }
  }
  if (!wasm->memories.empty()) {
    addMemoryFuncs(ast, wasm);
  }
  ast->push_back(
    ValueBuilder::makeStatement(ValueBuilder::makeReturn(exports)));
}

void Wasm2JSBuilder::addGlobal(Ref ast, Global* global, Module* module) {
  Ref theVar = ValueBuilder::makeVar();
  ast->push_back(theVar);
  Ref init = processExpression(global->init, module);
  ValueBuilder::appendToVar(
    theVar, fromName(global->name, NameScope::Top), init);
}

Ref Wasm2JSBuilder::processFunction(Module* m,
                                    Function* func,
                                    bool standaloneFunction) {
  if (standaloneFunction) {
    // We are only printing a function, not a whole module. Prepare it for
    // translation now (if there were a module, we'd have done this for all
    // functions in parallel, earlier).
    PassRunner runner(m);
    // We only run a subset of all passes here. TODO: create a full valid module
    // for each assertion body.
    runner.add("flatten");
    runner.add("simplify-locals-notee-nostructure");
    runner.add("reorder-locals");
    runner.add("remove-unused-names");
    runner.add("vacuum");
    runner.runOnFunction(func);
  }

  // We process multiple functions from a single Wasm2JSBuilder instance, so
  // clean up the function-specific local state before each function.
  frees.clear();
  temps.clear();

  // We will be symbolically referring to all variables in the function, so make
  // sure that everything has a name and it's unique.
  Names::ensureNames(func);
  Ref ret = ValueBuilder::makeFunction(fromName(func->name, NameScope::Top));
  // arguments
  bool needCoercions = options.optimizeLevel == 0 || standaloneFunction ||
                       functionsCallableFromOutside.count(func->name);
  for (Index i = 0; i < func->getNumParams(); i++) {
    IString name = fromName(func->getLocalNameOrGeneric(i), NameScope::Local);
    ValueBuilder::appendArgumentToFunction(ret, name);
    if (needCoercions) {
      auto jsType = wasmToJsType(func->getLocalType(i));
      if (needsJsCoercion(jsType)) {
        ret[3]->push_back(ValueBuilder::makeStatement(ValueBuilder::makeBinary(
          ValueBuilder::makeName(name),
          SET,
          makeJsCoercion(ValueBuilder::makeName(name), jsType))));
      }
    }
  }
  Ref theVar = ValueBuilder::makeVar();
  size_t theVarIndex = ret[3]->size();
  ret[3]->push_back(theVar);
  // body
  flattenAppend(ret,
                processExpression(func->body, m, func, standaloneFunction));
  // vars, including new temp vars
  for (Index i = func->getVarIndexBase(); i < func->getNumLocals(); i++) {
    ValueBuilder::appendToVar(
      theVar,
      fromName(func->getLocalNameOrGeneric(i), NameScope::Local),
      makeJsCoercedZero(wasmToJsType(func->getLocalType(i))));
  }
  if (theVar[1]->size() == 0) {
    ret[3]->splice(theVarIndex, 1);
  }
  return ret;
}

Ref Wasm2JSBuilder::processExpression(Expression* curr,
                                      Module* m,
                                      Function* func,
                                      bool standaloneFunction) {
  // Switches are tricky to handle - in wasm they often come with
  // massively-nested "towers" of blocks, which if naively translated
  // to JS may exceed parse recursion limits of VMs. Therefore even when
  // not optimizing we work hard to emit minimal and minimally-nested
  // switches.
  // We do so by pre-scanning for br_tables and noting which of their
  // targets can be hoisted up into them, e.g.
  //
  // (block $a
  //  (block $b
  //   (block $c
  //    (block $d
  //     (block $e
  //      (br_table $a $b $c $d $e (..))
  //     )
  //     ;; code X (for block $e)
  //     ;; implicit fallthrough - can be done in the switch too
  //    )
  //    ;; code Y
  //    (br $c) ;; branch which is identical to a fallthrough
  //   )
  //   ;; code Z
  //   (br $a) ;; skip some blocks - can't do this in a switch!
  //  )
  //  ;; code W
  // )
  //
  // Every branch we see is a potential hazard - all targets must not
  // be optimized into the switch, since they must be reached normally,
  // unless they happen to be right after us, in which case it's just
  // a fallthrough anyhow.
  struct SwitchProcessor : public ExpressionStackWalker<SwitchProcessor> {
    // A list of expressions we don't need to emit, as we are handling them
    // in another way.
    std::set<Expression*> unneededExpressions;

    struct SwitchCase {
      Name target;
      std::vector<Expression*> code;
      SwitchCase(Name target) : target(target) {}
    };

    // The switch cases we found that we can hoist up.
    std::map<Switch*, std::vector<SwitchCase>> hoistedSwitchCases;

    void visitSwitch(Switch* brTable) {
      Index i = expressionStack.size() - 1;
      assert(expressionStack[i] == brTable);
      // A set of names we must stop at, since we've seen branches to them.
      std::set<Name> namesBranchedTo;
      while (1) {
        // Stop if we are at the top level.
        if (i == 0) {
          break;
        }
        i--;
        auto* child = expressionStack[i + 1];
        auto* curr = expressionStack[i];
        // Stop if the current node is not a block with the child in the
        // first position, i.e., the classic switch pattern.
        auto* block = curr->dynCast<Block>();
        if (!block || block->list[0] != child) {
          break;
        }
        // Ignore the case of a name-less block for simplicity (merge-blocks
        // would have removed it).
        if (!block->name.is()) {
          break;
        }
        // If we have already seen this block, stop here.
        if (unneededExpressions.count(block)) {
          // XXX FIXME we should probably abort the entire optimization
          break;
        }
        auto& list = block->list;
        if (child == brTable) {
          // Nothing more to do here (we can in fact skip any code til
          // the parent block).
          continue;
        }
        // Ok, we are a block and our child in the first position is a
        // block, and the neither is branched to - unless maybe the child
        // branches to the parent, check that. Note how we treat the
        // final element which may be a break that is a fallthrough.
        Expression* unneededBr = nullptr;
        for (Index j = 1; j < list.size(); j++) {
          auto* item = list[j];
          auto newBranches = BranchUtils::getExitingBranches(item);
          if (auto* br = item->dynCast<Break>()) {
            if (j == list.size() - 1) {
              if (!br->condition && br->name == block->name) {
                // This is a natural, unnecessary-to-emit fallthrough.
                unneededBr = br;
                break;
              }
            }
          }
          namesBranchedTo.insert(newBranches.begin(), newBranches.end());
        }
        if (namesBranchedTo.count(block->name)) {
          break;
        }
        // We can move code after the child (reached by branching on the
        // child) into the switch.
        auto* childBlock = child->cast<Block>();
        hoistedSwitchCases[brTable].emplace_back(childBlock->name);
        SwitchCase& case_ = hoistedSwitchCases[brTable].back();
        for (Index j = 1; j < list.size(); j++) {
          auto* item = list[j];
          if (item != unneededBr) {
            case_.code.push_back(item);
          }
        }
        list.resize(1);
        // Finally, mark the block as unneeded outside the switch.
        unneededExpressions.insert(childBlock);
      }
    }
  };

  struct ExpressionProcessor
    : public OverriddenVisitor<ExpressionProcessor, Ref> {
    Wasm2JSBuilder* parent;
    IString result; // TODO: remove
    Function* func;
    Module* module;
    bool standaloneFunction;

    SwitchProcessor switchProcessor;

    ExpressionProcessor(Wasm2JSBuilder* parent,
                        Module* m,
                        Function* func,
                        bool standaloneFunction)
      : parent(parent), func(func), module(m),
        standaloneFunction(standaloneFunction) {}

    Ref process(Expression* curr) {
      switchProcessor.walk(curr);
      return visit(curr, NO_RESULT);
    }

    // A scoped temporary variable.
    struct ScopedTemp {
      Wasm2JSBuilder* parent;
      Type type;
      IString temp; // TODO: switch to indexes; avoid names
      bool needFree;
      // @param possible if provided, this is a variable we can use as our temp.
      //                 it has already been allocated in a higher scope, and we
      //                 can just assign to it as our result is going there
      //                 anyhow.
      ScopedTemp(Type type,
                 Wasm2JSBuilder* parent,
                 Function* func,
                 IString possible = NO_RESULT)
        : parent(parent), type(type) {
        assert(possible != EXPRESSION_RESULT);
        if (possible == NO_RESULT) {
          temp = parent->getTemp(type, func);
          needFree = true;
        } else {
          temp = possible;
          needFree = false;
        }
      }
      ~ScopedTemp() {
        if (needFree) {
          parent->freeTemp(type, temp);
        }
      }

      IString getName() { return temp; }
      Ref getAstName() { return ValueBuilder::makeName(temp); }
    };

    Ref visit(Expression* curr, IString nextResult) {
      IString old = result;
      result = nextResult;
      Ref ret = OverriddenVisitor::visit(curr);
      // keep it consistent for the rest of this frame, which may call visit on
      // multiple children
      result = old;
      return ret;
    }

    Ref visit(Expression* curr, ScopedTemp& temp) {
      return visit(curr, temp.temp);
    }

    Ref visitAndAssign(Expression* curr, IString result) {
      assert(result != NO_RESULT);
      Ref ret = visit(curr, result);
      return ValueBuilder::makeStatement(
        ValueBuilder::makeBinary(ValueBuilder::makeName(result), SET, ret));
    }

    Ref visitAndAssign(Expression* curr, ScopedTemp& temp) {
      return visitAndAssign(curr, temp.getName());
    }

    // Expressions with control flow turn into a block, which we must
    // then handle, even if we are an expression.
    bool isBlock(Ref ast) { return !!ast && ast->isArray() && ast[0] == BLOCK; }

    Ref blockify(Ref ast) {
      if (isBlock(ast)) {
        return ast;
      }
      Ref ret = ValueBuilder::makeBlock();
      ret[1]->push_back(ValueBuilder::makeStatement(ast));
      return ret;
    }

    // Breaks to the top of a loop should be emitted as continues, to that
    // loop's main label
    std::unordered_set<Name> continueLabels;

    IString fromName(Name name, NameScope scope) {
      return parent->fromName(name, scope);
    }

    // Visitors

    Ref visitBlock(Block* curr) {
      if (switchProcessor.unneededExpressions.count(curr)) {
        // We have had our tail hoisted into a switch that is nested in our
        // first position, so we don't need to emit that code again, or
        // ourselves in fact.
        return visit(curr->list[0], NO_RESULT);
      }
      Ref ret = ValueBuilder::makeBlock();
      size_t size = curr->list.size();
      for (size_t i = 0; i < size; i++) {
        flattenAppend(
          ret, ValueBuilder::makeStatement(visit(curr->list[i], NO_RESULT)));
      }
      if (curr->name.is()) {
        ret =
          ValueBuilder::makeLabel(fromName(curr->name, NameScope::Label), ret);
      }
      return ret;
    }

    Ref visitIf(If* curr) {
      Ref condition = visit(curr->condition, EXPRESSION_RESULT);
      Ref ifTrue = visit(curr->ifTrue, NO_RESULT);
      Ref ifFalse;
      if (curr->ifFalse) {
        ifFalse = visit(curr->ifFalse, NO_RESULT);
      }
      return ValueBuilder::makeIf(condition, ifTrue, ifFalse); // simple if
    }

    Ref visitLoop(Loop* curr) {
      Name asmLabel = curr->name;
      continueLabels.insert(asmLabel);
      Ref body = visit(curr->body, result);
      // if we can reach the end of the block, we must leave the while (1) loop
      if (curr->body->type != Type::unreachable) {
        assert(curr->body->type == Type::none); // flat IR
        body = blockify(body);
        flattenAppend(
          body, ValueBuilder::makeBreak(fromName(asmLabel, NameScope::Label)));
      }
      Ref ret = ValueBuilder::makeWhile(ValueBuilder::makeInt(1), body);
      return ValueBuilder::makeLabel(fromName(asmLabel, NameScope::Label), ret);
    }

    Ref makeBreakOrContinue(Name name) {
      if (continueLabels.count(name)) {
        return ValueBuilder::makeContinue(fromName(name, NameScope::Label));
      } else {
        return ValueBuilder::makeBreak(fromName(name, NameScope::Label));
      }
    }

    Ref visitBreak(Break* curr) {
      if (curr->condition) {
        // we need an equivalent to an if here, so use that code
        Break fakeBreak = *curr;
        fakeBreak.condition = nullptr;
        If fakeIf;
        fakeIf.condition = curr->condition;
        fakeIf.ifTrue = &fakeBreak;
        return visit(&fakeIf, result);
      }
      return makeBreakOrContinue(curr->name);
    }

    Expression* defaultBody = nullptr; // default must be last in asm.js

    Ref visitSwitch(Switch* curr) {
      // Even without optimizations, we work hard here to emit minimal and
      // especially minimally-nested code, since otherwise we may get block
      // nesting of a size that JS engines can't handle.
      Ref condition = visit(curr->condition, EXPRESSION_RESULT);
      Ref theSwitch =
        ValueBuilder::makeSwitch(makeJsCoercion(condition, JS_INT));
      // First, group the switch targets.
      std::map<Name, std::vector<Index>> targetIndexes;
      for (size_t i = 0; i < curr->targets.size(); i++) {
        targetIndexes[curr->targets[i]].push_back(i);
      }
      // Emit first any hoisted groups.
      auto& hoistedCases = switchProcessor.hoistedSwitchCases[curr];
      std::set<Name> emittedTargets;
      bool hoistedEndsWithUnreachable = false;
      for (auto& case_ : hoistedCases) {
        auto target = case_.target;
        auto& code = case_.code;
        emittedTargets.insert(target);
        if (target != curr->default_) {
          auto& indexes = targetIndexes[target];
          for (auto i : indexes) {
            ValueBuilder::appendCaseToSwitch(theSwitch,
                                             ValueBuilder::makeNum(i));
          }
        } else {
          ValueBuilder::appendDefaultToSwitch(theSwitch);
        }
        for (auto* c : code) {
          ValueBuilder::appendCodeToSwitch(
            theSwitch, blockify(visit(c, NO_RESULT)), false);
          hoistedEndsWithUnreachable = c->type == Type::unreachable;
        }
      }
      // After the hoisted cases, if any remain we must make sure not to
      // fall through into them. If no code was hoisted, this is unnecessary,
      // and if the hoisted code ended with an unreachable it also is not
      // necessary.
      bool stoppedFurtherFallthrough = false;
      auto stopFurtherFallthrough = [&]() {
        if (!stoppedFurtherFallthrough && !hoistedCases.empty() &&
            !hoistedEndsWithUnreachable) {
          stoppedFurtherFallthrough = true;
          ValueBuilder::appendCodeToSwitch(
            theSwitch, blockify(ValueBuilder::makeBreak(IString())), false);
        }
      };

      // Emit any remaining groups by just emitting branches to their code,
      // which will appear outside the switch.
      for (auto& [target, indexes] : targetIndexes) {
        if (emittedTargets.count(target)) {
          continue;
        }
        stopFurtherFallthrough();
        if (target != curr->default_) {
          for (auto i : indexes) {
            ValueBuilder::appendCaseToSwitch(theSwitch,
                                             ValueBuilder::makeNum(i));
          }
          ValueBuilder::appendCodeToSwitch(
            theSwitch, blockify(makeBreakOrContinue(target)), false);
        } else {
          // For the group going to the same place as the default, we can just
          // emit the default itself, which we do at the end.
        }
      }
      // TODO: if the group the default is in is not the largest, we can turn
      // the largest into
      //       the default by using a local and a check on the range
      if (!emittedTargets.count(curr->default_)) {
        stopFurtherFallthrough();
        ValueBuilder::appendDefaultToSwitch(theSwitch);
        ValueBuilder::appendCodeToSwitch(
          theSwitch, blockify(makeBreakOrContinue(curr->default_)), false);
      }
      return theSwitch;
    }

    Ref visitCall(Call* curr) {
      if (curr->isReturn) {
        Fatal() << "tail calls not yet supported in wasm2js";
      }
      Ref theCall =
        ValueBuilder::makeCall(fromName(curr->target, NameScope::Top));
      // For wasm => wasm calls, we don't need coercions. TODO: even imports
      // might be safe?
      bool needCoercions = parent->options.optimizeLevel == 0 ||
                           standaloneFunction ||
                           module->getFunction(curr->target)->imported();
      for (auto operand : curr->operands) {
        auto value = visit(operand, EXPRESSION_RESULT);
        if (needCoercions) {
          value = makeJsCoercion(value, wasmToJsType(operand->type));
        }
        theCall[2]->push_back(value);
      }
      if (needCoercions) {
        theCall = makeJsCoercion(theCall, wasmToJsType(curr->type));
      }
      return theCall;
    }

    Ref visitCallIndirect(CallIndirect* curr) {
      if (curr->isReturn) {
        Fatal() << "tail calls not yet supported in wasm2js";
      }
      // If the target has effects that interact with the operands, we must
      // reorder it to the start.
      bool mustReorder = false;
      EffectAnalyzer targetEffects(parent->options, *module, curr->target);
      if (targetEffects.hasAnything()) {
        for (auto* operand : curr->operands) {
          if (targetEffects.invalidates(
                EffectAnalyzer(parent->options, *module, operand))) {
            mustReorder = true;
            break;
          }
        }
      }
      // Ensure the function pointer is a number. In general in wasm2js we are
      // ok with true/false being present, as they are immediately cast to a
      // number anyhow on their use. However, FUNCTION_TABLE[true] is *not* the
      // same as FUNCTION_TABLE[1], so we must cast. This is a rare exception
      // because FUNCTION_TABLE is just a normal JS object, not a typed array
      // or a mathematical operation (all of which coerce to a number for us).
      auto target = visit(curr->target, EXPRESSION_RESULT);
      target = makeJsCoercion(target, JS_INT);
      if (mustReorder) {
        Ref ret;
        ScopedTemp idx(Type::i32, parent, func);
        std::vector<ScopedTemp*> temps; // TODO: utility class, with destructor?
        for (auto* operand : curr->operands) {
          temps.push_back(new ScopedTemp(operand->type, parent, func));
          IString temp = temps.back()->temp;
          sequenceAppend(ret, visitAndAssign(operand, temp));
        }
        sequenceAppend(ret,
                       ValueBuilder::makeBinary(
                         ValueBuilder::makeName(idx.getName()), SET, target));
        Ref theCall = ValueBuilder::makeCall(ValueBuilder::makeSub(
          ValueBuilder::makeName(FUNCTION_TABLE), idx.getAstName()));
        for (size_t i = 0; i < temps.size(); i++) {
          IString temp = temps[i]->temp;
          auto& operand = curr->operands[i];
          theCall[2]->push_back(makeJsCoercion(ValueBuilder::makeName(temp),
                                               wasmToJsType(operand->type)));
        }
        theCall = makeJsCoercion(theCall, wasmToJsType(curr->type));
        sequenceAppend(ret, theCall);
        for (auto temp : temps) {
          delete temp;
        }
        return ret;
      } else {
        // Target has no side effects, emit simple code
        Ref theCall = ValueBuilder::makeCall(ValueBuilder::makeSub(
          ValueBuilder::makeName(FUNCTION_TABLE), target));
        for (auto* operand : curr->operands) {
          theCall[2]->push_back(visit(operand, EXPRESSION_RESULT));
        }
        theCall = makeJsCoercion(theCall, wasmToJsType(curr->type));
        return theCall;
      }
    }

    // TODO: remove
    Ref makeSetVar(Expression* curr,
                   Expression* value,
                   Name name,
                   NameScope scope) {
      return ValueBuilder::makeBinary(
        ValueBuilder::makeName(fromName(name, scope)),
        SET,
        visit(value, EXPRESSION_RESULT));
    }

    Ref visitLocalGet(LocalGet* curr) {
      return ValueBuilder::makeName(
        fromName(func->getLocalNameOrGeneric(curr->index), NameScope::Local));
    }

    Ref visitLocalSet(LocalSet* curr) {
      return makeSetVar(curr,
                        curr->value,
                        func->getLocalNameOrGeneric(curr->index),
                        NameScope::Local);
    }

    Ref visitGlobalGet(GlobalGet* curr) {
      return ValueBuilder::makeName(fromName(curr->name, NameScope::Top));
    }

    Ref visitGlobalSet(GlobalSet* curr) {
      return makeSetVar(curr, curr->value, curr->name, NameScope::Top);
    }

    Ref visitLoad(Load* curr) {
      // Unaligned loads and stores must have been fixed up already.
      assert(curr->align == 0 || curr->align == curr->bytes);
      // normal load
      Ref ptr = makePointer(curr->ptr, curr->offset);
      Ref ret;
      switch (curr->type.getBasic()) {
        case Type::i32: {
          switch (curr->bytes) {
            case 1:
              ret = ValueBuilder::makeSub(
                ValueBuilder::makeName(
                  LoadUtils::isSignRelevant(curr) && curr->signed_ ? HEAP8
                                                                   : HEAPU8),
                ValueBuilder::makePtrShift(ptr, 0));
              break;
            case 2:
              ret = ValueBuilder::makeSub(
                ValueBuilder::makeName(
                  LoadUtils::isSignRelevant(curr) && curr->signed_ ? HEAP16
                                                                   : HEAPU16),
                ValueBuilder::makePtrShift(ptr, 1));
              break;
            case 4:
              ret = ValueBuilder::makeSub(ValueBuilder::makeName(HEAP32),
                                          ValueBuilder::makePtrShift(ptr, 2));
              break;
            default: {
              Fatal() << "Unhandled number of bytes in i32 load: "
                      << curr->bytes;
            }
          }
          break;
        }
        case Type::f32:
          ret = ValueBuilder::makeSub(ValueBuilder::makeName(HEAPF32),
                                      ValueBuilder::makePtrShift(ptr, 2));
          break;
        case Type::f64:
          ret = ValueBuilder::makeSub(ValueBuilder::makeName(HEAPF64),
                                      ValueBuilder::makePtrShift(ptr, 3));
          break;
        default: {
          Fatal() << "Unhandled type in load: " << curr->type;
        }
      }
      if (curr->isAtomic) {
        Ref call = ValueBuilder::makeCall(
          ValueBuilder::makeDot(ValueBuilder::makeName(ATOMICS), LOAD));
        ValueBuilder::appendToCall(call, ret[1]);
        ValueBuilder::appendToCall(call, ret[2]);
        ret = call;
      }
      // Coercions are not actually needed, as if the user reads beyond valid
      // memory, it's undefined behavior anyhow, and so we don't care much about
      // slowness of undefined values etc.
      bool needCoercions =
        parent->options.optimizeLevel == 0 || standaloneFunction;
      if (needCoercions) {
        ret = makeJsCoercion(ret, wasmToJsType(curr->type));
      }
      return ret;
    }

    Ref visitStore(Store* curr) {
      if (!module->memories.empty() &&
          module->memories[0]->initial < module->memories[0]->max &&
          curr->type != Type::unreachable) {
        // In JS, if memory grows then it is dangerous to write
        //  HEAP[f()] = ..
        // or
        //  HEAP[..] = f()
        // since if the call swaps HEAP (in a growth operation) then
        // we will not actually write to the new version (since the
        // semantics of JS mean we already looked at HEAP and have
        // decided where to assign to).
        if (!FindAll<Call>(curr->ptr).list.empty() ||
            !FindAll<Call>(curr->value).list.empty() ||
            !FindAll<CallIndirect>(curr->ptr).list.empty() ||
            !FindAll<CallIndirect>(curr->value).list.empty() ||
            !FindAll<MemoryGrow>(curr->ptr).list.empty() ||
            !FindAll<MemoryGrow>(curr->value).list.empty()) {
          Ref ret;
          ScopedTemp ptr(Type::i32, parent, func);
          sequenceAppend(ret, visitAndAssign(curr->ptr, ptr));
          ScopedTemp value(curr->value->type, parent, func);
          sequenceAppend(ret, visitAndAssign(curr->value, value));
          LocalGet getPtr;
          getPtr.index = func->getLocalIndex(ptr.getName());
          getPtr.type = Type::i32;
          LocalGet getValue;
          getValue.index = func->getLocalIndex(value.getName());
          getValue.type = curr->value->type;
          Store fakeStore = *curr;
          fakeStore.ptr = &getPtr;
          fakeStore.value = &getValue;
          sequenceAppend(ret, visitStore(&fakeStore));
          return ret;
        }
      }
      // FIXME if memory growth, store ptr cannot contain a function call
      //       also other stores to memory, check them, all makeSub's
      // Unaligned loads and stores must have been fixed up already.
      assert(curr->align == 0 || curr->align == curr->bytes);
      // normal store
      Ref ptr = makePointer(curr->ptr, curr->offset);
      Ref value = visit(curr->value, EXPRESSION_RESULT);
      Ref ret;
      switch (curr->valueType.getBasic()) {
        case Type::i32: {
          switch (curr->bytes) {
            case 1:
              ret = ValueBuilder::makeSub(ValueBuilder::makeName(HEAP8),
                                          ValueBuilder::makePtrShift(ptr, 0));
              break;
            case 2:
              ret = ValueBuilder::makeSub(ValueBuilder::makeName(HEAP16),
                                          ValueBuilder::makePtrShift(ptr, 1));
              break;
            case 4:
              ret = ValueBuilder::makeSub(ValueBuilder::makeName(HEAP32),
                                          ValueBuilder::makePtrShift(ptr, 2));
              break;
            default:
              abort();
          }
          break;
        }
        case Type::f32:
          ret = ValueBuilder::makeSub(ValueBuilder::makeName(HEAPF32),
                                      ValueBuilder::makePtrShift(ptr, 2));
          break;
        case Type::f64:
          ret = ValueBuilder::makeSub(ValueBuilder::makeName(HEAPF64),
                                      ValueBuilder::makePtrShift(ptr, 3));
          break;
        default: {
          Fatal() << "Unhandled type in store: " << curr->valueType;
        }
      }
      if (curr->isAtomic) {
        Ref call = ValueBuilder::makeCall(
          ValueBuilder::makeDot(ValueBuilder::makeName(ATOMICS), STORE));
        ValueBuilder::appendToCall(call, ret[1]);
        ValueBuilder::appendToCall(call, ret[2]);
        ValueBuilder::appendToCall(call, value);
        return call;
      }
      return ValueBuilder::makeBinary(ret, SET, value);
    }

    Ref visitDrop(Drop* curr) { return visit(curr->value, NO_RESULT); }

    Ref visitConst(Const* curr) {
      switch (curr->type.getBasic()) {
        case Type::i32:
          return ValueBuilder::makeInt(curr->value.geti32());
        // An i64 argument translates to two actual arguments to asm.js
        // functions, so we do a bit of a hack here to get our one `Ref` to look
        // like two function arguments.
        case Type::i64: {
          auto lo = (unsigned)curr->value.geti64();
          auto hi = (unsigned)(curr->value.geti64() >> 32);
          std::ostringstream out;
          out << lo << "," << hi;
          std::string os = out.str();
          IString name(os.c_str(), false);
          return ValueBuilder::makeName(name);
        }
        case Type::f32: {
          Ref ret = ValueBuilder::makeCall(MATH_FROUND);
          Const fake;
          fake.value = Literal(double(curr->value.getf32()));
          fake.type = Type::f64;
          ret[2]->push_back(visitConst(&fake));
          return ret;
        }
        case Type::f64: {
          double d = curr->value.getf64();
          if (d == 0 && std::signbit(d)) { // negative zero
            return ValueBuilder::makeUnary(
              PLUS,
              ValueBuilder::makeUnary(MINUS, ValueBuilder::makeDouble(0)));
          }
          return ValueBuilder::makeUnary(
            PLUS, ValueBuilder::makeDouble(curr->value.getf64()));
        }
        default:
          Fatal() << "unknown const type";
      }
    }

    Ref visitUnary(Unary* curr) {
      // normal unary
      switch (curr->type.getBasic()) {
        case Type::i32: {
          switch (curr->op) {
            case ClzInt32: {
              return ValueBuilder::makeCall(
                MATH_CLZ32, visit(curr->value, EXPRESSION_RESULT));
            }
            case CtzInt32:
            case PopcntInt32: {
              WASM_UNREACHABLE("i32 unary should have been removed");
            }
            case EqZInt32: {
              // XXX !x does change the type to bool, which is correct, but may
              // be slower?
              return ValueBuilder::makeUnary(
                L_NOT, visit(curr->value, EXPRESSION_RESULT));
            }
            case ReinterpretFloat32: {
              ABI::wasm2js::ensureHelpers(module,
                                          ABI::wasm2js::SCRATCH_STORE_F32);
              ABI::wasm2js::ensureHelpers(module,
                                          ABI::wasm2js::SCRATCH_LOAD_I32);

              Ref store =
                ValueBuilder::makeCall(ABI::wasm2js::SCRATCH_STORE_F32,
                                       visit(curr->value, EXPRESSION_RESULT));
              // 32-bit scratch memory uses index 2, so that it does not
              // conflict with indexes 0, 1 which are used for 64-bit, see
              // comment where |scratchBuffer| is defined.
              Ref load = ValueBuilder::makeCall(ABI::wasm2js::SCRATCH_LOAD_I32,
                                                ValueBuilder::makeInt(2));
              return ValueBuilder::makeSeq(store, load);
            }
            // generate (~~expr), what Emscripten does
            case TruncSFloat32ToInt32:
            case TruncSFloat64ToInt32:
            case TruncSatSFloat32ToInt32:
            case TruncSatSFloat64ToInt32: {
              return ValueBuilder::makeUnary(
                B_NOT,
                ValueBuilder::makeUnary(B_NOT,
                                        visit(curr->value, EXPRESSION_RESULT)));
            }
            // generate (~~expr >>> 0), what Emscripten does
            case TruncUFloat32ToInt32:
            case TruncUFloat64ToInt32:
            case TruncSatUFloat32ToInt32:
            case TruncSatUFloat64ToInt32: {
              return ValueBuilder::makeBinary(
                ValueBuilder::makeUnary(
                  B_NOT,
                  ValueBuilder::makeUnary(
                    B_NOT, visit(curr->value, EXPRESSION_RESULT))),
                TRSHIFT,
                ValueBuilder::makeNum(0));
            }
            case ExtendS8Int32: {
              return ValueBuilder::makeBinary(
                ValueBuilder::makeBinary(visit(curr->value, EXPRESSION_RESULT),
                                         LSHIFT,
                                         ValueBuilder::makeNum(24)),
                RSHIFT,
                ValueBuilder::makeNum(24));
            }
            case ExtendS16Int32: {
              return ValueBuilder::makeBinary(
                ValueBuilder::makeBinary(visit(curr->value, EXPRESSION_RESULT),
                                         LSHIFT,
                                         ValueBuilder::makeNum(16)),
                RSHIFT,
                ValueBuilder::makeNum(16));
            }
            default:
              WASM_UNREACHABLE("unhandled unary");
          }
        }
        case Type::f32:
        case Type::f64: {
          Ref ret;
          switch (curr->op) {
            case NegFloat32:
            case NegFloat64:
              ret = ValueBuilder::makeUnary(
                MINUS, visit(curr->value, EXPRESSION_RESULT));
              break;
            case AbsFloat32:
            case AbsFloat64:
              ret = ValueBuilder::makeCall(
                MATH_ABS, visit(curr->value, EXPRESSION_RESULT));
              break;
            case CeilFloat32:
            case CeilFloat64:
              ret = ValueBuilder::makeCall(
                MATH_CEIL, visit(curr->value, EXPRESSION_RESULT));
              break;
            case FloorFloat32:
            case FloorFloat64:
              ret = ValueBuilder::makeCall(
                MATH_FLOOR, visit(curr->value, EXPRESSION_RESULT));
              break;
            case TruncFloat32:
            case TruncFloat64:
              ret = ValueBuilder::makeCall(
                MATH_TRUNC, visit(curr->value, EXPRESSION_RESULT));
              break;
            case SqrtFloat32:
            case SqrtFloat64:
              ret = ValueBuilder::makeCall(
                MATH_SQRT, visit(curr->value, EXPRESSION_RESULT));
              break;
            case PromoteFloat32:
              return makeJsCoercion(visit(curr->value, EXPRESSION_RESULT),
                                    JS_DOUBLE);
            case DemoteFloat64:
              return makeJsCoercion(visit(curr->value, EXPRESSION_RESULT),
                                    JS_FLOAT);
            case ReinterpretInt32: {
              ABI::wasm2js::ensureHelpers(module,
                                          ABI::wasm2js::SCRATCH_STORE_I32);
              ABI::wasm2js::ensureHelpers(module,
                                          ABI::wasm2js::SCRATCH_LOAD_F32);

              // 32-bit scratch memory uses index 2, so that it does not
              // conflict with indexes 0, 1 which are used for 64-bit, see
              // comment where |scratchBuffer| is defined.
              Ref store =
                ValueBuilder::makeCall(ABI::wasm2js::SCRATCH_STORE_I32,
                                       ValueBuilder::makeNum(2),
                                       visit(curr->value, EXPRESSION_RESULT));
              Ref load = ValueBuilder::makeCall(ABI::wasm2js::SCRATCH_LOAD_F32);
              return ValueBuilder::makeSeq(store, load);
            }
            // Coerce the integer to a float as emscripten does
            case ConvertSInt32ToFloat32:
              return makeJsCoercion(
                makeJsCoercion(visit(curr->value, EXPRESSION_RESULT), JS_INT),
                JS_FLOAT);
            case ConvertSInt32ToFloat64:
              return makeJsCoercion(
                makeJsCoercion(visit(curr->value, EXPRESSION_RESULT), JS_INT),
                JS_DOUBLE);

            // Generate (expr >>> 0), followed by a coercion
            case ConvertUInt32ToFloat32:
              return makeJsCoercion(
                ValueBuilder::makeBinary(visit(curr->value, EXPRESSION_RESULT),
                                         TRSHIFT,
                                         ValueBuilder::makeInt(0)),
                JS_FLOAT);
            case ConvertUInt32ToFloat64:
              return makeJsCoercion(
                ValueBuilder::makeBinary(visit(curr->value, EXPRESSION_RESULT),
                                         TRSHIFT,
                                         ValueBuilder::makeInt(0)),
                JS_DOUBLE);
            // TODO: more complex unary conversions
            case NearestFloat32:
            case NearestFloat64:
              WASM_UNREACHABLE(
                "operation should have been removed in previous passes");

            default:
              WASM_UNREACHABLE("unhandled unary float operator");
          }
          if (curr->type == Type::f32) { // doubles need much less coercing
            return makeJsCoercion(ret, JS_FLOAT);
          }
          return ret;
        }
        default: {
          Fatal() << "Unhandled type in unary: " << curr;
        }
      }
    }

    Ref visitBinary(Binary* curr) {
      // normal binary
      Ref left = visit(curr->left, EXPRESSION_RESULT);
      Ref right = visit(curr->right, EXPRESSION_RESULT);
      Ref ret;
      switch (curr->type.getBasic()) {
        case Type::i32: {
          switch (curr->op) {
            case AddInt32:
              ret = ValueBuilder::makeBinary(left, PLUS, right);
              break;
            case SubInt32:
              ret = ValueBuilder::makeBinary(left, MINUS, right);
              break;
            case MulInt32: {
              if (curr->type == Type::i32) {
                // TODO: when one operand is a small int, emit a multiply
                return ValueBuilder::makeCall(MATH_IMUL, left, right);
              } else {
                return ValueBuilder::makeBinary(left, MUL, right);
              }
            }
            case DivSInt32:
              ret = ValueBuilder::makeBinary(makeSigning(left, JS_SIGNED),
                                             DIV,
                                             makeSigning(right, JS_SIGNED));
              break;
            case DivUInt32:
              ret = ValueBuilder::makeBinary(makeSigning(left, JS_UNSIGNED),
                                             DIV,
                                             makeSigning(right, JS_UNSIGNED));
              break;
            case RemSInt32:
              ret = ValueBuilder::makeBinary(makeSigning(left, JS_SIGNED),
                                             MOD,
                                             makeSigning(right, JS_SIGNED));
              break;
            case RemUInt32:
              ret = ValueBuilder::makeBinary(makeSigning(left, JS_UNSIGNED),
                                             MOD,
                                             makeSigning(right, JS_UNSIGNED));
              break;
            case AndInt32:
              ret = ValueBuilder::makeBinary(left, AND, right);
              break;
            case OrInt32:
              ret = ValueBuilder::makeBinary(left, OR, right);
              break;
            case XorInt32:
              ret = ValueBuilder::makeBinary(left, XOR, right);
              break;
            case ShlInt32:
              ret = ValueBuilder::makeBinary(left, LSHIFT, right);
              break;
            case ShrUInt32:
              ret = ValueBuilder::makeBinary(left, TRSHIFT, right);
              break;
            case ShrSInt32:
              ret = ValueBuilder::makeBinary(left, RSHIFT, right);
              break;
            case EqInt32: {
              return ValueBuilder::makeBinary(makeSigning(left, JS_SIGNED),
                                              EQ,
                                              makeSigning(right, JS_SIGNED));
            }
            case NeInt32: {
              return ValueBuilder::makeBinary(makeSigning(left, JS_SIGNED),
                                              NE,
                                              makeSigning(right, JS_SIGNED));
            }
            case LtSInt32:
              return ValueBuilder::makeBinary(makeSigning(left, JS_SIGNED),
                                              LT,
                                              makeSigning(right, JS_SIGNED));
            case LtUInt32:
              return ValueBuilder::makeBinary(makeSigning(left, JS_UNSIGNED),
                                              LT,
                                              makeSigning(right, JS_UNSIGNED));
            case LeSInt32:
              return ValueBuilder::makeBinary(makeSigning(left, JS_SIGNED),
                                              LE,
                                              makeSigning(right, JS_SIGNED));
            case LeUInt32:
              return ValueBuilder::makeBinary(makeSigning(left, JS_UNSIGNED),
                                              LE,
                                              makeSigning(right, JS_UNSIGNED));
            case GtSInt32:
              return ValueBuilder::makeBinary(makeSigning(left, JS_SIGNED),
                                              GT,
                                              makeSigning(right, JS_SIGNED));
            case GtUInt32:
              return ValueBuilder::makeBinary(makeSigning(left, JS_UNSIGNED),
                                              GT,
                                              makeSigning(right, JS_UNSIGNED));
            case GeSInt32:
              return ValueBuilder::makeBinary(makeSigning(left, JS_SIGNED),
                                              GE,
                                              makeSigning(right, JS_SIGNED));
            case GeUInt32:
              return ValueBuilder::makeBinary(makeSigning(left, JS_UNSIGNED),
                                              GE,
                                              makeSigning(right, JS_UNSIGNED));
            case EqFloat32:
            case EqFloat64:
              return ValueBuilder::makeBinary(left, EQ, right);
            case NeFloat32:
            case NeFloat64:
              return ValueBuilder::makeBinary(left, NE, right);
            case GeFloat32:
            case GeFloat64:
              return ValueBuilder::makeBinary(left, GE, right);
            case GtFloat32:
            case GtFloat64:
              return ValueBuilder::makeBinary(left, GT, right);
            case LeFloat32:
            case LeFloat64:
              return ValueBuilder::makeBinary(left, LE, right);
            case LtFloat32:
            case LtFloat64:
              return ValueBuilder::makeBinary(left, LT, right);
            case RotLInt32:
            case RotRInt32:
              WASM_UNREACHABLE("should be removed already");
            default:
              WASM_UNREACHABLE("unhandled i32 binary operator");
          }
          break;
        }
        case Type::f32:
        case Type::f64:
          switch (curr->op) {
            case AddFloat32:
            case AddFloat64:
              ret = ValueBuilder::makeBinary(left, PLUS, right);
              break;
            case SubFloat32:
            case SubFloat64:
              ret = ValueBuilder::makeBinary(left, MINUS, right);
              break;
            case MulFloat32:
            case MulFloat64:
              ret = ValueBuilder::makeBinary(left, MUL, right);
              break;
            case DivFloat32:
            case DivFloat64:
              ret = ValueBuilder::makeBinary(left, DIV, right);
              break;
            case MinFloat32:
            case MinFloat64:
              ret = ValueBuilder::makeCall(MATH_MIN, left, right);
              break;
            case MaxFloat32:
            case MaxFloat64:
              ret = ValueBuilder::makeCall(MATH_MAX, left, right);
              break;
            case CopySignFloat32:
            case CopySignFloat64:
            default:
              Fatal() << "Unhandled binary float operator: ";
          }
          if (curr->type == Type::f32) {
            return makeJsCoercion(ret, JS_FLOAT);
          }
          return ret;
        default:
          Fatal() << "Unhandled type in binary: " << curr;
      }
      return makeJsCoercion(ret, wasmToJsType(curr->type));
    }

    Ref visitSelect(Select* curr) {
      // If the condition has effects that interact with the operands, we must
      // reorder it to the start. We must also use locals if the values have
      // side effects, as a JS conditional does not visit both sides.
      bool useLocals = false;
      EffectAnalyzer conditionEffects(
        parent->options, *module, curr->condition);
      EffectAnalyzer ifTrueEffects(parent->options, *module, curr->ifTrue);
      EffectAnalyzer ifFalseEffects(parent->options, *module, curr->ifFalse);
      if (conditionEffects.invalidates(ifTrueEffects) ||
          conditionEffects.invalidates(ifFalseEffects) ||
          ifTrueEffects.hasSideEffects() || ifFalseEffects.hasSideEffects()) {
        useLocals = true;
      }
      if (useLocals) {
        ScopedTemp tempIfTrue(curr->type, parent, func),
          tempIfFalse(curr->type, parent, func),
          tempCondition(Type::i32, parent, func);
        Ref ifTrue = visit(curr->ifTrue, EXPRESSION_RESULT);
        Ref ifFalse = visit(curr->ifFalse, EXPRESSION_RESULT);
        Ref condition = visit(curr->condition, EXPRESSION_RESULT);
        return ValueBuilder::makeSeq(
          ValueBuilder::makeBinary(tempIfTrue.getAstName(), SET, ifTrue),
          ValueBuilder::makeSeq(
            ValueBuilder::makeBinary(tempIfFalse.getAstName(), SET, ifFalse),
            ValueBuilder::makeSeq(
              ValueBuilder::makeBinary(
                tempCondition.getAstName(), SET, condition),
              ValueBuilder::makeConditional(tempCondition.getAstName(),
                                            tempIfTrue.getAstName(),
                                            tempIfFalse.getAstName()))));
      } else {
        // Simple case without reordering.
        return ValueBuilder::makeConditional(
          visit(curr->condition, EXPRESSION_RESULT),
          visit(curr->ifTrue, EXPRESSION_RESULT),
          visit(curr->ifFalse, EXPRESSION_RESULT));
      }
    }

    Ref visitReturn(Return* curr) {
      if (!curr->value) {
        return ValueBuilder::makeReturn(Ref());
      }
      Ref val = visit(curr->value, EXPRESSION_RESULT);
      bool needCoercion =
        parent->options.optimizeLevel == 0 || standaloneFunction ||
        parent->functionsCallableFromOutside.count(func->name);
      if (needCoercion) {
        val = makeJsCoercion(val, wasmToJsType(curr->value->type));
      }
      return ValueBuilder::makeReturn(val);
    }

    Ref visitMemorySize(MemorySize* curr) {
      return ValueBuilder::makeCall(WASM_MEMORY_SIZE);
    }

    Ref visitMemoryGrow(MemoryGrow* curr) {
      if (!module->memories.empty() &&
          module->memories[0]->max > module->memories[0]->initial) {
        return ValueBuilder::makeCall(
          WASM_MEMORY_GROW,
          makeJsCoercion(visit(curr->delta, EXPRESSION_RESULT),
                         wasmToJsType(curr->delta->type)));
      } else {
        ABI::wasm2js::ensureHelpers(module, ABI::wasm2js::TRAP);
        return ValueBuilder::makeCall(ABI::wasm2js::TRAP);
      }
    }

    Ref visitNop(Nop* curr) { return ValueBuilder::makeToplevel(); }
    Ref visitUnreachable(Unreachable* curr) {
      ABI::wasm2js::ensureHelpers(module, ABI::wasm2js::TRAP);
      return ValueBuilder::makeCall(ABI::wasm2js::TRAP);
    }

    // Atomics

    struct HeapAndPointer {
      Ref heap;
      Ref ptr;
    };

    HeapAndPointer
    getHeapAndAdjustedPointer(Index bytes, Expression* ptr, Index offset) {
      IString heap;
      Ref adjustedPtr = makePointer(ptr, offset);
      switch (bytes) {
        case 1:
          heap = HEAP8;
          break;
        case 2:
          heap = HEAP16;
          adjustedPtr = ValueBuilder::makePtrShift(adjustedPtr, 1);
          break;
        case 4:
          heap = HEAP32;
          adjustedPtr = ValueBuilder::makePtrShift(adjustedPtr, 2);
          break;
        default: {
          WASM_UNREACHABLE("unimp");
        }
      }
      return {ValueBuilder::makeName(heap), adjustedPtr};
    }

    Ref visitAtomicRMW(AtomicRMW* curr) {
      auto hap =
        getHeapAndAdjustedPointer(curr->bytes, curr->ptr, curr->offset);
      IString target;
      switch (curr->op) {
        case RMWAdd:
          target = IString("add");
          break;
        case RMWSub:
          target = IString("sub");
          break;
        case RMWAnd:
          target = IString("and");
          break;
        case RMWOr:
          target = IString("or");
          break;
        case RMWXor:
          target = IString("xor");
          break;
        case RMWXchg:
          target = IString("exchange");
          break;
        default:
          WASM_UNREACHABLE("unimp");
      }
      Ref call = ValueBuilder::makeCall(
        ValueBuilder::makeDot(ValueBuilder::makeName(ATOMICS), target));
      ValueBuilder::appendToCall(call, hap.heap);
      ValueBuilder::appendToCall(call, hap.ptr);
      ValueBuilder::appendToCall(call, visit(curr->value, EXPRESSION_RESULT));
      return call;
    }

    Ref visitAtomicCmpxchg(AtomicCmpxchg* curr) {
      auto hap =
        getHeapAndAdjustedPointer(curr->bytes, curr->ptr, curr->offset);
      Ref expected = visit(curr->expected, EXPRESSION_RESULT);
      Ref replacement = visit(curr->replacement, EXPRESSION_RESULT);
      Ref call = ValueBuilder::makeCall(ValueBuilder::makeDot(
        ValueBuilder::makeName(ATOMICS), COMPARE_EXCHANGE));
      ValueBuilder::appendToCall(call, hap.heap);
      ValueBuilder::appendToCall(call, hap.ptr);
      ValueBuilder::appendToCall(call, expected);
      ValueBuilder::appendToCall(call, replacement);
      return makeJsCoercion(call, wasmToJsType(curr->type));
    }

    Ref visitAtomicWait(AtomicWait* curr) {
      unimplemented(curr);
      WASM_UNREACHABLE("unimp");
    }

    Ref visitAtomicNotify(AtomicNotify* curr) {
      Ref call = ValueBuilder::makeCall(ValueBuilder::makeDot(
        ValueBuilder::makeName(ATOMICS), IString("notify")));
      ValueBuilder::appendToCall(call, ValueBuilder::makeName(HEAP32));
      ValueBuilder::appendToCall(
        call,
        ValueBuilder::makePtrShift(makePointer(curr->ptr, curr->offset), 2));
      ValueBuilder::appendToCall(
        call,
        makeSigning(visit(curr->notifyCount, EXPRESSION_RESULT), JS_UNSIGNED));
      return call;
    }

    Ref visitAtomicFence(AtomicFence* curr) {
      // Sequentially consistent fences can be lowered to no operation
      return ValueBuilder::makeToplevel();
    }

    // TODOs

    Ref visitSIMDExtract(SIMDExtract* curr) {
      unimplemented(curr);
      WASM_UNREACHABLE("unimp");
    }
    Ref visitSIMDReplace(SIMDReplace* curr) {
      unimplemented(curr);
      WASM_UNREACHABLE("unimp");
    }
    Ref visitSIMDShuffle(SIMDShuffle* curr) {
      unimplemented(curr);
      WASM_UNREACHABLE("unimp");
    }
    Ref visitSIMDTernary(SIMDTernary* curr) {
      unimplemented(curr);
      WASM_UNREACHABLE("unimp");
    }
    Ref visitSIMDShift(SIMDShift* curr) {
      unimplemented(curr);
      WASM_UNREACHABLE("unimp");
    }
    Ref visitSIMDLoad(SIMDLoad* curr) {
      unimplemented(curr);
      WASM_UNREACHABLE("unimp");
    }
    Ref visitSIMDLoadStoreLane(SIMDLoadStoreLane* curr) {
      unimplemented(curr);
      WASM_UNREACHABLE("unimp");
    }
    Ref visitMemoryInit(MemoryInit* curr) {
      ABI::wasm2js::ensureHelpers(module, ABI::wasm2js::MEMORY_INIT);
      return ValueBuilder::makeCall(
        ABI::wasm2js::MEMORY_INIT,
        ValueBuilder::makeNum(parent->getDataIndex(curr->segment)),
        visit(curr->dest, EXPRESSION_RESULT),
        visit(curr->offset, EXPRESSION_RESULT),
        visit(curr->size, EXPRESSION_RESULT));
    }
    Ref visitDataDrop(DataDrop* curr) {
      ABI::wasm2js::ensureHelpers(module, ABI::wasm2js::DATA_DROP);
      return ValueBuilder::makeCall(
        ABI::wasm2js::DATA_DROP,
        ValueBuilder::makeNum(parent->getDataIndex(curr->segment)));
    }
    Ref visitMemoryCopy(MemoryCopy* curr) {
      ABI::wasm2js::ensureHelpers(module, ABI::wasm2js::MEMORY_COPY);
      return ValueBuilder::makeCall(ABI::wasm2js::MEMORY_COPY,
                                    visit(curr->dest, EXPRESSION_RESULT),
                                    visit(curr->source, EXPRESSION_RESULT),
                                    visit(curr->size, EXPRESSION_RESULT));
    }
    Ref visitMemoryFill(MemoryFill* curr) {
      ABI::wasm2js::ensureHelpers(module, ABI::wasm2js::MEMORY_FILL);
      return ValueBuilder::makeCall(ABI::wasm2js::MEMORY_FILL,
                                    visit(curr->dest, EXPRESSION_RESULT),
                                    visit(curr->value, EXPRESSION_RESULT),
                                    visit(curr->size, EXPRESSION_RESULT));
    }
    Ref visitRefNull(RefNull* curr) { return ValueBuilder::makeName(NULL_); }
    Ref visitRefIsNull(RefIsNull* curr) {
      return ValueBuilder::makeBinary(visit(curr->value, EXPRESSION_RESULT),
                                      EQ,
                                      ValueBuilder::makeName(NULL_));
    }
    Ref visitRefFunc(RefFunc* curr) {
      return ValueBuilder::makeName(fromName(curr->func, NameScope::Top));
    }
    Ref visitRefEq(RefEq* curr) {
      return ValueBuilder::makeBinary(visit(curr->left, EXPRESSION_RESULT),
                                      EQ,
                                      visit(curr->right, EXPRESSION_RESULT));
    }
    Ref visitTableGet(TableGet* curr) {
      return ValueBuilder::makeSub(ValueBuilder::makeName(FUNCTION_TABLE),
                                   visit(curr->index, EXPRESSION_RESULT));
    }
    Ref visitTableSet(TableSet* curr) {
      auto sub = ValueBuilder::makeSub(ValueBuilder::makeName(FUNCTION_TABLE),
                                       visit(curr->index, EXPRESSION_RESULT));
      auto value = visit(curr->value, EXPRESSION_RESULT);
      return ValueBuilder::makeBinary(sub, SET, value);
    }
    Ref visitTableSize(TableSize* curr) {
      return ValueBuilder::makeDot(ValueBuilder::makeName(FUNCTION_TABLE),
                                   ValueBuilder::makeName(LENGTH));
    }
    Ref visitTableGrow(TableGrow* curr) {
      ABI::wasm2js::ensureHelpers(module, ABI::wasm2js::TABLE_GROW);
      // Also ensure fill, as grow calls fill internally.
      ABI::wasm2js::ensureHelpers(module, ABI::wasm2js::TABLE_FILL);
      return ValueBuilder::makeCall(ABI::wasm2js::TABLE_GROW,
                                    visit(curr->value, EXPRESSION_RESULT),
                                    visit(curr->delta, EXPRESSION_RESULT));
    }
    Ref visitTableFill(TableFill* curr) {
      ABI::wasm2js::ensureHelpers(module, ABI::wasm2js::TABLE_FILL);
      return ValueBuilder::makeCall(ABI::wasm2js::TABLE_FILL,
                                    visit(curr->dest, EXPRESSION_RESULT),
                                    visit(curr->value, EXPRESSION_RESULT),
                                    visit(curr->size, EXPRESSION_RESULT));
    }
    Ref visitTableCopy(TableCopy* curr) {
      ABI::wasm2js::ensureHelpers(module, ABI::wasm2js::TABLE_COPY);
      return ValueBuilder::makeCall(ABI::wasm2js::TABLE_COPY,
                                    visit(curr->dest, EXPRESSION_RESULT),
                                    visit(curr->source, EXPRESSION_RESULT),
                                    visit(curr->size, EXPRESSION_RESULT));
    }
    Ref visitTableInit(TableInit* curr) {
      unimplemented(curr);
      WASM_UNREACHABLE("unimp");
    }
    Ref visitTry(Try* curr) {
      unimplemented(curr);
      WASM_UNREACHABLE("unimp");
    }
    Ref visitTryTable(TryTable* curr) {
      unimplemented(curr);
      WASM_UNREACHABLE("unimp");
    }
    Ref visitThrow(Throw* curr) {
      unimplemented(curr);
      WASM_UNREACHABLE("unimp");
    }
    Ref visitRethrow(Rethrow* curr) {
      unimplemented(curr);
      WASM_UNREACHABLE("unimp");
    }
    Ref visitThrowRef(ThrowRef* curr) {
      unimplemented(curr);
      WASM_UNREACHABLE("unimp");
    }
    Ref visitPop(Pop* curr) {
      unimplemented(curr);
      WASM_UNREACHABLE("unimp");
    }
    Ref visitTupleMake(TupleMake* curr) {
      unimplemented(curr);
      WASM_UNREACHABLE("unimp");
    }
    Ref visitTupleExtract(TupleExtract* curr) {
      unimplemented(curr);
      WASM_UNREACHABLE("unimp");
    }
    Ref visitRefI31(RefI31* curr) {
      unimplemented(curr);
      WASM_UNREACHABLE("unimp");
    }
    Ref visitI31Get(I31Get* curr) {
      unimplemented(curr);
      WASM_UNREACHABLE("unimp");
    }
    Ref visitCallRef(CallRef* curr) {
      unimplemented(curr);
      WASM_UNREACHABLE("unimp");
    }
    Ref visitRefTest(RefTest* curr) {
      unimplemented(curr);
      WASM_UNREACHABLE("unimp");
    }
    Ref visitRefCast(RefCast* curr) {
      unimplemented(curr);
      WASM_UNREACHABLE("unimp");
    }
    Ref visitBrOn(BrOn* curr) {
      unimplemented(curr);
      WASM_UNREACHABLE("unimp");
    }
    Ref visitStructNew(StructNew* curr) {
      unimplemented(curr);
      WASM_UNREACHABLE("unimp");
    }
    Ref visitStructGet(StructGet* curr) {
      unimplemented(curr);
      WASM_UNREACHABLE("unimp");
    }
    Ref visitStructSet(StructSet* curr) {
      unimplemented(curr);
      WASM_UNREACHABLE("unimp");
    }
    Ref visitArrayNew(ArrayNew* curr) {
      unimplemented(curr);
      WASM_UNREACHABLE("unimp");
    }
    Ref visitArrayNewData(ArrayNewData* curr) {
      unimplemented(curr);
      WASM_UNREACHABLE("unimp");
    }
    Ref visitArrayNewElem(ArrayNewElem* curr) {
      unimplemented(curr);
      WASM_UNREACHABLE("unimp");
    }
    Ref visitArrayNewFixed(ArrayNewFixed* curr) {
      unimplemented(curr);
      WASM_UNREACHABLE("unimp");
    }
    Ref visitArrayGet(ArrayGet* curr) {
      unimplemented(curr);
      WASM_UNREACHABLE("unimp");
    }
    Ref visitArraySet(ArraySet* curr) {
      unimplemented(curr);
      WASM_UNREACHABLE("unimp");
    }
    Ref visitArrayLen(ArrayLen* curr) {
      unimplemented(curr);
      WASM_UNREACHABLE("unimp");
    }
    Ref visitArrayCopy(ArrayCopy* curr) {
      unimplemented(curr);
      WASM_UNREACHABLE("unimp");
    }
    Ref visitArrayFill(ArrayFill* curr) {
      unimplemented(curr);
      WASM_UNREACHABLE("unimp");
    }
    Ref visitArrayInitData(ArrayInitData* curr) {
      unimplemented(curr);
      WASM_UNREACHABLE("unimp");
    }
    Ref visitArrayInitElem(ArrayInitElem* curr) {
      unimplemented(curr);
      WASM_UNREACHABLE("unimp");
    }
    Ref visitStringNew(StringNew* curr) {
      unimplemented(curr);
      WASM_UNREACHABLE("unimp");
    }
    Ref visitStringConst(StringConst* curr) {
      unimplemented(curr);
      WASM_UNREACHABLE("unimp");
    }
    Ref visitStringMeasure(StringMeasure* curr) {
      unimplemented(curr);
      WASM_UNREACHABLE("unimp");
    }
    Ref visitStringEncode(StringEncode* curr) {
      unimplemented(curr);
      WASM_UNREACHABLE("unimp");
    }
    Ref visitStringConcat(StringConcat* curr) {
      unimplemented(curr);
      WASM_UNREACHABLE("unimp");
    }
    Ref visitStringEq(StringEq* curr) {
      unimplemented(curr);
      WASM_UNREACHABLE("unimp");
    }
    Ref visitStringWTF16Get(StringWTF16Get* curr) {
      unimplemented(curr);
      WASM_UNREACHABLE("unimp");
    }
    Ref visitStringSliceWTF(StringSliceWTF* curr) {
      unimplemented(curr);
      WASM_UNREACHABLE("unimp");
    }
    Ref visitRefAs(RefAs* curr) {
      // TODO: support others
      assert(curr->op == RefAsNonNull);

      // value || trap()
      ABI::wasm2js::ensureHelpers(module, ABI::wasm2js::TRAP);
      return ValueBuilder::makeBinary(
        visit(curr->value, EXPRESSION_RESULT),
        IString("||"),
        ValueBuilder::makeCall(ABI::wasm2js::TRAP));
    }

    Ref visitContBind(ContBind* curr) {
      unimplemented(curr);
      WASM_UNREACHABLE("unimp");
    }
    Ref visitContNew(ContNew* curr) {
      unimplemented(curr);
      WASM_UNREACHABLE("unimp");
    }
    Ref visitResume(Resume* curr) {
      unimplemented(curr);
      WASM_UNREACHABLE("unimp");
    }
    Ref visitSuspend(Suspend* curr) {
      unimplemented(curr);
      WASM_UNREACHABLE("unimp");
    }

  private:
    Ref makePointer(Expression* ptr, Address offset) {
      auto ret = visit(ptr, EXPRESSION_RESULT);
      if (offset) {
        ret = makeJsCoercion(
          ValueBuilder::makeBinary(ret, PLUS, ValueBuilder::makeNum(offset)),
          JS_INT);
      }
      return ret;
    }

    void unimplemented(Expression* curr) {
      Fatal() << "wasm2js cannot convert " << *curr;
    }
  };

  return ExpressionProcessor(this, m, func, standaloneFunction).process(curr);
}

void Wasm2JSBuilder::addMemoryFuncs(Ref ast, Module* wasm) {
  Ref memorySizeFunc = ValueBuilder::makeFunction(WASM_MEMORY_SIZE);
  memorySizeFunc[3]->push_back(ValueBuilder::makeReturn(
    makeJsCoercion(ValueBuilder::makeBinary(
                     ValueBuilder::makeDot(ValueBuilder::makeName(BUFFER),
                                           IString("byteLength")),
                     DIV,
                     ValueBuilder::makeInt(Memory::kPageSize)),
                   JsType::JS_INT)));
  ast->push_back(memorySizeFunc);

  if (!wasm->memories.empty() &&
      wasm->memories[0]->max > wasm->memories[0]->initial) {
    addMemoryGrowFunc(ast, wasm);
  }
}

void Wasm2JSBuilder::addMemoryGrowFunc(Ref ast, Module* wasm) {
  Ref memoryGrowFunc = ValueBuilder::makeFunction(WASM_MEMORY_GROW);
  ValueBuilder::appendArgumentToFunction(memoryGrowFunc, IString("pagesToAdd"));

  memoryGrowFunc[3]->push_back(
    ValueBuilder::makeStatement(ValueBuilder::makeBinary(
      ValueBuilder::makeName(IString("pagesToAdd")),
      SET,
      makeJsCoercion(ValueBuilder::makeName(IString("pagesToAdd")),
                     JsType::JS_INT))));

  Ref oldPages = ValueBuilder::makeVar();
  memoryGrowFunc[3]->push_back(oldPages);
  ValueBuilder::appendToVar(
    oldPages,
    IString("oldPages"),
    makeJsCoercion(ValueBuilder::makeCall(WASM_MEMORY_SIZE), JsType::JS_INT));

  Ref newPages = ValueBuilder::makeVar();
  memoryGrowFunc[3]->push_back(newPages);
  ValueBuilder::appendToVar(
    newPages,
    IString("newPages"),
    makeJsCoercion(
      ValueBuilder::makeBinary(ValueBuilder::makeName(IString("oldPages")),
                               PLUS,
                               ValueBuilder::makeName(IString("pagesToAdd"))),
      JsType::JS_INT));

  Ref block = ValueBuilder::makeBlock();
  memoryGrowFunc[3]->push_back(ValueBuilder::makeIf(
    ValueBuilder::makeBinary(
      ValueBuilder::makeBinary(ValueBuilder::makeName(IString("oldPages")),
                               LT,
                               ValueBuilder::makeName(IString("newPages"))),
      IString("&&"),
      ValueBuilder::makeBinary(ValueBuilder::makeName(IString("newPages")),
                               LT,
                               ValueBuilder::makeInt(Memory::kMaxSize32))),
    block,
    NULL));

  Ref newBuffer = ValueBuilder::makeVar();
  ValueBuilder::appendToBlock(block, newBuffer);
  ValueBuilder::appendToVar(
    newBuffer,
    IString("newBuffer"),
    ValueBuilder::makeNew(ValueBuilder::makeCall(
      ARRAY_BUFFER,
      ValueBuilder::makeCall(MATH_IMUL,
                             ValueBuilder::makeName(IString("newPages")),
                             ValueBuilder::makeInt(Memory::kPageSize)))));

  Ref newHEAP8 = ValueBuilder::makeVar();
  ValueBuilder::appendToBlock(block, newHEAP8);
  ValueBuilder::appendToVar(newHEAP8,
                            IString("newHEAP8"),
                            ValueBuilder::makeNew(ValueBuilder::makeCall(
                              ValueBuilder::makeName(INT8ARRAY),
                              ValueBuilder::makeName(IString("newBuffer")))));

  ValueBuilder::appendToBlock(
    block,
    ValueBuilder::makeCall(
      ValueBuilder::makeDot(ValueBuilder::makeName(IString("newHEAP8")),
                            IString("set")),
      ValueBuilder::makeName(HEAP8)));

  auto setHeap = [&](IString name, IString view) {
    ValueBuilder::appendToBlock(
      block,
      ValueBuilder::makeBinary(
        ValueBuilder::makeName(name),
        SET,
        ValueBuilder::makeNew(ValueBuilder::makeCall(
          ValueBuilder::makeName(view),
          ValueBuilder::makeName(IString("newBuffer"))))));
  };

  setHeap(HEAP8, INT8ARRAY);
  setHeap(HEAP16, INT16ARRAY);
  setHeap(HEAP32, INT32ARRAY);
  setHeap(HEAPU8, UINT8ARRAY);
  setHeap(HEAPU16, UINT16ARRAY);
  setHeap(HEAPU32, UINT32ARRAY);
  setHeap(HEAPF32, FLOAT32ARRAY);
  setHeap(HEAPF64, FLOAT64ARRAY);

  ValueBuilder::appendToBlock(
    block,
    ValueBuilder::makeBinary(ValueBuilder::makeName(BUFFER),
                             SET,
                             ValueBuilder::makeName(IString("newBuffer"))));

  // apply the changes to the memory import
  if (!wasm->memories.empty() && wasm->memories[0]->imported()) {
    ValueBuilder::appendToBlock(
      block,
      ValueBuilder::makeBinary(
        ValueBuilder::makeDot(ValueBuilder::makeName("memory"),
                              ValueBuilder::makeName(BUFFER)),
        SET,
        ValueBuilder::makeName(BUFFER)));
  }

  if (needsBufferView(*wasm)) {
    ValueBuilder::appendToBlock(
      block,
      ValueBuilder::makeBinary(ValueBuilder::makeName("bufferView"),
                               SET,
                               ValueBuilder::makeName(HEAPU8)));
  }

  memoryGrowFunc[3]->push_back(
    ValueBuilder::makeReturn(ValueBuilder::makeName(IString("oldPages"))));

  ast->push_back(memoryGrowFunc);
}

// Wasm2JSBuilder emits the core of the module - the functions etc. that would
// be the asm.js function in an asm.js world. This class emits the rest of the
// "glue" around that.
class Wasm2JSGlue {
public:
  Wasm2JSGlue(Module& wasm,
              Output& out,
              Wasm2JSBuilder::Flags flags,
              Name moduleName)
    : wasm(wasm), out(out), flags(flags), moduleName(moduleName) {}

  void emitPre();
  void emitPost();

private:
  Module& wasm;
  Output& out;
  Wasm2JSBuilder::Flags flags;
  Name moduleName;

  void emitPreEmscripten();
  void emitPreES6();
  void emitPostEmscripten();
  void emitPostES6();

  void emitMemory();
  void emitSpecialSupport();
};

void Wasm2JSGlue::emitPre() {
  if (flags.emscripten) {
    emitPreEmscripten();
  } else {
    emitPreES6();
  }

  if (isTableExported(wasm)) {
    out << "function Table(ret) {\n";
    if (wasm.tables[0]->initial == wasm.tables[0]->max) {
      out << "  // grow method not included; table is not growable\n";
    } else {
      out << "  ret.grow = function(by) {\n"
          << "    var old = this.length;\n"
          << "    this.length = this.length + by;\n"
          << "    return old;\n"
          << "  };\n";
    }
    out << "  ret.set = function(i, func) {\n"
        << "    this[i] = func;\n"
        << "  };\n"
        << "  ret.get = function(i) {\n"
        << "    return this[i];\n"
        << "  };\n"
        << "  return ret;\n"
        << "}\n\n";
  }

  emitMemory();
  emitSpecialSupport();
}

void Wasm2JSGlue::emitPreEmscripten() {
  out << "function instantiate(info) {\n";
}

void Wasm2JSGlue::emitPreES6() {
  std::unordered_map<Name, Name> baseModuleMap;
  std::unordered_set<Name> seenModules;

  auto noteImport = [&](Name module, Name base) {
    // Right now codegen requires a flat namespace going into the module,
    // meaning we don't support importing the same name from multiple namespaces
    // yet.
    if (baseModuleMap.count(base) && baseModuleMap[base] != module) {
      Fatal() << "the name " << base << " cannot be imported from "
              << "two different modules yet";
    }
    baseModuleMap[base] = module;
    if (seenModules.count(module) == 0) {
      out << "import * as " << asmangle(module.toString()) << " from '"
          << module << "';\n";
      seenModules.insert(module);
    }
  };

  ImportInfo imports(wasm);

  ModuleUtils::iterImportedGlobals(
    wasm, [&](Global* import) { noteImport(import->module, import->base); });
  ModuleUtils::iterImportedTables(
    wasm, [&](Table* import) { noteImport(import->module, import->base); });
  ModuleUtils::iterImportedFunctions(wasm, [&](Function* import) {
    // The special helpers are emitted in the glue, see code and comments
    // below.
    if (ABI::wasm2js::isHelper(import->base)) {
      return;
    }
    noteImport(import->module, import->base);
  });

  out << '\n';
}

void Wasm2JSGlue::emitPost() {
  if (flags.emscripten) {
    emitPostEmscripten();
  } else {
    emitPostES6();
  }
}

void Wasm2JSGlue::emitPostEmscripten() {
  out << "  return asmFunc(info);\n}\n";
}

void Wasm2JSGlue::emitPostES6() {
  // Create an initial `ArrayBuffer` and populate it with static data.
  // Currently we use base64 encoding to encode static data and we decode it at
  // instantiation time.
  //
  // Note that the translation here expects that the lower values of this memory
  // can be used for conversions, so make sure there's at least one page.
  if (!wasm.memories.empty() && wasm.memories[0]->imported()) {
    out << "var mem" << moduleName.str << " = new ArrayBuffer("
        << wasm.memories[0]->initial.addr * Memory::kPageSize << ");\n";
  }

  // Actually invoke the `asmFunc` generated function, passing in all global
  // values followed by all imports
  out << "var ret" << moduleName.str << " = " << moduleName.str << "({\n";

  std::unordered_set<Name> seenModules;

  ModuleUtils::iterImportedFunctions(wasm, [&](Function* import) {
    // The special helpers are emitted in the glue, see code and comments
    // below.
    if (ABI::wasm2js::isHelper(import->base)) {
      return;
    }
    if (seenModules.count(import->module) > 0) {
      return;
    }
    out << "  \"" << import->module
        << "\": " << asmangle(import->module.toString()) << ",\n";
    seenModules.insert(import->module);
  });

  ModuleUtils::iterImportedMemories(wasm, [&](Memory* import) {
    // The special helpers are emitted in the glue, see code and comments
    // below.
    if (ABI::wasm2js::isHelper(import->base)) {
      return;
    }
    out << "  \"" << import->module << "\": {\n";
    out << "    " << asmangle(import->base.toString()) << ": { buffer : mem"
        << moduleName.str << " }\n";
    out << "  },\n";
  });

  ModuleUtils::iterImportedTables(wasm, [&](Table* import) {
    // The special helpers are emitted in the glue, see code and comments
    // below.
    if (ABI::wasm2js::isHelper(import->base)) {
      return;
    }
    if (seenModules.count(import->module) > 0) {
      return;
    }
    out << "  \"" << import->module
        << "\": " << asmangle(import->module.toString()) << ",\n";
    seenModules.insert(import->module);
  });

  out << "});\n";

  if (flags.allowAsserts) {
    return;
  }

  // And now that we have our returned instance, export all our functions
  // that are hanging off it.
  for (auto& exp : wasm.exports) {
    switch (exp->kind) {
      case ExternalKind::Function:
      case ExternalKind::Global:
      case ExternalKind::Memory:
        break;

      // Exported globals and function tables aren't supported yet
      default:
        continue;
    }
    std::ostringstream export_name;
    for (char c : exp->name.str) {
      if (c == '-') {
        export_name << '_';
      } else {
        export_name << c;
      }
    }
    out << "export var " << asmangle(exp->name.toString()) << " = ret"
        << moduleName << "." << asmangle(exp->name.toString()) << ";\n";
  }
}

void Wasm2JSGlue::emitMemory() {
  if (needsBufferView(wasm)) {
    // Create a helper bufferView to access the buffer if we need one. We use it
    // for creating memory segments if we have any (we may not if the segments
    // are shipped in a side .mem file, for example), and also in bulk memory
    // operations.
    // This will get assigned during `asmFunc` (and potentially re-assigned
    // during __wasm_memory_grow).
    // TODO: We should probably just share a single HEAPU8 var.
    out << "  var bufferView;\n";
  }

  // If there are no memory segments, we don't need to emit any support code for
  // segment creation.
  if (wasm.dataSegments.empty()) {
    return;
  }

  // If we have passive memory segments, we need to store those.
  for (auto& seg : wasm.dataSegments) {
    if (seg->isPassive) {
      out << "  var memorySegments = {};\n";
      break;
    }
  }

  out <<
    R"(  var base64ReverseLookup = new Uint8Array(123/*'z'+1*/);
  for (var i = 25; i >= 0; --i) {
    base64ReverseLookup[48+i] = 52+i; // '0-9'
    base64ReverseLookup[65+i] = i; // 'A-Z'
    base64ReverseLookup[97+i] = 26+i; // 'a-z'
  }
  base64ReverseLookup[43] = 62; // '+'
  base64ReverseLookup[47] = 63; // '/'
  /** @noinline Inlining this function would mean expanding the base64 string 4x times in the source code, which Closure seems to be happy to do. */
  function base64DecodeToExistingUint8Array(uint8Array, offset, b64) {
    var b1, b2, i = 0, j = offset, bLength = b64.length, end = offset + (bLength*3>>2) - (b64[bLength-2] == '=') - (b64[bLength-1] == '=');
    for (; i < bLength; i += 4) {
      b1 = base64ReverseLookup[b64.charCodeAt(i+1)];
      b2 = base64ReverseLookup[b64.charCodeAt(i+2)];
      uint8Array[j++] = base64ReverseLookup[b64.charCodeAt(i)] << 2 | b1 >> 4;
      if (j < end) uint8Array[j++] = b1 << 4 | b2 >> 2;
      if (j < end) uint8Array[j++] = b2 << 6 | base64ReverseLookup[b64.charCodeAt(i+3)];
    })";
  if (wasm.features.hasBulkMemory()) {
    // Passive segments in bulk memory are initialized into new arrays that are
    // passed into here, and we need to return them.
    out << R"(
    return uint8Array;)";
  }
  out << R"(
  }
)";

  for (Index i = 0; i < wasm.dataSegments.size(); i++) {
    auto& seg = wasm.dataSegments[i];
    if (seg->isPassive) {
      // Fancy passive segments are decoded into typed arrays on the side, for
      // later copying.
      out << "memorySegments[" << i
          << "] = base64DecodeToExistingUint8Array(new Uint8Array("
          << seg->data.size() << ")"
          << ", 0, \"" << base64Encode(seg->data) << "\");\n";
    }
  }

  if (hasActiveSegments(wasm)) {
    auto globalOffset = [&](const DataSegment& segment) {
      if (auto* c = segment.offset->dynCast<Const>()) {
        return std::to_string(c->value.getInteger());
      }
      if (auto* get = segment.offset->dynCast<GlobalGet>()) {
        auto internalName = get->name;
        auto importedGlobal = wasm.getGlobal(internalName);
        return std::string("imports['") + importedGlobal->module.toString() +
               "']['" + importedGlobal->base.toString() + "']";
      }
      Fatal() << "non-constant offsets aren't supported yet\n";
    };

    out << "function initActiveSegments(imports) {\n";
    for (Index i = 0; i < wasm.dataSegments.size(); i++) {
      auto& seg = wasm.dataSegments[i];
      if (!seg->isPassive) {
        // Plain active segments are decoded directly into the main memory.
        out << "  base64DecodeToExistingUint8Array(bufferView, "
            << globalOffset(*seg) << ", \"" << base64Encode(seg->data)
            << "\");\n";
      }
    }
    out << "}\n";
  }
}

void Wasm2JSGlue::emitSpecialSupport() {
  // The special support functions are emitted as part of the JS glue, if we
  // need them.
  bool need = false;
  bool needScratch = false;
  ModuleUtils::iterImportedFunctions(wasm, [&](Function* import) {
    if (ABI::wasm2js::isHelper(import->base)) {
      need = true;
    }
    if (import->base == ABI::wasm2js::SCRATCH_STORE_I32 ||
        import->base == ABI::wasm2js::SCRATCH_LOAD_I32 ||
        import->base == ABI::wasm2js::SCRATCH_STORE_F32 ||
        import->base == ABI::wasm2js::SCRATCH_LOAD_F32 ||
        import->base == ABI::wasm2js::SCRATCH_STORE_F64 ||
        import->base == ABI::wasm2js::SCRATCH_LOAD_F64) {
      needScratch = true;
    }
  });
  if (!need) {
    return;
  }

  // Scratch memory uses 3 indexes, each referring to 4 bytes. Indexes 0, 1 are
  // used for 64-bit operations, while 2 is for 32-bit. These operations need
  // separate indexes because we need to handle the case where the optimizer
  // reorders a 32-bit reinterpret in between a 64-bit's split-out parts.
  // That situation can occur because the 64-bit reinterpret was split up into
  // pieces early, in the 64-bit lowering pass, while the 32-bit reinterprets
  // are lowered only at the very end, and until then the optimizer sees wasm
  // reinterprets which have no side effects (but they will have the side effect
  // of altering scratch memory). That is, conceptual code like this:
  //
  //   a = reinterpret_64(b)
  //   x = reinterpret_32(y)
  //
  // turns into
  //
  //   scratch_write(b)
  //   a_low = scratch_read(0)
  //   a_high = scratch_read(1)
  //   x = reinterpret_32(y)
  //
  // (Note how the single wasm instruction for a 64-bit reinterpret turns into
  // multiple operations. We have to do such splitting, because in JS we will
  // have to have separate operations to receive each 32-bit chunk anyhow. A
  // *32*-bit reinterpret *could* be a single function, but given we have the
  // separate functions anyhow for the 64-bit case, it's more compact to reuse
  // those.)
  // At this point, the scratch_* functions look like they have side effects to
  // the optimizer (which is true, as they modify scratch memory), but the
  // reinterpret_32 is still a normal wasm instruction without side effects, so
  // the optimizer might do this:
  //
  //   scratch_write(b)
  //   a_low = scratch_read(0)
  //   x = reinterpret_32(y)    ;; this moved one line up
  //   a_high = scratch_read(1)
  //
  // When we do lower the reinterpret_32 into JS, we get:
  //
  //   scratch_write(b)
  //   a_low = scratch_read(0)
  //   scratch_write(y)
  //   x = scratch_read()
  //   a_high = scratch_read(1)
  //
  // The second write occurs before the first's values have been read, so they
  // interfere.
  //
  // There isn't a problem with reordering 32-bit reinterprets with each other
  // as each is lowered into a pair of write+read in JS (after the wasm
  // optimizer runs), so they are guaranteed to be adjacent (and a JS optimizer
  // that runs later will handle that ok since they are calls, which can always
  // have side effects).
  if (needScratch) {
    out << R"(
  var scratchBuffer = new ArrayBuffer(16);
  var i32ScratchView = new Int32Array(scratchBuffer);
  var f32ScratchView = new Float32Array(scratchBuffer);
  var f64ScratchView = new Float64Array(scratchBuffer);
  )";
  }

  ModuleUtils::iterImportedFunctions(wasm, [&](Function* import) {
    if (!ABI::wasm2js::isHelper(import->base)) {
      return;
    }
    if (import->base == ABI::wasm2js::SCRATCH_STORE_I32) {
      out << R"(
  function wasm2js_scratch_store_i32(index, value) {
    i32ScratchView[index] = value;
  }
      )";
    } else if (import->base == ABI::wasm2js::SCRATCH_LOAD_I32) {
      out << R"(
  function wasm2js_scratch_load_i32(index) {
    return i32ScratchView[index];
  }
      )";
    } else if (import->base == ABI::wasm2js::SCRATCH_STORE_F32) {
      out << R"(
  function wasm2js_scratch_store_f32(value) {
    f32ScratchView[2] = value;
  }
      )";
    } else if (import->base == ABI::wasm2js::SCRATCH_LOAD_F32) {
      out << R"(
  function wasm2js_scratch_load_f32() {
    return f32ScratchView[2];
  }
      )";
    } else if (import->base == ABI::wasm2js::SCRATCH_STORE_F64) {
      out << R"(
  function wasm2js_scratch_store_f64(value) {
    f64ScratchView[0] = value;
  }
      )";
    } else if (import->base == ABI::wasm2js::SCRATCH_LOAD_F64) {
      out << R"(
  function wasm2js_scratch_load_f64() {
    return f64ScratchView[0];
  }
      )";
    } else if (import->base == ABI::wasm2js::MEMORY_INIT) {
      out << R"(
  function wasm2js_memory_init(segment, dest, offset, size) {
    // TODO: traps on invalid things
    bufferView.set(memorySegments[segment].subarray(offset, offset + size), dest);
  }
      )";
    } else if (import->base == ABI::wasm2js::MEMORY_FILL) {
      out << R"(
  function wasm2js_memory_fill(dest, value, size) {
    dest = dest >>> 0;
    size = size >>> 0;
    if (dest + size > bufferView.length) throw "trap: invalid memory.fill";
    bufferView.fill(value, dest, dest + size);
  }
      )";
    } else if (import->base == ABI::wasm2js::MEMORY_COPY) {
      out << R"(
  function wasm2js_memory_copy(dest, source, size) {
    // TODO: traps on invalid things
    bufferView.copyWithin(dest, source, source + size);
  }
      )";
    } else if (import->base == ABI::wasm2js::TABLE_GROW) {
      out << R"(
  function wasm2js_table_grow(value, delta) {
    // TODO: traps on invalid things
    var oldSize = FUNCTION_TABLE.length;
    FUNCTION_TABLE.length = oldSize + delta;
    if (newSize > oldSize) {
      __wasm_table_fill(oldSize, value, delta)
    }
    return oldSize;
  }
      )";
    } else if (import->base == ABI::wasm2js::TABLE_FILL) {
      out << R"(
  function __wasm_table_fill(dest, value, size) {
    // TODO: traps on invalid things
    for (var i = 0; i < size; i++) {
      FUNCTION_TABLE[dest + i] = value;
    }
  }
      )";
    } else if (import->base == ABI::wasm2js::TABLE_COPY) {
      out << R"(
  function __wasm_table_copy(dest, source, size) {
    // TODO: traps on invalid things
    for (var i = 0; i < size; i++) {
      FUNCTION_TABLE[dest + i] = FUNCTION_TABLE[source + i];
    }
  }
      )";
    } else if (import->base == ABI::wasm2js::DATA_DROP) {
      out << R"(
  function wasm2js_data_drop(segment) {
    // TODO: traps on invalid things
    memorySegments[segment] = new Uint8Array(0);
  }
      )";
    } else if (import->base == ABI::wasm2js::ATOMIC_WAIT_I32) {
      out << R"(
  function wasm2js_atomic_wait_i32(offset, ptr, expected, timeoutLow, timeoutHigh) {
    ptr = (ptr + offset) >> 2;
    var timeout = Infinity;
    if (timeoutHigh >= 0) {
      // Convert from nanoseconds to milliseconds
      // Taken from convertI32PairToI53 in emscripten's library_int53.js
      timeout = ((timeoutLow >>> 0) / 1e6) + timeoutHigh * (4294967296 / 1e6);
    }
    var view = new Int32Array(bufferView.buffer); // TODO cache
    var result = Atomics.wait(view, ptr, expected, timeout);
    if (result == 'ok') return 0;
    if (result == 'not-equal') return 1;
    if (result == 'timed-out') return 2;
    throw 'bad result ' + result;
  }
      )";
    } else if (import->base == ABI::wasm2js::ATOMIC_RMW_I64) {
      out << R"(
  function wasm2js_atomic_rmw_i64(op, bytes, offset, ptr, valueLow, valueHigh) {
    // TODO: support bytes=1, 2, 4 as well as 8.
    var view = new BigInt64Array(bufferView.buffer); // TODO cache
    ptr = (ptr + offset) >> 3;
    var value = BigInt(valueLow >>> 0) | (BigInt(valueHigh >>> 0) << BigInt(32));
    var result;
    switch (op) {
      case 0: { // Add
        result = Atomics.add(view, ptr, value);
        break;
      }
      case 1: { // Sub
        result = Atomics.sub(view, ptr, value);
        break;
      }
      case 2: { // And
        result = Atomics.and(view, ptr, value);
        break;
      }
      case 3: { // Or
        result = Atomics.or(view, ptr, value);
        break;
      }
      case 4: { // Xor
        result = Atomics.xor(view, ptr, value);
        break;
      }
      case 5: { // Xchg
        result = Atomics.exchange(view, ptr, value);
        break;
      }
      default: throw 'bad op';
    }
    var low = Number(result & BigInt(0xffffffff)) | 0;
    var high = Number((result >> BigInt(32)) & BigInt(0xffffffff)) | 0;
    stashedBits = high;
    return low;
  }
      )";
    } else if (import->base == ABI::wasm2js::GET_STASHED_BITS) {
      out << R"(
  var stashedBits = 0;

  function wasm2js_get_stashed_bits() {
    return stashedBits;
  }
      )";
    } else if (import->base == ABI::wasm2js::TRAP) {
      out << "function wasm2js_trap() { throw new Error('abort'); }\n";
    } else {
      WASM_UNREACHABLE("bad helper function");
    }
  });

  out << '\n';
}

} // namespace wasm

#endif // wasm_wasm2js_h
