//===- ELFDynamic.cpp -------------    ------------------------------------===//
//
//                     The MCLinker Project
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include <llvm/Support/Host.h>
#include <llvm/Support/ErrorHandling.h>
#include <mcld/Target/ELFDynamic.h>
#include <mcld/Target/GNULDBackend.h>
#include <mcld/LD/ELFFileFormat.h>
#include <mcld/MC/MCLDInfo.h>
#include <mcld/Support/MemoryRegion.h>

using namespace mcld;
using namespace elf_dynamic;

//===----------------------------------------------------------------------===//
// elf_dynamic::EntryIF
EntryIF::EntryIF()
{
}

EntryIF::~EntryIF()
{
}

//===----------------------------------------------------------------------===//
// ELFDynamic
ELFDynamic::ELFDynamic(const GNULDBackend& pParent)
  : m_pEntryFactory(NULL), m_Idx(0) {
  if (32 == pParent.bitclass() && pParent.isLittleEndian()) {
    m_pEntryFactory = new Entry<32, true>();
  }
  // FIXME: support big-endian and 64-bit machine.
}


ELFDynamic::~ELFDynamic()
{
  if (NULL != m_pEntryFactory)
    delete m_pEntryFactory;

  EntryListType::iterator entry, entryEnd = m_EntryList.end();
  for (entry = m_EntryList.begin(); entry != entryEnd; ++entry) {
    if (NULL != *entry)
      delete (*entry);
  }

  entryEnd = m_NeedList.end();
  for (entry = m_NeedList.begin(); entry != entryEnd; ++entry) {
    if (NULL != *entry)
      delete (*entry);
  }
}

size_t ELFDynamic::size() const
{
  return (m_NeedList.size() + m_EntryList.size());
}

size_t ELFDynamic::numOfBytes() const
{
  return size()*entrySize();
}

size_t ELFDynamic::entrySize() const
{
  return m_pEntryFactory->size();
}

void ELFDynamic::reserveOne(uint64_t pTag)
{
  assert(NULL != m_pEntryFactory);
  m_EntryList.push_back(new elf_dynamic::Entry<32, true>());
}

void ELFDynamic::applyOne(uint64_t pTag, uint64_t pValue)
{
  assert(m_Idx < m_EntryList.size());
  m_EntryList[m_Idx]->setValue(pTag, pValue);
  ++m_Idx;
}


/// reserveEntries - reserve entries
void ELFDynamic::reserveEntries(const MCLDInfo& pLDInfo,
                                const ELFFileFormat& pFormat)
{
  if (pLDInfo.output().type() == Output::DynObj) {
    reserveOne(llvm::ELF::DT_SONAME); // DT_SONAME

    if (pLDInfo.options().Bsymbolic())
      reserveOne(llvm::ELF::DT_SYMBOLIC); // DT_SYMBOLIC
  }

  if (pFormat.hasInit())
    reserveOne(llvm::ELF::DT_INIT); // DT_INIT

  if (pFormat.hasFini())
    reserveOne(llvm::ELF::DT_FINI); // DT_FINI

  if (pFormat.hasInitArray()) {
    reserveOne(llvm::ELF::DT_INIT_ARRAY); // DT_INIT_ARRAY
    reserveOne(llvm::ELF::DT_INIT_ARRAYSZ); // DT_INIT_ARRAYSZ
  }

  if (pFormat.hasFiniArray()) {
    reserveOne(llvm::ELF::DT_FINI_ARRAY); // DT_FINI_ARRAY
    reserveOne(llvm::ELF::DT_FINI_ARRAYSZ); // DT_FINI_ARRAYSZ
  }

  if (pFormat.hasHashTab())
    reserveOne(llvm::ELF::DT_HASH); // DT_HASH

  if (pFormat.hasDynSymTab()) {
    reserveOne(llvm::ELF::DT_SYMTAB); // DT_SYMTAB
    reserveOne(llvm::ELF::DT_SYMENT); // DT_SYMENT
  }

  if (pFormat.hasDynStrTab()) {
    reserveOne(llvm::ELF::DT_STRTAB); // DT_STRTAB
    reserveOne(llvm::ELF::DT_STRSZ); // DT_STRSZ
  }

  reserveTargetEntries(pFormat); // DT_PLTGOT

  if (pFormat.hasRelPlt() || pFormat.hasRelaPlt())
    reserveOne(llvm::ELF::DT_PLTREL); // DT_PLTREL

  if (pFormat.hasPLT()) {
    reserveOne(llvm::ELF::DT_JMPREL); // DT_JMPREL
    reserveOne(llvm::ELF::DT_PLTRELSZ); // DT_PLTRELSZ
  }

  if (pFormat.hasRelDyn()) {
    reserveOne(llvm::ELF::DT_REL); // DT_REL
    reserveOne(llvm::ELF::DT_RELSZ); // DT_RELSZ
    reserveOne(llvm::ELF::DT_RELENT); // DT_RELENT
  }

  if (pFormat.hasRelaDyn()) {
    reserveOne(llvm::ELF::DT_RELA); // DT_RELA
    reserveOne(llvm::ELF::DT_RELASZ); // DT_RELASZ
    reserveOne(llvm::ELF::DT_RELAENT); // DT_RELAENT
  }
  reserveOne(llvm::ELF::DT_NULL); // for DT_NULL
}

/// applyEntries - apply entries
void ELFDynamic::applyEntries(const MCLDInfo& pInfo,
                              const ELFFileFormat& pFormat)
{
  if (pInfo.output().type() == Output::DynObj &&
      pInfo.options().Bsymbolic()) {
      applyOne(llvm::ELF::DT_SYMBOLIC, 0x0); // DT_SYMBOLIC
  }

  if (pFormat.hasInit())
    applyOne(llvm::ELF::DT_INIT, pFormat.getInit().addr()); // DT_INIT

  if (pFormat.hasFini())
    applyOne(llvm::ELF::DT_FINI, pFormat.getFini().addr()); // DT_FINI

  if (pFormat.hasInitArray()) {
    // DT_INIT_ARRAY
    applyOne(llvm::ELF::DT_INIT_ARRAY, pFormat.getInitArray().addr());

    // DT_INIT_ARRAYSZ
    applyOne(llvm::ELF::DT_INIT_ARRAYSZ, pFormat.getInitArray().size());
  }

  if (pFormat.hasFiniArray()) {
    // DT_FINI_ARRAY
    applyOne(llvm::ELF::DT_FINI_ARRAY, pFormat.getFiniArray().addr());

    // DT_FINI_ARRAYSZ
    applyOne(llvm::ELF::DT_FINI_ARRAYSZ, pFormat.getFiniArray().size());
  }

  if (pFormat.hasHashTab())
    applyOne(llvm::ELF::DT_HASH, pFormat.getHashTab().addr()); // DT_HASH

  if (pFormat.hasDynSymTab()) {
    applyOne(llvm::ELF::DT_SYMTAB, pFormat.getDynSymTab().addr()); // DT_SYMTAB
    applyOne(llvm::ELF::DT_SYMENT, symbolSize()); // DT_SYMENT
  }

  if (pFormat.hasDynStrTab()) {
    applyOne(llvm::ELF::DT_STRTAB, pFormat.getDynStrTab().addr()); // DT_STRTAB
    applyOne(llvm::ELF::DT_STRSZ, pFormat.getDynStrTab().size()); // DT_STRSZ
  }

  applyTargetEntries(pFormat); // DT_PLTGOT

  if (pFormat.hasRelPlt())
    applyOne(llvm::ELF::DT_PLTREL, llvm::ELF::DT_REL); // DT_PLTREL
  else if (pFormat.hasRelaPlt())
    applyOne(llvm::ELF::DT_PLTREL, llvm::ELF::DT_RELA); // DT_PLTREL

  if (pFormat.hasRelPlt()) {
    applyOne(llvm::ELF::DT_JMPREL, pFormat.getRelPlt().addr()); // DT_JMPREL
    applyOne(llvm::ELF::DT_PLTRELSZ, pFormat.getRelPlt().size()); // DT_PLTRELSZ
  }

  if (pFormat.hasRelDyn()) {
    applyOne(llvm::ELF::DT_REL, pFormat.getRelDyn().addr()); // DT_REL
    applyOne(llvm::ELF::DT_RELSZ, pFormat.getRelDyn().size()); // DT_RELSZ
    applyOne(llvm::ELF::DT_RELENT, m_pEntryFactory->relSize()); // DT_RELENT
  }

  if (pFormat.hasRelaDyn()) {
    applyOne(llvm::ELF::DT_RELA, pFormat.getRelaDyn().addr()); // DT_RELA
    applyOne(llvm::ELF::DT_RELASZ, pFormat.getRelaDyn().size()); // DT_RELASZ
    applyOne(llvm::ELF::DT_RELAENT, m_pEntryFactory->relaSize()); // DT_RELAENT
  }

  applyOne(llvm::ELF::DT_NULL, 0x0); // for DT_NULL
}

/// symbolSize
size_t ELFDynamic::symbolSize() const
{
  return m_pEntryFactory->symbolSize();
}

/// reserveNeedEntry - reserve on DT_NEED entry.
void ELFDynamic::reserveNeedEntry()
{
  m_NeedList.push_back(m_pEntryFactory->clone());
}

/// emit
void ELFDynamic::emit(const LDSection& pSection, MemoryRegion& pRegion) const
{
  if (pRegion.size() < pSection.size()) {
    llvm::report_fatal_error(llvm::Twine("the given memory is smaller") +
                             llvm::Twine(" than the section's demaind.\n"));
  }

  uint8_t* address = (uint8_t*)pRegion.start();
  EntryListType::const_iterator entry, entryEnd = m_NeedList.end();
  for (entry = m_NeedList.begin(); entry != entryEnd; ++entry)
    address += (*entry)->emit(address);

  entryEnd = m_EntryList.end();
  for (entry = m_EntryList.begin(); entry != entryEnd; ++entry)
    address += (*entry)->emit(address);
}

void ELFDynamic::applySoname(uint64_t pStrTabIdx)
{
  applyOne(llvm::ELF::DT_SONAME, pStrTabIdx); // DT_SONAME
}