#include "third_party/chibicc/chibicc.h"

#define REDZONE(X)  \
  "sub\t$16,%rsp\n" \
  "\t" X "\n"       \
  "\tadd\t$16,%rsp"

#define PUSHPOPRAX(X) \
  "push\t%rax\n"      \
  "\t" X "\n"         \
  "\tpop\t%rax"

#ifdef __SSE3__
#define FIST(X) REDZONE("fistt" X)
#else
#define FIST(X)                      \
  REDZONE("fnstcw\t8(%rsp)\n"        \
          "\tmovzwl\t8(%rsp),%eax\n" \
          "\tor\t$12,%ah\n"          \
          "\tmov\t%ax,10(%rsp)\n"    \
          "\tfldcw\t10(%rsp)\n"      \
          "\tfist" X "\n"            \
          "\tfldcw\t8(%rsp)")
#endif

#define u64f64              \
  "test\t%rax,%rax\n"       \
  "\tjs\t1f\n"              \
  "\tpxor\t%xmm0,%xmm0\n"   \
  "\tcvtsi2sd %rax,%xmm0\n" \
  "\tjmp\t2f\n"             \
  "1:\n"                    \
  "\tmov\t%rax,%rdi\n"      \
  "\tand\t$1,%eax\n"        \
  "\tpxor\t%xmm0,%xmm0\n"   \
  "\tshr\t%rdi\n"           \
  "\tor\t%rax,%rdi\n"       \
  "\tcvtsi2sd %rdi,%xmm0\n" \
  "\taddsd\t%xmm0,%xmm0\n"  \
  "2:"

#define u64f80                             \
  PUSHPOPRAX("fildq\t(%rsp)\n"             \
             "\ttest\t%rax,%rax\n"         \
             "\tjns\t1f\n"                 \
             "\tmov\t$0x5f800000,(%rsp)\n" \
             "\tfadds\t(%rsp)\n"           \
             "1:")

#define i32i8   "movsbl\t%al,%eax"
#define i32u8   "movzbl\t%al,%eax"
#define i32i16  "cwtl"
#define i32u16  "movzwl\t%ax,%eax"
#define i32f32  "cvtsi2ssl %eax,%xmm0"
#define i32i64  "cltq"
#define i32f64  "cvtsi2sdl %eax,%xmm0"
#define u32i64  "mov\t%eax,%eax"
#define i64f32  "cvtsi2ssq %rax,%xmm0"
#define i64f64  "cvtsi2sdq %rax,%xmm0"
#define f32i32  "cvttss2sil %xmm0,%eax"
#define f32u32  "cvttss2siq %xmm0,%rax"
#define f32i64  "cvttss2siq %xmm0,%rax"
#define f32u64  "cvttss2siq %xmm0,%rax"
#define f32f64  "cvtss2sd %xmm0,%xmm0"
#define f64i32  "cvttsd2sil %xmm0,%eax"
#define f64u32  "cvttsd2siq %xmm0,%rax"
#define f64i64  "cvttsd2siq %xmm0,%rax"
#define f64u64  "cvttsd2siq %xmm0,%rax"
#define f64f32  "cvtsd2ss %xmm0,%xmm0"
#define u64f32  "cvtsi2ssq %rax,%xmm0"
#define f32i8   "cvttss2sil %xmm0,%eax\n\tmovsbl\t%al,%eax"
#define f32u8   "cvttss2sil %xmm0,%eax\n\tmovzbl\t%al,%eax"
#define f32i16  "cvttss2sil %xmm0,%eax\n\tmovswl\t%ax,%eax"
#define f32u16  "cvttss2sil %xmm0,%eax\n\tmovzwl\t%ax,%eax"
#define f64i8   "cvttsd2sil %xmm0,%eax\n\tmovsbl\t%al,%eax"
#define f64u8   "cvttsd2sil %xmm0,%eax\n\tmovzbl\t%al,%eax"
#define f64i16  "cvttsd2sil %xmm0,%eax\n\tmovswl\t%ax,%eax"
#define f64u16  "cvttsd2sil %xmm0,%eax\n\tmovzwl\t%ax,%eax"
#define f64f80  REDZONE("movsd\t%xmm0,(%rsp)\n\tfldl\t(%rsp)")
#define f80i8   FIST("ps\t(%rsp)\n\tmovsbl\t(%rsp),%eax")
#define f80u8   FIST("ps\t(%rsp)\n\tmovzbl\t(%rsp),%eax")
#define f80i16  FIST("ps\t(%rsp)\n\tmovzbl\t(%rsp),%eax")
#define f80u16  FIST("pl\t(%rsp)\n\tmovswl\t(%rsp),%eax")
#define f80i32  FIST("pl\t(%rsp)\n\tmov\t(%rsp),%eax")
#define f80u32  FIST("pl\t(%rsp)\n\tmov\t(%rsp),%eax")
#define f80i64  FIST("pq\t(%rsp)\n\tmov\t(%rsp),%rax")
#define f80u64  FIST("pq\t(%rsp)\n\tmov\t(%rsp),%rax")
#define f80f32  REDZONE("fstps\t(%rsp)\n\tmovss\t(%rsp),%xmm0")
#define f80f64  REDZONE("fstpl\t(%rsp)\n\tmovsd\t(%rsp),%xmm0")
#define i32f80  PUSHPOPRAX("fildl\t(%rsp)")
#define u32f32  "mov\t%eax,%eax\n\tcvtsi2ssq %rax,%xmm0"
#define u32f64  "mov\t%eax,%eax\n\tcvtsi2sdq %rax,%xmm0"
#define u32f80  "mov\t%eax,%eax\n\tpush %rax\n\tfildll\t(%rsp)\n\tpop\t%rax"
#define i64f80  PUSHPOPRAX("fildll\t(%rsp)")
#define f32f80  REDZONE("movss\t%xmm0,(%rsp)\n\tflds\t(%rsp)")
#define i8i128  "movsbq\t%al,%rax\n\tcqto"
#define i16i128 "movswq\t%ax,%rax\n\tcqto"
#define i32i128 "cltq\n\tcqto"
#define i64i128 "cqto"
#define u8i128  "movzbq\t%al,%rax\n\txor\t%edx,%edx"
#define u16i128 "movzwq\t%ax,%rax\n\txor\t%edx,%edx"
#define u32i128 "cltq\n\txor\t%edx,%edx"
#define u64i128 "xor\t%edx,%edx"
#define i128f32 "mov\t%rax,%rdi\n\tmov\t%rdx,%rsi\n\tcall\t__floattisf"
#define i128f64 "mov\t%rax,%rdi\n\tmov\t%rdx,%rsi\n\tcall\t__floattidf"
#define i128f80 "mov\t%rax,%rdi\n\tmov\t%rdx,%rsi\n\tcall\t__floattixf"
#define u128f32 "mov\t%rax,%rdi\n\tmov\t%rdx,%rsi\n\tcall\t__floatuntisf"
#define u128f64 "mov\t%rax,%rdi\n\tmov\t%rdx,%rsi\n\tcall\t__floatuntidf"
#define u128f80 "mov\t%rax,%rdi\n\tmov\t%rdx,%rsi\n\tcall\t__floatuntixf"
#define f32i128 "call\t__fixsfti"
#define f64i128 "call\t__fixdfti"
#define f80i128 REDZONE("fstpt\t(%rsp)\n\tcall\t__fixxfti")
#define f32u128 "call\t__fixunssfti"
#define f64u128 "call\t__fixunsdfti"
#define f80u128 REDZONE("fstpt\t(%rsp)\n\tcall\t__fixunsxfti")

enum { I8, I16, I32, I64, U8, U16, U32, U64, F32, F64, F80, I128, U128 };
static const char *const cast_table[13][13] = /* clang-format off */ {
  // i8    i16      i32      i64      u8      u16      u32      u64      f32      f64      f80      i128      u128
  {NULL,   NULL,    NULL,    i32i64,  i32u8,  i32u16,  NULL,    i32i64,  i32f32,  i32f64,  i32f80,  i32i128, i32i128}, // i8
  {i32i8,  NULL,    NULL,    i32i64,  i32u8,  i32u16,  NULL,    i32i64,  i32f32,  i32f64,  i32f80,  i32i128, i32i128}, // i16
  {i32i8,  i32i16,  NULL,    i32i64,  i32u8,  i32u16,  NULL,    i32i64,  i32f32,  i32f64,  i32f80,  i32i128, i32i128}, // i32
  {i32i8,  i32i16,  NULL,    NULL,    i32u8,  i32u16,  NULL,    NULL,    i64f32,  i64f64,  i64f80,  i64i128, i64i128}, // i64
  {i32i8,  NULL,    NULL,    i32i64,  NULL,   NULL,    NULL,    i32i64,  i32f32,  i32f64,  i32f80,  i32i128, i32i128}, // u8
  {i32i8,  i32i16,  NULL,    i32i64,  i32u8,  NULL,    NULL,    i32i64,  i32f32,  i32f64,  i32f80,  i32i128, i32i128}, // u16
  {i32i8,  i32i16,  NULL,    u32i64,  i32u8,  i32u16,  NULL,    u32i64,  u32f32,  u32f64,  u32f80,  u32i128, i32i128}, // u32
  {i32i8,  i32i16,  NULL,    NULL,    i32u8,  i32u16,  NULL,    NULL,    u64f32,  u64f64,  u64f80,  u64i128, u64i128}, // u64
  {f32i8,  f32i16,  f32i32,  f32i64,  f32u8,  f32u16,  f32u32,  f32u64,  NULL,    f32f64,  f32f80,  f32i128, f32u128}, // f32
  {f64i8,  f64i16,  f64i32,  f64i64,  f64u8,  f64u16,  f64u32,  f64u64,  f64f32,  NULL,    f64f80,  f64i128, f64u128}, // f64
  {f80i8,  f80i16,  f80i32,  f80i64,  f80u8,  f80u16,  f80u32,  f80u64,  f80f32,  f80f64,  NULL,    f80i128, f80u128}, // f80
  {i32i8,  i32i16,  NULL,    NULL,    i32u8,  i32u16,  NULL,    NULL,    i128f32, i128f64, i128f80, NULL,    NULL   }, // i128
  {i32i8,  i32i16,  NULL,    NULL,    i32u8,  i32u16,  NULL,    NULL,    u128f32, u128f64, u128f80, NULL,    NULL   }, // u128
} /* clang-format on */;

static int getTypeId(Type *ty) {
  switch (ty->kind) {
    case TY_CHAR:
      return ty->is_unsigned ? U8 : I8;
    case TY_SHORT:
      return ty->is_unsigned ? U16 : I16;
    case TY_INT:
      return ty->is_unsigned ? U32 : I32;
    case TY_LONG:
      return ty->is_unsigned ? U64 : I64;
    case TY_INT128:
      return ty->is_unsigned ? U128 : I128;
    case TY_FLOAT:
      return F32;
    case TY_DOUBLE:
      return F64;
    case TY_LDOUBLE:
      return F80;
    default:
      return U64;
  }
}

void gen_cast(Type *from, Type *to) {
  bool skew;
  if (to->kind == TY_VOID) return;
  if (to->kind == TY_BOOL) {
    cmp_zero(from);
    println("\tsetne\t%%al");
    println("\tmovzbl\t%%al,%%eax");
    return;
  }
  int t1 = getTypeId(from);
  int t2 = getTypeId(to);
  if (cast_table[t1][t2]) {
    if ((skew = (depth & 1) && strstr(cast_table[t1][t2], "call"))) {
      println("\tsub\t$8,%%rsp");
      depth++;
    }
    println("\t%s", cast_table[t1][t2]);
    if (skew) {
      println("\tadd\t$8,%%rsp");
      depth--;
    }
  }
}
