1 /*
2 * Copyright 2024 Alyssa Rosenzweig
3 */
4
5 #include "agx_compiler.h"
6 #include "agx_opcodes.h"
7 #include "nir.h"
8
9 /* Validatation doesn't make sense in release builds */
10 #ifndef NDEBUG
11
12 /* Represents a single 16-bit slice of an SSA var */
13 struct var_offset {
14 uint32_t var;
15 uint8_t offset;
16 uint8_t defined;
17 uint8_t pad[2];
18 };
19 static_assert(sizeof(struct var_offset) == 8);
20
21 static struct var_offset
var_index(agx_index idx,uint32_t offset)22 var_index(agx_index idx, uint32_t offset)
23 {
24 assert(idx.type == AGX_INDEX_NORMAL);
25
26 return (struct var_offset){
27 .var = idx.value,
28 .offset = offset,
29 .defined = true,
30 };
31 }
32
33 static struct var_offset
var_undef()34 var_undef()
35 {
36 return (struct var_offset){.defined = false};
37 }
38
39 static bool
vars_equal(struct var_offset x,struct var_offset y)40 vars_equal(struct var_offset x, struct var_offset y)
41 {
42 return x.defined && y.defined && x.var == y.var && x.offset == y.offset;
43 }
44
45 /* Represents the contents of a single register file */
46 struct regfile {
47 struct var_offset r[RA_CLASSES][AGX_NUM_MODELED_REGS];
48 };
49
50 static void
print_regfile(struct regfile * file,FILE * fp)51 print_regfile(struct regfile *file, FILE *fp)
52 {
53 fprintf(fp, "regfile: \n");
54 for (enum ra_class cls = 0; cls < RA_CLASSES; ++cls) {
55 for (unsigned r = 0; r < AGX_NUM_MODELED_REGS; ++r) {
56 struct var_offset v = file->r[cls][r];
57
58 if (v.defined) {
59 fprintf(fp, " %c%u = %u[%u]\n", cls == RA_MEM ? 'm' : 'h', r,
60 v.var, v.offset);
61 }
62 }
63 }
64 fprintf(fp, "\n");
65 }
66
67 #define agx_validate_assert(file, I, s, offs, stmt) \
68 if (!(stmt)) { \
69 fprintf(stderr, "failed to validate RA source %u offs %u: " #stmt "\n", \
70 s, offs); \
71 agx_print_instr(I, stderr); \
72 print_regfile(file, stderr); \
73 return false; \
74 }
75
76 static void
copy_reg(struct regfile * file,agx_index dst,agx_index src)77 copy_reg(struct regfile *file, agx_index dst, agx_index src)
78 {
79 assert(dst.type == AGX_INDEX_REGISTER);
80 assert(src.type == AGX_INDEX_REGISTER);
81
82 enum ra_class dst_cls = ra_class_for_index(dst);
83 enum ra_class src_cls = ra_class_for_index(src);
84
85 for (uint8_t offs = 0; offs < agx_index_size_16(dst); ++offs) {
86 assert(dst.value + offs < ARRAY_SIZE(file->r[dst_cls]));
87 file->r[dst_cls][dst.value + offs] = file->r[src_cls][src.value + offs];
88 }
89 }
90
91 static void
swap_regs(struct regfile * file,agx_index a,agx_index b)92 swap_regs(struct regfile *file, agx_index a, agx_index b)
93 {
94 assert(a.type == AGX_INDEX_REGISTER);
95 assert(b.type == AGX_INDEX_REGISTER);
96
97 enum ra_class a_cls = ra_class_for_index(a);
98 enum ra_class b_cls = ra_class_for_index(b);
99
100 unsigned size = agx_index_size_16(a);
101 assert(size == agx_index_size_16(b));
102
103 for (uint8_t offs = 0; offs < size; ++offs) {
104 assert(a.value + offs < ARRAY_SIZE(file->r[a_cls]));
105 assert(b.value + offs < ARRAY_SIZE(file->r[b_cls]));
106
107 struct var_offset tmp = file->r[a_cls][a.value + offs];
108 file->r[a_cls][a.value + offs] = file->r[b_cls][b.value + offs];
109 file->r[b_cls][b.value + offs] = tmp;
110 }
111 }
112
113 static void
record_dest(struct regfile * file,agx_index idx)114 record_dest(struct regfile *file, agx_index idx)
115 {
116 assert(idx.type == AGX_INDEX_NORMAL && idx.has_reg);
117 enum ra_class cls = ra_class_for_index(idx);
118
119 for (uint8_t offs = 0; offs < agx_index_size_16(idx); ++offs) {
120 assert(idx.reg + offs < ARRAY_SIZE(file->r[cls]));
121 file->r[cls][idx.reg + offs] = var_index(idx, offs);
122 }
123 }
124
125 static bool
validate_src(agx_instr * I,unsigned s,struct regfile * file,agx_index idx)126 validate_src(agx_instr *I, unsigned s, struct regfile *file, agx_index idx)
127 {
128 assert(idx.type == AGX_INDEX_NORMAL && idx.has_reg);
129 enum ra_class cls = ra_class_for_index(idx);
130
131 for (uint8_t offs = 0; offs < agx_index_size_16(idx); ++offs) {
132 assert(idx.reg + offs < ARRAY_SIZE(file->r[cls]));
133 struct var_offset actual = file->r[cls][idx.reg + offs];
134
135 agx_validate_assert(file, I, s, offs, actual.defined);
136 agx_validate_assert(file, I, s, offs, actual.var == idx.value);
137 agx_validate_assert(file, I, s, offs, actual.offset == offs);
138 }
139
140 return true;
141 }
142
143 static bool
validate_block(agx_context * ctx,agx_block * block,struct regfile * blocks)144 validate_block(agx_context *ctx, agx_block *block, struct regfile *blocks)
145 {
146 struct regfile *file = &blocks[block->index];
147 bool success = true;
148
149 /* Pathological shaders can end up with loop headers that have only a single
150 * predecessor and act like normal blocks. Validate them as such, since RA
151 * treats them as such implicitly. Affects:
152 *
153 * dEQP-VK.graphicsfuzz.spv-stable-mergesort-dead-code
154 */
155 bool loop_header = block->loop_header && agx_num_predecessors(block) > 1;
156
157 /* Initialize the register file based on predecessors. This only works in
158 * non-loop headers, since loop headers have unprocessed predecessors.
159 * However, loop headers phi-declare everything instead of using implicit
160 * live-in sources, so that's ok.
161 */
162 if (!loop_header) {
163 bool first_pred = true;
164 agx_foreach_predecessor(block, pred) {
165 struct regfile *pred_file = &blocks[(*pred)->index];
166
167 for (enum ra_class cls = 0; cls < RA_CLASSES; ++cls) {
168 for (unsigned r = 0; r < AGX_NUM_MODELED_REGS; ++r) {
169 if (first_pred)
170 file->r[cls][r] = pred_file->r[cls][r];
171 else if (!vars_equal(file->r[cls][r], pred_file->r[cls][r]))
172 file->r[cls][r] = var_undef();
173 }
174 }
175
176 first_pred = false;
177 }
178 }
179
180 agx_foreach_instr_in_block(block, I) {
181 /* Phis are special since they happen along the edge */
182 if (I->op != AGX_OPCODE_PHI) {
183 agx_foreach_ssa_src(I, s) {
184 success &= validate_src(I, s, file, I->src[s]);
185 }
186 }
187
188 agx_foreach_ssa_dest(I, d) {
189 record_dest(file, I->dest[d]);
190 }
191
192 /* Lowered live range splits don't have SSA associated, handle
193 * directly at the register level.
194 */
195 if (I->op == AGX_OPCODE_MOV && I->dest[0].type == AGX_INDEX_REGISTER &&
196 I->src[0].type == AGX_INDEX_REGISTER) {
197
198 copy_reg(file, I->dest[0], I->src[0]);
199 } else if (I->op == AGX_OPCODE_SWAP) {
200 swap_regs(file, I->src[0], I->src[1]);
201 } else if (I->op == AGX_OPCODE_PHI &&
202 I->dest[0].type == AGX_INDEX_REGISTER) {
203 /* Register-only phis which resolve to the same variable in all blocks.
204 * This is generated for edge case live range splits.
205 */
206 assert(!I->dest[0].memory);
207 assert(!loop_header);
208 for (uint8_t offs = 0; offs < agx_index_size_16(I->dest[0]); ++offs) {
209 bool all_same = true;
210 bool first = true;
211 struct var_offset same = var_undef();
212 agx_foreach_predecessor(block, pred) {
213 unsigned idx = agx_predecessor_index(block, *pred);
214 agx_index src = I->src[idx];
215
216 assert(!src.memory);
217 if (src.type != AGX_INDEX_REGISTER) {
218 all_same = false;
219 first = false;
220 continue;
221 }
222
223 struct regfile *pred_file = &blocks[(*pred)->index];
224 struct var_offset var = pred_file->r[RA_GPR][src.value + offs];
225 all_same &= first || vars_equal(var, same);
226 same = var;
227 first = false;
228 }
229
230 if (all_same) {
231 file->r[RA_GPR][I->dest[0].value + offs] = same;
232 }
233 }
234 } else if (I->op == AGX_OPCODE_PHI && I->dest[0].has_reg &&
235 !loop_header) {
236 /* Phis which resolve to the same variable in all blocks.
237 * This is generated for live range splits.
238 */
239 enum ra_class cls = ra_class_for_index(I->dest[0]);
240 for (uint8_t offs = 0; offs < agx_index_size_16(I->dest[0]); ++offs) {
241 bool all_same = true;
242 bool first = true;
243 struct var_offset same = var_undef();
244 agx_foreach_predecessor(block, pred) {
245 unsigned idx = agx_predecessor_index(block, *pred);
246 agx_index src = I->src[idx];
247
248 assert(ra_class_for_index(src) == cls);
249 if (!src.has_reg) {
250 all_same = false;
251 first = false;
252 continue;
253 }
254
255 struct regfile *pred_file = &blocks[(*pred)->index];
256 struct var_offset var = pred_file->r[cls][src.reg + offs];
257 all_same &= first || vars_equal(var, same);
258 same = var;
259 first = false;
260 }
261
262 if (all_same && I->dest[0].value == I->src[0].value) {
263 file->r[cls][I->dest[0].reg + offs] = same;
264 }
265 }
266 }
267 }
268
269 /* After processing a block, process the block's source in its successors'
270 * phis. These happen on the edge so we have all the information here, even
271 * with backedges.
272 */
273 agx_foreach_successor(block, succ) {
274 unsigned idx = agx_predecessor_index(succ, block);
275
276 agx_foreach_phi_in_block(succ, phi) {
277 if (phi->src[idx].type == AGX_INDEX_NORMAL) {
278 success &= validate_src(phi, idx, file, phi->src[idx]);
279 }
280 }
281 }
282
283 return success;
284 }
285
286 void
agx_validate_ra(agx_context * ctx)287 agx_validate_ra(agx_context *ctx)
288 {
289 bool succ = true;
290 struct regfile *blocks = calloc(ctx->num_blocks, sizeof(*blocks));
291
292 agx_foreach_block(ctx, block) {
293 succ &= validate_block(ctx, block, blocks);
294 }
295
296 if (!succ) {
297 agx_print_shader(ctx, stderr);
298 unreachable("invalid RA");
299 }
300
301 free(blocks);
302 }
303
304 #endif /* NDEBUG */
305