| /* |
| * Copyright © 2018 Intel Corporation |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining a |
| * copy of this software and associated documentation files (the "Software"), |
| * to deal in the Software without restriction, including without limitation |
| * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
| * and/or sell copies of the Software, and to permit persons to whom the |
| * Software is furnished to do so, subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice (including the next |
| * paragraph) shall be included in all copies or substantial portions of the |
| * Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
| * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
| * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
| * IN THE SOFTWARE. |
| */ |
| |
| #include "nir.h" |
| #include "nir_builder.h" |
| #include "nir_control_flow.h" |
| #include "nir_worklist.h" |
| |
| static bool |
| nir_op_is_derivative(nir_op op) |
| { |
| return op == nir_op_fddx || |
| op == nir_op_fddy || |
| op == nir_op_fddx_fine || |
| op == nir_op_fddy_fine || |
| op == nir_op_fddx_coarse || |
| op == nir_op_fddy_coarse; |
| } |
| |
| static bool |
| nir_texop_implies_derivative(nir_texop op) |
| { |
| return op == nir_texop_tex || |
| op == nir_texop_txb || |
| op == nir_texop_lod; |
| } |
| #define MOVE_INSTR_FLAG 1 |
| #define STOP_PROCESSING_INSTR_FLAG 2 |
| |
| /** Check recursively if the source can be moved to the top of the shader. |
| * Sets instr->pass_flags to MOVE_INSTR_FLAG and adds the instr |
| * to the given worklist |
| */ |
| static bool |
| can_move_src(nir_src *src, void *worklist) |
| { |
| if (!src->is_ssa) |
| return false; |
| |
| nir_instr *instr = src->ssa->parent_instr; |
| if (instr->pass_flags) |
| return true; |
| |
| /* Phi instructions can't be moved at all. Also, if we're dependent on |
| * a phi then we are dependent on some other bit of control flow and |
| * it's hard to figure out the proper condition. |
| */ |
| if (instr->type == nir_instr_type_phi) |
| return false; |
| |
| if (instr->type == nir_instr_type_intrinsic) { |
| nir_intrinsic_instr *intrin = nir_instr_as_intrinsic(instr); |
| if (intrin->intrinsic == nir_intrinsic_load_deref) { |
| nir_deref_instr *deref = nir_src_as_deref(intrin->src[0]); |
| if (!nir_deref_mode_is_one_of(deref, nir_var_read_only_modes)) |
| return false; |
| } else if (!(nir_intrinsic_infos[intrin->intrinsic].flags & |
| NIR_INTRINSIC_CAN_REORDER)) { |
| return false; |
| } |
| } |
| |
| /* set pass_flags and remember the instruction for potential cleanup */ |
| instr->pass_flags = MOVE_INSTR_FLAG; |
| nir_instr_worklist_push_tail(worklist, instr); |
| |
| if (!nir_foreach_src(instr, can_move_src, worklist)) { |
| return false; |
| } |
| return true; |
| } |
| |
| /** Try to mark a discard or demote instruction for moving |
| * |
| * This function does two things. One is that it searches through the |
| * dependency chain to see if this discard is an instruction that we can move |
| * up to the top. Second, if the discard is one we can move, it tags the |
| * discard and its dependencies (using pass_flags = 1). |
| * Demote are handled the same way, except that they can still be moved up |
| * when implicit derivatives are used. |
| */ |
| static bool |
| try_move_discard(nir_intrinsic_instr *discard) |
| { |
| /* We require the discard to be in the top level of control flow. We |
| * could, in theory, move discards that are inside ifs or loops but that |
| * would be a lot more work. |
| */ |
| if (discard->instr.block->cf_node.parent->type != nir_cf_node_function) |
| return false; |
| |
| /* Build the set of all instructions discard depends on to be able to |
| * clear the flags in case the discard cannot be moved. |
| */ |
| nir_instr_worklist *work = nir_instr_worklist_create(); |
| if (!work) |
| return false; |
| discard->instr.pass_flags = MOVE_INSTR_FLAG; |
| |
| bool can_move_discard = can_move_src(&discard->src[0], work); |
| if (!can_move_discard) { |
| /* Moving the discard is impossible: clear the flags */ |
| discard->instr.pass_flags = 0; |
| nir_foreach_instr_in_worklist(instr, work) |
| instr->pass_flags = 0; |
| } |
| |
| nir_instr_worklist_destroy(work); |
| |
| return can_move_discard; |
| } |
| |
| static bool |
| opt_move_discards_to_top_impl(nir_function_impl *impl) |
| { |
| bool progress = false; |
| bool consider_discards = true; |
| bool moved = false; |
| |
| /* Walk through the instructions and look for a discard that we can move |
| * to the top of the program. If we hit any operation along the way that |
| * we cannot safely move a discard above, break out of the loop and stop |
| * trying to move any more discards. |
| */ |
| nir_foreach_block(block, impl) { |
| nir_foreach_instr_safe(instr, block) { |
| instr->pass_flags = 0; |
| |
| switch (instr->type) { |
| case nir_instr_type_alu: { |
| nir_alu_instr *alu = nir_instr_as_alu(instr); |
| if (nir_op_is_derivative(alu->op)) |
| consider_discards = false; |
| continue; |
| } |
| |
| case nir_instr_type_deref: |
| case nir_instr_type_load_const: |
| case nir_instr_type_ssa_undef: |
| case nir_instr_type_phi: |
| /* These are all safe */ |
| continue; |
| |
| case nir_instr_type_call: |
| instr->pass_flags = STOP_PROCESSING_INSTR_FLAG; |
| /* We don't know what the function will do */ |
| goto break_all; |
| |
| case nir_instr_type_tex: { |
| nir_tex_instr *tex = nir_instr_as_tex(instr); |
| if (nir_texop_implies_derivative(tex->op)) |
| consider_discards = false; |
| continue; |
| } |
| |
| case nir_instr_type_intrinsic: { |
| nir_intrinsic_instr *intrin = nir_instr_as_intrinsic(instr); |
| if (nir_intrinsic_writes_external_memory(intrin)) { |
| instr->pass_flags = STOP_PROCESSING_INSTR_FLAG; |
| goto break_all; |
| } |
| |
| if ((intrin->intrinsic == nir_intrinsic_discard_if && consider_discards) || |
| intrin->intrinsic == nir_intrinsic_demote_if) |
| moved = moved || try_move_discard(intrin); |
| continue; |
| } |
| |
| case nir_instr_type_jump: { |
| nir_jump_instr *jump = nir_instr_as_jump(instr); |
| /* A return would cause the discard to not get executed */ |
| if (jump->type == nir_jump_return) { |
| instr->pass_flags = STOP_PROCESSING_INSTR_FLAG; |
| goto break_all; |
| } |
| continue; |
| } |
| |
| case nir_instr_type_parallel_copy: |
| unreachable("Unhanded instruction type"); |
| } |
| } |
| } |
| break_all: |
| |
| if (moved) { |
| /* Walk the list of instructions and move the discard/demote and |
| * everything it depends on to the top. We walk the instruction list |
| * here because it ensures that everything stays in its original order. |
| * This provides stability for the algorithm and ensures that we don't |
| * accidentally get dependencies out-of-order. |
| */ |
| nir_cursor cursor = nir_before_block(nir_start_block(impl)); |
| nir_foreach_block(block, impl) { |
| nir_foreach_instr_safe(instr, block) { |
| if (instr->pass_flags == STOP_PROCESSING_INSTR_FLAG) |
| return progress; |
| if (instr->pass_flags == MOVE_INSTR_FLAG) { |
| progress |= nir_instr_move(cursor, instr); |
| cursor = nir_after_instr(instr); |
| } |
| } |
| } |
| } |
| |
| return progress; |
| } |
| |
| /* This optimization only operates on discard_if/demoe_if so |
| * nir_opt_conditional_discard and nir_lower_discard_or_demote |
| * should have been called before. |
| */ |
| bool |
| nir_opt_move_discards_to_top(nir_shader *shader) |
| { |
| assert(shader->info.stage == MESA_SHADER_FRAGMENT); |
| |
| bool progress = false; |
| |
| if (!shader->info.fs.uses_discard) |
| return false; |
| |
| nir_foreach_function(function, shader) { |
| if (function->impl && opt_move_discards_to_top_impl(function->impl)) { |
| nir_metadata_preserve(function->impl, nir_metadata_block_index | |
| nir_metadata_dominance); |
| progress = true; |
| } |
| } |
| |
| return progress; |
| } |