//===-- Compiler.cpp - Java bytecode compiler -------------------*- C++ -*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file was developed by the LLVM research group and is distributed under
// the University of Illinois Open Source License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file contains Java bytecode to LLVM bytecode compiler.
//
//===----------------------------------------------------------------------===//

#define DEBUG_TYPE "javacompiler"

#include <llvm/Java/Compiler.h>
#include "BasicBlockBuilder.h"
#include "Locals.h"
#include "OperandStack.h"
#include "Resolver.h"
#include <llvm/Java/Bytecode.h>
#include <llvm/Java/BytecodeParser.h>
#include <llvm/Java/ClassFile.h>
#include <llvm/Constants.h>
#include <llvm/DerivedTypes.h>
#include <llvm/Value.h>
#include <llvm/Type.h>
#include <llvm/ADT/STLExtras.h>
#include <llvm/ADT/SetVector.h>
#include <llvm/ADT/StringExtras.h>
#include <llvm/Support/CFG.h>
#include <llvm/Support/Debug.h>
#include <iostream>
#include <list>
#include <vector>

using namespace llvm;
using namespace llvm::Java;

namespace llvm { namespace Java { namespace {

  const std::string TMP("tmp");

  llvm::Constant* ONE = ConstantSInt::get(Type::IntTy, 1);
  llvm::Constant* ZERO = ConstantSInt::get(Type::IntTy, 0);
  llvm::Constant* MINUS_ONE = ConstantSInt::get(Type::IntTy, -1);

  llvm::Constant* INT_SHIFT_MASK = ConstantUInt::get(Type::UByteTy, 0x1f);
  llvm::Constant* LONG_SHIFT_MASK = ConstantUInt::get(Type::UByteTy, 0x3f);

  class Compiler : public BytecodeParser<Compiler> {
    Module* module_;
    std::auto_ptr<Resolver> resolver_;
    GlobalVariable* JNIEnvPtr_;
    const VMClass* class_;
    std::auto_ptr<BasicBlockBuilder> bbBuilder_;
    std::list<BasicBlock*> bbWorkList_;
    typedef std::map<BasicBlock*, unsigned> OpStackDepthMap;
    OpStackDepthMap opStackDepthMap_;
    typedef std::map<std::string, GlobalVariable*> StringMap;
    StringMap stringMap_;
    BasicBlock* currentBB_;
    Locals locals_;
    OperandStack opStack_;
    Function *getClassRecord_, *setClassRecord_, *throw_, *isInstanceOf_,
      *memcpy_, *memset_;
    std::vector<llvm::Constant*> classInitializers_;

    SetVector<const VMMethod*> toCompileMethods_;

  public:
    Compiler(Module* m)
      : module_(m),
        resolver_(new Resolver(module_)),
        locals_(resolver_.get(), 0),
        opStack_(resolver_.get(), 0) {
      Type* JNIEnvTy = OpaqueType::get();
      module_->addTypeName("JNIEnv", JNIEnvTy);
      JNIEnvPtr_ = new GlobalVariable(JNIEnvTy,
                                      true,
                                      GlobalVariable::ExternalLinkage,
                                      NULL,
                                      "llvm_java_JNIEnv",
                                      module_);
      const Type* classRecordPtrType = resolver_->getClassRecordPtrType();
      const Type* objectBaseType = resolver_->getObjectBaseType();

      getClassRecord_ = module_->getOrInsertFunction(
        "llvm_java_get_class_record", classRecordPtrType,
        objectBaseType, NULL);
      setClassRecord_ = module_->getOrInsertFunction(
        "llvm_java_set_class_record", Type::VoidTy,
        objectBaseType, classRecordPtrType, NULL);
      throw_ = module_->getOrInsertFunction(
        "llvm_java_throw", Type::IntTy,
        objectBaseType, NULL);
      isInstanceOf_ = module_->getOrInsertFunction(
        "llvm_java_is_instance_of", Type::IntTy,
        objectBaseType, classRecordPtrType, NULL);
      memcpy_ = module_->getOrInsertFunction(
        "llvm.memcpy", Type::VoidTy,
        PointerType::get(Type::SByteTy),
        PointerType::get(Type::SByteTy),
        Type::ULongTy, Type::UIntTy, NULL);
      memset_ = module_->getOrInsertFunction(
        "llvm.memset", Type::VoidTy,
        PointerType::get(Type::SByteTy),
        Type::UByteTy, Type::ULongTy, Type::UIntTy, NULL);
    }

  private:
    void push(Value* value) {
      opStack_.push(value, currentBB_);
    }

    Value* pop(const Type* type) {
      return opStack_.pop(type, currentBB_);
    }

    /// Schedule a method for compilation. Returns true if this is the
    /// first time this function was scheduled.
    bool scheduleMethod(const VMMethod* method) {
      if (toCompileMethods_.insert(method)) {
        DEBUG(std::cerr << "Scheduling function: "
              << method->getFunction()->getName() << " for compilation\n");
        return true;
      }
      return false;
    }

    template <typename InsertionPointTy>
    void initializeString(Value* globalString,
                          const std::string& str,
                          InsertionPointTy* ip) {
      // Create a new byte[] object and initialize it with the
      // contents of this string constant.
      Value* count = ConstantUInt::get(Type::UIntTy, str.size());
      Value* arrayRef = allocateArray(resolver_->getClass("[B"), count, ip);
      // Copy string data.
      std::vector<Value*> indices;
      indices.reserve(3);
      indices.push_back(ConstantUInt::get(Type::UIntTy, 0));
      indices.push_back(ConstantUInt::get(Type::UIntTy, 2));
      indices.push_back(ConstantUInt::get(Type::UIntTy, 0));
      Value* arrayData = new GetElementPtrInst(arrayRef, indices, TMP, ip);
      llvm::Constant* init = ConstantArray::get(str);
      GlobalVariable* chars = new GlobalVariable(
        init->getType(),
        true,
        GlobalVariable::InternalLinkage,
        init,
        str + ".str",
        module_);

      std::vector<Value*> params;
      params.reserve(4);
      params.clear();
      params.push_back(arrayData);
      params.push_back(ConstantExpr::getPtrPtrFromArrayPtr(chars));
      params.push_back(new CastInst(count, Type::ULongTy, TMP, ip));
      params.push_back(ConstantUInt::get(Type::UIntTy, 0));
      new CallInst(memcpy_, params, "", ip);

      // Get class information for java/lang/String.
      const VMClass* clazz = resolver_->getClass("java/lang/String");
      emitClassInitializers(clazz);

      // Install the class record.
      Value* objBase =
        new CastInst(globalString, resolver_->getObjectBaseType(), TMP, ip);
      const Type* classRecordPtrType = resolver_->getClassRecordPtrType();
      Value* classRecord =
        new CastInst(clazz->getClassRecord(), classRecordPtrType, TMP, ip);
      new CallInst(setClassRecord_, objBase, classRecord, "", ip);

      // Initialize it: call java/lang/String/<init>(byte[],int)
      const VMMethod* method = clazz->getMethod("<init>([BI)V");

      params.reserve(3);
      params.clear();
      params.push_back(objBase);
      params.push_back(
        new CastInst(arrayRef, resolver_->getObjectBaseType(), TMP, ip));
      params.push_back(ConstantSInt::get(Type::IntTy, 0));
      new CallInst(method->getFunction(), params, "", ip);
    }

    /// Returns the type of the Java string descriptor for JNI.
    const Type* getJNIType(const std::string& descr) {
      unsigned i = 0;
      return getJNITypeHelper(descr, i);
    }

    const Type* getJNITypeHelper(const std::string& descr, unsigned& i) {
      assert(i < descr.size());
      switch (descr[i++]) {
      case 'B': return Type::SByteTy;
      case 'C': return Type::UShortTy;
      case 'D': return Type::DoubleTy;
      case 'F': return Type::FloatTy;
      case 'I': return Type::IntTy;
      case 'J': return Type::LongTy;
      case 'S': return Type::ShortTy;
      case 'Z': return Type::BoolTy;
      case 'V': return Type::VoidTy;
        // Both array and object types are pointers to llvm_object_base
      case 'L': {
        unsigned e = descr.find(';', i);
        i = e + 1;
        return resolver_->getObjectBaseType();
      }
      case '[':
        // Skip '['s.
        if (descr[i] == '[')
          do { ++i; } while (descr[i] == '[');
        // Consume the element type
        getJNITypeHelper(descr, i);
        return resolver_->getObjectBaseType();
      case '(': {
        std::vector<const Type*> params;
        // JNIEnv*
        params.push_back(JNIEnvPtr_->getType());
        params.push_back(resolver_->getObjectBaseType());
        while (descr[i] != ')')
          params.push_back(getJNITypeHelper(descr, i));
        return FunctionType::get(getJNITypeHelper(descr, ++i), params, false);
      }
        // FIXME: Throw something
      default:  assert(0 && "Cannot parse type descriptor!");
      }
      return 0; // not reached
    }

    std::string getMangledString(const std::string& str) {
      std::string mangledStr;

      for (unsigned i = 0, e = str.size(); i != e; ++i) {
        if (str[i] == '/')
          mangledStr += '_';
        else if (str[i] == '_')
          mangledStr += "_1";
        else if (str[i] == ';')
          mangledStr += "_2";
        else if (str[i] == '[')
          mangledStr += "_3";
        else
          mangledStr += str[i];
      }
      return mangledStr;
    }

    /// Compiles the passed method only (it does not compile any
    /// callers or methods of objects it creates).
    void compileMethodOnly(const VMMethod* method) {
      class_ = method->getParent();

      Function* function = method->getFunction();
      if (!function->empty()) {
        DEBUG(std::cerr << "Function: " << function->getName()
              << " is already compiled!\n");
        return;
      }

      if (method->isNative()) {
        DEBUG(std::cerr << "Adding stub for natively implemented method: "
              << function->getName() << '\n');
        const FunctionType* jniFuncTy =
          cast<FunctionType>(getJNIType(method->getDescriptor()));

        std::string funcName =
          "Java_" +
          getMangledString(class_->getName()) + '_' +
          getMangledString(method->getName());
        if (class_->getClassFile()->isNativeMethodOverloaded(*method->getMethod())) {
          // We need to add two underscores and a mangled argument signature
          funcName += "__";
          const std::string descr = method->getDescriptor();
          funcName += getMangledString(
            std::string(descr.begin() + descr.find('(') + 1,
                        descr.begin() + descr.find(')')));
        }

        Function* jniFunction =
          module_->getOrInsertFunction(funcName, jniFuncTy);

        BasicBlock* bb = new BasicBlock("entry", function);
        std::vector<Value*> params;
        params.push_back(JNIEnvPtr_);
        if (method->isStatic())
          params.push_back(
            new CastInst(method->getParent()->getClassRecord(),
                         resolver_->getObjectBaseType(), TMP, bb));
        for (Function::arg_iterator A = function->arg_begin(),
               E = function->arg_end(); A != E; ++A) {
          params.push_back(
            new CastInst(A, jniFuncTy->getParamType(params.size()), TMP, bb));
        }
        Value* result = new CallInst(jniFunction, params, "", bb);
        if (result->getType() != Type::VoidTy)
          result = new CastInst(result, function->getReturnType(), TMP,bb);
        new ReturnInst(result, bb);

        return;
      }

      assert (!method->isAbstract() && "Trying to compile an abstract method!");

      // HACK: skip most of the class libraries.
      const std::string& funcName = function->getName();
      if ((funcName.find("java/") == 0 &&
           funcName.find("java/lang/Object") != 0 &&
           (funcName.find("java/lang/Throwable") != 0 ||
            funcName.find("java/lang/Throwable$StaticData/<cl") == 0) &&
           funcName.find("java/lang/Exception") != 0 &&
           funcName.find("java/lang/IllegalArgumentException") != 0 &&
           funcName.find("java/lang/IllegalStateException") != 0 &&
           funcName.find("java/lang/IndexOutOfBoundsException") != 0 &&
           funcName.find("java/lang/RuntimeException") != 0 &&
           (funcName.find("java/lang/Math") != 0 ||
            funcName.find("java/lang/Math/<cl") == 0) &&
           funcName.find("java/lang/Number") != 0 &&
           funcName.find("java/lang/Byte") != 0 &&
           funcName.find("java/lang/Float") != 0 &&
           funcName.find("java/lang/Integer") != 0 &&
           funcName.find("java/lang/Long") != 0 &&
           funcName.find("java/lang/Short") != 0 &&
           (funcName.find("java/lang/String") != 0 ||
            funcName.find("java/lang/String/<cl") == 0) &&
           funcName.find("java/lang/StringBuffer") != 0 &&
           (funcName.find("java/lang/System") != 0 ||
            funcName.find("java/lang/System/loadLibrary") == 0) &&
           funcName.find("java/lang/VMSystem") != 0 &&
           (funcName.find("java/util/") != 0 ||
            funcName.find("java/util/Locale/<cl") == 0 ||
            funcName.find("java/util/ResourceBundle/<cl") == 0 ||
            funcName.find("java/util/Calendar/<cl") == 0) ||
            funcName.find("java/util/PropertyPermission/<cl") == 0) ||
          (funcName.find("gnu/") == 0)) {
        DEBUG(std::cerr << "Skipping compilation of method: "
              << funcName << '\n');
        return;
      }

      DEBUG(std::cerr << "Compiling method: " << funcName << '\n');

      Java::CodeAttribute* codeAttr = method->getMethod()->getCodeAttribute();

      opStackDepthMap_.clear();
      bbBuilder_.reset(new BasicBlockBuilder(function, codeAttr));

      // Put arguments into locals.
      locals_ = Locals(resolver_.get(), codeAttr->getMaxLocals());

      unsigned index = 0;
      for (Function::arg_iterator a = function->arg_begin(),
             ae = function->arg_end(); a != ae; ++a) {
        locals_.store(index, a, &function->getEntryBlock());
        index += resolver_->isTwoSlotType(a->getType()) ? 2 : 1;
      }

      BasicBlock* bb0 = bbBuilder_->getBasicBlock(0);

      // For bb0 the operand stack is empty and the locals contain the
      // arguments to the function.
      //
      // NOTE: We create an operand stack one size too big because we
      // push extra values on the stack to simplify code generation
      // (see implementation of ifne).
      opStack_ = OperandStack(resolver_.get(), codeAttr->getMaxStack()+2);
      opStackDepthMap_.insert(std::make_pair(bb0, 0));

      // Insert bb0 in the work list.
      bbWorkList_.push_back(bb0);

      // Process the work list until we compile the whole function.
      while (!bbWorkList_.empty()) {
        currentBB_ = bbWorkList_.front();
        bbWorkList_.pop_front();

        OpStackDepthMap::iterator opStackDepth =
          opStackDepthMap_.find(currentBB_);
        assert(opStackDepth != opStackDepthMap_.end() &&
               "Unknown operand stack depth for basic block in work list!");

        opStack_.setDepth(opStackDepth->second);

        unsigned start, end;
        tie(start, end) = bbBuilder_->getBytecodeIndices(currentBB_);

        // Compile this basic block.
        parse(codeAttr->getCode(), start, end);

        // If this basic block does not have a terminator, it should
        // have an unconditional branch to the next basic block
        // (fallthrough).
        if (!currentBB_->getTerminator())
          new BranchInst(bbBuilder_->getBasicBlock(end), currentBB_);

        // For each successor of this basic block we can compute its
        // entry operand stack depth. We do so, and add it to the work
        // list. If a successor already has an entry operand stack and
        // locals we assume the computation was correct and do not add
        // it to the work list.
        for (succ_iterator
               SI = succ_begin(currentBB_), SE = succ_end(currentBB_);
             SI != SE; ++SI) {
          BasicBlock* Succ = *SI;
          OpStackDepthMap::iterator succOpStackDepth =
            opStackDepthMap_.lower_bound(Succ);
          if (succOpStackDepth == opStackDepthMap_.end() ||
              succOpStackDepth->first != Succ) {
            opStackDepthMap_.insert(succOpStackDepth,
                                    std::make_pair(Succ, opStack_.getDepth()));
            bbWorkList_.push_back(Succ);
          }
        }
      }

      // Add an unconditional branch from the entry block to bb0.
      new BranchInst(bb0, &function->getEntryBlock());

      // FIXME: remove empty basic blocks (we have empty basic blocks
      // because of our lack of exception support).
      for (Function::iterator bb = function->begin(), be = function->end();
           bb != be; )
        if (bb->empty())
          bb = function->getBasicBlockList().erase(bb);
        else
          ++bb;

      DEBUG(std::cerr << "Finished compilation of method: "<< funcName << '\n');
      // DEBUG(function->dump());
    }

    /// Emits static initializers for this class if not done already.
    void emitClassInitializers(const VMClass* clazz) {
      static SetVector<const VMClass*> toInitClasses;

      // If this is a primitive class we are done.
      if (clazz->isPrimitive())
        return;

      // If this class is already initialized, we are done.
      if (!toInitClasses.insert(clazz))
        return;

      // If this class has a super class, initialize that first.
      if (const VMClass* superClass = clazz->getSuperClass())
        emitClassInitializers(superClass);

      // If this class is an array, initialize its component class now.
      if (const VMClass* componentClass = clazz->getComponentClass())
        emitClassInitializers(componentClass);

      // Schedule all its dynamically bound non abstract methods for
      // compilation.
      for (unsigned i = 0, e = clazz->getNumDynamicMethods(); i != e; ++i) {
        const VMMethod* method = clazz->getDynamicMethod(i);
        if (!method->isAbstract())
          scheduleMethod(method);
      }

      // Schedule all its statically bound non abstract methods for
      // compilation.
      for (unsigned i = 0, e = clazz->getNumStaticMethods(); i != e; ++i) {
        const VMMethod* method = clazz->getStaticMethod(i);
        if (!method->isAbstract())
          scheduleMethod(method);
      }
      
      // If this class has a constant pool (was loaded from a
      // classfile), create constant strings for it.
      if (const ClassFile* classfile = clazz->getClassFile()) {
        Function* stringConstructors = module_->getOrInsertFunction(
          clazz->getName() + "<strinit>",
          FunctionType::get(Type::VoidTy, std::vector<const Type*>(), false));
        Instruction* I =
          new ReturnInst(NULL, new BasicBlock("entry", stringConstructors));
        for (unsigned i = 0, e = classfile->getNumConstants(); i != e; ++i)
          if (ConstantString* s = dynamic_cast<ConstantString*>(classfile->getConstant(i)))
            initializeString(clazz->getConstant(i), s->getValue()->str(), I);

        // Insert string constructors method in class initializers array.
        classInitializers_.push_back(stringConstructors);

        // Call its class initialization method if it exists.
        if (const VMMethod* method = clazz->getMethod("<clinit>()V"))
          classInitializers_.push_back(method->getFunction());
      }
    }

  public:
    /// Compiles the specified method given a <class,method>
    /// descriptor and the transitive closure of all methods
    /// (possibly) called by it.
    const VMMethod* compileMethod(const std::string& className,
                                  const std::string& methodDesc) {
      // Load the class.
      const VMClass* clazz = resolver_->getClass(className);
      emitClassInitializers(clazz);

      // Find the method.
      const VMMethod* method = clazz->getMethod(methodDesc);
      // Compile the transitive closure of methods called by this method.
      for (unsigned i = 0; i != toCompileMethods_.size(); ++i) {
        const VMMethod* m = toCompileMethods_[i];
        compileMethodOnly(m);
        DEBUG(std::cerr << i+1 << '/' << toCompileMethods_.size()
              << " functions compiled\n");
      }

      // Null terminate the static initializers array and add the
      // global to the module.
      Type* classInitializerType = PointerType::get(
          FunctionType::get(Type::VoidTy, std::vector<const Type*>(), false));
      classInitializers_.push_back(
        llvm::Constant::getNullValue(classInitializerType));

      ArrayType* classInitializersType =
        ArrayType::get(classInitializerType, classInitializers_.size());
      new GlobalVariable(classInitializersType,
                         true,
                         GlobalVariable::ExternalLinkage,
                         ConstantArray::get(classInitializersType,
                                            classInitializers_),
                         "llvm_java_class_initializers",
                         module_);

      // Emit the array of all class records.
      resolver_->emitClassRecordsArray();

      return method;
    }

    void do_aconst_null() {
      push(llvm::Constant::getNullValue(resolver_->getObjectBaseType()));
    }

    void do_iconst(int value) {
      push(ConstantSInt::get(Type::IntTy, value));
    }

    void do_lconst(long long value) {
      push(ConstantSInt::get(Type::LongTy, value));
    }

    void do_fconst(float value) {
      push(ConstantFP::get(Type::FloatTy, value));
    }

    void do_dconst(double value) {
      push(ConstantFP::get(Type::DoubleTy, value));
    }

    void do_ldc(unsigned index) {
      llvm::Constant* c = class_->getConstant(index);
      assert(c && "Java constant not handled!");
      push(c);
    }

    void do_ldc2(unsigned index) {
      do_ldc(index);
    }

    void do_iload(unsigned index) { do_load_common(index, Type::IntTy); }
    void do_lload(unsigned index) { do_load_common(index, Type::LongTy); }
    void do_fload(unsigned index) { do_load_common(index, Type::FloatTy); }
    void do_dload(unsigned index) { do_load_common(index, Type::DoubleTy); }
    void do_aload(unsigned index) { do_load_common(index, resolver_->getObjectBaseType()); }

    void do_load_common(unsigned index, const Type* type) {
      Value* val = locals_.load(index, type, currentBB_);
      push(val);
    }

    void do_iaload() { do_aload_common("[I"); }
    void do_laload() { do_aload_common("[J"); }
    void do_faload() { do_aload_common("[F"); }
    void do_daload() { do_aload_common("[D"); }
    void do_aaload() { do_aload_common("[Ljava/lang/Object;"); }
    void do_baload() { do_aload_common("[B"); }
    void do_caload() { do_aload_common("[C"); }
    void do_saload() { do_aload_common("[S"); }

    void do_aload_common(const std::string& className) {
      const VMClass* arrayClass = resolver_->getClass(className);
      assert(arrayClass->isArray() && "Not an array class!");
      Value* index = pop(Type::IntTy);
      Value* arrayRef = pop(arrayClass->getType());

      std::vector<Value*> indices;
      indices.reserve(3);
      indices.push_back(ConstantUInt::get(Type::UIntTy, 0));
      indices.push_back(ConstantUInt::get(Type::UIntTy, 2));
      indices.push_back(index);
      Value* elementPtr =
        new GetElementPtrInst(arrayRef, indices, TMP, currentBB_);
      Value* result = new LoadInst(elementPtr, TMP, currentBB_);
      push(result);
    }

    void do_istore(unsigned index) { do_store_common(index, Type::IntTy); }
    void do_lstore(unsigned index) { do_store_common(index, Type::LongTy); }
    void do_fstore(unsigned index) { do_store_common(index, Type::FloatTy); }
    void do_dstore(unsigned index) { do_store_common(index, Type::DoubleTy); }
    void do_astore(unsigned index) { do_store_common(index, resolver_->getObjectBaseType()); }

    void do_store_common(unsigned index, const Type* type) {
      Value* val = pop(type);
      locals_.store(index, val, currentBB_);
    }

    void do_iastore() { do_astore_common("[I"); }
    void do_lastore() { do_astore_common("[J"); }
    void do_fastore() { do_astore_common("[F"); }
    void do_dastore() { do_astore_common("[D"); }
    void do_aastore() { do_astore_common("[Ljava/lang/Object;"); }
    void do_bastore() { do_astore_common("[B"); }
    void do_castore() { do_astore_common("[C"); }
    void do_sastore() { do_astore_common("[S"); }

    void do_astore_common(const std::string& className) {
      const VMClass* arrayClass = resolver_->getClass(className);
      assert(arrayClass->isArray() && "Not an array class!");
      const VMClass* componentClass = arrayClass->getComponentClass();
      Value* value = pop(componentClass->getType());
      Value* index = pop(Type::IntTy);
      Value* arrayRef = pop(arrayClass->getType());

      std::vector<Value*> indices;
      indices.reserve(3);
      indices.push_back(ConstantUInt::get(Type::UIntTy, 0));
      indices.push_back(ConstantUInt::get(Type::UIntTy, 2));
      indices.push_back(index);
      Value* elementPtr =
        new GetElementPtrInst(arrayRef, indices, TMP, currentBB_);

      new StoreInst(value, elementPtr, currentBB_);
    }

    void do_pop() {
      opStack_.do_pop(currentBB_);
    }

    void do_pop2() {
      opStack_.do_pop2(currentBB_);
    }

    void do_dup() {
      opStack_.do_dup(currentBB_);
    }

    void do_dup_x1() {
      opStack_.do_dup_x1(currentBB_);
    }

    void do_dup_x2() {
      opStack_.do_dup_x2(currentBB_);
    }

    void do_dup2() {
      opStack_.do_dup2(currentBB_);
    }

    void do_dup2_x1() {
      opStack_.do_dup2_x1(currentBB_);
    }

    void do_dup2_x2() {
      opStack_.do_dup2_x2(currentBB_);
    }

    void do_swap() {
      opStack_.do_swap(currentBB_);
    }

    void do_iadd() { do_binary_op_common(Instruction::Add, Type::IntTy); }
    void do_ladd() { do_binary_op_common(Instruction::Add, Type::LongTy); }
    void do_fadd() { do_binary_op_common(Instruction::Add, Type::FloatTy); }
    void do_dadd() { do_binary_op_common(Instruction::Add, Type::DoubleTy); }

    void do_isub() { do_binary_op_common(Instruction::Sub, Type::IntTy); }
    void do_lsub() { do_binary_op_common(Instruction::Sub, Type::LongTy); }
    void do_fsub() { do_binary_op_common(Instruction::Sub, Type::FloatTy); }
    void do_dsub() { do_binary_op_common(Instruction::Sub, Type::DoubleTy); }

    void do_imul() { do_binary_op_common(Instruction::Mul, Type::IntTy); }
    void do_lmul() { do_binary_op_common(Instruction::Mul, Type::LongTy); }
    void do_fmul() { do_binary_op_common(Instruction::Mul, Type::FloatTy); }
    void do_dmul() { do_binary_op_common(Instruction::Mul, Type::DoubleTy); }

    void do_idiv() { do_binary_op_common(Instruction::Div, Type::IntTy); }
    void do_ldiv() { do_binary_op_common(Instruction::Div, Type::LongTy); }
    void do_fdiv() { do_binary_op_common(Instruction::Div, Type::FloatTy); }
    void do_ddiv() { do_binary_op_common(Instruction::Div, Type::DoubleTy); }

    void do_irem() { do_binary_op_common(Instruction::Rem, Type::IntTy); }
    void do_lrem() { do_binary_op_common(Instruction::Rem, Type::LongTy); }
    void do_frem() { do_binary_op_common(Instruction::Rem, Type::FloatTy); }
    void do_drem() { do_binary_op_common(Instruction::Rem, Type::DoubleTy); }

    void do_ineg() { do_neg_common(Type::IntTy); }
    void do_lneg() { do_neg_common(Type::LongTy); }
    void do_fneg() { do_neg_common(Type::FloatTy); }
    void do_dneg() { do_neg_common(Type::DoubleTy); }

    void do_neg_common(const Type* type) {
      Value* v1 = pop(type);
      Value* r = BinaryOperator::createNeg(v1, TMP, currentBB_);
      push(r);
    }

    void do_ishl() { do_shift_common(Instruction::Shl, Type::IntTy); }
    void do_lshl() { do_shift_common(Instruction::Shl, Type::LongTy); }
    void do_ishr() { do_shift_common(Instruction::Shr, Type::IntTy); }
    void do_lshr() { do_shift_common(Instruction::Shr, Type::LongTy); }

    void do_iushr() { do_shift_common(Instruction::Shr, Type::UIntTy); }
    void do_lushr() { do_shift_common(Instruction::Shr, Type::ULongTy); }

    void do_shift_common(Instruction::OtherOps op, const Type* type) {
      llvm::Constant* mask =
        type == Type::IntTy ? INT_SHIFT_MASK : LONG_SHIFT_MASK;
      Value* a = pop(Type::UByteTy);
      a = BinaryOperator::create(Instruction::And, a, mask, TMP, currentBB_);
      Value* v = pop(type);
      Value* r = new ShiftInst(op, v, a, TMP, currentBB_);
      push(r);
    }

    void do_iand() { do_binary_op_common(Instruction::And, Type::IntTy); }
    void do_land() { do_binary_op_common(Instruction::And, Type::LongTy); }
    void do_ior() { do_binary_op_common(Instruction::Or, Type::IntTy); }
    void do_lor() { do_binary_op_common(Instruction::Or, Type::LongTy); }
    void do_ixor() { do_binary_op_common(Instruction::Xor, Type::IntTy); }
    void do_lxor() { do_binary_op_common(Instruction::Xor, Type::LongTy); }

    void do_binary_op_common(Instruction::BinaryOps op, const Type* type) {
      Value* v2 = pop(type);
      Value* v1 = pop(type);
      Value* r = BinaryOperator::create(op, v1, v2, TMP, currentBB_);
      push(r);
    }

    void do_iinc(unsigned index, int amount) {
      Value* v = locals_.load(index, Type::IntTy, currentBB_);
      Value* a = ConstantSInt::get(Type::IntTy, amount);
      v = BinaryOperator::createAdd(v, a, TMP, currentBB_);
      locals_.store(index, v, currentBB_);
    }

    void do_i2l() { do_cast_common(Type::IntTy, Type::LongTy); }
    void do_i2f() { do_cast_common(Type::IntTy, Type::FloatTy); }
    void do_i2d() { do_cast_common(Type::IntTy, Type::DoubleTy); }
    void do_l2i() { do_cast_common(Type::LongTy, Type::IntTy); }
    void do_l2f() { do_cast_common(Type::LongTy, Type::FloatTy); }
    void do_l2d() { do_cast_common(Type::LongTy, Type::DoubleTy); }
    void do_f2i() { do_cast_common(Type::FloatTy, Type::IntTy); }
    void do_f2l() { do_cast_common(Type::FloatTy, Type::LongTy); }
    void do_f2d() { do_cast_common(Type::FloatTy, Type::DoubleTy); }
    void do_d2i() { do_cast_common(Type::DoubleTy, Type::IntTy); }
    void do_d2l() { do_cast_common(Type::DoubleTy, Type::LongTy); }
    void do_d2f() { do_cast_common(Type::DoubleTy, Type::FloatTy); }
    void do_i2b() { do_cast_common(Type::IntTy, Type::SByteTy); }
    void do_i2c() { do_cast_common(Type::IntTy, Type::UShortTy); }
    void do_i2s() { do_cast_common(Type::IntTy, Type::ShortTy); }

    void do_cast_common(const Type* from, const Type* to) {
      Value* v1 = pop(from);
      push(new CastInst(v1, to, TMP, currentBB_));
    }

    void do_lcmp() {
      Value* v2 = pop(Type::LongTy);
      Value* v1 = pop(Type::LongTy);
      Value* c = BinaryOperator::createSetGT(v1, v2, TMP, currentBB_);
      Value* r = new SelectInst(c, ONE, ZERO, TMP, currentBB_);
      c = BinaryOperator::createSetLT(v1, v2, TMP, currentBB_);
      r = new SelectInst(c, MINUS_ONE, r, TMP, currentBB_);
      push(r);
    }

    void do_fcmpl() { do_cmp_common(Type::FloatTy, false); }
    void do_dcmpl() { do_cmp_common(Type::DoubleTy, false); }
    void do_fcmpg() { do_cmp_common(Type::FloatTy, true); }
    void do_dcmpg() { do_cmp_common(Type::DoubleTy, true); }

    void do_cmp_common(const Type* type, bool pushOne) {
      Value* v2 = pop(type);
      Value* v1 = pop(type);
      Value* c = BinaryOperator::createSetGT(v1, v2, TMP, currentBB_);
      Value* r = new SelectInst(c, ONE, ZERO, TMP, currentBB_);
      c = BinaryOperator::createSetLT(v1, v2, TMP, currentBB_);
      r = new SelectInst(c, MINUS_ONE, r, TMP, currentBB_);
      c = new CallInst(module_->getOrInsertFunction
                       ("llvm.isunordered",
                        Type::BoolTy, v1->getType(), v2->getType(), NULL),
                       v1, v2, TMP, currentBB_);
      r = new SelectInst(c, pushOne ? ONE : MINUS_ONE, r, TMP, currentBB_);
      push(r);
    }

    void do_ifeq(unsigned t, unsigned f) {
      do_iconst(0);
      do_if_common(Instruction::SetEQ, Type::IntTy, t, f);
    }
    void do_ifne(unsigned t, unsigned f) {
      do_iconst(0);
      do_if_common(Instruction::SetNE, Type::IntTy, t, f);
    }
    void do_iflt(unsigned t, unsigned f) {
      do_iconst(0);
      do_if_common(Instruction::SetLT, Type::IntTy, t, f);
    }
    void do_ifge(unsigned t, unsigned f) {
      do_iconst(0);
      do_if_common(Instruction::SetGE, Type::IntTy, t, f);
    }
    void do_ifgt(unsigned t, unsigned f) {
      do_iconst(0);
      do_if_common(Instruction::SetGT, Type::IntTy, t, f);
    }
    void do_ifle(unsigned t, unsigned f) {
      do_iconst(0);
      do_if_common(Instruction::SetLE, Type::IntTy, t, f);
    }
    void do_if_icmpeq(unsigned t, unsigned f) {
      do_if_common(Instruction::SetEQ, Type::IntTy, t, f);
    }
    void do_if_icmpne(unsigned t, unsigned f) {
      do_if_common(Instruction::SetNE, Type::IntTy, t, f);
    }
    void do_if_icmplt(unsigned t, unsigned f) {
      do_if_common(Instruction::SetLT, Type::IntTy, t, f);
    }
    void do_if_icmpge(unsigned t, unsigned f) {
      do_if_common(Instruction::SetGE, Type::IntTy, t, f);
    }
    void do_if_icmpgt(unsigned t, unsigned f) {
      do_if_common(Instruction::SetGT, Type::IntTy, t, f);
    }
    void do_if_icmple(unsigned t, unsigned f) {
      do_if_common(Instruction::SetLE, Type::IntTy, t, f);
    }
    void do_if_acmpeq(unsigned t, unsigned f) {
      do_if_common(Instruction::SetEQ, resolver_->getObjectBaseType(), t, f);
    }
    void do_if_acmpne(unsigned t, unsigned f) {
      do_if_common(Instruction::SetNE, resolver_->getObjectBaseType(), t, f);
    }
    void do_ifnull(unsigned t, unsigned f) {
      do_aconst_null();
      do_if_common(Instruction::SetEQ, resolver_->getObjectBaseType(), t, f);
    }
    void do_ifnonnull(unsigned t, unsigned f) {
      do_aconst_null();
      do_if_common(Instruction::SetNE, resolver_->getObjectBaseType(), t, f);
    }

    void do_if_common(Instruction::BinaryOps cc, const Type* type,
                      unsigned t, unsigned f) {
      Value* v2 = pop(type);
      Value* v1 = pop(type);
      Value* c = new SetCondInst(cc, v1, v2, TMP, currentBB_);
      new BranchInst(bbBuilder_->getBasicBlock(t),
                     bbBuilder_->getBasicBlock(f),
                     c, currentBB_);
    }

    void do_goto(unsigned target) {
      new BranchInst(bbBuilder_->getBasicBlock(target), currentBB_);
    }

    void do_ireturn() { do_return_common(Type::IntTy); }
    void do_lreturn() { do_return_common(Type::LongTy); }
    void do_freturn() { do_return_common(Type::FloatTy); }
    void do_dreturn() { do_return_common(Type::DoubleTy); }
    void do_areturn() { do_return_common(resolver_->getObjectBaseType()); }

    void do_return_common(const Type* type) {
      Value* r = pop(type);
      const Type* retTy = currentBB_->getParent()->getReturnType();
      if (retTy != r->getType())
        r = new CastInst(r, retTy, TMP, currentBB_);
      new ReturnInst(r, currentBB_);
    }

    void do_return() {
      new ReturnInst(NULL, currentBB_);
    }

    void do_jsr(unsigned target, unsigned retAddress) {
      // FIXME: this is currently a noop.
      push(llvm::Constant::getNullValue(Type::IntTy));
    }

    void do_ret(unsigned index) {
      // FIXME: this is currently a noop.
    }

    void do_tableswitch(unsigned defTarget, const SwitchCases& sw) {
      do_switch_common(defTarget, sw);
    }

    void do_lookupswitch(unsigned defTarget, const SwitchCases& sw) {
      do_switch_common(defTarget, sw);
    }

    void do_switch_common(unsigned defTarget, const SwitchCases& sw) {
      Value* v = pop(Type::IntTy);
      SwitchInst* in =
        new SwitchInst(v, bbBuilder_->getBasicBlock(defTarget), sw.size(),
                       currentBB_);
      for (unsigned i = 0, e = sw.size(); i != e; ++i)
        in->addCase(ConstantSInt::get(Type::IntTy, sw[i].first),
                    bbBuilder_->getBasicBlock(sw[i].second));
    }

    void do_getstatic(unsigned index) {
      const VMField* field = class_->getField(index);
      emitClassInitializers(field->getParent());

      Value* v = new LoadInst(field->getGlobal(), TMP, currentBB_);
      push(v);
    }

    void do_putstatic(unsigned index) {
      const VMField* field = class_->getField(index);
      emitClassInitializers(field->getParent());

      Value* v = pop(field->getClass()->getType());
      new StoreInst(v, field->getGlobal(), currentBB_);
    }

    void do_getfield(unsigned index) {
      const VMField* field = class_->getField(index);

      Value* p = pop(field->getParent()->getType());
      std::vector<Value*> indices(2);
      indices[0] = ConstantUInt::get(Type::UIntTy, 0);
      indices[1] = ConstantUInt::get(Type::UIntTy, field->getMemberIndex());
      Value* fieldPtr =
        new GetElementPtrInst(p, indices, field->getName()+'*', currentBB_);
      Value* v = new LoadInst(fieldPtr, field->getName(), currentBB_);
      push(v);
    }

    void do_putfield(unsigned index) {
      const VMField* field = class_->getField(index);

      Value* v = pop(field->getClass()->getType());
      Value* p = pop(field->getParent()->getType());
      std::vector<Value*> indices(2);
      indices[0] = ConstantUInt::get(Type::UIntTy, 0);
      indices[1] = ConstantUInt::get(Type::UIntTy, field->getMemberIndex());
      Value* fieldPtr =
        new GetElementPtrInst(p, indices, field->getName()+'*', currentBB_);
      new StoreInst(v, fieldPtr, currentBB_);
    }

    void makeCall(Value* fun, const std::vector<Value*> params) {
      const PointerType* funPtrTy = cast<PointerType>(fun->getType());
      const FunctionType* funTy =
        cast<FunctionType>(funPtrTy->getElementType());

      if (funTy->getReturnType() == Type::VoidTy)
        new CallInst(fun, params, "", currentBB_);
      else {
        Value* r = new CallInst(fun, params, TMP, currentBB_);
        push(r);
      }
    }

    std::vector<Value*> getParams(const FunctionType* funTy) {
      unsigned numParams = funTy->getNumParams();
      std::vector<Value*> params(numParams);
      while (numParams--)
        params[numParams] = pop(funTy->getParamType(numParams));

      return params;
    }

    void do_invokevirtual(unsigned index) {
      const VMMethod* method = class_->getMethod(index);
      const VMClass* clazz = method->getParent();

      Function* function = method->getFunction();
      std::vector<Value*> params(getParams(function->getFunctionType()));

      Value* objRef = params.front();
      objRef = new CastInst(objRef, clazz->getType(), "this", currentBB_);
      Value* objBase =
        new CastInst(objRef, resolver_->getObjectBaseType(), TMP, currentBB_);
      Value* classRecord =
        new CallInst(getClassRecord_, objBase, TMP, currentBB_);
      classRecord = new CastInst(classRecord,
                                 clazz->getClassRecord()->getType(),
                                 clazz->getName() + ".classRecord", currentBB_);
      std::vector<Value*> indices(1, ConstantUInt::get(Type::UIntTy, 0));
      assert(method->getMethodIndex() != -1 &&
             "Method index not found for dynamically bound method!");
      indices.push_back(
        ConstantUInt::get(Type::UIntTy, method->getMethodIndex()+1));
      Value* funPtr =
        new GetElementPtrInst(classRecord, indices, TMP, currentBB_);
      Value* fun = new LoadInst(funPtr, function->getName(), currentBB_);

      makeCall(fun, params);
    }

    void do_invokespecial(unsigned index) {
      const VMMethod* method = class_->getMethod(index);
      Function* function = method->getFunction();
      makeCall(function, getParams(function->getFunctionType()));
    }

    void do_invokestatic(unsigned index) {
      const VMMethod* method = class_->getMethod(index);
      emitClassInitializers(method->getParent());
      Function* function = method->getFunction();
      makeCall(function, getParams(function->getFunctionType()));
    }

    void do_invokeinterface(unsigned index) {
      const VMMethod* method = class_->getMethod(index);
      const VMClass* clazz = method->getParent();
      assert(clazz->isInterface() && "Class must be an interface!");

      Function* function = method->getFunction();
      std::vector<Value*> params(getParams(function->getFunctionType()));

      Value* objRef = params.front();
      objRef = new CastInst(objRef, clazz->getType(), "this", currentBB_);
      Value* objBase =
        new CastInst(objRef, resolver_->getObjectBaseType(), TMP, currentBB_);
      Value* classRecord =
        new CallInst(getClassRecord_, objBase, TMP, currentBB_);
      classRecord = new CastInst(classRecord,
                                 resolver_->getClassRecordPtrType(),
                                 TMP, currentBB_);
      // get the interfaces array of class records
      std::vector<Value*> indices(2, ConstantUInt::get(Type::UIntTy, 0));
      indices.push_back(ConstantUInt::get(Type::UIntTy, 4));
      Value* interfaceClassRecords =
        new GetElementPtrInst(classRecord, indices, TMP, currentBB_);
      interfaceClassRecords =
        new LoadInst(interfaceClassRecords, TMP, currentBB_);
      // Get the actual interface class record.
      indices.clear();
      indices.push_back(
        ConstantUInt::get(Type::UIntTy, clazz->getInterfaceIndex()));
      Value* interfaceClassRecord =
        new GetElementPtrInst(interfaceClassRecords, indices, TMP, currentBB_);
      interfaceClassRecord =
        new LoadInst(interfaceClassRecord,
                     clazz->getName() + ".classRecord", currentBB_);
      interfaceClassRecord =
        new CastInst(interfaceClassRecord,
                     clazz->getClassRecord()->getType(), TMP, currentBB_);
      // Get the function pointer.
      assert(method->getMethodIndex() != -1 &&
             "Method index not found for dynamically bound method!");
      indices.resize(2);
      indices[0] = ConstantUInt::get(Type::UIntTy, 0);
      indices[1] = ConstantUInt::get(Type::UIntTy, method->getMethodIndex()+1);
      Value* funPtr =
        new GetElementPtrInst(interfaceClassRecord, indices, TMP, currentBB_);
      Value* fun = new LoadInst(funPtr, function->getName(), currentBB_);

      makeCall(fun, params);
    }

    template<typename InsertionPointTy>
    Value* allocateObject(const VMClass& clazz, InsertionPointTy* ip) {
      static std::vector<Value*> params(4);

      Value* objRef = new MallocInst(clazz.getLayoutType(), NULL, TMP, ip);
      params[0] =
        new CastInst(objRef, PointerType::get(Type::SByteTy), TMP, ip); // dest
      params[1] = ConstantUInt::get(Type::UByteTy, 0); // value
      params[2] = ConstantExpr::getSizeOf(clazz.getLayoutType()); // size
      params[3] = ConstantUInt::get(Type::UIntTy, 0); // alignment
      new CallInst(memset_, params, "", ip);

      // Install the class record.
      Value* objBase =
        new CastInst(objRef, resolver_->getObjectBaseType(), TMP, ip);
      const Type* classRecordPtrType = resolver_->getClassRecordPtrType();
      Value* classRecord =
        new CastInst(clazz.getClassRecord(), classRecordPtrType, TMP, ip);
      new CallInst(setClassRecord_, objBase, classRecord, "", ip);

      return objRef;
    }

    void do_new(unsigned index) {
      const VMClass* clazz = class_->getClass(index);
      emitClassInitializers(clazz);
      push(allocateObject(*clazz, currentBB_));
    }

    template <typename InsertionPointTy>
    Value* getArrayLengthPtr(Value* arrayRef, InsertionPointTy* ip) const {
      std::vector<Value*> indices;
      indices.reserve(2);
      indices.push_back(ConstantUInt::get(Type::UIntTy, 0));
      indices.push_back(ConstantUInt::get(Type::UIntTy, 1));

      return new GetElementPtrInst(arrayRef, indices, TMP, ip);
    }

    template <typename InsertionPointTy>
    Value* getArrayObjectBasePtr(Value* arrayRef, InsertionPointTy* ip) const {
      std::vector<Value*> indices;
      indices.reserve(2);
      indices.push_back(ConstantUInt::get(Type::UIntTy, 0));
      indices.push_back(ConstantUInt::get(Type::UIntTy, 0));

      return new GetElementPtrInst(arrayRef, indices, TMP, ip);
    }

    template<typename InsertionPointTy>
    Value* allocateArray(const VMClass* clazz,
                         Value* count,
                         InsertionPointTy* ip) {
      static std::vector<Value*> params(4);

      assert(clazz->isArray() && "Not an array class!");
      const VMClass* componentClass = clazz->getComponentClass();
      const Type* elementTy = componentClass->getType();

      // The size of the element.
      llvm::Constant* elementSize =
        ConstantExpr::getCast(ConstantExpr::getSizeOf(elementTy), Type::UIntTy);

      // The size of the array part of the struct.
      Value* size = BinaryOperator::create(
        Instruction::Mul, count, elementSize, TMP, ip);
      // The size of the rest of the array object.
      llvm::Constant* arrayObjectSize =
        ConstantExpr::getCast(ConstantExpr::getSizeOf(clazz->getLayoutType()),
                              Type::UIntTy);

      // Add the array part plus the object part together.
      size = BinaryOperator::create(
        Instruction::Add, size, arrayObjectSize, TMP, ip);
      // Allocate memory for the object.
      Value* objRef = new MallocInst(Type::SByteTy, size, TMP, ip);
      params[0] = objRef; // dest
      params[1] = ConstantUInt::get(Type::UByteTy, 0); // value
      params[2] = new CastInst(size, Type::ULongTy, TMP, ip); // size
      params[3] = ConstantUInt::get(Type::UIntTy, 0); // alignment
      new CallInst(memset_, params, "", ip);

      // Cast back to array type.
      objRef = new CastInst(objRef, clazz->getType(), TMP, ip);

      // Store the size.
      Value* lengthPtr = getArrayLengthPtr(objRef, ip);
      new StoreInst(count, lengthPtr, ip);

      // Install the class record.
      Value* objBase = new CastInst(objRef, resolver_->getObjectBaseType(), TMP, ip);
      const Type* classRecordPtrType = resolver_->getClassRecordPtrType();
      Value* classRecord =
        new CastInst(clazz->getClassRecord(), classRecordPtrType, TMP, ip);
      new CallInst(setClassRecord_, objBase, classRecord, "", ip);

      return objRef;
    }

    void do_newarray(JType type) {
      Value* count = pop(Type::UIntTy);

      const VMClass* clazz = resolver_->getClass(type);
      const VMClass* arrayClass = resolver_->getArrayClass(clazz);
      emitClassInitializers(arrayClass);

      push(allocateArray(arrayClass, count, currentBB_));
    }

    void do_anewarray(unsigned index) {
      Value* count = pop(Type::UIntTy);

      const VMClass* clazz = class_->getClass(index);
      const VMClass* arrayClass = resolver_->getArrayClass(clazz);
      emitClassInitializers(arrayClass);

      push(allocateArray(arrayClass, count, currentBB_));
    }

    void do_arraylength() {
      const VMClass* clazz = resolver_->getClass("[Ljava/lang/Object;");
      Value* arrayRef = pop(clazz->getType());
      Value* lengthPtr = getArrayLengthPtr(arrayRef, currentBB_);
      Value* length = new LoadInst(lengthPtr, TMP, currentBB_);
      push(length);
    }

    void do_athrow() {
      Value* objRef = pop(resolver_->getObjectBaseType());
      new CallInst(throw_, objRef, "", currentBB_);
      new UnreachableInst(currentBB_);
    }

    void do_checkcast(unsigned index) {
      const VMClass* clazz = class_->getClass(index);

      Value* objRef = pop(resolver_->getObjectBaseType());
      const Type* classRecordPtrType = resolver_->getClassRecordPtrType();
      Value* classRecord = new CastInst(clazz->getClassRecord(),
                                        classRecordPtrType, TMP, currentBB_);
      Value* r =
        new CallInst(isInstanceOf_, objRef, classRecord, TMP, currentBB_);

      Value* b = new SetCondInst(Instruction::SetEQ,
                                 r, ConstantSInt::get(Type::IntTy, 1),
                                 TMP, currentBB_);
      // FIXME: if b is false we must throw a ClassCast exception
      push(objRef);
    }

    void do_instanceof(unsigned index) {
      const VMClass* clazz = class_->getClass(index);

      Value* objRef = pop(resolver_->getObjectBaseType());
      const Type* classRecordPtrType = resolver_->getClassRecordPtrType();
      Value* classRecord = new CastInst(clazz->getClassRecord(),
                                        classRecordPtrType, TMP, currentBB_);
      Value* r =
        new CallInst(isInstanceOf_, objRef, classRecord, TMP, currentBB_);
      push(r);
    }

    void do_monitorenter() {
      // FIXME: This is currently a noop.
      pop(resolver_->getObjectBaseType());
    }

    void do_monitorexit() {
      // FIXME: This is currently a noop.
      pop(resolver_->getObjectBaseType());
    }

    void do_multianewarray(unsigned index, unsigned dims) {
      assert(0 && "not implemented");
    }
  };

} } } // namespace llvm::Java::

std::auto_ptr<Module> llvm::Java::compile(const std::string& className)
{
  DEBUG(std::cerr << "Compiling class: " << className << '\n');

  std::auto_ptr<Module> m(new Module(className));
  // Require the Java runtime.
  m->addLibrary("jrt");

  Compiler c(m.get());
  const VMMethod* main =
    c.compileMethod(className, "main([Ljava/lang/String;)V");

  Function* javaMain = m->getOrInsertFunction
    ("llvm_java_main", Type::VoidTy,
     Type::IntTy, PointerType::get(PointerType::get(Type::SByteTy)), NULL);

  BasicBlock* bb = new BasicBlock("entry", javaMain);
  const FunctionType* mainTy = main->getFunction()->getFunctionType();
  new CallInst(main->getFunction(),
               // FIXME: Forward correct params from llvm_java_main
               llvm::Constant::getNullValue(mainTy->getParamType(0)),
               "",
               bb);
  new ReturnInst(NULL, bb);

  return m;
}
