//===- MCLinker.cpp -------------------------------------------------------===//
//
//                     The MCLinker Project
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file implements the MCLinker class.
//
//===----------------------------------------------------------------------===//
#include <mcld/CodeGen/MCLinker.h>

#include <mcld/Module.h>
#include <mcld/LinkerConfig.h>
#include <mcld/InputTree.h>
#include <mcld/Linker.h>
#include <mcld/IRBuilder.h>
#include <mcld/MC/InputBuilder.h>
#include <mcld/MC/FileAction.h>
#include <mcld/MC/CommandAction.h>
#include <mcld/Object/ObjectLinker.h>
#include <mcld/Support/CommandLine.h>
#include <mcld/Support/FileSystem.h>
#include <mcld/Support/MsgHandling.h>
#include <mcld/Support/FileHandle.h>
#include <mcld/Support/raw_ostream.h>
#include <mcld/Support/MemoryArea.h>

#include <llvm/IR/Module.h>
#include <llvm/Support/CommandLine.h>

#include <algorithm>
#include <vector>
#include <string>

using namespace mcld;
using namespace llvm;

char MCLinker::m_ID = 0;

//===----------------------------------------------------------------------===//
// Help Functions
//===----------------------------------------------------------------------===//
static inline bool CompareAction(const InputAction* X, const InputAction* Y)
{
  return (X->position() < Y->position());
}

//===----------------------------------------------------------------------===//
// Positional Options
// There are four kinds of positional options:
//   1. Inputs, object files, such as /tmp/XXXX.o
//   2. Namespecs, short names of libraries. A namespec may refer to an archive
//      or a shared library. For example, -lm.
//   3. Attributes of inputs. Attributes describe inputs appears after them.
//      For example, --as-needed and --whole-archive.
//   4. Groups. A Group is a set of archives. Linkers repeatedly read archives
//      in groups until there is no new undefined symbols.
//   5. Bitcode. Bitcode is a kind of object files. MCLinker compiles it to
//      object file first, then link it as a object file. (Bitcode is recorded
//      in BitcodeOption, not be read by LLVM Command Line library.)
//===----------------------------------------------------------------------===//
// Inputs
//===----------------------------------------------------------------------===//
static cl::list<mcld::sys::fs::Path>
ArgInputObjectFiles(cl::Positional,
                    cl::desc("[input object files]"),
                    cl::ZeroOrMore);

//===----------------------------------------------------------------------===//
// Namespecs
//===----------------------------------------------------------------------===//
static cl::list<std::string>
ArgNameSpecList("l",
            cl::ZeroOrMore,
            cl::desc("Add the archive or object file specified by namespec to "
                     "the list of files to link."),
            cl::value_desc("namespec"),
            cl::Prefix);

static cl::alias
ArgNameSpecListAlias("library",
                 cl::desc("alias for -l"),
                 cl::aliasopt(ArgNameSpecList));

//===----------------------------------------------------------------------===//
// Attributes
//===----------------------------------------------------------------------===//
static cl::list<bool>
ArgWholeArchiveList("whole-archive",
               cl::ValueDisallowed,
               cl::desc("For each archive mentioned on the command line after "
                        "the --whole-archive option, include all object files "
                        "in the archive."));

static cl::list<bool>
ArgNoWholeArchiveList("no-whole-archive",
               cl::ValueDisallowed,
               cl::desc("Turn off the effect of the --whole-archive option for "
                        "subsequent archive files."));

static cl::list<bool>
ArgAsNeededList("as-needed",
               cl::ValueDisallowed,
               cl::desc("This option affects ELF DT_NEEDED tags for dynamic "
                        "libraries mentioned on the command line after the "
                        "--as-needed option."));

static cl::list<bool>
ArgNoAsNeededList("no-as-needed",
               cl::ValueDisallowed,
               cl::desc("Turn off the effect of the --as-needed option for "
                        "subsequent dynamic libraries"));

static cl::list<bool>
ArgAddNeededList("add-needed",
                cl::ValueDisallowed,
                cl::desc("--add-needed causes DT_NEEDED tags are always "
                         "emitted for those libraries from DT_NEEDED tags. "
                         "This is the default behavior."));

static cl::list<bool>
ArgNoAddNeededList("no-add-needed",
                cl::ValueDisallowed,
                cl::desc("--no-add-needed causes DT_NEEDED tags will never be "
                         "emitted for those libraries from DT_NEEDED tags"));

static cl::list<bool>
ArgBDynamicList("Bdynamic",
                cl::ValueDisallowed,
                cl::desc("Link against dynamic library"));

static cl::alias
ArgBDynamicListAlias1("dy",
                cl::desc("alias for --Bdynamic"),
                cl::aliasopt(ArgBDynamicList));

static cl::alias
ArgBDynamicListAlias2("call_shared",
                cl::desc("alias for --Bdynamic"),
                cl::aliasopt(ArgBDynamicList));

static cl::list<bool>
ArgBStaticList("Bstatic",
                cl::ValueDisallowed,
                cl::desc("Link against static library"));

static cl::alias
ArgBStaticListAlias1("dn",
                cl::desc("alias for --Bstatic"),
                cl::aliasopt(ArgBStaticList));

static cl::alias
ArgBStaticListAlias2("static",
                cl::desc("alias for --Bstatic"),
                cl::aliasopt(ArgBStaticList));

static cl::alias
ArgBStaticListAlias3("non_shared",
                cl::desc("alias for --Bstatic"),
                cl::aliasopt(ArgBStaticList));

//===----------------------------------------------------------------------===//
// Groups
//===----------------------------------------------------------------------===//
static cl::list<bool>
ArgStartGroupList("start-group",
                  cl::ValueDisallowed,
                  cl::desc("start to record a group of archives"));

static cl::alias
ArgStartGroupListAlias("(",
                       cl::desc("alias for --start-group"),
                       cl::aliasopt(ArgStartGroupList));

static cl::list<bool>
ArgEndGroupList("end-group",
                cl::ValueDisallowed,
                cl::desc("stop recording a group of archives"));

static cl::alias
ArgEndGroupListAlias(")",
                     cl::desc("alias for --end-group"),
                     cl::aliasopt(ArgEndGroupList));

//===----------------------------------------------------------------------===//
// MCLinker
//===----------------------------------------------------------------------===//
MCLinker::MCLinker(LinkerConfig& pConfig,
                   mcld::Module& pModule,
                   MemoryArea& pOutput)
  : MachineFunctionPass(m_ID),
    m_Config(pConfig),
    m_Module(pModule),
    m_Output(pOutput),
    m_pBuilder(NULL),
    m_pLinker(NULL) {
}

MCLinker::~MCLinker()
{
  delete m_pLinker;
  delete m_pBuilder;
}

bool MCLinker::doInitialization(llvm::Module &pM)
{
  // Now, all input arguments are prepared well, send it into ObjectLinker
  m_pLinker = new Linker();

  if (!m_pLinker->emulate(m_Module.getScript(), m_Config))
    return false;

  m_pBuilder = new IRBuilder(m_Module, m_Config);

  initializeInputTree(*m_pBuilder);

  return true;
}

bool MCLinker::doFinalization(llvm::Module &pM)
{
  if (!m_pLinker->link(m_Module, *m_pBuilder))
    return true;

  if (!m_pLinker->emit(m_Output))
    return true;

  return false;
}

bool MCLinker::runOnMachineFunction(MachineFunction& pF)
{
  // basically, linkers do nothing during function is generated.
  return false;
}

void MCLinker::initializeInputTree(IRBuilder& pBuilder)
{
  if (0 == ArgInputObjectFiles.size() &&
      0 == ArgNameSpecList.size() &&
      !m_Config.bitcode().hasDefined()) {
    fatal(diag::err_no_inputs);
    return;
  }

  size_t num_actions = ArgInputObjectFiles.size() +
                       ArgNameSpecList.size() +
                       ArgWholeArchiveList.size() +
                       ArgNoWholeArchiveList.size() +
                       ArgAsNeededList.size() +
                       ArgNoAsNeededList.size() +
                       ArgAddNeededList.size() +
                       ArgNoAddNeededList.size() +
                       ArgBDynamicList.size() +
                       ArgBStaticList.size() +
                       ArgStartGroupList.size() +
                       ArgEndGroupList.size() +
                       1; // bitcode
  std::vector<InputAction*> actions;
  actions.reserve(num_actions);

  // -----  inputs  ----- //
  cl::list<mcld::sys::fs::Path>::iterator input, inBegin, inEnd;
  inBegin = ArgInputObjectFiles.begin();
  inEnd = ArgInputObjectFiles.end();
  for (input = inBegin; input != inEnd; ++input) {
    unsigned int pos = ArgInputObjectFiles.getPosition(input - inBegin);
    actions.push_back(new InputFileAction(pos, *input));
    actions.push_back(new ContextAction(pos));
    actions.push_back(new MemoryAreaAction(pos, FileHandle::ReadOnly));
  }

  // -----  namespecs  ----- //
  cl::list<std::string>::iterator namespec, nsBegin, nsEnd;
  nsBegin = ArgNameSpecList.begin();
  nsEnd = ArgNameSpecList.end();
  mcld::Module& module = pBuilder.getModule();
  for (namespec = nsBegin; namespec != nsEnd; ++namespec) {
    unsigned int pos = ArgNameSpecList.getPosition(namespec - nsBegin);
    actions.push_back(new NamespecAction(pos, *namespec,
                                         module.getScript().directories()));
    actions.push_back(new ContextAction(pos));
    actions.push_back(new MemoryAreaAction(pos, FileHandle::ReadOnly));
  }

  // -----  attributes  ----- //
  /// --whole-archive
  cl::list<bool>::iterator attr, attrBegin, attrEnd;
  attrBegin = ArgWholeArchiveList.begin();
  attrEnd   = ArgWholeArchiveList.end();
  for (attr = attrBegin; attr != attrEnd; ++attr) {
    unsigned int pos = ArgWholeArchiveList.getPosition(attr - attrBegin);
    actions.push_back(new WholeArchiveAction(pos));
  }

  /// --no-whole-archive
  attrBegin = ArgNoWholeArchiveList.begin();
  attrEnd   = ArgNoWholeArchiveList.end();
  for (attr = attrBegin; attr != attrEnd; ++attr) {
    unsigned int pos = ArgNoWholeArchiveList.getPosition(attr - attrBegin);
    actions.push_back(new NoWholeArchiveAction(pos));
  }

  /// --as-needed
  attrBegin = ArgAsNeededList.begin();
  attrEnd   = ArgAsNeededList.end();
  for (attr = attrBegin; attr != attrEnd; ++attr) {
    unsigned int pos = ArgAsNeededList.getPosition(attr - attrBegin);
    actions.push_back(new AsNeededAction(pos));
  }

  /// --no-as-needed
  attrBegin = ArgNoAsNeededList.begin();
  attrEnd   = ArgNoAsNeededList.end();
  for (attr = attrBegin; attr != attrEnd; ++attr) {
    unsigned int pos = ArgNoAsNeededList.getPosition(attr - attrBegin);
    actions.push_back(new NoAsNeededAction(pos));
  }

  /// --add--needed
  attrBegin = ArgAddNeededList.begin();
  attrEnd   = ArgAddNeededList.end();
  for (attr = attrBegin; attr != attrEnd; ++attr) {
    unsigned int pos = ArgAddNeededList.getPosition(attr - attrBegin);
    actions.push_back(new AddNeededAction(pos));
  }

  /// --no-add--needed
  attrBegin = ArgNoAddNeededList.begin();
  attrEnd   = ArgNoAddNeededList.end();
  for (attr = attrBegin; attr != attrEnd; ++attr) {
    unsigned int pos = ArgNoAddNeededList.getPosition(attr - attrBegin);
    actions.push_back(new NoAddNeededAction(pos));
  }

  /// --Bdynamic
  attrBegin = ArgBDynamicList.begin();
  attrEnd   = ArgBDynamicList.end();
  for (attr = attrBegin; attr != attrEnd; ++attr) {
    unsigned int pos = ArgBDynamicList.getPosition(attr - attrBegin);
    actions.push_back(new BDynamicAction(pos));
  }

  /// --Bstatic
  attrBegin = ArgBStaticList.begin();
  attrEnd   = ArgBStaticList.end();
  for (attr = attrBegin; attr != attrEnd; ++attr) {
    unsigned int pos = ArgBStaticList.getPosition(attr - attrBegin);
    actions.push_back(new BStaticAction(pos));
  }

  // -----  groups  ----- //
  /// --start-group
  cl::list<bool>::iterator group, gsBegin, gsEnd;
  gsBegin = ArgStartGroupList.begin();
  gsEnd   = ArgStartGroupList.end();
  for (group = gsBegin; group != gsEnd; ++group) {
    unsigned int pos = ArgStartGroupList.getPosition(group - gsBegin);
    actions.push_back(new StartGroupAction(pos));
  }

  /// --end-group
  gsBegin = ArgEndGroupList.begin();
  gsEnd   = ArgEndGroupList.end();
  for (group = gsBegin; group != gsEnd; ++group) {
    unsigned int pos = ArgEndGroupList.getPosition(group - gsBegin);
    actions.push_back(new EndGroupAction(pos));
  }

  // -----  bitcode  ----- //
  if (m_Config.bitcode().hasDefined()) {
    actions.push_back(new BitcodeAction(m_Config.bitcode().getPosition(),
                                        m_Config.bitcode().getPath()));
  }

  // stable sort
  std::stable_sort(actions.begin(), actions.end(), CompareAction);

  // build up input tree
  std::vector<InputAction*>::iterator action, actionEnd = actions.end();
  for (action = actions.begin(); action != actionEnd; ++action) {
    (*action)->activate(pBuilder.getInputBuilder());
    delete *action;
  }

  if (pBuilder.getInputBuilder().isInGroup())
    report_fatal_error("no matched --start-group and --end-group");
}