/*
 * Copyright (C) 2019 Intel Corporation. All rights reserved.
 * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 */

#include "aot_reloc.h"

#define R_XTENSA_32        1   /* Direct 32 bit */
#define R_XTENSA_SLOT0_OP  20  /* PC relative */

/* for soft-float */
void __floatsidf();
void __divdf3();
void __ltdf2();

/* for mul32 */
void __mulsi3();
void __muldi3();

void __modsi3();

void __divdi3();

static SymbolMap target_sym_map[] = {
    REG_COMMON_SYMBOLS

    /* API's for soft-float */
    /* TODO: only register these symbols when Floating-Point Coprocessor
     * Option is not enabled */
    REG_SYM(__floatsidf),
    REG_SYM(__divdf3),
    REG_SYM(__ltdf2),

    /* API's for 32-bit integer multiply */
    /* TODO: only register these symbols when 32-bit Integer Multiply Option
     * is not enabled */
    REG_SYM(__mulsi3),
    REG_SYM(__muldi3),

    REG_SYM(__modsi3),

    REG_SYM(__divdi3),
};

static void
set_error_buf(char *error_buf, uint32 error_buf_size, const char *string)
{
    if (error_buf != NULL)
        snprintf(error_buf, error_buf_size, "%s", string);
}

SymbolMap *
get_target_symbol_map(uint32 *sym_num)
{
    *sym_num = sizeof(target_sym_map) / sizeof(SymbolMap);
    return target_sym_map;
}

void
get_current_target(char *target_buf, uint32 target_buf_size)
{
    snprintf(target_buf, target_buf_size, "xtensa");
}

static uint32
get_plt_item_size()
{
    return 0;
}

void
init_plt_table(uint8 *plt)
{
    (void)plt;
}

uint32
get_plt_table_size()
{
    return get_plt_item_size() * (sizeof(target_sym_map) / sizeof(SymbolMap));
}

static bool
check_reloc_offset(uint32 target_section_size,
                   uint64 reloc_offset, uint32 reloc_data_size,
                   char *error_buf, uint32 error_buf_size)
{
    if (!(reloc_offset < (uint64)target_section_size
          && reloc_offset + reloc_data_size <= (uint64)target_section_size)) {
        set_error_buf(error_buf, error_buf_size,
                      "AOT module load failed: invalid relocation offset.");
        return false;
    }
    return true;
}

/*
 * CPU like esp32 can read and write data through the instruction bus, but only
 * in a word aligned manner; non-word-aligned access will cause a CPU exception.
 * This function uses a world aligned manner to write 16bit value to instruction
 * addreess.
 */
static void
put_imm16_to_addr(int16 imm16, int16 *addr)
{
    int8 bytes[8];
    int32 *addr_aligned1, *addr_aligned2;

    addr_aligned1 = (int32*)((intptr_t)addr & ~3);

    if ((intptr_t)addr % 4 != 3) {
        *(int32*)bytes = *addr_aligned1;
        *(int16*)(bytes + ((intptr_t)addr % 4)) = imm16;
        memcpy(addr_aligned1, bytes, 4);
    }
    else {
        addr_aligned2 = (int32*)(((intptr_t)addr + 3) & ~3);
        *(int32*)bytes = *addr_aligned1;
        *(int32*)(bytes + 4) = *addr_aligned2;
        *(int16*)(bytes + 3) = imm16;
        memcpy(addr_aligned1, bytes, 8);
    }
}

static union {
    int a;
    char b;
} __ue = { .a = 1 };

#define is_little_endian() (__ue.b == 1)

typedef union {
    struct l32r_le {
        int8 other;
        int16 imm16;
    } __packed l;

    struct l32r_be {
        int16 imm16;
        int8 other;
    } __packed b;
} l32r_insn_t;

bool
apply_relocation(AOTModule *module,
                 uint8 *target_section_addr, uint32 target_section_size,
                 uint64 reloc_offset, uint64 reloc_addend,
                 uint32 reloc_type, void *symbol_addr, int32 symbol_index,
                 char *error_buf, uint32 error_buf_size)
{
    switch (reloc_type) {
        case R_XTENSA_32:
        {
            uint8 *insn_addr = target_section_addr + reloc_offset;
            int32 initial_addend;
            /* (S + A) */
            if ((intptr_t)insn_addr & 3) {
                set_error_buf(error_buf, error_buf_size,
                              "AOT module load failed: "
                              "instruction address unaligned.");
                return false;
            }
            CHECK_RELOC_OFFSET(4);
            initial_addend = *(int32*)insn_addr;
            *(uint8**)insn_addr
                = (uint8*)symbol_addr + initial_addend + reloc_addend;
            break;
        }

        case R_XTENSA_SLOT0_OP:
        {
            uint8 *insn_addr = target_section_addr + reloc_offset;
            /* Currently only l32r instruction generates R_XTENSA_SLOT0_OP relocation */
            l32r_insn_t *l32r_insn = (l32r_insn_t *)insn_addr;
            uint8 *reloc_addr;
            int32 relative_offset/*, initial_addend */;
            int16 imm16;

            CHECK_RELOC_OFFSET(3); /* size of l32r instruction */

            /*
            imm16 = is_little_endian() ?
                    l32r_insn->l.imm16 : l32r_insn->b.imm16;
            initial_addend = (int32)imm16 << 2;
            */

            reloc_addr = (uint8*)symbol_addr + reloc_addend;

            if ((intptr_t)reloc_addr & 3) {
                set_error_buf(error_buf, error_buf_size,
                              "AOT module load failed: "
                              "relocation address unaligned.");
                return false;
            }

            relative_offset = (int32)
                              ((intptr_t)reloc_addr -
                               (((intptr_t)insn_addr + 3) & ~(intptr_t)3));
            /* relative_offset += initial_addend; */

            /* check relative offset boundary */
            if (relative_offset < -256 * BH_KB || relative_offset > -4) {
                set_error_buf(error_buf, error_buf_size,
                              "AOT module load failed: "
                              "target address out of range.");
                return false;
            }

            imm16 = (int16)(relative_offset >> 2);

            /* write back the imm16 to the l32r instruction */
            if (is_little_endian())
                put_imm16_to_addr(imm16, &l32r_insn->l.imm16);
            else
                put_imm16_to_addr(imm16, &l32r_insn->b.imm16);

            break;
        }

        default:
            if (error_buf != NULL)
                snprintf(error_buf, error_buf_size,
                         "Load relocation section failed: "
                         "invalid relocation type %d.",
                         reloc_type);
            return false;
    }

    return true;
}

