| /* |
| * Copyright 2024 Valve Corporation |
| * SPDX-License-Identifier: MIT |
| */ |
| |
| #include "nir.h" |
| |
| static bool |
| defined_before_loop(nir_src *src, void *state) |
| { |
| unsigned *loop_preheader_idx = state; |
| return src->ssa->parent_instr->block->index <= *loop_preheader_idx; |
| } |
| |
| static bool |
| is_instr_loop_invariant(nir_instr *instr, unsigned loop_preheader_idx) |
| { |
| switch (instr->type) { |
| case nir_instr_type_load_const: |
| case nir_instr_type_undef: |
| return true; |
| |
| case nir_instr_type_intrinsic: |
| if (!nir_intrinsic_can_reorder(nir_instr_as_intrinsic(instr))) |
| return false; |
| FALLTHROUGH; |
| |
| case nir_instr_type_alu: |
| case nir_instr_type_tex: |
| case nir_instr_type_deref: |
| return nir_foreach_src(instr, defined_before_loop, &loop_preheader_idx); |
| |
| case nir_instr_type_phi: |
| case nir_instr_type_call: |
| case nir_instr_type_jump: |
| default: |
| return false; |
| } |
| } |
| |
| static bool |
| visit_block(nir_block *block, nir_block *preheader) |
| { |
| bool progress = false; |
| nir_foreach_instr_safe(instr, block) { |
| if (is_instr_loop_invariant(instr, preheader->index)) { |
| nir_instr_remove(instr); |
| nir_instr_insert_after_block(preheader, instr); |
| progress = true; |
| } |
| } |
| |
| return progress; |
| } |
| |
| static bool |
| should_optimize_loop(nir_loop *loop) |
| { |
| /* Ignore loops without back-edge */ |
| if (nir_loop_first_block(loop)->predecessors->entries == 1) |
| return false; |
| |
| nir_foreach_block_in_cf_node(block, &loop->cf_node) { |
| /* Check for an early exit inside the loop. */ |
| nir_foreach_instr(instr, block) { |
| if (instr->type == nir_instr_type_intrinsic) { |
| nir_intrinsic_instr *intrin = nir_instr_as_intrinsic(instr); |
| if (intrin->intrinsic == nir_intrinsic_terminate || |
| intrin->intrinsic == nir_intrinsic_terminate_if) |
| return false; |
| } |
| } |
| |
| /* The loop must not contains any return statement. */ |
| if (nir_block_ends_in_return_or_halt(block)) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static bool |
| visit_cf_list(struct exec_list *list, nir_block *preheader, nir_block *exit) |
| { |
| bool progress = false; |
| |
| foreach_list_typed(nir_cf_node, node, node, list) { |
| switch (node->type) { |
| case nir_cf_node_block: { |
| /* By only visiting blocks which dominate the loop exit, we |
| * ensure that we don't speculatively hoist any instructions |
| * which otherwise might not be executed. |
| * |
| * Note, that the proper check would be whether this block |
| * postdominates the loop preheader. |
| */ |
| nir_block *block = nir_cf_node_as_block(node); |
| if (exit && nir_block_dominates(block, exit)) |
| progress |= visit_block(block, preheader); |
| break; |
| } |
| case nir_cf_node_if: { |
| nir_if *nif = nir_cf_node_as_if(node); |
| progress |= visit_cf_list(&nif->then_list, preheader, exit); |
| progress |= visit_cf_list(&nif->else_list, preheader, exit); |
| break; |
| } |
| case nir_cf_node_loop: { |
| nir_loop *loop = nir_cf_node_as_loop(node); |
| bool opt = should_optimize_loop(loop); |
| nir_block *inner_preheader = opt ? nir_cf_node_cf_tree_prev(node) : preheader; |
| nir_block *inner_exit = opt ? nir_cf_node_cf_tree_next(node) : exit; |
| progress |= visit_cf_list(&loop->body, inner_preheader, inner_exit); |
| progress |= visit_cf_list(&loop->continue_list, inner_preheader, inner_exit); |
| break; |
| } |
| case nir_cf_node_function: |
| unreachable("NIR LICM: Unsupported cf_node type."); |
| } |
| } |
| |
| return progress; |
| } |
| |
| bool |
| nir_opt_licm(nir_shader *shader) |
| { |
| bool progress = false; |
| |
| nir_foreach_function_impl(impl, shader) { |
| nir_metadata_require(impl, nir_metadata_block_index | |
| nir_metadata_dominance); |
| |
| if (visit_cf_list(&impl->body, NULL, NULL)) { |
| progress = true; |
| nir_metadata_preserve(impl, nir_metadata_block_index | |
| nir_metadata_dominance); |
| } else { |
| nir_metadata_preserve(impl, nir_metadata_all); |
| } |
| } |
| |
| return progress; |
| } |