//===- ObjectBuilder.cpp --------------------------------------------------===//
//
//                     The MCLinker Project
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "mcld/Object/ObjectBuilder.h"

#include "mcld/IRBuilder.h"
#include "mcld/LinkerScript.h"
#include "mcld/Module.h"
#include "mcld/Fragment/AlignFragment.h"
#include "mcld/Fragment/FillFragment.h"
#include "mcld/Fragment/NullFragment.h"
#include "mcld/LD/DebugString.h"
#include "mcld/LD/EhFrame.h"
#include "mcld/LD/LDSection.h"
#include "mcld/LD/SectionData.h"
#include "mcld/Object/SectionMap.h"

#include <llvm/Support/Casting.h>

namespace mcld {

//===----------------------------------------------------------------------===//
// ObjectBuilder
//===----------------------------------------------------------------------===//
ObjectBuilder::ObjectBuilder(Module& pTheModule) : m_Module(pTheModule) {
}

/// CreateSection - create an output section.
LDSection* ObjectBuilder::CreateSection(const std::string& pName,
                                        LDFileFormat::Kind pKind,
                                        uint32_t pType,
                                        uint32_t pFlag,
                                        uint32_t pAlign) {
  // try to get one from output LDSection
  SectionMap::const_mapping pair =
      m_Module.getScript().sectionMap().find("*", pName);

  std::string output_name = (pair.first == NULL) ? pName : pair.first->name();

  LDSection* output_sect = m_Module.getSection(output_name);
  if (output_sect == NULL) {
    output_sect = LDSection::Create(pName, pKind, pType, pFlag);
    output_sect->setAlign(pAlign);
    m_Module.getSectionTable().push_back(output_sect);
  }
  return output_sect;
}

/// MergeSection - merge the pInput section to the pOutput section
LDSection* ObjectBuilder::MergeSection(const Input& pInputFile,
                                       LDSection& pInputSection) {
  SectionMap::mapping pair = m_Module.getScript().sectionMap().find(
      pInputFile.path().native(), pInputSection.name());

  if (pair.first != NULL && pair.first->isDiscard()) {
    pInputSection.setKind(LDFileFormat::Ignore);
    return NULL;
  }

  std::string output_name =
      (pair.first == NULL) ? pInputSection.name() : pair.first->name();
  LDSection* target = m_Module.getSection(output_name);

  if (target == NULL) {
    target = LDSection::Create(output_name,
                               pInputSection.kind(),
                               pInputSection.type(),
                               pInputSection.flag());
    target->setAlign(pInputSection.align());
    m_Module.getSectionTable().push_back(target);
  }

  switch (target->kind()) {
    case LDFileFormat::EhFrame: {
      EhFrame* eh_frame = NULL;
      if (target->hasEhFrame())
        eh_frame = target->getEhFrame();
      else
        eh_frame = IRBuilder::CreateEhFrame(*target);

      eh_frame->merge(pInputFile, *pInputSection.getEhFrame());
      UpdateSectionAlign(*target, pInputSection);
      return target;
    }
    case LDFileFormat::DebugString: {
      DebugString* debug_str = NULL;
      if (target->hasDebugString())
        debug_str = target->getDebugString();
      else
        debug_str = IRBuilder::CreateDebugString(*target);

      debug_str->merge(pInputSection);
      UpdateSectionAlign(*target, pInputSection);
      return target;
    }
    default: {
      if (!target->hasSectionData())
        IRBuilder::CreateSectionData(*target);

      SectionData* data = NULL;
      if (pair.first != NULL) {
        assert(pair.second != NULL);
        data = pair.second->getSection()->getSectionData();

        // force input alignment from ldscript if any
        if (pair.first->prolog().hasSubAlign()) {
          pInputSection.setAlign(pair.second->getSection()->align());
        }
      } else {
        // orphan section
        data = target->getSectionData();
      }

      if (MoveSectionData(*pInputSection.getSectionData(), *data)) {
        UpdateSectionAlign(*target, pInputSection);
        return target;
      }
      return NULL;
    }
  }
  return target;
}

/// MoveSectionData - move the fragments of pTO section data to pTo
bool ObjectBuilder::MoveSectionData(SectionData& pFrom, SectionData& pTo) {
  assert(&pFrom != &pTo && "Cannot move section data to itself!");

  uint64_t offset = pTo.getSection().size();
  AlignFragment* align = NULL;
  if (pFrom.getSection().align() > 1) {
    // if the align constraint is larger than 1, append an alignment
    unsigned int alignment = pFrom.getSection().align();
    align = new AlignFragment(/*alignment*/alignment,
                              /*the filled value*/0x0,
                              /*the size of filled value*/1u,
                              /*max bytes to emit*/alignment - 1);
    align->setOffset(offset);
    align->setParent(&pTo);
    pTo.getFragmentList().push_back(align);
    offset += align->size();
  }

  // move fragments from pFrom to pTO
  SectionData::FragmentListType& from_list = pFrom.getFragmentList();
  SectionData::FragmentListType& to_list = pTo.getFragmentList();
  SectionData::FragmentListType::iterator frag, fragEnd = from_list.end();
  for (frag = from_list.begin(); frag != fragEnd; ++frag) {
    frag->setParent(&pTo);
    frag->setOffset(offset);
    offset += frag->size();
  }
  to_list.splice(to_list.end(), from_list);

  // set up pTo's header
  pTo.getSection().setSize(offset);

  return true;
}

/// UpdateSectionAlign - update alignment for input section
void ObjectBuilder::UpdateSectionAlign(LDSection& pTo, const LDSection& pFrom) {
  if (pFrom.align() > pTo.align())
    pTo.setAlign(pFrom.align());
}

/// UpdateSectionAlign - update alignment for input section
void ObjectBuilder::UpdateSectionAlign(LDSection& pSection,
                                       uint32_t pAlignConstraint) {
  if (pSection.align() < pAlignConstraint)
    pSection.setAlign(pAlignConstraint);
}

/// AppendFragment - To append pFrag to the given SectionData pSD.
uint64_t ObjectBuilder::AppendFragment(Fragment& pFrag,
                                       SectionData& pSD,
                                       uint32_t pAlignConstraint) {
  // get initial offset.
  uint64_t offset = 0;
  if (!pSD.empty())
    offset = pSD.back().getOffset() + pSD.back().size();

  AlignFragment* align = NULL;
  if (pAlignConstraint > 1) {
    // if the align constraint is larger than 1, append an alignment
    align = new AlignFragment(/*alignment*/pAlignConstraint,
                              /*the filled value*/0x0,
                              /*the size of filled value*/1u,
                              /*max bytes to emit*/pAlignConstraint - 1);
    align->setOffset(offset);
    align->setParent(&pSD);
    pSD.getFragmentList().push_back(align);
    offset += align->size();
  }

  // append the fragment
  pFrag.setParent(&pSD);
  pFrag.setOffset(offset);
  pSD.getFragmentList().push_back(&pFrag);

  // append the null fragment
  offset += pFrag.size();
  NullFragment* null = new NullFragment(&pSD);
  null->setOffset(offset);

  if (align != NULL)
    return align->size() + pFrag.size();
  else
    return pFrag.size();
}

}  // namespace mcld