<div dir="ltr"><div class="gmail_quote"><div dir="ltr">On Thu, Aug 23, 2018 at 8:16 PM Caio Marcelo de Oliveira Filho <<a href="mailto:caio.oliveira@intel.com">caio.oliveira@intel.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">On Sat, Jul 28, 2018 at 10:44:39PM -0700, Jason Ekstrand wrote:<br>
> This pass looks for variables with vector or array-of-vector types and<br>
> narrows the type to only the components used.<br>
> ---<br>
>  src/compiler/nir/nir.h            |   1 +<br>
>  src/compiler/nir/nir_split_vars.c | 694 ++++++++++++++++++++++++++++++<br>
>  2 files changed, 695 insertions(+)<br>
<br>
This patch and the one that enables the pass are<br>
<br>
Reviewed-by: Caio Marcelo de Oliveira Filho <<a href="mailto:caio.oliveira@intel.com" target="_blank">caio.oliveira@intel.com</a>><br></blockquote><div><br></div><div>Thanks!<br></div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
I have some suggestions below, pick the ones you like.<br></blockquote><div><br></div><div>I took all but one.  Thanks for your careful review.<br></div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
(...)<br>
<br>
> +static void<br>
> +mark_deref_used(nir_deref_instr *deref,<br>
> +                nir_component_mask_t comps_read,<br>
> +                nir_component_mask_t comps_written,<br>
> +                nir_deref_instr *copy_deref,<br>
> +                struct hash_table *var_usage_map,<br>
> +                nir_variable_mode modes,<br>
> +                void *mem_ctx)<br>
> +{<br>
> +   if (!(deref->mode & modes))<br>
> +      return;<br>
> +<br>
> +   nir_variable *var = nir_deref_instr_get_variable(deref);<br>
> +<br>
> +   struct vec_var_usage *usage =<br>
> +      get_vec_var_usage(var, var_usage_map, true, mem_ctx);<br>
> +   if (!usage)<br>
> +      return;<br>
> +<br>
> +   const unsigned num_var_components =<br>
> +      glsl_get_components(glsl_without_array_or_matrix(var->type));<br>
<br>
Consider at vec_var_usage creation time, setting a usage->comps (or<br>
similar) to "(1u << num_var_components) - 1".  Some cheap savings here<br>
and in other passes, also would result in less noise lines here and<br>
there.<br></blockquote><div><br></div><div>I considered it at one point but decided against it.  After going through the exercise at your suggestion, there were a lot more occurrences than I thought.  Thanks!<br></div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
> +<br>
> +   usage->comps_read |= comps_read & ((1u << num_var_components) - 1);<br>
> +   usage->comps_written |= comps_written & ((1u << num_var_components) - 1);<br>
> +<br>
> +   struct vec_var_usage *copy_usage = NULL;<br>
> +   if (copy_deref) {<br>
> +      copy_usage = get_vec_deref_usage(copy_deref, var_usage_map, modes,<br>
> +                                       true, mem_ctx);<br>
> +      if (copy_usage) {<br>
> +         if (usage->vars_copied == NULL) {<br>
> +            usage->vars_copied = _mesa_set_create(mem_ctx, _mesa_hash_pointer,<br>
> +                                                  _mesa_key_pointer_equal);<br>
> +         }<br>
> +         _mesa_set_add(usage->vars_copied, copy_usage);<br>
> +      } else {<br>
> +         usage->has_external_copy = true;<br>
> +      }<br>
> +   }<br>
> +<br>
> +   nir_deref_path path;<br>
> +   nir_deref_path_init(&path, deref, mem_ctx);<br>
> +<br>
> +   nir_deref_path copy_path;<br>
> +   if (copy_usage)<br>
> +      nir_deref_path_init(&copy_path, copy_deref, mem_ctx);<br>
> +<br>
> +   unsigned copy_i = 0;<br>
> +   for (unsigned i = 0; i < usage->num_levels; i++) {<br>
> +      struct array_level_usage *level = &usage->levels[i];<br>
> +      nir_deref_instr *deref = path.path[i + 1];<br>
> +      assert(deref->deref_type == nir_deref_type_array ||<br>
> +             deref->deref_type == nir_deref_type_array_wildcard);<br>
> +<br>
> +      unsigned max_used;<br>
> +      if (deref->deref_type == nir_deref_type_array) {<br>
> +         nir_const_value *const_index =<br>
> +            nir_src_as_const_value(deref->arr.index);<br>
> +         max_used = const_index ? const_index->u32[0] : UINT_MAX;<br>
> +      } else {<br>
> +         /* For wildcards, we read or wrote the whole thing. */<br>
> +         assert(deref->deref_type == nir_deref_type_array_wildcard);<br>
> +         max_used = level->array_len - 1;<br>
> +<br>
> +         if (copy_usage) {<br>
> +            /* Match each wildcard level with the level on copy_usage */<br>
> +            for (; copy_path.path[copy_i + 1]; copy_i++) {<br>
> +               if (copy_path.path[copy_i + 1]->deref_type ==<br>
> +                   nir_deref_type_array_wildcard)<br>
> +                  break;<br>
> +            }<br>
> +            struct array_level_usage *copy_level =<br>
> +               &copy_usage->levels[copy_i++];<br>
> +<br>
> +            if (level->levels_copied == NULL) {<br>
> +               level->levels_copied =<br>
> +                  _mesa_set_create(mem_ctx, _mesa_hash_pointer,<br>
> +                                   _mesa_key_pointer_equal);<br>
> +            }<br>
> +            _mesa_set_add(level->levels_copied, copy_level);<br>
<br>
Since once level->has_external_copy is set we don't really use the<br>
levels_copied, maybe wrap the bootstrap/set block with if<br>
(!level->has_external_copy)?<br></blockquote><div><br></div><div>_mesa_set_add is cheap and I think I'd rather keep track of all the internal copies for now in case this pass changes to need them.  I could see this triggering some time but then it's very dependent on the external copy being hit before the internal copy.  I think I'd rather leave this one as-is if you don't mind.<br></div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
> +         } else {<br>
> +            /* We have a wildcard and we don't know where this copy came from,<br>
> +             * flag it and we'll know to not shorten this array.<br>
> +             */<br>
<br>
Maybe instead of "we don't know" say that it comes from a variable<br>
from other mode.<br></blockquote><div><br></div><div>I said "comes from a variable we aren't tracking"<br></div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
> +            level->has_external_copy = true;<br>
> +         }<br>
> +      }<br>
> +<br>
> +      if (comps_written)<br>
> +         level->max_written = MAX2(level->max_written, max_used);<br>
> +      if (comps_read)<br>
> +         level->max_read = MAX2(level->max_read, max_used);<br>
> +   }<br>
> +}<br>
<br>
(...)<br>
<br>
> +static void<br>
> +find_used_components_impl(nir_function_impl *impl,<br>
> +                          struct hash_table *var_usage_map,<br>
> +                          nir_variable_mode modes,<br>
> +                          void *mem_ctx)<br>
> +{<br>
> +   nir_foreach_block(block, impl) {<br>
> +      nir_foreach_instr_safe(instr, block) {<br>
<br>
Unless I'm missing something, we don't need "safe" variant here<br></blockquote><div><br></div><div>Correct.<br></div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
(...)<br>
<br>
> +static bool<br>
> +shrink_vec_var_list(struct exec_list *vars,<br>
> +                    struct hash_table *var_usage_map)<br>
> +{<br>
> +   /* Initialize the components kept field of each variable.  This is the<br>
> +    * AND of the components written and components read.  If a component is<br>
> +    * written but never read, it's dead.  If it is read but never written,<br>
> +    * then all values read are undefined garbage and we may as well not read<br>
> +    * them.<br>
> +    *<br>
> +    * The same logic applies to the array length.  We make the array length<br>
> +    * the minimum needed required length between read and write and plan to<br>
> +    * discard any OOB access.  The one exception here is indirect writes<br>
> +    * because we don't know where they will land and we can't shrink an array<br>
> +    * with indirect writes because previously in-bounds writes may become<br>
> +    * out-of-bounds and have undefined behavior.<br>
> +    *<br>
> +    * Also, if we have a copy that to/from something we can't shrink, we need<br>
> +    * to leave components and array_len of any wildcards alone.<br>
> +    */<br>
> +   nir_foreach_variable(var, vars) {<br>
> +      struct vec_var_usage *usage =<br>
> +         get_vec_var_usage(var, var_usage_map, false, NULL);<br>
> +      if (!usage)<br>
> +         continue;<br>
<br>
After reading I thought here and in the fix-point loop would be a<br>
better call to iterate directly var_usage_map.  Due to the way we<br>
reuse var_usage_map, we would have to skip based on modes, which kind<br>
of spoil things a bit, but maybe still a win.  Or use different sets<br>
and join them for the final step... Probably won't make much<br>
difference.<br>
<br>
<br>
> +<br>
> +      const unsigned var_num_components =<br>
> +         glsl_get_components(glsl_without_array_or_matrix(var->type));<br>
> +<br>
> +      assert(usage->comps_kept == 0);<br>
> +      if (usage->has_external_copy)<br>
> +         usage->comps_kept = (1u << var_num_components) - 1;<br>
> +      else<br>
> +         usage->comps_kept = usage->comps_read & usage->comps_written;<br>
> +<br>
> +      for (unsigned i = 0; i < usage->num_levels; i++) {<br>
> +         struct array_level_usage *level = &usage->levels[i];<br>
> +         assert(level->array_len > 0);<br>
> +<br>
> +         if (level->max_written == UINT_MAX || level->has_external_copy)<br>
> +            continue; /* Can't shrink */<br>
> +<br>
> +         unsigned max_used = MIN2(level->max_read, level->max_written);<br>
> +         level->array_len = MIN2(max_used, level->array_len - 1) + 1;<br>
> +      }<br>
> +   }<br>
> +<br>
> +   /* In order for variable copies to work, we have to have the same data type<br>
> +    * on the source and the destination.  In order to satisfy this, we run a<br>
> +    * little fixed-point algorithm to transitively ensure that we get enough<br>
> +    * components and array elements for this to hold for all copies.<br>
> +    */<br>
> +   bool fp_progress;<br>
> +   do {<br>
> +      fp_progress = false;<br>
> +      nir_foreach_variable(var, vars) {<br>
> +         struct vec_var_usage *var_usage =<br>
> +            get_vec_var_usage(var, var_usage_map, false, NULL);<br>
> +         if (!var_usage || !var_usage->vars_copied)<br>
> +            continue;<br>
> +<br>
> +         struct set_entry *copy_entry;<br>
> +         set_foreach(var_usage->vars_copied, copy_entry) {<br>
> +            const struct vec_var_usage *copy_usage = copy_entry->key;<br>
> +            if (copy_usage->comps_kept & ~var_usage->comps_kept) {<br>
> +               var_usage->comps_kept |= copy_usage->comps_kept;<br>
<br>
Could we set both sides here, i.e. do<br>
"copy_usage->comps_kept = var_usage->comps_kept"?  The reasoning<br>
is that it might save an iteration, e.g. A <-> C and B <-> C,<br>
but iteration happens in order: A, B then C.  Information from A<br>
will take two iterations to propagate to B.<br></blockquote><div><br></div><div>Sure.  Easy enough to do.  At first I thought it wouldn't save us anything so I didn't do it but I wasn't thinking about the third variable.  You're right, it may be more efficient.  I've also done the same thing with the array length below.<br></div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
> +               fp_progress = true;<br>
> +            }<br>
> +         }<br>
> +<br>
> +         for (unsigned i = 0; i < var_usage->num_levels; i++) {<br>
> +            struct array_level_usage *var_level = &var_usage->levels[i];<br>
> +            if (!var_level->levels_copied)<br>
> +               continue;<br>
> +<br>
> +            set_foreach(var_level->levels_copied, copy_entry) {<br>
> +               const struct array_level_usage *copy_level = copy_entry->key;<br>
> +               if (var_level->array_len < copy_level->array_len) {<br>
> +                  var_level->array_len = copy_level->array_len;<br>
> +                  fp_progress = true;<br>
> +               }<br>
> +            }<br>
> +         }<br>
> +      }<br>
> +   } while (fp_progress);<br>
> +<br>
> +   bool vars_shrunk = false;<br>
> +   nir_foreach_variable_safe(var, vars) {<br>
> +      struct vec_var_usage *usage =<br>
> +         get_vec_var_usage(var, var_usage_map, false, NULL);<br>
> +      if (!usage)<br>
> +         continue;<br>
> +<br>
> +      bool shrunk = false;<br>
> +      const struct glsl_type *vec_type = var->type;<br>
> +      for (unsigned i = 0; i < usage->num_levels; i++) {<br>
> +         /* If we've reduced the array to zero elements at some level, just<br>
> +          * set comps_kept to 0 and delete the variable.<br>
> +          */<br>
> +         if (usage->levels[i].array_len == 0)<br>
> +            usage->comps_kept = 0;<br>
<br>
Add a break here.  The rest of the loop execution would try to find a<br>
shrink situation, but regardless the result it will be ignored since<br>
we bail when comps_kept == 0.<br></blockquote><div><br></div><div>Done.<br></div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
> +<br>
> +         assert(usage->levels[i].array_len <= glsl_get_length(vec_type));<br>
> +         if (usage->levels[i].array_len < glsl_get_length(vec_type))<br>
> +            shrunk = true;<br>
> +         vec_type = glsl_get_array_element(vec_type);<br>
> +      }<br>
> +      assert(glsl_type_is_vector_or_scalar(vec_type));<br>
> +<br>
> +      assert(usage->comps_kept < (1u << glsl_get_components(vec_type)));<br>
> +      if (usage->comps_kept != (1u << glsl_get_components(vec_type)) - 1)<br>
> +         shrunk = true;<br>
> +<br>
> +      if (usage->comps_kept == 0) {<br>
> +         /* This variable is dead, remove it */<br>
> +         vars_shrunk = true;<br>
> +         exec_node_remove(&var->node);<br>
> +         continue;<br>
> +      }<br>
> +<br>
> +      if (!shrunk) {<br>
> +         /* This variable doesn't need to be shrunk.  Remove it from the<br>
> +          * hash table so later passes will ignore it.<br>
<br>
s/later passes/later steps/ assuming you are referring to the access<br>
fixing step.</blockquote><div><br></div><div>Done.<br></div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
> +          */<br>
> +         _mesa_hash_table_remove_key(var_usage_map, var);<br>
> +         continue;<br>
> +      }<br>
<br>
(...)<br>
<br>
> +static void<br>
> +shrink_vec_var_access_impl(nir_function_impl *impl,<br>
> +                           struct hash_table *var_usage_map,<br>
> +                           nir_variable_mode modes)<br>
> +{<br>
> +   nir_builder b;<br>
> +   nir_builder_init(&b, impl);<br>
> +<br>
> +   nir_foreach_block(block, impl) {<br>
> +      nir_foreach_instr_safe(instr, block) {<br>
> +         switch (instr->type) {<br>
> +         case nir_instr_type_deref: {<br>
> +            nir_deref_instr *deref = nir_instr_as_deref(instr);<br>
> +            if (!(deref->mode & modes))<br>
> +               break;<br>
> +<br>
> +            /* Clean up any dead derefs we find lying around.  They may refer<br>
> +             * to variables we've deleted.<br>
> +             */<br>
> +            if (nir_deref_instr_remove_if_unused(deref))<br>
> +               break;<br>
> +<br>
> +            /* We don't need to check if this is one of the derefs we're<br>
> +             * shrinking because this is a no-op if it isn't.  The worst that<br>
> +             * could happen is that we accidentally fix an invalid deref.<br>
> +             */<br>
<br>
Consider prefixing this comment with something like: "Update the new<br>
variable type in the deref."<br></blockquote><div><br></div><div>Done. <br></div></div></div>