//===----------------- vmjc.cpp - Java static compiler --------------------===//
//
//                           The VMKit project
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This utility may be invoked in the following manner:
//  vmjc [options] x - Read Java bytecode from the x.class file, write llvm
//                     bytecode to the x.bc file.
//  Options:
//      --help   - Output information about command line switches
//
//===----------------------------------------------------------------------===//

#include "llvm/LinkAllPasses.h"
#include "llvm/LinkAllVMCore.h"
#include "llvm/Module.h"
#include "llvm/PassManager.h"
#include "llvm/Assembly/PrintModulePass.h"
#include "llvm/CodeGen/LinkAllCodegenComponents.h"
#include "llvm/Bitcode/ReaderWriter.h"
#include "llvm/ExecutionEngine/ExecutionEngine.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/ManagedStatic.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/PassNameParser.h"
#include "llvm/Support/PluginLoader.h"
#include "llvm/Support/RegistryParser.h"
#include "llvm/Support/SystemUtils.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/System/Signals.h"
#include "llvm/Target/TargetData.h"
#include "llvm/Target/TargetMachine.h"


#include "MvmGC.h"
#include "mvm/JIT.h"
#include "mvm/Object.h"
#include "mvm/VirtualMachine.h"
#include "mvm/Threads/Thread.h"

#include "j3/JavaAOTCompiler.h"

#include "../../lib/J3/VMCore/JnjvmClassLoader.h"
#include "../../lib/J3/VMCore/Jnjvm.h"

#include <iostream>
#include <fstream>
#include <memory>
#include <string>

using namespace j3;
using namespace llvm;

static cl::opt<std::string>
InputFilename(cl::Positional, cl::desc("<input Java bytecode>"), cl::init("-"));

static cl::opt<std::string>
OutputFilename("o", cl::desc("Override output filename"),
               cl::value_desc("filename"));

static cl::opt<bool>
Force("f", cl::desc("Overwrite output files"));

static cl::opt<std::string>
MainClass("main", cl::desc("Specify main class"));

static cl::opt<bool>
WithJIT("with-jit", cl::desc("Generate main function with JIT support"));

static cl::opt<bool>
DisableOutput("disable-output", cl::desc("Disable output"), cl::init(false));

static cl::opt<std::string>
TargetTriple("mtriple", cl::desc("Override target triple for module"));

static cl::opt<bool>
DisableExceptions("disable-exceptions",
              cl::desc("Disable Java exceptions"));

static cl::opt<bool>
DisableCooperativeGC("disable-cooperativegc",
              cl::desc("Disable cooperative garbage collection"));


static cl::opt<bool>
DisableStubs("disable-stubs",
              cl::desc("Disable Java stubs"));

static cl::opt<bool>
AssumeCompiled("assume-compiled",
              cl::desc("Assume external Java classes are compiled"));

static cl::opt<bool> 
PrintStats("print-aot-stats", 
           cl::desc("Print stats by the AOT compiler"));


static cl::list<std::string> 
Properties("D", cl::desc("Set a property"), cl::Prefix, cl::ZeroOrMore);

static cl::list<std::string> 
WithClinit("with-clinit", cl::desc("Classes to clinit"), cl::ZeroOrMore,
           cl::CommaSeparated);

int main(int argc, char **argv) {
  llvm_shutdown_obj X;  // Call llvm_shutdown() on exit.
  cl::ParseCommandLineOptions(argc, argv, "vmkit .class -> .ll compiler\n");
  sys::PrintStackTraceOnErrorSignal();

  std::string ErrorMessage;

    
  if (InputFilename == "-") {
    cl::PrintHelpMessage();
    return 0;
  }
   
  // Disable cross-compiling for now.
  if (false) {
    Module* TheModule = new Module("bootstrap module",
                                   *(new llvm::LLVMContext()));
    if (!TargetTriple.empty())
      TheModule->setTargetTriple(TargetTriple);
    else
      TheModule->setTargetTriple(mvm::MvmModule::getHostTriple());

#if 0
    // explicitly specified an architecture to compile for.
    const Target *TheTarget = 0;
    if (!MArch.empty()) {
      for (TargetRegistry::iterator it = TargetRegistry::begin(),
           ie = TargetRegistry::end(); it != ie; ++it) {
        if (MArch == it->getName()) {
          TheTarget = &*it;
          break;
        }
      }

      if (!TheTarget) {
        errs() << argv[0] << ": error: invalid target '" << MArch << "'.\n";
        return 1;
      }
    } else {
      std::string Err;
      TheTarget =
        TargetRegistry::getClosestStaticTargetForModule(*TheModule, Err);
      if (TheTarget == 0) {
        errs() << argv[0] << ": error auto-selecting target for module '"
               << Err << "'.  Please use the -march option to explicitly "
               << "pick a target.\n";
        return 1;
      }
    }

    std::string FeaturesStr;
    std::auto_ptr<TargetMachine>
      target(TheTarget->createTargetMachine(*TheModule, FeaturesStr));
    assert(target.get() && "Could not allocate target machine!");
    TargetMachine &Target = *target.get();

    // Install information about target datalayout stuff into the module for
    // optimizer use.
    TheModule->setDataLayout(Target.getTargetData()->
                             getStringRepresentation());


    mvm::MvmModule::initialise(CodeGenOpt::Default, TheModule, &Target);
#endif
  } else {
    mvm::MvmModule::initialise();
  }

  JavaAOTCompiler* Comp = new JavaAOTCompiler("AOT");

  mvm::Collector::initialise();

  mvm::BumpPtrAllocator allocator;
  JnjvmBootstrapLoader* loader = new(allocator, "Bootstrap loader")
    JnjvmBootstrapLoader(allocator, Comp, false);

  if (DisableExceptions) Comp->disableExceptions();
  if (DisableStubs) Comp->generateStubs = false;
  if (AssumeCompiled) Comp->assumeCompiled = true;
  if (DisableCooperativeGC) Comp->disableCooperativeGC();
    
  Jnjvm* vm = new(allocator, "Bootstrap loader") Jnjvm(allocator, loader);
  
  for (std::vector<std::string>::iterator i = Properties.begin(),
       e = Properties.end(); i != e; ++i) {

    char* key = new char [(*i).size()+1];
    strcpy(key, (*i).c_str());
    char* value = strchr(key, '=');
    if (!value) {
      delete[] key;
    } else {
      value[0] = 0;
      vm->addProperty(key, &value[1]);
    }
  }

  Comp->clinits = &WithClinit;
  Comp->compileFile(vm, InputFilename.c_str());

  if (!MainClass.empty()) {
    Comp->generateMain(MainClass.c_str(), WithJIT);
  }

  if (PrintStats)
    Comp->printStats();

  // Infer the output filename if needed.
  if (OutputFilename.empty()) {
    if (InputFilename == "-") {
      OutputFilename = "-";
    } else {
      std::string IFN = InputFilename;
      int Len = IFN.length();
      if (IFN[Len-3] == '.' && IFN[Len-2] == 'l' && IFN[Len-1] == 'l') {
        // Source ends in .ll
        OutputFilename = std::string(IFN.begin(), IFN.end()-3);
      } else {
        OutputFilename = IFN;   // Append a .bc to it
      }   
      OutputFilename += ".bc";
    }   
  }
  
  std::string ErrorInfo;
  std::auto_ptr<raw_ostream> Out 
    (new raw_fd_ostream(OutputFilename.c_str(), ErrorInfo,
                        raw_fd_ostream::F_Binary));
  if (!ErrorInfo.empty()) {
    errs() << ErrorInfo << '\n';
    return 1;
  }
  
  
  // Make sure that the Out file gets unlinked from the disk if we get a
  // SIGINT.
  if (OutputFilename != "-")
    sys::RemoveFileOnSignal(sys::Path(OutputFilename));

  if (!DisableOutput)
    if (Force || !CheckBitcodeOutputToConsole(*Out, true))
      WriteBitcodeToFile(Comp->getLLVMModule(), *Out);

  return 0;
}

