[Mesa-dev] [PATCH RFC 07/11] glsl: add SSA infrastructure

Connor Abbott cwabbott0 at gmail.com
Wed Jan 22 09:16:54 PST 2014


This patch introduces all the changes to the IR that are necessary for
representing programs in the SSA form. This consists of a new variable
mode, the SSA temporary, which is guarenteed to be written to exactly
once, and classes to represent phi nodes in the IR.

In the current code, variables are first declared using an ir_variable
instruction inserted into the instruction stream, and then every
dereference will point to the ir_variable declared earlier. SSA
temporaries, however, do not work this way. Instead, the variable
is declared when it is assigned. That is, the variable is "owned" by
the one and only instruction where it is defined.

In SSA, phi nodes may exist at the beginning of any "join nodes," or
basic blocks with more than one predecessor. In our IR, this can happen
in one of three places:

- After an if statement, where the then branch and the else branch
converge.
- At the beginning of a loop, which can be reached from either before
the loop (on the first iteration), the end of the loop (when we get to
the end of the loop and jump back to the beginning), or any continue
statement.
- At the end of a loop, which can be reached from any break statement
within the loop.

Accordingly, there are three different types of phi nodes: if phi nodes,
phi nodes at the beginning of a loop, and phi nodes at the end of a
loop, all of which derive from the ir_phi base class.
---
 src/glsl/ir.cpp                                |  56 +++++++
 src/glsl/ir.h                                  | 196 ++++++++++++++++++++++++-
 src/glsl/ir_clone.cpp                          | 147 ++++++++++++++++---
 src/glsl/ir_hierarchical_visitor.cpp           |  36 +++++
 src/glsl/ir_hierarchical_visitor.h             |  11 ++
 src/glsl/ir_hv_accept.cpp                      |  55 ++++++-
 src/glsl/ir_print_visitor.cpp                  | 196 ++++++++++++++++++++++++-
 src/glsl/ir_print_visitor.h                    |  15 ++
 src/glsl/ir_validate.cpp                       | 158 +++++++++++++++++++-
 src/glsl/ir_visitor.h                          |   8 +
 src/mesa/drivers/dri/i965/brw_fs.h             |   4 +
 src/mesa/drivers/dri/i965/brw_fs_visitor.cpp   |  28 ++++
 src/mesa/drivers/dri/i965/brw_vec4.h           |   4 +
 src/mesa/drivers/dri/i965/brw_vec4_visitor.cpp |  24 +++
 src/mesa/program/ir_to_mesa.cpp                |  28 ++++
 src/mesa/state_tracker/st_glsl_to_tgsi.cpp     |  29 ++++
 16 files changed, 956 insertions(+), 39 deletions(-)

diff --git a/src/glsl/ir.cpp b/src/glsl/ir.cpp
index 1a36bd6..f1ded80 100644
--- a/src/glsl/ir.cpp
+++ b/src/glsl/ir.cpp
@@ -1229,6 +1229,37 @@ ir_loop::ir_loop()
 }
 
 
+ir_phi::ir_phi()
+{
+   this->dest = NULL;
+}
+
+
+ir_phi_if::ir_phi_if(ir_variable *dest, ir_variable *if_src,
+		     ir_variable *else_src)
+   : if_src(if_src), else_src(else_src)
+{
+   this->ir_type = ir_type_phi_if;
+   this->dest = dest;
+}
+
+
+ir_phi_loop_begin::ir_phi_loop_begin(ir_variable* dest, ir_variable* enter_src,
+				     ir_variable* repeat_src)
+   : enter_src(enter_src), repeat_src(repeat_src)
+{
+   this->ir_type = ir_type_phi_loop_begin;
+   this->dest = dest;
+}
+
+
+ir_phi_loop_end::ir_phi_loop_end(ir_variable *dest)
+{
+   this->ir_type = ir_type_phi_loop_end;
+   this->dest = dest;
+}
+
+
 ir_dereference_variable::ir_dereference_variable(ir_variable *var)
 {
    assert(var != NULL);
@@ -1554,6 +1585,9 @@ ir_variable::ir_variable(const struct glsl_type *type, const char *name,
    this->data.max_array_access = 0;
    this->data.atomic.buffer_index = 0;
    this->data.atomic.offset = 0;
+   this->ssa_assignment = NULL;
+   this->ssa_phi = NULL;
+   this->ssa_call = NULL;
 
    if (type != NULL) {
       if (type->base_type == GLSL_TYPE_SAMPLER)
@@ -1722,12 +1756,19 @@ steal_memory(ir_instruction *ir, void *new_ctx)
 {
    ir_variable *var = ir->as_variable();
    ir_constant *constant = ir->as_constant();
+   ir_dereference_variable *deref = ir->as_dereference_variable();
+   ir_phi *phi = ir->as_phi();
+   ir_phi_loop_begin *phi_loop_begin = ir->as_phi_loop_begin();
+   ir_phi_loop_end *phi_loop_end = ir->as_phi_loop_end();
    if (var != NULL && var->constant_value != NULL)
       steal_memory(var->constant_value, ir);
 
    if (var != NULL && var->constant_initializer != NULL)
       steal_memory(var->constant_initializer, ir);
 
+   if (deref != NULL && deref->var->data.mode == ir_var_temporary_ssa)
+      steal_memory(deref->var, ir);
+
    /* The components of aggregate constants are not visited by the normal
     * visitor, so steal their values by hand.
     */
@@ -1744,6 +1785,21 @@ steal_memory(ir_instruction *ir, void *new_ctx)
       }
    }
 
+   if (phi != NULL)
+      steal_memory(phi->dest, new_ctx);
+
+   if (phi_loop_begin != NULL) {
+      foreach_list(n, &phi_loop_begin->continue_srcs) {
+	 ralloc_steal(new_ctx, n);
+      }
+   }
+
+   if (phi_loop_end != NULL) {
+      foreach_list(n, &phi_loop_end->break_srcs) {
+	 ralloc_steal(new_ctx, n);
+      }
+   }
+
    ralloc_steal(new_ctx, ir);
 }
 
diff --git a/src/glsl/ir.h b/src/glsl/ir.h
index d1e790d..8af8eed 100644
--- a/src/glsl/ir.h
+++ b/src/glsl/ir.h
@@ -78,6 +78,9 @@ enum ir_node_type {
    ir_type_if,
    ir_type_loop,
    ir_type_loop_jump,
+   ir_type_phi_if,
+   ir_type_phi_loop_begin,
+   ir_type_phi_loop_end,
    ir_type_return,
    ir_type_swizzle,
    ir_type_texture,
@@ -139,6 +142,10 @@ public:
    virtual class ir_discard *           as_discard()          { return NULL; }
    virtual class ir_jump *              as_jump()             { return NULL; }
    virtual class ir_loop_jump *         as_loop_jump()        { return NULL; }
+   virtual class ir_phi *               as_phi()              { return NULL; }
+   virtual class ir_phi_if *            as_phi_if()           { return NULL; }
+   virtual class ir_phi_loop_begin *    as_phi_loop_begin()   { return NULL; }
+   virtual class ir_phi_loop_end *      as_phi_loop_end()     { return NULL; }
    /*@}*/
 
    /**
@@ -289,10 +296,11 @@ enum ir_variable_mode {
    ir_var_function_in,
    ir_var_function_out,
    ir_var_function_inout,
-   ir_var_const_in,	/**< "in" param that must be a constant expression */
-   ir_var_system_value, /**< Ex: front-face, instance-id, etc. */
-   ir_var_temporary,	/**< Temporary variable generated during compilation. */
-   ir_var_mode_count	/**< Number of variable modes */
+   ir_var_const_in,	 /**< "in" param that must be a constant expression */
+   ir_var_system_value,  /**< Ex: front-face, instance-id, etc. */
+   ir_var_temporary,	 /**< Temporary variable generated during compilation. */
+   ir_var_temporary_ssa, /**< Temporary variable with only one definition. */
+   ir_var_mode_count	 /**< Number of variable modes */
 };
 
 /**
@@ -736,6 +744,43 @@ public:
     */
    ir_constant *constant_initializer;
 
+   /**
+    * Assignment that creates this variable, if it is an SSA temporary
+    *
+    * SSA variables are declared in the assignment/phi node/return where they
+    * are defined, unlike everything else where the lhs defereference always
+    * points to an ir_variable somewhere else in the ir tree. Storing the
+    * parent assignment here allows us to more easily traverse the use-def
+    * chains during optimizations.
+    *
+    * \sa ir_variable::ssa_phi
+    * \sa ir_variable::ssa_call
+    */
+
+   ir_assignment *ssa_assignment;
+
+   /**
+    * Phi node that creates this variable, if it is an SSA temporary
+    *
+    * Note: if this variable is an SSA temporary, then one of this field,
+    * \c ::ssa_assignment, or \c ::ssa_call is non-null, but not both.
+    *
+    * \sa ir_variable::ssa_assignment
+    * \sa ir_variable::ssa_call
+    */
+
+   ir_phi *ssa_phi;
+
+   /**
+    * Call whose return dereference creates this variable, if it is an SSA
+    * temporary
+    *
+    * \sa ir_variable::ssa_assignment
+    * \sa ir_variable::ssa_phi
+    */
+
+   ir_call *ssa_call;
+
 private:
    /**
     * For variables that are in an interface block or are an instance of an
@@ -987,6 +1032,8 @@ public:
    exec_list  then_instructions;
    /** List of ir_instruction for the body of the else branch */
    exec_list  else_instructions;
+   /** List of phi nodes at the end of the if */
+   exec_list phi_nodes;
 };
 
 
@@ -1013,6 +1060,147 @@ public:
 
    /** List of ir_instruction that make up the body of the loop. */
    exec_list body_instructions;
+
+   /** List of phi nodes at the beginning of the body of the loop. */
+   exec_list begin_phi_nodes;
+   /** List of phi nodes immediately after the loop. */
+   exec_list end_phi_nodes;
+};
+
+/**
+ * Base class for instructions representing phi nodes
+ *
+ * There are three join points where phi nodes can occur:
+ * * after an if statement
+ * * at the beginning of a loop
+ * * after a loop
+ *
+ * Each of these points has an associated subclass of ir_phi with places to
+ * store the input variables corresponding to each predecessor basic block.
+ * Note that an input may be null, in which case the value coming from that
+ * basic block is undefined. A null input conveys important information about
+ * the original program before the conversion to SSA, and so it *cannot* be
+ * optimized away (see "Global Code Motion Global Value Numbering" by Click for
+ * details).
+ */
+
+
+class ir_phi : public ir_instruction {
+public:
+   virtual ir_phi *clone(void *mem_ctx, hash_table *ht) const;
+
+   virtual void accept(ir_visitor *v)
+   {
+      v->visit(this);
+   }
+
+   virtual ir_visitor_status accept(ir_hierarchical_visitor *);
+
+   virtual ir_phi *as_phi()
+   {
+      return this;
+   }
+
+   ir_variable *dest;
+
+protected:
+   ir_phi();
+};
+
+
+class ir_phi_if : public ir_phi {
+public:
+   ir_phi_if(ir_variable *dest, ir_variable *if_src, ir_variable *else_src);
+
+   virtual ir_phi_if *clone(void *mem_ctx, hash_table *ht) const;
+
+   virtual void accept(ir_visitor *v)
+   {
+      v->visit(this);
+   }
+
+   virtual ir_visitor_status accept(ir_hierarchical_visitor *);
+
+   virtual ir_phi_if *as_phi_if()
+   {
+      return this;
+   }
+
+   /** the value dest takes if the if branch is taken */
+   ir_variable *if_src;
+   /** the value dest takes if the if branch is not taken */
+   ir_variable *else_src;
+};
+
+
+/**
+ * Represents a phi node source from a loop jump instruction (break or continue)
+ */
+
+struct ir_phi_jump_src : public exec_node {
+   ir_loop_jump *jump;
+   ir_variable *src;
+};
+
+
+class ir_phi_loop_begin : public ir_phi {
+public:
+   ir_phi_loop_begin(ir_variable *dest, ir_variable *enter_src, ir_variable *repeat_src);
+
+   virtual ir_phi_loop_begin *clone(void *mem_ctx, hash_table *ht) const;
+
+   virtual void accept(ir_visitor *v)
+   {
+      v->visit(this);
+   }
+
+   virtual ir_visitor_status accept(ir_hierarchical_visitor *);
+
+   virtual ir_phi_loop_begin *as_phi_loop_begin()
+   {
+      return this;
+   }
+
+   /** the value dest takes on the first iteration of the loop */
+   ir_variable *enter_src;
+
+   /**
+    * The value dest takes after reaching the end of the loop and going back to
+    * the beginning.
+    */
+   ir_variable *repeat_src;
+
+   /**
+    * A list of ir_phi_jump_src structures representing the value dest will take
+    * after each continue.
+    */
+   exec_list continue_srcs;
+};
+
+
+class ir_phi_loop_end : public ir_phi {
+public:
+   ir_phi_loop_end(ir_variable *dest);
+
+   virtual ir_phi_loop_end *clone(void *mem_ctx, hash_table *ht) const;
+
+   virtual void accept(ir_visitor *v)
+   {
+      v->visit(this);
+   }
+
+   virtual ir_visitor_status accept(ir_hierarchical_visitor *);
+
+   virtual ir_phi_loop_end *as_phi_loop_end()
+   {
+      return this;
+   }
+
+   /**
+    * A list of ir_phi_jump_src structures representing the value dest will take
+    * after each break.
+    */
+   exec_list break_srcs;
 };
 
 
diff --git a/src/glsl/ir_clone.cpp b/src/glsl/ir_clone.cpp
index cb732a5..7075579 100644
--- a/src/glsl/ir_clone.cpp
+++ b/src/glsl/ir_clone.cpp
@@ -40,7 +40,15 @@ ir_rvalue::clone(void *mem_ctx, struct hash_table *ht) const
 ir_variable *
 ir_variable::clone(void *mem_ctx, struct hash_table *ht) const
 {
-   ir_variable *var = new(mem_ctx) ir_variable(this->type, this->name,
+   ir_variable *var;
+
+   if (ht) {
+      var = (ir_variable *)hash_table_find(ht, this);
+      if (var)
+	 return var;
+   }
+
+   var = new(mem_ctx) ir_variable(this->type, this->name,
 					       (ir_variable_mode) this->data.mode);
 
    var->data.max_array_access = this->data.max_array_access;
@@ -113,9 +121,92 @@ ir_discard::clone(void *mem_ctx, struct hash_table *ht) const
 ir_loop_jump *
 ir_loop_jump::clone(void *mem_ctx, struct hash_table *ht) const
 {
-   (void)ht;
+   /*
+    * ir_phi_loop_begin and ir_phi_loop_end will clone loop_jump statements,
+    * so like with ir_variable we need to use the hash table to make sure we
+    * don't clone the same loop_jump twice
+    */
 
-   return new(mem_ctx) ir_loop_jump(this->mode);
+   ir_loop_jump *new_loop_jump;
+   if (ht)
+   {
+      new_loop_jump = (ir_loop_jump *)hash_table_find(ht, this);
+      if (new_loop_jump) {
+	 return new_loop_jump;
+      }
+   }
+
+   new_loop_jump = new(mem_ctx) ir_loop_jump(this->mode);
+   if (ht) {
+      hash_table_insert(ht, new_loop_jump,
+			(void *)const_cast<ir_loop_jump *>(this));
+   }
+   return new_loop_jump;
+}
+
+static ir_variable *
+find_variable(ir_variable *var, void *mem_ctx, struct hash_table *ht)
+{
+   if (ht) {
+      ir_variable *new_var = (ir_variable *)hash_table_find(ht, var);
+      if (new_var)
+	 return new_var;
+   }
+
+   return var;
+}
+
+ir_phi *
+ir_phi::clone(void *mem_ctx, struct hash_table *ht) const
+{
+   /* shouldn't get here */
+   assert(0);
+   return new(mem_ctx) ir_phi();
+}
+
+ir_phi_if *
+ir_phi_if::clone(void *mem_ctx, struct hash_table *ht) const
+{
+   return new(mem_ctx) ir_phi_if(this->dest->clone(mem_ctx, ht),
+				 this->if_src->clone(mem_ctx, ht),
+				 this->else_src->clone(mem_ctx, ht));
+}
+
+static ir_phi_jump_src *
+clone_phi_jump_src(ir_phi_jump_src *src, void *mem_ctx, struct hash_table *ht)
+{
+   ir_phi_jump_src *new_src = new(mem_ctx) ir_phi_jump_src();
+   new_src->src = src->src->clone(mem_ctx, ht);
+   new_src->jump = src->jump->clone(mem_ctx, ht);
+   return new_src;
+}
+
+ir_phi_loop_begin *
+ir_phi_loop_begin::clone(void *mem_ctx, struct hash_table *ht) const
+{
+   ir_phi_loop_begin *new_phi = new(mem_ctx) ir_phi_loop_begin(this->dest->clone(mem_ctx, ht),
+							       this->enter_src->clone(mem_ctx, ht),
+							       this->repeat_src->clone(mem_ctx, ht));
+
+   foreach_list(n, &this->continue_srcs) {
+      ir_phi_jump_src *src = (ir_phi_jump_src *) n;
+      new_phi->continue_srcs.push_tail(clone_phi_jump_src(src, mem_ctx, ht));
+   }
+
+   return new_phi;
+}
+
+ir_phi_loop_end *
+ir_phi_loop_end::clone(void *mem_ctx, struct hash_table *ht) const
+{
+   ir_phi_loop_end *new_phi = new(mem_ctx) ir_phi_loop_end(this->dest->clone(mem_ctx, ht));
+
+   foreach_list(n, &this->break_srcs) {
+      ir_phi_jump_src *src = (ir_phi_jump_src *) n;
+      new_phi->break_srcs.push_tail(clone_phi_jump_src(src, mem_ctx, ht));
+   }
+
+   return new_phi;
 }
 
 ir_if *
@@ -123,6 +214,11 @@ ir_if::clone(void *mem_ctx, struct hash_table *ht) const
 {
    ir_if *new_if = new(mem_ctx) ir_if(this->condition->clone(mem_ctx, ht));
 
+   foreach_list(n, &this->phi_nodes) {
+      ir_phi_if *ir = (ir_phi_if *) n;
+      new_if->phi_nodes.push_tail(ir->clone(mem_ctx, ht));
+   }
+
    foreach_list(n, &this->then_instructions) {
       ir_instruction *ir = (ir_instruction *) n;
       new_if->then_instructions.push_tail(ir->clone(mem_ctx, ht));
@@ -141,6 +237,16 @@ ir_loop::clone(void *mem_ctx, struct hash_table *ht) const
 {
    ir_loop *new_loop = new(mem_ctx) ir_loop();
 
+   foreach_list(n, &this->begin_phi_nodes) {
+      ir_phi_loop_begin *iplb = (ir_phi_loop_begin *) n;
+      new_loop->begin_phi_nodes.push_tail(iplb->clone(mem_ctx, ht));
+   }
+
+   foreach_list(n, &this->end_phi_nodes) {
+      ir_phi_loop_end *iple = (ir_phi_loop_end *) n;
+      new_loop->end_phi_nodes.push_tail(iple->clone(mem_ctx, ht));
+   }
+
    foreach_list(n, &this->body_instructions) {
       ir_instruction *ir = (ir_instruction *) n;
       new_loop->body_instructions.push_tail(ir->clone(mem_ctx, ht));
@@ -183,17 +289,8 @@ ir_expression::clone(void *mem_ctx, struct hash_table *ht) const
 ir_dereference_variable *
 ir_dereference_variable::clone(void *mem_ctx, struct hash_table *ht) const
 {
-   ir_variable *new_var;
-
-   if (ht) {
-      new_var = (ir_variable *)hash_table_find(ht, this->var);
-      if (!new_var)
-	 new_var = this->var;
-   } else {
-      new_var = this->var;
-   }
-
-   return new(mem_ctx) ir_dereference_variable(new_var);
+   return new(mem_ctx) ir_dereference_variable(find_variable(this->var,
+							      mem_ctx, ht));
 }
 
 ir_dereference_array *
@@ -261,14 +358,28 @@ ir_assignment *
 ir_assignment::clone(void *mem_ctx, struct hash_table *ht) const
 {
    ir_rvalue *new_condition = NULL;
+   ir_variable *new_var = NULL;
 
    if (this->condition)
       new_condition = this->condition->clone(mem_ctx, ht);
 
-   return new(mem_ctx) ir_assignment(this->lhs->clone(mem_ctx, ht),
-				     this->rhs->clone(mem_ctx, ht),
-				     new_condition,
-				     this->write_mask);
+   if (this->lhs->variable_referenced()
+       && this->lhs->variable_referenced()->data.mode == ir_var_temporary_ssa) {
+      new_var = this->lhs->variable_referenced()->clone(mem_ctx, ht);
+   }
+
+
+
+   ir_assignment *ret = new(mem_ctx) ir_assignment(this->lhs->clone(mem_ctx, ht),
+						   this->rhs->clone(mem_ctx, ht),
+						   new_condition,
+						   this->write_mask);
+
+   if (new_var) {
+      new_var->ssa_assignment = ret;
+   }
+
+   return ret;
 }
 
 ir_function *
diff --git a/src/glsl/ir_hierarchical_visitor.cpp b/src/glsl/ir_hierarchical_visitor.cpp
index 2e606dd..46a2f8f 100644
--- a/src/glsl/ir_hierarchical_visitor.cpp
+++ b/src/glsl/ir_hierarchical_visitor.cpp
@@ -96,6 +96,42 @@ ir_hierarchical_visitor::visit(ir_dereference_variable *ir)
 }
 
 ir_visitor_status
+ir_hierarchical_visitor::visit(ir_phi *ir)
+{
+   if (this->callback != NULL)
+      this->callback(ir, this->data);
+
+   return visit_continue;
+}
+
+ir_visitor_status
+ir_hierarchical_visitor::visit(ir_phi_if *ir)
+{
+   if (this->callback != NULL)
+      this->callback(ir, this->data);
+
+   return visit_continue;
+}
+
+ir_visitor_status
+ir_hierarchical_visitor::visit(ir_phi_loop_begin *ir)
+{
+   if (this->callback != NULL)
+      this->callback(ir, this->data);
+
+   return visit_continue;
+}
+
+ir_visitor_status
+ir_hierarchical_visitor::visit(ir_phi_loop_end *ir)
+{
+   if (this->callback != NULL)
+      this->callback(ir, this->data);
+
+   return visit_continue;
+}
+
+ir_visitor_status
 ir_hierarchical_visitor::visit_enter(ir_loop *ir)
 {
    if (this->callback != NULL)
diff --git a/src/glsl/ir_hierarchical_visitor.h b/src/glsl/ir_hierarchical_visitor.h
index 647d2e0..67ea832 100644
--- a/src/glsl/ir_hierarchical_visitor.h
+++ b/src/glsl/ir_hierarchical_visitor.h
@@ -105,6 +105,17 @@ public:
     * that couldn't just be done in the visit method.
     */
    virtual ir_visitor_status visit(class ir_dereference_variable *);
+
+   /**
+    * In order to be consistent with ir_dereference_variable being treated as a
+    * leaf, phi nodes are treated as a leaf as well, even though it defines an
+    * ir_variable.
+    */
+   virtual ir_visitor_status visit(class ir_phi *);
+   virtual ir_visitor_status visit(class ir_phi_if *);
+   virtual ir_visitor_status visit(class ir_phi_loop_begin *);
+   virtual ir_visitor_status visit(class ir_phi_loop_end *);
+
    /*@}*/
 
    /**
diff --git a/src/glsl/ir_hv_accept.cpp b/src/glsl/ir_hv_accept.cpp
index 2a1f70e..3b29e10 100644
--- a/src/glsl/ir_hv_accept.cpp
+++ b/src/glsl/ir_hv_accept.cpp
@@ -87,9 +87,23 @@ ir_loop::accept(ir_hierarchical_visitor *v)
    if (s != visit_continue)
       return (s == visit_continue_with_parent) ? visit_continue : s;
 
-   s = visit_list_elements(v, &this->body_instructions);
-   if (s == visit_stop)
-      return s;
+   if (s != visit_continue_with_parent) {
+      s = visit_list_elements(v, &this->begin_phi_nodes, false);
+      if (s == visit_stop)
+	 return s;
+   }
+
+   if (s != visit_continue_with_parent) {
+      s = visit_list_elements(v, &this->body_instructions);
+      if (s == visit_stop)
+	 return s;
+   }
+
+   if (s != visit_continue_with_parent) {
+      s = visit_list_elements(v, &this->end_phi_nodes, false);
+      if (s == visit_stop)
+	 return s;
+   }
 
    return v->visit_leave(this);
 }
@@ -399,9 +413,44 @@ ir_if::accept(ir_hierarchical_visitor *v)
 	 return s;
    }
 
+   if (s != visit_continue_with_parent) {
+      s = visit_list_elements(v, &this->phi_nodes, false);
+      if (s == visit_stop)
+	 return s;
+   }
+
    return v->visit_leave(this);
 }
 
+
+ir_visitor_status
+ir_phi::accept(ir_hierarchical_visitor *v)
+{
+   return v->visit(this);
+}
+
+
+ir_visitor_status
+ir_phi_if::accept(ir_hierarchical_visitor *v)
+{
+   return v->visit(this);
+}
+
+
+ir_visitor_status
+ir_phi_loop_begin::accept(ir_hierarchical_visitor *v)
+{
+   return v->visit(this);
+}
+
+
+ir_visitor_status
+ir_phi_loop_end::accept(ir_hierarchical_visitor *v)
+{
+   return v->visit(this);
+}
+
+
 ir_visitor_status
 ir_emit_vertex::accept(ir_hierarchical_visitor *v)
 {
diff --git a/src/glsl/ir_print_visitor.cpp b/src/glsl/ir_print_visitor.cpp
index 9357821..1491109 100644
--- a/src/glsl/ir_print_visitor.cpp
+++ b/src/glsl/ir_print_visitor.cpp
@@ -124,6 +124,29 @@ ir_print_visitor::unique_name(ir_variable *var)
    return name;
 }
 
+const char *
+ir_print_visitor::unique_name(ir_loop_jump *jump)
+{
+   const char *name = (const char *) hash_table_find(this->printable_names, jump);
+   if (name != NULL)
+      return name;
+
+   if (jump->is_continue()) {
+      static unsigned i = 0;
+      name = ralloc_asprintf(this->mem_ctx, "cont@%u", ++i);
+   } else if (jump->is_break()) {
+      static unsigned i = 0;
+      name = ralloc_asprintf(this->mem_ctx, "break@%u", ++i);
+   } else {
+      assert(!"shouldn't get here");
+      name = NULL;
+   }
+   hash_table_insert(this->printable_names, (void *) name, jump);
+
+   return name;
+}
+
+
 static void
 print_type(const glsl_type *t)
 {
@@ -153,7 +176,8 @@ void ir_print_visitor::visit(ir_variable *ir)
    const char *const inv = (ir->data.invariant) ? "invariant " : "";
    const char *const mode[] = { "", "uniform ", "shader_in ", "shader_out ",
                                 "in ", "out ", "inout ",
-			        "const_in ", "sys ", "temporary " };
+			        "const_in ", "sys ", "temporary ",
+			        "temporary_ssa " };
    STATIC_ASSERT(ARRAY_SIZE(mode) == ir_var_mode_count);
    const char *const interp[] = { "", "smooth", "flat", "noperspective" };
    STATIC_ASSERT(ARRAY_SIZE(interp) == INTERP_QUALIFIER_COUNT);
@@ -338,7 +362,14 @@ void ir_print_visitor::visit(ir_swizzle *ir)
 void ir_print_visitor::visit(ir_dereference_variable *ir)
 {
    ir_variable *var = ir->variable_referenced();
-   printf("(var_ref %s) ", unique_name(var));
+   printf("(var_ref ");
+   if (var->data.mode == ir_var_temporary_ssa
+       && var->ssa_assignment && ir == var->ssa_assignment->lhs) {
+      var->accept(this);
+      printf(")");
+   } else {
+      printf(" %s) ", unique_name(var));
+   }
 }
 
 
@@ -513,6 +544,25 @@ ir_print_visitor::visit(ir_if *ir)
       }
       indentation--;
       indent();
+      printf(")\n");
+   } else {
+      printf("()\n");
+   }
+
+   indent();
+   if (!ir->phi_nodes.is_empty()) {
+      printf("(\n");
+      indentation++;
+
+      foreach_list(n, &ir->phi_nodes) {
+	 ir_phi_if *const phi = (ir_phi_if *) n;
+
+	 indent();
+	 phi->accept(this);
+	 printf("\n");
+      }
+      indentation--;
+      indent();
       printf("))\n");
    } else {
       printf("())\n");
@@ -523,9 +573,26 @@ ir_print_visitor::visit(ir_if *ir)
 void
 ir_print_visitor::visit(ir_loop *ir)
 {
-   printf("(loop (\n");
-   indentation++;
+   printf("(loop (");
+
+   if(!ir->begin_phi_nodes.is_empty()) {
+      indentation++;
+      printf("\n");
+      foreach_list(n, &ir->begin_phi_nodes) {
+	 ir_phi_loop_begin *const phi = (ir_phi_loop_begin *) n;
 
+	 indent();
+	 phi->accept(this);
+	 printf("\n");
+      }
+      indentation--;
+      indent();
+   }
+   printf(")\n");
+
+   indent();
+   printf("(\n");
+   indentation++;
    foreach_list(n, &ir->body_instructions) {
       ir_instruction *const inst = (ir_instruction *) n;
 
@@ -535,14 +602,131 @@ ir_print_visitor::visit(ir_loop *ir)
    }
    indentation--;
    indent();
-   printf("))\n");
+   printf(")\n");
+
+   indent();
+   if (!ir->end_phi_nodes.is_empty()) {
+      printf("(\n");
+      indentation++;
+
+      foreach_list(n, &ir->end_phi_nodes) {
+	 ir_phi_loop_end *const phi = (ir_phi_loop_end *) n;
+
+	 indent();
+	 phi->accept(this);
+	 printf("\n");
+      }
+      indentation--;
+      indent();
+      printf("))\n");
+   } else {
+      printf("())\n");
+   }
+}
+
+
+void
+ir_print_visitor::visit(ir_phi *ir)
+{
+   printf("error");
+}
+
+
+void
+ir_print_visitor::visit(ir_phi_if *ir)
+{
+   printf("(phi\n");
+
+   indentation++;
+   indent();
+   ir->dest->accept(this);
+   printf("\n");
+
+   indent();
+   if (ir->if_src) {
+      printf("%s ", unique_name(ir->if_src));
+   } else {
+      printf("(undefined) ");
+   }
+   if (ir->else_src) {
+      printf("%s)", unique_name(ir->else_src));
+   } else {
+      printf("(undefined))");
+   }
+   indentation--;
+}
+
+void
+ir_print_visitor::print_phi_jump_src(ir_phi_jump_src *src)
+{
+   printf("(%s ", unique_name(src->jump));
+   if (src->src) {
+      printf(" %s)\n", unique_name(src->src));
+   } else {
+      printf(" (undefined))\n");
+   }
+}
+
+void
+ir_print_visitor::visit(ir_phi_loop_begin *ir)
+{
+   printf("(phi\n");
+
+   indentation++;
+   indent();
+   ir->dest->accept(this);
+   printf("\n");
+
+   indent();
+   if (ir->enter_src) {
+      printf("%s ", unique_name(ir->enter_src));
+   } else {
+      printf("(undefined) ");
+   }
+   if (ir->repeat_src) {
+      printf("%s\n", unique_name(ir->repeat_src));
+   } else {
+      printf("(undefined)\n");
+   }
+
+   foreach_list(n, &ir->continue_srcs) {
+      ir_phi_jump_src *src = (ir_phi_jump_src *) n;
+      indent();
+      print_phi_jump_src(src);
+   }
+
+   indentation--;
+   indent();
+   printf(")");
+}
+
+
+void
+ir_print_visitor::visit(ir_phi_loop_end *ir)
+{
+   printf("(phi\n");
+
+   indentation++;
+   indent();
+   ir->dest->accept(this);
+   printf("\n");
+
+   foreach_list(n, &ir->break_srcs) {
+      ir_phi_jump_src *src = (ir_phi_jump_src *) n;
+      indent();
+      print_phi_jump_src(src);
+   }
+
+   indentation--;
+   indent();
+   printf(")");
 }
 
 
 void
 ir_print_visitor::visit(ir_loop_jump *ir)
 {
-   printf("%s", ir->is_break() ? "break" : "continue");
+   printf("(%s %s)", ir->is_break() ? "break" : "continue", unique_name(ir));
 }
 
 void
diff --git a/src/glsl/ir_print_visitor.h b/src/glsl/ir_print_visitor.h
index 865376f..32fdcc1 100644
--- a/src/glsl/ir_print_visitor.h
+++ b/src/glsl/ir_print_visitor.h
@@ -69,6 +69,10 @@ public:
    virtual void visit(ir_if *);
    virtual void visit(ir_loop *);
    virtual void visit(ir_loop_jump *);
+   virtual void visit(ir_phi *);
+   virtual void visit(ir_phi_if *);
+   virtual void visit(ir_phi_loop_begin *);
+   virtual void visit(ir_phi_loop_end *);
    virtual void visit(ir_emit_vertex *);
    virtual void visit(ir_end_primitive *);
    /*@}*/
@@ -82,6 +86,17 @@ private:
     */
    const char *unique_name(ir_variable *var);
 
+   /**
+    * Fetch/generate a unique name for ir_loop_jump.
+    *
+    * ir_phi_loop_begin and ir_phi_loop_end refer to specific ir_loop_jump's, so
+    * we need a name to know which is which in the printout, even though there
+    * is no name associated with the jump in the IR.
+    */
+   const char *unique_name(ir_loop_jump *jump);
+
+   void print_phi_jump_src(ir_phi_jump_src *src);
+
    /** A mapping from ir_variable * -> unique printable names. */
    hash_table *printable_names;
    _mesa_symbol_table *symbols;
diff --git a/src/glsl/ir_validate.cpp b/src/glsl/ir_validate.cpp
index 527acea..5c43ecc 100644
--- a/src/glsl/ir_validate.cpp
+++ b/src/glsl/ir_validate.cpp
@@ -61,6 +61,10 @@ public:
    virtual ir_visitor_status visit(ir_variable *v);
    virtual ir_visitor_status visit(ir_dereference_variable *ir);
 
+   virtual ir_visitor_status visit(ir_phi_if *ir);
+   virtual ir_visitor_status visit(ir_phi_loop_begin *ir);
+   virtual ir_visitor_status visit(ir_phi_loop_end *ir);
+
    virtual ir_visitor_status visit_enter(ir_if *ir);
 
    virtual ir_visitor_status visit_enter(ir_function *ir);
@@ -84,21 +88,29 @@ public:
 
 } /* anonymous namespace */
 
-ir_visitor_status
-ir_validate::visit(ir_dereference_variable *ir)
+
+static void
+validate_var_use(ir_variable *var, struct hash_table *ht, void *ptr,
+		 const char *name)
 {
-   if ((ir->var == NULL) || (ir->var->as_variable() == NULL)) {
-      printf("ir_dereference_variable @ %p does not specify a variable %p\n",
-	     (void *) ir, (void *) ir->var);
+   if ((var == NULL) || (var->as_variable() == NULL)) {
+      printf("%s @ %p does not specify a variable %p\n",
+	     name, ptr, (void *) var);
       abort();
    }
 
-   if (hash_table_find(ht, ir->var) == NULL) {
-      printf("ir_dereference_variable @ %p specifies undeclared variable "
+   if (hash_table_find(ht, var) == NULL) {
+      printf("%s @ %p specifies undeclared variable "
 	     "`%s' @ %p\n",
-	     (void *) ir, ir->var->name, (void *) ir->var);
+	     name, ptr, var->name, (void *) var);
       abort();
    }
+}
+
+ir_visitor_status
+ir_validate::visit(ir_dereference_variable *ir)
+{
+   validate_var_use(ir->var, this->ht, (void *) ir, "ir_dereference_variable");
 
    this->validate_ir(ir, this->data);
 
@@ -132,6 +144,116 @@ ir_validate::visit_enter(class ir_dereference_array *ir)
    return visit_continue;
 }
 
+static void
+validate_ssa_def(ir_variable *var, ir_instruction *ir, struct hash_table *ht)
+{
+   if (hash_table_find(ht, var)) {
+      printf("SSA variable `%s' at %p assigned more than once\n",
+	     var->name, (void *) var);
+      if (var->ssa_assignment != NULL) {
+	 printf("First assignment at ir_assignment %p:\n",
+	        (void *) var->ssa_assignment);
+	 var->ssa_assignment->print();
+      } else if (var->ssa_phi != NULL) {
+	 printf("First assignment at ir_phi %p:\n",
+		(void *) var->ssa_phi);
+	 var->ssa_phi->print();
+      } else {
+	 printf("First assignment at ir_call %p:\n",
+		(void *) var->ssa_call);
+	 var->ssa_call->print();
+      }
+      printf("\nSecond assignment at %p:\n", (void *) ir);
+      ir->print();
+      printf("\n");
+      abort();
+   }
+   hash_table_insert(ht, var, var);
+}
+
+static void
+validate_phi_dest(ir_phi *ir, struct hash_table *ht)
+{
+   if ((ir->dest == NULL) || (ir->dest->as_variable() == NULL)) {
+      printf("ir_phi @ %p does not specify a destination %p\n",
+	     (void *) ir, (void *) ir->dest);
+      abort();
+   }
+
+   validate_ssa_def(ir->dest, ir, ht);
+
+   assert(ir->dest->ssa_phi == ir);
+   assert(ir->dest->ssa_assignment == NULL);
+   assert(ir->dest->ssa_call == NULL);
+}
+
+ir_visitor_status
+ir_validate::visit(ir_phi_if *ir)
+{
+   validate_phi_dest(ir, this->ht);
+
+   if (ir->if_src) {
+      validate_var_use(ir->if_src, this->ht, (void *) ir, "ir_phi_if");
+   }
+
+   if (ir->else_src) {
+      validate_var_use(ir->else_src, this->ht, (void *) ir, "ir_phi_if");
+   }
+
+   return visit_continue;
+}
+
+
+ir_visitor_status
+ir_validate::visit(ir_phi_loop_begin *ir)
+{
+   validate_phi_dest(ir, this->ht);
+
+   /*
+    * note: ir_phi_loop_begin is a special case in that variables may be, and in
+    * fact normally are, defined *after* they are used in everything except
+    * enter_src
+    */
+
+   if (ir->enter_src) {
+      validate_var_use(ir->enter_src, this->ht, (void *) ir, "ir_phi_loop_begin");
+   }
+
+   if ((ir->repeat_src == NULL) && (ir->repeat_src->as_variable() == NULL)) {
+      printf("ir_phi_loop_begin @ %p does not specify a variable %p\n",
+	    (void *) ir, (void *) ir->repeat_src);
+      abort();
+   }
+
+   foreach_list(n, &ir->continue_srcs) {
+      ir_phi_jump_src *src = (ir_phi_jump_src *) n;
+
+      if ((src->src == NULL) && (src->src->as_variable() == NULL)) {
+	 printf("ir_phi_loop_begin @ %p does not specify a variable %p\n",
+	       (void *) ir, (void *) src->src);
+	 abort();
+      }
+   }
+
+   return visit_continue;
+}
+
+ir_visitor_status
+ir_validate::visit(ir_phi_loop_end *ir)
+{
+   validate_phi_dest(ir, this->ht);
+
+   foreach_list(n, &ir->break_srcs) {
+      ir_phi_jump_src *src = (ir_phi_jump_src *) n;
+      if (src->src) {
+	 validate_var_use(src->src, this->ht, (void *) ir, "ir_phi_loop_end");
+      }
+   }
+
+   return visit_continue;
+}
+
+
 ir_visitor_status
 ir_validate::visit_enter(ir_if *ir)
 {
@@ -684,6 +806,8 @@ ir_visitor_status
 ir_validate::visit_enter(ir_assignment *ir)
 {
    const ir_dereference *const lhs = ir->lhs;
+   ir_variable *var = lhs->variable_referenced();
+
    if (lhs->type->is_scalar() || lhs->type->is_vector()) {
       if (ir->write_mask == 0) {
 	 printf("Assignment LHS is %s, but write mask is 0:\n",
@@ -707,6 +831,15 @@ ir_validate::visit_enter(ir_assignment *ir)
       }
    }
 
+   if (var && var->data.mode == ir_var_temporary_ssa) {
+      validate_ssa_def(var, ir, this->ht);
+
+      assert(var->ssa_assignment == ir);
+      assert(var->ssa_phi == NULL);
+      assert(var->ssa_call == NULL);
+      assert(ir->write_mask == (1 << var->type->vector_elements) - 1);
+   }
+
    this->validate_ir(ir, this->data);
 
    return visit_continue;
@@ -728,6 +861,15 @@ ir_validate::visit_enter(ir_call *ir)
 	        callee->return_type->name, ir->return_deref->type->name);
 	 abort();
       }
+
+      ir_variable *return_var = ir->return_deref->variable_referenced();
+      if (return_var != NULL
+	  && return_var->data.mode == ir_var_temporary_ssa) {
+	 validate_ssa_def(return_var, ir, this->ht);
+	 assert(return_var->ssa_assignment == NULL);
+	 assert(return_var->ssa_phi == NULL);
+	 assert(return_var->ssa_call == ir);
+      }
    } else if (callee->return_type != glsl_type::void_type) {
       printf("ir_call has non-void callee but no return storage\n");
       abort();
diff --git a/src/glsl/ir_visitor.h b/src/glsl/ir_visitor.h
index 40f96ff..b9d6297 100644
--- a/src/glsl/ir_visitor.h
+++ b/src/glsl/ir_visitor.h
@@ -62,6 +62,10 @@ public:
    virtual void visit(class ir_discard *) = 0;
    virtual void visit(class ir_if *) = 0;
    virtual void visit(class ir_loop *) = 0;
+   virtual void visit(class ir_phi *) = 0;
+   virtual void visit(class ir_phi_if *) = 0;
+   virtual void visit(class ir_phi_loop_begin *) = 0;
+   virtual void visit(class ir_phi_loop_end *) = 0;
    virtual void visit(class ir_loop_jump *) = 0;
    virtual void visit(class ir_emit_vertex *) = 0;
    virtual void visit(class ir_end_primitive *) = 0;
@@ -83,6 +87,10 @@ public:
    virtual void visit(class ir_assignment *) {}
    virtual void visit(class ir_constant *) {}
    virtual void visit(class ir_call *) {}
+   virtual void visit(class ir_phi *) {}
+   virtual void visit(class ir_phi_if *) {}
+   virtual void visit(class ir_phi_loop_begin *) {}
+   virtual void visit(class ir_phi_loop_end *) {}
    virtual void visit(class ir_emit_vertex *) {}
    virtual void visit(class ir_end_primitive *) {}
 };
diff --git a/src/mesa/drivers/dri/i965/brw_fs.h b/src/mesa/drivers/dri/i965/brw_fs.h
index a903908..d641162 100644
--- a/src/mesa/drivers/dri/i965/brw_fs.h
+++ b/src/mesa/drivers/dri/i965/brw_fs.h
@@ -226,6 +226,10 @@ public:
    void visit(ir_call *ir);
    void visit(ir_function *ir);
    void visit(ir_function_signature *ir);
+   void visit(ir_phi *ir);
+   void visit(ir_phi_if *ir);
+   void visit(ir_phi_loop_begin *ir);
+   void visit(ir_phi_loop_end *ir);
    void visit(ir_emit_vertex *);
    void visit(ir_end_primitive *);
 
diff --git a/src/mesa/drivers/dri/i965/brw_fs_visitor.cpp b/src/mesa/drivers/dri/i965/brw_fs_visitor.cpp
index 69ca940..57f994b 100644
--- a/src/mesa/drivers/dri/i965/brw_fs_visitor.cpp
+++ b/src/mesa/drivers/dri/i965/brw_fs_visitor.cpp
@@ -2309,6 +2309,34 @@ fs_visitor::visit(ir_function_signature *ir)
 }
 
 void
+fs_visitor::visit(ir_phi *ir)
+{
+   assert(!"not reached");
+   (void)ir;
+}
+
+void
+fs_visitor::visit(ir_phi_if *ir)
+{
+   assert(!"not reached");
+   (void)ir;
+}
+
+void
+fs_visitor::visit(ir_phi_loop_begin *ir)
+{
+   assert(!"not reached");
+   (void)ir;
+}
+
+void
+fs_visitor::visit(ir_phi_loop_end *ir)
+{
+   assert(!"not reached");
+   (void)ir;
+}
+
+void
 fs_visitor::visit(ir_emit_vertex *)
 {
    assert(!"not reached");
diff --git a/src/mesa/drivers/dri/i965/brw_vec4.h b/src/mesa/drivers/dri/i965/brw_vec4.h
index 4a5b577..0ba1140 100644
--- a/src/mesa/drivers/dri/i965/brw_vec4.h
+++ b/src/mesa/drivers/dri/i965/brw_vec4.h
@@ -318,6 +318,10 @@ public:
    virtual void visit(ir_discard *);
    virtual void visit(ir_texture *);
    virtual void visit(ir_if *);
+   virtual void visit(ir_phi *);
+   virtual void visit(ir_phi_if *);
+   virtual void visit(ir_phi_loop_begin *);
+   virtual void visit(ir_phi_loop_end *);
    virtual void visit(ir_emit_vertex *);
    virtual void visit(ir_end_primitive *);
    /*@}*/
diff --git a/src/mesa/drivers/dri/i965/brw_vec4_visitor.cpp b/src/mesa/drivers/dri/i965/brw_vec4_visitor.cpp
index 15a6cbd..eb4af54 100644
--- a/src/mesa/drivers/dri/i965/brw_vec4_visitor.cpp
+++ b/src/mesa/drivers/dri/i965/brw_vec4_visitor.cpp
@@ -2621,6 +2621,30 @@ vec4_visitor::visit(ir_if *ir)
 }
 
 void
+vec4_visitor::visit(ir_phi *)
+{
+   assert(!"not reached");
+}
+
+void
+vec4_visitor::visit(ir_phi_if *)
+{
+   assert(!"not reached");
+}
+
+void
+vec4_visitor::visit(ir_phi_loop_begin *)
+{
+   assert(!"not reached");
+}
+
+void
+vec4_visitor::visit(ir_phi_loop_end *)
+{
+   assert(!"not reached");
+}
+
+void
 vec4_visitor::visit(ir_emit_vertex *)
 {
    assert(!"not reached");
diff --git a/src/mesa/program/ir_to_mesa.cpp b/src/mesa/program/ir_to_mesa.cpp
index 74c512b..54223bb 100644
--- a/src/mesa/program/ir_to_mesa.cpp
+++ b/src/mesa/program/ir_to_mesa.cpp
@@ -261,6 +261,10 @@ public:
    virtual void visit(ir_discard *);
    virtual void visit(ir_texture *);
    virtual void visit(ir_if *);
+   virtual void visit(ir_phi *);
+   virtual void visit(ir_phi_if *);
+   virtual void visit(ir_phi_loop_begin *);
+   virtual void visit(ir_phi_loop_end *);
    virtual void visit(ir_emit_vertex *);
    virtual void visit(ir_end_primitive *);
    /*@}*/
@@ -2227,6 +2231,30 @@ ir_to_mesa_visitor::visit(ir_if *ir)
 }
 
 void
+ir_to_mesa_visitor::visit(ir_phi *ir)
+{
+   assert(!"Should not get here.");
+}
+
+void
+ir_to_mesa_visitor::visit(ir_phi_if *ir)
+{
+   assert(!"Should not get here.");
+}
+
+void
+ir_to_mesa_visitor::visit(ir_phi_loop_begin *ir)
+{
+   assert(!"Should not get here.");
+}
+
+void
+ir_to_mesa_visitor::visit(ir_phi_loop_end *ir)
+{
+   assert(!"Should not get here.");
+}
+
+void
 ir_to_mesa_visitor::visit(ir_emit_vertex *ir)
 {
    assert(!"Geometry shaders not supported.");
diff --git a/src/mesa/state_tracker/st_glsl_to_tgsi.cpp b/src/mesa/state_tracker/st_glsl_to_tgsi.cpp
index 0871dd0..61f6b28 100644
--- a/src/mesa/state_tracker/st_glsl_to_tgsi.cpp
+++ b/src/mesa/state_tracker/st_glsl_to_tgsi.cpp
@@ -379,6 +379,10 @@ public:
    virtual void visit(ir_discard *);
    virtual void visit(ir_texture *);
    virtual void visit(ir_if *);
+   virtual void visit(ir_phi *);
+   virtual void visit(ir_phi_if *);
+   virtual void visit(ir_phi_loop_begin *);
+   virtual void visit(ir_phi_loop_end *);
    virtual void visit(ir_emit_vertex *);
    virtual void visit(ir_end_primitive *);
    /*@}*/
@@ -3011,6 +3015,31 @@ glsl_to_tgsi_visitor::visit(ir_if *ir)
 
 
 void
+glsl_to_tgsi_visitor::visit(ir_phi *ir)
+{
+   assert(!"Should not get here");
+}
+
+void
+glsl_to_tgsi_visitor::visit(ir_phi_if *ir)
+{
+   assert(!"SSA not supported");
+}
+
+void
+glsl_to_tgsi_visitor::visit(ir_phi_loop_begin *ir)
+{
+   assert(!"SSA not supported");
+}
+
+void
+glsl_to_tgsi_visitor::visit(ir_phi_loop_end *ir)
+{
+   assert(!"SSA not supported");
+}
+
+
+void
 glsl_to_tgsi_visitor::visit(ir_emit_vertex *ir)
 {
    assert(this->prog->Target == GL_GEOMETRY_PROGRAM_NV);
-- 
1.8.3.1



More information about the mesa-dev mailing list