//===- MipsGOT.cpp --------------------------------------------------------===//
//
//                     The MCLinker Project
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "MipsGOT.h"

#include <llvm/Support/Casting.h>

#include <mcld/LD/ResolveInfo.h>
#include <mcld/Support/MemoryRegion.h>
#include <mcld/Support/MsgHandling.h>

namespace {
  const size_t MipsGOTEntrySize = 4;
  const size_t MipsGOT0Num = 1;
}

using namespace mcld;

//===----------------------------------------------------------------------===//
// MipsGOT
MipsGOT::MipsGOT(LDSection& pSection, SectionData& pSectionData)
  : GOT(pSection, pSectionData, MipsGOTEntrySize),
    m_pLocalNum(0)
{
  // Create GOT0 entries.
  for (size_t i = 0; i < MipsGOT0Num; ++i) {
    GOTEntry* entry =
      new (std::nothrow) GOTEntry(0, MipsGOTEntrySize, &m_SectionData);

    if (NULL == entry)
      fatal(diag::fail_allocate_memory_got);

    m_Section.setSize(m_Section.size() + MipsGOTEntrySize);
  }

  // Skip GOT0 entries.
  iterator it = m_SectionData.begin();

  for (size_t i = 1; i < MipsGOT0Num; ++i) {
    assert((it != m_SectionData.end()) &&
           "Generation of GOT0 entries is incomplete!");

    ++it;
  }

  m_LocalGOTIterator = it;
  m_GlobalGOTIterator = it;
  m_pLocalNum = MipsGOT0Num;
}

MipsGOT::iterator MipsGOT::begin()
{
  return m_SectionData.getFragmentList().begin();
}

MipsGOT::iterator MipsGOT::end()
{
  return m_SectionData.getFragmentList().end();
}

MipsGOT::const_iterator MipsGOT::begin() const
{
  return m_SectionData.getFragmentList().begin();
}

MipsGOT::const_iterator MipsGOT::end() const
{
  return m_SectionData.getFragmentList().end();
}

uint64_t MipsGOT::emit(MemoryRegion& pRegion)
{
  uint32_t* buffer = reinterpret_cast<uint32_t*>(pRegion.getBuffer());

  size_t entry_size = getEntrySize();

  uint64_t result = 0;
  for (iterator it = begin(), ie = end();
       it != ie; ++it, ++buffer) {
    GOTEntry* got = &(llvm::cast<GOTEntry>((*it)));
    *buffer = static_cast<uint32_t>(got->getContent());
    result += entry_size;
  }
  return result;
}

void MipsGOT::reserveEntry(size_t pNum)
{
  for (size_t i = 0; i < pNum; ++i) {
    GOTEntry* entry =
      new (std::nothrow) GOTEntry(0, MipsGOTEntrySize, &m_SectionData);

    if (NULL == entry)
      fatal(diag::fail_allocate_memory_got);

    m_Section.setSize(m_Section.size() + MipsGOTEntrySize);
  }
}

void MipsGOT::reserveLocalEntry()
{
  reserveEntry(1);
  ++m_pLocalNum;

  // Move global entries iterator forward.
  // We need to put global GOT entries after all local ones.
  ++m_GlobalGOTIterator;
}

void MipsGOT::reserveGlobalEntry()
{
  reserveEntry(1);
}

GOTEntry* MipsGOT::getEntry(const ResolveInfo& pInfo, bool& pExist)
{
  if (isLocal(&pInfo) && pInfo.type() == ResolveInfo::Section) {
    pExist = false;
    iterator& it = m_LocalGOTIterator;
    ++it;
    assert(it != m_SectionData.getFragmentList().end() &&
           "The number of GOT Entries and ResolveInfo doesn't match");
    GOTEntry* entry = llvm::cast<GOTEntry>(&(*it));
    return entry;
  }

  GOTEntry*& entry = m_GeneralGOTMap[&pInfo];

  pExist = NULL != entry;

  if (!pExist) {
    iterator& it = isLocal(&pInfo)  ? m_LocalGOTIterator : m_GlobalGOTIterator;

    ++it;

    assert(it != m_SectionData.getFragmentList().end() &&
           "The number of GOT Entries and ResolveInfo doesn't match");

    entry = llvm::cast<GOTEntry>(&(*it));
  }

  return entry;
}

size_t MipsGOT::getTotalNum() const
{
  return m_SectionData.getFragmentList().size();
}

size_t MipsGOT::getLocalNum() const
{
  return m_pLocalNum;
}