glsl: fix the type of ir_constant_data::u16
[mesa.git] / src / compiler / glsl / lower_if_to_cond_assign.cpp
index 6a7034794b2c565587f652b257a4156e79256714..ca61f1d52739f9eace58a5a25a2392cbebccd4c6 100644 (file)
 /**
  * \file lower_if_to_cond_assign.cpp
  *
- * This attempts to flatten if-statements to conditional assignments for
- * GPUs with limited or no flow control support.
+ * This flattens if-statements to conditional assignments if:
+ *
+ * - the GPU has limited or no flow control support
+ *   (controlled by max_depth)
+ *
+ * - small conditional branches are more expensive than conditional assignments
+ *   (controlled by min_branch_cost, that's the cost for a branch to be
+ *    preserved)
  *
  * It can't handle other control flow being inside of its block, such
  * as calls or loops.  Hopefully loop unrolling and inlining will take
 
 #include "compiler/glsl_types.h"
 #include "ir.h"
-#include "program/hash_table.h"
+#include "util/set.h"
+#include "util/hash_table.h" /* Needed for the hashing functions */
+#include "main/macros.h" /* for MAX2 */
 
 namespace {
 
 class ir_if_to_cond_assign_visitor : public ir_hierarchical_visitor {
 public:
-   ir_if_to_cond_assign_visitor(unsigned max_depth)
+   ir_if_to_cond_assign_visitor(gl_shader_stage stage,
+                                unsigned max_depth,
+                                unsigned min_branch_cost)
    {
       this->progress = false;
+      this->stage = stage;
       this->max_depth = max_depth;
+      this->min_branch_cost = min_branch_cost;
       this->depth = 0;
 
-      this->condition_variables = hash_table_ctor(0, hash_table_pointer_hash,
-                                                 hash_table_pointer_compare);
+      this->condition_variables = _mesa_pointer_set_create(NULL);
    }
 
    ~ir_if_to_cond_assign_visitor()
    {
-      hash_table_dtor(this->condition_variables);
+      _mesa_set_destroy(this->condition_variables, NULL);
    }
 
    ir_visitor_status visit_enter(ir_if *);
    ir_visitor_status visit_leave(ir_if *);
 
+   bool found_unsupported_op;
+   bool found_expensive_op;
+   bool found_dynamic_arrayref;
+   bool is_then;
    bool progress;
+   gl_shader_stage stage;
+   unsigned then_cost;
+   unsigned else_cost;
+   unsigned min_branch_cost;
    unsigned max_depth;
    unsigned depth;
 
-   struct hash_table *condition_variables;
+   struct set *condition_variables;
 };
 
 } /* anonymous namespace */
 
 bool
-lower_if_to_cond_assign(exec_list *instructions, unsigned max_depth)
+lower_if_to_cond_assign(gl_shader_stage stage, exec_list *instructions,
+                        unsigned max_depth, unsigned min_branch_cost)
 {
    if (max_depth == UINT_MAX)
       return false;
 
-   ir_if_to_cond_assign_visitor v(max_depth);
+   ir_if_to_cond_assign_visitor v(stage, max_depth, min_branch_cost);
 
    visit_list_elements(&v, instructions);
 
    return v.progress;
 }
 
-void
-check_control_flow(ir_instruction *ir, void *data)
+static void
+check_ir_node(ir_instruction *ir, void *data)
 {
-   bool *found_control_flow = (bool *)data;
+   ir_if_to_cond_assign_visitor *v = (ir_if_to_cond_assign_visitor *)data;
+
    switch (ir->ir_type) {
    case ir_type_call:
    case ir_type_discard:
    case ir_type_loop:
    case ir_type_loop_jump:
    case ir_type_return:
-      *found_control_flow = true;
+   case ir_type_emit_vertex:
+   case ir_type_end_primitive:
+   case ir_type_barrier:
+      v->found_unsupported_op = true;
+      break;
+
+   case ir_type_dereference_variable: {
+      ir_variable *var = ir->as_dereference_variable()->variable_referenced();
+
+      /* Lowering branches with TCS output accesses breaks many piglit tests,
+       * so don't touch them for now.
+       */
+      if (v->stage == MESA_SHADER_TESS_CTRL &&
+          var->data.mode == ir_var_shader_out)
+         v->found_unsupported_op = true;
+      break;
+   }
+
+   /* SSBO, images, atomic counters are handled by ir_type_call */
+   case ir_type_texture:
+      v->found_expensive_op = true;
       break;
+
+   case ir_type_dereference_array: {
+      ir_dereference_array *deref = ir->as_dereference_array();
+
+      if (deref->array_index->ir_type != ir_type_constant)
+         v->found_dynamic_arrayref = true;
+   } /* fall-through */
+   case ir_type_expression:
+   case ir_type_dereference_record:
+      if (v->is_then)
+         v->then_cost++;
+      else
+         v->else_cost++;
+      break;
+
    default:
       break;
    }
 }
 
-void
+static void
 move_block_to_cond_assign(void *mem_ctx,
-                         ir_if *if_ir, ir_rvalue *cond_expr,
-                         exec_list *instructions,
-                         struct hash_table *ht)
+                          ir_if *if_ir, ir_rvalue *cond_expr,
+                          exec_list *instructions,
+                          struct set *set)
 {
    foreach_in_list_safe(ir_instruction, ir, instructions) {
       if (ir->ir_type == ir_type_assignment) {
-        ir_assignment *assign = (ir_assignment *)ir;
-
-        if (hash_table_find(ht, assign) == NULL) {
-           hash_table_insert(ht, assign, assign);
-
-           /* If the LHS of the assignment is a condition variable that was
-            * previously added, insert an additional assignment of false to
-            * the variable.
-            */
-           const bool assign_to_cv =
-              hash_table_find(ht, assign->lhs->variable_referenced()) != NULL;
-
-           if (!assign->condition) {
-              if (assign_to_cv) {
-                 assign->rhs =
-                    new(mem_ctx) ir_expression(ir_binop_logic_and,
-                                               glsl_type::bool_type,
-                                               cond_expr->clone(mem_ctx, NULL),
-                                               assign->rhs);
-              } else {
-                 assign->condition = cond_expr->clone(mem_ctx, NULL);
-              }
-           } else {
-              assign->condition =
-                 new(mem_ctx) ir_expression(ir_binop_logic_and,
-                                            glsl_type::bool_type,
-                                            cond_expr->clone(mem_ctx, NULL),
-                                            assign->condition);
-           }
-        }
+         ir_assignment *assign = (ir_assignment *)ir;
+
+         if (_mesa_set_search(set, assign) == NULL) {
+            _mesa_set_add(set, assign);
+
+            /* If the LHS of the assignment is a condition variable that was
+             * previously added, insert an additional assignment of false to
+             * the variable.
+             */
+            const bool assign_to_cv =
+               _mesa_set_search(
+                  set, assign->lhs->variable_referenced()) != NULL;
+
+            if (!assign->condition) {
+               if (assign_to_cv) {
+                  assign->rhs =
+                     new(mem_ctx) ir_expression(ir_binop_logic_and,
+                                                glsl_type::bool_type,
+                                                cond_expr->clone(mem_ctx, NULL),
+                                                assign->rhs);
+               } else {
+                  assign->condition = cond_expr->clone(mem_ctx, NULL);
+               }
+            } else {
+               assign->condition =
+                  new(mem_ctx) ir_expression(ir_binop_logic_and,
+                                             glsl_type::bool_type,
+                                             cond_expr->clone(mem_ctx, NULL),
+                                             assign->condition);
+            }
+         }
       }
 
       /* Now, move from the if block to the block surrounding it. */
@@ -157,9 +214,8 @@ move_block_to_cond_assign(void *mem_ctx,
 }
 
 ir_visitor_status
-ir_if_to_cond_assign_visitor::visit_enter(ir_if *ir)
+ir_if_to_cond_assign_visitor::visit_enter(ir_if *)
 {
-   (void) ir;
    this->depth++;
 
    return visit_continue;
@@ -168,21 +224,46 @@ ir_if_to_cond_assign_visitor::visit_enter(ir_if *ir)
 ir_visitor_status
 ir_if_to_cond_assign_visitor::visit_leave(ir_if *ir)
 {
+   bool must_lower = this->depth-- > this->max_depth;
+
    /* Only flatten when beyond the GPU's maximum supported nesting depth. */
-   if (this->depth-- <= this->max_depth)
+   if (!must_lower && this->min_branch_cost == 0)
       return visit_continue;
 
-   bool found_control_flow = false;
+   this->found_unsupported_op = false;
+   this->found_expensive_op = false;
+   this->found_dynamic_arrayref = false;
+   this->then_cost = 0;
+   this->else_cost = 0;
+
    ir_assignment *assign;
 
    /* Check that both blocks don't contain anything we can't support. */
+   this->is_then = true;
    foreach_in_list(ir_instruction, then_ir, &ir->then_instructions) {
-      visit_tree(then_ir, check_control_flow, &found_control_flow);
+      visit_tree(then_ir, check_ir_node, this);
    }
+
+   this->is_then = false;
    foreach_in_list(ir_instruction, else_ir, &ir->else_instructions) {
-      visit_tree(else_ir, check_control_flow, &found_control_flow);
+      visit_tree(else_ir, check_ir_node, this);
    }
-   if (found_control_flow)
+
+   if (this->found_unsupported_op)
+      return visit_continue; /* can't handle inner unsupported opcodes */
+
+   /* Skip if the branch cost is high enough or if there's an expensive op.
+    *
+    * Also skip if non-constant array indices were encountered, since those
+    * can be out-of-bounds for a not-taken branch, and so generating an
+    * assignment would be incorrect. In the case of must_lower, it's up to the
+    * backend to deal with any potential fall-out (perhaps by translating the
+    * assignments to hardware-predicated moves).
+    */
+   if (!must_lower &&
+       (this->found_expensive_op ||
+        this->found_dynamic_arrayref ||
+        MAX2(this->then_cost, this->else_cost) >= this->min_branch_cost))
       return visit_continue;
 
    void *mem_ctx = ralloc_parent(ir);
@@ -193,8 +274,8 @@ ir_if_to_cond_assign_visitor::visit_leave(ir_if *ir)
     */
    ir_variable *const then_var =
       new(mem_ctx) ir_variable(glsl_type::bool_type,
-                              "if_to_cond_assign_then",
-                              ir_var_temporary);
+                               "if_to_cond_assign_then",
+                               ir_var_temporary);
    ir->insert_before(then_var);
 
    ir_dereference_variable *then_cond =
@@ -204,13 +285,13 @@ ir_if_to_cond_assign_visitor::visit_leave(ir_if *ir)
    ir->insert_before(assign);
 
    move_block_to_cond_assign(mem_ctx, ir, then_cond,
-                            &ir->then_instructions,
-                            this->condition_variables);
+                             &ir->then_instructions,
+                             this->condition_variables);
 
    /* Add the new condition variable to the hash table.  This allows us to
     * find this variable when lowering other (enclosing) if-statements.
     */
-   hash_table_insert(this->condition_variables, then_var, then_var);
+   _mesa_set_add(this->condition_variables, then_var);
 
    /* If there are instructions in the else-clause, store the inverse of the
     * condition to a variable.  Move all of the instructions from the
@@ -219,29 +300,29 @@ ir_if_to_cond_assign_visitor::visit_leave(ir_if *ir)
     */
    if (!ir->else_instructions.is_empty()) {
       ir_variable *const else_var =
-        new(mem_ctx) ir_variable(glsl_type::bool_type,
-                                 "if_to_cond_assign_else",
-                                 ir_var_temporary);
+         new(mem_ctx) ir_variable(glsl_type::bool_type,
+                                  "if_to_cond_assign_else",
+                                  ir_var_temporary);
       ir->insert_before(else_var);
 
       ir_dereference_variable *else_cond =
-        new(mem_ctx) ir_dereference_variable(else_var);
+         new(mem_ctx) ir_dereference_variable(else_var);
 
       ir_rvalue *inverse =
-        new(mem_ctx) ir_expression(ir_unop_logic_not,
-                                   then_cond->clone(mem_ctx, NULL));
+         new(mem_ctx) ir_expression(ir_unop_logic_not,
+                                    then_cond->clone(mem_ctx, NULL));
 
       assign = new(mem_ctx) ir_assignment(else_cond, inverse);
       ir->insert_before(assign);
 
       move_block_to_cond_assign(mem_ctx, ir, else_cond,
-                               &ir->else_instructions,
-                               this->condition_variables);
+                                &ir->else_instructions,
+                                this->condition_variables);
 
       /* Add the new condition variable to the hash table.  This allows us to
        * find this variable when lowering other (enclosing) if-statements.
        */
-      hash_table_insert(this->condition_variables, else_var, else_var);
+      _mesa_set_add(this->condition_variables, else_var);
    }
 
    ir->remove();