/*
 * Mesa 3-D graphics library
 *
 * Copyright (C) 2005-2008  Brian Paul   All Rights Reserved.
 * Copyright (C) 2008 VMware, Inc.   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 slang_emit.c
 * Emit program instructions (PI code) from IR trees.
 * \author Brian Paul
 */

/***
 *** NOTES
 ***
 *** To emit GPU instructions, we basically just do an in-order traversal
 *** of the IR tree.
 ***/


#include "main/imports.h"
#include "main/context.h"
#include "shader/program.h"
#include "shader/prog_instruction.h"
#include "shader/prog_parameter.h"
#include "shader/prog_print.h"
#include "slang_builtin.h"
#include "slang_emit.h"
#include "slang_mem.h"


#define PEEPHOLE_OPTIMIZATIONS 1
#define ANNOTATE 0


typedef struct
{
   slang_info_log *log;
   slang_var_table *vt;
   struct gl_program *prog;
   struct gl_program **Subroutines;
   GLuint NumSubroutines;

   GLuint MaxInstructions;  /**< size of prog->Instructions[] buffer */

   GLboolean UnresolvedFunctions;

   /* code-gen options */
   GLboolean EmitHighLevelInstructions;
   GLboolean EmitCondCodes;
   GLboolean EmitComments;
   GLboolean EmitBeginEndSub; /* XXX TEMPORARY */
} slang_emit_info;



static struct gl_program *
new_subroutine(slang_emit_info *emitInfo, GLuint *id)
{
   GET_CURRENT_CONTEXT(ctx);
   const GLuint n = emitInfo->NumSubroutines;

   emitInfo->Subroutines = (struct gl_program **)
      _mesa_realloc(emitInfo->Subroutines,
                    n * sizeof(struct gl_program *),
                    (n + 1) * sizeof(struct gl_program *));
   emitInfo->Subroutines[n] = ctx->Driver.NewProgram(ctx, emitInfo->prog->Target, 0);
   emitInfo->Subroutines[n]->Parameters = emitInfo->prog->Parameters;
   emitInfo->NumSubroutines++;
   *id = n;
   return emitInfo->Subroutines[n];
}


/**
 * Convert a writemask to a swizzle.  Used for testing cond codes because
 * we only want to test the cond code component(s) that was set by the
 * previous instruction.
 */
static GLuint
writemask_to_swizzle(GLuint writemask)
{
   if (writemask == WRITEMASK_X)
      return SWIZZLE_XXXX;
   if (writemask == WRITEMASK_Y)
      return SWIZZLE_YYYY;
   if (writemask == WRITEMASK_Z)
      return SWIZZLE_ZZZZ;
   if (writemask == WRITEMASK_W)
      return SWIZZLE_WWWW;
   return SWIZZLE_XYZW;  /* shouldn't be hit */
}


/**
 * Convert a swizzle mask to a writemask.
 * Note that the slang_ir_storage->Swizzle field can represent either a
 * swizzle mask or a writemask, depending on how it's used.  For example,
 * when we parse "direction.yz" alone, we don't know whether .yz is a
 * writemask or a swizzle.  In this case, we encode ".yz" in store->Swizzle
 * as a swizzle mask (.yz?? actually).  Later, if direction.yz is used as
 * an R-value, we use store->Swizzle as-is.  Otherwise, if direction.yz is
 * used as an L-value, we convert it to a writemask.
 */
static GLuint
swizzle_to_writemask(GLuint swizzle)
{
   GLuint i, writemask = 0x0;
   for (i = 0; i < 4; i++) {
      GLuint swz = GET_SWZ(swizzle, i);
      if (swz <= SWIZZLE_W) {
         writemask |= (1 << swz);
      }
   }
   return writemask;
}


/**
 * Swizzle a swizzle (function composition).
 * That is, return swz2(swz1), or said another way: swz1.szw2
 * Example: swizzle_swizzle(".zwxx", ".xxyw") yields ".zzwx"
 */
GLuint
_slang_swizzle_swizzle(GLuint swz1, GLuint swz2)
{
   GLuint i, swz, s[4];
   for (i = 0; i < 4; i++) {
      GLuint c = GET_SWZ(swz2, i);
      if (c <= SWIZZLE_W)
         s[i] = GET_SWZ(swz1, c);
      else
         s[i] = c;
   }
   swz = MAKE_SWIZZLE4(s[0], s[1], s[2], s[3]);
   return swz;
}


/**
 * Return the default swizzle mask for accessing a variable of the
 * given size (in floats).  If size = 1, comp is used to identify
 * which component [0..3] of the register holds the variable.
 */
GLuint
_slang_var_swizzle(GLint size, GLint comp)
{
   switch (size) {
   case 1:
      return MAKE_SWIZZLE4(comp, SWIZZLE_NIL, SWIZZLE_NIL, SWIZZLE_NIL);
   case 2:
      return MAKE_SWIZZLE4(SWIZZLE_X, SWIZZLE_Y, SWIZZLE_NIL, SWIZZLE_NIL);
   case 3:
      return MAKE_SWIZZLE4(SWIZZLE_X, SWIZZLE_Y, SWIZZLE_Z, SWIZZLE_NIL);
   default:
      return SWIZZLE_XYZW;
   }
}



/**
 * Allocate storage for the given node (if it hasn't already been allocated).
 *
 * Typically this is temporary storage for an intermediate result (such as
 * for a multiply or add, etc).
 *
 * If n->Store does not exist it will be created and will be of the size
 * specified by defaultSize.
 */
static GLboolean
alloc_node_storage(slang_emit_info *emitInfo, slang_ir_node *n,
                   GLint defaultSize)
{
   assert(!n->Var);
   if (!n->Store) {
      assert(defaultSize > 0);
      n->Store = _slang_new_ir_storage(PROGRAM_TEMPORARY, -1, defaultSize);
      if (!n->Store) {
         return GL_FALSE;
      }
   }

   /* now allocate actual register(s).  I.e. set n->Store->Index >= 0 */
   if (n->Store->Index < 0) {
      if (!_slang_alloc_temp(emitInfo->vt, n->Store)) {
         slang_info_log_error(emitInfo->log,
                              "Ran out of registers, too many temporaries");
         _slang_free(n->Store);
         n->Store = NULL;
         return GL_FALSE;
      }
   }
   return GL_TRUE;
}


/**
 * Free temporary storage, if n->Store is, in fact, temp storage.
 * Otherwise, no-op.
 */
static void
free_node_storage(slang_var_table *vt, slang_ir_node *n)
{
   if (n->Store->File == PROGRAM_TEMPORARY &&
       n->Store->Index >= 0 &&
       n->Opcode != IR_SWIZZLE) {
      if (_slang_is_temp(vt, n->Store)) {
         _slang_free_temp(vt, n->Store);
         n->Store->Index = -1;
         n->Store = NULL; /* XXX this may not be needed */
      }
   }
}


/**
 * Helper function to allocate a short-term temporary.
 * Free it with _slang_free_temp().
 */
static GLboolean
alloc_local_temp(slang_emit_info *emitInfo, slang_ir_storage *temp, GLint size)
{
   assert(size >= 1);
   assert(size <= 4);
   memset(temp, 0, sizeof(*temp));
   temp->Size = size;
   temp->File = PROGRAM_TEMPORARY;
   temp->Index = -1;
   return _slang_alloc_temp(emitInfo->vt, temp);
}


/**
 * Remove any SWIZZLE_NIL terms from given swizzle mask.
 * For a swizzle like .z??? generate .zzzz (replicate single component).
 * Else, for .wx?? generate .wxzw (insert default component for the position).
 */
static GLuint
fix_swizzle(GLuint swizzle)
{
   GLuint c0 = GET_SWZ(swizzle, 0),
      c1 = GET_SWZ(swizzle, 1),
      c2 = GET_SWZ(swizzle, 2),
      c3 = GET_SWZ(swizzle, 3);
   if (c1 == SWIZZLE_NIL && c2 == SWIZZLE_NIL && c3 == SWIZZLE_NIL) {
      /* smear first component across all positions */
      c1 = c2 = c3 = c0;
   }
   else {
      /* insert default swizzle components */
      if (c0 == SWIZZLE_NIL)
         c0 = SWIZZLE_X;
      if (c1 == SWIZZLE_NIL)
         c1 = SWIZZLE_Y;
      if (c2 == SWIZZLE_NIL)
         c2 = SWIZZLE_Z;
      if (c3 == SWIZZLE_NIL)
         c3 = SWIZZLE_W;
   }
   return MAKE_SWIZZLE4(c0, c1, c2, c3);
}



/**
 * Convert IR storage to an instruction dst register.
 */
static void
storage_to_dst_reg(struct prog_dst_register *dst, const slang_ir_storage *st)
{
   const GLboolean relAddr = st->RelAddr;
   const GLint size = st->Size;
   GLint index = st->Index;
   GLuint swizzle = st->Swizzle;

   assert(index >= 0);
   /* if this is storage relative to some parent storage, walk up the tree */
   while (st->Parent) {
      st = st->Parent;
      assert(st->Index >= 0);
      index += st->Index;
      swizzle = _slang_swizzle_swizzle(st->Swizzle, swizzle);
   }

   assert(st->File != PROGRAM_UNDEFINED);
   dst->File = st->File;

   assert(index >= 0);
   dst->Index = index;

   assert(size >= 1);
   assert(size <= 4);

   if (swizzle != SWIZZLE_XYZW) {
      dst->WriteMask = swizzle_to_writemask(swizzle);
   }
   else {
      switch (size) {
      case 1:
         dst->WriteMask = WRITEMASK_X << GET_SWZ(st->Swizzle, 0);
         break;
      case 2:
         dst->WriteMask = WRITEMASK_XY;
         break;
      case 3:
         dst->WriteMask = WRITEMASK_XYZ;
         break;
      case 4:
         dst->WriteMask = WRITEMASK_XYZW;
         break;
      default:
         ; /* error would have been caught above */
      }
   }

   dst->RelAddr = relAddr;
}


/**
 * Convert IR storage to an instruction src register.
 */
static void
storage_to_src_reg(struct prog_src_register *src, const slang_ir_storage *st)
{
   const GLboolean relAddr = st->RelAddr;
   GLint index = st->Index;
   GLuint swizzle = st->Swizzle;

   /* if this is storage relative to some parent storage, walk up the tree */
   assert(index >= 0);
   while (st->Parent) {
      st = st->Parent;
      if (st->Index < 0) {
         /* an error should have been reported already */
         return;
      }
      assert(st->Index >= 0);
      index += st->Index;
      swizzle = _slang_swizzle_swizzle(fix_swizzle(st->Swizzle), swizzle);
   }

   assert(st->File >= 0);
#if 1 /* XXX temporary */
   if (st->File == PROGRAM_UNDEFINED) {
      slang_ir_storage *st0 = (slang_ir_storage *) st;
      st0->File = PROGRAM_TEMPORARY;
   }
#endif
   assert(st->File < PROGRAM_UNDEFINED);
   src->File = st->File;

   assert(index >= 0);
   src->Index = index;

   swizzle = fix_swizzle(swizzle);
   assert(GET_SWZ(swizzle, 0) <= SWIZZLE_W);
   assert(GET_SWZ(swizzle, 1) <= SWIZZLE_W);
   assert(GET_SWZ(swizzle, 2) <= SWIZZLE_W);
   assert(GET_SWZ(swizzle, 3) <= SWIZZLE_W);
   src->Swizzle = swizzle;

   src->RelAddr = relAddr;
}


/*
 * Setup storage pointing to a scalar constant/literal.
 */
static void
constant_to_storage(slang_emit_info *emitInfo,
                    GLfloat val,
                    slang_ir_storage *store)
{
   GLuint swizzle;
   GLint reg;
   GLfloat value[4];

   value[0] = val;
   reg = _mesa_add_unnamed_constant(emitInfo->prog->Parameters,
                                        value, 1, &swizzle);

   memset(store, 0, sizeof(*store));
   store->File = PROGRAM_CONSTANT;
   store->Index = reg;
   store->Swizzle = swizzle;
}


/**
 * Add new instruction at end of given program.
 * \param prog  the program to append instruction onto
 * \param opcode  opcode for the new instruction
 * \return pointer to the new instruction
 */
static struct prog_instruction *
new_instruction(slang_emit_info *emitInfo, gl_inst_opcode opcode)
{
   struct gl_program *prog = emitInfo->prog;
   struct prog_instruction *inst;

#if 0
   /* print prev inst */
   if (prog->NumInstructions > 0) {
      _mesa_print_instruction(prog->Instructions + prog->NumInstructions - 1);
   }
#endif
   assert(prog->NumInstructions <= emitInfo->MaxInstructions);

   if (prog->NumInstructions == emitInfo->MaxInstructions) {
      /* grow the instruction buffer */
      emitInfo->MaxInstructions += 20;
      prog->Instructions =
         _mesa_realloc_instructions(prog->Instructions,
                                    prog->NumInstructions,
                                    emitInfo->MaxInstructions);
      if (!prog->Instructions) {
         return NULL;
      }
   }

   inst = prog->Instructions + prog->NumInstructions;
   prog->NumInstructions++;
   _mesa_init_instructions(inst, 1);
   inst->Opcode = opcode;
   inst->BranchTarget = -1; /* invalid */
   /*
   printf("New inst %d: %p %s\n", prog->NumInstructions-1,(void*)inst,
          _mesa_opcode_string(inst->Opcode));
   */
   return inst;
}


static struct prog_instruction *
emit_arl_load(slang_emit_info *emitInfo,
              gl_register_file file, GLint index, GLuint swizzle)
{
   struct prog_instruction *inst = new_instruction(emitInfo, OPCODE_ARL);
   if (inst) {
      inst->SrcReg[0].File = file;
      inst->SrcReg[0].Index = index;
      inst->SrcReg[0].Swizzle = fix_swizzle(swizzle);
      inst->DstReg.File = PROGRAM_ADDRESS;
      inst->DstReg.Index = 0;
      inst->DstReg.WriteMask = WRITEMASK_X;
   }
   return inst;
}


/**
 * Emit a new instruction with given opcode, operands.
 * At this point the instruction may have multiple indirect register
 * loads/stores.  We convert those into ARL loads and address-relative
 * operands.  See comments inside.
 * At some point in the future we could directly emit indirectly addressed
 * registers in Mesa GPU instructions.
 */
static struct prog_instruction *
emit_instruction(slang_emit_info *emitInfo,
                 gl_inst_opcode opcode,
                 const slang_ir_storage *dst,
                 const slang_ir_storage *src0,
                 const slang_ir_storage *src1,
                 const slang_ir_storage *src2)
{
   struct prog_instruction *inst;
   GLuint numIndirect = 0;
   const slang_ir_storage *src[3];
   slang_ir_storage newSrc[3], newDst;
   GLuint i;
   GLboolean isTemp[3];

   isTemp[0] = isTemp[1] = isTemp[2] = GL_FALSE;

   src[0] = src0;
   src[1] = src1;
   src[2] = src2;

   /* count up how many operands are indirect loads */
   for (i = 0; i < 3; i++) {
      if (src[i] && src[i]->IsIndirect)
         numIndirect++;
   }
   if (dst && dst->IsIndirect)
      numIndirect++;

   /* Take special steps for indirect register loads.
    * If we had multiple address registers this would be simpler.
    * For example, this GLSL code:
    *    x[i] = y[j] + z[k];
    * would translate into something like:
    *    ARL ADDR.x, i;
    *    ARL ADDR.y, j;
    *    ARL ADDR.z, k;
    *    ADD TEMP[ADDR.x+5], TEMP[ADDR.y+9], TEMP[ADDR.z+4];
    * But since we currently only have one address register we have to do this:
    *    ARL ADDR.x, i;
    *    MOV t1, TEMP[ADDR.x+9];
    *    ARL ADDR.x, j;
    *    MOV t2, TEMP[ADDR.x+4];
    *    ARL ADDR.x, k;
    *    ADD TEMP[ADDR.x+5], t1, t2;
    * The code here figures this out...
    */
   if (numIndirect > 0) {
      for (i = 0; i < 3; i++) {
         if (src[i] && src[i]->IsIndirect) {
            /* load the ARL register with the indirect register */
            emit_arl_load(emitInfo,
                          src[i]->IndirectFile,
                          src[i]->IndirectIndex,
                          src[i]->IndirectSwizzle);

            if (numIndirect > 1) {
               /* Need to load src[i] into a temporary register */
               slang_ir_storage srcRelAddr;
               alloc_local_temp(emitInfo, &newSrc[i], src[i]->Size);
               isTemp[i] = GL_TRUE;

               /* set RelAddr flag on src register */
               srcRelAddr = *src[i];
               srcRelAddr.RelAddr = GL_TRUE;
               srcRelAddr.IsIndirect = GL_FALSE; /* not really needed */

               /* MOV newSrc, srcRelAddr; */
               inst = emit_instruction(emitInfo,
                                       OPCODE_MOV,
                                       &newSrc[i],
                                       &srcRelAddr,
                                       NULL,
                                       NULL);
               if (!inst) {
                  return NULL;
               }

               src[i] = &newSrc[i];
            }
            else {
               /* just rewrite the src[i] storage to be ARL-relative */
               newSrc[i] = *src[i];
               newSrc[i].RelAddr = GL_TRUE;
               newSrc[i].IsIndirect = GL_FALSE; /* not really needed */
               src[i] = &newSrc[i];
            }
         }
      }
   }

   /* Take special steps for indirect dest register write */
   if (dst && dst->IsIndirect) {
      /* load the ARL register with the indirect register */
      emit_arl_load(emitInfo,
                    dst->IndirectFile,
                    dst->IndirectIndex,
                    dst->IndirectSwizzle);
      newDst = *dst;
      newDst.RelAddr = GL_TRUE;
      newDst.IsIndirect = GL_FALSE;
      dst = &newDst;
   }

   /* OK, emit the instruction and its dst, src regs */
   inst = new_instruction(emitInfo, opcode);
   if (!inst)
      return NULL;

   if (dst)
      storage_to_dst_reg(&inst->DstReg, dst);

   for (i = 0; i < 3; i++) {
      if (src[i])
         storage_to_src_reg(&inst->SrcReg[i], src[i]);
   }

   /* Free any temp registers that we allocated above */
   for (i = 0; i < 3; i++) {
      if (isTemp[i])
         _slang_free_temp(emitInfo->vt, &newSrc[i]);
   }

   return inst;
}



/**
 * Put a comment on the given instruction.
 */
static void
inst_comment(struct prog_instruction *inst, const char *comment)
{
   if (inst)
      inst->Comment = _mesa_strdup(comment);
}



/**
 * Return pointer to last instruction in program.
 */
static struct prog_instruction *
prev_instruction(slang_emit_info *emitInfo)
{
   struct gl_program *prog = emitInfo->prog;
   if (prog->NumInstructions == 0)
      return NULL;
   else
      return prog->Instructions + prog->NumInstructions - 1;
}


static struct prog_instruction *
emit(slang_emit_info *emitInfo, slang_ir_node *n);


/**
 * Return an annotation string for given node's storage.
 */
static char *
storage_annotation(const slang_ir_node *n, const struct gl_program *prog)
{
#if ANNOTATE
   const slang_ir_storage *st = n->Store;
   static char s[100] = "";

   if (!st)
      return _mesa_strdup("");

   switch (st->File) {
   case PROGRAM_CONSTANT:
      if (st->Index >= 0) {
         const GLfloat *val = prog->Parameters->ParameterValues[st->Index];
         if (st->Swizzle == SWIZZLE_NOOP)
            sprintf(s, "{%g, %g, %g, %g}", val[0], val[1], val[2], val[3]);
         else {
            sprintf(s, "%g", val[GET_SWZ(st->Swizzle, 0)]);
         }
      }
      break;
   case PROGRAM_TEMPORARY:
      if (n->Var)
         sprintf(s, "%s", (char *) n->Var->a_name);
      else
         sprintf(s, "t[%d]", st->Index);
      break;
   case PROGRAM_STATE_VAR:
   case PROGRAM_UNIFORM:
      sprintf(s, "%s", prog->Parameters->Parameters[st->Index].Name);
      break;
   case PROGRAM_VARYING:
      sprintf(s, "%s", prog->Varying->Parameters[st->Index].Name);
      break;
   case PROGRAM_INPUT:
      sprintf(s, "input[%d]", st->Index);
      break;
   case PROGRAM_OUTPUT:
      sprintf(s, "output[%d]", st->Index);
      break;
   default:
      s[0] = 0;
   }
   return _mesa_strdup(s);
#else
   return NULL;
#endif
}


/**
 * Return an annotation string for an instruction.
 */
static char *
instruction_annotation(gl_inst_opcode opcode, char *dstAnnot,
                       char *srcAnnot0, char *srcAnnot1, char *srcAnnot2)
{
#if ANNOTATE
   const char *operator;
   char *s;
   int len = 50;

   if (dstAnnot)
      len += strlen(dstAnnot);
   else
      dstAnnot = _mesa_strdup("");

   if (srcAnnot0)
      len += strlen(srcAnnot0);
   else
      srcAnnot0 = _mesa_strdup("");

   if (srcAnnot1)
      len += strlen(srcAnnot1);
   else
      srcAnnot1 = _mesa_strdup("");

   if (srcAnnot2)
      len += strlen(srcAnnot2);
   else
      srcAnnot2 = _mesa_strdup("");

   switch (opcode) {
   case OPCODE_ADD:
      operator = "+";
      break;
   case OPCODE_SUB:
      operator = "-";
      break;
   case OPCODE_MUL:
      operator = "*";
      break;
   case OPCODE_DP2:
      operator = "DP2";
      break;
   case OPCODE_DP3:
      operator = "DP3";
      break;
   case OPCODE_DP4:
      operator = "DP4";
      break;
   case OPCODE_XPD:
      operator = "XPD";
      break;
   case OPCODE_RSQ:
      operator = "RSQ";
      break;
   case OPCODE_SGT:
      operator = ">";
      break;
   default:
      operator = ",";
   }

   s = (char *) malloc(len);
   sprintf(s, "%s = %s %s %s %s", dstAnnot,
           srcAnnot0, operator, srcAnnot1, srcAnnot2);
   assert(strlen(s) < len);

   free(dstAnnot);
   free(srcAnnot0);
   free(srcAnnot1);
   free(srcAnnot2);

   return s;
#else
   return NULL;
#endif
}


/**
 * Emit an instruction that's just a comment.
 */
static struct prog_instruction *
emit_comment(slang_emit_info *emitInfo, const char *comment)
{
   struct prog_instruction *inst = new_instruction(emitInfo, OPCODE_NOP);
   if (inst) {
      inst_comment(inst, comment);
   }
   return inst;
}


/**
 * Generate code for a simple arithmetic instruction.
 * Either 1, 2 or 3 operands.
 */
static struct prog_instruction *
emit_arith(slang_emit_info *emitInfo, slang_ir_node *n)
{
   const slang_ir_info *info = _slang_ir_info(n->Opcode);
   struct prog_instruction *inst;
   GLuint i;

   assert(info);
   assert(info->InstOpcode != OPCODE_NOP);

#if PEEPHOLE_OPTIMIZATIONS
   /* Look for MAD opportunity */
   if (info->NumParams == 2 &&
       n->Opcode == IR_ADD && n->Children[0]->Opcode == IR_MUL) {
      /* found pattern IR_ADD(IR_MUL(A, B), C) */
      emit(emitInfo, n->Children[0]->Children[0]);  /* A */
      emit(emitInfo, n->Children[0]->Children[1]);  /* B */
      emit(emitInfo, n->Children[1]);  /* C */
      if (!alloc_node_storage(emitInfo, n, -1)) {  /* dest */
         return NULL;
      }

      inst = emit_instruction(emitInfo,
                              OPCODE_MAD,
                              n->Store,
                              n->Children[0]->Children[0]->Store,
                              n->Children[0]->Children[1]->Store,
                              n->Children[1]->Store);

      free_node_storage(emitInfo->vt, n->Children[0]->Children[0]);
      free_node_storage(emitInfo->vt, n->Children[0]->Children[1]);
      free_node_storage(emitInfo->vt, n->Children[1]);
      return inst;
   }

   if (info->NumParams == 2 &&
       n->Opcode == IR_ADD && n->Children[1]->Opcode == IR_MUL) {
      /* found pattern IR_ADD(A, IR_MUL(B, C)) */
      emit(emitInfo, n->Children[0]);  /* A */
      emit(emitInfo, n->Children[1]->Children[0]);  /* B */
      emit(emitInfo, n->Children[1]->Children[1]);  /* C */
      if (!alloc_node_storage(emitInfo, n, -1)) {  /* dest */
         return NULL;
      }

      inst = emit_instruction(emitInfo,
                              OPCODE_MAD,
                              n->Store,
                              n->Children[1]->Children[0]->Store,
                              n->Children[1]->Children[1]->Store,
                              n->Children[0]->Store);

      free_node_storage(emitInfo->vt, n->Children[1]->Children[0]);
      free_node_storage(emitInfo->vt, n->Children[1]->Children[1]);
      free_node_storage(emitInfo->vt, n->Children[0]);
      return inst;
   }
#endif

   /* gen code for children, may involve temp allocation */
   for (i = 0; i < info->NumParams; i++) {
      emit(emitInfo, n->Children[i]);
      if (!n->Children[i] || !n->Children[i]->Store) {
         /* error recovery */
         return NULL;
      }
   }

   /* result storage */
   if (!alloc_node_storage(emitInfo, n, -1)) {
      return NULL;
   }

   inst = emit_instruction(emitInfo,
                           info->InstOpcode,
                           n->Store,  /* dest */
                           (info->NumParams > 0 ? n->Children[0]->Store : NULL),
                           (info->NumParams > 1 ? n->Children[1]->Store : NULL),
                           (info->NumParams > 2 ? n->Children[2]->Store : NULL)
                           );

   /* free temps */
   for (i = 0; i < info->NumParams; i++)
      free_node_storage(emitInfo->vt, n->Children[i]);

   return inst;
}


/**
 * Emit code for == and != operators.  These could normally be handled
 * by emit_arith() except we need to be able to handle structure comparisons.
 */
static struct prog_instruction *
emit_compare(slang_emit_info *emitInfo, slang_ir_node *n)
{
   struct prog_instruction *inst = NULL;
   GLint size;

   assert(n->Opcode == IR_EQUAL || n->Opcode == IR_NOTEQUAL);

   /* gen code for children */
   emit(emitInfo, n->Children[0]);
   emit(emitInfo, n->Children[1]);

   if (n->Children[0]->Store->Size != n->Children[1]->Store->Size) {
      /* XXX this error should have been caught in slang_codegen.c */
      slang_info_log_error(emitInfo->log, "invalid operands to == or !=");
      n->Store = NULL;
      return NULL;
   }

   /* final result is 1 bool */
   if (!alloc_node_storage(emitInfo, n, 1))
      return NULL;

   size = n->Children[0]->Store->Size;

   if (size == 1) {
      gl_inst_opcode opcode = n->Opcode == IR_EQUAL ? OPCODE_SEQ : OPCODE_SNE;
      inst =  emit_instruction(emitInfo,
                               opcode,
                               n->Store, /* dest */
                               n->Children[0]->Store,
                               n->Children[1]->Store,
                               NULL);
   }
   else if (size <= 4) {
      /* compare two vectors.
       * Unfortunately, there's no instruction to compare vectors and
       * return a scalar result.  Do it with some compare and dot product
       * instructions...
       */
      GLuint swizzle;
      gl_inst_opcode dotOp;
      slang_ir_storage tempStore;

      if (!alloc_local_temp(emitInfo, &tempStore, 4)) {
         n->Store = NULL;
         return NULL;
         /* out of temps */
      }

      if (size == 4) {
         dotOp = OPCODE_DP4;
         swizzle = SWIZZLE_XYZW;
      }
      else if (size == 3) {
         dotOp = OPCODE_DP3;
         swizzle = SWIZZLE_XYZW;
      }
      else {
         assert(size == 2);
         dotOp = OPCODE_DP3; /* XXX use OPCODE_DP2 eventually */
         swizzle = MAKE_SWIZZLE4(SWIZZLE_X, SWIZZLE_Y, SWIZZLE_Y, SWIZZLE_Y);
      }

      /* Compute inequality (temp = (A != B)) */
      inst = emit_instruction(emitInfo,
                              OPCODE_SNE,
                              &tempStore,
                              n->Children[0]->Store,
                              n->Children[1]->Store,
                              NULL);
      if (!inst) {
         return NULL;
      }
      inst_comment(inst, "Compare values");

      /* Compute val = DOT(temp, temp)  (reduction) */
      inst = emit_instruction(emitInfo,
                              dotOp,
                              n->Store,
                              &tempStore,
                              &tempStore,
                              NULL);
      if (!inst) {
         return NULL;
      }
      inst->SrcReg[0].Swizzle = inst->SrcReg[1].Swizzle = swizzle; /*override*/
      inst_comment(inst, "Reduce vec to bool");

      _slang_free_temp(emitInfo->vt, &tempStore); /* free temp */

      if (n->Opcode == IR_EQUAL) {
         /* compute val = !val.x  with SEQ val, val, 0; */
         slang_ir_storage zero;
         constant_to_storage(emitInfo, 0.0, &zero);
         inst = emit_instruction(emitInfo,
                                 OPCODE_SEQ,
                                 n->Store, /* dest */
                                 n->Store,
                                 &zero,
                                 NULL);
         if (!inst) {
            return NULL;
         }
         inst_comment(inst, "Invert true/false");
      }
   }
   else {
      /* size > 4, struct or array compare.
       * XXX this won't work reliably for structs with padding!!
       */
      GLint i, num = (n->Children[0]->Store->Size + 3) / 4;
      slang_ir_storage accTemp, sneTemp;

      if (!alloc_local_temp(emitInfo, &accTemp, 4))
         return NULL;

      if (!alloc_local_temp(emitInfo, &sneTemp, 4))
         return NULL;

      for (i = 0; i < num; i++) {
         slang_ir_storage srcStore0 = *n->Children[0]->Store;
         slang_ir_storage srcStore1 = *n->Children[1]->Store;
         srcStore0.Index += i;
         srcStore1.Index += i;

         if (i == 0) {
            /* SNE accTemp, left[i], right[i] */
            inst = emit_instruction(emitInfo, OPCODE_SNE,
                                    &accTemp, /* dest */
                                    &srcStore0,
                                    &srcStore1,
                                    NULL);
            if (!inst) {
               return NULL;
            }
            inst_comment(inst, "Begin struct/array comparison");
         }
         else {
            /* SNE sneTemp, left[i], right[i] */
            inst = emit_instruction(emitInfo, OPCODE_SNE,
                                    &sneTemp, /* dest */
                                    &srcStore0,
                                    &srcStore1,
                                    NULL);
            if (!inst) {
               return NULL;
            }
            /* ADD accTemp, accTemp, sneTemp; # like logical-OR */
            inst = emit_instruction(emitInfo, OPCODE_ADD,
                                    &accTemp, /* dest */
                                    &accTemp,
                                    &sneTemp,
                                    NULL);
            if (!inst) {
               return NULL;
            }
         }
      }

      /* compute accTemp.x || accTemp.y || accTemp.z || accTemp.w with DOT4 */
      inst = emit_instruction(emitInfo, OPCODE_DP4,
                              n->Store,
                              &accTemp,
                              &accTemp,
                              NULL);
      if (!inst) {
         return NULL;
      }
      inst_comment(inst, "End struct/array comparison");

      if (n->Opcode == IR_EQUAL) {
         /* compute tmp.x = !tmp.x  via tmp.x = (tmp.x == 0) */
         slang_ir_storage zero;
         constant_to_storage(emitInfo, 0.0, &zero);
         inst = emit_instruction(emitInfo, OPCODE_SEQ,
                                 n->Store, /* dest */
                                 n->Store,
                                 &zero,
                                 NULL);
         if (!inst) {
            return NULL;
         }
         inst_comment(inst, "Invert true/false");
      }

      _slang_free_temp(emitInfo->vt, &accTemp);
      _slang_free_temp(emitInfo->vt, &sneTemp);
   }

   /* free temps */
   free_node_storage(emitInfo->vt, n->Children[0]);
   free_node_storage(emitInfo->vt, n->Children[1]);

   return inst;
}



/**
 * Generate code for an IR_CLAMP instruction.
 */
static struct prog_instruction *
emit_clamp(slang_emit_info *emitInfo, slang_ir_node *n)
{
   struct prog_instruction *inst;
   slang_ir_node tmpNode;

   assert(n->Opcode == IR_CLAMP);
   /* ch[0] = value
    * ch[1] = min limit
    * ch[2] = max limit
    */

   inst = emit(emitInfo, n->Children[0]);

   /* If lower limit == 0.0 and upper limit == 1.0,
    *    set prev instruction's SaturateMode field to SATURATE_ZERO_ONE.
    * Else,
    *    emit OPCODE_MIN, OPCODE_MAX sequence.
    */
#if 0
   /* XXX this isn't quite finished yet */
   if (n->Children[1]->Opcode == IR_FLOAT &&
       n->Children[1]->Value[0] == 0.0 &&
       n->Children[1]->Value[1] == 0.0 &&
       n->Children[1]->Value[2] == 0.0 &&
       n->Children[1]->Value[3] == 0.0 &&
       n->Children[2]->Opcode == IR_FLOAT &&
       n->Children[2]->Value[0] == 1.0 &&
       n->Children[2]->Value[1] == 1.0 &&
       n->Children[2]->Value[2] == 1.0 &&
       n->Children[2]->Value[3] == 1.0) {
      if (!inst) {
         inst = prev_instruction(prog);
      }
      if (inst && inst->Opcode != OPCODE_NOP) {
         /* and prev instruction's DstReg matches n->Children[0]->Store */
         inst->SaturateMode = SATURATE_ZERO_ONE;
         n->Store = n->Children[0]->Store;
         return inst;
      }
   }
#else
   (void) inst;
#endif

   if (!alloc_node_storage(emitInfo, n, n->Children[0]->Store->Size))
      return NULL;

   emit(emitInfo, n->Children[1]);
   emit(emitInfo, n->Children[2]);

   /* Some GPUs don't allow reading from output registers.  So if the
    * dest for this clamp() is an output reg, we can't use that reg for
    * the intermediate result.  Use a temp register instead.
    */
   memset(&tmpNode, 0, sizeof(tmpNode));
   if (!alloc_node_storage(emitInfo, &tmpNode, n->Store->Size)) {
      return NULL;
   }

   /* tmp = max(ch[0], ch[1]) */
   inst = emit_instruction(emitInfo, OPCODE_MAX,
                           tmpNode.Store, /* dest */
                           n->Children[0]->Store,
                           n->Children[1]->Store,
                           NULL);
   if (!inst) {
      return NULL;
   }

   /* n->dest = min(tmp, ch[2]) */
   inst = emit_instruction(emitInfo, OPCODE_MIN,
                           n->Store, /* dest */
                           tmpNode.Store,
                           n->Children[2]->Store,
                           NULL);

   free_node_storage(emitInfo->vt, &tmpNode);

   return inst;
}


static struct prog_instruction *
emit_negation(slang_emit_info *emitInfo, slang_ir_node *n)
{
   /* Implement as MOV dst, -src; */
   /* XXX we could look at the previous instruction and in some circumstances
    * modify it to accomplish the negation.
    */
   struct prog_instruction *inst;

   emit(emitInfo, n->Children[0]);

   if (!alloc_node_storage(emitInfo, n, n->Children[0]->Store->Size))
      return NULL;

   inst = emit_instruction(emitInfo,
                           OPCODE_MOV,
                           n->Store, /* dest */
                           n->Children[0]->Store,
                           NULL,
                           NULL);
   if (inst) {
      inst->SrcReg[0].Negate = NEGATE_XYZW;
   }
   return inst;
}


static struct prog_instruction *
emit_label(slang_emit_info *emitInfo, const slang_ir_node *n)
{
   assert(n->Label);
#if 0
   /* XXX this fails in loop tail code - investigate someday */
   assert(_slang_label_get_location(n->Label) < 0);
   _slang_label_set_location(n->Label, emitInfo->prog->NumInstructions,
                             emitInfo->prog);
#else
   if (_slang_label_get_location(n->Label) < 0)
      _slang_label_set_location(n->Label, emitInfo->prog->NumInstructions,
                                emitInfo->prog);
#endif
   return NULL;
}


/**
 * Emit code for a function call.
 * Note that for each time a function is called, we emit the function's
 * body code again because the set of available registers may be different.
 */
static struct prog_instruction *
emit_fcall(slang_emit_info *emitInfo, slang_ir_node *n)
{
   struct gl_program *progSave;
   struct prog_instruction *inst;
   GLuint subroutineId;
   GLuint maxInstSave;

   assert(n->Opcode == IR_CALL);
   assert(n->Label);

   /* save/push cur program */
   maxInstSave = emitInfo->MaxInstructions;
   progSave = emitInfo->prog;

   emitInfo->prog = new_subroutine(emitInfo, &subroutineId);
   emitInfo->MaxInstructions = emitInfo->prog->NumInstructions;

   _slang_label_set_location(n->Label, emitInfo->prog->NumInstructions,
                             emitInfo->prog);

   if (emitInfo->EmitBeginEndSub) {
      /* BGNSUB isn't a real instruction.
       * We require a label (i.e. "foobar:") though, if we're going to
       * print the program in the NV format.  The BNGSUB instruction is
       * really just a NOP to attach the label to.
       */
      inst = new_instruction(emitInfo, OPCODE_BGNSUB);
      if (!inst) {
         return NULL;
      }
      inst_comment(inst, n->Label->Name);
   }

   /* body of function: */
   emit(emitInfo, n->Children[0]);
   n->Store = n->Children[0]->Store;

   /* add RET instruction now, if needed */
   inst = prev_instruction(emitInfo);
   if (inst && inst->Opcode != OPCODE_RET) {
      inst = new_instruction(emitInfo, OPCODE_RET);
      if (!inst) {
         return NULL;
      }
   }

   if (emitInfo->EmitBeginEndSub) {
      inst = new_instruction(emitInfo, OPCODE_ENDSUB);
      if (!inst) {
         return NULL;
      }
      inst_comment(inst, n->Label->Name);
   }

   /* pop/restore cur program */
   emitInfo->prog = progSave;
   emitInfo->MaxInstructions = maxInstSave;

   /* emit the function call */
   inst = new_instruction(emitInfo, OPCODE_CAL);
   if (!inst) {
      return NULL;
   }
   /* The branch target is just the subroutine number (changed later) */
   inst->BranchTarget = subroutineId;
   inst_comment(inst, n->Label->Name);
   assert(inst->BranchTarget >= 0);

   return inst;
}


/**
 * Emit code for a 'return' statement.
 */
static struct prog_instruction *
emit_return(slang_emit_info *emitInfo, slang_ir_node *n)
{
   struct prog_instruction *inst;
   assert(n);
   assert(n->Opcode == IR_RETURN);
   assert(n->Label);
   inst = new_instruction(emitInfo, OPCODE_RET);
   if (inst) {
      inst->DstReg.CondMask = COND_TR;  /* always return */
   }
   return inst;
}


static struct prog_instruction *
emit_kill(slang_emit_info *emitInfo)
{
   struct gl_fragment_program *fp;
   struct prog_instruction *inst;
   /* NV-KILL - discard fragment depending on condition code.
    * Note that ARB-KILL depends on sign of vector operand.
    */
   inst = new_instruction(emitInfo, OPCODE_KIL_NV);
   if (!inst) {
      return NULL;
   }
   inst->DstReg.CondMask = COND_TR;  /* always kill */

   assert(emitInfo->prog->Target == GL_FRAGMENT_PROGRAM_ARB);
   fp = (struct gl_fragment_program *) emitInfo->prog;
   fp->UsesKill = GL_TRUE;

   return inst;
}


static struct prog_instruction *
emit_tex(slang_emit_info *emitInfo, slang_ir_node *n)
{
   struct prog_instruction *inst;
   gl_inst_opcode opcode;
   GLboolean shadow = GL_FALSE;

   switch (n->Opcode) {
   case IR_TEX:
      opcode = OPCODE_TEX;
      break;
   case IR_TEX_SH:
      opcode = OPCODE_TEX;
      shadow = GL_TRUE;
      break;
   case IR_TEXB:
      opcode = OPCODE_TXB;
      break;
   case IR_TEXB_SH:
      opcode = OPCODE_TXB;
      shadow = GL_TRUE;
      break;
   case IR_TEXP:
      opcode = OPCODE_TXP;
      break;
   case IR_TEXP_SH:
      opcode = OPCODE_TXP;
      shadow = GL_TRUE;
      break;
   default:
      _mesa_problem(NULL, "Bad IR TEX code");
      return NULL;
   }

   if (n->Children[0]->Opcode == IR_ELEMENT) {
      /* array is the sampler (a uniform which'll indicate the texture unit) */
      assert(n->Children[0]->Children[0]->Store);
      assert(n->Children[0]->Children[0]->Store->File == PROGRAM_SAMPLER);

      emit(emitInfo, n->Children[0]);

      n->Children[0]->Var = n->Children[0]->Children[0]->Var;
   } else {
      /* this is the sampler (a uniform which'll indicate the texture unit) */
      assert(n->Children[0]->Store);
      assert(n->Children[0]->Store->File == PROGRAM_SAMPLER);
   }

   /* emit code for the texcoord operand */
   (void) emit(emitInfo, n->Children[1]);

   /* alloc storage for result of texture fetch */
   if (!alloc_node_storage(emitInfo, n, 4))
      return NULL;

   /* emit TEX instruction;  Child[1] is the texcoord */
   inst = emit_instruction(emitInfo,
                           opcode,
                           n->Store,
                           n->Children[1]->Store,
                           NULL,
                           NULL);
   if (!inst) {
      return NULL;
   }

   inst->TexShadow = shadow;

   /* Store->Index is the uniform/sampler index */
   assert(n->Children[0]->Store->Index >= 0);
   inst->TexSrcUnit = n->Children[0]->Store->Index;
   inst->TexSrcTarget = n->Children[0]->Store->TexTarget;

   /* mark the sampler as being used */
   _mesa_use_uniform(emitInfo->prog->Parameters,
                     (char *) n->Children[0]->Var->a_name);

   return inst;
}


/**
 * Assignment/copy
 */
static struct prog_instruction *
emit_copy(slang_emit_info *emitInfo, slang_ir_node *n)
{
   struct prog_instruction *inst;

   assert(n->Opcode == IR_COPY);

   /* lhs */
   emit(emitInfo, n->Children[0]);
   if (!n->Children[0]->Store || n->Children[0]->Store->Index < 0) {
      /* an error should have been already recorded */
      return NULL;
   }

   /* rhs */
   assert(n->Children[1]);
   inst = emit(emitInfo, n->Children[1]);

   if (!n->Children[1]->Store || n->Children[1]->Store->Index < 0) {
      if (!emitInfo->log->text && !emitInfo->UnresolvedFunctions) {
         /* XXX this error should have been caught in slang_codegen.c */
         slang_info_log_error(emitInfo->log, "invalid assignment");
      }
      return NULL;
   }

   assert(n->Children[1]->Store->Index >= 0);

   /*assert(n->Children[0]->Store->Size == n->Children[1]->Store->Size);*/

   n->Store = n->Children[0]->Store;

   if (n->Store->File == PROGRAM_SAMPLER) {
      /* no code generated for sampler assignments,
       * just copy the sampler index/target at compile time.
       */
      n->Store->Index = n->Children[1]->Store->Index;
      n->Store->TexTarget = n->Children[1]->Store->TexTarget;
      return NULL;
   }

#if PEEPHOLE_OPTIMIZATIONS
   if (inst &&
       (n->Children[1]->Opcode != IR_SWIZZLE) &&
       _slang_is_temp(emitInfo->vt, n->Children[1]->Store) &&
       (inst->DstReg.File == n->Children[1]->Store->File) &&
       (inst->DstReg.Index == n->Children[1]->Store->Index) &&
       !n->Children[0]->Store->IsIndirect &&
       n->Children[0]->Store->Size <= 4) {
      /* Peephole optimization:
       * The Right-Hand-Side has its results in a temporary place.
       * Modify the RHS (and the prev instruction) to store its results
       * in the destination specified by n->Children[0].
       * Then, this MOVE is a no-op.
       * Ex:
       *   MUL tmp, x, y;
       *   MOV a, tmp;
       * becomes:
       *   MUL a, x, y;
       */

      /* fixup the previous instruction (which stored the RHS result) */
      assert(n->Children[0]->Store->Index >= 0);
      storage_to_dst_reg(&inst->DstReg, n->Children[0]->Store);
      return inst;
   }
   else
#endif
   {
      if (n->Children[0]->Store->Size > 4) {
         /* move matrix/struct etc (block of registers) */
         slang_ir_storage dstStore = *n->Children[0]->Store;
         slang_ir_storage srcStore = *n->Children[1]->Store;
         GLint size = srcStore.Size;
         ASSERT(n->Children[1]->Store->Swizzle == SWIZZLE_NOOP);
         dstStore.Size = 4;
         srcStore.Size = 4;
         while (size >= 4) {
            inst = emit_instruction(emitInfo, OPCODE_MOV,
                                    &dstStore,
                                    &srcStore,
                                    NULL,
                                    NULL);
            if (!inst) {
               return NULL;
            }
            inst_comment(inst, "IR_COPY block");
            srcStore.Index++;
            dstStore.Index++;
            size -= 4;
         }
      }
      else {
         /* single register move */
         char *srcAnnot, *dstAnnot;
         assert(n->Children[0]->Store->Index >= 0);
         inst = emit_instruction(emitInfo, OPCODE_MOV,
                                 n->Children[0]->Store, /* dest */
                                 n->Children[1]->Store,
                                 NULL,
                                 NULL);
         if (!inst) {
            return NULL;
         }
         dstAnnot = storage_annotation(n->Children[0], emitInfo->prog);
         srcAnnot = storage_annotation(n->Children[1], emitInfo->prog);
         inst->Comment = instruction_annotation(inst->Opcode, dstAnnot,
                                                srcAnnot, NULL, NULL);
      }
      free_node_storage(emitInfo->vt, n->Children[1]);
      return inst;
   }
}


/**
 * An IR_COND node wraps a boolean expression which is used by an
 * IF or WHILE test.  This is where we'll set condition codes, if needed.
 */
static struct prog_instruction *
emit_cond(slang_emit_info *emitInfo, slang_ir_node *n)
{
   struct prog_instruction *inst;

   assert(n->Opcode == IR_COND);

   if (!n->Children[0])
      return NULL;

   /* emit code for the expression */
   inst = emit(emitInfo, n->Children[0]);

   if (!n->Children[0]->Store) {
      /* error recovery */
      return NULL;
   }

   assert(n->Children[0]->Store);
   /*assert(n->Children[0]->Store->Size == 1);*/

   if (emitInfo->EmitCondCodes) {
      if (inst &&
          n->Children[0]->Store &&
          inst->DstReg.File == n->Children[0]->Store->File &&
          inst->DstReg.Index == n->Children[0]->Store->Index) {
         /* The previous instruction wrote to the register who's value
          * we're testing.  Just fix that instruction so that the
          * condition codes are computed.
          */
         inst->CondUpdate = GL_TRUE;
         n->Store = n->Children[0]->Store;
         return inst;
      }
      else {
         /* This'll happen for things like "if (i) ..." where no code
          * is normally generated for the expression "i".
          * Generate a move instruction just to set condition codes.
          */
         if (!alloc_node_storage(emitInfo, n, 1))
            return NULL;
         inst = emit_instruction(emitInfo, OPCODE_MOV,
                                 n->Store, /* dest */
                                 n->Children[0]->Store,
                                 NULL,
                                 NULL);
         if (!inst) {
            return NULL;
         }
         inst->CondUpdate = GL_TRUE;
         inst_comment(inst, "COND expr");
         _slang_free_temp(emitInfo->vt, n->Store);
         return inst;
      }
   }
   else {
      /* No-op: the boolean result of the expression is in a regular reg */
      n->Store = n->Children[0]->Store;
      return inst;
   }
}


/**
 * Logical-NOT
 */
static struct prog_instruction *
emit_not(slang_emit_info *emitInfo, slang_ir_node *n)
{
   static const struct {
      gl_inst_opcode op, opNot;
   } operators[] = {
      { OPCODE_SLT, OPCODE_SGE },
      { OPCODE_SLE, OPCODE_SGT },
      { OPCODE_SGT, OPCODE_SLE },
      { OPCODE_SGE, OPCODE_SLT },
      { OPCODE_SEQ, OPCODE_SNE },
      { OPCODE_SNE, OPCODE_SEQ },
      { 0, 0 }
   };
   struct prog_instruction *inst;
   slang_ir_storage zero;
   GLuint i;

   /* child expr */
   inst = emit(emitInfo, n->Children[0]);

#if PEEPHOLE_OPTIMIZATIONS
   if (inst) {
      /* if the prev instruction was a comparison instruction, invert it */
      for (i = 0; operators[i].op; i++) {
         if (inst->Opcode == operators[i].op) {
            inst->Opcode = operators[i].opNot;
            n->Store = n->Children[0]->Store;
            return inst;
         }
      }
   }
#endif

   /* else, invert using SEQ (v = v == 0) */
   if (!alloc_node_storage(emitInfo, n, n->Children[0]->Store->Size))
      return NULL;

   constant_to_storage(emitInfo, 0.0, &zero);
   inst = emit_instruction(emitInfo,
                           OPCODE_SEQ,
                           n->Store,
                           n->Children[0]->Store,
                           &zero,
                           NULL);
   if (!inst) {
      return NULL;
   }
   inst_comment(inst, "NOT");

   free_node_storage(emitInfo->vt, n->Children[0]);

   return inst;
}


static struct prog_instruction *
emit_if(slang_emit_info *emitInfo, slang_ir_node *n)
{
   struct gl_program *prog = emitInfo->prog;
   GLuint ifInstLoc, elseInstLoc = 0;
   GLuint condWritemask = 0;

   /* emit condition expression code */
   {
      struct prog_instruction *inst;
      inst = emit(emitInfo, n->Children[0]);
      if (emitInfo->EmitCondCodes) {
         if (!inst) {
            /* error recovery */
            return NULL;
         }
         condWritemask = inst->DstReg.WriteMask;
      }
   }

   if (!n->Children[0]->Store)
      return NULL;

#if 0
   assert(n->Children[0]->Store->Size == 1); /* a bool! */
#endif

   ifInstLoc = prog->NumInstructions;
   if (emitInfo->EmitHighLevelInstructions) {
      if (emitInfo->EmitCondCodes) {
         /* IF condcode THEN ... */
         struct prog_instruction *ifInst = new_instruction(emitInfo, OPCODE_IF);
         if (!ifInst) {
            return NULL;
         }
         ifInst->DstReg.CondMask = COND_NE;  /* if cond is non-zero */
         /* only test the cond code (1 of 4) that was updated by the
          * previous instruction.
          */
         ifInst->DstReg.CondSwizzle = writemask_to_swizzle(condWritemask);
      }
      else {
         struct prog_instruction *inst;

         /* IF src[0] THEN ... */
         inst = emit_instruction(emitInfo, OPCODE_IF,
                                 NULL, /* dst */
                                 n->Children[0]->Store, /* op0 */
                                 NULL,
                                 NULL);
         if (!inst) {
            return NULL;
         }
      }
   }
   else {
      /* conditional jump to else, or endif */
      struct prog_instruction *ifInst = new_instruction(emitInfo, OPCODE_BRA);
      if (!ifInst) {
         return NULL;
      }
      ifInst->DstReg.CondMask = COND_EQ;  /* BRA if cond is zero */
      inst_comment(ifInst, "if zero");
      ifInst->DstReg.CondSwizzle = writemask_to_swizzle(condWritemask);
   }

   /* if body */
   emit(emitInfo, n->Children[1]);

   if (n->Children[2]) {
      /* have else body */
      elseInstLoc = prog->NumInstructions;
      if (emitInfo->EmitHighLevelInstructions) {
         struct prog_instruction *inst = new_instruction(emitInfo, OPCODE_ELSE);
         if (!inst) {
            return NULL;
         }
         prog->Instructions[ifInstLoc].BranchTarget = prog->NumInstructions - 1;
      }
      else {
         /* jump to endif instruction */
         struct prog_instruction *inst = new_instruction(emitInfo, OPCODE_BRA);
         if (!inst) {
            return NULL;
         }
         inst_comment(inst, "else");
         inst->DstReg.CondMask = COND_TR;  /* always branch */
         prog->Instructions[ifInstLoc].BranchTarget = prog->NumInstructions;
      }
      emit(emitInfo, n->Children[2]);
   }
   else {
      /* no else body */
      prog->Instructions[ifInstLoc].BranchTarget = prog->NumInstructions;
   }

   if (emitInfo->EmitHighLevelInstructions) {
      struct prog_instruction *inst = new_instruction(emitInfo, OPCODE_ENDIF);
      if (!inst) {
         return NULL;
      }
   }

   if (elseInstLoc) {
      /* point ELSE instruction BranchTarget at ENDIF */
      if (emitInfo->EmitHighLevelInstructions) {
         prog->Instructions[elseInstLoc].BranchTarget = prog->NumInstructions - 1;
      }
      else {
         prog->Instructions[elseInstLoc].BranchTarget = prog->NumInstructions;
      }
   }
   return NULL;
}


static struct prog_instruction *
emit_loop(slang_emit_info *emitInfo, slang_ir_node *n)
{
   struct gl_program *prog = emitInfo->prog;
   struct prog_instruction *endInst;
   GLuint beginInstLoc, tailInstLoc, endInstLoc;
   slang_ir_node *ir;

   /* emit OPCODE_BGNLOOP */
   beginInstLoc = prog->NumInstructions;
   if (emitInfo->EmitHighLevelInstructions) {
      struct prog_instruction *inst = new_instruction(emitInfo, OPCODE_BGNLOOP);
      if (!inst) {
         return NULL;
      }
   }

   /* body */
   emit(emitInfo, n->Children[0]);

   /* tail */
   tailInstLoc = prog->NumInstructions;
   if (n->Children[1]) {
      if (emitInfo->EmitComments)
         emit_comment(emitInfo, "Loop tail code:");
      emit(emitInfo, n->Children[1]);
   }

   endInstLoc = prog->NumInstructions;
   if (emitInfo->EmitHighLevelInstructions) {
      /* emit OPCODE_ENDLOOP */
      endInst = new_instruction(emitInfo, OPCODE_ENDLOOP);
      if (!endInst) {
         return NULL;
      }
   }
   else {
      /* emit unconditional BRA-nch */
      endInst = new_instruction(emitInfo, OPCODE_BRA);
      if (!endInst) {
         return NULL;
      }
      endInst->DstReg.CondMask = COND_TR;  /* always true */
   }
   /* ENDLOOP's BranchTarget points to the BGNLOOP inst */
   endInst->BranchTarget = beginInstLoc;

   if (emitInfo->EmitHighLevelInstructions) {
      /* BGNLOOP's BranchTarget points to the ENDLOOP inst */
      prog->Instructions[beginInstLoc].BranchTarget = prog->NumInstructions -1;
   }

   /* Done emitting loop code.  Now walk over the loop's linked list of
    * BREAK and CONT nodes, filling in their BranchTarget fields (which
    * will point to the corresponding ENDLOOP instruction.
    */
   for (ir = n->List; ir; ir = ir->List) {
      struct prog_instruction *inst = prog->Instructions + ir->InstLocation;
      assert(inst->BranchTarget < 0);
      if (ir->Opcode == IR_BREAK ||
          ir->Opcode == IR_BREAK_IF_TRUE) {
         assert(inst->Opcode == OPCODE_BRK ||
                inst->Opcode == OPCODE_BRA);
         /* go to instruction at end of loop */
         if (emitInfo->EmitHighLevelInstructions) {
            inst->BranchTarget = endInstLoc;
         }
         else {
            inst->BranchTarget = endInstLoc + 1;
         }
      }
      else {
         assert(ir->Opcode == IR_CONT ||
                ir->Opcode == IR_CONT_IF_TRUE);
         assert(inst->Opcode == OPCODE_CONT ||
                inst->Opcode == OPCODE_BRA);
         /* go to instruction at tail of loop */
         inst->BranchTarget = endInstLoc;
      }
   }
   return NULL;
}


/**
 * Unconditional "continue" or "break" statement.
 * Either OPCODE_CONT, OPCODE_BRK or OPCODE_BRA will be emitted.
 */
static struct prog_instruction *
emit_cont_break(slang_emit_info *emitInfo, slang_ir_node *n)
{
   gl_inst_opcode opcode;
   struct prog_instruction *inst;

   if (n->Opcode == IR_CONT) {
      /* we need to execute the loop's tail code before doing CONT */
      assert(n->Parent);
      assert(n->Parent->Opcode == IR_LOOP);
      if (n->Parent->Children[1]) {
         /* emit tail code */
         if (emitInfo->EmitComments) {
            emit_comment(emitInfo, "continue - tail code:");
         }
         emit(emitInfo, n->Parent->Children[1]);
      }
   }

   /* opcode selection */
   if (emitInfo->EmitHighLevelInstructions) {
      opcode = (n->Opcode == IR_CONT) ? OPCODE_CONT : OPCODE_BRK;
   }
   else {
      opcode = OPCODE_BRA;
   }
   n->InstLocation = emitInfo->prog->NumInstructions;
   inst = new_instruction(emitInfo, opcode);
   if (inst) {
      inst->DstReg.CondMask = COND_TR;  /* always true */
   }
   return inst;
}


/**
 * Conditional "continue" or "break" statement.
 * Either OPCODE_CONT, OPCODE_BRK or OPCODE_BRA will be emitted.
 */
static struct prog_instruction *
emit_cont_break_if_true(slang_emit_info *emitInfo, slang_ir_node *n)
{
   struct prog_instruction *inst;

   assert(n->Opcode == IR_CONT_IF_TRUE ||
          n->Opcode == IR_BREAK_IF_TRUE);

   /* evaluate condition expr, setting cond codes */
   inst = emit(emitInfo, n->Children[0]);
   if (emitInfo->EmitCondCodes) {
      assert(inst);
      inst->CondUpdate = GL_TRUE;
   }

   n->InstLocation = emitInfo->prog->NumInstructions;

   /* opcode selection */
   if (emitInfo->EmitHighLevelInstructions) {
      const gl_inst_opcode opcode
         = (n->Opcode == IR_CONT_IF_TRUE) ? OPCODE_CONT : OPCODE_BRK;
      if (emitInfo->EmitCondCodes) {
         /* Get the writemask from the previous instruction which set
          * the condcodes.  Use that writemask as the CondSwizzle.
          */
         const GLuint condWritemask = inst->DstReg.WriteMask;
         inst = new_instruction(emitInfo, opcode);
         if (inst) {
            inst->DstReg.CondMask = COND_NE;
            inst->DstReg.CondSwizzle = writemask_to_swizzle(condWritemask);
         }
         return inst;
      }
      else {
         /* IF reg
          *    BRK/CONT;
          * ENDIF
          */
         GLint ifInstLoc;
         ifInstLoc = emitInfo->prog->NumInstructions;
         inst = emit_instruction(emitInfo, OPCODE_IF,
                                 NULL, /* dest */
                                 n->Children[0]->Store,
                                 NULL,
                                 NULL);
         if (!inst) {
            return NULL;
         }
         n->InstLocation = emitInfo->prog->NumInstructions;

         inst = new_instruction(emitInfo, opcode);
         if (!inst) {
            return NULL;
         }
         inst = new_instruction(emitInfo, OPCODE_ENDIF);
         if (!inst) {
            return NULL;
         }

         emitInfo->prog->Instructions[ifInstLoc].BranchTarget
            = emitInfo->prog->NumInstructions - 1;
         return inst;
      }
   }
   else {
      const GLuint condWritemask = inst->DstReg.WriteMask;
      assert(emitInfo->EmitCondCodes);
      inst = new_instruction(emitInfo, OPCODE_BRA);
      if (inst) {
         inst->DstReg.CondMask = COND_NE;
         inst->DstReg.CondSwizzle = writemask_to_swizzle(condWritemask);
      }
      return inst;
   }
}


/**
 * Return the size of a swizzle mask given that some swizzle components
 * may be NIL/undefined.  For example:
 *  swizzle_size(".zzxx") = 4
 *  swizzle_size(".xy??") = 2
 *  swizzle_size(".w???") = 1
 */
static GLuint
swizzle_size(GLuint swizzle)
{
   GLuint i;
   for (i = 0; i < 4; i++) {
      if (GET_SWZ(swizzle, i) == SWIZZLE_NIL)
         return i;
   }
   return 4;
}


static struct prog_instruction *
emit_swizzle(slang_emit_info *emitInfo, slang_ir_node *n)
{
   struct prog_instruction *inst;

   inst = emit(emitInfo, n->Children[0]);

   if (!n->Store->Parent) {
      /* this covers a case such as "(b ? p : q).x" */
      n->Store->Parent = n->Children[0]->Store;
      assert(n->Store->Parent);
   }

   {
      const GLuint swizzle = n->Store->Swizzle;
      /* new storage is parent storage with updated Swizzle + Size fields */
      _slang_copy_ir_storage(n->Store, n->Store->Parent);
      /* Apply this node's swizzle to parent's storage */
      n->Store->Swizzle = _slang_swizzle_swizzle(n->Store->Swizzle, swizzle);
      /* Update size */
      n->Store->Size = swizzle_size(n->Store->Swizzle);
   }

   assert(!n->Store->Parent);
   assert(n->Store->Index >= 0);

   return inst;
}


/**
 * Dereference array element:  element == array[index]
 * This basically involves emitting code for computing the array index
 * and updating the node/element's storage info.
 */
static struct prog_instruction *
emit_array_element(slang_emit_info *emitInfo, slang_ir_node *n)
{
   slang_ir_storage *arrayStore, *indexStore;
   const int elemSize = n->Store->Size;           /* number of floats */
   const GLint elemSizeVec = (elemSize + 3) / 4;  /* number of vec4 */
   struct prog_instruction *inst;

   assert(n->Opcode == IR_ELEMENT);
   assert(elemSize > 0);

   /* special case for built-in state variables, like light state */
   {
      slang_ir_storage *root = n->Store;
      assert(!root->Parent);
      while (root->Parent)
         root = root->Parent;

      if (root->File == PROGRAM_STATE_VAR) {
         GLboolean direct;
         GLint index =
            _slang_alloc_statevar(n, emitInfo->prog->Parameters, &direct);
         if (index < 0) {
            /* error */
            return NULL;
         }
         if (direct) {
            n->Store->Index = index;
            return NULL; /* all done */
         }
      }
   }

   /* do codegen for array itself */
   emit(emitInfo, n->Children[0]);
   arrayStore = n->Children[0]->Store;

   /* The initial array element storage is the array's storage,
    * then modified below.
    */
   _slang_copy_ir_storage(n->Store, arrayStore);


   if (n->Children[1]->Opcode == IR_FLOAT) {
      /* Constant array index */
      const GLint element = (GLint) n->Children[1]->Value[0];

      /* this element's storage is the array's storage, plus constant offset */
      n->Store->Index += elemSizeVec * element;
   }
   else {
      /* Variable array index */

      /* do codegen for array index expression */
      emit(emitInfo, n->Children[1]);
      indexStore = n->Children[1]->Store;

      if (indexStore->IsIndirect) {
         /* need to put the array index into a temporary since we can't
          * directly support a[b[i]] constructs.
          */


         /*indexStore = tempstore();*/
      }


      if (elemSize > 4) {
         /* need to multiply array index by array element size */
         struct prog_instruction *inst;
         slang_ir_storage *indexTemp;
         slang_ir_storage elemSizeStore;

         /* allocate 1 float indexTemp */
         indexTemp = _slang_new_ir_storage(PROGRAM_TEMPORARY, -1, 1);
         _slang_alloc_temp(emitInfo->vt, indexTemp);

         /* allocate a constant containing the element size */
         constant_to_storage(emitInfo, (float) elemSizeVec, &elemSizeStore);

         /* multiply array index by element size */
         inst = emit_instruction(emitInfo,
                                 OPCODE_MUL,
                                 indexTemp, /* dest */
                                 indexStore, /* the index */
                                 &elemSizeStore,
                                 NULL);
         if (!inst) {
            return NULL;
         }

         indexStore = indexTemp;
      }

      if (arrayStore->IsIndirect) {
         /* ex: in a[i][j], a[i] (the arrayStore) is indirect */
         /* Need to add indexStore to arrayStore->Indirect store */
         slang_ir_storage indirectArray;
         slang_ir_storage *indexTemp;

         _slang_init_ir_storage(&indirectArray,
                                arrayStore->IndirectFile,
                                arrayStore->IndirectIndex,
                                1,
                                arrayStore->IndirectSwizzle);

         /* allocate 1 float indexTemp */
         indexTemp = _slang_new_ir_storage(PROGRAM_TEMPORARY, -1, 1);
         _slang_alloc_temp(emitInfo->vt, indexTemp);

         inst = emit_instruction(emitInfo,
                                 OPCODE_ADD,
                                 indexTemp,      /* dest */
                                 indexStore,     /* the index */
                                 &indirectArray, /* indirect array base */
                                 NULL);
         if (!inst) {
            return NULL;
         }

         indexStore = indexTemp;
      }

      /* update the array element storage info */
      n->Store->IsIndirect = GL_TRUE;
      n->Store->IndirectFile = indexStore->File;
      n->Store->IndirectIndex = indexStore->Index;
      n->Store->IndirectSwizzle = indexStore->Swizzle;
   }

   n->Store->Size = elemSize;
   n->Store->Swizzle = _slang_var_swizzle(elemSize, 0);

   return NULL; /* no instruction */
}


/**
 * Resolve storage for accessing a structure field.
 */
static struct prog_instruction *
emit_struct_field(slang_emit_info *emitInfo, slang_ir_node *n)
{
   slang_ir_storage *root = n->Store;
   GLint fieldOffset, fieldSize;

   assert(n->Opcode == IR_FIELD);

   assert(!root->Parent);
   while (root->Parent)
      root = root->Parent;

   /* If this is the field of a state var, allocate constant/uniform
    * storage for it now if we haven't already.
    * Note that we allocate storage (uniform/constant slots) for state
    * variables here rather than at declaration time so we only allocate
    * space for the ones that we actually use!
    */
   if (root->File == PROGRAM_STATE_VAR) {
      GLboolean direct;
      GLint index = _slang_alloc_statevar(n, emitInfo->prog->Parameters, &direct);
      if (index < 0) {
         slang_info_log_error(emitInfo->log, "Error parsing state variable");
         return NULL;
      }
      if (direct) {
         root->Index = index;
         return NULL; /* all done */
      }
   }

   /* do codegen for struct */
   emit(emitInfo, n->Children[0]);
   assert(n->Children[0]->Store->Index >= 0);


   fieldOffset = n->Store->Index;
   fieldSize = n->Store->Size;

   _slang_copy_ir_storage(n->Store, n->Children[0]->Store);

   n->Store->Index = n->Children[0]->Store->Index + fieldOffset / 4;
   n->Store->Size = fieldSize;

   switch (fieldSize) {
   case 1:
      {
         GLint swz = fieldOffset % 4;
         n->Store->Swizzle = MAKE_SWIZZLE4(swz, swz, swz, swz);
      }
      break;
   case 2:
      n->Store->Swizzle = MAKE_SWIZZLE4(SWIZZLE_X, SWIZZLE_Y,
                                        SWIZZLE_NIL, SWIZZLE_NIL);
      break;
   case 3:
      n->Store->Swizzle = MAKE_SWIZZLE4(SWIZZLE_X, SWIZZLE_Y,
                                        SWIZZLE_Z, SWIZZLE_NIL);
      break;
   default:
      n->Store->Swizzle = SWIZZLE_XYZW;
   }

   assert(n->Store->Index >= 0);

   return NULL; /* no instruction */
}


/**
 * Emit code for a variable declaration.
 * This usually doesn't result in any code generation, but just
 * memory allocation.
 */
static struct prog_instruction *
emit_var_decl(slang_emit_info *emitInfo, slang_ir_node *n)
{
   assert(n->Store);
   assert(n->Store->File != PROGRAM_UNDEFINED);
   assert(n->Store->Size > 0);
   /*assert(n->Store->Index < 0);*/

   if (!n->Var || n->Var->isTemp) {
      /* a nameless/temporary variable, will be freed after first use */
      /*NEW*/
      if (n->Store->Index < 0 && !_slang_alloc_temp(emitInfo->vt, n->Store)) {
         slang_info_log_error(emitInfo->log,
                              "Ran out of registers, too many temporaries");
         return NULL;
      }
   }
   else {
      /* a regular variable */
      _slang_add_variable(emitInfo->vt, n->Var);
      if (!_slang_alloc_var(emitInfo->vt, n->Store)) {
         slang_info_log_error(emitInfo->log,
                              "Ran out of registers, too many variables");
         return NULL;
      }
      /*
        printf("IR_VAR_DECL %s %d store %p\n",
        (char*) n->Var->a_name, n->Store->Index, (void*) n->Store);
      */
      assert(n->Var->store == n->Store);
   }
   if (emitInfo->EmitComments) {
      /* emit NOP with comment describing the variable's storage location */
      char s[1000];
      sprintf(s, "TEMP[%d]%s = variable %s (size %d)",
              n->Store->Index,
              _mesa_swizzle_string(n->Store->Swizzle, 0, GL_FALSE), 
              (n->Var ? (char *) n->Var->a_name : "anonymous"),
              n->Store->Size);
      emit_comment(emitInfo, s);
   }
   return NULL;
}


/**
 * Emit code for a reference to a variable.
 * Actually, no code is generated but we may do some memory allocation.
 * In particular, state vars (uniforms) are allocated on an as-needed basis.
 */
static struct prog_instruction *
emit_var_ref(slang_emit_info *emitInfo, slang_ir_node *n)
{
   assert(n->Store);
   assert(n->Store->File != PROGRAM_UNDEFINED);

   if (n->Store->File == PROGRAM_STATE_VAR && n->Store->Index < 0) {
      GLboolean direct;
      GLint index = _slang_alloc_statevar(n, emitInfo->prog->Parameters, &direct);
      if (index < 0) {
         /* error */
         char s[100];
         /* XXX isn't this really an out of memory/resources error? */
         _mesa_snprintf(s, sizeof(s), "Undefined variable '%s'",
		  (char *) n->Var->a_name);
         slang_info_log_error(emitInfo->log, s);
         return NULL;
      }

      n->Store->Index = index;
   }
   else if (n->Store->File == PROGRAM_UNIFORM ||
            n->Store->File == PROGRAM_SAMPLER) {
      /* mark var as used */
      _mesa_use_uniform(emitInfo->prog->Parameters, (char *) n->Var->a_name);
   }
   else if (n->Store->File == PROGRAM_INPUT) {
      assert(n->Store->Index >= 0);
      emitInfo->prog->InputsRead |= (1 << n->Store->Index);
   }

   if (n->Store->Index < 0) {
      /* probably ran out of registers */
      return NULL;
   }
   assert(n->Store->Size > 0);

   return NULL;
}


static struct prog_instruction *
emit(slang_emit_info *emitInfo, slang_ir_node *n)
{
   struct prog_instruction *inst;
   if (!n)
      return NULL;

   if (emitInfo->log->error_flag) {
      return NULL;
   }

   if (n->Comment) {
      inst = new_instruction(emitInfo, OPCODE_NOP);
      if (inst) {
         inst->Comment = _mesa_strdup(n->Comment);
      }
      inst = NULL;
   }

   switch (n->Opcode) {
   case IR_SEQ:
      /* sequence of two sub-trees */
      assert(n->Children[0]);
      assert(n->Children[1]);
      emit(emitInfo, n->Children[0]);
      if (emitInfo->log->error_flag)
         return NULL;
      inst = emit(emitInfo, n->Children[1]);
#if 0
      assert(!n->Store);
#endif
      n->Store = n->Children[1]->Store;
      return inst;

   case IR_SCOPE:
      /* new variable scope */
      _slang_push_var_table(emitInfo->vt);
      inst = emit(emitInfo, n->Children[0]);
      _slang_pop_var_table(emitInfo->vt);
      return inst;

   case IR_VAR_DECL:
      /* Variable declaration - allocate a register for it */
      inst = emit_var_decl(emitInfo, n);
      return inst;

   case IR_VAR:
      /* Reference to a variable
       * Storage should have already been resolved/allocated.
       */
      return emit_var_ref(emitInfo, n);

   case IR_ELEMENT:
      return emit_array_element(emitInfo, n);
   case IR_FIELD:
      return emit_struct_field(emitInfo, n);
   case IR_SWIZZLE:
      return emit_swizzle(emitInfo, n);

   /* Simple arithmetic */
   /* unary */
   case IR_MOVE:
   case IR_RSQ:
   case IR_RCP:
   case IR_FLOOR:
   case IR_FRAC:
   case IR_F_TO_I:
   case IR_I_TO_F:
   case IR_ABS:
   case IR_SIN:
   case IR_COS:
   case IR_DDX:
   case IR_DDY:
   case IR_EXP:
   case IR_EXP2:
   case IR_LOG2:
   case IR_NOISE1:
   case IR_NOISE2:
   case IR_NOISE3:
   case IR_NOISE4:
   case IR_NRM4:
   case IR_NRM3:
   /* binary */
   case IR_ADD:
   case IR_SUB:
   case IR_MUL:
   case IR_DOT4:
   case IR_DOT3:
   case IR_DOT2:
   case IR_CROSS:
   case IR_MIN:
   case IR_MAX:
   case IR_SEQUAL:
   case IR_SNEQUAL:
   case IR_SGE:
   case IR_SGT:
   case IR_SLE:
   case IR_SLT:
   case IR_POW:
   /* trinary operators */
   case IR_LRP:
   case IR_CMP:
      return emit_arith(emitInfo, n);

   case IR_EQUAL:
   case IR_NOTEQUAL:
      return emit_compare(emitInfo, n);

   case IR_CLAMP:
      return emit_clamp(emitInfo, n);
   case IR_TEX:
   case IR_TEXB:
   case IR_TEXP:
   case IR_TEX_SH:
   case IR_TEXB_SH:
   case IR_TEXP_SH:
      return emit_tex(emitInfo, n);
   case IR_NEG:
      return emit_negation(emitInfo, n);
   case IR_FLOAT:
      /* find storage location for this float constant */
      n->Store->Index = _mesa_add_unnamed_constant(emitInfo->prog->Parameters,
                                                   n->Value,
                                                   n->Store->Size,
                                                   &n->Store->Swizzle);
      if (n->Store->Index < 0) {
         slang_info_log_error(emitInfo->log, "Ran out of space for constants");
         return NULL;
      }
      return NULL;

   case IR_COPY:
      return emit_copy(emitInfo, n);

   case IR_COND:
      return emit_cond(emitInfo, n);

   case IR_NOT:
      return emit_not(emitInfo, n);

   case IR_LABEL:
      return emit_label(emitInfo, n);

   case IR_KILL:
      return emit_kill(emitInfo);

   case IR_CALL:
      /* new variable scope for subroutines/function calls */
      _slang_push_var_table(emitInfo->vt);
      inst = emit_fcall(emitInfo, n);
      _slang_pop_var_table(emitInfo->vt);
      return inst;

   case IR_IF:
      return emit_if(emitInfo, n);

   case IR_LOOP:
      return emit_loop(emitInfo, n);
   case IR_BREAK_IF_TRUE:
   case IR_CONT_IF_TRUE:
      return emit_cont_break_if_true(emitInfo, n);
   case IR_BREAK:
      /* fall-through */
   case IR_CONT:
      return emit_cont_break(emitInfo, n);

   case IR_BEGIN_SUB:
      return new_instruction(emitInfo, OPCODE_BGNSUB);
   case IR_END_SUB:
      return new_instruction(emitInfo, OPCODE_ENDSUB);
   case IR_RETURN:
      return emit_return(emitInfo, n);

   case IR_NOP:
      return NULL;

   default:
      _mesa_problem(NULL, "Unexpected IR opcode in emit()\n");
   }
   return NULL;
}


/**
 * After code generation, any subroutines will be in separate program
 * objects.  This function appends all the subroutines onto the main
 * program and resolves the linking of all the branch/call instructions.
 * XXX this logic should really be part of the linking process...
 */
static void
_slang_resolve_subroutines(slang_emit_info *emitInfo)
{
   GET_CURRENT_CONTEXT(ctx);
   struct gl_program *mainP = emitInfo->prog;
   GLuint *subroutineLoc, i, total;

   subroutineLoc
      = (GLuint *) malloc(emitInfo->NumSubroutines * sizeof(GLuint));

   /* total number of instructions */
   total = mainP->NumInstructions;
   for (i = 0; i < emitInfo->NumSubroutines; i++) {
      subroutineLoc[i] = total;
      total += emitInfo->Subroutines[i]->NumInstructions;
   }

   /* adjust BranchTargets within the functions */
   for (i = 0; i < emitInfo->NumSubroutines; i++) {
      struct gl_program *sub = emitInfo->Subroutines[i];
      GLuint j;
      for (j = 0; j < sub->NumInstructions; j++) {
         struct prog_instruction *inst = sub->Instructions + j;
         if (inst->Opcode != OPCODE_CAL && inst->BranchTarget >= 0) {
            inst->BranchTarget += subroutineLoc[i];
         }
      }
   }

   /* append subroutines' instructions after main's instructions */
   mainP->Instructions = _mesa_realloc_instructions(mainP->Instructions,
                                                    mainP->NumInstructions,
                                                    total);
   mainP->NumInstructions = total;
   for (i = 0; i < emitInfo->NumSubroutines; i++) {
      struct gl_program *sub = emitInfo->Subroutines[i];
      _mesa_copy_instructions(mainP->Instructions + subroutineLoc[i],
                              sub->Instructions,
                              sub->NumInstructions);
      /* delete subroutine code */
      sub->Parameters = NULL; /* prevent double-free */
      _mesa_reference_program(ctx, &emitInfo->Subroutines[i], NULL);
   }

   /* free subroutine list */
   if (emitInfo->Subroutines) {
      free(emitInfo->Subroutines);
      emitInfo->Subroutines = NULL;
   }
   emitInfo->NumSubroutines = 0;

   /* Examine CAL instructions.
    * At this point, the BranchTarget field of the CAL instruction is
    * the number/id of the subroutine to call (an index into the
    * emitInfo->Subroutines list).
    * Translate that into an actual instruction location now.
    */
   for (i = 0; i < mainP->NumInstructions; i++) {
      struct prog_instruction *inst = mainP->Instructions + i;
      if (inst->Opcode == OPCODE_CAL) {
         const GLuint f = inst->BranchTarget;
         inst->BranchTarget = subroutineLoc[f];
      }
   }

   free(subroutineLoc);
}



/**
 * Convert the IR tree into GPU instructions.
 * \param n  root of IR tree
 * \param vt  variable table
 * \param prog  program to put GPU instructions into
 * \param pragmas  controls codegen options
 * \param withEnd  if true, emit END opcode at end
 * \param log  log for emitting errors/warnings/info
 */
GLboolean
_slang_emit_code(slang_ir_node *n, slang_var_table *vt,
                 struct gl_program *prog,
                 const struct gl_sl_pragmas *pragmas,
                 GLboolean withEnd,
                 slang_info_log *log)
{
   GET_CURRENT_CONTEXT(ctx);
   GLboolean success;
   slang_emit_info emitInfo;
   GLuint maxUniforms;

   emitInfo.log = log;
   emitInfo.vt = vt;
   emitInfo.prog = prog;
   emitInfo.Subroutines = NULL;
   emitInfo.NumSubroutines = 0;
   emitInfo.MaxInstructions = prog->NumInstructions;

   emitInfo.EmitHighLevelInstructions = ctx->Shader.EmitHighLevelInstructions;
   emitInfo.EmitCondCodes = ctx->Shader.EmitCondCodes;
   emitInfo.EmitComments = ctx->Shader.EmitComments || pragmas->Debug;
   emitInfo.EmitBeginEndSub = GL_TRUE;

   if (!emitInfo.EmitCondCodes) {
      emitInfo.EmitHighLevelInstructions = GL_TRUE;
   }      

   /* Check uniform/constant limits */
   if (prog->Target == GL_FRAGMENT_PROGRAM_ARB) {
      maxUniforms = ctx->Const.FragmentProgram.MaxUniformComponents / 4;
   }
   else {
      assert(prog->Target == GL_VERTEX_PROGRAM_ARB);
      maxUniforms = ctx->Const.VertexProgram.MaxUniformComponents / 4;
   }
   if (prog->Parameters->NumParameters > maxUniforms) {
      slang_info_log_error(log, "Constant/uniform register limit exceeded "
                           "(max=%u vec4)", maxUniforms);

      return GL_FALSE;
   }

   (void) emit(&emitInfo, n);

   /* finish up by adding the END opcode to program */
   if (withEnd) {
      struct prog_instruction *inst;
      inst = new_instruction(&emitInfo, OPCODE_END);
      if (!inst) {
         return GL_FALSE;
      }
   }

   _slang_resolve_subroutines(&emitInfo);

   success = GL_TRUE;

#if 0
   printf("*********** End emit code (%u inst):\n", prog->NumInstructions);
   _mesa_print_program(prog);
   _mesa_print_program_parameters(ctx,prog);
#endif

   return success;
}
