/*========================== begin_copyright_notice ============================

Copyright (C) 2017-2021 Intel Corporation

SPDX-License-Identifier: MIT

============================= end_copyright_notice ===========================*/

#include "CISALinker.h"
#include <stdio.h>

// *** Macros ***

#define ASSERT(assertion, ...)                                          \
    if (!(assertion)) {                                                 \
        fprintf(stderr, "Error: " __VA_ARGS__);                         \
        fprintf(stderr, "!\n");                                         \
        return 1;                                                       \
    }

#define ALLOC_ASSERT(allocation)                                        \
    ASSERT(                                                             \
        allocation, "Memory allocation failure at %s:%u",               \
        __FILE__, __LINE__);

#define INTERNAL_ASSERT(assertion)                                      \
    ASSERT(                                                             \
        assertion, "Internal error at %s:%u",                           \
        __FILE__, __LINE__)


#define TRY(x)                                                          \
    if ((x)) {                                                          \
        return 1;                                                       \
    }

#define BUF_APPEND_SCALAR(X)                                                \
    memcpy(                                                                    \
        (char *)_cisaObjInfos[_numCisaObjInfos].buf + _cisaEmitBufOffset,   \
        &X, sizeof(X));                                                        \
    _cisaEmitBufOffset += sizeof(X);

#define BUF_APPEND_VECTOR(X, Y)                                                \
    memcpy(                                                                    \
        (char *)_cisaObjInfos[_numCisaObjInfos].buf + _cisaEmitBufOffset,    \
        (X), Y);                                                            \
    _cisaEmitBufOffset += Y;

#define CLOSURE(x)     ((x).scratch)
#define UNITCLOSURE(x) (((CompiledUnitClosure *) CLOSURE(x)))
#define VARCLOSURE(x)  (((CompiledVarClosure *) CLOSURE(x)))

#define cisaBytePos byte_pos

// *** Public functions ***

CISALinker::CISALinker(
    int numOptions, const char *options[],
    vISA::Mem_Manager& mem, ExternalHeapAllocator *externalAllocator)
    : _numOptions(numOptions), _options(options), _mem(mem),
      _countLinkedUnits(0), _countLinkedVars(0), _cisaEmitBufOffset(0),
      _numCisaObjInfos(0), _cisaObjInfos(NULL)
{
    _linkedUnitIndexToClosureMap.reserve(64);
    if (externalAllocator) {
        _allocator = externalAllocator;
    }
    else {
        _allocator = (ExternalHeapAllocator*) malloc;
    }
}

CISALinker::~CISALinker()
{
}

int
CISALinker::LinkCisaMemObjs(
    const char* knlName, int numCisaObjs, const CisaObj cisaObjs[],
    CisaObj& cisaLinkedObj)
{
    TRY(AllocateCisaObjInfos(numCisaObjs));

    for (int i = 0; i < numCisaObjs; i++) {
        TRY(ReadCisaMemObj(cisaObjs[i], _cisaObjInfos[i]));
    }

    std::string knlNameStr(knlName);
    TRY(LinkCisaObjInfos(knlNameStr));
    TRY(WriteCisaMemObj(cisaLinkedObj));

    return 0;
}

int
CISALinker::LinkCisaFileObjs(
    const char* knlName, int numCisaObjs, const char *cisaObjs[],
    const char *cisaLinkedObj)
{
    TRY(AllocateCisaObjInfos(numCisaObjs));

    for (int i = 0; i < numCisaObjs; i++) {
        const char *cisaExt = ".isa";
        const char *cisaObjFile = cisaObjs[i];
        const char *argExt = strstr(cisaObjFile, cisaExt);
        ASSERT(
            argExt != NULL && argExt[4] =='\0',
            "CISA object file %s must have .isa extension", cisaObjFile);
        TRY(ReadCisaFileObj(cisaObjFile, _cisaObjInfos[i]));
    }

    std::string knlNameStr(knlName);
    TRY(LinkCisaObjInfos(knlNameStr));
    TRY(WriteCisaFileObj(cisaLinkedObj));

    return 0;
}

// *** Private functions ***

int
CISALinker::LinkCisaObjInfos(const std::string& knlName)
{
    CisaObjInfo& cisaLinkedObjInfo = _cisaObjInfos[_numCisaObjInfos];
    TRY(InitGlobalInfoTables());
    CompiledUnitClosureList cisaKnlClosureList;
    TRY(CreateKernelClosures(knlName, cisaKnlClosureList));
    CompiledUnitClosureList::iterator iter = cisaKnlClosureList.begin();

    for (; iter != cisaKnlClosureList.end(); ++iter) {
        CompiledUnitClosure& knlClosure = **iter;
        CisaObjInfo& knlObjInfo = *knlClosure.unitObjInfo;
        CompiledUnitClosureList localUnitClosureList;
        localUnitClosureList.push_back(&knlClosure);
        TRY(LinkLocalCisaObjInfos(knlObjInfo, localUnitClosureList));
        TRY(LinkExternCisaObjInfos());
    }

    TRY(BuildLinkedCisaObj(cisaKnlClosureList));
    TRY(UpdateLinkedCisaImageOffsetsAndSize());
    ClearGlobalInfoTables();

    return 0;
}

int
CISALinker::InitGlobalInfoTables()
{
    for (int i = 0; i < _numCisaObjInfos; i++) {
        CisaObjInfo& cisaObjInfo = _cisaObjInfos[i];

        for (int j = 0; j < cisaObjInfo.hdr.num_global_functions; j++) {
            int k = j + cisaObjInfo.hdr.num_extern_functions;
            CompiledUnitInfo& unit = cisaObjInfo.hdr.functions[k];
            CompiledUnitClosure *unitClosure =
                (CompiledUnitClosure *)
                _mem.alloc(sizeof(CompiledUnitClosure));
            unitClosure->unit = &unit;
            unitClosure->unitObjInfo = &cisaObjInfo;
            unitClosure->linkedUnitIndex = -1;
            CLOSURE(unit) = unitClosure;
            std::string key(unit.name, unit.name_len);
            std::pair<GlobalUnitNameToInfoMap::iterator, bool> loc =
                _globalUnitNameToInfoMap.insert(
                    GlobalUnitNameToInfoMap::value_type(key, &unit));
            ASSERT(
                loc.second, "Found duplicate global functions for %s",
                key.c_str());
        }
    }

    for (int i = 0; i < _numCisaObjInfos; i++) {
        CisaObjInfo& cisaObjInfo = _cisaObjInfos[i];

        for (int j = 0; j < cisaObjInfo.hdr.num_global_variables; j++) {
            int k = j + cisaObjInfo.hdr.num_extern_variables;
            CompiledVarInfo& var = cisaObjInfo.hdr.filescope_variables[k];
            CompiledVarClosure * varClosure =
                (CompiledVarClosure *) _mem.alloc(sizeof(CompiledVarClosure));
            varClosure->var = &var;
            varClosure->varObjInfo = &cisaObjInfo;
            varClosure->linkedVarIndex = -1;
            CLOSURE(var) = varClosure;
            std::string key((char *)var.name, var.name_len);
            std::pair<GlobalVarNameToInfoMap::iterator, bool> loc =
                _globalVarNameToInfoMap.insert(
                    GlobalVarNameToInfoMap::value_type(key, &var));
            ASSERT(
                loc.second, "Found duplicate global variables for %s",
                key.c_str());
        }
    }

    return 0;
}

inline void
CISALinker::ClearGlobalInfoTables()
{
    _globalUnitNameToInfoMap.clear();
    _globalVarNameToInfoMap.clear();
}

inline int
CISALinker::GetGlobalUnitInfo(
    const std::string& unitName, CompiledUnitInfo *& globalUnitInfo)
{
    GlobalUnitNameToInfoMap::iterator loc =
        _globalUnitNameToInfoMap.find(unitName);
    ASSERT(
        loc != _globalUnitNameToInfoMap.end(),
        "Cannot find global function %s", unitName.c_str());
    globalUnitInfo = (*loc).second;

    return 0;
}

inline int
CISALinker::GetGlobalUnitClosure(
    const std::string& unitName, CompiledUnitClosure *& globalUnitClosure)
{
    CompiledUnitInfo *globalUnitInfo = NULL;
    TRY(GetGlobalUnitInfo(unitName, globalUnitInfo));
    globalUnitClosure = UNITCLOSURE(*globalUnitInfo);
    INTERNAL_ASSERT(globalUnitClosure);

    return 0;
}

inline int
CISALinker::GetGlobalVarInfo(
    const std::string& unitName, CompiledVarInfo *& globalVarInfo)
{
    GlobalVarNameToInfoMap::iterator loc =
        _globalVarNameToInfoMap.find(unitName);
    ASSERT(
        loc != _globalVarNameToInfoMap.end(),
        "Cannot find global variable %s", unitName.c_str());
    globalVarInfo = (*loc).second;

    return 0;
}

inline int
CISALinker::GetGlobalVarClosure(
    const std::string& unitName, CompiledVarClosure *& globalVarClosure)
{
    CompiledVarInfo *globalVarInfo = NULL;
    TRY(GetGlobalVarInfo(unitName, globalVarInfo));
    globalVarClosure = VARCLOSURE(*globalVarInfo);
    INTERNAL_ASSERT(globalVarClosure);

    return 0;
}

int
CISALinker::CreateKernelClosures(
    const std::string& knlName, CompiledUnitClosureList& knlClosureList)
{
    for (int i = 0; i < _numCisaObjInfos; i++) {
        CisaObjInfo& cisaObjInfo = _cisaObjInfos[i];

        for (int j = 0; j < cisaObjInfo.hdr.num_kernels; j++) {
            CompiledUnitInfo& kernel = cisaObjInfo.hdr.kernels[j];

            if ((knlName.size() == 0) ||
                (knlName.size() == kernel.name_len &&
                 strncmp(kernel.name, knlName.c_str(), knlName.size()) == 0)) {
                CompiledUnitClosure *knlClosure =
                    (CompiledUnitClosure *)
                    _mem.alloc(sizeof(CompiledUnitClosure));
                ALLOC_ASSERT(knlClosure);
                knlClosure->unit = &kernel;
                knlClosure->unitObjInfo = &cisaObjInfo;
                knlClosure->linkedUnitIndex = 0;
                CLOSURE(kernel) = knlClosure;
                knlClosureList.push_back(knlClosure);

                if (knlName.size() && knlClosureList.size()) {
                    break;
                }
            }

            if (knlName.size() && knlClosureList.size()) {
                break;
            }
        }
    }

    if (knlName.size()) {
        ASSERT(
            knlClosureList.size() > 0,
            "Cannot find kernel %s", knlName.c_str());
    }
    else {
        ASSERT(
            knlClosureList.size() > 0,
            "Cannot find any kernel in input CISA objects");
    }

    return 0;
}

int
CISALinker::LinkLocalCisaObjInfos(
    CisaObjInfo& cisaObjInfo, CompiledUnitClosureList& localUnitClosureList)
{
    _localUnitMap = cisaObjInfo.hdr.functions;
    _localVarMap  = cisaObjInfo.hdr.filescope_variables;
    CompiledUnitClosureList::iterator iter = localUnitClosureList.begin();

    for ( ; iter != localUnitClosureList.end(); ++iter) {
        TRY(LinkInDepUnits(**iter));
        TRY(LinkInDepVars(**iter));
    }

    for (iter = _pendingLocalClosures.begin();
         iter != _pendingLocalClosures.end(); ++iter) {
        TRY(LinkInDepUnits(**iter));
        TRY(LinkInDepVars(**iter));
    }

    for (iter = _pendingLocalClosures.begin();
         iter != _pendingLocalClosures.end(); ++iter) {
        TRY(RelocFunctionSyms(**iter));
        TRY(RelocVariableSyms(**iter));
    }

    for (iter = localUnitClosureList.begin();
         iter != localUnitClosureList.end(); ++iter) {
        TRY(RelocFunctionSyms(**iter));
        TRY(RelocVariableSyms(**iter));
    }

    _pendingLocalClosures.clear();
    _localUnitMap = NULL;
    _localVarMap = NULL;

    return 0;
}

int
CISALinker::LinkExternCisaObjInfos()
{
    CompiledUnitClosureList::iterator iter = _pendingExternClosures.begin();

    for ( ; iter != _pendingExternClosures.end(); ++iter) {
        CompiledUnitClosureList localUnitClosuresList;
        localUnitClosuresList.push_back(*iter);

        CompiledUnitClosureList::iterator jter = iter;

        for ( ++jter; jter != _pendingExternClosures.end(); ) {

            if ((*jter)->unitObjInfo == (*iter)->unitObjInfo) {
                localUnitClosuresList.push_back(*jter);
                CompiledUnitClosureList::iterator kter = jter++;
                _pendingExternClosures.erase(kter);
            }
            else {
                ++jter;
            }
        }

        CisaObjInfo& unitObjInfo = *(*iter)->unitObjInfo;
        TRY(LinkLocalCisaObjInfos(unitObjInfo, localUnitClosuresList));
    }

    _pendingExternClosures.clear();

    return 0;
}

int
CISALinker::LinkInDepUnits(CompiledUnitClosure& cisaUnitClosure)
{
    CompiledUnitInfo& cisaUnitInfo = *cisaUnitClosure.unit;
    int globalStartIndex =
        cisaUnitClosure.unitObjInfo->hdr.num_extern_functions;
    int staticStartIndex =
        globalStartIndex +
        cisaUnitClosure.unitObjInfo->hdr.num_global_functions;

    for (int i = 0; i < cisaUnitInfo.function_reloc_symtab.num_syms; i++) {
        int inputRefIndex =
            cisaUnitInfo.function_reloc_symtab.reloc_syms[i].resolved_index;

        if (inputRefIndex >= staticStartIndex) {

            if (CLOSURE(_localUnitMap[inputRefIndex]) == NULL) {
                CompiledUnitClosure *depUnitClosure =
                    (CompiledUnitClosure *)
                    _mem.alloc(sizeof(CompiledUnitClosure));
                CLOSURE(_localUnitMap[inputRefIndex]) = depUnitClosure;
                depUnitClosure->unit =
                    &cisaUnitClosure.unitObjInfo->hdr.functions[inputRefIndex];
                depUnitClosure->unitObjInfo = cisaUnitClosure.unitObjInfo;
                depUnitClosure->linkedUnitIndex = _countLinkedUnits++;
                _linkedUnitIndexToClosureMap.push_back(depUnitClosure);
                _pendingLocalClosures.push_back(depUnitClosure);
            }
        }
        else if (inputRefIndex >= globalStartIndex) {
            CompiledUnitClosure *depUnitClosure =
                UNITCLOSURE(_localUnitMap[inputRefIndex]);
            INTERNAL_ASSERT(depUnitClosure);

            if (depUnitClosure->linkedUnitIndex == -1) {
                depUnitClosure->linkedUnitIndex = _countLinkedUnits++;
                _linkedUnitIndexToClosureMap.push_back(depUnitClosure);
                _pendingLocalClosures.push_back(depUnitClosure);
            }
        }
        else {
            CompiledUnitInfo& unit =
                cisaUnitClosure.unitObjInfo->hdr.functions[inputRefIndex];
            CompiledUnitClosure *externUnitClosure = NULL;
            std::string unitName(unit.name, unit.name_len);
            TRY(GetGlobalUnitClosure(unitName, externUnitClosure));

            if (externUnitClosure->linkedUnitIndex == -1) {
                externUnitClosure->linkedUnitIndex = _countLinkedUnits++;
                _linkedUnitIndexToClosureMap.push_back(externUnitClosure);
                _pendingExternClosures.push_back(externUnitClosure);
            }
        }
    }

    return 0;
}

int
CISALinker::LinkInDepVars(CompiledUnitClosure& cisaUnitClosure)
{
    CompiledVarInfo *vars =
        cisaUnitClosure.unitObjInfo->hdr.filescope_variables;
    CompiledUnitInfo& cisaUnitInfo = *cisaUnitClosure.unit;
    int globalStartIndex =
        cisaUnitClosure.unitObjInfo->hdr.num_extern_variables;
    int staticStartIndex =
        globalStartIndex +
        cisaUnitClosure.unitObjInfo->hdr.num_global_variables;

    for (int i = 0; i < cisaUnitInfo.variable_reloc_symtab.num_syms; i++) {
        int inputRefIndex =
            cisaUnitInfo.variable_reloc_symtab.reloc_syms[i].resolved_index;

        if (inputRefIndex >= staticStartIndex) {

            if (CLOSURE(_localVarMap[inputRefIndex]) == NULL) {
                CompiledVarClosure * depVarClosure =
                    (CompiledVarClosure *)
                    _mem.alloc(sizeof(CompiledVarClosure));
                CLOSURE(_localVarMap[inputRefIndex]) = depVarClosure;
                CompiledVarInfo& var = vars[inputRefIndex];
                depVarClosure->var =&var;
                depVarClosure->varObjInfo = cisaUnitClosure.unitObjInfo;
                depVarClosure->linkedVarIndex = _countLinkedVars++;
                _linkedVarIndexToClosureMap.push_back(depVarClosure);
            }
        }
        else if (inputRefIndex >= globalStartIndex) {
            CompiledVarClosure *depVarClosure =
                VARCLOSURE(_localVarMap[inputRefIndex]);
            INTERNAL_ASSERT(depVarClosure);

            if (depVarClosure->linkedVarIndex == -1) {
                depVarClosure->linkedVarIndex = _countLinkedVars++;
                _linkedVarIndexToClosureMap.push_back(depVarClosure);
            }
        }
        else {
            CompiledVarInfo& variable = vars[inputRefIndex];
            CompiledVarClosure *externVarClosure = NULL;
            std::string varName((char *) variable.name, variable.name_len);
            TRY(GetGlobalVarClosure(varName, externVarClosure));

            if (externVarClosure->linkedVarIndex == -1) {
                externVarClosure->linkedVarIndex = _countLinkedVars++;
                _linkedVarIndexToClosureMap.push_back(externVarClosure);
            }
        }
    }

    return 0;
}

int
CISALinker::RelocFunctionSyms(CompiledUnitClosure& localUnitClosure)
{
    CompiledUnitInfo& localUnitInfo = *localUnitClosure.unit;

    for (int i = 0; i < localUnitInfo.function_reloc_symtab.num_syms; i++) {
        int inputRefIndex =
            localUnitInfo.function_reloc_symtab.reloc_syms[i].resolved_index;
        int offset = localUnitClosure.unitObjInfo->hdr.num_extern_functions;

        if (inputRefIndex >= offset) {
            int linkedUnitIndex =
                UNITCLOSURE(_localUnitMap[inputRefIndex])->linkedUnitIndex;
            INTERNAL_ASSERT(linkedUnitIndex >= 0);
            localUnitInfo.function_reloc_symtab.reloc_syms[i].resolved_index =
                linkedUnitIndex;
        }
        else {
            CompiledUnitInfo *units =
                localUnitClosure.unitObjInfo->hdr.functions;
            CompiledUnitInfo& unit = units[inputRefIndex];
            CompiledUnitClosure *externUnitClosure = NULL;
            std::string unitName(unit.name, unit.name_len);
            TRY(GetGlobalUnitClosure(unitName, externUnitClosure));
            int linkedUnitIndex = externUnitClosure->linkedUnitIndex;
            INTERNAL_ASSERT(linkedUnitIndex >= 0);
            localUnitInfo.function_reloc_symtab.reloc_syms[i].resolved_index =
                linkedUnitIndex;
        }
    }

    return 0;
}

int
CISALinker::RelocVariableSyms(CompiledUnitClosure& localUnitClosure)
{
    CompiledUnitInfo& localUnitInfo = *localUnitClosure.unit;
    CompiledVarInfo *vars =
        (CompiledVarInfo *)
        localUnitClosure.unitObjInfo->hdr.filescope_variables;

    for (int i = 0; i < localUnitInfo.variable_reloc_symtab.num_syms; i++) {
        int inputRefIndex =
            localUnitInfo.variable_reloc_symtab.reloc_syms[i].resolved_index;
        int offset =
            localUnitClosure.unitObjInfo->hdr.num_extern_variables;

        if (inputRefIndex >= offset) {
            int linkedVarIndex =
                VARCLOSURE(_localVarMap[inputRefIndex])->linkedVarIndex;
            INTERNAL_ASSERT(linkedVarIndex >= 0);
            localUnitInfo.variable_reloc_symtab.reloc_syms[i].resolved_index =
                linkedVarIndex;
        }
        else {
            CompiledVarInfo& variable = vars[inputRefIndex];
            CompiledVarClosure * externVarClosure = NULL;
            std::string unitName((char *) variable.name, variable.name_len);
            TRY(GetGlobalVarClosure(unitName, externVarClosure));
            int linkedVarIndex = externVarClosure->linkedVarIndex;
            INTERNAL_ASSERT(linkedVarIndex >= 0);
            localUnitInfo.variable_reloc_symtab.reloc_syms[i].resolved_index =
                externVarClosure->linkedVarIndex;
        }
    }

    return 0;
}

int
CISALinker::BuildLinkedCisaObj(CompiledUnitClosureList& knlClosureList)
{
    CisaObjInfo& linkedObjInfo = _cisaObjInfos[_numCisaObjInfos];
    const CisaHeader& firstKnlHdr = knlClosureList.front()->unitObjInfo->hdr;

    linkedObjInfo.hdr.magic_number = firstKnlHdr.magic_number;
    linkedObjInfo.hdr.major_version = firstKnlHdr.major_version;
    linkedObjInfo.hdr.minor_version = firstKnlHdr.minor_version;
    linkedObjInfo.hdr.num_kernels = knlClosureList.size();
    linkedObjInfo.hdr.num_extern_variables = 0;
    linkedObjInfo.hdr.num_global_variables = 0;
    linkedObjInfo.hdr.num_static_variables = _countLinkedVars;
    linkedObjInfo.hdr.num_filescope_variables = _countLinkedVars;
    linkedObjInfo.hdr.num_extern_functions = 0;
    linkedObjInfo.hdr.num_global_functions = 0;
    linkedObjInfo.hdr.num_static_functions = _countLinkedUnits;
    linkedObjInfo.hdr.num_functions = _countLinkedUnits;
    linkedObjInfo.hdr.kernels =
        (CompiledUnitInfo *)
        _mem.alloc(sizeof(CompiledUnitInfo) * knlClosureList.size());
    ALLOC_ASSERT(linkedObjInfo.hdr.kernels);
    linkedObjInfo.hdr.filescope_variables =
        (CompiledVarInfo *) _mem.alloc(
            sizeof(CompiledUnitInfo) * _countLinkedVars);

    if (_countLinkedUnits) {
        linkedObjInfo.hdr.functions =
            (CompiledUnitInfo *) _mem.alloc(
                sizeof(CompiledUnitInfo) * _countLinkedUnits);
        ALLOC_ASSERT(_countLinkedUnits == 0 || linkedObjInfo.hdr.functions);
    }
    else {
        linkedObjInfo.hdr.functions = NULL;
    }

    TRY(BuildLinkedVarInfo(linkedObjInfo.hdr));
    CompiledUnitClosureList::iterator iter = knlClosureList.begin();

    for (int i = 0; iter != knlClosureList.end(); ++iter, ++i) {
        CompiledUnitClosure& knlClosure = **iter;
        CompiledUnitInfo& knlInfo = *knlClosure.unit;
        std::string knlName(knlInfo.name, knlInfo.name_len);
        TRY(BuildLinkedUnitInfo(
                knlName, knlClosure, linkedObjInfo.hdr.kernels[i]));
    }

    for (int i = 0; i < _countLinkedUnits; i++) {
        CompiledUnitClosure& funcClosure = *_linkedUnitIndexToClosureMap[i];
        CompiledUnitInfo& funcInfo = *funcClosure.unit;
        std::string unitName(funcInfo.name, funcInfo.name_len);
        TRY(BuildLinkedUnitInfo(
                unitName, funcClosure,linkedObjInfo.hdr.functions[i]));
    }

    linkedObjInfo.buf = NULL;
    linkedObjInfo.size = 0;

    return 0;
}

int
CISALinker::BuildLinkedUnitInfo(
    const std::string &unitName, CompiledUnitClosure& objUnitClosure,
    CompiledUnitInfo& linkedUnitInfo)
{
    CompiledUnitInfo& objUnit = *objUnitClosure.unit;

    linkedUnitInfo.name_len = unitName.size();
    strncpy(linkedUnitInfo.name, unitName.c_str(), unitName.size());
    linkedUnitInfo.offset = objUnit.offset;
    linkedUnitInfo.size = objUnit.size;
    linkedUnitInfo.input_offset = objUnit.input_offset;
    BuildRelocSymTab(
        linkedUnitInfo.variable_reloc_symtab, objUnit.variable_reloc_symtab);
    BuildRelocSymTab(
        linkedUnitInfo.function_reloc_symtab, objUnit.function_reloc_symtab);
    linkedUnitInfo.num_gen_binaries = 0;
    linkedUnitInfo.cisa_binary_buffer =
        (char *) objUnitClosure.unitObjInfo->buf + objUnit.offset;

    return 0;
}

int
CISALinker::BuildRelocSymTab(RelocTab& dst, RelocTab& src)
{
    dst.num_syms = src.num_syms;

    if (dst.num_syms) {
        dst.reloc_syms =
            (reloc_sym *) _mem.alloc(sizeof(reloc_sym) * src.num_syms);
        ALLOC_ASSERT(dst.reloc_syms);

        for (int i = 0; i < src.num_syms; i++) {
            dst.reloc_syms[i].symbolic_index =
                src.reloc_syms[i].symbolic_index;
            dst.reloc_syms[i].resolved_index =
                src.reloc_syms[i].resolved_index;
        }
    }
    else {
        dst.reloc_syms = NULL;
    }

    return 0;
}

int
CISALinker::BuildLinkedVarInfo(CisaHeader& linkedUnitHdr)
{
    for (int i = 0; i < _countLinkedVars; i++) {
        CompiledVarInfo& objVariable = *_linkedVarIndexToClosureMap[i]->var;
        CompiledVarInfo& linkedVariable = linkedUnitHdr.filescope_variables[i];
        linkedVariable.name_len = objVariable.name_len;
        linkedVariable.name = objVariable.name;
        linkedVariable.bit_properties = objVariable.bit_properties;
        linkedVariable.num_elements = objVariable.num_elements;
        linkedVariable.attribute_count = objVariable.attribute_count;
        linkedVariable.attributes = objVariable.attributes;
    }

    return 0;
}

int
CISALinker::UpdateLinkedCisaImageOffsetsAndSize()
{
    CisaObjInfo& linkedObjInfo    = _cisaObjInfos[_numCisaObjInfos];
    CisaHeader& linkedObjHdr      = linkedObjInfo.hdr;

    _linkedCisaImageSize = 0;
    _linkedCisaImageSize += sizeof(linkedObjHdr.magic_number);
    _linkedCisaImageSize += sizeof(linkedObjHdr.major_version);
    _linkedCisaImageSize += sizeof(linkedObjHdr.minor_version);
    _linkedCisaImageSize += sizeof(linkedObjHdr.num_kernels);

    for (int i = 0; i < linkedObjHdr.num_kernels; i++) {
        _linkedCisaImageSize += CalculateSize(linkedObjHdr.kernels[i], false);
    }

    _linkedCisaImageSize += sizeof(linkedObjHdr.num_filescope_variables);

    for (int i = 0; i < linkedObjHdr.num_filescope_variables; i++) {
        _linkedCisaImageSize +=
            CalculateSize(linkedObjHdr.filescope_variables[i]);
    }

    _linkedCisaImageSize += sizeof(linkedObjHdr.num_functions);

    for (int i = 0; i < linkedObjHdr.num_functions; i++) {
        _linkedCisaImageSize += CalculateSize(linkedObjHdr.functions[i], true);
    }

    for (int i = 0; i < linkedObjHdr.num_kernels; i++) {
        CompiledUnitInfo& knlUnitInfo = linkedObjHdr.kernels[i];
        unsigned knlUnitDataStartOffset = _linkedCisaImageSize;
        knlUnitInfo.input_offset = knlUnitInfo.input_offset - knlUnitInfo.offset + knlUnitDataStartOffset;
        knlUnitInfo.offset = knlUnitDataStartOffset;
        _linkedCisaImageSize += knlUnitInfo.size;
    }

    for (int i = 0; i < linkedObjHdr.num_functions; i++) {
        CompiledUnitInfo& unitInfo = linkedObjHdr.functions[i];
        unsigned unitDataStartOffset = _linkedCisaImageSize;
        unitInfo.offset = unitDataStartOffset;
        _linkedCisaImageSize += unitInfo.size;
    }

    linkedObjInfo.size = _linkedCisaImageSize;

    return 0;
}

int
CISALinker::CalculateSize(CompiledUnitInfo& unit, bool isFunction)
{
    unsigned size = 0;
    if( isFunction == true )
    {
        size += sizeof(unit.linkage);
    }

    size += sizeof(unit.name_len) + unit.name_len;
    size += sizeof(unit.offset);
    size += sizeof(unit.size);

    if( isFunction == false )
    {
        size += sizeof(unit.input_offset);
    }

    size += CalculateSize(unit.variable_reloc_symtab);
    size += CalculateSize(unit.function_reloc_symtab);

    if( isFunction == false )
    {
        size += sizeof(unit.num_gen_binaries);
    }

    return size;
}

int
CISALinker::CalculateSize(CompiledVarInfo& var)
{
    unsigned size = 0;
    size += sizeof(var.linkage);
    size += sizeof(var.name_len) + var.name_len;
    size += sizeof(var.bit_properties);
    size += sizeof(var.num_elements);
    size += sizeof(var.attribute_count);

    for (int i = 0; i < var.attribute_count; i++) {
        size += sizeof(var.attributes[i].nameIndex);
        size +=
            sizeof(var.attributes[i].size) + var.attributes[i].size;
    }

    return size;
}

int
CISALinker::CalculateSize(RelocTab& symTab)
{
    unsigned size = 0;
    size += sizeof(symTab.num_syms);
    size += sizeof(reloc_sym) * symTab.num_syms;

    return size;
}

int
CISALinker::AllocateCisaObjInfos(int numCisaObjs)
{
    _cisaObjInfos =
        (CisaObjInfo *)_mem.alloc(sizeof(CisaObjInfo) * (numCisaObjs + 1));
    ALLOC_ASSERT(_cisaObjInfos);
    _numCisaObjInfos = numCisaObjs;

    for (int i = 0; i < numCisaObjs + 1; i++) {
        _cisaObjInfos[i].index = i;
    }

    return 0;
}

int
CISALinker::ReadCisaMemObj(const CisaObj& cisaObj, CisaObjInfo& cisaObjInfo)
{
    unsigned cisaBytePos = 0;

    if (ExtractCisaMemObjHdr(cisaObjInfo.hdr, cisaBytePos, cisaObj.buf)) {
        return 1;
    }

    ASSERT(cisaBytePos <= cisaObj.size, "Corrupted CISA object");
    cisaObjInfo.buf = cisaObj.buf;
    cisaObjInfo.size = cisaObj.size;

    return 0;
}


inline int
CISALinker::ExtractCisaMemObjHdr(
    CisaHeader& cisaHdr, unsigned& cisaBytePos, const void *cisaBuffer)
{
    TRY(::processCommonISAHeader(cisaHdr, cisaBytePos, cisaBuffer, &_mem));
    ASSERT(
        cisaHdr.major_version >= 3, "Linking is supported only for CISA 3.0+");

    return 0;
}

int
CISALinker::ReadCisaFileObj(const char *cisaFileName, CisaObjInfo& cisaObjInfo)
{
    FILE *cisaFile = fopen(cisaFileName, "rb");
    ASSERT(cisaFile != NULL, "Cannot open %s", cisaFileName);
    fseek(cisaFile, 0, SEEK_END);
    unsigned fileSize = ftell(cisaFile);
    char *cisaBuffer = (char *) _mem.alloc(fileSize);
    fseek(cisaFile, 0, SEEK_SET);
    ALLOC_ASSERT(cisaBuffer);
    ASSERT(
        fread(cisaBuffer, 1, fileSize, cisaFile) != 0,
        "Cannot read CISA object %s", cisaFileName);
    fclose(cisaFile);
    CisaObj cisaObj;
    cisaObj.buf = cisaBuffer;
    cisaObj.size = fileSize;

    return ReadCisaMemObj(cisaObj, cisaObjInfo);
}

int
CISALinker::WriteCisaMemObj(CisaObj& cisaObj)
{
    TRY(WriteCisaMemObj());
    cisaObj.size = _linkedCisaImageSize;
    cisaObj.buf = (char *) (*_allocator)(_linkedCisaImageSize);
    CisaObjInfo& linkedObjInfo = _cisaObjInfos[_numCisaObjInfos];
    memcpy((void *) cisaObj.buf, linkedObjInfo.buf, cisaObj.size);
    ALLOC_ASSERT(cisaObj.buf);

    return 0;
}

int
CISALinker::WriteCisaMemObj()
{
    CisaObjInfo&      linkedObjInfo = _cisaObjInfos[_numCisaObjInfos];
    CisaHeader&       linkedObjHdr  = linkedObjInfo.hdr;
    CompiledUnitInfo& knlUnitInfo   = linkedObjHdr.kernels[0];

    linkedObjInfo.size = _linkedCisaImageSize;
    linkedObjInfo.buf = (char *) _mem.alloc(_linkedCisaImageSize);
    ALLOC_ASSERT(linkedObjInfo.buf);

    BUF_APPEND_SCALAR(linkedObjHdr.magic_number);
    BUF_APPEND_SCALAR(linkedObjHdr.major_version);
    BUF_APPEND_SCALAR(linkedObjHdr.minor_version);
    BUF_APPEND_SCALAR(linkedObjHdr.num_kernels);

    for (int i = 0; i < linkedObjHdr.num_kernels; i++) {
        WriteCisaCompiledUnitInfo(linkedObjHdr.kernels[i], false);
    }

    BUF_APPEND_SCALAR(linkedObjHdr.num_filescope_variables);

    for (int i = 0; i < linkedObjHdr.num_filescope_variables; i++) {
        WriteCisaVarInfo(linkedObjHdr.filescope_variables[i]);
    }

    BUF_APPEND_SCALAR(linkedObjHdr.num_functions);
    for (int i = 0; i < linkedObjHdr.num_functions; i++) {
        WriteCisaCompiledUnitInfo(linkedObjHdr.functions[i], true);
    }

    for (int i = 0; i < linkedObjHdr.num_kernels; i++) {
        WriteCisaCompiledUnitData(linkedObjHdr.kernels[i]);
    }

    for (int i = 0; i < linkedObjHdr.num_functions; i++) {
        WriteCisaCompiledUnitData(linkedObjHdr.functions[i]);
    }

    INTERNAL_ASSERT(linkedObjInfo.size == _linkedCisaImageSize);

    return 0;
}

int
CISALinker::WriteCisaCompiledUnitInfo(CompiledUnitInfo& unit, bool isFunction)
{
    if( isFunction == true )
    {
        BUF_APPEND_SCALAR(unit.linkage);
    }

    BUF_APPEND_SCALAR(unit.name_len);
    BUF_APPEND_VECTOR(unit.name, unit.name_len);
    BUF_APPEND_SCALAR(unit.offset);
    BUF_APPEND_SCALAR(unit.size);

    if( isFunction == false )
    {
        BUF_APPEND_SCALAR(unit.input_offset);
    }

    WriteRelocSymTab(unit.variable_reloc_symtab);
    WriteRelocSymTab(unit.function_reloc_symtab);

    if( isFunction == false )
    {
        unit.num_gen_binaries = 0;
        BUF_APPEND_SCALAR(unit.num_gen_binaries);
    }

    return 0;
}

int
CISALinker::WriteCisaVarInfo(CompiledVarInfo& var)
{
    BUF_APPEND_SCALAR(var.linkage);
    BUF_APPEND_SCALAR(var.name_len);
    BUF_APPEND_VECTOR(var.name, var.name_len);
    BUF_APPEND_SCALAR(var.bit_properties);
    BUF_APPEND_SCALAR(var.num_elements);
    BUF_APPEND_SCALAR(var.attribute_count);

    for (int i = 0; i < var.attribute_count; i++) {
        BUF_APPEND_SCALAR(var.attributes[i].nameIndex);
        BUF_APPEND_SCALAR(var.attributes[i].size);
        BUF_APPEND_VECTOR(
            var.attributes[i].value.stringVal, var.attributes[i].size);
    }

    return 0;
}

int
CISALinker::WriteRelocSymTab(RelocTab& symTab)
{
    BUF_APPEND_SCALAR(symTab.num_syms);

    for (int i = 0; i < symTab.num_syms; i++) {
        BUF_APPEND_SCALAR(symTab.reloc_syms[i].symbolic_index);
        BUF_APPEND_SCALAR(symTab.reloc_syms[i].resolved_index);
    }

    return 0;
}

int
CISALinker::WriteCisaCompiledUnitData(CompiledUnitInfo& unitInfo)
{
    if (unitInfo.size) {
        BUF_APPEND_VECTOR(unitInfo.cisa_binary_buffer, unitInfo.size);
    }

    return 0;
}

int
CISALinker::WriteCisaFileObj(const char *cisaFileName)
{
    WriteCisaMemObj();
    FILE *cisaFile = fopen(cisaFileName, "wb");
    ASSERT(cisaFile != NULL, "Cannot open %s for write", cisaFileName);
    CisaObjInfo& cisaObjInfo = _cisaObjInfos[_numCisaObjInfos];
    ASSERT(
        fwrite(cisaObjInfo.buf, 1, cisaObjInfo.size, cisaFile) ==
        cisaObjInfo.size,
        "Error in writing linked CISA object");
    fclose(cisaFile);

    return 0;
}
