//===- lib/FileFormat/MachO/ArchHandler_x86.cpp ---------------------------===//
//
//                             The LLVM Linker
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "ArchHandler.h"
#include "Atoms.h"
#include "MachONormalizedFileBinaryUtils.h"

#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/StringSwitch.h"
#include "llvm/ADT/Triple.h"

#include "llvm/Support/ErrorHandling.h"

using namespace llvm::MachO;
using namespace lld::mach_o::normalized;

namespace lld {
namespace mach_o {

class ArchHandler_x86 : public ArchHandler {
public:
           ArchHandler_x86();
  virtual ~ArchHandler_x86();

  const Registry::KindStrings *kindStrings() override { return _sKindStrings; }

  Reference::KindArch kindArch() override { return Reference::KindArch::x86; }

  const StubInfo &stubInfo() override { return _sStubInfo; }
  bool isCallSite(const Reference &) override;
  bool isPointer(const Reference &) override;
  bool isPairedReloc(const normalized::Relocation &) override;
  std::error_code getReferenceInfo(const normalized::Relocation &reloc,
                                   const DefinedAtom *inAtom,
                                   uint32_t offsetInAtom,
                                   uint64_t fixupAddress, bool swap,
                                   FindAtomBySectionAndAddress atomFromAddress,
                                   FindAtomBySymbolIndex atomFromSymbolIndex,
                                   Reference::KindValue *kind,
                                   const lld::Atom **target,
                                   Reference::Addend *addend) override;
  std::error_code
      getPairReferenceInfo(const normalized::Relocation &reloc1,
                           const normalized::Relocation &reloc2,
                           const DefinedAtom *inAtom,
                           uint32_t offsetInAtom,
                           uint64_t fixupAddress, bool swap,
                           FindAtomBySectionAndAddress atomFromAddress,
                           FindAtomBySymbolIndex atomFromSymbolIndex,
                           Reference::KindValue *kind,
                           const lld::Atom **target,
                           Reference::Addend *addend) override;

  void generateAtomContent(const DefinedAtom &atom, bool relocatable,
                           FindAddressForAtom findAddress,
                           uint8_t *atomContentBuffer) override;

  void appendSectionRelocations(const DefinedAtom &atom,
                                uint64_t atomSectionOffset,
                                const Reference &ref,
                                FindSymbolIndexForAtom symbolIndexForAtom,
                                FindSectionIndexForAtom sectionIndexForAtom,
                                FindAddressForAtom addressForAtom,
                                normalized::Relocations &relocs) override;

private:
  static const Registry::KindStrings _sKindStrings[];
  static const StubInfo              _sStubInfo;

  enum : Reference::KindValue {
    invalid,               /// for error condition

    // Kinds found in mach-o .o files:
    branch32,              /// ex: call _foo
    branch16,              /// ex: callw _foo
    abs32,                 /// ex: movl _foo, %eax
    funcRel32,             /// ex: movl _foo-L1(%eax), %eax
    pointer32,             /// ex: .long _foo
    delta32,               /// ex: .long _foo - .
    negDelta32,            /// ex: .long . - _foo

    // Kinds introduced by Passes:
    lazyPointer,           /// Location contains a lazy pointer.
    lazyImmediateLocation, /// Location contains immediate value used in stub.
  };
  
  static bool useExternalRelocationTo(const Atom &target);

  void applyFixupFinal(const Reference &ref, uint8_t *location,
                       uint64_t fixupAddress, uint64_t targetAddress,
                       uint64_t inAtomAddress);

  void applyFixupRelocatable(const Reference &ref, uint8_t *location,
                             uint64_t fixupAddress,
                             uint64_t targetAddress,
                             uint64_t inAtomAddress);

  const bool _swap;
};

//===----------------------------------------------------------------------===//
//  ArchHandler_x86
//===----------------------------------------------------------------------===//

ArchHandler_x86::ArchHandler_x86() :
  _swap(!MachOLinkingContext::isHostEndian(MachOLinkingContext::arch_x86)) {}
  
ArchHandler_x86::~ArchHandler_x86() { }
  
const Registry::KindStrings ArchHandler_x86::_sKindStrings[] = {
  LLD_KIND_STRING_ENTRY(invalid),
  LLD_KIND_STRING_ENTRY(branch32),
  LLD_KIND_STRING_ENTRY(branch16),
  LLD_KIND_STRING_ENTRY(abs32),
  LLD_KIND_STRING_ENTRY(funcRel32),
  LLD_KIND_STRING_ENTRY(pointer32),
  LLD_KIND_STRING_ENTRY(delta32),
  LLD_KIND_STRING_ENTRY(negDelta32),
  LLD_KIND_STRING_ENTRY(lazyPointer),
  LLD_KIND_STRING_ENTRY(lazyImmediateLocation),
  LLD_KIND_STRING_END
};

const ArchHandler::StubInfo ArchHandler_x86::_sStubInfo = {
  "dyld_stub_binder",

  // Lazy pointer references 
  { Reference::KindArch::x86, pointer32, 0, 0 },
  { Reference::KindArch::x86, lazyPointer, 0, 0 },
  
  // GOT pointer to dyld_stub_binder
  { Reference::KindArch::x86, pointer32, 0, 0 },

  // x86 code alignment
  1, 
  
  // Stub size and code
  6, 
  { 0xff, 0x25, 0x00, 0x00, 0x00, 0x00 },       // jmp *lazyPointer
  { Reference::KindArch::x86, abs32, 2, 0 },
  
  // Stub Helper size and code
  10,
  { 0x68, 0x00, 0x00, 0x00, 0x00,               // pushl $lazy-info-offset
    0xE9, 0x00, 0x00, 0x00, 0x00 },             // jmp helperhelper
  { Reference::KindArch::x86, lazyImmediateLocation, 1, 0 },
  { Reference::KindArch::x86, branch32, 6, 0 },
  
  // Stub Helper-Common size and code
  12,
  { 0x68, 0x00, 0x00, 0x00, 0x00,               // pushl $dyld_ImageLoaderCache
    0xFF, 0x25, 0x00, 0x00, 0x00, 0x00,         // jmp *_fast_lazy_bind
    0x90 },                                     // nop
  { Reference::KindArch::x86, abs32, 1, 0 },
  { Reference::KindArch::x86, abs32, 7, 0 }
};

bool ArchHandler_x86::isCallSite(const Reference &ref) {
  return (ref.kindValue() == branch32);
}

bool ArchHandler_x86::isPointer(const Reference &ref) {
  return (ref.kindValue() == pointer32);
}

bool ArchHandler_x86::isPairedReloc(const Relocation &reloc) {
  if (!reloc.scattered)
    return false;
  return (reloc.type == GENERIC_RELOC_LOCAL_SECTDIFF) ||
         (reloc.type == GENERIC_RELOC_SECTDIFF);
}

std::error_code
ArchHandler_x86::getReferenceInfo(const Relocation &reloc,
                                  const DefinedAtom *inAtom,
                                  uint32_t offsetInAtom,
                                  uint64_t fixupAddress, bool swap,
                                  FindAtomBySectionAndAddress atomFromAddress,
                                  FindAtomBySymbolIndex atomFromSymbolIndex,
                                  Reference::KindValue *kind,
                                  const lld::Atom **target,
                                  Reference::Addend *addend) {
  typedef std::error_code E;
  DefinedAtom::ContentPermissions perms;
  const uint8_t *fixupContent = &inAtom->rawContent()[offsetInAtom];
  uint64_t targetAddress;
  switch (relocPattern(reloc)) {
  case GENERIC_RELOC_VANILLA | rPcRel | rExtern | rLength4:
    // ex: call _foo (and _foo undefined)
    *kind = branch32;
    if (E ec = atomFromSymbolIndex(reloc.symbol, target))
      return ec;
    *addend = fixupAddress + 4 + readS32(swap, fixupContent);
    break;
  case GENERIC_RELOC_VANILLA | rPcRel | rLength4:
    // ex: call _foo (and _foo defined)
    *kind = branch32;
    targetAddress = fixupAddress + 4 + readS32(swap, fixupContent);
    return atomFromAddress(reloc.symbol, targetAddress, target, addend);
    break;
  case GENERIC_RELOC_VANILLA | rScattered | rPcRel | rLength4:
    // ex: call _foo+n (and _foo defined)
    *kind = branch32;
    targetAddress = fixupAddress + 4 + readS32(swap, fixupContent);
    if (E ec = atomFromAddress(0, reloc.value, target, addend))
      return ec;
    *addend = targetAddress - reloc.value;
    break;
  case GENERIC_RELOC_VANILLA | rPcRel | rExtern | rLength2:
    // ex: callw _foo (and _foo undefined)
    *kind = branch16;
    if (E ec = atomFromSymbolIndex(reloc.symbol, target))
      return ec;
    *addend = fixupAddress + 2 + readS16(swap, fixupContent);
    break;
  case GENERIC_RELOC_VANILLA | rPcRel | rLength2:
    // ex: callw _foo (and _foo defined)
    *kind = branch16;
    targetAddress = fixupAddress + 2 + readS16(swap, fixupContent);
    return atomFromAddress(reloc.symbol, targetAddress, target, addend);
    break;
  case GENERIC_RELOC_VANILLA | rScattered | rPcRel | rLength2:
    // ex: callw _foo+n (and _foo defined)
    *kind = branch16;
    targetAddress = fixupAddress + 2 + readS16(swap, fixupContent);
    if (E ec = atomFromAddress(0, reloc.value, target, addend))
      return ec;
    *addend = targetAddress - reloc.value;
    break;
  case GENERIC_RELOC_VANILLA | rExtern | rLength4:
    // ex: movl	_foo, %eax   (and _foo undefined)
    // ex: .long _foo        (and _foo undefined)
    perms = inAtom->permissions();
    *kind =
        ((perms & DefinedAtom::permR_X) == DefinedAtom::permR_X) ? abs32
                                                                 : pointer32;
    if (E ec = atomFromSymbolIndex(reloc.symbol, target))
      return ec;
    *addend = readU32(swap, fixupContent);
    break;
  case GENERIC_RELOC_VANILLA | rLength4:
    // ex: movl	_foo, %eax   (and _foo defined)
    // ex: .long _foo        (and _foo defined)
    perms = inAtom->permissions();
    *kind =
        ((perms & DefinedAtom::permR_X) == DefinedAtom::permR_X) ? abs32
                                                                 : pointer32;
    targetAddress = readU32(swap, fixupContent);
    return atomFromAddress(reloc.symbol, targetAddress, target, addend);
    break;
  case GENERIC_RELOC_VANILLA | rScattered | rLength4:
    // ex: .long _foo+n      (and _foo defined)
    perms = inAtom->permissions();
    *kind =
        ((perms & DefinedAtom::permR_X) == DefinedAtom::permR_X) ? abs32
                                                                 : pointer32;
    if (E ec = atomFromAddress(0, reloc.value, target, addend))
      return ec;
    *addend = readU32(swap, fixupContent) - reloc.value;
    break;
  default:
    return make_dynamic_error_code(Twine("unsupported i386 relocation type"));
  }
  return std::error_code();
}

std::error_code
ArchHandler_x86::getPairReferenceInfo(const normalized::Relocation &reloc1,
                                      const normalized::Relocation &reloc2,
                                      const DefinedAtom *inAtom,
                                      uint32_t offsetInAtom,
                                      uint64_t fixupAddress, bool swap,
                                      FindAtomBySectionAndAddress atomFromAddr,
                                      FindAtomBySymbolIndex atomFromSymbolIndex,
                                      Reference::KindValue *kind,
                                      const lld::Atom **target,
                                      Reference::Addend *addend) {
  const uint8_t *fixupContent = &inAtom->rawContent()[offsetInAtom];
  std::error_code ec;
  DefinedAtom::ContentPermissions perms = inAtom->permissions();
  uint32_t fromAddress;
  uint32_t toAddress;
  uint32_t value;
  const lld::Atom *fromTarget;
  Reference::Addend offsetInTo;
  Reference::Addend offsetInFrom;
  switch (relocPattern(reloc1) << 16 | relocPattern(reloc2)) {
  case ((GENERIC_RELOC_SECTDIFF | rScattered | rLength4) << 16 |
         GENERIC_RELOC_PAIR | rScattered | rLength4):
  case ((GENERIC_RELOC_LOCAL_SECTDIFF | rScattered | rLength4) << 16 |
         GENERIC_RELOC_PAIR | rScattered | rLength4):
    toAddress = reloc1.value;
    fromAddress = reloc2.value;
    value = readS32(swap, fixupContent);
    ec = atomFromAddr(0, toAddress, target, &offsetInTo);
    if (ec)
      return ec;
    ec = atomFromAddr(0, fromAddress, &fromTarget, &offsetInFrom);
    if (ec)
      return ec;
    if (fromTarget != inAtom) {
      if (*target != inAtom) 
        return make_dynamic_error_code(Twine("SECTDIFF relocation where "
                                             "neither target is in atom"));
      *kind = negDelta32;
      *addend = toAddress - value - fromAddress;
      *target = fromTarget;
    } else {
      if ((perms & DefinedAtom::permR_X) == DefinedAtom::permR_X) {
        // SECTDIFF relocations are used in i386 codegen where the function
        // prolog does a CALL to the next instruction which POPs the return
        // address into EBX which becomes the pic-base register.  The POP
        // instruction is label the used for the subtrahend in expressions.
        // The funcRel32 kind represents the 32-bit delta to some symbol from
        // the start of the function (atom) containing the funcRel32.
        *kind = funcRel32;
        uint32_t ta = fromAddress + value - toAddress;
        *addend = ta - offsetInFrom;
      } else {
        *kind = delta32;
        *addend = fromAddress + value - toAddress;
      }
    }
    return std::error_code();
    break;
  default:
    return make_dynamic_error_code(Twine("unsupported i386 relocation type"));
  }
}

void ArchHandler_x86::generateAtomContent(const DefinedAtom &atom,
                                           bool relocatable,
                                           FindAddressForAtom findAddress,
                                           uint8_t *atomContentBuffer) {
  // Copy raw bytes.
  memcpy(atomContentBuffer, atom.rawContent().data(), atom.size());
  // Apply fix-ups.
  for (const Reference *ref : atom) {
    uint32_t offset = ref->offsetInAtom();
    const Atom *target = ref->target();
    uint64_t targetAddress = 0;
    if (isa<DefinedAtom>(target))
      targetAddress = findAddress(*target);
    uint64_t atomAddress = findAddress(atom);
    uint64_t fixupAddress = atomAddress + offset;
    if (relocatable) {
      applyFixupRelocatable(*ref, &atomContentBuffer[offset],
                                        fixupAddress, targetAddress,
                                        atomAddress);
    } else {
      applyFixupFinal(*ref, &atomContentBuffer[offset],
                                  fixupAddress, targetAddress,
                                  atomAddress);
    }
  }
}

void ArchHandler_x86::applyFixupFinal(const Reference &ref, uint8_t *location,
                                      uint64_t fixupAddress,
                                      uint64_t targetAddress,
                                      uint64_t inAtomAddress) {
  if (ref.kindNamespace() != Reference::KindNamespace::mach_o)
    return;
  assert(ref.kindArch() == Reference::KindArch::x86);
  int32_t *loc32 = reinterpret_cast<int32_t *>(location);
  int16_t *loc16 = reinterpret_cast<int16_t *>(location);
  switch (ref.kindValue()) {
  case branch32:
    write32(*loc32, _swap, (targetAddress - (fixupAddress + 4)) + ref.addend());
    break;
  case branch16:
    write16(*loc16, _swap, (targetAddress - (fixupAddress + 2)) + ref.addend());
    break;
  case pointer32:
  case abs32:
    write32(*loc32, _swap, targetAddress + ref.addend());
    break;
  case funcRel32:
    write32(*loc32, _swap, targetAddress - inAtomAddress + ref.addend()); 
    break;
  case delta32:
    write32(*loc32, _swap, targetAddress - fixupAddress + ref.addend());
    break;
  case negDelta32:
    write32(*loc32, _swap, fixupAddress - targetAddress + ref.addend());
    break;
  case lazyPointer:
  case lazyImmediateLocation:
    // do nothing
    break;
  default:
    llvm_unreachable("invalid x86 Reference Kind");
    break;
  }
}

void ArchHandler_x86::applyFixupRelocatable(const Reference &ref,
                                               uint8_t *location,
                                               uint64_t fixupAddress,
                                               uint64_t targetAddress,
                                               uint64_t inAtomAddress) {
  int32_t *loc32 = reinterpret_cast<int32_t *>(location);
  int16_t *loc16 = reinterpret_cast<int16_t *>(location);
  bool useExternalReloc = useExternalRelocationTo(*ref.target());
  switch (ref.kindValue()) {
  case branch32:
    if (useExternalReloc)
      write32(*loc32, _swap, ref.addend() - (fixupAddress + 4));
    else
      write32(*loc32, _swap, (targetAddress - (fixupAddress+4)) + ref.addend());
    break;
  case branch16:
    if (useExternalReloc)
      write16(*loc16, _swap, ref.addend() - (fixupAddress + 2));
    else
      write16(*loc16, _swap, (targetAddress - (fixupAddress+2)) + ref.addend());
    break;
  case pointer32:
  case abs32:
    write32(*loc32, _swap, targetAddress + ref.addend());
    break;
  case funcRel32:
    write32(*loc32, _swap, targetAddress - inAtomAddress + ref.addend()); // FIXME
    break;
  case delta32:
    write32(*loc32, _swap, targetAddress - fixupAddress + ref.addend());
    break;
  case negDelta32:
    write32(*loc32, _swap, fixupAddress - targetAddress + ref.addend());
    break;
  case lazyPointer:
  case lazyImmediateLocation:
    // do nothing
    break;
  default:
    llvm_unreachable("invalid x86 Reference Kind");
    break;
  }
}

bool ArchHandler_x86::useExternalRelocationTo(const Atom &target) {
  // Undefined symbols are referenced via external relocations.
  if (isa<UndefinedAtom>(&target))
    return true;
  if (const DefinedAtom *defAtom = dyn_cast<DefinedAtom>(&target)) {
     switch (defAtom->merge()) {
     case DefinedAtom::mergeAsTentative:
       // Tentative definitions are referenced via external relocations.
       return true;
     case DefinedAtom::mergeAsWeak:
     case DefinedAtom::mergeAsWeakAndAddressUsed:
       // Global weak-defs are referenced via external relocations.
       return (defAtom->scope() == DefinedAtom::scopeGlobal);
     default:
       break;
    }
  }
  // Everything else is reference via an internal relocation.
  return false;
}


void ArchHandler_x86::appendSectionRelocations(
                                   const DefinedAtom &atom,
                                   uint64_t atomSectionOffset,
                                   const Reference &ref,
                                   FindSymbolIndexForAtom symbolIndexForAtom,
                                   FindSectionIndexForAtom sectionIndexForAtom,
                                   FindAddressForAtom addressForAtom,
                                   normalized::Relocations &relocs) {
  if (ref.kindNamespace() != Reference::KindNamespace::mach_o)
    return;
  assert(ref.kindArch() == Reference::KindArch::x86);
  uint32_t sectionOffset = atomSectionOffset + ref.offsetInAtom();
  bool useExternalReloc = useExternalRelocationTo(*ref.target());
  switch (ref.kindValue()) {
  case branch32:
    if (useExternalReloc) {
      appendReloc(relocs, sectionOffset, symbolIndexForAtom(*ref.target()), 0,
                  GENERIC_RELOC_VANILLA | rExtern    | rPcRel | rLength4);
    } else {
      if (ref.addend() != 0)
        appendReloc(relocs, sectionOffset, 0, addressForAtom(*ref.target()),
                  GENERIC_RELOC_VANILLA | rScattered | rPcRel |  rLength4);
      else
        appendReloc(relocs, sectionOffset, sectionIndexForAtom(*ref.target()),0,
                  GENERIC_RELOC_VANILLA |              rPcRel | rLength4);
    }
    break;
  case branch16:
    if (useExternalReloc) {
      appendReloc(relocs, sectionOffset, symbolIndexForAtom(*ref.target()), 0,
                  GENERIC_RELOC_VANILLA | rExtern    | rPcRel | rLength2);
    } else {
      if (ref.addend() != 0)
        appendReloc(relocs, sectionOffset, 0, addressForAtom(*ref.target()),
                  GENERIC_RELOC_VANILLA | rScattered | rPcRel |  rLength2);
      else
        appendReloc(relocs, sectionOffset, sectionIndexForAtom(*ref.target()),0,
                  GENERIC_RELOC_VANILLA |              rPcRel | rLength2);
    }
    break;
  case pointer32:
  case abs32:
    if (useExternalReloc)
      appendReloc(relocs, sectionOffset, symbolIndexForAtom(*ref.target()),  0,
                GENERIC_RELOC_VANILLA |    rExtern     |  rLength4);
    else {
      if (ref.addend() != 0)
        appendReloc(relocs, sectionOffset, 0, addressForAtom(*ref.target()),
                GENERIC_RELOC_VANILLA |    rScattered  |  rLength4);
      else
        appendReloc(relocs, sectionOffset, sectionIndexForAtom(*ref.target()), 0,
                GENERIC_RELOC_VANILLA |                   rLength4);
    }
    break;
  case funcRel32:
    appendReloc(relocs, sectionOffset, 0, addressForAtom(*ref.target()),
              GENERIC_RELOC_SECTDIFF |  rScattered    | rLength4);
    appendReloc(relocs, sectionOffset, 0, addressForAtom(atom) - ref.addend(),
              GENERIC_RELOC_PAIR     |  rScattered    | rLength4);
    break;
  case delta32:
    appendReloc(relocs, sectionOffset, 0, addressForAtom(*ref.target()),
              GENERIC_RELOC_SECTDIFF |  rScattered    | rLength4);
    appendReloc(relocs, sectionOffset, 0, addressForAtom(atom) +
                                                           ref.offsetInAtom(),
              GENERIC_RELOC_PAIR     |  rScattered    | rLength4);
    break;
  case negDelta32:
    appendReloc(relocs, sectionOffset, 0, addressForAtom(atom) +
                                                           ref.offsetInAtom(),
              GENERIC_RELOC_SECTDIFF |  rScattered    | rLength4);
    appendReloc(relocs, sectionOffset, 0, addressForAtom(*ref.target()),
              GENERIC_RELOC_PAIR     |  rScattered    | rLength4);
    break;
  case lazyPointer:
  case lazyImmediateLocation:
    llvm_unreachable("lazy reference kind implies Stubs pass was run");
    break;
  default:
    llvm_unreachable("unknown x86 Reference Kind");
    break;

  }
}


std::unique_ptr<mach_o::ArchHandler> ArchHandler::create_x86() {
  return std::unique_ptr<mach_o::ArchHandler>(new ArchHandler_x86());
}

} // namespace mach_o
} // namespace lld
