[Mesa-dev] [RFC 11/12] nir: Add return lowering pass

Jason Ekstrand jason at jlekstrand.net
Sat Dec 26 14:57:31 PST 2015


On Dec 26, 2015 2:22 PM, "Connor Abbott" <cwabbott0 at gmail.com> wrote:
>
> On Sat, Dec 26, 2015 at 2:09 PM, Jason Ekstrand <jason at jlekstrand.net>
wrote:
> > This commit adds a NIR pass for lowering away returns in functions.  If
the
> > return is in a loop, it is lowered to a break.  If it is not in a loop,
> > it's lowered away by moving/deleting code as needed.
> > ---
> >
> > Unfortunately, the loop-handling portion hasn't had proper testing.  I'm
> > pretty sure the approach is good, but my SPIR-V branch doesn't have
working
> > loops at the moment so I can't properly test.
> >
> >  src/glsl/Makefile.sources        |   1 +
> >  src/glsl/nir/nir.h               |   3 +
> >  src/glsl/nir/nir_lower_returns.c | 243
+++++++++++++++++++++++++++++++++++++++
> >  3 files changed, 247 insertions(+)
> >  create mode 100644 src/glsl/nir/nir_lower_returns.c
> >
> > diff --git a/src/glsl/Makefile.sources b/src/glsl/Makefile.sources
> > index fc10f14..fa3868c 100644
> > --- a/src/glsl/Makefile.sources
> > +++ b/src/glsl/Makefile.sources
> > @@ -43,6 +43,7 @@ NIR_FILES = \
> >         nir/nir_lower_alu_to_scalar.c \
> >         nir/nir_lower_atomics.c \
> >         nir/nir_lower_clip.c \
> > +       nir/nir_lower_returns.c \
> >         nir/nir_lower_global_vars_to_local.c \
> >         nir/nir_lower_gs_intrinsics.c \
> >         nir/nir_lower_load_const_to_scalar.c \
> > diff --git a/src/glsl/nir/nir.h b/src/glsl/nir/nir.h
> > index b6c8ffe..2e29617 100644
> > --- a/src/glsl/nir/nir.h
> > +++ b/src/glsl/nir/nir.h
> > @@ -1935,6 +1935,9 @@ int nir_gs_count_vertices(const nir_shader
*shader);
> >
> >  bool nir_split_var_copies(nir_shader *shader);
> >
> > +bool nir_lower_returns_impl(nir_function_impl *impl);
> > +bool nir_lower_returns(nir_shader *shader);
> > +
> >  void nir_lower_var_copy_instr(nir_intrinsic_instr *copy, void
*mem_ctx);
> >  void nir_lower_var_copies(nir_shader *shader);
> >
> > diff --git a/src/glsl/nir/nir_lower_returns.c
b/src/glsl/nir/nir_lower_returns.c
> > new file mode 100644
> > index 0000000..ec77dde
> > --- /dev/null
> > +++ b/src/glsl/nir/nir_lower_returns.c
> > @@ -0,0 +1,243 @@
> > +/*
> > + * Copyright © 2015 Intel Corporation
> > + *
> > + * Permission is hereby granted, free of charge, to any person
obtaining a
> > + * copy of this software and associated documentation files (the
"Software"),
> > + * to deal in the Software without restriction, including without
limitation
> > + * the rights to use, copy, modify, merge, publish, distribute,
sublicense,
> > + * and/or sell copies of the Software, and to permit persons to whom
the
> > + * Software is furnished to do so, subject to the following conditions:
> > + *
> > + * The above copyright notice and this permission notice (including
the next
> > + * paragraph) shall be included in all copies or substantial portions
of the
> > + * Software.
> > + *
> > + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR
> > + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY,
> > + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT
SHALL
> > + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
OR OTHER
> > + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING
> > + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS
> > + * IN THE SOFTWARE.
> > + */
> > +
> > +#include "nir.h"
> > +#include "nir_builder.h"
> > +#include "nir_control_flow.h"
> > +
> > +struct lower_returns_state {
> > +   nir_builder builder;
> > +   struct exec_list *parent_cf_list;
> > +   struct exec_list *cf_list;
> > +   nir_loop *loop;
> > +   nir_if *if_stmt;
> > +   nir_variable *return_flag;
> > +};
> > +
> > +static bool lower_returns_in_cf_list(struct exec_list *cf_list,
> > +                                     struct lower_returns_state
*state);
> > +
> > +static bool
> > +lower_returns_in_loop(nir_loop *loop, struct lower_returns_state
*state)
> > +{
> > +   nir_loop *parent = state->loop;
> > +   state->loop = loop;
> > +   bool progress = lower_returns_in_cf_list(&loop->body, state);
> > +   state->loop = parent;
> > +
> > +   /* Nothing interesting */
> > +   if (!progress)
> > +      return false;
> > +
> > +   /* In this case, there was a return somewhere inside of the loop.
That
> > +    * return would have been turned into a write to the return_flag
> > +    * variable and a break.  We need to insert a predicated return
right
> > +    * after the loop ends.
> > +    */
> > +
> > +   assert(state->return_flag);
> > +
> > +   nir_intrinsic_instr *load =
> > +      nir_intrinsic_instr_create(state->builder.shader,
nir_intrinsic_load_var);
> > +   load->num_components = 1;
> > +   load->variables[0] = nir_deref_var_create(load, state->return_flag);
> > +   nir_ssa_dest_init(&load->instr, &load->dest, 1, "return");
> > +   nir_instr_insert(nir_after_cf_node(&loop->cf_node), &load->instr);
> > +
> > +   nir_if *if_stmt = nir_if_create(state->builder.shader);
> > +   if_stmt->condition = nir_src_for_ssa(&load->dest.ssa);
> > +   nir_cf_node_insert(nir_after_instr(&load->instr),
&if_stmt->cf_node);
> > +
> > +   nir_jump_instr *ret =
> > +      nir_jump_instr_create(state->builder.shader, nir_jump_return);
> > +   nir_instr_insert(nir_before_cf_list(&if_stmt->then_list),
&ret->instr);
> > +
> > +   return true;
> > +}
> > +
> > +static bool
> > +lower_returns_in_if(nir_if *if_stmt, struct lower_returns_state *state)
> > +{
> > +   bool progress;
> > +
> > +   nir_if *parent = state->if_stmt;
> > +   state->if_stmt = if_stmt;
> > +   progress = lower_returns_in_cf_list(&if_stmt->then_list, state);
> > +   progress = lower_returns_in_cf_list(&if_stmt->else_list, state) ||
progress;
> > +   state->if_stmt = parent;
> > +
> > +   return progress;
> > +}
> > +
> > +static bool
> > +lower_returns_in_block(nir_block *block, struct lower_returns_state
*state)
> > +{
> > +   if (block->predecessors->entries == 0 &&
> > +       block != nir_start_block(state->builder.impl)) {
> > +      /* This block is unreachable.  Delete it and everything after
it. */
> > +      nir_cf_list list;
> > +      nir_cf_extract(&list, nir_before_cf_node(&block->cf_node),
> > +                            nir_after_cf_list(state->cf_list));
> > +
> > +      if (exec_list_is_empty(&list.list)) {
> > +         /* There's nothing here, which also means there's nothing in
this
> > +          * block so we have nothing to do.
> > +          */
> > +         return false;
> > +      } else {
> > +         nir_cf_delete(&list);
> > +         return true;
> > +      }
> > +   }
> > +
> > +   nir_instr *last_instr = nir_block_last_instr(block);
> > +   if (last_instr == NULL)
> > +      return false;
> > +
> > +   if (last_instr->type != nir_instr_type_jump)
> > +      return false;
> > +
> > +   nir_jump_instr *jump = nir_instr_as_jump(last_instr);
> > +   if (jump->type != nir_jump_return)
> > +      return false;
> > +
> > +   if (state->loop) {
> > +      /* We're in a loop.  Just set the return flag to true and break.
> > +       * lower_returns_in_loop will do the rest.
> > +       */
> > +      nir_builder *b = &state->builder;
> > +      b->cursor = nir_before_instr(&jump->instr);
> > +
> > +      if (state->return_flag == NULL) {
> > +         state->return_flag =
> > +            nir_local_variable_create(b->impl, glsl_bool_type(),
"return");
> > +
> > +         /* Set a default value of false */
> > +         state->return_flag->constant_initializer =
> > +            rzalloc(state->return_flag, nir_constant);
> > +      }
> > +
> > +      nir_store_var(b, state->return_flag, nir_imm_int(b, NIR_TRUE),
1);
> > +      jump->type = nir_jump_return;
> > +   } else if (state->if_stmt) {
> > +      /* If we're not in a loop but in an if, just move the rest of
the CF
> > +       * list into the the other case of the if.
> > +       */
> > +      nir_cf_list list;
> > +      nir_cf_extract(&list,
nir_after_cf_node(&state->if_stmt->cf_node),
> > +                            nir_after_cf_list(state->parent_cf_list));
> > +
> > +      nir_instr_remove(&jump->instr);
> > +
> > +      if (state->cf_list == &state->if_stmt->then_list) {
> > +         nir_cf_reinsert(&list,
> > +
 nir_after_cf_list(&state->if_stmt->else_list));
> > +      } else if (state->cf_list == &state->if_stmt->else_list) {
> > +         nir_cf_reinsert(&list,
> > +
 nir_after_cf_list(&state->if_stmt->then_list));
> > +      } else {
> > +         unreachable("Invalid CF list");
> > +      }
> > +   } else {
> > +      nir_instr_remove(&jump->instr);
> > +
> > +      /* No if, no nothing.  Just delete the return and whatever
follows. */
> > +      nir_cf_list list;
> > +      nir_cf_extract(&list, nir_after_cf_node(&block->cf_node),
> > +                            nir_after_cf_list(state->parent_cf_list));
> > +      nir_cf_delete(&list);
> > +   }
> > +
> > +   return true;
> > +}
>
>
> There are a few problems with this approach.
>
> First off, imagine something like:
>
> if (...) {
>   if (...) return;
> }
> ...
>
> If I'm reading this correctly, we'd lower this to:
>
> if (...) {
>    if (...) ;
> }
> ...

Oops... You're right.  I meant to add returns.  This

if (...) {
   if (...) return;
   // foo
}

Should be lowered to

if (...) {
   if (...) {
   } else {
      // foo
   }
   return;
}

Which we can then lower further.

That said, I need to think more about the order I iterate over things.
--Jason

> which isn't right. We want to skip the stuff outside the outer if only
> if the condition of the inner if is true, that is we want:
>
> if (...) {
>    if (...) return_flag = true;
> }
> if (!return_flag) {
>   ...
> }
>
> Similarly, this won't work if you have a loop with a return inside an
> if. I think you need to unconditionally set the return_flag to true,
> and then in lower_returns_in_if() you need to predicate the rest of
> the rest of the control flow list with !return_flag if it's not empty
> and you're not inside a loop.
>
> There are also some other things you could do to clean up the result
> of this pass. Imagine that you have something like:
>
> if (...) {
>   while(...) {
>     if (...) return;
>   }
> }
> ...
>
> this would get lowered to:
>
> return_flag = false;
> if (cond) {
>   while(...) {
>     if(...) { return_flag = true; break; }
>   }
> }
> if (!return_flag) {
>   ...
> }
>
> The first thing to notice is that the return_flag will get lowered to
> a phi node after the first (outer) if which will be true for the then
> branch and false for the else branch. We can recognize that as being
> equivalent to the condition of the if:
>
> if (cond) {
>   while(...) {
>     if(...) { break; }
>   }
> }
> if (!cond) {
>   ...
> }
>
> Now, we can turn if (cond) { ... } if (!cond) { ... } into if (cond) {
> ... } else { ... }:
>
> if (cond) {
>   while(...) {
>     if(...) { break; }
>   }
> } else {
>   ...
> }
>
> Note that these two optimizations combined make what you do here in
> the "if (state->if_stmt)" case unnecessary. It also makes it more
> robust. Imagine that in the earlier example I gave:
>
> if (...) {
>   if (...) return;
> }
> ...
>
> we're able to determine, in NIR, that the outer if's condition is
> always true. With what we have now, if dead_cf runs first then we get:
>
> if (...) {
> } else {
>   ...
> }
>
> whereas if lower_returns runs first we get:
>
> if (...) { return_flag = true; }
> if (!return_flag) {
>   ...
> }
>
> which we don't want. But with the two optimizations, we always get the
> right thing.
>
> > +
> > +static bool
> > +lower_returns_in_cf_list(struct exec_list *cf_list,
> > +                         struct lower_returns_state *state)
> > +{
> > +   bool progress = false;
> > +
> > +   struct exec_list *prev_parent_list = state->parent_cf_list;
> > +   state->parent_cf_list = state->cf_list;
> > +   state->cf_list = cf_list;
> > +
> > +   foreach_list_typed_reverse_safe(nir_cf_node, node, node, cf_list) {
> > +      switch (node->type) {
> > +      case nir_cf_node_block:
> > +         if (lower_returns_in_block(nir_cf_node_as_block(node), state))
> > +            progress = true;
> > +         break;
> > +
> > +      case nir_cf_node_if:
> > +         if (lower_returns_in_if(nir_cf_node_as_if(node), state))
> > +            progress = true;
> > +         break;
> > +
> > +      case nir_cf_node_loop:
> > +         if (lower_returns_in_loop(nir_cf_node_as_loop(node), state))
> > +            progress = true;
> > +         break;
> > +
> > +      default:
> > +         unreachable("Invalid inner CF node type");
> > +      }
> > +   }
> > +
> > +   state->cf_list = state->parent_cf_list;
> > +   state->parent_cf_list = prev_parent_list;
> > +
> > +   return progress;
> > +}
> > +
> > +bool
> > +nir_lower_returns_impl(nir_function_impl *impl)
> > +{
> > +   struct lower_returns_state state;
> > +
> > +   state.parent_cf_list = NULL;
> > +   state.cf_list = &impl->body;
> > +   state.loop = NULL;
> > +   state.if_stmt = NULL;
> > +   state.return_flag = NULL;
> > +   nir_builder_init(&state.builder, impl);
> > +
> > +   bool progress = lower_returns_in_cf_list(&impl->body, &state);
> > +
> > +   if (progress)
> > +      nir_metadata_preserve(impl, nir_metadata_none);
> > +
> > +   return progress;
> > +}
> > +
> > +bool
> > +nir_lower_returns(nir_shader *shader)
> > +{
> > +   bool progress = false;
> > +
> > +   nir_foreach_function(shader, function) {
> > +      if (function->impl)
> > +         progress = nir_lower_returns_impl(function->impl) || progress;
> > +   }
> > +
> > +   return progress;
> > +}
> > --
> > 2.5.0.400.gff86faf
> >
> > _______________________________________________
> > mesa-dev mailing list
> > mesa-dev at lists.freedesktop.org
> > http://lists.freedesktop.org/mailman/listinfo/mesa-dev
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.freedesktop.org/archives/mesa-dev/attachments/20151226/9baa7d8a/attachment-0001.html>


More information about the mesa-dev mailing list