<div dir="ltr"><div class="gmail_extra"><div class="gmail_quote">On Thu, Dec 7, 2017 at 9:10 AM, Pohjolainen, Topi <span dir="ltr"><<a href="mailto:topi.pohjolainen@gmail.com" target="_blank">topi.pohjolainen@gmail.com</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div class="HOEnZb"><div class="h5">On Wed, Dec 06, 2017 at 08:34:19PM -0800, Jason Ekstrand wrote:<br>
> This rewires the logic for assigning uniform locations to work in terms<br>
> of "complex alignments".  The basic idea is that, as we walk the list of<br>
> instructions, we keep track of the alignment and continuity requirements<br>
> of each slot and assert that the alignments all match up.  We then use<br>
> those alignments in the compaction stage to ensure that everything gets<br>
> placed at a properly aligned register.  The old mechanism handled<br>
> alignments by special-casing each of the bit sizes and placing 64-bit<br>
> values first followed by 32-bit values.<br>
><br>
> The old scheme had the advantage of never leaving a hole since all the<br>
> 64-bit values could be tightly packed and so could the 32-bit values.<br>
> However, the new scheme has no type size special cases so it handles not<br>
> only 32 and 64-bit types but should gracefully extend to 16 and 8-bit<br>
> types as the need arises.<br>
><br>
> Tested-by: Jose Maria Casanova Crespo <<a href="mailto:jmcasanova@igalia.com">jmcasanova@igalia.com</a>><br>
> ---<br>
>  src/intel/compiler/brw_fs.cpp | 307 ++++++++++++++++++++++++------<wbr>------------<br>
>  1 file changed, 174 insertions(+), 133 deletions(-)<br>
><br>
> diff --git a/src/intel/compiler/brw_fs.<wbr>cpp b/src/intel/compiler/brw_fs.<wbr>cpp<br>
> index 93bb6b4..41260b4 100644<br>
> --- a/src/intel/compiler/brw_fs.<wbr>cpp<br>
> +++ b/src/intel/compiler/brw_fs.<wbr>cpp<br>
> @@ -1906,62 +1906,6 @@ fs_visitor::compact_virtual_<wbr>grfs()<br>
>     return progress;<br>
>  }<br>
><br>
> -static void<br>
> -set_push_pull_constant_loc(<wbr>unsigned uniform, int *chunk_start,<br>
> -                           unsigned *max_chunk_bitsize,<br>
> -                           bool contiguous, unsigned bitsize,<br>
> -                           const unsigned target_bitsize,<br>
> -                           int *push_constant_loc, int *pull_constant_loc,<br>
> -                           unsigned *num_push_constants,<br>
> -                           unsigned *num_pull_constants,<br>
> -                           const unsigned max_push_components,<br>
> -                           const unsigned max_chunk_size,<br>
> -                           bool allow_pull_constants,<br>
> -                           struct brw_stage_prog_data *stage_prog_data)<br>
> -{<br>
> -   /* This is the first live uniform in the chunk */<br>
> -   if (*chunk_start < 0)<br>
> -      *chunk_start = uniform;<br>
> -<br>
> -   /* Keep track of the maximum bit size access in contiguous uniforms */<br>
> -   *max_chunk_bitsize = MAX2(*max_chunk_bitsize, bitsize);<br>
> -<br>
> -   /* If this element does not need to be contiguous with the next, we<br>
> -    * split at this point and everything between chunk_start and u forms a<br>
> -    * single chunk.<br>
> -    */<br>
> -   if (!contiguous) {<br>
> -      /* If bitsize doesn't match the target one, skip it */<br>
> -      if (*max_chunk_bitsize != target_bitsize) {<br>
> -         /* FIXME: right now we only support 32 and 64-bit accesses */<br>
> -         assert(*max_chunk_bitsize == 4 || *max_chunk_bitsize == 8);<br>
> -         *max_chunk_bitsize = 0;<br>
> -         *chunk_start = -1;<br>
> -         return;<br>
> -      }<br>
> -<br>
> -      unsigned chunk_size = uniform - *chunk_start + 1;<br>
> -<br>
> -      /* Decide whether we should push or pull this parameter.  In the<br>
> -       * Vulkan driver, push constants are explicitly exposed via the API<br>
> -       * so we push everything.  In GL, we only push small arrays.<br>
> -       */<br>
> -      if (!allow_pull_constants ||<br>
> -          (*num_push_constants + chunk_size <= max_push_components &&<br>
> -           chunk_size <= max_chunk_size)) {<br>
> -         assert(*num_push_constants + chunk_size <= max_push_components);<br>
> -         for (unsigned j = *chunk_start; j <= uniform; j++)<br>
> -            push_constant_loc[j] = (*num_push_constants)++;<br>
> -      } else {<br>
> -         for (unsigned j = *chunk_start; j <= uniform; j++)<br>
> -            pull_constant_loc[j] = (*num_pull_constants)++;<br>
> -      }<br>
> -<br>
> -      *max_chunk_bitsize = 0;<br>
> -      *chunk_start = -1;<br>
> -   }<br>
> -}<br>
> -<br>
>  static int<br>
>  get_subgroup_id_param_index(<wbr>const brw_stage_prog_data *prog_data)<br>
>  {<br>
> @@ -1977,6 +1921,98 @@ get_subgroup_id_param_index(<wbr>const brw_stage_prog_data *prog_data)<br>
>  }<br>
><br>
>  /**<br>
> + * Struct for handling complex alignments.<br>
> + *<br>
> + * A complex alignment is stored as multiplier and an offset.  A value is<br>
> + * considered to be aligned if it is congruent to the offset modulo the<br>
> + * multiplier.<br>
> + */<br>
> +struct cplx_align {<br>
> +   unsigned mul:4;<br>
> +   unsigned offset:4;<br>
> +};<br>
> +<br>
> +#define CPLX_ALIGN_MAX_MUL 8<br>
> +<br>
> +static void<br>
> +cplx_align_assert_sane(struct cplx_align a)<br>
> +{<br>
> +   assert(a.mul > 0 && util_is_power_of_two(a.mul));<br>
> +   assert(a.offset < a.mul);<br>
> +}<br>
> +<br>
> +/**<br>
> + * Combines two alignments to produce a least multiple of sorts.<br>
> + *<br>
> + * The returned alignment is the smallest (in terms of multiplier) such that<br>
> + * anything aligned to both a and b will be aligned to the new alignment.<br>
> + * This function will assert-fail if a and b are not compatible, i.e. if the<br>
> + * offset parameters are such that no common alignment is possible.<br>
> + */<br>
> +static struct cplx_align<br>
> +cplx_align_combine(struct cplx_align a, struct cplx_align b)<br>
> +{<br>
> +   cplx_align_assert_sane(a);<br>
> +   cplx_align_assert_sane(b);<br>
> +<br>
> +   /* Assert that the alignments agree. */<br>
> +   assert((a.offset & (b.mul - 1)) == (b.offset & (a.mul - 1)));<br>
> +<br>
> +   return a.mul > b.mul ? a : b;<br>
> +}<br>
> +<br>
> +/**<br>
> + * Apply a complex alignment<br>
> + *<br>
> + * This function will return the smallest number greater than or equal to<br>
> + * offset that is aligned to align.<br>
> + */<br>
> +static unsigned<br>
> +cplx_align_apply(struct cplx_align align, unsigned offset)<br>
> +{<br>
<br>
</div></div>Should we assert that offset >= 4?<span class=""><br></span></blockquote><div><br></div><div>Not here, no.  I intentionally want this code to be as generic as possible.  We may have reason to re-use it (or something similar) elsewhere and I don't want to build in lots of push-constant assumptions.<br></div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><span class="">
> +   return ALIGN(offset - align.offset, align.mul) + align.offset;<br>
<br>
</span>Soon I'm going to continue with glsl mediump support and thought better to<br>
read these patches. I stumbled on something (probably missing something<br>
again):<br></blockquote><div><br></div><div>:-)<br></div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
To me it looks there can ever be only three different instances of cplx_align:<br></blockquote><div><br></div><div>Today, yes, but there may come a day when we want to re-arrange with a finer granularity than dwords.<br></div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
{8, 0}, {8, 4} and {4, 0} and that "offset" argument here is always multiple<br>
of 4.<br>
<br>
In case of align == {8, 0} "offset" gets aligned to 8, and in case of<br>
align == {4, 0} cplx_align_apply() is nop.<br>
<br>
But in case of {8, 4} and "offset" not multiple of 8, "offset" - 4 is<br>
multiple of 8 and returned value becomes ALIGN(offset - 4, 8) + 4 which<br>
in turn isn't aligned by 8.<br><div><div class="h5"></div></div></blockquote><div><br></div><div>Correct, and it shouldn't be.  Let's walk through the calculation with a more complex alignment: {8, 6} (no, that isn't possible today but the calculations should work with it) and some concrete numbers: 6, 8, 10, 12, 14, 16<br></div><div><br></div><div>6 -> ALIGN(6 - 6, 8) + 6 = 6<br></div><div>8 -> ALIGN(8 - 6, 8) + 6 = 14</div><div>10 -> ALIGN(10 - 6, 8) + 6 = 14</div><div>12 -> ALIGN(12 - 6, 8) + 6 = 14</div><div>14 -> ALIGN(14 - 6, 8) + 6 = 14</div><div>16 -> ALIGN(16 - 6, 8) + 6 = 22</div><div><br></div><div>As you can see, the result of each is the next number that is 6 more than a multiple of 8.  There is also a question about negative numbers.  Let's look at one more calculation, this time with more detail about the guts of ALIGN():</div><div><br></div><div>4 -> ALIGN(4 - 6, 8) + 6 = (((4 - 6) + (8 - 1)) & ~(8-1)) + 6 = (5 & ~7) + 6 = 6</div><div><br></div><div>That one's a bit more complicated but I think you see what's going on.  If it makes things more clear, we could do something such as</div><div><br></div><div>return (offset + (align.mul - align.offset - 1)) & ~(align.mul - 1)) + align.offset;<br></div><div> <br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div><div class="h5">
> +}<br>
> +<br>
> +#define UNIFORM_SLOT_SIZE 4<br>
> +<br>
> +struct uniform_slot_info {<br>
> +   /** True if the given uniform slot is live */<br>
> +   unsigned is_live:1;<br>
> +<br>
> +   /** True if this slot and the slot must remain contiguous */<br>
> +   unsigned contiguous:1;<br>
> +<br>
> +   struct cplx_align align;<br>
> +};<br>
> +<br>
> +static void<br>
> +mark_uniform_slots_read(<wbr>struct uniform_slot_info *slots,<br>
> +                        unsigned num_slots, unsigned alignment)<br>
> +{<br>
> +   assert(alignment > 0 && util_is_power_of_two(<wbr>alignment));<br>
> +   assert(alignment <= CPLX_ALIGN_MAX_MUL);<br>
> +<br>
> +   /* We can't align a slot to anything less than the slot size */<br>
> +   alignment = MAX2(alignment, UNIFORM_SLOT_SIZE);<br>
> +<br>
> +   struct cplx_align align = {alignment, 0};<br>
> +   cplx_align_assert_sane(align);<br>
> +<br>
> +   for (unsigned i = 0; i < num_slots; i++) {<br>
> +      slots[i].is_live = true;<br>
> +      if (i < num_slots - 1)<br>
> +         slots[i].contiguous = true;<br>
> +<br>
> +      align.offset = (i * UNIFORM_SLOT_SIZE) & (align.mul - 1);<br>
> +      if (slots[i].align.mul == 0) {<br>
> +         slots[i].align = align;<br>
> +      } else {<br>
> +         slots[i].align = cplx_align_combine(slots[i].<wbr>align, align);<br>
> +      }<br>
> +   }<br>
> +}<br>
> +<br>
> +/**<br>
>   * Assign UNIFORM file registers to either push constants or pull constants.<br>
>   *<br>
>   * We allow a fragment shader to have more than the specified minimum<br>
> @@ -1994,60 +2030,44 @@ fs_visitor::assign_constant_<wbr>locations()<br>
>        return;<br>
>     }<br>
><br>
> -   bool is_live[uniforms];<br>
> -   memset(is_live, 0, sizeof(is_live));<br>
> -   unsigned bitsize_access[uniforms];<br>
> -   memset(bitsize_access, 0, sizeof(bitsize_access));<br>
> -<br>
> -   /* For each uniform slot, a value of true indicates that the given slot and<br>
> -    * the next slot must remain contiguous.  This is used to keep us from<br>
> -    * splitting arrays and 64-bit values apart.<br>
> -    */<br>
> -   bool contiguous[uniforms];<br>
> -   memset(contiguous, 0, sizeof(contiguous));<br>
> +   struct uniform_slot_info slots[uniforms];<br>
> +   memset(slots, 0, sizeof(slots));<br>
><br>
> -   /* First, we walk through the instructions and do two things:<br>
> -    *<br>
> -    *  1) Figure out which uniforms are live.<br>
> -    *<br>
> -    *  2) Mark any indirectly used ranges of registers as contiguous.<br>
> -    *<br>
> -    * Note that we don't move constant-indexed accesses to arrays.  No<br>
> -    * testing has been done of the performance impact of this choice.<br>
> -    */<br>
>     foreach_block_and_inst_safe(<wbr>block, fs_inst, inst, cfg) {<br>
>        for (int i = 0 ; i < inst->sources; i++) {<br>
>           if (inst->src[i].file != UNIFORM)<br>
>              continue;<br>
><br>
> -         int constant_nr = inst->src[i].nr + inst->src[i].offset / 4;<br>
> +         /* NIR tightly packs things so the uniform number might not be<br>
> +          * aligned (if we have a double right after a float, for instance).<br>
> +          * This is fine because the process of re-arranging them will ensure<br>
> +          * that things are properly aligned.  The offset into that uniform,<br>
> +          * however, must be aligned.<br>
> +          *<br>
> +          * In Vulkan, we have explicit offsets but everything is crammed<br>
> +          * into a single "variable" so inst->src[i].nr will always be 0.<br>
> +          * Everything will be properly aligned relative to that one base.<br>
> +          */<br>
> +         assert(inst->src[i].offset % type_sz(inst->src[i].type) == 0);<br>
> +<br>
> +         unsigned u = inst->src[i].nr +<br>
> +                      inst->src[i].offset / UNIFORM_SLOT_SIZE;<br>
><br>
> +         if (u >= uniforms)<br>
> +            continue;<br>
> +<br>
> +         unsigned slots_read;<br>
>           if (inst->opcode == SHADER_OPCODE_MOV_INDIRECT && i == 0) {<br>
> -            assert(inst->src[2].ud % 4 == 0);<br>
> -            unsigned last = constant_nr + (inst->src[2].ud / 4) - 1;<br>
> -            assert(last < uniforms);<br>
> -<br>
> -            for (unsigned j = constant_nr; j < last; j++) {<br>
> -               is_live[j] = true;<br>
> -               contiguous[j] = true;<br>
> -               bitsize_access[j] = MAX2(bitsize_access[j], type_sz(inst->src[i].type));<br>
> -            }<br>
> -            is_live[last] = true;<br>
> -            bitsize_access[last] = MAX2(bitsize_access[last], type_sz(inst->src[i].type));<br>
> +            slots_read = DIV_ROUND_UP(inst->src[2].ud, UNIFORM_SLOT_SIZE);<br>
>           } else {<br>
> -            if (constant_nr >= 0 && constant_nr < (int) uniforms) {<br>
> -               int regs_read = inst->components_read(i) *<br>
> -                  type_sz(inst->src[i].type) / 4;<br>
> -               assert(regs_read <= 2);<br>
> -               if (regs_read == 2)<br>
> -                  contiguous[constant_nr] = true;<br>
> -               for (int j = 0; j < regs_read; j++) {<br>
> -                  is_live[constant_nr + j] = true;<br>
> -                  bitsize_access[constant_nr + j] =<br>
> -                     MAX2(bitsize_access[constant_<wbr>nr + j], type_sz(inst->src[i].type));<br>
> -               }<br>
> -            }<br>
> +            unsigned bytes_read = inst->components_read(i) *<br>
> +                                  type_sz(inst->src[i].type);<br>
> +            slots_read = DIV_ROUND_UP(bytes_read, UNIFORM_SLOT_SIZE);<br>
>           }<br>
> +<br>
> +         assert(u + slots_read <= uniforms);<br>
> +         mark_uniform_slots_read(&<wbr>slots[u], slots_read,<br>
> +                                 type_sz(inst->src[i].type));<br>
>        }<br>
>     }<br>
><br>
> @@ -2082,43 +2102,64 @@ fs_visitor::assign_constant_<wbr>locations()<br>
>     memset(pull_constant_loc, -1, uniforms * sizeof(*pull_constant_loc));<br>
><br>
>     int chunk_start = -1;<br>
> -   unsigned max_chunk_bitsize = 0;<br>
> -<br>
> -   /* First push 64-bit uniforms to ensure they are properly aligned */<br>
> -   const unsigned uniform_64_bit_size = type_sz(BRW_REGISTER_TYPE_DF);<br>
> +   struct cplx_align align;<br>
>     for (unsigned u = 0; u < uniforms; u++) {<br>
> -      if (!is_live[u])<br>
> +      if (!slots[u].is_live) {<br>
> +         assert(chunk_start == -1);<br>
>           continue;<br>
> +      }<br>
><br>
> -      set_push_pull_constant_loc(u, &chunk_start, &max_chunk_bitsize,<br>
> -                                 contiguous[u], bitsize_access[u],<br>
> -                                 uniform_64_bit_size,<br>
> -                                 push_constant_loc, pull_constant_loc,<br>
> -                                 &num_push_constants, &num_pull_constants,<br>
> -                                 max_push_components, max_chunk_size,<br>
> -                                 compiler->supports_pull_<wbr>constants,<br>
> -                                 stage_prog_data);<br>
> +      /* Skip subgroup_id_index to put it in the last push register. */<br>
> +      if (subgroup_id_index == (int)u)<br>
> +         continue;<br>
><br>
> -   }<br>
> +      if (chunk_start == -1) {<br>
> +         chunk_start = u;<br>
> +         align = slots[u].align;<br>
> +      } else {<br>
> +         /* Offset into the chunk */<br>
> +         unsigned chunk_offset = (u - chunk_start) * UNIFORM_SLOT_SIZE;<br>
><br>
> -   /* Then push the rest of uniforms */<br>
> -   const unsigned uniform_32_bit_size = type_sz(BRW_REGISTER_TYPE_F);<br>
> -   for (unsigned u = 0; u < uniforms; u++) {<br>
> -      if (!is_live[u])<br>
> -         continue;<br>
> +         /* Shift the slot alignment down by the chunk offset so it is<br>
> +          * comparable with the base chunk alignment.<br>
> +          */<br>
> +         struct cplx_align slot_align = slots[u].align;<br>
> +         slot_align.offset =<br>
> +            (slot_align.offset - chunk_offset) & (align.mul - 1);<br>
><br>
> -      /* Skip subgroup_id_index to put it in the last push register. */<br>
> -      if (subgroup_id_index == (int)u)<br>
> +         align = cplx_align_combine(align, slot_align);<br>
> +      }<br>
> +<br>
> +      /* Sanity check the alignment */<br>
> +      cplx_align_assert_sane(align);<br>
> +<br>
> +      if (slots[u].contiguous)<br>
>           continue;<br>
><br>
> -      set_push_pull_constant_loc(u, &chunk_start, &max_chunk_bitsize,<br>
> -                                 contiguous[u], bitsize_access[u],<br>
> -                                 uniform_32_bit_size,<br>
> -                                 push_constant_loc, pull_constant_loc,<br>
> -                                 &num_push_constants, &num_pull_constants,<br>
> -                                 max_push_components, max_chunk_size,<br>
> -                                 compiler->supports_pull_<wbr>constants,<br>
> -                                 stage_prog_data);<br>
> +      /* Adjust the alignment to be in terms of slots, not bytes */<br>
> +      assert((align.mul & (UNIFORM_SLOT_SIZE - 1)) == 0);<br>
> +      assert((align.offset & (UNIFORM_SLOT_SIZE - 1)) == 0);<br>
> +      align.mul /= UNIFORM_SLOT_SIZE;<br>
> +      align.offset /= UNIFORM_SLOT_SIZE;<br>
> +<br>
> +      unsigned push_start_align = cplx_align_apply(align, num_push_constants);<br>
> +      unsigned chunk_size = u - chunk_start + 1;<br>
> +      if (!compiler->supports_pull_<wbr>constants ||<br>
> +          (chunk_size < max_chunk_size &&<br>
> +           push_start_align + chunk_size <= max_push_components)) {<br>
> +         /* Align up the number of push constants */<br>
> +         num_push_constants = push_start_align;<br>
> +         for (unsigned i = 0; i < chunk_size; i++)<br>
> +            push_constant_loc[chunk_start + i] = num_push_constants++;<br>
> +      } else {<br>
> +         /* We need to pull this one */<br>
> +         num_pull_constants = cplx_align_apply(align, num_pull_constants);<br>
> +         for (unsigned i = 0; i < chunk_size; i++)<br>
> +            pull_constant_loc[chunk_start + i] = num_pull_constants++;<br>
> +      }<br>
> +<br>
> +      /* Reset the chunk and start again */<br>
> +      chunk_start = -1;<br>
>     }<br>
><br>
>     /* Add the CS local thread ID uniform at the end of the push constants */<br>
> @@ -2131,8 +2172,8 @@ fs_visitor::assign_constant_<wbr>locations()<br>
>     uint32_t *param = stage_prog_data->param;<br>
>     stage_prog_data->nr_params = num_push_constants;<br>
>     if (num_push_constants) {<br>
> -      stage_prog_data->param = ralloc_array(mem_ctx, uint32_t,<br>
> -                                            num_push_constants);<br>
> +      stage_prog_data->param = rzalloc_array(mem_ctx, uint32_t,<br>
> +                                             num_push_constants);<br>
>     } else {<br>
>        stage_prog_data->param = NULL;<br>
>     }<br>
> @@ -2140,8 +2181,8 @@ fs_visitor::assign_constant_<wbr>locations()<br>
>     assert(stage_prog_data->pull_<wbr>param == NULL);<br>
>     if (num_pull_constants > 0) {<br>
>        stage_prog_data->nr_pull_<wbr>params = num_pull_constants;<br>
> -      stage_prog_data->pull_param = ralloc_array(mem_ctx, uint32_t,<br>
> -                                                 num_pull_constants);<br>
> +      stage_prog_data->pull_param = rzalloc_array(mem_ctx, uint32_t,<br>
> +                                                  num_pull_constants);<br>
>     }<br>
><br>
>     /* Now that we know how many regular uniforms we'll push, reduce the<br>
> --<br>
> 2.5.0.400.gff86faf<br>
><br>
</div></div>> ______________________________<wbr>_________________<br>
> mesa-dev mailing list<br>
> <a href="mailto:mesa-dev@lists.freedesktop.org">mesa-dev@lists.freedesktop.org</a><br>
> <a href="https://lists.freedesktop.org/mailman/listinfo/mesa-dev" rel="noreferrer" target="_blank">https://lists.freedesktop.org/<wbr>mailman/listinfo/mesa-dev</a><br>
</blockquote></div><br></div></div>