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

#include "mcld/LD/EhFrame.h"
#include "mcld/LD/LDSection.h"

#include <llvm/Support/Dwarf.h>
#include <llvm/Support/DataTypes.h>

#include <algorithm>
#include <cstring>

namespace mcld {

//===----------------------------------------------------------------------===//
// Helper Function
//===----------------------------------------------------------------------===//
namespace bit32 {

typedef std::pair<SizeTraits<32>::Address, SizeTraits<32>::Address> Entry;

bool EntryCompare(const Entry& pX, const Entry& pY) {
  return (pX.first < pY.first);
}

}  // namespace bit32

//===----------------------------------------------------------------------===//
// Template Specification Functions
//===----------------------------------------------------------------------===//
/// emitOutput<32> - write out eh_frame_hdr
template <>
void EhFrameHdr::emitOutput<32>(FileOutputBuffer& pOutput) {
  MemoryRegion ehframehdr_region =
      pOutput.request(m_EhFrameHdr.offset(), m_EhFrameHdr.size());

  MemoryRegion ehframe_region =
      pOutput.request(m_EhFrame.offset(), m_EhFrame.size());

  uint8_t* data = ehframehdr_region.begin();
  // version
  data[0] = 1;
  // eh_frame_ptr_enc
  data[1] = llvm::dwarf::DW_EH_PE_pcrel | llvm::dwarf::DW_EH_PE_sdata4;

  // eh_frame_ptr
  uint32_t* eh_frame_ptr = reinterpret_cast<uint32_t*>(data + 4);
  *eh_frame_ptr = m_EhFrame.addr() - (m_EhFrameHdr.addr() + 4);

  // fde_count
  uint32_t* fde_count = reinterpret_cast<uint32_t*>(data + 8);
  if (m_EhFrame.hasEhFrame())
    *fde_count = m_EhFrame.getEhFrame()->numOfFDEs();
  else
    *fde_count = 0;

  if (*fde_count == 0) {
    // fde_count_enc
    data[2] = llvm::dwarf::DW_EH_PE_omit;
    // table_enc
    data[3] = llvm::dwarf::DW_EH_PE_omit;
  } else {
    // fde_count_enc
    data[2] = llvm::dwarf::DW_EH_PE_udata4;
    // table_enc
    data[3] = llvm::dwarf::DW_EH_PE_datarel | llvm::dwarf::DW_EH_PE_sdata4;

    // prepare the binary search table
    typedef std::vector<bit32::Entry> SearchTableType;
    SearchTableType search_table;

    for (EhFrame::const_cie_iterator i = m_EhFrame.getEhFrame()->cie_begin(),
                                     e = m_EhFrame.getEhFrame()->cie_end();
         i != e;
         ++i) {
      EhFrame::CIE& cie = **i;
      for (EhFrame::const_fde_iterator fi = cie.begin(), fe = cie.end();
           fi != fe;
           ++fi) {
        EhFrame::FDE& fde = **fi;
        SizeTraits<32>::Offset offset;
        SizeTraits<32>::Address fde_pc;
        SizeTraits<32>::Address fde_addr;
        offset = fde.getOffset();
        fde_pc = computePCBegin(fde, ehframe_region);
        fde_addr = m_EhFrame.addr() + offset;
        search_table.push_back(std::make_pair(fde_pc, fde_addr));
      }
    }

    std::sort(search_table.begin(), search_table.end(), bit32::EntryCompare);

    // write out the binary search table
    uint32_t* bst = reinterpret_cast<uint32_t*>(data + 12);
    SearchTableType::const_iterator entry, entry_end = search_table.end();
    size_t id = 0;
    for (entry = search_table.begin(); entry != entry_end; ++entry) {
      bst[id++] = (*entry).first - m_EhFrameHdr.addr();
      bst[id++] = (*entry).second - m_EhFrameHdr.addr();
    }
  }
}

//===----------------------------------------------------------------------===//
// EhFrameHdr
//===----------------------------------------------------------------------===//

EhFrameHdr::EhFrameHdr(LDSection& pEhFrameHdr, const LDSection& pEhFrame)
    : m_EhFrameHdr(pEhFrameHdr), m_EhFrame(pEhFrame) {
}

EhFrameHdr::~EhFrameHdr() {
}

/// @ref lsb core generic 4.1
/// .eh_frame_hdr section format
/// uint8_t : version
/// uint8_t : eh_frame_ptr_enc
/// uint8_t : fde_count_enc
/// uint8_t : table_enc
/// uint32_t : eh_frame_ptr
/// uint32_t : fde_count
/// __________________________ when fde_count > 0
/// <uint32_t, uint32_t>+ : binary search table
/// sizeOutput - base on the fde count to size output
void EhFrameHdr::sizeOutput() {
  size_t size = 12;
  if (m_EhFrame.hasEhFrame())
    size += 8 * m_EhFrame.getEhFrame()->numOfFDEs();
  m_EhFrameHdr.setSize(size);
}

/// computePCBegin - return the address of FDE's pc
uint32_t EhFrameHdr::computePCBegin(const EhFrame::FDE& pFDE,
                                    const MemoryRegion& pEhFrameRegion) {
  uint8_t fde_encoding = pFDE.getCIE().getFDEEncode();
  unsigned int eh_value = fde_encoding & 0x7;

  // check the size to read in
  if (eh_value == llvm::dwarf::DW_EH_PE_absptr) {
    eh_value = llvm::dwarf::DW_EH_PE_udata4;
  }

  size_t pc_size = 0x0;
  switch (eh_value) {
    case llvm::dwarf::DW_EH_PE_udata2:
      pc_size = 2;
      break;
    case llvm::dwarf::DW_EH_PE_udata4:
      pc_size = 4;
      break;
    case llvm::dwarf::DW_EH_PE_udata8:
      pc_size = 8;
      break;
    default:
      // TODO
      break;
  }

  SizeTraits<32>::Address pc = 0x0;
  const uint8_t* offset = (const uint8_t*)pEhFrameRegion.begin() +
                          pFDE.getOffset() + EhFrame::getDataStartOffset<32>();
  std::memcpy(&pc, offset, pc_size);

  // adjust the signed value
  bool is_signed = (fde_encoding & llvm::dwarf::DW_EH_PE_signed) != 0x0;
  if (llvm::dwarf::DW_EH_PE_udata2 == eh_value && is_signed)
    pc = (pc ^ 0x8000) - 0x8000;

  // handle eh application
  switch (fde_encoding & 0x70) {
    case llvm::dwarf::DW_EH_PE_absptr:
      break;
    case llvm::dwarf::DW_EH_PE_pcrel:
      pc += m_EhFrame.addr() + pFDE.getOffset() +
            EhFrame::getDataStartOffset<32>();
      break;
    case llvm::dwarf::DW_EH_PE_datarel:
      // TODO
      break;
    default:
      // TODO
      break;
  }
  return pc;
}

}  // namespace mcld