//===- SectLinker.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 SectLinker class.
//
//===----------------------------------------------------------------------===//
#include <mcld/Support/FileHandle.h>
#include <mcld/MC/InputTree.h>
#include <mcld/MC/MCLDDriver.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/MemoryAreaFactory.h>
#include <mcld/Support/DerivedPositionDependentOptions.h>
#include <mcld/Target/TargetLDBackend.h>
#include <mcld/CodeGen/SectLinker.h>
#include <mcld/CodeGen/SectLinkerOption.h>

#include <llvm/Module.h>

#include <algorithm>
#include <stack>
#include <string>

using namespace mcld;
using namespace llvm;

//===----------------------------------------------------------------------===//
// Forward declarations
char SectLinker::m_ID = 0;
static bool CompareOption(const PositionDependentOption* X,
                          const PositionDependentOption* Y);

//===----------------------------------------------------------------------===//
// SectLinker
SectLinker::SectLinker(SectLinkerOption &pOption,
                       TargetLDBackend& pLDBackend)
  : MachineFunctionPass(m_ID),
    m_pOption(&pOption),
    m_pLDBackend(&pLDBackend),
    m_pLDDriver(NULL),
    m_pMemAreaFactory(NULL)
{
  m_pMemAreaFactory = new MemoryAreaFactory(32);
}

SectLinker::~SectLinker()
{
  delete m_pLDDriver;

  // FIXME: current implementation can not change the order of delete.
  //
  // Instance of TargetLDBackend was created outside and is not managed by
  // SectLinker. It should not be destroyed here and by SectLinker. However, in
  // order to follow the LLVM convention - that is, the pass manages all the
  // objects it used during the processing, we destroy the object of
  // TargetLDBackend here.
  delete m_pLDBackend;

  delete m_pMemAreaFactory;
}

bool SectLinker::doInitialization(Module &pM)
{
  MCLDInfo &info = m_pOption->info();

  // ----- convert position dependent options into tree of input files  ----- //
  PositionDependentOptions &PosDepOpts = m_pOption->pos_dep_options();
  std::stable_sort(PosDepOpts.begin(), PosDepOpts.end(), CompareOption);
  initializeInputTree(PosDepOpts);
  initializeInputOutput(info);
  // Now, all input arguments are prepared well, send it into MCLDDriver
  m_pLDDriver = new MCLDDriver(info, *m_pLDBackend, *memAreaFactory());

  return false;
}

bool SectLinker::doFinalization(Module &pM)
{
  const MCLDInfo &info = m_pOption->info();

  // 2. - initialize MCLinker
  if (!m_pLDDriver->initMCLinker())
    return true;

  // 3. - initialize output's standard sections
  if (!m_pLDDriver->initStdSections())
    return true;

  // 4. - normalize the input tree
  m_pLDDriver->normalize();

  if (info.options().trace()) {
    static int counter = 0;
    mcld::outs() << "** name\ttype\tpath\tsize (" << info.inputs().size() << ")\n";
    InputTree::const_dfs_iterator input, inEnd = info.inputs().dfs_end();
    for (input=info.inputs().dfs_begin(); input!=inEnd; ++input) {
      mcld::outs() << counter++ << " *  " << (*input)->name();
      switch((*input)->type()) {
      case Input::Archive:
        mcld::outs() << "\tarchive\t(";
        break;
      case Input::Object:
        mcld::outs() << "\tobject\t(";
        break;
      case Input::DynObj:
        mcld::outs() << "\tshared\t(";
        break;
      case Input::Script:
        mcld::outs() << "\tscript\t(";
        break;
      case Input::External:
        mcld::outs() << "\textern\t(";
        break;
      default:
        unreachable(diag::err_cannot_trace_file) << (*input)->type()
                                                 << (*input)->name()
                                                 << (*input)->path();
      }
      mcld::outs() << (*input)->path() << ")\n";
    }
  }

  // 5. - check if we can do static linking and if we use split-stack.
  if (!m_pLDDriver->linkable())
    return true;


  // 6. - merge all sections
  if (!m_pLDDriver->mergeSections())
    return true;

  // 7. - add standard symbols and target-dependent symbols
  // m_pLDDriver->addUndefSymbols();
  if (!m_pLDDriver->addStandardSymbols() ||
      !m_pLDDriver->addTargetSymbols())
    return true;

  // 8. - read all relocation entries from input files
  m_pLDDriver->readRelocations();

  // 9. - pre-layout
  m_pLDDriver->prelayout();

  // 10. - linear layout
  m_pLDDriver->layout();

  // 10.b - post-layout (create segment, instruction relaxing)
  m_pLDDriver->postlayout();

  // 11. - finalize symbol value
  m_pLDDriver->finalizeSymbolValue();

  // 12. - apply relocations
  m_pLDDriver->relocation();

  // 13. - write out output
  m_pLDDriver->emitOutput();

  // 14. - post processing
  m_pLDDriver->postProcessing();
  return false;
}

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

void SectLinker::initializeInputOutput(MCLDInfo &pLDInfo)
{
  // -----  initialize output file  ----- //
  FileHandle::Permission perm;
  if (Output::Object == pLDInfo.output().type())
    perm = 0544;
  else
    perm = 0755;

  MemoryArea* out_area = memAreaFactory()->produce(pLDInfo.output().path(),
                                                 FileHandle::ReadWrite,
                                                 perm);

  if (!out_area->handler()->isGood()) {
    // make sure output is openend successfully.
    fatal(diag::err_cannot_open_output_file) << pLDInfo.output().name()
                                             << pLDInfo.output().path();
  }

  pLDInfo.output().setMemArea(out_area);
  pLDInfo.output().setContext(pLDInfo.contextFactory().produce());

  // -----  initialize input files  ----- //
  InputTree::dfs_iterator input, inEnd = pLDInfo.inputs().dfs_end();
  for (input = pLDInfo.inputs().dfs_begin(); input!=inEnd; ++input) {
    // already got type - for example, bitcode
    if ((*input)->type() == Input::Script ||
        (*input)->type() == Input::Object ||
        (*input)->type() == Input::DynObj  ||
        (*input)->type() == Input::Archive)
      continue;

    MemoryArea *input_memory =
        memAreaFactory()->produce((*input)->path(), FileHandle::ReadOnly);

    if (input_memory->handler()->isGood()) {
      (*input)->setMemArea(input_memory);
    }
    else {
      error(diag::err_cannot_open_input) << (*input)->name() << (*input)->path();
      return;
    }

    LDContext *input_context =
        pLDInfo.contextFactory().produce((*input)->path());

    (*input)->setContext(input_context);
  }
}

void SectLinker::initializeInputTree(const PositionDependentOptions &pPosDepOptions) const
{
  if (pPosDepOptions.empty())
    fatal(diag::err_no_inputs);

  MCLDInfo &info = m_pOption->info();
  PositionDependentOptions::const_iterator option = pPosDepOptions.begin();
  if (1 == pPosDepOptions.size() &&
      ((*option)->type() != PositionDependentOption::INPUT_FILE &&
       (*option)->type() != PositionDependentOption::NAMESPEC) &&
       (*option)->type() != PositionDependentOption::BITCODE) {
    // if we only have one positional options, and the option is
    // not an input file, then emit error message.
    fatal(diag::err_no_inputs);
  }

  // -----  Input tree insertion algorithm  ----- //
  //   The type of the previsou node indicates the direction of the current
  //   insertion.
  //
  //     root   : the parent node who being inserted.
  //     mover  : the direcion of current movement.
  //
  //   for each positional options:
  //     insert the options in current root.
  //     calculate the next movement

  // Initialization
  InputTree::Mover *move = &InputTree::Downward;
  InputTree::iterator root = info.inputs().root();
  PositionDependentOptions::const_iterator optionEnd = pPosDepOptions.end();
  std::stack<InputTree::iterator> returnStack;

  while (option != optionEnd ) {

    switch ((*option)->type()) {
      /** bitcode **/
      case PositionDependentOption::BITCODE: {

        const BitcodeOption *bitcode_option =
            static_cast<const BitcodeOption*>(*option);

        // threat bitcode as an external IR in this version.
        info.inputs().insert(root, *move,
                             bitcode_option->path()->native(),
                             *(bitcode_option->path()),
                             Input::External);

        info.setBitcode(**root);

        // move root on the new created node.
        move->move(root);

        // the next file is appended after bitcode file.
        move = &InputTree::Afterward;
        break;
      }

      /** input object file **/
      case PositionDependentOption::INPUT_FILE: {
        const InputFileOption *input_file_option =
            static_cast<const InputFileOption*>(*option);

        info.inputs().insert(root, *move,
                             input_file_option->path()->native(),
                             *(input_file_option->path()));

        // move root on the new created node.
        move->move(root);

        // the next file is appended after object file.
        move = &InputTree::Afterward;
        break;
      }

    /** -lnamespec **/
    case PositionDependentOption::NAMESPEC: {
      sys::fs::Path* path = NULL;
      const NamespecOption *namespec_option =
          static_cast<const NamespecOption*>(*option);

      // find out the real path of the namespec.
      if (info.attrFactory().constraint().isSharedSystem()) {
        // In the system with shared object support, we can find both archive
        // and shared object.

        if (info.attrFactory().last().isStatic()) {
          // with --static, we must search an archive.
          path = info.options().directories().find(namespec_option->namespec(),
                                                   Input::Archive);
        }
        else {
          // otherwise, with --Bdynamic, we can find either an archive or a
          // shared object.
          path = info.options().directories().find(namespec_option->namespec(),
                                                   Input::DynObj);
        }
      }
      else {
        // In the system without shared object support, we only look for an
        // archive.
        path = info.options().directories().find(namespec_option->namespec(),
                                                 Input::Archive);
      }

      if (NULL == path)
        fatal(diag::err_cannot_find_namespec) << namespec_option->namespec();

      info.inputs().insert(root, *move,
                           namespec_option->namespec(),
                           *path);

      // iterate root on the new created node.
      move->move(root);

      // the file after a namespec must be appended afterward.
      move = &InputTree::Afterward;
      break;
    }

    /** start group **/
    case PositionDependentOption::START_GROUP:
      info.inputs().enterGroup(root, *move);
      move->move(root);
      returnStack.push(root);
      move = &InputTree::Downward;
      break;
    /** end group **/
    case PositionDependentOption::END_GROUP:
      root = returnStack.top();
      returnStack.pop();
      move = &InputTree::Afterward;
      break;
    case PositionDependentOption::WHOLE_ARCHIVE:
      info.attrFactory().last().setWholeArchive();
      break;
    case PositionDependentOption::NO_WHOLE_ARCHIVE:
      info.attrFactory().last().unsetWholeArchive();
      break;
    case PositionDependentOption::AS_NEEDED:
      info.attrFactory().last().setAsNeeded();
      break;
    case PositionDependentOption::NO_AS_NEEDED:
      info.attrFactory().last().unsetAsNeeded();
      break;
    case PositionDependentOption::ADD_NEEDED:
      info.attrFactory().last().setAddNeeded();
      break;
    case PositionDependentOption::NO_ADD_NEEDED:
      info.attrFactory().last().unsetAddNeeded();
      break;
    case PositionDependentOption::BSTATIC:
      info.attrFactory().last().setStatic();
      break;
    case PositionDependentOption::BDYNAMIC:
      info.attrFactory().last().setDynamic();
      break;
    default:
      fatal(diag::err_cannot_identify_option) << (*option)->position()
                                              << (uint32_t)(*option)->type();
    } // end of switch
    ++option;
  } // end of while

  if (!returnStack.empty()) {
    report_fatal_error("no matched --start-group and --end-group");
  }
}

//===----------------------------------------------------------------------===//
// Non-member functions
static bool CompareOption(const PositionDependentOption* X,
                          const PositionDependentOption* Y)
{
  return (X->position() < Y->position());
}