diff --git a/src/compiler/nir/nir_opt_peephole_select.c b/src/compiler/nir/nir_opt_peephole_select.c index 2d7bc04a693..840d0fe06a5 100644 --- a/src/compiler/nir/nir_opt_peephole_select.c +++ b/src/compiler/nir/nir_opt_peephole_select.c @@ -26,6 +26,7 @@ */ #include "nir.h" +#include "nir/nir_builder.h" #include "nir_control_flow.h" #include "nir_search_helpers.h" @@ -211,6 +212,116 @@ block_check_for_allowed_instrs(nir_block *block, unsigned *count, return true; } +/** + * Try to collapse nested ifs: + * This optimization turns + * + * if (cond1) { + * + * if (cond2) { + * + * } else { + * } + * } else { + * } + * + * into + * + * + * if (cond1 && cond2) { + * + * } else { + * } + * + */ +static bool +nir_opt_collapse_if(nir_if *if_stmt, nir_shader *shader, unsigned limit, + bool indirect_load_ok, bool expensive_alu_ok) +{ + /* the if has to be nested */ + if (if_stmt->cf_node.parent->type != nir_cf_node_if) + return false; + + /* check if the else block is empty */ + if (!nir_cf_list_is_empty_block(&if_stmt->else_list)) + return false; + + /* this opt doesn't make much sense if the branch is empty */ + if (nir_cf_list_is_empty_block(&if_stmt->then_list)) + return false; + + /* the nested if has to be the only cf_node: + * i.e. */ + nir_if *parent_if = nir_cf_node_as_if(if_stmt->cf_node.parent); + if (exec_list_length(&parent_if->then_list) != 3) + return false; + + /* check if the else block of the parent if is empty */ + if (!nir_cf_list_is_empty_block(&parent_if->else_list)) + return false; + + /* check if the block after the nested if is empty except for phis */ + nir_block *last = nir_if_last_then_block(parent_if); + nir_instr *last_instr = nir_block_last_instr(last); + if (last_instr && last_instr->type != nir_instr_type_phi) + return false; + + /* check if all outer phis become trivial after merging the ifs */ + nir_foreach_instr(instr, last) { + nir_phi_instr *phi = nir_instr_as_phi(instr); + nir_phi_src *else_src = + nir_phi_get_src_from_block(phi, nir_if_first_else_block(if_stmt)); + + nir_foreach_use (src, &phi->dest.ssa) { + assert(src->parent_instr->type == nir_instr_type_phi); + nir_phi_src *phi_src = + nir_phi_get_src_from_block(nir_instr_as_phi(src->parent_instr), + nir_if_first_else_block(parent_if)); + if (phi_src->src.ssa != else_src->src.ssa) + return false; + } + } + + /* check if the block before the nested if matches the requirements */ + nir_block *first = nir_if_first_then_block(parent_if); + unsigned count = 0; + if (!block_check_for_allowed_instrs(first, &count, limit != 0, + indirect_load_ok, expensive_alu_ok)) + return false; + + if (count > limit) + return false; + + /* trivialize succeeding phis */ + nir_foreach_instr(instr, last) { + nir_phi_instr *phi = nir_instr_as_phi(instr); + nir_foreach_use_safe(src, &phi->dest.ssa) { + nir_phi_src *phi_src = + nir_phi_get_src_from_block(nir_instr_as_phi(src->parent_instr), + nir_if_first_else_block(parent_if)); + nir_instr_rewrite_src(src->parent_instr, &phi_src->src, + nir_src_for_ssa(&phi->dest.ssa)); + } + } + + /* combine the conditions */ + struct nir_builder b; + nir_builder_init(&b, nir_cf_node_get_function(&if_stmt->cf_node)->function->impl); + b.cursor = nir_before_cf_node(&if_stmt->cf_node); + nir_ssa_def *cond = nir_iand(&b, if_stmt->condition.ssa, + parent_if->condition.ssa); + nir_if_rewrite_condition(if_stmt, nir_src_for_ssa(cond)); + + /* move the whole inner if before the parent if */ + nir_cf_list tmp; + nir_cf_extract(&tmp, nir_before_block(first), + nir_after_block(last)); + nir_cf_reinsert(&tmp, nir_before_cf_node(&parent_if->cf_node)); + + /* The now empty parent if will be cleaned up by other passes */ + return true; +} + static bool nir_opt_peephole_select_block(nir_block *block, nir_shader *shader, unsigned limit, bool indirect_load_ok, @@ -225,6 +336,11 @@ nir_opt_peephole_select_block(nir_block *block, nir_shader *shader, nir_if *if_stmt = nir_cf_node_as_if(prev_node); + /* first, try to collapse the if */ + if (nir_opt_collapse_if(if_stmt, shader, limit, + indirect_load_ok, expensive_alu_ok)) + return true; + if (if_stmt->control == nir_selection_control_dont_flatten) return false;