
#include "nv50_ir.h"
#include "nv50_ir_target.h"
#include "nv50_ir_build_util.h"

#include "nv50_ir_from_sm4.h"

// WTF: pass-through is implicit ??? check ReadWriteMask

namespace tgsi {

static nv50_ir::SVSemantic irSemantic(unsigned sn)
{
   switch (sn) {
   case TGSI_SEMANTIC_POSITION:      return nv50_ir::SV_POSITION;
   case TGSI_SEMANTIC_FACE:          return nv50_ir::SV_FACE;
   case NV50_SEMANTIC_LAYER:         return nv50_ir::SV_LAYER;
   case NV50_SEMANTIC_VIEWPORTINDEX: return nv50_ir::SV_VIEWPORT_INDEX;
   case TGSI_SEMANTIC_PSIZE:         return nv50_ir::SV_POINT_SIZE;
   case NV50_SEMANTIC_CLIPDISTANCE:  return nv50_ir::SV_CLIP_DISTANCE;
   case NV50_SEMANTIC_VERTEXID:      return nv50_ir::SV_VERTEX_ID;
   case TGSI_SEMANTIC_INSTANCEID:    return nv50_ir::SV_INSTANCE_ID;
   case TGSI_SEMANTIC_PRIMID:        return nv50_ir::SV_PRIMITIVE_ID;
   case NV50_SEMANTIC_TESSFACTOR:    return nv50_ir::SV_TESS_FACTOR;
   case NV50_SEMANTIC_TESSCOORD:     return nv50_ir::SV_TESS_COORD;
   default:
      return nv50_ir::SV_UNDEFINED;
   }
}

} // namespace tgsi

namespace {

using namespace nv50_ir;

#define NV50_IR_MAX_RESOURCES 64

class Converter : public BuildUtil
{
public:
   Converter(Program *, struct nv50_ir_prog_info *);
   ~Converter();

private:
   DataArray tData32;
   DataArray tData64;
   unsigned int nrRegVals;

   DataArray *lData;
   unsigned int nrArrays;
   unsigned int arrayVol;

   DataArray oData;

   uint8_t interpMode[PIPE_MAX_SHADER_INPUTS];

   // outputs for each phase
   struct nv50_ir_varying out[3][PIPE_MAX_SHADER_OUTPUTS];

   int phase;
   int subPhaseCnt[2];
   int subPhase;
   unsigned int phaseStart;
   unsigned int phaseInstance;
   unsigned int *phaseInstCnt[2];
   bool unrollPhase;
   bool phaseInstanceUsed;
   int phaseEnded; // (phase + 1) if $phase ended

   bool finalized;

   Value *srcPtr[3][3]; // for indirect addressing, save pointer values
   Value *dstPtr[3];
   Value *vtxBase[3]; // base address of vertex in a primitive (TP/GP)

   Value *domainPt[3]; // pre-fetched TessCoord

   unsigned int nDstOpnds;

   Stack condBBs;
   Stack joinBBs;
   Stack loopBBs;
   Stack breakBBs;
   Stack entryBBs;
   Stack leaveBBs;
   Stack retIPs;

   bool shadow[NV50_IR_MAX_RESOURCES];
   TexTarget resourceType[NV50_IR_MAX_RESOURCES][2];

   struct nv50_ir_prog_info& info;

   Value *fragCoord[4];

public:
   bool run();

private:
   bool handleInstruction(unsigned int pos);
   bool inspectInstruction(unsigned int pos);
   bool handleDeclaration(const sm4_dcl& dcl);
   bool inspectDeclaration(const sm4_dcl& dcl);
   bool parseSignature();

   bool haveNextPhase(unsigned int pos) const;

   void allocateValues();
   void exportOutputs();

   void emitTex(Value *dst0[4], TexInstruction *, const uint8_t swizzle[4]);
   void handleLOAD(Value *dst0[4]);
   void handleSAMPLE(operation, Value *dst0[4]);
   void handleQUERY(Value *dst0[4], enum TexQuery query);
   void handleDP(Value *dst0[4], int dim);

   Symbol *iSym(int i, int c);
   Symbol *oSym(int i, int c);

   Value *src(int i, int c);
   Value *src(const sm4_op&, int c, int i);
   Value *dst(int i, int c);
   Value *dst(const sm4_op&, int c, int i);
   void saveDst(int i, int c, Value *value);
   void saveDst(const sm4_op&, int c, Value *value, int i);
   void saveFragDepth(operation op, Value *value);

   Value *interpolate(const sm4_op&, int c, int i);

   Value *getSrcPtr(int s, int dim, int shl);
   Value *getDstPtr(int d, int dim, int shl);
   Value *getVtxPtr(int s);

   bool checkDstSrcAliasing() const;
   void insertConvergenceOps(BasicBlock *conv, BasicBlock *fork);
   void finalizeShader();

   operation cvtOpcode(enum sm4_opcode op) const;
   unsigned int getDstOpndCount(enum sm4_opcode opcode) const;

   DataType inferSrcType(enum sm4_opcode op) const;
   DataType inferDstType(enum sm4_opcode op) const;

   unsigned g3dPrim(const unsigned prim, unsigned *patchSize = NULL) const;
   CondCode cvtCondCode(enum sm4_opcode op) const;
   RoundMode cvtRoundingMode(enum sm4_opcode op) const;
   TexTarget cvtTexTarget(enum sm4_target,
                           enum sm4_opcode, operation *) const;
   SVSemantic cvtSemantic(enum sm4_sv, uint8_t &index) const;
   uint8_t cvtInterpMode(enum sm4_interpolation) const;

   unsigned tgsiSemantic(SVSemantic, int index);
   void recordSV(unsigned sn, unsigned si, unsigned mask, bool input);

private:
   sm4_insn *insn;
   DataType dTy, sTy;

   const struct sm4_program& sm4;
   Program *prog;
};

#define PRIM_CASE(a, b) \
   case D3D_PRIMITIVE_TOPOLOGY_##a: return PIPE_PRIM_##b;

unsigned
Converter::g3dPrim(const unsigned prim, unsigned *patchSize) const
{
   switch (prim) {
   PRIM_CASE(UNDEFINED, POINTS);
   PRIM_CASE(POINTLIST, POINTS);
   PRIM_CASE(LINELIST, LINES);
   PRIM_CASE(LINESTRIP, LINE_STRIP);
   PRIM_CASE(TRIANGLELIST, TRIANGLES);
   PRIM_CASE(TRIANGLESTRIP, TRIANGLE_STRIP);
   PRIM_CASE(LINELIST_ADJ, LINES_ADJACENCY);
   PRIM_CASE(LINESTRIP_ADJ, LINE_STRIP_ADJACENCY);
   PRIM_CASE(TRIANGLELIST_ADJ, TRIANGLES_ADJACENCY);
   PRIM_CASE(TRIANGLESTRIP_ADJ, TRIANGLES_ADJACENCY);
   default:
      if (prim < D3D_PRIMITIVE_TOPOLOGY_1_CONTROL_POINT_PATCHLIST ||
          prim > D3D_PRIMITIVE_TOPOLOGY_32_CONTROL_POINT_PATCHLIST)
         return PIPE_PRIM_POINTS;
      if (patchSize)
         *patchSize =
            prim - D3D_PRIMITIVE_TOPOLOGY_1_CONTROL_POINT_PATCHLIST + 1;
      return NV50_PRIM_PATCHES;
   }
}

#define IPM_CASE(n, a, b) \
   case SM4_INTERPOLATION_##n: return NV50_IR_INTERP_##a | NV50_IR_INTERP_##b

uint8_t
Converter::cvtInterpMode(enum sm4_interpolation mode) const
{
   switch (mode) {
   IPM_CASE(CONSTANT,                      FLAT, FLAT);
   IPM_CASE(LINEAR,                        PERSPECTIVE, PERSPECTIVE);
   IPM_CASE(LINEAR_CENTROID,               PERSPECTIVE, CENTROID);
   IPM_CASE(LINEAR_NOPERSPECTIVE,          LINEAR, LINEAR);
   IPM_CASE(LINEAR_NOPERSPECTIVE_CENTROID, LINEAR, CENTROID);
   IPM_CASE(LINEAR_SAMPLE,                 PERSPECTIVE, OFFSET);
   IPM_CASE(LINEAR_NOPERSPECTIVE_SAMPLE,   LINEAR, OFFSET);
   IPM_CASE(UNDEFINED,                     LINEAR, LINEAR);
   default:
      assert(!"invalid interpolation mode");
      return 0;
   }
}

static void
setVaryingInterpMode(struct nv50_ir_varying *var, uint8_t mode)
{
   switch (mode & NV50_IR_INTERP_MODE_MASK) {
   case NV50_IR_INTERP_LINEAR:
      var->linear = 1;
      break;
   case NV50_IR_INTERP_FLAT:
      var->flat = 1;
      break;
   default:
      break;
   }
   if (mode & NV50_IR_INTERP_CENTROID)
      var->centroid = 1;
}

RoundMode
Converter::cvtRoundingMode(enum sm4_opcode op) const
{
   switch (op) {
   case SM4_OPCODE_ROUND_NE: return ROUND_NI;
   case SM4_OPCODE_ROUND_NI: return ROUND_MI;
   case SM4_OPCODE_ROUND_PI: return ROUND_PI;
   case SM4_OPCODE_ROUND_Z:  return ROUND_ZI;
   default:
      return ROUND_N;
   }
}

CondCode
Converter::cvtCondCode(enum sm4_opcode op) const
{
   switch (op) {
   case SM4_OPCODE_EQ:
   case SM4_OPCODE_DEQ:
   case SM4_OPCODE_IEQ: return CC_EQ;
   case SM4_OPCODE_GE:
   case SM4_OPCODE_DGE:
   case SM4_OPCODE_IGE:
   case SM4_OPCODE_UGE: return CC_GE;
   case SM4_OPCODE_LT:
   case SM4_OPCODE_DLT:
   case SM4_OPCODE_ILT:
   case SM4_OPCODE_ULT: return CC_LT;
   case SM4_OPCODE_NE:
   case SM4_OPCODE_INE:
   case SM4_OPCODE_DNE: return CC_NEU;
   default:
      return CC_ALWAYS;
   }
}

DataType
Converter::inferSrcType(enum sm4_opcode op) const
{
   switch (op) {
   case SM4_OPCODE_IADD:
   case SM4_OPCODE_IEQ:
   case SM4_OPCODE_IGE:
   case SM4_OPCODE_ILT:
   case SM4_OPCODE_IMAD:
   case SM4_OPCODE_IMAX:
   case SM4_OPCODE_IMIN:
   case SM4_OPCODE_IMUL:
   case SM4_OPCODE_INE:
   case SM4_OPCODE_INEG:
   case SM4_OPCODE_ISHL:
   case SM4_OPCODE_ISHR:
   case SM4_OPCODE_ITOF:
   case SM4_OPCODE_ATOMIC_IADD:
   case SM4_OPCODE_ATOMIC_IMAX:
   case SM4_OPCODE_ATOMIC_IMIN:
      return TYPE_S32;
   case SM4_OPCODE_AND:
   case SM4_OPCODE_NOT:
   case SM4_OPCODE_OR:
   case SM4_OPCODE_UDIV:
   case SM4_OPCODE_ULT:
   case SM4_OPCODE_UGE:
   case SM4_OPCODE_UMUL:
   case SM4_OPCODE_UMAD:
   case SM4_OPCODE_UMAX:
   case SM4_OPCODE_UMIN:
   case SM4_OPCODE_USHR:
   case SM4_OPCODE_UTOF:
   case SM4_OPCODE_XOR:
   case SM4_OPCODE_UADDC:
   case SM4_OPCODE_USUBB:
   case SM4_OPCODE_ATOMIC_AND:
   case SM4_OPCODE_ATOMIC_OR:
   case SM4_OPCODE_ATOMIC_XOR:
   case SM4_OPCODE_ATOMIC_UMAX:
   case SM4_OPCODE_ATOMIC_UMIN:
      return TYPE_U32;
   case SM4_OPCODE_DADD:
   case SM4_OPCODE_DMAX:
   case SM4_OPCODE_DMIN:
   case SM4_OPCODE_DMUL:
   case SM4_OPCODE_DEQ:
   case SM4_OPCODE_DGE:
   case SM4_OPCODE_DLT:
   case SM4_OPCODE_DNE:
   case SM4_OPCODE_DMOV:
   case SM4_OPCODE_DMOVC:
   case SM4_OPCODE_DTOF:
      return TYPE_F64;
   case SM4_OPCODE_F16TOF32:
      return TYPE_F16;
   default:
      return TYPE_F32;
   }
}

DataType
Converter::inferDstType(enum sm4_opcode op) const
{
   switch (op) {
   case SM4_OPCODE_FTOI:
      return TYPE_S32;
   case SM4_OPCODE_FTOU:
   case SM4_OPCODE_EQ:
   case SM4_OPCODE_GE:
   case SM4_OPCODE_LT:
   case SM4_OPCODE_NE:
      return TYPE_U32;
   case SM4_OPCODE_FTOD:
      return TYPE_F64;
   case SM4_OPCODE_F32TOF16:
      return TYPE_F16;
   case SM4_OPCODE_ITOF:
   case SM4_OPCODE_UTOF:
   case SM4_OPCODE_DTOF:
      return TYPE_F32;
   default:
      return inferSrcType(op);
   }
}

operation
Converter::cvtOpcode(enum sm4_opcode op) const
{
   switch (op) {
   case SM4_OPCODE_ADD:         return OP_ADD;
   case SM4_OPCODE_AND:         return OP_AND;
   case SM4_OPCODE_BREAK:       return OP_BREAK;
   case SM4_OPCODE_BREAKC:      return OP_BREAK;
   case SM4_OPCODE_CALL:        return OP_CALL;
   case SM4_OPCODE_CALLC:       return OP_CALL;
   case SM4_OPCODE_CASE:        return OP_NOP;
   case SM4_OPCODE_CONTINUE:    return OP_CONT;
   case SM4_OPCODE_CONTINUEC:   return OP_CONT;
   case SM4_OPCODE_CUT:         return OP_RESTART;
   case SM4_OPCODE_DEFAULT:     return OP_NOP;
   case SM4_OPCODE_DERIV_RTX:   return OP_DFDX;
   case SM4_OPCODE_DERIV_RTY:   return OP_DFDY;
   case SM4_OPCODE_DISCARD:     return OP_DISCARD;
   case SM4_OPCODE_DIV:         return OP_DIV;
   case SM4_OPCODE_DP2:         return OP_MAD;
   case SM4_OPCODE_DP3:         return OP_MAD;
   case SM4_OPCODE_DP4:         return OP_MAD;
   case SM4_OPCODE_ELSE:        return OP_BRA;
   case SM4_OPCODE_EMIT:        return OP_EMIT;
   case SM4_OPCODE_EMITTHENCUT: return OP_EMIT;
   case SM4_OPCODE_ENDIF:       return OP_BRA;
   case SM4_OPCODE_ENDLOOP:     return OP_PREBREAK;
   case SM4_OPCODE_ENDSWITCH:   return OP_NOP;
   case SM4_OPCODE_EQ:          return OP_SET;
   case SM4_OPCODE_EXP:         return OP_EX2;
   case SM4_OPCODE_FRC:         return OP_CVT;
   case SM4_OPCODE_FTOI:        return OP_CVT;
   case SM4_OPCODE_FTOU:        return OP_CVT;
   case SM4_OPCODE_GE:          return OP_SET;
   case SM4_OPCODE_IADD:        return OP_ADD;
   case SM4_OPCODE_IF:          return OP_BRA;
   case SM4_OPCODE_IEQ:         return OP_SET;
   case SM4_OPCODE_IGE:         return OP_SET;
   case SM4_OPCODE_ILT:         return OP_SET;
   case SM4_OPCODE_IMAD:        return OP_MAD;
   case SM4_OPCODE_IMAX:        return OP_MAX;
   case SM4_OPCODE_IMIN:        return OP_MIN;
   case SM4_OPCODE_IMUL:        return OP_MUL;
   case SM4_OPCODE_INE:         return OP_SET;
   case SM4_OPCODE_INEG:        return OP_NEG;
   case SM4_OPCODE_ISHL:        return OP_SHL;
   case SM4_OPCODE_ISHR:        return OP_SHR;
   case SM4_OPCODE_ITOF:        return OP_CVT;
   case SM4_OPCODE_LD:          return OP_TXF;
   case SM4_OPCODE_LD_MS:       return OP_TXF;
   case SM4_OPCODE_LOG:         return OP_LG2;
   case SM4_OPCODE_LOOP:        return OP_PRECONT;
   case SM4_OPCODE_LT:          return OP_SET;
   case SM4_OPCODE_MAD:         return OP_MAD;
   case SM4_OPCODE_MIN:         return OP_MIN;
   case SM4_OPCODE_MAX:         return OP_MAX;
   case SM4_OPCODE_MOV:         return OP_MOV;
   case SM4_OPCODE_MOVC:        return OP_MOV;
   case SM4_OPCODE_MUL:         return OP_MUL;
   case SM4_OPCODE_NE:          return OP_SET;
   case SM4_OPCODE_NOP:         return OP_NOP;
   case SM4_OPCODE_NOT:         return OP_NOT;
   case SM4_OPCODE_OR:          return OP_OR;
   case SM4_OPCODE_RESINFO:     return OP_TXQ;
   case SM4_OPCODE_RET:         return OP_RET;
   case SM4_OPCODE_RETC:        return OP_RET;
   case SM4_OPCODE_ROUND_NE:    return OP_CVT;
   case SM4_OPCODE_ROUND_NI:    return OP_FLOOR;
   case SM4_OPCODE_ROUND_PI:    return OP_CEIL;
   case SM4_OPCODE_ROUND_Z:     return OP_TRUNC;
   case SM4_OPCODE_RSQ:         return OP_RSQ;
   case SM4_OPCODE_SAMPLE:      return OP_TEX;
   case SM4_OPCODE_SAMPLE_C:    return OP_TEX;
   case SM4_OPCODE_SAMPLE_C_LZ: return OP_TEX;
   case SM4_OPCODE_SAMPLE_L:    return OP_TXL;
   case SM4_OPCODE_SAMPLE_D:    return OP_TXD;
   case SM4_OPCODE_SAMPLE_B:    return OP_TXB;
   case SM4_OPCODE_SQRT:        return OP_SQRT;
   case SM4_OPCODE_SWITCH:      return OP_NOP;
   case SM4_OPCODE_SINCOS:      return OP_PRESIN;
   case SM4_OPCODE_UDIV:        return OP_DIV;
   case SM4_OPCODE_ULT:         return OP_SET;
   case SM4_OPCODE_UGE:         return OP_SET;
   case SM4_OPCODE_UMUL:        return OP_MUL;
   case SM4_OPCODE_UMAD:        return OP_MAD;
   case SM4_OPCODE_UMAX:        return OP_MAX;
   case SM4_OPCODE_UMIN:        return OP_MIN;
   case SM4_OPCODE_USHR:        return OP_SHR;
   case SM4_OPCODE_UTOF:        return OP_CVT;
   case SM4_OPCODE_XOR:         return OP_XOR;

   case SM4_OPCODE_GATHER4:            return OP_TXG;
   case SM4_OPCODE_SAMPLE_POS:         return OP_PIXLD;
   case SM4_OPCODE_SAMPLE_INFO:        return OP_PIXLD;
   case SM4_OPCODE_EMIT_STREAM:        return OP_EMIT;
   case SM4_OPCODE_CUT_STREAM:         return OP_RESTART;
   case SM4_OPCODE_EMITTHENCUT_STREAM: return OP_EMIT;
   case SM4_OPCODE_INTERFACE_CALL:     return OP_CALL;
   case SM4_OPCODE_BUFINFO:            return OP_TXQ;
   case SM4_OPCODE_DERIV_RTX_COARSE:   return OP_DFDX;
   case SM4_OPCODE_DERIV_RTX_FINE:     return OP_DFDX;
   case SM4_OPCODE_DERIV_RTY_COARSE:   return OP_DFDY;
   case SM4_OPCODE_DERIV_RTY_FINE:     return OP_DFDY;
   case SM4_OPCODE_GATHER4_C:          return OP_TXG;
   case SM4_OPCODE_GATHER4_PO:         return OP_TXG;
   case SM4_OPCODE_GATHER4_PO_C:       return OP_TXG;

   case SM4_OPCODE_RCP:       return OP_RCP;
   case SM4_OPCODE_F32TOF16:  return OP_CVT;
   case SM4_OPCODE_F16TOF32:  return OP_CVT;
   case SM4_OPCODE_UADDC:     return OP_ADD;
   case SM4_OPCODE_USUBB:     return OP_SUB;
   case SM4_OPCODE_COUNTBITS: return OP_POPCNT;

   case SM4_OPCODE_ATOMIC_AND:       return OP_AND;
   case SM4_OPCODE_ATOMIC_OR:        return OP_OR;
   case SM4_OPCODE_ATOMIC_XOR:       return OP_XOR;
   case SM4_OPCODE_ATOMIC_CMP_STORE: return OP_STORE;
   case SM4_OPCODE_ATOMIC_IADD:      return OP_ADD;
   case SM4_OPCODE_ATOMIC_IMAX:      return OP_MAX;
   case SM4_OPCODE_ATOMIC_IMIN:      return OP_MIN;
   case SM4_OPCODE_ATOMIC_UMAX:      return OP_MAX;
   case SM4_OPCODE_ATOMIC_UMIN:      return OP_MIN;

   case SM4_OPCODE_SYNC:  return OP_MEMBAR;
   case SM4_OPCODE_DADD:  return OP_ADD;
   case SM4_OPCODE_DMAX:  return OP_MAX;
   case SM4_OPCODE_DMIN:  return OP_MIN;
   case SM4_OPCODE_DMUL:  return OP_MUL;
   case SM4_OPCODE_DEQ:   return OP_SET;
   case SM4_OPCODE_DGE:   return OP_SET;
   case SM4_OPCODE_DLT:   return OP_SET;
   case SM4_OPCODE_DNE:   return OP_SET;
   case SM4_OPCODE_DMOV:  return OP_MOV;
   case SM4_OPCODE_DMOVC: return OP_MOV;
   case SM4_OPCODE_DTOF:  return OP_CVT;
   case SM4_OPCODE_FTOD:  return OP_CVT;

   default:
      return OP_NOP;
   }
}

unsigned int
Converter::getDstOpndCount(enum sm4_opcode opcode) const
{
   switch (opcode) {
   case SM4_OPCODE_SINCOS:
   case SM4_OPCODE_UDIV:
   case SM4_OPCODE_IMUL:
   case SM4_OPCODE_UMUL:
      return 2;
   case SM4_OPCODE_BREAK:
   case SM4_OPCODE_BREAKC:
   case SM4_OPCODE_CALL:
   case SM4_OPCODE_CALLC:
   case SM4_OPCODE_CONTINUE:
   case SM4_OPCODE_CONTINUEC:
   case SM4_OPCODE_DISCARD:
   case SM4_OPCODE_EMIT:
   case SM4_OPCODE_EMIT_STREAM:
   case SM4_OPCODE_CUT:
   case SM4_OPCODE_CUT_STREAM:
   case SM4_OPCODE_EMITTHENCUT:
   case SM4_OPCODE_EMITTHENCUT_STREAM:
   case SM4_OPCODE_IF:
   case SM4_OPCODE_ELSE:
   case SM4_OPCODE_ENDIF:
   case SM4_OPCODE_LOOP:
   case SM4_OPCODE_ENDLOOP:
   case SM4_OPCODE_RET:
   case SM4_OPCODE_RETC:
   case SM4_OPCODE_SYNC:
   case SM4_OPCODE_SWITCH:
   case SM4_OPCODE_CASE:
   case SM4_OPCODE_HS_DECLS:
   case SM4_OPCODE_HS_CONTROL_POINT_PHASE:
   case SM4_OPCODE_HS_FORK_PHASE:
   case SM4_OPCODE_HS_JOIN_PHASE:
      return 0;
   default:
      return 1;
   }
}

#define TARG_CASE_1(a, b) case SM4_TARGET_##a: return TEX_TARGET_##b;
#define TARG_CASE_2(a, b) case SM4_TARGET_##a: \
   return dc ? TEX_TARGET_##b##_SHADOW : TEX_TARGET_##b

TexTarget
Converter::cvtTexTarget(enum sm4_target targ,
                        enum sm4_opcode op, operation *opr) const
{
   bool dc = (op == SM4_OPCODE_SAMPLE_C ||
              op == SM4_OPCODE_SAMPLE_C_LZ ||
              op == SM4_OPCODE_GATHER4_C ||
              op == SM4_OPCODE_GATHER4_PO_C);

   if (opr) {
      switch (targ) {
      case SM4_TARGET_RAW_BUFFER:        *opr = OP_LOAD; break;
      case SM4_TARGET_STRUCTURED_BUFFER: *opr = OP_SULD; break;
      default:
         *opr = OP_TEX;
         break;
      }
   }

   switch (targ) {
   TARG_CASE_1(UNKNOWN, 2D);
   TARG_CASE_2(TEXTURE1D,         1D);
   TARG_CASE_2(TEXTURE2D,         2D);
   TARG_CASE_1(TEXTURE2DMS,       2D_MS);
   TARG_CASE_1(TEXTURE3D,         3D);
   TARG_CASE_2(TEXTURECUBE,       CUBE);
   TARG_CASE_2(TEXTURE1DARRAY,    1D_ARRAY);
   TARG_CASE_2(TEXTURE2DARRAY,    2D_ARRAY);
   TARG_CASE_1(TEXTURE2DMSARRAY,  2D_MS_ARRAY);
   TARG_CASE_2(TEXTURECUBEARRAY,  CUBE_ARRAY);
   TARG_CASE_1(BUFFER,            BUFFER);
   TARG_CASE_1(RAW_BUFFER,        BUFFER);
   TARG_CASE_1(STRUCTURED_BUFFER, BUFFER);
   default:
      assert(!"invalid SM4 texture target");
      return dc ? TEX_TARGET_2D_SHADOW : TEX_TARGET_2D;
   }
}

static inline uint32_t
getSVIndex(enum sm4_sv sv)
{
   switch (sv) {
   case SM4_SV_FINAL_QUAD_U_EQ_0_EDGE_TESSFACTOR: return 0;
   case SM4_SV_FINAL_QUAD_V_EQ_0_EDGE_TESSFACTOR: return 1;
   case SM4_SV_FINAL_QUAD_U_EQ_1_EDGE_TESSFACTOR: return 2;
   case SM4_SV_FINAL_QUAD_V_EQ_1_EDGE_TESSFACTOR: return 3;

   case SM4_SV_FINAL_QUAD_U_INSIDE_TESSFACTOR: return 4;
   case SM4_SV_FINAL_QUAD_V_INSIDE_TESSFACTOR: return 5;

   case SM4_SV_FINAL_TRI_U_EQ_0_EDGE_TESSFACTOR: return 0;
   case SM4_SV_FINAL_TRI_V_EQ_0_EDGE_TESSFACTOR: return 1;
   case SM4_SV_FINAL_TRI_W_EQ_0_EDGE_TESSFACTOR: return 2;

   case SM4_SV_FINAL_TRI_INSIDE_TESSFACTOR: return 4;

   case SM4_SV_FINAL_LINE_DETAIL_TESSFACTOR: return 0;

   case SM4_SV_FINAL_LINE_DENSITY_TESSFACTOR: return 4;

   default:
      return 0;
   }
}

SVSemantic
Converter::cvtSemantic(enum sm4_sv sv, uint8_t &idx) const
{
   idx = 0;

   switch (sv) {
   case SM4_SV_UNDEFINED:     return SV_UNDEFINED;
   case SM4_SV_POSITION:      return SV_POSITION;
   case SM4_SV_CLIP_DISTANCE: return SV_CLIP_DISTANCE;
   case SM4_SV_CULL_DISTANCE: return SV_CLIP_DISTANCE; // XXX: distinction
   case SM4_SV_RENDER_TARGET_ARRAY_INDEX: return SV_LAYER;
   case SM4_SV_VIEWPORT_ARRAY_INDEX:  return SV_VIEWPORT_INDEX;
   case SM4_SV_VERTEX_ID:     return SV_VERTEX_ID;
   case SM4_SV_PRIMITIVE_ID:  return SV_PRIMITIVE_ID;
   case SM4_SV_INSTANCE_ID:   return SV_INSTANCE_ID;
   case SM4_SV_IS_FRONT_FACE: return SV_FACE;
   case SM4_SV_SAMPLE_INDEX:  return SV_SAMPLE_INDEX;

   case SM4_SV_FINAL_QUAD_U_EQ_0_EDGE_TESSFACTOR:
   case SM4_SV_FINAL_QUAD_V_EQ_0_EDGE_TESSFACTOR:
   case SM4_SV_FINAL_QUAD_U_EQ_1_EDGE_TESSFACTOR:
   case SM4_SV_FINAL_QUAD_V_EQ_1_EDGE_TESSFACTOR:
   case SM4_SV_FINAL_QUAD_U_INSIDE_TESSFACTOR:
   case SM4_SV_FINAL_QUAD_V_INSIDE_TESSFACTOR:
   case SM4_SV_FINAL_TRI_U_EQ_0_EDGE_TESSFACTOR:
   case SM4_SV_FINAL_TRI_V_EQ_0_EDGE_TESSFACTOR:
   case SM4_SV_FINAL_TRI_W_EQ_0_EDGE_TESSFACTOR:
   case SM4_SV_FINAL_TRI_INSIDE_TESSFACTOR:
   case SM4_SV_FINAL_LINE_DETAIL_TESSFACTOR:
   case SM4_SV_FINAL_LINE_DENSITY_TESSFACTOR:
      idx = getSVIndex(sv);
      return SV_TESS_FACTOR;

   default:
      assert(!"invalid SM4 system value");
      return SV_UNDEFINED;
   }
}

unsigned
Converter::tgsiSemantic(SVSemantic sv, int index)
{
   switch (sv) {
   case SV_POSITION:       return TGSI_SEMANTIC_POSITION;
   case SV_FACE:           return TGSI_SEMANTIC_FACE;
   case SV_LAYER:          return NV50_SEMANTIC_LAYER;
   case SV_VIEWPORT_INDEX: return NV50_SEMANTIC_VIEWPORTINDEX;
   case SV_POINT_SIZE:     return TGSI_SEMANTIC_PSIZE;
   case SV_CLIP_DISTANCE:  return NV50_SEMANTIC_CLIPDISTANCE;
   case SV_VERTEX_ID:      return NV50_SEMANTIC_VERTEXID;
   case SV_INSTANCE_ID:    return TGSI_SEMANTIC_INSTANCEID;
   case SV_PRIMITIVE_ID:   return TGSI_SEMANTIC_PRIMID;
   case SV_TESS_FACTOR:    return NV50_SEMANTIC_TESSFACTOR;
   case SV_TESS_COORD:     return NV50_SEMANTIC_TESSCOORD;
   case SV_INVOCATION_ID:  return NV50_SEMANTIC_INVOCATIONID;
   default:
      return TGSI_SEMANTIC_GENERIC;
   }
}

void
Converter::recordSV(unsigned sn, unsigned si, unsigned mask, bool input)
{
   unsigned int i;
   for (i = 0; i < info.numSysVals; ++i)
      if (info.sv[i].sn == sn &&
          info.sv[i].si == si)
         return;
   info.numSysVals = i + 1;
   info.sv[i].sn = sn;
   info.sv[i].si = si;
   info.sv[i].mask = mask;
   info.sv[i].input = input ? 1 : 0;
}

bool
Converter::parseSignature()
{
   struct nv50_ir_varying *patch;
   unsigned int i, r, n;

   info.numInputs = 0;
   info.numOutputs = 0;
   info.numPatchConstants = 0;

   for (n = 0, i = 0; i < sm4.num_params_in; ++i) {
      r = sm4.params_in[i].Register;

      info.in[r].mask |= sm4.params_in[i].ReadWriteMask;
      // mask might be uninitialized ...
      if (!sm4.params_in[i].ReadWriteMask)
	  info.in[r].mask = 0xf;
      info.in[r].id = r;
      if (info.in[r].regular) // already assigned semantic name/index
         continue;
      info.in[r].regular = 1;
      info.in[r].patch = 0;

      info.numInputs = MAX2(info.numInputs, r + 1);

      switch (sm4.params_in[i].SystemValueType) {
      case D3D_NAME_UNDEFINED:
         info.in[r].sn = TGSI_SEMANTIC_GENERIC;
         info.in[r].si = n++;
         break;
      case D3D_NAME_POSITION:
         info.in[r].sn = TGSI_SEMANTIC_POSITION;
         break;
      case D3D_NAME_VERTEX_ID:
         info.in[r].sn = NV50_SEMANTIC_VERTEXID;
         break;
      case D3D_NAME_PRIMITIVE_ID:
         info.in[r].sn = TGSI_SEMANTIC_PRIMID;
         // no corresponding output
         recordSV(TGSI_SEMANTIC_PRIMID, 0, 1, true);
         break;
      case D3D_NAME_INSTANCE_ID:
         info.in[r].sn = TGSI_SEMANTIC_INSTANCEID;
         break;
      case D3D_NAME_IS_FRONT_FACE:
         info.in[r].sn = TGSI_SEMANTIC_FACE;
         // no corresponding output
         recordSV(TGSI_SEMANTIC_FACE, 0, 1, true);
         break;
      default:
         assert(!"invalid/unsupported input linkage semantic");
         break;
      }
   }

   for (n = 0, i = 0; i < sm4.num_params_out; ++i) {
      r = sm4.params_out[i].Register;

      info.out[r].mask |= ~sm4.params_out[i].ReadWriteMask;
      info.out[r].id = r;
      if (info.out[r].regular) // already assigned semantic name/index
         continue;
      info.out[r].regular = 1;
      info.out[r].patch = 0;

      info.numOutputs = MAX2(info.numOutputs, r + 1);

      switch (sm4.params_out[i].SystemValueType) {
      case D3D_NAME_UNDEFINED:
         if (prog->getType() == Program::TYPE_FRAGMENT) {
            info.out[r].sn = TGSI_SEMANTIC_COLOR;
            info.out[r].si = info.prop.fp.numColourResults++;
         } else {
            info.out[r].sn = TGSI_SEMANTIC_GENERIC;
            info.out[r].si = n++;
         }
         break;
      case D3D_NAME_POSITION:
      case D3D_NAME_DEPTH:
      case D3D_NAME_DEPTH_GREATER_EQUAL:
      case D3D_NAME_DEPTH_LESS_EQUAL:
         info.out[r].sn = TGSI_SEMANTIC_POSITION;
         info.io.fragDepth = r;
         break;
      case D3D_NAME_CULL_DISTANCE:
      case D3D_NAME_CLIP_DISTANCE:
         info.out[r].sn = NV50_SEMANTIC_CLIPDISTANCE;
         info.out[r].si = sm4.params_out[i].SemanticIndex;
         break;
      case D3D_NAME_RENDER_TARGET_ARRAY_INDEX:
         info.out[r].sn = NV50_SEMANTIC_LAYER;
         break;
      case D3D_NAME_VIEWPORT_ARRAY_INDEX:
         info.out[r].sn = NV50_SEMANTIC_VIEWPORTINDEX;
         break;
      case D3D_NAME_PRIMITIVE_ID:
         info.out[r].sn = TGSI_SEMANTIC_PRIMID;
         break;
      case D3D_NAME_TARGET:
         info.out[r].sn = TGSI_SEMANTIC_COLOR;
         info.out[r].si = sm4.params_out[i].SemanticIndex;
         break;
      case D3D_NAME_COVERAGE:
         info.out[r].sn = NV50_SEMANTIC_SAMPLEMASK;
         info.io.sampleMask = r;
         break;
      case D3D_NAME_SAMPLE_INDEX:
      default:
         assert(!"invalid/unsupported output linkage semantic");
         break;
      }
   }

   if (prog->getType() == Program::TYPE_TESSELLATION_EVAL)
      patch = &info.in[info.numInputs];
   else
      patch = &info.out[info.numOutputs];

   for (n = 0, i = 0; i < sm4.num_params_patch; ++i) {
      r = sm4.params_patch[i].Register;

      patch[r].mask |= sm4.params_patch[i].Mask;
      patch[r].id = r;
      if (patch[r].regular) // already visited
         continue;
      patch[r].regular = 1;
      patch[r].patch = 1;

      info.numPatchConstants = MAX2(info.numPatchConstants, r + 1);

      switch (sm4.params_patch[i].SystemValueType) {
      case D3D_NAME_UNDEFINED:
         patch[r].sn = TGSI_SEMANTIC_GENERIC;
         patch[r].si = n++;
         break;
      case D3D_NAME_FINAL_QUAD_EDGE_TESSFACTOR:
      case D3D_NAME_FINAL_TRI_EDGE_TESSFACTOR:
      case D3D_NAME_FINAL_LINE_DETAIL_TESSFACTOR:
         patch[r].sn = NV50_SEMANTIC_TESSFACTOR;
         patch[r].si = sm4.params_patch[i].SemanticIndex;
         break;
      case D3D_NAME_FINAL_QUAD_INSIDE_TESSFACTOR:
      case D3D_NAME_FINAL_TRI_INSIDE_TESSFACTOR:
      case D3D_NAME_FINAL_LINE_DENSITY_TESSFACTOR:
         patch[r].sn = NV50_SEMANTIC_TESSFACTOR;
         patch[r].si = sm4.params_patch[i].SemanticIndex + 4;
         break;
      default:
         assert(!"invalid patch-constant linkage semantic");
         break;
      }
   }
   if (prog->getType() == Program::TYPE_TESSELLATION_EVAL)
      info.numInputs += info.numPatchConstants;
   else
      info.numOutputs += info.numPatchConstants;

   return true;
}

bool
Converter::inspectDeclaration(const sm4_dcl& dcl)
{
   int idx = -1;
   enum sm4_interpolation ipa_mode;

   if (dcl.op.get() && dcl.op->is_index_simple(0))
      idx = dcl.op->indices[0].disp;

   switch (dcl.opcode) {
   case SM4_OPCODE_DCL_SAMPLER:
      assert(idx >= 0);
      shadow[idx] = dcl.dcl_sampler.shadow;
      break;
   case SM4_OPCODE_DCL_RESOURCE:
   {
      enum sm4_target targ = (enum sm4_target)dcl.dcl_resource.target;

      assert(idx >= 0 && idx < NV50_IR_MAX_RESOURCES);
      resourceType[idx][0] = cvtTexTarget(targ, SM4_OPCODE_SAMPLE, NULL);
      resourceType[idx][1] = cvtTexTarget(targ, SM4_OPCODE_SAMPLE_C, NULL);
   }
      break;
   case SM4_OPCODE_DCL_CONSTANT_BUFFER:
      // nothing to do
      break;
   case SM4_OPCODE_CUSTOMDATA:
      info.immd.bufSize = dcl.num * 4;
      info.immd.buf = (uint32_t *)MALLOC(info.immd.bufSize);
      memcpy(info.immd.buf, dcl.data, info.immd.bufSize);
      break;
   case SM4_OPCODE_DCL_INDEX_RANGE:
      // XXX: ?
      break;
   case SM4_OPCODE_DCL_INPUT_PS_SGV:
   case SM4_OPCODE_DCL_INPUT_PS_SIV:
   case SM4_OPCODE_DCL_INPUT_PS:
   {
      assert(idx >= 0 && idx < info.numInputs);
      ipa_mode = (enum sm4_interpolation)dcl.dcl_input_ps.interpolation;
      interpMode[idx] = cvtInterpMode(ipa_mode);
      setVaryingInterpMode(&info.in[idx], interpMode[idx]);
   }
      break;
   case SM4_OPCODE_DCL_INPUT_SGV:
   case SM4_OPCODE_DCL_INPUT_SIV:
   case SM4_OPCODE_DCL_INPUT:
      if (dcl.op->file == SM4_FILE_INPUT_DOMAIN_POINT) {
         idx = info.numInputs++;
         info.in[idx].sn = NV50_SEMANTIC_TESSCOORD;
         info.in[idx].mask = dcl.op->mask;
      }
      // rest handled in parseSignature
      break;
   case SM4_OPCODE_DCL_OUTPUT_SGV:
   case SM4_OPCODE_DCL_OUTPUT_SIV:
      switch (dcl.sv) {
      case SM4_SV_POSITION:
         assert(prog->getType() != Program::TYPE_FRAGMENT);
         break;
      case SM4_SV_CULL_DISTANCE: // XXX: order ?
         info.io.cullDistanceMask |= 1 << info.io.clipDistanceCount;
	 // fall through
      case SM4_SV_CLIP_DISTANCE:
         info.io.clipDistanceCount++;
         break;
      default:
         break;
      }
      switch (dcl.op->file) {
      case SM4_FILE_OUTPUT_DEPTH_LESS_EQUAL:
      case SM4_FILE_OUTPUT_DEPTH_GREATER_EQUAL:
      case SM4_FILE_OUTPUT_DEPTH:
         if (info.io.fragDepth < 0xff)
            break;
         idx = info.io.fragDepth = info.numOutputs++;
         info.out[idx].sn = TGSI_SEMANTIC_POSITION;
         break;
      case SM4_FILE_OUTPUT_COVERAGE_MASK:
         if (info.io.sampleMask < 0xff)
            break;
         idx = info.io.sampleMask = info.numOutputs++;
         info.out[idx].sn = NV50_SEMANTIC_SAMPLEMASK;
         break;
      default:
         break;
      }
      break;
   case SM4_OPCODE_DCL_OUTPUT:
      // handled in parseSignature
      break;
   case SM4_OPCODE_DCL_TEMPS:
      nrRegVals += dcl.num;
      break;
   case SM4_OPCODE_DCL_INDEXABLE_TEMP:
      nrArrays++;
      break;
   case SM4_OPCODE_DCL_GLOBAL_FLAGS:
      if (prog->getType() == Program::TYPE_FRAGMENT)
         info.prop.fp.earlyFragTests = dcl.dcl_global_flags.early_depth_stencil;
      break;

   case SM4_OPCODE_DCL_FUNCTION_BODY:
      break;
   case SM4_OPCODE_DCL_FUNCTION_TABLE:
      break;
   case SM4_OPCODE_DCL_INTERFACE:
      break;

      // GP
   case SM4_OPCODE_DCL_GS_OUTPUT_PRIMITIVE_TOPOLOGY:
      info.prop.gp.outputPrim = g3dPrim(
         dcl.dcl_gs_output_primitive_topology.primitive_topology);
      break;
   case SM4_OPCODE_DCL_GS_INPUT_PRIMITIVE:
      info.prop.gp.inputPrim = g3dPrim(dcl.dcl_gs_input_primitive.primitive);
      break;
   case SM4_OPCODE_DCL_MAX_OUTPUT_VERTEX_COUNT:
      info.prop.gp.maxVertices = dcl.num;
      break;
   case SM4_OPCODE_DCL_GS_INSTANCE_COUNT:
      info.prop.gp.instanceCount = dcl.num;
      break;
   case SM4_OPCODE_DCL_STREAM:
      break;

      // TCP/TEP
   case SM4_OPCODE_DCL_INPUT_CONTROL_POINT_COUNT:
      info.prop.tp.inputPatchSize =
         dcl.dcl_input_control_point_count.control_points;
      break;
   case SM4_OPCODE_DCL_OUTPUT_CONTROL_POINT_COUNT:
      info.prop.tp.outputPatchSize =
         dcl.dcl_output_control_point_count.control_points;
      break;
   case SM4_OPCODE_DCL_TESS_DOMAIN:
      switch (dcl.dcl_tess_domain.domain) {
      case D3D_TESSELLATOR_DOMAIN_ISOLINE:
         info.prop.tp.domain = PIPE_PRIM_LINES;
         break;
      case D3D_TESSELLATOR_DOMAIN_TRI:
         info.prop.tp.domain = PIPE_PRIM_TRIANGLES;
         break;
      case D3D_TESSELLATOR_DOMAIN_QUAD:
         info.prop.tp.domain = PIPE_PRIM_QUADS;
         break;
      case D3D_TESSELLATOR_DOMAIN_UNDEFINED:
      default:
         info.prop.tp.domain = PIPE_PRIM_MAX;
         break;
      }
      break;
   case SM4_OPCODE_DCL_TESS_PARTITIONING:
      switch (dcl.dcl_tess_partitioning.partitioning) {
      case D3D_TESSELLATOR_PARTITIONING_FRACTIONAL_ODD:
         info.prop.tp.partitioning = NV50_TESS_PART_FRACT_ODD;
         break;
      case D3D_TESSELLATOR_PARTITIONING_FRACTIONAL_EVEN:
         info.prop.tp.partitioning = NV50_TESS_PART_FRACT_EVEN;
         break;
      case D3D_TESSELLATOR_PARTITIONING_POW2:
         info.prop.tp.partitioning = NV50_TESS_PART_POW2;
         break;
      case D3D_TESSELLATOR_PARTITIONING_INTEGER:
      case D3D_TESSELLATOR_PARTITIONING_UNDEFINED:
      default:
         info.prop.tp.partitioning = NV50_TESS_PART_INTEGER;
         break;
      }
      break;
   case SM4_OPCODE_DCL_TESS_OUTPUT_PRIMITIVE:
      switch (dcl.dcl_tess_output_primitive.primitive) {
      case D3D_TESSELLATOR_OUTPUT_LINE:
         info.prop.tp.outputPrim = PIPE_PRIM_LINES;
         break;
      case D3D_TESSELLATOR_OUTPUT_TRIANGLE_CW:
         info.prop.tp.outputPrim = PIPE_PRIM_TRIANGLES;
         info.prop.tp.winding = +1;
         break;
      case D3D_TESSELLATOR_OUTPUT_TRIANGLE_CCW:
         info.prop.tp.outputPrim = PIPE_PRIM_TRIANGLES;
         info.prop.tp.winding = -1;
         break;
      case D3D_TESSELLATOR_OUTPUT_POINT:
         info.prop.tp.outputPrim = PIPE_PRIM_POINTS;
         break;
      case D3D_TESSELLATOR_OUTPUT_UNDEFINED:
      default:
         info.prop.tp.outputPrim = PIPE_PRIM_MAX;
         break;
      }
      break;

   case SM4_OPCODE_HS_FORK_PHASE:
      ++subPhaseCnt[0];
      phase = 1;
      break;
   case SM4_OPCODE_HS_JOIN_PHASE:
      phase = 2;
      ++subPhaseCnt[1];
      break;
   case SM4_OPCODE_DCL_HS_FORK_PHASE_INSTANCE_COUNT:
   case SM4_OPCODE_DCL_HS_JOIN_PHASE_INSTANCE_COUNT:
   case SM4_OPCODE_DCL_HS_MAX_TESSFACTOR:
      break;

      // weird stuff
   case SM4_OPCODE_DCL_THREAD_GROUP:
   case SM4_OPCODE_DCL_UNORDERED_ACCESS_VIEW_TYPED:
   case SM4_OPCODE_DCL_UNORDERED_ACCESS_VIEW_RAW:
   case SM4_OPCODE_DCL_UNORDERED_ACCESS_VIEW_STRUCTURED:
   case SM4_OPCODE_DCL_THREAD_GROUP_SHARED_MEMORY_RAW:
   case SM4_OPCODE_DCL_THREAD_GROUP_SHARED_MEMORY_STRUCTURED:
   case SM4_OPCODE_DCL_RESOURCE_RAW:
   case SM4_OPCODE_DCL_RESOURCE_STRUCTURED:
      ERROR("unhandled declaration\n");
      abort();
      return false;

   default:
      assert(!"invalid SM4 declaration");
      return false;
   }
   return true;
}

void
Converter::allocateValues()
{
   lData = new DataArray[nrArrays];

   for (unsigned int i = 0; i < nrArrays; ++i)
      lData[i].setParent(this);

   tData32.setup(0, nrRegVals, 4, 4, FILE_GPR);
   tData64.setup(0, nrRegVals, 2, 8, FILE_GPR);

   if (prog->getType() == Program::TYPE_FRAGMENT)
      oData.setup(0, info.numOutputs, 4, 4, FILE_GPR);
}

bool Converter::handleDeclaration(const sm4_dcl& dcl)
{
   switch (dcl.opcode) {
   case SM4_OPCODE_DCL_INDEXABLE_TEMP:
      lData[nrArrays++].setup(arrayVol,
                              dcl.indexable_temp.num, dcl.indexable_temp.comps,
                              4, FILE_MEMORY_LOCAL);
      arrayVol += dcl.indexable_temp.num * dcl.indexable_temp.comps * 4;
      break;
   case SM4_OPCODE_HS_FORK_PHASE:
      if (subPhaseCnt[0])
         phaseInstCnt[0][subPhaseCnt[0]] = phaseInstCnt[0][subPhaseCnt[0] - 1];
      ++subPhaseCnt[0];
      break;
   case SM4_OPCODE_HS_JOIN_PHASE:
      if (subPhaseCnt[1])
         phaseInstCnt[1][subPhaseCnt[1]] = phaseInstCnt[1][subPhaseCnt[1] - 1];
      ++subPhaseCnt[1];
      break;
   case SM4_OPCODE_DCL_HS_FORK_PHASE_INSTANCE_COUNT:
      phaseInstCnt[0][subPhaseCnt[0] - 1] = dcl.num;
      break;
   case SM4_OPCODE_DCL_HS_JOIN_PHASE_INSTANCE_COUNT:
      phaseInstCnt[1][subPhaseCnt[1] - 1] = dcl.num;
      break;

   default:
      break; // already handled in inspection
   }

   return true;
}

Symbol *
Converter::iSym(int i, int c)
{
   if (info.in[i].regular) {
      return mkSymbol(FILE_SHADER_INPUT, 0, sTy, info.in[i].slot[c] * 4);
   } else {
      return mkSysVal(tgsi::irSemantic(info.in[i].sn), info.in[i].si);
   }
}

Symbol *
Converter::oSym(int i, int c)
{
   if (info.out[i].regular) {
      return mkSymbol(FILE_SHADER_OUTPUT, 0, dTy, info.out[i].slot[c] * 4);
   } else {
      return mkSysVal(tgsi::irSemantic(info.out[i].sn), info.out[i].si);
   }
}

Value *
Converter::getSrcPtr(int s, int dim, int shl)
{
   if (srcPtr[s][dim])
      return srcPtr[s][dim];

   sm4_op *op = insn->ops[s + nDstOpnds]->indices[dim].reg.get();

   if (!op)
      return NULL;

   Value *index = src(*op, 0, s);

   srcPtr[s][dim] = index;
   if (shl)
      srcPtr[s][dim] = mkOp2v(OP_SHL, TYPE_U32, getSSA(), index, mkImm(shl));
   return srcPtr[s][dim];
}

Value *
Converter::getDstPtr(int d, int dim, int shl)
{
   assert(d == 0);
   if (dstPtr[dim])
      return dstPtr[dim];

   sm4_op *op = insn->ops[d]->indices[dim].reg.get();
   if (!op)
      return NULL;

   Value *index = src(*op, 0, d);
   if (shl)
      index = mkOp2v(OP_SHL, TYPE_U32, getSSA(), index, mkImm(shl));

   return (dstPtr[dim] = index);
}

Value *
Converter::getVtxPtr(int s)
{
   assert(s < 3);
   if (vtxBase[s])
      return vtxBase[s];

   sm4_op *op = insn->ops[s + nDstOpnds].get();
   if (!op)
      return NULL;
   int idx = op->indices[0].disp;

   vtxBase[s] = getSrcPtr(s, 0, 0);
   vtxBase[s] = mkOp2v(OP_PFETCH, TYPE_U32, getSSA(), mkImm(idx), vtxBase[s]);
   return vtxBase[s];
}

Value *
Converter::src(int i, int c)
{
   return src(*insn->ops[i + nDstOpnds], c, i);
}

Value *
Converter::dst(int i, int c)
{
   return dst(*insn->ops[i], c, i);
}

void
Converter::saveDst(int i, int c, Value *value)
{
   if (insn->insn.sat)
      mkOp1(OP_SAT, dTy, value, value);
   return saveDst(*insn->ops[i], c, value, i);
}

Value *
Converter::interpolate(const sm4_op& op, int c, int i)
{
   int idx = op.indices[0].disp;
   int swz = op.swizzle[c];
   operation opr =
      (info.in[idx].linear || info.in[idx].flat) ? OP_LINTERP : OP_PINTERP;

   Value *ptr = getSrcPtr(i, 0, 4);

   Instruction *insn = new_Instruction(func, opr, TYPE_F32);

   insn->setDef(0, getScratch());
   insn->setSrc(0, iSym(idx, swz));
   if (opr == OP_PINTERP)
      insn->setSrc(1, fragCoord[3]);
   if (ptr)
      insn->setIndirect(0, 0, ptr);

   insn->setInterpolate(interpMode[idx]);

   bb->insertTail(insn);
   return insn->getDef(0);
}

Value *
Converter::src(const sm4_op& op, int c, int s)
{
   const int size = typeSizeof(sTy);

   Instruction *ld;
   Value *res, *ptr, *vtx;
   int idx, dim, off;
   const int swz = op.swizzle[c];

   switch (op.file) {
   case SM4_FILE_IMMEDIATE32:
      res = loadImm(NULL, (uint32_t)op.imm_values[swz].u32);
      break;
   case SM4_FILE_IMMEDIATE64:
      assert(c < 2);
      res = loadImm(NULL, op.imm_values[swz].u64);
      break;
   case SM4_FILE_TEMP:
      assert(op.is_index_simple(0));
      idx = op.indices[0].disp;
      if (size == 8)
         res = tData64.load(idx, swz, NULL);
      else
         res = tData32.load(idx, swz, NULL);
      break;
   case SM4_FILE_INPUT:
   case SM4_FILE_INPUT_CONTROL_POINT:
   case SM4_FILE_INPUT_PATCH_CONSTANT:
      if (prog->getType() == Program::TYPE_FRAGMENT)
         return interpolate(op, c, s);

      idx = 0;
      if (op.file == SM4_FILE_INPUT_PATCH_CONSTANT)
         idx = info.numInputs - info.numPatchConstants;

      if (op.num_indices == 2) {
         vtx = getVtxPtr(s);
         ptr = getSrcPtr(s, 1, 4);
         idx += op.indices[1].disp;
         res = getSSA();
         ld = mkOp1(OP_VFETCH, TYPE_U32, res, iSym(idx, swz));
         ld->setIndirect(0, 0, ptr);
         ld->setIndirect(0, 1, vtx);
      } else {
         idx += op.indices[0].disp;
         res = mkLoad(sTy, iSym(idx, swz), getSrcPtr(s, 0, 4));
      }
      if (op.file == SM4_FILE_INPUT_PATCH_CONSTANT)
         res->defs->getInsn()->perPatch = 1;
      break;
   case SM4_FILE_CONSTANT_BUFFER:
      assert(op.num_indices == 2);
      assert(op.is_index_simple(0));

      ptr = getSrcPtr(s, 1, 4);
      dim = op.indices[0].disp;
      off = (op.indices[1].disp * 4 + swz) * (sTy == TYPE_F64 ? 8 : 4);

      res = mkLoad(sTy, mkSymbol(FILE_MEMORY_CONST, dim, sTy, off), ptr);
      break;
   case SM4_FILE_IMMEDIATE_CONSTANT_BUFFER:
      ptr = getSrcPtr(s, 0, 4);
      off = (op.indices[0].disp * 4 + swz) * 4;
      res = mkLoad(sTy, mkSymbol(FILE_MEMORY_CONST, 14, sTy, off), ptr);
      break;
   case SM4_FILE_INDEXABLE_TEMP:
   {
      assert(op.is_index_simple(0));
      int a = op.indices[0].disp;
      idx = op.indices[1].disp;
      res = lData[a].load(idx, swz, getSrcPtr(s, 1, 4));
   }
      break;
   case SM4_FILE_INPUT_PRIMITIVEID:
      recordSV(TGSI_SEMANTIC_PRIMID, 0, 1, true);
      res = mkOp1v(OP_RDSV, TYPE_U32, getSSA(), mkSysVal(SV_PRIMITIVE_ID, 0));
      break;
   case SM4_FILE_INPUT_GS_INSTANCE_ID:
   case SM4_FILE_OUTPUT_CONTROL_POINT_ID:
      recordSV(NV50_SEMANTIC_INVOCATIONID, 0, 1, true);
      res = mkOp1v(OP_RDSV, TYPE_U32, getSSA(), mkSysVal(SV_INVOCATION_ID, 0));
      break;
   case SM4_FILE_CYCLE_COUNTER:
      res =
         mkOp1v(OP_RDSV, TYPE_U32, getSSA(), mkSysVal(SV_CLOCK, swz ? 1 : 0));
      break;
   case SM4_FILE_INPUT_FORK_INSTANCE_ID:
   case SM4_FILE_INPUT_JOIN_INSTANCE_ID:
   {
      phaseInstanceUsed = true;
      if (unrollPhase)
         return loadImm(NULL, phaseInstance);
      const unsigned int cnt = phaseInstCnt[phase - 1][subPhase];
      res = getScratch();
      res = mkOp1v(OP_RDSV, TYPE_U32, res, mkSysVal(SV_INVOCATION_ID, 0));
      res = mkOp2v(OP_MIN, TYPE_U32, res, res, loadImm(NULL, cnt - 1));
   }
      break;
   case SM4_FILE_INPUT_DOMAIN_POINT:
      assert(swz < 3);
      res = domainPt[swz];
      break;
   case SM4_FILE_THREAD_GROUP_SHARED_MEMORY:
      off = (op.indices[0].disp * 4 + swz) * (sTy == TYPE_F64 ? 8 : 4);
      ptr = getSrcPtr(s, 0, 4);
      res = mkLoad(sTy, mkSymbol(FILE_MEMORY_SHARED, 0, sTy, off), ptr);
      break;
   case SM4_FILE_RESOURCE:
   case SM4_FILE_SAMPLER:
   case SM4_FILE_UNORDERED_ACCESS_VIEW:
      return NULL;
   case SM4_FILE_INPUT_THREAD_ID:
      res = mkOp1v(OP_RDSV, TYPE_U32, getSSA(), mkSysVal(SV_TID, swz));
      break;
   case SM4_FILE_INPUT_THREAD_GROUP_ID:
      res = mkOp1v(OP_RDSV, TYPE_U32, getSSA(), mkSysVal(SV_CTAID, swz));
      break;
   case SM4_FILE_FUNCTION_INPUT:
   case SM4_FILE_INPUT_THREAD_ID_IN_GROUP:
      assert(!"unhandled source file");
      return NULL;
   default:
      assert(!"invalid source file");
      return NULL;
   }

   if (op.abs)
      res = mkOp1v(OP_ABS, sTy, getSSA(res->reg.size), res);
   if (op.neg)
      res = mkOp1v(OP_NEG, sTy, getSSA(res->reg.size), res);
   return res;
}

Value *
Converter::dst(const sm4_op &op, int c, int i)
{
   switch (op.file) {
   case SM4_FILE_TEMP:
      return tData32.acquire(op.indices[0].disp, c);
   case SM4_FILE_INDEXABLE_TEMP:
      return getScratch();
   case SM4_FILE_OUTPUT:
      if (prog->getType() == Program::TYPE_FRAGMENT)
         return oData.acquire(op.indices[0].disp, c);
      return getScratch();
   case SM4_FILE_NULL:
      return NULL;
   case SM4_FILE_OUTPUT_DEPTH:
   case SM4_FILE_OUTPUT_DEPTH_GREATER_EQUAL:
   case SM4_FILE_OUTPUT_DEPTH_LESS_EQUAL:
   case SM4_FILE_OUTPUT_COVERAGE_MASK:
      return getScratch();
   case SM4_FILE_IMMEDIATE32:
   case SM4_FILE_IMMEDIATE64:
   case SM4_FILE_CONSTANT_BUFFER:
   case SM4_FILE_RESOURCE:
   case SM4_FILE_SAMPLER:
   case SM4_FILE_UNORDERED_ACCESS_VIEW:
      assert(!"invalid destination file");
      return NULL;
   default:
      assert(!"invalid file");
      return NULL;
   }
}

void
Converter::saveFragDepth(operation op, Value *value)
{
   if (op == OP_MIN || op == OP_MAX) {
      Value *zIn;
      zIn = mkOp1v(OP_RDSV, TYPE_F32, getSSA(), mkSysVal(SV_POSITION, 2));
      value = mkOp2v(op, TYPE_F32, getSSA(), value, zIn);
   }
   oData.store(info.io.fragDepth, 2, NULL, value);
}

void
Converter::saveDst(const sm4_op &op, int c, Value *value, int s)
{
   Symbol *sym;
   Instruction *st;
   int a, idx;

   switch (op.file) {
   case SM4_FILE_TEMP:
      idx = op.indices[0].disp;
      tData32.store(idx, c, NULL, value);
      break;
   case SM4_FILE_INDEXABLE_TEMP:
      a = op.indices[0].disp;
      idx = op.indices[1].disp;
      // FIXME: shift is wrong, depends in lData
      lData[a].store(idx, c, getDstPtr(s, 1, 4), value);
      break;
   case SM4_FILE_OUTPUT:
      assert(op.num_indices == 1);
      idx = op.indices[0].disp;
      if (prog->getType() == Program::TYPE_FRAGMENT) {
         oData.store(idx, c, NULL, value);
      } else {
         if (phase)
            idx += info.numOutputs - info.numPatchConstants;
         const int shl = (info.out[idx].sn == NV50_SEMANTIC_TESSFACTOR) ? 2 : 4;
         sym = oSym(idx, c);
         if (sym->reg.file == FILE_SHADER_OUTPUT)
            st = mkStore(OP_EXPORT, dTy, sym, getDstPtr(s, 0, shl), value);
         else
            st = mkStore(OP_WRSV, dTy, sym, getDstPtr(s, 0, 2), value);
         st->perPatch = phase ? 1 : 0;
      }
      break;
   case SM4_FILE_OUTPUT_DEPTH_GREATER_EQUAL:
      saveFragDepth(OP_MAX, value);
      break;
   case SM4_FILE_OUTPUT_DEPTH_LESS_EQUAL:
      saveFragDepth(OP_MIN, value);
      break;
   case SM4_FILE_OUTPUT_DEPTH:
      saveFragDepth(OP_NOP, value);
      break;
   case SM4_FILE_OUTPUT_COVERAGE_MASK:
      oData.store(info.io.sampleMask, 0, NULL, value);
      break;
   case SM4_FILE_IMMEDIATE32:
   case SM4_FILE_IMMEDIATE64:
   case SM4_FILE_INPUT:
   case SM4_FILE_CONSTANT_BUFFER:
   case SM4_FILE_RESOURCE:
   case SM4_FILE_SAMPLER:
      assert(!"invalid destination file");
      return;
   default:
      assert(!"invalid file");
      return;
   }
}

void
Converter::emitTex(Value *dst0[4], TexInstruction *tex, const uint8_t swz[4])
{
   Value *res[4] = { NULL, NULL, NULL, NULL };
   unsigned int c, d;

   for (c = 0; c < 4; ++c)
      if (dst0[c])
         tex->tex.mask |= 1 << swz[c];
   for (d = 0, c = 0; c < 4; ++c)
      if (tex->tex.mask & (1 << c))
         tex->setDef(d++, (res[c] = getScratch()));

   bb->insertTail(tex);

   if (insn->opcode == SM4_OPCODE_RESINFO) {
      if (tex->tex.target.getDim() == 1) {
	 res[2] = loadImm(NULL, 0);
         if (!tex->tex.target.isArray())
            res[1] = res[2];
      } else
      if (tex->tex.target.getDim() == 2 && !tex->tex.target.isArray()) {
         res[2] = loadImm(NULL, 0);
      }
      for (c = 0; c < 4; ++c) {
         if (!dst0[c])
            continue;
         Value *src = res[swz[c]];
         assert(src);
         switch (insn->insn.resinfo_return_type) {
         case 0:
            mkCvt(OP_CVT, TYPE_F32, dst0[c], TYPE_U32, src);
            break;
         case 1:
            mkCvt(OP_CVT, TYPE_F32, dst0[c], TYPE_U32, src);
            if (swz[c] < tex->tex.target.getDim())
               mkOp1(OP_RCP, TYPE_F32, dst0[c], dst0[c]);
            break;
         default:
            mkMov(dst0[c], src);
            break;
         }
      }
   } else {
      for (c = 0; c < 4; ++c)
         if (dst0[c])
            mkMov(dst0[c], res[swz[c]]);
   }
}

void
Converter::handleQUERY(Value *dst0[4], enum TexQuery query)
{
   TexInstruction *texi = new_TexInstruction(func, OP_TXQ);
   texi->tex.query = query;

   assert(insn->ops[2]->file == SM4_FILE_RESOURCE); // TODO: UAVs

   const int rOp = (query == TXQ_DIMS) ? 2 : 1;
   const int sOp = (query == TXQ_DIMS) ? 0 : 1;

   const int tR = insn->ops[rOp]->indices[0].disp;

   texi->setTexture(resourceType[tR][0], tR, 0);

   texi->setSrc(0, src(sOp, 0)); // mip level or sample index

   emitTex(dst0, texi, insn->ops[rOp]->swizzle);
}

void
Converter::handleLOAD(Value *dst0[4])
{
   TexInstruction *texi = new_TexInstruction(func, OP_TXF);
   unsigned int c;

   const int tR = insn->ops[2]->indices[0].disp;

   texi->setTexture(resourceType[tR][0], tR, 0);

   for (c = 0; c < texi->tex.target.getArgCount(); ++c)
      texi->setSrc(c, src(0, c));

   if (texi->tex.target == TEX_TARGET_BUFFER) {
      texi->tex.levelZero = true;
   } else {
      texi->setSrc(c++, src(0, 3));
      for (c = 0; c < 3; ++c) {
         texi->tex.offset[0][c] = insn->sample_offset[c];
	 if (texi->tex.offset[0][c])
            texi->tex.useOffsets = 1;
      }
   }

   emitTex(dst0, texi, insn->ops[2]->swizzle);
}

// order of nv50 ir sources: x y z/layer lod/bias dc
void
Converter::handleSAMPLE(operation opr, Value *dst0[4])
{
   TexInstruction *texi = new_TexInstruction(func, opr);
   unsigned int c, s;
   Value *arg[4], *src0[4];
   Value *val;
   Value *lod = NULL, *dc = NULL;

   const int tR = insn->ops[2]->indices[0].disp;
   const int tS = insn->ops[3]->indices[0].disp;

   TexInstruction::Target tgt = resourceType[tR][shadow[tS] ? 1 : 0];

   for (c = 0; c < tgt.getArgCount(); ++c)
      arg[c] = src0[c] = src(0, c);

   if (insn->opcode == SM4_OPCODE_SAMPLE_L ||
       insn->opcode == SM4_OPCODE_SAMPLE_B) {
      lod = src(3, 0);
   } else
   if (insn->opcode == SM4_OPCODE_SAMPLE_C ||
       insn->opcode == SM4_OPCODE_SAMPLE_C_LZ) {
      dc = src(3, 0);
      if (insn->opcode == SM4_OPCODE_SAMPLE_C_LZ)
         texi->tex.levelZero = true;
   } else
   if (insn->opcode == SM4_OPCODE_SAMPLE_D) {
      for (c = 0; c < tgt.getDim(); ++c) {
         texi->dPdx[c] = src(3, c);
         texi->dPdy[c] = src(4, c);
      }
   }

   if (tgt.isCube()) {
      for (c = 0; c < 3; ++c)
         src0[c] = mkOp1v(OP_ABS, TYPE_F32, getSSA(), arg[c]);
      val = getScratch();
      mkOp2(OP_MAX, TYPE_F32, val, src0[0], src0[1]);
      mkOp2(OP_MAX, TYPE_F32, val, src0[2], val);
      mkOp1(OP_RCP, TYPE_F32, val, val);
      for (c = 0; c < 3; ++c)
         src0[c] = mkOp2v(OP_MUL, TYPE_F32, getSSA(), arg[c], val);
   }

   for (s = 0; s < tgt.getArgCount(); ++s)
      texi->setSrc(s, src0[s]);
   if (lod)
      texi->setSrc(s++, lod);
   if (dc)
      texi->setSrc(s++, dc);

   for (c = 0; c < 3; ++c) {
      texi->tex.offset[0][c] = insn->sample_offset[c];
      if (texi->tex.offset[0][c])
         texi->tex.useOffsets = 1;
   }

   texi->setTexture(tgt, tR, tS);

   emitTex(dst0, texi, insn->ops[2]->swizzle);
}

void
Converter::handleDP(Value *dst0[4], int dim)
{
   Value *src0 = src(0, 0), *src1 = src(1, 0);
   Value *dotp = getScratch();

   assert(dim > 0);

   mkOp2(OP_MUL, TYPE_F32, dotp, src0, src1);
   for (int c = 1; c < dim; ++c)
      mkOp3(OP_MAD, TYPE_F32, dotp, src(0, c), src(1, c), dotp);

   for (int c = 0; c < 4; ++c)
      dst0[c] = dotp;
}

void
Converter::insertConvergenceOps(BasicBlock *conv, BasicBlock *fork)
{
   FlowInstruction *join = new_FlowInstruction(func, OP_JOIN, NULL);
   join->fixed = 1;
   conv->insertHead(join);

   fork->joinAt = new_FlowInstruction(func, OP_JOINAT, conv);
   fork->insertBefore(fork->getExit(), fork->joinAt);
}

void
Converter::finalizeShader()
{
   if (finalized)
      return;
   BasicBlock *epilogue = reinterpret_cast<BasicBlock *>(leaveBBs.pop().u.p);
   entryBBs.pop();

   finalized = true;

   bb->cfg.attach(&epilogue->cfg, Graph::Edge::TREE);
   setPosition(epilogue, true);

   if (prog->getType() == Program::TYPE_FRAGMENT)
      exportOutputs();

   mkOp(OP_EXIT, TYPE_NONE, NULL)->terminator = 1;
}

#define FOR_EACH_DST0_ENABLED_CHANNEL32(chan)         \
   for ((chan) = 0; (chan) < 4; ++(chan))             \
      if (insn->ops[0].get()->mask & (1 << (chan)))

#define FOR_EACH_DST0_ENABLED_CHANNEL64(chan)         \
   for ((chan) = 0; (chan) < 2; ++(chan))             \
      if (insn->ops[0].get()->mask & (1 << (chan)))

bool
Converter::checkDstSrcAliasing() const
{
   for (unsigned int d = 0; d < nDstOpnds; ++d) {
      for (unsigned int s = nDstOpnds; s < insn->num_ops; ++s) {
         if (insn->ops[d]->file != insn->ops[s]->file)
            continue;
         int i = insn->ops[s]->num_indices - 1;
         if (i != insn->ops[d]->num_indices - 1)
            continue;
         if (insn->ops[d]->is_index_simple(i) &&
             insn->ops[s]->is_index_simple(i) &&
             insn->ops[d]->indices[i].disp == insn->ops[s]->indices[i].disp)
            return true;
      }
   }
   return false;
}

bool
Converter::handleInstruction(unsigned int pos)
{
   Value *dst0[4], *rDst0[4];
   Value *dst1[4], *rDst1[4];
   int c, nc;

   insn = sm4.insns[pos];
   enum sm4_opcode opcode = static_cast<sm4_opcode>(insn->opcode);

   operation op = cvtOpcode(opcode);

   sTy = inferSrcType(opcode);
   dTy = inferDstType(opcode);

   nc = dTy == TYPE_F64 ? 2 : 4;

   nDstOpnds = getDstOpndCount(opcode);

   bool useScratchDst = checkDstSrcAliasing();

   INFO("SM4_OPCODE_##%u, aliasing = %u\n", insn->opcode, useScratchDst);

   if (nDstOpnds >= 1) {
      for (c = 0; c < nc; ++c)
         rDst0[c] = dst0[c] =
            insn->ops[0].get()->mask & (1 << c) ? dst(0, c) : NULL;
      if (useScratchDst)
         for (c = 0; c < nc; ++c)
            dst0[c] = rDst0[c] ? getScratch() : NULL;
   }

   if (nDstOpnds >= 2) {
      for (c = 0; c < nc; ++c)
         rDst1[c] = dst1[c] =
            insn->ops[1].get()->mask & (1 << c) ? dst(1, c) : NULL;
      if (useScratchDst)
         for (c = 0; c < nc; ++c)
            dst1[c] = rDst1[c] ? getScratch() : NULL;
   }

   switch (insn->opcode) {
   case SM4_OPCODE_ADD:
   case SM4_OPCODE_AND:
   case SM4_OPCODE_DIV:
   case SM4_OPCODE_IADD:
   case SM4_OPCODE_IMAX:
   case SM4_OPCODE_IMIN:
   case SM4_OPCODE_MIN:
   case SM4_OPCODE_MAX:
   case SM4_OPCODE_MUL:
   case SM4_OPCODE_OR:
   case SM4_OPCODE_UMAX:
   case SM4_OPCODE_UMIN:
   case SM4_OPCODE_XOR:
      FOR_EACH_DST0_ENABLED_CHANNEL32(c) {
         Instruction *insn = mkOp2(op, dTy, dst0[c], src(0, c), src(1, c));
         if (dTy == TYPE_F32)
            insn->ftz = 1;
      }
      break;

   case SM4_OPCODE_ISHL:
   case SM4_OPCODE_ISHR:
   case SM4_OPCODE_USHR:
      FOR_EACH_DST0_ENABLED_CHANNEL32(c) {
         Instruction *insn = mkOp2(op, dTy, dst0[c], src(0, c), src(1, c));
         insn->subOp = NV50_IR_SUBOP_SHIFT_WRAP;
      }
      break;

   case SM4_OPCODE_IMAD:
   case SM4_OPCODE_MAD:
   case SM4_OPCODE_UMAD:
      FOR_EACH_DST0_ENABLED_CHANNEL32(c) {
         mkOp3(OP_MAD, dTy, dst0[c], src(0, c), src(1, c), src(2, c));
      }
      break;

   case SM4_OPCODE_DADD:
   case SM4_OPCODE_DMAX:
   case SM4_OPCODE_DMIN:
   case SM4_OPCODE_DMUL:
      FOR_EACH_DST0_ENABLED_CHANNEL64(c) {
         mkOp2(op, dTy, dst0[c], src(0, c), src(1, c));
      }
      break;

   case SM4_OPCODE_UDIV:
      for (c = 0; c < 4; ++c) {
         Value *dvn, *dvs;
         if (dst0[c] || dst1[c]) {
            dvn = src(0, c);
            dvs = src(1, c);
         }
         if (dst0[c])
            mkOp2(OP_DIV, TYPE_U32, dst0[c], dvn, dvs);
         if (dst1[c])
            mkOp2(OP_MOD, TYPE_U32, dst1[c], dvn, dvs);
      }
      break;

   case SM4_OPCODE_IMUL:
   case SM4_OPCODE_UMUL:
      for (c = 0; c < 4; ++c) {
         Value *a, *b;
         if (dst0[c] || dst1[c]) {
            a = src(0, c);
            b = src(1, c);
         }
         if (dst0[c])
            mkOp2(OP_MUL, dTy, dst0[c], a, b)->subOp =
               NV50_IR_SUBOP_MUL_HIGH;
         if (dst1[c])
            mkOp2(OP_MUL, dTy, dst1[c], a, b);
      }
      break;

   case SM4_OPCODE_DP2:
      handleDP(dst0, 2);
      break;
   case SM4_OPCODE_DP3:
      handleDP(dst0, 3);
      break;
   case SM4_OPCODE_DP4:
      handleDP(dst0, 4);
      break;

   case SM4_OPCODE_DERIV_RTX:
   case SM4_OPCODE_DERIV_RTX_COARSE:
   case SM4_OPCODE_DERIV_RTX_FINE:
   case SM4_OPCODE_DERIV_RTY:
   case SM4_OPCODE_DERIV_RTY_COARSE:
   case SM4_OPCODE_DERIV_RTY_FINE:
   case SM4_OPCODE_MOV:
   case SM4_OPCODE_INEG:
   case SM4_OPCODE_NOT:
   case SM4_OPCODE_SQRT:
   case SM4_OPCODE_COUNTBITS:
   case SM4_OPCODE_EXP:
   case SM4_OPCODE_LOG:
   case SM4_OPCODE_RCP:
      FOR_EACH_DST0_ENABLED_CHANNEL32(c) {
         mkOp1(op, dTy, dst0[c], src(0, c));
      }
      break;

   case SM4_OPCODE_FRC:
      FOR_EACH_DST0_ENABLED_CHANNEL32(c) {
         Value *val = getScratch();
         Value *src0 = src(0, c);
         mkOp1(OP_FLOOR, TYPE_F32, val, src0);
         mkOp2(OP_SUB, TYPE_F32, dst0[c], src0, val);
      }
      break;

   case SM4_OPCODE_MOVC:
      FOR_EACH_DST0_ENABLED_CHANNEL32(c)
         mkCmp(OP_SLCT, CC_NE, TYPE_U32, dst0[c], src(1, c), src(2, c),
               src(0, c));
      break;

   case SM4_OPCODE_ROUND_NE:
   case SM4_OPCODE_ROUND_NI:
   case SM4_OPCODE_ROUND_PI:
   case SM4_OPCODE_ROUND_Z:
      FOR_EACH_DST0_ENABLED_CHANNEL32(c) {
         Instruction *rnd = mkOp1(op, dTy, dst0[c], src(0, c));
         rnd->ftz = 1;
         rnd->rnd = cvtRoundingMode(opcode);
      }
      break;

   case SM4_OPCODE_RSQ:
      FOR_EACH_DST0_ENABLED_CHANNEL32(c)
         mkOp1(op, dTy, dst0[c], src(0, c));
      break;

   case SM4_OPCODE_SINCOS:
      for (c = 0; c < 4; ++c) {
         if (!dst0[c] && !dst1[c])
            continue;
         Value *val = mkOp1v(OP_PRESIN, TYPE_F32, getScratch(), src(0, c));
         if (dst0[c])
            mkOp1(OP_SIN, TYPE_F32, dst0[c], val);
         if (dst1[c])
            mkOp1(OP_COS, TYPE_F32, dst1[c], val);
      }
      break;

   case SM4_OPCODE_EQ:
   case SM4_OPCODE_GE:
   case SM4_OPCODE_IEQ:
   case SM4_OPCODE_IGE:
   case SM4_OPCODE_ILT:
   case SM4_OPCODE_LT:
   case SM4_OPCODE_NE:
   case SM4_OPCODE_INE:
   case SM4_OPCODE_ULT:
   case SM4_OPCODE_UGE:
   case SM4_OPCODE_DEQ:
   case SM4_OPCODE_DGE:
   case SM4_OPCODE_DLT:
   case SM4_OPCODE_DNE:
   {
      CondCode cc = cvtCondCode(opcode);
      FOR_EACH_DST0_ENABLED_CHANNEL32(c) {
         CmpInstruction *set;
         set = mkCmp(op, cc, sTy, dst0[c], src(0, c), src(1, c), NULL);
         set->setType(dTy, sTy);
         if (sTy == TYPE_F32)
            set->ftz = 1;
      }
   }
      break;

   case SM4_OPCODE_FTOI:
   case SM4_OPCODE_FTOU:
      FOR_EACH_DST0_ENABLED_CHANNEL32(c)
         mkCvt(op, dTy, dst0[c], sTy, src(0, c))->rnd = ROUND_Z;
      break;
   case SM4_OPCODE_ITOF:
   case SM4_OPCODE_UTOF:
   case SM4_OPCODE_F32TOF16:
   case SM4_OPCODE_F16TOF32:
   case SM4_OPCODE_DTOF:
   case SM4_OPCODE_FTOD:
      FOR_EACH_DST0_ENABLED_CHANNEL32(c)
         mkCvt(op, dTy, dst0[c], sTy, src(0, c));
      break;

   case SM4_OPCODE_CUT:
   case SM4_OPCODE_CUT_STREAM:
      mkOp1(OP_RESTART, TYPE_U32, NULL, mkImm(0))->fixed = 1;
      break;
   case SM4_OPCODE_EMIT:
   case SM4_OPCODE_EMIT_STREAM:
      mkOp1(OP_EMIT, TYPE_U32, NULL, mkImm(0))->fixed = 1;
      break;
   case SM4_OPCODE_EMITTHENCUT:
   case SM4_OPCODE_EMITTHENCUT_STREAM:
   {
      Instruction *cut = mkOp1(OP_EMIT, TYPE_U32, NULL,  mkImm(0));
      cut->fixed = 1;
      cut->subOp = NV50_IR_SUBOP_EMIT_RESTART;
   }
      break;

   case SM4_OPCODE_DISCARD:
      info.prop.fp.usesDiscard = TRUE;
      mkOp(OP_DISCARD, TYPE_NONE, NULL)->setPredicate(
         insn->insn.test_nz ? CC_P : CC_NOT_P, src(0, 0));
      break;

   case SM4_OPCODE_CALL:
   case SM4_OPCODE_CALLC:
      assert(!"CALL/CALLC not implemented");
      break;

   case SM4_OPCODE_RET:
      // XXX: the following doesn't work with subroutines / early ret
      if (!haveNextPhase(pos))
         finalizeShader();
      else
         phaseEnded = phase + 1;
      break;

   case SM4_OPCODE_IF:
   {
      BasicBlock *ifClause = new BasicBlock(func);

      bb->cfg.attach(&ifClause->cfg, Graph::Edge::TREE);
      condBBs.push(bb);
      joinBBs.push(bb);

      mkFlow(OP_BRA, NULL, insn->insn.test_nz ? CC_NOT_P : CC_P, src(0, 0));

      setPosition(ifClause, true);
   }
      break;
   case SM4_OPCODE_ELSE:
   {
      BasicBlock *elseClause = new BasicBlock(func);
      BasicBlock *forkPoint = reinterpret_cast<BasicBlock *>(condBBs.pop().u.p);

      forkPoint->cfg.attach(&elseClause->cfg, Graph::Edge::TREE);
      condBBs.push(bb);

      forkPoint->getExit()->asFlow()->target.bb = elseClause;
      if (!bb->isTerminated())
         mkFlow(OP_BRA, NULL, CC_ALWAYS, NULL);

      setPosition(elseClause, true);
   }
      break;
   case SM4_OPCODE_ENDIF:
   {
      BasicBlock *convPoint = new BasicBlock(func);
      BasicBlock *lastBB = reinterpret_cast<BasicBlock *>(condBBs.pop().u.p);
      BasicBlock *forkPoint = reinterpret_cast<BasicBlock *>(joinBBs.pop().u.p);

      if (!bb->isTerminated()) {
         // we only want join if none of the clauses ended with CONT/BREAK/RET
         if (lastBB->getExit()->op == OP_BRA && joinBBs.getSize() < 6)
            insertConvergenceOps(convPoint, forkPoint);
         mkFlow(OP_BRA, convPoint, CC_ALWAYS, NULL);
         bb->cfg.attach(&convPoint->cfg, Graph::Edge::FORWARD);
      }

      if (lastBB->getExit()->op == OP_BRA) {
         lastBB->cfg.attach(&convPoint->cfg, Graph::Edge::FORWARD);
         lastBB->getExit()->asFlow()->target.bb = convPoint;
      }
      setPosition(convPoint, true);
   }
      break;

   case SM4_OPCODE_SWITCH:
   case SM4_OPCODE_CASE:
   case SM4_OPCODE_ENDSWITCH:
      assert(!"SWITCH/CASE/ENDSWITCH not implemented");
      break;

   case SM4_OPCODE_LOOP:
   {
      BasicBlock *loopHeader = new BasicBlock(func);
      BasicBlock *loopBreak = new BasicBlock(func);

      loopBBs.push(loopHeader);
      breakBBs.push(loopBreak);
      if (loopBBs.getSize() > func->loopNestingBound)
         func->loopNestingBound++;

      mkFlow(OP_PREBREAK, loopBreak, CC_ALWAYS, NULL);

      bb->cfg.attach(&loopHeader->cfg, Graph::Edge::TREE);
      setPosition(loopHeader, true);
      mkFlow(OP_PRECONT, loopHeader, CC_ALWAYS, NULL);
   }
      break;
   case SM4_OPCODE_ENDLOOP:
   {
      BasicBlock *loopBB = reinterpret_cast<BasicBlock *>(loopBBs.pop().u.p);

      if (!bb->isTerminated()) {
         mkFlow(OP_CONT, loopBB, CC_ALWAYS, NULL);
         bb->cfg.attach(&loopBB->cfg, Graph::Edge::BACK);
      }
      setPosition(reinterpret_cast<BasicBlock *>(breakBBs.pop().u.p), true);
   }
      break;
   case SM4_OPCODE_BREAK:
   {
      if (bb->isTerminated())
         break;
      BasicBlock *breakBB = reinterpret_cast<BasicBlock *>(breakBBs.peek().u.p);
      mkFlow(OP_BREAK, breakBB, CC_ALWAYS, NULL);
      bb->cfg.attach(&breakBB->cfg, Graph::Edge::CROSS);
   }
      break;
   case SM4_OPCODE_BREAKC:
   {
      BasicBlock *nextBB = new BasicBlock(func);
      BasicBlock *breakBB = reinterpret_cast<BasicBlock *>(breakBBs.peek().u.p);
      CondCode cc = insn->insn.test_nz ? CC_P : CC_NOT_P;
      mkFlow(OP_BREAK, breakBB, cc, src(0, 0));
      bb->cfg.attach(&breakBB->cfg, Graph::Edge::CROSS);
      bb->cfg.attach(&nextBB->cfg, Graph::Edge::FORWARD);
      setPosition(nextBB, true);
   }
      break;
   case SM4_OPCODE_CONTINUE:
   {
      if (bb->isTerminated())
         break;
      BasicBlock *contBB = reinterpret_cast<BasicBlock *>(loopBBs.peek().u.p);
      mkFlow(OP_CONT, contBB, CC_ALWAYS, NULL);
      contBB->explicitCont = true;
      bb->cfg.attach(&contBB->cfg, Graph::Edge::BACK);
   }
      break;
   case SM4_OPCODE_CONTINUEC:
   {
      BasicBlock *nextBB = new BasicBlock(func);
      BasicBlock *contBB = reinterpret_cast<BasicBlock *>(loopBBs.peek().u.p);
      mkFlow(OP_CONT, contBB, insn->insn.test_nz ? CC_P : CC_NOT_P, src(0, 0));
      bb->cfg.attach(&contBB->cfg, Graph::Edge::BACK);
      bb->cfg.attach(&nextBB->cfg, Graph::Edge::FORWARD);
      setPosition(nextBB, true);
   }
      break;

   case SM4_OPCODE_SAMPLE:
   case SM4_OPCODE_SAMPLE_C:
   case SM4_OPCODE_SAMPLE_C_LZ:
   case SM4_OPCODE_SAMPLE_L:
   case SM4_OPCODE_SAMPLE_D:
   case SM4_OPCODE_SAMPLE_B:
      handleSAMPLE(op, dst0);
      break;
   case SM4_OPCODE_LD:
   case SM4_OPCODE_LD_MS:
      handleLOAD(dst0);
      break;

   case SM4_OPCODE_GATHER4:
      assert(!"GATHER4 not implemented\n");
      break;

   case SM4_OPCODE_RESINFO:
      handleQUERY(dst0, TXQ_DIMS);
      break;
   case SM4_OPCODE_SAMPLE_POS:
      handleQUERY(dst0, TXQ_SAMPLE_POSITION);
      break;

   case SM4_OPCODE_NOP:
      mkOp(OP_NOP, TYPE_NONE, NULL);
      break;

   case SM4_OPCODE_HS_DECLS:
      // XXX: any significance ?
      break;
   case SM4_OPCODE_HS_CONTROL_POINT_PHASE:
      phase = 0;
      break;
   case SM4_OPCODE_HS_FORK_PHASE:
      if (phase != 1)
         subPhase = 0;
      phase = 1;
      phaseInstance = (phaseStart == pos) ? (phaseInstance + 1) : 0;
      phaseStart = pos;
      if (info.prop.tp.outputPatchSize < phaseInstCnt[0][subPhase])
         unrollPhase = true;
      break;
   case SM4_OPCODE_HS_JOIN_PHASE:
      if (phase != 2)
         subPhase = 0;
      phase = 2;
      phaseInstance = (phaseStart == pos) ? (phaseInstance + 1) : 0;
      phaseStart = pos;
      if (info.prop.tp.outputPatchSize < phaseInstCnt[1][subPhase])
         unrollPhase = true;
      break;

   default:
      ERROR("SM4_OPCODE_#%u illegal / not supported\n", insn->opcode);
      abort();
      return false;
   }

   for (c = 0; c < nc; ++c) {
      if (nDstOpnds >= 1 && rDst0[c]) {
         if (dst0[c] != rDst0[c])
            mkMov(rDst0[c], dst0[c]);
         saveDst(0, c, rDst0[c]);
      }
      if (nDstOpnds >= 2 && rDst1[c]) {
         if (dst1[c] != rDst1[c])
            mkMov(rDst1[c], dst1[c]);
         saveDst(1, c, rDst1[c]);
      }
   }

   memset(srcPtr, 0, sizeof(srcPtr));
   memset(dstPtr, 0, sizeof(dstPtr));
   memset(vtxBase, 0, sizeof(vtxBase));
   return true;
}

void
Converter::exportOutputs()
{
   for (int i = 0; i < info.numOutputs; ++i) {
      for (int c = 0; c < 4; ++c) {
         if (!oData.exists(i, c))
            continue;
         Symbol *sym = mkSymbol(FILE_SHADER_OUTPUT, 0, TYPE_F32,
                                info.out[i].slot[c] * 4);
         Value *val = oData.load(i, c, NULL);
         if (val)
            mkStore(OP_EXPORT, TYPE_F32, sym, NULL, val);
      }
   }
}

Converter::Converter(Program *p, struct nv50_ir_prog_info *s)
   : tData32(this),
     tData64(this),
     oData(this),
     info(*s),
     sm4(*reinterpret_cast<const sm4_program *>(s->bin.source)),
     prog(p)
{
   memset(srcPtr, 0, sizeof(srcPtr));
   memset(dstPtr, 0, sizeof(dstPtr));
   memset(vtxBase, 0, sizeof(vtxBase));

   memset(interpMode, 0, sizeof(interpMode));

   nrRegVals = nrArrays = arrayVol = 0;

   for (phase = 3; phase > 0; --phase)
      for (unsigned int i = 0; i < PIPE_MAX_SHADER_OUTPUTS; ++i)
         out[phase - 1][i].sn = TGSI_SEMANTIC_COUNT;

   unrollPhase = false;
   phaseStart = 0;
   subPhaseCnt[0] = subPhaseCnt[1] = 0;
}

Converter::~Converter()
{
   if (lData)
      delete[] lData;

   if (subPhaseCnt[0])
      delete[] phaseInstCnt[0];
   if (subPhaseCnt[1])
      delete[] phaseInstCnt[1];
}

bool
Converter::haveNextPhase(unsigned int pos) const
{
   ++pos;
   return (pos < sm4.insns.size()) &&
      (sm4.insns[pos]->opcode == SM4_OPCODE_HS_FORK_PHASE ||
       sm4.insns[pos]->opcode == SM4_OPCODE_HS_JOIN_PHASE);
}

bool
Converter::run()
{
   parseSignature();

   for (unsigned int pos = 0; pos < sm4.dcls.size(); ++pos)
      inspectDeclaration(*sm4.dcls[pos]);

   phaseInstCnt[0] = new unsigned int [subPhaseCnt[0]];
   phaseInstCnt[1] = new unsigned int [subPhaseCnt[1]];
   for (int i = 0; i < subPhaseCnt[0]; ++i)
      phaseInstCnt[0][i] = -1;
   for (int i = 0; i < subPhaseCnt[1]; ++i)
      phaseInstCnt[1][i] = -1;
   // re-increased in handleDeclaration:
   subPhaseCnt[0] = subPhaseCnt[1] = 0;

   allocateValues();
   nrArrays = 0;
   for (unsigned int pos = 0; pos < sm4.dcls.size(); ++pos)
      handleDeclaration(*sm4.dcls[pos]);

   info.assignSlots(&info);

   if (sm4.dcls.size() == 0 && sm4.insns.size() == 0)
      return true;

   BasicBlock *entry = new BasicBlock(prog->main);
   BasicBlock *leave = new BasicBlock(prog->main);

   prog->main->setEntry(entry);
   prog->main->setExit(leave);

   setPosition(entry, true);

   entryBBs.push(entry);
   leaveBBs.push(leave);

   if (prog->getType() == Program::TYPE_FRAGMENT) {
      Symbol *sv = mkSysVal(SV_POSITION, 3);
      fragCoord[3] = mkOp1v(OP_RDSV, TYPE_F32, getSSA(), sv);
      mkOp1(OP_RCP, TYPE_F32, fragCoord[3], fragCoord[3]);
   } else
   if (prog->getType() == Program::TYPE_TESSELLATION_EVAL) {
      const int n = (info.prop.tp.domain == PIPE_PRIM_TRIANGLES) ? 3 : 2;
      int c;
      for (c = 0; c < n; ++c)
         domainPt[c] =
            mkOp1v(OP_RDSV, TYPE_F32, getSSA(), mkSysVal(SV_TESS_COORD, c));
      if (c == 2)
         domainPt[2] = loadImm(NULL, 0.0f);
   }

   finalized = false;
   phaseEnded = 0;
   phase = 0;
   subPhase = 0;
   for (unsigned int pos = 0; pos < sm4.insns.size(); ++pos) {
      handleInstruction(pos);
      if (likely(phase == 0) || (phaseEnded < 2))
         continue;
      phaseEnded = 0;
      if (!unrollPhase || !phaseInstanceUsed) {
         ++subPhase;
         continue;
      }
      phaseInstanceUsed = false;
      if (phaseInstance < (phaseInstCnt[phase - 1][subPhase] - 1))
         pos = phaseStart - 1;
      else
         ++subPhase;
   }
   finalizeShader();

   return true;
}

} // anonymous namespace

namespace nv50_ir {

bool
Program::makeFromSM4(struct nv50_ir_prog_info *info)
{
   Converter bld(this, info);
   return bld.run();
}

} // namespace nv50_ir
