/*
 * Copyright (c) 2016 Etnaviv Project
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sub license,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the
 * next paragraph) shall be included in all copies or substantial portions
 * of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 * Authors:
 *    Christian Gmeiner <christian.gmeiner@gmail.com>
 */

#include "etnaviv_disasm.h"
#include "etnaviv_asm.h"

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

#include "hw/isa.xml.h"
#include "util/u_math.h"
#include "util/u_half.h"

struct instr {
   /* dword0: */
   uint32_t opc         : 6;
   uint32_t cond        : 5;
   uint32_t sat         : 1;
   uint32_t dst_use     : 1;
   uint32_t dst_amode   : 3;
   uint32_t dst_reg     : 7;
   uint32_t dst_comps   : 4;
   uint32_t tex_id      : 5;

   /* dword1: */
   uint32_t tex_amode   : 3;
   uint32_t tex_swiz    : 8;
   uint32_t src0_use    : 1;
   uint32_t src0_reg    : 9;
   uint32_t type_bit2   : 1;
   uint32_t src0_swiz   : 8;
   uint32_t src0_neg    : 1;
   uint32_t src0_abs    : 1;

   /* dword2: */
   uint32_t src0_amode  : 3;
   uint32_t src0_rgroup : 3;
   uint32_t src1_use    : 1;
   uint32_t src1_reg    : 9;
   uint32_t opcode_bit6 : 1;
   uint32_t src1_swiz   : 8;
   uint32_t src1_neg    : 1;
   uint32_t src1_abs    : 1;
   uint32_t src1_amode  : 3;
   uint32_t type_bit01  : 2;

   /* dword3: */
   union {
      struct {
         uint32_t src1_rgroup : 3;
         uint32_t src2_use    : 1;
         uint32_t src2_reg    : 9;
         uint32_t sel_0       : 1;
         uint32_t src2_swiz   : 8;
         uint32_t src2_neg    : 1;
         uint32_t src2_abs    : 1;
         uint32_t sel_1       : 1;
         uint32_t src2_amode  : 3;
         uint32_t src2_rgroup : 3;
         uint32_t dst_full    : 1;
      };
      uint32_t dword3;
   };
};
struct opc_operands {
   struct etna_inst_dst *dst;
   struct etna_inst_tex *tex;
   struct etna_inst_src *src0;
   struct etna_inst_src *src1;
   struct etna_inst_src *src2;

   int imm;
};

static void
printf_type(uint8_t type)
{
   switch(type) {
   case INST_TYPE_F32:
      /* as f32 is the default print nothing */
      break;

   case INST_TYPE_S32:
      printf(".s32");
      break;

   case INST_TYPE_S8:
      printf(".s8");
      break;

   case INST_TYPE_U16:
      printf(".u16");
      break;

   case INST_TYPE_F16:
      printf(".f16");
      break;

   case INST_TYPE_S16:
      printf(".s16");
      break;

   case INST_TYPE_U32:
      printf(".u32");
      break;

   case INST_TYPE_U8:
      printf(".u8");
      break;

   default:
      abort();
      break;
   }
}

static void
print_condition(uint8_t condition)
{
   switch (condition) {
   case INST_CONDITION_TRUE:
      break;

   case INST_CONDITION_GT:
      printf(".GT");
      break;

   case INST_CONDITION_LT:
      printf(".LT");
      break;

   case INST_CONDITION_GE:
      printf(".GE");
      break;

   case INST_CONDITION_LE:
      printf(".LE");
      break;

   case INST_CONDITION_EQ:
      printf(".EQ");
      break;

   case INST_CONDITION_NE:
      printf(".NE");
      break;

   case INST_CONDITION_AND:
      printf(".AND");
      break;

   case INST_CONDITION_OR:
      printf(".OR");
      break;

   case INST_CONDITION_XOR:
      printf(".XOR");
      break;

   case INST_CONDITION_NOT:
      printf(".NOT");
      break;

   case INST_CONDITION_NZ:
      printf(".NZ");
      break;

   case INST_CONDITION_GEZ:
      printf(".GEZ");
      break;

   case INST_CONDITION_GZ:
      printf(".GZ");
      break;

   case INST_CONDITION_LEZ:
      printf(".LEZ");
      break;

   case INST_CONDITION_LZ:
      printf(".LZ");
      break;

   default:
      abort();
      break;
   }
}

static void
print_rgroup(uint8_t rgoup)
{
   switch (rgoup) {
   case INST_RGROUP_TEMP:
      printf("t");
      break;

   case INST_RGROUP_INTERNAL:
      printf("i");
      break;

   case INST_RGROUP_UNIFORM_0:
   case INST_RGROUP_UNIFORM_1:
      printf("u");
      break;
   case 4:
      printf("th");
      break;
   }
}

static void
print_components(uint8_t components)
{
   if (components == 15)
      return;

   printf(".");
   if (components & INST_COMPS_X)
      printf("x");
   else
      printf("_");

   if (components & INST_COMPS_Y)
      printf("y");
   else
      printf("_");

   if (components & INST_COMPS_Z)
      printf("z");
   else
      printf("_");

   if (components & INST_COMPS_W)
      printf("w");
   else
      printf("_");
}

static inline void
print_swiz_comp(uint8_t swiz_comp)
{
   switch (swiz_comp) {
   case INST_SWIZ_COMP_X:
      printf("x");
      break;

   case INST_SWIZ_COMP_Y:
      printf("y");
      break;

   case INST_SWIZ_COMP_Z:
      printf("z");
      break;

   case INST_SWIZ_COMP_W:
      printf("w");
      break;

   default:
      abort();
      break;
   }
}

static void
print_swiz(uint8_t swiz)
{
   // if a null swizzle
   if (swiz == 0xe4)
      return;

   const unsigned x = swiz & 0x3;
   const unsigned y = (swiz & 0x0C) >> 2;
   const unsigned z = (swiz & 0x30) >> 4;
   const unsigned w = (swiz & 0xc0) >> 6;

   printf(".");
   print_swiz_comp(x);
   print_swiz_comp(y);
   print_swiz_comp(z);
   print_swiz_comp(w);
}

static void
print_amode(uint8_t amode)
{
   switch (amode) {
   case INST_AMODE_DIRECT:
      /* nothing to output */
      break;

   case INST_AMODE_ADD_A_X:
      printf("[a.x]");
      break;

   case INST_AMODE_ADD_A_Y:
      printf("[a.y]");
      break;

   case INST_AMODE_ADD_A_Z:
      printf("[a.z]");
      break;

   case INST_AMODE_ADD_A_W:
      printf("[a.w]");
      break;

   default:
      abort();
      break;
   }
}

static void
print_dst(struct etna_inst_dst *dst, bool sep)
{
   if (dst->use) {
      printf("t%u", dst->reg);
      print_amode(dst->amode);
      print_components(dst->write_mask);
   } else {
      printf("void");
   }

   if (sep)
      printf(", ");
}

static void
print_tex(struct etna_inst_tex *tex, bool sep)
{
   printf("tex%u", tex->id);
   print_amode(tex->amode);
   print_swiz(tex->swiz);

   if (sep)
      printf(", ");
}

static void
print_src(struct etna_inst_src *src, bool sep)
{
   if (src->use) {
      if (src->rgroup == INST_RGROUP_IMMEDIATE) {
         switch (src->imm_type) {
         case 0: /* float */
            printf("%f", uif(src->imm_val << 12));
            break;
         case 1: /* signed */
            printf("%d", ((int) src->imm_val << 12) >> 12);
            break;
         case 2: /* unsigned */
            printf("%d", src->imm_val);
            break;
         case 3: /* 16-bit */
            printf("%f/%.5X", util_half_to_float(src->imm_val), src->imm_val);
            break;
         }
      } else {
         if (src->neg)
            printf("-");

         if (src->abs)
            printf("|");

         if (src->rgroup == INST_RGROUP_UNIFORM_1)
            src->reg += 128;

         print_rgroup(src->rgroup);
         printf("%u", src->reg);
         print_amode(src->amode);
         print_swiz(src->swiz);

         if (src->abs)
            printf("|");
      }
   } else {
      printf("void");
   }

   if (sep)
      printf(", ");
}

static void
print_opc_default(struct opc_operands *operands)
{
   print_dst(operands->dst, true);
   print_src(operands->src0, true);
   print_src(operands->src1, true);
   print_src(operands->src2, false);
}

static void
print_opc_mov(struct opc_operands *operands)
{
   // dst (areg)
   printf("a%u", operands->dst->reg);
   print_components(operands->dst->write_mask);
   printf(", ");

   print_src(operands->src0, true);
   print_src(operands->src1, true);
   print_src(operands->src2, false);
}

static void
print_opc_tex(struct opc_operands *operands)
{
   print_dst(operands->dst, true);
   print_tex(operands->tex, true);
   print_src(operands->src0, true);
   print_src(operands->src1, true);
   print_src(operands->src2, false);
}

static void
print_opc_imm(struct opc_operands *operands)
{
   print_dst(operands->dst, true);
   print_src(operands->src0, true);
   print_src(operands->src1, true);
   printf("label_%04d", operands->imm);
}

#define OPC_BITS 7

static const struct opc_info {
   const char *name;
   void (*print)(struct opc_operands *operands);
} opcs[1 << OPC_BITS] = {
#define OPC(opc) [INST_OPCODE_##opc] = {#opc, print_opc_default}
#define OPC_MOV(opc) [INST_OPCODE_##opc] = {#opc, print_opc_mov}
#define OPC_TEX(opc) [INST_OPCODE_##opc] = {#opc, print_opc_tex}
#define OPC_IMM(opc) [INST_OPCODE_##opc] = {#opc, print_opc_imm}
   OPC(NOP),
   OPC(ADD),
   OPC(MAD),
   OPC(MUL),
   OPC(DST),
   OPC(DP3),
   OPC(DP4),
   OPC(DSX),
   OPC(DSY),
   OPC(MOV),
   OPC_MOV(MOVAR),
   OPC_MOV(MOVAF),
   OPC_MOV(MOVAI),
   OPC(RCP),
   OPC(RSQ),
   OPC(LITP),
   OPC(SELECT),
   OPC(SET),
   OPC(EXP),
   OPC(LOG),
   OPC(FRC),
   OPC_IMM(CALL),
   OPC(RET),
   OPC_IMM(BRANCH),
   OPC_TEX(TEXKILL),
   OPC_TEX(TEXLD),
   OPC_TEX(TEXLDB),
   OPC_TEX(TEXLDD),
   OPC_TEX(TEXLDL),
   OPC_TEX(TEXLDPCF),
   OPC_TEX(TEXLDLPCF),
   OPC_TEX(TEXLDGPCF),
   OPC(REP),
   OPC(ENDREP),
   OPC(LOOP),
   OPC(ENDLOOP),
   OPC(SQRT),
   OPC(SIN),
   OPC(COS),
   OPC(FLOOR),
   OPC(CEIL),
   OPC(SIGN),
   OPC(I2F),
   OPC(F2I),
   OPC(CMP),
   OPC(LOAD),
   OPC(STORE),
   OPC(IMULLO0),
   OPC(IMULHI0),
   OPC(IMADLO0),
   OPC(IMADHI0),
   OPC(LEADZERO),
   OPC(LSHIFT),
   OPC(RSHIFT),
   OPC(ROTATE),
   OPC(OR),
   OPC(AND),
   OPC(XOR),
   OPC(NOT),
   OPC(DP2),
   OPC(DIV),
   OPC(IABS),
};

static void
print_instr(uint32_t *dwords, int n, enum debug_t debug)
{
   struct instr *instr = (struct instr *)dwords;
   const unsigned opc = instr->opc | (instr->opcode_bit6 << 6);
   const char *name = opcs[opc].name;

   printf("%04d: ", n);
   if (debug & PRINT_RAW)
      printf("%08x %08x %08x %08x  ", dwords[0], dwords[1], dwords[2],
             dwords[3]);

   if (name) {

      struct etna_inst_dst dst = {
         .use = instr->dst_use,
         .amode = instr->dst_amode,
         .reg = instr->dst_reg,
         .write_mask = instr->dst_comps
      };

      struct etna_inst_tex tex = {
         .id = instr->tex_id,
         .amode = instr->tex_amode,
         .swiz = instr->tex_swiz,
      };

      struct etna_inst_src src0 = {
         .use = instr->src0_use,
         .neg = instr->src0_neg,
         .abs = instr->src0_abs,
         .rgroup = instr->src0_rgroup,
         .reg = instr->src0_reg,
         .swiz = instr->src0_swiz,
         .amode = instr->src0_amode,
      };

      struct etna_inst_src src1 = {
         .use = instr->src1_use,
         .neg = instr->src1_neg,
         .abs = instr->src1_abs,
         .rgroup = instr->src1_rgroup,
         .reg = instr->src1_reg,
         .swiz = instr->src1_swiz,
         .amode = instr->src1_amode,
      };

      struct etna_inst_src src2 = {
         .use = instr->src2_use,
         .neg = instr->src2_neg,
         .abs = instr->src2_abs,
         .rgroup = instr->src2_rgroup,
         .reg = instr->src2_reg,
         .swiz = instr->src2_swiz,
         .amode = instr->src2_amode,
      };

      int imm = (instr->dword3 & VIV_ISA_WORD_3_SRC2_IMM__MASK)
                >> VIV_ISA_WORD_3_SRC2_IMM__SHIFT;

      struct opc_operands operands = {
         .dst = &dst,
         .tex = &tex,
         .src0 = &src0,
         .src1 = &src1,
         .src2 = &src2,
         .imm = imm,
      };

      uint8_t type = instr->type_bit01 | (instr->type_bit2 << 2);

      printf("%s", name);
      printf_type(type);
      if (instr->sat)
         printf(".SAT");
      print_condition(instr->cond);
      printf(" ");
      if (instr->sel_0)
         printf("SEL_0 ");
      if (instr->sel_1)
         printf("SEL_1 ");
      if (instr->dst_full)
         printf("DST_FULL ");
      opcs[opc].print(&operands);
   } else {
      printf("unknown (%d)", instr->opc);
   }

   printf("\n");
}

void
etna_disasm(uint32_t *dwords, int sizedwords, enum debug_t debug)
{
   unsigned i;

   assert((sizedwords % 2) == 0);

   for (i = 0; i < sizedwords; i += 4)
      print_instr(&dwords[i], i / 4, debug);
}
