aboutsummaryrefslogtreecommitdiffstats
path: root/src/gallium/drivers/nv50
diff options
context:
space:
mode:
authorChristoph Bumiller <[email protected]>2011-09-14 16:18:23 +0200
committerChristoph Bumiller <[email protected]>2011-09-14 16:19:52 +0200
commit57594065c30feec9376be9b2132659f7d87362ee (patch)
tree7e6808e0c5240b513851b7925c5be6678663b5e5 /src/gallium/drivers/nv50
parenta42eca84c56f6860e67c0c57f4765a5530cc5f81 (diff)
nv50/ir: import new shader backend code
Diffstat (limited to 'src/gallium/drivers/nv50')
-rw-r--r--src/gallium/drivers/nv50/Makefile2
-rw-r--r--src/gallium/drivers/nv50/Makefile.sources14
-rw-r--r--src/gallium/drivers/nv50/codegen/nv50_ir.cpp1008
-rw-r--r--src/gallium/drivers/nv50/codegen/nv50_ir.h1049
-rw-r--r--src/gallium/drivers/nv50/codegen/nv50_ir_bb.cpp409
-rw-r--r--src/gallium/drivers/nv50/codegen/nv50_ir_build_util.cpp501
-rw-r--r--src/gallium/drivers/nv50/codegen/nv50_ir_build_util.h245
-rw-r--r--src/gallium/drivers/nv50/codegen/nv50_ir_driver.h149
-rw-r--r--src/gallium/drivers/nv50/codegen/nv50_ir_emit_nv50.cpp1333
-rw-r--r--src/gallium/drivers/nv50/codegen/nv50_ir_from_tgsi.cpp2288
-rw-r--r--src/gallium/drivers/nv50/codegen/nv50_ir_graph.cpp381
-rw-r--r--src/gallium/drivers/nv50/codegen/nv50_ir_graph.h207
-rw-r--r--src/gallium/drivers/nv50/codegen/nv50_ir_inlines.h328
-rw-r--r--src/gallium/drivers/nv50/codegen/nv50_ir_peephole.cpp2192
-rw-r--r--src/gallium/drivers/nv50/codegen/nv50_ir_print.cpp558
-rw-r--r--src/gallium/drivers/nv50/codegen/nv50_ir_ra.cpp963
-rw-r--r--src/gallium/drivers/nv50/codegen/nv50_ir_ssa.cpp463
-rw-r--r--src/gallium/drivers/nv50/codegen/nv50_ir_target.cpp304
-rw-r--r--src/gallium/drivers/nv50/codegen/nv50_ir_target.h164
-rw-r--r--src/gallium/drivers/nv50/codegen/nv50_ir_util.cpp253
-rw-r--r--src/gallium/drivers/nv50/codegen/nv50_ir_util.h585
21 files changed, 13395 insertions, 1 deletions
diff --git a/src/gallium/drivers/nv50/Makefile b/src/gallium/drivers/nv50/Makefile
index 220adf696b3..18e30b0a54f 100644
--- a/src/gallium/drivers/nv50/Makefile
+++ b/src/gallium/drivers/nv50/Makefile
@@ -3,7 +3,7 @@ include $(TOP)/configs/current
LIBNAME = nv50
-# get C_SOURCES
+# get C/CPP_SOURCES
include Makefile.sources
LIBRARY_INCLUDES = \
diff --git a/src/gallium/drivers/nv50/Makefile.sources b/src/gallium/drivers/nv50/Makefile.sources
index 756f90be979..cc9321bef7e 100644
--- a/src/gallium/drivers/nv50/Makefile.sources
+++ b/src/gallium/drivers/nv50/Makefile.sources
@@ -21,3 +21,17 @@ C_SOURCES := \
nv50_pc_regalloc.c \
nv50_push.c \
nv50_query.c
+
+CPP_SOURCES := \
+ codegen/nv50_ir.cpp \
+ codegen/nv50_ir_bb.cpp \
+ codegen/nv50_ir_build_util.cpp \
+ codegen/nv50_ir_emit_nv50.cpp \
+ codegen/nv50_ir_from_tgsi.cpp \
+ codegen/nv50_ir_graph.cpp \
+ codegen/nv50_ir_peephole.cpp \
+ codegen/nv50_ir_print.cpp \
+ codegen/nv50_ir_ra.cpp \
+ codegen/nv50_ir_ssa.cpp \
+ codegen/nv50_ir_target.cpp \
+ codegen/nv50_ir_util.cpp
diff --git a/src/gallium/drivers/nv50/codegen/nv50_ir.cpp b/src/gallium/drivers/nv50/codegen/nv50_ir.cpp
new file mode 100644
index 00000000000..ff2e6ef3401
--- /dev/null
+++ b/src/gallium/drivers/nv50/codegen/nv50_ir.cpp
@@ -0,0 +1,1008 @@
+
+#include "nv50_ir.h"
+#include "nv50_ir_target.h"
+#include "nv50_ir_driver.h"
+
+extern "C" {
+#include "nv50/nv50_program.h"
+#include "nv50/nv50_debug.h"
+}
+
+namespace nv50_ir {
+
+Modifier::Modifier(operation op)
+{
+ switch (op) {
+ case OP_NEG: bits = NV50_IR_MOD_NEG; break;
+ case OP_ABS: bits = NV50_IR_MOD_ABS; break;
+ case OP_SAT: bits = NV50_IR_MOD_SAT; break;
+ case OP_NOP: bits = NV50_IR_MOD_NOT; break;
+ default:
+ bits = 0;
+ break;
+ }
+}
+
+Modifier Modifier::operator*(const Modifier m) const
+{
+ unsigned int a, b, c;
+
+ b = m.bits;
+ if (this->bits & NV50_IR_MOD_ABS)
+ b &= ~NV50_IR_MOD_NEG;
+
+ a = (this->bits ^ b) & (NV50_IR_MOD_NOT | NV50_IR_MOD_NEG);
+ c = (this->bits | m.bits) & (NV50_IR_MOD_ABS | NV50_IR_MOD_SAT);
+
+ return Modifier(a | c);
+}
+
+ValueRef::ValueRef() : value(0), insn(0), next(this), prev(this)
+{
+ indirect[0] = -1;
+ indirect[1] = -1;
+ usedAsPtr = false;
+}
+
+ValueRef::~ValueRef()
+{
+ this->set(NULL);
+}
+
+ImmediateValue *ValueRef::getImmediate() const
+{
+ Value *src = value;
+
+ while (src) {
+ if (src->reg.file == FILE_IMMEDIATE)
+ return src->asImm();
+
+ Instruction *insn = src->getUniqueInsn();
+
+ src = (insn && insn->op == OP_MOV) ? insn->getSrc(0) : NULL;
+ }
+ return NULL;
+}
+
+ValueDef::ValueDef() : value(0), insn(0), next(this), prev(this)
+{
+ // nothing to do
+}
+
+ValueDef::~ValueDef()
+{
+ this->set(NULL);
+}
+
+void
+ValueRef::set(const ValueRef &ref)
+{
+ this->set(ref.get());
+ mod = ref.mod;
+ indirect[0] = ref.indirect[0];
+ indirect[1] = ref.indirect[1];
+}
+
+void
+ValueRef::set(Value *refVal)
+{
+ if (value == refVal)
+ return;
+ if (value) {
+ if (value->uses == this)
+ value->uses = (next == this) ? NULL : next;
+ value->unref();
+ DLLIST_DEL(this);
+ }
+
+ if (refVal) {
+ if (refVal->uses)
+ DLLIST_ADDTAIL(refVal->uses, this);
+ else
+ refVal->uses = this;
+ refVal->ref();
+ }
+ value = refVal;
+}
+
+void
+ValueDef::set(Value *defVal)
+{
+ assert(next != this || prev == this); // check that SSA hack isn't active
+
+ if (value == defVal)
+ return;
+ if (value) {
+ if (value->defs == this)
+ value->defs = (next == this) ? NULL : next;
+ DLLIST_DEL(this);
+ }
+
+ if (defVal) {
+ if (defVal->defs)
+ DLLIST_ADDTAIL(defVal->defs, this);
+ else
+ defVal->defs = this;
+ }
+ value = defVal;
+}
+
+// TODO: make me faster by using a safe iterator
+void
+ValueDef::replace(Value *repVal, bool doSet)
+{
+ ValueRef **refs = new ValueRef * [value->refCount()];
+ int n = 0;
+
+ if (!refs && value->refCount())
+ FATAL("memory allocation failed");
+
+ for (ValueRef::Iterator iter = value->uses->iterator(); !iter.end();
+ iter.next()) {
+ assert(n < value->refCount());
+ refs[n++] = iter.get();
+ }
+ while (n)
+ refs[--n]->set(repVal);
+
+ if (doSet)
+ this->set(repVal);
+
+ if (refs)
+ delete[] refs;
+}
+
+void
+ValueDef::mergeDefs(ValueDef *join)
+{
+ DLLIST_MERGE(this, join, ValueDef *);
+}
+
+Value::Value()
+{
+ refCnt = 0;
+ uses = NULL;
+ defs = NULL;
+ join = this;
+
+ memset(&reg, 0, sizeof(reg));
+ reg.size = 4;
+}
+
+bool
+Value::coalesce(Value *jval, bool force)
+{
+ Value *repr = this->join; // new representative
+ Value *jrep = jval->join;
+
+ if (reg.file != jval->reg.file || reg.size != jval->reg.size) {
+ if (!force)
+ return false;
+ ERROR("forced coalescing of values of different sizes/files");
+ }
+
+ if (!force && (repr->reg.data.id != jrep->reg.data.id)) {
+ if (repr->reg.data.id >= 0 &&
+ jrep->reg.data.id >= 0)
+ return false;
+ if (jrep->reg.data.id >= 0) {
+ repr = jval->join;
+ jrep = this->join;
+ jval = this;
+ }
+
+ // need to check all fixed register values of the program for overlap
+ Function *func = defs->getInsn()->bb->getFunction();
+
+ // TODO: put values in by register-id bins per function
+ ArrayList::Iterator iter = func->allLValues.iterator();
+ for (; !iter.end(); iter.next()) {
+ Value *fixed = reinterpret_cast<Value *>(iter.get());
+ assert(fixed);
+ if (fixed->reg.data.id == repr->reg.data.id)
+ if (fixed->livei.overlaps(jrep->livei))
+ return false;
+ }
+ }
+ if (repr->livei.overlaps(jrep->livei)) {
+ if (!force)
+ return false;
+ // do we really want this ? if at all, only for constraint ops
+ INFO("NOTE: forced coalescing with live range overlap\n");
+ }
+
+ ValueDef::Iterator iter = jrep->defs->iterator();
+ for (; !iter.end(); iter.next())
+ iter.get()->get()->join = repr;
+
+ repr->defs->mergeDefs(jrep->defs);
+ repr->livei.unify(jrep->livei);
+
+ assert(repr->join == repr && jval->join == repr);
+ return true;
+}
+
+LValue::LValue(Function *fn, DataFile file)
+{
+ reg.file = file;
+ reg.size = (file != FILE_PREDICATE) ? 4 : 1;
+ reg.data.id = -1;
+
+ affinity = -1;
+
+ fn->add(this, this->id);
+}
+
+LValue::LValue(Function *fn, LValue *lval)
+{
+ assert(lval);
+
+ reg.file = lval->reg.file;
+ reg.size = lval->reg.size;
+ reg.data.id = -1;
+
+ affinity = -1;
+
+ fn->add(this, this->id);
+}
+
+Value *LValue::clone(Function *func) const
+{
+ LValue *that = new_LValue(func, reg.file);
+
+ that->reg.size = this->reg.size;
+ that->reg.type = this->reg.type;
+ that->reg.data = this->reg.data;
+
+ return that;
+}
+
+Symbol::Symbol(Program *prog, DataFile f, ubyte fidx)
+{
+ baseSym = NULL;
+
+ reg.file = f;
+ reg.fileIndex = fidx;
+ reg.data.offset = 0;
+
+ prog->add(this, this->id);
+}
+
+Value *
+Symbol::clone(Function *func) const
+{
+ Program *prog = func->getProgram();
+
+ Symbol *that = new_Symbol(prog, reg.file, reg.fileIndex);
+
+ that->reg.size = this->reg.size;
+ that->reg.type = this->reg.type;
+ that->reg.data = this->reg.data;
+
+ that->baseSym = this->baseSym;
+
+ return that;
+}
+
+ImmediateValue::ImmediateValue(Program *prog, uint32_t uval)
+{
+ memset(&reg, 0, sizeof(reg));
+
+ reg.file = FILE_IMMEDIATE;
+ reg.size = 4;
+ reg.type = TYPE_U32;
+
+ reg.data.u32 = uval;
+
+ prog->add(this, this->id);
+}
+
+ImmediateValue::ImmediateValue(Program *prog, float fval)
+{
+ memset(&reg, 0, sizeof(reg));
+
+ reg.file = FILE_IMMEDIATE;
+ reg.size = 4;
+ reg.type = TYPE_F32;
+
+ reg.data.f32 = fval;
+
+ prog->add(this, this->id);
+}
+
+ImmediateValue::ImmediateValue(Program *prog, double dval)
+{
+ memset(&reg, 0, sizeof(reg));
+
+ reg.file = FILE_IMMEDIATE;
+ reg.size = 8;
+ reg.type = TYPE_F64;
+
+ reg.data.f64 = dval;
+
+ prog->add(this, this->id);
+}
+
+ImmediateValue::ImmediateValue(const ImmediateValue *proto, DataType ty)
+{
+ reg = proto->reg;
+
+ reg.type = ty;
+ reg.size = typeSizeof(ty);
+}
+
+bool
+ImmediateValue::isInteger(const int i) const
+{
+ switch (reg.type) {
+ case TYPE_S8:
+ return reg.data.s8 == i;
+ case TYPE_U8:
+ return reg.data.u8 == i;
+ case TYPE_S16:
+ return reg.data.s16 == i;
+ case TYPE_U16:
+ return reg.data.u16 == i;
+ case TYPE_S32:
+ case TYPE_U32:
+ return reg.data.s32 == i; // as if ...
+ case TYPE_F32:
+ return reg.data.f32 == static_cast<float>(i);
+ case TYPE_F64:
+ return reg.data.f64 == static_cast<double>(i);
+ default:
+ return false;
+ }
+}
+
+bool
+ImmediateValue::isNegative() const
+{
+ switch (reg.type) {
+ case TYPE_S8: return reg.data.s8 < 0;
+ case TYPE_S16: return reg.data.s16 < 0;
+ case TYPE_S32:
+ case TYPE_U32: return reg.data.s32 < 0;
+ case TYPE_F32: return reg.data.u32 & (1 << 31);
+ case TYPE_F64: return reg.data.u64 & (1ULL << 63);
+ default:
+ return false;
+ }
+}
+
+bool
+ImmediateValue::isPow2() const
+{
+ switch (reg.type) {
+ case TYPE_U8:
+ case TYPE_U16:
+ case TYPE_U32: return util_is_power_of_two(reg.data.u32);
+ default:
+ return false;
+ }
+}
+
+void
+ImmediateValue::applyLog2()
+{
+ switch (reg.type) {
+ case TYPE_S8:
+ case TYPE_S16:
+ case TYPE_S32:
+ assert(!this->isNegative());
+ // fall through
+ case TYPE_U8:
+ case TYPE_U16:
+ case TYPE_U32:
+ reg.data.u32 = util_logbase2(reg.data.u32);
+ break;
+ case TYPE_F32:
+ reg.data.f32 = log2f(reg.data.f32);
+ break;
+ case TYPE_F64:
+ reg.data.f64 = log2(reg.data.f64);
+ break;
+ default:
+ assert(0);
+ break;
+ }
+}
+
+bool
+ImmediateValue::compare(CondCode cc, float fval) const
+{
+ if (reg.type != TYPE_F32)
+ ERROR("immediate value is not of type f32");
+
+ switch (static_cast<CondCode>(cc & 7)) {
+ case CC_TR: return true;
+ case CC_FL: return false;
+ case CC_LT: return reg.data.f32 < fval;
+ case CC_LE: return reg.data.f32 <= fval;
+ case CC_GT: return reg.data.f32 > fval;
+ case CC_GE: return reg.data.f32 >= fval;
+ case CC_EQ: return reg.data.f32 == fval;
+ case CC_NE: return reg.data.f32 != fval;
+ default:
+ assert(0);
+ return false;
+ }
+}
+
+bool
+Value::interfers(const Value *that) const
+{
+ uint32_t idA, idB;
+
+ if (that->reg.file != reg.file || that->reg.fileIndex != reg.fileIndex)
+ return false;
+ if (this->asImm())
+ return false;
+
+ if (this->asSym()) {
+ idA = this->join->reg.data.offset;
+ idB = that->join->reg.data.offset;
+ } else {
+ idA = this->join->reg.data.id * this->reg.size;
+ idB = that->join->reg.data.id * that->reg.size;
+ }
+
+ if (idA < idB)
+ return (idA + this->reg.size > idB);
+ else
+ if (idA > idB)
+ return (idB + that->reg.size > idA);
+ else
+ return (idA == idB);
+}
+
+bool
+Value::equals(const Value *that, bool strict) const
+{
+ that = that->join;
+
+ if (strict)
+ return this == that;
+
+ if (that->reg.file != reg.file || that->reg.fileIndex != reg.fileIndex)
+ return false;
+ if (that->reg.size != this->reg.size)
+ return false;
+
+ if (that->reg.data.id != this->reg.data.id)
+ return false;
+
+ return true;
+}
+
+bool
+ImmediateValue::equals(const Value *that, bool strict) const
+{
+ const ImmediateValue *imm = that->asImm();
+ if (!imm)
+ return false;
+ return reg.data.u64 == imm->reg.data.u64;
+}
+
+bool
+Symbol::equals(const Value *that, bool strict) const
+{
+ if (this->reg.file != that->reg.file)
+ return false;
+ assert(that->asSym());
+
+ if (this->baseSym != that->asSym()->baseSym)
+ return false;
+
+ return this->reg.data.offset == that->reg.data.offset;
+}
+
+void Instruction::init()
+{
+ next = prev = 0;
+
+ cc = CC_ALWAYS;
+ rnd = ROUND_N;
+ cache = CACHE_CA;
+ subOp = 0;
+
+ saturate = 0;
+ join = terminator = 0;
+ ftz = dnz = 0;
+ atomic = 0;
+ perPatch = 0;
+ fixed = 0;
+ encSize = 0;
+ ipa = 0;
+
+ lanes = 0xf;
+
+ postFactor = 0;
+
+ for (int p = 0; p < NV50_IR_MAX_DEFS; ++p)
+ def[p].setInsn(this);
+ for (int p = 0; p < NV50_IR_MAX_SRCS; ++p)
+ src[p].setInsn(this);
+
+ predSrc = -1;
+ flagsDef = -1;
+ flagsSrc = -1;
+}
+
+Instruction::Instruction()
+{
+ init();
+
+ op = OP_NOP;
+ dType = sType = TYPE_F32;
+
+ id = -1;
+ bb = 0;
+}
+
+Instruction::Instruction(Function *fn, operation opr, DataType ty)
+{
+ init();
+
+ op = opr;
+ dType = sType = ty;
+
+ fn->add(this, id);
+}
+
+Instruction::~Instruction()
+{
+ if (bb) {
+ Function *fn = bb->getFunction();
+ bb->remove(this);
+ fn->allInsns.remove(id);
+ }
+
+ for (int s = 0; srcExists(s); ++s)
+ setSrc(s, NULL);
+ // must unlink defs too since the list pointers will get deallocated
+ for (int d = 0; defExists(d); ++d)
+ setDef(d, NULL);
+}
+
+void
+Instruction::setSrc(int s, ValueRef& ref)
+{
+ setSrc(s, ref.get());
+ src[s].mod = ref.mod;
+}
+
+void
+Instruction::swapSources(int a, int b)
+{
+ Value *value = src[a].get();
+ Modifier m = src[a].mod;
+
+ setSrc(a, src[b]);
+
+ src[b].set(value);
+ src[b].mod = m;
+}
+
+void
+Instruction::takeExtraSources(int s, Value *values[3])
+{
+ values[0] = getIndirect(s, 0);
+ if (values[0])
+ setIndirect(s, 0, NULL);
+
+ values[1] = getIndirect(s, 1);
+ if (values[1])
+ setIndirect(s, 1, NULL);
+
+ values[2] = getPredicate();
+ if (values[2])
+ setPredicate(cc, NULL);
+}
+
+void
+Instruction::putExtraSources(int s, Value *values[3])
+{
+ if (values[0])
+ setIndirect(s, 0, values[0]);
+ if (values[1])
+ setIndirect(s, 1, values[1]);
+ if (values[2])
+ setPredicate(cc, values[2]);
+}
+
+Instruction *
+Instruction::clone(bool deep) const
+{
+ Instruction *insn = new_Instruction(bb->getFunction(), op, dType);
+ assert(!asCmp() && !asFlow());
+ cloneBase(insn, deep);
+ return insn;
+}
+
+void
+Instruction::cloneBase(Instruction *insn, bool deep) const
+{
+ insn->sType = this->sType;
+
+ insn->cc = this->cc;
+ insn->rnd = this->rnd;
+ insn->cache = this->cache;
+ insn->subOp = this->subOp;
+
+ insn->saturate = this->saturate;
+ insn->atomic = this->atomic;
+ insn->ftz = this->ftz;
+ insn->dnz = this->dnz;
+ insn->ipa = this->ipa;
+ insn->lanes = this->lanes;
+ insn->perPatch = this->perPatch;
+
+ insn->postFactor = this->postFactor;
+
+ if (deep) {
+ if (!bb)
+ return;
+ Function *fn = bb->getFunction();
+ for (int d = 0; this->defExists(d); ++d)
+ insn->setDef(d, this->getDef(d)->clone(fn));
+ } else {
+ for (int d = 0; this->defExists(d); ++d)
+ insn->setDef(d, this->getDef(d));
+ }
+
+ for (int s = 0; this->srcExists(s); ++s)
+ insn->src[s].set(this->src[s]);
+
+ insn->predSrc = this->predSrc;
+ insn->flagsDef = this->flagsDef;
+ insn->flagsSrc = this->flagsSrc;
+}
+
+unsigned int
+Instruction::defCount(unsigned int mask) const
+{
+ unsigned int i, n;
+
+ for (n = 0, i = 0; this->defExists(i); ++i, mask >>= 1)
+ n += mask & 1;
+ return n;
+}
+
+unsigned int
+Instruction::srcCount(unsigned int mask) const
+{
+ unsigned int i, n;
+
+ for (n = 0, i = 0; this->srcExists(i); ++i, mask >>= 1)
+ n += mask & 1;
+ return n;
+}
+
+bool
+Instruction::setIndirect(int s, int dim, Value *value)
+{
+ int p = src[s].indirect[dim];
+
+ assert(this->srcExists(s));
+ if (p < 0) {
+ if (!value)
+ return true;
+ for (p = s + 1; this->srcExists(p); ++p);
+ }
+ assert(p < NV50_IR_MAX_SRCS);
+
+ src[p] = value;
+ src[p].usedAsPtr = (value != 0);
+ src[s].indirect[dim] = value ? p : -1;
+ return true;
+}
+
+bool
+Instruction::setPredicate(CondCode ccode, Value *value)
+{
+ cc = ccode;
+
+ if (!value) {
+ if (predSrc >= 0) {
+ src[predSrc] = 0;
+ predSrc = -1;
+ }
+ return true;
+ }
+
+ if (predSrc < 0) {
+ int s;
+ for (s = 0; this->srcExists(s); ++s)
+ assert(s < NV50_IR_MAX_SRCS);
+ predSrc = s;
+ }
+ src[predSrc] = value;
+ return true;
+}
+
+bool
+Instruction::writesPredicate() const
+{
+ for (int d = 0; d < 2 && def[d].exists(); ++d)
+ if (def[d].exists() &&
+ (getDef(d)->inFile(FILE_PREDICATE) || getDef(d)->inFile(FILE_FLAGS)))
+ return true;
+ return false;
+}
+
+static bool
+insnCheckCommutation(const Instruction *a, const Instruction *b)
+{
+ for (int d = 0; a->defExists(d); ++d)
+ for (int s = 0; b->srcExists(s); ++s)
+ if (a->getDef(d)->interfers(b->getSrc(s)))
+ return false;
+ return true;
+}
+
+bool
+Instruction::isCommutationLegal(const Instruction *i) const
+{
+ bool ret = true;
+ ret = ret && insnCheckCommutation(this, i);
+ ret = ret && insnCheckCommutation(i, this);
+ return ret;
+}
+
+TexInstruction::TexInstruction(Function *fn, operation op)
+ : Instruction(fn, op, TYPE_F32)
+{
+ memset(&tex, 0, sizeof(tex));
+
+ tex.rIndirectSrc = -1;
+ tex.sIndirectSrc = -1;
+}
+
+TexInstruction::~TexInstruction()
+{
+ for (int c = 0; c < 3; ++c) {
+ dPdx[c].set(NULL);
+ dPdy[c].set(NULL);
+ }
+}
+
+Instruction *
+TexInstruction::clone(bool deep) const
+{
+ TexInstruction *tex = new_TexInstruction(bb->getFunction(), op);
+ cloneBase(tex, deep);
+
+ tex->tex = this->tex;
+
+ if (op == OP_TXD) {
+ for (unsigned int c = 0; c < tex->tex.target.getDim(); ++c) {
+ tex->dPdx[c].set(dPdx[c]);
+ tex->dPdy[c].set(dPdy[c]);
+ }
+ }
+
+ return tex;
+}
+
+const struct TexInstruction::Target::Desc TexInstruction::Target::descTable[] =
+{
+ { "1D", 1, 1, false, false, false },
+ { "2D", 2, 2, false, false, false },
+ { "2D_MS", 2, 2, false, false, false },
+ { "3D", 3, 3, false, false, false },
+ { "CUBE", 2, 3, false, true, false },
+ { "1D_SHADOW", 1, 1, false, false, true },
+ { "2D_SHADOW", 2, 2, false, false, true },
+ { "CUBE_SHADOW", 2, 3, false, true, true },
+ { "1D_ARRAY", 1, 2, true, false, false },
+ { "2D_ARRAY", 2, 3, true, false, false },
+ { "2D_MS_ARRAY", 2, 3, true, false, false },
+ { "CUBE_ARRAY", 2, 3, true, true, false },
+ { "1D_ARRAY_SHADOW", 1, 2, true, false, true },
+ { "2D_ARRAY_SHADOW", 2, 3, true, false, true },
+ { "RECT", 2, 2, false, false, false },
+ { "RECT_SHADOW", 2, 2, false, false, true },
+ { "CUBE_ARRAY_SHADOW", 2, 4, true, true, true },
+ { "BUFFER", 1, 1, false, false, false },
+};
+
+CmpInstruction::CmpInstruction(Function *fn, operation op)
+ : Instruction(fn, op, TYPE_F32)
+{
+ setCond = CC_ALWAYS;
+}
+
+Instruction *
+CmpInstruction::clone(bool deep) const
+{
+ CmpInstruction *cmp = new_CmpInstruction(bb->getFunction(), op);
+ cloneBase(cmp, deep);
+ cmp->setCond = setCond;
+ cmp->dType = dType;
+ return cmp;
+}
+
+FlowInstruction::FlowInstruction(Function *fn, operation op,
+ BasicBlock *targ)
+ : Instruction(fn, op, TYPE_NONE)
+{
+ target.bb = targ;
+
+ if (op == OP_BRA ||
+ op == OP_CONT || op == OP_BREAK ||
+ op == OP_RET || op == OP_EXIT)
+ terminator = 1;
+ else
+ if (op == OP_JOIN)
+ terminator = targ ? 1 : 0;
+
+ allWarp = absolute = limit = 0;
+}
+
+Program::Program(Type type, Target *arch)
+ : progType(type),
+ target(arch),
+ mem_Instruction(sizeof(Instruction), 6),
+ mem_CmpInstruction(sizeof(CmpInstruction), 4),
+ mem_TexInstruction(sizeof(TexInstruction), 4),
+ mem_FlowInstruction(sizeof(FlowInstruction), 4),
+ mem_LValue(sizeof(LValue), 8),
+ mem_Symbol(sizeof(Symbol), 7),
+ mem_ImmediateValue(sizeof(ImmediateValue), 7)
+{
+ code = NULL;
+ binSize = 0;
+
+ maxGPR = -1;
+
+ main = new Function(this, "MAIN");
+
+ dbgFlags = 0;
+}
+
+Program::~Program()
+{
+ if (main)
+ delete main;
+}
+
+void Program::releaseInstruction(Instruction *insn)
+{
+ // TODO: make this not suck so much
+
+ insn->~Instruction();
+
+ if (insn->asCmp())
+ mem_CmpInstruction.release(insn);
+ else
+ if (insn->asTex())
+ mem_TexInstruction.release(insn);
+ else
+ if (insn->asFlow())
+ mem_FlowInstruction.release(insn);
+ else
+ mem_Instruction.release(insn);
+}
+
+void Program::releaseValue(Value *value)
+{
+ if (value->asLValue())
+ mem_LValue.release(value);
+ else
+ if (value->asImm())
+ mem_ImmediateValue.release(value);
+ else
+ if (value->asSym())
+ mem_Symbol.release(value);
+}
+
+
+} // namespace nv50_ir
+
+extern "C" {
+
+static void
+nv50_ir_init_prog_info(struct nv50_ir_prog_info *info)
+{
+ info->io.clipDistance = 0xff;
+ info->io.pointSize = 0xff;
+ info->io.edgeFlagIn = 0xff;
+ info->io.edgeFlagOut = 0xff;
+ info->io.fragDepth = 0xff;
+ info->io.sampleMask = 0xff;
+ info->io.backFaceColor[0] = info->io.backFaceColor[1] = 0xff;
+}
+
+int
+nv50_ir_generate_code(struct nv50_ir_prog_info *info)
+{
+ int ret = 0;
+
+ nv50_ir::Program::Type type;
+
+ nv50_ir_init_prog_info(info);
+
+#define PROG_TYPE_CASE(a, b) \
+ case PIPE_SHADER_##a: type = nv50_ir::Program::TYPE_##b; break
+
+ switch (info->type) {
+ PROG_TYPE_CASE(VERTEX, VERTEX);
+// PROG_TYPE_CASE(HULL, TESSELLATION_CONTROL);
+// PROG_TYPE_CASE(DOMAIN, TESSELLATION_EVAL);
+ PROG_TYPE_CASE(GEOMETRY, GEOMETRY);
+ PROG_TYPE_CASE(FRAGMENT, FRAGMENT);
+ default:
+ type = nv50_ir::Program::TYPE_COMPUTE;
+ break;
+ }
+ INFO_DBG(info->dbgFlags, VERBOSE, "translating program of type %u\n", type);
+
+ nv50_ir::Target *targ = nv50_ir::Target::create(info->target);
+ if (!targ)
+ return -1;
+
+ nv50_ir::Program *prog = new nv50_ir::Program(type, targ);
+ if (!prog)
+ return -1;
+ prog->dbgFlags = info->dbgFlags;
+
+ switch (info->bin.sourceRep) {
+#if 0
+ case PIPE_IR_LLVM:
+ case PIPE_IR_GLSL:
+ return -1;
+ case PIPE_IR_SM4:
+ ret = prog->makeFromSM4(info) ? 0 : -2;
+ break;
+ case PIPE_IR_TGSI:
+#endif
+ default:
+ ret = prog->makeFromTGSI(info) ? 0 : -2;
+ break;
+ }
+ if (ret < 0)
+ goto out;
+ if (prog->dbgFlags & NV50_IR_DEBUG_VERBOSE)
+ prog->print();
+
+ prog->getTarget()->runLegalizePass(prog, nv50_ir::CG_STAGE_PRE_SSA);
+
+ prog->convertToSSA();
+
+ if (prog->dbgFlags & NV50_IR_DEBUG_VERBOSE)
+ prog->print();
+
+ prog->optimizeSSA(info->optLevel);
+ prog->getTarget()->runLegalizePass(prog, nv50_ir::CG_STAGE_SSA);
+
+ if (prog->dbgFlags & NV50_IR_DEBUG_BASIC)
+ prog->print();
+
+ if (!prog->registerAllocation()) {
+ ret = -4;
+ goto out;
+ }
+ prog->getTarget()->runLegalizePass(prog, nv50_ir::CG_STAGE_POST_RA);
+
+ prog->optimizePostRA(info->optLevel);
+
+ if (!prog->emitBinary(info)) {
+ ret = -5;
+ goto out;
+ }
+
+out:
+ INFO_DBG(prog->dbgFlags, VERBOSE, "nv50_ir_generate_code: ret = %i\n", ret);
+
+ info->bin.maxGPR = prog->maxGPR;
+ info->bin.code = prog->code;
+ info->bin.codeSize = prog->binSize;
+
+ delete prog;
+ nv50_ir::Target::destroy(targ);
+
+ return ret;
+}
+
+} // extern "C"
diff --git a/src/gallium/drivers/nv50/codegen/nv50_ir.h b/src/gallium/drivers/nv50/codegen/nv50_ir.h
new file mode 100644
index 00000000000..6eef1abb69d
--- /dev/null
+++ b/src/gallium/drivers/nv50/codegen/nv50_ir.h
@@ -0,0 +1,1049 @@
+
+#ifndef __NV50_IR_H__
+#define __NV50_IR_H__
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+#include "nv50_ir_util.h"
+#include "nv50_ir_graph.h"
+
+#include "nv50_ir_driver.h"
+
+namespace nv50_ir {
+
+enum operation
+{
+ OP_NOP = 0,
+ OP_PHI,
+ OP_UNION, // unify a new definition and several source values
+ OP_SPLIT, // $r0d -> { $r0, $r1 } ($r0d and $r0/$r1 will be coalesced)
+ OP_MERGE, // opposite of split, e.g. combine 2 32 bit into a 64 bit value
+ OP_CONSTRAINT, // copy values into consecutive registers
+ OP_MOV,
+ OP_LOAD,
+ OP_STORE,
+ OP_ADD,
+ OP_SUB,
+ OP_MUL,
+ OP_DIV,
+ OP_MOD,
+ OP_MAD,
+ OP_FMA,
+ OP_SAD, // abs(src0 - src1) + src2
+ OP_ABS,
+ OP_NEG,
+ OP_NOT,
+ OP_AND,
+ OP_OR,
+ OP_XOR,
+ OP_SHL,
+ OP_SHR,
+ OP_MAX,
+ OP_MIN,
+ OP_SAT, // CLAMP(f32, 0.0, 1.0)
+ OP_CEIL,
+ OP_FLOOR,
+ OP_TRUNC,
+ OP_CVT,
+ OP_SET_AND, // dst = (src0 CMP src1) & src2
+ OP_SET_OR,
+ OP_SET_XOR,
+ OP_SET,
+ OP_SELP, // dst = src2 ? src0 : src1
+ OP_SLCT, // dst = (src2 CMP 0) ? src0 : src1
+ OP_RCP,
+ OP_RSQ,
+ OP_LG2,
+ OP_SIN,
+ OP_COS,
+ OP_EX2,
+ OP_EXP, // exponential (base M_E)
+ OP_LOG, // natural logarithm
+ OP_PRESIN,
+ OP_PREEX2,
+ OP_SQRT,
+ OP_POW,
+ OP_BRA,
+ OP_CALL,
+ OP_RET,
+ OP_CONT,
+ OP_BREAK,
+ OP_PRERET,
+ OP_PRECONT,
+ OP_PREBREAK,
+ OP_BRKPT, // breakpoint (not related to loops)
+ OP_JOINAT, // push control flow convergence point
+ OP_JOIN, // converge
+ OP_DISCARD,
+ OP_EXIT,
+ OP_MEMBAR,
+ OP_VFETCH, // indirection 0 in attribute space, indirection 1 is vertex base
+ OP_PFETCH, // fetch base address of vertex src0 (immediate) [+ src1]
+ OP_EXPORT,
+ OP_LINTERP,
+ OP_PINTERP,
+ OP_EMIT, // emit vertex
+ OP_RESTART, // restart primitive
+ OP_TEX,
+ OP_TXB, // texture bias
+ OP_TXL, // texure lod
+ OP_TXF, // texel fetch
+ OP_TXQ, // texture size query
+ OP_TXD, // texture derivatives
+ OP_TXG, // texture gather
+ OP_TEXCSAA,
+ OP_SULD, // surface load
+ OP_SUST, // surface store
+ OP_DFDX,
+ OP_DFDY,
+ OP_RDSV, // read system value
+ OP_WRSV, // write system value
+ OP_PIXLD,
+ OP_QUADOP,
+ OP_QUADON,
+ OP_QUADPOP,
+ OP_POPCNT, // bitcount(src0 & src1)
+ OP_INSBF, // insert first src1[8:15] bits of src0 into src2 at src1[0:7]
+ OP_EXTBF,
+ OP_LAST
+};
+
+#define NV50_IR_SUBOP_MUL_HIGH 1
+#define NV50_IR_SUBOP_EMIT_RESTART 1
+#define NV50_IR_SUBOP_LDC_IL 1
+#define NV50_IR_SUBOP_LDC_IS 2
+#define NV50_IR_SUBOP_LDC_ISL 3
+
+enum DataType
+{
+ TYPE_NONE,
+ TYPE_U8,
+ TYPE_S8,
+ TYPE_U16,
+ TYPE_S16,
+ TYPE_U32,
+ TYPE_S32,
+ TYPE_U64, // 64 bit operations are only lowered after register allocation
+ TYPE_S64,
+ TYPE_F16,
+ TYPE_F32,
+ TYPE_F64,
+ TYPE_B96,
+ TYPE_B128
+};
+
+enum CondCode
+{
+ CC_FL = 0,
+ CC_NEVER = CC_FL, // when used with FILE_FLAGS
+ CC_LT = 1,
+ CC_EQ = 2,
+ CC_NOT_P = CC_EQ, // when used with FILE_PREDICATE
+ CC_LE = 3,
+ CC_GT = 4,
+ CC_NE = 5,
+ CC_P = CC_NE,
+ CC_GE = 6,
+ CC_TR = 7,
+ CC_ALWAYS = CC_TR,
+ CC_U = 8,
+ CC_LTU = 9,
+ CC_EQU = 10,
+ CC_LEU = 11,
+ CC_GTU = 12,
+ CC_NEU = 13,
+ CC_GEU = 14,
+ CC_NO = 0x10,
+ CC_NC = 0x11,
+ CC_NS = 0x12,
+ CC_NA = 0x13,
+ CC_A = 0x14,
+ CC_S = 0x15,
+ CC_C = 0x16,
+ CC_O = 0x17
+};
+
+enum RoundMode
+{
+ ROUND_N, // nearest
+ ROUND_M, // towards -inf
+ ROUND_Z, // towards 0
+ ROUND_P, // towards +inf
+ ROUND_NI, // nearest integer
+ ROUND_MI, // to integer towards -inf
+ ROUND_ZI, // to integer towards 0
+ ROUND_PI, // to integer towards +inf
+};
+
+enum CacheMode
+{
+ CACHE_CA, // cache at all levels
+ CACHE_WB = CACHE_CA, // cache write back
+ CACHE_CG, // cache at global level
+ CACHE_CS, // cache streaming
+ CACHE_CV, // cache as volatile
+ CACHE_WT = CACHE_CV // cache write-through
+};
+
+enum DataFile
+{
+ FILE_NULL = 0,
+ FILE_GPR,
+ FILE_PREDICATE, // boolean predicate
+ FILE_FLAGS, // zero/sign/carry/overflow bits
+ FILE_ADDRESS,
+ FILE_IMMEDIATE,
+ FILE_MEMORY_CONST,
+ FILE_SHADER_INPUT,
+ FILE_SHADER_OUTPUT,
+ FILE_MEMORY_GLOBAL,
+ FILE_MEMORY_SHARED,
+ FILE_MEMORY_LOCAL,
+ FILE_SYSTEM_VALUE,
+ DATA_FILE_COUNT
+};
+
+enum TexTarget
+{
+ TEX_TARGET_1D,
+ TEX_TARGET_2D,
+ TEX_TARGET_2D_MS,
+ TEX_TARGET_3D,
+ TEX_TARGET_CUBE,
+ TEX_TARGET_1D_SHADOW,
+ TEX_TARGET_2D_SHADOW,
+ TEX_TARGET_CUBE_SHADOW,
+ TEX_TARGET_1D_ARRAY,
+ TEX_TARGET_2D_ARRAY,
+ TEX_TARGET_2D_MS_ARRAY,
+ TEX_TARGET_CUBE_ARRAY,
+ TEX_TARGET_1D_ARRAY_SHADOW,
+ TEX_TARGET_2D_ARRAY_SHADOW,
+ TEX_TARGET_RECT,
+ TEX_TARGET_RECT_SHADOW,
+ TEX_TARGET_CUBE_ARRAY_SHADOW,
+ TEX_TARGET_BUFFER,
+ TEX_TARGET_COUNT
+};
+
+enum SVSemantic
+{
+ SV_POSITION, // WPOS
+ SV_VERTEX_ID,
+ SV_INSTANCE_ID,
+ SV_INVOCATION_ID,
+ SV_PRIMITIVE_ID,
+ SV_VERTEX_COUNT, // gl_PatchVerticesIn
+ SV_LAYER,
+ SV_VIEWPORT_INDEX,
+ SV_YDIR,
+ SV_FACE,
+ SV_POINT_SIZE,
+ SV_POINT_COORD,
+ SV_CLIP_DISTANCE,
+ SV_SAMPLE_INDEX,
+ SV_TESS_FACTOR,
+ SV_TESS_COORD,
+ SV_TID,
+ SV_CTAID,
+ SV_NTID,
+ SV_GRIDID,
+ SV_NCTAID,
+ SV_LANEID,
+ SV_PHYSID,
+ SV_NPHYSID,
+ SV_CLOCK,
+ SV_LBASE,
+ SV_SBASE,
+ SV_UNDEFINED,
+ SV_LAST
+};
+
+class Program;
+class Function;
+class BasicBlock;
+
+class Target;
+
+class Instruction;
+class CmpInstruction;
+class TexInstruction;
+class FlowInstruction;
+
+class Value;
+class LValue;
+class Symbol;
+class ImmediateValue;
+
+struct Storage
+{
+ DataFile file;
+ int8_t fileIndex; // signed, may be indirect for CONST[]
+ uint8_t size; // this should match the Instruction type's size
+ DataType type; // mainly for pretty printing
+ union {
+ uint64_t u64; // immediate values
+ uint32_t u32;
+ uint16_t u16;
+ uint8_t u8;
+ int64_t s64;
+ int32_t s32;
+ int16_t s16;
+ int8_t s8;
+ float f32;
+ double f64;
+ int32_t offset; // offset from 0 (base of address space)
+ int32_t id; // register id (< 0 if virtual/unassigned)
+ struct {
+ SVSemantic sv;
+ int index;
+ } sv;
+ } data;
+};
+
+// precedence: NOT after SAT after NEG after ABS
+#define NV50_IR_MOD_ABS (1 << 0)
+#define NV50_IR_MOD_NEG (1 << 1)
+#define NV50_IR_MOD_SAT (1 << 2)
+#define NV50_IR_MOD_NOT (1 << 3)
+#define NV50_IR_MOD_NEG_ABS (NV50_IR_MOD_NEG | NV50_IR_MOD_ABS)
+
+#define NV50_IR_INTERP_MODE_MASK 0x3
+#define NV50_IR_INTERP_LINEAR (0 << 0)
+#define NV50_IR_INTERP_PERSPECTIVE (1 << 0)
+#define NV50_IR_INTERP_FLAT (2 << 0)
+#define NV50_IR_INTERP_SC (3 << 0) // what exactly is that ?
+#define NV50_IR_INTERP_SAMPLE_MASK 0xc
+#define NV50_IR_INTERP_DEFAULT (0 << 2)
+#define NV50_IR_INTERP_CENTROID (1 << 2)
+#define NV50_IR_INTERP_OFFSET (2 << 2)
+#define NV50_IR_INTERP_SAMPLEID (3 << 2)
+
+// do we really want this to be a class ?
+class Modifier
+{
+public:
+ Modifier() : bits(0) { }
+ Modifier(unsigned int m) : bits(m) { }
+ Modifier(operation op);
+
+ // @return new Modifier applying a after b (asserts if unrepresentable)
+ Modifier operator*(const Modifier) const;
+ Modifier operator==(const Modifier m) const { return m.bits == bits; }
+ Modifier operator!=(const Modifier m) const { return m.bits != bits; }
+
+ inline Modifier operator&(const Modifier m) const { return bits & m.bits; }
+ inline Modifier operator|(const Modifier m) const { return bits | m.bits; }
+ inline Modifier operator^(const Modifier m) const { return bits ^ m.bits; }
+
+ operation getOp() const;
+
+ inline int neg() const { return (bits & NV50_IR_MOD_NEG) ? 1 : 0; }
+ inline int abs() const { return (bits & NV50_IR_MOD_ABS) ? 1 : 0; }
+
+ inline operator bool() { return bits ? true : false; }
+
+ void applyTo(ImmediateValue &imm) const;
+
+ int print(char *buf, size_t size) const;
+
+private:
+ uint8_t bits;
+};
+
+class ValueRef
+{
+public:
+ ValueRef();
+ ~ValueRef();
+
+ inline ValueRef& operator=(Value *val) { this->set(val); return *this; }
+
+ inline bool exists() const { return value != NULL; }
+
+ void set(Value *);
+ void set(const ValueRef&);
+ inline Value *get() const { return value; }
+ inline Value *rep() const;
+
+ inline Instruction *getInsn() const { return insn; }
+ inline void setInsn(Instruction *inst) { insn = inst; }
+
+ inline bool isIndirect(int dim) const { return indirect[dim] >= 0; }
+ inline const ValueRef *getIndirect(int dim) const;
+
+ inline DataFile getFile() const;
+ inline unsigned getSize() const;
+
+ // SSA: return eventual (traverse MOVs) literal value, if it exists
+ ImmediateValue *getImmediate() const;
+
+ class Iterator
+ {
+ public:
+ Iterator(ValueRef *ref) : pos(ref), ini(ref) { }
+
+ inline ValueRef *get() const { return pos; }
+ inline bool end() const { return pos == NULL; }
+ inline void next() { pos = (pos->next != ini) ? pos->next : 0; }
+
+ private:
+ ValueRef *pos, *ini;
+ };
+
+ inline Iterator iterator() { return Iterator(this); }
+
+public:
+ Modifier mod;
+ int8_t indirect[2]; // >= 0 if relative to lvalue in insn->src[indirect[i]]
+ uint8_t swizzle;
+
+ bool usedAsPtr; // for printing
+
+private:
+ Value *value;
+ Instruction *insn;
+ ValueRef *next; // to link uses of the value
+ ValueRef *prev;
+};
+
+class ValueDef
+{
+public:
+ ValueDef();
+ ~ValueDef();
+
+ inline ValueDef& operator=(Value *val) { this->set(val); return *this; }
+
+ inline bool exists() const { return value != NULL; }
+
+ inline Value *get() const { return value; }
+ inline Value *rep() const;
+ void set(Value *);
+ void replace(Value *, bool doSet); // replace all uses of the old value
+
+ inline Instruction *getInsn() const { return insn; }
+ inline void setInsn(Instruction *inst) { insn = inst; }
+
+ inline DataFile getFile() const;
+ inline unsigned getSize() const;
+
+ // HACK: save the pre-SSA value in 'prev', in SSA we don't need the def list
+ // but we'll use it again for coalescing in register allocation
+ inline void setSSA(LValue *);
+ inline const LValue *preSSA() const;
+ inline void restoreDefList(); // after having been abused for SSA hack
+ void mergeDefs(ValueDef *);
+
+ class Iterator
+ {
+ public:
+ Iterator(ValueDef *def) : pos(def), ini(def) { }
+
+ inline ValueDef *get() const { return pos; }
+ inline bool end() const { return pos == NULL; }
+ inline void next() { pos = (pos->next != ini) ? pos->next : NULL; }
+
+ private:
+ ValueDef *pos, *ini;
+ };
+
+ inline Iterator iterator() { return Iterator(this); }
+
+private:
+ Value *value; // should make this LValue * ...
+ Instruction *insn;
+ ValueDef *next; // circular list of all definitions of the same value
+ ValueDef *prev;
+};
+
+class Value
+{
+public:
+ Value();
+
+ virtual Value *clone(Function *) const { return NULL; }
+
+ virtual int print(char *, size_t, DataType ty = TYPE_NONE) const = 0;
+
+ virtual bool equals(const Value *, bool strict = false) const;
+ virtual bool interfers(const Value *) const;
+
+ inline Instruction *getUniqueInsn() const;
+ inline Instruction *getInsn() const; // use when uniqueness is certain
+
+ inline int refCount() { return refCnt; }
+ inline int ref() { return ++refCnt; }
+ inline int unref() { --refCnt; assert(refCnt >= 0); return refCnt; }
+
+ inline LValue *asLValue();
+ inline Symbol *asSym();
+ inline ImmediateValue *asImm();
+ inline const Symbol *asSym() const;
+ inline const ImmediateValue *asImm() const;
+
+ bool coalesce(Value *, bool force = false);
+
+ inline bool inFile(DataFile f) { return reg.file == f; }
+
+ static inline Value *get(Iterator&);
+
+protected:
+ int refCnt;
+
+ friend class ValueDef;
+ friend class ValueRef;
+
+public:
+ int id;
+ ValueRef *uses;
+ ValueDef *defs;
+ Storage reg;
+
+ // TODO: these should be in LValue:
+ Interval livei;
+ Value *join;
+};
+
+class LValue : public Value
+{
+public:
+ LValue(Function *, DataFile file);
+ LValue(Function *, LValue *);
+
+ virtual Value *clone(Function *) const;
+
+ virtual int print(char *, size_t, DataType ty = TYPE_NONE) const;
+
+public:
+ unsigned ssa : 1;
+
+ int affinity;
+};
+
+class Symbol : public Value
+{
+public:
+ Symbol(Program *, DataFile file = FILE_MEMORY_CONST, ubyte fileIdx = 0);
+
+ virtual Value *clone(Function *) const;
+
+ virtual bool equals(const Value *that, bool strict) const;
+
+ virtual int print(char *, size_t, DataType ty = TYPE_NONE) const;
+
+ // print with indirect values
+ int print(char *, size_t, Value *, Value *, DataType ty = TYPE_NONE) const;
+
+ inline void setFile(DataFile file, ubyte fileIndex = 0)
+ {
+ reg.file = file;
+ reg.fileIndex = fileIndex;
+ }
+
+ inline void setOffset(int32_t offset);
+ inline void setAddress(Symbol *base, int32_t offset);
+ inline void setSV(SVSemantic sv, uint32_t idx = 0);
+
+ inline const Symbol *getBase() const { return baseSym; }
+
+private:
+ Symbol *baseSym; // array base for Symbols representing array elements
+};
+
+class ImmediateValue : public Value
+{
+public:
+ ImmediateValue(Program *, uint32_t);
+ ImmediateValue(Program *, float);
+ ImmediateValue(Program *, double);
+
+ // NOTE: not added to program with
+ ImmediateValue(const ImmediateValue *, DataType ty);
+
+ virtual bool equals(const Value *that, bool strict) const;
+
+ // these only work if 'type' is valid (we mostly use untyped literals):
+ bool isInteger(const int ival) const; // ival is cast to this' type
+ bool isNegative() const;
+ bool isPow2() const;
+
+ void applyLog2();
+
+ // for constant folding:
+ ImmediateValue operator+(const ImmediateValue&) const;
+ ImmediateValue operator-(const ImmediateValue&) const;
+ ImmediateValue operator*(const ImmediateValue&) const;
+ ImmediateValue operator/(const ImmediateValue&) const;
+
+ bool compare(CondCode cc, float fval) const;
+
+ virtual int print(char *, size_t, DataType ty = TYPE_NONE) const;
+};
+
+
+#define NV50_IR_MAX_DEFS 4
+#define NV50_IR_MAX_SRCS 8
+
+class Instruction
+{
+public:
+ Instruction();
+ Instruction(Function *, operation, DataType);
+ virtual ~Instruction();
+
+ virtual Instruction *clone(bool deep) const;
+
+ inline void setDef(int i, Value *val) { def[i].set(val); }
+ inline void setSrc(int s, Value *val) { src[s].set(val); }
+ void setSrc(int s, ValueRef&);
+ void swapSources(int a, int b);
+ bool setIndirect(int s, int dim, Value *);
+
+ inline Value *getDef(int d) const { return def[d].get(); }
+ inline Value *getSrc(int s) const { return src[s].get(); }
+ inline Value *getIndirect(int s, int dim) const;
+
+ inline bool defExists(int d) const { return d < 4 && def[d].exists(); }
+ inline bool srcExists(int s) const { return s < 8 && src[s].exists(); }
+
+ inline bool constrainedDefs() const { return def[1].exists(); }
+
+ bool setPredicate(CondCode ccode, Value *);
+ inline Value *getPredicate() const;
+ bool writesPredicate() const;
+
+ unsigned int defCount(unsigned int mask) const;
+ unsigned int srcCount(unsigned int mask) const;
+
+ // save & remove / set indirect[0,1] and predicate source
+ void takeExtraSources(int s, Value *[3]);
+ void putExtraSources(int s, Value *[3]);
+
+ inline void setType(DataType type) { dType = sType = type; }
+
+ inline void setType(DataType dtype, DataType stype)
+ {
+ dType = dtype;
+ sType = stype;
+ }
+
+ inline bool isPseudo() const { return op < OP_MOV; }
+ bool isDead() const;
+ bool isNop() const;
+ bool isCommutationLegal(const Instruction *) const; // must be adjacent !
+ bool isActionEqual(const Instruction *) const;
+ bool isResultEqual(const Instruction *) const;
+
+ void print() const;
+
+ inline CmpInstruction *asCmp();
+ inline TexInstruction *asTex();
+ inline FlowInstruction *asFlow();
+ inline const TexInstruction *asTex() const;
+ inline const CmpInstruction *asCmp() const;
+ inline const FlowInstruction *asFlow() const;
+
+public:
+ Instruction *next;
+ Instruction *prev;
+ int id;
+ int serial; // CFG order
+
+ operation op;
+ DataType dType; // destination or defining type
+ DataType sType; // source or secondary type
+ CondCode cc;
+ RoundMode rnd;
+ CacheMode cache;
+
+ uint8_t subOp; // quadop, 1 for mul-high, etc.
+
+ unsigned encSize : 4; // encoding size in bytes
+ unsigned saturate : 1; // to [0.0f, 1.0f]
+ unsigned join : 1; // converge control flow (use OP_JOIN until end)
+ unsigned fixed : 1; // prevent dead code elimination
+ unsigned terminator : 1; // end of basic block
+ unsigned atomic : 1;
+ unsigned ftz : 1; // flush denormal to zero
+ unsigned dnz : 1; // denormals, NaN are zero
+ unsigned ipa : 4; // interpolation mode
+ unsigned lanes : 4;
+ unsigned perPatch : 1;
+ unsigned exit : 1; // terminate program after insn
+
+ int8_t postFactor; // MUL/DIV(if < 0) by 1 << postFactor
+
+ int8_t predSrc;
+ int8_t flagsDef;
+ int8_t flagsSrc;
+
+ // NOTE: should make these pointers, saves space and work on shuffling
+ ValueDef def[NV50_IR_MAX_DEFS]; // no gaps !
+ ValueRef src[NV50_IR_MAX_SRCS]; // no gaps !
+
+ BasicBlock *bb;
+
+ // instruction specific methods:
+ // (don't want to subclass, would need more constructors and memory pools)
+public:
+ inline void setInterpolate(unsigned int mode) { ipa = mode; }
+
+ unsigned int getInterpMode() const { return ipa & 0x3; }
+ unsigned int getSampleMode() const { return ipa & 0xc; }
+
+private:
+ void init();
+protected:
+ void cloneBase(Instruction *clone, bool deep) const;
+};
+
+enum TexQuery
+{
+ TXQ_DIMS,
+ TXQ_TYPE,
+ TXQ_SAMPLE_POSITION,
+ TXQ_FILTER,
+ TXQ_LOD,
+ TXQ_WRAP,
+ TXQ_BORDER_COLOUR
+};
+
+class TexInstruction : public Instruction
+{
+public:
+ class Target
+ {
+ public:
+ Target(TexTarget targ = TEX_TARGET_2D) : target(targ) { }
+
+ const char *getName() const { return descTable[target].name; }
+ unsigned int getArgCount() const { return descTable[target].argc; }
+ unsigned int getDim() const { return descTable[target].dim; }
+ int isArray() const { return descTable[target].array ? 1 : 0; }
+ int isCube() const { return descTable[target].cube ? 1 : 0; }
+ int isShadow() const { return descTable[target].shadow ? 1 : 0; }
+
+ Target& operator=(TexTarget targ)
+ {
+ assert(targ < TEX_TARGET_COUNT);
+ return *this;
+ }
+
+ inline bool operator==(TexTarget targ) const { return target == targ; }
+
+ private:
+ struct Desc
+ {
+ char name[19];
+ uint8_t dim;
+ uint8_t argc;
+ bool array;
+ bool cube;
+ bool shadow;
+ };
+
+ static const struct Desc descTable[TEX_TARGET_COUNT];
+
+ private:
+ enum TexTarget target;
+ };
+
+public:
+ TexInstruction(Function *, operation);
+ virtual ~TexInstruction();
+
+ virtual Instruction *clone(bool deep) const;
+
+ inline void setTexture(Target targ, uint8_t r, uint8_t s)
+ {
+ tex.r = r;
+ tex.s = s;
+ tex.target = targ;
+ }
+
+ inline Value *getIndirectR() const;
+ inline Value *getIndirectS() const;
+
+public:
+ struct {
+ Target target;
+
+ uint8_t r;
+ int8_t rIndirectSrc;
+ uint8_t s;
+ int8_t sIndirectSrc;
+
+ uint8_t mask;
+ uint8_t gatherComp;
+
+ bool liveOnly; // only execute on live pixels of a quad (optimization)
+ bool levelZero;
+
+ int8_t useOffsets; // 0, 1, or 4 for textureGatherOffsets
+ int8_t offset[4][3];
+
+ enum TexQuery query;
+ } tex;
+
+ ValueRef dPdx[3];
+ ValueRef dPdy[3];
+};
+
+class CmpInstruction : public Instruction
+{
+public:
+ CmpInstruction(Function *, operation);
+
+ virtual Instruction *clone(bool deep) const;
+
+ void setCondition(CondCode cond) { setCond = cond; }
+ CondCode getCondition() const { return setCond; }
+
+public:
+ CondCode setCond;
+};
+
+class FlowInstruction : public Instruction
+{
+public:
+ FlowInstruction(Function *, operation, BasicBlock *target);
+
+public:
+ unsigned allWarp : 1;
+ unsigned absolute : 1;
+ unsigned limit : 1;
+ unsigned builtin : 1; // true for calls to emulation code
+
+ union {
+ BasicBlock *bb;
+ int builtin;
+ Function *fn;
+ } target;
+};
+
+class BasicBlock
+{
+public:
+ BasicBlock(Function *);
+ ~BasicBlock();
+
+ inline int getId() const { return id; }
+ inline unsigned int getInsnCount() const { return numInsns; }
+ inline bool isTerminated() const { return exit && exit->terminator; }
+
+ bool dominatedBy(BasicBlock *bb);
+ inline bool reachableBy(BasicBlock *by, BasicBlock *term);
+
+ // returns mask of conditional out blocks
+ // e.g. 3 for IF { .. } ELSE { .. } ENDIF, 1 for IF { .. } ENDIF
+ unsigned int initiatesSimpleConditional() const;
+
+public:
+ Function *getFunction() const { return func; }
+ Program *getProgram() const { return program; }
+
+ Instruction *getEntry() const { return entry; } // first non-phi instruction
+ Instruction *getPhi() const { return phi; }
+ Instruction *getFirst() const { return phi ? phi : entry; }
+ Instruction *getExit() const { return exit; }
+
+ void insertHead(Instruction *);
+ void insertTail(Instruction *);
+ void insertBefore(Instruction *, Instruction *);
+ void insertAfter(Instruction *, Instruction *);
+ void remove(Instruction *);
+ void permuteAdjacent(Instruction *, Instruction *);
+
+ BasicBlock *idom() const;
+
+ DLList& getDF() { return df; }
+ DLList::Iterator iterDF() { return df.iterator(); }
+
+ static inline BasicBlock *get(Iterator&);
+ static inline BasicBlock *get(Graph::Node *);
+
+public:
+ Graph::Node cfg; // first edge is branch *taken* (the ELSE branch)
+ Graph::Node dom;
+
+ BitSet liveSet;
+
+ uint32_t binPos;
+ uint32_t binSize;
+
+ Instruction *joinAt; // for quick reference
+
+ bool explicitCont; // loop headers: true if loop contains continue stmts
+
+private:
+ int id;
+ DLList df;
+
+ Instruction *phi;
+ Instruction *entry;
+ Instruction *exit;
+
+ unsigned int numInsns;
+
+private:
+ Function *func;
+ Program *program;
+};
+
+class Function
+{
+public:
+ Function(Program *, const char *name);
+ ~Function();
+
+ inline Program *getProgram() const { return prog; }
+ inline const char *getName() const { return name; }
+ inline int getId() const { return id; }
+
+ void print();
+ void printLiveIntervals() const;
+ void printCFGraph(const char *filePath);
+
+ bool setEntry(BasicBlock *);
+ bool setExit(BasicBlock *);
+
+ unsigned int orderInstructions(ArrayList&);
+
+ inline void add(BasicBlock *bb, int& id) { allBBlocks.insert(bb, id); }
+ inline void add(Instruction *insn, int& id) { allInsns.insert(insn, id); }
+ inline void add(LValue *lval, int& id) { allLValues.insert(lval, id); }
+
+ inline LValue *getLValue(int id);
+
+ bool convertToSSA();
+
+public:
+ Graph cfg;
+ Graph::Node *cfgExit;
+ Graph *domTree;
+ Graph::Node call; // node in the call graph
+
+ BasicBlock **bbArray; // BBs in emission order
+ int bbCount;
+
+ unsigned int loopNestingBound;
+ int regClobberMax;
+
+ uint32_t binPos;
+ uint32_t binSize;
+
+ ArrayList allBBlocks;
+ ArrayList allInsns;
+ ArrayList allLValues;
+
+private:
+ void buildLiveSetsPreSSA(BasicBlock *, const int sequence);
+
+private:
+ int id;
+ const char *const name;
+ Program *prog;
+};
+
+enum CGStage
+{
+ CG_STAGE_PRE_SSA,
+ CG_STAGE_SSA, // expected directly before register allocation
+ CG_STAGE_POST_RA
+};
+
+class Program
+{
+public:
+ enum Type
+ {
+ TYPE_VERTEX,
+ TYPE_TESSELLATION_CONTROL,
+ TYPE_TESSELLATION_EVAL,
+ TYPE_GEOMETRY,
+ TYPE_FRAGMENT,
+ TYPE_COMPUTE
+ };
+
+ Program(Type type, Target *targ);
+ ~Program();
+
+ void print();
+
+ Type getType() const { return progType; }
+
+ inline void add(Function *fn, int& id) { allFuncs.insert(fn, id); }
+ inline void add(Value *rval, int& id) { allRValues.insert(rval, id); }
+
+ bool makeFromTGSI(struct nv50_ir_prog_info *);
+ bool makeFromSM4(struct nv50_ir_prog_info *);
+ bool convertToSSA();
+ bool optimizeSSA(int level);
+ bool optimizePostRA(int level);
+ bool registerAllocation();
+ bool emitBinary(struct nv50_ir_prog_info *);
+
+ const Target *getTarget() const { return target; }
+
+private:
+ Type progType;
+ Target *target;
+
+public:
+ Function *main;
+ Graph calls;
+
+ ArrayList allFuncs;
+ ArrayList allRValues;
+
+ uint32_t *code;
+ uint32_t binSize;
+
+ int maxGPR;
+
+ MemoryPool mem_Instruction;
+ MemoryPool mem_CmpInstruction;
+ MemoryPool mem_TexInstruction;
+ MemoryPool mem_FlowInstruction;
+ MemoryPool mem_LValue;
+ MemoryPool mem_Symbol;
+ MemoryPool mem_ImmediateValue;
+
+ uint32_t dbgFlags;
+
+ void releaseInstruction(Instruction *);
+ void releaseValue(Value *);
+};
+
+// TODO: add const version
+class Pass
+{
+public:
+ bool run(Program *, bool ordered = false, bool skipPhi = false);
+ bool run(Function *, bool ordered = false, bool skipPhi = false);
+
+private:
+ // return false to continue with next entity on next higher level
+ virtual bool visit(Function *) { return true; }
+ virtual bool visit(BasicBlock *) { return true; }
+ virtual bool visit(Instruction *) { return false; }
+
+ bool doRun(Program *, bool ordered, bool skipPhi);
+ bool doRun(Function *, bool ordered, bool skipPhi);
+
+protected:
+ bool err;
+ Function *func;
+ Program *prog;
+};
+
+// =============================================================================
+
+#include "nv50_ir_inlines.h"
+
+} // namespace nv50_ir
+
+#endif // __NV50_IR_H__
diff --git a/src/gallium/drivers/nv50/codegen/nv50_ir_bb.cpp b/src/gallium/drivers/nv50/codegen/nv50_ir_bb.cpp
new file mode 100644
index 00000000000..5bf08b37c51
--- /dev/null
+++ b/src/gallium/drivers/nv50/codegen/nv50_ir_bb.cpp
@@ -0,0 +1,409 @@
+
+#include "nv50_ir.h"
+
+namespace nv50_ir {
+
+Function::Function(Program *p, const char *fnName)
+ : call(this),
+ name(fnName),
+ prog(p)
+{
+ cfgExit = NULL;
+ domTree = NULL;
+
+ bbArray = NULL;
+ bbCount = 0;
+ loopNestingBound = 0;
+ regClobberMax = 0;
+
+ binPos = 0;
+ binSize = 0;
+
+ prog->add(this, id);
+}
+
+Function::~Function()
+{
+ if (domTree)
+ delete domTree;
+ if (bbArray)
+ delete[] bbArray;
+
+ for (ArrayList::Iterator BBs = allBBlocks.iterator(); !BBs.end(); BBs.next())
+ delete reinterpret_cast<BasicBlock *>(BBs.get());
+}
+
+BasicBlock::BasicBlock(Function *fn) : cfg(this), dom(this), func(fn)
+{
+ program = func->getProgram();
+
+ joinAt = phi = entry = exit = NULL;
+
+ numInsns = 0;
+ binPos = 0;
+ binSize = 0;
+
+ explicitCont = false;
+
+ func->add(this, this->id);
+}
+
+BasicBlock::~BasicBlock()
+{
+ // nothing yet
+}
+
+BasicBlock *
+BasicBlock::idom() const
+{
+ Graph::Node *dn = dom.parent();
+ return dn ? BasicBlock::get(dn) : NULL;
+}
+
+void
+BasicBlock::insertHead(Instruction *inst)
+{
+ assert(inst->next == 0 && inst->prev == 0);
+
+ if (inst->op == OP_PHI) {
+ if (phi) {
+ insertBefore(phi, inst);
+ } else {
+ if (entry) {
+ insertBefore(entry, phi);
+ } else {
+ assert(!exit);
+ phi = exit = inst;
+ inst->bb = this;
+ ++numInsns;
+ }
+ }
+ } else {
+ if (entry) {
+ insertBefore(entry, inst);
+ } else {
+ if (phi) {
+ insertAfter(phi, inst);
+ } else {
+ assert(!exit);
+ entry = exit = inst;
+ inst->bb = this;
+ ++numInsns;
+ }
+ }
+ }
+}
+
+void
+BasicBlock::insertTail(Instruction *inst)
+{
+ assert(inst->next == 0 && inst->prev == 0);
+
+ if (inst->op == OP_PHI) {
+ if (entry) {
+ insertBefore(entry, inst);
+ } else
+ if (exit) {
+ assert(phi);
+ insertAfter(exit, inst);
+ } else {
+ assert(!phi);
+ phi = exit = inst;
+ inst->bb = this;
+ ++numInsns;
+ }
+ } else {
+ if (exit) {
+ insertAfter(exit, inst);
+ } else {
+ assert(!phi);
+ entry = exit = inst;
+ inst->bb = this;
+ ++numInsns;
+ }
+ }
+}
+
+void
+BasicBlock::insertBefore(Instruction *q, Instruction *p)
+{
+ assert(p && q);
+
+ assert(p->next == 0 && p->prev == 0);
+
+ if (q == entry) {
+ if (p->op == OP_PHI) {
+ if (!phi)
+ phi = p;
+ } else {
+ entry = p;
+ }
+ } else
+ if (q == phi) {
+ assert(p->op == OP_PHI);
+ phi = p;
+ }
+
+ p->next = q;
+ p->prev = q->prev;
+ if (p->prev)
+ p->prev->next = p;
+ q->prev = p;
+
+ p->bb = this;
+ ++numInsns;
+}
+
+void
+BasicBlock::insertAfter(Instruction *p, Instruction *q)
+{
+ assert(p && q);
+ assert(q->op != OP_PHI || p->op == OP_PHI);
+
+ assert(q->next == 0 && q->prev == 0);
+
+ if (p == exit)
+ exit = q;
+ if (p->op == OP_PHI && q->op != OP_PHI)
+ entry = q;
+
+ q->prev = p;
+ q->next = p->next;
+ if (q->next)
+ q->next->prev = q;
+ p->next = q;
+
+ q->bb = this;
+ ++numInsns;
+}
+
+void
+BasicBlock::remove(Instruction *insn)
+{
+ assert(insn->bb == this);
+
+ if (insn->prev)
+ insn->prev->next = insn->next;
+
+ if (insn->next)
+ insn->next->prev = insn->prev;
+ else
+ exit = insn->prev;
+
+ if (insn == entry)
+ entry = insn->next ? insn->next : insn->prev;
+
+ if (insn == phi)
+ phi = (insn->next && insn->next->op == OP_PHI) ? insn->next : 0;
+
+ --numInsns;
+ insn->bb = NULL;
+ insn->next =
+ insn->prev = NULL;
+}
+
+void BasicBlock::permuteAdjacent(Instruction *a, Instruction *b)
+{
+ assert(a->bb == b->bb);
+
+ if (a->next != b) {
+ Instruction *i = a;
+ a = b;
+ b = i;
+ }
+ assert(a->next == b);
+ assert(a->op != OP_PHI && b->op != OP_PHI);
+
+ if (b == exit)
+ exit = a;
+ if (a == entry)
+ entry = b;
+
+ b->prev = a->prev;
+ a->next = b->next;
+ b->next = a;
+ a->prev = b;
+
+ if (b->prev)
+ b->prev->next = b;
+ if (a->prev)
+ a->next->prev = a;
+}
+
+bool
+BasicBlock::dominatedBy(BasicBlock *that)
+{
+ Graph::Node *bn = &that->dom;
+ Graph::Node *dn = &this->dom;
+
+ while (dn && dn != bn)
+ dn = dn->parent();
+
+ return dn != NULL;
+}
+
+unsigned int
+BasicBlock::initiatesSimpleConditional() const
+{
+ Graph::Node *out[2];
+ int n;
+ Graph::Edge::Type eR;
+
+ if (cfg.outgoingCount() != 2) // -> if and -> else/endif
+ return false;
+
+ n = 0;
+ for (Graph::EdgeIterator ei = cfg.outgoing(); !ei.end(); ei.next())
+ out[n++] = ei.getNode();
+ eR = out[1]->outgoing().getType();
+
+ // IF block is out edge to the right
+ if (eR == Graph::Edge::CROSS || eR == Graph::Edge::BACK)
+ return 0x2;
+
+ if (out[1]->outgoingCount() != 1) // 0 is IF { RET; }, >1 is more divergence
+ return 0x0;
+ // do they reconverge immediately ?
+ if (out[1]->outgoing().getNode() == out[0])
+ return 0x1;
+ if (out[0]->outgoingCount() == 1)
+ if (out[0]->outgoing().getNode() == out[1]->outgoing().getNode())
+ return 0x3;
+
+ return 0x0;
+}
+
+bool
+Function::setEntry(BasicBlock *bb)
+{
+ if (cfg.getRoot())
+ return false;
+ cfg.insert(&bb->cfg);
+ return true;
+}
+
+bool
+Function::setExit(BasicBlock *bb)
+{
+ if (cfgExit)
+ return false;
+ cfgExit = &bb->cfg;
+ return true;
+}
+
+unsigned int
+Function::orderInstructions(ArrayList &result)
+{
+ Iterator *iter;
+ for (iter = cfg.iteratorCFG(); !iter->end(); iter->next())
+ for (Instruction *insn = BasicBlock::get(*iter)->getFirst();
+ insn; insn = insn->next)
+ result.insert(insn, insn->serial);
+ cfg.putIterator(iter);
+ return result.getSize();
+}
+
+bool
+Pass::run(Program *prog, bool ordered, bool skipPhi)
+{
+ this->prog = prog;
+ err = false;
+ return doRun(prog, ordered, skipPhi);
+}
+
+bool
+Pass::doRun(Program *prog, bool ordered, bool skipPhi)
+{
+ for (ArrayList::Iterator fi = prog->allFuncs.iterator();
+ !fi.end(); fi.next()) {
+ Function *fn = reinterpret_cast<Function *>(fi.get());
+ if (!doRun(fn, ordered, skipPhi))
+ return false;
+ }
+ return !err;
+}
+
+bool
+Pass::run(Function *func, bool ordered, bool skipPhi)
+{
+ prog = func->getProgram();
+ err = false;
+ return doRun(func, ordered, skipPhi);
+}
+
+bool
+Pass::doRun(Function *func, bool ordered, bool skipPhi)
+{
+ Iterator *bbIter;
+ BasicBlock *bb;
+ Instruction *insn, *next;
+
+ this->func = func;
+ if (!visit(func))
+ return false;
+
+ bbIter = ordered ? func->cfg.iteratorCFG() : func->cfg.iteratorDFS();
+
+ for (; !bbIter->end(); bbIter->next()) {
+ bb = BasicBlock::get(reinterpret_cast<Graph::Node *>(bbIter->get()));
+ if (!visit(bb))
+ break;
+ for (insn = skipPhi ? bb->getEntry() : bb->getFirst(); insn != NULL;
+ insn = next) {
+ next = insn->next;
+ if (!visit(insn))
+ break;
+ }
+ }
+ func->cfg.putIterator(bbIter);
+ return !err;
+}
+
+void
+Function::printCFGraph(const char *filePath)
+{
+ FILE *out = fopen(filePath, "a");
+ if (!out) {
+ ERROR("failed to open file: %s\n", filePath);
+ return;
+ }
+ INFO("printing control flow graph to: %s\n", filePath);
+
+ fprintf(out, "digraph G {\n");
+
+ Iterator *iter;
+ for (iter = cfg.iteratorDFS(); !iter->end(); iter->next()) {
+ BasicBlock *bb = BasicBlock::get(
+ reinterpret_cast<Graph::Node *>(iter->get()));
+ int idA = bb->getId();
+ for (Graph::EdgeIterator ei = bb->cfg.outgoing(); !ei.end(); ei.next()) {
+ int idB = BasicBlock::get(ei.getNode())->getId();
+ switch (ei.getType()) {
+ case Graph::Edge::TREE:
+ fprintf(out, "\t%i -> %i;\n", idA, idB);
+ break;
+ case Graph::Edge::FORWARD:
+ fprintf(out, "\t%i -> %i [color=green];\n", idA, idB);
+ break;
+ case Graph::Edge::CROSS:
+ fprintf(out, "\t%i -> %i [color=red];\n", idA, idB);
+ break;
+ case Graph::Edge::BACK:
+ fprintf(out, "\t%i -> %i;\n", idA, idB);
+ break;
+ case Graph::Edge::DUMMY:
+ fprintf(out, "\t%i -> %i [style=dotted];\n", idA, idB);
+ break;
+ default:
+ assert(0);
+ break;
+ }
+ }
+ }
+ cfg.putIterator(iter);
+
+ fprintf(out, "}\n");
+ fclose(out);
+}
+
+} // namespace nv50_ir
diff --git a/src/gallium/drivers/nv50/codegen/nv50_ir_build_util.cpp b/src/gallium/drivers/nv50/codegen/nv50_ir_build_util.cpp
new file mode 100644
index 00000000000..284736838ab
--- /dev/null
+++ b/src/gallium/drivers/nv50/codegen/nv50_ir_build_util.cpp
@@ -0,0 +1,501 @@
+
+#include "nv50_ir.h"
+#include "nv50_ir_build_util.h"
+
+namespace nv50_ir {
+
+BuildUtil::BuildUtil()
+{
+ prog = NULL;
+ func = NULL;
+ bb = NULL;
+ pos = NULL;
+
+ memset(imms, 0, sizeof(imms));
+ immCount = 0;
+}
+
+void
+BuildUtil::addImmediate(ImmediateValue *imm)
+{
+ if (immCount > (NV50_IR_BUILD_IMM_HT_SIZE * 3) / 4)
+ return;
+
+ unsigned int pos = u32Hash(imm->reg.data.u32);
+
+ while (imms[pos])
+ pos = (pos + 1) % NV50_IR_BUILD_IMM_HT_SIZE;
+ imms[pos] = imm;
+ immCount++;
+}
+
+Instruction *
+BuildUtil::mkOp1(operation op, DataType ty, Value *dst, Value *src)
+{
+ Instruction *insn = new_Instruction(func, op, ty);
+
+ insn->setDef(0, dst);
+ insn->setSrc(0, src);
+
+ insert(insn);
+ return insn;
+}
+
+Instruction *
+BuildUtil::mkOp2(operation op, DataType ty, Value *dst,
+ Value *src0, Value *src1)
+{
+ Instruction *insn = new_Instruction(func, op, ty);
+
+ insn->setDef(0, dst);
+ insn->setSrc(0, src0);
+ insn->setSrc(1, src1);
+
+ insert(insn);
+ return insn;
+}
+
+Instruction *
+BuildUtil::mkOp3(operation op, DataType ty, Value *dst,
+ Value *src0, Value *src1, Value *src2)
+{
+ Instruction *insn = new_Instruction(func, op, ty);
+
+ insn->setDef(0, dst);
+ insn->setSrc(0, src0);
+ insn->setSrc(1, src1);
+ insn->setSrc(2, src2);
+
+ insert(insn);
+ return insn;
+}
+
+LValue *
+BuildUtil::mkLoad(DataType ty, Symbol *mem, Value *ptr)
+{
+ Instruction *insn = new_Instruction(func, OP_LOAD, ty);
+ LValue *def = getScratch();
+
+ insn->setDef(0, def);
+ insn->setSrc(0, mem);
+ if (ptr)
+ insn->setIndirect(0, 0, ptr);
+
+ insert(insn);
+ return def;
+}
+
+Instruction *
+BuildUtil::mkStore(operation op, DataType ty, Symbol *mem, Value *ptr,
+ Value *stVal)
+{
+ Instruction *insn = new_Instruction(func, op, ty);
+
+ insn->setSrc(0, mem);
+ insn->setSrc(1, stVal);
+ if (ptr)
+ insn->setIndirect(0, 0, ptr);
+
+ insert(insn);
+ return insn;
+}
+
+Instruction *
+BuildUtil::mkFetch(Value *dst, DataType ty, DataFile file, int32_t offset,
+ Value *attrRel, Value *primRel)
+{
+ Symbol *sym = mkSymbol(file, 0, ty, offset);
+
+ Instruction *insn = mkOp1(OP_VFETCH, ty, dst, sym);
+
+ insn->setIndirect(0, 0, attrRel);
+ insn->setIndirect(0, 1, primRel);
+
+ // already inserted
+ return insn;
+}
+
+Instruction *
+BuildUtil::mkMov(Value *dst, Value *src, DataType ty)
+{
+ Instruction *insn = new_Instruction(func, OP_MOV, ty);
+
+ insn->setDef(0, dst);
+ insn->setSrc(0, src);
+
+ insert(insn);
+ return insn;
+}
+
+Instruction *
+BuildUtil::mkMovToReg(int id, Value *src)
+{
+ Instruction *insn = new_Instruction(func, OP_MOV, typeOfSize(src->reg.size));
+
+ insn->setDef(0, new_LValue(func, FILE_GPR));
+ insn->getDef(0)->reg.data.id = id;
+ insn->setSrc(0, src);
+
+ insert(insn);
+ return insn;
+}
+
+Instruction *
+BuildUtil::mkMovFromReg(Value *dst, int id)
+{
+ Instruction *insn = new_Instruction(func, OP_MOV, typeOfSize(dst->reg.size));
+
+ insn->setDef(0, dst);
+ insn->setSrc(0, new_LValue(func, FILE_GPR));
+ insn->getSrc(0)->reg.data.id = id;
+
+ insert(insn);
+ return insn;
+}
+
+Instruction *
+BuildUtil::mkCvt(operation op,
+ DataType dstTy, Value *dst, DataType srcTy, Value *src)
+{
+ Instruction *insn = new_Instruction(func, op, dstTy);
+
+ insn->setType(dstTy, srcTy);
+ insn->setDef(0, dst);
+ insn->setSrc(0, src);
+
+ insert(insn);
+ return insn;
+}
+
+Instruction *
+BuildUtil::mkCmp(operation op, CondCode cc, DataType ty, Value *dst,
+ Value *src0, Value *src1, Value *src2)
+{
+ CmpInstruction *insn = new_CmpInstruction(func, op);
+
+ insn->setType(dst->reg.file == FILE_PREDICATE ? TYPE_U8 : ty, ty);
+ insn->setCondition(cc);
+ insn->setDef(0, dst);
+ insn->setSrc(0, src0);
+ insn->setSrc(1, src1);
+ if (src2)
+ insn->setSrc(2, src2);
+
+ insert(insn);
+ return insn;
+}
+
+Instruction *
+BuildUtil::mkTex(operation op, TexTarget targ, uint8_t tic, uint8_t tsc,
+ Value **def, Value **src)
+{
+ TexInstruction *tex = new_TexInstruction(func, op);
+
+ for (int d = 0; d < 4 && def[d]; ++d)
+ tex->setDef(d, def[d]);
+ for (int s = 0; s < 4 && src[s]; ++s)
+ tex->setSrc(s, src[s]);
+
+ tex->setTexture(targ, tic, tsc);
+
+ return tex;
+}
+
+Instruction *
+BuildUtil::mkQuadop(uint8_t q, Value *def, uint8_t l, Value *src0, Value *src1)
+{
+ Instruction *quadop = mkOp2(OP_QUADOP, TYPE_F32, def, src0, src1);
+ quadop->subOp = q;
+ quadop->lanes = l;
+ return quadop;
+}
+
+Instruction *
+BuildUtil::mkSelect(Value *pred, Value *dst, Value *trSrc, Value *flSrc)
+{
+ Instruction *insn;
+ LValue *def0 = getSSA();
+ LValue *def1 = getSSA();
+
+ mkMov(def0, trSrc)->setPredicate(CC_P, pred);
+ mkMov(def1, flSrc)->setPredicate(CC_NOT_P, pred);
+
+ insn = mkOp2(OP_UNION, typeOfSize(dst->reg.size), dst, def0, def1);
+
+ insert(insn);
+ return insn;
+}
+
+FlowInstruction *
+BuildUtil::mkFlow(operation op, BasicBlock *targ, CondCode cc, Value *pred)
+{
+ FlowInstruction *insn = new_FlowInstruction(func, op, targ);
+
+ if (pred)
+ insn->setPredicate(cc, pred);
+
+ insert(insn);
+ return insn;
+}
+
+void
+BuildUtil::mkClobber(DataFile f, uint32_t rMask, int unit)
+{
+ static const uint16_t baseSize2[16] =
+ {
+ 0x0000, 0x0010, 0x0011, 0x0020, 0x0012, 0x1210, 0x1211, 0x1220,
+ 0x0013, 0x1310, 0x1311, 0x0020, 0x1320, 0x0022, 0x2210, 0x0040,
+ };
+
+ int base = 0;
+
+ for (; rMask; rMask >>= 4, base += 4) {
+ const uint32_t mask = rMask & 0xf;
+ if (!mask)
+ continue;
+ int base1 = (baseSize2[mask] >> 0) & 0xf;
+ int size1 = (baseSize2[mask] >> 4) & 0xf;
+ int base2 = (baseSize2[mask] >> 8) & 0xf;
+ int size2 = (baseSize2[mask] >> 12) & 0xf;
+ Instruction *insn = mkOp(OP_NOP, TYPE_NONE, NULL);
+ if (1) { // size1 can't be 0
+ LValue *reg = new_LValue(func, f);
+ reg->reg.size = size1 << unit;
+ reg->reg.data.id = base + base1;
+ insn->setDef(0, reg);
+ }
+ if (size2) {
+ LValue *reg = new_LValue(func, f);
+ reg->reg.size = size2 << unit;
+ reg->reg.data.id = base + base2;
+ insn->setDef(1, reg);
+ }
+ }
+}
+
+ImmediateValue *
+BuildUtil::mkImm(uint32_t u)
+{
+ unsigned int pos = u32Hash(u);
+
+ while (imms[pos] && imms[pos]->reg.data.u32 != u)
+ pos = (pos + 1) % NV50_IR_BUILD_IMM_HT_SIZE;
+
+ ImmediateValue *imm = imms[pos];
+ if (!imm) {
+ imm = new_ImmediateValue(prog, u);
+ addImmediate(imm);
+ }
+ return imm;
+}
+
+ImmediateValue *
+BuildUtil::mkImm(uint64_t u)
+{
+ ImmediateValue *imm = new_ImmediateValue(prog, (uint32_t)0);
+
+ imm->reg.size = 8;
+ imm->reg.type = TYPE_U64;
+ imm->reg.data.u64 = u;
+
+ return imm;
+}
+
+ImmediateValue *
+BuildUtil::mkImm(float f)
+{
+ union {
+ float f32;
+ uint32_t u32;
+ } u;
+ u.f32 = f;
+ return mkImm(u.u32);
+}
+
+Value *
+BuildUtil::loadImm(Value *dst, float f)
+{
+ return mkOp1v(OP_MOV, TYPE_F32, dst ? dst : getScratch(), mkImm(f));
+}
+
+Value *
+BuildUtil::loadImm(Value *dst, uint32_t u)
+{
+ return mkOp1v(OP_MOV, TYPE_U32, dst ? dst : getScratch(), mkImm(u));
+}
+
+Value *
+BuildUtil::loadImm(Value *dst, uint64_t u)
+{
+ return mkOp1v(OP_MOV, TYPE_U64, dst ? dst : getScratch(8), mkImm(u));
+}
+
+Symbol *
+BuildUtil::mkSymbol(DataFile file, int8_t fileIndex, DataType ty,
+ uint32_t baseAddr)
+{
+ Symbol *sym = new_Symbol(prog, file, fileIndex);
+
+ sym->setOffset(baseAddr);
+ sym->reg.type = ty;
+ sym->reg.size = typeSizeof(ty);
+
+ return sym;
+}
+
+Symbol *
+BuildUtil::mkSysVal(SVSemantic svName, uint32_t svIndex)
+{
+ Symbol *sym = new_Symbol(prog, FILE_SYSTEM_VALUE, 0);
+
+ assert(svIndex < 4 ||
+ (svName == SV_CLIP_DISTANCE || svName == SV_TESS_FACTOR));
+
+ switch (svName) {
+ case SV_POSITION:
+ case SV_FACE:
+ case SV_YDIR:
+ case SV_POINT_SIZE:
+ case SV_POINT_COORD:
+ case SV_CLIP_DISTANCE:
+ case SV_TESS_FACTOR:
+ sym->reg.type = TYPE_F32;
+ break;
+ default:
+ sym->reg.type = TYPE_U32;
+ break;
+ }
+ sym->reg.size = typeSizeof(sym->reg.type);
+
+ sym->reg.data.sv.sv = svName;
+ sym->reg.data.sv.index = svIndex;
+
+ return sym;
+}
+
+void
+BuildUtil::DataArray::init()
+{
+ values = NULL;
+ baseAddr = 0;
+ arrayLen = 0;
+
+ vecDim = 4;
+ eltSize = 2;
+
+ file = FILE_GPR;
+ regOnly = true;
+}
+
+BuildUtil::DataArray::DataArray()
+{
+ init();
+}
+
+BuildUtil::DataArray::DataArray(BuildUtil *bld) : up(bld)
+{
+ init();
+}
+
+BuildUtil::DataArray::~DataArray()
+{
+ if (values)
+ delete[] values;
+}
+
+void
+BuildUtil::DataArray::setup(uint32_t base, int len, int v, int size,
+ DataFile f, int8_t fileIndex)
+{
+ baseAddr = base;
+ arrayLen = len;
+
+ vecDim = v;
+ eltSize = size;
+
+ file = f;
+ regOnly = !isMemoryFile(f);
+
+ values = new Value * [arrayLen * vecDim];
+ if (values)
+ memset(values, 0, arrayLen * vecDim * sizeof(Value *));
+
+ if (!regOnly) {
+ baseSym = new_Symbol(up->getProgram(), file, fileIndex);
+ baseSym->setOffset(baseAddr);
+ baseSym->reg.size = size;
+ }
+}
+
+Value *
+BuildUtil::DataArray::acquire(int i, int c)
+{
+ const unsigned int idx = i * vecDim + c;
+
+ assert(idx < arrayLen * vecDim);
+
+ if (regOnly) {
+ const unsigned int idx = i * 4 + c; // vecDim always 4 if regOnly
+ if (!values[idx])
+ values[idx] = new_LValue(up->getFunction(), file);
+ return values[idx];
+ } else {
+ return up->getScratch();
+ }
+}
+
+Value *
+BuildUtil::DataArray::load(int i, int c, Value *ptr)
+{
+ const unsigned int idx = i * vecDim + c;
+
+ assert(idx < arrayLen * vecDim);
+
+ if (regOnly) {
+ if (!values[idx])
+ values[idx] = new_LValue(up->getFunction(), file);
+ return values[idx];
+ } else {
+ Symbol *sym = reinterpret_cast<Symbol *>(values[idx]);
+ if (!sym)
+ values[idx] = sym = this->mkSymbol(i, c, baseSym);
+ return up->mkLoad(typeOfSize(eltSize), sym, ptr);
+ }
+}
+
+void
+BuildUtil::DataArray::store(int i, int c, Value *ptr, Value *value)
+{
+ const unsigned int idx = i * vecDim + c;
+
+ assert(idx < arrayLen * vecDim);
+
+ if (regOnly) {
+ assert(!ptr);
+ assert(!values[idx] || values[idx] == value);
+ values[idx] = value;
+ } else {
+ Symbol *sym = reinterpret_cast<Symbol *>(values[idx]);
+ if (!sym)
+ values[idx] = sym = this->mkSymbol(i, c, baseSym);
+ up->mkStore(OP_STORE, typeOfSize(value->reg.size), sym, ptr, value);
+ }
+}
+
+Symbol *
+BuildUtil::DataArray::mkSymbol(int i, int c, Symbol *base)
+{
+ const unsigned int idx = i * vecDim + c;
+
+ Symbol *sym = new_Symbol(up->getProgram(), file, 0);
+
+ assert(base || (idx < arrayLen && c < vecDim));
+
+ sym->reg.size = eltSize;
+ sym->reg.type = typeOfSize(eltSize);
+
+ sym->setAddress(base, baseAddr + idx * eltSize);
+ return sym;
+}
+
+} // namespace nv50_ir
diff --git a/src/gallium/drivers/nv50/codegen/nv50_ir_build_util.h b/src/gallium/drivers/nv50/codegen/nv50_ir_build_util.h
new file mode 100644
index 00000000000..4c3addb27e4
--- /dev/null
+++ b/src/gallium/drivers/nv50/codegen/nv50_ir_build_util.h
@@ -0,0 +1,245 @@
+
+#ifndef __NV50_IR_BUILD_UTIL__
+#define __NV50_IR_BUILD_UTIL__
+
+namespace nv50_ir {
+
+class BuildUtil
+{
+public:
+ BuildUtil();
+
+ inline void setProgram(Program *);
+ inline Program *getProgram() const { return prog; }
+ inline Function *getFunction() const { return func; }
+
+ // keeps inserting at head/tail of block
+ inline void setPosition(BasicBlock *, bool tail);
+ // position advances only if @after is true
+ inline void setPosition(Instruction *, bool after);
+
+ inline BasicBlock *getBB() { return bb; }
+
+ inline void insert(Instruction *);
+ inline void remove(Instruction *i) { assert(i->bb == bb); bb->remove(i); }
+
+ inline LValue *getScratch(int size = 4);
+ inline LValue *getSSA(int size = 4); // scratch value for a single assignment
+
+ inline Instruction *mkOp(operation, DataType, Value *);
+ Instruction *mkOp1(operation, DataType, Value *, Value *);
+ Instruction *mkOp2(operation, DataType, Value *, Value *, Value *);
+ Instruction *mkOp3(operation, DataType, Value *, Value *, Value *, Value *);
+
+ LValue *mkOp1v(operation, DataType, Value *, Value *);
+ LValue *mkOp2v(operation, DataType, Value *, Value *, Value *);
+ LValue *mkOp3v(operation, DataType, Value *, Value *, Value *, Value *);
+
+ LValue *mkLoad(DataType, Symbol *, Value *ptr);
+ Instruction *mkStore(operation, DataType, Symbol *, Value *ptr, Value *val);
+
+ Instruction *mkMov(Value *, Value *, DataType = TYPE_U32);
+ Instruction *mkMovToReg(int id, Value *);
+ Instruction *mkMovFromReg(Value *, int id);
+
+ Instruction *mkFetch(Value *, DataType, DataFile, int32_t offset,
+ Value *attrRel, Value *primRel);
+
+ Instruction *mkCvt(operation, DataType, Value *, DataType, Value *);
+ Instruction *mkCmp(operation, CondCode, DataType,
+ Value *,
+ Value *, Value *, Value * = NULL);
+ Instruction *mkTex(operation, TexTarget, uint8_t tic, uint8_t tsc,
+ Value **def, Value **src);
+ Instruction *mkQuadop(uint8_t qop, Value *, uint8_t l, Value *, Value *);
+
+ FlowInstruction *mkFlow(operation, BasicBlock *target,
+ CondCode, Value *pred);
+
+ Instruction *mkSelect(Value *pred, Value *dst, Value *trSrc, Value *flSrc);
+
+ void mkClobber(DataFile file, uint32_t regMask, int regUnitLog2);
+
+ ImmediateValue *mkImm(float);
+ ImmediateValue *mkImm(uint32_t);
+ ImmediateValue *mkImm(uint64_t);
+
+ ImmediateValue *mkImm(int i) { return mkImm((uint32_t)i); }
+
+ Value *loadImm(Value *dst, float);
+ Value *loadImm(Value *dst, uint32_t);
+ Value *loadImm(Value *dst, uint64_t);
+
+ Value *loadImm(Value *dst, int i) { return loadImm(dst, (uint32_t)i); }
+
+ class DataArray
+ {
+ public:
+ DataArray();
+ DataArray(BuildUtil *);
+ ~DataArray();
+
+ inline void setParent(BuildUtil *bld) { assert(!up); up = bld; }
+
+ void setup(uint32_t base, int len, int vecDim, int size,
+ DataFile, int8_t fileIndex = 0);
+
+ inline bool exists(unsigned int i, unsigned int c);
+
+ Value *load(int i, int c, Value *ptr);
+ void store(int i, int c, Value *ptr, Value *value);
+ Value *acquire(int i, int c);
+
+ private:
+ Symbol *mkSymbol(int i, int c, Symbol *base);
+
+ private:
+ Value **values;
+ uint32_t baseAddr;
+ uint32_t arrayLen;
+ Symbol *baseSym;
+
+ uint8_t vecDim;
+ uint8_t eltSize; // in bytes
+
+ DataFile file;
+ bool regOnly;
+
+ BuildUtil *up;
+
+ void init();
+ };
+
+ Symbol *mkSymbol(DataFile file, int8_t fileIndex,
+ DataType ty, uint32_t baseAddress);
+
+ Symbol *mkSysVal(SVSemantic svName, uint32_t svIndex);
+
+private:
+ void addImmediate(ImmediateValue *);
+ inline unsigned int u32Hash(uint32_t);
+
+protected:
+ Program *prog;
+ Function *func;
+ Instruction *pos;
+ BasicBlock *bb;
+ bool tail;
+
+#define NV50_IR_BUILD_IMM_HT_SIZE 256
+
+ ImmediateValue *imms[NV50_IR_BUILD_IMM_HT_SIZE];
+ unsigned int immCount;
+};
+
+unsigned int BuildUtil::u32Hash(uint32_t u)
+{
+ return (u % 273) % NV50_IR_BUILD_IMM_HT_SIZE;
+}
+
+void BuildUtil::setProgram(Program *program)
+{
+ prog = program;
+}
+
+void
+BuildUtil::setPosition(BasicBlock *block, bool atTail)
+{
+ bb = block;
+ prog = bb->getProgram();
+ func = bb->getFunction();
+ pos = NULL;
+ tail = atTail;
+}
+
+void
+BuildUtil::setPosition(Instruction *i, bool after)
+{
+ bb = i->bb;
+ prog = bb->getProgram();
+ func = bb->getFunction();
+ pos = i;
+ tail = after;
+ assert(bb);
+}
+
+LValue *
+BuildUtil::getScratch(int size)
+{
+ LValue *lval = new_LValue(func, FILE_GPR);
+ if (size != 4)
+ lval->reg.size = size;
+ return lval;
+}
+
+LValue *
+BuildUtil::getSSA(int size)
+{
+ LValue *lval = new_LValue(func, FILE_GPR);
+ lval->ssa = 1;
+ if (size != 4)
+ lval->reg.size = size;
+ return lval;
+}
+
+void BuildUtil::insert(Instruction *i)
+{
+ if (!pos) {
+ tail ? bb->insertTail(i) : bb->insertHead(i);
+ } else {
+ if (tail) {
+ bb->insertAfter(pos, i);
+ pos = i;
+ } else {
+ bb->insertBefore(pos, i);
+ }
+ }
+}
+
+Instruction *
+BuildUtil::mkOp(operation op, DataType ty, Value *dst)
+{
+ Instruction *insn = new_Instruction(func, op, ty);
+ insn->setDef(0, dst);
+ insert(insn);
+ if (op == OP_DISCARD || op == OP_EXIT ||
+ op == OP_JOIN ||
+ op == OP_QUADON || op == OP_QUADPOP ||
+ op == OP_EMIT || op == OP_RESTART)
+ insn->fixed = 1;
+ return insn;
+}
+
+inline LValue *
+BuildUtil::mkOp1v(operation op, DataType ty, Value *dst, Value *src)
+{
+ mkOp1(op, ty, dst, src);
+ return dst->asLValue();
+}
+
+inline LValue *
+BuildUtil::mkOp2v(operation op, DataType ty, Value *dst,
+ Value *src0, Value *src1)
+{
+ mkOp2(op, ty, dst, src0, src1);
+ return dst->asLValue();
+}
+
+inline LValue *
+BuildUtil::mkOp3v(operation op, DataType ty, Value *dst,
+ Value *src0, Value *src1, Value *src2)
+{
+ mkOp3(op, ty, dst, src0, src1, src2);
+ return dst->asLValue();
+}
+
+bool
+BuildUtil::DataArray::exists(unsigned int i, unsigned int c)
+{
+ assert(i < arrayLen && c < vecDim);
+ return !regOnly || values[i * vecDim + c];
+}
+
+} // namespace nv50_ir
+
+#endif // __NV50_IR_BUILD_UTIL_H__
diff --git a/src/gallium/drivers/nv50/codegen/nv50_ir_driver.h b/src/gallium/drivers/nv50/codegen/nv50_ir_driver.h
new file mode 100644
index 00000000000..27e435d4ea1
--- /dev/null
+++ b/src/gallium/drivers/nv50/codegen/nv50_ir_driver.h
@@ -0,0 +1,149 @@
+
+#ifndef __NV50_IR_DRIVER_H__
+#define __NV50_IR_DRIVER_H__
+
+#include "pipe/p_shader_tokens.h"
+
+#include "tgsi/tgsi_util.h"
+#include "tgsi/tgsi_parse.h"
+#include "tgsi/tgsi_scan.h"
+
+/*
+ * This struct constitutes linkage information in TGSI terminology.
+ *
+ * It is created by the code generator and handed to the pipe driver
+ * for input/output slot assignment.
+ */
+struct nv50_ir_varying
+{
+ uint8_t slot[4]; /* native slots for xyzw (addresses in 32-bit words) */
+
+ unsigned mask : 4; /* vec4 mask */
+ unsigned linear : 1; /* linearly interpolated if true (and not flat) */
+ unsigned flat : 1;
+ unsigned centroid : 1;
+ unsigned patch : 1; /* patch constant value */
+ unsigned regular : 1; /* driver-specific meaning (e.g. input in sreg) */
+ unsigned input : 1; /* indicates direction of system values */
+ unsigned oread : 1; /* true if output is read from parallel TCP */
+
+ ubyte id; /* TGSI register index */
+ ubyte sn; /* TGSI semantic name */
+ ubyte si; /* TGSI semantic index */
+};
+
+#define NV50_PROGRAM_IR_TGSI 0
+#define NV50_PROGRAM_IR_SM4 1
+#define NV50_PROGRAM_IR_GLSL 2
+#define NV50_PROGRAM_IR_LLVM 3
+
+#ifdef DEBUG
+# define NV50_IR_DEBUG_BASIC (1 << 0)
+# define NV50_IR_DEBUG_VERBOSE (2 << 0)
+# define NV50_IR_DEBUG_REG_ALLOC (1 << 2)
+#else
+# define NV50_IR_DEBUG_BASIC 0
+# define NV50_IR_DEBUG_VERBOSE 0
+# define NV50_IR_DEBUG_REG_ALLOC 0
+#endif
+
+struct nv50_ir_prog_info
+{
+ uint16_t target; /* chipset (0x50, 0x84, 0xc0, ...) */
+
+ uint8_t type; /* PIPE_SHADER */
+
+ uint8_t optLevel; /* optimization level (0 to 3) */
+ uint8_t dbgFlags;
+
+ struct {
+ int16_t maxGPR; /* may be -1 if none used */
+ int16_t maxOutput;
+ uint32_t tlsSpace; /* required local memory per thread */
+ uint32_t *code;
+ uint32_t codeSize;
+ uint8_t sourceRep; /* NV50_PROGRAM_IR */
+ const void *source;
+ void *relocData;
+ } bin;
+
+ struct nv50_ir_varying sv[PIPE_MAX_SHADER_INPUTS];
+ struct nv50_ir_varying in[PIPE_MAX_SHADER_INPUTS];
+ struct nv50_ir_varying out[PIPE_MAX_SHADER_OUTPUTS];
+ uint8_t numInputs;
+ uint8_t numOutputs;
+ uint8_t numPatchConstants; /* also included in numInputs/numOutputs */
+ uint8_t numSysVals;
+
+ struct {
+ uint32_t *buf; /* for IMMEDIATE_ARRAY */
+ uint16_t bufSize; /* size of immediate array */
+ uint16_t count; /* count of inline immediates */
+ uint32_t *data; /* inline immediate data */
+ uint8_t *type; /* for each vec4 (128 bit) */
+ } immd;
+
+ union {
+ struct {
+ uint32_t inputMask[4]; /* mask of attributes read (1 bit per scalar) */
+ } vp;
+ struct {
+ uint8_t inputPatchSize;
+ uint8_t outputPatchSize;
+ uint8_t partitioning; /* PIPE_TESS_PART */
+ int8_t winding; /* +1 (clockwise) / -1 (counter-clockwise) */
+ uint8_t domain; /* PIPE_PRIM_{QUADS,TRIANGLES,LINES} */
+ uint8_t outputPrim; /* PIPE_PRIM_{TRIANGLES,LINES,POINTS} */
+ } tp;
+ struct {
+ uint8_t inputPrim;
+ uint8_t outputPrim;
+ unsigned instanceCount;
+ unsigned maxVertices;
+ } gp;
+ struct {
+ unsigned numColourResults;
+ boolean writesDepth;
+ boolean earlyFragTests;
+ boolean separateFragData;
+ boolean usesDiscard;
+ } fp;
+ } prop;
+
+ struct {
+ uint8_t clipDistance; /* index of first clip distance output */
+ uint8_t clipDistanceCount;
+ uint8_t cullDistanceMask; /* clip distance mode (1 bit per output) */
+ uint8_t pointSize; /* output index for PointSize */
+ uint8_t edgeFlagIn;
+ uint8_t edgeFlagOut;
+ uint8_t fragDepth; /* output index of FragDepth */
+ uint8_t sampleMask; /* output index of SampleMask */
+ uint8_t backFaceColor[2]; /* input/output indices of back face colour */
+ uint8_t globalAccess; /* 1 for read, 2 for wr, 3 for rw */
+ } io;
+
+ /* driver callback to assign input/output locations */
+ int (*assignSlots)(struct nv50_ir_prog_info *);
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern int nv50_ir_generate_code(struct nv50_ir_prog_info *);
+
+extern void nv50_ir_relocate_code(void *relocData, uint32_t *code,
+ uint32_t codePos,
+ uint32_t libPos,
+ uint32_t dataPos);
+
+/* obtain code that will be shared among programs */
+extern void nv50_ir_get_target_library(uint32_t chipset,
+ const uint32_t **code, uint32_t *size);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // __NV50_IR_DRIVER_H__
diff --git a/src/gallium/drivers/nv50/codegen/nv50_ir_emit_nv50.cpp b/src/gallium/drivers/nv50/codegen/nv50_ir_emit_nv50.cpp
new file mode 100644
index 00000000000..0a61a1ddaef
--- /dev/null
+++ b/src/gallium/drivers/nv50/codegen/nv50_ir_emit_nv50.cpp
@@ -0,0 +1,1333 @@
+
+#include "nv50_ir.h"
+#include "nv50_ir_target.h"
+
+namespace nv50_ir {
+
+class CodeEmitterNV50 : public CodeEmitter
+{
+public:
+ CodeEmitterNV50(const Target *);
+
+ virtual bool emitInstruction(Instruction *);
+
+ virtual uint32_t getMinEncodingSize(const Instruction *) const;
+
+ inline void setProgramType(Program::Type pType) { progType = pType; }
+
+private:
+ const Target *targ;
+
+ Program::Type progType;
+
+private:
+ inline void defId(const ValueDef&, const int pos);
+ inline void srcId(const ValueRef&, const int pos);
+ inline void srcId(const ValueRef *, const int pos);
+
+ inline void srcAddr16(const ValueRef&, const int pos);
+ inline void srcAddr8(const ValueRef&, const int pos);
+
+ void emitFlagsRd(const Instruction *);
+ void emitFlagsWr(const Instruction *);
+
+ void emitCondCode(CondCode cc, int pos);
+
+ inline void setARegBits(unsigned int);
+
+ void setAReg16(const Instruction *, int s);
+ void setImmediate(const Instruction *, int s);
+
+ void setDst(const Value *);
+ void setDst(const Instruction *, int d);
+ void emitSrc0(const ValueRef&);
+ void emitSrc1(const ValueRef&);
+ void emitSrc2(const ValueRef&);
+
+ void emitForm_MAD(const Instruction *);
+ void emitForm_ADD(const Instruction *);
+ void emitForm_MUL(const Instruction *);
+ void emitForm_IMM(const Instruction *);
+
+ void emitLoadStoreSize(DataType ty, int pos);
+
+ void roundMode_MAD(const Instruction *);
+ void roundMode_CVT(RoundMode);
+
+ void emitMNeg12(const Instruction *);
+
+ void emitLOAD(const Instruction *);
+ void emitSTORE(const Instruction *);
+ void emitMOV(const Instruction *);
+ void emitNOP();
+ void emitINTERP(const Instruction *);
+ void emitPFETCH(const Instruction *);
+ void emitOUT(const Instruction *);
+
+ void emitUADD(const Instruction *);
+ void emitAADD(const Instruction *);
+ void emitFADD(const Instruction *);
+ void emitUMUL(const Instruction *);
+ void emitFMUL(const Instruction *);
+ void emitFMAD(const Instruction *);
+
+ void emitMINMAX(const Instruction *);
+
+ void emitPreOp(const Instruction *);
+ void emitSFnOp(const Instruction *, uint8_t subOp);
+
+ void emitShift(const Instruction *);
+ void emitARL(const Instruction *);
+ void emitLogicOp(const Instruction *);
+
+ void emitCVT(const Instruction *);
+ void emitSET(const Instruction *);
+
+ void emitTEX(const TexInstruction *);
+
+ void emitQUADOP(const Instruction *, uint8_t lane, uint8_t quOp);
+
+ void emitFlow(const Instruction *, uint8_t flowOp);
+};
+
+#define SDATA(a) ((a).rep()->reg.data)
+#define DDATA(a) ((a).rep()->reg.data)
+
+void CodeEmitterNV50::srcId(const ValueRef& src, const int pos)
+{
+ assert(src.get());
+ code[pos / 32] |= SDATA(src).id << (pos % 32);
+}
+
+void CodeEmitterNV50::srcId(const ValueRef *src, const int pos)
+{
+ assert(src->get());
+ code[pos / 32] |= SDATA(*src).id << (pos % 32);
+}
+
+void CodeEmitterNV50::srcAddr16(const ValueRef& src, const int pos)
+{
+ assert(src.get());
+
+ uint32_t offset = SDATA(src).offset;
+
+ assert(offset <= 0xffff && (pos % 32) <= 16);
+
+ code[pos / 32] |= offset << (pos % 32);
+}
+
+void CodeEmitterNV50::srcAddr8(const ValueRef& src, const int pos)
+{
+ assert(src.get());
+
+ uint32_t offset = SDATA(src).offset;
+
+ assert(offset <= 0x1fc && !(offset & 0x3));
+
+ code[pos / 32] |= (offset >> 2) << (pos % 32);
+}
+
+void CodeEmitterNV50::defId(const ValueDef& def, const int pos)
+{
+ assert(def.get());
+ code[pos / 32] |= DDATA(def).id << (pos % 32);
+}
+
+void
+CodeEmitterNV50::roundMode_MAD(const Instruction *insn)
+{
+ switch (insn->rnd) {
+ case ROUND_M: code[1] |= 1 << 22; break;
+ case ROUND_P: code[1] |= 2 << 22; break;
+ case ROUND_Z: code[1] |= 3 << 22; break;
+ default:
+ assert(insn->rnd == ROUND_N);
+ break;
+ }
+}
+
+void
+CodeEmitterNV50::emitMNeg12(const Instruction *i)
+{
+ code[1] |= i->src[0].mod.neg() << 26;
+ code[1] |= i->src[1].mod.neg() << 27;
+}
+
+void CodeEmitterNV50::emitCondCode(CondCode cc, int pos)
+{
+ uint8_t enc;
+
+ assert(pos >= 32 || pos <= 27);
+
+ switch (cc) {
+ case CC_LT: enc = 0x1; break;
+ case CC_LTU: enc = 0x9; break;
+ case CC_EQ: enc = 0x2; break;
+ case CC_EQU: enc = 0xa; break;
+ case CC_LE: enc = 0x3; break;
+ case CC_LEU: enc = 0xb; break;
+ case CC_GT: enc = 0x4; break;
+ case CC_GTU: enc = 0xc; break;
+ case CC_NE: enc = 0x5; break;
+ case CC_NEU: enc = 0xd; break;
+ case CC_GE: enc = 0x6; break;
+ case CC_GEU: enc = 0xe; break;
+ case CC_TR: enc = 0xf; break;
+ case CC_FL: enc = 0x0; break;
+
+ case CC_O: enc = 0x10; break;
+ case CC_C: enc = 0x11; break;
+ case CC_A: enc = 0x12; break;
+ case CC_S: enc = 0x13; break;
+ case CC_NS: enc = 0x1c; break;
+ case CC_NA: enc = 0x1d; break;
+ case CC_NC: enc = 0x1e; break;
+ case CC_NO: enc = 0x1f; break;
+
+ default:
+ enc = 0;
+ assert(!"invalid condition code");
+ break;
+ }
+ code[pos / 32] |= enc << (pos % 32);
+}
+
+void
+CodeEmitterNV50::emitFlagsRd(const Instruction *i)
+{
+ int s = (i->flagsSrc >= 0) ? i->flagsSrc : i->predSrc;
+
+ assert(!(code[1] & 0x00003f80));
+
+ if (s >= 0) {
+ assert(i->getSrc(s)->reg.file == FILE_FLAGS);
+ emitCondCode(i->cc, 32 + 7);
+ srcId(i->src[s], 32 + 12);
+ } else {
+ code[1] |= 0x0780;
+ }
+}
+
+void
+CodeEmitterNV50::emitFlagsWr(const Instruction *i)
+{
+ assert(!(code[1] & 0x70));
+
+ if (i->flagsDef >= 0)
+ code[1] |= (DDATA(i->def[i->flagsDef]).id << 4) | 0x40;
+}
+
+void
+CodeEmitterNV50::setARegBits(unsigned int u)
+{
+ code[0] |= (u & 3) << 26;
+ code[1] |= (u & 4);
+}
+
+void
+CodeEmitterNV50::setAReg16(const Instruction *i, int s)
+{
+ s = i->src[s].indirect[0];
+ if (s >= 0)
+ setARegBits(SDATA(i->src[s]).id + 1);
+}
+
+void
+CodeEmitterNV50::setImmediate(const Instruction *i, int s)
+{
+ const ImmediateValue *imm = i->src[s].get()->asImm();
+ assert(imm);
+
+ code[1] |= 3;
+ code[0] |= (imm->reg.data.u32 & 0x3f) << 16;
+ code[1] |= (imm->reg.data.u32 >> 6) << 2;
+}
+
+void
+CodeEmitterNV50::setDst(const Value *dst)
+{
+ const Storage *reg = &dst->join->reg;
+
+ assert(reg->file != FILE_ADDRESS);
+
+ if (reg->data.id < 0) {
+ code[0] |= (127 << 2) | 1;
+ code[1] |= 8;
+ } else {
+ if (reg->file == FILE_SHADER_OUTPUT)
+ code[1] |= 8;
+ code[0] |= reg->data.id << 2;
+ }
+}
+
+void
+CodeEmitterNV50::setDst(const Instruction *i, int d)
+{
+ if (i->defExists(d)) {
+ setDst(i->getDef(d));
+ } else
+ if (!d) {
+ code[0] |= 0x01fc; // bit bucket
+ code[1] |= 0x0008;
+ }
+}
+
+void
+CodeEmitterNV50::emitSrc0(const ValueRef& ref)
+{
+ const Storage *reg = &ref.rep()->reg;
+
+ if (reg->file == FILE_SHADER_INPUT)
+ code[1] |= 0x00200000;
+ else
+ if (reg->file != FILE_GPR)
+ ERROR("invalid src0 register file: %d\n", reg->file);
+
+ assert(reg->data.id < 128);
+ code[0] |= reg->data.id << 9;
+}
+
+void
+CodeEmitterNV50::emitSrc1(const ValueRef& ref)
+{
+ const Storage *reg = &ref.rep()->reg;
+
+ if (reg->file == FILE_MEMORY_CONST) {
+ assert(!(code[1] & 0x01800000));
+ code[0] |= 1 << 23;
+ code[1] |= reg->fileIndex << 22;
+ } else
+ if (reg->file != FILE_GPR) {
+ ERROR("invalid src1 register file: %d\n", reg->file);
+ }
+
+ assert(reg->data.id < 128);
+ code[0] |= reg->data.id << 16;
+}
+
+void
+CodeEmitterNV50::emitSrc2(const ValueRef& ref)
+{
+ const Storage *reg = &ref.rep()->reg;
+
+ if (reg->file == FILE_MEMORY_CONST) {
+ assert(!(code[1] & 0x01800000));
+ code[0] |= 1 << 24;
+ code[1] |= reg->fileIndex << 22;
+ } else
+ if (reg->file != FILE_GPR) {
+ ERROR("invalid src1 register file: %d\n", reg->file);
+ }
+
+ assert(reg->data.id < 128);
+ code[1] |= reg->data.id << 14;
+}
+
+// the default form:
+// - long instruction
+// - 1 to 3 sources in slots 0, 1, 2
+// - address & flags
+void
+CodeEmitterNV50::emitForm_MAD(const Instruction *i)
+{
+ assert(i->encSize == 8);
+ code[0] |= 1;
+
+ emitFlagsRd(i);
+ emitFlagsWr(i);
+
+ setDst(i, 0);
+
+ if (i->srcExists(0))
+ emitSrc0(i->src[0]);
+
+ if (i->srcExists(1))
+ emitSrc1(i->src[1]);
+
+ if (i->srcExists(2))
+ emitSrc2(i->src[2]);
+
+ setAReg16(i, 1);
+}
+
+// like default form, but 2nd source in slot 2, and no 3rd source
+void
+CodeEmitterNV50::emitForm_ADD(const Instruction *i)
+{
+ assert(i->encSize == 8);
+ code[0] |= 1;
+
+ emitFlagsRd(i);
+ emitFlagsWr(i);
+
+ setDst(i, 0);
+
+ if (i->srcExists(0))
+ emitSrc0(i->src[0]);
+
+ if (i->srcExists(1))
+ emitSrc2(i->src[1]);
+
+ setAReg16(i, 1);
+}
+
+// default short form
+void
+CodeEmitterNV50::emitForm_MUL(const Instruction *i)
+{
+ assert(i->encSize == 4 && !(code[0] & 1));
+ assert(i->defExists(0));
+ assert(!i->getPredicate());
+
+ setDst(i, 0);
+
+ if (i->srcExists(0))
+ emitSrc0(i->src[0]);
+
+ if (i->srcExists(1))
+ emitSrc1(i->src[1]);
+}
+
+// usual immediate form
+// - 1 to 3 sources where last is immediate
+// - no address or predicate possible
+void
+CodeEmitterNV50::emitForm_IMM(const Instruction *i)
+{
+ assert(i->encSize == 8);
+ code[0] |= 1;
+
+ assert(i->defExists(0) && i->srcExists(0));
+
+ setDst(i, 0);
+
+ if (i->srcExists(2)) {
+ emitSrc0(i->src[0]);
+ emitSrc1(i->src[1]);
+ setImmediate(i, 2);
+ } else
+ if (i->srcExists(1)) {
+ emitSrc0(i->src[0]);
+ setImmediate(i, 1);
+ } else {
+ setImmediate(i, 0);
+ }
+}
+
+void
+CodeEmitterNV50::emitLoadStoreSize(DataType ty, int pos)
+{
+ uint8_t enc;
+
+ switch (ty) {
+ case TYPE_F32: // fall through
+ case TYPE_S32: // fall through
+ case TYPE_U32: enc = 0x6; break;
+ case TYPE_B128: enc = 0x5; break;
+ case TYPE_F64: enc = 0x4; break;
+ case TYPE_S16: enc = 0x3; break;
+ case TYPE_U16: enc = 0x2; break;
+ case TYPE_S8: enc = 0x1; break;
+ case TYPE_U8: enc = 0x0; break;
+ default:
+ enc = 0;
+ assert(!"invalid load/store type");
+ break;
+ }
+ code[pos / 32] |= enc << (pos % 32);
+}
+
+void
+CodeEmitterNV50::emitLOAD(const Instruction *i)
+{
+ DataFile sf = i->src[0].getFile();
+
+ switch (sf) {
+ case FILE_SHADER_INPUT:
+ code[0] = 0x10000001;
+ code[1] = 0x04200000 | (i->lanes << 14);
+ break;
+ case FILE_MEMORY_CONST:
+ code[0] = 0x10000001;
+ code[1] = 0x24000000 | (i->getSrc(0)->reg.fileIndex << 22);
+ break;
+ case FILE_MEMORY_LOCAL:
+ code[0] = 0xd0000001;
+ code[1] = 0x40000000;
+ break;
+ case FILE_MEMORY_GLOBAL:
+ code[0] = 0xd0000001 | (i->getSrc(0)->reg.fileIndex << 16);
+ code[1] = 0x80000000;
+ break;
+ default:
+ assert(!"invalid load source file");
+ break;
+ }
+ if (sf == FILE_MEMORY_LOCAL ||
+ sf == FILE_MEMORY_GLOBAL)
+ emitLoadStoreSize(i->sType, 21 + 32);
+
+ setDst(i, 0);
+
+ emitFlagsRd(i);
+ emitFlagsWr(i);
+
+ if (i->src[0].getFile() == FILE_MEMORY_GLOBAL) {
+ srcId(*i->src[0].getIndirect(0), 9);
+ } else {
+ setAReg16(i, 0);
+ srcAddr16(i->src[0], 9);
+ }
+}
+
+void
+CodeEmitterNV50::emitSTORE(const Instruction *i)
+{
+ DataFile f = i->getSrc(0)->reg.file;
+ int32_t offset = i->getSrc(0)->reg.data.offset;
+
+ switch (f) {
+ case FILE_SHADER_OUTPUT:
+ code[0] = 0x00000001 | ((offset >> 2) << 2);
+ code[1] = 0x80c00000;
+ srcId(i->src[1], 32 + 15);
+ break;
+ case FILE_MEMORY_GLOBAL:
+ code[0] = 0xd0000000;
+ code[1] = 0xa0000000;
+ emitLoadStoreSize(i->dType, 21 + 32);
+ break;
+ case FILE_MEMORY_LOCAL:
+ code[0] = 0xd0000001;
+ code[1] = 0x60000000;
+ emitLoadStoreSize(i->dType, 21 + 32);
+ break;
+ case FILE_MEMORY_SHARED:
+ code[0] = 0x00000001;
+ code[1] = 0xe0000000;
+ switch (typeSizeof(i->dType)) {
+ case 1:
+ code[0] |= offset << 9;
+ code[1] |= 0x00400000;
+ break;
+ case 2:
+ code[0] |= (offset >> 1) << 9;
+ break;
+ case 4:
+ code[0] |= (offset >> 2) << 9;
+ code[1] |= 0x04000000;
+ break;
+ default:
+ assert(0);
+ break;
+ }
+ break;
+ default:
+ assert(!"invalid store destination file");
+ break;
+ }
+
+ if (f != FILE_SHADER_OUTPUT) {
+ srcId(i->src[1], 2);
+ if (f == FILE_MEMORY_GLOBAL)
+ srcId(*i->src[0].getIndirect(0), 9);
+ if (f == FILE_MEMORY_LOCAL)
+ srcAddr16(i->src[0], 9);
+ }
+ if (f != FILE_MEMORY_GLOBAL)
+ setAReg16(i, 0);
+
+ emitFlagsRd(i);
+}
+
+void
+CodeEmitterNV50::emitMOV(const Instruction *i)
+{
+ DataFile sf = i->getSrc(0)->reg.file;
+ DataFile df = i->getDef(0)->reg.file;
+
+ assert(sf == FILE_GPR || df == FILE_GPR);
+
+ if (sf == FILE_FLAGS) {
+ code[0] = 0x00000001;
+ code[1] = 0x20000000;
+ defId(i->def[0], 2);
+ srcId(i->src[0], 12);
+ emitFlagsRd(i);
+ } else
+ if (sf == FILE_ADDRESS) {
+ code[0] = 0x00000001;
+ code[1] = 0x40000000;
+ defId(i->def[0], 2);
+ setARegBits(SDATA(i->src[0]).id + 1);
+ } else
+ if (df == FILE_FLAGS) {
+ code[0] = 0x00000001;
+ code[1] = 0xa0000000;
+ defId(i->def[0], 4);
+ srcId(i->src[0], 9);
+ emitFlagsRd(i);
+ } else
+ if (sf == FILE_IMMEDIATE) {
+ code[0] = 0x10008001;
+ code[1] = 0x00000003;
+ emitForm_IMM(i);
+ } else {
+ if (i->encSize == 4) {
+ code[0] = 0x10008000;
+ } else {
+ code[0] = 0x10000001;
+ code[1] = 0x04000000 | (i->lanes << 14);
+ }
+ defId(i->def[0], 2);
+ srcId(i->src[0], 9);
+ }
+ if (df == FILE_SHADER_OUTPUT) {
+ assert(i->encSize == 8);
+ code[1] |= 0x8;
+ }
+}
+
+void
+CodeEmitterNV50::emitNOP()
+{
+ code[0] = 0xf0000001;
+ code[1] = 0xe0000000;
+}
+
+void
+CodeEmitterNV50::emitQUADOP(const Instruction *i, uint8_t lane, uint8_t quOp)
+{
+ code[0] = 0xc0000000 | (lane << 16);
+ code[1] = 0x80000000;
+
+ code[0] |= (quOp & 0x03) << 20;
+ code[1] |= (quOp & 0xfc) << 20;
+
+ emitForm_ADD(i);
+
+ if (!i->srcExists(1))
+ srcId(i->src[0], 32 + 14);
+}
+
+void
+CodeEmitterNV50::emitPFETCH(const Instruction *i)
+{
+ code[0] = 0x11800001;
+ code[1] = 0x04200000 | (0xf << 14);
+
+ defId(i->def[0], 2);
+ srcAddr8(i->src[0], 9);
+ setAReg16(i, 0);
+}
+
+void
+CodeEmitterNV50::emitINTERP(const Instruction *i)
+{
+ code[0] = 0x80000000;
+
+ defId(i->def[0], 2);
+ srcAddr8(i->src[0], 16);
+
+ if (i->getInterpMode() == NV50_IR_INTERP_FLAT) {
+ code[0] |= 1 << 8;
+ } else {
+ if (i->op == OP_PINTERP) {
+ code[0] |= 1 << 25;
+ srcId(i->src[1], 9);
+ }
+ if (i->getSampleMode() == NV50_IR_INTERP_CENTROID)
+ code[0] |= 1 << 24;
+ }
+
+ if (i->encSize == 8) {
+ emitFlagsRd(i);
+ code[1] |=
+ (code[0] & (3 << 24)) >> (24 - 16) |
+ (code[0] & (1 << 8)) >> (18 - 8);
+ code[0] &= ~0x03000100;
+ code[0] |= 1;
+ }
+}
+
+void
+CodeEmitterNV50::emitMINMAX(const Instruction *i)
+{
+ if (i->dType == TYPE_F64) {
+ code[0] = 0xe0000000;
+ code[1] = (i->op == OP_MIN) ? 0xa0000000 : 0xc0000000;
+ } else {
+ code[0] = 0x30000000;
+ code[1] = 0x80000000;
+ if (i->op == OP_MIN)
+ code[1] |= 0x20000000;
+
+ switch (i->dType) {
+ case TYPE_F32: code[0] |= 0x80000000; break;
+ case TYPE_S32: code[1] |= 0x8c000000; break;
+ case TYPE_U32: code[1] |= 0x84000000; break;
+ case TYPE_S16: code[1] |= 0x80000000; break;
+ case TYPE_U16: break;
+ default:
+ assert(0);
+ break;
+ }
+ code[1] |= i->src[0].mod.abs() << 20;
+ code[1] |= i->src[1].mod.abs() << 19;
+ }
+ emitForm_MAD(i);
+}
+
+void
+CodeEmitterNV50::emitFMAD(const Instruction *i)
+{
+ const int neg_mul = i->src[0].mod.neg() ^ i->src[1].mod.neg();
+ const int neg_add = i->src[2].mod.neg();
+
+ code[0] = 0xe0000000;
+
+ if (i->encSize == 4) {
+ emitForm_MUL(i);
+ assert(!neg_mul && !neg_add);
+ } else {
+ emitForm_MAD(i);
+ code[1] |= neg_mul << 26;
+ code[1] |= neg_add << 27;
+ if (i->saturate)
+ code[1] |= 1 << 29;
+ }
+}
+
+void
+CodeEmitterNV50::emitFADD(const Instruction *i)
+{
+ const int neg0 = i->src[0].mod.neg();
+ const int neg1 = i->src[1].mod.neg() ^ ((i->op == OP_SUB) ? 1 : 0);
+
+ code[0] = 0xb0000000;
+
+ assert(!(i->src[0].mod | i->src[1].mod).abs());
+
+ if (i->src[1].getFile() == FILE_IMMEDIATE) {
+ emitForm_IMM(i);
+ code[0] |= neg0 << 15;
+ code[0] |= neg1 << 22;
+ } else
+ if (i->encSize == 8) {
+ emitForm_ADD(i);
+ code[1] |= neg0 << 26;
+ code[1] |= neg1 << 27;
+ if (i->saturate)
+ code[1] |= 1 << 29;
+ } else {
+ emitForm_MUL(i);
+ code[0] |= neg0 << 15;
+ code[0] |= neg1 << 22;
+ }
+}
+
+void
+CodeEmitterNV50::emitUADD(const Instruction *i)
+{
+ code[0] = 0x20008000;
+
+ if (i->src[0].getFile() == FILE_IMMEDIATE) {
+ emitForm_IMM(i);
+ } else
+ if (i->encSize == 8) {
+ code[0] = 0x20000000;
+ code[1] = 0x04000000;
+ emitForm_ADD(i);
+ } else {
+ emitForm_MUL(i);
+ }
+ assert(!(i->src[0].mod.neg() && i->src[1].mod.neg()));
+ code[0] |= i->src[0].mod.neg() << 28;
+ code[0] |= i->src[1].mod.neg() << 22;
+}
+
+void
+CodeEmitterNV50::emitAADD(const Instruction *i)
+{
+ const int s = (i->op == OP_MOV) ? 0 : 1;
+
+ code[0] = 0xd0000001 | (i->getSrc(s)->reg.data.u16 << 9);
+ code[1] = 0x20000000;
+
+ code[0] |= (DDATA(i->def[0]).id + 1) << 2;
+
+ emitFlagsRd(i);
+
+ if (s && i->srcExists(0))
+ setARegBits(SDATA(i->src[0]).id + 1);
+}
+
+void
+CodeEmitterNV50::emitFMUL(const Instruction *i)
+{
+ const int neg = (i->src[0].mod ^ i->src[1].mod).neg();
+
+ code[0] = 0xc0000000;
+
+ if (i->src[0].getFile() == FILE_IMMEDIATE) {
+ emitForm_IMM(i);
+ if (neg)
+ code[0] |= 0x8000;
+ } else
+ if (i->encSize == 8) {
+ emitForm_MAD(i);
+ if (neg)
+ code[1] |= 0x08000000;
+ } else {
+ emitForm_MUL(i);
+ if (neg)
+ code[0] |= 0x8000;
+ }
+}
+
+void
+CodeEmitterNV50::emitSET(const Instruction *i)
+{
+ code[0] = 0x30000000;
+ code[1] = 0x60000000;
+
+ emitCondCode(i->asCmp()->setCond, 32 + 14);
+
+ switch (i->sType) {
+ case TYPE_F32: code[0] |= 0x80000000; break;
+ case TYPE_S32: code[1] |= 0x0c000000; break;
+ case TYPE_U32: code[1] |= 0x04000000; break;
+ case TYPE_S16: code[1] |= 0x08000000; break;
+ case TYPE_U16: break;
+ default:
+ assert(0);
+ break;
+ }
+ emitForm_MAD(i);
+}
+
+void
+CodeEmitterNV50::roundMode_CVT(RoundMode rnd)
+{
+ switch (rnd) {
+ case ROUND_NI: code[1] |= 0x08000000; break;
+ case ROUND_M: code[1] |= 0x00020000; break;
+ case ROUND_MI: code[1] |= 0x08020000; break;
+ case ROUND_P: code[1] |= 0x00040000; break;
+ case ROUND_PI: code[1] |= 0x08040000; break;
+ case ROUND_Z: code[1] |= 0x00060000; break;
+ case ROUND_ZI: code[1] |= 0x08060000; break;
+ default:
+ assert(rnd == ROUND_N);
+ break;
+ }
+}
+
+void
+CodeEmitterNV50::emitCVT(const Instruction *i)
+{
+ const bool f2f = isFloatType(i->dType) && isFloatType(i->sType);
+ RoundMode rnd;
+
+ switch (i->op) {
+ case OP_CEIL: rnd = f2f ? ROUND_PI : ROUND_P; break;
+ case OP_FLOOR: rnd = f2f ? ROUND_MI : ROUND_M; break;
+ case OP_TRUNC: rnd = f2f ? ROUND_ZI : ROUND_Z; break;
+ default:
+ rnd = i->rnd;
+ break;
+ }
+
+ code[0] = 0xa0000000;
+
+ switch (i->dType) {
+ case TYPE_F64:
+ switch (i->sType) {
+ case TYPE_F64: code[1] = 0xc4404000; break;
+ case TYPE_S64: code[1] = 0x44414000; break;
+ case TYPE_U64: code[1] = 0x44404000; break;
+ case TYPE_F32: code[1] = 0xc4400000; break;
+ case TYPE_S32: code[1] = 0x44410000; break;
+ case TYPE_U32: code[1] = 0x44400000; break;
+ default:
+ assert(0);
+ break;
+ }
+ break;
+ case TYPE_S64:
+ switch (i->sType) {
+ case TYPE_F64: code[1] = 0x8c404000; break;
+ case TYPE_F32: code[1] = 0x8c400000; break;
+ default:
+ assert(0);
+ break;
+ }
+ break;
+ case TYPE_U64:
+ switch (i->sType) {
+ case TYPE_F64: code[1] = 0x84404000; break;
+ case TYPE_F32: code[1] = 0x84400000; break;
+ default:
+ assert(0);
+ break;
+ }
+ break;
+ case TYPE_F32:
+ switch (i->sType) {
+ case TYPE_F64: code[1] = 0xc0404000; break;
+ case TYPE_S64: code[1] = 0x40414000; break;
+ case TYPE_U64: code[1] = 0x40404000; break;
+ case TYPE_F32: code[1] = 0xc4004000; break;
+ case TYPE_S32: code[1] = 0x44014000; break;
+ case TYPE_U32: code[1] = 0x44004000; break;
+ case TYPE_F16: code[1] = 0xc4000000; break;
+ default:
+ assert(0);
+ break;
+ }
+ break;
+ case TYPE_S32:
+ switch (i->sType) {
+ case TYPE_F64: code[1] = 0x88404000; break;
+ case TYPE_F32: code[1] = 0x8c004000; break;
+ case TYPE_S32: code[1] = 0x0c014000; break;
+ case TYPE_U32: code[1] = 0x0c004000; break;
+ case TYPE_F16: code[1] = 0x8c000000; break;
+ case TYPE_S16: code[1] = 0x0c010000; break;
+ case TYPE_U16: code[1] = 0x0c000000; break;
+ case TYPE_S8: code[1] = 0x0c018000; break;
+ case TYPE_U8: code[1] = 0x0c008000; break;
+ default:
+ assert(0);
+ break;
+ }
+ break;
+ case TYPE_U32:
+ switch (i->sType) {
+ case TYPE_F64: code[1] = 0x80404000; break;
+ case TYPE_F32: code[1] = 0x84004000; break;
+ case TYPE_S32: code[1] = 0x04014000; break;
+ case TYPE_U32: code[1] = 0x04004000; break;
+ case TYPE_F16: code[1] = 0x84000000; break;
+ case TYPE_S16: code[1] = 0x04010000; break;
+ case TYPE_U16: code[1] = 0x04000000; break;
+ case TYPE_S8: code[1] = 0x04018000; break;
+ case TYPE_U8: code[1] = 0x04008000; break;
+ default:
+ assert(0);
+ break;
+ }
+ case TYPE_S16:
+ case TYPE_U16:
+ case TYPE_S8:
+ case TYPE_U8:
+ default:
+ assert(0);
+ break;
+ }
+ if (typeSizeof(i->sType) == 1 && i->getSrc(0)->reg.size == 4)
+ code[1] |= 0x00004000;
+
+ roundMode_CVT(rnd);
+
+ switch (i->op) {
+ case OP_ABS: code[1] |= 1 << 20; break;
+ case OP_SAT: code[1] |= 1 << 19; break;
+ case OP_NEG: code[1] |= 1 << 29; break;
+ default:
+ break;
+ }
+ code[1] ^= i->src[0].mod.neg() << 29;
+ code[1] |= i->src[0].mod.abs() << 20;
+ if (i->saturate)
+ code[1] |= 1 << 19;
+
+ assert(i->op != OP_ABS || !i->src[0].mod.neg());
+
+ emitForm_MAD(i);
+}
+
+void
+CodeEmitterNV50::emitPreOp(const Instruction *i)
+{
+ code[0] = 0xb0000000;
+ code[1] = (i->op == OP_PREEX2) ? 0xc0004000 : 0xc0000000;
+
+ code[1] |= i->src[0].mod.abs() << 20;
+ code[1] |= i->src[0].mod.neg() << 26;
+
+ emitForm_MAD(i);
+}
+
+void
+CodeEmitterNV50::emitSFnOp(const Instruction *i, uint8_t subOp)
+{
+ code[0] = 0x90000000;
+
+ if (i->encSize == 4) {
+ assert(i->op == OP_RCP);
+ emitForm_MUL(i);
+ } else {
+ code[1] = subOp << 29;
+ code[1] |= i->src[0].mod.abs() << 20;
+ code[1] |= i->src[0].mod.neg() << 26;
+ emitForm_MAD(i);
+ }
+}
+
+void
+CodeEmitterNV50::emitLogicOp(const Instruction *i)
+{
+ code[0] = 0xd0000000;
+
+ if (i->src[1].getFile() == FILE_IMMEDIATE) {
+ switch (i->op) {
+ case OP_OR: code[0] |= 0x0100; break;
+ case OP_XOR: code[0] |= 0x8000; break;
+ default:
+ assert(i->op == OP_AND);
+ break;
+ }
+ emitForm_IMM(i);
+ } else {
+ switch (i->op) {
+ case OP_AND: code[1] = 0x04000000; break;
+ case OP_OR: code[1] = 0x04004000; break;
+ case OP_XOR: code[1] = 0x04008000; break;
+ default:
+ assert(0);
+ break;
+ }
+ emitForm_MAD(i);
+ }
+}
+
+void
+CodeEmitterNV50::emitARL(const Instruction *i)
+{
+ assert(i->src[1].getFile() == FILE_IMMEDIATE);
+
+ code[0] = 0x00000001 | (i->getSrc(1)->reg.data.u32 & 0x3f) << 16;
+ code[1] = 0xc0000000;
+
+ code[0] |= (DDATA(i->def[0]).id + 1) << 2;
+ emitSrc0(i->src[0]);
+ emitFlagsRd(i);
+}
+
+void
+CodeEmitterNV50::emitShift(const Instruction *i)
+{
+ if (i->def[0].getFile() == FILE_ADDRESS) {
+ emitARL(i);
+ } else {
+ code[0] = 0x30000001;
+ code[1] = (i->op == OP_SHR) ? 0xe4000000 : 0xc4000000;
+ if (isSignedType(i->sType))
+ code[1] |= 1 << 27;
+
+ if (i->src[1].getFile() == FILE_IMMEDIATE) {
+ code[1] |= 1 << 20;
+ code[0] |= (i->getSrc(1)->reg.data.u32 & 0x7f) << 16;
+ emitFlagsRd(i);
+ } else {
+ emitForm_MAD(i);
+ }
+ }
+}
+
+void
+CodeEmitterNV50::emitOUT(const Instruction *i)
+{
+ code[0] = (i->op == OP_EMIT) ? 0xf0000200 : 0xf0000400;
+ code[1] = 0xc0000001;
+
+ emitFlagsRd(i);
+}
+
+void
+CodeEmitterNV50::emitTEX(const TexInstruction *i)
+{
+ code[0] = 0xf0000001;
+ code[1] = 0x00000000;
+
+ switch (i->op) {
+ case OP_TXB:
+ code[1] = 0x20000000;
+ break;
+ case OP_TXL:
+ code[1] = 0x40000000;
+ break;
+ case OP_TXF:
+ code[0] = 0x01000000;
+ break;
+ case OP_TXG:
+ code[0] = 0x01000000;
+ code[1] = 0x80000000;
+ break;
+ default:
+ assert(i->op == OP_TEX);
+ break;
+ }
+
+ code[0] |= i->tex.r << 9;
+ code[0] |= i->tex.s << 17;
+
+ int argc = i->tex.target.getArgCount();
+
+ if (i->op == OP_TXB || i->op == OP_TXL)
+ argc += 1;
+ if (i->tex.target.isShadow())
+ argc += 1;
+ assert(argc <= 4);
+
+ code[0] |= (argc - 1) << 22;
+
+ if (i->tex.target.isCube()) {
+ code[0] |= 0x08000000;
+ } else
+ if (i->tex.useOffsets) {
+ code[1] |= (i->tex.offset[0][0] & 0xf) << 16;
+ code[1] |= (i->tex.offset[0][1] & 0xf) << 20;
+ code[1] |= (i->tex.offset[0][2] & 0xf) << 24;
+ }
+
+ code[0] |= (i->tex.mask & 0x3) << 25;
+ code[1] |= (i->tex.mask & 0xc) << 12;
+
+ if (i->tex.liveOnly)
+ code[1] |= 4;
+
+ defId(i->def[0], 2);
+
+ emitFlagsRd(i);
+}
+
+void
+CodeEmitterNV50::emitFlow(const Instruction *i, uint8_t flowOp)
+{
+ const FlowInstruction *f = i->asFlow();
+
+ code[0] = 0x00000003 | (flowOp << 28);
+ code[1] = 0x00000000;
+
+ emitFlagsRd(i);
+
+ if (f && f->target.bb) {
+ uint32_t pos;
+
+ if (f->op == OP_CALL) {
+ if (f->builtin) {
+ pos = 0; // XXX: TODO
+ } else {
+ pos = f->target.fn->binPos;
+ }
+ } else {
+ pos = f->target.bb->binPos;
+ }
+
+ code[0] |= ((pos >> 2) & 0xffff) << 11;
+ code[1] |= ((pos >> 18) & 0x003f) << 14;
+ }
+}
+
+bool
+CodeEmitterNV50::emitInstruction(Instruction *insn)
+{
+ if (!insn->encSize) {
+ ERROR("skipping unencodable instruction: "); insn->print();
+ return false;
+ } else
+ if (codeSize + insn->encSize > codeSizeLimit) {
+ ERROR("code emitter output buffer too small\n");
+ return false;
+ }
+
+ switch (insn->op) {
+ case OP_MOV:
+ emitMOV(insn);
+ break;
+ case OP_NOP:
+ case OP_JOIN:
+ emitNOP();
+ break;
+ case OP_VFETCH:
+ case OP_LOAD:
+ emitLOAD(insn);
+ break;
+ case OP_EXPORT:
+ case OP_STORE:
+ emitSTORE(insn);
+ break;
+ case OP_PFETCH:
+ emitPFETCH(insn);
+ break;
+ case OP_LINTERP:
+ case OP_PINTERP:
+ emitINTERP(insn);
+ break;
+ case OP_ADD:
+ case OP_SUB:
+ if (isFloatType(insn->dType))
+ emitFADD(insn);
+ else
+ emitUADD(insn);
+ break;
+ case OP_MUL:
+ if (isFloatType(insn->dType))
+ emitFMUL(insn);
+ else
+ emitUMUL(insn);
+ break;
+ case OP_MAD:
+ case OP_FMA:
+ emitFMAD(insn);
+ break;
+ break;
+ case OP_AND:
+ case OP_OR:
+ case OP_XOR:
+ emitLogicOp(insn);
+ break;
+ case OP_MIN:
+ case OP_MAX:
+ emitMINMAX(insn);
+ break;
+ case OP_CEIL:
+ case OP_FLOOR:
+ case OP_TRUNC:
+ case OP_CVT:
+ emitCVT(insn);
+ break;
+ case OP_RCP:
+ emitSFnOp(insn, 0);
+ break;
+ case OP_RSQ:
+ emitSFnOp(insn, 2);
+ break;
+ case OP_LG2:
+ emitSFnOp(insn, 3);
+ break;
+ case OP_SIN:
+ emitSFnOp(insn, 4);
+ break;
+ case OP_COS:
+ emitSFnOp(insn, 5);
+ break;
+ case OP_EX2:
+ emitSFnOp(insn, 6);
+ break;
+ case OP_PRESIN:
+ case OP_PREEX2:
+ emitPreOp(insn);
+ break;
+ case OP_TEX:
+ case OP_TXB:
+ case OP_TXL:
+ emitTEX(insn->asTex());
+ break;
+ case OP_EMIT:
+ case OP_RESTART:
+ emitOUT(insn);
+ break;
+ case OP_DISCARD:
+ emitFlow(insn, 0x0);
+ break;
+ case OP_BRA:
+ emitFlow(insn, 0x1);
+ break;
+ case OP_CALL:
+ emitFlow(insn, 0x2);
+ break;
+ case OP_RET:
+ emitFlow(insn, 0x3);
+ break;
+ case OP_PREBREAK:
+ emitFlow(insn, 0x4);
+ break;
+ case OP_BREAK:
+ emitFlow(insn, 0x5);
+ break;
+ case OP_QUADON:
+ emitFlow(insn, 0x6);
+ break;
+ case OP_QUADPOP:
+ emitFlow(insn, 0x7);
+ break;
+ case OP_JOINAT:
+ emitFlow(insn, 0xa);
+ break;
+ case OP_PRERET:
+ emitFlow(insn, 0xd);
+ break;
+ case OP_QUADOP:
+ emitQUADOP(insn, insn->lanes, insn->subOp);
+ break;
+ case OP_DFDX:
+ emitQUADOP(insn, 4, insn->src[0].mod.neg() ? 0x66 : 0x99);
+ break;
+ case OP_DFDY:
+ emitQUADOP(insn, 5, insn->src[0].mod.neg() ? 0x5a : 0xa5);
+ break;
+ case OP_PHI:
+ case OP_UNION:
+ case OP_CONSTRAINT:
+ ERROR("operation should have been eliminated");
+ return false;
+ case OP_EXP:
+ case OP_LOG:
+ case OP_SQRT:
+ case OP_POW:
+ case OP_SELP:
+ case OP_SLCT:
+ case OP_TXD:
+ case OP_PRECONT:
+ case OP_CONT:
+ case OP_POPCNT:
+ case OP_INSBF:
+ case OP_EXTBF:
+ ERROR("operation should have been lowered\n");
+ return false;
+ default:
+ ERROR("unknow op\n");
+ return false;
+ }
+ if (insn->join)
+ code[1] |= 0x2;
+ else
+ if (insn->exit)
+ code[1] |= 0x1;
+
+ assert((insn->encSize == 8) == (code[1] & 1));
+
+ code += insn->encSize / 4;
+ codeSize += insn->encSize;
+ return true;
+}
+
+uint32_t
+CodeEmitterNV50::getMinEncodingSize(const Instruction *i) const
+{
+ const Target::OpInfo &info = targ->getOpInfo(i);
+
+ if (info.minEncSize == 8)
+ return 8;
+
+ return 4;
+}
+
+CodeEmitterNV50::CodeEmitterNV50(const Target *target) : targ(target)
+{
+ code = NULL;
+ codeSize = codeSizeLimit = 0;
+}
+
+CodeEmitter *
+Target::getCodeEmitter(Program::Type type)
+{
+ CodeEmitterNV50 *emit = new CodeEmitterNV50(this);
+ emit->setProgramType(type);
+ return emit;
+}
+
+} // namespace nv50_ir
diff --git a/src/gallium/drivers/nv50/codegen/nv50_ir_from_tgsi.cpp b/src/gallium/drivers/nv50/codegen/nv50_ir_from_tgsi.cpp
new file mode 100644
index 00000000000..c2f464de31b
--- /dev/null
+++ b/src/gallium/drivers/nv50/codegen/nv50_ir_from_tgsi.cpp
@@ -0,0 +1,2288 @@
+
+extern "C" {
+#include "tgsi/tgsi_dump.h"
+#include "tgsi/tgsi_scan.h"
+}
+
+#include "nv50_ir.h"
+#include "nv50_ir_util.h"
+#include "nv50_ir_build_util.h"
+
+namespace tgsi {
+
+class Source;
+
+static nv50_ir::operation translateOpcode(uint opcode);
+static nv50_ir::DataFile translateFile(uint file);
+static nv50_ir::TexTarget translateTexture(uint texTarg);
+static nv50_ir::SVSemantic translateSysVal(uint sysval);
+
+class Instruction
+{
+public:
+ Instruction(const struct tgsi_full_instruction *inst) : insn(inst) { }
+
+ class SrcRegister
+ {
+ public:
+ SrcRegister(const struct tgsi_full_src_register *src)
+ : reg(src->Register),
+ fsr(src)
+ { }
+
+ SrcRegister(const struct tgsi_src_register& src) : reg(src), fsr(NULL) { }
+
+ struct tgsi_src_register offsetToSrc(struct tgsi_texture_offset off)
+ {
+ struct tgsi_src_register reg;
+ memset(&reg, 0, sizeof(reg));
+ reg.Index = off.Index;
+ reg.File = off.File;
+ reg.SwizzleX = off.SwizzleX;
+ reg.SwizzleY = off.SwizzleY;
+ reg.SwizzleZ = off.SwizzleZ;
+ return reg;
+ }
+
+ SrcRegister(const struct tgsi_texture_offset& off) :
+ reg(offsetToSrc(off)),
+ fsr(NULL)
+ { }
+
+ uint getFile() const { return reg.File; }
+
+ bool is2D() const { return reg.Dimension; }
+
+ bool isIndirect(int dim) const
+ {
+ return (dim && fsr) ? fsr->Dimension.Indirect : reg.Indirect;
+ }
+
+ int getIndex(int dim) const
+ {
+ return (dim && fsr) ? fsr->Dimension.Index : reg.Index;
+ }
+
+ int getSwizzle(int chan) const
+ {
+ return tgsi_util_get_src_register_swizzle(&reg, chan);
+ }
+
+ nv50_ir::Modifier getMod(int chan) const;
+
+ SrcRegister getIndirect(int dim) const
+ {
+ assert(fsr && isIndirect(dim));
+ if (dim)
+ return SrcRegister(fsr->DimIndirect);
+ return SrcRegister(fsr->Indirect);
+ }
+
+ uint32_t getValueU32(int c, const struct nv50_ir_prog_info *info) const
+ {
+ assert(reg.File == TGSI_FILE_IMMEDIATE);
+ assert(!reg.Absolute);
+ assert(!reg.Negate);
+ return info->immd.data[reg.Index * 4 + getSwizzle(c)];
+ }
+
+ private:
+ const struct tgsi_src_register reg;
+ const struct tgsi_full_src_register *fsr;
+ };
+
+ class DstRegister
+ {
+ public:
+ DstRegister(const struct tgsi_full_dst_register *dst)
+ : reg(dst->Register),
+ fdr(dst)
+ { }
+
+ DstRegister(const struct tgsi_dst_register& dst) : reg(dst), fdr(NULL) { }
+
+ uint getFile() const { return reg.File; }
+
+ bool is2D() const { return reg.Dimension; }
+
+ bool isIndirect(int dim) const
+ {
+ return (dim && fdr) ? fdr->Dimension.Indirect : reg.Indirect;
+ }
+
+ int getIndex(int dim) const
+ {
+ return (dim && fdr) ? fdr->Dimension.Dimension : reg.Index;
+ }
+
+ unsigned int getMask() const { return reg.WriteMask; }
+
+ bool isMasked(int chan) const { return !(getMask() & (1 << chan)); }
+
+ SrcRegister getIndirect(int dim) const
+ {
+ assert(fdr && isIndirect(dim));
+ if (dim)
+ return SrcRegister(fdr->DimIndirect);
+ return SrcRegister(fdr->Indirect);
+ }
+
+ private:
+ const struct tgsi_dst_register reg;
+ const struct tgsi_full_dst_register *fdr;
+ };
+
+ inline uint getOpcode() const { return insn->Instruction.Opcode; }
+
+ unsigned int srcCount() const { return insn->Instruction.NumSrcRegs; }
+ unsigned int dstCount() const { return insn->Instruction.NumDstRegs; }
+
+ // mask of used components of source s
+ unsigned int srcMask(unsigned int s) const;
+
+ SrcRegister getSrc(unsigned int s) const
+ {
+ assert(s < srcCount());
+ return SrcRegister(&insn->Src[s]);
+ }
+
+ DstRegister getDst(unsigned int d) const
+ {
+ assert(d < dstCount());
+ return DstRegister(&insn->Dst[d]);
+ }
+
+ SrcRegister getTexOffset(unsigned int i) const
+ {
+ assert(i < TGSI_FULL_MAX_TEX_OFFSETS);
+ return SrcRegister(insn->TexOffsets[i]);
+ }
+
+ unsigned int getNumTexOffsets() const { return insn->Texture.NumOffsets; }
+
+ bool checkDstSrcAliasing() const;
+
+ inline nv50_ir::operation getOP() const {
+ return translateOpcode(getOpcode()); }
+
+ nv50_ir::DataType inferSrcType() const;
+ nv50_ir::DataType inferDstType() const;
+
+ nv50_ir::CondCode getSetCond() const;
+
+ nv50_ir::TexInstruction::Target getTexture(const Source *, int s) const;
+
+ inline uint getLabel() { return insn->Label.Label; }
+
+ unsigned getSaturate() const { return insn->Instruction.Saturate; }
+
+ void print() const
+ {
+ tgsi_dump_instruction(insn, 1);
+ }
+
+private:
+ const struct tgsi_full_instruction *insn;
+};
+
+unsigned int Instruction::srcMask(unsigned int s) const
+{
+ unsigned int mask = insn->Dst[0].Register.WriteMask;
+
+ switch (insn->Instruction.Opcode) {
+ case TGSI_OPCODE_COS:
+ case TGSI_OPCODE_SIN:
+ return (mask & 0x8) | ((mask & 0x7) ? 0x1 : 0x0);
+ case TGSI_OPCODE_DP3:
+ return 0x7;
+ case TGSI_OPCODE_DP4:
+ case TGSI_OPCODE_DPH:
+ case TGSI_OPCODE_KIL: /* WriteMask ignored */
+ return 0xf;
+ case TGSI_OPCODE_DST:
+ return mask & (s ? 0xa : 0x6);
+ case TGSI_OPCODE_EX2:
+ case TGSI_OPCODE_EXP:
+ case TGSI_OPCODE_LG2:
+ case TGSI_OPCODE_LOG:
+ case TGSI_OPCODE_POW:
+ case TGSI_OPCODE_RCP:
+ case TGSI_OPCODE_RSQ:
+ case TGSI_OPCODE_SCS:
+ return 0x1;
+ case TGSI_OPCODE_IF:
+ return 0x1;
+ case TGSI_OPCODE_LIT:
+ return 0xb;
+ case TGSI_OPCODE_TEX:
+ case TGSI_OPCODE_TXB:
+ case TGSI_OPCODE_TXD:
+ case TGSI_OPCODE_TXL:
+ case TGSI_OPCODE_TXP:
+ {
+ const struct tgsi_instruction_texture *tex = &insn->Texture;
+
+ assert(insn->Instruction.Texture);
+
+ mask = 0x7;
+ if (insn->Instruction.Opcode != TGSI_OPCODE_TEX &&
+ insn->Instruction.Opcode != TGSI_OPCODE_TXD)
+ mask |= 0x8; /* bias, lod or proj */
+
+ switch (tex->Texture) {
+ case TGSI_TEXTURE_1D:
+ mask &= 0x9;
+ break;
+ case TGSI_TEXTURE_SHADOW1D:
+ mask &= 0x5;
+ break;
+ case TGSI_TEXTURE_1D_ARRAY:
+ case TGSI_TEXTURE_2D:
+ case TGSI_TEXTURE_RECT:
+ mask &= 0xb;
+ break;
+ default:
+ break;
+ }
+ }
+ return mask;
+ case TGSI_OPCODE_XPD:
+ {
+ unsigned int x = 0;
+ if (mask & 1) x |= 0x6;
+ if (mask & 2) x |= 0x5;
+ if (mask & 4) x |= 0x3;
+ return x;
+ }
+ default:
+ break;
+ }
+
+ return mask;
+}
+
+nv50_ir::Modifier Instruction::SrcRegister::getMod(int chan) const
+{
+ nv50_ir::Modifier m(0);
+
+ if (reg.Absolute)
+ m = m | nv50_ir::Modifier(NV50_IR_MOD_ABS);
+ if (reg.Negate)
+ m = m | nv50_ir::Modifier(NV50_IR_MOD_NEG);
+ return m;
+}
+
+static nv50_ir::DataFile translateFile(uint file)
+{
+ switch (file) {
+ case TGSI_FILE_CONSTANT: return nv50_ir::FILE_MEMORY_CONST;
+ case TGSI_FILE_INPUT: return nv50_ir::FILE_SHADER_INPUT;
+ case TGSI_FILE_OUTPUT: return nv50_ir::FILE_SHADER_OUTPUT;
+ case TGSI_FILE_TEMPORARY: return nv50_ir::FILE_GPR;
+ case TGSI_FILE_ADDRESS: return nv50_ir::FILE_ADDRESS;
+ case TGSI_FILE_PREDICATE: return nv50_ir::FILE_PREDICATE;
+ case TGSI_FILE_IMMEDIATE: return nv50_ir::FILE_IMMEDIATE;
+ case TGSI_FILE_SYSTEM_VALUE: return nv50_ir::FILE_SYSTEM_VALUE;
+ case TGSI_FILE_IMMEDIATE_ARRAY: return nv50_ir::FILE_IMMEDIATE;
+ case TGSI_FILE_TEMPORARY_ARRAY: return nv50_ir::FILE_MEMORY_LOCAL;
+ case TGSI_FILE_RESOURCE: return nv50_ir::FILE_MEMORY_GLOBAL;
+ case TGSI_FILE_SAMPLER:
+ case TGSI_FILE_NULL:
+ default:
+ return nv50_ir::FILE_NULL;
+ }
+}
+
+static nv50_ir::SVSemantic translateSysVal(uint sysval)
+{
+ switch (sysval) {
+ case TGSI_SEMANTIC_FACE: return nv50_ir::SV_FACE;
+ case TGSI_SEMANTIC_PSIZE: return nv50_ir::SV_POINT_SIZE;
+ case TGSI_SEMANTIC_PRIMID: return nv50_ir::SV_PRIMITIVE_ID;
+ case TGSI_SEMANTIC_INSTANCEID: return nv50_ir::SV_INSTANCE_ID;
+ default:
+ assert(0);
+ return nv50_ir::SV_CLOCK;
+ }
+}
+
+#define NV50_IR_TEX_TARG_CASE(a, b) \
+ case TGSI_TEXTURE_##a: return nv50_ir::TEX_TARGET_##b;
+
+static nv50_ir::TexTarget translateTexture(uint tex)
+{
+ switch (tex) {
+ NV50_IR_TEX_TARG_CASE(1D, 1D);
+ NV50_IR_TEX_TARG_CASE(2D, 2D);
+ NV50_IR_TEX_TARG_CASE(3D, 3D);
+ NV50_IR_TEX_TARG_CASE(CUBE, CUBE);
+ NV50_IR_TEX_TARG_CASE(RECT, RECT);
+ NV50_IR_TEX_TARG_CASE(1D_ARRAY, 1D_ARRAY);
+ NV50_IR_TEX_TARG_CASE(2D_ARRAY, 2D_ARRAY);
+ NV50_IR_TEX_TARG_CASE(SHADOW1D, 1D_SHADOW);
+ NV50_IR_TEX_TARG_CASE(SHADOW2D, 2D_SHADOW);
+ NV50_IR_TEX_TARG_CASE(SHADOW1D_ARRAY, 1D_ARRAY_SHADOW);
+ NV50_IR_TEX_TARG_CASE(SHADOW2D_ARRAY, 2D_ARRAY_SHADOW);
+ NV50_IR_TEX_TARG_CASE(SHADOWRECT, RECT_SHADOW);
+
+ case TGSI_TEXTURE_UNKNOWN:
+ default:
+ assert(!"invalid texture target");
+ return nv50_ir::TEX_TARGET_2D;
+ }
+}
+
+nv50_ir::DataType Instruction::inferSrcType() const
+{
+ switch (getOpcode()) {
+ case TGSI_OPCODE_AND:
+ case TGSI_OPCODE_OR:
+ case TGSI_OPCODE_XOR:
+ case TGSI_OPCODE_U2F:
+ case TGSI_OPCODE_UADD:
+ case TGSI_OPCODE_UDIV:
+ case TGSI_OPCODE_UMOD:
+ case TGSI_OPCODE_UMAD:
+ case TGSI_OPCODE_UMUL:
+ case TGSI_OPCODE_UMAX:
+ case TGSI_OPCODE_UMIN:
+ case TGSI_OPCODE_USEQ:
+ case TGSI_OPCODE_USGE:
+ case TGSI_OPCODE_USLT:
+ case TGSI_OPCODE_USNE:
+ case TGSI_OPCODE_USHR:
+ case TGSI_OPCODE_UCMP:
+ return nv50_ir::TYPE_U32;
+ case TGSI_OPCODE_I2F:
+ case TGSI_OPCODE_IDIV:
+ case TGSI_OPCODE_IMAX:
+ case TGSI_OPCODE_IMIN:
+ case TGSI_OPCODE_INEG:
+ case TGSI_OPCODE_ISGE:
+ case TGSI_OPCODE_ISHR:
+ case TGSI_OPCODE_ISLT:
+ case TGSI_OPCODE_SAD: // not sure about SAD, but no one has a float version
+ case TGSI_OPCODE_MOD:
+ case TGSI_OPCODE_UARL:
+ return nv50_ir::TYPE_S32;
+ default:
+ return nv50_ir::TYPE_F32;
+ }
+}
+
+nv50_ir::DataType Instruction::inferDstType() const
+{
+ switch (getOpcode()) {
+ case TGSI_OPCODE_F2U: return nv50_ir::TYPE_U32;
+ case TGSI_OPCODE_F2I: return nv50_ir::TYPE_S32;
+ case TGSI_OPCODE_I2F:
+ case TGSI_OPCODE_U2F:
+ return nv50_ir::TYPE_F32;
+ default:
+ return inferSrcType();
+ }
+}
+
+nv50_ir::CondCode Instruction::getSetCond() const
+{
+ using namespace nv50_ir;
+
+ switch (getOpcode()) {
+ case TGSI_OPCODE_SLT:
+ case TGSI_OPCODE_ISLT:
+ case TGSI_OPCODE_USLT:
+ return CC_LT;
+ case TGSI_OPCODE_SLE:
+ return CC_LE;
+ case TGSI_OPCODE_SGE:
+ case TGSI_OPCODE_ISGE:
+ case TGSI_OPCODE_USGE:
+ return CC_GE;
+ case TGSI_OPCODE_SGT:
+ return CC_GT;
+ case TGSI_OPCODE_SEQ:
+ case TGSI_OPCODE_USEQ:
+ return CC_EQ;
+ case TGSI_OPCODE_SNE:
+ case TGSI_OPCODE_USNE:
+ return CC_NE;
+ case TGSI_OPCODE_SFL:
+ return CC_NEVER;
+ case TGSI_OPCODE_STR:
+ default:
+ return CC_ALWAYS;
+ }
+}
+
+#define NV50_IR_OPCODE_CASE(a, b) case TGSI_OPCODE_##a: return nv50_ir::OP_##b
+
+static nv50_ir::operation translateOpcode(uint opcode)
+{
+ switch (opcode) {
+ NV50_IR_OPCODE_CASE(ARL, SHL);
+ NV50_IR_OPCODE_CASE(MOV, MOV);
+
+ NV50_IR_OPCODE_CASE(RCP, RCP);
+ NV50_IR_OPCODE_CASE(RSQ, RSQ);
+
+ NV50_IR_OPCODE_CASE(MUL, MUL);
+ NV50_IR_OPCODE_CASE(ADD, ADD);
+
+ NV50_IR_OPCODE_CASE(MIN, MIN);
+ NV50_IR_OPCODE_CASE(MAX, MAX);
+ NV50_IR_OPCODE_CASE(SLT, SET);
+ NV50_IR_OPCODE_CASE(SGE, SET);
+ NV50_IR_OPCODE_CASE(MAD, MAD);
+ NV50_IR_OPCODE_CASE(SUB, SUB);
+
+ NV50_IR_OPCODE_CASE(FLR, FLOOR);
+ NV50_IR_OPCODE_CASE(ROUND, CVT);
+ NV50_IR_OPCODE_CASE(EX2, EX2);
+ NV50_IR_OPCODE_CASE(LG2, LG2);
+ NV50_IR_OPCODE_CASE(POW, POW);
+
+ NV50_IR_OPCODE_CASE(ABS, ABS);
+
+ NV50_IR_OPCODE_CASE(COS, COS);
+ NV50_IR_OPCODE_CASE(DDX, DFDX);
+ NV50_IR_OPCODE_CASE(DDY, DFDY);
+ NV50_IR_OPCODE_CASE(KILP, DISCARD);
+
+ NV50_IR_OPCODE_CASE(SEQ, SET);
+ NV50_IR_OPCODE_CASE(SFL, SET);
+ NV50_IR_OPCODE_CASE(SGT, SET);
+ NV50_IR_OPCODE_CASE(SIN, SIN);
+ NV50_IR_OPCODE_CASE(SLE, SET);
+ NV50_IR_OPCODE_CASE(SNE, SET);
+ NV50_IR_OPCODE_CASE(STR, SET);
+ NV50_IR_OPCODE_CASE(TEX, TEX);
+ NV50_IR_OPCODE_CASE(TXD, TXD);
+ NV50_IR_OPCODE_CASE(TXP, TEX);
+
+ NV50_IR_OPCODE_CASE(BRA, BRA);
+ NV50_IR_OPCODE_CASE(CAL, CALL);
+ NV50_IR_OPCODE_CASE(RET, RET);
+ NV50_IR_OPCODE_CASE(CMP, SLCT);
+
+ NV50_IR_OPCODE_CASE(TXB, TXB);
+
+ NV50_IR_OPCODE_CASE(DIV, DIV);
+
+ NV50_IR_OPCODE_CASE(TXL, TXL);
+
+ NV50_IR_OPCODE_CASE(CEIL, CEIL);
+ NV50_IR_OPCODE_CASE(I2F, CVT);
+ NV50_IR_OPCODE_CASE(NOT, NOT);
+ NV50_IR_OPCODE_CASE(TRUNC, TRUNC);
+ NV50_IR_OPCODE_CASE(SHL, SHL);
+
+ NV50_IR_OPCODE_CASE(AND, AND);
+ NV50_IR_OPCODE_CASE(OR, OR);
+ NV50_IR_OPCODE_CASE(MOD, MOD);
+ NV50_IR_OPCODE_CASE(XOR, XOR);
+ NV50_IR_OPCODE_CASE(SAD, SAD);
+ NV50_IR_OPCODE_CASE(TXF, TXF);
+ NV50_IR_OPCODE_CASE(TXQ, TXQ);
+
+ NV50_IR_OPCODE_CASE(EMIT, EMIT);
+ NV50_IR_OPCODE_CASE(ENDPRIM, RESTART);
+
+ NV50_IR_OPCODE_CASE(KIL, DISCARD);
+
+ NV50_IR_OPCODE_CASE(F2I, CVT);
+ NV50_IR_OPCODE_CASE(IDIV, DIV);
+ NV50_IR_OPCODE_CASE(IMAX, MAX);
+ NV50_IR_OPCODE_CASE(IMIN, MIN);
+ NV50_IR_OPCODE_CASE(INEG, NEG);
+ NV50_IR_OPCODE_CASE(ISGE, SET);
+ NV50_IR_OPCODE_CASE(ISHR, SHR);
+ NV50_IR_OPCODE_CASE(ISLT, SET);
+ NV50_IR_OPCODE_CASE(F2U, CVT);
+ NV50_IR_OPCODE_CASE(U2F, CVT);
+ NV50_IR_OPCODE_CASE(UADD, ADD);
+ NV50_IR_OPCODE_CASE(UDIV, DIV);
+ NV50_IR_OPCODE_CASE(UMAD, MAD);
+ NV50_IR_OPCODE_CASE(UMAX, MAX);
+ NV50_IR_OPCODE_CASE(UMIN, MIN);
+ NV50_IR_OPCODE_CASE(UMOD, MOD);
+ NV50_IR_OPCODE_CASE(UMUL, MUL);
+ NV50_IR_OPCODE_CASE(USEQ, SET);
+ NV50_IR_OPCODE_CASE(USGE, SET);
+ NV50_IR_OPCODE_CASE(USHR, SHR);
+ NV50_IR_OPCODE_CASE(USLT, SET);
+ NV50_IR_OPCODE_CASE(USNE, SET);
+
+ NV50_IR_OPCODE_CASE(LOAD, TXF);
+ NV50_IR_OPCODE_CASE(SAMPLE, TEX);
+ NV50_IR_OPCODE_CASE(SAMPLE_B, TXB);
+ NV50_IR_OPCODE_CASE(SAMPLE_C, TEX);
+ NV50_IR_OPCODE_CASE(SAMPLE_C_LZ, TEX);
+ NV50_IR_OPCODE_CASE(SAMPLE_D, TXD);
+ NV50_IR_OPCODE_CASE(SAMPLE_L, TXL);
+ NV50_IR_OPCODE_CASE(GATHER4, TXG);
+ NV50_IR_OPCODE_CASE(RESINFO, TXQ);
+
+ NV50_IR_OPCODE_CASE(END, EXIT);
+
+ default:
+ return nv50_ir::OP_NOP;
+ }
+}
+
+bool Instruction::checkDstSrcAliasing() const
+{
+ if (insn->Dst[0].Register.Indirect) // no danger if indirect, using memory
+ return false;
+
+ for (int s = 0; s < TGSI_FULL_MAX_SRC_REGISTERS; ++s) {
+ if (insn->Src[s].Register.File == TGSI_FILE_NULL)
+ break;
+ if (insn->Src[s].Register.File == insn->Dst[0].Register.File &&
+ insn->Src[s].Register.Index == insn->Dst[0].Register.Index)
+ return true;
+ }
+ return false;
+}
+
+class Source
+{
+public:
+ Source(struct nv50_ir_prog_info *);
+ ~Source();
+
+ struct Subroutine
+ {
+ unsigned pc;
+ };
+
+public:
+ bool scanSource();
+ unsigned fileSize(unsigned file) const { return scan.file_max[file] + 1; }
+
+public:
+ struct tgsi_shader_info scan;
+ struct tgsi_full_instruction *insns;
+ const struct tgsi_token *tokens;
+ struct nv50_ir_prog_info *info;
+
+ nv50_ir::DynArray tempArrays;
+ nv50_ir::DynArray immdArrays;
+ int tempArrayCount;
+ int immdArrayCount;
+
+ bool mainTempsInLMem;
+
+ uint8_t *resourceTargets; // TGSI_TEXTURE_*
+ unsigned resourceCount;
+
+ Subroutine *subroutines;
+ unsigned subroutineCount;
+
+private:
+ int inferSysValDirection(unsigned sn) const;
+ bool scanDeclaration(const struct tgsi_full_declaration *);
+ bool scanInstruction(const struct tgsi_full_instruction *);
+ void scanProperty(const struct tgsi_full_property *);
+ void scanImmediate(const struct tgsi_full_immediate *);
+
+ inline bool isEdgeFlagPassthrough(const Instruction&) const;
+};
+
+Source::Source(struct nv50_ir_prog_info *prog) : info(prog)
+{
+ tokens = (const struct tgsi_token *)info->bin.source;
+
+ if (prog->dbgFlags & NV50_IR_DEBUG_BASIC)
+ tgsi_dump(tokens, 0);
+
+ resourceTargets = NULL;
+ subroutines = NULL;
+
+ mainTempsInLMem = FALSE;
+}
+
+Source::~Source()
+{
+ if (insns)
+ FREE(insns);
+
+ if (info->immd.data)
+ FREE(info->immd.data);
+ if (info->immd.type)
+ FREE(info->immd.type);
+
+ if (resourceTargets)
+ delete[] resourceTargets;
+ if (subroutines)
+ delete[] subroutines;
+}
+
+bool Source::scanSource()
+{
+ unsigned insnCount = 0;
+ unsigned subrCount = 0;
+ struct tgsi_parse_context parse;
+
+ tgsi_scan_shader(tokens, &scan);
+
+ insns = (struct tgsi_full_instruction *)MALLOC(scan.num_instructions *
+ sizeof(insns[0]));
+ if (!insns)
+ return false;
+
+ resourceCount = scan.file_max[TGSI_FILE_RESOURCE] + 1;
+ resourceTargets = new uint8_t[resourceCount];
+
+ subroutineCount = scan.opcode_count[TGSI_OPCODE_BGNSUB] + 1;
+ subroutines = new Subroutine[subroutineCount];
+
+ info->immd.bufSize = 0;
+ tempArrayCount = 0;
+ immdArrayCount = 0;
+
+ info->numInputs = scan.file_max[TGSI_FILE_INPUT] + 1;
+ info->numOutputs = scan.file_max[TGSI_FILE_OUTPUT] + 1;
+ info->numSysVals = scan.file_max[TGSI_FILE_SYSTEM_VALUE] + 1;
+
+ if (info->type == PIPE_SHADER_FRAGMENT) {
+ info->prop.fp.writesDepth = scan.writes_z;
+ info->prop.fp.usesDiscard = scan.uses_kill;
+ } else
+ if (info->type == PIPE_SHADER_GEOMETRY) {
+ info->prop.gp.instanceCount = 1; // default value
+ }
+
+ info->immd.data = (uint32_t *)MALLOC(scan.immediate_count * 16);
+ info->immd.type = (ubyte *)MALLOC(scan.immediate_count * sizeof(ubyte));
+
+ tgsi_parse_init(&parse, tokens);
+ while (!tgsi_parse_end_of_tokens(&parse)) {
+ tgsi_parse_token(&parse);
+
+ switch (parse.FullToken.Token.Type) {
+ case TGSI_TOKEN_TYPE_IMMEDIATE:
+ scanImmediate(&parse.FullToken.FullImmediate);
+ break;
+ case TGSI_TOKEN_TYPE_DECLARATION:
+ scanDeclaration(&parse.FullToken.FullDeclaration);
+ break;
+ case TGSI_TOKEN_TYPE_INSTRUCTION:
+ insns[insnCount++] = parse.FullToken.FullInstruction;
+ if (insns[insnCount - 1].Instruction.Opcode == TGSI_OPCODE_BGNSUB)
+ subroutines[++subrCount].pc = insnCount - 1;
+ else
+ scanInstruction(&parse.FullToken.FullInstruction);
+ break;
+ case TGSI_TOKEN_TYPE_PROPERTY:
+ scanProperty(&parse.FullToken.FullProperty);
+ break;
+ default:
+ INFO("unknown TGSI token type: %d\n", parse.FullToken.Token.Type);
+ break;
+ }
+ }
+ tgsi_parse_free(&parse);
+
+ if (mainTempsInLMem)
+ info->bin.tlsSpace += (scan.file_max[TGSI_FILE_TEMPORARY] + 1) * 16;
+
+ return info->assignSlots(info) == 0;
+}
+
+void Source::scanProperty(const struct tgsi_full_property *prop)
+{
+ switch (prop->Property.PropertyName) {
+ case TGSI_PROPERTY_GS_OUTPUT_PRIM:
+ info->prop.gp.outputPrim = prop->u[0].Data;
+ break;
+ case TGSI_PROPERTY_GS_INPUT_PRIM:
+ info->prop.gp.inputPrim = prop->u[0].Data;
+ break;
+ case TGSI_PROPERTY_GS_MAX_OUTPUT_VERTICES:
+ info->prop.gp.maxVertices = prop->u[0].Data;
+ break;
+#if 0
+ case TGSI_PROPERTY_GS_INSTANCE_COUNT:
+ info->prop.gp.instanceCount = prop->u[0].Data;
+ break;
+#endif
+ case TGSI_PROPERTY_FS_COLOR0_WRITES_ALL_CBUFS:
+ info->prop.fp.separateFragData = TRUE;
+ break;
+ case TGSI_PROPERTY_FS_COORD_ORIGIN:
+ case TGSI_PROPERTY_FS_COORD_PIXEL_CENTER:
+ // we don't care
+ break;
+ default:
+ INFO("unhandled TGSI property %d\n", prop->Property.PropertyName);
+ break;
+ }
+}
+
+void Source::scanImmediate(const struct tgsi_full_immediate *imm)
+{
+ const unsigned n = info->immd.count++;
+
+ assert(n < scan.immediate_count);
+
+ for (int c = 0; c < 4; ++c)
+ info->immd.data[n * 4 + c] = imm->u[c].Uint;
+
+ info->immd.type[n] = imm->Immediate.DataType;
+}
+
+int Source::inferSysValDirection(unsigned sn) const
+{
+ switch (sn) {
+ case TGSI_SEMANTIC_INSTANCEID:
+// case TGSI_SEMANTIC_VERTEXID:
+ return 1;
+#if 0
+ case TGSI_SEMANTIC_LAYER:
+ case TGSI_SEMANTIC_VIEWPORTINDEX:
+ return 0;
+#endif
+ case TGSI_SEMANTIC_PRIMID:
+ return (info->type == PIPE_SHADER_FRAGMENT) ? 1 : 0;
+ default:
+ return 0;
+ }
+}
+
+bool Source::scanDeclaration(const struct tgsi_full_declaration *decl)
+{
+ unsigned i;
+ unsigned sn = TGSI_SEMANTIC_GENERIC;
+ unsigned si = 0;
+ const unsigned first = decl->Range.First, last = decl->Range.Last;
+
+ if (decl->Declaration.Semantic) {
+ sn = decl->Semantic.Name;
+ si = decl->Semantic.Index;
+ }
+
+ switch (decl->Declaration.File) {
+ case TGSI_FILE_INPUT:
+ if (info->type == PIPE_SHADER_VERTEX) {
+ // all vertex attributes are equal
+ for (i = first; i <= last; ++i) {
+ info->in[i].sn = TGSI_SEMANTIC_GENERIC;
+ info->in[i].si = i;
+ }
+ } else {
+ for (i = first; i <= last; ++i, ++si) {
+ info->in[i].id = i;
+ info->in[i].sn = sn;
+ info->in[i].si = si;
+ if (info->type == PIPE_SHADER_FRAGMENT) {
+ // translate interpolation mode
+ switch (decl->Declaration.Interpolate) {
+ case TGSI_INTERPOLATE_CONSTANT:
+ info->in[i].flat = 1;
+ break;
+ case TGSI_INTERPOLATE_LINEAR:
+ if (sn != TGSI_SEMANTIC_COLOR) // GL_NICEST
+ info->in[i].linear = 1;
+ break;
+ default:
+ break;
+ }
+ if (decl->Declaration.Centroid)
+ info->in[i].centroid = 1;
+ }
+ }
+ }
+ break;
+ case TGSI_FILE_OUTPUT:
+ for (i = first; i <= last; ++i, ++si) {
+ switch (sn) {
+ case TGSI_SEMANTIC_POSITION:
+ if (info->type == PIPE_SHADER_FRAGMENT)
+ info->io.fragDepth = i;
+ break;
+ case TGSI_SEMANTIC_COLOR:
+ if (info->type == PIPE_SHADER_FRAGMENT)
+ info->prop.fp.numColourResults++;
+ break;
+ case TGSI_SEMANTIC_EDGEFLAG:
+ info->io.edgeFlagOut = i;
+ break;
+ default:
+ break;
+ }
+ info->out[i].id = i;
+ info->out[i].sn = sn;
+ info->out[i].si = si;
+ }
+ break;
+ case TGSI_FILE_SYSTEM_VALUE:
+ for (i = first; i <= last; ++i, ++si) {
+ info->sv[i].sn = sn;
+ info->sv[i].si = si;
+ info->sv[i].input = inferSysValDirection(sn);
+ }
+ break;
+ case TGSI_FILE_RESOURCE:
+ for (i = first; i <= last; ++i)
+ resourceTargets[i] = decl->Resource.Resource;
+ break;
+ case TGSI_FILE_IMMEDIATE_ARRAY:
+ {
+ if (decl->Dim.Index2D >= immdArrayCount)
+ immdArrayCount = decl->Dim.Index2D + 1;
+ immdArrays[decl->Dim.Index2D].u32 = (last + 1) << 2;
+ int c;
+ uint32_t base, count;
+ switch (decl->Declaration.UsageMask) {
+ case 0x1: c = 1; break;
+ case 0x3: c = 2; break;
+ default:
+ c = 4;
+ break;
+ }
+ immdArrays[decl->Dim.Index2D].u32 |= c;
+ count = (last + 1) * c;
+ base = info->immd.bufSize / 4;
+ info->immd.bufSize = (info->immd.bufSize + count * 4 + 0xf) & ~0xf;
+ info->immd.buf = (uint32_t *)REALLOC(info->immd.buf, base * 4,
+ info->immd.bufSize);
+ // NOTE: this assumes array declarations are ordered by Dim.Index2D
+ for (i = 0; i < count; ++i)
+ info->immd.buf[base + i] = decl->ImmediateData.u[i].Uint;
+ }
+ break;
+ case TGSI_FILE_TEMPORARY_ARRAY:
+ {
+ if (decl->Dim.Index2D >= tempArrayCount)
+ tempArrayCount = decl->Dim.Index2D + 1;
+ tempArrays[decl->Dim.Index2D].u32 = (last + 1) << 2;
+ int c;
+ uint32_t count;
+ switch (decl->Declaration.UsageMask) {
+ case 0x1: c = 1; break;
+ case 0x3: c = 2; break;
+ default:
+ c = 4;
+ break;
+ }
+ tempArrays[decl->Dim.Index2D].u32 |= c;
+ count = (last + 1) * c;
+ info->bin.tlsSpace += (info->bin.tlsSpace + count * 4 + 0xf) & ~0xf;
+ }
+ break;
+ case TGSI_FILE_NULL:
+ case TGSI_FILE_TEMPORARY:
+ case TGSI_FILE_ADDRESS:
+ case TGSI_FILE_CONSTANT:
+ case TGSI_FILE_IMMEDIATE:
+ case TGSI_FILE_PREDICATE:
+ case TGSI_FILE_SAMPLER:
+ break;
+ default:
+ ERROR("unhandled TGSI_FILE %d\n", decl->Declaration.File);
+ return false;
+ }
+ return true;
+}
+
+inline bool Source::isEdgeFlagPassthrough(const Instruction& insn) const
+{
+ return insn.getOpcode() == TGSI_OPCODE_MOV &&
+ insn.getDst(0).getIndex(0) == info->io.edgeFlagOut &&
+ insn.getSrc(0).getFile() == TGSI_FILE_INPUT;
+}
+
+bool Source::scanInstruction(const struct tgsi_full_instruction *inst)
+{
+ Instruction insn(inst);
+
+ if (insn.dstCount()) {
+ if (insn.getDst(0).getFile() == TGSI_FILE_OUTPUT) {
+ Instruction::DstRegister dst = insn.getDst(0);
+
+ if (dst.isIndirect(0))
+ for (unsigned i = 0; i < info->numOutputs; ++i)
+ info->out[i].mask = 0xf;
+ else
+ info->out[dst.getIndex(0)].mask |= dst.getMask();
+
+ if (isEdgeFlagPassthrough(insn))
+ info->io.edgeFlagIn = insn.getSrc(0).getIndex(0);
+ } else
+ if (insn.getDst(0).getFile() == TGSI_FILE_TEMPORARY) {
+ if (insn.getDst(0).isIndirect(0))
+ mainTempsInLMem = TRUE;
+ }
+ }
+
+ for (unsigned s = 0; s < insn.srcCount(); ++s) {
+ Instruction::SrcRegister src = insn.getSrc(s);
+ if (src.getFile() == TGSI_FILE_TEMPORARY)
+ if (src.isIndirect(0))
+ mainTempsInLMem = TRUE;
+ if (src.getFile() != TGSI_FILE_INPUT)
+ continue;
+ unsigned mask = insn.srcMask(s);
+
+ if (src.isIndirect(0)) {
+ for (unsigned i = 0; i < info->numInputs; ++i)
+ info->in[i].mask = 0xf;
+ } else {
+ for (unsigned c = 0; c < 4; ++c) {
+ if (!(mask & (1 << c)))
+ continue;
+ int k = src.getSwizzle(c);
+ int i = src.getIndex(0);
+ if (info->in[i].sn != TGSI_SEMANTIC_FOG || k == TGSI_SWIZZLE_X)
+ if (k <= TGSI_SWIZZLE_W)
+ info->in[i].mask |= 1 << k;
+ }
+ }
+ }
+ return true;
+}
+
+nv50_ir::TexInstruction::Target
+Instruction::getTexture(const tgsi::Source *code, int s) const
+{
+ if (insn->Instruction.Texture) {
+ return translateTexture(insn->Texture.Texture);
+ } else {
+ // XXX: indirect access
+ unsigned int r = getSrc(s).getIndex(0);
+ assert(r < code->resourceCount);
+ return translateTexture(code->resourceTargets[r]);
+ }
+}
+
+} // namespace tgsi
+
+namespace {
+
+using namespace nv50_ir;
+
+class Converter : public BuildUtil
+{
+public:
+ Converter(Program *, const tgsi::Source *);
+ ~Converter();
+
+ bool run();
+
+private:
+ Value *getVertexBase(int s);
+ Value *fetchSrc(int s, int c);
+ Value *acquireDst(int d, int c);
+ void storeDst(int d, int c, Value *);
+
+ Value *fetchSrc(const tgsi::Instruction::SrcRegister src, int c, Value *ptr);
+ void storeDst(const tgsi::Instruction::DstRegister dst, int c,
+ Value *val, Value *ptr);
+
+ Value *applySrcMod(Value *, int s, int c);
+
+ Symbol *makeSym(uint file, int fileIndex, int idx, int c, uint32_t addr);
+ Symbol *srcToSym(tgsi::Instruction::SrcRegister, int c);
+ Symbol *dstToSym(tgsi::Instruction::DstRegister, int c);
+
+ bool handleInstruction(const struct tgsi_full_instruction *);
+ void exportOutputs();
+ inline bool isEndOfSubroutine(uint ip);
+
+ void loadProjTexCoords(Value *dst[4], Value *src[4], unsigned int mask);
+
+ // R,S,L,C,Dx,Dy encode TGSI sources for respective values (0xSf for auto)
+ void setTexRS(TexInstruction *, unsigned int& s, int R, int S);
+ void handleTEX(Value *dst0[4], int R, int S, int L, int C, int Dx, int Dy);
+ void handleTXF(Value *dst0[4], int R);
+ void handleTXQ(Value *dst0[4], enum TexQuery);
+ void handleLIT(Value *dst0[4]);
+ void handleUserClipPlanes();
+
+ Value *interpolate(tgsi::Instruction::SrcRegister, int c, Value *ptr);
+
+ void insertConvergenceOps(BasicBlock *conv, BasicBlock *fork);
+
+ Value *buildDot(int dim);
+
+private:
+ const struct tgsi::Source *code;
+ const struct nv50_ir_prog_info *info;
+
+ uint ip; // instruction pointer
+
+ tgsi::Instruction tgsi;
+
+ DataType dstTy;
+ DataType srcTy;
+
+ DataArray tData; // TGSI_FILE_TEMPORARY
+ DataArray aData; // TGSI_FILE_ADDRESS
+ DataArray pData; // TGSI_FILE_PREDICATE
+ DataArray oData; // TGSI_FILE_OUTPUT (if outputs in registers)
+ DataArray *lData; // TGSI_FILE_TEMPORARY_ARRAY
+ DataArray *iData; // TGSI_FILE_IMMEDIATE_ARRAY
+
+ Value *zero;
+ Value *fragCoord[4];
+ Value *clipVtx[4];
+
+ Value *vtxBase[5]; // base address of vertex in primitive (for TP/GP)
+ uint8_t vtxBaseValid;
+
+ Stack condBBs; // fork BB, then else clause BB
+ Stack joinBBs; // fork BB, for inserting join ops on ENDIF
+ Stack loopBBs; // loop headers
+ Stack breakBBs; // end of / after loop
+ Stack entryBBs; // start of current (inlined) subroutine
+ Stack leaveBBs; // end of current (inlined) subroutine
+ Stack retIPs; // return instruction pointer
+};
+
+Symbol *
+Converter::srcToSym(tgsi::Instruction::SrcRegister src, int c)
+{
+ const int swz = src.getSwizzle(c);
+
+ return makeSym(src.getFile(),
+ src.is2D() ? src.getIndex(1) : 0,
+ src.isIndirect(0) ? -1 : src.getIndex(0), swz,
+ src.getIndex(0) * 16 + swz * 4);
+}
+
+Symbol *
+Converter::dstToSym(tgsi::Instruction::DstRegister dst, int c)
+{
+ return makeSym(dst.getFile(),
+ dst.is2D() ? dst.getIndex(1) : 0,
+ dst.isIndirect(0) ? -1 : dst.getIndex(0), c,
+ dst.getIndex(0) * 16 + c * 4);
+}
+
+Symbol *
+Converter::makeSym(uint tgsiFile, int fileIdx, int idx, int c, uint32_t address)
+{
+ Symbol *sym = new_Symbol(prog, tgsi::translateFile(tgsiFile));
+
+ sym->reg.fileIndex = fileIdx;
+
+ if (idx >= 0) {
+ if (sym->reg.file == FILE_SHADER_INPUT)
+ sym->setOffset(info->in[idx].slot[c] * 4);
+ else
+ if (sym->reg.file == FILE_SHADER_OUTPUT)
+ sym->setOffset(info->out[idx].slot[c] * 4);
+ else
+ if (sym->reg.file == FILE_SYSTEM_VALUE)
+ sym->setSV(tgsi::translateSysVal(info->sv[idx].sn), c);
+ else
+ sym->setOffset(address);
+ } else {
+ sym->setOffset(address);
+ }
+ return sym;
+}
+
+static inline uint8_t
+translateInterpMode(const struct nv50_ir_varying *var, operation& op)
+{
+ uint8_t mode;
+
+ if (var->flat)
+ mode = NV50_IR_INTERP_FLAT;
+ else
+ if (var->linear)
+ mode = NV50_IR_INTERP_LINEAR;
+ else
+ mode = NV50_IR_INTERP_PERSPECTIVE;
+
+ op = (mode == NV50_IR_INTERP_PERSPECTIVE) ? OP_PINTERP : OP_LINTERP;
+
+ if (var->centroid)
+ mode |= NV50_IR_INTERP_CENTROID;
+
+ return mode;
+}
+
+Value *
+Converter::interpolate(tgsi::Instruction::SrcRegister src, int c, Value *ptr)
+{
+ operation op;
+
+ // XXX: no way to know interpolation mode if we don't know what's accessed
+ const uint8_t mode = translateInterpMode(&info->in[ptr ? 0 :
+ src.getIndex(0)], op);
+
+ Instruction *insn = new_Instruction(func, op, TYPE_F32);
+
+ insn->setDef(0, getScratch());
+ insn->setSrc(0, srcToSym(src, c));
+ if (op == OP_PINTERP)
+ insn->setSrc(1, fragCoord[3]);
+ if (ptr)
+ insn->setIndirect(0, 0, ptr);
+
+ insn->setInterpolate(mode);
+
+ bb->insertTail(insn);
+ return insn->getDef(0);
+}
+
+Value *
+Converter::applySrcMod(Value *val, int s, int c)
+{
+ Modifier m = tgsi.getSrc(s).getMod(c);
+ DataType ty = tgsi.inferSrcType();
+
+ if (m & Modifier(NV50_IR_MOD_ABS))
+ val = mkOp1v(OP_ABS, ty, getScratch(), val);
+
+ if (m & Modifier(NV50_IR_MOD_NEG))
+ val = mkOp1v(OP_NEG, ty, getScratch(), val);
+
+ return val;
+}
+
+Value *
+Converter::getVertexBase(int s)
+{
+ assert(s < 5);
+ if (!(vtxBaseValid & (1 << s))) {
+ const int index = tgsi.getSrc(s).getIndex(1);
+ Value *rel = NULL;
+ if (tgsi.getSrc(s).isIndirect(1))
+ rel = fetchSrc(tgsi.getSrc(s).getIndirect(1), 0, NULL);
+ vtxBaseValid |= 1 << s;
+ vtxBase[s] = mkOp2v(OP_PFETCH, TYPE_U32, getSSA(), mkImm(index), rel);
+ }
+ return vtxBase[s];
+}
+
+Value *
+Converter::fetchSrc(int s, int c)
+{
+ Value *res;
+ Value *ptr = NULL, *dimRel = NULL;
+
+ tgsi::Instruction::SrcRegister src = tgsi.getSrc(s);
+
+ if (src.isIndirect(0))
+ ptr = fetchSrc(src.getIndirect(0), 0, NULL);
+
+ if (src.is2D()) {
+ switch (src.getFile()) {
+ case TGSI_FILE_INPUT:
+ dimRel = getVertexBase(s);
+ break;
+ case TGSI_FILE_CONSTANT:
+ // on NVC0, this is valid and c{I+J}[k] == cI[(J << 16) + k]
+ if (src.isIndirect(1))
+ dimRel = fetchSrc(src.getIndirect(1), 0, 0);
+ break;
+ default:
+ break;
+ }
+ }
+
+ res = fetchSrc(src, c, ptr);
+
+ if (dimRel)
+ res->getInsn()->setIndirect(0, 1, dimRel);
+
+ return applySrcMod(res, s, c);
+}
+
+Value *
+Converter::fetchSrc(tgsi::Instruction::SrcRegister src, int c, Value *ptr)
+{
+ const int idx = src.getIndex(0);
+ const int swz = src.getSwizzle(c);
+
+ switch (src.getFile()) {
+ case TGSI_FILE_TEMPORARY:
+ return tData.load(idx, swz, ptr);
+ case TGSI_FILE_PREDICATE:
+ return pData.load(idx, swz, ptr);
+ case TGSI_FILE_ADDRESS:
+ return aData.load(idx, swz, ptr);
+
+ case TGSI_FILE_TEMPORARY_ARRAY:
+ assert(src.is2D() && src.getIndex(1) < code->tempArrayCount);
+ return lData[src.getIndex(1)].load(idx, swz, ptr);
+ case TGSI_FILE_IMMEDIATE_ARRAY:
+ assert(src.is2D() && src.getIndex(1) < code->immdArrayCount);
+ return iData[src.getIndex(1)].load(idx, swz, ptr);
+
+ case TGSI_FILE_IMMEDIATE:
+ assert(!ptr);
+ return loadImm(NULL, info->immd.data[idx * 4 + swz]);
+
+ case TGSI_FILE_CONSTANT:
+ return mkLoad(TYPE_U32, srcToSym(src, c), ptr);
+
+ case TGSI_FILE_INPUT:
+ if (prog->getType() == Program::TYPE_FRAGMENT) {
+ // don't load masked inputs, won't be assigned a slot
+ if (!ptr && !(info->in[idx].mask & (1 << swz)))
+ return loadImm(NULL, swz == TGSI_SWIZZLE_W ? 1.0f : 0.0f);
+ return interpolate(src, c, ptr);
+ }
+ return mkLoad(TYPE_U32, srcToSym(src, c), ptr);
+
+ case TGSI_FILE_SYSTEM_VALUE:
+ assert(!ptr);
+ return mkOp1v(OP_RDSV, TYPE_U32, getSSA(), srcToSym(src, c));
+
+ case TGSI_FILE_OUTPUT:
+ case TGSI_FILE_RESOURCE:
+ case TGSI_FILE_SAMPLER:
+ case TGSI_FILE_NULL:
+ default:
+ assert(!"invalid/unhandled TGSI source file");
+ return NULL;
+ }
+}
+
+Value *
+Converter::acquireDst(int d, int c)
+{
+ const tgsi::Instruction::DstRegister dst = tgsi.getDst(d);
+
+ if (dst.isMasked(c))
+ return NULL;
+ if (dst.isIndirect(0))
+ return getScratch();
+
+ const int idx = dst.getIndex(0);
+
+ switch (dst.getFile()) {
+ case TGSI_FILE_TEMPORARY:
+ return tData.acquire(idx, c);
+ case TGSI_FILE_TEMPORARY_ARRAY:
+ return getScratch();
+ case TGSI_FILE_PREDICATE:
+ return pData.acquire(idx, c);
+ case TGSI_FILE_ADDRESS:
+ return aData.acquire(idx, c);
+
+ case TGSI_FILE_OUTPUT:
+ if (prog->getType() == Program::TYPE_FRAGMENT)
+ return oData.acquire(idx, c);
+ // fall through
+ case TGSI_FILE_SYSTEM_VALUE:
+ return getScratch();
+
+ default:
+ assert(!"invalid dst file");
+ return NULL;
+ }
+}
+
+void
+Converter::storeDst(int d, int c, Value *val)
+{
+ const tgsi::Instruction::DstRegister dst = tgsi.getDst(d);
+
+ switch (tgsi.getSaturate()) {
+ case TGSI_SAT_NONE:
+ break;
+ case TGSI_SAT_ZERO_ONE:
+ mkOp1(OP_SAT, dstTy, val, val);
+ break;
+ case TGSI_SAT_MINUS_PLUS_ONE:
+ mkOp2(OP_MAX, dstTy, val, val, mkImm(-1.0f));
+ mkOp2(OP_MIN, dstTy, val, val, mkImm(+1.0f));
+ break;
+ default:
+ assert(!"invalid saturation mode");
+ break;
+ }
+
+ Value *ptr = dst.isIndirect(0) ?
+ fetchSrc(dst.getIndirect(0), 0, NULL) : NULL;
+
+ if (info->io.clipDistanceCount &&
+ dst.getFile() == TGSI_FILE_OUTPUT &&
+ info->out[dst.getIndex(0)].sn == TGSI_SEMANTIC_POSITION) {
+ mkMov(clipVtx[c], val);
+ val = clipVtx[c];
+ }
+
+ storeDst(dst, c, val, ptr);
+}
+
+void
+Converter::storeDst(const tgsi::Instruction::DstRegister dst, int c,
+ Value *val, Value *ptr)
+{
+ const int idx = dst.getIndex(0);
+
+ switch (dst.getFile()) {
+ case TGSI_FILE_TEMPORARY:
+ tData.store(idx, c, ptr, val);
+ break;
+ case TGSI_FILE_TEMPORARY_ARRAY:
+ assert(dst.is2D() && dst.getIndex(1) < code->tempArrayCount);
+ lData[dst.getIndex(1)].store(idx, c, ptr, val);
+ break;
+ case TGSI_FILE_PREDICATE:
+ pData.store(idx, c, ptr, val);
+ break;
+ case TGSI_FILE_ADDRESS:
+ aData.store(idx, c, ptr, val);
+ break;
+
+ case TGSI_FILE_OUTPUT:
+ if (prog->getType() == Program::TYPE_FRAGMENT)
+ oData.store(idx, c, ptr, val);
+ else
+ mkStore(OP_EXPORT, TYPE_U32, dstToSym(dst, c), ptr, val);
+ break;
+
+ case TGSI_FILE_SYSTEM_VALUE:
+ assert(!ptr);
+ mkOp2(OP_WRSV, TYPE_U32, NULL, dstToSym(dst, c), val);
+ break;
+
+ default:
+ assert(!"invalid dst file");
+ break;
+ }
+}
+
+#define FOR_EACH_DST_ENABLED_CHANNEL(d, chan, inst) \
+ for (chan = 0; chan < 4; ++chan) \
+ if (!inst.getDst(d).isMasked(chan))
+
+Value *
+Converter::buildDot(int dim)
+{
+ assert(dim > 0);
+
+ Value *src0 = fetchSrc(0, 0), *src1 = fetchSrc(1, 0);
+ Value *dotp = getScratch();
+
+ mkOp2(OP_MUL, TYPE_F32, dotp, src0, src1);
+
+ for (int c = 1; c < dim; ++c) {
+ src0 = fetchSrc(0, c);
+ src1 = fetchSrc(1, c);
+ mkOp3(OP_MAD, TYPE_F32, dotp, src0, src1, dotp);
+ }
+ return dotp;
+}
+
+void
+Converter::insertConvergenceOps(BasicBlock *conv, BasicBlock *fork)
+{
+ FlowInstruction *join = new_FlowInstruction(func, OP_JOIN, NULL);
+ join->fixed = 1;
+ conv->insertHead(join);
+
+ fork->joinAt = new_FlowInstruction(func, OP_JOINAT, conv);
+ fork->insertBefore(fork->getExit(), fork->joinAt);
+}
+
+void
+Converter::setTexRS(TexInstruction *tex, unsigned int& s, int R, int S)
+{
+ unsigned rIdx = 0, sIdx = 0;
+
+ if (R >= 0)
+ rIdx = tgsi.getSrc(R).getIndex(0);
+ if (S >= 0)
+ sIdx = tgsi.getSrc(S).getIndex(0);
+
+ tex->setTexture(tgsi.getTexture(code, R), rIdx, sIdx);
+
+ if (tgsi.getSrc(R).isIndirect(0)) {
+ tex->tex.rIndirectSrc = s;
+ tex->setSrc(s++, fetchSrc(tgsi.getSrc(R).getIndirect(0), 0, NULL));
+ }
+ if (S >= 0 && tgsi.getSrc(S).isIndirect(0)) {
+ tex->tex.sIndirectSrc = s;
+ tex->setSrc(s++, fetchSrc(tgsi.getSrc(S).getIndirect(0), 0, NULL));
+ }
+}
+
+void
+Converter::handleTXQ(Value *dst0[4], enum TexQuery query)
+{
+ TexInstruction *tex = new_TexInstruction(func, OP_TXQ);
+ tex->tex.query = query;
+ unsigned int c, d;
+
+ for (d = 0, c = 0; c < 4; ++c) {
+ if (!dst0[c])
+ continue;
+ tex->tex.mask |= 1 << c;
+ tex->setDef(d++, dst0[c]);
+ }
+ tex->setSrc((c = 0), fetchSrc(0, 0)); // mip level
+
+ setTexRS(tex, c, 1, -1);
+
+ bb->insertTail(tex);
+}
+
+void
+Converter::loadProjTexCoords(Value *dst[4], Value *src[4], unsigned int mask)
+{
+ Value *proj = fetchSrc(0, 3);
+ Instruction *insn = proj->getUniqueInsn();
+ int c;
+
+ if (insn->op == OP_PINTERP) {
+ bb->insertTail(insn = insn->clone(true));
+ insn->op = OP_LINTERP;
+ insn->setInterpolate(NV50_IR_INTERP_LINEAR | insn->getSampleMode());
+ insn->setSrc(1, NULL);
+ proj = insn->getDef(0);
+ }
+ proj = mkOp1v(OP_RCP, TYPE_F32, getSSA(), proj);
+
+ for (c = 0; c < 4; ++c) {
+ if (!(mask & (1 << c)))
+ continue;
+ if ((insn = src[c]->getUniqueInsn())->op != OP_PINTERP)
+ continue;
+ mask &= ~(1 << c);
+
+ bb->insertTail(insn = insn->clone(true));
+ insn->setInterpolate(NV50_IR_INTERP_PERSPECTIVE | insn->getSampleMode());
+ insn->setSrc(1, proj);
+ dst[c] = insn->getDef(0);
+ }
+ if (!mask)
+ return;
+
+ proj = mkOp1v(OP_RCP, TYPE_F32, getSSA(), fetchSrc(0, 3));
+
+ for (c = 0; c < 4; ++c)
+ if (mask & (1 << c))
+ dst[c] = mkOp2v(OP_MUL, TYPE_F32, getSSA(), src[c], proj);
+}
+
+// order of nv50 ir sources: x y z layer lod/bias shadow
+// order of TGSI TEX sources: x y z layer shadow lod/bias
+// lowering will finally set the hw specific order (like array first on nvc0)
+void
+Converter::handleTEX(Value *dst[4], int R, int S, int L, int C, int Dx, int Dy)
+{
+ Value *val;
+ Value *arg[4], *src[8];
+ Value *lod = NULL, *shd = NULL;
+ unsigned int s, c, d;
+ TexInstruction *texi = new_TexInstruction(func, tgsi.getOP());
+
+ TexInstruction::Target tgt = tgsi.getTexture(code, R);
+
+ for (s = 0; s < tgt.getArgCount(); ++s)
+ arg[s] = src[s] = fetchSrc(0, s);
+
+ if (texi->op == OP_TXL || texi->op == OP_TXB)
+ lod = fetchSrc(L >> 4, L & 3);
+
+ if (C == 0x0f)
+ C = 0x00 | MAX2(tgt.getArgCount(), 2); // guess DC src
+
+ if (tgt.isShadow())
+ shd = fetchSrc(C >> 4, C & 3);
+
+ if (texi->op == OP_TXD) {
+ for (c = 0; c < tgt.getDim(); ++c) {
+ texi->dPdx[c] = fetchSrc(Dx >> 4, (Dx & 3) + c);
+ texi->dPdy[c] = fetchSrc(Dy >> 4, (Dy & 3) + c);
+ }
+ }
+
+ // cube textures don't care about projection value, it's divided out
+ if (tgsi.getOpcode() == TGSI_OPCODE_TXP && !tgt.isCube() && !tgt.isArray()) {
+ unsigned int n = tgt.getDim();
+ if (shd) {
+ arg[n] = shd;
+ ++n;
+ assert(tgt.getDim() == tgt.getArgCount());
+ }
+ loadProjTexCoords(src, arg, (1 << n) - 1);
+ if (shd)
+ shd = src[n - 1];
+ }
+
+ if (tgt.isCube()) {
+ for (c = 0; c < 3; ++c)
+ src[c] = mkOp1v(OP_ABS, TYPE_F32, getSSA(), arg[c]);
+ val = getScratch();
+ mkOp2(OP_MAX, TYPE_F32, val, src[0], src[1]);
+ mkOp2(OP_MAX, TYPE_F32, val, src[2], val);
+ mkOp1(OP_RCP, TYPE_F32, val, val);
+ for (c = 0; c < 3; ++c)
+ src[c] = mkOp2v(OP_MUL, TYPE_F32, getSSA(), arg[c], val);
+ }
+
+ for (c = 0, d = 0; c < 4; ++c) {
+ if (dst[c]) {
+ texi->setDef(d++, dst[c]);
+ texi->tex.mask |= 1 << c;
+ } else {
+ // NOTE: maybe hook up def too, for CSE
+ }
+ }
+ for (s = 0; s < tgt.getArgCount(); ++s)
+ texi->setSrc(s, src[s]);
+ if (lod)
+ texi->setSrc(s++, lod);
+ if (shd)
+ texi->setSrc(s++, shd);
+
+ setTexRS(texi, s, R, S);
+
+ if (tgsi.getOpcode() == TGSI_OPCODE_SAMPLE_C_LZ)
+ texi->tex.levelZero = true;
+
+ bb->insertTail(texi);
+}
+
+// 1st source: xyz = coordinates, w = lod
+// 2nd source: offset
+void
+Converter::handleTXF(Value *dst[4], int R)
+{
+ TexInstruction *texi = new_TexInstruction(func, tgsi.getOP());
+ unsigned int c, d, s;
+
+ texi->tex.target = tgsi.getTexture(code, R);
+
+ for (c = 0, d = 0; c < 4; ++c) {
+ if (dst[c]) {
+ texi->setDef(d++, dst[c]);
+ texi->tex.mask |= 1 << c;
+ }
+ }
+ for (c = 0; c < texi->tex.target.getArgCount(); ++c)
+ texi->setSrc(c, fetchSrc(0, c));
+ texi->setSrc(c++, fetchSrc(0, 3)); // lod
+
+ setTexRS(texi, c, R, -1);
+
+ for (s = 0; s < tgsi.getNumTexOffsets(); ++s) {
+ for (c = 0; c < 3; ++c) {
+ texi->tex.offset[s][c] = tgsi.getTexOffset(s).getValueU32(c, info);
+ if (texi->tex.offset[s][c])
+ texi->tex.useOffsets = s + 1;
+ }
+ }
+
+ bb->insertTail(texi);
+}
+
+void
+Converter::handleLIT(Value *dst0[4])
+{
+ Value *val0 = NULL;
+ unsigned int mask = tgsi.getDst(0).getMask();
+
+ if (mask & (1 << 0))
+ loadImm(dst0[0], 1.0f);
+
+ if (mask & (1 << 3))
+ loadImm(dst0[3], 1.0f);
+
+ if (mask & (3 << 1)) {
+ val0 = getScratch();
+ mkOp2(OP_MAX, TYPE_F32, val0, fetchSrc(0, 0), zero);
+ if (mask & (1 << 1))
+ mkMov(dst0[1], val0);
+ }
+
+ if (mask & (1 << 2)) {
+ Value *src1 = fetchSrc(0, 1), *src3 = fetchSrc(0, 3);
+ Value *val1 = getScratch(), *val3 = getScratch();
+
+ Value *pos128 = loadImm(NULL, +127.999999f);
+ Value *neg128 = loadImm(NULL, -127.999999f);
+
+ mkOp2(OP_MAX, TYPE_F32, val1, src1, zero);
+ mkOp2(OP_MAX, TYPE_F32, val3, src3, neg128);
+ mkOp2(OP_MIN, TYPE_F32, val3, val3, pos128);
+ mkOp2(OP_POW, TYPE_F32, val3, val1, val3);
+
+ mkCmp(OP_SLCT, CC_GT, TYPE_F32, dst0[2], val3, zero, val0);
+ }
+}
+
+bool
+Converter::isEndOfSubroutine(uint ip)
+{
+ assert(ip < code->scan.num_instructions);
+ tgsi::Instruction insn(&code->insns[ip]);
+ return (insn.getOpcode() == TGSI_OPCODE_END ||
+ insn.getOpcode() == TGSI_OPCODE_ENDSUB ||
+ // does END occur at end of main or the very end ?
+ insn.getOpcode() == TGSI_OPCODE_BGNSUB);
+}
+
+bool
+Converter::handleInstruction(const struct tgsi_full_instruction *insn)
+{
+ Value *dst0[4], *rDst0[4];
+ Value *src0, *src1, *src2;
+ Value *val0, *val1;
+ int c;
+
+ tgsi = tgsi::Instruction(insn);
+
+ bool useScratchDst = tgsi.checkDstSrcAliasing();
+
+ operation op = tgsi.getOP();
+ dstTy = tgsi.inferDstType();
+ srcTy = tgsi.inferSrcType();
+
+ unsigned int mask = tgsi.dstCount() ? tgsi.getDst(0).getMask() : 0;
+
+ if (tgsi.dstCount()) {
+ for (c = 0; c < 4; ++c) {
+ rDst0[c] = acquireDst(0, c);
+ dst0[c] = (useScratchDst && rDst0[c]) ? getScratch() : rDst0[c];
+ }
+ }
+
+ switch (tgsi.getOpcode()) {
+ case TGSI_OPCODE_ADD:
+ case TGSI_OPCODE_UADD:
+ case TGSI_OPCODE_AND:
+ case TGSI_OPCODE_DIV:
+ case TGSI_OPCODE_IDIV:
+ case TGSI_OPCODE_UDIV:
+ case TGSI_OPCODE_MAX:
+ case TGSI_OPCODE_MIN:
+ case TGSI_OPCODE_IMAX:
+ case TGSI_OPCODE_IMIN:
+ case TGSI_OPCODE_UMAX:
+ case TGSI_OPCODE_UMIN:
+ case TGSI_OPCODE_MOD:
+ case TGSI_OPCODE_UMOD:
+ case TGSI_OPCODE_MUL:
+ case TGSI_OPCODE_UMUL:
+ case TGSI_OPCODE_OR:
+ case TGSI_OPCODE_POW:
+ case TGSI_OPCODE_SHL:
+ case TGSI_OPCODE_ISHR:
+ case TGSI_OPCODE_USHR:
+ case TGSI_OPCODE_SUB:
+ case TGSI_OPCODE_XOR:
+ FOR_EACH_DST_ENABLED_CHANNEL(0, c, tgsi) {
+ src0 = fetchSrc(0, c);
+ src1 = fetchSrc(1, c);
+ mkOp2(op, dstTy, dst0[c], src0, src1);
+ }
+ break;
+ case TGSI_OPCODE_MAD:
+ case TGSI_OPCODE_UMAD:
+ case TGSI_OPCODE_SAD:
+ FOR_EACH_DST_ENABLED_CHANNEL(0, c, tgsi) {
+ src0 = fetchSrc(0, c);
+ src1 = fetchSrc(1, c);
+ src2 = fetchSrc(2, c);
+ mkOp3(op, dstTy, dst0[c], src0, src1, src2);
+ }
+ break;
+ case TGSI_OPCODE_MOV:
+ case TGSI_OPCODE_ABS:
+ case TGSI_OPCODE_CEIL:
+ case TGSI_OPCODE_FLR:
+ case TGSI_OPCODE_TRUNC:
+ case TGSI_OPCODE_RCP:
+ case TGSI_OPCODE_INEG:
+ case TGSI_OPCODE_NOT:
+ case TGSI_OPCODE_DDX:
+ case TGSI_OPCODE_DDY:
+ FOR_EACH_DST_ENABLED_CHANNEL(0, c, tgsi)
+ mkOp1(op, dstTy, dst0[c], fetchSrc(0, c));
+ break;
+ case TGSI_OPCODE_RSQ:
+ src0 = fetchSrc(0, 0);
+ val0 = getScratch();
+ mkOp1(OP_ABS, TYPE_F32, val0, src0);
+ mkOp1(OP_RSQ, TYPE_F32, val0, val0);
+ FOR_EACH_DST_ENABLED_CHANNEL(0, c, tgsi)
+ mkMov(dst0[c], val0);
+ break;
+ case TGSI_OPCODE_ARL:
+ FOR_EACH_DST_ENABLED_CHANNEL(0, c, tgsi) {
+ src0 = fetchSrc(0, c);
+ mkCvt(OP_CVT, TYPE_S32, dst0[c], TYPE_F32, src0)->rnd = ROUND_M;
+ mkOp2(OP_SHL, TYPE_U32, dst0[c], dst0[c], mkImm(4));
+ }
+ break;
+ case TGSI_OPCODE_UARL:
+ FOR_EACH_DST_ENABLED_CHANNEL(0, c, tgsi)
+ mkOp2(OP_SHL, TYPE_U32, dst0[c], fetchSrc(0, c), mkImm(4));
+ break;
+ case TGSI_OPCODE_EX2:
+ case TGSI_OPCODE_LG2:
+ val0 = mkOp1(op, TYPE_F32, getScratch(), fetchSrc(0, 0))->getDef(0);
+ FOR_EACH_DST_ENABLED_CHANNEL(0, c, tgsi)
+ mkOp1(OP_MOV, TYPE_F32, dst0[c], val0);
+ break;
+ case TGSI_OPCODE_COS:
+ case TGSI_OPCODE_SIN:
+ val0 = getScratch();
+ if (mask & 7) {
+ mkOp1(OP_PRESIN, TYPE_F32, val0, fetchSrc(0, 0));
+ mkOp1(op, TYPE_F32, val0, val0);
+ for (c = 0; c < 3; ++c)
+ if (dst0[c])
+ mkMov(dst0[c], val0);
+ }
+ if (dst0[3]) {
+ mkOp1(OP_PRESIN, TYPE_F32, val0, fetchSrc(0, 3));
+ mkOp1(op, TYPE_F32, dst0[3], val0);
+ }
+ break;
+ case TGSI_OPCODE_SCS:
+ if (mask & 3) {
+ val0 = mkOp1v(OP_PRESIN, TYPE_F32, getSSA(), fetchSrc(0, 0));
+ if (dst0[0])
+ mkOp1(OP_COS, TYPE_F32, dst0[0], val0);
+ if (dst0[1])
+ mkOp1(OP_SIN, TYPE_F32, dst0[1], val0);
+ }
+ if (dst0[2])
+ loadImm(dst0[2], 0.0f);
+ if (dst0[3])
+ loadImm(dst0[3], 1.0f);
+ break;
+ case TGSI_OPCODE_EXP:
+ src0 = fetchSrc(0, 0);
+ val0 = mkOp1v(OP_FLOOR, TYPE_F32, getSSA(), src0);
+ if (dst0[1])
+ mkOp2(OP_SUB, TYPE_F32, dst0[1], src0, val0);
+ if (dst0[0])
+ mkOp1(OP_EX2, TYPE_F32, dst0[0], val0);
+ if (dst0[2])
+ mkOp1(OP_EX2, TYPE_F32, dst0[2], src0);
+ if (dst0[3])
+ loadImm(dst0[3], 1.0f);
+ break;
+ case TGSI_OPCODE_LOG:
+ src0 = mkOp1v(OP_ABS, TYPE_F32, getSSA(), fetchSrc(0, 0));
+ val0 = mkOp1v(OP_LG2, TYPE_F32, dst0[2] ? dst0[2] : getSSA(), src0);
+ if (dst0[0] || dst0[1])
+ val1 = mkOp1v(OP_FLOOR, TYPE_F32, dst0[0] ? dst0[0] : getSSA(), val0);
+ if (dst0[1]) {
+ mkOp1(OP_EX2, TYPE_F32, dst0[1], val1);
+ mkOp1(OP_RCP, TYPE_F32, dst0[1], dst0[1]);
+ mkOp2(OP_MUL, TYPE_F32, dst0[1], dst0[1], src0);
+ }
+ if (dst0[3])
+ loadImm(dst0[3], 1.0f);
+ break;
+ case TGSI_OPCODE_DP2:
+ val0 = buildDot(2);
+ FOR_EACH_DST_ENABLED_CHANNEL(0, c, tgsi)
+ mkMov(dst0[c], val0);
+ break;
+ case TGSI_OPCODE_DP3:
+ val0 = buildDot(3);
+ FOR_EACH_DST_ENABLED_CHANNEL(0, c, tgsi)
+ mkMov(dst0[c], val0);
+ break;
+ case TGSI_OPCODE_DP4:
+ val0 = buildDot(4);
+ FOR_EACH_DST_ENABLED_CHANNEL(0, c, tgsi)
+ mkMov(dst0[c], val0);
+ break;
+ case TGSI_OPCODE_DPH:
+ val0 = buildDot(3);
+ src1 = fetchSrc(1, 3);
+ mkOp2(OP_ADD, TYPE_F32, val0, val0, src1);
+ FOR_EACH_DST_ENABLED_CHANNEL(0, c, tgsi)
+ mkMov(dst0[c], val0);
+ break;
+ case TGSI_OPCODE_DST:
+ if (dst0[0])
+ loadImm(dst0[0], 1.0f);
+ if (dst0[1]) {
+ src0 = fetchSrc(0, 1);
+ src1 = fetchSrc(1, 1);
+ mkOp2(OP_MUL, TYPE_F32, dst0[1], src0, src1);
+ }
+ if (dst0[2])
+ mkMov(dst0[2], fetchSrc(0, 2));
+ if (dst0[3])
+ mkMov(dst0[3], fetchSrc(1, 3));
+ break;
+ case TGSI_OPCODE_LRP:
+ FOR_EACH_DST_ENABLED_CHANNEL(0, c, tgsi) {
+ src0 = fetchSrc(0, c);
+ src1 = fetchSrc(1, c);
+ src2 = fetchSrc(2, c);
+ mkOp3(OP_MAD, TYPE_F32, dst0[c],
+ mkOp2v(OP_SUB, TYPE_F32, getSSA(), src1, src2), src0, src2);
+ }
+ break;
+ case TGSI_OPCODE_LIT:
+ handleLIT(dst0);
+ break;
+ case TGSI_OPCODE_XPD:
+ FOR_EACH_DST_ENABLED_CHANNEL(0, c, tgsi) {
+ if (c < 3) {
+ val0 = getSSA();
+ src0 = fetchSrc(1, (c + 1) % 3);
+ src1 = fetchSrc(0, (c + 2) % 3);
+ mkOp2(OP_MUL, TYPE_F32, val0, src0, src1);
+ mkOp1(OP_NEG, TYPE_F32, val0, val0);
+
+ src0 = fetchSrc(0, (c + 1) % 3);
+ src1 = fetchSrc(1, (c + 2) % 3);
+ mkOp3(OP_MAD, TYPE_F32, dst0[c], src0, src1, val0);
+ } else {
+ loadImm(dst0[c], 1.0f);
+ }
+ }
+ break;
+ case TGSI_OPCODE_SSG:
+ FOR_EACH_DST_ENABLED_CHANNEL(0, c, tgsi) {
+ src0 = fetchSrc(0, c);
+ val0 = getScratch();
+ val1 = getScratch();
+ mkCmp(OP_SET, CC_GT, TYPE_F32, val0, src0, zero);
+ mkCmp(OP_SET, CC_LT, TYPE_F32, val1, src0, zero);
+ mkOp2(OP_SUB, TYPE_F32, dst0[c], val0, val1);
+ }
+ break;
+ case TGSI_OPCODE_UCMP:
+ case TGSI_OPCODE_CMP:
+ FOR_EACH_DST_ENABLED_CHANNEL(0, c, tgsi) {
+ src0 = fetchSrc(0, c);
+ src1 = fetchSrc(1, c);
+ src2 = fetchSrc(2, c);
+ if (src1 == src2)
+ mkMov(dst0[c], src1);
+ else
+ mkCmp(OP_SLCT, (srcTy == TYPE_F32) ? CC_LT : CC_NE,
+ srcTy, dst0[c], src1, src2, src0);
+ }
+ break;
+ case TGSI_OPCODE_FRC:
+ FOR_EACH_DST_ENABLED_CHANNEL(0, c, tgsi) {
+ src0 = fetchSrc(0, c);
+ val0 = getScratch();
+ mkOp1(OP_FLOOR, TYPE_F32, val0, src0);
+ mkOp2(OP_SUB, TYPE_F32, dst0[c], src0, val0);
+ }
+ break;
+ case TGSI_OPCODE_ROUND:
+ FOR_EACH_DST_ENABLED_CHANNEL(0, c, tgsi)
+ mkCvt(OP_CVT, TYPE_F32, dst0[c], TYPE_F32, fetchSrc(0, c))
+ ->rnd = ROUND_NI;
+ break;
+ case TGSI_OPCODE_CLAMP:
+ FOR_EACH_DST_ENABLED_CHANNEL(0, c, tgsi) {
+ src0 = fetchSrc(0, c);
+ src1 = fetchSrc(1, c);
+ src2 = fetchSrc(2, c);
+ val0 = getScratch();
+ mkOp2(OP_MIN, TYPE_F32, val0, src0, src1);
+ mkOp2(OP_MAX, TYPE_F32, dst0[c], val0, src2);
+ }
+ break;
+ case TGSI_OPCODE_SLT:
+ case TGSI_OPCODE_SGE:
+ case TGSI_OPCODE_SEQ:
+ case TGSI_OPCODE_SFL:
+ case TGSI_OPCODE_SGT:
+ case TGSI_OPCODE_SLE:
+ case TGSI_OPCODE_SNE:
+ case TGSI_OPCODE_STR:
+ case TGSI_OPCODE_ISGE:
+ case TGSI_OPCODE_ISLT:
+ case TGSI_OPCODE_USEQ:
+ case TGSI_OPCODE_USGE:
+ case TGSI_OPCODE_USLT:
+ case TGSI_OPCODE_USNE:
+ FOR_EACH_DST_ENABLED_CHANNEL(0, c, tgsi) {
+ src0 = fetchSrc(0, c);
+ src1 = fetchSrc(1, c);
+ mkCmp(op, tgsi.getSetCond(), dstTy, dst0[c], src0, src1);
+ }
+ break;
+ case TGSI_OPCODE_KIL:
+ val0 = new_LValue(func, FILE_PREDICATE);
+ for (c = 0; c < 4; ++c) {
+ mkCmp(OP_SET, CC_LT, TYPE_F32, val0, fetchSrc(0, c), zero);
+ mkOp(OP_DISCARD, TYPE_NONE, NULL)->setPredicate(CC_P, val0);
+ }
+ break;
+ case TGSI_OPCODE_KILP:
+ mkOp(OP_DISCARD, TYPE_NONE, NULL);
+ break;
+ case TGSI_OPCODE_TEX:
+ case TGSI_OPCODE_TXB:
+ case TGSI_OPCODE_TXL:
+ case TGSI_OPCODE_TXP:
+ // R S L C Dx Dy
+ handleTEX(dst0, 1, 1, 0x03, 0x0f, 0x00, 0x00);
+ break;
+ case TGSI_OPCODE_TXD:
+ handleTEX(dst0, 3, 3, 0x03, 0x0f, 0x10, 0x20);
+ break;
+ case TGSI_OPCODE_SAMPLE:
+ case TGSI_OPCODE_SAMPLE_B:
+ case TGSI_OPCODE_SAMPLE_D:
+ case TGSI_OPCODE_SAMPLE_L:
+ case TGSI_OPCODE_SAMPLE_C:
+ case TGSI_OPCODE_SAMPLE_C_LZ:
+ handleTEX(dst0, 1, 2, 0x30, 0x31, 0x40, 0x50);
+ break;
+ case TGSI_OPCODE_TXF:
+ case TGSI_OPCODE_LOAD:
+ handleTXF(dst0, 1);
+ break;
+ case TGSI_OPCODE_TXQ:
+ case TGSI_OPCODE_RESINFO:
+ handleTXQ(dst0, TXQ_DIMS);
+ break;
+ case TGSI_OPCODE_F2I:
+ case TGSI_OPCODE_F2U:
+ FOR_EACH_DST_ENABLED_CHANNEL(0, c, tgsi)
+ mkCvt(OP_CVT, dstTy, dst0[c], srcTy, fetchSrc(0, c))->rnd = ROUND_Z;
+ break;
+ case TGSI_OPCODE_I2F:
+ case TGSI_OPCODE_U2F:
+ FOR_EACH_DST_ENABLED_CHANNEL(0, c, tgsi)
+ mkCvt(OP_CVT, dstTy, dst0[c], srcTy, fetchSrc(0, c));
+ break;
+ case TGSI_OPCODE_EMIT:
+ case TGSI_OPCODE_ENDPRIM:
+ // get vertex stream if specified (must be immediate)
+ src0 = tgsi.srcCount() ?
+ mkImm(tgsi.getSrc(0).getValueU32(0, info)) : zero;
+ mkOp1(op, TYPE_U32, NULL, src0)->fixed = 1;
+ break;
+ case TGSI_OPCODE_IF:
+ {
+ BasicBlock *ifBB = new BasicBlock(func);
+
+ bb->cfg.attach(&ifBB->cfg, Graph::Edge::TREE);
+ condBBs.push(bb);
+ joinBBs.push(bb);
+
+ mkFlow(OP_BRA, NULL, CC_NOT_P, fetchSrc(0, 0));
+
+ setPosition(ifBB, true);
+ }
+ break;
+ case TGSI_OPCODE_ELSE:
+ {
+ BasicBlock *elseBB = new BasicBlock(func);
+ BasicBlock *forkBB = reinterpret_cast<BasicBlock *>(condBBs.pop().u.p);
+
+ forkBB->cfg.attach(&elseBB->cfg, Graph::Edge::TREE);
+ condBBs.push(bb);
+
+ forkBB->getExit()->asFlow()->target.bb = elseBB;
+ if (!bb->isTerminated())
+ mkFlow(OP_BRA, NULL, CC_ALWAYS, NULL);
+
+ setPosition(elseBB, true);
+ }
+ break;
+ case TGSI_OPCODE_ENDIF:
+ {
+ BasicBlock *convBB = new BasicBlock(func);
+ BasicBlock *prevBB = reinterpret_cast<BasicBlock *>(condBBs.pop().u.p);
+ BasicBlock *forkBB = reinterpret_cast<BasicBlock *>(joinBBs.pop().u.p);
+
+ if (!bb->isTerminated()) {
+ // we only want join if none of the clauses ended with CONT/BREAK/RET
+ if (prevBB->getExit()->op == OP_BRA && joinBBs.getSize() < 6)
+ insertConvergenceOps(convBB, forkBB);
+ mkFlow(OP_BRA, convBB, CC_ALWAYS, NULL);
+ bb->cfg.attach(&convBB->cfg, Graph::Edge::FORWARD);
+ }
+
+ if (prevBB->getExit()->op == OP_BRA) {
+ prevBB->cfg.attach(&convBB->cfg, Graph::Edge::FORWARD);
+ prevBB->getExit()->asFlow()->target.bb = convBB;
+ }
+ setPosition(convBB, true);
+ }
+ break;
+ case TGSI_OPCODE_BGNLOOP:
+ {
+ BasicBlock *lbgnBB = new BasicBlock(func);
+ BasicBlock *lbrkBB = new BasicBlock(func);
+
+ loopBBs.push(lbgnBB);
+ breakBBs.push(lbrkBB);
+ if (loopBBs.getSize() > func->loopNestingBound)
+ func->loopNestingBound++;
+
+ mkFlow(OP_PREBREAK, lbrkBB, CC_ALWAYS, NULL);
+
+ bb->cfg.attach(&lbgnBB->cfg, Graph::Edge::TREE);
+ setPosition(lbgnBB, true);
+ mkFlow(OP_PRECONT, lbgnBB, CC_ALWAYS, NULL);
+ }
+ break;
+ case TGSI_OPCODE_ENDLOOP:
+ {
+ BasicBlock *loopBB = reinterpret_cast<BasicBlock *>(loopBBs.pop().u.p);
+
+ if (!bb->isTerminated()) {
+ mkFlow(OP_CONT, loopBB, CC_ALWAYS, NULL);
+ bb->cfg.attach(&loopBB->cfg, Graph::Edge::BACK);
+ }
+ setPosition(reinterpret_cast<BasicBlock *>(breakBBs.pop().u.p), true);
+ }
+ break;
+ case TGSI_OPCODE_BRK:
+ {
+ if (bb->isTerminated())
+ break;
+ BasicBlock *brkBB = reinterpret_cast<BasicBlock *>(breakBBs.peek().u.p);
+ mkFlow(OP_BREAK, brkBB, CC_ALWAYS, NULL);
+ bb->cfg.attach(&brkBB->cfg, Graph::Edge::CROSS);
+ }
+ break;
+ case TGSI_OPCODE_CONT:
+ {
+ if (bb->isTerminated())
+ break;
+ BasicBlock *contBB = reinterpret_cast<BasicBlock *>(loopBBs.peek().u.p);
+ mkFlow(OP_CONT, contBB, CC_ALWAYS, NULL);
+ contBB->explicitCont = true;
+ bb->cfg.attach(&contBB->cfg, Graph::Edge::BACK);
+ }
+ break;
+ case TGSI_OPCODE_BGNSUB:
+ {
+ if (!retIPs.getSize()) {
+ // end of main function
+ ip = code->scan.num_instructions - 2; // goto END
+ return true;
+ }
+ BasicBlock *entry = new BasicBlock(func);
+ BasicBlock *leave = new BasicBlock(func);
+ entryBBs.push(entry);
+ leaveBBs.push(leave);
+ bb->cfg.attach(&entry->cfg, Graph::Edge::TREE);
+ setPosition(entry, true);
+ }
+ return true;
+ case TGSI_OPCODE_ENDSUB:
+ {
+ BasicBlock *leave = reinterpret_cast<BasicBlock *>(leaveBBs.pop().u.p);
+ entryBBs.pop();
+ bb->cfg.attach(&leave->cfg, Graph::Edge::TREE);
+ setPosition(leave, true);
+ ip = retIPs.pop().u.u;
+ }
+ return true;
+ case TGSI_OPCODE_CAL:
+ // we don't have function declarations, so inline everything
+ retIPs.push(ip);
+ ip = code->subroutines[tgsi.getLabel()].pc - 1; // +1 after return
+ return true;
+ case TGSI_OPCODE_RET:
+ {
+ if (bb->isTerminated())
+ return true;
+ BasicBlock *entry = reinterpret_cast<BasicBlock *>(entryBBs.peek().u.p);
+ BasicBlock *leave = reinterpret_cast<BasicBlock *>(leaveBBs.peek().u.p);
+ if (!isEndOfSubroutine(ip + 1)) {
+ // insert a PRERET at the entry if this is an early return
+ FlowInstruction *preRet = new_FlowInstruction(func, OP_PRERET, leave);
+ preRet->fixed = 1;
+ entry->insertHead(preRet);
+ bb->cfg.attach(&leave->cfg, Graph::Edge::CROSS);
+ }
+ // everything inlined so RET serves only to wrap up the stack
+ if (entry->getEntry() && entry->getEntry()->op == OP_PRERET)
+ mkFlow(OP_RET, NULL, CC_ALWAYS, NULL)->fixed = 1;
+ }
+ break;
+ case TGSI_OPCODE_END:
+ {
+ // attach and generate epilogue code
+ BasicBlock *epilogue = reinterpret_cast<BasicBlock *>(leaveBBs.pop().u.p);
+ entryBBs.pop();
+ bb->cfg.attach(&epilogue->cfg, Graph::Edge::TREE);
+ setPosition(epilogue, true);
+ if (prog->getType() == Program::TYPE_FRAGMENT)
+ exportOutputs();
+ if (info->io.clipDistanceCount)
+ handleUserClipPlanes();
+ mkOp(OP_EXIT, TYPE_NONE, NULL)->terminator = 1;
+ }
+ break;
+ case TGSI_OPCODE_SWITCH:
+ case TGSI_OPCODE_CASE:
+ ERROR("switch/case opcode encountered, should have been lowered\n");
+ abort();
+ break;
+ default:
+ ERROR("unhandled TGSI opcode: %u\n", tgsi.getOpcode());
+ assert(0);
+ break;
+ }
+
+ if (tgsi.dstCount()) {
+ for (c = 0; c < 4; ++c) {
+ if (!dst0[c])
+ continue;
+ if (dst0[c] != rDst0[c])
+ mkMov(rDst0[c], dst0[c]);
+ storeDst(0, c, rDst0[c]);
+ }
+ }
+ vtxBaseValid = 0;
+
+ return true;
+}
+
+void
+Converter::handleUserClipPlanes()
+{
+ Value *res[8];
+ int i, c;
+
+ for (c = 0; c < 4; ++c) {
+ for (i = 0; i < info->io.clipDistanceCount; ++i) {
+ Value *ucp;
+ ucp = mkLoad(TYPE_F32, mkSymbol(FILE_MEMORY_CONST, 15, TYPE_F32,
+ i * 16 + c * 4), NULL);
+ if (c == 0)
+ res[i] = mkOp2v(OP_MUL, TYPE_F32, getScratch(), clipVtx[c], ucp);
+ else
+ mkOp3(OP_MAD, TYPE_F32, res[i], clipVtx[c], ucp, res[i]);
+ }
+ }
+
+ for (i = 0; i < info->io.clipDistanceCount; ++i)
+ mkOp2(OP_WRSV, TYPE_F32, NULL, mkSysVal(SV_CLIP_DISTANCE, i), res[i]);
+}
+
+void
+Converter::exportOutputs()
+{
+ for (unsigned int i = 0; i < info->numOutputs; ++i) {
+ for (unsigned int c = 0; c < 4; ++c) {
+ if (!oData.exists(i, c))
+ continue;
+ Symbol *sym = mkSymbol(FILE_SHADER_OUTPUT, 0, TYPE_F32,
+ info->out[i].slot[c] * 4);
+ Value *val = oData.load(i, c, NULL);
+ if (val)
+ mkStore(OP_EXPORT, TYPE_F32, sym, NULL, val);
+ }
+ }
+}
+
+Converter::Converter(Program *ir, const tgsi::Source *src)
+ : code(src),
+ tgsi(NULL),
+ tData(this), aData(this), pData(this), oData(this)
+{
+ prog = ir;
+ info = code->info;
+
+ DataFile tFile = code->mainTempsInLMem ? FILE_MEMORY_LOCAL : FILE_GPR;
+
+ tData.setup(0, code->fileSize(TGSI_FILE_TEMPORARY), 4, 4, tFile);
+ pData.setup(0, code->fileSize(TGSI_FILE_PREDICATE), 4, 4, FILE_PREDICATE);
+ aData.setup(0, code->fileSize(TGSI_FILE_ADDRESS), 4, 4, FILE_ADDRESS);
+ oData.setup(0, code->fileSize(TGSI_FILE_OUTPUT), 4, 4, FILE_GPR);
+
+ lData = NULL;
+ iData = NULL;
+
+ zero = mkImm((uint32_t)0);
+
+ vtxBaseValid = 0;
+}
+
+Converter::~Converter()
+{
+ if (lData)
+ delete[] lData;
+ if (iData)
+ delete[] iData;
+}
+
+bool
+Converter::run()
+{
+ BasicBlock *entry = new BasicBlock(prog->main);
+ BasicBlock *leave = new BasicBlock(prog->main);
+
+ if (code->tempArrayCount && !lData) {
+ uint32_t volume = 0;
+ lData = new DataArray[code->tempArrayCount];
+ if (!lData)
+ return false;
+ for (int i = 0; i < code->tempArrayCount; ++i) {
+ int len = code->tempArrays[i].u32 >> 2;
+ int dim = code->tempArrays[i].u32 & 3;
+ lData[i].setParent(this);
+ lData[i].setup(volume, len, dim, 4, FILE_MEMORY_LOCAL);
+ volume += (len * dim * 4 + 0xf) & ~0xf;
+ }
+ }
+ if (code->immdArrayCount && !iData) {
+ uint32_t volume = 0;
+ iData = new DataArray[code->immdArrayCount];
+ if (!iData)
+ return false;
+ for (int i = 0; i < code->immdArrayCount; ++i) {
+ int len = code->immdArrays[i].u32 >> 2;
+ int dim = code->immdArrays[i].u32 & 3;
+ iData[i].setParent(this);
+ iData[i].setup(volume, len, dim, 4, FILE_MEMORY_CONST, 14);
+ volume += (len * dim * 4 + 0xf) & ~0xf;
+ }
+ }
+
+ prog->main->setEntry(entry);
+ prog->main->setExit(leave);
+
+ setPosition(entry, true);
+ entryBBs.push(entry);
+ leaveBBs.push(leave);
+
+ if (info->io.clipDistanceCount) {
+ for (int c = 0; c < 4; ++c)
+ clipVtx[c] = getScratch();
+ }
+
+ if (prog->getType() == Program::TYPE_FRAGMENT) {
+ Symbol *sv = mkSysVal(SV_POSITION, 3);
+ fragCoord[3] = mkOp1v(OP_RDSV, TYPE_F32, getSSA(), sv);
+ mkOp1(OP_RCP, TYPE_F32, fragCoord[3], fragCoord[3]);
+ }
+
+ for (ip = 0; ip < code->scan.num_instructions; ++ip) {
+ if (!handleInstruction(&code->insns[ip]))
+ return false;
+ }
+ return true;
+}
+
+} // unnamed namespace
+
+namespace nv50_ir {
+
+bool
+Program::makeFromTGSI(struct nv50_ir_prog_info *info)
+{
+ tgsi::Source src(info);
+ if (!src.scanSource())
+ return false;
+
+ Converter builder(this, &src);
+ return builder.run();
+}
+
+} // namespace nv50_ir
diff --git a/src/gallium/drivers/nv50/codegen/nv50_ir_graph.cpp b/src/gallium/drivers/nv50/codegen/nv50_ir_graph.cpp
new file mode 100644
index 00000000000..08075751d14
--- /dev/null
+++ b/src/gallium/drivers/nv50/codegen/nv50_ir_graph.cpp
@@ -0,0 +1,381 @@
+
+#include "nv50_ir_graph.h"
+
+namespace nv50_ir {
+
+Graph::Graph()
+{
+ root = NULL;
+ size = 0;
+ sequence = 0;
+}
+
+Graph::~Graph()
+{
+ Iterator *iter = this->safeIteratorDFS();
+
+ for (; !iter->end(); iter->next())
+ reinterpret_cast<Node *>(iter->get())->cut();
+
+ putIterator(iter);
+}
+
+void Graph::insert(Node *node)
+{
+ if (!root) {
+ root = node;
+ size = 1;
+ node->graph = this;
+ } else {
+ root->attach(node, Edge::TREE);
+ }
+}
+
+void Graph::Edge::unlink()
+{
+ if (origin) {
+ prev[0]->next[0] = next[0];
+ next[0]->prev[0] = prev[0];
+ if (origin->out == this)
+ origin->out = (next[0] == this) ? NULL : next[0];
+
+ --origin->outCount;
+ }
+ if (target) {
+ prev[1]->next[1] = next[1];
+ next[1]->prev[1] = prev[1];
+ if (target->in == this)
+ target->in = (next[1] == this) ? NULL : next[1];
+
+ --target->inCount;
+ }
+}
+
+const char *Graph::Edge::typeStr() const
+{
+ switch (type) {
+ case TREE: return "tree";
+ case FORWARD: return "forward";
+ case BACK: return "back";
+ case CROSS: return "cross";
+ case DUMMY: return "dummy";
+ case UNKNOWN:
+ default:
+ return "unk";
+ }
+}
+
+Graph::Node::Node(void *priv) : data(priv),
+ in(0), out(0), graph(0),
+ visited(0),
+ inCount(0), outCount(0)
+{
+ // nothing to do
+}
+
+void Graph::Node::attach(Node *node, Edge::Type kind)
+{
+ Edge *edge = new Edge(this, node, kind);
+
+ // insert head
+ if (this->out) {
+ edge->next[0] = this->out;
+ edge->prev[0] = this->out->prev[0];
+ edge->prev[0]->next[0] = edge;
+ this->out->prev[0] = edge;
+ }
+ this->out = edge;
+
+ if (node->in) {
+ edge->next[1] = node->in;
+ edge->prev[1] = node->in->prev[1];
+ edge->prev[1]->next[1] = edge;
+ node->in->prev[1] = edge;
+ }
+ node->in = edge;
+
+ ++this->outCount;
+ ++node->inCount;
+
+ assert(this->graph);
+ if (!node->graph) {
+ node->graph = this->graph;
+ ++node->graph->size;
+ }
+
+ if (kind == Edge::UNKNOWN)
+ graph->classifyEdges();
+}
+
+bool Graph::Node::detach(Graph::Node *node)
+{
+ EdgeIterator ei = this->outgoing();
+ for (; !ei.end(); ei.next())
+ if (ei.getNode() == node)
+ break;
+ if (ei.end()) {
+ ERROR("no such node attached\n");
+ return false;
+ }
+ delete ei.getEdge();
+ return true;
+}
+
+// Cut a node from the graph, deleting all attached edges.
+void Graph::Node::cut()
+{
+ if (!graph || (!in && !out))
+ return;
+
+ while (out)
+ delete out;
+ while (in)
+ delete in;
+
+ if (graph->root == this)
+ graph->root = NULL;
+}
+
+Graph::Edge::Edge(Node *org, Node *tgt, Type kind)
+{
+ target = tgt;
+ origin = org;
+ type = kind;
+
+ next[0] = next[1] = this;
+ prev[0] = prev[1] = this;
+}
+
+bool
+Graph::Node::reachableBy(Node *node, Node *term)
+{
+ Stack stack;
+ Node *pos;
+ const int seq = graph->nextSequence();
+
+ stack.push(node);
+
+ while (stack.getSize()) {
+ pos = reinterpret_cast<Node *>(stack.pop().u.p);
+
+ if (pos == this)
+ return true;
+ if (pos == term)
+ continue;
+
+ for (EdgeIterator ei = pos->outgoing(); !ei.end(); ei.next()) {
+ if (ei.getType() == Edge::BACK || ei.getType() == Edge::DUMMY)
+ continue;
+ if (ei.getNode()->visit(seq))
+ stack.push(ei.getNode());
+ }
+ }
+ return pos == this;
+}
+
+class DFSIterator : public Graph::GraphIterator
+{
+public:
+ DFSIterator(Graph *graph, const bool preorder)
+ {
+ unsigned int seq = graph->nextSequence();
+
+ nodes = new Graph::Node * [graph->getSize() + 1];
+ count = 0;
+ pos = 0;
+ nodes[graph->getSize()] = 0;
+
+ if (graph->getRoot()) {
+ graph->getRoot()->visit(seq);
+ search(graph->getRoot(), preorder, seq);
+ }
+ }
+
+ ~DFSIterator()
+ {
+ if (nodes)
+ delete[] nodes;
+ }
+
+ void search(Graph::Node *node, const bool preorder, const int sequence)
+ {
+ if (preorder)
+ nodes[count++] = node;
+
+ for (Graph::EdgeIterator ei = node->outgoing(); !ei.end(); ei.next())
+ if (ei.getNode()->visit(sequence))
+ search(ei.getNode(), preorder, sequence);
+
+ if (!preorder)
+ nodes[count++] = node;
+ }
+
+ virtual bool end() const { return pos >= count; }
+ virtual void next() { if (pos < count) ++pos; }
+ virtual void *get() const { return nodes[pos]; }
+
+ void reset() { pos = 0; }
+
+protected:
+ Graph::Node **nodes;
+ int count;
+ int pos;
+};
+
+Graph::GraphIterator *Graph::iteratorDFS(bool preorder)
+{
+ return new DFSIterator(this, preorder);
+}
+
+Graph::GraphIterator *Graph::safeIteratorDFS(bool preorder)
+{
+ return this->iteratorDFS(preorder);
+}
+
+class CFGIterator : public Graph::GraphIterator
+{
+public:
+ CFGIterator(Graph *graph)
+ {
+ nodes = new Graph::Node * [graph->getSize() + 1];
+ count = 0;
+ pos = 0;
+ nodes[graph->getSize()] = 0;
+
+ // TODO: argh, use graph->sequence instead of tag and just raise it by > 1
+ Iterator *iter;
+ for (iter = graph->iteratorDFS(); !iter->end(); iter->next())
+ reinterpret_cast<Graph::Node *>(iter->get())->tag = 0;
+ graph->putIterator(iter);
+
+ if (graph->getRoot())
+ search(graph->getRoot(), graph->nextSequence());
+ }
+
+ ~CFGIterator()
+ {
+ if (nodes)
+ delete[] nodes;
+ }
+
+ virtual void *get() const { return nodes[pos]; }
+ virtual bool end() const { return pos >= count; }
+ virtual void next() { if (pos < count) ++pos; }
+
+private:
+ void search(Graph::Node *node, const int sequence)
+ {
+ Stack bb, cross;
+
+ bb.push(node);
+
+ while (bb.getSize()) {
+ node = reinterpret_cast<Graph::Node *>(bb.pop().u.p);
+ assert(node);
+ if (!node->visit(sequence))
+ continue;
+ node->tag = 0;
+
+ for (Graph::EdgeIterator ei = node->outgoing(); !ei.end(); ei.next()) {
+ switch (ei.getType()) {
+ case Graph::Edge::TREE:
+ case Graph::Edge::FORWARD:
+ case Graph::Edge::DUMMY:
+ if (++(ei.getNode()->tag) == ei.getNode()->incidentCountFwd())
+ bb.push(ei.getNode());
+ break;
+ case Graph::Edge::BACK:
+ continue;
+ case Graph::Edge::CROSS:
+ if (++(ei.getNode()->tag) == 1)
+ cross.push(ei.getNode());
+ break;
+ default:
+ assert(!"unknown edge kind in CFG");
+ break;
+ }
+ }
+ nodes[count++] = node;
+
+ if (bb.getSize() == 0)
+ cross.moveTo(bb);
+ }
+ }
+
+private:
+ Graph::Node **nodes;
+ int count;
+ int pos;
+};
+
+Graph::GraphIterator *Graph::iteratorCFG()
+{
+ return new CFGIterator(this);
+}
+
+Graph::GraphIterator *Graph::safeIteratorCFG()
+{
+ return this->iteratorCFG();
+}
+
+void Graph::classifyEdges()
+{
+ DFSIterator *iter;
+ int seq;
+
+ for (iter = new DFSIterator(this, true); !iter->end(); iter->next()) {
+ Node *node = reinterpret_cast<Node *>(iter->get());
+ node->visit(0);
+ node->tag = 0;
+ }
+ putIterator(iter);
+
+ classifyDFS(root, (seq = 0));
+
+ sequence = seq;
+}
+
+void Graph::classifyDFS(Node *curr, int& seq)
+{
+ Graph::Edge *edge;
+ Graph::Node *node;
+
+ curr->visit(++seq);
+ curr->tag = 1;
+
+ for (edge = curr->out; edge; edge = edge->next[0]) {
+ node = edge->target;
+ if (edge->type == Edge::DUMMY)
+ continue;
+
+ if (node->getSequence() == 0) {
+ edge->type = Edge::TREE;
+ classifyDFS(node, seq);
+ } else
+ if (node->getSequence() > curr->getSequence()) {
+ edge->type = Edge::FORWARD;
+ } else {
+ edge->type = node->tag ? Edge::BACK : Edge::CROSS;
+ }
+ }
+
+ for (edge = curr->in; edge; edge = edge->next[1]) {
+ node = edge->origin;
+ if (edge->type == Edge::DUMMY)
+ continue;
+
+ if (node->getSequence() == 0) {
+ edge->type = Edge::TREE;
+ classifyDFS(node, seq);
+ } else
+ if (node->getSequence() > curr->getSequence()) {
+ edge->type = Edge::FORWARD;
+ } else {
+ edge->type = node->tag ? Edge::BACK : Edge::CROSS;
+ }
+ }
+
+ curr->tag = 0;
+}
+
+} // namespace nv50_ir
diff --git a/src/gallium/drivers/nv50/codegen/nv50_ir_graph.h b/src/gallium/drivers/nv50/codegen/nv50_ir_graph.h
new file mode 100644
index 00000000000..6407ff98ab5
--- /dev/null
+++ b/src/gallium/drivers/nv50/codegen/nv50_ir_graph.h
@@ -0,0 +1,207 @@
+
+#ifndef __NV50_IR_GRAPH_H__
+#define __NV50_IR_GRAPH_H__
+
+#include "nv50_ir_util.h"
+
+namespace nv50_ir {
+
+#define ITER_NODE(x) reinterpret_cast<Graph::Node *>((x).get())
+#define ITER_EDGE(x) reinterpret_cast<Graph::Edge *>((x).get())
+
+// A connected graph.
+class Graph
+{
+public:
+ class Node;
+
+ class GraphIterator : public Iterator
+ {
+ public:
+ virtual ~GraphIterator() { };
+ };
+
+ class Edge
+ {
+ public:
+ enum Type
+ {
+ UNKNOWN,
+ TREE,
+ FORWARD,
+ BACK,
+ CROSS, // e.g. loop break
+ DUMMY
+ };
+
+ Edge(Node *dst, Node *src, Type kind);
+ ~Edge() { unlink(); }
+
+ inline Node *getOrigin() const { return origin; }
+ inline Node *getTarget() const { return target; }
+
+ inline Type getType() const { return type; }
+ const char *typeStr() const;
+
+ private:
+ Node *origin;
+ Node *target;
+
+ Type type;
+ Edge *next[2]; // next edge outgoing/incident from/to origin/target
+ Edge *prev[2];
+
+ void unlink();
+
+ friend class Graph;
+ };
+
+ class EdgeIterator : public Iterator
+ {
+ public:
+ EdgeIterator() : e(0), t(0), d(0) { }
+ EdgeIterator(Graph::Edge *first, int dir) : e(first), t(first), d(dir) { }
+
+ virtual void next() { e = (e->next[d] == t) ? 0 : e->next[d]; }
+ virtual bool end() const { return !e; }
+ virtual void *get() const { return e; }
+
+ inline Node *getNode() const { assert(e); return d ?
+ e->origin : e->target; }
+ inline Edge *getEdge() const { return e; }
+ inline Edge::Type getType() { return e ? e->getType() : Edge::UNKNOWN; }
+
+ private:
+ Graph::Edge *e;
+ Graph::Edge *t;
+ int d;
+ };
+
+ class Node
+ {
+ public:
+ Node(void *);
+ ~Node() { cut(); }
+
+ void attach(Node *, Edge::Type);
+ bool detach(Node *);
+ void cut();
+
+ inline EdgeIterator outgoing() const;
+ inline EdgeIterator incident() const;
+
+ inline Node *parent() const; // returns NULL if count(incident edges) != 1
+
+ bool reachableBy(Node *node, Node *term);
+
+ inline bool visit(int);
+ inline int getSequence() const;
+
+ inline int incidentCountFwd() const; // count of incident non-back edges
+ inline int incidentCount() const { return inCount; }
+ inline int outgoingCount() const { return outCount; }
+
+ Graph *getGraph() const { return graph; }
+
+ void *data;
+
+ private:
+ Edge *in;
+ Edge *out;
+ Graph *graph;
+
+ int visited;
+
+ int16_t inCount;
+ int16_t outCount;
+ public:
+ int tag; // for temporary use
+
+ friend class Graph;
+ };
+
+public:
+ Graph();
+ ~Graph(); // does *not* free the nodes (make it an option ?)
+
+ inline Node *getRoot() const { return root; }
+
+ inline unsigned int getSize() const { return size; }
+
+ inline int nextSequence();
+
+ void insert(Node *node); // attach to or set as root
+
+ GraphIterator *iteratorDFS(bool preorder = true);
+ GraphIterator *iteratorCFG();
+
+ // safe iterators are unaffected by changes to the *edges* of the graph
+ GraphIterator *safeIteratorDFS(bool preorder = true);
+ GraphIterator *safeIteratorCFG();
+
+ inline void putIterator(Iterator *); // should be GraphIterator *
+
+ void classifyEdges();
+
+private:
+ void classifyDFS(Node *, int&);
+
+private:
+ Node *root;
+ unsigned int size;
+ int sequence;
+};
+
+int Graph::nextSequence()
+{
+ return ++sequence;
+}
+
+Graph::Node *Graph::Node::parent() const
+{
+ if (inCount != 1)
+ return NULL;
+ assert(in);
+ return in->origin;
+}
+
+bool Graph::Node::visit(int v)
+{
+ if (visited == v)
+ return false;
+ visited = v;
+ return true;
+}
+
+int Graph::Node::getSequence() const
+{
+ return visited;
+}
+
+void Graph::putIterator(Iterator *iter)
+{
+ delete reinterpret_cast<GraphIterator *>(iter);
+}
+
+Graph::EdgeIterator Graph::Node::outgoing() const
+{
+ return EdgeIterator(out, 0);
+}
+
+Graph::EdgeIterator Graph::Node::incident() const
+{
+ return EdgeIterator(in, 1);
+}
+
+int Graph::Node::incidentCountFwd() const
+{
+ int n = 0;
+ for (EdgeIterator ei = incident(); !ei.end(); ei.next())
+ if (ei.getType() != Edge::BACK)
+ ++n;
+ return n;
+}
+
+} // namespace nv50_ir
+
+#endif // __NV50_IR_GRAPH_H__
diff --git a/src/gallium/drivers/nv50/codegen/nv50_ir_inlines.h b/src/gallium/drivers/nv50/codegen/nv50_ir_inlines.h
new file mode 100644
index 00000000000..8730e953482
--- /dev/null
+++ b/src/gallium/drivers/nv50/codegen/nv50_ir_inlines.h
@@ -0,0 +1,328 @@
+
+#ifndef __NV50_IR_INLINES_H__
+#define __NV50_IR_INLINES_H__
+
+static inline CondCode reverseCondCode(CondCode cc)
+{
+ static const uint8_t ccRev[8] = { 0, 4, 2, 6, 1, 5, 3, 7 };
+
+ return static_cast<CondCode>(ccRev[cc & 7] | (cc & ~7));
+}
+
+static inline CondCode inverseCondCode(CondCode cc)
+{
+ return static_cast<CondCode>(cc ^ 7);
+}
+
+static inline bool isMemoryFile(DataFile f)
+{
+ return (f >= FILE_MEMORY_CONST && f <= FILE_MEMORY_LOCAL);
+}
+
+static inline bool isTextureOp(operation op)
+{
+ return (op >= OP_TEX && op <= OP_TEXCSAA);
+}
+
+static inline unsigned int typeSizeof(DataType ty)
+{
+ switch (ty) {
+ case TYPE_U8:
+ case TYPE_S8:
+ return 1;
+ case TYPE_F16:
+ case TYPE_U16:
+ case TYPE_S16:
+ return 2;
+ case TYPE_F32:
+ case TYPE_U32:
+ case TYPE_S32:
+ return 4;
+ case TYPE_F64:
+ case TYPE_U64:
+ case TYPE_S64:
+ return 8;
+ case TYPE_B96:
+ return 12;
+ case TYPE_B128:
+ return 16;
+ default:
+ return 0;
+ }
+}
+
+static inline DataType typeOfSize(unsigned int size,
+ bool flt = false, bool sgn = false)
+{
+ switch (size) {
+ case 1: return sgn ? TYPE_S8 : TYPE_U8;
+ case 2: return flt ? TYPE_F16 : (sgn ? TYPE_S16 : TYPE_U16);
+ case 8: return flt ? TYPE_F64 : (sgn ? TYPE_S64 : TYPE_U64);
+ case 12: return TYPE_B96;
+ case 16: return TYPE_B128;
+ case 4:
+ default:
+ return flt ? TYPE_F32 : (sgn ? TYPE_S32 : TYPE_U32);
+ }
+}
+
+static inline bool isFloatType(DataType ty)
+{
+ return (ty >= TYPE_F16 && ty <= TYPE_F64);
+}
+
+static inline bool isSignedIntType(DataType ty)
+{
+ return (ty == TYPE_S8 || ty == TYPE_S16 || ty == TYPE_S32);
+}
+
+static inline bool isSignedType(DataType ty)
+{
+ switch (ty) {
+ case TYPE_NONE:
+ case TYPE_U8:
+ case TYPE_U16:
+ case TYPE_U32:
+ case TYPE_B96:
+ case TYPE_B128:
+ return false;
+ default:
+ return true;
+ }
+}
+
+const ValueRef *ValueRef::getIndirect(int dim) const
+{
+ return isIndirect(dim) ? &insn->src[indirect[dim]] : NULL;
+}
+
+DataFile ValueRef::getFile() const
+{
+ return value ? value->reg.file : FILE_NULL;
+}
+
+unsigned int ValueRef::getSize() const
+{
+ return value ? value->reg.size : 0;
+}
+
+Value *ValueRef::rep() const
+{
+ assert(value);
+ return value->join;
+}
+
+Value *ValueDef::rep() const
+{
+ assert(value);
+ return value->join;
+}
+
+DataFile ValueDef::getFile() const
+{
+ return value ? value->reg.file : FILE_NULL;
+}
+
+unsigned int ValueDef::getSize() const
+{
+ return value ? value->reg.size : 0;
+}
+
+void ValueDef::setSSA(LValue *lval)
+{
+ Value *save = value;
+
+ this->set(NULL);
+ prev = reinterpret_cast<ValueDef *>(save);
+ value = lval;
+ lval->defs = this;
+}
+
+void ValueDef::restoreDefList()
+{
+ if (next == this)
+ prev = this;
+}
+
+const LValue *ValueDef::preSSA() const
+{
+ return reinterpret_cast<LValue *>(prev);
+}
+
+Instruction *Value::getInsn() const
+{
+ assert(!defs || getUniqueInsn());
+ return defs ? defs->getInsn() : NULL;
+}
+
+Instruction *Value::getUniqueInsn() const
+{
+ if (defs) {
+ if (join != this) {
+ ValueDef::Iterator it = defs->iterator();
+ while (!it.end() && it.get()->get() != this)
+ it.next();
+ assert(it.get()->get() == this);
+ return it.get()->getInsn();
+ }
+
+ // after regalloc, the definitions of coalesced values are linked
+ if (reg.data.id < 0) {
+ ValueDef::Iterator it = defs->iterator();
+ int nDef;
+ for (nDef = 0; !it.end() && nDef < 2; it.next())
+ if (it.get()->get() == this) // don't count joined values
+ ++nDef;
+ if (nDef > 1)
+ WARN("value %%%i not uniquely defined\n", id); // return NULL ?
+ }
+
+ assert(defs->get() == this);
+ return defs->getInsn();
+ }
+ return NULL;
+}
+
+Value *Instruction::getIndirect(int s, int dim) const
+{
+ return src[s].isIndirect(dim) ? getSrc(src[s].indirect[dim]) : NULL;
+}
+
+Value *Instruction::getPredicate() const
+{
+ return (predSrc >= 0) ? getSrc(predSrc) : NULL;
+}
+
+Value *TexInstruction::getIndirectR() const
+{
+ return tex.rIndirectSrc >= 0 ? getSrc(tex.rIndirectSrc) : NULL;
+}
+
+Value *TexInstruction::getIndirectS() const
+{
+ return tex.rIndirectSrc >= 0 ? getSrc(tex.rIndirectSrc) : NULL;
+}
+
+CmpInstruction *Instruction::asCmp()
+{
+ if (op >= OP_SET_AND && op <= OP_SLCT && op != OP_SELP)
+ return static_cast<CmpInstruction *>(this);
+ return NULL;
+}
+
+const CmpInstruction *Instruction::asCmp() const
+{
+ if (op >= OP_SET_AND && op <= OP_SLCT && op != OP_SELP)
+ return static_cast<const CmpInstruction *>(this);
+ return NULL;
+}
+
+FlowInstruction *Instruction::asFlow()
+{
+ if (op >= OP_BRA && op <= OP_JOIN)
+ return static_cast<FlowInstruction *>(this);
+ return NULL;
+}
+
+const FlowInstruction *Instruction::asFlow() const
+{
+ if (op >= OP_BRA && op <= OP_JOINAT)
+ return static_cast<const FlowInstruction *>(this);
+ return NULL;
+}
+
+TexInstruction *Instruction::asTex()
+{
+ if (op >= OP_TEX && op <= OP_TEXCSAA)
+ return static_cast<TexInstruction *>(this);
+ return NULL;
+}
+
+const TexInstruction *Instruction::asTex() const
+{
+ if (op >= OP_TEX && op <= OP_TEXCSAA)
+ return static_cast<const TexInstruction *>(this);
+ return NULL;
+}
+
+// XXX: use a virtual function so we're really really safe ?
+LValue *Value::asLValue()
+{
+ if (reg.file >= FILE_GPR && reg.file <= FILE_ADDRESS)
+ return static_cast<LValue *>(this);
+ return NULL;
+}
+
+Symbol *Value::asSym()
+{
+ if (reg.file >= FILE_MEMORY_CONST)
+ return static_cast<Symbol *>(this);
+ return NULL;
+}
+
+const Symbol *Value::asSym() const
+{
+ if (reg.file >= FILE_MEMORY_CONST)
+ return static_cast<const Symbol *>(this);
+ return NULL;
+}
+
+void Symbol::setOffset(int32_t offset)
+{
+ reg.data.offset = offset;
+}
+
+void Symbol::setAddress(Symbol *base, int32_t offset)
+{
+ baseSym = base;
+ reg.data.offset = offset;
+}
+
+void Symbol::setSV(SVSemantic sv, uint32_t index)
+{
+ reg.data.sv.sv = sv;
+ reg.data.sv.index = index;
+}
+
+ImmediateValue *Value::asImm()
+{
+ if (reg.file == FILE_IMMEDIATE)
+ return static_cast<ImmediateValue *>(this);
+ return NULL;
+}
+
+const ImmediateValue *Value::asImm() const
+{
+ if (reg.file == FILE_IMMEDIATE)
+ return static_cast<const ImmediateValue *>(this);
+ return NULL;
+}
+
+Value *Value::get(Iterator &it)
+{
+ return reinterpret_cast<Value *>(it.get());
+}
+
+bool BasicBlock::reachableBy(BasicBlock *by, BasicBlock *term)
+{
+ return cfg.reachableBy(&by->cfg, &term->cfg);
+}
+
+BasicBlock *BasicBlock::get(Iterator &iter)
+{
+ return reinterpret_cast<BasicBlock *>(iter.get());
+}
+
+BasicBlock *BasicBlock::get(Graph::Node *node)
+{
+ assert(node);
+ return reinterpret_cast<BasicBlock *>(node->data);
+}
+
+LValue *Function::getLValue(int id)
+{
+ assert((unsigned int)id < (unsigned int)allLValues.getSize());
+ return reinterpret_cast<LValue *>(allLValues.get(id));
+}
+
+#endif // __NV50_IR_INLINES_H__
diff --git a/src/gallium/drivers/nv50/codegen/nv50_ir_peephole.cpp b/src/gallium/drivers/nv50/codegen/nv50_ir_peephole.cpp
new file mode 100644
index 00000000000..bd331ea8f03
--- /dev/null
+++ b/src/gallium/drivers/nv50/codegen/nv50_ir_peephole.cpp
@@ -0,0 +1,2192 @@
+
+#include "nv50_ir.h"
+#include "nv50_ir_target.h"
+#include "nv50_ir_build_util.h"
+
+extern "C" {
+#include "util/u_math.h"
+}
+
+namespace nv50_ir {
+
+bool
+Instruction::isNop() const
+{
+ if (op == OP_CONSTRAINT || op == OP_PHI)
+ return true;
+ if (terminator || join) // XXX: should terminator imply flow ?
+ return false;
+ if (!fixed && op == OP_NOP)
+ return true;
+
+ if (def[0].exists() && def[0].rep()->reg.data.id < 0) {
+ for (int d = 1; defExists(d); ++d)
+ if (def[d].rep()->reg.data.id >= 0)
+ WARN("part of vector result is unused !\n");
+ return true;
+ }
+
+ if (op == OP_MOV || op == OP_UNION) {
+ if (!def[0].rep()->equals(getSrc(0)))
+ return false;
+ if (op == OP_UNION)
+ if (!def[0].rep()->equals(getSrc(1)))
+ return false;
+ return true;
+ }
+
+ return false;
+}
+
+bool Instruction::isDead() const
+{
+ if (op == OP_STORE ||
+ op == OP_EXPORT)
+ return false;
+
+ for (int d = 0; defExists(d); ++d)
+ if (getDef(d)->refCount() || getDef(d)->reg.data.id >= 0)
+ return false;
+
+ if (terminator || asFlow())
+ return false;
+ if (fixed)
+ return false;
+
+ return true;
+};
+
+// =============================================================================
+
+class CopyPropagation : public Pass
+{
+private:
+ virtual bool visit(BasicBlock *);
+};
+
+// Propagate all MOVs forward to make subsequent optimization easier, except if
+// the sources stem from a phi, in which case we don't want to mess up potential
+// swaps $rX <-> $rY, i.e. do not create live range overlaps of phi src and def.
+bool
+CopyPropagation::visit(BasicBlock *bb)
+{
+ Instruction *mov, *si, *next;
+
+ for (mov = bb->getEntry(); mov; mov = next) {
+ next = mov->next;
+ if (mov->op != OP_MOV || mov->fixed || !mov->getSrc(0)->asLValue())
+ continue;
+ si = mov->getSrc(0)->getInsn();
+ if (mov->getDef(0)->reg.data.id < 0 && si && si->op != OP_PHI) {
+ // propagate
+ mov->def[0].replace(mov->getSrc(0), false);
+ delete_Instruction(prog, mov);
+ }
+ }
+ return true;
+}
+
+// =============================================================================
+
+class LoadPropagation : public Pass
+{
+private:
+ virtual bool visit(BasicBlock *);
+
+ void checkSwapSrc01(Instruction *);
+
+ bool isCSpaceLoad(Instruction *);
+ bool isImmd32Load(Instruction *);
+};
+
+bool
+LoadPropagation::isCSpaceLoad(Instruction *ld)
+{
+ return ld && ld->op == OP_LOAD && ld->src[0].getFile() == FILE_MEMORY_CONST;
+}
+
+bool
+LoadPropagation::isImmd32Load(Instruction *ld)
+{
+ if (!ld || (ld->op != OP_MOV) || (typeSizeof(ld->dType) != 4))
+ return false;
+ return ld->src[0].getFile() == FILE_IMMEDIATE;
+}
+
+void
+LoadPropagation::checkSwapSrc01(Instruction *insn)
+{
+ if (!prog->getTarget()->getOpInfo(insn).commutative)
+ if (insn->op != OP_SET && insn->op != OP_SLCT)
+ return;
+ if (insn->src[1].getFile() != FILE_GPR)
+ return;
+
+ Instruction *i0 = insn->getSrc(0)->getInsn();
+ Instruction *i1 = insn->getSrc(1)->getInsn();
+
+ if (isCSpaceLoad(i0)) {
+ if (!isCSpaceLoad(i1))
+ insn->swapSources(0, 1);
+ else
+ return;
+ } else
+ if (isImmd32Load(i0)) {
+ if (!isCSpaceLoad(i1) && !isImmd32Load(i1))
+ insn->swapSources(0, 1);
+ else
+ return;
+ } else {
+ return;
+ }
+
+ if (insn->op == OP_SET)
+ insn->asCmp()->setCond = reverseCondCode(insn->asCmp()->setCond);
+ else
+ if (insn->op == OP_SLCT)
+ insn->asCmp()->setCond = inverseCondCode(insn->asCmp()->setCond);
+}
+
+bool
+LoadPropagation::visit(BasicBlock *bb)
+{
+ const Target *targ = prog->getTarget();
+ Instruction *next;
+
+ for (Instruction *i = bb->getEntry(); i; i = next) {
+ next = i->next;
+
+ if (i->srcExists(1))
+ checkSwapSrc01(i);
+
+ for (int s = 0; i->srcExists(s); ++s) {
+ Instruction *ld = i->getSrc(s)->getInsn();
+
+ if (!ld || ld->fixed || (ld->op != OP_LOAD && ld->op != OP_MOV))
+ continue;
+ if (!targ->insnCanLoad(i, s, ld))
+ continue;
+
+ // propagate !
+ i->setSrc(s, ld->getSrc(0));
+ if (ld->src[0].isIndirect(0))
+ i->setIndirect(s, 0, ld->getIndirect(0, 0));
+
+ if (ld->getDef(0)->refCount() == 0)
+ delete_Instruction(prog, ld);
+ }
+ }
+ return true;
+}
+
+// =============================================================================
+
+// Evaluate constant expressions.
+class ConstantFolding : public Pass
+{
+public:
+ bool foldAll(Program *);
+
+private:
+ virtual bool visit(BasicBlock *);
+
+ void expr(Instruction *, ImmediateValue *, ImmediateValue *);
+ void opnd(Instruction *, ImmediateValue *, int s);
+
+ void unary(Instruction *, const ImmediateValue&);
+
+ // TGSI 'true' is converted to -1 by F2I(NEG(SET)), track back to SET
+ CmpInstruction *findOriginForTestWithZero(Value *);
+
+ unsigned int foldCount;
+
+ BuildUtil bld;
+};
+
+// TODO: remember generated immediates and only revisit these
+bool
+ConstantFolding::foldAll(Program *prog)
+{
+ unsigned int iterCount = 0;
+ do {
+ foldCount = 0;
+ if (!run(prog))
+ return false;
+ } while (foldCount && ++iterCount < 2);
+ return true;
+}
+
+bool
+ConstantFolding::visit(BasicBlock *bb)
+{
+ Instruction *i, *next;
+
+ for (i = bb->getEntry(); i; i = next) {
+ next = i->next;
+ if (i->op == OP_MOV) // continue early, MOV appears frequently
+ continue;
+
+ ImmediateValue *src0 = i->src[0].getImmediate();
+ ImmediateValue *src1 = i->src[1].getImmediate();
+
+ if (src0 && src1)
+ expr(i, src0, src1);
+ else
+ if (src0)
+ opnd(i, src0, 0);
+ else
+ if (src1)
+ opnd(i, src1, 1);
+ }
+ return true;
+}
+
+CmpInstruction *
+ConstantFolding::findOriginForTestWithZero(Value *value)
+{
+ if (!value)
+ return NULL;
+ Instruction *insn = value->getInsn();
+
+ while (insn && insn->op != OP_SET) {
+ Instruction *next = NULL;
+ switch (insn->op) {
+ case OP_NEG:
+ case OP_ABS:
+ case OP_CVT:
+ next = insn->getSrc(0)->getInsn();
+ if (insn->sType != next->dType)
+ return NULL;
+ break;
+ case OP_MOV:
+ next = insn->getSrc(0)->getInsn();
+ break;
+ default:
+ return NULL;
+ }
+ insn = next;
+ }
+ return insn ? insn->asCmp() : NULL;
+}
+
+void
+Modifier::applyTo(ImmediateValue& imm) const
+{
+ switch (imm.reg.type) {
+ case TYPE_F32:
+ if (bits & NV50_IR_MOD_ABS)
+ imm.reg.data.f32 = fabsf(imm.reg.data.f32);
+ if (bits & NV50_IR_MOD_NEG)
+ imm.reg.data.f32 = -imm.reg.data.f32;
+ if (bits & NV50_IR_MOD_SAT) {
+ if (imm.reg.data.f32 < 0.0f)
+ imm.reg.data.f32 = 0.0f;
+ else
+ if (imm.reg.data.f32 > 1.0f)
+ imm.reg.data.f32 = 1.0f;
+ }
+ assert(!(bits & NV50_IR_MOD_NOT));
+ break;
+
+ case TYPE_S8: // NOTE: will be extended
+ case TYPE_S16:
+ case TYPE_S32:
+ case TYPE_U8: // NOTE: treated as signed
+ case TYPE_U16:
+ case TYPE_U32:
+ if (bits & NV50_IR_MOD_ABS)
+ imm.reg.data.s32 = (imm.reg.data.s32 >= 0) ?
+ imm.reg.data.s32 : -imm.reg.data.s32;
+ if (bits & NV50_IR_MOD_NEG)
+ imm.reg.data.s32 = -imm.reg.data.s32;
+ if (bits & NV50_IR_MOD_NOT)
+ imm.reg.data.s32 = ~imm.reg.data.s32;
+ break;
+
+ case TYPE_F64:
+ if (bits & NV50_IR_MOD_ABS)
+ imm.reg.data.f64 = fabs(imm.reg.data.f64);
+ if (bits & NV50_IR_MOD_NEG)
+ imm.reg.data.f64 = -imm.reg.data.f64;
+ if (bits & NV50_IR_MOD_SAT) {
+ if (imm.reg.data.f64 < 0.0)
+ imm.reg.data.f64 = 0.0;
+ else
+ if (imm.reg.data.f64 > 1.0)
+ imm.reg.data.f64 = 1.0;
+ }
+ assert(!(bits & NV50_IR_MOD_NOT));
+ break;
+
+ default:
+ assert(!"invalid/unhandled type");
+ imm.reg.data.u64 = 0;
+ break;
+ }
+}
+
+operation
+Modifier::getOp() const
+{
+ switch (bits) {
+ case NV50_IR_MOD_ABS: return OP_ABS;
+ case NV50_IR_MOD_NEG: return OP_NEG;
+ case NV50_IR_MOD_SAT: return OP_SAT;
+ case NV50_IR_MOD_NOT: return OP_NOT;
+ case 0:
+ return OP_MOV;
+ default:
+ return OP_CVT;
+ }
+}
+
+void
+ConstantFolding::expr(Instruction *i,
+ ImmediateValue *src0, ImmediateValue *src1)
+{
+ ImmediateValue imm0(src0, i->sType);
+ ImmediateValue imm1(src1, i->sType);
+ struct Storage res;
+ struct Storage *const a = &imm0.reg, *const b = &imm1.reg;
+
+ i->src[0].mod.applyTo(imm0);
+ i->src[1].mod.applyTo(imm1);
+
+ switch (i->op) {
+ case OP_MAD:
+ case OP_FMA:
+ case OP_MUL:
+ if (i->dnz && i->dType == TYPE_F32) {
+ if (!isfinite(a->data.f32))
+ a->data.f32 = 0.0f;
+ if (!isfinite(b->data.f32))
+ b->data.f32 = 0.0f;
+ }
+ switch (i->dType) {
+ case TYPE_F32: res.data.f32 = a->data.f32 * b->data.f32; break;
+ case TYPE_F64: res.data.f64 = a->data.f64 * b->data.f64; break;
+ case TYPE_S32:
+ case TYPE_U32: res.data.u32 = a->data.u32 * b->data.u32; break;
+ default:
+ return;
+ }
+ break;
+ case OP_DIV:
+ if (b->data.u32 == 0)
+ break;
+ switch (i->dType) {
+ case TYPE_F32: res.data.f32 = a->data.f32 / b->data.f32; break;
+ case TYPE_F64: res.data.f64 = a->data.f64 / b->data.f64; break;
+ case TYPE_S32: res.data.s32 = a->data.s32 / b->data.s32; break;
+ case TYPE_U32: res.data.u32 = a->data.u32 / b->data.u32; break;
+ default:
+ return;
+ }
+ break;
+ case OP_ADD:
+ switch (i->dType) {
+ case TYPE_F32: res.data.f32 = a->data.f32 + b->data.f32; break;
+ case TYPE_F64: res.data.f64 = a->data.f64 + b->data.f64; break;
+ case TYPE_S32:
+ case TYPE_U32: res.data.u32 = a->data.u32 + b->data.u32; break;
+ default:
+ return;
+ }
+ break;
+ case OP_POW:
+ switch (i->dType) {
+ case TYPE_F32: res.data.f32 = pow(a->data.f32, b->data.f32); break;
+ case TYPE_F64: res.data.f64 = pow(a->data.f64, b->data.f64); break;
+ default:
+ return;
+ }
+ break;
+ case OP_MAX:
+ switch (i->dType) {
+ case TYPE_F32: res.data.f32 = MAX2(a->data.f32, b->data.f32); break;
+ case TYPE_F64: res.data.f64 = MAX2(a->data.f64, b->data.f64); break;
+ case TYPE_S32: res.data.s32 = MAX2(a->data.s32, b->data.s32); break;
+ case TYPE_U32: res.data.u32 = MAX2(a->data.u32, b->data.u32); break;
+ default:
+ return;
+ }
+ break;
+ case OP_MIN:
+ switch (i->dType) {
+ case TYPE_F32: res.data.f32 = MIN2(a->data.f32, b->data.f32); break;
+ case TYPE_F64: res.data.f64 = MIN2(a->data.f64, b->data.f64); break;
+ case TYPE_S32: res.data.s32 = MIN2(a->data.s32, b->data.s32); break;
+ case TYPE_U32: res.data.u32 = MIN2(a->data.u32, b->data.u32); break;
+ default:
+ return;
+ }
+ break;
+ case OP_AND:
+ res.data.u64 = a->data.u64 & b->data.u64;
+ break;
+ case OP_OR:
+ res.data.u64 = a->data.u64 | b->data.u64;
+ break;
+ case OP_XOR:
+ res.data.u64 = a->data.u64 ^ b->data.u64;
+ break;
+ case OP_SHL:
+ res.data.u32 = a->data.u32 << b->data.u32;
+ break;
+ case OP_SHR:
+ switch (i->dType) {
+ case TYPE_S32: res.data.s32 = a->data.s32 >> b->data.u32; break;
+ case TYPE_U32: res.data.u32 = a->data.u32 >> b->data.u32; break;
+ default:
+ return;
+ }
+ break;
+ case OP_SLCT:
+ if (a->data.u32 != b->data.u32)
+ return;
+ res.data.u32 = a->data.u32;
+ break;
+ default:
+ return;
+ }
+ ++foldCount;
+
+ i->src[0].mod = Modifier(0);
+ i->src[1].mod = Modifier(0);
+
+ i->setSrc(0, new_ImmediateValue(i->bb->getProgram(), res.data.u32));
+ i->setSrc(1, NULL);
+
+ i->getSrc(0)->reg.data = res.data;
+
+ if (i->op == OP_MAD || i->op == OP_FMA) {
+ i->op = OP_ADD;
+
+ i->setSrc(1, i->getSrc(0));
+ i->setSrc(0, i->getSrc(2));
+ i->setSrc(2, NULL);
+
+ i->src[1].mod = i->src[2].mod;
+
+ src0 = i->src[0].getImmediate();
+ if (src0)
+ expr(i, src0, i->getSrc(1)->asImm());
+ } else {
+ i->op = OP_MOV;
+ }
+}
+
+void
+ConstantFolding::unary(Instruction *i, const ImmediateValue &imm)
+{
+ Storage res;
+
+ if (i->dType != TYPE_F32)
+ return;
+ switch (i->op) {
+ case OP_NEG: res.data.f32 = -imm.reg.data.f32; break;
+ case OP_ABS: res.data.f32 = fabsf(imm.reg.data.f32); break;
+ case OP_RCP: res.data.f32 = 1.0f / imm.reg.data.f32; break;
+ case OP_RSQ: res.data.f32 = 1.0f / sqrtf(imm.reg.data.f32); break;
+ case OP_LG2: res.data.f32 = log2f(imm.reg.data.f32); break;
+ case OP_EX2: res.data.f32 = exp2f(imm.reg.data.f32); break;
+ case OP_SIN: res.data.f32 = sinf(imm.reg.data.f32); break;
+ case OP_COS: res.data.f32 = cosf(imm.reg.data.f32); break;
+ case OP_SQRT: res.data.f32 = sqrtf(imm.reg.data.f32); break;
+ case OP_PRESIN:
+ case OP_PREEX2:
+ // these should be handled in subsequent OP_SIN/COS/EX2
+ res.data.f32 = imm.reg.data.f32;
+ break;
+ default:
+ return;
+ }
+ i->op = OP_MOV;
+ i->setSrc(0, new_ImmediateValue(i->bb->getProgram(), res.data.f32));
+ i->src[0].mod = Modifier(0);
+}
+
+void
+ConstantFolding::opnd(Instruction *i, ImmediateValue *src, int s)
+{
+ const int t = !s;
+ const operation op = i->op;
+
+ ImmediateValue imm(src, i->sType);
+
+ i->src[s].mod.applyTo(imm);
+
+ switch (i->op) {
+ case OP_MUL:
+ if (i->dType == TYPE_F32 && i->getSrc(t)->refCount() == 1) {
+ Instruction *si = i->getSrc(t)->getUniqueInsn();
+
+ if (si && si->op == OP_MUL) {
+ float f = imm.reg.data.f32;
+
+ if (si->src[1].getImmediate()) {
+ f *= si->src[1].getImmediate()->reg.data.f32;
+ si->setSrc(1, new_ImmediateValue(prog, f));
+ i->def[0].replace(i->getSrc(t), false);
+ break;
+ } else {
+ int fac;
+ if (f == 0.125f) fac = -3;
+ else
+ if (f == 0.250f) fac = -2;
+ else
+ if (f == 0.500f) fac = -1;
+ else
+ if (f == 2.000f) fac = +1;
+ else
+ if (f == 4.000f) fac = +2;
+ else
+ if (f == 8.000f) fac = +3;
+ else
+ fac = 0;
+ if (fac) {
+ // FIXME: allowed & modifier
+ si->postFactor = fac;
+ i->def[0].replace(i->getSrc(t), false);
+ break;
+ }
+ }
+ }
+ }
+ if (imm.isInteger(0)) {
+ i->op = OP_MOV;
+ i->setSrc(0, i->getSrc(s));
+ i->setSrc(1, NULL);
+ } else
+ if (imm.isInteger(1) || imm.isInteger(-1)) {
+ if (imm.isNegative())
+ i->src[t].mod = i->src[t].mod ^ Modifier(NV50_IR_MOD_NEG);
+ i->op = i->src[t].mod.getOp();
+ if (s == 0) {
+ i->setSrc(0, i->getSrc(1));
+ i->src[0].mod = i->src[1].mod;
+ i->src[1].mod = 0;
+ }
+ if (i->op != OP_CVT)
+ i->src[0].mod = 0;
+ i->setSrc(1, NULL);
+ } else
+ if (imm.isInteger(2) || imm.isInteger(-2)) {
+ if (imm.isNegative())
+ i->src[t].mod = i->src[t].mod ^ Modifier(NV50_IR_MOD_NEG);
+ i->op = OP_ADD;
+ i->setSrc(s, i->getSrc(t));
+ i->src[s].mod = i->src[t].mod;
+ } else
+ if (!isFloatType(i->sType) && !imm.isNegative() && imm.isPow2()) {
+ i->op = OP_SHL;
+ imm.applyLog2();
+ i->setSrc(1, new_ImmediateValue(prog, imm.reg.data.u32));
+ }
+ break;
+ case OP_ADD:
+ if (imm.isInteger(0)) {
+ if (s == 0) {
+ i->setSrc(0, i->getSrc(1));
+ i->src[0].mod = i->src[1].mod;
+ }
+ i->setSrc(1, NULL);
+ i->op = i->src[0].mod.getOp();
+ if (i->op != OP_CVT)
+ i->src[0].mod = Modifier(0);
+ }
+ break;
+
+ case OP_DIV:
+ if (s != 1 || (i->dType != TYPE_S32 && i->dType != TYPE_U32))
+ break;
+ bld.setPosition(i, false);
+ if (imm.reg.data.u32 == 0) {
+ break;
+ } else
+ if (imm.reg.data.u32 == 1) {
+ i->op = OP_MOV;
+ i->setSrc(1, NULL);
+ } else
+ if (i->dType == TYPE_U32 && imm.isPow2()) {
+ i->op = OP_SHL;
+ i->setSrc(1, bld.mkImm(util_logbase2(imm.reg.data.u32)));
+ } else
+ if (i->dType == TYPE_U32) {
+ Instruction *mul;
+ Value *tA, *tB;
+ const uint32_t d = imm.reg.data.u32;
+ uint32_t m;
+ int r, s;
+ uint32_t l = util_logbase2(d);
+ if (((uint32_t)1 << l) < d)
+ ++l;
+ m = (((uint64_t)1 << 32) * (((uint64_t)1 << l) - d)) / d + 1;
+ r = l ? 1 : 0;
+ s = l ? (l - 1) : 0;
+
+ tA = bld.getSSA();
+ tB = bld.getSSA();
+ mul = bld.mkOp2(OP_MUL, TYPE_U32, tA, i->getSrc(0),
+ bld.loadImm(NULL, m));
+ mul->subOp = NV50_IR_SUBOP_MUL_HIGH;
+ bld.mkOp2(OP_SUB, TYPE_U32, tB, i->getSrc(0), tA);
+ tA = bld.getSSA();
+ if (r)
+ bld.mkOp2(OP_SHR, TYPE_U32, tA, tB, bld.mkImm(r));
+ else
+ tA = tB;
+ tB = s ? bld.getSSA() : i->getDef(0);
+ bld.mkOp2(OP_ADD, TYPE_U32, tB, mul->getDef(0), tA);
+ if (s)
+ bld.mkOp2(OP_SHR, TYPE_U32, i->getDef(0), tB, bld.mkImm(s));
+
+ delete_Instruction(prog, i);
+ } else
+ if (imm.reg.data.s32 == -1) {
+ i->op = OP_NEG;
+ i->setSrc(1, NULL);
+ } else {
+ LValue *tA, *tB;
+ LValue *tD;
+ const int32_t d = imm.reg.data.s32;
+ int32_t m;
+ int32_t l = util_logbase2(static_cast<unsigned>(abs(d)));
+ if ((1 << l) < abs(d))
+ ++l;
+ if (!l)
+ l = 1;
+ m = ((uint64_t)1 << (32 + l - 1)) / abs(d) + 1 - ((uint64_t)1 << 32);
+
+ tA = bld.getSSA();
+ tB = bld.getSSA();
+ bld.mkOp3(OP_MAD, TYPE_S32, tA, i->getSrc(0), bld.loadImm(NULL, m),
+ i->getSrc(0))->subOp = NV50_IR_SUBOP_MUL_HIGH;
+ if (l > 1)
+ bld.mkOp2(OP_SHR, TYPE_S32, tB, tA, bld.mkImm(l - 1));
+ else
+ tB = tA;
+ tA = bld.getSSA();
+ bld.mkCmp(OP_SET, CC_LT, TYPE_S32, tA, i->getSrc(0), bld.mkImm(0));
+ tD = (d < 0) ? bld.getSSA() : i->getDef(0)->asLValue();
+ bld.mkOp2(OP_SUB, TYPE_U32, tD, tB, tA);
+ if (d < 0)
+ bld.mkOp1(OP_NEG, TYPE_S32, i->getDef(0), tB);
+
+ delete_Instruction(prog, i);
+ }
+ break;
+
+ case OP_SET: // TODO: SET_AND,OR,XOR
+ {
+ CmpInstruction *si = findOriginForTestWithZero(i->getSrc(t));
+ CondCode cc, ccZ;
+ if (i->src[t].mod != Modifier(0))
+ return;
+ if (imm.reg.data.u32 != 0 || !si || si->op != OP_SET)
+ return;
+ cc = si->setCond;
+ ccZ = (CondCode)((unsigned int)i->asCmp()->setCond & ~CC_U);
+ if (s == 0)
+ ccZ = reverseCondCode(ccZ);
+ switch (ccZ) {
+ case CC_LT: cc = CC_FL; break;
+ case CC_GE: cc = CC_TR; break;
+ case CC_EQ: cc = inverseCondCode(cc); break;
+ case CC_LE: cc = inverseCondCode(cc); break;
+ case CC_GT: break;
+ case CC_NE: break;
+ default:
+ return;
+ }
+ i->asCmp()->setCond = cc;
+ i->setSrc(0, si->src[0]);
+ i->setSrc(1, si->src[1]);
+ i->sType = si->sType;
+ }
+ break;
+
+ case OP_SHL:
+ {
+ if (s != 1 || i->src[0].mod != Modifier(0))
+ break;
+ // try to concatenate shifts
+ Instruction *si = i->getSrc(0)->getInsn();
+ if (!si ||
+ si->op != OP_SHL || si->src[1].mod != Modifier(0))
+ break;
+ ImmediateValue *siImm = si->src[1].getImmediate();
+ if (siImm) {
+ bld.setPosition(i, false);
+ i->setSrc(0, si->getSrc(0));
+ i->setSrc(1, bld.loadImm(NULL,
+ imm.reg.data.u32 + siImm->reg.data.u32));
+ }
+ }
+ break;
+
+ case OP_ABS:
+ case OP_NEG:
+ case OP_LG2:
+ case OP_RCP:
+ case OP_SQRT:
+ case OP_RSQ:
+ case OP_PRESIN:
+ case OP_SIN:
+ case OP_COS:
+ case OP_PREEX2:
+ case OP_EX2:
+ unary(i, imm);
+ break;
+ default:
+ return;
+ }
+ if (i->op != op)
+ foldCount++;
+}
+
+// =============================================================================
+
+// Merge modifier operations (ABS, NEG, NOT) into ValueRefs where allowed.
+class ModifierFolding : public Pass
+{
+private:
+ virtual bool visit(BasicBlock *);
+};
+
+bool
+ModifierFolding::visit(BasicBlock *bb)
+{
+ const Target *target = prog->getTarget();
+
+ Instruction *i, *next, *mi;
+ Modifier mod;
+
+ for (i = bb->getEntry(); i; i = next) {
+ next = i->next;
+
+ if (0 && i->op == OP_SUB) {
+ // turn "sub" into "add neg" (do we really want this ?)
+ i->op = OP_ADD;
+ i->src[0].mod = i->src[0].mod ^ Modifier(NV50_IR_MOD_NEG);
+ }
+
+ for (int s = 0; s < 3 && i->srcExists(s); ++s) {
+ mi = i->getSrc(s)->getInsn();
+ if (!mi ||
+ mi->predSrc >= 0 || mi->getDef(0)->refCount() > 8)
+ continue;
+ if (i->sType == TYPE_U32 && mi->dType == TYPE_S32) {
+ if ((i->op != OP_ADD &&
+ i->op != OP_MUL) ||
+ (mi->op != OP_ABS &&
+ mi->op != OP_NEG))
+ continue;
+ } else
+ if (i->sType != mi->dType) {
+ continue;
+ }
+ if ((mod = Modifier(mi->op)) == Modifier(0))
+ continue;
+ mod = mod * mi->src[0].mod;
+
+ if ((i->op == OP_ABS) || i->src[s].mod.abs()) {
+ // abs neg [abs] = abs
+ mod = mod & Modifier(~(NV50_IR_MOD_NEG | NV50_IR_MOD_ABS));
+ } else
+ if ((i->op == OP_NEG) && mod.neg()) {
+ assert(s == 0);
+ // neg as both opcode and modifier on same insn is prohibited
+ // neg neg abs = abs, neg neg = identity
+ mod = mod & Modifier(~NV50_IR_MOD_NEG);
+ i->op = mod.getOp();
+ mod = mod & Modifier(~NV50_IR_MOD_ABS);
+ if (mod == Modifier(0))
+ i->op = OP_MOV;
+ }
+
+ if (target->isModSupported(i, s, mod)) {
+ i->setSrc(s, mi->getSrc(0));
+ i->src[s].mod = i->src[s].mod * mod;
+ }
+ }
+
+ if (i->op == OP_SAT) {
+ mi = i->getSrc(0)->getInsn();
+ if (mi &&
+ mi->getDef(0)->refCount() <= 1 && target->isSatSupported(mi)) {
+ mi->saturate = 1;
+ mi->setDef(0, i->getDef(0));
+ delete_Instruction(prog, i);
+ }
+ }
+ }
+
+ return true;
+}
+
+// =============================================================================
+
+// MUL + ADD -> MAD/FMA
+// MIN/MAX(a, a) -> a, etc.
+// SLCT(a, b, const) -> cc(const) ? a : b
+// RCP(RCP(a)) -> a
+// MUL(MUL(a, b), const) -> MUL_Xconst(a, b)
+class AlgebraicOpt : public Pass
+{
+private:
+ virtual bool visit(BasicBlock *);
+
+ void handleADD(Instruction *);
+ void handleMINMAX(Instruction *);
+ void handleRCP(Instruction *);
+ void handleSLCT(Instruction *);
+ void handleLOGOP(Instruction *);
+ void handleCVT(Instruction *);
+};
+
+void
+AlgebraicOpt::handleADD(Instruction *add)
+{
+ Value *src0 = add->getSrc(0);
+ Value *src1 = add->getSrc(1);
+ Value *src;
+ int s;
+ Modifier mod[4];
+
+ if (!prog->getTarget()->isOpSupported(OP_MAD, add->dType))
+ return;
+
+ if (src0->reg.file != FILE_GPR || src1->reg.file != FILE_GPR)
+ return;
+
+ if (src0->refCount() == 1 &&
+ src0->getUniqueInsn() && src0->getUniqueInsn()->op == OP_MUL)
+ s = 0;
+ else
+ if (src1->refCount() == 1 &&
+ src1->getUniqueInsn() && src1->getUniqueInsn()->op == OP_MUL)
+ s = 1;
+ else
+ return;
+
+ if ((src0->getUniqueInsn() && src0->getUniqueInsn()->bb != add->bb) ||
+ (src1->getUniqueInsn() && src1->getUniqueInsn()->bb != add->bb))
+ return;
+
+ src = add->getSrc(s);
+
+ mod[0] = add->src[0].mod;
+ mod[1] = add->src[1].mod;
+ mod[2] = src->getUniqueInsn()->src[0].mod;
+ mod[3] = src->getUniqueInsn()->src[1].mod;
+
+ if (((mod[0] | mod[1]) | (mod[2] | mod[3])) & Modifier(~NV50_IR_MOD_NEG))
+ return;
+
+ add->op = OP_MAD;
+ add->subOp = src->getInsn()->subOp; // potentially mul-high
+
+ add->setSrc(2, add->src[s ? 0 : 1]);
+
+ add->setSrc(0, src->getInsn()->getSrc(0));
+ add->src[0].mod = mod[2] ^ mod[s];
+ add->setSrc(1, src->getInsn()->getSrc(1));
+ add->src[1].mod = mod[3];
+}
+
+void
+AlgebraicOpt::handleMINMAX(Instruction *minmax)
+{
+ Value *src0 = minmax->getSrc(0);
+ Value *src1 = minmax->getSrc(1);
+
+ if (src0 != src1 || src0->reg.file != FILE_GPR)
+ return;
+ if (minmax->src[0].mod == minmax->src[1].mod) {
+ if (minmax->src[0].mod) {
+ minmax->op = OP_CVT;
+ minmax->setSrc(1, NULL);
+ } else {
+ minmax->def[0].replace(minmax->getSrc(0), false);
+ minmax->bb->remove(minmax);
+ }
+ } else {
+ // TODO:
+ // min(x, -x) = -abs(x)
+ // min(x, -abs(x)) = -abs(x)
+ // min(x, abs(x)) = x
+ // max(x, -abs(x)) = x
+ // max(x, abs(x)) = abs(x)
+ // max(x, -x) = abs(x)
+ }
+}
+
+void
+AlgebraicOpt::handleRCP(Instruction *rcp)
+{
+ Instruction *si = rcp->getSrc(0)->getUniqueInsn();
+
+ if (si && si->op == OP_RCP) {
+ Modifier mod = rcp->src[0].mod * si->src[0].mod;
+ rcp->op = mod.getOp();
+ rcp->setSrc(0, si->getSrc(0));
+ }
+}
+
+void
+AlgebraicOpt::handleSLCT(Instruction *slct)
+{
+ if (slct->getSrc(2)->reg.file == FILE_IMMEDIATE) {
+ if (slct->getSrc(2)->asImm()->compare(slct->asCmp()->setCond, 0.0f))
+ slct->setSrc(0, slct->getSrc(1));
+ } else
+ if (slct->getSrc(0) != slct->getSrc(1)) {
+ return;
+ }
+ slct->op = OP_MOV;
+ slct->setSrc(1, NULL);
+ slct->setSrc(2, NULL);
+}
+
+void
+AlgebraicOpt::handleLOGOP(Instruction *logop)
+{
+ Value *src0 = logop->getSrc(0);
+ Value *src1 = logop->getSrc(1);
+
+ if (src0->reg.file != FILE_GPR || src1->reg.file != FILE_GPR)
+ return;
+
+ if (src0 == src1) {
+ if (logop->src[0].mod != Modifier(0) ||
+ logop->src[1].mod != Modifier(0))
+ return;
+ if (logop->op == OP_AND || logop->op == OP_OR) {
+ logop->def[0].replace(logop->getSrc(0), false);
+ delete_Instruction(prog, logop);
+ }
+ } else {
+ // try AND(SET, SET) -> SET_AND(SET)
+ Instruction *set0 = src0->getInsn();
+ Instruction *set1 = src1->getInsn();
+
+ if (!set0 || set0->fixed || !set1 || set1->fixed)
+ return;
+ if (set1->op != OP_SET) {
+ Instruction *xchg = set0;
+ set0 = set1;
+ set1 = xchg;
+ if (set1->op != OP_SET)
+ return;
+ }
+ if (set0->op != OP_SET &&
+ set0->op != OP_SET_AND &&
+ set0->op != OP_SET_OR &&
+ set0->op != OP_SET_XOR)
+ return;
+ if (set0->getDef(0)->refCount() > 1 &&
+ set1->getDef(0)->refCount() > 1)
+ return;
+ if (set0->getPredicate() || set1->getPredicate())
+ return;
+ // check that they don't source each other
+ for (int s = 0; s < 2; ++s)
+ if (set0->getSrc(s) == set1->getDef(0) ||
+ set1->getSrc(s) == set0->getDef(0))
+ return;
+
+ set0 = set0->clone(true);
+ set1 = set1->clone(false);
+ logop->bb->insertAfter(logop, set1);
+ logop->bb->insertAfter(logop, set0);
+
+ set0->dType = TYPE_U8;
+ set0->getDef(0)->reg.file = FILE_PREDICATE;
+ set0->getDef(0)->reg.size = 1;
+ set1->setSrc(2, set0->getDef(0));
+ switch (logop->op) {
+ case OP_AND: set1->op = OP_SET_AND; break;
+ case OP_OR: set1->op = OP_SET_OR; break;
+ case OP_XOR: set1->op = OP_SET_XOR; break;
+ default:
+ assert(0);
+ break;
+ }
+ set1->setDef(0, logop->getDef(0));
+ delete_Instruction(prog, logop);
+ }
+}
+
+// F2I(NEG(SET with result 1.0f/0.0f)) -> SET with result -1/0
+void
+AlgebraicOpt::handleCVT(Instruction *cvt)
+{
+ if (cvt->sType != TYPE_F32 ||
+ cvt->dType != TYPE_S32 || cvt->src[0].mod != Modifier(0))
+ return;
+ Instruction *insn = cvt->getSrc(0)->getInsn();
+ if (!insn || insn->op != OP_NEG || insn->dType != TYPE_F32)
+ return;
+ if (insn->src[0].mod != Modifier(0))
+ return;
+ insn = insn->getSrc(0)->getInsn();
+ if (!insn || insn->op != OP_SET || insn->dType != TYPE_F32)
+ return;
+
+ Instruction *bset = insn->clone(false);
+ bset->dType = TYPE_U32;
+ bset->setDef(0, cvt->getDef(0));
+ cvt->bb->insertAfter(cvt, bset);
+ delete_Instruction(prog, cvt);
+}
+
+bool
+AlgebraicOpt::visit(BasicBlock *bb)
+{
+ Instruction *next;
+ for (Instruction *i = bb->getEntry(); i; i = next) {
+ next = i->next;
+ switch (i->op) {
+ case OP_ADD:
+ handleADD(i);
+ break;
+ case OP_RCP:
+ handleRCP(i);
+ break;
+ case OP_MIN:
+ case OP_MAX:
+ handleMINMAX(i);
+ break;
+ case OP_SLCT:
+ handleSLCT(i);
+ break;
+ case OP_AND:
+ case OP_OR:
+ case OP_XOR:
+ handleLOGOP(i);
+ break;
+ case OP_CVT:
+ handleCVT(i);
+ break;
+ default:
+ break;
+ }
+ }
+
+ return true;
+}
+
+// =============================================================================
+
+static inline void
+updateLdStOffset(Instruction *ldst, int32_t offset, Function *fn)
+{
+ if (offset != ldst->getSrc(0)->reg.data.offset) {
+ if (ldst->getSrc(0)->refCount() > 1)
+ ldst->setSrc(0, ldst->getSrc(0)->clone(fn));
+ ldst->getSrc(0)->reg.data.offset = offset;
+ }
+}
+
+// Combine loads and stores, forward stores to loads where possible.
+class MemoryOpt : public Pass
+{
+private:
+ class Record
+ {
+ public:
+ Record *next;
+ Instruction *insn;
+ const Value *rel[2];
+ const Value *base;
+ int32_t offset;
+ int8_t fileIndex;
+ uint8_t size;
+ bool locked;
+ Record *prev;
+
+ bool overlaps(const Instruction *ldst) const;
+
+ inline void link(Record **);
+ inline void unlink(Record **);
+ inline void set(const Instruction *ldst);
+ };
+
+public:
+ MemoryOpt();
+
+ Record *loads[DATA_FILE_COUNT];
+ Record *stores[DATA_FILE_COUNT];
+
+ MemoryPool recordPool;
+
+private:
+ virtual bool visit(BasicBlock *);
+ bool runOpt(BasicBlock *);
+
+ Record **getList(const Instruction *);
+
+ Record *findRecord(const Instruction *, bool load, bool& isAdjacent) const;
+
+ // merge @insn into load/store instruction from @rec
+ bool combineLd(Record *rec, Instruction *ld);
+ bool combineSt(Record *rec, Instruction *st);
+
+ bool replaceLdFromLd(Instruction *ld, Record *ldRec);
+ bool replaceLdFromSt(Instruction *ld, Record *stRec);
+ bool replaceStFromSt(Instruction *restrict st, Record *stRec);
+
+ void addRecord(Instruction *ldst);
+ void purgeRecords(Instruction *const st, DataFile);
+ void lockStores(Instruction *const ld);
+ void reset();
+
+private:
+ Record *prevRecord;
+};
+
+MemoryOpt::MemoryOpt() : recordPool(sizeof(MemoryOpt::Record), 6)
+{
+ for (int i = 0; i < DATA_FILE_COUNT; ++i) {
+ loads[i] = NULL;
+ stores[i] = NULL;
+ }
+ prevRecord = NULL;
+}
+
+void
+MemoryOpt::reset()
+{
+ for (unsigned int i = 0; i < DATA_FILE_COUNT; ++i) {
+ Record *it, *next;
+ for (it = loads[i]; it; it = next) {
+ next = it->next;
+ recordPool.release(it);
+ }
+ loads[i] = NULL;
+ for (it = stores[i]; it; it = next) {
+ next = it->next;
+ recordPool.release(it);
+ }
+ stores[i] = NULL;
+ }
+}
+
+bool
+MemoryOpt::combineLd(Record *rec, Instruction *ld)
+{
+ int32_t offRc = rec->offset;
+ int32_t offLd = ld->getSrc(0)->reg.data.offset;
+ int sizeRc = rec->size;
+ int sizeLd = typeSizeof(ld->dType);
+ int size = sizeRc + sizeLd;
+ int d, j;
+
+ // only VFETCH can do a 96 byte load
+ if (ld->op != OP_VFETCH && size == 12)
+ return false;
+ // no unaligned loads
+ if (((size == 0x8) && (MIN2(offLd, offRc) & 0x7)) ||
+ ((size == 0xc) && (MIN2(offLd, offRc) & 0xf)))
+ return false;
+
+ assert(sizeRc + sizeLd <= 16 && offRc != offLd);
+
+ for (j = 0; sizeRc; sizeRc -= rec->insn->getDef(j)->reg.size, ++j);
+
+ if (offLd < offRc) {
+ int sz;
+ for (sz = 0, d = 0; sz < sizeLd; sz += ld->getDef(d)->reg.size, ++d);
+ // d: nr of definitions in ld
+ // j: nr of definitions in rec->insn, move:
+ for (d = d + j - 1; j > 0; --j, --d)
+ rec->insn->setDef(d, rec->insn->getDef(j - 1));
+
+ if (rec->insn->getSrc(0)->refCount() > 1)
+ rec->insn->setSrc(0, rec->insn->getSrc(0)->clone(func));
+ rec->offset = rec->insn->getSrc(0)->reg.data.offset = offLd;
+
+ d = 0;
+ } else {
+ d = j;
+ }
+ // move definitions of @ld to @rec->insn
+ for (j = 0; sizeLd; ++j, ++d) {
+ sizeLd -= ld->getDef(j)->reg.size;
+ rec->insn->setDef(d, ld->getDef(j));
+ }
+
+ rec->size = size;
+ rec->insn->setType(typeOfSize(size));
+
+ delete_Instruction(prog, ld);
+
+ return true;
+}
+
+bool
+MemoryOpt::combineSt(Record *rec, Instruction *st)
+{
+ int32_t offRc = rec->offset;
+ int32_t offSt = st->getSrc(0)->reg.data.offset;
+ int sizeRc = rec->size;
+ int sizeSt = typeSizeof(st->dType);
+ int s = sizeSt / 4;
+ int size = sizeRc + sizeSt;
+ int j, k;
+ Value *src[4]; // no modifiers in ValueRef allowed for st
+ Value *extra[3];
+
+ if (size == 12) // XXX: check if EXPORT a[] can do this after all
+ return false;
+ if (size == 8 && MIN2(offRc, offSt) & 0x7)
+ return false;
+
+ st->takeExtraSources(0, extra); // save predicate and indirect address
+
+ if (offRc < offSt) {
+ // save values from @st
+ for (s = 0; sizeSt; ++s) {
+ sizeSt -= st->getSrc(s + 1)->reg.size;
+ src[s] = st->getSrc(s + 1);
+ }
+ // set record's values as low sources of @st
+ for (j = 1; sizeRc; ++j) {
+ sizeRc -= st->getSrc(j)->reg.size;
+ st->setSrc(j, rec->insn->getSrc(j));
+ }
+ // set saved values as high sources of @st
+ for (k = j, j = 0; j < s; ++j)
+ st->setSrc(k++, src[j]);
+
+ updateLdStOffset(st, offRc, func);
+ } else {
+ for (j = 1; sizeSt; ++j)
+ sizeSt -= st->getSrc(j)->reg.size;
+ for (s = 1; sizeRc; ++j, ++s) {
+ sizeRc -= rec->insn->getSrc(s)->reg.size;
+ st->setSrc(j, rec->insn->getSrc(s));
+ }
+ rec->offset = offSt;
+ }
+ st->putExtraSources(0, extra); // restore pointer and predicate
+
+ delete_Instruction(prog, rec->insn);
+ rec->insn = st;
+ rec->size = size;
+ rec->insn->setType(typeOfSize(size));
+ return true;
+}
+
+void
+MemoryOpt::Record::set(const Instruction *ldst)
+{
+ const Symbol *mem = ldst->getSrc(0)->asSym();
+ fileIndex = mem->reg.fileIndex;
+ rel[0] = ldst->getIndirect(0, 0);
+ rel[1] = ldst->getIndirect(0, 1);
+ offset = mem->reg.data.offset;
+ base = mem->getBase();
+ size = typeSizeof(ldst->sType);
+}
+
+void
+MemoryOpt::Record::link(Record **list)
+{
+ next = *list;
+ if (next)
+ next->prev = this;
+ prev = NULL;
+ *list = this;
+}
+
+void
+MemoryOpt::Record::unlink(Record **list)
+{
+ if (next)
+ next->prev = prev;
+ if (prev)
+ prev->next = next;
+ else
+ *list = next;
+}
+
+MemoryOpt::Record **
+MemoryOpt::getList(const Instruction *insn)
+{
+ if (insn->op == OP_LOAD || insn->op == OP_VFETCH)
+ return &loads[insn->src[0].getFile()];
+ return &stores[insn->src[0].getFile()];
+}
+
+void
+MemoryOpt::addRecord(Instruction *i)
+{
+ Record **list = getList(i);
+ Record *it = reinterpret_cast<Record *>(recordPool.allocate());
+
+ it->link(list);
+ it->set(i);
+ it->insn = i;
+ it->locked = false;
+}
+
+MemoryOpt::Record *
+MemoryOpt::findRecord(const Instruction *insn, bool load, bool& isAdj) const
+{
+ const Symbol *sym = insn->getSrc(0)->asSym();
+ const int size = typeSizeof(insn->sType);
+ Record *rec = NULL;
+ Record *it = load ? loads[sym->reg.file] : stores[sym->reg.file];
+
+ for (; it; it = it->next) {
+ if (it->locked && insn->op != OP_LOAD)
+ continue;
+ if ((it->offset >> 4) != (sym->reg.data.offset >> 4) ||
+ it->rel[0] != insn->getIndirect(0, 0) ||
+ it->fileIndex != sym->reg.fileIndex ||
+ it->rel[1] != insn->getIndirect(0, 1))
+ continue;
+
+ if (it->offset < sym->reg.data.offset) {
+ if (it->offset + it->size >= sym->reg.data.offset) {
+ isAdj = (it->offset + it->size == sym->reg.data.offset);
+ if (!isAdj)
+ return it;
+ if (!(it->offset & 0x7))
+ rec = it;
+ }
+ } else {
+ isAdj = it->offset != sym->reg.data.offset;
+ if (size <= it->size && !isAdj)
+ return it;
+ else
+ if (!(sym->reg.data.offset & 0x7))
+ if (it->offset - size <= sym->reg.data.offset)
+ rec = it;
+ }
+ }
+ return rec;
+}
+
+bool
+MemoryOpt::replaceLdFromSt(Instruction *ld, Record *rec)
+{
+ Instruction *st = rec->insn;
+ int32_t offSt = rec->offset;
+ int32_t offLd = ld->getSrc(0)->reg.data.offset;
+ int d, s;
+
+ for (s = 1; offSt != offLd && st->srcExists(s); ++s)
+ offSt += st->getSrc(s)->reg.size;
+ if (offSt != offLd)
+ return false;
+
+ for (d = 0; ld->defExists(d) && st->srcExists(s); ++d, ++s) {
+ if (ld->getDef(d)->reg.size != st->getSrc(s)->reg.size)
+ return false;
+ if (st->getSrc(s)->reg.file != FILE_GPR)
+ return false;
+ ld->def[d].replace(st->getSrc(s), false);
+ }
+ ld->bb->remove(ld);
+ return true;
+}
+
+bool
+MemoryOpt::replaceLdFromLd(Instruction *ldE, Record *rec)
+{
+ Instruction *ldR = rec->insn;
+ int32_t offR = rec->offset;
+ int32_t offE = ldE->getSrc(0)->reg.data.offset;
+ int dR, dE;
+
+ assert(offR <= offE);
+ for (dR = 0; offR < offE && ldR->defExists(dR); ++dR)
+ offR += ldR->getDef(dR)->reg.size;
+ if (offR != offE)
+ return false;
+
+ for (dE = 0; ldE->defExists(dE) && ldR->defExists(dR); ++dE, ++dR) {
+ if (ldE->getDef(dE)->reg.size != ldR->getDef(dR)->reg.size)
+ return false;
+ ldE->def[dE].replace(ldR->getDef(dR), false);
+ }
+
+ delete_Instruction(prog, ldE);
+ return true;
+}
+
+bool
+MemoryOpt::replaceStFromSt(Instruction *restrict st, Record *rec)
+{
+ const Instruction *const ri = rec->insn;
+ Value *extra[3];
+
+ int32_t offS = st->getSrc(0)->reg.data.offset;
+ int32_t offR = rec->offset;
+ int32_t endS = offS + typeSizeof(st->dType);
+ int32_t endR = offR + typeSizeof(ri->dType);
+
+ rec->size = MAX2(endS, endR) - MIN2(offS, offR);
+
+ st->takeExtraSources(0, extra);
+
+ if (offR < offS) {
+ Value *vals[4];
+ int s, n;
+ int k = 0;
+ // get non-replaced sources of ri
+ for (s = 1; offR < offS; offR += ri->getSrc(s)->reg.size, ++s)
+ vals[k++] = ri->getSrc(s);
+ n = s;
+ // get replaced sources of st
+ for (s = 1; st->srcExists(s); offS += st->getSrc(s)->reg.size, ++s)
+ vals[k++] = st->getSrc(s);
+ // skip replaced sources of ri
+ for (s = n; offR < endS; offR += ri->getSrc(s)->reg.size, ++s);
+ // get non-replaced sources after values covered by st
+ for (; offR < endR; offR += ri->getSrc(s)->reg.size, ++s)
+ vals[k++] = ri->getSrc(s);
+ for (s = 0; s < k; ++s)
+ st->setSrc(s + 1, vals[s]);
+ st->setSrc(0, ri->getSrc(0));
+ } else
+ if (endR > endS) {
+ int j, s;
+ for (j = 1; offR < endS; offR += ri->getSrc(j++)->reg.size);
+ for (s = 1; offS < endS; offS += st->getSrc(s++)->reg.size);
+ for (; offR < endR; offR += ri->getSrc(j++)->reg.size)
+ st->setSrc(s++, ri->getSrc(j));
+ }
+ st->putExtraSources(0, extra);
+
+ delete_Instruction(prog, rec->insn);
+
+ rec->insn = st;
+ rec->offset = st->getSrc(0)->reg.data.offset;
+
+ st->setType(typeOfSize(rec->size));
+
+ return true;
+}
+
+bool
+MemoryOpt::Record::overlaps(const Instruction *ldst) const
+{
+ Record that;
+ that.set(ldst);
+
+ if (this->fileIndex != that.fileIndex)
+ return false;
+
+ if (this->rel[0] || that.rel[0])
+ return this->base == that.base;
+ return
+ (this->offset < that.offset + that.size) &&
+ (this->offset + this->size > that.offset);
+}
+
+// We must not eliminate stores that affect the result of @ld if
+// we find later stores to the same location, and we may no longer
+// merge them with later stores.
+// The stored value can, however, still be used to determine the value
+// returned by future loads.
+void
+MemoryOpt::lockStores(Instruction *const ld)
+{
+ for (Record *r = stores[ld->src[0].getFile()]; r; r = r->next)
+ if (!r->locked && r->overlaps(ld))
+ r->locked = true;
+}
+
+// Prior loads from the location of @st are no longer valid.
+// Stores to the location of @st may no longer be used to derive
+// the value at it nor be coalesced into later stores.
+void
+MemoryOpt::purgeRecords(Instruction *const st, DataFile f)
+{
+ if (st)
+ f = st->src[0].getFile();
+
+ for (Record *r = loads[f]; r; r = r->next)
+ if (!st || r->overlaps(st))
+ r->unlink(&loads[f]);
+
+ for (Record *r = stores[f]; r; r = r->next)
+ if (!st || r->overlaps(st))
+ r->unlink(&stores[f]);
+}
+
+bool
+MemoryOpt::visit(BasicBlock *bb)
+{
+ bool ret = runOpt(bb);
+ // Run again, one pass won't combine 4 32 bit ld/st to a single 128 bit ld/st
+ // where 96 bit memory operations are forbidden.
+ if (ret)
+ ret = runOpt(bb);
+ return ret;
+}
+
+bool
+MemoryOpt::runOpt(BasicBlock *bb)
+{
+ Instruction *ldst, *next;
+ Record *rec;
+ bool isAdjacent = true;
+
+ for (ldst = bb->getEntry(); ldst; ldst = next) {
+ bool keep = true;
+ bool isLoad = true;
+ next = ldst->next;
+
+ if (ldst->op == OP_LOAD || ldst->op == OP_VFETCH) {
+ if (ldst->isDead()) {
+ // might have been produced by earlier optimization
+ delete_Instruction(prog, ldst);
+ continue;
+ }
+ } else
+ if (ldst->op == OP_STORE || ldst->op == OP_EXPORT) {
+ isLoad = false;
+ } else {
+ // TODO: maybe have all fixed ops act as barrier ?
+ if (ldst->op == OP_CALL) {
+ purgeRecords(NULL, FILE_MEMORY_LOCAL);
+ purgeRecords(NULL, FILE_MEMORY_GLOBAL);
+ purgeRecords(NULL, FILE_MEMORY_SHARED);
+ purgeRecords(NULL, FILE_SHADER_OUTPUT);
+ } else
+ if (ldst->op == OP_EMIT || ldst->op == OP_RESTART) {
+ purgeRecords(NULL, FILE_SHADER_OUTPUT);
+ }
+ continue;
+ }
+ if (ldst->getPredicate()) // TODO: handle predicated ld/st
+ continue;
+
+ if (isLoad) {
+ DataFile file = ldst->src[0].getFile();
+
+ // if ld l[]/g[] look for previous store to eliminate the reload
+ if (file == FILE_MEMORY_GLOBAL || file == FILE_MEMORY_LOCAL) {
+ // TODO: shared memory ?
+ rec = findRecord(ldst, false, isAdjacent);
+ if (rec && !isAdjacent)
+ keep = !replaceLdFromSt(ldst, rec);
+ }
+
+ // or look for ld from the same location and replace this one
+ rec = keep ? findRecord(ldst, true, isAdjacent) : NULL;
+ if (rec) {
+ if (!isAdjacent)
+ keep = !replaceLdFromLd(ldst, rec);
+ else
+ // or combine a previous load with this one
+ keep = !combineLd(rec, ldst);
+ }
+ if (keep)
+ lockStores(ldst);
+ } else {
+ rec = findRecord(ldst, false, isAdjacent);
+ if (rec) {
+ if (!isAdjacent)
+ keep = !replaceStFromSt(ldst, rec);
+ else
+ keep = !combineSt(rec, ldst);
+ }
+ if (keep)
+ purgeRecords(ldst, DATA_FILE_COUNT);
+ }
+ if (keep)
+ addRecord(ldst);
+ }
+ reset();
+
+ return true;
+}
+
+// =============================================================================
+
+// Turn control flow into predicated instructions (after register allocation !).
+// TODO:
+// Could move this to before register allocation on NVC0 and also handle nested
+// constructs.
+class FlatteningPass : public Pass
+{
+private:
+ virtual bool visit(BasicBlock *);
+
+ bool tryPredicateConditional(BasicBlock *);
+ void predicateInstructions(BasicBlock *, Value *pred, CondCode cc);
+ void tryPropagateBranch(BasicBlock *);
+ inline bool isConstantCondition(Value *pred);
+ inline bool mayPredicate(const Instruction *, const Value *pred) const;
+ inline void removeFlow(Instruction *);
+};
+
+bool
+FlatteningPass::isConstantCondition(Value *pred)
+{
+ Instruction *insn = pred->getUniqueInsn();
+ assert(insn);
+ if (insn->op != OP_SET || insn->srcExists(2))
+ return false;
+
+ for (int s = 0; s < 2 && insn->srcExists(s); ++s) {
+ Instruction *ld = insn->getSrc(s)->getUniqueInsn();
+ DataFile file;
+ if (ld) {
+ if (ld->op != OP_MOV && ld->op != OP_LOAD)
+ return false;
+ if (ld->src[0].isIndirect(0))
+ return false;
+ file = ld->src[0].getFile();
+ } else {
+ file = insn->src[s].getFile();
+ // catch $r63 on NVC0
+ if (file == FILE_GPR && insn->getSrc(s)->reg.data.id > prog->maxGPR)
+ file = FILE_IMMEDIATE;
+ }
+ if (file != FILE_IMMEDIATE && file != FILE_MEMORY_CONST)
+ return false;
+ }
+ return true;
+}
+
+void
+FlatteningPass::removeFlow(Instruction *insn)
+{
+ FlowInstruction *term = insn ? insn->asFlow() : NULL;
+ if (!term)
+ return;
+ Graph::Edge::Type ty = term->bb->cfg.outgoing().getType();
+
+ if (term->op == OP_BRA) {
+ // TODO: this might get more difficult when we get arbitrary BRAs
+ if (ty == Graph::Edge::CROSS || ty == Graph::Edge::BACK)
+ return;
+ } else
+ if (term->op != OP_JOIN)
+ return;
+
+ delete_Instruction(prog, term);
+
+ Value *pred = term->getPredicate();
+
+ if (pred && pred->refCount() == 0) {
+ Instruction *pSet = pred->getUniqueInsn();
+ pred->join->reg.data.id = -1; // deallocate
+ if (pSet->isDead())
+ delete_Instruction(prog, pSet);
+ }
+}
+
+void
+FlatteningPass::predicateInstructions(BasicBlock *bb, Value *pred, CondCode cc)
+{
+ for (Instruction *i = bb->getEntry(); i; i = i->next) {
+ if (i->isNop())
+ continue;
+ assert(!i->getPredicate());
+ i->setPredicate(cc, pred);
+ }
+ removeFlow(bb->getExit());
+}
+
+bool
+FlatteningPass::mayPredicate(const Instruction *insn, const Value *pred) const
+{
+ if (insn->isPseudo())
+ return true;
+ // TODO: calls where we don't know which registers are modified
+
+ if (!prog->getTarget()->mayPredicate(insn, pred))
+ return false;
+ for (int d = 0; insn->defExists(d); ++d)
+ if (insn->getDef(d)->equals(pred))
+ return false;
+ return true;
+}
+
+// If we conditionally skip over or to a branch instruction, replace it.
+// NOTE: We do not update the CFG anymore here !
+void
+FlatteningPass::tryPropagateBranch(BasicBlock *bb)
+{
+ BasicBlock *bf = NULL;
+ unsigned int i;
+
+ if (bb->cfg.outgoingCount() != 2)
+ return;
+ if (!bb->getExit() || bb->getExit()->op != OP_BRA)
+ return;
+ Graph::EdgeIterator ei = bb->cfg.outgoing();
+
+ for (i = 0; !ei.end(); ++i, ei.next()) {
+ bf = BasicBlock::get(ei.getNode());
+ if (bf->getInsnCount() == 1)
+ break;
+ }
+ if (ei.end() || !bf->getExit())
+ return;
+ FlowInstruction *bra = bb->getExit()->asFlow();
+ FlowInstruction *rep = bf->getExit()->asFlow();
+
+ if (rep->getPredicate())
+ return;
+ if (rep->op != OP_BRA &&
+ rep->op != OP_JOIN &&
+ rep->op != OP_EXIT)
+ return;
+
+ bra->op = rep->op;
+ bra->target.bb = rep->target.bb;
+ if (i) // 2nd out block means branch not taken
+ bra->cc = inverseCondCode(bra->cc);
+ bf->remove(rep);
+}
+
+bool
+FlatteningPass::visit(BasicBlock *bb)
+{
+ if (tryPredicateConditional(bb))
+ return true;
+
+ // try to attach join to previous instruction
+ Instruction *insn = bb->getExit();
+ if (insn && insn->op == OP_JOIN && !insn->getPredicate()) {
+ insn = insn->prev;
+ if (insn && !insn->getPredicate() && !insn->asFlow() && !insn->isNop()) {
+ insn->join = 1;
+ bb->remove(bb->getExit());
+ return true;
+ }
+ }
+
+ tryPropagateBranch(bb);
+
+ return true;
+}
+
+bool
+FlatteningPass::tryPredicateConditional(BasicBlock *bb)
+{
+ BasicBlock *bL = NULL, *bR = NULL;
+ unsigned int nL = 0, nR = 0, limit = 12;
+ Instruction *insn;
+ unsigned int mask;
+
+ mask = bb->initiatesSimpleConditional();
+ if (!mask)
+ return false;
+
+ assert(bb->getExit());
+ Value *pred = bb->getExit()->getPredicate();
+ assert(pred);
+
+ if (isConstantCondition(pred))
+ limit = 4;
+
+ Graph::EdgeIterator ei = bb->cfg.outgoing();
+
+ if (mask & 1) {
+ bL = BasicBlock::get(ei.getNode());
+ for (insn = bL->getEntry(); insn; insn = insn->next, ++nL)
+ if (!mayPredicate(insn, pred))
+ return false;
+ if (nL > limit)
+ return false; // too long, do a real branch
+ }
+ ei.next();
+
+ if (mask & 2) {
+ bR = BasicBlock::get(ei.getNode());
+ for (insn = bR->getEntry(); insn; insn = insn->next, ++nR)
+ if (!mayPredicate(insn, pred))
+ return false;
+ if (nR > limit)
+ return false; // too long, do a real branch
+ }
+
+ if (bL)
+ predicateInstructions(bL, pred, bb->getExit()->cc);
+ if (bR)
+ predicateInstructions(bR, pred, inverseCondCode(bb->getExit()->cc));
+
+ if (bb->joinAt) {
+ bb->remove(bb->joinAt);
+ bb->joinAt = NULL;
+ }
+ removeFlow(bb->getExit()); // delete the branch/join at the fork point
+
+ // remove potential join operations at the end of the conditional
+ if (prog->getTarget()->joinAnterior) {
+ bb = BasicBlock::get((bL ? bL : bR)->cfg.outgoing().getNode());
+ if (bb->getEntry() && bb->getEntry()->op == OP_JOIN)
+ removeFlow(bb->getEntry());
+ }
+
+ return true;
+}
+
+// =============================================================================
+
+// Common subexpression elimination. Stupid O^2 implementation.
+class LocalCSE : public Pass
+{
+private:
+ virtual bool visit(BasicBlock *);
+
+ inline bool tryReplace(Instruction **, Instruction *);
+
+ DLList ops[OP_LAST + 1];
+};
+
+class GlobalCSE : public Pass
+{
+private:
+ virtual bool visit(BasicBlock *);
+};
+
+bool
+Instruction::isActionEqual(const Instruction *that) const
+{
+ if (this->op != that->op ||
+ this->dType != that->dType ||
+ this->sType != that->sType)
+ return false;
+ if (this->cc != that->cc)
+ return false;
+
+ if (this->asTex()) {
+ if (memcmp(&this->asTex()->tex,
+ &that->asTex()->tex,
+ sizeof(this->asTex()->tex)))
+ return false;
+ } else
+ if (this->asCmp()) {
+ if (this->asCmp()->setCond != that->asCmp()->setCond)
+ return false;
+ } else
+ if (this->asFlow()) {
+ return false;
+ } else {
+ if (this->atomic != that->atomic ||
+ this->ipa != that->ipa ||
+ this->lanes != that->lanes ||
+ this->perPatch != that->perPatch)
+ return false;
+ if (this->postFactor != that->postFactor)
+ return false;
+ }
+
+ if (this->subOp != that->subOp ||
+ this->saturate != that->saturate ||
+ this->rnd != that->rnd ||
+ this->ftz != that->ftz ||
+ this->dnz != that->dnz ||
+ this->cache != that->cache)
+ return false;
+
+ return true;
+}
+
+bool
+Instruction::isResultEqual(const Instruction *that) const
+{
+ unsigned int d, s;
+
+ // NOTE: location of discard only affects tex with liveOnly and quadops
+ if (!this->defExists(0) && this->op != OP_DISCARD)
+ return false;
+
+ if (!isActionEqual(that))
+ return false;
+
+ if (this->predSrc != that->predSrc)
+ return false;
+
+ for (d = 0; this->defExists(d); ++d) {
+ if (!that->defExists(d) ||
+ !this->getDef(d)->equals(that->getDef(d), false))
+ return false;
+ }
+ if (that->defExists(d))
+ return false;
+
+ for (s = 0; this->srcExists(s); ++s) {
+ if (!that->srcExists(s))
+ return false;
+ if (this->src[s].mod != that->src[s].mod)
+ return false;
+ if (!this->getSrc(s)->equals(that->getSrc(s), true))
+ return false;
+ }
+ if (that->srcExists(s))
+ return false;
+
+ if (op == OP_LOAD || op == OP_VFETCH) {
+ switch (src[0].getFile()) {
+ case FILE_MEMORY_CONST:
+ case FILE_SHADER_INPUT:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ return true;
+}
+
+// pull through common expressions from different in-blocks
+bool
+GlobalCSE::visit(BasicBlock *bb)
+{
+ Instruction *phi, *next, *ik;
+ int s;
+
+ for (phi = bb->getPhi(); phi && phi->op == OP_PHI; phi = next) {
+ next = phi->next;
+ if (phi->getSrc(0)->refCount() > 1)
+ continue;
+ ik = phi->getSrc(0)->getInsn();
+ for (s = 1; phi->srcExists(s); ++s) {
+ if (phi->getSrc(s)->refCount() > 1)
+ break;
+ if (!phi->getSrc(s)->getInsn()->isResultEqual(ik))
+ break;
+ }
+ if (!phi->srcExists(s)) {
+ Instruction *entry = bb->getEntry();
+ ik->bb->remove(ik);
+ if (!entry || entry->op != OP_JOIN)
+ bb->insertHead(ik);
+ else
+ bb->insertAfter(entry, ik);
+ ik->setDef(0, phi->getDef(0));
+ delete_Instruction(prog, phi);
+ }
+ }
+
+ return true;
+}
+
+bool
+LocalCSE::tryReplace(Instruction **ptr, Instruction *i)
+{
+ Instruction *old = *ptr;
+ if (!old->isResultEqual(i))
+ return false;
+ for (int d = 0; old->defExists(d); ++d)
+ old->def[d].replace(i->getDef(d), false);
+ delete_Instruction(prog, old);
+ *ptr = NULL;
+ return true;
+}
+
+bool
+LocalCSE::visit(BasicBlock *bb)
+{
+ unsigned int replaced;
+
+ do {
+ Instruction *ir, *next;
+
+ replaced = 0;
+
+ // will need to know the order of instructions
+ int serial = 0;
+ for (ir = bb->getEntry(); ir; ir = ir->next)
+ ir->serial = serial++;
+
+ for (ir = bb->getEntry(); ir; ir = next) {
+ int s;
+ Value *src = NULL;
+
+ next = ir->next;
+
+ if (ir->fixed) {
+ ops[ir->op].insert(ir);
+ continue;
+ }
+
+ for (s = 0; ir->srcExists(s); ++s)
+ if (ir->getSrc(s)->asLValue())
+ if (!src || ir->getSrc(s)->refCount() < src->refCount())
+ src = ir->getSrc(s);
+
+ if (src) {
+ for (ValueRef::Iterator refs = src->uses->iterator(); !refs.end();
+ refs.next()) {
+ Instruction *ik = refs.get()->getInsn();
+ if (ik->serial < ir->serial && ik->bb == ir->bb)
+ if (tryReplace(&ir, ik))
+ break;
+ }
+ } else {
+ DLLIST_FOR_EACH(&ops[ir->op], iter)
+ {
+ Instruction *ik = reinterpret_cast<Instruction *>(iter.get());
+ if (tryReplace(&ir, ik))
+ break;
+ }
+ }
+
+ if (ir)
+ ops[ir->op].insert(ir);
+ else
+ ++replaced;
+ }
+ for (unsigned int i = 0; i <= OP_LAST; ++i)
+ ops[i].clear();
+
+ } while (replaced);
+
+ return true;
+}
+
+// =============================================================================
+
+// Remove computations of unused values.
+class DeadCodeElim : public Pass
+{
+public:
+ bool buryAll(Program *);
+
+private:
+ virtual bool visit(BasicBlock *);
+
+ void checkSplitLoad(Instruction *ld); // for partially dead loads
+
+ unsigned int deadCount;
+};
+
+bool
+DeadCodeElim::buryAll(Program *prog)
+{
+ do {
+ deadCount = 0;
+ if (!this->run(prog, false, false))
+ return false;
+ } while (deadCount);
+
+ return true;
+}
+
+bool
+DeadCodeElim::visit(BasicBlock *bb)
+{
+ Instruction *next;
+
+ for (Instruction *i = bb->getFirst(); i; i = next) {
+ next = i->next;
+ if (i->isDead()) {
+ ++deadCount;
+ delete_Instruction(prog, i);
+ } else
+ if (i->defExists(1) && (i->op == OP_VFETCH || i->op == OP_LOAD)) {
+ checkSplitLoad(i);
+ }
+ }
+ return true;
+}
+
+void
+DeadCodeElim::checkSplitLoad(Instruction *ld1)
+{
+ Instruction *ld2 = NULL; // can get at most 2 loads
+ Value *def1[4];
+ Value *def2[4];
+ int32_t addr1, addr2;
+ int32_t size1, size2;
+ int d, n1, n2;
+ uint32_t mask = 0xffffffff;
+
+ for (d = 0; ld1->defExists(d); ++d)
+ if (!ld1->getDef(d)->refCount() && ld1->getDef(d)->reg.data.id < 0)
+ mask &= ~(1 << d);
+ if (mask == 0xffffffff)
+ return;
+
+ addr1 = ld1->getSrc(0)->reg.data.offset;
+ n1 = n2 = 0;
+ size1 = size2 = 0;
+ for (d = 0; ld1->defExists(d); ++d) {
+ if (mask & (1 << d)) {
+ if (size1 && (addr1 & 0x7))
+ break;
+ def1[n1] = ld1->getDef(d);
+ size1 += def1[n1++]->reg.size;
+ } else
+ if (!n1) {
+ addr1 += ld1->getDef(d)->reg.size;
+ } else {
+ break;
+ }
+ }
+ for (addr2 = addr1 + size1; ld1->defExists(d); ++d) {
+ if (mask & (1 << d)) {
+ def2[n2] = ld1->getDef(d);
+ size2 += def2[n2++]->reg.size;
+ } else {
+ assert(!n2);
+ addr2 += ld1->getDef(d)->reg.size;
+ }
+ }
+
+ updateLdStOffset(ld1, addr1, func);
+ ld1->setType(typeOfSize(size1));
+ for (d = 0; d < 4; ++d)
+ ld1->setDef(d, (d < n1) ? def1[d] : NULL);
+
+ if (!n2)
+ return;
+
+ ld2 = ld1->clone(false);
+ updateLdStOffset(ld2, addr2, func);
+ ld2->setType(typeOfSize(size2));
+ for (d = 0; d < 4; ++d)
+ ld2->setDef(d, (d < n2) ? def2[d] : NULL);
+
+ ld1->bb->insertAfter(ld1, ld2);
+}
+
+// =============================================================================
+
+#define RUN_PASS(l, n, f) \
+ if (level >= (l)) { \
+ if (dbgFlags & NV50_IR_DEBUG_VERBOSE) \
+ INFO("PEEPHOLE: %s\n", #n); \
+ n pass; \
+ if (!pass.f(this)) \
+ return false; \
+ }
+
+bool
+Program::optimizeSSA(int level)
+{
+ RUN_PASS(1, DeadCodeElim, buryAll);
+ RUN_PASS(1, CopyPropagation, run);
+ RUN_PASS(2, GlobalCSE, run);
+ RUN_PASS(1, LocalCSE, run);
+ RUN_PASS(2, AlgebraicOpt, run);
+ RUN_PASS(2, ModifierFolding, run); // before load propagation -> less checks
+ RUN_PASS(1, ConstantFolding, foldAll);
+ RUN_PASS(1, LoadPropagation, run);
+ RUN_PASS(2, MemoryOpt, run);
+ RUN_PASS(2, LocalCSE, run);
+ RUN_PASS(0, DeadCodeElim, buryAll);
+ return true;
+}
+
+bool
+Program::optimizePostRA(int level)
+{
+ RUN_PASS(2, FlatteningPass, run);
+ return true;
+}
+
+}
diff --git a/src/gallium/drivers/nv50/codegen/nv50_ir_print.cpp b/src/gallium/drivers/nv50/codegen/nv50_ir_print.cpp
new file mode 100644
index 00000000000..b5ca3814098
--- /dev/null
+++ b/src/gallium/drivers/nv50/codegen/nv50_ir_print.cpp
@@ -0,0 +1,558 @@
+
+#include "nv50_ir.h"
+#include "nv50_ir_target.h"
+
+namespace nv50_ir {
+
+enum TextStyle
+{
+ TXT_DEFAULT,
+ TXT_GPR,
+ TXT_REGISTER,
+ TXT_FLAGS,
+ TXT_MEM,
+ TXT_IMMD,
+ TXT_BRA,
+ TXT_INSN
+};
+
+static const char *colour[8] =
+{
+#if 1
+ "\x1b[00m",
+ "\x1b[34m",
+ "\x1b[35m",
+ "\x1b[35m",
+ "\x1b[36m",
+ "\x1b[33m",
+ "\x1b[37m",
+ "\x1b[32m"
+#else
+ "", "", "", "", "", "", "", ""
+#endif
+};
+
+const char *operationStr[OP_LAST + 1] =
+{
+ "nop",
+ "phi",
+ "union",
+ "split",
+ "merge",
+ "consec",
+ "mov",
+ "ld",
+ "st",
+ "add",
+ "sub",
+ "mul",
+ "div",
+ "mod",
+ "mad",
+ "fma",
+ "sad",
+ "abs",
+ "neg",
+ "not",
+ "and",
+ "or",
+ "xor",
+ "shl",
+ "shr",
+ "max",
+ "min",
+ "sat",
+ "ceil",
+ "floor",
+ "trunc",
+ "cvt",
+ "set and",
+ "set or",
+ "set xor",
+ "set",
+ "selp",
+ "slct",
+ "rcp",
+ "rsq",
+ "lg2",
+ "sin",
+ "cos",
+ "ex2",
+ "exp",
+ "log",
+ "presin",
+ "preex2",
+ "sqrt",
+ "pow",
+ "bra",
+ "call",
+ "ret",
+ "cont",
+ "break",
+ "preret",
+ "precont",
+ "prebreak",
+ "brkpt",
+ "joinat",
+ "join",
+ "discard",
+ "exit",
+ "barrier",
+ "vfetch",
+ "pfetch",
+ "export",
+ "linterp",
+ "pinterp",
+ "emit",
+ "restart",
+ "tex",
+ "texbias",
+ "texlod",
+ "texfetch",
+ "texquery",
+ "texgrad",
+ "texgather",
+ "texcsaa",
+ "suld",
+ "sust",
+ "dfdx",
+ "dfdy",
+ "rdsv",
+ "wrsv",
+ "pixld",
+ "quadop",
+ "quadon",
+ "quadpop",
+ "popcnt",
+ "insbf",
+ "extbf",
+ "(invalid)"
+};
+
+static const char *DataTypeStr[] =
+{
+ "-",
+ "u8", "s8",
+ "u16", "s16",
+ "u32", "s32",
+ "u64", "s64",
+ "f16", "f32", "f64",
+ "b96", "b128"
+};
+
+static const char *RoundModeStr[] =
+{
+ "", "rm", "rz", "rp", "rni", "rmi", "rzi", "rpi"
+};
+
+static const char *CondCodeStr[] =
+{
+ "never",
+ "lt",
+ "eq",
+ "le",
+ "gt",
+ "ne",
+ "ge",
+ "",
+ "(invalid)",
+ "ltu",
+ "equ",
+ "leu",
+ "gtu",
+ "neu",
+ "geu",
+ "",
+ "no",
+ "nc",
+ "ns",
+ "na",
+ "a",
+ "s",
+ "c",
+ "o"
+};
+
+static const char *SemanticStr[SV_LAST + 1] =
+{
+ "POSITION",
+ "VERTEX_ID",
+ "INSTANCE_ID",
+ "INVOCATION_ID",
+ "PRIMITIVE_ID",
+ "VERTEX_COUNT",
+ "LAYER",
+ "VIEWPORT_INDEX",
+ "Y_DIR",
+ "FACE",
+ "POINT_SIZE",
+ "POINT_COORD",
+ "CLIP_DISTANCE",
+ "SAMPLE_INDEX",
+ "TESS_FACTOR",
+ "TESS_COORD",
+ "TID",
+ "CTAID",
+ "NTID",
+ "GRIDID",
+ "NCTAID",
+ "LANEID",
+ "PHYSID",
+ "NPHYSID",
+ "CLOCK",
+ "LBASE",
+ "SBASE",
+ "?",
+ "(INVALID)"
+};
+
+#define PRINT(args...) \
+ do { \
+ pos += snprintf(&buf[pos], size - pos, args); \
+ } while(0)
+
+#define SPACE_PRINT(cond, args...) \
+ do { \
+ if (cond) \
+ buf[pos++] = ' '; \
+ pos += snprintf(&buf[pos], size - pos, args); \
+ } while(0)
+
+#define SPACE() \
+ do { \
+ if (pos < size) \
+ buf[pos++] = ' '; \
+ } while(0)
+
+int Modifier::print(char *buf, size_t size) const
+{
+ size_t pos = 0;
+
+ if (bits)
+ PRINT("%s", colour[TXT_INSN]);
+
+ size_t base = pos;
+
+ if (bits & NV50_IR_MOD_NOT)
+ PRINT("not");
+ if (bits & NV50_IR_MOD_SAT)
+ SPACE_PRINT(pos > base && pos < size, "sat");
+ if (bits & NV50_IR_MOD_NEG)
+ SPACE_PRINT(pos > base && pos < size, "neg");
+ if (bits & NV50_IR_MOD_ABS)
+ SPACE_PRINT(pos > base && pos < size, "abs");
+
+ return pos;
+}
+
+int LValue::print(char *buf, size_t size, DataType ty) const
+{
+ const char *postFix = "";
+ size_t pos = 0;
+ int idx = join->reg.data.id >= 0 ? join->reg.data.id : id;
+ char p = join->reg.data.id >= 0 ? '$' : '%';
+ char r;
+ int col = TXT_DEFAULT;
+
+ switch (reg.file) {
+ case FILE_GPR:
+ r = 'r'; col = TXT_GPR;
+ if (reg.size == 8)
+ postFix = "d";
+ else
+ if (reg.size == 16)
+ postFix = "q";
+ break;
+ case FILE_PREDICATE:
+ r = 'p'; col = TXT_REGISTER;
+ if (reg.size == 2)
+ postFix = "d";
+ else
+ if (reg.size == 4)
+ postFix = "q";
+ break;
+ case FILE_FLAGS:
+ r = 'c'; col = TXT_FLAGS;
+ break;
+ case FILE_ADDRESS:
+ r = 'a'; col = TXT_REGISTER;
+ break;
+ default:
+ assert(!"invalid file for lvalue");
+ r = '?';
+ break;
+ }
+
+ PRINT("%s%c%c%i%s", colour[col], p, r, idx, postFix);
+
+ return pos;
+}
+
+int ImmediateValue::print(char *buf, size_t size, DataType ty) const
+{
+ size_t pos = 0;
+
+ PRINT("%s", colour[TXT_IMMD]);
+
+ switch (ty) {
+ case TYPE_F32: PRINT("%f", reg.data.f32); break;
+ case TYPE_F64: PRINT("%f", reg.data.f64); break;
+ case TYPE_U8: PRINT("0x%02x", reg.data.u8); break;
+ case TYPE_S8: PRINT("%i", reg.data.s8); break;
+ case TYPE_U16: PRINT("0x%04x", reg.data.u16); break;
+ case TYPE_S16: PRINT("%i", reg.data.s16); break;
+ case TYPE_U32: PRINT("0x%08x", reg.data.u32); break;
+ case TYPE_S32: PRINT("%i", reg.data.s32); break;
+ case TYPE_U64:
+ case TYPE_S64:
+ default:
+ PRINT("0x%016lx", reg.data.u64);
+ break;
+ }
+ return pos;
+}
+
+int Symbol::print(char *buf, size_t size, DataType ty) const
+{
+ return print(buf, size, NULL, NULL, ty);
+}
+
+int Symbol::print(char *buf, size_t size,
+ Value *rel, Value *dimRel, DataType ty) const
+{
+ size_t pos = 0;
+ char c;
+
+ if (ty == TYPE_NONE)
+ ty = typeOfSize(reg.size);
+
+ if (reg.file == FILE_SYSTEM_VALUE) {
+ PRINT("%ssv[%s%s:%i%s", colour[TXT_MEM],
+ colour[TXT_REGISTER],
+ SemanticStr[reg.data.sv.sv], reg.data.sv.index, colour[TXT_MEM]);
+ if (rel) {
+ PRINT("%s+", colour[TXT_DEFAULT]);
+ pos += rel->print(&buf[pos], size - pos);
+ }
+ PRINT("%s]", colour[TXT_MEM]);
+ return pos;
+ }
+
+ switch (reg.file) {
+ case FILE_MEMORY_CONST: c = 'c'; break;
+ case FILE_SHADER_INPUT: c = 'a'; break;
+ case FILE_SHADER_OUTPUT: c = 'o'; break;
+ case FILE_MEMORY_GLOBAL: c = 'g'; break;
+ case FILE_MEMORY_SHARED: c = 's'; break;
+ case FILE_MEMORY_LOCAL: c = 'l'; break;
+ default:
+ assert(!"invalid file");
+ c = '?';
+ break;
+ }
+
+ if (c == 'c')
+ PRINT("%s%c%i[", colour[TXT_MEM], c, reg.fileIndex);
+ else
+ PRINT("%s%c[", colour[TXT_MEM], c);
+
+ if (dimRel) {
+ pos += dimRel->print(&buf[pos], size - pos, TYPE_S32);
+ PRINT("%s][", colour[TXT_MEM]);
+ }
+
+ if (rel) {
+ pos += rel->print(&buf[pos], size - pos);
+ PRINT("%s%c", colour[TXT_DEFAULT], (reg.data.offset < 0) ? '-' : '+');
+ } else {
+ assert(reg.data.offset >= 0);
+ }
+ PRINT("%s0x%x%s]", colour[TXT_IMMD], abs(reg.data.offset), colour[TXT_MEM]);
+
+ return pos;
+}
+
+void Instruction::print() const
+{
+ #define BUFSZ 512
+
+ const size_t size = BUFSZ;
+
+ char buf[BUFSZ];
+ int s, d;
+ size_t pos = 0;
+
+ PRINT("%s", colour[TXT_INSN]);
+
+ if (join)
+ PRINT("join ");
+
+ if (predSrc >= 0) {
+ const size_t pre = pos;
+ if (getSrc(predSrc)->reg.file == FILE_PREDICATE) {
+ if (cc == CC_NOT_P)
+ PRINT("not");
+ } else {
+ PRINT("%s", CondCodeStr[cc]);
+ }
+ if (pos > pre + 1)
+ SPACE();
+ pos += src[predSrc].get()->print(&buf[pos], BUFSZ - pos);
+ PRINT(" %s", colour[TXT_INSN]);
+ }
+
+ if (saturate)
+ PRINT("sat ");
+
+ if (asFlow()) {
+ PRINT("%s", operationStr[op]);
+ if (op == OP_CALL && asFlow()->builtin) {
+ PRINT(" %sBUILTIN:%i", colour[TXT_BRA], asFlow()->target.builtin);
+ } else
+ if (op == OP_CALL && asFlow()->target.fn) {
+ PRINT(" %s%s", colour[TXT_BRA], asFlow()->target.fn->getName());
+ } else
+ if (asFlow()->target.bb)
+ PRINT(" %sBB:%i", colour[TXT_BRA], asFlow()->target.bb->getId());
+ } else {
+ PRINT("%s ", operationStr[op]);
+ if (perPatch)
+ PRINT("patch ");
+ if (asTex())
+ PRINT("%s ", asTex()->tex.target.getName());
+ if (postFactor)
+ PRINT("x2^%i ", postFactor);
+ PRINT("%s%s", dnz ? "dnz " : (ftz ? "ftz " : ""), DataTypeStr[dType]);
+ }
+
+ if (rnd != ROUND_N)
+ PRINT(" %s", RoundModeStr[rnd]);
+
+ if (def[1].exists())
+ PRINT(" {");
+ for (d = 0; defExists(d); ++d) {
+ SPACE();
+ pos += def[d].get()->print(&buf[pos], size - pos);
+ }
+ if (d > 1)
+ PRINT(" %s}", colour[TXT_INSN]);
+ else
+ if (!d && !asFlow())
+ PRINT(" %s#", colour[TXT_INSN]);
+
+ if (asCmp())
+ PRINT(" %s%s", colour[TXT_INSN], CondCodeStr[asCmp()->setCond]);
+
+ if (sType != dType)
+ PRINT(" %s%s", colour[TXT_INSN], DataTypeStr[sType]);
+
+ for (s = 0; srcExists(s); ++s) {
+ if (s == predSrc || src[s].usedAsPtr)
+ continue;
+ const size_t pre = pos;
+ SPACE();
+ pos += src[s].mod.print(&buf[pos], BUFSZ - pos);
+ if (pos > pre + 1)
+ SPACE();
+ if (src[s].isIndirect(0) || src[s].isIndirect(1))
+ pos += src[s].get()->asSym()->print(&buf[pos], BUFSZ - pos,
+ getIndirect(s, 0),
+ getIndirect(s, 1));
+ else
+ pos += src[s].get()->print(&buf[pos], BUFSZ - pos, sType);
+ }
+
+ PRINT("%s", colour[TXT_DEFAULT]);
+
+ buf[MIN2(pos, BUFSZ - 1)] = 0;
+
+ INFO("%s (%u)\n", buf, encSize);
+}
+
+class PrintPass : public Pass
+{
+public:
+ PrintPass() : serial(0) { }
+
+ virtual bool visit(Function *);
+ virtual bool visit(BasicBlock *);
+ virtual bool visit(Instruction *);
+
+private:
+ int serial;
+};
+
+bool
+PrintPass::visit(Function *fn)
+{
+ INFO("\n%s:\n", fn->getName());
+
+ return true;
+}
+
+bool
+PrintPass::visit(BasicBlock *bb)
+{
+#if 0
+ INFO("---\n");
+ for (Graph::EdgeIterator ei = bb->cfg.incident(); !ei.end(); ei.next())
+ INFO(" <- BB:%i (%s)\n",
+ BasicBlock::get(ei.getNode())->getId(),
+ ei.getEdge()->typeStr());
+#endif
+ INFO("BB:%i (%u instructions) - ", bb->getId(), bb->getInsnCount());
+
+ if (bb->idom())
+ INFO("idom = BB:%i, ", bb->idom()->getId());
+
+ INFO("df = { ");
+ for (DLList::Iterator df = bb->getDF().iterator(); !df.end(); df.next())
+ INFO("BB:%i ", BasicBlock::get(df)->getId());
+
+ INFO("}\n");
+
+ for (Graph::EdgeIterator ei = bb->cfg.outgoing(); !ei.end(); ei.next())
+ INFO(" -> BB:%i (%s)\n",
+ BasicBlock::get(ei.getNode())->getId(),
+ ei.getEdge()->typeStr());
+
+ return true;
+}
+
+bool
+PrintPass::visit(Instruction *insn)
+{
+ INFO("%3i: ", serial++);
+ insn->print();
+ return true;
+}
+
+void
+Function::print()
+{
+ PrintPass pass;
+ pass.run(this, true, false);
+}
+
+void
+Program::print()
+{
+ PrintPass pass;
+ pass.run(this, true, false);
+}
+
+void
+Function::printLiveIntervals() const
+{
+ INFO("printing live intervals ...\n");
+
+ for (ArrayList::Iterator it = allLValues.iterator(); !it.end(); it.next()) {
+ const Value *lval = Value::get(it)->asLValue();
+ if (lval && !lval->livei.isEmpty()) {
+ INFO("livei(%%%i): ", lval->id);
+ lval->livei.print();
+ }
+ }
+}
+
+} // namespace nv50_ir
diff --git a/src/gallium/drivers/nv50/codegen/nv50_ir_ra.cpp b/src/gallium/drivers/nv50/codegen/nv50_ir_ra.cpp
new file mode 100644
index 00000000000..7e3c44d3b15
--- /dev/null
+++ b/src/gallium/drivers/nv50/codegen/nv50_ir_ra.cpp
@@ -0,0 +1,963 @@
+
+#include "nv50_ir.h"
+#include "nv50_ir_target.h"
+
+#include "nv50/nv50_debug.h"
+
+namespace nv50_ir {
+
+#define MAX_REGISTER_FILE_SIZE 256
+
+class RegisterSet
+{
+public:
+ RegisterSet();
+ RegisterSet(const Target *);
+
+ void init(const Target *);
+ void reset(); // reset allocation status, but not max assigned regs
+
+ void periodicMask(DataFile f, uint32_t lock, uint32_t unlock);
+ void intersect(DataFile f, const RegisterSet *);
+
+ bool assign(Value **, int nr);
+ void release(const Value *);
+ void occupy(const Value *);
+
+ int getMaxAssigned(DataFile f) const { return fill[f]; }
+
+ void print() const;
+
+private:
+ uint32_t bits[FILE_ADDRESS + 1][(MAX_REGISTER_FILE_SIZE + 31) / 32];
+
+ int unit[FILE_ADDRESS + 1]; // log2 of allocation granularity
+
+ int last[FILE_ADDRESS + 1];
+ int fill[FILE_ADDRESS + 1];
+};
+
+void
+RegisterSet::reset()
+{
+ memset(bits, 0, sizeof(bits));
+}
+
+RegisterSet::RegisterSet()
+{
+ reset();
+}
+
+void
+RegisterSet::init(const Target *targ)
+{
+ for (unsigned int rf = 0; rf <= FILE_ADDRESS; ++rf) {
+ DataFile f = static_cast<DataFile>(rf);
+ last[rf] = targ->getFileSize(f) - 1;
+ unit[rf] = targ->getFileUnit(f);
+ fill[rf] = -1;
+ assert(last[rf] < MAX_REGISTER_FILE_SIZE);
+ }
+}
+
+RegisterSet::RegisterSet(const Target *targ)
+{
+ reset();
+ init(targ);
+}
+
+void
+RegisterSet::periodicMask(DataFile f, uint32_t lock, uint32_t unlock)
+{
+ for (int i = 0; i < (last[f] + 31) / 32; ++i)
+ bits[f][i] = (bits[f][i] | lock) & ~unlock;
+}
+
+void
+RegisterSet::intersect(DataFile f, const RegisterSet *set)
+{
+ for (int i = 0; i < (last[f] + 31) / 32; ++i)
+ bits[f][i] |= set->bits[f][i];
+}
+
+void
+RegisterSet::print() const
+{
+ INFO("GPR:");
+ for (int i = 0; i < (last[FILE_GPR] + 31) / 32; ++i)
+ INFO(" %08x", bits[FILE_GPR][i]);
+ INFO("\n");
+}
+
+bool
+RegisterSet::assign(Value **def, int nr)
+{
+ DataFile f = def[0]->reg.file;
+ int n = nr;
+ if (n == 3)
+ n = 4;
+ int s = (n * def[0]->reg.size) >> unit[f];
+ uint32_t m = (1 << s) - 1;
+
+ int id = last[f] + 1;
+ int i;
+
+ for (i = 0; (i * 32) < last[f]; ++i) {
+ if (bits[f][i] == 0xffffffff)
+ continue;
+
+ for (id = 0; id < 32; id += s)
+ if (!(bits[f][i] & (m << id)))
+ break;
+ if (id < 32)
+ break;
+ }
+ id += i * 32;
+ if (id > last[f])
+ return false;
+
+ bits[f][id / 32] |= m << (id % 32);
+
+ if (id + (s - 1) > fill[f])
+ fill[f] = id + (s - 1);
+
+ for (i = 0; i < nr; ++i, ++id)
+ if (!def[i]->livei.isEmpty()) // XXX: really increased id if empty ?
+ def[i]->reg.data.id = id;
+ return true;
+}
+
+void
+RegisterSet::occupy(const Value *val)
+{
+ int id = val->reg.data.id;
+ if (id < 0)
+ return;
+ unsigned int f = val->reg.file;
+
+ uint32_t m = (1 << (val->reg.size >> unit[f])) - 1;
+
+ INFO_DBG(0, REG_ALLOC, "reg occupy: %u[%i] %x\n", f, id, m);
+
+ bits[f][id / 32] |= m << (id % 32);
+
+ if (fill[f] < id)
+ fill[f] = id;
+}
+
+void
+RegisterSet::release(const Value *val)
+{
+ int id = val->reg.data.id;
+ if (id < 0)
+ return;
+ unsigned int f = val->reg.file;
+
+ uint32_t m = (1 << (val->reg.size >> unit[f])) - 1;
+
+ INFO_DBG(0, REG_ALLOC, "reg release: %u[%i] %x\n", f, id, m);
+
+ bits[f][id / 32] &= ~(m << (id % 32));
+}
+
+#define JOIN_MASK_PHI (1 << 0)
+#define JOIN_MASK_UNION (1 << 1)
+#define JOIN_MASK_MOV (1 << 2)
+#define JOIN_MASK_TEX (1 << 3)
+#define JOIN_MASK_CONSTRAINT (1 << 4)
+
+class RegAlloc
+{
+public:
+ RegAlloc(Program *program) : prog(program), sequence(0) { }
+
+ bool exec();
+ bool execFunc();
+
+private:
+ bool coalesceValues(unsigned int mask);
+ bool linearScan();
+ bool allocateConstrainedValues();
+
+private:
+ class PhiMovesPass : public Pass {
+ private:
+ virtual bool visit(BasicBlock *);
+ inline bool needNewElseBlock(BasicBlock *b, BasicBlock *p);
+ };
+
+ class BuildIntervalsPass : public Pass {
+ private:
+ virtual bool visit(BasicBlock *);
+ void collectLiveValues(BasicBlock *);
+ void addLiveRange(Value *, const BasicBlock *, int end);
+ };
+
+ class InsertConstraintsPass : public Pass {
+ public:
+ bool exec(Function *func);
+ private:
+ virtual bool visit(BasicBlock *);
+
+ bool insertConstraintMoves();
+
+ void addHazard(Instruction *i, const ValueRef *src);
+ void textureMask(TexInstruction *);
+ void addConstraint(Instruction *, int s, int n);
+ bool detectConflict(Instruction *, int s);
+
+ DLList constrList;
+ };
+
+ bool buildLiveSets(BasicBlock *);
+ void collectLValues(DLList&, bool assignedOnly);
+
+ void insertOrderedTail(DLList&, Value *);
+ inline Instruction *insnBySerial(int);
+
+private:
+ Program *prog;
+ Function *func;
+
+ // instructions in control flow / chronological order
+ ArrayList insns;
+
+ int sequence; // for manual passes through CFG
+};
+
+Instruction *
+RegAlloc::insnBySerial(int serial)
+{
+ return reinterpret_cast<Instruction *>(insns.get(serial));
+}
+
+void
+RegAlloc::BuildIntervalsPass::addLiveRange(Value *val,
+ const BasicBlock *bb,
+ int end)
+{
+ Instruction *insn = val->getUniqueInsn();
+
+ if (!insn)
+ return;
+ assert(bb->getFirst()->serial <= bb->getExit()->serial);
+ assert(bb->getExit()->serial + 1 >= end);
+
+ int begin = insn->serial;
+ if (begin < bb->getEntry()->serial || begin > bb->getExit()->serial)
+ begin = bb->getEntry()->serial;
+
+ INFO_DBG(prog->dbgFlags, REG_ALLOC, "%%%i <- live range [%i(%i), %i)\n",
+ val->id, begin, insn->serial, end);
+
+ if (begin != end) // empty ranges are only added as hazards for fixed regs
+ val->livei.extend(begin, end);
+}
+
+bool
+RegAlloc::PhiMovesPass::needNewElseBlock(BasicBlock *b, BasicBlock *p)
+{
+ if (b->cfg.incidentCount() <= 1)
+ return false;
+
+ int n = 0;
+ for (Graph::EdgeIterator ei = p->cfg.outgoing(); !ei.end(); ei.next())
+ if (ei.getType() == Graph::Edge::TREE ||
+ ei.getType() == Graph::Edge::FORWARD)
+ ++n;
+ return (n == 2);
+}
+
+// For each operand of each PHI in b, generate a new value by inserting a MOV
+// at the end of the block it is coming from and replace the operand with its
+// result. This eliminates liveness conflicts and enables us to let values be
+// copied to the right register if such a conflict exists nonetheless.
+//
+// These MOVs are also crucial in making sure the live intervals of phi srces
+// are extended until the end of the loop, since they are not included in the
+// live-in sets.
+bool
+RegAlloc::PhiMovesPass::visit(BasicBlock *bb)
+{
+ Instruction *phi, *mov;
+ BasicBlock *pb, *pn;
+
+ for (Graph::EdgeIterator ei = bb->cfg.incident(); !ei.end(); ei.next()) {
+ pb = pn = BasicBlock::get(ei.getNode());
+ assert(pb);
+
+ if (needNewElseBlock(bb, pb)) {
+ pn = new BasicBlock(func);
+
+ // deletes an edge, iterator is invalid after this:
+ pb->cfg.detach(&bb->cfg);
+ pb->cfg.attach(&pn->cfg, Graph::Edge::TREE);
+ pn->cfg.attach(&bb->cfg, Graph::Edge::FORWARD); // XXX: check order !
+
+ assert(pb->getExit()->op != OP_CALL);
+ if (pb->getExit()->asFlow()->target.bb == bb)
+ pb->getExit()->asFlow()->target.bb = pn;
+ break;
+ }
+ }
+
+ // insert MOVs (phi->src[j] should stem from j-th in-BB)
+ int j = 0;
+ for (Graph::EdgeIterator ei = bb->cfg.incident(); !ei.end(); ei.next()) {
+ pb = BasicBlock::get(ei.getNode());
+ if (!pb->isTerminated())
+ pb->insertTail(new_FlowInstruction(func, OP_BRA, bb));
+
+ for (phi = bb->getPhi(); phi && phi->op == OP_PHI; phi = phi->next) {
+ mov = new_Instruction(func, OP_MOV, TYPE_U32);
+
+ mov->setSrc(0, phi->getSrc(j));
+ mov->setDef(0, new_LValue(func, phi->getDef(0)->asLValue()));
+ phi->setSrc(j, mov->getDef(0));
+
+ pb->insertBefore(pb->getExit(), mov);
+ }
+ ++j;
+ }
+
+ return true;
+}
+
+// Build the set of live-in variables of bb.
+bool
+RegAlloc::buildLiveSets(BasicBlock *bb)
+{
+ BasicBlock *bn;
+ Instruction *i;
+ unsigned int s, d;
+
+ INFO_DBG(prog->dbgFlags, REG_ALLOC, "buildLiveSets(BB:%i)\n", bb->getId());
+
+ bb->liveSet.allocate(func->allLValues.getSize(), false);
+
+ int n = 0;
+ for (Graph::EdgeIterator ei = bb->cfg.outgoing(); !ei.end(); ei.next()) {
+ bn = BasicBlock::get(ei.getNode());
+ if (bn == bb)
+ continue;
+ if (bn->cfg.visit(sequence))
+ if (!buildLiveSets(bn))
+ return false;
+ if (n++ == 0)
+ bb->liveSet = bn->liveSet;
+ else
+ bb->liveSet |= bn->liveSet;
+ }
+ if (!n && !bb->liveSet.marker)
+ bb->liveSet.fill(0);
+ bb->liveSet.marker = true;
+
+ if (prog->dbgFlags & NV50_IR_DEBUG_REG_ALLOC) {
+ INFO("BB:%i live set of out blocks:\n", bb->getId());
+ bb->liveSet.print();
+ }
+
+ // if (!bb->getEntry())
+ // return true;
+
+ for (i = bb->getExit(); i && i != bb->getEntry()->prev; i = i->prev) {
+ for (d = 0; i->defExists(d); ++d)
+ bb->liveSet.clr(i->getDef(d)->id);
+ for (s = 0; i->srcExists(s); ++s)
+ if (i->getSrc(s)->asLValue())
+ bb->liveSet.set(i->getSrc(s)->id);
+ }
+ for (i = bb->getPhi(); i && i->op == OP_PHI; i = i->next)
+ bb->liveSet.clr(i->getDef(0)->id);
+
+ if (prog->dbgFlags & NV50_IR_DEBUG_REG_ALLOC) {
+ INFO("BB:%i live set after propagation:\n", bb->getId());
+ bb->liveSet.print();
+ }
+
+ return true;
+}
+
+void
+RegAlloc::BuildIntervalsPass::collectLiveValues(BasicBlock *bb)
+{
+ BasicBlock *bbA = NULL, *bbB = NULL;
+
+ assert(bb->cfg.incidentCount() || bb->liveSet.popCount() == 0);
+
+ if (bb->cfg.outgoingCount()) {
+ // trickery to save a loop of OR'ing liveSets
+ // aliasing works fine with BitSet::setOr
+ for (Graph::EdgeIterator ei = bb->cfg.outgoing(); !ei.end(); ei.next()) {
+ if (ei.getType() == Graph::Edge::DUMMY)
+ continue;
+ if (bbA) {
+ bb->liveSet.setOr(&bbA->liveSet, &bbB->liveSet);
+ bbA = bb;
+ } else {
+ bbA = bbB;
+ }
+ bbB = BasicBlock::get(ei.getNode());
+ }
+ bb->liveSet.setOr(&bbB->liveSet, bbA ? &bbA->liveSet : NULL);
+ } else
+ if (bb->cfg.incidentCount()) {
+ bb->liveSet.fill(0);
+ }
+}
+
+bool
+RegAlloc::BuildIntervalsPass::visit(BasicBlock *bb)
+{
+ collectLiveValues(bb);
+
+ INFO_DBG(prog->dbgFlags, REG_ALLOC, "BuildIntervals(BB:%i)\n", bb->getId());
+
+ // go through out blocks and delete phi sources that do not originate from
+ // the current block from the live set
+ for (Graph::EdgeIterator ei = bb->cfg.outgoing(); !ei.end(); ei.next()) {
+ BasicBlock *out = BasicBlock::get(ei.getNode());
+
+ for (Instruction *i = out->getPhi(); i && i->op == OP_PHI; i = i->next) {
+ bb->liveSet.clr(i->getDef(0)->id);
+
+ for (int s = 0; s < NV50_IR_MAX_SRCS && i->src[s].exists(); ++s) {
+ assert(i->src[s].getInsn());
+ if (i->getSrc(s)->getUniqueInsn()->bb == bb) // XXX: reachableBy ?
+ bb->liveSet.set(i->getSrc(s)->id);
+ else
+ bb->liveSet.clr(i->getSrc(s)->id);
+ }
+ }
+ }
+
+ // remaining live-outs are live until end
+ if (bb->getExit()) {
+ for (unsigned int j = 0; j < bb->liveSet.getSize(); ++j)
+ if (bb->liveSet.test(j))
+ addLiveRange(func->getLValue(j), bb, bb->getExit()->serial + 1);
+ }
+
+ for (Instruction *i = bb->getExit(); i && i->op != OP_PHI; i = i->prev) {
+ for (int d = 0; i->defExists(d); ++d) {
+ bb->liveSet.clr(i->getDef(d)->id);
+ if (i->getDef(d)->reg.data.id >= 0) // add hazard for fixed regs
+ i->getDef(d)->livei.extend(i->serial, i->serial);
+ }
+
+ for (int s = 0; i->srcExists(s); ++s) {
+ if (!i->getSrc(s)->asLValue())
+ continue;
+ if (!bb->liveSet.test(i->getSrc(s)->id)) {
+ bb->liveSet.set(i->getSrc(s)->id);
+ addLiveRange(i->getSrc(s), bb, i->serial);
+ }
+ }
+ }
+
+ return true;
+}
+
+bool
+RegAlloc::coalesceValues(unsigned int mask)
+{
+ int c, n;
+
+ for (n = 0; n < insns.getSize(); ++n) {
+ Instruction *i;
+ Instruction *insn = insnBySerial(n);
+
+ switch (insn->op) {
+ case OP_PHI:
+ if (!(mask & JOIN_MASK_PHI))
+ break;
+ for (c = 0; insn->srcExists(c); ++c)
+ if (!insn->getDef(0)->coalesce(insn->getSrc(c), false)) {
+ ERROR("failed to coalesce phi operands\n");
+ return false;
+ }
+ break;
+ case OP_UNION:
+ if (!(mask & JOIN_MASK_UNION))
+ break;
+ for (c = 0; insn->srcExists(c); ++c)
+ insn->getDef(0)->coalesce(insn->getSrc(c), true);
+ break;
+ case OP_CONSTRAINT:
+ if (!(mask & JOIN_MASK_CONSTRAINT))
+ break;
+ for (c = 0; c < 4 && insn->srcExists(c); ++c)
+ insn->getDef(c)->coalesce(insn->getSrc(c), true);
+ break;
+ case OP_MOV:
+ if (!(mask & JOIN_MASK_MOV))
+ break;
+ i = insn->getSrc(0)->getUniqueInsn();
+ if (i && !i->constrainedDefs())
+ insn->getDef(0)->coalesce(insn->getSrc(0), false);
+ break;
+ case OP_TEX:
+ case OP_TXB:
+ case OP_TXL:
+ case OP_TXF:
+ case OP_TXQ:
+ case OP_TXD:
+ case OP_TXG:
+ case OP_TEXCSAA:
+ if (!(mask & JOIN_MASK_TEX))
+ break;
+ for (c = 0; c < 4 && insn->srcExists(c); ++c)
+ insn->getDef(c)->coalesce(insn->getSrc(c), true);
+ break;
+ default:
+ break;
+ }
+ }
+ return true;
+}
+
+void
+RegAlloc::insertOrderedTail(DLList &list, Value *val)
+{
+ // we insert the live intervals in order, so this should be short
+ DLList::Iterator iter = list.revIterator();
+ const int begin = val->livei.begin();
+ for (; !iter.end(); iter.next()) {
+ if (reinterpret_cast<Value *>(iter.get())->livei.begin() <= begin)
+ break;
+ }
+ iter.insert(val);
+}
+
+static void
+checkList(DLList &list)
+{
+ Value *prev = NULL;
+ Value *next = NULL;
+
+ for (DLList::Iterator iter = list.iterator(); !iter.end(); iter.next()) {
+ next = Value::get(iter);
+ assert(next);
+ if (prev) {
+ assert(prev->livei.begin() <= next->livei.begin());
+ }
+ assert(next->join == next);
+ prev = next;
+ }
+}
+
+void
+RegAlloc::collectLValues(DLList &list, bool assignedOnly)
+{
+ for (int n = 0; n < insns.getSize(); ++n) {
+ Instruction *i = insnBySerial(n);
+
+ for (int d = 0; i->defExists(d); ++d)
+ if (!i->getDef(d)->livei.isEmpty())
+ if (!assignedOnly || i->getDef(d)->reg.data.id >= 0)
+ insertOrderedTail(list, i->getDef(d));
+ }
+ checkList(list);
+}
+
+bool
+RegAlloc::allocateConstrainedValues()
+{
+ Value *defs[4];
+ RegisterSet regSet[4];
+ DLList regVals;
+
+ INFO_DBG(prog->dbgFlags, REG_ALLOC, "RA: allocating constrained values\n");
+
+ collectLValues(regVals, true);
+
+ for (int c = 0; c < 4; ++c)
+ regSet[c].init(prog->getTarget());
+
+ for (int n = 0; n < insns.getSize(); ++n) {
+ Instruction *i = insnBySerial(n);
+
+ const int vecSize = i->defCount(0xf);
+ if (vecSize < 2)
+ continue;
+ assert(vecSize <= 4);
+
+ for (int c = 0; c < vecSize; ++c)
+ defs[c] = i->def[c].rep();
+
+ if (defs[0]->reg.data.id >= 0) {
+ for (int c = 1; c < vecSize; ++c) {
+ assert(defs[c]->reg.data.id >= 0);
+ }
+ continue;
+ }
+
+ for (int c = 0; c < vecSize; ++c) {
+ uint32_t mask;
+ regSet[c].reset();
+
+ for (DLList::Iterator it = regVals.iterator(); !it.end(); it.next()) {
+ Value *rVal = Value::get(it);
+ if (rVal->reg.data.id >= 0 && rVal->livei.overlaps(defs[c]->livei))
+ regSet[c].occupy(rVal);
+ }
+ mask = 0x11111111;
+ if (vecSize == 2) // granularity is 2 instead of 4
+ mask |= 0x11111111 << 2;
+ regSet[c].periodicMask(defs[0]->reg.file, 0, ~(mask << c));
+
+ if (!defs[c]->livei.isEmpty())
+ insertOrderedTail(regVals, defs[c]);
+ }
+ for (int c = 1; c < vecSize; ++c)
+ regSet[0].intersect(defs[0]->reg.file, &regSet[c]);
+
+ if (!regSet[0].assign(&defs[0], vecSize)) // TODO: spilling
+ return false;
+ }
+ for (int c = 0; c < 4; c += 2)
+ if (regSet[c].getMaxAssigned(FILE_GPR) > prog->maxGPR)
+ prog->maxGPR = regSet[c].getMaxAssigned(FILE_GPR);
+ return true;
+}
+
+bool
+RegAlloc::linearScan()
+{
+ Value *cur, *val;
+ DLList unhandled, active, inactive;
+ RegisterSet f(prog->getTarget()), free(prog->getTarget());
+
+ INFO_DBG(prog->dbgFlags, REG_ALLOC, "RA: linear scan\n");
+
+ collectLValues(unhandled, false);
+
+ for (DLList::Iterator cI = unhandled.iterator(); !cI.end();) {
+ cur = Value::get(cI);
+ cI.erase();
+
+ for (DLList::Iterator aI = active.iterator(); !aI.end();) {
+ val = Value::get(aI);
+ if (val->livei.end() <= cur->livei.begin()) {
+ free.release(val);
+ aI.erase();
+ } else
+ if (!val->livei.contains(cur->livei.begin())) {
+ free.release(val);
+ aI.moveToList(inactive);
+ } else {
+ aI.next();
+ }
+ }
+
+ for (DLList::Iterator iI = inactive.iterator(); !iI.end();) {
+ val = Value::get(iI);
+ if (val->livei.end() <= cur->livei.begin()) {
+ iI.erase();
+ } else
+ if (val->livei.contains(cur->livei.begin())) {
+ free.occupy(val);
+ iI.moveToList(active);
+ } else {
+ iI.next();
+ }
+ }
+ f = free;
+
+ for (DLList::Iterator iI = inactive.iterator(); !iI.end(); iI.next()) {
+ val = Value::get(iI);
+ if (val->livei.overlaps(cur->livei))
+ f.occupy(val);
+ }
+
+ for (DLList::Iterator uI = unhandled.iterator(); !uI.end(); uI.next()) {
+ val = Value::get(uI);
+ if (val->reg.data.id >= 0 && val->livei.overlaps(cur->livei))
+ f.occupy(val);
+ }
+
+ if (cur->reg.data.id < 0) {
+ bool spill = !f.assign(&cur, 1);
+ if (spill) {
+ ERROR("out of registers of file %u\n", cur->reg.file);
+ abort();
+ }
+ }
+ free.occupy(cur);
+ active.insert(cur);
+ }
+
+ if (f.getMaxAssigned(FILE_GPR) > prog->maxGPR)
+ prog->maxGPR = f.getMaxAssigned(FILE_GPR);
+ if (free.getMaxAssigned(FILE_GPR) > prog->maxGPR)
+ prog->maxGPR = free.getMaxAssigned(FILE_GPR);
+ return true;
+}
+
+bool
+RegAlloc::exec()
+{
+ for (ArrayList::Iterator fi = prog->allFuncs.iterator();
+ !fi.end(); fi.next()) {
+ func = reinterpret_cast<Function *>(fi.get());
+ if (!execFunc())
+ return false;
+ }
+ return true;
+}
+
+bool
+RegAlloc::execFunc()
+{
+ InsertConstraintsPass insertConstr;
+ PhiMovesPass insertMoves;
+ BuildIntervalsPass buildIntervals;
+
+ unsigned int i;
+ bool ret;
+
+ ret = insertConstr.exec(func);
+ if (!ret)
+ goto out;
+
+ ret = insertMoves.run(func);
+ if (!ret)
+ goto out;
+
+ for (sequence = func->cfg.nextSequence(), i = 0;
+ ret && i <= func->loopNestingBound;
+ sequence = func->cfg.nextSequence(), ++i)
+ ret = buildLiveSets(BasicBlock::get(func->cfg.getRoot()));
+ if (!ret)
+ goto out;
+
+ func->orderInstructions(this->insns);
+
+ ret = buildIntervals.run(func);
+ if (!ret)
+ goto out;
+
+ ret = coalesceValues(JOIN_MASK_PHI);
+ if (!ret)
+ goto out;
+ switch (prog->getTarget()->getChipset() & 0xf0) {
+ case 0x50:
+ ret = coalesceValues(JOIN_MASK_UNION | JOIN_MASK_TEX);
+ break;
+ case 0xc0:
+ ret = coalesceValues(JOIN_MASK_UNION | JOIN_MASK_CONSTRAINT);
+ break;
+ default:
+ break;
+ }
+ if (!ret)
+ goto out;
+ ret = coalesceValues(JOIN_MASK_MOV);
+ if (!ret)
+ goto out;
+
+ if (prog->dbgFlags & NV50_IR_DEBUG_REG_ALLOC) {
+ func->print();
+ func->printLiveIntervals();
+ }
+
+ ret = allocateConstrainedValues() && linearScan();
+ if (!ret)
+ goto out;
+
+out:
+ // TODO: should probably call destructor on LValues later instead
+ for (ArrayList::Iterator it = func->allLValues.iterator();
+ !it.end(); it.next())
+ reinterpret_cast<LValue *>(it.get())->livei.clear();
+
+ return ret;
+}
+
+bool Program::registerAllocation()
+{
+ RegAlloc ra(this);
+ return ra.exec();
+}
+
+bool
+RegAlloc::InsertConstraintsPass::exec(Function *ir)
+{
+ constrList.clear();
+
+ bool ret = run(ir, true, true);
+ if (ret)
+ ret = insertConstraintMoves();
+ return ret;
+}
+
+// TODO: make part of texture insn
+void
+RegAlloc::InsertConstraintsPass::textureMask(TexInstruction *tex)
+{
+ Value *def[4];
+ int c, k, d;
+ uint8_t mask = 0;
+
+ for (d = 0, k = 0, c = 0; c < 4; ++c) {
+ if (!(tex->tex.mask & (1 << c)))
+ continue;
+ if (tex->getDef(k)->refCount()) {
+ mask |= 1 << c;
+ def[d++] = tex->getDef(k);
+ }
+ ++k;
+ }
+ tex->tex.mask = mask;
+
+#if 0 // reorder or set the unused ones NULL ?
+ for (c = 0; c < 4; ++c)
+ if (!(tex->tex.mask & (1 << c)))
+ def[d++] = tex->getDef(c);
+#endif
+ for (c = 0; c < d; ++c)
+ tex->setDef(c, def[c]);
+#if 1
+ for (; c < 4; ++c)
+ tex->setDef(c, NULL);
+#endif
+}
+
+bool
+RegAlloc::InsertConstraintsPass::detectConflict(Instruction *cst, int s)
+{
+ // current register allocation can't handle it if a value participates in
+ // multiple constraints
+ for (ValueRef::Iterator it = cst->src[s].iterator(); !it.end(); it.next()) {
+ Instruction *insn = it.get()->getInsn();
+ if (insn != cst)
+ return true;
+ }
+
+ // can start at s + 1 because detectConflict is called on all sources
+ for (int c = s + 1; cst->srcExists(c); ++c)
+ if (cst->getSrc(c) == cst->getSrc(s))
+ return true;
+
+ Instruction *defi = cst->getSrc(s)->getInsn();
+
+ return (!defi || defi->constrainedDefs());
+}
+
+void
+RegAlloc::InsertConstraintsPass::addConstraint(Instruction *i, int s, int n)
+{
+ Instruction *cst;
+ int d;
+
+ // first, look for an existing identical constraint op
+ for (DLList::Iterator it = constrList.iterator(); !it.end(); it.next()) {
+ cst = reinterpret_cast<Instruction *>(it.get());
+ if (!i->bb->dominatedBy(cst->bb))
+ break;
+ for (d = 0; d < n; ++d)
+ if (cst->getSrc(d) != i->getSrc(d + s))
+ break;
+ if (d >= n) {
+ for (d = 0; d < n; ++d, ++s)
+ i->setSrc(s, cst->getDef(d));
+ return;
+ }
+ }
+ cst = new_Instruction(func, OP_CONSTRAINT, i->dType);
+
+ for (d = 0; d < n; ++s, ++d) {
+ cst->setDef(d, new_LValue(func, FILE_GPR));
+ cst->setSrc(d, i->getSrc(s));
+ i->setSrc(s, cst->getDef(d));
+ }
+ i->bb->insertBefore(i, cst);
+
+ constrList.insert(cst);
+}
+
+// Add a dummy use of the pointer source of >= 8 byte loads after the load
+// to prevent it from being assigned a register which overlapping the load's
+// destination, which would produce random corruptions.
+void
+RegAlloc::InsertConstraintsPass::addHazard(Instruction *i, const ValueRef *src)
+{
+ Instruction *hzd = new_Instruction(func, OP_NOP, TYPE_NONE);
+ hzd->setSrc(0, src->get());
+ i->bb->insertAfter(i, hzd);
+
+}
+
+// Insert constraint markers for instructions whose multiple sources must be
+// located in consecutive registers.
+bool
+RegAlloc::InsertConstraintsPass::visit(BasicBlock *bb)
+{
+ TexInstruction *tex;
+ Instruction *next;
+ int s, n, size;
+
+ for (Instruction *i = bb->getEntry(); i; i = next) {
+ next = i->next;
+
+ if ((tex = i->asTex())) {
+ textureMask(tex);
+
+ // FIXME: this is target specific
+ if (tex->op == OP_TXQ) {
+ s = tex->srcCount(0xff);
+ n = 0;
+ } else {
+ s = tex->tex.target.getArgCount();
+ if (!tex->tex.target.isArray() &&
+ (tex->tex.rIndirectSrc >= 0 || tex->tex.sIndirectSrc >= 0))
+ ++s;
+ n = tex->srcCount(0xff) - s;
+ assert(n <= 4);
+ }
+
+ if (s > 1)
+ addConstraint(i, 0, s);
+ if (n > 1)
+ addConstraint(i, s, n);
+ } else
+ if (i->op == OP_EXPORT || i->op == OP_STORE) {
+ for (size = typeSizeof(i->dType), s = 1; size > 0; ++s) {
+ assert(i->srcExists(s));
+ size -= i->getSrc(s)->reg.size;
+ }
+ if ((s - 1) > 1)
+ addConstraint(i, 1, s - 1);
+ } else
+ if (i->op == OP_LOAD) {
+ if (i->src[0].isIndirect(0) && typeSizeof(i->dType) >= 8)
+ addHazard(i, i->src[0].getIndirect(0));
+ }
+ }
+ return true;
+}
+
+// Insert extra moves so that, if multiple register constraints on a value are
+// in conflict, these conflicts can be resolved.
+bool
+RegAlloc::InsertConstraintsPass::insertConstraintMoves()
+{
+ for (DLList::Iterator it = constrList.iterator(); !it.end(); it.next()) {
+ Instruction *cst = reinterpret_cast<Instruction *>(it.get());
+
+ for (int s = 0; cst->srcExists(s); ++s) {
+ if (!detectConflict(cst, s))
+ continue;
+ Instruction *mov = new_Instruction(func, OP_MOV,
+ typeOfSize(cst->src[s].getSize()));
+ mov->setSrc(0, cst->getSrc(s));
+ mov->setDef(0, new_LValue(func, FILE_GPR));
+ cst->setSrc(s, mov->getDef(0));
+
+ cst->bb->insertBefore(cst, mov);
+ }
+ }
+ return true;
+}
+
+} // namespace nv50_ir
diff --git a/src/gallium/drivers/nv50/codegen/nv50_ir_ssa.cpp b/src/gallium/drivers/nv50/codegen/nv50_ir_ssa.cpp
new file mode 100644
index 00000000000..841163b0ac9
--- /dev/null
+++ b/src/gallium/drivers/nv50/codegen/nv50_ir_ssa.cpp
@@ -0,0 +1,463 @@
+
+#include "nv50_ir.h"
+#include "nv50_ir_target.h"
+
+namespace nv50_ir {
+
+// Converts nv50 IR generated from TGSI to SSA form.
+
+// DominatorTree implements an algorithm for finding immediate dominators,
+// as described by T. Lengauer & R. Tarjan.
+class DominatorTree : public Graph
+{
+public:
+ DominatorTree(Graph *cfg);
+ ~DominatorTree() { }
+
+ bool dominates(BasicBlock *, BasicBlock *);
+
+ void findDominanceFrontiers();
+
+private:
+ void build();
+ void buildDFS(Node *);
+
+ void squash(int);
+ inline void link(int, int);
+ inline int eval(int);
+
+ void debugPrint();
+
+ Graph *cfg;
+
+ Node **vert;
+ int *data;
+ const int count;
+
+ #define SEMI(i) (data[(i) + 0 * count])
+ #define ANCESTOR(i) (data[(i) + 1 * count])
+ #define PARENT(i) (data[(i) + 2 * count])
+ #define LABEL(i) (data[(i) + 3 * count])
+ #define DOM(i) (data[(i) + 4 * count])
+};
+
+void DominatorTree::debugPrint()
+{
+ for (int i = 0; i < count; ++i) {
+ INFO("SEMI(%i) = %i\n", i, SEMI(i));
+ INFO("ANCESTOR(%i) = %i\n", i, ANCESTOR(i));
+ INFO("PARENT(%i) = %i\n", i, PARENT(i));
+ INFO("LABEL(%i) = %i\n", i, LABEL(i));
+ INFO("DOM(%i) = %i\n", i, DOM(i));
+ }
+}
+
+DominatorTree::DominatorTree(Graph *cfgraph) : cfg(cfgraph),
+ count(cfg->getSize())
+{
+ Iterator *iter;
+ int i;
+
+ vert = new Node * [count];
+ data = new int[5 * count];
+
+ for (i = 0, iter = cfg->iteratorDFS(true); !iter->end(); iter->next(), ++i) {
+ vert[i] = reinterpret_cast<Node *>(iter->get());
+ vert[i]->tag = i;
+ LABEL(i) = i;
+ SEMI(i) = ANCESTOR(i) = -1;
+ }
+ cfg->putIterator(iter);
+
+ build();
+
+ delete[] vert;
+ delete[] data;
+}
+
+void DominatorTree::buildDFS(Graph::Node *node)
+{
+ SEMI(node->tag) = node->tag;
+
+ for (Graph::EdgeIterator ei = node->outgoing(); !ei.end(); ei.next()) {
+ if (SEMI(ei.getNode()->tag) < 0) {
+ buildDFS(ei.getNode());
+ PARENT(ei.getNode()->tag) = node->tag;
+ }
+ }
+}
+
+void DominatorTree::squash(int v)
+{
+ if (ANCESTOR(ANCESTOR(v)) >= 0) {
+ squash(ANCESTOR(v));
+
+ if (SEMI(LABEL(ANCESTOR(v))) < SEMI(LABEL(v)))
+ LABEL(v) = LABEL(ANCESTOR(v));
+ ANCESTOR(v) = ANCESTOR(ANCESTOR(v));
+ }
+}
+
+int DominatorTree::eval(int v)
+{
+ if (ANCESTOR(v) < 0)
+ return v;
+ squash(v);
+ return LABEL(v);
+}
+
+void DominatorTree::link(int v, int w)
+{
+ ANCESTOR(w) = v;
+}
+
+void DominatorTree::build()
+{
+ DLList *bucket = new DLList[count];
+ Node *nv, *nw;
+ int p, u, v, w;
+
+ buildDFS(cfg->getRoot());
+
+ for (w = count - 1; w >= 1; --w) {
+ nw = vert[w];
+ assert(nw->tag == w);
+ for (Graph::EdgeIterator ei = nw->incident(); !ei.end(); ei.next()) {
+ nv = ei.getNode();
+ v = nv->tag;
+ u = eval(v);
+ if (SEMI(u) < SEMI(w))
+ SEMI(w) = SEMI(u);
+ }
+ p = PARENT(w);
+ bucket[SEMI(w)].insert(nw);
+ link(p, w);
+
+ for (DLList::Iterator it = bucket[p].iterator(); !it.end(); it.erase()) {
+ v = reinterpret_cast<Node *>(it.get())->tag;
+ u = eval(v);
+ DOM(v) = (SEMI(u) < SEMI(v)) ? u : p;
+ }
+ }
+ for (w = 1; w < count; ++w) {
+ if (DOM(w) != SEMI(w))
+ DOM(w) = DOM(DOM(w));
+ }
+ DOM(0) = 0;
+
+ insert(&BasicBlock::get(cfg->getRoot())->dom);
+ do {
+ p = 0;
+ for (v = 1; v < count; ++v) {
+ nw = &BasicBlock::get(vert[DOM(v)])->dom;;
+ nv = &BasicBlock::get(vert[v])->dom;
+ if (nw->getGraph() && !nv->getGraph()) {
+ ++p;
+ nw->attach(nv, Graph::Edge::TREE);
+ }
+ }
+ } while (p);
+
+ delete[] bucket;
+}
+
+#undef SEMI
+#undef ANCESTOR
+#undef PARENT
+#undef LABEL
+#undef DOM
+
+void DominatorTree::findDominanceFrontiers()
+{
+ Iterator *dtIter;
+ BasicBlock *bb;
+
+ for (dtIter = this->iteratorDFS(false); !dtIter->end(); dtIter->next()) {
+ EdgeIterator succIter, chldIter;
+
+ bb = BasicBlock::get(reinterpret_cast<Node *>(dtIter->get()));
+ bb->getDF().clear();
+
+ for (succIter = bb->cfg.outgoing(); !succIter.end(); succIter.next()) {
+ BasicBlock *dfLocal = BasicBlock::get(succIter.getNode());
+ if (dfLocal->idom() != bb)
+ bb->getDF().insert(dfLocal);
+ }
+
+ for (chldIter = bb->dom.outgoing(); !chldIter.end(); chldIter.next()) {
+ BasicBlock *cb = BasicBlock::get(chldIter.getNode());
+
+ DLList::Iterator dfIter = cb->getDF().iterator();
+ for (; !dfIter.end(); dfIter.next()) {
+ BasicBlock *dfUp = BasicBlock::get(dfIter);
+ if (dfUp->idom() != bb)
+ bb->getDF().insert(dfUp);
+ }
+ }
+ }
+ this->putIterator(dtIter);
+}
+
+// liveIn(bb) = usedBeforeAssigned(bb) U (liveOut(bb) - assigned(bb))
+void
+Function::buildLiveSetsPreSSA(BasicBlock *bb, const int seq)
+{
+ BitSet usedBeforeAssigned(allLValues.getSize(), true);
+ BitSet assigned(allLValues.getSize(), true);
+
+ bb->liveSet.allocate(allLValues.getSize(), false);
+
+ int n = 0;
+ for (Graph::EdgeIterator ei = bb->cfg.outgoing(); !ei.end(); ei.next()) {
+ BasicBlock *out = BasicBlock::get(ei.getNode());
+ if (out == bb)
+ continue;
+ if (out->cfg.visit(seq))
+ buildLiveSetsPreSSA(out, seq);
+ if (!n++)
+ bb->liveSet = out->liveSet;
+ else
+ bb->liveSet |= out->liveSet;
+ }
+ if (!n && !bb->liveSet.marker)
+ bb->liveSet.fill(0);
+ bb->liveSet.marker = true;
+
+ for (Instruction *i = bb->getEntry(); i; i = i->next) {
+ for (int s = 0; i->srcExists(s); ++s)
+ if (i->getSrc(s)->asLValue() && !assigned.test(i->getSrc(s)->id))
+ usedBeforeAssigned.set(i->getSrc(s)->id);
+ for (int d = 0; i->defExists(d); ++d)
+ assigned.set(i->getDef(d)->id);
+ }
+
+ bb->liveSet.andNot(assigned);
+ bb->liveSet |= usedBeforeAssigned;
+}
+
+class RenamePass
+{
+public:
+ RenamePass(Function *);
+ ~RenamePass();
+
+ bool run();
+ void search(BasicBlock *);
+
+ inline LValue *getStackTop(Value *);
+
+private:
+ Stack *stack;
+ Function *func;
+ Program *prog;
+ Instruction *undef;
+};
+
+bool
+Program::convertToSSA()
+{
+ for (ArrayList::Iterator fi = allFuncs.iterator(); !fi.end(); fi.next()) {
+ Function *fn = reinterpret_cast<Function *>(fi.get());
+ if (!fn->convertToSSA())
+ return false;
+ }
+ return true;
+}
+
+// XXX: add edge from entry to exit ?
+
+// Efficiently Computing Static Single Assignment Form and
+// the Control Dependence Graph,
+// R. Cytron, J. Ferrante, B. K. Rosen, M. N. Wegman, F. K. Zadeck
+bool
+Function::convertToSSA()
+{
+ // 0. calculate live in variables (for pruned SSA)
+ int seq = cfg.nextSequence();
+ for (unsigned i = 0; i <= loopNestingBound; seq = cfg.nextSequence(), ++i)
+ buildLiveSetsPreSSA(BasicBlock::get(cfg.getRoot()), seq);
+
+ // reset liveSet marker for use in regalloc
+ for (ArrayList::Iterator bi = allBBlocks.iterator(); !bi.end(); bi.next())
+ reinterpret_cast<BasicBlock *>(bi.get())->liveSet.marker = false;
+
+ // 1. create the dominator tree
+ domTree = new DominatorTree(&cfg);
+ reinterpret_cast<DominatorTree *>(domTree)->findDominanceFrontiers();
+
+ // 2. insert PHI functions
+ DLList workList;
+ LValue *lval;
+ BasicBlock *bb;
+ int var;
+ int iterCount = 0;
+ int *hasAlready = new int[allBBlocks.getSize() * 2];
+ int *work = &hasAlready[allBBlocks.getSize()];
+
+ memset(hasAlready, 0, allBBlocks.getSize() * 2 * sizeof(int));
+
+ // for each variable
+ for (var = 0; var < allLValues.getSize(); ++var) {
+ if (!allLValues.get(var))
+ continue;
+ lval = reinterpret_cast<Value *>(allLValues.get(var))->asLValue();
+ if (!lval || !lval->defs)
+ continue;
+ ++iterCount;
+
+ // TODO: don't add phi functions for values that aren't used outside
+ // the BB they're defined in
+
+ // gather blocks with assignments to lval in workList
+ for (ValueDef::Iterator d = lval->defs->iterator(); !d.end(); d.next()) {
+ bb = d.get()->getInsn()->bb;
+ if (!bb)
+ continue; // instruction likely been removed but not XXX deleted
+
+ if (work[bb->getId()] == iterCount)
+ continue;
+ work[bb->getId()] = iterCount;
+ workList.insert(bb);
+ }
+
+ // for each block in workList, insert a phi for lval in the block's
+ // dominance frontier (if we haven't already done so)
+ for (DLList::Iterator wI = workList.iterator(); !wI.end(); wI.erase()) {
+ bb = BasicBlock::get(wI);
+
+ DLList::Iterator dfIter = bb->getDF().iterator();
+ for (; !dfIter.end(); dfIter.next()) {
+ Instruction *phi;
+ BasicBlock *dfBB = BasicBlock::get(dfIter);
+
+ if (hasAlready[dfBB->getId()] >= iterCount)
+ continue;
+ hasAlready[dfBB->getId()] = iterCount;
+
+ // pruned SSA: don't need a phi if the value is not live-in
+ if (!dfBB->liveSet.test(lval->id))
+ continue;
+
+ // TODO: use dedicated PhiInstruction to lift this limit
+ assert(dfBB->cfg.incidentCount() <= NV50_IR_MAX_SRCS);
+
+ phi = new_Instruction(this, OP_PHI, typeOfSize(lval->reg.size));
+ dfBB->insertTail(phi);
+
+ phi->setDef(0, lval);
+ for (int s = 0; s < dfBB->cfg.incidentCount(); ++s)
+ phi->setSrc(s, lval);
+
+ if (work[dfBB->getId()] < iterCount) {
+ work[dfBB->getId()] = iterCount;
+ wI.insert(dfBB);
+ }
+ }
+ }
+ }
+ delete[] hasAlready;
+
+ RenamePass rename(this);
+ return rename.run();
+}
+
+RenamePass::RenamePass(Function *fn) : func(fn), prog(fn->getProgram())
+{
+ BasicBlock *root = BasicBlock::get(func->cfg.getRoot());
+
+ undef = new_Instruction(func, OP_NOP, TYPE_U32);
+ undef->setDef(0, new_LValue(func, FILE_GPR));
+ root->insertHead(undef);
+
+ stack = new Stack[func->allLValues.getSize()];
+}
+
+RenamePass::~RenamePass()
+{
+ if (stack)
+ delete[] stack;
+}
+
+LValue *
+RenamePass::getStackTop(Value *val)
+{
+ if (!stack[val->id].getSize())
+ return 0;
+ return reinterpret_cast<LValue *>(stack[val->id].peek().u.p);
+}
+
+bool RenamePass::run()
+{
+ if (!stack)
+ return false;
+ search(BasicBlock::get(func->domTree->getRoot()));
+
+ ArrayList::Iterator iter = func->allInsns.iterator();
+ for (; !iter.end(); iter.next()) {
+ Instruction *insn = reinterpret_cast<Instruction *>(iter.get());
+ for (int d = 0; insn->defExists(d); ++d)
+ insn->def[d].restoreDefList();
+ }
+
+ return true;
+}
+
+void RenamePass::search(BasicBlock *bb)
+{
+ LValue *lval;
+ int d, s;
+ const Target *targ = prog->getTarget();
+
+ for (Instruction *stmt = bb->getFirst(); stmt; stmt = stmt->next) {
+ if (stmt->op != OP_PHI) {
+ for (s = 0; stmt->srcExists(s); ++s) {
+ lval = stmt->getSrc(s)->asLValue();
+ if (!lval)
+ continue;
+ lval = getStackTop(lval);
+ if (!lval)
+ lval = static_cast<LValue *>(undef->getDef(0));
+ stmt->setSrc(s, lval);
+ }
+ }
+ for (d = 0; stmt->defExists(d); ++d) {
+ lval = stmt->def[d].get()->asLValue();
+ assert(lval);
+ stmt->def[d].setSSA(
+ new_LValue(func, targ->nativeFile(lval->reg.file)));
+ stmt->def[d].get()->reg.data.id = lval->reg.data.id;
+ stack[lval->id].push(stmt->def[d].get());
+ }
+ }
+
+ for (Graph::EdgeIterator ei = bb->cfg.outgoing(); !ei.end(); ei.next()) {
+ Instruction *phi;
+ int p = 0;
+ BasicBlock *sb = BasicBlock::get(ei.getNode());
+
+ // which predecessor of sb is bb ?
+ for (Graph::EdgeIterator ei = sb->cfg.incident(); !ei.end(); ei.next()) {
+ if (ei.getNode() == &bb->cfg)
+ break;
+ ++p;
+ }
+ assert(p < sb->cfg.incidentCount());
+
+ for (phi = sb->getPhi(); phi && phi->op == OP_PHI; phi = phi->next) {
+ lval = getStackTop(phi->getSrc(p));
+ if (!lval)
+ lval = undef->getDef(0)->asLValue();
+ phi->setSrc(p, lval);
+ }
+ }
+
+ for (Graph::EdgeIterator ei = bb->dom.outgoing(); !ei.end(); ei.next())
+ search(BasicBlock::get(ei.getNode()));
+
+ for (Instruction *stmt = bb->getFirst(); stmt; stmt = stmt->next) {
+ for (d = 0; stmt->defExists(d); ++d)
+ stack[stmt->def[d].preSSA()->id].pop();
+ }
+}
+
+} // namespace nv50_ir
diff --git a/src/gallium/drivers/nv50/codegen/nv50_ir_target.cpp b/src/gallium/drivers/nv50/codegen/nv50_ir_target.cpp
new file mode 100644
index 00000000000..59fb0c19b0b
--- /dev/null
+++ b/src/gallium/drivers/nv50/codegen/nv50_ir_target.cpp
@@ -0,0 +1,304 @@
+
+#include "nv50/codegen/nv50_ir.h"
+#include "nv50/codegen/nv50_ir_target.h"
+
+namespace nv50_ir {
+
+const uint8_t Target::operationSrcNr[OP_LAST + 1] =
+{
+ 0, 0, // NOP, PHI
+ 0, 0, 0, 0, // UNION, SPLIT, MERGE, CONSTRAINT
+ 1, 1, 2, // MOV, LOAD, STORE
+ 2, 2, 2, 2, 2, 3, 3, 3, // ADD, SUB, MUL, DIV, MOD, MAD, FMA, SAD
+ 1, 1, 1, // ABS, NEG, NOT
+ 2, 2, 2, 2, 2, // AND, OR, XOR, SHL, SHR
+ 2, 2, 1, // MAX, MIN, SAT
+ 1, 1, 1, 1, // CEIL, FLOOR, TRUNC, CVT
+ 3, 3, 3, 2, 3, 3, // SET_AND,OR,XOR, SET, SELP, SLCT
+ 1, 1, 1, 1, 1, 1, // RCP, RSQ, LG2, SIN, COS, EX2
+ 1, 1, 1, 1, 1, 2, // EXP, LOG, PRESIN, PREEX2, SQRT, POW
+ 0, 0, 0, 0, 0, // BRA, CALL, RET, CONT, BREAK,
+ 0, 0, 0, // PRERET,CONT,BREAK
+ 0, 0, 0, 0, 0, 0, // BRKPT, JOINAT, JOIN, DISCARD, EXIT, MEMBAR
+ 1, 1, 2, 1, 2, // VFETCH, PFETCH, EXPORT, LINTERP, PINTERP
+ 1, 1, // EMIT, RESTART
+ 1, 1, 1, // TEX, TXB, TXL,
+ 1, 1, 1, 1, 1, // TXF, TXQ, TXD, TXG, TEXCSAA
+ 1, 2, // SULD, SUST
+ 1, 1, // DFDX, DFDY
+ 1, 2, 2, 2, 0, 0, // RDSV, WRSV, PIXLD, QUADOP, QUADON, QUADPOP
+ 2, 3, 2, // POPCNT, INSBF, EXTBF
+ 0
+};
+
+
+extern Target *getTargetNVC0(unsigned int chipset);
+
+Target *Target::create(unsigned int chipset)
+{
+ switch (chipset & 0xf0) {
+ case 0xc0:
+ return getTargetNVC0(chipset);
+ case 0x50:
+ case 0x80:
+ case 0x90:
+ case 0xa0:
+ default:
+ ERROR("unsupported target: NV%x\n", chipset);
+ return 0;
+ }
+}
+
+void Target::destroy(Target *targ)
+{
+ delete targ;
+}
+
+void
+CodeEmitter::setCodeLocation(void *ptr, uint32_t size)
+{
+ code = reinterpret_cast<uint32_t *>(ptr);
+ codeSize = 0;
+ codeSizeLimit = size;
+}
+
+void
+CodeEmitter::printBinary() const
+{
+ uint32_t *bin = code - codeSize / 4;
+ INFO("program binary (%u bytes)", codeSize);
+ for (unsigned int pos = 0; pos < codeSize / 4; ++pos) {
+ if ((pos % 8) == 0)
+ INFO("\n");
+ INFO("%08x ", bin[pos]);
+ }
+ INFO("\n");
+}
+
+void
+CodeEmitter::prepareEmission(Program *prog)
+{
+ for (ArrayList::Iterator fi = prog->allFuncs.iterator();
+ !fi.end(); fi.next()) {
+ Function *func = reinterpret_cast<Function *>(fi.get());
+ func->binPos = prog->binSize;
+ prepareEmission(func);
+ prog->binSize += func->binSize;
+ }
+}
+
+void
+CodeEmitter::prepareEmission(Function *func)
+{
+ func->bbCount = 0;
+ func->bbArray = new BasicBlock * [func->cfg.getSize()];
+
+ BasicBlock::get(func->cfg.getRoot())->binPos = func->binPos;
+
+ Graph::GraphIterator *iter;
+ for (iter = func->cfg.iteratorCFG(); !iter->end(); iter->next())
+ prepareEmission(BasicBlock::get(*iter));
+ func->cfg.putIterator(iter);
+}
+
+void
+CodeEmitter::prepareEmission(BasicBlock *bb)
+{
+ Instruction *i, *next;
+ Function *func = bb->getFunction();
+ int j;
+ unsigned int nShort;
+
+ for (j = func->bbCount - 1; j >= 0 && !func->bbArray[j]->binSize; --j);
+
+ for (; j >= 0; --j) {
+ BasicBlock *in = func->bbArray[j];
+ Instruction *exit = in->getExit();
+
+ if (exit && exit->op == OP_BRA && exit->asFlow()->target.bb == bb) {
+ in->binSize -= 8;
+ func->binSize -= 8;
+
+ for (++j; j < func->bbCount; ++j)
+ func->bbArray[j]->binPos -= 8;
+
+ in->remove(exit);
+ }
+ bb->binPos = in->binPos + in->binSize;
+ if (in->binSize) // no more no-op branches to bb
+ break;
+ }
+ func->bbArray[func->bbCount++] = bb;
+
+ if (!bb->getExit())
+ return;
+
+ // determine encoding size, try to group short instructions
+ nShort = 0;
+ for (i = bb->getEntry(); i; i = next) {
+ next = i->next;
+
+ i->encSize = getMinEncodingSize(i);
+ if (next && i->encSize < 8)
+ ++nShort;
+ else
+ if ((nShort & 1) && next && getMinEncodingSize(next) == 4) {
+ if (i->isCommutationLegal(i->next)) {
+ bb->permuteAdjacent(i, next);
+ next->encSize = 4;
+ next = i;
+ i = i->prev;
+ ++nShort;
+ } else
+ if (i->isCommutationLegal(i->prev) && next->next) {
+ bb->permuteAdjacent(i->prev, i);
+ next->encSize = 4;
+ next = next->next;
+ bb->binSize += 4;
+ ++nShort;
+ } else {
+ i->encSize = 8;
+ i->prev->encSize = 8;
+ bb->binSize += 4;
+ nShort = 0;
+ }
+ } else {
+ i->encSize = 8;
+ if (nShort & 1) {
+ i->prev->encSize = 8;
+ bb->binSize += 4;
+ }
+ nShort = 0;
+ }
+ bb->binSize += i->encSize;
+ }
+
+ if (bb->getExit()->encSize == 4) {
+ assert(nShort);
+ bb->getExit()->encSize = 8;
+ bb->binSize += 4;
+
+ if ((bb->getExit()->prev->encSize == 4) && !(nShort & 1)) {
+ bb->binSize += 8;
+ bb->getExit()->prev->encSize = 8;
+ }
+ }
+ assert(!bb->getEntry() || (bb->getExit() && bb->getExit()->encSize == 8));
+
+ func->binSize += bb->binSize;
+}
+
+bool
+Program::emitBinary(struct nv50_ir_prog_info *info)
+{
+ CodeEmitter *emit = target->getCodeEmitter(progType);
+
+ emit->prepareEmission(this);
+
+ if (dbgFlags & NV50_IR_DEBUG_BASIC)
+ this->print();
+
+ if (!binSize) {
+ code = NULL;
+ return false;
+ }
+ code = reinterpret_cast<uint32_t *>(MALLOC(binSize));
+ if (!code)
+ return false;
+ emit->setCodeLocation(code, binSize);
+
+ for (ArrayList::Iterator fi = allFuncs.iterator(); !fi.end(); fi.next()) {
+ Function *fn = reinterpret_cast<Function *>(fi.get());
+
+ assert(emit->getCodeSize() == fn->binPos);
+
+ for (int b = 0; b < fn->bbCount; ++b)
+ for (Instruction *i = fn->bbArray[b]->getEntry(); i; i = i->next)
+ emit->emitInstruction(i);
+ }
+ info->bin.relocData = emit->getRelocInfo();
+
+ delete emit;
+ return true;
+}
+
+#define RELOC_ALLOC_INCREMENT 8
+
+bool
+CodeEmitter::addReloc(RelocEntry::Type ty, int w, uint32_t data, uint32_t m,
+ int s)
+{
+ unsigned int n = relocInfo ? relocInfo->count : 0;
+
+ if (!(n % RELOC_ALLOC_INCREMENT)) {
+ size_t size = sizeof(RelocInfo) + n * sizeof(RelocEntry);
+ relocInfo = reinterpret_cast<RelocInfo *>(
+ REALLOC(relocInfo, n ? size : 0,
+ size + RELOC_ALLOC_INCREMENT * sizeof(RelocEntry)));
+ if (!relocInfo)
+ return false;
+ }
+ ++relocInfo->count;
+
+ relocInfo->entry[n].data = data;
+ relocInfo->entry[n].mask = m;
+ relocInfo->entry[n].offset = codeSize + w * 4;
+ relocInfo->entry[n].bitPos = s;
+ relocInfo->entry[n].type = ty;
+
+ return true;
+}
+
+void
+RelocEntry::apply(uint32_t *binary, const RelocInfo *info) const
+{
+ uint32_t value = 0;
+
+ switch (type) {
+ case TYPE_CODE: value = info->codePos; break;
+ case TYPE_BUILTIN: value = info->libPos; break;
+ case TYPE_DATA: value = info->dataPos; break;
+ default:
+ assert(0);
+ break;
+ }
+ value += data;
+ value = (bitPos < 0) ? (value >> -bitPos) : (value << bitPos);
+
+ binary[offset / 4] &= ~mask;
+ binary[offset / 4] |= value & mask;
+}
+
+} // namespace nv50_ir
+
+
+#include "nv50/codegen/nv50_ir_driver.h"
+
+extern "C" {
+
+void
+nv50_ir_relocate_code(void *relocData, uint32_t *code,
+ uint32_t codePos,
+ uint32_t libPos,
+ uint32_t dataPos)
+{
+ nv50_ir::RelocInfo *info = reinterpret_cast<nv50_ir::RelocInfo *>(relocData);
+
+ info->codePos = codePos;
+ info->libPos = libPos;
+ info->dataPos = dataPos;
+
+ for (unsigned int i = 0; i < info->count; ++i)
+ info->entry[i].apply(code, info);
+}
+
+void
+nv50_ir_get_target_library(uint32_t chipset,
+ const uint32_t **code, uint32_t *size)
+{
+ nv50_ir::Target *targ = nv50_ir::Target::create(chipset);
+ targ->getBuiltinCode(code, size);
+ nv50_ir::Target::destroy(targ);
+}
+
+}
diff --git a/src/gallium/drivers/nv50/codegen/nv50_ir_target.h b/src/gallium/drivers/nv50/codegen/nv50_ir_target.h
new file mode 100644
index 00000000000..ddde5586890
--- /dev/null
+++ b/src/gallium/drivers/nv50/codegen/nv50_ir_target.h
@@ -0,0 +1,164 @@
+
+#ifndef __NV50_IR_TARGET_H__
+#define __NV50_IR_TARGET_H__
+
+#include "nv50_ir.h"
+
+namespace nv50_ir {
+
+struct RelocInfo;
+
+struct RelocEntry
+{
+ enum Type
+ {
+ TYPE_CODE,
+ TYPE_BUILTIN,
+ TYPE_DATA
+ };
+
+ uint32_t data;
+ uint32_t mask;
+ uint32_t offset;
+ int8_t bitPos;
+ Type type;
+
+ inline void apply(uint32_t *binary, const RelocInfo *info) const;
+};
+
+struct RelocInfo
+{
+ uint32_t codePos;
+ uint32_t libPos;
+ uint32_t dataPos;
+
+ uint32_t count;
+
+ RelocEntry entry[0];
+};
+
+class CodeEmitter
+{
+public:
+ // returns whether the instruction was encodable and written
+ virtual bool emitInstruction(Instruction *) = 0;
+
+ virtual uint32_t getMinEncodingSize(const Instruction *) const = 0;
+
+ void setCodeLocation(void *, uint32_t size);
+ inline void *getCodeLocation() const { return code; }
+ inline uint32_t getCodeSize() const { return codeSize; }
+
+ bool addReloc(RelocEntry::Type, int w, uint32_t data, uint32_t m,
+ int s);
+
+ inline void *getRelocInfo() const { return relocInfo; }
+
+ void prepareEmission(Program *);
+ void prepareEmission(Function *);
+ virtual void prepareEmission(BasicBlock *);
+
+ void printBinary() const;
+
+protected:
+ uint32_t *code;
+ uint32_t codeSize;
+ uint32_t codeSizeLimit;
+
+ RelocInfo *relocInfo;
+};
+
+class Target
+{
+public:
+ static Target *create(uint32_t chipset);
+ static void destroy(Target *);
+
+ // 0x50 and 0x84 to 0xaf for nv50
+ // 0xc0 to 0xdf for nvc0
+ inline uint32_t getChipset() const { return chipset; }
+
+ virtual CodeEmitter *getCodeEmitter(Program::Type) = 0;
+
+ // Drivers should upload this so we can use it from all programs.
+ // The address chosen is supplied to the relocation routine.
+ virtual void getBuiltinCode(const uint32_t **code, uint32_t *size) const = 0;
+
+ virtual bool runLegalizePass(Program *, CGStage stage) const = 0;
+
+public:
+ struct OpInfo
+ {
+ OpInfo *variants;
+ operation op;
+ uint16_t srcTypes;
+ uint16_t dstTypes;
+ uint32_t immdBits;
+ uint8_t srcNr;
+ uint8_t srcMods[3];
+ uint8_t dstMods;
+ uint8_t srcFiles[3];
+ uint8_t dstFiles;
+ unsigned int minEncSize : 4;
+ unsigned int vector : 1;
+ unsigned int predicate : 1;
+ unsigned int commutative : 1;
+ unsigned int pseudo : 1;
+ unsigned int flow : 1;
+ unsigned int hasDest : 1;
+ unsigned int terminator : 1;
+ };
+
+ inline const OpInfo& getOpInfo(const Instruction *) const;
+ inline const OpInfo& getOpInfo(const operation) const;
+
+ inline DataFile nativeFile(DataFile f) const;
+
+ virtual bool insnCanLoad(const Instruction *insn, int s,
+ const Instruction *ld) const = 0;
+ virtual bool isOpSupported(operation, DataType) const = 0;
+ virtual bool isModSupported(const Instruction *,
+ int s, Modifier) const = 0;
+ virtual bool isSatSupported(const Instruction *) const = 0;
+ virtual bool mayPredicate(const Instruction *,
+ const Value *) const = 0;
+
+ virtual int getLatency(const Instruction *) const { return 1; }
+ virtual int getThroughput(const Instruction *) const { return 1; }
+
+ virtual unsigned int getFileSize(DataFile) const = 0;
+ virtual unsigned int getFileUnit(DataFile) const = 0;
+
+ virtual uint32_t getSVAddress(DataFile, const Symbol *) const = 0;
+
+public:
+ bool joinAnterior; // true if join is executed before the op
+
+ static const uint8_t operationSrcNr[OP_LAST + 1];
+
+protected:
+ uint32_t chipset;
+
+ DataFile nativeFileMap[DATA_FILE_COUNT];
+
+ OpInfo opInfo[OP_LAST + 1];
+};
+
+const Target::OpInfo& Target::getOpInfo(const Instruction *insn) const
+{
+ return opInfo[MIN2(insn->op, OP_LAST)];
+}
+
+const Target::OpInfo& Target::getOpInfo(const operation op) const
+{
+ return opInfo[op];
+}
+
+inline DataFile Target::nativeFile(DataFile f) const
+{
+ return nativeFileMap[f];
+}
+
+} // namespace nv50_ir
+
+#endif // __NV50_IR_TARGET_H__
diff --git a/src/gallium/drivers/nv50/codegen/nv50_ir_util.cpp b/src/gallium/drivers/nv50/codegen/nv50_ir_util.cpp
new file mode 100644
index 00000000000..97f47a3ddbc
--- /dev/null
+++ b/src/gallium/drivers/nv50/codegen/nv50_ir_util.cpp
@@ -0,0 +1,253 @@
+
+#include "nv50_ir_util.h"
+
+namespace nv50_ir {
+
+void DLList::clear()
+{
+ for (Item *next, *item = head.next; item != &head; item = next) {
+ next = item->next;
+ delete item;
+ }
+ head.next = head.prev = &head;
+}
+
+void
+DLList::Iterator::erase()
+{
+ Item *rem = pos;
+
+ if (rem == term)
+ return;
+ pos = pos->next;
+
+ DLLIST_DEL(rem);
+ delete rem;
+}
+
+void DLList::Iterator::moveToList(DLList& dest)
+{
+ Item *item = pos;
+
+ assert(term != &dest.head);
+ assert(pos != term);
+
+ pos = pos->next;
+
+ DLLIST_DEL(item);
+ DLLIST_ADDHEAD(&dest.head, item);
+}
+
+bool
+DLList::Iterator::insert(void *data)
+{
+ Item *ins = new Item(data);
+
+ ins->next = pos->next;
+ ins->prev = pos;
+ pos->next->prev = ins;
+ pos->next = ins;
+
+ if (pos == term)
+ term = ins;
+
+ return true;
+}
+
+void
+Stack::moveTo(Stack& that)
+{
+ unsigned int newSize = this->size + that.size;
+
+ while (newSize > that.limit)
+ that.resize();
+ memcpy(&that.array[that.size], &array[0], this->size * sizeof(Item));
+
+ that.size = newSize;
+ this->size = 0;
+}
+
+Interval::~Interval()
+{
+ clear();
+}
+
+void
+Interval::clear()
+{
+ for (Range *next, *r = head; r; r = next) {
+ next = r->next;
+ delete r;
+ }
+}
+
+bool
+Interval::extend(int a, int b)
+{
+ Range *r, **nextp = &head;
+
+ // NOTE: we need empty intervals for fixed registers
+ // if (a == b)
+ // return false;
+ assert(a <= b);
+
+ for (r = head; r; r = r->next) {
+ if (b < r->bgn)
+ break; // insert before
+ if (a > r->end) {
+ // insert after
+ nextp = &r->next;
+ continue;
+ }
+
+ // overlap
+ if (a < r->bgn) {
+ r->bgn = a;
+ if (b > r->end)
+ r->end = b;
+ r->coalesce(&tail);
+ return true;
+ }
+ if (b > r->end) {
+ r->end = b;
+ r->coalesce(&tail);
+ return true;
+ }
+ assert(a >= r->bgn);
+ assert(b <= r->end);
+ return true;
+ }
+
+ (*nextp) = new Range(a, b);
+ (*nextp)->next = r;
+
+ for (r = (*nextp); r->next; r = r->next);
+ tail = r;
+ return true;
+}
+
+bool Interval::contains(int pos)
+{
+ for (Range *r = head; r && r->bgn <= pos; r = r->next)
+ if (r->end > pos)
+ return true;
+ return false;
+}
+
+bool Interval::overlaps(const Interval &iv) const
+{
+ for (Range *rA = this->head; rA; rA = rA->next)
+ for (Range *rB = iv.head; rB; rB = rB->next)
+ if (rB->bgn < rA->end &&
+ rB->end > rA->bgn)
+ return true;
+ return false;
+}
+
+void Interval::unify(Interval &that)
+{
+ assert(this != &that);
+ for (Range *next, *r = that.head; r; r = next) {
+ next = r->next;
+ this->extend(r->bgn, r->end);
+ delete r;
+ }
+ that.head = NULL;
+}
+
+void Interval::print() const
+{
+ if (!head)
+ return;
+ INFO("[%i %i)", head->bgn, head->end);
+ for (const Range *r = head->next; r; r = r->next)
+ INFO(" [%i %i)", r->bgn, r->end);
+ INFO("\n");
+}
+
+void
+BitSet::andNot(const BitSet &set)
+{
+ assert(data && set.data);
+ assert(size >= set.size);
+ for (unsigned int i = 0; i < (set.size + 31) / 32; ++i)
+ data[i] &= ~set.data[i];
+}
+
+BitSet& BitSet::operator|=(const BitSet &set)
+{
+ assert(data && set.data);
+ assert(size >= set.size);
+ for (unsigned int i = 0; i < (set.size + 31) / 32; ++i)
+ data[i] |= set.data[i];
+ return *this;
+}
+
+bool BitSet::allocate(unsigned int nBits, bool zero)
+{
+ if (data && size < nBits) {
+ FREE(data);
+ data = NULL;
+ }
+ size = nBits;
+
+ if (!data)
+ data = reinterpret_cast<uint32_t *>(CALLOC((size + 31) / 32, 4));
+
+ if (zero)
+ memset(data, 0, (size + 7) / 8);
+ else
+ data[(size + 31) / 32 - 1] = 0; // clear unused bits (e.g. for popCount)
+
+ return data;
+}
+
+unsigned int BitSet::popCount() const
+{
+ unsigned int count = 0;
+
+ for (unsigned int i = 0; i < (size + 31) / 32; ++i)
+ if (data[i])
+ count += util_bitcount(data[i]);
+ return count;
+}
+
+void BitSet::fill(uint32_t val)
+{
+ unsigned int i;
+ for (i = 0; i < (size + 31) / 32; ++i)
+ data[i] = val;
+ if (val)
+ data[i] &= ~(0xffffffff << (size % 32)); // BE ?
+}
+
+void BitSet::setOr(BitSet *pA, BitSet *pB)
+{
+ if (!pB) {
+ *this = *pA;
+ } else {
+ for (unsigned int i = 0; i < (size + 31) / 32; ++i)
+ data[i] = pA->data[i] | pB->data[i];
+ }
+}
+
+void BitSet::print() const
+{
+ unsigned int n = 0;
+ INFO("BitSet of size %u:\n", size);
+ for (unsigned int i = 0; i < (size + 31) / 32; ++i) {
+ uint32_t bits = data[i];
+ while (bits) {
+ int pos = ffs(bits) - 1;
+ bits &= ~(1 << pos);
+ INFO(" %i", i * 32 + pos);
+ ++n;
+ if ((n % 16) == 0)
+ INFO("\n");
+ }
+ }
+ if (n % 16)
+ INFO("\n");
+}
+
+} // namespace nv50_ir
diff --git a/src/gallium/drivers/nv50/codegen/nv50_ir_util.h b/src/gallium/drivers/nv50/codegen/nv50_ir_util.h
new file mode 100644
index 00000000000..2ffdcd65568
--- /dev/null
+++ b/src/gallium/drivers/nv50/codegen/nv50_ir_util.h
@@ -0,0 +1,585 @@
+
+#ifndef __NV50_IR_UTIL_H__
+#define __NV50_IR_UTIL_H__
+
+#include <new>
+#include <assert.h>
+#include <stdio.h>
+
+#include "util/u_inlines.h"
+#include "util/u_memory.h"
+
+#define ERROR(args...) debug_printf("ERROR: " args)
+#define WARN(args...) debug_printf("WARNING: " args)
+#define INFO(args...) debug_printf(args)
+
+#define INFO_DBG(m, f, args...) \
+ do { \
+ if (m & NV50_IR_DEBUG_##f) \
+ debug_printf(args); \
+ } while(0)
+
+#define FATAL(args...) \
+ do { \
+ fprintf(stderr, args); \
+ abort(); \
+ } while(0)
+
+
+#define NV50_IR_FUNC_ALLOC_OBJ_DEF(obj, f, args...) \
+ new ((f)->getProgram()->mem_##obj.allocate()) obj(f, args)
+
+#define new_Instruction(f, args...) \
+ NV50_IR_FUNC_ALLOC_OBJ_DEF(Instruction, f, args)
+#define new_CmpInstruction(f, args...) \
+ NV50_IR_FUNC_ALLOC_OBJ_DEF(CmpInstruction, f, args)
+#define new_TexInstruction(f, args...) \
+ NV50_IR_FUNC_ALLOC_OBJ_DEF(TexInstruction, f, args)
+#define new_FlowInstruction(f, args...) \
+ NV50_IR_FUNC_ALLOC_OBJ_DEF(FlowInstruction, f, args)
+
+#define new_LValue(f, args...) \
+ NV50_IR_FUNC_ALLOC_OBJ_DEF(LValue, f, args)
+
+
+#define NV50_IR_PROG_ALLOC_OBJ_DEF(obj, p, args...) \
+ new ((p)->mem_##obj.allocate()) obj(p, args)
+
+#define new_Symbol(p, args...) \
+ NV50_IR_PROG_ALLOC_OBJ_DEF(Symbol, p, args)
+#define new_ImmediateValue(p, args...) \
+ NV50_IR_PROG_ALLOC_OBJ_DEF(ImmediateValue, p, args)
+
+
+#define delete_Instruction(p, insn) (p)->releaseInstruction(insn)
+#define delete_Value(p, val) (p)->releaseValue(val)
+
+
+namespace nv50_ir {
+
+class Iterator
+{
+public:
+ virtual void next() = 0;
+ virtual void *get() const = 0;
+ virtual bool end() const = 0; // if true, get will return 0
+};
+
+class ManipIterator : public Iterator
+{
+public:
+ virtual bool insert(void *) = 0; // insert after current position
+ virtual void erase() = 0;
+};
+
+// WARNING: do not use a->prev/next for __item or __list
+
+#define DLLIST_DEL(__item) \
+ do { \
+ (__item)->prev->next = (__item)->next; \
+ (__item)->next->prev = (__item)->prev; \
+ (__item)->next = (__item); \
+ (__item)->prev = (__item); \
+ } while(0)
+
+#define DLLIST_ADDTAIL(__list, __item) \
+ do { \
+ (__item)->next = (__list); \
+ (__item)->prev = (__list)->prev; \
+ (__list)->prev->next = (__item); \
+ (__list)->prev = (__item); \
+ } while(0)
+
+#define DLLIST_ADDHEAD(__list, __item) \
+ do { \
+ (__item)->prev = (__list); \
+ (__item)->next = (__list)->next; \
+ (__list)->next->prev = (__item); \
+ (__list)->next = (__item); \
+ } while(0)
+
+#define DLLIST_MERGE(__listA, __listB, ty) \
+ do { \
+ ty prevB = (__listB)->prev; \
+ (__listA)->prev->next = (__listB); \
+ (__listB)->prev->next = (__listA); \
+ (__listB)->prev = (__listA)->prev; \
+ (__listA)->prev = prevB; \
+ } while(0)
+
+#define DLLIST_FOR_EACH(list, it) \
+ for (DLList::Iterator (it) = (list)->iterator(); !(it).end(); (it).next())
+
+class DLList
+{
+public:
+ class Item
+ {
+ public:
+ Item(void *priv) : next(this), prev(this), data(priv) { }
+
+ public:
+ Item *next;
+ Item *prev;
+ void *data;
+ };
+
+ DLList() : head(0) { }
+ ~DLList() { clear(); }
+
+ inline void insertHead(void *data)
+ {
+ Item *item = new Item(data);
+
+ assert(data);
+
+ item->prev = &head;
+ item->next = head.next;
+ head.next->prev = item;
+ head.next = item;
+ }
+
+ inline void insertTail(void *data)
+ {
+ Item *item = new Item(data);
+
+ assert(data);
+
+ DLLIST_ADDTAIL(&head, item);
+ }
+
+ inline void insert(void *data) { insertTail(data); }
+
+ void clear();
+
+ class Iterator : public ManipIterator
+ {
+ public:
+ Iterator(Item *head, bool r) : rev(r), pos(r ? head->prev : head->next),
+ term(head) { }
+
+ virtual void next() { if (!end()) pos = rev ? pos->prev : pos->next; }
+ virtual void *get() const { return pos->data; }
+ virtual bool end() const { return pos == term; }
+
+ // caution: if you're at end-2 and erase it, then do next, you're at end
+ virtual void erase();
+ virtual bool insert(void *data);
+
+ // move item to a another list, no consistency with its iterators though
+ void moveToList(DLList&);
+
+ private:
+ const bool rev;
+ Item *pos;
+ Item *term;
+
+ friend class DLList;
+ };
+
+ inline void erase(Iterator& pos)
+ {
+ pos.erase();
+ }
+
+ Iterator iterator()
+ {
+ return Iterator(&head, false);
+ }
+
+ Iterator revIterator()
+ {
+ return Iterator(&head, true);
+ }
+
+private:
+ Item head;
+};
+
+class Stack
+{
+public:
+ class Item {
+ public:
+ union {
+ void *p;
+ int i;
+ unsigned int u;
+ float f;
+ double d;
+ } u;
+
+ Item() { memset(&u, 0, sizeof(u)); }
+ };
+
+ Stack() : size(0), limit(0), array(0) { }
+ ~Stack() { if (array) FREE(array); }
+
+ inline void push(int i) { Item data; data.u.i = i; push(data); }
+ inline void push(unsigned int u) { Item data; data.u.u = u; push(data); }
+ inline void push(void *p) { Item data; data.u.p = p; push(data); }
+ inline void push(float f) { Item data; data.u.f = f; push(data); }
+
+ inline void push(Item data)
+ {
+ if (size == limit)
+ resize();
+ array[size++] = data;
+ }
+
+ inline Item pop()
+ {
+ if (!size) {
+ Item data;
+ assert(0);
+ return data;
+ }
+ return array[--size];
+ }
+
+ inline unsigned int getSize() { return size; }
+
+ inline Item& peek() { assert(size); return array[size - 1]; }
+
+ void clear(bool releaseStorage = false)
+ {
+ if (releaseStorage && array)
+ FREE(array);
+ size = limit = 0;
+ }
+
+ void moveTo(Stack&); // move all items to target (not like push(pop()))
+
+private:
+ void resize()
+ {
+ unsigned int sizeOld, sizeNew;
+
+ sizeOld = limit * sizeof(Item);
+ limit = MAX2(4, limit + limit);
+ sizeNew = limit * sizeof(Item);
+
+ array = (Item *)REALLOC(array, sizeOld, sizeNew);
+ }
+
+ unsigned int size;
+ unsigned int limit;
+ Item *array;
+};
+
+class DynArray
+{
+public:
+ class Item
+ {
+ public:
+ union {
+ uint32_t u32;
+ void *p;
+ };
+ };
+
+ DynArray() : data(NULL), size(0) { }
+
+ ~DynArray() { if (data) FREE(data); }
+
+ inline Item& operator[](unsigned int i)
+ {
+ if (i >= size)
+ resize(i);
+ return data[i];
+ }
+
+ inline const Item operator[](unsigned int i) const
+ {
+ return data[i];
+ }
+
+ void resize(unsigned int index)
+ {
+ const unsigned int oldSize = size * sizeof(Item);
+
+ if (!size)
+ size = 8;
+ while (size <= index)
+ size <<= 1;
+
+ data = (Item *)REALLOC(data, oldSize, size * sizeof(Item));
+ }
+
+private:
+ Item *data;
+ unsigned int size;
+};
+
+class ArrayList
+{
+public:
+ ArrayList() : size(0) { }
+
+ void insert(void *item, int& id)
+ {
+ id = ids.getSize() ? ids.pop().u.i : size++;
+ data[id].p = item;
+ }
+
+ void remove(int& id)
+ {
+ const unsigned int uid = id;
+ assert(uid < size && data[id].p);
+ ids.push(uid);
+ data[uid].p = NULL;
+ id = -1;
+ }
+
+ inline int getSize() const { return size; }
+
+ inline void *get(unsigned int id) { assert(id < size); return data[id].p; }
+
+ class Iterator : public nv50_ir::Iterator
+ {
+ public:
+ Iterator(const ArrayList *array) : pos(0), data(array->data)
+ {
+ size = array->getSize();
+ if (size)
+ nextValid();
+ }
+
+ void nextValid() { while ((pos < size) && !data[pos].p) ++pos; }
+
+ void next() { if (pos < size) { ++pos; nextValid(); } }
+ void *get() const { assert(pos < size); return data[pos].p; }
+ bool end() const { return pos >= size; }
+
+ private:
+ unsigned int pos;
+ unsigned int size;
+ const DynArray& data;
+
+ friend class ArrayList;
+ };
+
+ Iterator iterator() const { return Iterator(this); }
+
+private:
+ DynArray data;
+ Stack ids;
+ unsigned int size;
+};
+
+class Interval
+{
+public:
+ Interval() : head(0), tail(0) { }
+ ~Interval();
+
+ bool extend(int, int);
+ void unify(Interval&); // clears source interval
+ void clear();
+
+ inline int begin() { return head ? head->bgn : -1; }
+ inline int end() { checkTail(); return tail ? tail->end : -1; }
+ inline bool isEmpty() const { return !head; }
+ bool overlaps(const Interval&) const;
+ bool contains(int pos);
+
+ void print() const;
+
+ inline void checkTail() const;
+
+private:
+ class Range
+ {
+ public:
+ Range(int a, int b) : next(0), bgn(a), end(b) { }
+
+ Range *next;
+ int bgn;
+ int end;
+
+ void coalesce(Range **ptail)
+ {
+ Range *rnn;
+
+ while (next && end >= next->bgn) {
+ assert(bgn <= next->bgn);
+ rnn = next->next;
+ end = MAX2(end, next->end);
+ delete next;
+ next = rnn;
+ }
+ if (!next)
+ *ptail = this;
+ }
+ };
+
+ Range *head;
+ Range *tail;
+};
+
+class BitSet
+{
+public:
+ BitSet() : marker(false), data(0), size(0) { }
+ BitSet(unsigned int nBits, bool zero) : marker(false), data(0), size(0)
+ {
+ allocate(nBits, zero);
+ }
+ ~BitSet()
+ {
+ if (data)
+ FREE(data);
+ }
+
+ bool allocate(unsigned int nBits, bool zero);
+
+ inline unsigned int getSize() const { return size; }
+
+ void fill(uint32_t val);
+
+ void setOr(BitSet *, BitSet *); // second BitSet may be NULL
+
+ inline void set(unsigned int i)
+ {
+ assert(i < size);
+ data[i / 32] |= 1 << (i % 32);
+ }
+
+ inline void clr(unsigned int i)
+ {
+ assert(i < size);
+ data[i / 32] &= ~(1 << (i % 32));
+ }
+
+ inline bool test(unsigned int i) const
+ {
+ assert(i < size);
+ return data[i / 32] & (1 << (i % 32));
+ }
+
+ BitSet& operator|=(const BitSet&);
+
+ BitSet& operator=(const BitSet& set)
+ {
+ assert(data && set.data);
+ assert(size == set.size);
+ memcpy(data, set.data, (set.size + 7) / 8);
+ return *this;
+ }
+
+ void andNot(const BitSet&);
+
+ unsigned int popCount() const;
+
+ void print() const;
+
+public:
+ bool marker; // for user
+
+private:
+ uint32_t *data;
+ unsigned int size;
+};
+
+void Interval::checkTail() const
+{
+#if NV50_DEBUG & NV50_DEBUG_PROG_RA
+ Range *r = head;
+ while (r->next)
+ r = r->next;
+ assert(tail == r);
+#endif
+}
+
+class MemoryPool
+{
+private:
+ inline bool enlargeAllocationsArray(const unsigned int id, unsigned int nr)
+ {
+ const unsigned int size = sizeof(uint8_t *) * id;
+ const unsigned int incr = sizeof(uint8_t *) * nr;
+
+ uint8_t **alloc = (uint8_t **)REALLOC(allocArray, size, size + incr);
+ if (!alloc)
+ return false;
+ allocArray = alloc;
+ return true;
+ }
+
+ inline bool enlargeCapacity()
+ {
+ const unsigned int id = count >> objStepLog2;
+
+ uint8_t *const mem = (uint8_t *)MALLOC(objSize << objStepLog2);
+ if (!mem)
+ return false;
+
+ if (!(id % 32)) {
+ if (!enlargeAllocationsArray(id, 32)) {
+ FREE(mem);
+ return false;
+ }
+ }
+ allocArray[id] = mem;
+ return true;
+ }
+
+public:
+ MemoryPool(unsigned int size, unsigned int incr) : objSize(size),
+ objStepLog2(incr)
+ {
+ allocArray = NULL;
+ released = NULL;
+ count = 0;
+ }
+
+ ~MemoryPool()
+ {
+ unsigned int allocCount = (count + (1 << objStepLog2) - 1) >> objStepLog2;
+ for (unsigned int i = 0; i < allocCount && allocArray[i]; ++i)
+ FREE(allocArray[i]);
+ if (allocArray)
+ FREE(allocArray);
+ }
+
+ void *allocate()
+ {
+ void *ret;
+ const unsigned int mask = (1 << objStepLog2) - 1;
+
+ if (released) {
+ ret = released;
+ released = *(void **)released;
+ return ret;
+ }
+
+ if (!(count & mask))
+ if (!enlargeCapacity())
+ return NULL;
+
+ ret = allocArray[count >> objStepLog2] + (count & mask) * objSize;
+ ++count;
+ return ret;
+ }
+
+ void release(void *ptr)
+ {
+ *(void **)ptr = released;
+ released = ptr;
+ }
+
+private:
+ uint8_t **allocArray; // array (list) of MALLOC allocations
+
+ void *released; // list of released objects
+
+ unsigned int count; // highest allocated object
+
+ const unsigned int objSize;
+ const unsigned int objStepLog2;
+};
+
+} // namespace nv50_ir
+
+#endif // __NV50_IR_UTIL_H__