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