diff --git a/src/compiler/Makefile.sources b/src/compiler/Makefile.sources index 9b9d8af72121516d0790b40d0246e876e49a8423..a310f6b97634c93cceac6ed9f38ce17447b0e3bd 100644 --- a/src/compiler/Makefile.sources +++ b/src/compiler/Makefile.sources @@ -317,6 +317,7 @@ NIR_FILES = \ nir/nir_opt_dce.c \ nir/nir_opt_dead_cf.c \ nir/nir_opt_dead_write_vars.c \ + nir/nir_opt_empty_blocks.c \ nir/nir_opt_find_array_copies.c \ nir/nir_opt_gcm.c \ nir/nir_opt_idiv_const.c \ diff --git a/src/compiler/nir/meson.build b/src/compiler/nir/meson.build index e563bd2f27092a667b9517f65e931270f94ab749..0dfd4e8d732dc603f1b00c9fc32608560b2dd348 100644 --- a/src/compiler/nir/meson.build +++ b/src/compiler/nir/meson.build @@ -198,6 +198,7 @@ files_libnir = files( 'nir_opt_dce.c', 'nir_opt_dead_cf.c', 'nir_opt_dead_write_vars.c', + 'nir_opt_empty_blocks.c', 'nir_opt_find_array_copies.c', 'nir_opt_gcm.c', 'nir_opt_idiv_const.c', diff --git a/src/compiler/nir/nir.h b/src/compiler/nir/nir.h index d3ba1bf0f00868bf38a9de4dca79b30e8122143a..acce7862fe357233a2ea408783ff66699af90810 100644 --- a/src/compiler/nir/nir.h +++ b/src/compiler/nir/nir.h @@ -4768,6 +4768,8 @@ bool nir_opt_dead_write_vars(nir_shader *shader); bool nir_opt_deref_impl(nir_function_impl *impl); bool nir_opt_deref(nir_shader *shader); +bool nir_opt_empty_blocks(nir_shader *shader); + bool nir_opt_find_array_copies(nir_shader *shader); bool nir_opt_gcm(nir_shader *shader, bool value_number); diff --git a/src/compiler/nir/nir_opt_dce.c b/src/compiler/nir/nir_opt_dce.c index c2487b026eac2b4c7c46c97fe27316ea13dc212b..4760a55ef61d17a364d4f6749b6d776eb353beac 100644 --- a/src/compiler/nir/nir_opt_dce.c +++ b/src/compiler/nir/nir_opt_dce.c @@ -104,29 +104,35 @@ init_instr(nir_instr *instr, nir_instr_worklist *worklist) } } -static bool -init_block(nir_block *block, nir_instr_worklist *worklist) -{ - nir_foreach_instr(instr, block) - init_instr(instr, worklist); - - nir_if *following_if = nir_block_get_following_if(block); - if (following_if) { - if (following_if->condition.is_ssa && - !following_if->condition.ssa->parent_instr->pass_flags) - mark_and_push(worklist, following_if->condition.ssa->parent_instr); - } - - return true; -} - static bool nir_opt_dce_impl(nir_function_impl *impl) { nir_instr_worklist *worklist = nir_instr_worklist_create(); - nir_foreach_block(block, impl) { - init_block(block, worklist); + if (impl->structured) { + nir_foreach_block(block, impl) { + nir_foreach_instr(instr, block) + init_instr(instr, worklist); + + /* If we're structured, we need to deal with if statements. Because + * we use nir_foreach_block, we're guaranteed that the instruction + * which generates the if condition has already been initialized. + */ + nir_if *following_if = nir_block_get_following_if(block); + if (following_if) { + if (following_if->condition.is_ssa && + !following_if->condition.ssa->parent_instr->pass_flags) + mark_and_push(worklist, following_if->condition.ssa->parent_instr); + } + } + } else { + /* With unstructured, if conditions come as the sources of goto_if jump + * instructions so we don't need any special handling. + */ + nir_foreach_block_unstructured(block, impl) { + nir_foreach_instr(instr, block) + init_instr(instr, worklist); + } } nir_foreach_instr_in_worklist(instr, worklist) @@ -136,7 +142,7 @@ nir_opt_dce_impl(nir_function_impl *impl) bool progress = false; - nir_foreach_block(block, impl) { + nir_foreach_block_unstructured(block, impl) { nir_foreach_instr_safe(instr, block) { if (!instr->pass_flags) { nir_instr_remove(instr); diff --git a/src/compiler/nir/nir_opt_empty_blocks.c b/src/compiler/nir/nir_opt_empty_blocks.c new file mode 100644 index 0000000000000000000000000000000000000000..4ab584700019cb030287142f554a11279d9db010 --- /dev/null +++ b/src/compiler/nir/nir_opt_empty_blocks.c @@ -0,0 +1,285 @@ +/* + * Copyright © 2020 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_vla.h" + +static nir_phi_src * +phi_src_for_pred(nir_phi_instr *phi, nir_block *pred) +{ + nir_foreach_phi_src(src, phi) { + if (src->pred == pred) + return src; + } + return NULL; +} + +static void +phi_add_src(nir_phi_instr *phi, nir_block *pred, nir_src src) +{ + nir_phi_src *new_src = rzalloc(phi, nir_phi_src); + new_src->pred = pred; + new_src->src = NIR_SRC_INIT; + exec_list_push_tail(&phi->srcs, &new_src->node); + nir_instr_rewrite_src(&phi->instr, &new_src->src, src); +} + +static int +compare_blocks(const void *_a, const void *_b) +{ + const nir_block * const * a = _a; + const nir_block * const * b = _b; + + return (*a)->index - (*b)->index; +} + +static bool +merge_phis(nir_block *succ, nir_block *pred, bool test) +{ + if (succ->predecessors->entries == 1) { + /* If this is the unique predecessor of the successor, there's nothing + * interesting to do; we just copy in the phis. + */ + if (test) + return true; + + nir_foreach_instr_reverse_safe(instr, pred) { + if (instr->type != nir_instr_type_phi) { + assert(instr->type == nir_instr_type_jump); + continue; + } + nir_instr_remove(instr); + nir_instr_insert(nir_before_block(succ), instr); + } + } else { + const unsigned num_pred_preds = pred->predecessors->entries; + NIR_VLA(nir_block *, pred_preds, num_pred_preds); + { + unsigned i = 0; + set_foreach(pred->predecessors, entry) + pred_preds[i++] = (nir_block *)entry->key; + assert(i == num_pred_preds); + } + qsort(pred_preds, num_pred_preds, sizeof(*pred_preds), compare_blocks); + + nir_foreach_instr(instr, succ) { + if (instr->type != nir_instr_type_phi) + break; + + nir_phi_instr *phi = nir_instr_as_phi(instr); + nir_phi_src *phi_src = phi_src_for_pred(phi, pred); + assert(phi_src->src.is_ssa); + if (phi_src->src.ssa->parent_instr->block == pred) { + /* In this case, the phi source comes from a phi in the + * predecessor. We know a priori that the predecessor only + * contains phis and jumps so this must be a phi. + */ + nir_phi_instr *pred_phi = + nir_instr_as_phi(phi_src->src.ssa->parent_instr); + if (test) { + /* We need to ensure that any shared predecessors have the same + * value. + */ + nir_foreach_phi_src(pred_phi_src, pred_phi) { + nir_foreach_phi_src(succ_phi_src, phi) { + if (succ_phi_src->pred == pred_phi_src->pred && + succ_phi_src->src.ssa != pred_phi_src->src.ssa) + return false; + } + } + } else { + /* Any sources for non-shared predecessors need to get moved to + * the successor block. + */ + nir_foreach_phi_src_safe(pred_phi_src, pred_phi) { + if (_mesa_set_search(succ->predecessors, pred_phi_src->pred)) + continue; + + phi_add_src(phi, pred_phi_src->pred, pred_phi_src->src); + } + } + } else { + /* In this case, the phi source comes from something that + * dominates pred. + */ + if (test) { + /* We need to ensure that any shared predecessors have the same + * value. + */ + nir_foreach_phi_src(succ_phi_src, phi) { + if (_mesa_set_search(pred->predecessors, + succ_phi_src->pred) && + succ_phi_src->src.ssa != phi_src->src.ssa) + return false; + } + } else { + /* We need to add sources for any non-shared predecessors. */ + for (unsigned i = 0; i < num_pred_preds; i++) { + if (_mesa_set_search(succ->predecessors, pred_preds[i])) + continue; + + phi_add_src(phi, pred_preds[i], phi_src->src); + } + } + } + } + } + + return true; +} + +static void +rewrite_pred_jumps(nir_block *block, nir_block *new_target) +{ + set_foreach(block->predecessors, entry) { + nir_block *pred = (nir_block *)entry->key; + nir_jump_instr *pred_jump = + nir_instr_as_jump(nir_block_last_instr(pred)); + + if (pred_jump->type == nir_jump_goto) { + assert(pred_jump->target == block); + assert(pred->successors[0] == pred_jump->target); + assert(pred->successors[1] == NULL); + pred->successors[0] = pred_jump->target = new_target; + } else { + assert(pred_jump->type == nir_jump_goto_if); + assert(pred_jump->target == block || + pred_jump->else_target == block); + assert(pred->successors[0] == pred_jump->else_target); + assert(pred->successors[1] == pred_jump->target); + if (pred_jump->target == block) + pred->successors[1] = pred_jump->target = new_target; + if (pred_jump->else_target == block) + pred->successors[0] = pred_jump->else_target = new_target; + + /* nir_validate doesn't allow a block to have both successors + * point to the same block. Turn goto_if into goto if both + * blocks are the same. + */ + if (pred_jump->target == pred_jump->else_target) { + pred_jump->type = nir_jump_goto; + nir_instr_rewrite_src(&pred_jump->instr, + &pred_jump->condition, + NIR_SRC_INIT); + pred->successors[1] = pred_jump->else_target = NULL; + } + } + + _mesa_set_add(new_target->predecessors, pred); + } +} + +static bool +opt_empty_blocks_impl(nir_function_impl *impl) +{ + /* This only works on unstructured control-flow */ + if (impl->structured) { + nir_metadata_preserve(impl, nir_metadata_all); + return false; + } + + bool progress = false; + + nir_foreach_block_unstructured_safe(block, impl) { + /* If we only have one block, don't remove it, even if empty */ + if (exec_list_is_singular(&impl->body)) + break; + + /* We can only contract edges when the block has a single successor */ + nir_jump_instr *jump = nir_instr_as_jump(nir_block_last_instr(block)); + if (jump->type != nir_jump_goto) + continue; + + assert(block->successors[1] == NULL); + nir_block *succ = block->successors[0]; + + /* Don't remove the start block if its successor has any other + * predecessors. That would result in the start block being a loop + * head and that's invalid NIR. + */ + if (block == nir_start_block(impl) && succ->predecessors->entries > 1) + continue; + + if (succ == block) { + /* In this case, we're an infinite loop. That needs to be handled + * specially. We make it point to the end block. + */ + rewrite_pred_jumps(block, impl->end_block); + /* rewrite_pred_jumps will have added this block to end_block's + * predecessor list but we want to remove it. + */ + _mesa_set_remove_key(impl->end_block->predecessors, block); + } else { + /* The block must be empty except for the jump instruction and phis */ + nir_instr *prev = nir_instr_prev(&jump->instr); + if (prev != NULL && prev->type != nir_instr_type_phi) + continue; + + /* First, we attempt a "test" phi merge. If it fails, then we can't + * safely merge the phis between the two blocks. + */ + if (!merge_phis(succ, block, true)) + continue; + + merge_phis(succ, block, false); + + rewrite_pred_jumps(block, succ); + _mesa_set_remove_key(succ->predecessors, block); + } + + progress = true; + + /* Clear out the block and remove it from the CF list */ + nir_foreach_instr_safe(instr, block) { + if (instr->type != nir_instr_type_jump) + nir_instr_remove(instr); + } + exec_node_remove(&block->cf_node.node); + break; + } + + if (progress) { + nir_metadata_preserve(impl, nir_metadata_none); + } else { + nir_metadata_preserve(impl, nir_metadata_all); + } + + return progress; +} + +bool +nir_opt_empty_blocks(nir_shader *shader) +{ + bool progress = false; + + nir_foreach_function(function, shader) { + /* This only works on unstructured control-flow */ + if (function->impl == NULL || function->impl->structured) + continue; + + if (opt_empty_blocks_impl(function->impl)) + progress = true; + } + + return progress; +}