#include "cpu/exec.h"
#include "cpu/rtl.h"

/* shared by all helper functions */
DecodeInfo decoding;
rtlreg_t t0, t1, t2, t3, at;

void decoding_set_jmp(bool is_jmp) {
  decoding.is_jmp = is_jmp;
}

#define make_DopHelper_funcname(name) DopHelperImpl::name
#define make_DopHelper(name) void name (vaddr_t *eip, Operand *op, bool load_val)

namespace DopHelperImpl {
  /* Refer to Appendix A in i386 manual for the explanations of these abbreviations */
  
  /* Ib, Iv */
  static inline make_DopHelper(I) {
    /* eip here is pointing to the immediate */
    op->type = OP_TYPE_IMM;
    op->imm = instr_fetch(eip, op->width);
    rtl_li(&op->val, op->imm);
  
  #ifdef DEBUG
    snprintf(op->str, OP_STR_SIZE, "$0x%x", op->imm);
  #endif
  }
  
  /* I386 manual does not contain this abbreviation, but it is different from
   * the one above from the view of implementation. So we use another helper
   * function to decode it.
   */
  /* sign immediate */
  static inline make_DopHelper(SI) {
    assert(op->width == 1 || op->width == 4);
  
    op->type = OP_TYPE_IMM;
  
    /* TODO: Use instr_fetch() to read `op->width' bytes of memory
     * pointed by `eip'. Interpret the result as a signed immediate,
     * and assign it to op->simm.
     *
     op->simm = ???
     */
    op->simm = instr_fetch(eip, op->width);
  
    rtl_li(&op->val, op->simm);
  
  #ifdef DEBUG
    snprintf(op->str, OP_STR_SIZE, "$0x%x", op->simm);
  #endif
  }
  
  /* I386 manual does not contain this abbreviation.
   * It is convenient to merge them into a single helper function.
   */
  /* AL/eAX */
  static inline make_DopHelper(a) {
    op->type = OP_TYPE_REG;
    op->reg = R_EAX;
    if (load_val) {
      rtl_lr(&op->val, R_EAX, op->width);
    }
  
  #ifdef DEBUG
    snprintf(op->str, OP_STR_SIZE, "%%%s", reg_name(R_EAX, op->width));
  #endif
  }
  
  /* This helper function is use to decode register encoded in the opcode. */
  /* XX: AL, AH, BL, BH, CL, CH, DL, DH
   * eXX: eAX, eCX, eDX, eBX, eSP, eBP, eSI, eDI
   */
  static inline make_DopHelper(r) {
    op->type = OP_TYPE_REG;
    op->reg = decoding.opcode & 0x7;
    if (load_val) {
      rtl_lr(&op->val, op->reg, op->width);
    }
  
  #ifdef DEBUG
    snprintf(op->str, OP_STR_SIZE, "%%%s", reg_name(op->reg, op->width));
  #endif
  }
  
  /* I386 manual does not contain this abbreviation.
   * We decode everything of modR/M byte by one time.
   */
  /* Eb, Ew, Ev
   * Gb, Gv
   * Cd,
   * M
   * Rd
   * Sw
   */
  static inline void rm(vaddr_t *eip, Operand *rm, bool load_rm_val, Operand *reg, bool load_reg_val) {
    read_ModR_M(eip, rm, load_rm_val, reg, load_reg_val);
  }
  
  /* Ob, Ov */
  static inline make_DopHelper(O) {
    op->type = OP_TYPE_MEM;
    rtl_li(&op->addr, instr_fetch(eip, 4));
    if (load_val) {
      rtl_lm(&op->val, &op->addr, op->width);
    }
  
  #ifdef DEBUG
    snprintf(op->str, OP_STR_SIZE, "0x%x", op->addr);
  #endif
  }
} // end namespace DopHelperImpl

namespace DHelperImpl {

  /* Eb <- Gb
   * Ev <- Gv
   */
  make_DHelper(G2E) {
    make_DopHelper_funcname(rm)(eip, id_dest, true, id_src, true);
  }
  
  make_DHelper(mov_G2E) {
    make_DopHelper_funcname(rm)(eip, id_dest, false, id_src, true);
  }
  
  /* Gb <- Eb
   * Gv <- Ev
   */
  make_DHelper(E2G) {
    make_DopHelper_funcname(rm)(eip, id_src, true, id_dest, true);
  }
  
  make_DHelper(mov_E2G) {
    make_DopHelper_funcname(rm)(eip, id_src, true, id_dest, false);
  }
  
  make_DHelper(lea_M2G) {
    make_DopHelper_funcname(rm)(eip, id_src, false, id_dest, false);
  }
  
  /* AL <- Ib
   * eAX <- Iv
   */
  make_DHelper(I2a) {
    make_DopHelper_funcname(a)(eip, id_dest, true);
    make_DopHelper_funcname(I)(eip, id_src, true);
  }
  
  /* Gv <- EvIb
   * Gv <- EvIv
   * use for imul */
  make_DHelper(I_E2G) {
    make_DopHelper_funcname(rm)(eip, id_src2, true, id_dest, false);
    make_DopHelper_funcname(I)(eip, id_src, true);
  }
  
  /* Eb <- Ib
   * Ev <- Iv
   */
  make_DHelper(I2E) {
    make_DopHelper_funcname(rm)(eip, id_dest, true, NULL, false);
    make_DopHelper_funcname(I)(eip, id_src, true);
  }
  
  make_DHelper(mov_I2E) {
    make_DopHelper_funcname(rm)(eip, id_dest, false, NULL, false);
    make_DopHelper_funcname(I)(eip, id_src, true);
  }
  
  /* XX <- Ib
   * eXX <- Iv
   */
  make_DHelper(I2r) {
    make_DopHelper_funcname(r)(eip, id_dest, true);
    make_DopHelper_funcname(I)(eip, id_src, true);
  }
  
  make_DHelper(mov_I2r) {
    make_DopHelper_funcname(r)(eip, id_dest, false);
    make_DopHelper_funcname(I)(eip, id_src, true);
  }
  
  /* used by unary operations */
  make_DHelper(I) {
    make_DopHelper_funcname(I)(eip, id_dest, true);
  }
  
  make_DHelper(r) {
    make_DopHelper_funcname(r)(eip, id_dest, true);
  }
  
  make_DHelper(E) {
    make_DopHelper_funcname(rm)(eip, id_dest, true, NULL, false);
  }
  
  make_DHelper(setcc_E) {
    make_DopHelper_funcname(rm)(eip, id_dest, false, NULL, false);
  }
  
  make_DHelper(gp7_E) {
    make_DopHelper_funcname(rm)(eip, id_dest, false, NULL, false);
  }
  
  /* used by test in group3 */
  make_DHelper(test_I) {
    make_DopHelper_funcname(I)(eip, id_src, true);
  }
  
  make_DHelper(SI2E) {
    assert(id_dest->width == 2 || id_dest->width == 4);
    make_DopHelper_funcname(rm)(eip, id_dest, true, NULL, false);
    id_src->width = 1;
    make_DopHelper_funcname(SI)(eip, id_src, true);
    if (id_dest->width == 2) {
      id_src->val &= 0xffff;
    }
  }
  
  make_DHelper(SI_E2G) {
    assert(id_dest->width == 2 || id_dest->width == 4);
    make_DopHelper_funcname(rm)(eip, id_src2, true, id_dest, false);
    id_src->width = 1;
    make_DopHelper_funcname(SI)(eip, id_src, true);
    if (id_dest->width == 2) {
      id_src->val &= 0xffff;
    }
  }
  
  make_DHelper(gp2_1_E) {
    make_DopHelper_funcname(rm)(eip, id_dest, true, NULL, false);
    id_src->type = OP_TYPE_IMM;
    id_src->imm = 1;
    rtl_li(&id_src->val, 1);
  #ifdef DEBUG
    sprintf(id_src->str, "$1");
  #endif
  }
  
  make_DHelper(gp2_cl2E) {
    make_DopHelper_funcname(rm)(eip, id_dest, true, NULL, false);
    id_src->type = OP_TYPE_REG;
    id_src->reg = R_CL;
    rtl_lr(&id_src->val, R_CL, 1);
  #ifdef DEBUG
    sprintf(id_src->str, "%%cl");
  #endif
  }
  
  make_DHelper(gp2_Ib2E) {
    make_DopHelper_funcname(rm)(eip, id_dest, true, NULL, false);
    id_src->width = 1;
    make_DopHelper_funcname(I)(eip, id_src, true);
  }
  
  /* Ev <- GvIb
   * use for shld/shrd */
  make_DHelper(Ib_G2E) {
    make_DopHelper_funcname(rm)(eip, id_dest, true, id_src2, true);
    id_src->width = 1;
    make_DopHelper_funcname(I)(eip, id_src, true);
  }
  
  /* Ev <- GvCL
   * use for shld/shrd */
  make_DHelper(cl_G2E) {
    make_DopHelper_funcname(rm)(eip, id_dest, true, id_src2, true);
    id_src->type = OP_TYPE_REG;
    id_src->reg = R_CL;
    rtl_lr(&id_src->val, R_CL, 1);
  #ifdef DEBUG
    sprintf(id_src->str, "%%cl");
  #endif
  }
  
  make_DHelper(O2a) {
    make_DopHelper_funcname(O)(eip, id_src, true);
    make_DopHelper_funcname(a)(eip, id_dest, false);
  }
  
  make_DHelper(a2O) {
    make_DopHelper_funcname(a)(eip, id_src, true);
    make_DopHelper_funcname(O)(eip, id_dest, false);
  }
  
  make_DHelper(J) {
    make_DopHelper_funcname(SI)(eip, id_dest, false);
    rtl_sext(&id_dest->imm, &id_dest->imm, id_dest->width);
    // the target address can be computed in the decode stage
    decoding.jmp_eip = id_dest->simm + *eip;
  }
  
  make_DHelper(push_SI) {
    make_DopHelper_funcname(SI)(eip, id_dest, true);
  }
  
  make_DHelper(in_I2a) {
    id_src->width = 1;
    make_DopHelper_funcname(I)(eip, id_src, true);
    make_DopHelper_funcname(a)(eip, id_dest, false);
  }
  
  make_DHelper(in_dx2a) {
    id_src->type = OP_TYPE_REG;
    id_src->reg = R_DX;
    rtl_lr(&id_src->val, R_DX, 2);
  #ifdef DEBUG
    sprintf(id_src->str, "(%%dx)");
  #endif
  
    make_DopHelper_funcname(a)(eip, id_dest, false);
  }
  
  make_DHelper(out_a2I) {
    make_DopHelper_funcname(a)(eip, id_src, true);
    id_dest->width = 1;
    make_DopHelper_funcname(I)(eip, id_dest, true);
  }
  
  make_DHelper(out_a2dx) {
    make_DopHelper_funcname(a)(eip, id_src, true);
  
    id_dest->type = OP_TYPE_REG;
    id_dest->reg = R_DX;
    rtl_lr(&id_dest->val, R_DX, 2);
  #ifdef DEBUG
    sprintf(id_dest->str, "(%%dx)");
  #endif
  }
} // end namespace DHelperIMpl

void operand_write(Operand *op, rtlreg_t* src) {
  if (op->type == OP_TYPE_REG) { rtl_sr(op->reg, src, op->width); }
  else if (op->type == OP_TYPE_MEM) { rtl_sm(&op->addr, src, op->width); }
  else { assert(0); }
}
