/*
 * Mesa 3-D graphics library
 *
 * Copyright (C) 2012-2013 LunarG, Inc.
 *
 * 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 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:
 *    Chia-I Wu <olv@lunarg.com>
 */

#ifndef TOY_COMPILER_H
#define TOY_COMPILER_H

#include "util/u_slab.h"
#include "brw_defines.h"

#include "ilo_common.h"
#include "toy_compiler_reg.h"

/**
 * Toy opcodes.
 */
enum toy_opcode {
   /* 0..127 are reserved for BRW_OPCODE_x */
   TOY_OPCODE_LAST_HW = 127,

   /* TGSI register functions */
   TOY_OPCODE_TGSI_IN,
   TOY_OPCODE_TGSI_CONST,
   TOY_OPCODE_TGSI_SV,
   TOY_OPCODE_TGSI_IMM,
   TOY_OPCODE_TGSI_INDIRECT_FETCH,
   TOY_OPCODE_TGSI_INDIRECT_STORE,

   /* TGSI sampling functions */
   TOY_OPCODE_TGSI_TEX,
   TOY_OPCODE_TGSI_TXB,
   TOY_OPCODE_TGSI_TXD,
   TOY_OPCODE_TGSI_TXL,
   TOY_OPCODE_TGSI_TXP,
   TOY_OPCODE_TGSI_TXF,
   TOY_OPCODE_TGSI_TXQ,
   TOY_OPCODE_TGSI_TXQ_LZ,
   TOY_OPCODE_TGSI_TEX2,
   TOY_OPCODE_TGSI_TXB2,
   TOY_OPCODE_TGSI_TXL2,
   TOY_OPCODE_TGSI_SAMPLE,
   TOY_OPCODE_TGSI_SAMPLE_I,
   TOY_OPCODE_TGSI_SAMPLE_I_MS,
   TOY_OPCODE_TGSI_SAMPLE_B,
   TOY_OPCODE_TGSI_SAMPLE_C,
   TOY_OPCODE_TGSI_SAMPLE_C_LZ,
   TOY_OPCODE_TGSI_SAMPLE_D,
   TOY_OPCODE_TGSI_SAMPLE_L,
   TOY_OPCODE_TGSI_GATHER4,
   TOY_OPCODE_TGSI_SVIEWINFO,
   TOY_OPCODE_TGSI_SAMPLE_POS,
   TOY_OPCODE_TGSI_SAMPLE_INFO,

   /* math functions */
   TOY_OPCODE_INV,
   TOY_OPCODE_LOG,
   TOY_OPCODE_EXP,
   TOY_OPCODE_SQRT,
   TOY_OPCODE_RSQ,
   TOY_OPCODE_SIN,
   TOY_OPCODE_COS,
   TOY_OPCODE_FDIV,
   TOY_OPCODE_POW,
   TOY_OPCODE_INT_DIV_QUOTIENT,
   TOY_OPCODE_INT_DIV_REMAINDER,

   /* URB functions */
   TOY_OPCODE_URB_WRITE,

   /* GS-specific functions */
   TOY_OPCODE_EMIT,
   TOY_OPCODE_ENDPRIM,

   /* FS-specific functions */
   TOY_OPCODE_DDX,
   TOY_OPCODE_DDY,
   TOY_OPCODE_FB_WRITE,
   TOY_OPCODE_KIL,
};

/**
 * Toy instruction.
 */
struct toy_inst {
   unsigned opcode:8;            /* enum toy_opcode      */
   unsigned access_mode:1;       /* BRW_ALIGN_x          */
   unsigned mask_ctrl:1;         /* BRW_MASK_x           */
   unsigned dep_ctrl:2;          /* BRW_DEPENDENCY_x     */
   unsigned qtr_ctrl:2;          /* GEN6_COMPRESSION_x   */
   unsigned thread_ctrl:2;       /* BRW_THREAD_x         */
   unsigned pred_ctrl:4;         /* BRW_PREDICATE_x      */
   unsigned pred_inv:1;          /* true or false        */
   unsigned exec_size:3;         /* BRW_EXECUTE_x        */
   unsigned cond_modifier:4;     /* BRW_CONDITIONAL_x    */
   unsigned acc_wr_ctrl:1;       /* true or false        */
   unsigned saturate:1;          /* true or false        */

   /* true if the instruction should be ignored for instruction iteration */
   unsigned marker:1;

   unsigned pad:1;

   struct toy_dst dst;
   struct toy_src src[5];        /* match TGSI_FULL_MAX_SRC_REGISTERS */

   struct {
      int target;                /* TGSI_TEXTURE_x */
      struct toy_src offsets[1]; /* need to be 4 when GATHER4 is supported */
   } tex;

   struct list_head list;
};

/**
 * Toy compiler.
 */
struct toy_compiler {
   const struct ilo_dev_info *dev;

   struct toy_inst templ;
   struct util_slab_mempool mempool;
   struct list_head instructions;
   struct list_head *iter, *iter_next;

   /* this is not set until toy_compiler_legalize_for_asm() */
   int num_instructions;

   int rect_linear_width;
   int next_vrf;

   bool fail;
   const char *reason;
};

/**
 * Allocate the given number of VRF registers.
 */
static inline int
tc_alloc_vrf(struct toy_compiler *tc, int count)
{
   const int vrf = tc->next_vrf;

   tc->next_vrf += count;

   return vrf;
}

/**
 * Allocate a temporary register.
 */
static inline struct toy_dst
tc_alloc_tmp(struct toy_compiler *tc)
{
   return tdst(TOY_FILE_VRF, tc_alloc_vrf(tc, 1), 0);
}

/**
 * Allocate four temporary registers.
 */
static inline void
tc_alloc_tmp4(struct toy_compiler *tc, struct toy_dst *tmp)
{
   tmp[0] = tc_alloc_tmp(tc);
   tmp[1] = tc_alloc_tmp(tc);
   tmp[2] = tc_alloc_tmp(tc);
   tmp[3] = tc_alloc_tmp(tc);
}

/**
 * Duplicate an instruction at the current location.
 */
static inline struct toy_inst *
tc_duplicate_inst(struct toy_compiler *tc, const struct toy_inst *inst)
{
   struct toy_inst *new_inst;

   new_inst = util_slab_alloc(&tc->mempool);
   if (!new_inst)
      return NULL;

   *new_inst = *inst;
   list_addtail(&new_inst->list, tc->iter_next);

   return new_inst;
}

/**
 * Move an instruction to the current location.
 */
static inline void
tc_move_inst(struct toy_compiler *tc, struct toy_inst *inst)
{
   list_del(&inst->list);
   list_addtail(&inst->list, tc->iter_next);
}

/**
 * Discard an instruction.
 */
static inline void
tc_discard_inst(struct toy_compiler *tc, struct toy_inst *inst)
{
   list_del(&inst->list);
   util_slab_free(&tc->mempool, inst);
}

/**
 * Add a new instruction at the current location, using tc->templ as the
 * template.
 */
static inline struct toy_inst *
tc_add(struct toy_compiler *tc)
{
   return tc_duplicate_inst(tc, &tc->templ);
}

/**
 * A convenient version of tc_add() for instructions with 3 source operands.
 */
static inline struct toy_inst *
tc_add3(struct toy_compiler *tc, unsigned opcode,
        struct toy_dst dst,
        struct toy_src src0,
        struct toy_src src1,
        struct toy_src src2)
{
   struct toy_inst *inst;

   inst = tc_add(tc);
   if (!inst)
      return NULL;

   inst->opcode = opcode;
   inst->dst = dst;
   inst->src[0] = src0;
   inst->src[1] = src1;
   inst->src[2] = src2;

   return inst;
}

/**
 * A convenient version of tc_add() for instructions with 2 source operands.
 */
static inline struct toy_inst *
tc_add2(struct toy_compiler *tc, int opcode,
            struct toy_dst dst,
            struct toy_src src0,
            struct toy_src src1)
{
   return tc_add3(tc, opcode, dst, src0, src1, tsrc_null());
}

/**
 * A convenient version of tc_add() for instructions with 1 source operand.
 */
static inline struct toy_inst *
tc_add1(struct toy_compiler *tc, unsigned opcode,
        struct toy_dst dst,
        struct toy_src src0)
{
   return tc_add2(tc, opcode, dst, src0, tsrc_null());
}

/**
 * A convenient version of tc_add() for instructions without source or
 * destination operands.
 */
static inline struct toy_inst *
tc_add0(struct toy_compiler *tc, unsigned opcode)
{
   return tc_add1(tc, opcode, tdst_null(), tsrc_null());
}

#define TC_ALU0(func, opcode)             \
static inline struct toy_inst *           \
func(struct toy_compiler *tc)             \
{                                         \
   return tc_add0(tc, opcode);            \
}

#define TC_ALU1(func, opcode)             \
static inline struct toy_inst *           \
func(struct toy_compiler *tc,             \
     struct toy_dst dst,                  \
     struct toy_src src)                  \
{                                         \
   return tc_add1(tc, opcode, dst, src);  \
}

#define TC_ALU2(func, opcode)             \
static inline struct toy_inst *           \
func(struct toy_compiler *tc,             \
     struct toy_dst dst,                  \
     struct toy_src src0,                 \
     struct toy_src src1)                 \
{                                         \
   return tc_add2(tc, opcode,             \
         dst, src0, src1);                \
}

#define TC_ALU3(func, opcode)             \
static inline struct toy_inst *           \
func(struct toy_compiler *tc,             \
     struct toy_dst dst,                  \
     struct toy_src src0,                 \
     struct toy_src src1,                 \
     struct toy_src src2)                 \
{                                         \
   return tc_add3(tc, opcode,             \
         dst, src0, src1, src2);          \
}

#define TC_CND2(func, opcode)             \
static inline struct toy_inst *           \
func(struct toy_compiler *tc,             \
     struct toy_dst dst,                  \
     struct toy_src src0,                 \
     struct toy_src src1,                 \
     unsigned cond_modifier)              \
{                                         \
   struct toy_inst *inst;                 \
   inst = tc_add2(tc, opcode,             \
         dst, src0, src1);                \
   inst->cond_modifier = cond_modifier;   \
   return inst;                           \
}

TC_ALU0(tc_NOP, BRW_OPCODE_NOP)
TC_ALU0(tc_ELSE, BRW_OPCODE_ELSE)
TC_ALU0(tc_ENDIF, BRW_OPCODE_ENDIF)
TC_ALU1(tc_MOV, BRW_OPCODE_MOV)
TC_ALU1(tc_RNDD, BRW_OPCODE_RNDD)
TC_ALU1(tc_INV, TOY_OPCODE_INV)
TC_ALU1(tc_FRC, BRW_OPCODE_FRC)
TC_ALU1(tc_EXP, TOY_OPCODE_EXP)
TC_ALU1(tc_LOG, TOY_OPCODE_LOG)
TC_ALU2(tc_ADD, BRW_OPCODE_ADD)
TC_ALU2(tc_MUL, BRW_OPCODE_MUL)
TC_ALU2(tc_AND, BRW_OPCODE_AND)
TC_ALU2(tc_OR, BRW_OPCODE_OR)
TC_ALU2(tc_DP2, BRW_OPCODE_DP2)
TC_ALU2(tc_DP3, BRW_OPCODE_DP3)
TC_ALU2(tc_DP4, BRW_OPCODE_DP4)
TC_ALU2(tc_SHL, BRW_OPCODE_SHL)
TC_ALU2(tc_SHR, BRW_OPCODE_SHR)
TC_ALU2(tc_POW, TOY_OPCODE_POW)
TC_ALU3(tc_MAC, BRW_OPCODE_MAC)
TC_CND2(tc_SEL, BRW_OPCODE_SEL)
TC_CND2(tc_CMP, BRW_OPCODE_CMP)
TC_CND2(tc_IF, BRW_OPCODE_IF)
TC_CND2(tc_SEND, BRW_OPCODE_SEND)

/**
 * Upcast a list_head to an instruction.
 */
static inline struct toy_inst *
tc_list_to_inst(struct toy_compiler *tc, struct list_head *item)
{
   return container_of(item, (struct toy_inst *) NULL, list);
}

/**
 * Return the instruction at the current location.
 */
static inline struct toy_inst *
tc_current(struct toy_compiler *tc)
{
   return (tc->iter != &tc->instructions) ?
      tc_list_to_inst(tc, tc->iter) : NULL;
}

/**
 * Set the current location to the head.
 */
static inline void
tc_head(struct toy_compiler *tc)
{
   tc->iter = &tc->instructions;
   tc->iter_next = tc->iter->next;
}

/**
 * Set the current location to the tail.
 */
static inline void
tc_tail(struct toy_compiler *tc)
{
   tc->iter = &tc->instructions;
   tc->iter_next = tc->iter;
}

/**
 * Advance the current location.
 */
static inline struct toy_inst *
tc_next_no_skip(struct toy_compiler *tc)
{
   /* stay at the tail so that new instructions are added there */
   if (tc->iter_next == &tc->instructions) {
      tc_tail(tc);
      return NULL;
   }

   tc->iter = tc->iter_next;
   tc->iter_next = tc->iter_next->next;

   return tc_list_to_inst(tc, tc->iter);
}

/**
 * Advance the current location, skipping markers.
 */
static inline struct toy_inst *
tc_next(struct toy_compiler *tc)
{
   struct toy_inst *inst;

   do {
      inst = tc_next_no_skip(tc);
   } while (inst && inst->marker);

   return inst;
}

static inline void
tc_fail(struct toy_compiler *tc, const char *reason)
{
   if (!tc->fail) {
      tc->fail = true;
      tc->reason = reason;
   }
}

void
toy_compiler_init(struct toy_compiler *tc, const struct ilo_dev_info *dev);

void
toy_compiler_cleanup(struct toy_compiler *tc);

void
toy_compiler_dump(struct toy_compiler *tc);

void *
toy_compiler_assemble(struct toy_compiler *tc, int *size);

void
toy_compiler_disassemble(struct toy_compiler *tc, const void *kernel, int size);

#endif /* TOY_COMPILER_H */
