• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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