<div dir="ltr"><br><div class="gmail_extra"><br><div class="gmail_quote">On Tue, Dec 20, 2016 at 5:06 PM, Timothy Arceri <span dir="ltr"><<a href="mailto:timothy.arceri@collabora.com" target="_blank">timothy.arceri@collabora.com</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div class="HOEnZb"><div class="h5">On Tue, 2016-12-20 at 16:31 -0800, Jason Ekstrand wrote:<br>
> On Sun, Dec 18, 2016 at 9:47 PM, Timothy Arceri <timothy.arceri@colla<br>
> <a href="http://bora.com" rel="noreferrer" target="_blank">bora.com</a>> wrote:<br>
> > V2:<br>
> > - tidy ups suggested by Connor.<br>
> > - tidy up cloning logic and handle copy propagation<br>
> >  based of suggestion by Connor.<br>
> > - use nir_ssa_def_rewrite_uses to fix up lcssa phis<br>
> >   suggested by Connor.<br>
> > - add support for complex loop unrolling (two terminators)<br>
> > - handle case were the ssa defs use outside the loop is already a<br>
> > phi<br>
> > - support unrolling loops with multiple terminators when trip count<br>
> >   is know for each terminator<br>
> ><br>
> > V3:<br>
> > - set correct num_components when creating phi in complex unroll<br>
> > - rewrite update remap table based on Jasons suggestions.<br>
> > - remove unrequired extract_loop_body() helper as suggested by<br>
> > Jason.<br>
> > - simplify the lcssa phi fix up code for simple loops as per Jasons<br>
> > suggestions.<br>
> > - use mem context to keep track of hash table memory as suggested<br>
> > by Jason.<br>
> > - move is_{complex,simple}_loop helpers to the unroll code<br>
> > - require nir_metadata_block_index<br>
> > - partially rewrote complex unroll to be simpler and easier to<br>
> > follow.<br>
> ><br>
> > V4:<br>
> > - use rzalloc() when creating nir_phi_src but not setting pred<br>
> > right away<br>
> >  fixes regression cause by ralloc() no longer zeroing memory.<br>
> ><br>
> > V5:<br>
> > - simplify calling of complex_unroll()<br>
> > - use new loop terminator fields to get the break/continue from<br>
> > blocks<br>
> >   and simplify loop unrolling code<br>
> > - handle slightly less trivial loop terminators. if branches can<br>
> >   now have instructions but can only contain a single block.<br>
> > - use nir print type IR snippets in unroll function descriptions<br>
> > - add better explanation and variable for why we need to clone<br>
> >   additional times when the second terminator it the limiting<br>
> >   terminator.<br>
> > - partially convert out of ssa before unrolling loops (suggested by<br>
> > Jason)<br>
> > ---<br>
> >  src/compiler/Makefile.<wbr>sources          |   1 +<br>
> >  src/compiler/nir/nir.h                 |   2 +<br>
> >  src/compiler/nir/nir_opt_<wbr>loop_unroll.c | 559<br>
> > ++++++++++++++++++++++++++++++<wbr>+++<br>
> >  3 files changed, 562 insertions(+)<br>
> >  create mode 100644 src/compiler/nir/nir_opt_loop_<wbr>unroll.c<br>
> ><br>
> > diff --git a/src/compiler/Makefile.<wbr>sources<br>
> > b/src/compiler/Makefile.<wbr>sources<br>
> > index e8f7b02..ae3e5f0 100644<br>
> > --- a/src/compiler/Makefile.<wbr>sources<br>
> > +++ b/src/compiler/Makefile.<wbr>sources<br>
> > @@ -239,6 +239,7 @@ NIR_FILES = \<br>
> >         nir/nir_opt_dead_cf.c \<br>
> >         nir/nir_opt_gcm.c \<br>
> >         nir/nir_opt_global_to_local.c \<br>
> > +       nir/nir_opt_loop_unroll.c \<br>
> >         nir/nir_opt_peephole_select.c \<br>
> >         nir/nir_opt_remove_phis.c \<br>
> >         nir/nir_opt_undef.c \<br>
> > diff --git a/src/compiler/nir/nir.h b/src/compiler/nir/nir.h<br>
> > index 75a91ea..51bc6b2 100644<br>
> > --- a/src/compiler/nir/nir.h<br>
> > +++ b/src/compiler/nir/nir.h<br>
> > @@ -2552,6 +2552,8 @@ bool nir_opt_dead_cf(nir_shader *shader);<br>
> ><br>
> >  bool nir_opt_gcm(nir_shader *shader, bool value_number);<br>
> ><br>
> > +bool nir_opt_loop_unroll(nir_shader *shader, nir_variable_mode<br>
> > indirect_mask);<br>
> > +<br>
> >  bool nir_opt_peephole_select(nir_<wbr>shader *shader, unsigned limit);<br>
> ><br>
> >  bool nir_opt_remove_phis(nir_shader *shader);<br>
> > diff --git a/src/compiler/nir/nir_opt_<wbr>loop_unroll.c<br>
> > b/src/compiler/nir/nir_opt_<wbr>loop_unroll.c<br>
> > new file mode 100644<br>
> > index 0000000..7eb44cb<br>
> > --- /dev/null<br>
> > +++ b/src/compiler/nir/nir_opt_<wbr>loop_unroll.c<br>
> > @@ -0,0 +1,559 @@<br>
> > +/*<br>
> > + * Copyright © 2016 Intel Corporation<br>
> > + *<br>
> > + * Permission is hereby granted, free of charge, to any person<br>
> > obtaining a<br>
> > + * copy of this software and associated documentation files (the<br>
> > "Software"),<br>
> > + * to deal in the Software without restriction, including without<br>
> > limitation<br>
> > + * the rights to use, copy, modify, merge, publish, distribute,<br>
> > sublicense,<br>
> > + * and/or sell copies of the Software, and to permit persons to<br>
> > whom the<br>
> > + * Software is furnished to do so, subject to the following<br>
> > conditions:<br>
> > + *<br>
> > + * The above copyright notice and this permission notice<br>
> > (including the next<br>
> > + * paragraph) shall be included in all copies or substantial<br>
> > portions of the<br>
> > + * Software.<br>
> > + *<br>
> > + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,<br>
> > EXPRESS OR<br>
> > + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF<br>
> > MERCHANTABILITY,<br>
> > + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO<br>
> > EVENT SHALL<br>
> > + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,<br>
> > DAMAGES OR OTHER<br>
> > + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,<br>
> > ARISING<br>
> > + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR<br>
> > OTHER<br>
> > + * DEALINGS IN THE SOFTWARE.<br>
> > + */<br>
> > +<br>
> > +#include "nir.h"<br>
> > +#include "nir_builder.h"<br>
> > +#include "nir_control_flow.h"<br>
> > +#include "nir_loop_analyze.h"<br>
> > +<br>
> > +/* Convert all phis in the give block to regs, here we insert a<br>
> > mov in the<br>
> > + * pred block of the phi source to copy the src to the reg, then<br>
> > we rewrite<br>
> > + * all uses of the phi to the new reg.<br>
> > + */<br>
> > +static void<br>
> > +convert_phis_to_regs(nir_<wbr>builder *b, nir_block *block)<br>
> > +{<br>
><br>
> In patch 1/6 of the series I sent yesterday I added a slightly more<br>
> advanced version of this helper to nir_from_ssa as well as another<br>
> one I found useful in my pass.  You can either pull that into your<br>
> series or I can split it into three patches: Pull yours into to_ssa,<br>
> improve it, add the second.  I don't really care which so long as we<br>
> end up sharing the code.<br>
>  <br>
> > +   nir_foreach_instr_safe(instr, block) {<br>
> > +      if (instr->type != nir_instr_type_phi)<br>
> > +         break;<br>
> > +<br>
> > +      nir_phi_instr *phi = nir_instr_as_phi(instr);<br>
> > +<br>
> > +      nir_register *reg = nir_local_reg_create(b->impl);<br>
> > +      reg->num_components = phi->dest.ssa.num_components;<br>
> > +      reg->bit_size = phi->dest.ssa.bit_size;<br>
> > +<br>
> > +      nir_foreach_phi_src(src, phi) {<br>
> > +         nir_alu_instr *mov = nir_alu_instr_create(b-><wbr>shader,<br>
> > nir_op_imov);<br>
> > +         nir_src_copy(&mov->src[0].<wbr>src, &src->src, mov);<br>
> > +         mov->dest.dest = nir_dest_for_reg(reg);<br>
> > +         mov->dest.write_mask = (1 << reg->num_components) - 1;<br>
> > +<br>
> > +         nir_instr_insert(nir_after_<wbr>block_before_jump(src->pred),<br>
> > +                          &mov->instr);<br>
> > +      }<br>
> > +<br>
> > +      nir_alu_instr *mov = nir_alu_instr_create(b-><wbr>shader,<br>
> > nir_op_imov);<br>
> > +      mov->src[0].src = nir_src_for_reg(reg);<br>
> > +      mov->dest.write_mask = (1 << reg->num_components) - 1;<br>
> > +      nir_ssa_dest_init(&mov->instr, &mov->dest.dest, reg-<br>
> > >num_components,<br>
> > +                        reg->bit_size, NULL);<br>
> > +      nir_instr_insert(nir_after_<wbr>instr(&phi->instr), &mov->instr);<br>
> > +<br>
> > +      nir_ssa_def_rewrite_uses(&phi-<wbr>>dest.ssa,<br>
> > +                               nir_src_for_ssa(&mov-<br>
> > >dest.dest.ssa));<br>
> > +      nir_instr_remove(&phi->instr);<br>
> > +   }<br>
> > +}<br>
> > +<br>
> > +/* Convert the phis from the loops first block and the block that<br>
> > follows the<br>
> > + * loop into regs.  Partially converting out of SSA allows us to<br>
> > unroll the<br>
> > + * loop without having to keep track of and update phis along the<br>
> > way which<br>
> > + * gets tricky and doesn't add much value over conveting to regs.<br>
> > + */<br>
> > +static void<br>
> > +convert_loop_phis_to_reg(nir_<wbr>builder *b, nir_loop *loop,<br>
> > +                         nir_block *header_blk)<br>
> > +{<br>
> > +   convert_phis_to_regs(b, header_blk);<br>
> > +<br>
> > +   nir_block *block_after_loop =<br>
> > +      nir_cf_node_as_block(nir_cf_<wbr>node_next(&loop->cf_node));<br>
> > +<br>
> > +   convert_phis_to_regs(b, block_after_loop);<br>
> > +}<br>
> > +<br>
> > +/**<br>
> > + * Unroll a loop where we know exactly how many iterations there<br>
> > are and there<br>
> > + * is only a single exit point.  Note here we can unroll loops<br>
> > with multiple<br>
> > + * theoretical exits that only have a single terminating exit that<br>
> > we always<br>
> > + * know is the "real" exit.<br>
> > + *<br>
> > + *     loop {<br>
> > + *         ...instrs...<br>
> > + *     }<br>
> > + *<br>
> > + * And the iteration count is 3, the output will be:<br>
> > + *<br>
> > + *     ...instrs... ...instrs... ...instrs...<br>
> > + */<br>
> > +static void<br>
> > +simple_unroll(nir_loop *loop, nir_builder *b)<br>
> > +{<br>
> > +   nir_loop_terminator *limiting_term = loop->info-<br>
> > >limiting_terminator;<br>
> > +   assert(nir_is_trivial_loop_<wbr>if(limiting_term->nif,<br>
> > +                                 limiting_term->break_block));<br>
> > +<br>
> > +   nir_convert_loop_to_lcssa(<wbr>loop);<br>
> > +<br>
> > +   nir_block *header_blk = nir_loop_first_block(loop);<br>
> > +   convert_loop_phis_to_reg(b, loop, header_blk);<br>
> > +<br>
> > +   /* Skip over loop terminator and get the loop body. */<br>
> > +   list_for_each_entry(nir_loop_<wbr>terminator, terminator,<br>
> > +                       &loop->info->loop_terminator_<wbr>list,<br>
> > loop_terminator_link) {<br>
><br>
> This hits 81 characters<br>
>  <br>
> > +<br>
> > +      /* Remove all but the limiting terminator as we know the<br>
> > other exit<br>
> > +       * conditions can never be met. Note we need to extract any<br>
> > instructions<br>
> > +       * in the continue from branch and insert then into the loop<br>
> > body before<br>
> > +       * removing it.<br>
> > +       */<br>
> > +      if (terminator->nif != limiting_term->nif) {<br>
> > +         assert(nir_is_trivial_loop_<wbr>if(terminator->nif,<br>
> > +                                       terminator->break_block));<br>
> > +<br>
> > +         nir_cf_list continue_from_blk;<br>
> > +         nir_cf_extract(&continue_<wbr>from_blk,<br>
> > +                        nir_before_block(terminator-<br>
> > >continue_from_block),<br>
> > +                        nir_after_block(terminator-<br>
> > >continue_from_block));<br>
> > +         b->cursor = nir_after_cf_node(&terminator-<wbr>>nif->cf_node);<br>
> > +         nir_cf_reinsert(&continue_<wbr>from_blk, b->cursor);<br>
> > +<br>
> > +         nir_cf_node_remove(&<wbr>terminator->nif->cf_node);<br>
> > +      }<br>
> > +   }<br>
> > +<br>
> > +   /* Pluck out the loop header */<br>
> > +   nir_cf_list lp_header;<br>
> > +   nir_cf_extract(&lp_header, nir_before_block(header_blk),<br>
> > +                  nir_before_cf_node(&limiting_<wbr>term->nif-<br>
> > >cf_node));<br>
> > +<br>
> > +   /* Add the continue from block of the limiting terminator to<br>
> > the loop body<br>
> > +    */<br>
> > +   nir_cf_list continue_from_blk;<br>
> > +   nir_cf_extract(&continue_<wbr>from_blk,<br>
> > +                  nir_before_block(limiting_<wbr>term-<br>
> > >continue_from_block),<br>
> > +                  nir_after_block(limiting_term-<br>
> > >continue_from_block));<br>
> > +   b->cursor = nir_after_cf_node(&limiting_<wbr>term->nif->cf_node);<br>
> > +   nir_cf_reinsert(&continue_<wbr>from_blk, b->cursor);<br>
> > +<br>
> > +   /* Pluck out the loop body */<br>
> > +   nir_cf_list loop_body;<br>
> > +   nir_cf_extract(&loop_body, nir_after_cf_node(&limiting_<wbr>term-<br>
> > >nif->cf_node),<br>
> > +                  nir_after_block(nir_loop_last_<wbr>block(loop)));<br>
> > +<br>
> > +   struct hash_table *remap_table =<br>
> > +      _mesa_hash_table_create(NULL, _mesa_hash_pointer,<br>
> > +                              _mesa_key_pointer_equal);<br>
> > +<br>
> > +   /* Clone the loop header */<br>
> > +   nir_cf_list cloned_header;<br>
> > +   nir_cf_list_clone(&cloned_<wbr>header, &lp_header, loop-<br>
> > >cf_node.parent,<br>
> > +                     remap_table);<br>
> > +<br>
> > +   /* Insert cloned loop header before the loop */<br>
> > +   b->cursor = nir_before_cf_node(&loop->cf_<wbr>node);<br>
> > +   nir_cf_reinsert(&cloned_<wbr>header, b->cursor);<br>
> > +<br>
> > +   /* Temp list to store the cloned loop body as we unroll */<br>
> > +   nir_cf_list unrolled_lp_body;<br>
> > +<br>
> > +   /* Clone loop header and append to the loop body */<br>
> > +   for (unsigned i = 0; i < loop->info->trip_count; i++) {<br>
> > +      /* Clone loop body */<br>
> > +      nir_cf_list_clone(&unrolled_<wbr>lp_body, &loop_body, loop-<br>
> > >cf_node.parent,<br>
> > +                        remap_table);<br>
> > +<br>
> > +      /* Insert unrolled loop body before the loop */<br>
> > +      b->cursor = nir_before_cf_node(&loop->cf_<wbr>node);<br>
> > +      nir_cf_reinsert(&unrolled_lp_<wbr>body, b->cursor);<br>
> > +<br>
> > +      /* Clone loop header */<br>
> > +      nir_cf_list_clone(&cloned_<wbr>header, &lp_header, loop-<br>
> > >cf_node.parent,<br>
> > +                        remap_table);<br>
> > +<br>
> > +      /* Insert loop header after loop body */<br>
> > +      b->cursor = nir_before_cf_node(&loop->cf_<wbr>node);<br>
><br>
> Why are we using a builder?  Why not just have a nir_cursor cursor<br>
> variable?  We're not inserting any instructions.<br>
>  <br>
> > +      nir_cf_reinsert(&cloned_<wbr>header, b->cursor);<br>
><br>
> For that matter, this could just be "nir_cf_reinsert(&cloned_<wbr>header,<br>
> nir_before_cf_node(&loop->cf_<wbr>node))"<br>
>  <br>
> > +   }<br>
> > +<br>
> > +   /* Remove the break from the loop terminator and add<br>
> > instructions from<br>
> > +    * the break block after the unrolled loop.<br>
> > +    */<br>
> > +   nir_instr *break_instr = nir_block_last_instr(limiting_<wbr>term-<br>
> > >break_block);<br>
> > +   nir_instr_remove(break_instr)<wbr>;<br>
> > +   nir_cf_list break_blk;<br>
> > +   nir_cf_extract(&break_blk,<br>
> > +                  nir_before_block(limiting_<wbr>term->break_block),<br>
> > +                  nir_after_block(limiting_term-<wbr>>break_block));<br>
> > +<br>
> > +   /* Clone so things get properly remapped */<br>
> > +   nir_cf_list cloned_break_blk;<br>
> > +   nir_cf_list_clone(&cloned_<wbr>break_blk, &break_blk, loop-<br>
> > >cf_node.parent,<br>
> > +                     remap_table);<br>
> > +<br>
> > +   b->cursor = nir_before_cf_node(&loop->cf_<wbr>node);<br>
> > +   nir_cf_reinsert(&cloned_<wbr>break_blk, b->cursor);<br>
> > +<br>
> > +   /* Remove the loop */<br>
> > +   nir_cf_node_remove(&loop->cf_<wbr>node);<br>
> > +<br>
> > +   /* Delete the original loop body, break block & header */<br>
> > +   nir_cf_delete(&lp_header);<br>
> > +   nir_cf_delete(&loop_body);<br>
> > +   nir_cf_delete(&break_blk);<br>
> > +<br>
> > +   _mesa_hash_table_destroy(<wbr>remap_table, NULL);<br>
> > +<br>
><br>
> I *think* you can just convert to SSA once at the end but this is<br>
> probably safer.<br>
<br>
</div></div>Yeah I think we has discussed this before. Since we only unroll a<br>
single loop per pass I don't think it matters where we call this.<br>
<div><div class="h5"><br>
<br>
>  <br>
> > +   nir_convert_to_ssa_impl(b-><wbr>impl);<br>
> > +}<br>
> > +<br>
> > +static void<br>
> > +move_cf_list_into_loop_term(<wbr>nir_cf_list *lst, nir_loop_terminator<br>
> > *term)<br>
> > +{<br>
> > +   /* Move the rest of the loop inside the continue-from-block */<br>
> > +   nir_cf_reinsert(lst, nir_after_block(term-<br>
> > >continue_from_block));<br>
> > +<br>
> > +   /* Remove the break */<br>
> > +   nir_instr_remove(nir_block_<wbr>last_instr(term->break_block))<wbr>;<br>
> > +}<br>
> > +<br>
> > +/**<br>
> > + * Unroll a loop with two exists when the trip count of one of the<br>
> > exits is<br>
> > + * unknown.  If continue_from_then is true, the loop is repeated<br>
> > only when the<br>
> > + * "then" branch of the if is taken; otherwise it is repeated only<br>
> > + * when the "else" branch of the if is taken.<br>
> > + *<br>
> > + * For example, if the input is:<br>
> > + *<br>
> > + *      loop {<br>
> > + *         ...phis/condition...<br>
> > + *         if condition {<br>
> > + *            ...then instructions...<br>
> > + *         } else {<br>
> > + *            ...continue instructions...<br>
> > + *            break<br>
> > + *         }<br>
> > + *         ...body...<br>
> > + *      }<br>
> > + *<br>
> > + * And the iteration count is 3, and unlimit_term-<br>
> > >continue_from_then is true,<br>
> > + * then the output will be:<br>
> > + *<br>
> > + *      ...condition...<br>
> > + *      if condition {<br>
> > + *         ...then instructions...<br>
> > + *         ...body...<br>
> > + *         if condition {<br>
> > + *            ...then instructions...<br>
> > + *            ...body...<br>
> > + *            if condition {<br>
> > + *               ...then instructions...<br>
> > + *               ...body...<br>
> > + *            } else {<br>
> > + *               ...continue instructions...<br>
> > + *            }<br>
> > + *         } else {<br>
> > + *            ...continue instructions...<br>
> > + *         }<br>
> > + *      } else {<br>
> > + *         ...continue instructions...<br>
> > + *      }<br>
> > + */<br>
> > +static void<br>
> > +complex_unroll(nir_loop *loop, nir_builder *b,<br>
> > +               nir_loop_terminator *unlimit_term, bool<br>
> > limiting_term_second)<br>
> > +{<br>
> > +   assert(nir_is_trivial_loop_<wbr>if(unlimit_term->nif,<br>
> > +                                 unlimit_term->break_block));<br>
> > +<br>
> > +   nir_loop_terminator *limiting_term = loop->info-<br>
> > >limiting_terminator;<br>
> > +   assert(nir_is_trivial_loop_<wbr>if(limiting_term->nif,<br>
> > +                                 limiting_term->break_block));<br>
> > +<br>
> > +   nir_convert_loop_to_lcssa(<wbr>loop);<br>
> > +<br>
> > +   nir_block *header_blk = nir_loop_first_block(loop);<br>
> > +   convert_loop_phis_to_reg(b, loop, header_blk);<br>
> > +<br>
> > +   /* Pluck out the loop header */<br>
> > +   nir_cf_list lp_header;<br>
> > +   nir_cf_extract(&lp_header, nir_before_block(header_blk),<br>
> > +                  nir_before_cf_node(nir_cf_<wbr>node_next(&header_blk-<br>
> > >cf_node)));<br>
><br>
> Why not just use nir_after_block(header_blk)? <br>
<br>
</div></div>Agreed.<br>
<span class=""><br>
>  Much simpler.  Also, do we really only want to grab the header block<br>
> and not everything up until the first terminator?<br>
<br>
</span>Yeah I think you are right.<br></blockquote><div><br></div><div>Another test maybe?<br></div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div><div class="h5">
>  <br>
> > +<br>
> > +   nir_cf_list limit_break_blk;<br>
> > +   unsigned num_times_to_clone;<br>
> > +   if (limiting_term_second) {<br>
> > +      /* We need some special handling when its the second<br>
> > terminator causing<br>
> > +       * us to exit the loop for example:<br>
> > +       *<br>
> > +       *   for (int i = 0; i < uniform_lp_count; i++) {<br>
> > +       *      colour = vec4(0.0, 1.0, 0.0, 1.0);<br>
> > +       *<br>
> > +       *      if (i == 1) {<br>
> > +       *         break;<br>
> > +       *      }<br>
> > +       *      ... any further code is unreachable after i == 1 ...<br>
> > +       *   }<br>
> > +       */<br>
> > +      nir_cf_list after_lt;<br>
> > +      nir_if *limit_if = limiting_term->nif;<br>
> > +      nir_cf_extract(&after_lt, nir_after_cf_node(&limit_if-<br>
> > >cf_node),<br>
> > +                     nir_after_block(nir_loop_<wbr>last_block(loop)));<br>
> > +      move_cf_list_into_loop_term(&<wbr>after_lt, limiting_term);<br>
> > +<br>
> > +      /* Because the trip count is the number of times we pass<br>
> > over the extire<br>
> > +       * loop before hitting a break when the second terminator is<br>
> > the<br>
> > +       * limiting terminator we can actually execute code inside<br>
> > the loop when<br>
> > +       * trip count == 0 e.g. the code above the break.  So we<br>
> > need to bump<br>
> > +       * the trip_count in order for the code below to clone<br>
> > anything.  When<br>
> > +       * trip count == 1 we execute the code above the loop twice<br>
> > and the code<br>
> > +       * below it once so we need clone things twice and so on.<br>
><br>
> This comment is way better than I remember it being.  Thanks!<br>
>  <br>
> > +       */<br>
> > +      num_times_to_clone = loop->info->trip_count + 1;<br>
> > +   } else {<br>
> > +      /* Remove the break then extract instructions from the break<br>
> > block so we<br>
> > +       * can insert them in the innermost else of the unrolled<br>
> > loop.<br>
> > +       */<br>
> > +      nir_instr *break_instr = nir_block_last_instr(limiting_<wbr>term-<br>
> > >break_block);<br>
> > +      nir_instr_remove(break_instr);<br>
> > +      nir_cf_extract(&limit_break_<wbr>blk,<br>
> > +                     nir_before_block(limiting_<wbr>term->break_block),<br>
> > +                     nir_after_block(limiting_<wbr>term->break_block));<br>
> > +<br>
> > +      nir_cf_list continue_blk;<br>
> > +      nir_cf_extract(&continue_blk,<br>
> > +                     nir_before_block(limiting_<wbr>term-<br>
> > >continue_from_block),<br>
> > +                     nir_after_block(limiting_<wbr>term-<br>
> > >continue_from_block));<br>
><br>
> These two extracts assume that the continue and break sides of the if<br>
> each contain a single block.  Is this a valid assumption?<br>
<br>
</div></div>Hmm is was until I made the analysis pass better in this lastest<br>
version. Will fix.<span class=""><br></span></blockquote><div><br></div><div>Sounds like we need a test or two :)<br></div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><span class="">
>  <br>
> > +<br>
> > +      b->cursor = nir_after_cf_node(&limiting_<wbr>term->nif->cf_node);<br>
> > +      nir_cf_reinsert(&continue_blk, b->cursor);<br>
><br>
> This is a nice simplification, but I'm not sure it's valid...  If you<br>
> have real stuff in the continue I think it will end up getting<br>
> executed in the last iteration.<br>
<br>
</span>I think that's what we want though, as here trip count ==<br>
 num_times_to_clone which means we iterate the number of times we did a<br>
full pass over the loop exiting from the continue. There is further<br>
special handing below to add the header/break branch instructions to<br>
the end of unrolling.<br><div><div class="h5"></div></div></blockquote><div><br></div><div>Looking at it again, I think this is actually ok.  In this case, you do all the unrolling and then you insert header and break which is correct because the limiting terminator comes first.  I think it's correct.  Please make sure we have a test that exercises this so we know it behaves the way we think it does. :)<br></div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div><div class="h5">
>  <br>
> > +<br>
> > +      nir_cf_node_remove(&limiting_<wbr>term->nif->cf_node);<br>
> > +<br>
> > +      num_times_to_clone = loop->info->trip_count;<br>
> > +   }<br>
> > +<br>
> > +   /* In the terminator that we have no trip count for move<br>
> > everything after<br>
> > +    * the terminator into the continue from branch.<br>
> > +    */<br>
> > +   nir_cf_list loop_end;<br>
> > +   nir_cf_extract(&loop_end, nir_after_cf_node(&unlimit_<wbr>term->nif-<br>
> > >cf_node),<br>
> > +                  nir_after_block(nir_loop_last_<wbr>block(loop)));<br>
> > +   move_cf_list_into_loop_term(&<wbr>loop_end, unlimit_term);<br>
> > +<br>
> > +   /* Pluck out the loop body. */<br>
> > +   nir_cf_list loop_body;<br>
> > +   nir_cf_extract(&loop_body,<br>
> > nir_before_block(nir_loop_<wbr>first_block(loop)),<br>
> > +                  nir_after_block(nir_loop_last_<wbr>block(loop)));<br>
> > +<br>
> > +   struct hash_table *remap_table =<br>
> > +      _mesa_hash_table_create(NULL, _mesa_hash_pointer,<br>
> > +                              _mesa_key_pointer_equal);<br>
> > +<br>
> > +   /* Set the cursor to before the loop */<br>
> > +   b->cursor = nir_before_cf_node(&loop->cf_<wbr>node);<br>
> > +<br>
> > +   /* Temp lists to store the cloned loop as we unroll */<br>
> > +   nir_cf_list unrolled_lp_body;<br>
> > +   nir_cf_list cloned_header;<br>
> > +<br>
> > +   for (unsigned i = 0; i < num_times_to_clone; i++) {<br>
> > +      /* Clone loop header */<br>
> > +      nir_cf_list_clone(&cloned_<wbr>header, &lp_header, loop-<br>
> > >cf_node.parent,<br>
> > +                        remap_table);<br>
> > +<br>
> > +      /* Insert cloned loop header */<br>
> > +      nir_cf_reinsert(&cloned_<wbr>header, b->cursor);<br>
> > +<br>
> > +      /* Clone loop body */<br>
> > +      nir_cf_list_clone(&unrolled_<wbr>lp_body, &loop_body, loop-<br>
> > >cf_node.parent,<br>
> > +                        remap_table);<br>
> > +<br>
> > +      nir_cf_node *last_node =<br>
> > +         exec_node_data(nir_cf_node,<br>
> > +                       <br>
> > exec_list_get_tail(&unrolled_<wbr>lp_body.list), node);<br>
> > +      assert(last_node->type == nir_cf_node_block &&<br>
> > +             exec_list_is_empty(&nir_cf_<wbr>node_as_block(last_node)-<br>
> > >instr_list));<br>
> > +<br>
> > +      /* Insert unrolled loop body */<br>
> > +      nir_cf_reinsert(&unrolled_lp_<wbr>body, b->cursor);<br>
><br>
> I don't believe your use of cursors here is valid.  Once you've<br>
> inserted something at a cursor that cursor should be assumed to be<br>
> invalid.  Instead, I think you probably want to keep track of the<br>
> current if (if any) and always insert after the last block in the if<br>
> or before the loop as needed.  Yes, that's annoying, but I'm not<br>
> quite sure how what you have here even works and I wouldn't trust it<br>
> to with changes in nir_cursor.<br>
<br>
</div></div>That's what I had but it worked without it. I'll add it back in.<br></blockquote><div><br></div><div>In general, i think we should probably not be using nir_builder in this pass.  Your one real use of it is the phis_to_regs function at the top.  Everywhere else it's just a place to store a cursor which probably isn't what you want anyway.<br></div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div class="HOEnZb"><div class="h5">
>  <br>
> > +<br>
> > +      nir_cf_node *if_node = nir_cf_node_prev(last_node);<br>
> > +      assert(if_node->type == nir_cf_node_if);<br>
><br>
> The cast helper does this for you now.<br>
>  <br>
> > +      nir_if *if_stmt = nir_cf_node_as_if(if_node);<br>
> > +<br>
> > +      /* Set the cursor to the last if in the loop body we just<br>
> > unrolled ready<br>
> > +       * for the next iteration.<br>
> > +       */<br>
> > +      if (unlimit_term->continue_from_<wbr>then) {<br>
> > +         b->cursor =<br>
> > nir_after_block(nir_if_last_<wbr>then_block(if_stmt));<br>
> > +      } else {<br>
> > +         b->cursor =<br>
> > nir_after_block(nir_if_last_<wbr>else_block(if_stmt));<br>
> > +      }<br>
> > +   }<br>
> > +<br>
> > +   if (!limiting_term_second) {<br>
> > +      nir_cf_list_clone(&cloned_<wbr>header, &lp_header, loop-<br>
> > >cf_node.parent,<br>
> > +                        remap_table);<br>
> > +<br>
> > +      /* Insert cloned loop header */<br>
> > +      nir_cf_reinsert(&cloned_<wbr>header, b->cursor);<br>
> > +<br>
> > +      /* Clone so things get properly remapped, and insert break<br>
> > block from<br>
> > +       * the limiting terminator.<br>
> > +       */<br>
> > +      nir_cf_list cloned_break_blk;<br>
> > +      nir_cf_list_clone(&cloned_<wbr>break_blk, &limit_break_blk,<br>
> > +                        loop->cf_node.parent, remap_table);<br>
> > +<br>
> > +      nir_cf_reinsert(&cloned_break_<wbr>blk, b->cursor);<br>
> > +      nir_cf_delete(&limit_break_<wbr>blk);<br>
> > +   }<br>
> > +<br>
> > +   /* The loop has been unrolled so remove it. */<br>
> > +   nir_cf_node_remove(&loop->cf_<wbr>node);<br>
> > +<br>
> > +   /* Delete the original loop header and body */<br>
> > +   nir_cf_delete(&lp_header);<br>
> > +   nir_cf_delete(&loop_body);<br>
> > +<br>
> > +   _mesa_hash_table_destroy(<wbr>remap_table, NULL);<br>
> > +<br>
> > +   nir_convert_to_ssa_impl(b-><wbr>impl);<br>
> > +}<br>
> > +<br>
> > +static bool<br>
> > +is_loop_small_enough_to_<wbr>unroll(nir_shader *shader, nir_loop_info<br>
> > *li)<br>
> > +{<br>
> > +   unsigned max_iter = shader->options->max_unroll_<wbr>iterations;<br>
> > +<br>
> > +   if (li->trip_count > max_iter)<br>
> > +      return false;<br>
> > +<br>
> > +   if (li->force_unroll)<br>
> > +      return true;<br>
> > +<br>
> > +   bool loop_not_too_large =<br>
> > +      li->num_instructions * li->trip_count <= max_iter * 25;<br>
> > +<br>
> > +   return loop_not_too_large;<br>
> > +}<br>
> > +<br>
> > +static bool<br>
> > +process_loops(nir_cf_node *cf_node, nir_builder *b, bool<br>
> > *innermost_loop)<br>
> > +{<br>
> > +   bool progress = false;<br>
> > +   nir_loop *loop;<br>
> > +<br>
> > +   switch (cf_node->type) {<br>
> > +   case nir_cf_node_block:<br>
> > +      return progress;<br>
> > +   case nir_cf_node_if: {<br>
> > +      nir_if *if_stmt = nir_cf_node_as_if(cf_node);<br>
> > +      foreach_list_typed_safe(nir_<wbr>cf_node, nested_node, node,<br>
> > &if_stmt->then_list)<br>
> > +         progress |= process_loops(nested_node, b,<br>
> > innermost_loop);<br>
> > +      foreach_list_typed_safe(nir_<wbr>cf_node, nested_node, node,<br>
> > &if_stmt->else_list)<br>
> > +         progress |= process_loops(nested_node, b,<br>
> > innermost_loop);<br>
> > +      return progress;<br>
> > +   }<br>
> > +   case nir_cf_node_loop: {<br>
> > +      loop = nir_cf_node_as_loop(cf_node);<br>
> > +      foreach_list_typed_safe(nir_<wbr>cf_node, nested_node, node,<br>
> > &loop->body)<br>
> > +         progress |= process_loops(nested_node, b,<br>
> > innermost_loop);<br>
> > +      break;<br>
> > +   }<br>
> > +   default:<br>
> > +      unreachable("unknown cf node type");<br>
> > +   }<br>
> > +<br>
> > +   if (*innermost_loop) {<br>
> > +      /* Don't attempt to unroll outer loops or a second inner<br>
> > loop in<br>
> > +       * this pass wait until the next pass as we have altered the<br>
> > cf.<br>
> > +       */<br>
> > +      *innermost_loop = false;<br>
> > +<br>
> > +      if (loop->info->limiting_<wbr>terminator == NULL)<br>
> > +         return progress;<br>
> > +<br>
> > +      if (!is_loop_small_enough_to_<wbr>unroll(b->shader, loop->info))<br>
> > +         return progress;<br>
> > +<br>
> > +      if (loop->info->is_trip_count_<wbr>known) {<br>
> > +         simple_unroll(loop, b);<br>
> > +         progress = true;<br>
> > +      } else {<br>
> > +         /* Attempt to unroll loops with two terminators. */<br>
> > +         unsigned num_lt = list_length(&loop->info-<br>
> > >loop_terminator_list);<br>
> > +         if (num_lt == 2) {<br>
> > +            bool limiting_term_second = true;<br>
> > +            nir_loop_terminator *terminator =<br>
> > +               list_last_entry(&loop->info-><wbr>loop_terminator_list,<br>
> > +                                nir_loop_terminator,<br>
> > loop_terminator_link);<br>
> > +<br>
> > +<br>
> > +            if (terminator->nif == loop->info-<br>
> > >limiting_terminator->nif) {<br>
> > +               limiting_term_second = false;<br>
> > +               terminator =<br>
> > +                  list_first_entry(&loop->info-<br>
> > >loop_terminator_list,<br>
> > +                                  nir_loop_terminator,<br>
> > loop_terminator_link);<br>
> > +            }<br>
> > +<br>
> > +            /* If the first terminator has a trip count of zero<br>
> > and is the<br>
> > +             * limiting terminator just do a simple unroll as the<br>
> > second<br>
> > +             * terminator can never be reached.<br>
> > +             */<br>
> > +            if (loop->info->trip_count == 0 &&<br>
> > !limiting_term_second) {<br>
> > +               simple_unroll(loop, b);<br>
> > +            } else {<br>
> > +               complex_unroll(loop, b, terminator,<br>
> > limiting_term_second);<br>
> > +            }<br>
> > +            progress = true;<br>
> > +         }<br>
> > +      }<br>
> > +   }<br>
> > +<br>
> > +   return progress;<br>
> > +}<br>
> > +<br>
> > +static bool<br>
> > +nir_opt_loop_unroll_impl(nir_<wbr>function_impl *impl,<br>
> > +                         nir_variable_mode indirect_mask)<br>
> > +{<br>
> > +   bool progress = false;<br>
> > +   nir_metadata_require(impl, nir_metadata_loop_analysis,<br>
> > indirect_mask);<br>
> > +   nir_metadata_require(impl, nir_metadata_block_index);<br>
> > +<br>
> > +   nir_builder b;<br>
> > +   nir_builder_init(&b, impl);<br>
> > +<br>
> > +   foreach_list_typed_safe(nir_<wbr>cf_node, node, node, &impl->body) {<br>
> > +      bool innermost_loop = true;<br>
> > +      progress |= process_loops(node, &b, &innermost_loop);<br>
> > +   }<br>
> > +<br>
> > +   return progress;<br>
> > +}<br>
> > +<br>
> > +bool<br>
> > +nir_opt_loop_unroll(nir_<wbr>shader *shader, nir_variable_mode<br>
> > indirect_mask)<br>
> > +{<br>
> > +   bool progress = false;<br>
> > +<br>
> > +   nir_foreach_function(<wbr>function, shader) {<br>
> > +      if (function->impl) {<br>
> > +         progress |= nir_opt_loop_unroll_impl(<wbr>function->impl,<br>
> > indirect_mask);<br>
> > +      }<br>
> > +   }<br>
> > +   return false;<br>
> > +}<br>
> > --<br>
> > 2.9.3<br>
> ><br>
> > ______________________________<wbr>_________________<br>
> > mesa-dev mailing list<br>
> > <a href="mailto:mesa-dev@lists.freedesktop.org">mesa-dev@lists.freedesktop.org</a><br>
> > <a href="https://lists.freedesktop.org/mailman/listinfo/mesa-dev" rel="noreferrer" target="_blank">https://lists.freedesktop.org/<wbr>mailman/listinfo/mesa-dev</a><br>
> ><br>
><br>
> ______________________________<wbr>_________________<br>
> mesa-dev mailing list<br>
> <a href="mailto:mesa-dev@lists.freedesktop.org">mesa-dev@lists.freedesktop.org</a><br>
> <a href="https://lists.freedesktop.org/mailman/listinfo/mesa-dev" rel="noreferrer" target="_blank">https://lists.freedesktop.org/<wbr>mailman/listinfo/mesa-dev</a><br>
</div></div></blockquote></div><br></div></div>