blob: 3af301eacdf1e448aa8e3eb78d78de0c941b1161 [file] [log] [blame]
/*
* 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 "main/macros.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);
}
/* 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);
_mesa_bzero(temp, 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);
}
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);
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);
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(_mesa_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);
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 */
alloc_node_storage(emitInfo, n, -1); /* dest */
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 */
alloc_node_storage(emitInfo, n, -1); /* dest */
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 */
alloc_node_storage(emitInfo, n, -1);
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);
inst_comment(inst, "Compare values");
/* Compute val = DOT(temp, temp) (reduction) */
inst = emit_instruction(emitInfo,
dotOp,
n->Store,
&tempStore,
&tempStore,
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);
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);
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);
/* ADD accTemp, accTemp, sneTemp; # like logical-OR */
inst = emit_instruction(emitInfo, OPCODE_ADD,
&accTemp, /* dest */
&accTemp,
&sneTemp,
NULL);
}
}
/* compute accTemp.x || accTemp.y || accTemp.z || accTemp.w with DOT4 */
inst = emit_instruction(emitInfo, OPCODE_DP4,
n->Store,
&accTemp,
&accTemp,
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);
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;
}
}
#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.
*/
_mesa_bzero(&tmpNode, sizeof(tmpNode));
alloc_node_storage(emitInfo, &tmpNode, n->Store->Size);
/* tmp = max(ch[0], ch[1]) */
inst = emit_instruction(emitInfo, OPCODE_MAX,
tmpNode.Store, /* dest */
n->Children[0]->Store,
n->Children[1]->Store,
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);
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);
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 (emitInfo->EmitBeginEndSub) {
inst = new_instruction(emitInfo, OPCODE_ENDSUB);
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);
/* 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);
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);
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);
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);
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);
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);
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);
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;
ifInst = new_instruction(emitInfo, OPCODE_IF);
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 {
/* IF src[0] THEN ... */
emit_instruction(emitInfo, OPCODE_IF,
NULL, /* dst */
n->Children[0]->Store, /* op0 */
NULL,
NULL);
}
}
else {
/* conditional jump to else, or endif */
struct prog_instruction *ifInst = new_instruction(emitInfo, OPCODE_BRA);
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) {
(void) new_instruction(emitInfo, OPCODE_ELSE);
}
else {
/* jump to endif instruction */
struct prog_instruction *inst;
inst = new_instruction(emitInfo, OPCODE_BRA);
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) {
(void) new_instruction(emitInfo, OPCODE_ENDIF);
}
if (n->Children[2]) {
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) {
(void) new_instruction(emitInfo, OPCODE_BGNLOOP);
}
/* 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);
}
else {
/* emit unconditional BRA-nch */
endInst = new_instruction(emitInfo, OPCODE_BRA);
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 ENDLOOP+1 or BGNLOOP instructions, respectively).
*/
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 after end of loop */
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);
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);
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);
n->InstLocation = emitInfo->prog->NumInstructions;
inst = new_instruction(emitInfo, opcode);
inst = new_instruction(emitInfo, OPCODE_ENDIF);
emitInfo->prog->Instructions[ifInstLoc].BranchTarget
= emitInfo->prog->NumInstructions;
return inst;
}
}
else {
const GLuint condWritemask = inst->DstReg.WriteMask;
assert(emitInfo->EmitCondCodes);
inst = new_instruction(emitInfo, OPCODE_BRA);
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);
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);
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);
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 *) _mesa_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) {
_mesa_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];
}
}
_mesa_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);
}
_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;
}