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

#include "mcld/Fragment/Relocation.h"
#include "mcld/LD/LDContext.h"
#include "mcld/LD/LDSection.h"
#include "mcld/LD/LDSymbol.h"
#include "mcld/LD/RelocData.h"
#include "mcld/LD/ResolveInfo.h"
#include "mcld/LD/SectionData.h"
#include "mcld/MC/Input.h"
#include "mcld/Object/ObjectBuilder.h"
#include "mcld/Support/GCFactory.h"

#include <llvm/Support/ManagedStatic.h>

namespace mcld {

typedef GCFactory<EhFrame, MCLD_SECTIONS_PER_INPUT> EhFrameFactory;

static llvm::ManagedStatic<EhFrameFactory> g_EhFrameFactory;

//===----------------------------------------------------------------------===//
// EhFrame::Record
//===----------------------------------------------------------------------===//
EhFrame::Record::Record(llvm::StringRef pRegion) : RegionFragment(pRegion) {
}

EhFrame::Record::~Record() {
  // llvm::iplist will manage and delete the fragments
}

//===----------------------------------------------------------------------===//
// EhFrame::CIE
//===----------------------------------------------------------------------===//
EhFrame::CIE::CIE(llvm::StringRef pRegion)
    : EhFrame::Record(pRegion),
      m_FDEEncode(0u),
      m_Mergeable(false),
      m_pReloc(0),
      m_PersonalityOffset(0) {
}

EhFrame::CIE::~CIE() {
}

//===----------------------------------------------------------------------===//
// EhFrame::FDE
//===----------------------------------------------------------------------===//
EhFrame::FDE::FDE(llvm::StringRef pRegion, EhFrame::CIE& pCIE)
    : EhFrame::Record(pRegion), m_pCIE(&pCIE) {
}

EhFrame::FDE::~FDE() {
}

void EhFrame::FDE::setCIE(EhFrame::CIE& pCIE) {
  m_pCIE = &pCIE;
  m_pCIE->add(*this);
}

//===----------------------------------------------------------------------===//
// EhFrame::GeneratedCIE
//===----------------------------------------------------------------------===//
EhFrame::GeneratedCIE::GeneratedCIE(llvm::StringRef pRegion)
    : EhFrame::CIE(pRegion) {
}

EhFrame::GeneratedCIE::~GeneratedCIE() {
}

//===----------------------------------------------------------------------===//
// EhFrame::GeneratedFDE
//===----------------------------------------------------------------------===//
EhFrame::GeneratedFDE::GeneratedFDE(llvm::StringRef pRegion, CIE& pCIE)
    : EhFrame::FDE(pRegion, pCIE) {
}

EhFrame::GeneratedFDE::~GeneratedFDE() {
}

//===----------------------------------------------------------------------===//
// EhFrame
//===----------------------------------------------------------------------===//
EhFrame::EhFrame() : m_pSection(NULL), m_pSectionData(NULL) {
}

EhFrame::EhFrame(LDSection& pSection)
    : m_pSection(&pSection), m_pSectionData(NULL) {
  m_pSectionData = SectionData::Create(pSection);
}

EhFrame::~EhFrame() {
}

EhFrame* EhFrame::Create(LDSection& pSection) {
  EhFrame* result = g_EhFrameFactory->allocate();
  new (result) EhFrame(pSection);
  return result;
}

void EhFrame::Destroy(EhFrame*& pSection) {
  pSection->~EhFrame();
  g_EhFrameFactory->deallocate(pSection);
  pSection = NULL;
}

void EhFrame::Clear() {
  g_EhFrameFactory->clear();
}

const LDSection& EhFrame::getSection() const {
  assert(m_pSection != NULL);
  return *m_pSection;
}

LDSection& EhFrame::getSection() {
  assert(m_pSection != NULL);
  return *m_pSection;
}

void EhFrame::addFragment(Fragment& pFrag) {
  uint32_t offset = 0;
  if (!m_pSectionData->empty())
    offset = m_pSectionData->back().getOffset() + m_pSectionData->back().size();

  m_pSectionData->getFragmentList().push_back(&pFrag);
  pFrag.setParent(m_pSectionData);
  pFrag.setOffset(offset);
}

void EhFrame::addCIE(EhFrame::CIE& pCIE, bool pAlsoAddFragment) {
  m_CIEs.push_back(&pCIE);
  if (pAlsoAddFragment)
    addFragment(pCIE);
}

void EhFrame::addFDE(EhFrame::FDE& pFDE, bool pAlsoAddFragment) {
  pFDE.getCIE().add(pFDE);
  if (pAlsoAddFragment)
    addFragment(pFDE);
}

size_t EhFrame::numOfFDEs() const {
  // FDE number only used by .eh_frame_hdr computation, and the number of CIE
  // is usually not too many. It is worthy to compromise space by time
  size_t size = 0u;
  for (const_cie_iterator i = cie_begin(), e = cie_end(); i != e; ++i)
    size += (*i)->numOfFDEs();
  return size;
}

EhFrame& EhFrame::merge(const Input& pInput, EhFrame& pFrame) {
  assert(this != &pFrame);
  if (pFrame.emptyCIEs()) {
    // May be a partial linking, or the eh_frame has no data.
    // Just append the fragments.
    moveInputFragments(pFrame);
    return *this;
  }

  const LDContext& ctx = *pInput.context();
  const LDSection* rel_sec = 0;
  for (LDContext::const_sect_iterator ri = ctx.relocSectBegin(),
                                      re = ctx.relocSectEnd();
       ri != re;
       ++ri) {
    if ((*ri)->getLink() == &pFrame.getSection()) {
      rel_sec = *ri;
      break;
    }
  }
  pFrame.setupAttributes(rel_sec);

  // Most CIE will be merged, so we don't reserve space first.
  for (cie_iterator i = pFrame.cie_begin(), e = pFrame.cie_end(); i != e; ++i) {
    CIE& input_cie = **i;
    // CIE number is usually very few, so we just use vector sequential search.
    if (!input_cie.getMergeable()) {
      moveInputFragments(pFrame, input_cie);
      addCIE(input_cie, /*AlsoAddFragment=*/false);
      continue;
    }

    cie_iterator out_i = cie_begin();
    for (cie_iterator out_e = cie_end(); out_i != out_e; ++out_i) {
      CIE& output_cie = **out_i;
      if (output_cie == input_cie) {
        // This input CIE can be merged
        moveInputFragments(pFrame, input_cie, &output_cie);
        removeAndUpdateCIEForFDE(pFrame, input_cie, output_cie, rel_sec);
        break;
      }
    }
    if (out_i == cie_end()) {
      moveInputFragments(pFrame, input_cie);
      addCIE(input_cie, /*AlsoAddFragment=*/false);
    }
  }
  return *this;
}

void EhFrame::setupAttributes(const LDSection* rel_sec) {
  for (cie_iterator i = cie_begin(), e = cie_end(); i != e; ++i) {
    CIE* cie = *i;
    removeDiscardedFDE(*cie, rel_sec);

    if (cie->getPersonalityName().size() == 0) {
      // There's no personality data encoding inside augmentation string.
      cie->setMergeable();
    } else {
      if (!rel_sec) {
        // No relocation to eh_frame section
        assert(cie->getPersonalityName() != "" &&
               "PR name should be a symbol address or offset");
        continue;
      }
      const RelocData* reloc_data = rel_sec->getRelocData();
      for (RelocData::const_iterator ri = reloc_data->begin(),
                                     re = reloc_data->end();
           ri != re;
           ++ri) {
        const Relocation& rel = *ri;
        if (rel.targetRef().getOutputOffset() ==
            cie->getOffset() + cie->getPersonalityOffset()) {
          cie->setMergeable();
          cie->setPersonalityName(rel.symInfo()->outSymbol()->name());
          cie->setRelocation(rel);
          break;
        }
      }

      assert(cie->getPersonalityName() != "" &&
             "PR name should be a symbol address or offset");
    }
  }
}

void EhFrame::removeDiscardedFDE(CIE& pCIE, const LDSection* pRelocSect) {
  if (!pRelocSect)
    return;

  typedef std::vector<FDE*> FDERemoveList;
  FDERemoveList to_be_removed_fdes;
  const RelocData* reloc_data = pRelocSect->getRelocData();
  for (fde_iterator i = pCIE.begin(), e = pCIE.end(); i != e; ++i) {
    FDE& fde = **i;
    for (RelocData::const_iterator ri = reloc_data->begin(),
                                   re = reloc_data->end();
         ri != re;
         ++ri) {
      const Relocation& rel = *ri;
      if (rel.targetRef().getOutputOffset() ==
          fde.getOffset() + getDataStartOffset<32>()) {
        bool has_section = rel.symInfo()->outSymbol()->hasFragRef();
        if (!has_section)
          // The section was discarded, just ignore this FDE.
          // This may happen when redundant group section was read.
          to_be_removed_fdes.push_back(&fde);
        break;
      }
    }
  }

  for (FDERemoveList::iterator i = to_be_removed_fdes.begin(),
                               e = to_be_removed_fdes.end();
       i != e;
       ++i) {
    FDE& fde = **i;
    fde.getCIE().remove(fde);

    // FIXME: This traverses relocations from the beginning on each FDE, which
    // may cause performance degration. Actually relocations will be sequential
    // order, so we can bookkeep the previously found relocation for next use.
    // Note: We must ensure FDE order is ordered.
    for (RelocData::const_iterator ri = reloc_data->begin(),
                                   re = reloc_data->end();
         ri != re;) {
      Relocation& rel = const_cast<Relocation&>(*ri++);
      if (rel.targetRef().getOutputOffset() >= fde.getOffset() &&
          rel.targetRef().getOutputOffset() < fde.getOffset() + fde.size()) {
        const_cast<RelocData*>(reloc_data)->remove(rel);
      }
    }
  }
}

void EhFrame::removeAndUpdateCIEForFDE(EhFrame& pInFrame,
                                       CIE& pInCIE,
                                       CIE& pOutCIE,
                                       const LDSection* rel_sect) {
  // Make this relocation to be ignored.
  Relocation* rel = const_cast<Relocation*>(pInCIE.getRelocation());
  if (rel && rel_sect)
    const_cast<RelocData*>(rel_sect->getRelocData())->remove(*rel);

  // Update the CIE-pointed FDEs
  for (fde_iterator i = pInCIE.begin(), e = pInCIE.end(); i != e; ++i)
    (*i)->setCIE(pOutCIE);

  // We cannot know whether there are references to this fragment, so just
  // keep it in input fragment list instead of memory deallocation
  pInCIE.clearFDEs();
}

void EhFrame::moveInputFragments(EhFrame& pInFrame) {
  SectionData& in_sd = *pInFrame.getSectionData();
  SectionData::FragmentListType& in_frag_list = in_sd.getFragmentList();
  SectionData& out_sd = *getSectionData();
  SectionData::FragmentListType& out_frag_list = out_sd.getFragmentList();

  while (!in_frag_list.empty()) {
    Fragment* frag = in_frag_list.remove(in_frag_list.begin());
    out_frag_list.push_back(frag);
    frag->setParent(&out_sd);
  }
}

void EhFrame::moveInputFragments(EhFrame& pInFrame, CIE& pInCIE, CIE* pOutCIE) {
  SectionData& in_sd = *pInFrame.getSectionData();
  SectionData::FragmentListType& in_frag_list = in_sd.getFragmentList();
  SectionData& out_sd = *getSectionData();
  SectionData::FragmentListType& out_frag_list = out_sd.getFragmentList();

  if (!pOutCIE) {
    // Newly inserted
    Fragment* frag = in_frag_list.remove(SectionData::iterator(pInCIE));
    out_frag_list.push_back(frag);
    frag->setParent(&out_sd);
    for (fde_iterator i = pInCIE.begin(), e = pInCIE.end(); i != e; ++i) {
      frag = in_frag_list.remove(SectionData::iterator(**i));
      out_frag_list.push_back(frag);
      frag->setParent(&out_sd);
    }
    return;
  }

  SectionData::iterator cur_iter(*pOutCIE);
  assert(cur_iter != out_frag_list.end());
  for (fde_iterator i = pInCIE.begin(), e = pInCIE.end(); i != e; ++i) {
    Fragment* frag = in_frag_list.remove(SectionData::iterator(**i));
    cur_iter = out_frag_list.insertAfter(cur_iter, frag);
    frag->setParent(&out_sd);
  }
}

size_t EhFrame::computeOffsetSize() {
  size_t offset = 0u;
  SectionData::FragmentListType& frag_list =
      getSectionData()->getFragmentList();
  for (SectionData::iterator i = frag_list.begin(), e = frag_list.end(); i != e;
       ++i) {
    Fragment& frag = *i;
    frag.setOffset(offset);
    offset += frag.size();
  }
  getSection().setSize(offset);
  return offset;
}

bool operator==(const EhFrame::CIE& p1, const EhFrame::CIE& p2) {
  return p1.getPersonalityName() == p2.getPersonalityName() &&
         p1.getAugmentationData() == p2.getAugmentationData();
}

}  // namespace mcld