<p dir="ltr"><br>
On Dec 26, 2015 3:04 PM, "Connor Abbott" <<a href="mailto:cwabbott0@gmail.com">cwabbott0@gmail.com</a>> wrote:<br>
><br>
> On Sat, Dec 26, 2015 at 5:57 PM, Jason Ekstrand <<a href="mailto:jason@jlekstrand.net">jason@jlekstrand.net</a>> wrote:<br>
> ><br>
> > On Dec 26, 2015 2:22 PM, "Connor Abbott" <<a href="mailto:cwabbott0@gmail.com">cwabbott0@gmail.com</a>> wrote:<br>
> >><br>
> >> On Sat, Dec 26, 2015 at 2:09 PM, Jason Ekstrand <<a href="mailto:jason@jlekstrand.net">jason@jlekstrand.net</a>><br>
> >> wrote:<br>
> >> > This commit adds a NIR pass for lowering away returns in functions.  If<br>
> >> > the<br>
> >> > return is in a loop, it is lowered to a break.  If it is not in a loop,<br>
> >> > it's lowered away by moving/deleting code as needed.<br>
> >> > ---<br>
> >> ><br>
> >> > Unfortunately, the loop-handling portion hasn't had proper testing.  I'm<br>
> >> > pretty sure the approach is good, but my SPIR-V branch doesn't have<br>
> >> > working<br>
> >> > loops at the moment so I can't properly test.<br>
> >> ><br>
> >> >  src/glsl/Makefile.sources        |   1 +<br>
> >> >  src/glsl/nir/nir.h               |   3 +<br>
> >> >  src/glsl/nir/nir_lower_returns.c | 243<br>
> >> > +++++++++++++++++++++++++++++++++++++++<br>
> >> >  3 files changed, 247 insertions(+)<br>
> >> >  create mode 100644 src/glsl/nir/nir_lower_returns.c<br>
> >> ><br>
> >> > diff --git a/src/glsl/Makefile.sources b/src/glsl/Makefile.sources<br>
> >> > index fc10f14..fa3868c 100644<br>
> >> > --- a/src/glsl/Makefile.sources<br>
> >> > +++ b/src/glsl/Makefile.sources<br>
> >> > @@ -43,6 +43,7 @@ NIR_FILES = \<br>
> >> >         nir/nir_lower_alu_to_scalar.c \<br>
> >> >         nir/nir_lower_atomics.c \<br>
> >> >         nir/nir_lower_clip.c \<br>
> >> > +       nir/nir_lower_returns.c \<br>
> >> >         nir/nir_lower_global_vars_to_local.c \<br>
> >> >         nir/nir_lower_gs_intrinsics.c \<br>
> >> >         nir/nir_lower_load_const_to_scalar.c \<br>
> >> > diff --git a/src/glsl/nir/nir.h b/src/glsl/nir/nir.h<br>
> >> > index b6c8ffe..2e29617 100644<br>
> >> > --- a/src/glsl/nir/nir.h<br>
> >> > +++ b/src/glsl/nir/nir.h<br>
> >> > @@ -1935,6 +1935,9 @@ int nir_gs_count_vertices(const nir_shader<br>
> >> > *shader);<br>
> >> ><br>
> >> >  bool nir_split_var_copies(nir_shader *shader);<br>
> >> ><br>
> >> > +bool nir_lower_returns_impl(nir_function_impl *impl);<br>
> >> > +bool nir_lower_returns(nir_shader *shader);<br>
> >> > +<br>
> >> >  void nir_lower_var_copy_instr(nir_intrinsic_instr *copy, void<br>
> >> > *mem_ctx);<br>
> >> >  void nir_lower_var_copies(nir_shader *shader);<br>
> >> ><br>
> >> > diff --git a/src/glsl/nir/nir_lower_returns.c<br>
> >> > b/src/glsl/nir/nir_lower_returns.c<br>
> >> > new file mode 100644<br>
> >> > index 0000000..ec77dde<br>
> >> > --- /dev/null<br>
> >> > +++ b/src/glsl/nir/nir_lower_returns.c<br>
> >> > @@ -0,0 +1,243 @@<br>
> >> > +/*<br>
> >> > + * Copyright © 2015 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 whom<br>
> >> > the<br>
> >> > + * Software is furnished to do so, subject to the following conditions:<br>
> >> > + *<br>
> >> > + * The above copyright notice and this permission notice (including the<br>
> >> > next<br>
> >> > + * paragraph) shall be included in all copies or substantial portions<br>
> >> > 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 EVENT<br>
> >> > SHALL<br>
> >> > + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR<br>
> >> > 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 OTHER<br>
> >> > DEALINGS<br>
> >> > + * IN THE SOFTWARE.<br>
> >> > + */<br>
> >> > +<br>
> >> > +#include "nir.h"<br>
> >> > +#include "nir_builder.h"<br>
> >> > +#include "nir_control_flow.h"<br>
> >> > +<br>
> >> > +struct lower_returns_state {<br>
> >> > +   nir_builder builder;<br>
> >> > +   struct exec_list *parent_cf_list;<br>
> >> > +   struct exec_list *cf_list;<br>
> >> > +   nir_loop *loop;<br>
> >> > +   nir_if *if_stmt;<br>
> >> > +   nir_variable *return_flag;<br>
> >> > +};<br>
> >> > +<br>
> >> > +static bool lower_returns_in_cf_list(struct exec_list *cf_list,<br>
> >> > +                                     struct lower_returns_state<br>
> >> > *state);<br>
> >> > +<br>
> >> > +static bool<br>
> >> > +lower_returns_in_loop(nir_loop *loop, struct lower_returns_state<br>
> >> > *state)<br>
> >> > +{<br>
> >> > +   nir_loop *parent = state->loop;<br>
> >> > +   state->loop = loop;<br>
> >> > +   bool progress = lower_returns_in_cf_list(&loop->body, state);<br>
> >> > +   state->loop = parent;<br>
> >> > +<br>
> >> > +   /* Nothing interesting */<br>
> >> > +   if (!progress)<br>
> >> > +      return false;<br>
> >> > +<br>
> >> > +   /* In this case, there was a return somewhere inside of the loop.<br>
> >> > That<br>
> >> > +    * return would have been turned into a write to the return_flag<br>
> >> > +    * variable and a break.  We need to insert a predicated return<br>
> >> > right<br>
> >> > +    * after the loop ends.<br>
> >> > +    */<br>
> >> > +<br>
> >> > +   assert(state->return_flag);<br>
> >> > +<br>
> >> > +   nir_intrinsic_instr *load =<br>
> >> > +      nir_intrinsic_instr_create(state->builder.shader,<br>
> >> > nir_intrinsic_load_var);<br>
> >> > +   load->num_components = 1;<br>
> >> > +   load->variables[0] = nir_deref_var_create(load, state->return_flag);<br>
> >> > +   nir_ssa_dest_init(&load->instr, &load->dest, 1, "return");<br>
> >> > +   nir_instr_insert(nir_after_cf_node(&loop->cf_node), &load->instr);<br>
> >> > +<br>
> >> > +   nir_if *if_stmt = nir_if_create(state->builder.shader);<br>
> >> > +   if_stmt->condition = nir_src_for_ssa(&load->dest.ssa);<br>
> >> > +   nir_cf_node_insert(nir_after_instr(&load->instr),<br>
> >> > &if_stmt->cf_node);<br>
> >> > +<br>
> >> > +   nir_jump_instr *ret =<br>
> >> > +      nir_jump_instr_create(state->builder.shader, nir_jump_return);<br>
> >> > +   nir_instr_insert(nir_before_cf_list(&if_stmt->then_list),<br>
> >> > &ret->instr);<br>
> >> > +<br>
> >> > +   return true;<br>
> >> > +}<br>
> >> > +<br>
> >> > +static bool<br>
> >> > +lower_returns_in_if(nir_if *if_stmt, struct lower_returns_state *state)<br>
> >> > +{<br>
> >> > +   bool progress;<br>
> >> > +<br>
> >> > +   nir_if *parent = state->if_stmt;<br>
> >> > +   state->if_stmt = if_stmt;<br>
> >> > +   progress = lower_returns_in_cf_list(&if_stmt->then_list, state);<br>
> >> > +   progress = lower_returns_in_cf_list(&if_stmt->else_list, state) ||<br>
> >> > progress;<br>
> >> > +   state->if_stmt = parent;<br>
> >> > +<br>
> >> > +   return progress;<br>
> >> > +}<br>
> >> > +<br>
> >> > +static bool<br>
> >> > +lower_returns_in_block(nir_block *block, struct lower_returns_state<br>
> >> > *state)<br>
> >> > +{<br>
> >> > +   if (block->predecessors->entries == 0 &&<br>
> >> > +       block != nir_start_block(state->builder.impl)) {<br>
> >> > +      /* This block is unreachable.  Delete it and everything after it.<br>
> >> > */<br>
> >> > +      nir_cf_list list;<br>
> >> > +      nir_cf_extract(&list, nir_before_cf_node(&block->cf_node),<br>
> >> > +                            nir_after_cf_list(state->cf_list));<br>
> >> > +<br>
> >> > +      if (exec_list_is_empty(&list.list)) {<br>
> >> > +         /* There's nothing here, which also means there's nothing in<br>
> >> > this<br>
> >> > +          * block so we have nothing to do.<br>
> >> > +          */<br>
> >> > +         return false;<br>
> >> > +      } else {<br>
> >> > +         nir_cf_delete(&list);<br>
> >> > +         return true;<br>
> >> > +      }<br>
> >> > +   }<br>
> >> > +<br>
> >> > +   nir_instr *last_instr = nir_block_last_instr(block);<br>
> >> > +   if (last_instr == NULL)<br>
> >> > +      return false;<br>
> >> > +<br>
> >> > +   if (last_instr->type != nir_instr_type_jump)<br>
> >> > +      return false;<br>
> >> > +<br>
> >> > +   nir_jump_instr *jump = nir_instr_as_jump(last_instr);<br>
> >> > +   if (jump->type != nir_jump_return)<br>
> >> > +      return false;<br>
> >> > +<br>
> >> > +   if (state->loop) {<br>
> >> > +      /* We're in a loop.  Just set the return flag to true and break.<br>
> >> > +       * lower_returns_in_loop will do the rest.<br>
> >> > +       */<br>
> >> > +      nir_builder *b = &state->builder;<br>
> >> > +      b->cursor = nir_before_instr(&jump->instr);<br>
> >> > +<br>
> >> > +      if (state->return_flag == NULL) {<br>
> >> > +         state->return_flag =<br>
> >> > +            nir_local_variable_create(b->impl, glsl_bool_type(),<br>
> >> > "return");<br>
> >> > +<br>
> >> > +         /* Set a default value of false */<br>
> >> > +         state->return_flag->constant_initializer =<br>
> >> > +            rzalloc(state->return_flag, nir_constant);<br>
> >> > +      }<br>
> >> > +<br>
> >> > +      nir_store_var(b, state->return_flag, nir_imm_int(b, NIR_TRUE),<br>
> >> > 1);<br>
> >> > +      jump->type = nir_jump_return;<br>
> >> > +   } else if (state->if_stmt) {<br>
> >> > +      /* If we're not in a loop but in an if, just move the rest of the<br>
> >> > CF<br>
> >> > +       * list into the the other case of the if.<br>
> >> > +       */<br>
> >> > +      nir_cf_list list;<br>
> >> > +      nir_cf_extract(&list,<br>
> >> > nir_after_cf_node(&state->if_stmt->cf_node),<br>
> >> > +                            nir_after_cf_list(state->parent_cf_list));<br>
> >> > +<br>
> >> > +      nir_instr_remove(&jump->instr);<br>
> >> > +<br>
> >> > +      if (state->cf_list == &state->if_stmt->then_list) {<br>
> >> > +         nir_cf_reinsert(&list,<br>
> >> > +<br>
> >> > nir_after_cf_list(&state->if_stmt->else_list));<br>
> >> > +      } else if (state->cf_list == &state->if_stmt->else_list) {<br>
> >> > +         nir_cf_reinsert(&list,<br>
> >> > +<br>
> >> > nir_after_cf_list(&state->if_stmt->then_list));<br>
> >> > +      } else {<br>
> >> > +         unreachable("Invalid CF list");<br>
> >> > +      }<br>
> >> > +   } else {<br>
> >> > +      nir_instr_remove(&jump->instr);<br>
> >> > +<br>
> >> > +      /* No if, no nothing.  Just delete the return and whatever<br>
> >> > follows. */<br>
> >> > +      nir_cf_list list;<br>
> >> > +      nir_cf_extract(&list, nir_after_cf_node(&block->cf_node),<br>
> >> > +                            nir_after_cf_list(state->parent_cf_list));<br>
> >> > +      nir_cf_delete(&list);<br>
> >> > +   }<br>
> >> > +<br>
> >> > +   return true;<br>
> >> > +}<br>
> >><br>
> >><br>
> >> There are a few problems with this approach.<br>
> >><br>
> >> First off, imagine something like:<br>
> >><br>
> >> if (...) {<br>
> >>   if (...) return;<br>
> >> }<br>
> >> ...<br>
> >><br>
> >> If I'm reading this correctly, we'd lower this to:<br>
> >><br>
> >> if (...) {<br>
> >>    if (...) ;<br>
> >> }<br>
> >> ...<br>
> ><br>
> > Oops... You're right.  I meant to add returns.  This<br>
> ><br>
> > if (...) {<br>
> >    if (...) return;<br>
> >    // foo<br>
> > }<br>
> ><br>
> > Should be lowered to<br>
> ><br>
> > if (...) {<br>
> >    if (...) {<br>
> >    } else {<br>
> >       // foo<br>
> >    }<br>
> >    return;<br>
> > }<br>
> ><br>
> > Which we can then lower further.<br>
><br>
> I don't think that's correct though. What if there's stuff after the<br>
> outer if? It'll only get executed if the outer if condition is false,<br>
> instead of if either one is false. I think we really need the flag in<br>
> this case.</p>
<p dir="ltr">If there's stuff in the outer if, it gets moved into the else</p>
<p dir="ltr">> ><br>
> > That said, I need to think more about the order I iterate over things.<br>
> > --Jason<br>
> ><br>
> >> which isn't right. We want to skip the stuff outside the outer if only<br>
> >> if the condition of the inner if is true, that is we want:<br>
> >><br>
> >> if (...) {<br>
> >>    if (...) return_flag = true;<br>
> >> }<br>
> >> if (!return_flag) {<br>
> >>   ...<br>
> >> }<br>
> >><br>
> >> Similarly, this won't work if you have a loop with a return inside an<br>
> >> if. I think you need to unconditionally set the return_flag to true,<br>
> >> and then in lower_returns_in_if() you need to predicate the rest of<br>
> >> the rest of the control flow list with !return_flag if it's not empty<br>
> >> and you're not inside a loop.<br>
> >><br>
> >> There are also some other things you could do to clean up the result<br>
> >> of this pass. Imagine that you have something like:<br>
> >><br>
> >> if (...) {<br>
> >>   while(...) {<br>
> >>     if (...) return;<br>
> >>   }<br>
> >> }<br>
> >> ...<br>
> >><br>
> >> this would get lowered to:<br>
> >><br>
> >> return_flag = false;<br>
> >> if (cond) {<br>
> >>   while(...) {<br>
> >>     if(...) { return_flag = true; break; }<br>
> >>   }<br>
> >> }<br>
> >> if (!return_flag) {<br>
> >>   ...<br>
> >> }<br>
> >><br>
> >> The first thing to notice is that the return_flag will get lowered to<br>
> >> a phi node after the first (outer) if which will be true for the then<br>
> >> branch and false for the else branch. We can recognize that as being<br>
> >> equivalent to the condition of the if:<br>
> >><br>
> >> if (cond) {<br>
> >>   while(...) {<br>
> >>     if(...) { break; }<br>
> >>   }<br>
> >> }<br>
> >> if (!cond) {<br>
> >>   ...<br>
> >> }<br>
> >><br>
> >> Now, we can turn if (cond) { ... } if (!cond) { ... } into if (cond) {<br>
> >> ... } else { ... }:<br>
> >><br>
> >> if (cond) {<br>
> >>   while(...) {<br>
> >>     if(...) { break; }<br>
> >>   }<br>
> >> } else {<br>
> >>   ...<br>
> >> }<br>
> >><br>
> >> Note that these two optimizations combined make what you do here in<br>
> >> the "if (state->if_stmt)" case unnecessary. It also makes it more<br>
> >> robust. Imagine that in the earlier example I gave:<br>
> >><br>
> >> if (...) {<br>
> >>   if (...) return;<br>
> >> }<br>
> >> ...<br>
> >><br>
> >> we're able to determine, in NIR, that the outer if's condition is<br>
> >> always true. With what we have now, if dead_cf runs first then we get:<br>
> >><br>
> >> if (...) {<br>
> >> } else {<br>
> >>   ...<br>
> >> }<br>
> >><br>
> >> whereas if lower_returns runs first we get:<br>
> >><br>
> >> if (...) { return_flag = true; }<br>
> >> if (!return_flag) {<br>
> >>   ...<br>
> >> }<br>
> >><br>
> >> which we don't want. But with the two optimizations, we always get the<br>
> >> right thing.<br>
> >><br>
> >> > +<br>
> >> > +static bool<br>
> >> > +lower_returns_in_cf_list(struct exec_list *cf_list,<br>
> >> > +                         struct lower_returns_state *state)<br>
> >> > +{<br>
> >> > +   bool progress = false;<br>
> >> > +<br>
> >> > +   struct exec_list *prev_parent_list = state->parent_cf_list;<br>
> >> > +   state->parent_cf_list = state->cf_list;<br>
> >> > +   state->cf_list = cf_list;<br>
> >> > +<br>
> >> > +   foreach_list_typed_reverse_safe(nir_cf_node, node, node, cf_list) {<br>
> >> > +      switch (node->type) {<br>
> >> > +      case nir_cf_node_block:<br>
> >> > +         if (lower_returns_in_block(nir_cf_node_as_block(node), state))<br>
> >> > +            progress = true;<br>
> >> > +         break;<br>
> >> > +<br>
> >> > +      case nir_cf_node_if:<br>
> >> > +         if (lower_returns_in_if(nir_cf_node_as_if(node), state))<br>
> >> > +            progress = true;<br>
> >> > +         break;<br>
> >> > +<br>
> >> > +      case nir_cf_node_loop:<br>
> >> > +         if (lower_returns_in_loop(nir_cf_node_as_loop(node), state))<br>
> >> > +            progress = true;<br>
> >> > +         break;<br>
> >> > +<br>
> >> > +      default:<br>
> >> > +         unreachable("Invalid inner CF node type");<br>
> >> > +      }<br>
> >> > +   }<br>
> >> > +<br>
> >> > +   state->cf_list = state->parent_cf_list;<br>
> >> > +   state->parent_cf_list = prev_parent_list;<br>
> >> > +<br>
> >> > +   return progress;<br>
> >> > +}<br>
> >> > +<br>
> >> > +bool<br>
> >> > +nir_lower_returns_impl(nir_function_impl *impl)<br>
> >> > +{<br>
> >> > +   struct lower_returns_state state;<br>
> >> > +<br>
> >> > +   state.parent_cf_list = NULL;<br>
> >> > +   state.cf_list = &impl->body;<br>
> >> > +   state.loop = NULL;<br>
> >> > +   state.if_stmt = NULL;<br>
> >> > +   state.return_flag = NULL;<br>
> >> > +   nir_builder_init(&state.builder, impl);<br>
> >> > +<br>
> >> > +   bool progress = lower_returns_in_cf_list(&impl->body, &state);<br>
> >> > +<br>
> >> > +   if (progress)<br>
> >> > +      nir_metadata_preserve(impl, nir_metadata_none);<br>
> >> > +<br>
> >> > +   return progress;<br>
> >> > +}<br>
> >> > +<br>
> >> > +bool<br>
> >> > +nir_lower_returns(nir_shader *shader)<br>
> >> > +{<br>
> >> > +   bool progress = false;<br>
> >> > +<br>
> >> > +   nir_foreach_function(shader, function) {<br>
> >> > +      if (function->impl)<br>
> >> > +         progress = nir_lower_returns_impl(function->impl) || progress;<br>
> >> > +   }<br>
> >> > +<br>
> >> > +   return progress;<br>
> >> > +}<br>
> >> > --<br>
> >> > 2.5.0.400.gff86faf<br>
> >> ><br>
> >> > _______________________________________________<br>
> >> > mesa-dev mailing list<br>
> >> > <a href="mailto:mesa-dev@lists.freedesktop.org">mesa-dev@lists.freedesktop.org</a><br>
> >> > <a href="http://lists.freedesktop.org/mailman/listinfo/mesa-dev">http://lists.freedesktop.org/mailman/listinfo/mesa-dev</a><br>
</p>