glsl: Detect do-while-false loops and unroll them
authorIan Romanick <ian.d.romanick@intel.com>
Fri, 19 Feb 2016 02:08:58 +0000 (18:08 -0800)
committerIan Romanick <ian.d.romanick@intel.com>
Thu, 25 Feb 2016 02:43:40 +0000 (18:43 -0800)
Previously loops like

   do {
      // ...
   } while (false);

that did not have any other loop-branch instructions would not be
unrolled.  This is commonly used to wrap multiline preprocessor macros.

This produces IR like

    (loop (
       ...
       break
    ))

Since limiting_terminator was NULL, the loop unroller would
throw up its hands and say, "I don't know how many iterations.  How
can I unroll this?"

We can detect this another way.  If there is no limiting_terminator
and the only loop-branch is a break as the last IR, there's only one
iteration.

On my very old checkout of shader-db, this removes a loop from Orbital
Explorer, but it does not otherwise affect the shader.  The loop removed
is the one the compiler inserts surrounding the switch statement.

This change does prevent some seriously bad code generation in some
patches to meta shaders that I recently sent out for review.

Signed-off-by: Ian Romanick <ian.d.romanick@intel.com>
Reviewed-by: Timothy Arceri <timothy.arceri@collabora.com>
src/compiler/glsl/loop_unroll.cpp

index aea2743cdb18f69a2f4268861bdf4825490a23fa..bc377dff3b9b0ef1860ede39a1d7bb3866dc1e82 100644 (file)
@@ -315,11 +315,33 @@ loop_unroll_visitor::visit_leave(ir_loop *ir)
       return visit_continue;
    }
 
-   /* Don't try to unroll loops where the number of iterations is not known
-    * at compile-time.
-    */
-   if (ls->limiting_terminator == NULL)
+   if (ls->limiting_terminator == NULL) {
+      ir_instruction *last_ir =
+         (ir_instruction *) ir->body_instructions.get_tail();
+
+      /* If a loop has no induction variable and the last instruction is
+       * a break, unroll the loop with a count of 1.  This is the classic
+       *
+       *    do {
+       *        // ...
+       *    } while (false)
+       *
+       * that is used to wrap multi-line macros.
+       *
+       * If num_loop_jumps is not zero, last_ir cannot be NULL... there has to
+       * be at least num_loop_jumps instructions in the loop.
+       */
+      if (ls->num_loop_jumps == 1 && is_break(last_ir)) {
+         last_ir->remove();
+
+         simple_unroll(ir, 1);
+      }
+
+      /* Don't try to unroll loops where the number of iterations is not known
+       * at compile-time.
+       */
       return visit_continue;
+   }
 
    iterations = ls->limiting_terminator->iterations;