//===- StubFactory.cpp ----------------------------------------------------===//
//
//                     The MCLinker Project
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include <mcld/LD/StubFactory.h>
#include <mcld/IRBuilder.h>
#include <mcld/LD/BranchIslandFactory.h>
#include <mcld/LD/BranchIsland.h>
#include <mcld/LD/LDSymbol.h>
#include <mcld/LD/ResolveInfo.h>
#include <mcld/Fragment/Stub.h>
#include <mcld/Fragment/Relocation.h>
#include <mcld/Fragment/FragmentRef.h>

#include <string>

using namespace mcld;

//===----------------------------------------------------------------------===//
// StubFactory
//===----------------------------------------------------------------------===//
StubFactory::~StubFactory()
{
  for (StubPoolType::iterator it = m_StubPool.begin(), ie = m_StubPool.end();
       it != ie; ++it)
    delete(*it);
}

/// addPrototype - register a stub prototype
void StubFactory::addPrototype(Stub* pPrototype)
{
  m_StubPool.push_back(pPrototype);
}

/// create - create a stub if needed, otherwise return NULL
Stub* StubFactory::create(Relocation& pReloc,
                          uint64_t pTargetSymValue,
                          IRBuilder& pBuilder,
                          BranchIslandFactory& pBRIslandFactory)
{
  // find if there is a prototype stub for the input relocation
  Stub* prototype = findPrototype(pReloc,
                                  pReloc.place(),
                                  pTargetSymValue);
  if (NULL != prototype) {
    // find the island for the input relocation
    BranchIsland* island = pBRIslandFactory.find(*(pReloc.targetRef().frag()));
    if (NULL == island) {
      island = pBRIslandFactory.produce(*(pReloc.targetRef().frag()));
    }

    // find if there is such a stub in the island already
    assert(NULL != island);
    Stub* stub = island->findStub(prototype, pReloc);
    if (NULL != stub) {
      // reset the branch target to the stub instead!
      pReloc.setSymInfo(stub->symInfo());
    }
    else {
      // create a stub from the prototype
      stub = prototype->clone();

      // build a name for stub symbol
      std::string name("__");
      name.append(pReloc.symInfo()->name());
      name.append("_");
      name.append(stub->name());
      name.append("@");
      name.append(island->name());

      // create LDSymbol for the stub
      LDSymbol* symbol =
        pBuilder.AddSymbol<IRBuilder::Force, IRBuilder::Resolve>(
                               name,
                               ResolveInfo::Function,
                               ResolveInfo::Define,
                               ResolveInfo::Local,
                               stub->size(), // size
                               stub->initSymValue(), // value
                               FragmentRef::Create(*stub, stub->initSymValue()),
                               ResolveInfo::Default);
      stub->setSymInfo(symbol->resolveInfo());

      // add relocations of this stub (i.e., set the branch target of the stub)
      for (Stub::fixup_iterator it = stub->fixup_begin(),
             ie = stub->fixup_end(); it != ie; ++it) {

        Relocation* reloc = Relocation::Create((*it)->type(),
                                 *(FragmentRef::Create(*stub, (*it)->offset())),
                                 (*it)->addend());
        reloc->setSymInfo(pReloc.symInfo());
        island->addRelocation(*reloc);
      }

      // add stub to the branch island
      island->addStub(prototype, pReloc, *stub);

      // reset the branch target of the input reloc to this stub instead!
      pReloc.setSymInfo(stub->symInfo());
      return stub;
    }
  }
  return NULL;
}

/// findPrototype - find if there is a registered stub prototype for the given
/// relocation
Stub* StubFactory::findPrototype(const Relocation& pReloc,
                                 uint64_t pSource,
                                 uint64_t pTargetSymValue)
{
  for (StubPoolType::iterator it = m_StubPool.begin(), ie = m_StubPool.end();
       it != ie; ++it) {
    if ((*it)->isMyDuty(pReloc, pSource, pTargetSymValue))
      return (*it);
  }
  return NULL;
}