[Mesa-dev] [PATCH v2 6/9] nir: Add a large constants optimization pass

Timothy Arceri tarceri at itsqueeze.com
Sat Jun 30 04:56:10 UTC 2018


On 30/06/18 10:13, Jason Ekstrand wrote:
> This pass searches for reasonably large local variables which can be
> statically proven to be constant and moves them into shader constant
> data.  This is especially useful when large tables are baked into the
> shader source code because they can be moved into a UBO by the driver to
> reduce register pressure and make indirect access cheaper.
> 
> v2 (Jason Ekstrand):
>   - Use a size/align function to ensure we get the right alignments
>   - Use the newly added deref offset helpers
> ---
>   src/compiler/Makefile.sources              |   1 +
>   src/compiler/nir/meson.build               |   1 +
>   src/compiler/nir/nir.h                     |   4 +
>   src/compiler/nir/nir_opt_large_constants.c | 301 +++++++++++++++++++++
>   4 files changed, 307 insertions(+)
>   create mode 100644 src/compiler/nir/nir_opt_large_constants.c
> 
> diff --git a/src/compiler/Makefile.sources b/src/compiler/Makefile.sources
> index 0fcbc5c5c5b..9e3fbdc2612 100644
> --- a/src/compiler/Makefile.sources
> +++ b/src/compiler/Makefile.sources
> @@ -276,6 +276,7 @@ NIR_FILES = \
>   	nir/nir_opt_if.c \
>   	nir/nir_opt_intrinsics.c \
>   	nir/nir_opt_loop_unroll.c \
> +	nir/nir_opt_large_constants.c \
>   	nir/nir_opt_move_comparisons.c \
>   	nir/nir_opt_move_load_ubo.c \
>   	nir/nir_opt_peephole_select.c \
> diff --git a/src/compiler/nir/meson.build b/src/compiler/nir/meson.build
> index eb7fb7b121e..28aa8de7014 100644
> --- a/src/compiler/nir/meson.build
> +++ b/src/compiler/nir/meson.build
> @@ -160,6 +160,7 @@ files_libnir = files(
>     'nir_opt_global_to_local.c',
>     'nir_opt_if.c',
>     'nir_opt_intrinsics.c',
> +  'nir_opt_large_constants.c',
>     'nir_opt_loop_unroll.c',
>     'nir_opt_move_comparisons.c',
>     'nir_opt_move_load_ubo.c',
> diff --git a/src/compiler/nir/nir.h b/src/compiler/nir/nir.h
> index cc5f88d6f54..92ab3a699cc 100644
> --- a/src/compiler/nir/nir.h
> +++ b/src/compiler/nir/nir.h
> @@ -2905,6 +2905,10 @@ bool nir_opt_if(nir_shader *shader);
>   
>   bool nir_opt_intrinsics(nir_shader *shader);
>   
> +bool nir_opt_large_constants(nir_shader *shader,
> +                             glsl_type_size_align_func size_align,
> +                             unsigned threshold);
> +
>   bool nir_opt_loop_unroll(nir_shader *shader, nir_variable_mode indirect_mask);
>   
>   bool nir_opt_move_comparisons(nir_shader *shader);
> diff --git a/src/compiler/nir/nir_opt_large_constants.c b/src/compiler/nir/nir_opt_large_constants.c
> new file mode 100644
> index 00000000000..027c6e8e5b5
> --- /dev/null
> +++ b/src/compiler/nir/nir_opt_large_constants.c
> @@ -0,0 +1,301 @@
> +/*
> + * Copyright © 2018 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_deref.h"
> +
> +struct var_info {
> +   bool is_constant;
> +   bool found_read;
> +};
> +
> +static nir_ssa_def *
> +build_constant_load(nir_builder *b, nir_deref_instr *deref,
> +                    glsl_type_size_align_func size_align)
> +{
> +   nir_variable *var = nir_deref_instr_get_variable(deref);
> +
> +   const unsigned bit_size = glsl_get_bit_size(deref->type);
> +   const unsigned num_components = glsl_get_vector_elements(deref->type);
> +
> +   UNUSED unsigned var_size, var_align;
> +   size_align(var->type, &var_size, &var_align);
> +   assert(var->data.location % var_align == 0);
> +
> +   nir_intrinsic_instr *load =
> +      nir_intrinsic_instr_create(b->shader, nir_intrinsic_load_constant);
> +   load->num_components = num_components;
> +   nir_intrinsic_set_base(load, var->data.location);
> +   nir_intrinsic_set_range(load, var_size);
> +   load->src[0] = nir_src_for_ssa(nir_build_deref_offset(b, deref, size_align));
> +   nir_ssa_dest_init(&load->instr, &load->dest,
> +                     num_components, bit_size, NULL);
> +   nir_builder_instr_insert(b, &load->instr);
> +
> +   return &load->dest.ssa;
> +}
> +
> +static void
> +handle_constant_store(nir_builder *b, nir_intrinsic_instr *store,
> +                      glsl_type_size_align_func size_align)
> +{
> +   nir_deref_instr *deref = nir_src_as_deref(store->src[0]);
> +   assert(!nir_deref_instr_has_indirect(deref));
> +
> +   nir_variable *var = nir_deref_instr_get_variable(deref);
> +
> +   const unsigned bit_size = glsl_get_bit_size(deref->type);
> +   const unsigned num_components = glsl_get_vector_elements(deref->type);
> +
> +   char *dst = (char *)b->shader->constant_data +
> +               var->data.location +
> +               nir_deref_instr_get_const_offset(deref, size_align);
> +
> +   nir_const_value *val = nir_src_as_const_value(store->src[1]);
> +   switch (bit_size) {
> +   case 8:
> +      for (unsigned i = 0; i < num_components; i++)
> +         ((uint8_t *)dst)[i] = val->u8[i];
> +      break;
> +
> +   case 16:
> +      for (unsigned i = 0; i < num_components; i++)
> +         ((uint16_t *)dst)[i] = val->u16[i];
> +      break;
> +
> +   case 32:
> +      for (unsigned i = 0; i < num_components; i++)
> +         ((uint32_t *)dst)[i] = val->u32[i];
> +      break;
> +
> +   case 64:
> +      for (unsigned i = 0; i < num_components; i++)
> +         ((uint64_t *)dst)[i] = val->u64[i];
> +      break;
> +
> +   default:
> +      unreachable("Invalid bit size");
> +   }
> +}
> +
> +/** Lower large constant variables to shader constant data
> + *
> + * This pass looks for large (type_size(var->type) > threshold) variables
> + * which are statically constant and moves them into shader constant data.
> + * This is especially useful when large tables are baked into the shader
> + * source code because they can be moved into a UBO by the driver to reduce
> + * register pressure and make indirect access cheaper.
> + */
> +bool
> +nir_opt_large_constants(nir_shader *shader,
> +                        glsl_type_size_align_func size_align,
> +                        unsigned threshold)
> +{
> +   /* Default to a natural alignment if none is provided */
> +   if (size_align == NULL)
> +      size_align = glsl_get_natural_size_align_bytes;
> +
> +   /* This only works with a single entrypoint */
> +   nir_function_impl *impl = nir_shader_get_entrypoint(shader);
> +
> +   /* This pass can only be run once */
> +   assert(shader->constant_data == NULL && shader->constant_data_size == 0);
> +
> +   /* The index parameter is unused for local variables so we'll use it for
> +    * indexing into our array of variable metadata.
> +    */
> +   unsigned num_locals = 0;
> +   nir_foreach_variable(var, &impl->locals)
> +      var->data.index = num_locals++;
> +
> +   struct var_info *var_infos = malloc(num_locals * sizeof(struct var_info));
> +   for (unsigned i = 0; i < num_locals; i++) {
> +      var_infos[i] = (struct var_info) {
> +         .is_constant = true,
> +         .found_read = false,
> +      };
> +   }
> +
> +   /* First, walk through the shader and figure out what variables we can
> +    * lower to the constant blob.
> +    */
> +   bool first_block = true;
> +   nir_foreach_block(block, impl) {
> +      nir_foreach_instr(instr, block) {
> +         if (instr->type != nir_instr_type_intrinsic)
> +            continue;
> +
> +         nir_intrinsic_instr *intrin = nir_instr_as_intrinsic(instr);
> +
> +         bool src_is_const = false;
> +         nir_deref_instr *src_deref = NULL, *dst_deref = NULL;
> +         switch (intrin->intrinsic) {
> +         case nir_intrinsic_store_deref:
> +            dst_deref = nir_src_as_deref(intrin->src[0]);
> +            src_is_const = nir_src_as_const_value(intrin->src[1]);
> +            break;
> +
> +         case nir_intrinsic_load_deref:
> +            src_deref = nir_src_as_deref(intrin->src[0]);
> +            break;
> +
> +         case nir_intrinsic_copy_deref:

Can we add a comment here? Something like:

/* We always assume the src and therefore the dst are not constants
  * here. Copy and constant propagation passes should have taken
  * care of this in most cases anyway.
  */

Does that sound correct?

Otherwise this series is:

Reviewed-by: Timothy Arceri <tarceri at itsqueeze.com>

Although I don't know the code i patch 8 very well so maybe someone else 
might want to take a look over that.

> +            dst_deref = nir_src_as_deref(intrin->src[0]);
> +            src_deref = nir_src_as_deref(intrin->src[1]);
> +            src_is_const = false > +            break;
> +
> +         default:
> +            continue;
> +         }
> +
> +         if (dst_deref && dst_deref->mode == nir_var_local) {
> +            nir_variable *var = nir_deref_instr_get_variable(dst_deref);
> +            assert(var->data.mode == nir_var_local);
> +
> +            /* We only consider variables constant if they only have constant
> +             * stores, all the stores come before any reads, and all stores
> +             * come in the first block.  We also can't handle indirect stores.
> +             */
> +            struct var_info *info = &var_infos[var->data.index];
> +            if (!src_is_const || info->found_read || !first_block ||
> +                nir_deref_instr_has_indirect(dst_deref))
> +               info->is_constant = false;
> +         }
> +
> +         if (src_deref && src_deref->mode == nir_var_local) {
> +            nir_variable *var = nir_deref_instr_get_variable(src_deref);
> +            assert(var->data.mode == nir_var_local);
> +
> +            var_infos[var->data.index].found_read = true;
> +         }
> +      }
> +      first_block = false;
> +   }
> +
> +   shader->constant_data_size = 0;
> +   nir_foreach_variable(var, &impl->locals) {
> +      struct var_info *info = &var_infos[var->data.index];
> +      if (!info->is_constant)
> +         continue;
> +
> +      unsigned var_size, var_align;
> +      size_align(var->type, &var_size, &var_align);
> +      if (var_size <= threshold || !info->found_read) {
> +         /* Don't bother lowering small stuff or data that's never read */
> +         info->is_constant = false;
> +         continue;
> +      }
> +
> +      var->data.location = ALIGN_POT(shader->constant_data_size, var_align);
> +      shader->constant_data_size = var->data.location + var_size;
> +   }
> +
> +   if (shader->constant_data_size == 0) {
> +      free(var_infos);
> +      return false;
> +   }
> +
> +   shader->constant_data = rzalloc_size(shader, shader->constant_data_size);
> +
> +   nir_builder b;
> +   nir_builder_init(&b, impl);
> +
> +   nir_foreach_block(block, impl) {
> +      nir_foreach_instr_safe(instr, block) {
> +         if (instr->type != nir_instr_type_intrinsic)
> +            continue;
> +
> +         nir_intrinsic_instr *intrin = nir_instr_as_intrinsic(instr);
> +
> +         switch (intrin->intrinsic) {
> +         case nir_intrinsic_load_deref: {
> +            nir_deref_instr *deref = nir_src_as_deref(intrin->src[0]);
> +            if (deref->mode != nir_var_local)
> +               continue;
> +
> +            nir_variable *var = nir_deref_instr_get_variable(deref);
> +            struct var_info *info = &var_infos[var->data.index];
> +            if (info->is_constant) {
> +               b.cursor = nir_after_instr(&intrin->instr);
> +               nir_ssa_def *val = build_constant_load(&b, deref, size_align);
> +               nir_ssa_def_rewrite_uses(&intrin->dest.ssa,
> +                                        nir_src_for_ssa(val));
> +               nir_instr_remove(&intrin->instr);
> +               nir_deref_instr_remove_if_unused(deref);
> +            }
> +            break;
> +         }
> +
> +         case nir_intrinsic_store_deref: {
> +            nir_deref_instr *deref = nir_src_as_deref(intrin->src[0]);
> +            if (deref->mode != nir_var_local)
> +               continue;
> +
> +            nir_variable *var = nir_deref_instr_get_variable(deref);
> +            struct var_info *info = &var_infos[var->data.index];
> +            if (info->is_constant) {
> +               b.cursor = nir_after_instr(&intrin->instr);
> +               handle_constant_store(&b, intrin, size_align);
> +               nir_instr_remove(&intrin->instr);
> +               nir_deref_instr_remove_if_unused(deref);
> +            }
> +            break;
> +         }
> +
> +         case nir_intrinsic_copy_deref: {
> +            nir_deref_instr *deref = nir_src_as_deref(intrin->src[1]);
> +            if (deref->mode != nir_var_local)
> +               continue;
> +
> +            nir_variable *var = nir_deref_instr_get_variable(deref);
> +            struct var_info *info = &var_infos[var->data.index];
> +            if (info->is_constant) {
> +               b.cursor = nir_after_instr(&intrin->instr);
> +               nir_ssa_def *val = build_constant_load(&b, deref, size_align);
> +               nir_store_deref(&b, nir_src_as_deref(intrin->src[0]), val, ~0);
> +               nir_instr_remove(&intrin->instr);
> +               nir_deref_instr_remove_if_unused(deref);
> +            }
> +            break;
> +         }
> +
> +         default:
> +            continue;
> +         }
> +      }
> +   }
> +
> +   /* Clean up the now unused variables */
> +   nir_foreach_variable_safe(var, &impl->locals) {
> +      if (var_infos[var->data.index].is_constant)
> +         exec_node_remove(&var->node);
> +   }
> +
> +   free(var_infos);
> +
> +   nir_metadata_preserve(impl, nir_metadata_block_index |
> +                               nir_metadata_dominance);
> +   return true;
> +}
> 


More information about the mesa-dev mailing list