nir/dce: perform DCE for unlooped instructions in a single pass
It's unnecessary to iterate twice for instructions outside loops. Compile-time (nir_opt_dce): Difference at 95.0% confidence -630.64 +/- 6.18761 -27.0751% +/- 0.223134% (Student's t, pooled s = 7.30785) Compile-time (entire run): Difference at 95.0% confidence -749.54 +/- 48.8272 -1.82644% +/- 0.117838% (Student's t, pooled s = 57.6672) Signed-off-by: Rhys Perry <pendingchaos02@gmail.com> Reviewed-by: Daniel Schürmann <daniel@schuermann.dev> Reviewed-by: Jason Ekstrand <jason@jlekstrand.net> Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/7691>
This commit is contained in:
@@ -106,14 +106,14 @@ struct loop_state {
|
|||||||
nir_block *preheader;
|
nir_block *preheader;
|
||||||
};
|
};
|
||||||
|
|
||||||
static void
|
static bool
|
||||||
mark_block(nir_block *block, BITSET_WORD *defs_live, struct loop_state *loop)
|
dce_block(nir_block *block, BITSET_WORD *defs_live, struct loop_state *loop)
|
||||||
{
|
{
|
||||||
|
bool progress = false;
|
||||||
bool phis_changed = false;
|
bool phis_changed = false;
|
||||||
nir_foreach_instr_reverse(instr, block) {
|
nir_foreach_instr_reverse_safe(instr, block) {
|
||||||
if (is_live(defs_live, instr)) {
|
bool live = is_live(defs_live, instr);
|
||||||
instr->pass_flags = 1;
|
if (live) {
|
||||||
|
|
||||||
if (instr->type == nir_instr_type_phi) {
|
if (instr->type == nir_instr_type_phi) {
|
||||||
nir_foreach_phi_src(src, nir_instr_as_phi(instr)) {
|
nir_foreach_phi_src(src, nir_instr_as_phi(instr)) {
|
||||||
phis_changed |= mark_src_live(&src->src, defs_live) &&
|
phis_changed |= mark_src_live(&src->src, defs_live) &&
|
||||||
@@ -122,8 +122,16 @@ mark_block(nir_block *block, BITSET_WORD *defs_live, struct loop_state *loop)
|
|||||||
} else {
|
} else {
|
||||||
nir_foreach_src(instr, mark_live_cb, defs_live);
|
nir_foreach_src(instr, mark_live_cb, defs_live);
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
instr->pass_flags = 0;
|
|
||||||
|
/* If we're not in a loop, remove it now if it's dead. If we are in a
|
||||||
|
* loop, leave instructions to be removed later if they're still dead.
|
||||||
|
*/
|
||||||
|
if (loop->preheader) {
|
||||||
|
instr->pass_flags = live;
|
||||||
|
} else if (!live) {
|
||||||
|
nir_instr_remove(instr);
|
||||||
|
progress = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,29 +140,40 @@ mark_block(nir_block *block, BITSET_WORD *defs_live, struct loop_state *loop)
|
|||||||
* setting header_phis_changed.
|
* setting header_phis_changed.
|
||||||
*/
|
*/
|
||||||
loop->header_phis_changed = phis_changed;
|
loop->header_phis_changed = phis_changed;
|
||||||
|
|
||||||
|
return progress;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static bool
|
||||||
mark_cf_list(struct exec_list *cf_list, BITSET_WORD *defs_live,
|
dce_cf_list(struct exec_list *cf_list, BITSET_WORD *defs_live,
|
||||||
struct loop_state *parent_loop)
|
struct loop_state *parent_loop)
|
||||||
{
|
{
|
||||||
|
bool progress = false;
|
||||||
foreach_list_typed_reverse(nir_cf_node, cf_node, node, cf_list) {
|
foreach_list_typed_reverse(nir_cf_node, cf_node, node, cf_list) {
|
||||||
switch (cf_node->type) {
|
switch (cf_node->type) {
|
||||||
case nir_cf_node_block: {
|
case nir_cf_node_block: {
|
||||||
nir_block *block = nir_cf_node_as_block(cf_node);
|
nir_block *block = nir_cf_node_as_block(cf_node);
|
||||||
mark_block(block, defs_live, parent_loop);
|
progress |= dce_block(block, defs_live, parent_loop);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case nir_cf_node_if: {
|
case nir_cf_node_if: {
|
||||||
nir_if *nif = nir_cf_node_as_if(cf_node);
|
nir_if *nif = nir_cf_node_as_if(cf_node);
|
||||||
mark_cf_list(&nif->else_list, defs_live, parent_loop);
|
progress |= dce_cf_list(&nif->else_list, defs_live, parent_loop);
|
||||||
mark_cf_list(&nif->then_list, defs_live, parent_loop);
|
progress |= dce_cf_list(&nif->then_list, defs_live, parent_loop);
|
||||||
mark_src_live(&nif->condition, defs_live);
|
mark_src_live(&nif->condition, defs_live);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case nir_cf_node_loop: {
|
case nir_cf_node_loop: {
|
||||||
nir_loop *loop = nir_cf_node_as_loop(cf_node);
|
nir_loop *loop = nir_cf_node_as_loop(cf_node);
|
||||||
|
|
||||||
|
/* Fast path if the loop has no continues: we can remove instructions
|
||||||
|
* as we mark the others live.
|
||||||
|
*/
|
||||||
|
if (nir_loop_first_block(loop)->predecessors->entries == 1) {
|
||||||
|
progress |= dce_cf_list(&loop->body, defs_live, parent_loop);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
/* Mark instructions as live until there is no more progress. */
|
/* Mark instructions as live until there is no more progress. */
|
||||||
struct loop_state inner_state;
|
struct loop_state inner_state;
|
||||||
inner_state.preheader = nir_cf_node_as_block(nir_cf_node_prev(cf_node));
|
inner_state.preheader = nir_cf_node_as_block(nir_cf_node_prev(cf_node));
|
||||||
@@ -163,14 +182,33 @@ mark_cf_list(struct exec_list *cf_list, BITSET_WORD *defs_live,
|
|||||||
/* dce_cf_list() resets inner_state.header_phis_changed itself, so
|
/* dce_cf_list() resets inner_state.header_phis_changed itself, so
|
||||||
* it doesn't have to be done here.
|
* it doesn't have to be done here.
|
||||||
*/
|
*/
|
||||||
mark_cf_list(&loop->body, defs_live, &inner_state);
|
dce_cf_list(&loop->body, defs_live, &inner_state);
|
||||||
} while (inner_state.header_phis_changed);
|
} while (inner_state.header_phis_changed);
|
||||||
|
|
||||||
|
/* We don't know how many times mark_cf_list() will repeat, so
|
||||||
|
* remove instructions separately.
|
||||||
|
*
|
||||||
|
* By checking parent_loop->preheader, we ensure that we only do this
|
||||||
|
* walk for the outer-most loops so it only happens once.
|
||||||
|
*/
|
||||||
|
if (!parent_loop->preheader) {
|
||||||
|
nir_foreach_block_in_cf_node(block, cf_node) {
|
||||||
|
nir_foreach_instr_safe(instr, block) {
|
||||||
|
if (!instr->pass_flags) {
|
||||||
|
nir_instr_remove(instr);
|
||||||
|
progress = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case nir_cf_node_function:
|
case nir_cf_node_function:
|
||||||
unreachable("Invalid cf type");
|
unreachable("Invalid cf type");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return progress;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
@@ -183,18 +221,7 @@ nir_opt_dce_impl(nir_function_impl *impl)
|
|||||||
|
|
||||||
struct loop_state loop;
|
struct loop_state loop;
|
||||||
loop.preheader = NULL;
|
loop.preheader = NULL;
|
||||||
mark_cf_list(&impl->body, defs_live, &loop);
|
bool progress = dce_cf_list(&impl->body, defs_live, &loop);
|
||||||
|
|
||||||
bool progress = false;
|
|
||||||
|
|
||||||
nir_foreach_block(block, impl) {
|
|
||||||
nir_foreach_instr_safe(instr, block) {
|
|
||||||
if (!instr->pass_flags) {
|
|
||||||
nir_instr_remove(instr);
|
|
||||||
progress = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ralloc_free(defs_live);
|
ralloc_free(defs_live);
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user