[Mesa-dev] [RFC 11/12] nir: Add return lowering pass
Connor Abbott
cwabbott0 at gmail.com
Sat Dec 26 16:59:22 PST 2015
On Sat, Dec 26, 2015 at 7:08 PM, Jason Ekstrand <jason at jlekstrand.net> wrote:
>
> On Dec 26, 2015 3:04 PM, "Connor Abbott" <cwabbott0 at gmail.com> wrote:
>>
>> On Sat, Dec 26, 2015 at 5:57 PM, Jason Ekstrand <jason at jlekstrand.net>
>> wrote:
>> >
>> > 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.
>>
>> I don't think that's correct though. What if there's stuff after the
>> outer if? It'll only get executed if the outer if condition is false,
>> instead of if either one is false. I think we really need the flag in
>> this case.
>
> If there's stuff in the outer if, it gets moved into the else
Stuff after the outer if, not in it.
>
>> >
>> > 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
More information about the mesa-dev
mailing list