<p dir="ltr"><br>
On Jan 24, 2015 8:18 AM, "Connor Abbott" <<a href="mailto:cwabbott0@gmail.com">cwabbott0@gmail.com</a>> wrote:<br>
><br>
> On Sat, Jan 24, 2015 at 1:00 AM, Jason Ekstrand <<a href="mailto:jason@jlekstrand.net">jason@jlekstrand.net</a>> wrote:<br>
> > ---<br>
> >  src/glsl/Makefile.sources               |   1 +<br>
> >  src/glsl/nir/nir.h                      |   2 +<br>
> >  src/glsl/nir/nir_lower_phis_to_scalar.c | 238 ++++++++++++++++++++++++++++++++<br>
> >  3 files changed, 241 insertions(+)<br>
> >  create mode 100644 src/glsl/nir/nir_lower_phis_to_scalar.c<br>
> ><br>
> > diff --git a/src/glsl/Makefile.sources b/src/glsl/Makefile.sources<br>
> > index 96c4ec5..02d0780 100644<br>
> > --- a/src/glsl/Makefile.sources<br>
> > +++ b/src/glsl/Makefile.sources<br>
> > @@ -28,6 +28,7 @@ NIR_FILES = \<br>
> >         nir/nir_lower_global_vars_to_local.c \<br>
> >         nir/nir_lower_locals_to_regs.c \<br>
> >         nir/nir_lower_io.c \<br>
> > +       nir/nir_lower_phis_to_scalar.c \<br>
> >         nir/nir_lower_samplers.cpp \<br>
> >         nir/nir_lower_system_values.c \<br>
> >         nir/nir_lower_to_source_mods.c \<br>
> > diff --git a/src/glsl/nir/nir.h b/src/glsl/nir/nir.h<br>
> > index 119ca01..cda14aa 100644<br>
> > --- a/src/glsl/nir/nir.h<br>
> > +++ b/src/glsl/nir/nir.h<br>
> > @@ -1523,6 +1523,8 @@ void nir_remove_dead_variables(nir_shader *shader);<br>
> >  void nir_lower_vec_to_movs(nir_shader *shader);<br>
> >  void nir_lower_alu_to_scalar(nir_shader *shader);<br>
> ><br>
> > +void nir_lower_phis_to_scalar(nir_shader *shader);<br>
> > +<br>
> >  void nir_lower_samplers(nir_shader *shader,<br>
> >                          struct gl_shader_program *shader_program,<br>
> >                          struct gl_program *prog);<br>
> > diff --git a/src/glsl/nir/nir_lower_phis_to_scalar.c b/src/glsl/nir/nir_lower_phis_to_scalar.c<br>
> > new file mode 100644<br>
> > index 0000000..9f901d6<br>
> > --- /dev/null<br>
> > +++ b/src/glsl/nir/nir_lower_phis_to_scalar.c<br>
> > @@ -0,0 +1,238 @@<br>
> > +/*<br>
> > + * Copyright © 2014 Intel Corporation<br>
> > + *<br>
> > + * Permission is hereby granted, free of charge, to any person obtaining a<br>
> > + * copy of this software and associated documentation files (the "Software"),<br>
> > + * to deal in the Software without restriction, including without limitation<br>
> > + * the rights to use, copy, modify, merge, publish, distribute, sublicense,<br>
> > + * and/or sell copies of the Software, and to permit persons to whom 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 next<br>
> > + * paragraph) shall be included in all copies or substantial portions of the<br>
> > + * Software.<br>
> > + *<br>
> > + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR<br>
> > + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,<br>
> > + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL<br>
> > + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER<br>
> > + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING<br>
> > + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS<br>
> > + * IN THE SOFTWARE.<br>
> > + *<br>
> > + * Authors:<br>
> > + *    Jason Ekstrand (<a href="mailto:jason@jlekstrand.net">jason@jlekstrand.net</a>)<br>
> > + *<br>
> > + */<br>
> > +<br>
> > +#include "nir.h"<br>
> > +<br>
> > +/*<br>
> > + * Implements common subexpression elimination<br>
> > + */<br>
><br>
> Whoops...</p>
<p dir="ltr">Yeah, I'll fix that.</p>
<p dir="ltr">><br>
> > +<br>
> > +struct lower_phis_to_scalar_state {<br>
> > +   void *mem_ctx;<br>
> > +   void *dead_ctx;<br>
> > +<br>
> > +   /* Hash table marking which phi nodes are scalarizable.  The key is<br>
> > +    * pointers to phi instructions and the entry is either NULL for not<br>
> > +    * scalarizable or non-null for scalarizable.<br>
> > +    */<br>
> > +   struct hash_table *phi_table;<br>
> > +};<br>
> > +<br>
> > +/* Determines if the given phi node should be lowered.  The only phi nodes<br>
> > + * we will scalarize at the moment are those where all of the sources are<br>
> > + * scalarizable.<br>
> > + */<br>
> > +static bool<br>
> > +should_lower_phi(nir_phi_instr *phi, struct lower_phis_to_scalar_state *state)<br>
> > +{<br>
> > +   /* Already scalar */<br>
> > +   if (phi->dest.ssa.num_components == 1)<br>
> > +      return false;<br>
> > +<br>
> > +   struct hash_entry *entry = _mesa_hash_table_search(state->phi_table, phi);<br>
> > +   if (entry)<br>
> > +      return entry->data != NULL;<br>
> > +<br>
> > +   nir_foreach_phi_src(phi, src) {<br>
> > +      /* Don't know what to do with non-ssa sources */<br>
> > +      if (!src->src.is_ssa)<br>
> > +         return false;<br>
> > +<br>
> > +      nir_instr *src_instr = src->src.ssa->parent_instr;<br>
> > +      switch (src_instr->type) {<br>
> > +      case nir_instr_type_alu: {<br>
> > +         nir_alu_instr *src_alu = nir_instr_as_alu(src_instr);<br>
> > +<br>
> > +         /* ALU operations with output_size == 0 should be scalarized.  We<br>
> > +          * will also see a bunch of vecN operations from scalarizing ALU<br>
> > +          * operations and, since they can easily be copy-propagated, they<br>
> > +          * are ok too.<br>
> > +          */<br>
> > +         return nir_op_infos[src_alu->op].output_size == 0 ||<br>
> > +                src_alu->op != nir_op_vec2 ||<br>
> > +                src_alu->op != nir_op_vec3 ||<br>
> > +                src_alu->op != nir_op_vec4;<br>
> > +      }<br>
> > +<br>
> > +      case nir_instr_type_phi: {<br>
> > +         nir_phi_instr *src_phi = nir_instr_as_phi(src_instr);<br>
> > +<br>
> > +         /* Insert an entry and mark it as scalarizable for now. That way<br>
> > +          * we don't recurse forever and a cycle in the depencence graph<br>
> > +          * won't automatically make us fail to scalarize.<br>
> > +          */<br>
> > +         entry = _mesa_hash_table_insert(state->phi_table, src_phi, (void *)1);<br>
> > +         bool scalarizable = should_lower_phi(src_phi, state);<br>
> > +         entry->data = (void *)scalarizable;<br>
> > +<br>
> > +         return scalarizable;<br>
> > +      }<br>
> > +<br>
> > +      default:<br>
> > +         /* We can't scalarize this type of instruction */<br>
> > +         return false;<br>
> > +      }<br>
> > +   }<br>
> > +<br>
> > +   return true;<br>
> > +}<br>
> > +<br>
> > +static bool<br>
> > +lower_phis_to_scalar_block(nir_block *block, void *void_state)<br>
> > +{<br>
> > +   struct lower_phis_to_scalar_state *state = void_state;<br>
> > +<br>
> > +   /* Find the last phi node in the block */<br>
> > +   nir_phi_instr *last_phi = NULL;<br>
> > +   nir_foreach_instr(block, instr) {<br>
> > +      if (instr->type != nir_instr_type_phi)<br>
> > +         break;<br>
> > +<br>
> > +      last_phi = nir_instr_as_phi(instr);<br>
> > +   }<br>
> > +<br>
> > +   /* We have to handle the phi nodes in their own pass due to the way<br>
> > +    * we're modifying the linked list of instructions.<br>
> > +    */<br>
> > +   nir_foreach_instr_safe(block, instr) {<br>
> > +      if (instr->type != nir_instr_type_phi)<br>
> > +         break;<br>
> > +<br>
> > +      nir_phi_instr *phi = nir_instr_as_phi(instr);<br>
> > +<br>
> > +      if (!should_lower_phi(phi, state))<br>
> > +         continue;<br>
> > +<br>
> > +      /* Create a vecN operation to combine the results.  Most of these<br>
> > +       * will be redundant, but copy propagation should clean them up for<br>
> > +       * us.  No need to add the complexity here.<br>
> > +       */<br>
> > +      nir_op vec_op;<br>
> > +      switch (phi->dest.ssa.num_components) {<br>
> > +      case 2: vec_op = nir_op_vec2; break;<br>
> > +      case 3: vec_op = nir_op_vec3; break;<br>
> > +      case 4: vec_op = nir_op_vec4; break;<br>
> > +      default: unreachable("Invalid number of components");<br>
> > +      }<br>
> > +<br>
> > +      nir_alu_instr *vec = nir_alu_instr_create(state->mem_ctx, vec_op);<br>
> > +      vec->dest.dest.is_ssa = true;<br>
> > +      nir_ssa_def_init(&vec->instr, &vec->dest.dest.ssa,<br>
> > +                       phi->dest.ssa.num_components, NULL);<br>
> > +      vec->dest.write_mask = (1 << phi->dest.ssa.num_components) - 1;<br>
> > +<br>
> > +      for (unsigned i = 0; i < phi->dest.ssa.num_components; i++) {<br>
> > +         nir_phi_instr *new_phi = nir_phi_instr_create(state->mem_ctx);<br>
> > +         new_phi->dest.is_ssa = true;<br>
> > +         nir_ssa_def_init(&new_phi->instr, &new_phi->dest.ssa, 1, NULL);<br>
> > +<br>
> > +         vec->src[i].src.is_ssa = true;<br>
> > +         vec->src[i].src.ssa = &new_phi->dest.ssa;<br>
> > +<br>
> > +         nir_foreach_phi_src(phi, src) {<br>
> > +            /* We need to insert a mov to grab the i'th component of src */<br>
> > +            nir_alu_instr *mov = nir_alu_instr_create(state->mem_ctx,<br>
> > +                                                      nir_op_imov);<br>
> > +            mov->dest.dest.is_ssa = true;<br>
> > +            nir_ssa_def_init(&mov->instr, &mov->dest.dest.ssa, 1, NULL);<br>
> > +            mov->dest.write_mask = 1;<br>
> > +            mov->src[0].src = nir_src_copy(src->src, state->mem_ctx);<br>
> > +            mov->src[0].swizzle[0] = i;<br>
> > +<br>
> > +            /* Insert at the end of the predecessor but before the jump */<br>
> > +            nir_instr *pred_last_instr = nir_block_last_instr(src->pred);<br>
> > +            if (pred_last_instr && pred_last_instr->type == nir_instr_type_jump)<br>
> > +               nir_instr_insert_before(pred_last_instr, &mov->instr);<br>
> > +            else<br>
> > +               nir_instr_insert_after_block(src->pred, &mov->instr);<br>
> > +<br>
> > +            nir_phi_src *new_src = ralloc(state->mem_ctx, nir_phi_src);<br>
> > +            new_src->pred = src->pred;<br>
> > +            new_src->src.is_ssa = true;<br>
> > +            new_src->src.ssa = &mov->dest.dest.ssa;<br>
> > +<br>
> > +            exec_list_push_tail(&new_phi->srcs, &new_src->node);<br>
> > +         }<br>
> > +<br>
> > +         nir_instr_insert_before(&phi->instr, &new_phi->instr);<br>
> > +      }<br>
> > +<br>
> > +      nir_instr_insert_after(&last_phi->instr, &vec->instr);<br>
> > +<br>
> > +      nir_ssa_def_rewrite_uses(&phi->dest.ssa,<br>
> > +                               nir_src_for_ssa(&vec->dest.dest.ssa),<br>
> > +                               state->mem_ctx);<br>
> > +<br>
> > +      ralloc_steal(state->dead_ctx, phi);<br>
> > +      nir_instr_remove(&phi->instr);<br>
> > +<br>
> > +      /* We're using the safe iterator and inserting all the newly<br>
> > +       * scalarized phi nodes before their non-scalarized version so that's<br>
> > +       * ok.  However, we are also inserting vec operations after all of<br>
><br>
> *all of the phi nodes (or "after the last phi node")<br>
><br>
> > +       * the last phi node so once we get here, we can't trust even the<br>
> > +       * safe iterator to stop properly.  We have to break manually.<br>
> > +       */<br>
><br>
> I think there's something I'm not getting here. If we're on the last<br>
> phi instruction, and we insert a vec instruction after it, isn't this<br>
> still ok because the safe iterator will just skip over the vec<br>
> instruction and go to the next instruction (or the end of the block)<br>
> and we'll just break out of the loop?</p>
<p dir="ltr">Yeah, that probably works.  This is kind of a remnant of an old bug that I'm not even 100% sure was caused by this.  I'll see if we can get rid of it.</p>
<p dir="ltr">><br>
> > +      if (instr == &last_phi->instr)<br>
> > +         break;<br>
> > +   }<br>
> > +<br>
> > +   return true;<br>
> > +}<br>
> > +<br>
> > +static void<br>
> > +lower_phis_to_scalar_impl(nir_function_impl *impl)<br>
> > +{<br>
> > +   struct lower_phis_to_scalar_state state;<br>
> > +<br>
> > +   state.mem_ctx = ralloc_parent(impl);<br>
> > +   state.dead_ctx = ralloc_context(NULL);<br>
> > +   state.phi_table = _mesa_hash_table_create(state.dead_ctx, _mesa_hash_pointer,<br>
> > +                                             _mesa_key_pointer_equal);<br>
> > +<br>
> > +   nir_foreach_block(impl, lower_phis_to_scalar_block, &state);<br>
> > +<br>
> > +   nir_metadata_preserve(impl, nir_metadata_block_index |<br>
> > +                               nir_metadata_dominance);<br>
> > +<br>
> > +   ralloc_free(state.dead_ctx);<br>
> > +}<br>
> > +<br>
> > +/** A pass that lowers vector phi nodes to scalar<br>
> > + *<br>
> > + * This pass loops through the blocks and lowers looks for vector phi nodes<br>
> > + * it can lower to scalar phi nodes.  Not all phi nodes are lowered.  For<br>
> > + * instance, if one of the sources is a non-scalarizable vector, then we<br>
> > + * don't bother lowering because that would generate hard-to-coalesce movs.<br>
> > + */<br>
><br>
> Just wondering... what's the advantage to not scalarizing all the phi<br>
> nodes? This is supposed to be used for scalar targets, so they<br>
> potentially might even want all the phi nodes to be scalar, so the<br>
> only non-scalar things are load and store instructions (we don't care<br>
> that much right now though). Woudn't splitting everything just save us<br>
> some of the work of splitting up mov instructions later on?</p>
<p dir="ltr">That's a valid question and it has a (hopefully equally valid) answer.  It comes down to coalescing.  Since phi sources can't swizzle, swizzles on phis have to be resolved by inserting a mov right before the phi.  If the source of the phi can't be vectorized, then that leaves us with an uncoalescable pile of moves just to pick off single components.  If we leave the phi in vector form, it makes it the responsibility of the sources to put things back together into a vector.</p>
<p dir="ltr">This also matters a lot for the backend register allocation and register coalescing.  I think this will be a lot better if we combine things into vectors on the source side rather than the destination.  In the case where the ssa def going into the phi is generated in the predecessor block, we can almost certainly coalesce the mov away.  In this case, we have an instruction that writes to a temporary register and then a mov into a component of the vector.  The intermediate register can very easily be coalesced into the vector.  On the other hand, if we have to recombine on the destination side, the movs will be very hard to get rid of because the source will be the register corresponding to the phi-web and will be assigned in several different basic blocks.  Therefore, if we have to reassemble into a vector on one side or the other, source-side is better.</p>
<p dir="ltr">But what if we don't care that it's a vector?  That's a valid question.  My first heuristic was to scalarize a phi node if and only if everything consuming that value was scalarizable.  However, without swizzles on phi sources, this leads to the component picking problem I mentioned above.  If we added swizzles to phis, this other heuristic is probably better.  (Side note: The thought has also ocurred to me to just let phis have ALU src and dest.  We are lowering them to ALU mov's after all.)</p>
<p dir="ltr">Hope that helps,<br>
--Jason Ekstrand</p>
<p dir="ltr">> > +void<br>
> > +nir_lower_phis_to_scalar(nir_shader *shader)<br>
> > +{<br>
> > +   nir_foreach_overload(shader, overload) {<br>
> > +      if (overload->impl)<br>
> > +         lower_phis_to_scalar_impl(overload->impl);<br>
> > +   }<br>
> > +}<br>
> > --<br>
> > 2.2.1<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>