diff --git a/src/compiler/nir/nir_opt_if.c b/src/compiler/nir/nir_opt_if.c index ab00f447097..691448a96e4 100644 --- a/src/compiler/nir/nir_opt_if.c +++ b/src/compiler/nir/nir_opt_if.c @@ -237,6 +237,100 @@ is_block_empty(nir_block *block) exec_list_is_empty(&block->instr_list); } +static bool +nir_block_ends_in_continue(nir_block *block) +{ + if (exec_list_is_empty(&block->instr_list)) + return false; + + nir_instr *instr = nir_block_last_instr(block); + return instr->type == nir_instr_type_jump && + nir_instr_as_jump(instr)->type == nir_jump_continue; +} + +/** + * This optimization turns: + * + * loop { + * ... + * if (cond) { + * do_work_1(); + * continue; + * } else { + * } + * do_work_2(); + * } + * + * into: + * + * loop { + * ... + * if (cond) { + * do_work_1(); + * continue; + * } else { + * do_work_2(); + * } + * } + * + * The continue should then be removed by nir_opt_trivial_continues() and the + * loop can potentially be unrolled. + * + * Note: do_work_2() is only ever blocks and nested loops. We could also nest + * other if-statments in the branch which would allow further continues to + * be removed. However in practice this can result in increased register + * pressure. + */ +static bool +opt_if_loop_last_continue(nir_loop *loop) +{ + /* Get the last if-stament in the loop */ + nir_block *last_block = nir_loop_last_block(loop); + nir_cf_node *if_node = nir_cf_node_prev(&last_block->cf_node); + while (if_node) { + if (if_node->type == nir_cf_node_if) + break; + + if_node = nir_cf_node_prev(if_node); + } + + if (!if_node || if_node->type != nir_cf_node_if) + return false; + + nir_if *nif = nir_cf_node_as_if(if_node); + nir_block *then_block = nir_if_last_then_block(nif); + nir_block *else_block = nir_if_last_else_block(nif); + + bool then_ends_in_continue = nir_block_ends_in_continue(then_block); + bool else_ends_in_continue = nir_block_ends_in_continue(else_block); + + /* If both branches end in a continue do nothing, this should be handled + * by nir_opt_dead_cf(). + */ + if (then_ends_in_continue && else_ends_in_continue) + return false; + + if (!then_ends_in_continue && !else_ends_in_continue) + return false; + + /* Move the last block of the loop inside the last if-statement */ + nir_cf_list tmp; + nir_cf_extract(&tmp, nir_after_cf_node(if_node), + nir_after_block(last_block)); + if (then_ends_in_continue) { + nir_cf_reinsert(&tmp, nir_after_cf_list(&nif->else_list)); + } else { + nir_cf_reinsert(&tmp, nir_after_cf_list(&nif->then_list)); + } + + /* In order to avoid running nir_lower_regs_to_ssa_impl() every time an if + * opt makes progress we leave nir_opt_trivial_continues() to remove the + * continue now that the end of the loop has been simplified. + */ + + return true; +} + /** * This optimization turns: * @@ -596,6 +690,7 @@ opt_if_cf_list(nir_builder *b, struct exec_list *cf_list) nir_loop *loop = nir_cf_node_as_loop(cf_node); progress |= opt_if_cf_list(b, &loop->body); progress |= opt_peel_loop_initial_if(loop); + progress |= opt_if_loop_last_continue(loop); break; }