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

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

#include "config.h"
#include "lib.h"
#include "type.h"
#include "asm_symbol.h"
#include "asm_code.h"
#include "asm_code_amd64.h"

static char *r0_by_size(model_t model)
{
  if (model != NULL) {
    switch (model_get_size(model)) {
    case -1: ERREXIT(1);
    case  1: return "al";
    case  2: return "ax";
    case  4: return "eax";
    default: break;
    }
  }
  return "rax";
}

static char *r1_by_size(model_t model)
{
  if (model != NULL) {
    switch (model_get_size(model)) {
    case -1: ERREXIT(1);
    case  1: return "dl";
    case  2: return "dx";
    case  4: return "edx";
    default: break;
    }
  }
  return "rdx";
}

static void get_value(FILE *out, long value)
{
  ASM_CODE_OUT(out, "\tmov\t$0x%lx, %%rax\n", value);
}

static void get_value_r1(FILE *out, long value)
{
  ASM_CODE_OUT(out, "\tmov\t$0x%lx, %%rdx\n", value);
}

static void get_address_stack(FILE *out, int offset)
{
  ASM_CODE_OUT(out, "\tlea\t0x%x(%%rsp), %%rax\n", offset);
}

static int get_address_stack_r1(FILE *out, int offset)
{
  ASM_CODE_OUT(out, "\tlea\t0x%x(%%rsp), %%rdx\n", offset);
  return 0;
}

static void get_address(FILE *out, char *label)
{
  ASM_CODE_OUT(out, "\tmov\t$%s, %%rax\n", label);
}

static void add_address(FILE *out, int offset)
{
  ASM_CODE_OUT(out, "\tlea\t0x%x(%%rax), %%rax\n", offset);
}

static void get_r1(FILE *out)
{
  ASM_CODE_OUT(out, "\tmov\t%%rdx, %%rax\n");
}

static void set_r1(FILE *out)
{
  ASM_CODE_OUT(out, "\tmov\t%%rax, %%rdx\n");
}

static void memory_load(FILE *out, int offset, model_t model)
{
  ASM_CODE_OUT(out, "\tmov\t0x%x(%%rdx), %%%s\n", offset, r0_by_size(model));
}

static void memory_store(FILE *out, int offset, model_t model)
{
  ASM_CODE_OUT(out, "\tmov\t%%%s, 0x%x(%%rdx)\n", r0_by_size(model), offset);
}

static void stack_load(FILE *out, int offset, model_t model)
{
  ASM_CODE_OUT(out, "\tmov\t0x%x(%%rsp), %%%s\n", offset, r0_by_size(model));
}

static void stack_store(FILE *out, int offset, model_t model)
{
  ASM_CODE_OUT(out, "\tmov\t%%%s, 0x%x(%%rsp)\n", r0_by_size(model), offset);
}

static void stack_load_r1(FILE *out, int offset, model_t model)
{
  ASM_CODE_OUT(out, "\tmov\t0x%x(%%rsp), %%%s\n", offset, r1_by_size(model));
}

static void stack_store_r1(FILE *out, int offset, model_t model)
{
  ASM_CODE_OUT(out, "\tmov\t%%%s, 0x%x(%%rsp)\n", r1_by_size(model), offset);
}

static void stack_expand(FILE *out, int size)
{
  ASM_CODE_OUT(out, "\tsub\t$0x%x, %%rsp\n", size);
}

static void stack_reduce(FILE *out, int size)
{
  ASM_CODE_OUT(out, "\tadd\t$0x%x, %%rsp\n", size);
}

static char *funccall_regs[] = {
  "rdi", "rsi", "rdx", "rcx", "r8", "r9"
};

static void funccall_reg_load(FILE *out, int number)
{
  ASM_CODE_OUT(out, "\tmov\t%%%s, %%rax\n", funccall_regs[number]);
}

static void funccall_reg_store(FILE *out, int number)
{
  ASM_CODE_OUT(out, "\tmov\t%%rax, %%%s\n", funccall_regs[number]);
}

static char *tmp_regs[] = {
  "rbx", "r12", "r13", "r14", "r15",
};

static void tmp_reg_load(FILE *out, int number)
{
  ASM_CODE_OUT(out, "\tmov\t%%%s, %%rax\n", tmp_regs[number]);
}

static void tmp_reg_save(FILE *out, int number)
{
  ASM_CODE_OUT(out, "\tmov\t%%rax, %%%s\n", tmp_regs[number]);
}

static void tmp_reg_load_r1(FILE *out, int number)
{
  ASM_CODE_OUT(out, "\tmov\t%%%s, %%rdx\n", tmp_regs[number]);
}

static void tmp_reg_save_r1(FILE *out, int number)
{
  ASM_CODE_OUT(out, "\tmov\t%%rdx, %%%s\n", tmp_regs[number]);
}

static int sign_extension_char(FILE *out)
{
  ASM_CODE_OUT(out, "\tmovsbq\t%%al, %%rax\n");
  return 0;
}

static int sign_extension_uchar(FILE *out)
{
  ASM_CODE_OUT(out, "\tmovzbl\t%%al, %%eax\n");
  return 0;
}

static int sign_extension_short(FILE *out)
{
  ASM_CODE_OUT(out, "\tmovswq\t%%ax, %%rax\n");
  return 0;
}

static int sign_extension_ushort(FILE *out)
{
  ASM_CODE_OUT(out, "\tmovzwl\t%%ax, %%eax\n");
  return 0;
}

static int sign_extension_int(FILE *out)
{
  ASM_CODE_OUT(out, "\tmovslq\t%%eax, %%rax\n");
  return 0;
}

static int sign_extension_uint(FILE *out)
{
  ASM_CODE_OUT(out, "\tmov\t%%eax, %%eax\n");
  return 0;
}

static int calc_inv(FILE *out, model_t model, model_t model_arg0)
{
  ASM_CODE_OUT(out, "\tnot\t%%rax\n");
  return 0;
}

static int calc_minus(FILE *out, model_t model, model_t model_arg0)
{
  ASM_CODE_OUT(out, "\tneg\t%%rax\n");
  return 0;
}

static int calc_op1(FILE *out, c_type_t type, model_t model, model_t model_arg0)
{
  return -1;
}

static void calc_add(FILE *out, model_t model, model_t model_arg0, model_t model_arg1)
{
  ASM_CODE_OUT(out, "\tadd\t%%rdx, %%rax\n");
}

static void calc_sub(FILE *out, model_t model, model_t model_arg0, model_t model_arg1)
{
  ASM_CODE_OUT(out, "\tsub\t%%rdx, %%rax\n");
}

static void calc_and(FILE *out, model_t model, model_t model_arg0, model_t model_arg1)
{
  ASM_CODE_OUT(out, "\tand\t%%rdx, %%rax\n");
}

static void calc_or(FILE *out, model_t model, model_t model_arg0, model_t model_arg1)
{
  ASM_CODE_OUT(out, "\tor\t%%rdx, %%rax\n");
}

static int calc_xor(FILE *out, model_t model, model_t model_arg0, model_t model_arg1)
{
  ASM_CODE_OUT(out, "\txor\t%%rdx, %%rax\n");
  return 0;
}

static int calc_mul(FILE *out, model_t model, model_t model_arg0, model_t model_arg1)
{
  ASM_CODE_OUT(out, "\timul\t%%%s\n", r1_by_size(model_arg0));
  return 0;
}

static int calc_div(FILE *out, model_t model, model_t model_arg0, model_t model_arg1)
{
  ASM_CODE_OUT(out, "\tmov\t%%rdx, %%rcx\n");
  if (model_arg0->flags & MODEL_FLAG_UNSIGNED) {
    ASM_CODE_OUT(out, "\tmov\t$0, %%rdx\n");
  } else {
    ASM_CODE_OUT(out, "\tcqto\n");
  }
  if (model->flags & MODEL_FLAG_UNSIGNED) {
    switch (model_get_size(model_arg1)) {
    case -1: ERREXIT(1);
    case  1: ASM_CODE_OUT(out, "\tdiv\t%%cl\n"); break;
    case  2: ASM_CODE_OUT(out, "\tdiv\t%%cx\n"); break;
    case  4: ASM_CODE_OUT(out, "\tdiv\t%%ecx\n"); break;
    default: ASM_CODE_OUT(out, "\tdiv\t%%rcx\n"); break;
    }
  } else {
    ASM_CODE_OUT(out, "\tidiv\t%%rcx\n");
  }
  return 0;
}

static int calc_mod(FILE *out, model_t model, model_t model_arg0, model_t model_arg1)
{
  ASM_CODE_OUT(out, "\tmov\t%%rdx, %%rcx\n");
  if (model_arg0->flags & MODEL_FLAG_UNSIGNED) {
    ASM_CODE_OUT(out, "\tmov\t$0, %%rdx\n");
  } else {
    ASM_CODE_OUT(out, "\tcqto\n");
  }
  if (model->flags & MODEL_FLAG_UNSIGNED) {
    ASM_CODE_OUT(out, "\tdiv\t%%rcx\n");
  } else {
    ASM_CODE_OUT(out, "\tidiv\t%%rcx\n");
  }
  ASM_CODE_OUT(out, "\tmov\t%%rdx, %%rax\n");
  return 0;
}

static int calc_llshift(FILE *out, model_t model, model_t model_arg0, model_t model_arg1)
{
  ASM_CODE_OUT(out, "\tmov\t%%rdx, %%rcx\n");
  ASM_CODE_OUT(out, "\tshl\t%%cl, %%%s\n", r0_by_size(model_arg0));
  return 0;
}

static int calc_rashift(FILE *out, model_t model, model_t model_arg0, model_t model_arg1)
{
  ASM_CODE_OUT(out, "\tmov\t%%rdx, %%rcx\n");
  ASM_CODE_OUT(out, "\tsar\t%%cl, %%%s\n", r0_by_size(model_arg0));
  return 0;
}

static int calc_rlshift(FILE *out, model_t model, model_t model_arg0, model_t model_arg1)
{
  ASM_CODE_OUT(out, "\tmov\t%%rdx, %%rcx\n");
  ASM_CODE_OUT(out, "\tshr\t%%cl, %%%s\n", r0_by_size(model_arg0));
  return 0;
}

static int calc_op2(FILE *out, c_type_t type, model_t model, model_t model_arg0, model_t model_arg1)
{
  return -1;
}

static void branch(FILE *out, char *label)
{
  ASM_CODE_OUT(out, "\tjmp\t%s\n", label);
}

static int branch_zero(FILE *out, char *label)
{
  ASM_CODE_OUT(out, "\tcmp\t$0, %%rax\n");
  ASM_CODE_OUT(out, "\tje\t%s\n", label);
  return 0;
}

static int branch_nzero(FILE *out, char *label)
{
  ASM_CODE_OUT(out, "\tcmp\t$0, %%rax\n");
  ASM_CODE_OUT(out, "\tjne\t%s\n", label);
  return 0;
}

static int branch_cmp_eq(FILE *out, char *label, model_t model)
{
  ASM_CODE_OUT(out, "\tcmp\t%%rdx, %%rax\n");
  ASM_CODE_OUT(out, "\tje\t%s\n", label);
  return 0;
}

static int branch_cmp_ne(FILE *out, char *label, model_t model)
{
  ASM_CODE_OUT(out, "\tcmp\t%%rdx, %%rax\n");
  ASM_CODE_OUT(out, "\tjne\t%s\n", label);
  return 0;
}

static int branch_cmp_lt(FILE *out, char *label, model_t model)
{
  ASM_CODE_OUT(out, "\tcmp\t%%rdx, %%rax\n");
  ASM_CODE_OUT(out, "\tjl\t%s\n", label);
  return 0;
}

static int branch_cmp_gt(FILE *out, char *label, model_t model)
{
  ASM_CODE_OUT(out, "\tcmp\t%%rdx, %%rax\n");
  ASM_CODE_OUT(out, "\tjg\t%s\n", label);
  return 0;
}

static int branch_cmp_le(FILE *out, char *label, model_t model)
{
  ASM_CODE_OUT(out, "\tcmp\t%%rdx, %%rax\n");
  ASM_CODE_OUT(out, "\tjle\t%s\n", label);
  return 0;
}

static int branch_cmp_ge(FILE *out, char *label, model_t model)
{
  ASM_CODE_OUT(out, "\tcmp\t%%rdx, %%rax\n");
  ASM_CODE_OUT(out, "\tjge\t%s\n", label);
  return 0;
}

static int branch_cmp_ult(FILE *out, char *label, model_t model)
{
  ASM_CODE_OUT(out, "\tcmp\t%%rdx, %%rax\n");
  ASM_CODE_OUT(out, "\tjb\t%s\n", label);
  return 0;
}

static int branch_cmp_ugt(FILE *out, char *label, model_t model)
{
  ASM_CODE_OUT(out, "\tcmp\t%%rdx, %%rax\n");
  ASM_CODE_OUT(out, "\tja\t%s\n", label);
  return 0;
}

static int branch_cmp_ule(FILE *out, char *label, model_t model)
{
  ASM_CODE_OUT(out, "\tcmp\t%%rdx, %%rax\n");
  ASM_CODE_OUT(out, "\tjbe\t%s\n", label);
  return 0;
}

static int branch_cmp_uge(FILE *out, char *label, model_t model)
{
  ASM_CODE_OUT(out, "\tcmp\t%%rdx, %%rax\n");
  ASM_CODE_OUT(out, "\tjae\t%s\n", label);
  return 0;
}

static int branch_cmp(FILE *out, char *label, c_type_t type, model_t model)
{
  return -1;
}

static int function_call(FILE *out, char *label)
{
  ASM_CODE_OUT(out, "\tcallq\t%s\n", label);
  return 0;
}

static int function_call_set(FILE *out)
{
  /*
   * Standard register(R0) is not used as arguments of the function call,
   * so it can be used as the pointer of the function.
   */
  return -1;
}

static int function_call_pointer(FILE *out)
{
  ASM_CODE_OUT(out, "\tcallq\t*%%rax\n");
  return 0;
}

static void function_start(FILE *out)
{
  ASM_CODE_OUT(out, "\tpushq\t%%rbp\n");
  ASM_CODE_OUT(out, "\tmov\t%%rsp, %%rbp\n");
}

static void function_end(FILE *out)
{
  ASM_CODE_OUT(out, "\tleaveq\n");
  ASM_CODE_OUT(out, "\tretq\n\n");
}

static void function_register_save(FILE *out)
{
  ASM_CODE_OUT(out, "\tpushq\t%%rbx\n");
  ASM_CODE_OUT(out, "\tpushq\t%%r12\n");
  ASM_CODE_OUT(out, "\tpushq\t%%r13\n");
  ASM_CODE_OUT(out, "\tpushq\t%%r14\n");
  ASM_CODE_OUT(out, "\tpushq\t%%r15\n");
}

static void function_register_load(FILE *out)
{
  ASM_CODE_OUT(out, "\tpopq\t%%r15\n");
  ASM_CODE_OUT(out, "\tpopq\t%%r14\n");
  ASM_CODE_OUT(out, "\tpopq\t%%r13\n");
  ASM_CODE_OUT(out, "\tpopq\t%%r12\n");
  ASM_CODE_OUT(out, "\tpopq\t%%rbx\n");
}

struct asm_code_callback asm_code_callback_amd64 = {
  NULL, /* type mark */

  8, /* word size */
  8, /* pointer size */
  6, /* funccall args reg number: RDI/RSI/RDX/RCX/R8/R9 (See funccall_regs[]) */
  VALUE_FUNCTION_ARGS_NUM, /* funccall args stack number */
  5, /* tmp reg number: RBX/R12/R13/R14/R15 (See tmp_regs[]) */
  VALUE_FUNCTION_ARGS_NUM, /* tmp stack number */
  5, /* function register number: RBX/R12/R13/R14/R15 (See function_register_save() */
  2, /* function saveparam number: RBP and return address (See function_start() */
  16, /* stack align size */
  0, /* stack correct size */

  get_value,
  get_value_r1,
  get_address_stack,
  get_address_stack_r1,
  get_address,
  add_address,
  get_r1,
  set_r1,

  memory_load,
  memory_store,
  stack_load,
  stack_store,
  stack_load_r1,
  stack_store_r1,
  stack_expand,
  stack_reduce,

  funccall_reg_load,
  funccall_reg_store,
  tmp_reg_load,
  tmp_reg_save,
  tmp_reg_load_r1,
  tmp_reg_save_r1,

  sign_extension_char,
  sign_extension_uchar,
  sign_extension_short,
  sign_extension_ushort,
  sign_extension_int,
  sign_extension_uint,

  calc_inv,
  calc_minus,

  calc_op1,

  calc_add,
  calc_sub,
  calc_and,
  calc_or,
  calc_xor,

  calc_mul,
  calc_div,
  calc_mod,
  calc_llshift,
  calc_rashift,
  calc_rlshift,

  calc_op2,

  branch,
  branch_zero,
  branch_nzero,

  branch_cmp_eq,
  branch_cmp_ne,

  branch_cmp_lt,
  branch_cmp_gt,
  branch_cmp_le,
  branch_cmp_ge,
  branch_cmp_ult,
  branch_cmp_ugt,
  branch_cmp_ule,
  branch_cmp_uge,

  branch_cmp,

  function_call,
  function_call_set,
  function_call_pointer,

  function_start,
  function_end,
  function_register_save,
  function_register_load,
};
