#include <stdio.h>
#include <stdlib.h>

#ifdef USE_NLLIBC
#include <nllibc.h>
#endif

#include "config.h"
#include "lib.h"
#include "type.h"
#include "asm_code.h"
#include "asm_code_i386.h"
#include "asm_code_amd64.h"
#include "asm_code_arm.h"
#include "asm_code_mips.h"
#include "asm_code_powerpc.h"
#include "asm_code_aarch64.h"
#include "asm_code_thumb.h"
#include "asm_code_thumb2.h"
#include "asm_code_mips16.h"
#include "asm_code_osecpu.h"
#include "asm_code_riscv32.h"
#include "asm_code_rx.h"

#ifdef USE_BUILTIN
#ifndef USE_BUILTIN_EXTENSION
#define USE_BUILTIN_EXTENSION
#endif
#ifndef USE_BUILTIN_INV
#define USE_BUILTIN_INV
#endif
#ifndef USE_BUILTIN_MINUS
#define USE_BUILTIN_MINUS
#endif
#ifndef USE_BUILTIN_OP1
#define USE_BUILTIN_OP1
#endif
#ifndef USE_BUILTIN_XOR
#define USE_BUILTIN_XOR
#endif
#ifndef USE_BUILTIN_MUL
#define USE_BUILTIN_MUL
#endif
#ifndef USE_BUILTIN_DIV
#define USE_BUILTIN_DIV
#endif
#ifndef USE_BUILTIN_MOD
#define USE_BUILTIN_MOD
#endif
#ifndef USE_BUILTIN_LSHIFT
#define USE_BUILTIN_LSHIFT
#endif
#ifndef USE_BUILTIN_RSHIFT
#define USE_BUILTIN_RSHIFT
#endif
#ifndef USE_BUILTIN_OP2
#define USE_BUILTIN_OP2
#endif
#ifndef USE_BUILTIN_BRANCH
#define USE_BUILTIN_BRANCH
#endif
#ifndef USE_BUILTIN_CMP
#define USE_BUILTIN_CMP
#endif
#ifndef USE_BUILTIN_CALL
#define USE_BUILTIN_CALL
#endif
#ifndef USE_BUILTIN_R1
#define USE_BUILTIN_R1
#endif
#endif

static struct asm_code_callback *callbacks[] = {
  NULL,
  &asm_code_callback_i386,
  &asm_code_callback_amd64,
  &asm_code_callback_arm,
  &asm_code_callback_mips,
  &asm_code_callback_powerpc,
  &asm_code_callback_aarch64,
  &asm_code_callback_thumb,
  &asm_code_callback_thumb2,
  &asm_code_callback_mips16,
  &asm_code_callback_osecpu,
  &asm_code_callback_riscv32,
  &asm_code_callback_rx,
  NULL
};

static asm_code_arch_t asm_code_arch = ASM_CODE_ARCH_DEFAULT;

int asm_init_arch(asm_code_arch_t arch)
{
  asm_code_arch = arch;
  return 0;
}

char *asm_info_type_mark(void)
{
  if (callbacks[asm_code_arch]->type_mark == NULL)
    return "@";
  return callbacks[asm_code_arch]->type_mark;
}

int asm_info_word_size(void)
{
  if (callbacks[asm_code_arch]->word_size < 0)
    return 4;
  return callbacks[asm_code_arch]->word_size;
}

int asm_info_pointer_size(void)
{
  if (callbacks[asm_code_arch]->pointer_size < 0)
    return 4;
  return callbacks[asm_code_arch]->pointer_size;
}

int asm_info_funccall_args_reg_number(void)
{
  if (callbacks[asm_code_arch]->funccall_args_reg_number < 0)
    return -1;
  return callbacks[asm_code_arch]->funccall_args_reg_number;
}

int asm_info_funccall_args_stack_number(void)
{
  if (callbacks[asm_code_arch]->funccall_args_stack_number < 0)
    return -1;
  return callbacks[asm_code_arch]->funccall_args_stack_number;
}

int asm_info_tmp_reg_number(void)
{
  if (callbacks[asm_code_arch]->tmp_reg_number < 0)
    return -1;
  return callbacks[asm_code_arch]->tmp_reg_number;
}

int asm_info_tmp_stack_number(void)
{
  if (callbacks[asm_code_arch]->tmp_stack_number < 0)
    return -1;
  return callbacks[asm_code_arch]->tmp_stack_number;
}

int asm_info_function_register_number(void)
{
  if (callbacks[asm_code_arch]->function_register_number < 0)
    return -1;
  return callbacks[asm_code_arch]->function_register_number;
}

int asm_info_function_saveparam_number(void)
{
  if (callbacks[asm_code_arch]->function_saveparam_number < 0)
    return -1;
  return callbacks[asm_code_arch]->function_saveparam_number;
}

int asm_info_stack_align_size(void)
{
  if (callbacks[asm_code_arch]->stack_align_size < 0)
    return -1;
  return callbacks[asm_code_arch]->stack_align_size;
}

int asm_info_stack_correct_size(void)
{
  if (callbacks[asm_code_arch]->stack_correct_size < 0)
    return -1;
  return callbacks[asm_code_arch]->stack_correct_size;
}

void asm_code_get_value(FILE *out, long value)
{
  if (callbacks[asm_code_arch]->get_value == NULL)
    ERREXIT(1);
  callbacks[asm_code_arch]->get_value(out, value);
}

void asm_code_get_value_r1(FILE *out, long value)
{
  if (callbacks[asm_code_arch]->get_value_r1 == NULL)
    ERREXIT(1);
  callbacks[asm_code_arch]->get_value_r1(out, value);
}

void asm_code_get_address_stack(FILE *out, int offset)
{
  if (callbacks[asm_code_arch]->get_address_stack == NULL)
    ERREXIT(1);
  callbacks[asm_code_arch]->get_address_stack(out, offset);
}

int asm_code_get_address_stack_r1(FILE *out, int offset)
{
#ifndef USE_BUILTIN_R1
  if (callbacks[asm_code_arch]->get_address_stack_r1 == NULL)
    return -1;
  return callbacks[asm_code_arch]->get_address_stack_r1(out, offset);
#else
  return -1;
#endif
}

void asm_code_get_address(FILE *out, char *label)
{
  if (callbacks[asm_code_arch]->get_address == NULL)
    ERREXIT(1);
  callbacks[asm_code_arch]->get_address(out, label);
}

void asm_code_add_address(FILE *out, int offset)
{
  if (callbacks[asm_code_arch]->add_address == NULL)
    ERREXIT(1);
  callbacks[asm_code_arch]->add_address(out, offset);
}

void asm_code_get_r1(FILE *out)
{
  if (callbacks[asm_code_arch]->get_r1 == NULL)
    ERREXIT(1);
  callbacks[asm_code_arch]->get_r1(out);
}

void asm_code_set_r1(FILE *out)
{
  if (callbacks[asm_code_arch]->set_r1 == NULL)
    ERREXIT(1);
  callbacks[asm_code_arch]->set_r1(out);
}

void asm_code_memory_load(FILE *out, int offset, model_t model)
{
  if (callbacks[asm_code_arch]->memory_load == NULL)
    ERREXIT(1);
  callbacks[asm_code_arch]->memory_load(out, offset, model);
}

void asm_code_memory_store(FILE *out, int offset, model_t model)
{
  if (callbacks[asm_code_arch]->memory_store == NULL)
    ERREXIT(1);
  callbacks[asm_code_arch]->memory_store(out, offset, model);
}

void asm_code_stack_load(FILE *out, int offset, model_t model)
{
  if (callbacks[asm_code_arch]->stack_load == NULL)
    ERREXIT(1);
  callbacks[asm_code_arch]->stack_load(out, offset, model);
}

void asm_code_stack_store(FILE *out, int offset, model_t model)
{
  if (callbacks[asm_code_arch]->stack_store == NULL)
    ERREXIT(1);
  callbacks[asm_code_arch]->stack_store(out, offset, model);
}

void asm_code_stack_load_r1(FILE *out, int offset, model_t model)
{
  if (callbacks[asm_code_arch]->stack_load_r1 == NULL)
    ERREXIT(1);
  callbacks[asm_code_arch]->stack_load_r1(out, offset, model);
}

void asm_code_stack_store_r1(FILE *out, int offset, model_t model)
{
  if (callbacks[asm_code_arch]->stack_store_r1 == NULL)
    ERREXIT(1);
  callbacks[asm_code_arch]->stack_store_r1(out, offset, model);
}

void asm_code_stack_expand(FILE *out, int size)
{
  if (callbacks[asm_code_arch]->stack_expand == NULL)
    ERREXIT(1);
  callbacks[asm_code_arch]->stack_expand(out, size);
}

void asm_code_stack_reduce(FILE *out, int size)
{
  if (callbacks[asm_code_arch]->stack_reduce == NULL)
    ERREXIT(1);
  callbacks[asm_code_arch]->stack_reduce(out, size);
}

void asm_code_funccall_reg_load(FILE *out, int number)
{
  if (callbacks[asm_code_arch]->funccall_reg_load == NULL)
    ERREXIT(1);
  callbacks[asm_code_arch]->funccall_reg_load(out, number);
}

void asm_code_funccall_reg_store(FILE *out, int number)
{
  if (callbacks[asm_code_arch]->funccall_reg_store == NULL)
    ERREXIT(1);
  callbacks[asm_code_arch]->funccall_reg_store(out, number);
}

void asm_code_tmp_reg_load(FILE *out, int number)
{
  if (callbacks[asm_code_arch]->tmp_reg_load == NULL)
    ERREXIT(1);
  callbacks[asm_code_arch]->tmp_reg_load(out, number);
}

void asm_code_tmp_reg_save(FILE *out, int number)
{
  if (callbacks[asm_code_arch]->tmp_reg_save == NULL)
    ERREXIT(1);
  callbacks[asm_code_arch]->tmp_reg_save(out, number);
}

void asm_code_tmp_reg_load_r1(FILE *out, int number)
{
  if (callbacks[asm_code_arch]->tmp_reg_load_r1 == NULL)
    ERREXIT(1);
  callbacks[asm_code_arch]->tmp_reg_load_r1(out, number);
}

void asm_code_tmp_reg_save_r1(FILE *out, int number)
{
  if (callbacks[asm_code_arch]->tmp_reg_save_r1 == NULL)
    ERREXIT(1);
  callbacks[asm_code_arch]->tmp_reg_save_r1(out, number);
}

int asm_code_sign_extension_char(FILE *out)
{
#ifndef USE_BUILTIN_EXTENSION
  if (callbacks[asm_code_arch]->sign_extension_char == NULL)
    return -1;
  return callbacks[asm_code_arch]->sign_extension_char(out);
#else
  return -1;
#endif
}

int asm_code_sign_extension_uchar(FILE *out)
{
#ifndef USE_BUILTIN_EXTENSION
  if (callbacks[asm_code_arch]->sign_extension_uchar == NULL)
    return -1;
  return callbacks[asm_code_arch]->sign_extension_uchar(out);
#else
  return -1;
#endif
}

int asm_code_sign_extension_short(FILE *out)
{
#ifndef USE_BUILTIN_EXTENSION
  if (callbacks[asm_code_arch]->sign_extension_short == NULL)
    return -1;
  return callbacks[asm_code_arch]->sign_extension_short(out);
#else
  return -1;
#endif
}

int asm_code_sign_extension_ushort(FILE *out)
{
#ifndef USE_BUILTIN_EXTENSION
  if (callbacks[asm_code_arch]->sign_extension_ushort == NULL)
    return -1;
  return callbacks[asm_code_arch]->sign_extension_ushort(out);
#else
  return -1;
#endif
}

int asm_code_sign_extension_int(FILE *out)
{
#ifndef USE_BUILTIN_EXTENSION
  if (callbacks[asm_code_arch]->sign_extension_int == NULL)
    return -1;
  return callbacks[asm_code_arch]->sign_extension_int(out);
#else
  return -1;
#endif
}

int asm_code_sign_extension_uint(FILE *out)
{
#ifndef USE_BUILTIN_EXTENSION
  if (callbacks[asm_code_arch]->sign_extension_uint == NULL)
    return -1;
  return callbacks[asm_code_arch]->sign_extension_uint(out);
#else
  return -1;
#endif
}

int asm_code_calc_inv(FILE *out, model_t model, model_t model_arg0)
{
#ifndef USE_BUILTIN_INV
  if (callbacks[asm_code_arch]->calc_inv == NULL)
    return -1;
  return callbacks[asm_code_arch]->calc_inv(out, model, model_arg0);
#else
  return -1;
#endif
}

int asm_code_calc_minus(FILE *out, model_t model, model_t model_arg0)
{
#ifndef USE_BUILTIN_MINUS
  if (callbacks[asm_code_arch]->calc_minus == NULL)
    return -1;
  return callbacks[asm_code_arch]->calc_minus(out, model, model_arg0);
#else
  return -1;
#endif
}

int asm_code_calc_op1(FILE *out, c_type_t type, model_t model, model_t model_arg0)
{
#ifndef USE_BUILTIN_OP1
  if (callbacks[asm_code_arch]->calc_op1 == NULL)
    return -1;
  return callbacks[asm_code_arch]->calc_op1(out, type, model, model_arg0);
#else
  return -1;
#endif
}

void asm_code_calc_add(FILE *out, model_t model, model_t model_arg0, model_t model_arg1)
{
  if (callbacks[asm_code_arch]->calc_add == NULL)
    ERREXIT(1);
  callbacks[asm_code_arch]->calc_add(out, model, model_arg0, model_arg1);
}

void asm_code_calc_sub(FILE *out, model_t model, model_t model_arg0, model_t model_arg1)
{
  if (callbacks[asm_code_arch]->calc_sub == NULL)
    ERREXIT(1);
  callbacks[asm_code_arch]->calc_sub(out, model, model_arg0, model_arg1);
}

void asm_code_calc_and(FILE *out, model_t model, model_t model_arg0, model_t model_arg1)
{
  if (callbacks[asm_code_arch]->calc_and == NULL)
    ERREXIT(1);
  callbacks[asm_code_arch]->calc_and(out, model, model_arg0, model_arg1);
}

void asm_code_calc_or(FILE *out, model_t model, model_t model_arg0, model_t model_arg1)
{
  if (callbacks[asm_code_arch]->calc_or == NULL)
    ERREXIT(1);
  callbacks[asm_code_arch]->calc_or(out, model, model_arg0, model_arg1);
}

int asm_code_calc_xor(FILE *out, model_t model, model_t model_arg0, model_t model_arg1)
{
#ifndef USE_BUILTIN_XOR
  if (callbacks[asm_code_arch]->calc_xor == NULL)
    return -1;
  return callbacks[asm_code_arch]->calc_xor(out, model, model_arg0, model_arg1);
#else
  return -1;
#endif
}

int asm_code_calc_mul(FILE *out, model_t model, model_t model_arg0, model_t model_arg1)
{
#ifndef USE_BUILTIN_MUL
  if (callbacks[asm_code_arch]->calc_mul == NULL)
    return -1;
  return callbacks[asm_code_arch]->calc_mul(out, model, model_arg0, model_arg1);
#else
  return -1;
#endif
}

int asm_code_calc_div(FILE *out, model_t model, model_t model_arg0, model_t model_arg1)
{
#ifndef USE_BUILTIN_DIV
  if (callbacks[asm_code_arch]->calc_div == NULL)
    return -1;
  return callbacks[asm_code_arch]->calc_div(out, model, model_arg0, model_arg1);
#else
  return -1;
#endif
}

int asm_code_calc_mod(FILE *out, model_t model, model_t model_arg0, model_t model_arg1)
{
#ifndef USE_BUILTIN_MOD
  if (callbacks[asm_code_arch]->calc_mod == NULL)
    return -1;
  return callbacks[asm_code_arch]->calc_mod(out, model, model_arg0, model_arg1);
#else
  return -1;
#endif
}

int asm_code_calc_llshift(FILE *out, model_t model, model_t model_arg0, model_t model_arg1)
{
#ifndef USE_BUILTIN_LSHIFT
  if (callbacks[asm_code_arch]->calc_llshift == NULL)
    return -1;
  return callbacks[asm_code_arch]->calc_llshift(out, model, model_arg0, model_arg1);
#else
  return -1;
#endif
}

int asm_code_calc_rashift(FILE *out, model_t model, model_t model_arg0, model_t model_arg1)
{
#ifndef USE_BUILTIN_RSHIFT
  if (callbacks[asm_code_arch]->calc_rashift == NULL)
    return -1;
  return callbacks[asm_code_arch]->calc_rashift(out, model, model_arg0, model_arg1);
#else
  return -1;
#endif
}

int asm_code_calc_rlshift(FILE *out, model_t model, model_t model_arg0, model_t model_arg1)
{
#ifndef USE_BUILTIN_RSHIFT
  if (callbacks[asm_code_arch]->calc_rlshift == NULL)
    return -1;
  return callbacks[asm_code_arch]->calc_rlshift(out, model, model_arg0, model_arg1);
#else
  return -1;
#endif
}

int asm_code_calc_op2(FILE *out, c_type_t type, model_t model, model_t model_arg0, model_t model_arg1)
{
#ifndef USE_BUILTIN_OP2
  if (callbacks[asm_code_arch]->calc_op2 == NULL)
    return -1;
  return callbacks[asm_code_arch]->calc_op2(out, type, model, model_arg0, model_arg1);
#else
  return -1;
#endif
}

void asm_code_branch(FILE *out, char *label)
{
  if (callbacks[asm_code_arch]->branch == NULL)
    ERREXIT(1);
  callbacks[asm_code_arch]->branch(out, label);
}

int asm_code_branch_zero(FILE *out, char *label)
{
#if 1 /* Cannot use builtin (used other builtin) */
  if (callbacks[asm_code_arch]->branch_zero == NULL)
    return -1;
  return callbacks[asm_code_arch]->branch_zero(out, label);
#else
  return -1;
#endif
}

int asm_code_branch_nzero(FILE *out, char *label)
{
#ifndef USE_BUILTIN_BRANCH
  if (callbacks[asm_code_arch]->branch_nzero == NULL)
    return -1;
  return callbacks[asm_code_arch]->branch_nzero(out, label);
#else
  return -1;
#endif
}

int asm_code_branch_cmp_eq(FILE *out, char *label, model_t model)
{
#if 1 /* Cannot use builtin (used other builtin) */
  if (callbacks[asm_code_arch]->branch_cmp_eq == NULL)
    return -1;
  return callbacks[asm_code_arch]->branch_cmp_eq(out, label, model);
#else
  return -1;
#endif
}

int asm_code_branch_cmp_ne(FILE *out, char *label, model_t model)
{
#ifndef USE_BUILTIN_CMP
  if (callbacks[asm_code_arch]->branch_cmp_ne == NULL)
    return -1;
  return callbacks[asm_code_arch]->branch_cmp_ne(out, label, model);
#else
  return -1;
#endif
}

int asm_code_branch_cmp_lt(FILE *out, char *label, model_t model)
{
#if 1 /* Cannot use builtin (used other builtin) */
  if (callbacks[asm_code_arch]->branch_cmp_lt == NULL)
    return -1;
  return callbacks[asm_code_arch]->branch_cmp_lt(out, label, model);
#else
  return -1;
#endif
}

int asm_code_branch_cmp_gt(FILE *out, char *label, model_t model)
{
#ifndef USE_BUILTIN_CMP
  if (callbacks[asm_code_arch]->branch_cmp_gt == NULL)
    return -1;
  return callbacks[asm_code_arch]->branch_cmp_gt(out, label, model);
#else
  return -1;
#endif
}

int asm_code_branch_cmp_le(FILE *out, char *label, model_t model)
{
#ifndef USE_BUILTIN_CMP
  if (callbacks[asm_code_arch]->branch_cmp_le == NULL)
    return -1;
  return callbacks[asm_code_arch]->branch_cmp_le(out, label, model);
#else
  return -1;
#endif
}

int asm_code_branch_cmp_ge(FILE *out, char *label, model_t model)
{
#ifndef USE_BUILTIN_CMP
  if (callbacks[asm_code_arch]->branch_cmp_ge == NULL)
    return -1;
  return callbacks[asm_code_arch]->branch_cmp_ge(out, label, model);
#else
  return -1;
#endif
}

int asm_code_branch_cmp_ult(FILE *out, char *label, model_t model)
{
#if 1 /* Cannot use builtin (used other builtin) */
  if (callbacks[asm_code_arch]->branch_cmp_ult == NULL)
    return -1;
  return callbacks[asm_code_arch]->branch_cmp_ult(out, label, model);
#else
  return -1;
#endif
}

int asm_code_branch_cmp_ugt(FILE *out, char *label, model_t model)
{
#ifndef USE_BUILTIN_CMP
  if (callbacks[asm_code_arch]->branch_cmp_ugt == NULL)
    return -1;
  return callbacks[asm_code_arch]->branch_cmp_ugt(out, label, model);
#else
  return -1;
#endif
}

int asm_code_branch_cmp_ule(FILE *out, char *label, model_t model)
{
#ifndef USE_BUILTIN_CMP
  if (callbacks[asm_code_arch]->branch_cmp_ule == NULL)
    return -1;
  return callbacks[asm_code_arch]->branch_cmp_ule(out, label, model);
#else
  return -1;
#endif
}

int asm_code_branch_cmp_uge(FILE *out, char *label, model_t model)
{
#ifndef USE_BUILTIN_CMP
  if (callbacks[asm_code_arch]->branch_cmp_uge == NULL)
    return -1;
  return callbacks[asm_code_arch]->branch_cmp_uge(out, label, model);
#else
  return -1;
#endif
}

int asm_code_branch_cmp(FILE *out, char *label, c_type_t type, model_t model)
{
#ifndef USE_BUILTIN_CMP
  if (callbacks[asm_code_arch]->branch_cmp == NULL)
    return -1;
  return callbacks[asm_code_arch]->branch_cmp(out, label, type, model);
#else
  return -1;
#endif
}

int asm_code_function_call(FILE *out, char *label)
{
#ifndef USE_BUILTIN_CALL
  if (callbacks[asm_code_arch]->function_call == NULL)
    return -1;
  return callbacks[asm_code_arch]->function_call(out, label);
#else
  return -1;
#endif
}

int asm_code_function_call_set(FILE *out)
{
#if 1 /* Cannot use builtin (used other builtin) */
  if (callbacks[asm_code_arch]->function_call_set == NULL)
    return -1;
  return callbacks[asm_code_arch]->function_call_set(out);
#else
  return -1;
#endif
}

int asm_code_function_call_pointer(FILE *out)
{
#if 1 /* Cannot use builtin (used other builtin) */
  if (callbacks[asm_code_arch]->function_call_pointer == NULL)
    return -1;
  return callbacks[asm_code_arch]->function_call_pointer(out);
#else
  return -1;
#endif
}

void asm_code_function_start(FILE *out)
{
  if (callbacks[asm_code_arch]->function_start == NULL)
    ERREXIT(1);
  callbacks[asm_code_arch]->function_start(out);
}

void asm_code_function_end(FILE *out)
{
  if (callbacks[asm_code_arch]->function_end == NULL)
    ERREXIT(1);
  callbacks[asm_code_arch]->function_end(out);
}

void asm_code_function_register_save(FILE *out)
{
  if (callbacks[asm_code_arch]->function_register_save == NULL)
    ERREXIT(1);
  callbacks[asm_code_arch]->function_register_save(out);
}

void asm_code_function_register_load(FILE *out)
{
  if (callbacks[asm_code_arch]->function_register_load == NULL)
    ERREXIT(1);
  callbacks[asm_code_arch]->function_register_load(out);
}
