summaryrefslogtreecommitdiffstats
path: root/src/compiler/nir/nir_opt_if.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/compiler/nir/nir_opt_if.c')
-rw-r--r--src/compiler/nir/nir_opt_if.c95
1 files changed, 95 insertions, 0 deletions
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;
}