/*
 * Copyright 2010 Christoph Bumiller
 *
 * 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, sublicense,
 * 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 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 NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS 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.
 */

#include "nv50_context.h"
#include "nv50_pc.h"

#define NVXX_DEBUG 0

#define PRINT(args...) debug_printf(args)

#ifndef ARRAY_SIZE
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
#endif

static const char *norm = "\x1b[00m";
static const char *gree = "\x1b[32m";
static const char *blue = "\x1b[34m";
static const char *cyan = "\x1b[36m";
static const char *orng = "\x1b[33m";
static const char *mgta = "\x1b[35m";

static const char *nv_opcode_names[NV_OP_COUNT + 1] = {
   "phi",
   "extract",
   "combine",
   "lda",
   "sta",
   "mov",
   "add",
   "sub",
   "neg",
   "mul",
   "mad",
   "cvt",
   "sat",
   "not",
   "and",
   "or",
   "xor",
   "shl",
   "shr",
   "rcp",
   "undef",
   "rsqrt",
   "lg2",
   "sin",
   "cos",
   "ex2",
   "presin",
   "preex2",
   "min",
   "max",
   "set",
   "sad",
   "kil",
   "bra",
   "call",
   "ret",
   "break",
   "breakaddr",
   "joinat",
   "tex",
   "texbias",
   "texlod",
   "texfetch",
   "texsize",
   "dfdx",
   "dfdy",
   "quadop",
   "linterp",
   "pinterp",
   "abs",
   "ceil",
   "floor",
   "trunc",
   "nop",
   "select",
   "export",
   "join",
   "round",
   "BAD_OP"
};

static const char *nv_cond_names[] =
{
   "never", "lt" , "eq" , "le" , "gt" , "ne" , "ge" , "",
   "never", "ltu", "equ", "leu", "gtu", "neu", "geu", "",
   "o", "c", "a", "s"
};

static const char *nv_modifier_strings[] =
{
   "",
   "neg",
   "abs",
   "neg abs",
   "not",
   "not neg"
   "not abs",
   "not neg abs",
   "sat",
   "BAD_MOD"
};

const char *
nv_opcode_name(uint opcode)
{
   return nv_opcode_names[MIN2(opcode, ARRAY_SIZE(nv_opcode_names) - 1)];
}

static INLINE const char *
nv_type_name(ubyte type)
{
   switch (type) {
   case NV_TYPE_U16: return "u16";
   case NV_TYPE_S16: return "s16";
   case NV_TYPE_F32: return "f32";
   case NV_TYPE_U32: return "u32";
   case NV_TYPE_S32: return "s32";
   case NV_TYPE_P32: return "p32";
   case NV_TYPE_F64: return "f64";
   default:
      return "BAD_TYPE";
   }
}

static INLINE const char *
nv_cond_name(ubyte cc)
{
   return nv_cond_names[MIN2(cc, 19)];
}

static INLINE const char *
nv_modifier_string(ubyte mod)
{
   return nv_modifier_strings[MIN2(mod, 9)];
}

static INLINE int
nv_value_id(struct nv_value *value)
{
   if (value->join->reg.id >= 0)
      return value->join->reg.id;
   return value->n;
}

static INLINE boolean
nv_value_allocated(struct nv_value *value)
{
   return (value->reg.id >= 0) ? TRUE : FALSE;
}

static INLINE void
nv_print_address(const char c, int buf, struct nv_value *a, int offset)
{
   const char ac =  (a && nv_value_allocated(a)) ? '$' : '%';

   if (buf >= 0)
      PRINT(" %s%c%i[", cyan, c, buf);
   else
      PRINT(" %s%c[", cyan, c);
   if (a)
      PRINT("%s%ca%i%s+", mgta, ac, nv_value_id(a), cyan);
   PRINT("%s0x%x%s]", orng, offset, cyan);
}

static INLINE void
nv_print_cond(struct nv_instruction *nvi)
{
   char pfx = nv_value_allocated(nvi->flags_src->value->join) ? '$' : '%';

   PRINT("%s%s %s%cc%i ",
         gree, nv_cond_name(nvi->cc),
         mgta, pfx, nv_value_id(nvi->flags_src->value));
}

static INLINE void
nv_print_value(struct nv_value *value, struct nv_value *ind, ubyte type)
{
   char reg_pfx = '$';

   if (type == NV_TYPE_ANY)
      type = value->reg.type;

   if (value->reg.file != NV_FILE_FLAGS)
      PRINT(" %s%s", gree, nv_type_name(type));

   if (!nv_value_allocated(value->join))
      reg_pfx = '%';

   switch (value->reg.file) {
   case NV_FILE_GPR:
      PRINT(" %s%cr%i", blue, reg_pfx, nv_value_id(value));
      break;
   case NV_FILE_OUT:
      PRINT(" %s%co%i", mgta, reg_pfx, nv_value_id(value));
      break;
   case NV_FILE_ADDR:
      PRINT(" %s%ca%i", mgta, reg_pfx, nv_value_id(value));
      break;
   case NV_FILE_FLAGS:
      PRINT(" %s%cc%i", mgta, reg_pfx, nv_value_id(value));
      break;
   case NV_FILE_MEM_L:
      nv_print_address('l', -1, ind, nv_value_id(value));
      break;
   case NV_FILE_MEM_S:
      nv_print_address('s', -1, ind, 4 * nv_value_id(value));
      break;
   case NV_FILE_MEM_P:
      nv_print_address('p', -1, ind, 4 * nv_value_id(value));
      break;
   case NV_FILE_MEM_V:
      nv_print_address('v', -1, ind, 4 * nv_value_id(value));
      break;
   case NV_FILE_IMM:
      switch (type) {
      case NV_TYPE_U16:
      case NV_TYPE_S16:
         PRINT(" %s0x%04x", orng, value->reg.imm.u32);
         break;
      case NV_TYPE_F32:
         PRINT(" %s%f", orng, value->reg.imm.f32);
         break;
      case NV_TYPE_F64:
         PRINT(" %s%f", orng, value->reg.imm.f64);
         break;
      case NV_TYPE_U32:
      case NV_TYPE_S32:
      case NV_TYPE_P32:
         PRINT(" %s0x%08x", orng, value->reg.imm.u32);
         break;
      }
      break;
   default:
      if (value->reg.file >= NV_FILE_MEM_G(0) &&
          value->reg.file <= NV_FILE_MEM_G(15))
         nv_print_address('g', value->reg.file - NV_FILE_MEM_G(0), ind,
                          nv_value_id(value) * 4);
      else
      if (value->reg.file >= NV_FILE_MEM_C(0) &&
          value->reg.file <= NV_FILE_MEM_C(15))
         nv_print_address('c', value->reg.file - NV_FILE_MEM_C(0), ind,
                          nv_value_id(value) * 4);
      else
         NOUVEAU_ERR(" BAD_FILE[%i]", nv_value_id(value));
      break;
   }
}

static INLINE void
nv_print_ref(struct nv_ref *ref, struct nv_value *ind)
{
   nv_print_value(ref->value, ind, ref->typecast);
}

void
nv_print_instruction(struct nv_instruction *i)
{
   int j;

   PRINT("%i: ", i->serial);

   if (i->flags_src)
      nv_print_cond(i);

   PRINT("%s", gree);
   if (i->opcode == NV_OP_SET)
      PRINT("set %s", nv_cond_name(i->set_cond));
   else
   if (i->saturate)
      PRINT("sat %s", nv_opcode_name(i->opcode));
   else
      PRINT("%s", nv_opcode_name(i->opcode));

   if (i->flags_def)
      nv_print_value(i->flags_def, NULL, NV_TYPE_ANY);

   /* Only STORE & STA can write to MEM, and they do not def
    * anything, so the address is thus part of the source.
    */
   if (i->def[0])
      nv_print_value(i->def[0], NULL, NV_TYPE_ANY);
   else
   if (i->target)
      PRINT(" %s(BB:%i)", orng, i->target->id);
   else
      PRINT(" #");

   for (j = 0; j < 4; ++j) {
      if (!i->src[j])
         continue;

      if (i->src[j]->mod)
         PRINT(" %s%s", gree, nv_modifier_string(i->src[j]->mod));

      nv_print_ref(i->src[j],
                   (j == nv50_indirect_opnd(i)) ?
                   i->src[4]->value : NULL);
   }
   PRINT(" %s%c\n", norm, i->is_long ? 'l' : 's');
}
