<p dir="ltr"><br>
On Sep 8, 2015 23:27, "Eduardo Lima Mitev" <<a href="mailto:elima@igalia.com">elima@igalia.com</a>> wrote:<br>
><br>
> This pass will propagate the destination components of a vecN instructions,<br>
> as destination of the instructions that define its sources; if certain<br>
> conditions are met.<br>
><br>
> If all the components of the destination register in the vecN instruction<br>
> can be propagated, the instruction is removed. Otherwise, a new, reduced<br>
> vecN instruction is emitted with the channels that remained.<br>
><br>
> This effectively coalesces registers and reduces indirection.<br>
><br>
> By now, this pass will only propagate to ALU instructions, but it could<br>
> be extended to include other instructions like load_input intrinsic.<br>
><br>
> It also propagates to instructions within the same block as the vecN<br>
> instruction. But it could be made to work cross-block in the future,<br>
> though there are non-trivial issues with this like considering<br>
> registers that are written in different branches of a conditional.<br>
> More analysis is needed to correctly cover these cases.<br>
><br>
> This pass works on a NIR shader in final form (after SSA), and is<br>
> expected to run before nir_lower_vec_to_movs().<br>
> ---<br>
>  src/glsl/Makefile.sources                 |   1 +<br>
>  src/glsl/nir/nir.h                        |   1 +<br>
>  src/glsl/nir/nir_lower_vec_and_coalesce.c | 301 ++++++++++++++++++++++++++++++<br>
>  3 files changed, 303 insertions(+)<br>
>  create mode 100644 src/glsl/nir/nir_lower_vec_and_coalesce.c<br>
><br>
> diff --git a/src/glsl/Makefile.sources b/src/glsl/Makefile.sources<br>
> index c422303..015f242 100644<br>
> --- a/src/glsl/Makefile.sources<br>
> +++ b/src/glsl/Makefile.sources<br>
> @@ -48,6 +48,7 @@ NIR_FILES = \<br>
>         nir/nir_lower_vars_to_ssa.c \<br>
>         nir/nir_lower_var_copies.c \<br>
>         nir/nir_lower_vec_to_movs.c \<br>
> +       nir/nir_lower_vec_and_coalesce.c \<br>
>         nir/nir_metadata.c \<br>
>         nir/nir_normalize_cubemap_coords.c \<br>
>         nir/nir_opt_constant_folding.c \<br>
> diff --git a/src/glsl/nir/nir.h b/src/glsl/nir/nir.h<br>
> index 3c375f3..6a89f1d 100644<br>
> --- a/src/glsl/nir/nir.h<br>
> +++ b/src/glsl/nir/nir.h<br>
> @@ -1786,6 +1786,7 @@ void nir_lower_vars_to_ssa(nir_shader *shader);<br>
>  void nir_remove_dead_variables(nir_shader *shader);<br>
><br>
>  void nir_lower_vec_to_movs(nir_shader *shader);<br>
> +void nir_lower_vec_and_coalesce(nir_shader *shader);<br>
>  void nir_lower_alu_to_scalar(nir_shader *shader);<br>
>  void nir_lower_load_const_to_scalar(nir_shader *shader);<br>
><br>
> diff --git a/src/glsl/nir/nir_lower_vec_and_coalesce.c b/src/glsl/nir/nir_lower_vec_and_coalesce.c<br>
> new file mode 100644<br>
> index 0000000..2b21ec1<br>
> --- /dev/null<br>
> +++ b/src/glsl/nir/nir_lower_vec_and_coalesce.c<br>
> @@ -0,0 +1,301 @@<br>
> +/*<br>
> + * Copyright © 2015 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>
> + *    Eduardo Lima Mitev (<a href="mailto:elima@igalia.com">elima@igalia.com</a>)<br>
> + *<br>
> + */<br>
> +<br>
> +#include "nir.h"<br>
> +<br>
> +/*<br>
> + * Implements a pass that lowers vecN instructions by propagating the<br>
> + * components of their destinations, as the destination of the<br>
> + * instructions that defines the sources of the vecN instruction.<br>
> + *<br>
> + * This effectively coalesces registers and reduces indirection.<br>
> + *<br>
> + * If all the components of the destination register in the vecN<br>
> + * instruction can be propagated, the instruction is removed. Otherwise,<br>
> + * a new, reduced vecN instruction is emitted with the channels that<br>
> + * remained.<br>
> + *<br>
> + * By now, this pass will only propagate to ALU instructions, but it could<br>
> + * be extended to include load_const instructions or some intrinsics like<br>
> + * load_input.<br>
> + *<br>
> + * This pass works on a NIR shader in final form (after SSA), and is<br>
> + * expected to run before nir_lower_vec_to_movs().<br>
> + */<br>
> +<br>
> +/**<br>
> + * Clone an ALU instruction and override the destination with the one given by<br>
> + * new_dest. It copies sources from original ALU to the new one, adjusting<br>
> + * their swizzles.<br>
> + *<br>
> + * Returns the new ALU instruction.<br>
> + */<br>
> +static nir_alu_instr *<br>
> +clone_alu_instr_and_override_dest(nir_alu_instr *alu_instr,<br>
> +                                  nir_alu_dest *new_dest, unsigned index,<br>
> +                                  void *mem_ctx)<br>
> +{<br>
> +   nir_alu_instr *new_alu_instr = nir_alu_instr_create(mem_ctx, alu_instr->op);<br>
> +<br>
> +   for (unsigned i = 0; i < nir_op_infos[alu_instr->op].num_inputs; i++) {<br>
> +      nir_alu_src_copy(&new_alu_instr->src[i], &alu_instr->src[i], mem_ctx);<br>
> +<br>
> +      /* Dot-product operations should not be swizzle'd */<br>
> +      switch (alu_instr->op) {<br>
> +      case nir_op_fdot2:<br>
> +      case nir_op_fdot3:<br>
> +      case nir_op_fdot4:<br>
> +         continue;<br>
> +      default:<br>
> +         break;<br>
> +      }</p>
<p dir="ltr">You should use nir_op_infos[alu_instr->op].output_size == 0 to determine whether or not the instruction is per-channel.</p>
<p dir="ltr">You also need to check that nir_op_infos[op].input_sizes[i] == 0 to make sure that the components of the source actually map to components of of the destination.  If output_size == 0 but input_sizes[i] != 0 for some source, we should probably bail entirely.  The one exception to this that I can think of off-hand is bcsel where it has a single one-component source and you are free to reswizzle the others.</p>
<p dir="ltr">> +      new_alu_instr->src[i].swizzle[index] =<br>
> +         alu_instr->src[i].swizzle[0];<br>
> +   }<br>
> +<br>
> +   nir_alu_dest_copy(&new_alu_instr->dest, new_dest, mem_ctx);<br>
> +   new_alu_instr->dest.write_mask = 1 << index;<br>
> +<br>
> +   return new_alu_instr;<br>
> +}<br>
> +<br>
> +/**<br>
> + * Simply searches an entry in the list, and return true if found, false<br>
> + * otherwise.<br>
> + */<br>
> +static bool<br>
> +register_already_tracked(const nir_register *reg, nir_register *list[4],<br>
> +                         unsigned num_items)<br>
> +{<br>
> +   for (unsigned i = 0; i < num_items; i++) {<br>
> +      if (list[i] == reg)<br>
> +         return true;<br>
> +   }<br>
> +   return false;<br>
> +}<br>
> +<br>
> +/**<br>
> + * Tells whether an instruction comes before another one inside the same<br>
> + * block. Return true if 'target' appears after 'other', false otherwise.<br>
> + */<br>
> +static bool<br>
> +instr_is_previous_in_block(nir_instr *target, nir_instr *other)<br>
> +{<br>
> +   nir_instr *prev = target;<br>
> +   while ((prev = nir_instr_prev(prev)) != NULL) {<br>
> +      if (prev == other)<br>
> +         return true;<br>
> +   }<br>
> +   return false;<br>
> +}<br>
> +<br>
> +static bool<br>
> +lower_vec_and_coalesce_block(nir_block *block, void *mem_ctx)<br>
> +{<br>
> +   nir_foreach_instr_safe(block, instr) {<br>
> +      if (instr->type != nir_instr_type_alu)<br>
> +         continue;<br>
> +<br>
> +      nir_alu_instr *vec = nir_instr_as_alu(instr);<br>
> +<br>
> +      switch (vec->op) {<br>
> +      case nir_op_vec2:<br>
> +      case nir_op_vec3:<br>
> +      case nir_op_vec4:<br>
> +         break;<br>
> +      default:<br>
> +         continue; /* The loop */<br>
> +      }<br>
> +<br>
> +      /* Since we insert multiple MOVs, we have to be non-SSA. */<br>
> +      assert(!vec->dest.dest.is_ssa);<br>
> +<br>
> +      unsigned finished_write_mask = 0;<br>
> +      nir_register *tracked_registers[4] = {0};<br>
> +      unsigned num_tracked_registers = 0;<br>
> +<br>
> +      for (unsigned i = 0; i < 4; i++) {<br>
> +         if (!(vec->dest.write_mask & (1 << i)))<br>
> +            continue;<br>
> +<br>
> +         if (vec->src[i].src.is_ssa)<br>
> +            continue;<br>
> +<br>
> +         nir_register *reg = vec->src[i].src.reg.reg;<br>
> +<br>
> +         /* By now, lets ignore the sources whose register is the same as the<br>
> +          * destination register of the vecN instruction.<br>
> +          */<br>
> +         if (reg == vec->dest.dest.reg.reg)<br>
> +            continue;<br>
> +<br>
> +         nir_foreach_def_safe(reg, src) {<br>
> +            nir_instr *parent_instr = src->reg.parent_instr;<br>
> +<br>
> +            /* By now, we enforce that the parent instruction must be in the<br>
> +             * same block as the vecN instruction. This is because if it is in<br>
> +             * other block, like a branch of a conditional, we need to be sure<br>
> +             * that the register component is propagated in all branches.<br>
> +             * Otherwise we cannot reduce the current vecN instruction.<br>
> +             * @TODO: To extend this pass to work across blocks, more analysis<br>
> +             * is needed.<br>
> +             */<br>
> +            if (parent_instr->block != block)<br>
> +               continue;<br>
> +<br>
> +            /* The instruction to propagate to must come before the vecN<br>
> +             * instruction inside the block.<br>
> +             */<br>
> +            if (!instr_is_previous_in_block(instr, parent_instr))<br>
> +               continue;<br>
> +<br>
> +            /* We only coalesce registers written by ALU instructions, by now.<br>
> +             * @TODO: consider other type of instructions, like intrinsics, etc.<br>
> +             */<br>
> +            if (parent_instr->type != nir_instr_type_alu)<br>
> +               continue;<br>
> +<br>
> +            nir_alu_instr *parent_alu_instr = nir_instr_as_alu(parent_instr);<br>
> +            nir_register *parent_dest_reg = parent_alu_instr->dest.dest.reg.reg;<br>
> +<br>
> +            /* We only override dest registers that are only used once, and in<br>
> +             * this vecX instruction.<br>
> +             * @TODO: In the future we might consider registers used more than<br>
> +             * once as sources of the same vecX instruction.<br>
> +             */</p>
<p dir="ltr">Yeah, we definitely want to handle that case and we want to handle it by emitting a single instruction that does multiple channels.</p>
<p dir="ltr">> +            if (list_length(&parent_dest_reg->uses) != 1)<br>
> +               continue;<br>
> +<br>
> +            nir_alu_instr *new_alu_instr =<br>
> +               clone_alu_instr_and_override_dest(parent_alu_instr, &vec->dest,<br>
> +                                                 i, mem_ctx);</p>
<p dir="ltr">This mess would be a lot easier if we were still in SSA form.  I've been thinking for a while that we should make lower_vec_to_movs work in SSA form (it would replace SSA destinations with register destinations).  Then use partial SSA lowering like we do in the FS backend.  That would make this pass much easier together correct.</p>
<p dir="ltr">In general, I'm kind of thinking that this might be better done as an extension to the lower_vec_to_moves pass or as a new version of it.  You would loop through the sources of a vecN and, if you can rewrite a destination, you do and if you can't, you insert moves.  We may also want to  it handle SSA while we're at it.  Note: it will get run after nir_from_ssa so it will have to handle partial SSA where some destinations are SSA and some are registers.</p>
<p dir="ltr">Does this seem reasonable?</p>
<p dir="ltr">> +            /* For imov and fmov operations, we set the original swizzle from<br>
> +             * the vecN source, directly into the new instruction's only<br>
> +             * source.<br>
> +             */<br>
> +            if (parent_alu_instr->op == nir_op_imov ||<br>
> +                parent_alu_instr->op == nir_op_fmov) {<br>
> +               new_alu_instr->src[0].swizzle[0] = vec->src[i].swizzle[i];<br>
> +            }<br>
> +<br>
> +            finished_write_mask |= new_alu_instr->dest.write_mask;<br>
> +<br>
> +            /* Remove the old instruction */<br>
> +            nir_instr_remove(&parent_alu_instr->instr);<br>
> +            ralloc_free(parent_alu_instr);<br>
> +<br>
> +            /* Track the intermediate register, to remove it later if not used */<br>
> +            if (!register_already_tracked(parent_dest_reg, tracked_registers,<br>
> +                                          num_tracked_registers)) {<br>
> +               tracked_registers[num_tracked_registers] = parent_dest_reg;<br>
> +               num_tracked_registers++;<br>
> +            }<br>
> +<br>
> +            /* Insert the new instruction with the overwritten destination */<br>
> +            nir_instr_insert_before(&vec->instr, &new_alu_instr->instr);<br>
> +         }<br>
> +      }<br>
> +<br>
> +      if (finished_write_mask == 0)<br>
> +         continue;<br>
> +<br>
> +      nir_alu_instr *new_alu_instr = nir_alu_instr_create(mem_ctx, nir_op_vec4);<br>
> +      nir_alu_dest_copy(&new_alu_instr->dest, &vec->dest, mem_ctx);<br>
> +      new_alu_instr->dest.write_mask = 0;<br>
> +<br>
> +      unsigned c = 0;<br>
> +      for (unsigned i = 0; i < nir_op_infos[vec->op].num_inputs; i++) {<br>
> +         if (!(vec->dest.write_mask & (1 << i)))<br>
> +            continue;<br>
> +<br>
> +         if (finished_write_mask & (1 << i))<br>
> +            continue;<br>
> +<br>
> +         nir_alu_src_copy(&new_alu_instr->src[c], &vec->src[i], mem_ctx);<br>
> +         new_alu_instr->src[c].swizzle[i] = vec->src[i].swizzle[c];<br>
> +<br>
> +         new_alu_instr->dest.write_mask |= (1 << i);<br>
> +<br>
> +         c++;<br>
> +      }<br>
> +<br>
> +      switch (c) {<br>
> +      case 0:<br>
> +         /* we coalesced all register components, so no need to emit any<br>
> +          * reduced instruction.<br>
> +          */<br>
> +         ralloc_free(new_alu_instr);<br>
> +         break;<br>
> +      case 1:<br>
> +         new_alu_instr->op = nir_op_imov;<br>
> +         break;<br>
> +      case 2:<br>
> +         new_alu_instr->op = nir_op_vec2;<br>
> +         break;<br>
> +      case 3:<br>
> +         new_alu_instr->op = nir_op_vec3;<br>
> +         break;<br>
> +      default:<br>
> +         unreachable("Not reached");<br>
> +      }<br>
> +<br>
> +      if (c != 0)<br>
> +         nir_instr_insert_before(&vec->instr, &new_alu_instr->instr);<br>
> +<br>
> +      /* Remove the original vecN instruction */<br>
> +      nir_instr_remove(&vec->instr);<br>
> +      ralloc_free(vec);<br>
> +<br>
> +      /* Remove the tracked registers that are no longer used */<br>
> +      for (unsigned i = 0; i < num_tracked_registers; i++) {<br>
> +         if (list_length(&tracked_registers[i]->defs) == 0 &&<br>
> +             list_length(&tracked_registers[i]->uses) == 0 &&<br>
> +             list_length(&tracked_registers[i]->if_uses) == 0) {<br>
> +            nir_reg_remove(tracked_registers[i]);<br>
> +         }<br>
> +      }<br>
> +   }<br>
> +<br>
> +   return true;<br>
> +}<br>
> +<br>
> +static void<br>
> +nir_lower_vec_and_coalesce_impl(nir_function_impl *impl)<br>
> +{<br>
> +   nir_foreach_block(impl, lower_vec_and_coalesce_block, ralloc_parent(impl));<br>
> +}<br>
> +<br>
> +void<br>
> +nir_lower_vec_and_coalesce(nir_shader *shader)<br>
> +{<br>
> +   nir_foreach_overload(shader, overload) {<br>
> +      if (overload->impl)<br>
> +         nir_lower_vec_and_coalesce_impl(overload->impl);<br>
> +   }<br>
> +}<br>
> --<br>
> 2.4.6<br>
><br>
</p>