/*
 * Mesa 3-D graphics library
 * Version:  6.3
 *
 * Copyright (C) 1999-2004  Brian Paul   All Rights Reserved.
 *
 * 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
 * BRIAN PAUL 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.
 */

/**
 * \file t_vb_arb_program_sse.c
 *
 * Translate simplified vertex_program representation to
 * x86/x87/SSE/SSE2 machine code using mesa's rtasm runtime assembler.
 *
 * This is very much a first attempt - build something that works.
 * There are probably better approaches for applying SSE to vertex
 * programs, and the whole thing is crying out for static analysis of
 * the programs to avoid redundant operations.
 *
 * \author Keith Whitwell
 */

#include "glheader.h"
#include "context.h"
#include "imports.h"
#include "macros.h"
#include "mtypes.h"
#include "arbprogparse.h"
#include "program.h"
#include "math/m_matrix.h"
#include "math/m_translate.h"
#include "t_context.h"
#include "t_vb_arbprogram.h"

#if defined(USE_SSE_ASM)

#include "x86/rtasm/x86sse.h"
#include "x86/common_x86_asm.h"

#define X    0
#define Y    1
#define Z    2
#define W    3

/* Reg usage:
 *
 * EAX - temp
 * EBX - point to 'm->File[0]'
 * ECX - point to 'm->File[3]'
 * EDX - holds 'm'
 * EBP,
 * ESI,
 * EDI
 */

#define DISASSEM 0

#define FAIL								\
do {									\
   _mesa_printf("x86 translation failed in %s\n", __FUNCTION__);	\
   return GL_FALSE;							\
} while (0)

struct compilation {
   struct x86_function func;
   struct tnl_compiled_program *p;   
   GLuint insn_counter;

   struct {
      GLuint file:2;
      GLuint idx:7;
      GLuint dirty:1;
      GLuint last_used:10;
   } xmm[8];

   struct {
      struct x86_reg base;
   } file[4];

   GLboolean have_sse2;
   GLshort fpucntl;
};

static INLINE GLboolean eq( struct x86_reg a,
			    struct x86_reg b )
{
   return (a.file == b.file &&
	   a.idx == b.idx &&
	   a.mod == b.mod &&
	   a.disp == b.disp);
}
      
static GLint get_offset( const void *a, const void *b )
{
   return (const char *)b - (const char *)a;
}


static struct x86_reg get_reg_ptr(GLuint file,
				  GLuint idx )
{
   struct x86_reg reg;

   switch (file) {
   case FILE_REG:
      reg = x86_make_reg(file_REG32, reg_BX);
      assert(idx != REG_UNDEF);
      break;
   case FILE_STATE_PARAM:
      reg = x86_make_reg(file_REG32, reg_CX);
      break;
   default:
      assert(0);
   }

   return x86_make_disp(reg, 16 * idx);
}
			  

static void spill( struct compilation *cp, GLuint idx )
{
   struct x86_reg oldval = get_reg_ptr(cp->xmm[idx].file,
				       cp->xmm[idx].idx);

   assert(cp->xmm[idx].dirty);
   sse_movups(&cp->func, oldval, x86_make_reg(file_XMM, idx));
   cp->xmm[idx].dirty = 0;
}

static struct x86_reg get_xmm_reg( struct compilation *cp )
{
   GLuint i;
   GLuint oldest = 0;

   for (i = 0; i < 8; i++) 
      if (cp->xmm[i].last_used < cp->xmm[oldest].last_used)
	 oldest = i;

   /* Need to write out the old value?
    */
   if (cp->xmm[oldest].dirty) 
      spill(cp, oldest);

   assert(cp->xmm[oldest].last_used != cp->insn_counter);

   cp->xmm[oldest].file = FILE_REG;
   cp->xmm[oldest].idx = REG_UNDEF;
   cp->xmm[oldest].last_used = cp->insn_counter;
   return x86_make_reg(file_XMM, oldest);
}

static void invalidate_xmm( struct compilation *cp, 
			    GLuint file, GLuint idx )
{
   GLuint i;

   /* Invalidate any old copy of this register in XMM0-7.  
    */
   for (i = 0; i < 8; i++) {
      if (cp->xmm[i].file == file && cp->xmm[i].idx == idx) {
	 cp->xmm[i].file = FILE_REG;
	 cp->xmm[i].idx = REG_UNDEF;
	 cp->xmm[i].dirty = 0;
	 break;
      }
   }
}
      

/* Return an XMM reg to receive the results of an operation.
 */
static struct x86_reg get_dst_xmm_reg( struct compilation *cp, 
				       GLuint file, GLuint idx )
{
   struct x86_reg reg;

   /* Invalidate any old copy of this register in XMM0-7.  Don't reuse
    * as this may be one of the arguments.
    */
   invalidate_xmm( cp, file, idx );

   reg = get_xmm_reg( cp );
   cp->xmm[reg.idx].file = file;
   cp->xmm[reg.idx].idx = idx;
   cp->xmm[reg.idx].dirty = 1;
   return reg;   
}

/* As above, but return a pointer.  Note - this pointer may alias
 * those returned by get_arg_ptr().
 */
static struct x86_reg get_dst_ptr( struct compilation *cp, 
				   GLuint file, GLuint idx )
{
   /* Invalidate any old copy of this register in XMM0-7.  Don't reuse
    * as this may be one of the arguments.
    */
   invalidate_xmm( cp, file, idx );

   return get_reg_ptr(file, idx);
}



/* Return an XMM reg if the argument is resident, otherwise return a
 * base+offset pointer to the saved value.
 */
static struct x86_reg get_arg( struct compilation *cp, GLuint file, GLuint idx )
{
   GLuint i;

   for (i = 0; i < 8; i++) {
      if (cp->xmm[i].file == file &&
	  cp->xmm[i].idx == idx) {
	 cp->xmm[i].last_used = cp->insn_counter;
	 return x86_make_reg(file_XMM, i);
      }
   }

   return get_reg_ptr(file, idx);
}

/* As above, but always return a pointer:
 */
static struct x86_reg get_arg_ptr( struct compilation *cp, GLuint file, GLuint idx )
{
   GLuint i;

   /* If there is a modified version of this register in one of the
    * XMM regs, write it out to memory.
    */
   for (i = 0; i < 8; i++) {
      if (cp->xmm[i].file == file && 
	  cp->xmm[i].idx == idx &&
	  cp->xmm[i].dirty) 
	 spill(cp, i);
   }

   return get_reg_ptr(file, idx);
}

/* Emulate pshufd insn in regular SSE, if necessary:
 */
static void emit_pshufd( struct compilation *cp,
			 struct x86_reg dst,
			 struct x86_reg arg0,
			 GLubyte shuf )
{
   if (cp->have_sse2) {
      sse2_pshufd(&cp->func, dst, arg0, shuf);
      cp->func.fn = 0;
   }
   else {
      if (!eq(dst, arg0)) 
	 sse_movups(&cp->func, dst, arg0);

      sse_shufps(&cp->func, dst, dst, shuf);
   }
}

static void set_fpu_round_neg_inf( struct compilation *cp )
{
   if (cp->fpucntl != RND_NEG_FPU) {
      struct x86_reg regEDX = x86_make_reg(file_REG32, reg_DX);
      struct arb_vp_machine *m = NULL;

      cp->fpucntl = RND_NEG_FPU;
      x87_fnclex(&cp->func);
      x87_fldcw(&cp->func, x86_make_disp(regEDX, get_offset(m, &m->fpucntl_rnd_neg)));
   }
}


/* Perform a reduced swizzle.  
 */
static GLboolean emit_RSW( struct compilation *cp, union instruction op ) 
{
   struct x86_reg arg0 = get_arg(cp, op.rsw.file0, op.rsw.idx0);
   struct x86_reg dst = get_dst_xmm_reg(cp, FILE_REG, op.rsw.dst);
   GLuint swz = op.rsw.swz;
   GLuint neg = op.rsw.neg;

   emit_pshufd(cp, dst, arg0, swz);
   
   if (neg) {
      struct x86_reg negs = get_arg(cp, FILE_REG, REG_SWZ);
      struct x86_reg tmp = get_xmm_reg(cp);
      /* Load 1,-1,0,0
       * Use neg as arg to pshufd
       * Multiply
       */
      emit_pshufd(cp, tmp, negs, 
		  SHUF((neg & 1) ? 1 : 0,
		       (neg & 2) ? 1 : 0,
		       (neg & 4) ? 1 : 0,
		       (neg & 8) ? 1 : 0));
      sse_mulps(&cp->func, dst, tmp);
   }

   return GL_TRUE;
}

/* Helper for writemask:
 */
static GLboolean emit_shuf_copy1( struct compilation *cp,
				  struct x86_reg dst,
				  struct x86_reg arg0,
				  struct x86_reg arg1,
				  GLubyte shuf )
{
   struct x86_reg tmp = get_xmm_reg(cp);
   sse_movups(&cp->func, dst, arg1);
   emit_pshufd(cp, dst, dst, shuf);
   emit_pshufd(cp, tmp, arg0, shuf);

   sse_movss(&cp->func, dst, tmp);

   emit_pshufd(cp, dst, dst, shuf);
   return GL_TRUE;
}


/* Helper for writemask:
 */
static GLboolean emit_shuf_copy2( struct compilation *cp,
				  struct x86_reg dst,
				  struct x86_reg arg0,
				  struct x86_reg arg1,
				  GLubyte shuf )
{
   struct x86_reg tmp = get_xmm_reg(cp);
   emit_pshufd(cp, dst, arg1, shuf);
   emit_pshufd(cp, tmp, arg0, shuf);

   sse_shufps(&cp->func, dst, tmp, SHUF(X, Y, Z, W));

   emit_pshufd(cp, dst, dst, shuf);
   return GL_TRUE;
}


static void emit_x87_ex2( struct compilation *cp )
{
   struct x86_reg st0 = x86_make_reg(file_x87, 0);
   struct x86_reg st1 = x86_make_reg(file_x87, 1);
   struct x86_reg st3 = x86_make_reg(file_x87, 3);

   set_fpu_round_neg_inf( cp );

   x87_fld(&cp->func, st0); /* a a */
   x87_fprndint( &cp->func );	/* int(a) a */
   x87_fld(&cp->func, st0); /* int(a) int(a) a */
   x87_fstp(&cp->func, st3); /* int(a) a int(a)*/
   x87_fsubp(&cp->func, st1); /* frac(a) int(a) */
   x87_f2xm1(&cp->func);    /* (2^frac(a))-1 int(a)*/
   x87_fld1(&cp->func);    /* 1 (2^frac(a))-1 int(a)*/
   x87_faddp(&cp->func, st1);	/* 2^frac(a) int(a) */
   x87_fscale(&cp->func);	/* 2^a */
}

#if 0
static GLboolean emit_MSK2( struct compilation *cp, union instruction op )
{
   struct x86_reg arg0 = get_arg(cp, op.msk.file, op.msk.arg);
   struct x86_reg arg1 = get_arg(cp, FILE_REG, op.msk.dst); /* NOTE! */
   struct x86_reg dst = get_dst_xmm_reg(cp, FILE_REG, op.msk.dst);
   
   /* make full width bitmask in tmp 
    * dst = ~tmp
    * tmp &= arg0
    * dst &= arg1
    * dst |= tmp
    */
   emit_pshufd(cp, tmp, get_arg(cp, FILE_REG, REG_NEGS), 
	       SHUF((op.msk.mask & 1) ? 2 : 0,
		    (op.msk.mask & 2) ? 2 : 0,
		    (op.msk.mask & 4) ? 2 : 0,
		    (op.msk.mask & 8) ? 2 : 0));
   sse2_pnot(&cp->func, dst, tmp);
   sse2_pand(&cp->func, arg0, tmp);
   sse2_pand(&cp->func, arg1, dst);
   sse2_por(&cp->func, tmp, dst);
   return GL_TRUE;
}
#endif


/* Used to implement write masking.  This and most of the other instructions
 * here would be easier to implement if there had been a translation
 * to a 2 argument format (dst/arg0, arg1) at the shader level before
 * attempting to translate to x86/sse code.
 */
static GLboolean emit_MSK( struct compilation *cp, union instruction op )
{
   struct x86_reg arg = get_arg(cp, op.msk.file, op.msk.idx);
   struct x86_reg dst0 = get_arg(cp, FILE_REG, op.msk.dst); /* NOTE! */
   struct x86_reg dst = get_dst_xmm_reg(cp, FILE_REG, op.msk.dst);
   
   /* Note that dst and dst0 refer to the same program variable, but
    * will definitely be different XMM registers.  We're effectively
    * treating this as a 2 argument SEL now, just one of which happens
    * always to be the same register as the destination.
    */

   switch (op.msk.mask) {
   case 0:
      sse_movups(&cp->func, dst, dst0);
      return GL_TRUE;

   case WRITEMASK_X:
      if (arg.file == file_XMM) {
	 sse_movups(&cp->func, dst, dst0);
	 sse_movss(&cp->func, dst, arg);
      }
      else {
	 struct x86_reg tmp = get_xmm_reg(cp);
	 sse_movups(&cp->func, dst, dst0);
	 sse_movss(&cp->func, tmp, arg);
	 sse_movss(&cp->func, dst, tmp);
      }
      return GL_TRUE;

   case WRITEMASK_XY:
      sse_movups(&cp->func, dst, dst0);
      sse_shufps(&cp->func, dst, arg, SHUF(X, Y, Z, W));
      return GL_TRUE;

   case WRITEMASK_ZW: 
      sse_movups(&cp->func, dst, arg);
      sse_shufps(&cp->func, dst, dst0, SHUF(X, Y, Z, W));
      return GL_TRUE;

   case WRITEMASK_YZW: 
      if (dst0.file == file_XMM) {
	 sse_movups(&cp->func, dst, arg);
	 sse_movss(&cp->func, dst, dst0);
      }
      else {
	 struct x86_reg tmp = get_xmm_reg(cp);      
	 sse_movups(&cp->func, dst, arg);
	 sse_movss(&cp->func, tmp, dst0);
	 sse_movss(&cp->func, dst, tmp);
      }
      return GL_TRUE;

   case WRITEMASK_Y:
      emit_shuf_copy1(cp, dst, arg, dst0, SHUF(Y,X,Z,W));
      return GL_TRUE;

   case WRITEMASK_Z: 
      emit_shuf_copy1(cp, dst, arg, dst0, SHUF(Z,Y,X,W));
      return GL_TRUE;

   case WRITEMASK_W: 
      emit_shuf_copy1(cp, dst, arg, dst0, SHUF(W,Y,Z,X));
      return GL_TRUE;

   case WRITEMASK_XZ:
      emit_shuf_copy2(cp, dst, arg, dst0, SHUF(X,Z,Y,W));
      return GL_TRUE;

   case WRITEMASK_XW: 
      emit_shuf_copy2(cp, dst, arg, dst0, SHUF(X,W,Z,Y));

   case WRITEMASK_YZ:      
      emit_shuf_copy2(cp, dst, arg, dst0, SHUF(Z,Y,X,W));
      return GL_TRUE;

   case WRITEMASK_YW:
      emit_shuf_copy2(cp, dst, arg, dst0, SHUF(W,Y,Z,X));
      return GL_TRUE;

   case WRITEMASK_XZW:
      emit_shuf_copy1(cp, dst, dst0, arg, SHUF(Y,X,Z,W));
      return GL_TRUE;

   case WRITEMASK_XYW: 
      emit_shuf_copy1(cp, dst, dst0, arg, SHUF(Z,Y,X,W));
      return GL_TRUE;

   case WRITEMASK_XYZ: 
      emit_shuf_copy1(cp, dst, dst0, arg, SHUF(W,Y,Z,X));
      return GL_TRUE;

   case WRITEMASK_XYZW:
      sse_movups(&cp->func, dst, arg);
      return GL_TRUE;      

   default:
      assert(0);
      break;
   }
}



static GLboolean emit_PRT( struct compilation *cp, union instruction op )
{
   FAIL;
}


/**
 * The traditional instructions.  All operate on internal registers
 * and ignore write masks and swizzling issues.
 */

static GLboolean emit_ABS( struct compilation *cp, union instruction op ) 
{
   struct x86_reg arg0 = get_arg(cp, op.alu.file0, op.alu.idx0);
   struct x86_reg dst = get_dst_xmm_reg(cp, FILE_REG, op.alu.dst);
   struct x86_reg neg = get_reg_ptr(FILE_REG, REG_NEG);

   sse_movups(&cp->func, dst, arg0);
   sse_mulps(&cp->func, dst, neg);
   sse_maxps(&cp->func, dst, arg0);
   return GL_TRUE;
}

static GLboolean emit_ADD( struct compilation *cp, union instruction op )
{
   struct x86_reg arg0 = get_arg(cp, op.alu.file0, op.alu.idx0);
   struct x86_reg arg1 = get_arg(cp, op.alu.file1, op.alu.idx1);
   struct x86_reg dst = get_dst_xmm_reg(cp, FILE_REG, op.alu.dst);

   sse_movups(&cp->func, dst, arg0);
   sse_addps(&cp->func, dst, arg1);
   return GL_TRUE;
}


/* The dotproduct instructions don't really do that well in sse:
 */
static GLboolean emit_DP3( struct compilation *cp, union instruction op )
{
   struct x86_reg arg0 = get_arg(cp, op.alu.file0, op.alu.idx0);
   struct x86_reg arg1 = get_arg(cp, op.alu.file1, op.alu.idx1);
   struct x86_reg dst = get_dst_xmm_reg(cp, FILE_REG, op.alu.dst);
   struct x86_reg tmp = get_xmm_reg(cp); 

   sse_movups(&cp->func, dst, arg0);
   sse_mulps(&cp->func, dst, arg1);
   
   /* Now the hard bit: sum the first 3 values:
    */ 
   sse_movhlps(&cp->func, tmp, dst);
   sse_addss(&cp->func, dst, tmp); /* a*x+c*z, b*y, ?, ? */
   emit_pshufd(cp, tmp, dst, SHUF(Y,X,W,Z));
   sse_addss(&cp->func, dst, tmp);
   sse_shufps(&cp->func, dst, dst, SHUF(X, X, X, X));
   return GL_TRUE;
}



static GLboolean emit_DP4( struct compilation *cp, union instruction op )
{
   struct x86_reg arg0 = get_arg(cp, op.alu.file0, op.alu.idx0);
   struct x86_reg arg1 = get_arg(cp, op.alu.file1, op.alu.idx1);
   struct x86_reg dst = get_dst_xmm_reg(cp, FILE_REG, op.alu.dst);
   struct x86_reg tmp = get_xmm_reg(cp);      

   sse_movups(&cp->func, dst, arg0);
   sse_mulps(&cp->func, dst, arg1);
   
   /* Now the hard bit: sum the values:
    */ 
   sse_movhlps(&cp->func, tmp, dst);
   sse_addps(&cp->func, dst, tmp); /* a*x+c*z, b*y+d*w, a*x+c*z, b*y+d*w */
   emit_pshufd(cp, tmp, dst, SHUF(Y,X,W,Z));
   sse_addss(&cp->func, dst, tmp);
   sse_shufps(&cp->func, dst, dst, SHUF(X, X, X, X));
   return GL_TRUE;
}

static GLboolean emit_DPH( struct compilation *cp, union instruction op )
{
   struct x86_reg arg0 = get_arg(cp, op.alu.file0, op.alu.idx0); 
   struct x86_reg arg1 = get_arg(cp, op.alu.file1, op.alu.idx1); 
   struct x86_reg dst = get_dst_xmm_reg(cp, FILE_REG, op.alu.dst);
   struct x86_reg ones = get_reg_ptr(FILE_REG, REG_ONES);
   struct x86_reg tmp = get_xmm_reg(cp);      

   emit_pshufd(cp, dst, arg0, SHUF(W,X,Y,Z));
   sse_movss(&cp->func, dst, ones);
   emit_pshufd(cp, dst, dst, SHUF(W,X,Y,Z));
   sse_mulps(&cp->func, dst, arg1);
   
   /* Now the hard bit: sum the values (from DP4):
    */ 
   sse_movhlps(&cp->func, tmp, dst);
   sse_addps(&cp->func, dst, tmp); /* a*x+c*z, b*y+d*w, a*x+c*z, b*y+d*w */
   emit_pshufd(cp, tmp, dst, SHUF(Y,X,W,Z));
   sse_addss(&cp->func, dst, tmp);
   sse_shufps(&cp->func, dst, dst, SHUF(X, X, X, X));
   return GL_TRUE;
}

#if 0
static GLboolean emit_DST( struct compilation *cp, union instruction op )
{
    struct x86_reg arg0 = get_arg_ptr(cp, op.alu.file0, op.alu.idx0); 
    struct x86_reg arg1 = get_arg_ptr(cp, op.alu.file1, op.alu.idx1); 
    struct x86_reg dst = get_dst_ptr(cp, FILE_REG, op.alu.dst); 

/*    dst[0] = 1.0     * 1.0F; */
/*    dst[1] = arg0[1] * arg1[1]; */
/*    dst[2] = arg0[2] * 1.0; */
/*    dst[3] = 1.0     * arg1[3]; */

    /* Would rather do some of this with integer regs, but:
     *  1) No proper support for immediate values yet
     *  2) I'd need to push/pop somewhere to get a free reg.
     */ 
    x87_fld1(&cp->func);
    x87_fstp(&cp->func, dst); /* would rather do an immediate store... */
    x87_fld(&cp->func, x86_make_disp(arg0, 4));
    x87_fmul(&cp->func, x86_make_disp(arg1, 4));
    x87_fstp(&cp->func, x86_make_disp(dst, 4));
    
    if (!eq(arg0, dst)) {
       x86_fld(&cp->func, x86_make_disp(arg0, 8));
       x86_stp(&cp->func, x86_make_disp(dst, 8));
    }

    if (!eq(arg1, dst)) {
       x86_fld(&cp->func, x86_make_disp(arg0, 12));
       x86_stp(&cp->func, x86_make_disp(dst, 12));
    } 

    return GL_TRUE;
}
#else
static GLboolean emit_DST( struct compilation *cp, union instruction op )
{
    struct x86_reg arg0 = get_arg(cp, op.alu.file0, op.alu.idx0); 
    struct x86_reg arg1 = get_arg(cp, op.alu.file1, op.alu.idx1); 
    struct x86_reg dst = get_dst_xmm_reg(cp, FILE_REG, op.alu.dst); 
    struct x86_reg tmp = get_xmm_reg(cp);
    struct x86_reg ones = get_reg_ptr(FILE_REG, REG_ONES);

    emit_shuf_copy2(cp, dst, arg0, ones, SHUF(X,W,Z,Y));
    emit_shuf_copy2(cp, tmp, arg1, ones, SHUF(X,Z,Y,W));
    sse_mulps(&cp->func, dst, tmp);

/*    dst[0] = 1.0     * 1.0F; */
/*    dst[1] = arg0[1] * arg1[1]; */
/*    dst[2] = arg0[2] * 1.0; */
/*    dst[3] = 1.0     * arg1[3]; */

    return GL_TRUE;
}
#endif

static GLboolean emit_LG2( struct compilation *cp, union instruction op ) 
{
   struct x86_reg arg0 = get_arg_ptr(cp, op.alu.file0, op.alu.idx0); 
   struct x86_reg dst = get_dst_ptr(cp, FILE_REG, op.alu.dst); 

   x87_fld1(&cp->func);		/* 1 */
   x87_fld(&cp->func, arg0);	/* a0 1 */
   x87_fyl2x(&cp->func);	/* log2(a0) */
   x87_fst(&cp->func, x86_make_disp(dst, 0));
   x87_fst(&cp->func, x86_make_disp(dst, 4));
   x87_fst(&cp->func, x86_make_disp(dst, 8));
   x87_fstp(&cp->func, x86_make_disp(dst, 12));
   
   return GL_TRUE;
}


static GLboolean emit_EX2( struct compilation *cp, union instruction op ) 
{
   struct x86_reg arg0 = get_arg_ptr(cp, op.alu.file0, op.alu.idx0); 
   struct x86_reg dst = get_dst_ptr(cp, FILE_REG, op.alu.dst);

   /* CAUTION: dst may alias arg0!
    */
   x87_fld(&cp->func, arg0);	

   emit_x87_ex2(cp);

   x87_fst(&cp->func, x86_make_disp(dst, 0));    
   x87_fst(&cp->func, x86_make_disp(dst, 4));    
   x87_fst(&cp->func, x86_make_disp(dst, 8));    
   x87_fst(&cp->func, x86_make_disp(dst, 12));    
   return GL_TRUE;
}

static GLboolean emit_EXP( struct compilation *cp, union instruction op )
{
    struct x86_reg arg0 = get_arg_ptr(cp, op.alu.file0, op.alu.idx0); 
    struct x86_reg dst = get_dst_ptr(cp, FILE_REG, op.alu.dst); 
    struct x86_reg st0 = x86_make_reg(file_x87, 0);
    struct x86_reg st1 = x86_make_reg(file_x87, 1);
    struct x86_reg st3 = x86_make_reg(file_x87, 3);

    /* CAUTION: dst may alias arg0!
     */
    x87_fld(&cp->func, arg0);	/* arg0.x */
    x87_fld(&cp->func, st0); /* arg arg */

    /* by default, fpu is setup to round-to-nearest.  We want to
     * change this now, and track the state through to the end of the
     * generated function so that it isn't repeated unnecessarily.
     * Alternately, could subtract .5 to get round to -inf behaviour.
     */
    set_fpu_round_neg_inf( cp );
    x87_fprndint( &cp->func );	/* flr(a) a */
    x87_fld(&cp->func, st0); /* flr(a) flr(a) a */
    x87_fld1(&cp->func);    /* 1 floor(a) floor(a) a */
    x87_fst(&cp->func, x86_make_disp(dst, 12));  /* stack unchanged */
    x87_fscale(&cp->func);  /* 2^floor(a) floor(a) a */
    x87_fst(&cp->func, st3); /* 2^floor(a) floor(a) a 2^floor(a)*/
    x87_fstp(&cp->func, x86_make_disp(dst, 0)); /* flr(a) a 2^flr(a) */
    x87_fsubrp(&cp->func, st1); /* frac(a) 2^flr(a) */
    x87_fst(&cp->func, x86_make_disp(dst, 4));    /* frac(a) 2^flr(a) */
    x87_f2xm1(&cp->func);    /* (2^frac(a))-1 2^flr(a)*/
    x87_fld1(&cp->func);    /* 1 (2^frac(a))-1 2^flr(a)*/
    x87_faddp(&cp->func, st1);	/* 2^frac(a) 2^flr(a) */
    x87_fmulp(&cp->func, st1);	/* 2^a */
    x87_fst(&cp->func, x86_make_disp(dst, 8));    
    


/*    dst[0] = 2^floor(tmp); */
/*    dst[1] = frac(tmp); */
/*    dst[2] = 2^floor(tmp) * 2^frac(tmp); */
/*    dst[3] = 1.0F; */
    return GL_TRUE;
}

static GLboolean emit_LOG( struct compilation *cp, union instruction op )
{
    struct x86_reg arg0 = get_arg_ptr(cp, op.alu.file0, op.alu.idx0); 
    struct x86_reg dst = get_dst_ptr(cp, FILE_REG, op.alu.dst); 
    struct x86_reg st0 = x86_make_reg(file_x87, 0);
    struct x86_reg st1 = x86_make_reg(file_x87, 1);
    struct x86_reg st2 = x86_make_reg(file_x87, 2);
 
    /* CAUTION: dst may alias arg0!
     */
    x87_fld(&cp->func, arg0);	/* arg0.x */
    x87_fabs(&cp->func);	/* |arg0.x| */
    x87_fxtract(&cp->func);	/* mantissa(arg0.x), exponent(arg0.x) */
    x87_fst(&cp->func, st2);	/* mantissa, exponent, mantissa */
    x87_fld1(&cp->func);	/* 1, mantissa, exponent, mantissa */
    x87_fyl2x(&cp->func); 	/* log2(mantissa), exponent, mantissa */
    x87_fadd(&cp->func, st0, st1);	/* e+l2(m), e, m  */
    x87_fstp(&cp->func, x86_make_disp(dst, 8)); /* e, m */

    x87_fld1(&cp->func);	/* 1, e, m */
    x87_fsub(&cp->func, st1, st0);	/* 1, e-1, m */
    x87_fstp(&cp->func, x86_make_disp(dst, 12)); /* e-1,m */
    x87_fstp(&cp->func, dst);	/* m */

    x87_fadd(&cp->func, st0, st0);	/* 2m */
    x87_fstp(&cp->func, x86_make_disp(dst, 4));	

    return GL_TRUE;
}

static GLboolean emit_FLR( struct compilation *cp, union instruction op ) 
{
   struct x86_reg arg0 = get_arg_ptr(cp, op.alu.file0, op.alu.idx0); 
   struct x86_reg dst = get_dst_ptr(cp, FILE_REG, op.alu.dst); 
   int i;

   set_fpu_round_neg_inf( cp );

   for (i = 0; i < 4; i++) {
      x87_fld(&cp->func, x86_make_disp(arg0, i*4));   
      x87_fprndint( &cp->func );   
      x87_fstp(&cp->func, x86_make_disp(dst, i*4));
   }


   return GL_TRUE;
}

static GLboolean emit_FRC( struct compilation *cp, union instruction op ) 
{
   struct x86_reg arg0 = get_arg_ptr(cp, op.alu.file0, op.alu.idx0); 
   struct x86_reg dst = get_dst_ptr(cp, FILE_REG, op.alu.dst); 
   struct x86_reg st0 = x86_make_reg(file_x87, 0);
   struct x86_reg st1 = x86_make_reg(file_x87, 1);
   int i;

   set_fpu_round_neg_inf( cp );

   /* Knowing liveness info or even just writemask would be useful
    * here:
    */
   for (i = 0; i < 4; i++) {
      x87_fld(&cp->func, x86_make_disp(arg0, i*4));   
      x87_fld(&cp->func, st0);	/* a a */
      x87_fprndint( &cp->func );   /* flr(a) a */
      x87_fsubrp(&cp->func, st1); /* frc(a) */
      x87_fstp(&cp->func, x86_make_disp(dst, i*4));
   }

   return GL_TRUE;
}



static GLboolean emit_LIT( struct compilation *cp, union instruction op )
{
#if 1
   struct x86_reg arg0 = get_arg_ptr(cp, op.alu.file0, op.alu.idx0); 
   struct x86_reg dst = get_dst_ptr(cp, FILE_REG, op.alu.dst); 
   struct x86_reg lit = get_arg(cp, FILE_REG, REG_LIT);
   struct x86_reg tmp = get_xmm_reg(cp);
   struct x86_reg st1 = x86_make_reg(file_x87, 1);
   struct x86_reg regEAX = x86_make_reg(file_REG32, reg_AX);
   GLubyte *fixup1, *fixup2;


   /* Load the interesting parts of arg0:
    */
   x87_fld(&cp->func, x86_make_disp(arg0, 12));	/* a3 */
   x87_fld(&cp->func, x86_make_disp(arg0, 4)); /* a1 a3 */
   x87_fld(&cp->func, x86_make_disp(arg0, 0)); /* a0 a1 a3 */
   
   /* Intialize dst:
    */
   sse_movaps(&cp->func, tmp, lit);
   sse_movaps(&cp->func, dst, tmp);
   
   /* Check arg0[0]:
    */
   x87_fldz(&cp->func);		/* 0 a0 a1 a3 */
   x87_fucomp(&cp->func, st1);	/* a0 a1 a3 */
   x87_fnstsw(&cp->func, regEAX);
   x86_sahf(&cp->func);
   fixup1 = x86_jcc_forward(&cp->func, cc_AE); 
   
   x87_fstp(&cp->func, x86_make_disp(dst, 4));	/* a1 a3 */

   /* Check arg0[1]:
    */ 
   x87_fldz(&cp->func);		/* 0 a1 a3 */
   x87_fucomp(&cp->func, st1);	/* a1 a3 */
   x87_fnstsw(&cp->func, regEAX);
   x86_sahf(&cp->func);
   fixup2 = x86_jcc_forward(&cp->func, cc_AE); 

   /* Compute pow(a1, a3)
    */
   x87_fyl2x(&cp->func);	/* a3*log2(a1) */

   emit_x87_ex2( cp );		/* 2^(a3*log2(a1)) */

   x87_fstp(&cp->func, x86_make_disp(dst, 8));
   
   /* Land jumps:
    */
   x86_fixup_fwd_jump(&cp->func, fixup1);
   x86_fixup_fwd_jump(&cp->func, fixup2);
#else
   struct x86_reg dst = get_dst_xmm_reg(cp, FILE_REG, op.alu.dst); 
   struct x86_reg ones = get_reg_ptr(FILE_REG, REG_LIT);
   sse_movups(&cp->func, dst, ones);
#endif   
   return GL_TRUE;
}



static GLboolean emit_MAX( struct compilation *cp, union instruction op )
{
   struct x86_reg arg0 = get_arg(cp, op.alu.file0, op.alu.idx0);
   struct x86_reg arg1 = get_arg(cp, op.alu.file1, op.alu.idx1);
   struct x86_reg dst = get_dst_xmm_reg(cp, FILE_REG, op.alu.dst);

   sse_movups(&cp->func, dst, arg0);
   sse_maxps(&cp->func, dst, arg1);
   return GL_TRUE;
}


static GLboolean emit_MIN( struct compilation *cp, union instruction op )
{
   struct x86_reg arg0 = get_arg(cp, op.alu.file0, op.alu.idx0);
   struct x86_reg arg1 = get_arg(cp, op.alu.file1, op.alu.idx1);
   struct x86_reg dst = get_dst_xmm_reg(cp, FILE_REG, op.alu.dst);

   sse_movups(&cp->func, dst, arg0);
   sse_minps(&cp->func, dst, arg1);
   return GL_TRUE;
}

static GLboolean emit_MOV( struct compilation *cp, union instruction op )
{
   struct x86_reg arg0 = get_arg(cp, op.alu.file0, op.alu.idx0);
   struct x86_reg dst = get_dst_xmm_reg(cp, FILE_REG, op.alu.dst);

   sse_movups(&cp->func, dst, arg0);
   return GL_TRUE;
}

static GLboolean emit_MUL( struct compilation *cp, union instruction op )
{
   struct x86_reg arg0 = get_arg(cp, op.alu.file0, op.alu.idx0);
   struct x86_reg arg1 = get_arg(cp, op.alu.file1, op.alu.idx1);
   struct x86_reg dst = get_dst_xmm_reg(cp, FILE_REG, op.alu.dst);

   sse_movups(&cp->func, dst, arg0);
   sse_mulps(&cp->func, dst, arg1);
   return GL_TRUE;
}


static GLboolean emit_POW( struct compilation *cp, union instruction op ) 
{
   struct x86_reg arg0 = get_arg_ptr(cp, op.alu.file0, op.alu.idx0); 
   struct x86_reg arg1 = get_arg_ptr(cp, op.alu.file1, op.alu.idx1); 
   struct x86_reg dst = get_dst_ptr(cp, FILE_REG, op.alu.dst);

   x87_fld(&cp->func, arg1);   	/* a1 */
   x87_fld(&cp->func, arg0);	/* a0 a1 */
   x87_fyl2x(&cp->func);	/* a1*log2(a0) */

   emit_x87_ex2( cp );		/* 2^(a1*log2(a0)) */

   x87_fst(&cp->func, x86_make_disp(dst, 0));    
   x87_fst(&cp->func, x86_make_disp(dst, 4));    
   x87_fst(&cp->func, x86_make_disp(dst, 8));    
   x87_fstp(&cp->func, x86_make_disp(dst, 12));    
    
   return GL_TRUE;
}

static GLboolean emit_REL( struct compilation *cp, union instruction op )
{
/*    GLuint idx = (op.alu.idx0 + (GLint)cp->File[0][REG_ADDR][0]) & (MAX_NV_VERTEX_PROGRAM_PARAMS-1); */
/*    GLuint idx = 0; */
/*    struct x86_reg arg0 = get_arg(cp, op.alu.file0, idx); */
/*    struct x86_reg dst = get_dst_xmm_reg(cp, FILE_REG, op.alu.dst); */

/*    dst[0] = arg0[0]; */
/*    dst[1] = arg0[1]; */
/*    dst[2] = arg0[2]; */
/*    dst[3] = arg0[3]; */

   FAIL;
}

static GLboolean emit_RCP( struct compilation *cp, union instruction op )
{
   struct x86_reg arg0 = get_arg(cp, op.alu.file0, op.alu.idx0);
   struct x86_reg dst = get_dst_xmm_reg(cp, FILE_REG, op.alu.dst);

   if (cp->have_sse2) {
      sse2_rcpss(&cp->func, dst, arg0);
   }
   else {
      struct x86_reg ones = get_reg_ptr(FILE_REG, REG_ONES);
      sse_movss(&cp->func, dst, ones);
      sse_divss(&cp->func, dst, arg0);
   }

   sse_shufps(&cp->func, dst, dst, SHUF(X, X, X, X));
   return GL_TRUE;
}

static GLboolean emit_RSQ( struct compilation *cp, union instruction op )
{
   struct x86_reg arg0 = get_arg(cp, op.alu.file0, op.alu.idx0);
   struct x86_reg dst = get_dst_xmm_reg(cp, FILE_REG, op.alu.dst);

   /* TODO: Calculate absolute value
    */
#if 0
   sse_movss(&cp->func, dst, arg0);
   sse_mulss(&cp->func, dst, neg);
   sse_maxss(&cp->func, dst, arg0);
#endif

   sse_rsqrtss(&cp->func, dst, arg0);
   sse_shufps(&cp->func, dst, dst, SHUF(X, X, X, X));
   return GL_TRUE;
}


static GLboolean emit_SGE( struct compilation *cp, union instruction op )
{
   struct x86_reg arg0 = get_arg(cp, op.alu.file0, op.alu.idx0);
   struct x86_reg arg1 = get_arg(cp, op.alu.file1, op.alu.idx1);
   struct x86_reg dst = get_dst_xmm_reg(cp, FILE_REG, op.alu.dst);
   struct x86_reg ones = get_reg_ptr(FILE_REG, REG_ONES);

   sse_movups(&cp->func, dst, arg0);
   sse_cmpps(&cp->func, dst, arg1, cc_NotLessThan);
   sse_andps(&cp->func, dst, ones);
   return GL_TRUE;
}


static GLboolean emit_SLT( struct compilation *cp, union instruction op )
{
   struct x86_reg arg0 = get_arg(cp, op.alu.file0, op.alu.idx0);
   struct x86_reg arg1 = get_arg(cp, op.alu.file1, op.alu.idx1);
   struct x86_reg dst = get_dst_xmm_reg(cp, FILE_REG, op.alu.dst);
   struct x86_reg ones = get_reg_ptr(FILE_REG, REG_ONES);
   
   sse_movups(&cp->func, dst, arg0);
   sse_cmpps(&cp->func, dst, arg1, cc_LessThan);
   sse_andps(&cp->func, dst, ones);
   return GL_TRUE;
}

static GLboolean emit_SUB( struct compilation *cp, union instruction op ) 
{
   struct x86_reg arg0 = get_arg(cp, op.alu.file0, op.alu.idx0);
   struct x86_reg arg1 = get_arg(cp, op.alu.file1, op.alu.idx1);
   struct x86_reg dst = get_dst_xmm_reg(cp, FILE_REG, op.alu.dst);

   sse_movups(&cp->func, dst, arg0);
   sse_subps(&cp->func, dst, arg1);
   return GL_TRUE;
}


static GLboolean emit_XPD( struct compilation *cp, union instruction op ) 
{
   struct x86_reg arg0 = get_arg(cp, op.alu.file0, op.alu.idx0);
   struct x86_reg arg1 = get_arg(cp, op.alu.file1, op.alu.idx1);
   struct x86_reg dst = get_dst_xmm_reg(cp, FILE_REG, op.alu.dst);
   struct x86_reg tmp0 = get_xmm_reg(cp);
   struct x86_reg tmp1 = get_xmm_reg(cp);

   /* Could avoid tmp0, tmp1 if we overwrote arg0, arg1.  Need a way
    * to invalidate registers.  This will come with better analysis
    * (liveness analysis) of the incoming program.
    */
   emit_pshufd(cp, dst, arg0, SHUF(Y, Z, X, W));
   emit_pshufd(cp, tmp1, arg1, SHUF(Z, X, Y, W));
   sse_mulps(&cp->func, dst, tmp1);
   emit_pshufd(cp, tmp0, arg0, SHUF(Z, X, Y, W));
   emit_pshufd(cp, tmp1, arg1, SHUF(Y, Z, X, W));
   sse_mulps(&cp->func, tmp0, tmp1);
   sse_subps(&cp->func, dst, tmp0);

/*    dst[0] = arg0[1] * arg1[2] - arg0[2] * arg1[1]; */
/*    dst[1] = arg0[2] * arg1[0] - arg0[0] * arg1[2]; */
/*    dst[2] = arg0[0] * arg1[1] - arg0[1] * arg1[0]; */
/*    dst[3] is undef */

   return GL_TRUE;
}

static GLboolean emit_NOP( struct compilation *cp, union instruction op ) 
{
   return GL_TRUE;
}


static GLboolean (* const emit_func[])(struct compilation *, union instruction) = 
{
   emit_ABS,
   emit_ADD,
   emit_NOP,
   emit_DP3,
   emit_DP4,
   emit_DPH,
   emit_DST,
   emit_NOP,
   emit_EX2,
   emit_EXP,
   emit_FLR,
   emit_FRC,
   emit_LG2,
   emit_LIT,
   emit_LOG,
   emit_NOP,
   emit_MAX,
   emit_MIN,
   emit_MOV,
   emit_MUL,
   emit_POW,
   emit_PRT,
   emit_NOP,
   emit_RCP,
   emit_RSQ,
   emit_SGE,
   emit_SLT,
   emit_SUB,
   emit_RSW,
   emit_XPD,
   emit_RSW,
   emit_MSK,
   emit_REL,
};



static GLboolean build_vertex_program( struct compilation *cp )
{
   struct arb_vp_machine *m = NULL;
   GLuint j;

   struct x86_reg regEBX = x86_make_reg(file_REG32, reg_BX);
   struct x86_reg regECX = x86_make_reg(file_REG32, reg_CX);
   struct x86_reg regEDX = x86_make_reg(file_REG32, reg_DX);

   x86_push(&cp->func, regEBX);

   x86_mov(&cp->func, regEDX, x86_fn_arg(&cp->func, 1));   
   x86_mov(&cp->func, regEBX, x86_make_disp(regEDX, get_offset(m, m->File + FILE_REG)));
   x86_mov(&cp->func, regECX, x86_make_disp(regEDX, get_offset(m, m->File + FILE_STATE_PARAM)));

   for (j = 0; j < cp->p->nr_instructions; j++) {
      union instruction inst = cp->p->instructions[j];	 
      cp->insn_counter = j+1;	/* avoid zero */
      
      if (DISASSEM) {
	 _mesa_printf("%p: ", cp->func.csr); 
	 _tnl_disassem_vba_insn( inst );
      }
      cp->func.fn = NULL;

      if (!emit_func[inst.alu.opcode]( cp, inst )) {
	 return GL_FALSE;
      }
   }

   /* TODO: only for outputs:
    */
   for (j = 0; j < 8; j++) {
      if (cp->xmm[j].dirty) 
	 spill(cp, j);
   }
      

   /* Exit mmx state?
    */
   if (cp->func.need_emms)
      mmx_emms(&cp->func);

   /* Restore FPU control word?
    */
   if (cp->fpucntl != RESTORE_FPU) {
      x87_fnclex(&cp->func);
      x87_fldcw(&cp->func, x86_make_disp(regEDX, get_offset(m, &m->fpucntl_restore)));
   }

   x86_pop(&cp->func, regEBX);
   x86_ret(&cp->func);

   return GL_TRUE;
}

/**
 * Execute the given vertex program.  
 * 
 * TODO: Integrate the t_vertex.c code here, to build machine vertices
 * directly at this point.
 *
 * TODO: Eliminate the VB struct entirely and just use
 * struct arb_vertex_machine.
 */
GLboolean
_tnl_sse_codegen_vertex_program(struct tnl_compiled_program *p)
{
   struct compilation cp;
   
   /* sanity checks */
   assert(emit_func[VP_OPCODE_ABS] == emit_ABS);
   assert(emit_func[VP_OPCODE_MUL] == emit_MUL);
   assert(emit_func[VP_OPCODE_XPD] == emit_XPD);

   memset(&cp, 0, sizeof(cp));
   cp.p = p;
   cp.have_sse2 = 1;

   if (p->compiled_func) {
      _mesa_free((void *)p->compiled_func);
      p->compiled_func = NULL;
   }

   x86_init_func(&cp.func);

   cp.fpucntl = RESTORE_FPU;


   /* Note ctx state is not referenced in building the function, so it
    * depends only on the list of instructions:
    */
   if (!build_vertex_program(&cp)) {
      x86_release_func( &cp.func );
      return GL_FALSE;
   }


   p->compiled_func = (void (*)(struct arb_vp_machine *))x86_get_func( &cp.func );
   return GL_TRUE;
}



#else

GLboolean
_tnl_sse_codegen_vertex_program(struct tnl_compiled_program *p)
{
   /* Dummy version for when USE_SSE_ASM not defined */
   return GL_FALSE;
}

#endif
