Initial Subzero WASM prototype.

BUG=
R=stichnot@chromium.org

Review URL: https://codereview.chromium.org/1837663002 .
diff --git a/Makefile.standalone b/Makefile.standalone
index 388a1be..208c220 100644
--- a/Makefile.standalone
+++ b/Makefile.standalone
@@ -179,6 +179,9 @@
 SB_OBJDIR := $(OBJDIR)+Sandboxed
 SBB_OBJDIR := $(OBJDIR)+SandboxedBrowser
 
+V8_DIR = $(NACL_ROOT)/../v8
+V8_CXXFLAGS := -I$(V8_DIR)
+
 $(info -----------------------------------------------)
 $(info Using LLVM_SRC_PATH = $(LLVM_SRC_PATH))
 $(info Using SB_LLVM_PATH = $(SB_LLVM_PATH))
@@ -251,10 +254,19 @@
   OBJDIR := $(OBJDIR)+Gplusplus
 endif
 
-BASE_CXXFLAGS := -std=gnu++11 -Wall -Wextra -Werror -fno-rtti \
+BASE_CXXFLAGS := -std=gnu++11 -Wall -Wextra -fno-rtti \
   -fno-exceptions $(OPTLEVEL) $(ASSERTIONS) -g -pedantic \
   $(LLVM_EXTRA_WARNINGS) $(CXX_EXTRA) -MP -MD
 
+ifdef WASM
+  # The WASM code inherits a lot of V8 code, which does not compile with
+  # -Werror.
+  BASE_CXXFLAGS := $(BASE_CXXFLAGS) $(V8_CXXFLAGS) -DALLOW_WASM=1
+  OBJDIR := $(OBJDIR)+Wasm
+else
+  BASE_CXXFLAGS := $(BASE_CXXFLAGS) -Werror -DALLOW_WASM=0
+endif
+
 CXXFLAGS := $(LLVM_CXXFLAGS) $(BASE_CXXFLAGS) $(CXX_DEFINES) $(HOST_FLAGS) \
   $(STDLIB_FLAGS)
 SB_CXXFLAGS := $(SB_LLVM_CXXFLAGS) $(BASE_CXXFLAGS) $(BASE_CXX_DEFINES) \
@@ -315,6 +327,11 @@
     IceTypeConverter.cpp
 endif
 
+ifdef WASM
+  SRCS += \
+    WasmTranslator.cpp
+endif
+
 OBJS=$(patsubst %.cpp, $(OBJDIR)/%.o, $(SRCS))
 SB_OBJS=$(patsubst %.cpp, $(SB_OBJDIR)/%.o, $(SRCS))
 SBB_OBJS=$(patsubst %.cpp, $(SBB_OBJDIR)/%.o, $(SRCS))
@@ -392,9 +409,18 @@
 
 compile_only: $(OBJS)
 
+V8_LIBDIR=$(V8_DIR)/out/native/lib.target
+
+ifdef WASM
+  V8_LIBS := \
+    $(V8_LIBDIR)/libv8.so \
+    $(V8_LIBDIR)/libicuuc.so \
+    $(V8_LIBDIR)/libicui18n.so
+endif
+
 $(OBJDIR)/pnacl-sz: $(OBJS)
 	$(CXX) $(LDFLAGS) -o $@ $^ $(LLVM_LDFLAGS) \
-          -Wl,-rpath=$(abspath $(LIBCXX_INSTALL_PATH)/lib)
+          -Wl,-rpath=$(abspath $(LIBCXX_INSTALL_PATH)/lib) $(V8_LIBS)
 
 $(SB_OBJDIR)/pnacl-sz.nonfinal.pexe: $(SB_OBJS)
 	$(SB_CXX) $(SB_LDFLAGS) -o $@ $^ $(SB_LLVM_LDFLAGS)
@@ -713,6 +739,7 @@
 FORMAT_BLACKLIST += ! -name IceParseTypesTest.cpp
 FORMAT_BLACKLIST += ! -name assembler_arm.h
 FORMAT_BLACKLIST += ! -name assembler_arm.cc
+FORMAT_BLACKLIST += ! -path "./wasm-install/*"
 format:
 	$(CLANG_FORMAT_PATH)/clang-format -style=LLVM -i \
           `find . -regex '.*\.\(c\|h\|cpp\)' $(FORMAT_BLACKLIST)`
diff --git a/README-wasm.md b/README-wasm.md
new file mode 100644
index 0000000..ef8d7be
--- /dev/null
+++ b/README-wasm.md
@@ -0,0 +1,23 @@
+# Wasm Prototype Experiment Notes
+
+Here's the command I use to test:
+
+```
+LD_LIBRARY_PATH=~/nacl/v8/out/native/lib.target make -j48 \
+    -f Makefile.standalone WASM=1 && \
+LD_LIBRARY_PATH=~/nacl/v8/out/native/lib.target ./pnacl-sz -O2 -filetype=asm \
+    -target=arm32 ./torture-s2wasm-sexpr-wasm/20000112-1.c.s.wast.wasm
+```
+
+You'll probably need to adjust your `LD_LIBRARY_PATH` to point to where your v8
+libraries are.
+
+You'll need to build v8 as a shared library. Build it like this:
+
+```
+make -j48 native component=shared_library
+```
+
+`wasm-run-torture-tests.py` can be used to run all the tests, or some subset.
+Running a subset will enable verbose output. You can download the torture tests
+from the [WebAssembly waterfall](https://wasm-stat.us/console).
diff --git a/pydir/wasm-run-torture-tests.py b/pydir/wasm-run-torture-tests.py
new file mode 100755
index 0000000..de0bb21
--- /dev/null
+++ b/pydir/wasm-run-torture-tests.py
@@ -0,0 +1,57 @@
+#!/usr/bin/env python2
+
+#===- subzero/wasm-run-torture-tests.py - Subzero WASM Torture Test Driver ===//
+#
+#                        The Subzero Code Generator
+#
+# This file is distributed under the University of Illinois Open Source
+# License. See LICENSE.TXT for details.
+#
+#===-----------------------------------------------------------------------===//
+
+import glob
+import os
+import sys
+
+success_count = 0
+fail_count = 0
+failures = []
+
+def run_test(test_file, verbose=False):
+  global success_count
+  global fail_count
+
+  cmd = """LD_LIBRARY_PATH=../../../../v8/out/native/lib.target ./pnacl-sz \
+               -filetype=asm -target=arm32 {} -threads=0 -O2 \
+               -verbose=wasm""".format(test_file)
+
+  if not verbose:
+    cmd += " &> /dev/null"
+
+  sys.stdout.write(test_file + "...");
+  status = os.system(cmd);
+  if status != 0:
+    fail_count += 1
+    print('\033[1;31m[fail]\033[1;m')
+    failures.append(test_file)
+  else:
+    success_count += 1
+    print('\033[1;32m[ok]\033[1;m')
+
+
+verbose = False
+
+if len(sys.argv) > 1:
+  test_files = sys.argv[1:]
+  verbose = True
+else:
+  test_files = glob.glob("./torture-s2wasm-sexpr-wasm.old/*.wasm")
+
+for test_file in test_files:
+  run_test(test_file, verbose)
+
+if len(failures) > 0:
+  print("Failures:")
+  for f in failures:
+    print("  \033[1;31m" + f + "\033[1;m")
+print("{} / {} tests passed".format(success_count, success_count + fail_count))
diff --git a/src/IceBuildDefs.h b/src/IceBuildDefs.h
index 3b32cec..8490c55 100644
--- a/src/IceBuildDefs.h
+++ b/src/IceBuildDefs.h
@@ -90,6 +90,8 @@
 constexpr bool llvmIrAsInput() { return ALLOW_LLVM_IR_AS_INPUT; }
 /// Return true if ALLOW_MINIMAL_BUILD is defined as a non-zero value
 constexpr bool minimal() { return ALLOW_MINIMAL_BUILD; }
+/// Return true if ALLOW_WASM is defined as a non-zero value
+constexpr bool wasm() { return ALLOW_WASM; }
 
 /// Return true if NDEBUG is defined
 constexpr bool asserts() {
diff --git a/src/IceCfg.cpp b/src/IceCfg.cpp
index 79dfd76..e2bee72 100644
--- a/src/IceCfg.cpp
+++ b/src/IceCfg.cpp
@@ -245,6 +245,22 @@
   }
 }
 
+void Cfg::fixPhiNodes() {
+  for (auto *Node : Nodes) {
+    // Fix all the phi edges since WASM can't tell how to make them correctly at
+    // the beginning.
+    assert(Node);
+    const auto &InEdges = Node->getInEdges();
+    for (auto &Instr : Node->getPhis()) {
+      auto *Phi = llvm::cast<InstPhi>(&Instr);
+      assert(Phi);
+      for (SizeT i = 0; i < InEdges.size(); ++i) {
+        Phi->setLabel(i, InEdges[i]);
+      }
+    }
+  }
+}
+
 void Cfg::computeInOutEdges() {
   // Compute the out-edges.
   for (CfgNode *Node : Nodes)
@@ -402,7 +418,7 @@
       // in-edge if the predecessor node was contracted). If this changes in
       // the future, rethink the strategy.
       assert(Node->getInEdges().size() >= 1);
-      assert(Node->getOutEdges().size() == 1);
+      assert(Node->hasSingleOutEdge());
 
       // If it's a (non-critical) edge where the successor has a single
       // in-edge, then place it before the successor.
diff --git a/src/IceCfg.h b/src/IceCfg.h
index f9682e0..abb6722 100644
--- a/src/IceCfg.h
+++ b/src/IceCfg.h
@@ -256,6 +256,13 @@
     Allocator->Deallocate(Array);
   }
 
+  /// Update Phi labels with InEdges.
+  ///
+  /// The WASM translator cannot always determine the right incoming edge for a
+  /// value due to the CFG being built incrementally. The fixPhiNodes pass fills
+  /// in the correct information once everything is known.
+  void fixPhiNodes();
+
 private:
   friend class CfgAllocatorTraits; // for Allocator access.
 
diff --git a/src/IceCfgNode.cpp b/src/IceCfgNode.cpp
index 9c59dac..4b5268a 100644
--- a/src/IceCfgNode.cpp
+++ b/src/IceCfgNode.cpp
@@ -37,10 +37,20 @@
 
 // Adds an instruction to either the Phi list or the regular instruction list.
 // Validates that all Phis are added before all regular instructions.
-void CfgNode::appendInst(Inst *Instr) {
+void CfgNode::appendInst(Inst *Instr, bool AllowPhisAnywhere) {
   ++InstCountEstimate;
+
+  if (BuildDefs::wasm()) {
+    if (llvm::isa<InstSwitch>(Instr) || llvm::isa<InstBr>(Instr)) {
+      for (auto *N : Instr->getTerminatorEdges()) {
+        N->addInEdge(this);
+        addOutEdge(N);
+      }
+    }
+  }
+
   if (auto *Phi = llvm::dyn_cast<InstPhi>(Instr)) {
-    if (!Insts.empty()) {
+    if (!AllowPhisAnywhere && !Insts.empty()) {
       Func->setError("Phi instruction added to the middle of a block");
       return;
     }
@@ -81,6 +91,8 @@
 }
 
 void CfgNode::computeSuccessors() {
+  OutEdges.clear();
+  InEdges.clear();
   OutEdges = Insts.rbegin()->getTerminatorEdges();
 }
 
@@ -889,7 +901,7 @@
   // Make sure there is actually a successor to repoint in-edges to.
   if (OutEdges.empty())
     return;
-  assert(OutEdges.size() == 1);
+  assert(hasSingleOutEdge());
   // Don't try to delete a self-loop.
   if (OutEdges[0] == this)
     return;
diff --git a/src/IceCfgNode.h b/src/IceCfgNode.h
index f0633df..cf3e83c 100644
--- a/src/IceCfgNode.h
+++ b/src/IceCfgNode.h
@@ -72,7 +72,7 @@
   /// @{
   InstList &getInsts() { return Insts; }
   PhiList &getPhis() { return Phis; }
-  void appendInst(Inst *Instr);
+  void appendInst(Inst *Instr, bool AllowPhisAnywhere = false);
   void renumberInstructions();
   /// Rough and generally conservative estimate of the number of instructions in
   /// the block. It is updated when an instruction is added, but not when
@@ -110,6 +110,13 @@
 
   void profileExecutionCount(VariableDeclaration *Var);
 
+  void addOutEdge(CfgNode *Out) { OutEdges.push_back(Out); }
+  void addInEdge(CfgNode *In) { InEdges.push_back(In); }
+
+  bool hasSingleOutEdge() const {
+    return (getOutEdges().size() == 1 || getOutEdges()[0] == getOutEdges()[1]);
+  }
+
 private:
   CfgNode(Cfg *Func, SizeT Number);
   bool livenessValidateIntervals(Liveness *Liveness) const;
diff --git a/src/IceClFlags.def b/src/IceClFlags.def
index c46697d..58f1777 100644
--- a/src/IceClFlags.def
+++ b/src/IceClFlags.def
@@ -329,6 +329,7 @@
                    "Global initializers"),                                     \
         clEnumValN(Ice::IceV_ConstPoolStats, "cpool",                          \
                    "Constant pool counters"),                                  \
+        clEnumValN(Ice::IceV_Wasm, "wasm", "WebAssembly builder"),             \
         clEnumValN(Ice::IceV_All, "all", "Use all verbose options"),           \
         clEnumValN(Ice::IceV_Most, "most",                                     \
                    "Use all verbose options except 'regalloc,global_init'"),   \
diff --git a/src/IceCompiler.cpp b/src/IceCompiler.cpp
index 3120bea..6a853d0 100644
--- a/src/IceCompiler.cpp
+++ b/src/IceCompiler.cpp
@@ -25,6 +25,7 @@
 #include "IceConverter.h"
 #include "IceELFObjectWriter.h"
 #include "PNaClTranslator.h"
+#include "WasmTranslator.h"
 
 #ifdef __clang__
 #pragma clang diagnostic push
@@ -54,6 +55,11 @@
          std::regex_match(Filename, std::regex(".*\\.ll"));
 }
 
+bool wasmInput(const std::string &Filename) {
+  return BuildDefs::llvmIrAsInput() &&
+         std::regex_match(Filename, std::regex(".*\\.wasm"));
+}
+
 } // end of anonymous namespace
 
 void Compiler::run(const Ice::ClFlags &Flags, GlobalContext &Ctx,
@@ -75,13 +81,27 @@
 
   std::unique_ptr<Translator> Translator;
   const std::string IRFilename = Flags.getIRFilename();
-  const bool BuildOnRead = Flags.getBuildOnRead() && !llvmIRInput(IRFilename);
+  const bool BuildOnRead = Flags.getBuildOnRead() && !llvmIRInput(IRFilename) &&
+                           !wasmInput(IRFilename);
+  const bool WasmBuildOnRead = Flags.getBuildOnRead() && wasmInput(IRFilename);
   if (BuildOnRead) {
     std::unique_ptr<PNaClTranslator> PTranslator(new PNaClTranslator(&Ctx));
     std::unique_ptr<llvm::StreamingMemoryObject> MemObj(
         new llvm::StreamingMemoryObjectImpl(InputStream.release()));
     PTranslator->translate(IRFilename, std::move(MemObj));
     Translator.reset(PTranslator.release());
+  } else if (WasmBuildOnRead) {
+    if (BuildDefs::wasm()) {
+      std::unique_ptr<WasmTranslator> WTranslator(new WasmTranslator(&Ctx));
+
+      WTranslator->translate(IRFilename, std::move(InputStream));
+
+      Translator.reset(WTranslator.release());
+    } else {
+      Ctx.getStrError() << "WASM support not enabled\n";
+      Ctx.getErrorStatus()->assign(EC_Args);
+      return;
+    }
   } else if (BuildDefs::llvmIr()) {
     if (BuildDefs::browser()) {
       Ctx.getStrError()
diff --git a/src/IceDefs.h b/src/IceDefs.h
index 8e8a064..e555412 100644
--- a/src/IceDefs.h
+++ b/src/IceDefs.h
@@ -341,6 +341,7 @@
   IceV_AvailableRegs = 1 << 21,
   IceV_GlobalInit = 1 << 22,
   IceV_ConstPoolStats = 1 << 23,
+  IceV_Wasm = 1 << 24,
   IceV_All = ~IceV_None,
   IceV_Most =
       IceV_All & ~IceV_LinearScan & ~IceV_GlobalInit & ~IceV_ConstPoolStats
diff --git a/src/IceInst.h b/src/IceInst.h
index c72af35..dfb873c 100644
--- a/src/IceInst.h
+++ b/src/IceInst.h
@@ -624,6 +624,7 @@
   Operand *getOperandForTarget(CfgNode *Target) const;
   void clearOperandForTarget(CfgNode *Target);
   CfgNode *getLabel(SizeT Index) const { return Labels[Index]; }
+  void setLabel(SizeT Index, CfgNode *Label) { Labels[Index] = Label; }
   void livenessPhiOperand(LivenessBV &Live, CfgNode *Target,
                           Liveness *Liveness);
   Inst *lower(Cfg *Func);
diff --git a/src/IceTranslator.cpp b/src/IceTranslator.cpp
index b58573a..1211510 100644
--- a/src/IceTranslator.cpp
+++ b/src/IceTranslator.cpp
@@ -24,20 +24,6 @@
 
 namespace Ice {
 
-class CfgOptWorkItem final : public OptWorkItem {
-  CfgOptWorkItem() = delete;
-  CfgOptWorkItem(const CfgOptWorkItem &) = delete;
-  CfgOptWorkItem &operator=(const CfgOptWorkItem &) = delete;
-
-public:
-  CfgOptWorkItem(std::unique_ptr<Cfg> Func) : Func(std::move(Func)) {}
-  std::unique_ptr<Cfg> getParsedCfg() override { return std::move(Func); }
-  ~CfgOptWorkItem() override = default;
-
-private:
-  std::unique_ptr<Ice::Cfg> Func;
-};
-
 Translator::Translator(GlobalContext *Ctx)
     : Ctx(Ctx), NextSequenceNumber(GlobalContext::getFirstSequenceNumber()),
       ErrorStatus() {}
diff --git a/src/IceTranslator.h b/src/IceTranslator.h
index 1f7b358..05601b2 100644
--- a/src/IceTranslator.h
+++ b/src/IceTranslator.h
@@ -74,6 +74,20 @@
   ErrorCode ErrorStatus;
 };
 
+class CfgOptWorkItem final : public OptWorkItem {
+  CfgOptWorkItem() = delete;
+  CfgOptWorkItem(const CfgOptWorkItem &) = delete;
+  CfgOptWorkItem &operator=(const CfgOptWorkItem &) = delete;
+
+public:
+  CfgOptWorkItem(std::unique_ptr<Cfg> Func) : Func(std::move(Func)) {}
+  std::unique_ptr<Cfg> getParsedCfg() override { return std::move(Func); }
+  ~CfgOptWorkItem() override = default;
+
+private:
+  std::unique_ptr<Ice::Cfg> Func;
+};
+
 } // end of namespace Ice
 
 #endif // SUBZERO_SRC_ICETRANSLATOR_H
diff --git a/src/WasmTranslator.cpp b/src/WasmTranslator.cpp
new file mode 100644
index 0000000..4773526
--- /dev/null
+++ b/src/WasmTranslator.cpp
@@ -0,0 +1,815 @@
+//===- subzero/src/WasmTranslator.cpp - WASM to Subzero Translation -------===//
+//
+//                        The Subzero Code Generator
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// \brief Defines a driver for translating Wasm bitcode into PNaCl bitcode.
+///
+/// The translator uses V8's WebAssembly decoder to handle the binary Wasm
+/// format but replaces the usual TurboFan builder with a new PNaCl builder.
+///
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Support/StreamingMemoryObject.h"
+
+#include "WasmTranslator.h"
+
+#include "src/wasm/module-decoder.h"
+#include "src/wasm/wasm-opcodes.h"
+#include "src/zone.h"
+
+#include "IceCfgNode.h"
+#include "IceGlobalInits.h"
+
+using namespace std;
+using namespace Ice;
+using namespace v8;
+using namespace v8::internal;
+using namespace v8::internal::wasm;
+using v8::internal::wasm::DecodeWasmModule;
+
+#include "src/wasm/ast-decoder-impl.h"
+
+#define LOG(Expr) log([&](Ostream & out) { Expr; })
+
+namespace {
+
+Ice::Type toIceType(v8::internal::MachineType) {
+  // TODO(eholk): actually convert this.
+  return IceType_i32;
+}
+
+Ice::Type toIceType(wasm::LocalType Type) {
+  switch (Type) {
+  default:
+    llvm::report_fatal_error("unexpected enum value");
+  case MachineRepresentation::kNone:
+    llvm::report_fatal_error("kNone type not supported");
+  case MachineRepresentation::kBit:
+    return IceType_i1;
+  case MachineRepresentation::kWord8:
+    return IceType_i8;
+  case MachineRepresentation::kWord16:
+    return IceType_i16;
+  case MachineRepresentation::kWord32:
+    return IceType_i32;
+  case MachineRepresentation::kWord64:
+    return IceType_i64;
+  case MachineRepresentation::kFloat32:
+    return IceType_f32;
+  case MachineRepresentation::kFloat64:
+    return IceType_f64;
+  case MachineRepresentation::kSimd128:
+    llvm::report_fatal_error("ambiguous SIMD type");
+  case MachineRepresentation::kTagged:
+    llvm::report_fatal_error("kTagged type not supported");
+  }
+}
+
+} // end of anonymous namespace
+
+/// This class wraps either an Operand or a CfgNode.
+///
+/// Turbofan's sea of nodes representation only has nodes for values, control
+/// flow, etc. In Subzero these concepts are all separate. This class lets V8's
+/// Wasm decoder treat Subzero objects as though they are all the same.
+class OperandNode {
+  static constexpr uintptr_t NODE_FLAG = 1;
+  static constexpr uintptr_t UNDEF_PTR = (uintptr_t)-1;
+
+  uintptr_t Data = UNDEF_PTR;
+
+public:
+  OperandNode() = default;
+  explicit OperandNode(Operand *Operand)
+      : Data(reinterpret_cast<uintptr_t>(Operand)) {}
+  explicit OperandNode(CfgNode *Node)
+      : Data(reinterpret_cast<uintptr_t>(Node) | NODE_FLAG) {}
+  explicit OperandNode(nullptr_t) : Data(UNDEF_PTR) {}
+
+  operator Operand *() const {
+    if (UNDEF_PTR == Data) {
+      return nullptr;
+    }
+    if (!isOperand()) {
+      llvm::report_fatal_error("This OperandNode is not an Operand");
+    }
+    return reinterpret_cast<Operand *>(Data);
+  }
+
+  operator CfgNode *() const {
+    if (UNDEF_PTR == Data) {
+      return nullptr;
+    }
+    if (!isCfgNode()) {
+      llvm::report_fatal_error("This OperandNode is not a CfgNode");
+    }
+    return reinterpret_cast<CfgNode *>(Data & ~NODE_FLAG);
+  }
+
+  explicit operator bool() const { return (Data != UNDEF_PTR) && Data; }
+  bool operator==(const OperandNode &Rhs) const {
+    return (Data == Rhs.Data) ||
+           (UNDEF_PTR == Data && (Rhs.Data == 0 || Rhs.Data == NODE_FLAG)) ||
+           (UNDEF_PTR == Rhs.Data && (Data == 0 || Data == NODE_FLAG));
+  }
+  bool operator!=(const OperandNode &Rhs) const { return !(*this == Rhs); }
+
+  bool isOperand() const { return (Data != UNDEF_PTR) && !(Data & NODE_FLAG); }
+  bool isCfgNode() const { return (Data != UNDEF_PTR) && (Data & NODE_FLAG); }
+
+  Operand *toOperand() const { return static_cast<Operand *>(*this); }
+
+  CfgNode *toCfgNode() const { return static_cast<CfgNode *>(*this); }
+};
+
+Ostream &operator<<(Ostream &Out, const OperandNode &Op) {
+  if (Op.isOperand()) {
+    Out << "(Operand*)" << Op.toOperand();
+  } else if (Op.isCfgNode()) {
+    Out << "(CfgNode*)" << Op.toCfgNode();
+  } else {
+    Out << "nullptr";
+  }
+  return Out;
+}
+
+constexpr bool isComparison(wasm::WasmOpcode Opcode) {
+  switch (Opcode) {
+  case kExprI32Ne:
+  case kExprI64Ne:
+  case kExprI32Eq:
+  case kExprI64Eq:
+  case kExprI32LtS:
+  case kExprI64LtS:
+  case kExprI32LtU:
+  case kExprI64LtU:
+  case kExprI32GeS:
+  case kExprI64GeS:
+  case kExprI32GtS:
+  case kExprI64GtS:
+  case kExprI32GtU:
+  case kExprI64GtU:
+    return true;
+  default:
+    return false;
+  }
+}
+
+class IceBuilder {
+  using Node = OperandNode;
+
+  IceBuilder() = delete;
+  IceBuilder(const IceBuilder &) = delete;
+  IceBuilder &operator=(const IceBuilder &) = delete;
+
+public:
+  explicit IceBuilder(class Cfg *Func)
+      : Func(Func), Ctx(Func->getContext()), ControlPtr(nullptr) {}
+
+  /// Allocates a buffer of Nodes for use by V8.
+  Node *Buffer(size_t Count) {
+    LOG(out << "Buffer(" << Count << ")\n");
+    return Func->allocateArrayOf<Node>(Count);
+  }
+
+  Node Error() { llvm::report_fatal_error("Error"); }
+  Node Start(unsigned Params) {
+    LOG(out << "Start(" << Params << ") = ");
+    auto *Entry = Func->makeNode();
+    Func->setEntryNode(Entry);
+    LOG(out << Node(Entry) << "\n");
+    return OperandNode(Entry);
+  }
+  Node Param(unsigned Index, wasm::LocalType Type) {
+    LOG(out << "Param(" << Index << ") = ");
+    auto *Arg = makeVariable(toIceType(Type));
+    assert(Index == NextArg);
+    Func->addArg(Arg);
+    ++NextArg;
+    LOG(out << Node(Arg) << "\n");
+    return OperandNode(Arg);
+  }
+  Node Loop(CfgNode *Entry) {
+    auto *Loop = Func->makeNode();
+    LOG(out << "Loop(" << Entry << ") = " << Loop << "\n");
+    Entry->appendInst(InstBr::create(Func, Loop));
+    return OperandNode(Loop);
+  }
+  void Terminate(Node Effect, Node Control) {
+    // TODO(eholk): this is almost certainly wrong
+    LOG(out << "Terminate(" << Effect << ", " << Control << ")"
+            << "\n");
+  }
+  Node Merge(unsigned Count, Node *Controls) {
+    LOG(out << "Merge(" << Count);
+    for (unsigned i = 0; i < Count; ++i) {
+      LOG(out << ", " << Controls[i]);
+    }
+    LOG(out << ") = ");
+
+    auto *MergedNode = Func->makeNode();
+
+    for (unsigned i = 0; i < Count; ++i) {
+      CfgNode *Control = Controls[i];
+      Control->appendInst(InstBr::create(Func, MergedNode));
+    }
+    LOG(out << (OperandNode)MergedNode << "\n");
+    return OperandNode(MergedNode);
+  }
+  Node Phi(wasm::LocalType Type, unsigned Count, Node *Vals, Node Control) {
+    LOG(out << "Phi(" << Count << ", " << Control);
+    for (int i = 0; i < Count; ++i) {
+      LOG(out << ", " << Vals[i]);
+    }
+    LOG(out << ") = ");
+
+    const auto &InEdges = Control.toCfgNode()->getInEdges();
+    assert(Count == InEdges.size());
+
+    assert(Count > 0);
+
+    auto *Dest = makeVariable(Vals[0].toOperand()->getType(), Control);
+
+    // Multiply by 10 in case more things get added later.
+
+    // TODO(eholk): find a better way besides multiplying by some arbitrary
+    // constant.
+    auto *Phi = InstPhi::create(Func, Count * 10, Dest);
+    for (int i = 0; i < Count; ++i) {
+      auto *Op = Vals[i].toOperand();
+      assert(Op);
+      Phi->addArgument(Op, InEdges[i]);
+    }
+    setDefiningInst(Dest, Phi);
+    Control.toCfgNode()->appendInst(Phi);
+    LOG(out << Node(Dest) << "\n");
+    return OperandNode(Dest);
+  }
+  Node EffectPhi(unsigned Count, Node *Effects, Node Control) {
+    // TODO(eholk): this function is almost certainly wrong.
+    LOG(out << "EffectPhi(" << Count << ", " << Control << "):\n");
+    for (unsigned i = 0; i < Count; ++i) {
+      LOG(out << "  " << Effects[i] << "\n");
+    }
+    return OperandNode(nullptr);
+  }
+  Node Int32Constant(int32_t Value) {
+    LOG(out << "Int32Constant(" << Value << ") = ");
+    auto *Const = Ctx->getConstantInt32(Value);
+    assert(Const);
+    assert(Control());
+    LOG(out << Node(Const) << "\n");
+    return OperandNode(Const);
+  }
+  Node Int64Constant(int64_t Value) {
+    LOG(out << "Int64Constant(" << Value << ") = ");
+    auto *Const = Ctx->getConstantInt64(Value);
+    assert(Const);
+    LOG(out << Node(Const) << "\n");
+    return OperandNode(Const);
+  }
+  Node Float32Constant(float Value) {
+    LOG(out << "Float32Constant(" << Value << ") = ");
+    auto *Const = Ctx->getConstantFloat(Value);
+    assert(Const);
+    LOG(out << Node(Const) << "\n");
+    return OperandNode(Const);
+  }
+  Node Float64Constant(double Value) {
+    LOG(out << "Float64Constant(" << Value << ") = ");
+    auto *Const = Ctx->getConstantDouble(Value);
+    assert(Const);
+    LOG(out << Node(Const) << "\n");
+    return OperandNode(Const);
+  }
+  Node Binop(wasm::WasmOpcode Opcode, Node Left, Node Right) {
+    LOG(out << "Binop(" << WasmOpcodes::OpcodeName(Opcode) << ", " << Left
+            << ", " << Right << ") = ");
+    auto *Dest = makeVariable(
+        isComparison(Opcode) ? IceType_i1 : Left.toOperand()->getType());
+    switch (Opcode) {
+    case kExprI32Add:
+    case kExprI64Add:
+      Control()->appendInst(
+          InstArithmetic::create(Func, InstArithmetic::Add, Dest, Left, Right));
+      break;
+    case kExprI32Sub:
+    case kExprI64Sub:
+      Control()->appendInst(
+          InstArithmetic::create(Func, InstArithmetic::Sub, Dest, Left, Right));
+      break;
+    case kExprI32Mul:
+    case kExprI64Mul:
+      Control()->appendInst(
+          InstArithmetic::create(Func, InstArithmetic::Mul, Dest, Left, Right));
+      break;
+    case kExprI32DivU:
+    case kExprI64DivU:
+      Control()->appendInst(InstArithmetic::create(Func, InstArithmetic::Udiv,
+                                                   Dest, Left, Right));
+      break;
+    case kExprI32RemU:
+    case kExprI64RemU:
+      Control()->appendInst(InstArithmetic::create(Func, InstArithmetic::Urem,
+                                                   Dest, Left, Right));
+      break;
+    case kExprI32Ior:
+    case kExprI64Ior:
+      Control()->appendInst(
+          InstArithmetic::create(Func, InstArithmetic::Or, Dest, Left, Right));
+      break;
+    case kExprI32Xor:
+    case kExprI64Xor:
+      Control()->appendInst(
+          InstArithmetic::create(Func, InstArithmetic::Xor, Dest, Left, Right));
+      break;
+    case kExprI32Shl:
+    case kExprI64Shl:
+      Control()->appendInst(
+          InstArithmetic::create(Func, InstArithmetic::Shl, Dest, Left, Right));
+      break;
+    case kExprI32ShrU:
+    case kExprI64ShrU:
+    case kExprI32ShrS:
+    case kExprI64ShrS:
+      Control()->appendInst(InstArithmetic::create(Func, InstArithmetic::Ashr,
+                                                   Dest, Left, Right));
+      break;
+    case kExprI32And:
+    case kExprI64And:
+      Control()->appendInst(
+          InstArithmetic::create(Func, InstArithmetic::And, Dest, Left, Right));
+      break;
+    case kExprI32Ne:
+    case kExprI64Ne:
+      Control()->appendInst(
+          InstIcmp::create(Func, InstIcmp::Ne, Dest, Left, Right));
+      break;
+    case kExprI32Eq:
+    case kExprI64Eq:
+      Control()->appendInst(
+          InstIcmp::create(Func, InstIcmp::Eq, Dest, Left, Right));
+      break;
+    case kExprI32LtS:
+    case kExprI64LtS:
+      Control()->appendInst(
+          InstIcmp::create(Func, InstIcmp::Slt, Dest, Left, Right));
+      break;
+    case kExprI32LtU:
+    case kExprI64LtU:
+      Control()->appendInst(
+          InstIcmp::create(Func, InstIcmp::Ult, Dest, Left, Right));
+      break;
+    case kExprI32GeS:
+    case kExprI64GeS:
+      Control()->appendInst(
+          InstIcmp::create(Func, InstIcmp::Sge, Dest, Left, Right));
+    case kExprI32GtS:
+    case kExprI64GtS:
+      Control()->appendInst(
+          InstIcmp::create(Func, InstIcmp::Sgt, Dest, Left, Right));
+      break;
+    case kExprI32GtU:
+    case kExprI64GtU:
+      Control()->appendInst(
+          InstIcmp::create(Func, InstIcmp::Ugt, Dest, Left, Right));
+      break;
+    default:
+      LOG(out << "Unknown binop: " << WasmOpcodes::OpcodeName(Opcode) << "\n");
+      llvm::report_fatal_error("Uncovered or invalid binop.");
+      return OperandNode(nullptr);
+    }
+    LOG(out << Dest << "\n");
+    return OperandNode(Dest);
+  }
+  Node Unop(wasm::WasmOpcode Opcode, Node Input) {
+    LOG(out << "Unop(" << WasmOpcodes::OpcodeName(Opcode) << ", " << Input
+            << ") = ");
+    Ice::Variable *Dest = nullptr;
+    switch (Opcode) {
+    case kExprF32Neg: {
+      Dest = makeVariable(IceType_f32);
+      Control()->appendInst(InstArithmetic::create(
+          Func, InstArithmetic::Fsub, Dest, Ctx->getConstantFloat(0), Input));
+      break;
+    }
+    case kExprF64Neg: {
+      Dest = makeVariable(IceType_f64);
+      Control()->appendInst(InstArithmetic::create(
+          Func, InstArithmetic::Fsub, Dest, Ctx->getConstantDouble(0), Input));
+      break;
+    }
+    case kExprI64UConvertI32:
+      Dest = makeVariable(IceType_i64);
+      Control()->appendInst(
+          InstCast::create(Func, InstCast::Zext, Dest, Input));
+      break;
+    default:
+      LOG(out << "Unknown unop: " << WasmOpcodes::OpcodeName(Opcode) << "\n");
+      llvm::report_fatal_error("Uncovered or invalid unop.");
+      return OperandNode(nullptr);
+    }
+    LOG(out << Dest << "\n");
+    return OperandNode(Dest);
+  }
+  unsigned InputCount(CfgNode *Node) const { return Node->getInEdges().size(); }
+  bool IsPhiWithMerge(Node Phi, Node Merge) const {
+    LOG(out << "IsPhiWithMerge(" << Phi << ", " << Merge << ")"
+            << "\n");
+    if (Phi && Phi.isOperand()) {
+      LOG(out << "  ...is operand"
+              << "\n");
+      if (auto *Inst = getDefiningInst(Phi)) {
+        LOG(out << "  ...has defining instruction"
+                << "\n");
+        LOG(out << getDefNode(Phi) << "\n");
+        LOG(out << "  ..." << (getDefNode(Phi) == Merge) << "\n");
+        return getDefNode(Phi) == Merge;
+      }
+    }
+    return false;
+  }
+  void AppendToMerge(CfgNode *Merge, CfgNode *From) const {
+    From->appendInst(InstBr::create(Func, Merge));
+  }
+  void AppendToPhi(Node Merge, Node Phi, Node From) {
+    LOG(out << "AppendToPhi(" << Merge << ", " << Phi << ", " << From << ")"
+            << "\n");
+    auto *Inst = getDefiningInst(Phi);
+    Inst->addArgument(From, getDefNode(From));
+  }
+
+  //-----------------------------------------------------------------------
+  // Operations that read and/or write {control} and {effect}.
+  //-----------------------------------------------------------------------
+  Node Branch(Node Cond, Node *TrueNode, Node *FalseNode) {
+    // true_node and false_node appear to be out parameters.
+    LOG(out << "Branch(" << Cond << ", ");
+
+    // save control here because true_node appears to alias control.
+    auto *Ctrl = Control();
+
+    *TrueNode = OperandNode(Func->makeNode());
+    *FalseNode = OperandNode(Func->makeNode());
+
+    LOG(out << *TrueNode << ", " << *FalseNode << ")"
+            << "\n");
+
+    Ctrl->appendInst(InstBr::create(Func, Cond, *TrueNode, *FalseNode));
+    return OperandNode(nullptr);
+  }
+  Node Switch(unsigned Count, Node Key) { llvm::report_fatal_error("Switch"); }
+  Node IfValue(int32_t Value, Node Sw) { llvm::report_fatal_error("IfValue"); }
+  Node IfDefault(Node Sw) { llvm::report_fatal_error("IfDefault"); }
+  Node Return(unsigned Count, Node *Vals) {
+    assert(1 >= Count);
+    LOG(out << "Return(");
+    if (Count > 0)
+      LOG(out << Vals[0]);
+    LOG(out << ")"
+            << "\n");
+    auto *Instr =
+        1 == Count ? InstRet::create(Func, Vals[0]) : InstRet::create(Func);
+    Control()->appendInst(Instr);
+    Control()->setHasReturn();
+    LOG(out << Node(nullptr) << "\n");
+    return OperandNode(nullptr);
+  }
+  Node ReturnVoid() {
+    LOG(out << "ReturnVoid() = ");
+    auto *Instr = InstRet::create(Func);
+    Control()->appendInst(Instr);
+    Control()->setHasReturn();
+    LOG(out << Node(nullptr) << "\n");
+    return OperandNode(nullptr);
+  }
+  Node Unreachable() {
+    LOG(out << "Unreachable() = ");
+    auto *Instr = InstUnreachable::create(Func);
+    Control()->appendInst(Instr);
+    LOG(out << Node(nullptr) << "\n");
+    return OperandNode(nullptr);
+  }
+
+  Node CallDirect(uint32_t Index, Node *Args) {
+    LOG(out << "CallDirect(" << Index << ")"
+            << "\n");
+    assert(Module->IsValidFunction(Index));
+    const auto *Module = this->Module->module;
+    assert(Module);
+    const auto &Target = Module->functions[Index];
+    const auto *Sig = Target.sig;
+    assert(Sig);
+    const auto NumArgs = Sig->parameter_count();
+    LOG(out << "  number of args: " << NumArgs << "\n");
+
+    const auto TargetName =
+        Ctx->getGlobalString(Module->GetName(Target.name_offset));
+    LOG(out << "  target name: " << TargetName << "\n");
+
+    assert(Sig->return_count() <= 1);
+
+    auto *TargetOperand = Ctx->getConstantSym(0, TargetName);
+
+    auto *Dest = Sig->return_count() > 0
+                     ? makeVariable(toIceType(Sig->GetReturn()))
+                     : nullptr;
+    auto *Call = InstCall::create(Func, NumArgs, Dest, TargetOperand,
+                                  false /* HasTailCall */);
+    for (int i = 0; i < NumArgs; ++i) {
+      // The builder reserves the first argument for the code object.
+      LOG(out << "  args[" << i << "] = " << Args[i + 1] << "\n");
+      Call->addArg(Args[i + 1]);
+    }
+
+    Control()->appendInst(Call);
+    LOG(out << "Call Result = " << Node(Dest) << "\n");
+    return OperandNode(Dest);
+  }
+  Node CallImport(uint32_t Index, Node *Args) {
+    LOG(out << "CallImport(" << Index << ")"
+            << "\n");
+    const auto *Module = this->Module->module;
+    assert(Module);
+    const auto *Sig = this->Module->GetImportSignature(Index);
+    assert(Sig);
+    const auto NumArgs = Sig->parameter_count();
+    LOG(out << "  number of args: " << NumArgs << "\n");
+
+    const auto &Target = Module->import_table[Index];
+    const auto TargetName =
+        Ctx->getGlobalString(Module->GetName(Target.function_name_offset));
+    LOG(out << "  target name: " << TargetName << "\n");
+
+    assert(Sig->return_count() <= 1);
+
+    auto *TargetOperand = Ctx->getConstantSym(0, TargetName);
+
+    auto *Dest = Sig->return_count() > 0
+                     ? makeVariable(toIceType(Sig->GetReturn()))
+                     : nullptr;
+    constexpr bool NoTailCall = false;
+    auto *Call =
+        InstCall::create(Func, NumArgs, Dest, TargetOperand, NoTailCall);
+    for (int i = 0; i < NumArgs; ++i) {
+      // The builder reserves the first argument for the code object.
+      LOG(out << "  args[" << i << "] = " << Args[i + 1] << "\n");
+      Call->addArg(Args[i + 1]);
+    }
+
+    Control()->appendInst(Call);
+    LOG(out << "Call Result = " << Node(Dest) << "\n");
+    return OperandNode(Dest);
+  }
+  Node CallIndirect(uint32_t Index, Node *Args) {
+    llvm::report_fatal_error("CallIndirect");
+  }
+  Node Invert(Node Node) { llvm::report_fatal_error("Invert"); }
+  Node FunctionTable() { llvm::report_fatal_error("FunctionTable"); }
+
+  //-----------------------------------------------------------------------
+  // Operations that concern the linear memory.
+  //-----------------------------------------------------------------------
+  Node MemSize(uint32_t Offset) { llvm::report_fatal_error("MemSize"); }
+  Node LoadGlobal(uint32_t Index) { llvm::report_fatal_error("LoadGlobal"); }
+  Node StoreGlobal(uint32_t Index, Node Val) {
+    llvm::report_fatal_error("StoreGlobal");
+  }
+  Node LoadMem(wasm::LocalType Type, MachineType MemType, Node Index,
+               uint32_t Offset) {
+    LOG(out << "LoadMem(" << Index << "[" << Offset << "]) = ");
+
+    // first, add the index and the offset together.
+    auto *OffsetConstant = Ctx->getConstantInt32(Offset);
+    auto *Addr = makeVariable(IceType_i32);
+    Control()->appendInst(InstArithmetic::create(Func, InstArithmetic::Add,
+                                                 Addr, Index, OffsetConstant));
+
+    // then load the memory
+    auto *LoadResult = makeVariable(toIceType(MemType));
+    Control()->appendInst(InstLoad::create(Func, LoadResult, Addr));
+
+    // and cast, if needed
+    Ice::Variable *Result = nullptr;
+    if (toIceType(Type) != toIceType(MemType)) {
+      Result = makeVariable(toIceType(Type));
+      // TODO(eholk): handle signs correctly.
+      Control()->appendInst(
+          InstCast::create(Func, InstCast::Sext, Result, LoadResult));
+    } else {
+      Result = LoadResult;
+    }
+
+    LOG(out << Result << "\n");
+    return OperandNode(Result);
+  }
+  void StoreMem(MachineType Type, Node Index, uint32_t Offset, Node Val) {
+    LOG(out << "StoreMem(" << Index << "[" << Offset << "] = " << Val << ")"
+            << "\n");
+
+    // TODO(eholk): surely there is a better way to do this.
+
+    // first, add the index and the offset together.
+    auto *OffsetConstant = Ctx->getConstantInt32(Offset);
+    auto *Addr = makeVariable(IceType_i32);
+    Control()->appendInst(InstArithmetic::create(Func, InstArithmetic::Add,
+                                                 Addr, Index, OffsetConstant));
+
+    // cast the value to the right type, if needed
+    Operand *StoreVal = nullptr;
+    if (toIceType(Type) != Val.toOperand()->getType()) {
+      auto *LocalStoreVal = makeVariable(toIceType(Type));
+      Control()->appendInst(
+          InstCast::create(Func, InstCast::Trunc, LocalStoreVal, Val));
+      StoreVal = LocalStoreVal;
+    } else {
+      StoreVal = Val;
+    }
+
+    // then store the memory
+    Control()->appendInst(InstStore::create(Func, StoreVal, Addr));
+  }
+
+  static void PrintDebugName(Node node) {
+    llvm::report_fatal_error("PrintDebugName");
+  }
+
+  CfgNode *Control() {
+    return ControlPtr ? ControlPtr->toCfgNode() : Func->getEntryNode();
+  }
+  Node Effect() { return *EffectPtr; }
+
+  void set_module(wasm::ModuleEnv *Module) { this->Module = Module; }
+
+  void set_control_ptr(Node *Control) { this->ControlPtr = Control; }
+
+  void set_effect_ptr(Node *Effect) { this->EffectPtr = Effect; }
+
+private:
+  wasm::ModuleEnv *Module;
+  Node *ControlPtr;
+  Node *EffectPtr;
+
+  class Cfg *Func;
+  GlobalContext *Ctx;
+
+  SizeT NextArg = 0;
+
+  CfgUnorderedMap<Operand *, InstPhi *> PhiMap;
+  CfgUnorderedMap<Operand *, CfgNode *> DefNodeMap;
+
+  InstPhi *getDefiningInst(Operand *Op) const {
+    const auto &Iter = PhiMap.find(Op);
+    if (Iter == PhiMap.end()) {
+      return nullptr;
+    }
+    return Iter->second;
+  }
+
+  void setDefiningInst(Operand *Op, InstPhi *Phi) {
+    LOG(out << "\n== setDefiningInst(" << Op << ", " << Phi << ") ==\n");
+    PhiMap.emplace(Op, Phi);
+  }
+
+  Ice::Variable *makeVariable(Ice::Type Type) {
+    return makeVariable(Type, Control());
+  }
+
+  Ice::Variable *makeVariable(Ice::Type Type, CfgNode *DefNode) {
+    auto *Var = Func->makeVariable(Type);
+    DefNodeMap.emplace(Var, DefNode);
+    return Var;
+  }
+
+  CfgNode *getDefNode(Operand *Op) const {
+    const auto &Iter = DefNodeMap.find(Op);
+    if (Iter == DefNodeMap.end()) {
+      return nullptr;
+    }
+    return Iter->second;
+  }
+
+  template <typename F = std::function<void(Ostream &)>> void log(F Fn) const {
+    if (BuildDefs::dump() && (getFlags().getVerbose() & IceV_Wasm)) {
+      Fn(Ctx->getStrDump());
+      Ctx->getStrDump().flush();
+    }
+  }
+};
+
+std::string fnNameFromId(uint32_t Id) {
+  return std::string("fn") + to_string(Id);
+}
+
+std::unique_ptr<Cfg> WasmTranslator::translateFunction(Zone *Zone,
+                                                       FunctionEnv *Env,
+                                                       const byte *Base,
+                                                       const byte *Start,
+                                                       const byte *End) {
+  OstreamLocker L1(Ctx);
+  auto Func = Cfg::create(Ctx, getNextSequenceNumber());
+  Ice::CfgLocalAllocatorScope L2(Func.get());
+
+  // TODO: parse the function signature...
+
+  IceBuilder Builder(Func.get());
+  LR_WasmDecoder<OperandNode, IceBuilder> Decoder(Zone, &Builder);
+
+  LOG(out << getFlags().getDefaultGlobalPrefix() << "\n");
+  Decoder.Decode(Env, Base, Start, End);
+
+  // We don't always know where the incoming branches are in phi nodes, so this
+  // function finds them.
+  Func->fixPhiNodes();
+
+  return Func;
+}
+
+WasmTranslator::WasmTranslator(GlobalContext *Ctx)
+    : Translator(Ctx), BufferSize(24 << 10), Buffer(new uint8_t[24 << 10]) {
+  // TODO(eholk): compute the correct buffer size. This uses 24k by default,
+  // which has been big enough for testing but is not a general solution.
+}
+
+void WasmTranslator::translate(
+    const std::string &IRFilename,
+    std::unique_ptr<llvm::DataStreamer> InputStream) {
+  LOG(out << "Initializing v8/wasm stuff..."
+          << "\n");
+  Zone Zone;
+  ZoneScope _(&Zone);
+
+  SizeT BytesRead = InputStream->GetBytes(Buffer.get(), BufferSize);
+  LOG(out << "Read " << BytesRead << " bytes"
+          << "\n");
+
+  LOG(out << "Decoding module " << IRFilename << "\n");
+
+  constexpr v8::internal::Isolate *NoIsolate = nullptr;
+  auto Result = DecodeWasmModule(NoIsolate, &Zone, Buffer.get(),
+                                 Buffer.get() + BytesRead, false, kWasmOrigin);
+
+  auto Module = Result.val;
+
+  LOG(out << "Module info:"
+          << "\n");
+  LOG(out << "  number of globals:       " << Module->globals.size() << "\n");
+  LOG(out << "  number of signatures:    " << Module->signatures.size()
+          << "\n");
+  LOG(out << "  number of functions:     " << Module->functions.size() << "\n");
+  LOG(out << "  number of data_segments: " << Module->data_segments.size()
+          << "\n");
+  LOG(out << "  function table size:     " << Module->function_table.size()
+          << "\n");
+
+  ModuleEnv ModuleEnv;
+  ModuleEnv.module = Module;
+
+  LOG(out << "\n"
+          << "Function information:"
+          << "\n");
+  for (const auto F : Module->functions) {
+    LOG(out << "  " << F.name_offset << ": " << Module->GetName(F.name_offset));
+    if (F.exported)
+      LOG(out << " export");
+    if (F.external)
+      LOG(out << " extern");
+    LOG(out << "\n");
+  }
+
+  FunctionEnv Fenv;
+  Fenv.module = &ModuleEnv;
+
+  LOG(out << "Translating " << IRFilename << "\n");
+
+  // Translate each function.
+  uint32_t Id = 0;
+  for (const auto Fn : Module->functions) {
+    std::string NewName = fnNameFromId(Id++);
+    LOG(out << "  " << Fn.name_offset << ": " << Module->GetName(Fn.name_offset)
+            << " -> " << NewName << "...");
+
+    Fenv.sig = Fn.sig;
+    Fenv.local_i32_count = Fn.local_i32_count;
+    Fenv.local_i64_count = Fn.local_i64_count;
+    Fenv.local_f32_count = Fn.local_f32_count;
+    Fenv.local_f64_count = Fn.local_f64_count;
+    Fenv.SumLocals();
+
+    auto Func = translateFunction(&Zone, &Fenv, Buffer.get(),
+                                  Buffer.get() + Fn.code_start_offset,
+                                  Buffer.get() + Fn.code_end_offset);
+    Func->setFunctionName(Ctx->getGlobalString(NewName));
+
+    Ctx->optQueueBlockingPush(makeUnique<CfgOptWorkItem>(std::move(Func)));
+    LOG(out << "done.\n");
+  }
+
+  return;
+}
diff --git a/src/WasmTranslator.h b/src/WasmTranslator.h
new file mode 100644
index 0000000..724d7b9
--- /dev/null
+++ b/src/WasmTranslator.h
@@ -0,0 +1,69 @@
+//===- subzero/src/WasmTranslator.h - WASM to Subzero Translation ---------===//
+//
+//                        The Subzero Code Generator
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// \brief Declares a driver for translating Wasm bitcode into PNaCl bitcode.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef SUBZERO_SRC_WASMTRANSLATOR_H
+#define SUBZERO_SRC_WASMTRANSLATOR_H
+
+#include "IceGlobalContext.h"
+#include "IceTranslator.h"
+
+namespace v8 {
+namespace internal {
+class Zone;
+namespace wasm {
+class FunctionEnv;
+} // end of namespace wasm
+} // end of namespace internal
+} // end of namespace v8
+
+namespace Ice {
+
+class WasmTranslator : public Translator {
+  WasmTranslator() = delete;
+  WasmTranslator(const WasmTranslator &) = delete;
+  WasmTranslator &operator=(const WasmTranslator &) = delete;
+
+  template <typename F = std::function<void(Ostream &)>> void log(F Fn) {
+    if (BuildDefs::dump() && (getFlags().getVerbose() & IceV_Wasm)) {
+      Fn(Ctx->getStrDump());
+      Ctx->getStrDump().flush();
+    }
+  }
+
+public:
+  explicit WasmTranslator(GlobalContext *Ctx);
+
+  void translate(const std::string &IRFilename,
+                 std::unique_ptr<llvm::DataStreamer> InputStream);
+
+  /// Translates a single Wasm function.
+  ///
+  /// Parameters:
+  ///   Zone - an arena for the V8 code to allocate from.
+  ///   Env - information about the function (signature, variable count, etc.).
+  ///   Base - a pointer to the start of the Wasm module.
+  ///   Start - a pointer to the start of the function within the module.
+  ///   End - a pointer to the end of the function.
+  std::unique_ptr<Cfg> translateFunction(v8::internal::Zone *Zone,
+                                         v8::internal::wasm::FunctionEnv *Env,
+                                         const uint8_t *Base,
+                                         const uint8_t *Start,
+                                         const uint8_t *End);
+
+private:
+  std::unique_ptr<uint8_t[]> Buffer;
+  SizeT BufferSize;
+};
+}
+#endif // SUBZERO_SRC_WASMTRANSLATOR_H