[Mesa-dev] [PATCH 09/21] nir: Add a phi node placement helper
Connor Abbott
cwabbott0 at gmail.com
Mon Mar 21 23:21:22 UTC 2016
On Mon, Mar 21, 2016 at 6:51 PM, Connor Abbott <cwabbott0 at gmail.com> wrote:
> So overall, I think that there needs to be some explanation of the
> design choices in the implementation. The API documentation is great,
> but digging into the implementation might be a little daunting without
> knowing e.g. why NEEDS_PHI is a thing. From what I gather, there are
> three potential states a phi node can be in, once it's determined by
> the usual dominance-frontier algorithm that it needs to be there:
>
> 1. There's just a NEEDS_PHI entry. IMHO this is a little bit of a
> misnomer. This means something like "we might need to insert a phi, if
> something uses it."
> 2. The phi has been constructed and added to the list of phis
> associated with this value, but it hasn't been inserted into the block
> yet. This means something like "we know that this phi is actually
> needed, but we haven't materialized its sources yet."
> 3. The phi has actually been inserted into the block.
>
> There isn't enough explanation as to why #2 needs to be separate from
> #3. I think it made more sense to me once I realized that the "phis"
> list is actually a worklist, and nir_phi_builder_finish() is really a
> worklist-based algorithm for recursively filling in phi node
> dependencies, and when we go from #1 to #2 we're really just inserting
> the phi into the worklist.
>
> On Sat, Feb 13, 2016 at 9:14 PM, Jason Ekstrand <jason at jlekstrand.net> wrote:
>> Right now, we have phi placement code in two places and there are other
>> places where it would be nice to be able to do this analysis. Instead of
>> repeating it all over the place, this commit adds a helper for placing all
>> of the needed phi nodes for a value.
>> ---
>> src/compiler/Makefile.sources | 2 +
>> src/compiler/nir/Makefile.sources | 2 +
>> src/compiler/nir/nir_phi_builder.c | 254 +++++++++++++++++++++++++++++++++++++
>> src/compiler/nir/nir_phi_builder.h | 84 ++++++++++++
>> 4 files changed, 342 insertions(+)
>> create mode 100644 src/compiler/nir/nir_phi_builder.c
>> create mode 100644 src/compiler/nir/nir_phi_builder.h
>>
>> diff --git a/src/compiler/Makefile.sources b/src/compiler/Makefile.sources
>> index c9780d6..4a1b120 100644
>> --- a/src/compiler/Makefile.sources
>> +++ b/src/compiler/Makefile.sources
>> @@ -213,6 +213,8 @@ NIR_FILES = \
>> nir/nir_opt_peephole_select.c \
>> nir/nir_opt_remove_phis.c \
>> nir/nir_opt_undef.c \
>> + nir/nir_phi_builder.c \
>> + nir/nir_phi_builder.h \
>> nir/nir_print.c \
>> nir/nir_remove_dead_variables.c \
>> nir/nir_search.c \
>> diff --git a/src/compiler/nir/Makefile.sources b/src/compiler/nir/Makefile.sources
>> index 0755a10..7269f9f 100644
>> --- a/src/compiler/nir/Makefile.sources
>> +++ b/src/compiler/nir/Makefile.sources
>> @@ -57,6 +57,8 @@ NIR_FILES = \
>> nir_opt_peephole_select.c \
>> nir_opt_remove_phis.c \
>> nir_opt_undef.c \
>> + nir_phi_builder.c \
>> + nir_phi_builder.h \
>> nir_print.c \
>> nir_remove_dead_variables.c \
>> nir_search.c \
>> diff --git a/src/compiler/nir/nir_phi_builder.c b/src/compiler/nir/nir_phi_builder.c
>> new file mode 100644
>> index 0000000..5429083
>> --- /dev/null
>> +++ b/src/compiler/nir/nir_phi_builder.c
>> @@ -0,0 +1,254 @@
>> +/*
>> + * Copyright © 2016 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_phi_builder.h"
>> +#include "nir/nir_vla.h"
>> +
>> +struct nir_phi_builder {
>> + nir_shader *shader;
>> + nir_function_impl *impl;
>> +
>> + /* Copied from the impl for easy access */
>> + unsigned num_blocks;
>> +
>> + /* Array of all blocks indexed by block->index. */
>> + nir_block **blocks;
>> +
>> + /* Hold on to the values so we can easily iterate over them. */
>> + struct exec_list values;
>> +
>> + /* Worklist for phi adding */
>> + unsigned iter_count;
>> + unsigned *work;
>> + nir_block **W;
>> +};
>> +
>> +#define NEEDS_PHI ((nir_ssa_def *)(intptr_t)-1)
>
> Instead of doing this, you could just do something like:
>
> static nir_ssa_def dummy;
> #define NEEDS_PHI &dummy
>
> We do similar things in the hash table code already.
>
>> +
>> +struct nir_phi_builder_value {
>> + struct exec_node node;
>> +
>> + struct nir_phi_builder *builder;
>> +
>> + /* Needed so we can create phis and undefs */
>> + unsigned num_components;
>> +
>> + /* The list of phi nodes associated with this value. Phi nodes are not
>> + * added directly. Instead, they are created, the instr->block pointer
>> + * set, and then added to this list. Later, in phi_builder_finish, we
>> + * set up their sources and add them to the top of their respective
>> + * blocks.
>> + */
>> + struct exec_list phis;
>
> In light of my comments above, how about we make this a worklist
> instead? This would also eliminate the need for NEEDS_PHI, since we
> can just insert the block into the worklist when we realize we need
> the phi node's value.
>
>> +
>> + /* Array of SSA defs, indexed by block. If a phi needs to be inserted
>> + * in a given block, it will have the magic value NEEDS_PHI.
>> + */
>> + nir_ssa_def *defs[0];
>> +};
>> +
>> +static bool
>> +fill_block_array(nir_block *block, void *void_data)
>> +{
>> + nir_block **blocks = void_data;
>> + blocks[block->index] = block;
>> + return true;
>> +}
>> +
>> +struct nir_phi_builder *
>> +nir_phi_builder_create(nir_function_impl *impl)
>> +{
>> + struct nir_phi_builder *pb = ralloc(NULL, struct nir_phi_builder);
>> +
>> + pb->shader = impl->function->shader;
>> + pb->impl = impl;
>> +
>> + assert(impl->valid_metadata & (nir_metadata_block_index |
>> + nir_metadata_dominance));
>> +
>> + pb->num_blocks = impl->num_blocks;
>> + pb->blocks = ralloc_array(pb, nir_block *, pb->num_blocks);
>> + nir_foreach_block(impl, fill_block_array, pb->blocks);
>> +
>> + exec_list_make_empty(&pb->values);
>> +
>> + pb->iter_count = 0;
>> + pb->work = rzalloc_array(pb, unsigned, pb->num_blocks);
>> + pb->W = ralloc_array(pb, nir_block *, pb->num_blocks);
>> +
>> + return pb;
>> +}
>> +
>> +struct nir_phi_builder_value *
>> +nir_phi_builder_add_value(struct nir_phi_builder *pb, unsigned num_components,
>> + const BITSET_WORD *defs)
>> +{
>> + struct nir_phi_builder_value *val;
>> + unsigned i, w_start = 0, w_end = 0;
>> +
>> + val = rzalloc_size(pb, sizeof(*val) + sizeof(val->defs[0]) * pb->num_blocks);
>> + val->builder = pb;
>> + val->num_components = num_components;
>> + exec_list_make_empty(&val->phis);
>> + exec_list_push_tail(&pb->values, &val->node);
>> +
>> + pb->iter_count++;
>> +
>> + BITSET_WORD tmp;
>> + BITSET_FOREACH_SET(i, tmp, defs, pb->num_blocks) {
>> + if (pb->work[i] < pb->iter_count)
>> + pb->W[w_end++] = pb->blocks[i];
>> + pb->work[i] = pb->iter_count;
>> + }
>> +
>> + while (w_start != w_end) {
>> + nir_block *cur = pb->W[w_start++];
>> + struct set_entry *dom_entry;
>> + set_foreach(cur->dom_frontier, dom_entry) {
>> + nir_block *next = (nir_block *) dom_entry->key;
>> +
>> + /*
>> + * If there's more than one return statement, then the end block
>> + * can be a join point for some definitions. However, there are
>> + * no instructions in the end block, so nothing would use those
>> + * phi nodes. Of course, we couldn't place those phi nodes
>> + * anyways due to the restriction of having no instructions in the
>> + * end block...
>> + */
>> + if (next == pb->impl->end_block)
>> + continue;
>> +
>> + if (val->defs[next->index] == NULL) {
>> + val->defs[next->index] = NEEDS_PHI;
>> +
>> + if (pb->work[next->index] < pb->iter_count) {
>> + pb->work[next->index] = pb->iter_count;
>> + pb->W[w_end++] = next;
>> + }
>> + }
>> + }
>> + }
>> +
>> + return val;
>> +}
>> +
>> +void
>> +nir_phi_builder_value_set_block_def(struct nir_phi_builder_value *val,
>> + nir_block *block, nir_ssa_def *def)
>> +{
>> + val->defs[block->index] = def;
>> +}
>> +
>> +nir_ssa_def *
>> +nir_phi_builder_value_get_block_def(struct nir_phi_builder_value *val,
>> + nir_block *block)
>> +{
>> + if (val->defs[block->index] == NULL) {
>> + if (block->imm_dom) {
>> + /* Grab it from our immediate dominator. We'll stash it here for
>> + * easy access later.
>> + */
>> + val->defs[block->index] =
>> + nir_phi_builder_value_get_block_def(val, block->imm_dom);
>> + return val->defs[block->index];
>> + } else {
>> + /* No immediate dominator means that this block is either the
>> + * start block or unreachable. In either case, the value is
>> + * undefined so we need an SSA undef.
>> + */
>> + nir_ssa_undef_instr *undef =
>> + nir_ssa_undef_instr_create(val->builder->shader,
>> + val->num_components);
>> + nir_instr_insert(nir_before_cf_list(&val->builder->impl->body),
>> + &undef->instr);
>> + val->defs[block->index] = &undef->def;
>> + return &undef->def;
>> + }
>> + } else if (val->defs[block->index] == NEEDS_PHI) {
>> + /* If we need a phi instruction, go ahead and create one but don't
>> + * add it to the program yet. Later, we'll go through and set up phi
>> + * sources and add the instructions will be added at that time.
>> + */
>> + nir_phi_instr *phi = nir_phi_instr_create(val->builder->shader);
>> + nir_ssa_dest_init(&phi->instr, &phi->dest, val->num_components, NULL);
>> + phi->instr.block = block;
>> + exec_list_push_tail(&val->phis, &phi->instr.node);
>> + val->defs[block->index] = &phi->dest.ssa;
>> + return &phi->dest.ssa;
>> + } else {
>> + return val->defs[block->index];
>> + }
>> +}
>> +
>> +static int
>> +compare_blocks(const void *_a, const void *_b)
>> +{
>> + nir_block * const * a = _a;
>> + nir_block * const * b = _b;
>> +
>> + return (*a)->index - (*b)->index;
>> +}
>> +
>> +void
>> +nir_phi_builder_finish(struct nir_phi_builder *pb)
>> +{
>> + const unsigned num_blocks = pb->num_blocks;
>> + NIR_VLA(nir_block *, preds, num_blocks);
>> +
>> + foreach_list_typed(struct nir_phi_builder_value, val, node, &pb->values) {
>> + /* We can't iterate over the list of phis normally because we are
>> + * removing them as we go and, in some cases, adding new phis as we
>> + * build the source lists of others.
>> + */
>> + while (!exec_list_is_empty(&val->phis)) {
>> + struct exec_node *head = exec_list_get_head(&val->phis);
>> + nir_phi_instr *phi = exec_node_data(nir_phi_instr, head, instr.node);
>> + assert(phi->instr.type == nir_instr_type_phi);
>> +
>> + exec_node_remove(&phi->instr.node);
>> +
>> + /* Construct an array of predecessors. We sort it to ensure
>> + * determinism in the phi insertion algorithm.
>> + *
>> + * XXX: Calling qsort this many times seems expensive.
>> + */
>> + int num_preds = 0;
>> + struct set_entry *entry;
>> + set_foreach(phi->instr.block->predecessors, entry)
>> + preds[num_preds++] = (nir_block *)entry->key;
>> + qsort(preds, num_preds, sizeof(*preds), compare_blocks);
>> +
>> + for (unsigned i = 0; i < num_preds; i++) {
>> + nir_phi_src *src = ralloc(phi, nir_phi_src);
>> + src->pred = preds[i];
>> + src->src = nir_src_for_ssa(
>> + nir_phi_builder_value_get_block_def(val, preds[i]));
>> + exec_list_push_tail(&phi->srcs, &src->node);
>> + }
>> +
>> + nir_instr_insert(nir_before_block(phi->instr.block), &phi->instr);
>> + }
>> + }
>> +
>> + ralloc_free(pb);
>> +}
>> diff --git a/src/compiler/nir/nir_phi_builder.h b/src/compiler/nir/nir_phi_builder.h
>> new file mode 100644
>> index 0000000..50251bf
>> --- /dev/null
>> +++ b/src/compiler/nir/nir_phi_builder.h
>> @@ -0,0 +1,84 @@
>> +/*
>> + * Copyright © 2016 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.
>> + */
>> +
>> +#pragma once
>> +
>> +#include "nir.h"
>> +
>> +struct nir_phi_builder;
>> +struct nir_phi_builder_value;
>> +
>> +/* Create a new phi builder.
>> + *
>> + * While this is fairly cheap, it does allocate some memory and walk the list
>> + * of blocks so it's recommended that you only call it once and use it to
>> + * build phis for several values.
>> + */
>> +struct nir_phi_builder *nir_phi_builder_create(nir_function_impl *impl);
>> +
>> +/* Register a value with the builder.
>> + *
>> + * The 'defs' parameter specifies a bitset of blocks in which the given value
>> + * is defined. This is used to determine where to place the phi nodes.
>> + */
>> +struct nir_phi_builder_value *
>> +nir_phi_builder_add_value(struct nir_phi_builder *pb, unsigned num_components,
>> + const BITSET_WORD *defs);
>> +
>> +/* Register a definition for the given value and block.
>> + *
>> + * It is safe to call this function as many times as you wish for any given
>> + * block/value pair. However, it always replaces whatever was there
>> + * previously even if that definition is from a phi node. The phi builder
>> + * always uses the latest information it has, so you must be careful about the
>> + * order in which you register definitions. The final value at the end of the
>> + * block must be the last value registered.
>> + */
>> +void
>> +nir_phi_builder_value_set_block_def(struct nir_phi_builder_value *val,
>> + nir_block *block, nir_ssa_def *def);
>> +
>> +/* Get the definition for the given value in the given block.
>> + *
>> + * This definition will always be the latest definition known for the given
>> + * block. If no definition is immediately available, it will crawl up the
>> + * dominance tree and insert phi nodes as needed until it finds one. In the
>> + * case that no suitable definition is found, it will return the result of a
>> + * nir_ssa_undef_instr with the correct number of components.
>> + *
>> + * Because this function only uses the latest available information for any
>> + * given block, you must have already finished registering definitions for any
>> + * blocks that dominate the current block in order to get the correct result.
>> + */
>
> I think this isn't conservative enough; you have to register *all* the
> definitions before you call nir_phi_builder_get_block_def() anywhere,
> since it might return a phi node created by a definition in a block
> that doesn't dominate the block you're considering. For example,
> consider something like:
>
> a = ...
> loop {
> ... = a;
> ...
> a = ...;
> }
Err, nevermind about this part. I was thinking, though, that maybe it
would be helpful to have a sort of pseudocode as to how you're
supposed to use the API in practice that ties all this information
together. Something like:
each variable, var, has:
a bitset var.defs of blocks where the variable is defined
a struct nir_phi_builder_value *pb_val
/* initialize bitsets */
foreach block:
foreach def of variable var:
var.defs[block] = true;
/* initialize phi builder */
pb = nir_phi_builder_create()
foreach var:
var.pb_val = nir_phi_builder_add_value(pb, var.defs)
foreach block: /* needs to visit dominators first,
nir_for_each_block() will be ok */
foreach instruction:
foreach use of variable var:
replace use with nir_phi_builder_get_block_def(var.pb_val)
foreach def of variable var:
create ssa def, register with
nir_phi_builder_set_block_def(var.pb_val)
nir_phi_builder_finish(pb)
did I get this right?
>
> if you call nir_phi_builder_value_get_block_def() on the use of a
> before registering the definition below it, then you'll get the first
> definition of a instead of the phi node at the top of the loop, since
> the builder hasn't yet figured out that a phi node needs to be
> inserted there.
>
>> +nir_ssa_def *
>> +nir_phi_builder_value_get_block_def(struct nir_phi_builder_value *val,
>> + nir_block *block);
>> +
>> +/* Finish building phi nodes and free the builder.
>> + *
>> + * This function does far more than just free memory. Prior to calling
>> + * nir_phi_builder_finish, no phi nodes have actually been inserted in the
>> + * program. This function is what finishes setting up phi node sources and
>> + * adds the phi nodes to the program.
>> + */
>> +void nir_phi_builder_finish(struct nir_phi_builder *pb);
>> --
>> 2.5.0.400.gff86faf
>>
>> _______________________________________________
>> mesa-dev mailing list
>> mesa-dev at lists.freedesktop.org
>> https://lists.freedesktop.org/mailman/listinfo/mesa-dev
More information about the mesa-dev
mailing list