#include "Rts.h"

#if defined(darwin_HOST_OS) || defined(ios_HOST_OS)

/* for roundUpToPage */
#include "sm/OSMem.h"

#include "RtsUtils.h"
#include "GetEnv.h"
#include "LinkerInternals.h"
#include "linker/MachO.h"
#include "linker/CacheFlush.h"
#include "linker/SymbolExtras.h"
#include "linker/MMap.h"

#include <string.h>
#include <regex.h>
#include <mach/machine.h>
#include <mach-o/fat.h>
#include <mach-o/loader.h>
#include <mach-o/nlist.h>
#include <mach-o/reloc.h>

#if defined(HAVE_SYS_MMAN_H) && RTS_LINKER_USE_MMAP
#  include <sys/mman.h>
#endif

#if defined(x86_64_HOST_ARCH)
#  include <mach-o/x86_64/reloc.h>
#endif

#if defined(aarch64_HOST_ARCH)
#  include <mach-o/arm64/reloc.h>
#endif

/*
  Support for MachO linking on Darwin/MacOS X
  by Wolfgang Thaller (wolfgang.thaller@gmx.net)

  I hereby formally apologize for the hackish nature of this code.
  Things that need to be done:
  *) implement ocVerifyImage_MachO
  *) add still more sanity checks.
*/
#if defined(aarch64_HOST_ARCH)
#  define  NEED_PLT
#  include "macho/plt.h"

/* aarch64 linker by moritz angermann <moritz@lichtzwerge.de> */

/* often times we need to extend some value of certain number of bits
 * int an int64_t for e.g. relative offsets.
 */
int64_t signExtend(uint64_t val, uint8_t bits);
/* Helper functions to check some instruction properties */
static bool isVectorOp(uint32_t *p);
static bool isLoadStore(uint32_t *p);

/* aarch64 relocations may contain an addend already in the position
 * where we want to write the address offset to. Thus decoding as well
 * as encoding is needed.
 */
bool fitsBits(size_t bits, int64_t value);
int64_t decodeAddend(ObjectCode * oc, Section * section,
                     MachORelocationInfo * ri);
void encodeAddend(ObjectCode * oc, Section * section,
                  MachORelocationInfo * ri, int64_t addend);

/* Global Offset Table logic */
bool isGotLoad(MachORelocationInfo * ri);
bool needGotSlot(MachONList * symbol);
bool makeGot(ObjectCode * oc);
void freeGot(ObjectCode * oc);
#endif /* aarch64_HOST_ARCH */

/*
 * Initialize some common data in the object code so we don't have to
 * continuously look up the addresses.
 */
void
ocInit_MachO(ObjectCode * oc)
{
    ocDeinit_MachO(oc);

    oc->info = (struct ObjectCodeFormatInfo*)stgCallocBytes(
                1, sizeof *oc->info,
                "ocInit_MachO(ObjectCodeFormatInfo)");
    oc->info->header  = (MachOHeader *) oc->image;
    oc->info->symCmd  = NULL;
    oc->info->segCmd  = NULL;
    oc->info->dsymCmd = NULL;

    MachOLoadCommand *lc = (MachOLoadCommand*)(oc->image + sizeof(MachOHeader));
    for(size_t i = 0; i < oc->info->header->ncmds; i++) {
        if (lc->cmd == LC_SEGMENT || lc->cmd == LC_SEGMENT_64) {
            oc->info->segCmd = (MachOSegmentCommand*) lc;
        }
        else if (lc->cmd == LC_SYMTAB) {
            oc->info->symCmd = (MachOSymtabCommand*) lc;
        }
        else if (lc->cmd == LC_DYSYMTAB) {
            oc->info->dsymCmd = (MachODsymtabCommand*) lc;
        }
        lc = (MachOLoadCommand *) ( ((char*)lc) + lc->cmdsize );
    }
    if (NULL == oc->info->segCmd) {
        barf("ocGetNames_MachO: no segment load command");
    }

    oc->info->macho_sections = (MachOSection*) (oc->info->segCmd+1);
    oc->n_sections = oc->info->segCmd->nsects;

    oc->info->nlist = oc->info->symCmd == NULL
              ? NULL
              : (MachONList *)(oc->image + oc->info->symCmd->symoff);
    oc->info->names = oc->info->symCmd == NULL
              ? NULL
              : (oc->image + oc->info->symCmd->stroff);

    /* If we have symbols, allocate and fill the macho_symbols
     * This will make relocation easier.
     */
    oc->info->n_macho_symbols = 0;
    oc->info->macho_symbols = NULL;

    if(NULL != oc->info->nlist) {
        oc->info->n_macho_symbols = oc->info->symCmd->nsyms;
        oc->info->macho_symbols = (MachOSymbol*)stgCallocBytes(
                                    oc->info->symCmd->nsyms,
                                    sizeof(MachOSymbol),
                                    "ocInit_MachO(MachOSymbol)");
        for(uint32_t i = 0; i < oc->info->symCmd->nsyms; i++) {
            oc->info->macho_symbols[i].name  = oc->info->names
                                             + oc->info->nlist[i].n_un.n_strx;
            oc->info->macho_symbols[i].nlist = &oc->info->nlist[i];
             /* We don't have an address for this symbol yet; this
              * will be populated during ocGetNames_MachO. Hence init
              * with NULL
              */
            oc->info->macho_symbols[i].addr  = NULL;
            oc->info->macho_symbols[i].got_addr = NULL;
        }
    }
}

void
ocDeinit_MachO(ObjectCode * oc) {
    if (oc->info != NULL) {
        if(oc->info->n_macho_symbols > 0) {
            stgFree(oc->info->macho_symbols);
        }
#if defined(aarch64_HOST_ARCH)
        freeGot(oc);
        if(oc->sections != NULL) {
            for(int i = 0; i < oc->n_sections; i++) {
                freeStubs(&oc->sections[i]);
            }
        }
#endif
        stgFree(oc->info);
        oc->info = NULL;
    }
}

static int
resolveImports(
    ObjectCode* oc,
    MachOSection *sect,    // ptr to lazy or non-lazy symbol pointer section
    unsigned long *indirectSyms);

#if NEED_SYMBOL_EXTRAS
#if defined(x86_64_HOST_ARCH) || defined(aarch64_HOST_ARCH)

int
ocAllocateExtras_MachO(ObjectCode* oc)
{
    IF_DEBUG(linker, debugBelch("ocAllocateExtras_MachO: start\n"));

    if (NULL != oc->info->symCmd) {
        IF_DEBUG(linker,
            debugBelch("ocAllocateExtras_MachO: allocate %d symbols\n",
                oc->info->symCmd->nsyms));
        IF_DEBUG(linker, debugBelch("ocAllocateExtras_MachO: done\n"));
        return ocAllocateExtras(oc, oc->info->symCmd->nsyms, 0, 0);
    }

    IF_DEBUG(linker,
        debugBelch("ocAllocateExtras_MachO: allocated no symbols\n"));
    IF_DEBUG(linker, debugBelch("ocAllocateExtras_MachO: done\n"));
    return ocAllocateExtras(oc, 0, 0, 0);
}

#else
#error Unknown MachO architecture
#endif /* HOST_ARCH */
#endif /* NEED_SYMBOL_EXTRAS */

int
ocVerifyImage_MachO(ObjectCode * oc)
{
    char *image = (char*) oc->image;
    MachOHeader *header = (MachOHeader*) image;

    IF_DEBUG(linker, debugBelch("ocVerifyImage_MachO: start\n"));

    if(header->magic != MH_MAGIC_64) {
        errorBelch("Could not load image %s: bad magic!\n"
                   "  Expected %08x (64bit), got %08x%s\n",
                   oc->fileName, MH_MAGIC_64, header->magic,
                   header->magic == MH_MAGIC ? " (32bit)." : ".");
        return 0;
    }

    // FIXME: do some more verifying here
    IF_DEBUG(linker, debugBelch("ocVerifyImage_MachO: done\n"));
    return 1;
}

static int
resolveImports(
    ObjectCode* oc,
    MachOSection *sect,    // ptr to lazy or non-lazy symbol pointer section
    unsigned long *indirectSyms)
{
    size_t itemSize = 4;

    IF_DEBUG(linker, debugBelch("resolveImports: start\n"));

    for(unsigned i = 0; i * itemSize < sect->size; i++)
    {
        // according to otool, reserved1 contains the first index into the
        // indirect symbol table
        unsigned long indirectSymbolIndex = indirectSyms[sect->reserved1+i];
        MachOSymbol *symbol = &oc->info->macho_symbols[indirectSymbolIndex];
        SymbolAddr* addr = NULL;

        IF_DEBUG(linker, debugBelch("resolveImports: resolving %s\n", symbol->name));

        if ((symbol->nlist->n_type & N_TYPE) == N_UNDF
            && (symbol->nlist->n_type & N_EXT) && (symbol->nlist->n_value != 0)) {
            addr = (SymbolAddr*) (symbol->nlist->n_value);
            IF_DEBUG(linker, debugBelch("resolveImports: undefined external %s has value %p\n", symbol->name, addr));
        } else {
            addr = lookupDependentSymbol(symbol->name, oc, NULL);
            IF_DEBUG(linker, debugBelch("resolveImports: looking up %s, %p\n", symbol->name, addr));
        }

        if (addr == NULL)
        {
            errorBelch("\nlookupSymbol failed in resolveImports\n"
                       "%s: unknown symbol `%s'", oc->fileName, symbol->name);
            return 0;
        }

        checkProddableBlock(&oc->proddables,
                            ((void**)(oc->image + sect->offset)) + i,
                            sizeof(void *));
        ((void**)(oc->image + sect->offset))[i] = addr;
    }

    IF_DEBUG(linker, debugBelch("resolveImports: done\n"));
    return 1;
}

#if defined(aarch64_HOST_ARCH)
/* aarch64 linker by moritz angermann <moritz@lichtzwerge.de> */

int64_t
signExtend(uint64_t val, uint8_t bits) {
    return (int64_t)(val << (64-bits)) >> (64-bits);
}

static bool
isVectorOp(uint32_t *p) {
    return (*p & 0x04800000) == 0x04800000;
}

static bool
isLoadStore(uint32_t *p) {
    return (*p & 0x3B000000) == 0x39000000;
}

int64_t
decodeAddend(ObjectCode * oc, Section * section, MachORelocationInfo * ri) {

    /* the instruction. It is 32bit wide */
    uint32_t * p = (uint32_t*)((uint8_t*)section->start + ri->r_address);

    checkProddableBlock(&oc->proddables, (void*)p, 1 << ri->r_length);

    switch(ri->r_type) {
        case ARM64_RELOC_UNSIGNED: {
            switch (ri->r_length) {
                case 0: return signExtend(*(uint8_t*)p,  8 << ri->r_length);
                case 1: return signExtend(*(uint16_t*)p, 8 << ri->r_length);
                case 2: return signExtend(*(uint32_t*)p, 8 << ri->r_length);
                case 3: return signExtend(*(uint64_t*)p, 8 << ri->r_length);
                default:
                    barf("Unsupported r_length (%d) for UNSIGNED relocation",
                         ri->r_length);
            }
        }
        case ARM64_RELOC_SUBTRACTOR: {
            switch (ri->r_length) {
                case 0: return signExtend(*(uint8_t*)p,  8 << ri->r_length);
                case 1: return signExtend(*(uint16_t*)p, 8 << ri->r_length);
                case 2: return signExtend(*(uint32_t*)p, 8 << ri->r_length);
                case 3: return signExtend(*(uint64_t*)p, 8 << ri->r_length);
                default:
                    barf("Unsupported r_length (%d) for SUBTRACTOR relocation",
                         ri->r_length);
            }
        }
        case ARM64_RELOC_BRANCH26:
            /* take the lower 26 bits and shift them by 2. The last two are
             * implicitly 0 (as the instructions must be aligned!) and sign
             * extend to 64 bits.
             */
            return signExtend( (*p & 0x03FFFFFF) << 2, 28 );
        case ARM64_RELOC_PAGE21:
        case ARM64_RELOC_GOT_LOAD_PAGE21:
            /* take the instruction bits masked with 0x6 (0110), and push them
             * down. into the last two bits, and mask in the
             *
             * the 21 bits are encoded as follows in the instruction
             *
             * -**- ---* **** **** **** **** ***-- ----
             *  ^^
             *  ''-- these are the low two bits.
             */
            return signExtend(   (*p & 0x60000000) >> 29
                               | ((*p & 0x01FFFFE0) >> 3) << 12, 33);
        case ARM64_RELOC_PAGEOFF12:
        case ARM64_RELOC_GOT_LOAD_PAGEOFF12: {
            /* the 12 bits for the page offset are encoded from bit 11 onwards
             *
             * ---- ---- --** **** **** **-- ---- ----
             */
            int64_t a = (*p & 0x003FFC00) >> 10;
            int shift = 0;
            if (isLoadStore(p)) {
                shift = (*p >> 30) & 0x3;
                if(0 == shift && isVectorOp(p)) {
                    shift = 4;
                }
            }
            return a << shift;
        }
    }
    barf("unsupported relocation type: %d\n", ri->r_type);
}

inline bool
fitsBits(size_t bits, int64_t value) {
    if(bits == 64) return true;
    if(bits > 64) barf("fits_bits with %zu bits and an 64bit integer!", bits);
    return  0 == (value >> bits)   // All bits off: 0
        || -1 == (value >> bits);  // All bits on: -1
}

void
encodeAddend(ObjectCode * oc, Section * section,
             MachORelocationInfo * ri, int64_t addend) {
    uint32_t * p = (uint32_t*)((uint8_t*)section->start + ri->r_address);

    checkProddableBlock(&oc->proddables, (void*)p, 1 << ri->r_length);

    switch (ri->r_type) {
        case ARM64_RELOC_UNSIGNED: {
            if(!fitsBits(8 << ri->r_length, addend))
                barf("Relocation out of range for UNSIGNED");
            switch (ri->r_length) {
                case 0: *(uint8_t*)p  = (uint8_t)addend; break;
                case 1: *(uint16_t*)p = (uint16_t)addend; break;
                case 2: *(uint32_t*)p = (uint32_t)addend; break;
                case 3: *(uint64_t*)p = (uint64_t)addend; break;
                default:
                    barf("Unsupported r_length (%d) for UNSIGNED relocation",
                         ri->r_length);
            }
            return;
        }
        case ARM64_RELOC_SUBTRACTOR: {
            if(!fitsBits(8 << ri->r_length, addend))
                barf("Relocation out of range for SUBTRACTOR");
            switch (ri->r_length) {
                case 0: *(uint8_t*)p  = (uint8_t)addend; break;
                case 1: *(uint16_t*)p = (uint16_t)addend; break;
                case 2: *(uint32_t*)p = (uint32_t)addend; break;
                case 3: *(uint64_t*)p = (uint64_t)addend; break;
                default:
                    barf("Unsupported r_length (%d) for SUBTRACTOR relocation",
                         ri->r_length);
            }
            return;
        }
        case ARM64_RELOC_BRANCH26: {
            /* We can only store 26 bits in the instruction, due to alignment we
             * do not need the last two bits of the value. If the value >> 2
             * still exceeds 26bits, we won't be able to reach it.
             */
            if(!fitsBits(26, addend >> 2))
                barf("Relocation target for BRACH26 out of range.");
            *p = (*p & 0xFC000000) | ((uint32_t)(addend >> 2) & 0x03FFFFFF);
            return;
        }
        case ARM64_RELOC_PAGE21:
        case ARM64_RELOC_GOT_LOAD_PAGE21: {
            /* We store 21bits, in bits 6 to 24, and bits 30 and 31.
             * The encoded value describes a multiple of 4k pages, and together
             * with the PAGEOFF12 relocation allows to address a relative range
             * of +-4GB.
             */
            if(!fitsBits(21, addend >> 12))
                barf("Relocation target for PAGE21 out of range.");
            *p = (*p & 0x9F00001F) | (uint32_t)((addend << 17) & 0x60000000)
                                   | (uint32_t)((addend >> 9)  & 0x00FFFFE0);
            return;
        }
        case ARM64_RELOC_PAGEOFF12:
        case ARM64_RELOC_GOT_LOAD_PAGEOFF12: {
            /* Store an offset into a page (4k). Depending on the instruction
             * the bits are stored at slightly different positions.
             */
            if(!fitsBits(12, addend))
                barf("Relocation target for PAGEOFF12 out or range.");

            int shift = 0;
            if(isLoadStore(p)) {
                shift = (*p >> 30) & 0x3;
                if(0 == shift && isVectorOp(p)) {
                    shift = 4;
                }
            }
            *p = (*p & 0xFFC003FF)
               | ((uint32_t)(addend >> shift << 10) & 0x003FFC00);
            return;
        }
    }
    barf("unsupported relocation type: %d\n", ri->r_type);
}

bool
isGotLoad(struct relocation_info * ri) {
    return ri->r_type == ARM64_RELOC_GOT_LOAD_PAGE21
    ||  ri->r_type == ARM64_RELOC_GOT_LOAD_PAGEOFF12;
}

/*
 * Check if we need a global offset table slot for a
 * given symbol
 */
bool
needGotSlot(MachONList * symbol) {
    return (symbol->n_type & N_EXT)             /* is an external symbol      */
        && (N_UNDF == (symbol->n_type & N_TYPE) /* and is undefined           */
            || NO_SECT != symbol->n_sect);      /*     or is defined in a
                                                 *        different section   */
}

bool
makeGot(ObjectCode * oc) {
    size_t got_slots = 0;

    for(size_t i=0; i < oc->info->n_macho_symbols; i++)
        if(needGotSlot(oc->info->macho_symbols[i].nlist))
            got_slots += 1;

    if(got_slots > 0) {
        oc->info->got_size =  got_slots * sizeof(void*);
        oc->info->got_start = mmapAnonForLinker(oc->info->got_size);
        if( oc->info->got_start == NULL ) {
            barf("MAP_FAILED. errno=%d", errno );
            return EXIT_FAILURE;
        }
        /* update got_addr */
        size_t slot = 0;
        for(size_t i=0; i < oc->info->n_macho_symbols; i++)
            if(needGotSlot(oc->info->macho_symbols[i].nlist))
                oc->info->macho_symbols[i].got_addr
                    = ((uint8_t*)oc->info->got_start)
                    + (slot++ * sizeof(void *));
    }
    return EXIT_SUCCESS;
}

void
freeGot(ObjectCode * oc) {
    /* sanity check */
    if(NULL != oc->info->got_start && oc->info->got_size > 0) {
        munmapForLinker(oc->info->got_start, oc->info->got_size, "freeGot");
    }
    oc->info->got_start = NULL;
    oc->info->got_size = 0;
}

// Retrieve symbol value
static uint64_t symbol_value(ObjectCode* oc, MachOSymbol* symbol) {
  uint64_t value = 0;
  if(symbol->nlist->n_type & N_EXT) {
      /* external symbols should be able to be
       * looked up via the lookupDependentSymbol function.
       * Either through the global symbol hashmap
       * or asking the system, if not found
       * in the symbol hashmap
       */
      value = (uint64_t)lookupDependentSymbol((char*)symbol->name, oc, NULL);
      if(!value)
          barf("Could not lookup symbol: %s!", symbol->name);
  } else {
      value = (uint64_t)symbol->addr;    // address of the symbol.
  }
  return value;
}

static int
relocateSectionAarch64(ObjectCode * oc, Section * section)
{
    if(section->size == 0)
        return 1;
    /* at this point, we have:
     *
     * - loaded the sections (potentially into non-contiguous memory),
     *   (in ocGetNames_MachO)
     * - registered exported symbols
     *   (in ocGetNames_MachO)
     * - and fixed the nlist[i].n_value for common storage symbols (N_UNDF,
     *   N_EXT and n_value != 0) so that they point into the common storage.
     *   (in ocGetNames_MachO)
     * - All oc->symbols however should now point at the right place.
     */

    /* we need to care about the explicit addend */
    int64_t explicit_addend = 0;
    size_t  nreloc = section->info->macho_section->nreloc;

    for(size_t i = 0; i < nreloc; i++) {
        MachORelocationInfo * ri = &section->info->relocation_info[i];
        switch (ri->r_type) {
            case ARM64_RELOC_UNSIGNED: {
                MachOSymbol* symbol = &oc->info->macho_symbols[ri->r_symbolnum];
                int64_t addend = decodeAddend(oc, section, ri);
                uint64_t value = symbol_value(oc, symbol);
                encodeAddend(oc, section, ri, value + addend);
                break;
            }
            case ARM64_RELOC_SUBTRACTOR:
            {
                // subtractor and unsigned are called in tandem:
                // first  pc <- pc - symbol address (SUBTRACTOR)
                // second pc <- pc + symbol address (UNSIGNED)
                // to achieve pc <- pc + target - base.

                // check that the following relocation exists and has the
                // expected ARM64_RELOC_UNSIGNED type
                if(!(i+1 < nreloc)
                   || !(section->info->relocation_info[i+1].r_type
                          == ARM64_RELOC_UNSIGNED))
                    barf("SUBTRACTOR relocation *must* be followed by UNSIGNED relocation.");

                // we *know* that the next relocation is ARM64_RELOC_UNSIGNED
                // (see above). So let's process both relocations and write the
                // combined result in the target location. This prevents
                // overflow. (Compared to trying to store the intermediate
                // result which may not fit in the target bits).

                // sub part (ARM64_RELOC_SUBTRACTOR)
                MachOSymbol* symbol1 = &oc->info->macho_symbols[ri->r_symbolnum];
                uint64_t sub_value = symbol_value(oc, symbol1);

                // add part (ARM64_RELOC_UNSIGNED)
                MachORelocationInfo * ri2 = &section->info->relocation_info[i+1];
                MachOSymbol* symbol2 = &oc->info->macho_symbols[ri2->r_symbolnum];
                uint64_t add_value = symbol_value(oc, symbol2);

                // combine with addend and store
                int64_t addend = decodeAddend(oc, section, ri);
                encodeAddend(oc, section, ri, addend - sub_value + add_value);

                // skip next relocation: we've already handled it
                i += 1;
                break;
            }
            case ARM64_RELOC_BRANCH26: {
                MachOSymbol* symbol = &oc->info->macho_symbols[ri->r_symbolnum];

                // pre-existing addend
                int64_t addend = decodeAddend(oc, section, ri);
                // address of the branch (b/bl) instruction.
                uint64_t pc = (uint64_t)section->start + ri->r_address;
                uint64_t value = 0;
                if(symbol->nlist->n_type & N_EXT) {
                    value = (uint64_t)lookupDependentSymbol((char*)symbol->name, oc, NULL);
                    if(!value)
                        barf("Could not lookup symbol: %s!", symbol->name);
                } else {
                    value = (uint64_t)symbol->addr;    // address of the symbol.
                }
                // We've got:
                // + 2  bits, for alignment
                // + 26 bits for for the relocation value
                // - 1  bit for signage.
                //
                // Thus we can encode 26 bits for relocation, including the sign
                // bit. However as branches need to be 4-byte aligned, we only
                // need 26 bits to address a 28 bit range. Thus discarding the
                // sign bit, we can encode a range of +/- 27bits.
                //
                if((value - pc + addend) >> (2 + 26 - 1)) {
                    /* we need a stub */
                    /* check if we already have that stub */
                    if(findStub(section, (void**)&value, 0)) {
                        /* did not find it. Crete a new stub. */
                        if(makeStub(section, (void**)&value, 0)) {
                            barf("could not find or make stub");
                        }
                    }
                }
                encodeAddend(oc, section, ri, value - pc + addend);
                break;
            }
            case ARM64_RELOC_PAGE21:
            case ARM64_RELOC_GOT_LOAD_PAGE21: {
                MachOSymbol* symbol = &oc->info->macho_symbols[ri->r_symbolnum];
                int64_t addend = decodeAddend(oc, section, ri);
                if(!(explicit_addend == 0 || addend == 0))
                    barf("explicit_addend and addend can't be set at the same time.");
                uint64_t pc = (uint64_t)section->start + ri->r_address;
                uint64_t value = (uint64_t)(isGotLoad(ri) ? symbol->got_addr : symbol->addr);
                encodeAddend(oc, section, ri, ((value + addend + explicit_addend) & (-4096)) - (pc & (-4096)));

                // reset, just in case.
                explicit_addend = 0;
                break;
            }
            case ARM64_RELOC_PAGEOFF12:
            case ARM64_RELOC_GOT_LOAD_PAGEOFF12: {
                MachOSymbol* symbol = &oc->info->macho_symbols[ri->r_symbolnum];
                int64_t addend = decodeAddend(oc, section, ri);
                if(!(explicit_addend == 0 || addend == 0))
                    barf("explicit_addend and addend can't be set at the same time.");
                uint64_t value = (uint64_t)(isGotLoad(ri) ? symbol->got_addr : symbol->addr);
                encodeAddend(oc, section, ri, 0xFFF & (value + addend + explicit_addend));

                // reset, just in case.
                explicit_addend = 0;
                break;
            }
            case ARM64_RELOC_ADDEND: {
                explicit_addend = signExtend(ri->r_symbolnum, 24);
                if(!(i+1 < nreloc)
                   || !(section->info->relocation_info[i+1].r_type == ARM64_RELOC_PAGE21
                        || section->info->relocation_info[i+1].r_type == ARM64_RELOC_PAGEOFF12))
                    barf("ADDEND relocation *must* be followed by PAGE or PAGEOFF relocation");
                break;
            }
            default: {
                barf("Relocation of type: %d not (yet) supported!\n", ri->r_type);
            }
        }
    }
    return 1;
}
#endif /* aarch64_HOST_ARCH */

#if defined(x86_64_HOST_ARCH)
static int
relocateSection(ObjectCode* oc, int curSection)
{
    Section * sect = &oc->sections[curSection];

    IF_DEBUG(linker, debugBelch("relocateSection %d, info: %p\n", curSection, (void*)sect->info));

    // empty sections (without segments), won't have their info filled.
    // there is no relocation to be done for them.
    if(sect->info == NULL)
        return 1;

    MachOSection * msect = sect->info->macho_section; // for access convenience
    MachORelocationInfo * relocs = sect->info->relocation_info;
    MachOSymbol * symbols = oc->info->macho_symbols;

    IF_DEBUG(linker, debugBelch("relocateSection %d (%s, %s): start\n",
                                curSection, msect->segname, msect->sectname));

    if(!strcmp(msect->sectname,"__la_symbol_ptr"))
        return 1;
    else if(!strcmp(msect->sectname,"__nl_symbol_ptr"))
        return 1;
    else if(!strcmp(msect->sectname,"__la_sym_ptr2"))
        return 1;
    else if(!strcmp(msect->sectname,"__la_sym_ptr3"))
        return 1;

    IF_DEBUG(linker, debugBelch("relocateSection: number of relocations: %d\n", msect->nreloc));

    for(uint32_t i = 0; i < msect->nreloc; i++)
    {
        MachORelocationInfo *reloc = &relocs[i];

        char    *thingPtr = (char *) sect->start + reloc->r_address;
        uint64_t thing;
        /* We shouldn't need to initialise this, but gcc on OS X 64 bit
           complains that it may be used uninitialized if we don't */
        uint64_t value = 0;
        uint64_t baseValue;
        int type = reloc->r_type;
        int relocLenBytes;
        int nextInstrAdj = 0;

        IF_DEBUG(linker_verbose, debugBelch("relocateSection: relocation %d\n", i));
        IF_DEBUG(linker_verbose, debugBelch("               : type      = %d\n", reloc->r_type));
        IF_DEBUG(linker_verbose, debugBelch("               : address   = %d\n", reloc->r_address));
        IF_DEBUG(linker_verbose, debugBelch("               : symbolnum = %u\n", reloc->r_symbolnum));
        IF_DEBUG(linker_verbose, debugBelch("               : pcrel     = %d\n", reloc->r_pcrel));
        IF_DEBUG(linker_verbose, debugBelch("               : length    = %d\n", reloc->r_length));
        IF_DEBUG(linker_verbose, debugBelch("               : extern    = %d\n", reloc->r_extern));
        IF_DEBUG(linker_verbose, debugBelch("               : type      = %d\n", reloc->r_type));

        switch(reloc->r_length)
        {
            case 0:
                thing = *(uint8_t*)thingPtr;
                relocLenBytes = 1;
                break;
            case 1:
                thing = *(uint16_t*)thingPtr;
                relocLenBytes = 2;
                break;
            case 2:
                thing = *(uint32_t*)thingPtr;
                relocLenBytes = 4;
                break;
            case 3:
                thing = *(uint64_t*)thingPtr;
                relocLenBytes = 8;
                break;
            default:
                barf("Unknown size.");
        }
        checkProddableBlock(&oc->proddables,thingPtr,relocLenBytes);

        /*
         * With SIGNED_N the relocation is not at the end of the
         * instruction and baseValue needs to be adjusted accordingly.
         */
        switch (type) {
            case X86_64_RELOC_SIGNED_1:
                nextInstrAdj = 1;
                break;
            case X86_64_RELOC_SIGNED_2:
                nextInstrAdj = 2;
                break;
            case X86_64_RELOC_SIGNED_4:
                nextInstrAdj = 4;
                break;
        }
        baseValue = (uint64_t)thingPtr + relocLenBytes + nextInstrAdj;



        IF_DEBUG(linker_verbose,
                 debugBelch("relocateSection: length = %d, thing = %" PRId64 ", baseValue = %p\n",
                            reloc->r_length, thing, (char *)baseValue));

        if (type == X86_64_RELOC_GOT
         || type == X86_64_RELOC_GOT_LOAD)
        {
            MachOSymbol *symbol = &symbols[reloc->r_symbolnum];
            SymbolName* nm = symbol->name;
            SymbolAddr* addr = NULL;

            IF_DEBUG(linker_verbose, debugBelch("relocateSection: making jump island for %s, extern = %d, X86_64_RELOC_GOT\n",
                                        nm, reloc->r_extern));

            if (reloc->r_extern == 0) {
                    errorBelch("\nrelocateSection: global offset table relocation for symbol with r_extern == 0\n");
            }

            if (symbol->nlist->n_type & N_EXT) {
                    // The external bit is set, meaning the symbol is exported,
                    // and therefore can be looked up in this object module's
                    // symtab, or it is undefined, meaning dlsym must be used
                    // to resolve it.

                    addr = lookupDependentSymbol(nm, oc, NULL);
                    IF_DEBUG(linker_verbose,
                             debugBelch("relocateSection: looked up %s, "
                                        "external X86_64_RELOC_GOT or X86_64_RELOC_GOT_LOAD\n"
                                        "               : addr = %p\n", nm, addr));

                    if (addr == NULL) {
                            errorBelch("\nlookupSymbol failed in relocateSection (RELOC_GOT)\n"
                                       "%s: unknown symbol `%s'", oc->fileName, nm);
                            return 0;
                    }
            } else {
                    IF_DEBUG(linker_verbose, debugBelch("relocateSection: %s is not an exported symbol\n", nm));

                    // The symbol is not exported, or defined in another
                    // module, so it must be in the current object module,
                    // at the location given by the section index and
                    // symbol address (symbol->n_value)

                    if ((symbol->nlist->n_type & N_TYPE) == N_SECT) {
                        if (symbol->addr == NULL) {
                            errorBelch("relocateSection: address of internal symbol %s was not resolved\n", nm);
                            return 0;
                        }

                        addr = symbol->addr;

                        IF_DEBUG(linker_verbose,
                                 debugBelch("relocateSection: calculated relocation of "
                                            "non-external X86_64_RELOC_GOT or X86_64_RELOC_GOT_LOAD\n"));
                        IF_DEBUG(linker_verbose,
                                 debugBelch("               : addr = %p\n", addr));
                    } else {
                        errorBelch("\nrelocateSection: %s is not exported,"
                                   " and should be defined in a section, but isn't!\n", nm);
                        return 0;
                    }
            }

            // creates a jump island for every relocation entry for a symbol
            // TODO (AP): use got_addr to store the loc. of a jump island to reuse later
            value = (uint64_t) &makeSymbolExtra(oc, reloc->r_symbolnum, (unsigned long)addr)->addr;

            type = X86_64_RELOC_SIGNED;
        }
        else if (reloc->r_extern)
        {
            MachOSymbol *symbol = &symbols[reloc->r_symbolnum];
            SymbolName* nm = symbol->name;
            SymbolAddr* addr = NULL;

            IF_DEBUG(linker_verbose, debugBelch("relocateSection: looking up external symbol %s\n", nm));
            IF_DEBUG(linker_verbose, debugBelch("               : type  = %d\n", symbol->nlist->n_type));
            IF_DEBUG(linker_verbose, debugBelch("               : sect  = %d\n", symbol->nlist->n_sect));
            IF_DEBUG(linker_verbose, debugBelch("               : desc  = %d\n", symbol->nlist->n_desc));
            IF_DEBUG(linker_verbose, debugBelch("               : value = %p\n", (void *)symbol->nlist->n_value));

            if ((symbol->nlist->n_type & N_TYPE) == N_SECT) {
                CHECK(symbol->addr != NULL);
                value = (uint64_t) symbol->addr;
                IF_DEBUG(linker_verbose,
                         debugBelch("relocateSection, defined external symbol %s, relocated address %p\n",
                                    nm, (void *)value));
            }
            else {
                addr = lookupDependentSymbol(nm, oc, NULL);
                if (addr == NULL)
                {
                     errorBelch("\nlookupSymbol failed in relocateSection (relocate external)\n"
                                "%s: unknown symbol `%s'", oc->fileName, nm);
                     return 0;
                }

                value = (uint64_t) addr;
                IF_DEBUG(linker_verbose,
                         debugBelch("relocateSection: external symbol %s, address %p\n",
                                    nm, (void *)value));
            }
        }
        else
        {
            /* Since the relocation is internal, r_symbolnum contains a section
             * number relative to which the relocation is.  Depending on whether
             * the relocation is unsigned or signed, the given displacement is
             * relative to the image or the section respectively.
             *
             * For instance, in a signed case:
             * thing = <displ. to to section r_symbolnum *in the image*> (1)
             *       + <offset within r_symbolnum section>
             * (1) needs to be updated due to different section placement in memory.
             */

            CHECKM(reloc->r_symbolnum > 0,
                   "relocateSection: unsupported r_symbolnum = %" PRIu32 " < 1 for internal relocation",
                   reloc->r_symbolnum);

            int targetSecNum = reloc->r_symbolnum - 1; // sec numbers start with 1
            Section * targetSec = &oc->sections[targetSecNum];
            MachOSection * targetMacho = targetSec->info->macho_section;

            IF_DEBUG(linker_verbose,
                     debugBelch("relocateSection: internal relocation relative to section %d (%s, %s)\n",
                                targetSecNum, targetMacho->segname, targetMacho->sectname));

            switch (type) {
            case X86_64_RELOC_UNSIGNED: {
                CHECKM(thing >= targetMacho->addr,
                       "relocateSection: unsigned displacement %" PRIx64 "before target section start address %" PRIx64 "\n",
                       thing, (uint64_t) targetMacho->addr);

                uint64_t thingRelativeOffset = thing - targetMacho->addr;
                IF_DEBUG(linker_verbose,
                         debugBelch("                 "
                                    "unsigned displacement %" PRIx64 " with section relative offset %" PRIx64 "\n",
                                            thing, thingRelativeOffset));

                thing = (uint64_t) targetSec->start + thingRelativeOffset;
                IF_DEBUG(linker_verbose,
                         debugBelch("                 "
                                    "relocated address is %p\n", (void *) thing));

                /* Compared to external relocation we don't need to adjust value
                 * any further since thing already has absolute address.
                 */
                value = 0;
                break;
            }
            case X86_64_RELOC_SIGNED:
            case X86_64_RELOC_SIGNED_1:
            case X86_64_RELOC_SIGNED_2:
            case X86_64_RELOC_SIGNED_4: {
                uint32_t baseValueOffset = reloc->r_address + relocLenBytes + nextInstrAdj;
                uint64_t imThingLoc = msect->addr + baseValueOffset + (int64_t) thing;

                CHECKM(imThingLoc >= targetMacho->addr,
                       "relocateSection: target location %p in image before target section start address %p\n",
                       (void *) imThingLoc, (void *) targetMacho->addr);

                int64_t thingRelativeOffset = imThingLoc - targetMacho->addr;
                IF_DEBUG(linker_verbose,
                     debugBelch("                 "
                                "original displacement %" PRId64 " to %p with section relative offset %" PRIu64 "\n",
                                thing, (void *) imThingLoc, thingRelativeOffset));

                thing = (int64_t) ((uint64_t) targetSec->start + thingRelativeOffset)
                                - ((uint64_t) sect->start + baseValueOffset);
                value = baseValue; // so that it further cancels out with baseValue
                IF_DEBUG(linker_verbose,
                         debugBelch("                 "
                                    "relocated displacement %" PRId64 " to %p\n",
                                    (int64_t) thing, (void *) (baseValue + thing)));
                break;
            }
            default:
                barf("relocateSection: unexpected internal relocation type %d\n", type);
                return 0;
            }
        }

        IF_DEBUG(linker_verbose, debugBelch("relocateSection: value = %p\n", (void *) value));

        if (type == X86_64_RELOC_BRANCH)
        {
            if((int32_t)(value - baseValue) != (int64_t)(value - baseValue))
            {
                CHECK(reloc->r_extern);
                value = (uint64_t) &makeSymbolExtra(oc, reloc->r_symbolnum, value)
                                        -> jumpIsland;
            }
            CHECK((int32_t)(value - baseValue) == (int64_t)(value - baseValue));
            type = X86_64_RELOC_SIGNED;
        }

        switch(type)
        {
            case X86_64_RELOC_UNSIGNED:
                CHECK(!reloc->r_pcrel);
                thing += value;
                break;
            case X86_64_RELOC_SIGNED:
            case X86_64_RELOC_SIGNED_1:
            case X86_64_RELOC_SIGNED_2:
            case X86_64_RELOC_SIGNED_4:
                CHECK(reloc->r_pcrel);
                thing += value - baseValue;
                break;
            case X86_64_RELOC_SUBTRACTOR:
                CHECK(!reloc->r_pcrel);
                thing -= value;
                break;
            default:
                barf("unknown relocation");
        }

        IF_DEBUG(linker_verbose, debugBelch("relocateSection: thing = %p\n", (void *) thing));

        /* Thing points to memory within one of the relocated sections. We can
         * probe the first byte to sanity check internal relocations.
         */
        if (0 == reloc->r_extern) {
            if (reloc->r_pcrel) {
                checkProddableBlock(&oc->proddables, (void *)((char *)thing + baseValue), 1);
            } else {
                checkProddableBlock(&oc->proddables, (void *)thing, 1);
            }
        }

        switch(reloc->r_length)
        {
            case 0:
                *(uint8_t*)thingPtr = thing;
                break;
            case 1:
                *(uint16_t*)thingPtr = thing;
                break;
            case 2:
                *(uint32_t*)thingPtr = thing;
                break;
            case 3:
                *(uint64_t*)thingPtr = thing;
                break;
        }
    }

    IF_DEBUG(linker, debugBelch("relocateSection: done\n"));
    return 1;
}
#endif /* x86_64_HOST_ARCH */

SectionKind
getSectionKind_MachO(MachOSection *section)
{
    uint8_t s_type = section->flags & SECTION_TYPE;
    if (s_type == S_MOD_INIT_FUNC_POINTERS) {
        return SECTIONKIND_INIT_ARRAY;
    } else if (s_type == S_MOD_TERM_FUNC_POINTERS) {
        return SECTIONKIND_FINI_ARRAY;
    } else if (0==strcmp(section->segname,"__TEXT")) {
        return SECTIONKIND_CODE_OR_RODATA;
    } else if (0==strcmp(section->segname,"__DATA")) {
        return SECTIONKIND_RWDATA;
    } else {
        return SECTIONKIND_OTHER;
    }
}

/* Calculate the # of active segments and their sizes based on section
 * sizes and alignments. This is done in 2 passes over sections:
 * 1. Calculate how many sections is going to be in each segment and
 * the total segment size.
 * 2. Fill in segment's sections_idx arrays.
 *
 * gbZerofillSegment is there because of this comment in mach-o/loader.h:
 * The gigabyte zero fill sections, those with the section type
 * S_GB_ZEROFILL, can only be in a segment with sections of this
 * type. These segments are then placed after all other segments.
 */
int
ocBuildSegments_MachO(ObjectCode *oc)
{
    int n_rxSections = 0;
    size_t size_rxSegment = 0;
    Segment *rxSegment = NULL;

    int n_rwSections = 0;
    size_t size_rwSegment = 0;
    Segment *rwSegment = NULL;

    int n_gbZerofills = 0;
    size_t size_gbZerofillSegment = 0;
    Segment *gbZerofillSegment = NULL;

    int n_activeSegments = 0;
    int curSegment = 0;
    size_t size_compound;

    Segment *segments = NULL;
    void *mem = NULL, *curMem = NULL;

    for (int i = 0; i < oc->n_sections; i++) {
        MachOSection *macho = &oc->info->macho_sections[i];
        if (0 == macho->size) {
            IF_DEBUG(linker, debugBelch("ocBuildSegments_MachO: found a zero length section, skipping\n"));
            continue;
        }

        size_t alignment = 1 << macho->align;

        if (S_GB_ZEROFILL == (macho->flags & SECTION_TYPE)) {
            size_gbZerofillSegment = roundUpToAlign(size_gbZerofillSegment, alignment);
            size_gbZerofillSegment += macho->size;
            n_gbZerofills++;
        } else if (getSectionKind_MachO(macho) == SECTIONKIND_CODE_OR_RODATA) {
            size_rxSegment = roundUpToAlign(size_rxSegment, alignment);
            size_rxSegment += macho->size;
            n_rxSections++;
        } else {
            size_rwSegment = roundUpToAlign(size_rwSegment, alignment);
            size_rwSegment += macho->size;
            n_rwSections++;
        }
    }

    size_compound = roundUpToPage(size_rxSegment) +
        roundUpToPage(size_rwSegment) +
        roundUpToPage(size_gbZerofillSegment);

    if (n_rxSections > 0) {
        n_activeSegments++;
    }
    if (n_rwSections > 0) {
        n_activeSegments++;
    }
    if (n_gbZerofills > 0) {
        n_activeSegments++;
    }

    // N.B. it's possible that there is nothing mappable in an object. In this
    // case we avoid the mmap call and segment allocation/building since it will
    // fail either here or further down the road, e.g. on size > 0 assert in
    // addProddableBlock. See #16701.
    if (0 == size_compound) {
        IF_DEBUG(linker, debugBelch("ocBuildSegments_MachO: all segments are empty, skipping\n"));
        return 1;
    }

    mem = mmapAnonForLinker(size_compound);
    if (NULL == mem) return 0;

    IF_DEBUG(linker, debugBelch("ocBuildSegments: allocating %d segments\n", n_activeSegments));
    segments = (Segment*)stgCallocBytes(n_activeSegments, sizeof(Segment),
                                        "ocBuildSegments_MachO(segments)");
    curMem = mem;

    /* Allocate space for RX segment */
    if (n_rxSections > 0) {
        rxSegment = &segments[curSegment];
        initSegment(rxSegment,
                    curMem,
                    roundUpToPage(size_rxSegment),
                    SEGMENT_PROT_RX,
                    n_rxSections);
        IF_DEBUG(linker, debugBelch("ocBuildSegments_MachO: init segment %d (RX) at %p size %zu\n",
                                    curSegment, rxSegment->start, rxSegment->size));
        curMem = (char *)curMem + rxSegment->size;
        curSegment++;
    }

    /* Allocate space for RW segment */
    if (n_rwSections > 0) {
        rwSegment = &segments[curSegment];
        initSegment(rwSegment,
                    curMem,
                    roundUpToPage(size_rwSegment),
                    SEGMENT_PROT_RWO,
                    n_rwSections);
        IF_DEBUG(linker, debugBelch("ocBuildSegments_MachO: init segment %d (RWO) at %p size %zu\n",
                                    curSegment, rwSegment->start, rwSegment->size));
        curMem = (char *)curMem + rwSegment->size;
        curSegment++;
    }

    /* Allocate space for GB_ZEROFILL segment */
    if (n_gbZerofills > 0) {
        gbZerofillSegment = &segments[curSegment];
        initSegment(gbZerofillSegment,
                    curMem,
                    roundUpToPage(size_gbZerofillSegment),
                    SEGMENT_PROT_RWO,
                    n_gbZerofills);
        IF_DEBUG(linker, debugBelch("ocBuildSegments_MachO: init segment %d (GB_ZEROFILL) at %p size %zu\n",
                                    curSegment, gbZerofillSegment->start, gbZerofillSegment->size));
        curMem = (char *)curMem + gbZerofillSegment->size;
        curSegment++;
    }

    /* Second pass over sections to fill in sections_idx arrays */
    for (int i = 0, rx = 0, rw = 0, gb = 0;
         i < oc->n_sections;
         i++)
    {
        MachOSection *macho = &oc->info->macho_sections[i];
        // Skip zero size sections here as well since there was no place
        // allocated for them in Segment's sections_idx array
        if (0 == macho->size) {
            continue;
        }

        if (S_GB_ZEROFILL == (macho->flags & SECTION_TYPE)) {
            gbZerofillSegment->sections_idx[gb++] = i;
        } else if (getSectionKind_MachO(macho) == SECTIONKIND_CODE_OR_RODATA) {
            rxSegment->sections_idx[rx++] = i;
        } else {
            rwSegment->sections_idx[rw++] = i;
        }
    }

    oc->segments = segments;
    oc->n_segments = n_activeSegments;

    return 1;
}

int
ocGetNames_MachO(ObjectCode* oc)
{
    unsigned curSymbol = 0;

    unsigned long commonSize = 0;
    SymbolAddr* commonStorage = NULL;
    unsigned long commonCounter;

    IF_DEBUG(linker,debugBelch("ocGetNames_MachO: %s start\n",  OC_INFORMATIVE_FILENAME(oc)));

    Section *secArray;
    secArray = (Section*)stgCallocBytes(
        oc->info->segCmd->nsects,
        sizeof(Section),
        "ocGetNames_MachO(sections)");

    oc->sections = secArray;

    IF_DEBUG(linker, debugBelch("ocGetNames_MachO: will load %d sections\n",
                                oc->n_sections));

    CHECKM(ocBuildSegments_MachO(oc), "ocGetNames_MachO: failed to build segments\n");

    for (int seg_n = 0; seg_n < oc->n_segments; seg_n++) {
        Segment *segment = &oc->segments[seg_n];
        void *curMem = segment->start;

        IF_DEBUG(linker,
                 debugBelch("ocGetNames_MachO: loading segment %d "
                            "(address = %p, size = %zu) "
                            "with %d sections\n",
                            seg_n, segment->start, segment->size, segment->n_sections));

        for (int sec_n = 0; sec_n < segment->n_sections; sec_n++) {
            int sec_idx = segment->sections_idx[sec_n];
            MachOSection *section = &oc->info->macho_sections[sec_idx];

            size_t alignment = 1 << section->align;
            SectionKind kind = getSectionKind_MachO(section);
            SectionAlloc alloc = SECTION_NOMEM;
            void *start = NULL, *mapped_start = NULL;
            StgWord mapped_size = 0, mapped_offset = 0;
            StgWord size = section->size;

            void *secMem = (void *)roundUpToAlign((size_t)curMem, alignment);

            start = secMem;

            IF_DEBUG(linker,
                     debugBelch("ocGetNames_MachO: loading section %d in segment %d "
                                "(#%d, %s %s)\n"
                                "                  skipped %zu bytes due to alignment of %zu\n",
                                sec_n, seg_n, sec_idx, section->segname, section->sectname,
                                (char *)secMem - (char *)curMem, alignment));

            switch (section->flags & SECTION_TYPE) {
            case S_ZEROFILL:
            case S_GB_ZEROFILL:
                IF_DEBUG(linker, debugBelch("ocGetNames_MachO: memset to 0 a ZEROFILL section\n"));
                memset(secMem, 0, section->size);
                addSection(&secArray[sec_idx], kind, alloc, start, size,
                            mapped_offset, mapped_start, mapped_size);
                break;
            default:
                IF_DEBUG(linker,
                         debugBelch("ocGetNames_MachO: copying from %p to %p"
                                    " a block of %" PRIu64 " bytes\n",
                                    (void *) (oc->image + section->offset), secMem, section->size));
#if defined(NEED_PLT)
                unsigned nstubs = numberOfStubsForSection(oc, sec_idx);
                unsigned stub_space = STUB_SIZE * nstubs;

                void * mem = mmapForLinker(section->size+stub_space, MEM_READ_WRITE, MAP_ANON, -1, 0);

                if( mem == MAP_FAILED ) {
                    sysErrorBelch("failed to mmap allocated memory to load section %d. "
                                  "errno = %d", sec_idx, errno);
                }
                /* copy only the image part over; we don't want to copy data
                * into the stub part.
                */
                memcpy( mem, oc->image + section->offset, size );

                alloc = SECTION_MMAP;
                mapped_offset = 0;
                mapped_size = roundUpToPage(size+stub_space);
                start = mem;
                mapped_start = mem;
#else
                memcpy(secMem, oc->image + section->offset, section->size);
#endif
                addSection(&secArray[sec_idx], kind, alloc, start, size,
                            mapped_offset, mapped_start, mapped_size);
            /* SECTION_NOMEM since memory is already allocated in segments */

#if defined(NEED_PLT)
                secArray[sec_idx].info->nstubs = 0;
                secArray[sec_idx].info->stub_offset = (uint8_t*)mem + size;
                secArray[sec_idx].info->stub_size = stub_space;
                secArray[sec_idx].info->stubs = NULL;
#else
                secArray[sec_idx].info->nstubs = 0;
                secArray[sec_idx].info->stub_offset = NULL;
                secArray[sec_idx].info->stub_size = 0;
                secArray[sec_idx].info->stubs = NULL;
#endif
                addProddableBlock(&oc->proddables, start, section->size);
            }

            curMem = (char*) secMem + section->size;

            secArray[sec_idx].info->macho_section = section;
            secArray[sec_idx].info->relocation_info
                = (MachORelocationInfo*)(oc->image + section->reloff);

        }

    }

    /* now, as all sections have been loaded, we can resolve the absolute
     * address of symbols defined in those sections.
     */
    for(size_t i=0; i < oc->info->n_macho_symbols; i++) {
        MachOSymbol * s = &oc->info->macho_symbols[i];
        if( N_SECT == (s->nlist->n_type & N_TYPE) ) {
            if( NO_SECT == s->nlist->n_sect )
                barf("Symbol with N_SECT type, but no section.");

            /* section is given, and n_sect is >0 */
            uint8_t n = s->nlist->n_sect - 1;
            if(0 == oc->info->macho_sections[n].size) {
                continue;
            }

            /* addr <-   address in memory where the relocated section resides | (a)
             *         - section's address in the image | (b)
             *         + symbol's address in the image  | (c)
             * (c) - (b) gives symbol's offset relative to section start
             * (a) - (b) + (c) gives symbol's address for the relocated section
             *
             * (c) and (b) are not _real_ addresses and not equal
             * to file offsets in the image.
             * Rather they are (virtual) aligned addresses within
             * a single segment of MH_OBJECT object file.
             */
            s->addr = (uint8_t*)oc->sections[n].start
                              - oc->info->macho_sections[n].addr
                              + s->nlist->n_value;
            if(NULL == s->addr)
                barf("Failed to compute address for symbol %s", s->name);
        }
    }

    // count external symbols defined here
    oc->n_symbols = 0;
    if (oc->info->symCmd) {
        for (size_t i = 0; i < oc->info->n_macho_symbols; i++) {
            if (oc->info->nlist[i].n_type & N_STAB) {
                ;
            }
            else if(oc->info->nlist[i].n_type & N_EXT)
            {
                if((oc->info->nlist[i].n_type & N_TYPE) == N_UNDF
                    && (oc->info->nlist[i].n_value != 0))
                {
                    oc->n_symbols++;
                    /* Only allocate space for COMMON symbols not already
                     * defined by a previously-loaded object. */
                    SymbolName *nm_c = oc->info->macho_symbols[i].name;
                    if (!lookupStrHashTable(symhash, nm_c)) {
                        commonSize += oc->info->nlist[i].n_value;
                    }
                }
                else if((oc->info->nlist[i].n_type & N_TYPE) == N_SECT)
                    oc->n_symbols++;
            }
        }
    }
    /* allocate space for the exported symbols
     * in the object code. This is used to track
     * which symbols will have to be removed when
     * this object code is unloaded
     */
    IF_DEBUG(linker, debugBelch("ocGetNames_MachO: %d external symbols\n",
                                oc->n_symbols));
    oc->symbols = stgCallocBytes(oc->n_symbols, sizeof(Symbol_t),
                                   "ocGetNames_MachO(oc->symbols)");

    if (oc->info->symCmd) {
        for (size_t i = 0; i < oc->info->n_macho_symbols; i++) {
            SymbolName* nm = oc->info->macho_symbols[i].name;
            if (oc->info->nlist[i].n_type & N_STAB)
            {
                IF_DEBUG(linker_verbose, debugBelch("ocGetNames_MachO: Skip STAB: %s\n", nm));
            }
            else if ((oc->info->nlist[i].n_type & N_TYPE) == N_SECT)
            {
                if (oc->info->nlist[i].n_type & N_EXT)
                {
                    if (   (oc->info->nlist[i].n_desc & N_WEAK_DEF)
                        && lookupDependentSymbol(nm, oc, NULL)) {
                        // weak definition, and we already have a definition
                        IF_DEBUG(linker_verbose, debugBelch("    weak: %s\n", nm));
                    }
                    else
                    {
                            IF_DEBUG(linker_verbose, debugBelch("ocGetNames_MachO: inserting %s\n", nm));
                            SymbolAddr* addr = oc->info->macho_symbols[i].addr;
                            // TODO: Make figure out how to determine this from the object file
                            SymType sym_type = SYM_TYPE_CODE;
                            ghciInsertSymbolTable( oc->fileName
                                                 , symhash
                                                 , nm
                                                 , addr
                                                 , HS_BOOL_FALSE
                                                 , sym_type
                                                 , 0
                                                 , oc);

                            oc->symbols[curSymbol].name = nm;
                            oc->symbols[curSymbol].addr = addr;
                            oc->symbols[curSymbol].type = sym_type;
                            curSymbol++;
                    }
                }
                else
                {
                    IF_DEBUG(linker_verbose, debugBelch("ocGetNames_MachO: \t...not external, skipping %s\n", nm));
                }
            }
            else
            {
                IF_DEBUG(linker_verbose, debugBelch("ocGetNames_MachO: \t...not defined in this section, skipping %s\n", nm));
            }
        }
    }

    /* setup the common storage */
    if (commonSize > 0) {
        commonStorage = stgCallocBytes(1, commonSize, "ocGetNames_MachO(common symbols)");
        commonCounter = (unsigned long)commonStorage;
    }

    if (oc->info->symCmd) {
        for (size_t i = 0; i < oc->info->n_macho_symbols; i++) {
            SymbolName* nm = oc->info->macho_symbols[i].name;
            MachONList *nlist = &oc->info->nlist[i];
            if((nlist->n_type & N_TYPE) == N_UNDF
             && (nlist->n_type & N_EXT)
             && (nlist->n_value != 0)) {
                unsigned long sz = nlist->n_value;
                /* TODO: Figure out how to determine this from object */
                SymType sym_type = SYM_TYPE_CODE;

                RtsSymbolInfo *existing = lookupStrHashTable(symhash, nm);
                if (existing != NULL) {
                    /* COMMON symbol already allocated by a previously-loaded
                     * object; reuse that address so relocations resolve to
                     * the same storage. */
                    if (sz > existing->size) {
                        barf("linker: trying to link COMMON symbols %s with"
                             " incompatible sizes: previous size %llu,"
                             " new size %lu\n",
                             nm,
                             (long long unsigned int) existing->size,
                             sz);
                    }
                    nlist->n_value = (unsigned long)existing->value;
                    oc->info->macho_symbols[i].addr = existing->value;
                    IF_DEBUG(linker_verbose,
                             debugBelch("ocGetNames_MachO: COMMON symbol %s"
                                        " reusing address %p\n",
                                        nm, existing->value));
                    /* Don't add to oc->symbols: not the owner */
                } else {
                    nlist->n_value = commonCounter;
                    /* also set the final address to the macho_symbol */
                    oc->info->macho_symbols[i].addr = (void*)commonCounter;

                    IF_DEBUG(linker_verbose,
                             debugBelch("ocGetNames_MachO: inserting common symbol: %s\n", nm));
                    ghciInsertSymbolTable(oc->fileName, symhash, nm,
                                         (void*)commonCounter, HS_BOOL_FALSE,
                                         sym_type,
                                         sz, oc);
                    oc->symbols[curSymbol].name = nm;
                    oc->symbols[curSymbol].addr = oc->info->macho_symbols[i].addr;
                    oc->symbols[curSymbol].type = sym_type;
                    curSymbol++;

                    commonCounter += sz;
                }
            }
        }
    }
#if defined(aarch64_HOST_ARCH)
    /* Setup the global offset table
     * This is for symbols that are external, and not defined here.
     * So that we can load their address indirectly.
     *
     * We will get GOT request for any symbol that is
     * - EXT and UNDF
     * - EXT and not in the same section.
     *
     * As sections are not necessarily contiguous and can live
     * anywhere in the addressable space. This obviously makes
     * sense.  However it took me a while to figure this out.
     */
    makeGot(oc);

    /* at this point, macho_symbols, should know the addresses for
     * all symbols defined by this object code.
     * - those that are defined in sections.
     * - those that are undefined, but have a value (common storage).
     */
#endif
    IF_DEBUG(linker, debugBelch("ocGetNames_MachO: done\n"));
    return 1;
}

static bool
ocMprotect_MachO( ObjectCode *oc )
{
    for(int i=0; i < oc->n_segments; i++) {
        Segment *segment = &oc->segments[i];
        if(segment->size == 0) continue;

        if(segment->prot == SEGMENT_PROT_RX) {
            mprotectForLinker(segment->start, segment->size, MEM_READ_EXECUTE);
        }
    }

    // Also mark mmaped, sections executable. Those are not part of the
    // segments anymore and have been mapped separately.
    for(int i=0; i < oc->n_sections; i++) {
        Section *section = &oc->sections[i];
        if(section->size == 0) continue;
        if(section->alloc != SECTION_MMAP) continue;
        // N.B. m32 handles protection of its allocations during
        // flushing.
        if(section->alloc == SECTION_M32) continue;
        switch (section->kind) {
        case SECTIONKIND_CODE_OR_RODATA: {
            mprotectForLinker(section->mapped_start, section->mapped_size, MEM_READ_EXECUTE);
            break;
        }
        default:
            break;
        }
    }

    return true;
}

int
ocResolve_MachO(ObjectCode* oc)
{
    IF_DEBUG(linker, debugBelch("ocResolve_MachO: %s start\n", OC_INFORMATIVE_FILENAME(oc)));

    if(NULL != oc->info->dsymCmd)
    {
        unsigned long *indirectSyms
            = (unsigned long*) (oc->image + oc->info->dsymCmd->indirectsymoff);

        IF_DEBUG(linker, debugBelch("ocResolve_MachO: resolving dsymLC\n"));
        for (int i = 0; i < oc->n_sections; i++)
        {
            const char * sectionName = oc->info->macho_sections[i].sectname;

            IF_DEBUG(linker, debugBelch("ocResolve_MachO: section %d/%d: %s\n", i, oc->n_sections, sectionName));

            if(    !strcmp(sectionName,"__la_symbol_ptr")
                || !strcmp(sectionName,"__la_sym_ptr2")
                || !strcmp(sectionName,"__la_sym_ptr3"))
            {
                if(!resolveImports(oc,&oc->info->macho_sections[i],
                                   indirectSyms))
                    return 0;
            }
            else if(!strcmp(sectionName,"__nl_symbol_ptr")
                ||  !strcmp(sectionName,"__pointers"))
            {
                if(!resolveImports(oc,&oc->info->macho_sections[i],
                                   indirectSyms))
                    return 0;
            }
            else if(!strcmp(sectionName,"__jump_table"))
            {
                if(!resolveImports(oc,&oc->info->macho_sections[i],
                                   indirectSyms))
                    return 0;
            }
            else
            {
                IF_DEBUG(linker, debugBelch("ocResolve_MachO: unknown section %d/%d\n", i, oc->n_sections));
            }
        }
    }
#if defined(aarch64_HOST_ARCH)
    /* fill the GOT table */
    for(size_t i = 0; i < oc->info->n_macho_symbols; i++) {
        MachOSymbol * symbol = &oc->info->macho_symbols[i];
        if(needGotSlot(symbol->nlist)) {
            if(N_UNDF == (symbol->nlist->n_type & N_TYPE)) {
                /* an undefined symbol. So we need to ensure we
                 * have the address.
                 */
                if(NULL == symbol->addr) {
                    symbol->addr = lookupDependentSymbol((char*)symbol->name, oc, NULL);
                    if(NULL == symbol->addr) {
                        errorBelch("Failed to lookup symbol: %s", symbol->name);
                        return 0;
                    }
                } else {
                    // we already have the address.
                }
            } /* else it was defined in the same object,
               * just a different section. We should have
               * the address as well already
               */
            if(NULL == symbol->addr) {
                errorBelch("Symbol %s has no address!\n", (char*)symbol->name);
                return 0;
            }
            if(NULL == symbol->got_addr) {
                errorBelch("Symbol %s has no Global Offset Table address!\n", (char*)symbol->name);
                return 0;
            }
            *(uint64_t*)symbol->got_addr = (uint64_t)symbol->addr;
        }
    }
#endif

    for(int i = 0; i < oc->n_sections; i++)
    {
        IF_DEBUG(linker, debugBelch("ocResolve_MachO: relocating section %d/%d\n", i, oc->n_sections));

#if defined(aarch64_HOST_ARCH)
        if (!relocateSectionAarch64(oc, &oc->sections[i]))
            return 0;
#else
        if (!relocateSection(oc, i))
            return 0;
#endif
    }
    if(!ocMprotect_MachO ( oc ))
        return 0;

    return 1;
}

int
ocRunInit_MachO ( ObjectCode *oc )
{
    if (NULL == oc->info->segCmd) {
        barf("ocRunInit_MachO: no segment load command");
    }

    int argc, envc;
    char **argv, **envv;

    getProgArgv(&argc, &argv);
    getProgEnvv(&envc, &envv);

    for (int i = 0; i < oc->n_sections; i++) {
        IF_DEBUG(linker, debugBelch("ocRunInit_MachO: checking section %d\n", i));

        if (oc->sections[i].kind == SECTIONKIND_INIT_ARRAY) {
            IF_DEBUG(linker, debugBelch("ocRunInit_MachO:     running mod init functions\n"));

            void *init_startC = oc->sections[i].start;
            init_t *init = (init_t*)init_startC;
            init_t *init_end = (init_t*)((uint8_t*)init_startC
                             + oc->sections[i].info->macho_section->size);

            for (int pn = 0; init < init_end; init++, pn++) {
                IF_DEBUG(linker, debugBelch("ocRunInit_MachO:     function pointer %d at %p to %p\n",
                                            pn, (void *) init, (void *) *init));
                (*init)(argc, argv, envv);
            }
        }
    }

    freeProgEnvv(envc, envv);
    return 1;
}

int
ocRunFini_MachO ( ObjectCode *oc )
{
    if (NULL == oc->info->segCmd) {
        barf("ocRunInit_MachO: no segment load command");
    }

    for (int i = 0; i < oc->n_sections; i++) {
        IF_DEBUG(linker, debugBelch("ocRunFini_MachO: checking section %d\n", i));

        if (oc->sections[i].kind == SECTIONKIND_FINI_ARRAY) {
            IF_DEBUG(linker, debugBelch("ocRunFini_MachO:     running mod fini functions\n"));

            void *fini_startC = oc->sections[i].start;
            fini_t *fini = (fini_t*)fini_startC;
            fini_t *fini_end = (fini_t*)((uint8_t*)fini_startC
                             + oc->sections[i].info->macho_section->size);

            for (int pn = 0; fini < fini_end; fini++, pn++) {
                IF_DEBUG(linker, debugBelch("ocRunFini_MachO:     function pointer %d at %p to %p\n",
                                            pn, (void *) fini, (void *) *fini));
                (*fini)();
            }
        }
    }

    return 1;
}

/*
 * Figure out by how much to shift the entire Mach-O file in memory
 * when loading so that its single segment ends up 16-byte-aligned
 */
int
machoGetMisalignment( FILE * f )
{
    MachOHeader header;
    int misalignment;

    {
        size_t n = fread(&header, sizeof(header), 1, f);
        if (n != 1) {
            barf("machoGetMisalignment: can't read the Mach-O header");
        }
    }
    fseek(f, -sizeof(header), SEEK_CUR);

    if(header.magic != MH_MAGIC_64) {
        barf("Bad magic. Expected: %08x, got: %08x.",
             MH_MAGIC_64, header.magic);
    }

    misalignment = (header.sizeofcmds + sizeof(header))
                    & 0xF;

    IF_DEBUG(linker, debugBelch("mach-o misalignment %d\n", misalignment));
    return misalignment ? (16 - misalignment) : 0;
}

#endif /* darwin_HOST_OS || ios_HOST_OS */
