xref: /aosp_15_r20/external/mesa3d/src/freedreno/ir3/ir3_nir_move_varying_inputs.c (revision 6104692788411f58d303aa86923a9ff6ecaded22)
1 /*
2  * Copyright © 2019 Red Hat
3  * SPDX-License-Identifier: MIT
4  */
5 
6 #include "compiler/nir/nir_builder.h"
7 #include "ir3_nir.h"
8 
9 /**
10  * This pass moves varying fetches (and the instructions they depend on
11  * into the start block.
12  *
13  * We need to set the (ei) "end input" flag on the last varying fetch.
14  * And we want to ensure that all threads execute the instruction that
15  * sets (ei).  The easiest way to ensure this is to move all varying
16  * fetches into the start block.  Which is something we used to get for
17  * free by using lower_all_io_to_temps=true.
18  *
19  * This may come at the cost of additional register usage.  OTOH setting
20  * the (ei) flag earlier probably frees up more VS to run.
21  *
22  * Not all varying fetches could be pulled into the start block.
23  * If there are fetches we couldn't pull, like load_interpolated_input
24  * with offset which depends on a non-reorderable ssbo load or on a
25  * phi node, this pass is skipped since it would be hard to find a place
26  * to set (ei) flag (beside at the very end).
27  * a5xx and a6xx do automatically release varying storage at the end.
28  */
29 
30 typedef struct {
31    nir_block *start_block;
32    bool precondition_failed;
33 } precond_state;
34 
35 typedef struct {
36    nir_shader *shader;
37    nir_block *start_block;
38 } state;
39 
40 static void check_precondition_instr(precond_state *state, nir_instr *instr);
41 static void move_instruction_to_start_block(state *state, nir_instr *instr);
42 
43 static bool
check_precondition_src(nir_src * src,void * state)44 check_precondition_src(nir_src *src, void *state)
45 {
46    check_precondition_instr(state, src->ssa->parent_instr);
47    return true;
48 }
49 
50 /* Recursively check if there is even a single dependency which
51  * cannot be moved.
52  */
53 static void
check_precondition_instr(precond_state * state,nir_instr * instr)54 check_precondition_instr(precond_state *state, nir_instr *instr)
55 {
56    if (instr->block == state->start_block)
57       return;
58 
59    switch (instr->type) {
60    case nir_instr_type_alu:
61    case nir_instr_type_deref:
62    case nir_instr_type_load_const:
63    case nir_instr_type_undef:
64       /* These could be safely moved around */
65       break;
66    case nir_instr_type_intrinsic: {
67       nir_intrinsic_instr *intr = nir_instr_as_intrinsic(instr);
68       if (!nir_intrinsic_can_reorder(intr)) {
69          state->precondition_failed = true;
70          return;
71       }
72       break;
73    }
74    default:
75       state->precondition_failed = true;
76       return;
77    }
78 
79    nir_foreach_src(instr, check_precondition_src, state);
80 }
81 
82 static void
check_precondition_block(precond_state * state,nir_block * block)83 check_precondition_block(precond_state *state, nir_block *block)
84 {
85    nir_foreach_instr_safe (instr, block) {
86       if (instr->type != nir_instr_type_intrinsic)
87          continue;
88 
89       nir_intrinsic_instr *intr = nir_instr_as_intrinsic(instr);
90 
91       switch (intr->intrinsic) {
92       case nir_intrinsic_load_interpolated_input:
93       case nir_intrinsic_load_input:
94          break;
95       default:
96          continue;
97       }
98 
99       check_precondition_instr(state, instr);
100 
101       if (state->precondition_failed)
102          return;
103    }
104 }
105 
106 static bool
move_src(nir_src * src,void * state)107 move_src(nir_src *src, void *state)
108 {
109    move_instruction_to_start_block(state, src->ssa->parent_instr);
110    return true;
111 }
112 
113 static void
move_instruction_to_start_block(state * state,nir_instr * instr)114 move_instruction_to_start_block(state *state, nir_instr *instr)
115 {
116    /* nothing to do if the instruction is already in the start block */
117    if (instr->block == state->start_block)
118       return;
119 
120    /* first move (recursively) all src's to ensure they appear before
121     * load*_input that we are trying to move:
122     */
123    nir_foreach_src(instr, move_src, state);
124 
125    /* and then move the instruction itself:
126     */
127    exec_node_remove(&instr->node);
128    exec_list_push_tail(&state->start_block->instr_list, &instr->node);
129    instr->block = state->start_block;
130 }
131 
132 static bool
move_varying_inputs_block(state * state,nir_block * block)133 move_varying_inputs_block(state *state, nir_block *block)
134 {
135    bool progress = false;
136 
137    nir_foreach_instr_safe (instr, block) {
138       if (instr->type != nir_instr_type_intrinsic)
139          continue;
140 
141       nir_intrinsic_instr *intr = nir_instr_as_intrinsic(instr);
142 
143       switch (intr->intrinsic) {
144       case nir_intrinsic_load_interpolated_input:
145       case nir_intrinsic_load_input:
146          /* TODO any others to handle? */
147          break;
148       default:
149          continue;
150       }
151 
152       move_instruction_to_start_block(state, instr);
153 
154       progress = true;
155    }
156 
157    return progress;
158 }
159 
160 bool
ir3_nir_move_varying_inputs(nir_shader * shader)161 ir3_nir_move_varying_inputs(nir_shader *shader)
162 {
163    bool progress = false;
164 
165    assert(shader->info.stage == MESA_SHADER_FRAGMENT);
166 
167    nir_foreach_function (function, shader) {
168       precond_state state;
169 
170       if (!function->impl)
171          continue;
172 
173       state.precondition_failed = false;
174       state.start_block = nir_start_block(function->impl);
175 
176       nir_foreach_block (block, function->impl) {
177          if (block == state.start_block)
178             continue;
179 
180          check_precondition_block(&state, block);
181 
182          if (state.precondition_failed)
183             return false;
184       }
185    }
186 
187    nir_foreach_function (function, shader) {
188       state state;
189 
190       if (!function->impl)
191          continue;
192 
193       state.shader = shader;
194       state.start_block = nir_start_block(function->impl);
195 
196       bool progress = false;
197       nir_foreach_block (block, function->impl) {
198          /* don't need to move anything that is already in the first block */
199          if (block == state.start_block)
200             continue;
201          progress |= move_varying_inputs_block(&state, block);
202       }
203 
204       if (progress) {
205          nir_metadata_preserve(
206             function->impl, nir_metadata_control_flow);
207       }
208    }
209 
210    return progress;
211 }
212