/*
 * Copyright 2012, The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "bcc/AndroidBitcode/ABCCompilerDriver.h"

#include <llvm/Module.h>
#include <llvm/Pass.h>
#include <llvm/Support/MemoryBuffer.h>
#include <llvm/Support/raw_ostream.h>
#include <mcld/Config/Config.h>

#include "bcc/Config/Config.h"
#include "bcc/Script.h"
#include "bcc/Source.h"
#include "bcc/Support/CompilerConfig.h"
#include "bcc/Support/LinkerConfig.h"
#include "bcc/Support/Log.h"
#include "bcc/Support/OutputFile.h"
#include "bcc/Support/TargetLinkerConfigs.h"
#include "bcc/Support/TargetCompilerConfigs.h"

#if defined(PROVIDE_ARM_CODEGEN)
# include "ARM/ARMABCCompilerDriver.h"
#endif
#if defined(PROVIDE_MIPS_CODEGEN)
# include "Mips/MipsABCCompilerDriver.h"
#endif
#if defined(PROVIDE_X86_CODEGEN)
# include "X86/X86ABCCompilerDriver.h"
#endif

namespace bcc {

ABCCompilerDriver::ABCCompilerDriver(const std::string &pTriple)
  : mContext(), mCompiler(*this), mLinker(),
    mCompilerConfig(NULL), mLinkerConfig(NULL),
    mTriple(pTriple), mAndroidSysroot("/") {
}

ABCCompilerDriver::~ABCCompilerDriver() {
  delete mCompilerConfig;
  delete mLinkerConfig;
}

bool ABCCompilerDriver::configCompiler() {
  if (mCompilerConfig != NULL) {
    return true;
  }

  mCompilerConfig = new (std::nothrow) CompilerConfig(mTriple);
  if (mCompilerConfig == NULL) {
    ALOGE("Out of memory when create the compiler configuration!");
    return false;
  }

  // Set PIC mode for relocatables.
  mCompilerConfig->setRelocationModel(llvm::Reloc::PIC_);

  // Set optimization level to -O1.
  mCompilerConfig->setOptimizationLevel(llvm::CodeGenOpt::Less);

  Compiler::ErrorCode result = mCompiler.config(*mCompilerConfig);

  if (result != Compiler::kSuccess) {
    ALOGE("Failed to configure the compiler! (detail: %s)",
          Compiler::GetErrorString(result));
    return false;
  }

  return true;
}

bool ABCCompilerDriver::configLinker() {
  if (mLinkerConfig != NULL) {
    return true;
  }

  mLinkerConfig = new (std::nothrow) LinkerConfig(mTriple);
  if (mLinkerConfig == NULL) {
    ALOGE("Out of memory when create the linker configuration!");
    return false;
  }

  // FIXME: how can we get the soname if input/output is file descriptor?
  mLinkerConfig->setSOName("");

  mLinkerConfig->setDyld("/system/bin/linker");
  mLinkerConfig->setSysRoot(mAndroidSysroot);
  mLinkerConfig->addSearchDir("=/system/lib");

  // Add non-portable function list. For each function X, linker will rename
  // it to X_portable. And X_portable" is implemented in libportable to solve
  // portable issues.
  const char **non_portable_func = getNonPortableList();
  if (non_portable_func != NULL) {
    while (*non_portable_func != NULL) {
      mLinkerConfig->addPortable(*non_portable_func);
      non_portable_func++;
    }
  }

  // -shared
  mLinkerConfig->setShared(true);

  // -Bsymbolic.
  mLinkerConfig->setBsymbolic(true);

  // Config the linker.
  Linker::ErrorCode result = mLinker.config(*mLinkerConfig);
  if (result != Linker::kSuccess) {
    ALOGE("Failed to configure the linker! (%s)",
          Linker::GetErrorString(result));
    return false;
  }

  return true;
}

//------------------------------------------------------------------------------

Script *ABCCompilerDriver::prepareScript(int pInputFd) {
  Source *source = Source::CreateFromFd(mContext, pInputFd);
  if (source == NULL) {
    ALOGE("Failed to load LLVM module from file descriptor `%d'", pInputFd);
    return NULL;
  }

  Script *script = new (std::nothrow) Script(*source);
  if (script == NULL) {
    ALOGE("Out of memory when create script for file descriptor `%d'!",
          pInputFd);
    delete source;
    return NULL;
  }

  return script;
}

bool ABCCompilerDriver::compile(Script &pScript, llvm::raw_ostream &pOutput) {
  // Config the compiler.
  if (!configCompiler()) {
    return false;
  }

  // Run the compiler.
  Compiler::ErrorCode result = mCompiler.compile(pScript, pOutput);
  if (result != Compiler::kSuccess) {
    ALOGE("Fatal error during compilation (%s)!",
          Compiler::GetErrorString(result));
    return false;
  }

  return true;
}

bool ABCCompilerDriver::link(const Script &pScript,
                             const std::string &input_relocatable,
                             int pOutputFd) {
  // Config the linker.
  if (!configLinker()) {
    return false;
  }

  // Prepare output file.
  Linker::ErrorCode result = mLinker.setOutput(pOutputFd);

  if (result != Linker::kSuccess) {
    ALOGE("Failed to open the output file! (file descriptor `%d': %s)",
          pOutputFd, Linker::GetErrorString(result));
    return false;
  }

  mLinker.addObject(mAndroidSysroot + "/system/lib/crtbegin_so.o");

  // Prepare the relocatables.
  //
  // FIXME: Ugly const_cast here.
  mLinker.addObject(const_cast<char *>(input_relocatable.data()),
                    input_relocatable.size());

  // Read dependent library list.
  const Source &source = pScript.getSource();
  for (llvm::Module::lib_iterator lib_iter = source.getModule().lib_begin(),
          lib_end = source.getModule().lib_end(); lib_iter != lib_end;
       ++lib_iter) {
    mLinker.addNameSpec(*lib_iter);
  }

  // TODO: Refactor libbcc/runtime/ to libcompilerRT.so and use it.
  mLinker.addNameSpec("bcc");

  mLinker.addObject(mAndroidSysroot + "/system/lib/crtend_so.o");

  // Perform linking.
  result = mLinker.link();
  if (result != Linker::kSuccess) {
    ALOGE("Failed to link the shared object (detail: %s)",
          Linker::GetErrorString(result));
    return false;
  }

  return true;
}

//------------------------------------------------------------------------------

ABCCompilerDriver *ABCCompilerDriver::Create(const std::string &pTriple) {
  std::string error;
  const llvm::Target *target =
      llvm::TargetRegistry::lookupTarget(pTriple, error);

  if (target == NULL) {
    ALOGE("Unsupported target '%s' (detail: %s)!", pTriple.c_str(),
          error.c_str());
    return NULL;
  }

  switch (llvm::Triple::getArchTypeForLLVMName(target->getName())) {
#if defined(PROVIDE_ARM_CODEGEN)
    case llvm::Triple::arm:
    case llvm::Triple::thumb: {
      return new ARMABCCompilerDriver(pTriple);
    }
#endif
#if defined(PROVIDE_MIPS_CODEGEN)
    case llvm::Triple::mipsel: {
      return new MipsABCCompilerDriver(pTriple);
    }
#endif
#if defined(PROVIDE_X86_CODEGEN)
    case llvm::Triple::x86: {
      return new X86ABCCompilerDriver(pTriple);
    }
#endif
    default: {
      ALOGE("Unknown architecture '%s' supplied in %s!", target->getName(),
            pTriple.c_str());
      break;
    }
  }

  return NULL;
}

bool ABCCompilerDriver::build(int pInputFd, int pOutputFd) {
  //===--------------------------------------------------------------------===//
  // Prepare the input.
  //===--------------------------------------------------------------------===//
  Script *script = prepareScript(pInputFd);
  if (script == NULL) {
    return false;
  }

  //===--------------------------------------------------------------------===//
  // Prepare the output.
  //===--------------------------------------------------------------------===//
  std::string output_relocatable;
  llvm::raw_ostream *output =
      new (std::nothrow) llvm::raw_string_ostream(output_relocatable);
  if (output == NULL) {
    ALOGE("Failed to prepare the output for compile the input from %d into "
          "relocatable object!", pInputFd);
    delete script;
    return false;
  }

  //===--------------------------------------------------------------------===//
  // Compile.
  //===--------------------------------------------------------------------===//
  if (!compile(*script, *output)) {
    delete output;
    delete script;
    return false;
  }

  //===--------------------------------------------------------------------===//
  // Close the output.
  //===--------------------------------------------------------------------===//
  delete output;

  //===--------------------------------------------------------------------===//
  // Link.
  //===--------------------------------------------------------------------===//
  if (!link(*script, output_relocatable, pOutputFd)) {
    delete script;
    return false;
  }

  //===--------------------------------------------------------------------===//
  // Clean up.
  //===--------------------------------------------------------------------===//
  delete script;

  return true;
}

} // namespace bcc