1 /*
2 * Copyright © 2024 Imagination Technologies Ltd.
3 *
4 * SPDX-License-Identifier: MIT
5 */
6
7 /**
8 * \file pco_opt.c
9 *
10 * \brief PCO optimization passes.
11 */
12
13 #include "pco.h"
14 #include "pco_builder.h"
15 #include "util/bitscan.h"
16 #include "util/bitset.h"
17 #include "util/macros.h"
18 #include "util/ralloc.h"
19 #include "util/u_dynarray.h"
20
21 #include <assert.h>
22 #include <stdbool.h>
23
24 /** Source use. */
25 struct pco_use {
26 pco_instr *instr;
27 pco_ref *psrc;
28 };
29
30 /** Shared optimization context. */
31 struct pco_opt_ctx {
32 void *mem_ctx;
33 struct util_dynarray mods;
34 };
35
36 /**
37 * \brief Prepares modifiers and their users for propagation.
38 *
39 * Instructions with commutative sources may use a modifier in the source which
40 * can't have said modifier applied to it, e.g. fadd can have {abs,neg,flr} set
41 * in src0, but src1 only supports abs.
42 *
43 * \param[in,out] shader PCO shader.
44 * \param[in,out] ctx Shared optimization context.
45 * \return True if any instructions were modified.
46 */
prep_mods(pco_shader * shader,struct pco_opt_ctx * ctx)47 static inline bool prep_mods(pco_shader *shader, struct pco_opt_ctx *ctx)
48 {
49 bool progress = false;
50
51 util_dynarray_init(&ctx->mods, ctx->mem_ctx);
52
53 /* TODO: support for more modifiers/ops. */
54 /* TODO: support cases where > 1 modifier can be applied (e.g. .abs.neg),
55 * and where modifiers might need to be applied on more than one source.
56 */
57 pco_foreach_func_in_shader (func, shader) {
58 pco_foreach_instr_in_func_safe (mod, func) {
59 if (mod->op != PCO_OP_NEG && mod->op != PCO_OP_ABS &&
60 mod->op != PCO_OP_FLR)
61 continue;
62
63 pco_foreach_instr_in_func_from (instr, mod) {
64 if (instr->op != PCO_OP_FADD && instr->op != PCO_OP_FMUL)
65 continue;
66
67 pco_ref *match_src = NULL;
68 pco_ref *other_src = NULL;
69 pco_foreach_instr_src (psrc, instr) {
70 if (!pco_ref_is_ssa(*psrc) || psrc->val != mod->dest[0].val)
71 other_src = psrc;
72 else
73 match_src = psrc;
74 }
75
76 /* Instruction doesn't use the mod. */
77 if (!match_src)
78 continue;
79
80 /* Mod used in *both* sources; swapping would do nothing. */
81 if (!other_src)
82 continue;
83
84 unsigned match_src_index = match_src - instr->src;
85 unsigned other_src_index = other_src - instr->src;
86
87 bool match_has_mod = false;
88 bool other_has_mod = false;
89
90 switch (mod->op) {
91 case PCO_OP_NEG:
92 match_has_mod = pco_instr_src_has_neg(instr, match_src_index);
93 other_has_mod = pco_instr_src_has_neg(instr, other_src_index);
94 break;
95
96 case PCO_OP_ABS:
97 match_has_mod = pco_instr_src_has_abs(instr, match_src_index);
98 other_has_mod = pco_instr_src_has_abs(instr, other_src_index);
99 break;
100
101 case PCO_OP_FLR:
102 match_has_mod = pco_instr_src_has_flr(instr, match_src_index);
103 other_has_mod = pco_instr_src_has_flr(instr, other_src_index);
104 break;
105
106 default:
107 unreachable();
108 }
109
110 /* Source can already have the mod set. */
111 if (match_has_mod)
112 continue;
113
114 /* Other source can't have the mod set either. */
115 if (!other_has_mod)
116 continue;
117
118 /* Swap the sources. */
119 pco_ref tmp = *match_src;
120 *match_src = *other_src;
121 *other_src = tmp;
122
123 progress = true;
124 }
125
126 /* Rewrite the mod op to a mov. */
127 pco_ref src = mod->src[0];
128 switch (mod->op) {
129 case PCO_OP_NEG:
130 src = pco_ref_neg(src);
131 break;
132
133 case PCO_OP_ABS:
134 src = pco_ref_abs(src);
135 break;
136
137 case PCO_OP_FLR:
138 src = pco_ref_flr(src);
139 break;
140
141 default:
142 unreachable();
143 }
144
145 pco_builder b = pco_builder_create(func, pco_cursor_before_instr(mod));
146 pco_instr *mov = pco_mov(&b, mod->dest[0], src);
147 util_dynarray_append(&ctx->mods, pco_instr *, mov);
148 pco_instr_delete(mod);
149
150 progress = true;
151 }
152 }
153
154 return progress;
155 }
156
157 /**
158 * \brief Lowers modifiers to hardware instructions.
159 *
160 * \param[in,out] shader PCO shader.
161 * \param[in,out] ctx Shared optimization context.
162 * \return True if any instructions were modified.
163 */
lower_mods(pco_shader * shader,struct pco_opt_ctx * ctx)164 static inline bool lower_mods(pco_shader *shader, struct pco_opt_ctx *ctx)
165 {
166 bool progress = false;
167
168 util_dynarray_foreach (&ctx->mods, pco_instr *, pmod) {
169 pco_instr *mod = *pmod;
170
171 pco_builder b =
172 pco_builder_create(mod->parent_func, pco_cursor_before_instr(mod));
173
174 if (mod->src[0].flr)
175 pco_fadd(&b, mod->dest[0], mod->src[0], pco_zero);
176 else
177 pco_mbyp0(&b, mod->dest[0], mod->src[0]);
178
179 pco_instr_delete(mod);
180
181 progress = true;
182 }
183
184 return progress;
185 }
186
187 /**
188 * \brief Checks if an instruction can be back-propagated.
189 *
190 * \param[in] to Instruction being back-propagated to.
191 * \param[in] from Instruction being back-propagated from.
192 * \return True if from can be back-propagated.
193 */
can_back_prop_instr(const pco_instr * to,const pco_instr * from)194 static inline bool can_back_prop_instr(const pco_instr *to,
195 const pco_instr *from)
196 {
197 const struct pco_op_info *info = &pco_op_info[from->op];
198
199 /* Ensure any op mods set in from can also be set in to. */
200 u_foreach_bit64 (mod, info->mods) {
201 if (pco_instr_has_mod(from, mod) && pco_instr_mod_is_set(from, mod) &&
202 !pco_instr_has_mod(to, mod))
203 return false;
204 }
205
206 return true;
207 }
208
209 /**
210 * \brief Transfers any op mods that have been set.
211 *
212 * \param[in] to Instruction receiving the op mods.
213 * \param[in] from Instruction providing the op mods.
214 */
xfer_set_op_mods(pco_instr * to,const pco_instr * from)215 static inline void xfer_set_op_mods(pco_instr *to, const pco_instr *from)
216 {
217 const struct pco_op_info *info = &pco_op_info[from->op];
218
219 /* Transfer set op mods. */
220 u_foreach_bit64 (mod, info->mods) {
221 if (pco_instr_has_mod(from, mod) && pco_instr_mod_is_set(from, mod)) {
222 assert(pco_instr_has_mod(to, mod));
223 pco_instr_set_mod(to, mod, pco_instr_get_mod(from, mod));
224 }
225 }
226 }
227
228 /**
229 * \brief Tries to back-propagate an instruction.
230 *
231 * \param[in] uses Global list of uses.
232 * \param[in] instr Instruction to try and back-propagate.
233 * \return True if back-propagation was successful.
234 */
try_back_prop_instr(struct pco_use * uses,pco_instr * instr)235 static inline bool try_back_prop_instr(struct pco_use *uses, pco_instr *instr)
236 {
237 pco_ref *pdest_to = &instr->dest[0];
238 if (instr->num_dests != 1 || !pco_ref_is_ssa(*pdest_to))
239 return false;
240
241 struct pco_use *use = &uses[pdest_to->val];
242 if (!use->instr)
243 return false;
244
245 /* TODO: allow propagating instructions which can have their dest/op
246 * modifiers set to perform the same operations as use source modifiers.
247 *
248 * Make sure to check in can_back_prop_instr when implementing this.
249 * We're fine for now since mov has no settable dest mods.
250 */
251 if (use->instr->op != PCO_OP_MOV || pco_ref_has_mods_set(*use->psrc))
252 return false;
253
254 if (!can_back_prop_instr(instr, use->instr))
255 return false;
256
257 pco_ref *pdest_from = &use->instr->dest[0];
258
259 assert(pco_ref_get_bits(*pdest_from) == pco_ref_get_bits(*pdest_to));
260 assert(pco_ref_get_chans(*pdest_from) == pco_ref_get_chans(*pdest_to));
261 assert(!pco_ref_has_mods_set(*pdest_from) &&
262 !pco_ref_has_mods_set(*pdest_to));
263
264 /* Propagate the destination and the set op mods. */
265 /* TODO: types? */
266 *pdest_to = *pdest_from;
267 xfer_set_op_mods(instr, use->instr);
268 pco_instr_delete(use->instr);
269
270 return true;
271 }
272
273 /**
274 * \brief Instruction back-propagation pass.
275 *
276 * \param[in,out] shader PCO shader.
277 * \return True if any back-propagations were performed.
278 */
back_prop(pco_shader * shader)279 static inline bool back_prop(pco_shader *shader)
280 {
281 bool progress = false;
282 struct pco_use *uses;
283 BITSET_WORD *multi_uses;
284
285 pco_foreach_func_in_shader_rev (func, shader) {
286 uses = rzalloc_array_size(NULL, sizeof(*uses), func->next_ssa);
287 multi_uses = rzalloc_array_size(uses,
288 sizeof(*multi_uses),
289 BITSET_WORDS(func->next_ssa));
290
291 pco_foreach_instr_in_func_safe_rev (instr, func) {
292 pco_foreach_instr_src_ssa (psrc, instr) {
293 if (BITSET_TEST(multi_uses, psrc->val) || uses[psrc->val].instr) {
294 BITSET_SET(multi_uses, psrc->val);
295 uses[psrc->val].instr = NULL;
296 continue;
297 }
298
299 uses[psrc->val] = (struct pco_use){
300 .instr = instr,
301 .psrc = psrc,
302 };
303 }
304
305 progress |= try_back_prop_instr(uses, instr);
306 }
307
308 ralloc_free(uses);
309 }
310
311 return progress;
312 }
313
314 /**
315 * \brief Checks if a source can be forward-propagated.
316 *
317 * \param[in] to_instr Instruction being forward-propagated to.
318 * \param[in] to Source being forward-propagated to.
319 * \param[in] from Source being forward-propagated from.
320 * \return True if from can be forward-propagated.
321 */
can_fwd_prop_src(const pco_instr * to_instr,const pco_ref * to,const pco_ref * from)322 static inline bool can_fwd_prop_src(const pco_instr *to_instr,
323 const pco_ref *to,
324 const pco_ref *from)
325 {
326 /* Check sizes. */
327 if (pco_ref_get_bits(*from) != pco_ref_get_bits(*to))
328 return false;
329
330 if (pco_ref_get_chans(*from) != pco_ref_get_chans(*to))
331 return false;
332
333 /* See if the modifiers can be propagated. */
334 unsigned to_src_index = to - to_instr->src;
335 if (pco_ref_has_mods_set(*from)) {
336 if (from->oneminus && !pco_instr_src_has_oneminus(to_instr, to_src_index))
337 return false;
338 if (from->clamp && !pco_instr_src_has_clamp(to_instr, to_src_index))
339 return false;
340 if (from->flr && !pco_instr_src_has_flr(to_instr, to_src_index))
341 return false;
342 if (from->abs && !pco_instr_src_has_abs(to_instr, to_src_index))
343 return false;
344 if (from->neg && !pco_instr_src_has_neg(to_instr, to_src_index))
345 return false;
346 if (from->elem && !pco_instr_src_has_elem(to_instr, to_src_index))
347 return false;
348 }
349
350 /* TODO: Also need to consider whether the source can be represented in the
351 * propagated instruction.
352 * Or, a legalize pass to insert movs; probably better since
353 * feature/arch-agnostic.
354 */
355
356 return true;
357 }
358
359 /**
360 * \brief Tries to forward-propagate an instruction.
361 *
362 * \param[in] writes Global list of writes.
363 * \param[in] instr Instruction to try and forward-propagate.
364 * \return True if forward-propagation was successful.
365 */
try_fwd_prop_instr(pco_instr ** writes,pco_instr * instr)366 static inline bool try_fwd_prop_instr(pco_instr **writes, pco_instr *instr)
367 {
368 bool progress = false;
369
370 pco_foreach_instr_src_ssa (psrc, instr) {
371 pco_instr *parent_instr = writes[psrc->val];
372
373 if (!parent_instr || parent_instr->op != PCO_OP_MOV)
374 continue;
375
376 if (!can_fwd_prop_src(instr, psrc, &parent_instr->src[0]))
377 continue;
378
379 /* Propagate the source. */
380 pco_ref repl = parent_instr->src[0];
381 if (psrc->flr)
382 repl = pco_ref_flr(repl);
383 else if (psrc->abs)
384 repl = pco_ref_abs(repl);
385
386 repl.neg ^= psrc->neg;
387
388 /* TODO: types? */
389 *psrc = repl;
390
391 progress = true;
392 }
393
394 return progress;
395 }
396
397 /**
398 * \brief Instruction forward-propagation pass.
399 *
400 * \param[in,out] shader PCO shader.
401 * \return True if any forward-propagations were performed.
402 */
fwd_prop(pco_shader * shader)403 static inline bool fwd_prop(pco_shader *shader)
404 {
405 bool progress = false;
406 pco_instr **writes;
407
408 pco_foreach_func_in_shader (func, shader) {
409 writes = rzalloc_array_size(NULL, sizeof(*writes), func->next_ssa);
410
411 pco_foreach_instr_in_func (instr, func) {
412 pco_foreach_instr_dest_ssa (pdest, instr) {
413 writes[pdest->val] = instr;
414 }
415
416 progress |= try_fwd_prop_instr(writes, instr);
417 }
418
419 ralloc_free(writes);
420 }
421
422 return progress;
423 }
424
425 /**
426 * \brief Tries to propagate a comp instruction referencing hw registers.
427 *
428 * \param[in] src Source to match for replacement.
429 * \param[in] repl Replacement hw register reference.
430 * \param[in] from Instruction to try and propagate from.
431 * \return True if propagation was successful.
432 */
try_prop_hw_comp(pco_ref src,pco_ref repl,pco_instr * from)433 static inline bool try_prop_hw_comp(pco_ref src, pco_ref repl, pco_instr *from)
434 {
435 bool progress = false;
436
437 pco_foreach_instr_in_func_from (instr, from) {
438 pco_foreach_instr_src_ssa (psrc, instr) {
439 if (psrc->val != src.val)
440 continue;
441
442 /* Propagate the source. */
443 pco_ref _repl = repl;
444 if (psrc->flr)
445 _repl = pco_ref_flr(_repl);
446 else if (psrc->abs)
447 _repl = pco_ref_abs(_repl);
448
449 _repl.neg ^= psrc->neg;
450
451 /* TODO: types? */
452 *psrc = _repl;
453
454 progress = true;
455 }
456 }
457
458 return progress;
459 }
460
461 /**
462 * \brief Pass to propagate comp instructions referencing hw registers.
463 *
464 * \param[in,out] shader PCO shader.
465 * \return True if any hw reg propagations were performed.
466 */
prop_hw_comps(pco_shader * shader)467 static inline bool prop_hw_comps(pco_shader *shader)
468 {
469 bool progress = false;
470 pco_foreach_func_in_shader (func, shader) {
471 pco_foreach_instr_in_func_safe (instr, func) {
472 if (instr->op != PCO_OP_COMP)
473 continue;
474
475 pco_ref vec_src = instr->src[0];
476 if (pco_ref_is_ssa(vec_src))
477 continue;
478
479 pco_ref dest = instr->dest[0];
480 assert(pco_ref_is_ssa(dest));
481
482 unsigned offset = pco_ref_get_imm(instr->src[1]);
483
484 /* Construct a replacement scalar reference. */
485 pco_ref repl = vec_src;
486 repl = pco_ref_chans(repl, 1);
487 repl = pco_ref_offset(repl, offset);
488
489 progress |= try_prop_hw_comp(dest, repl, instr);
490
491 pco_instr_delete(instr);
492 }
493 }
494
495 return progress;
496 }
497
498 /**
499 * \brief Performs shader optimizations.
500 *
501 * \param[in,out] shader PCO shader.
502 * \return True if the pass made progress.
503 */
pco_opt(pco_shader * shader)504 bool pco_opt(pco_shader *shader)
505 {
506 bool progress = false;
507 struct pco_opt_ctx ctx = { .mem_ctx = ralloc_context(NULL) };
508
509 progress |= prep_mods(shader, &ctx);
510 progress |= back_prop(shader);
511 progress |= fwd_prop(shader);
512 /* TODO: Track whether there are any comp instructions referencing hw
513 * registers resulting from the previous passes, and only run prop_hw_comps
514 * if this is the case.
515 */
516 progress |= prop_hw_comps(shader);
517
518 progress |= lower_mods(shader, &ctx);
519
520 ralloc_free(ctx.mem_ctx);
521 return progress;
522 }
523
524 /**
525 * \brief Checks whether an instruction has side-effects.
526 *
527 * \param[in] instr Instruction to check.
528 * \return True if the instruction has side-effects.
529 */
instr_has_side_effects(pco_instr * instr)530 static inline bool instr_has_side_effects(pco_instr *instr)
531 {
532 /* Atomic instructions. */
533 if (pco_instr_has_atom(instr) && pco_instr_get_atom(instr))
534 return true;
535
536 /* TODO:
537 * - gradient
538 * - conditional
539 * - sample writes (+ set the destination pointer to point to the write data)
540 * - others
541 */
542 return false;
543 }
544
545 /**
546 * \brief Performs DCE.
547 *
548 * \param[in,out] shader PCO shader.
549 * \return True if the pass made progress.
550 */
pco_dce(pco_shader * shader)551 bool pco_dce(pco_shader *shader)
552 {
553 bool progress = false;
554 BITSET_WORD *ssa_used;
555
556 pco_foreach_func_in_shader (func, shader) {
557 ssa_used = rzalloc_array_size(NULL,
558 sizeof(*ssa_used),
559 BITSET_WORDS(func->next_ssa));
560
561 /* Collect used SSA sources. */
562 pco_foreach_instr_in_func (instr, func) {
563 pco_foreach_instr_src_ssa (psrc, instr) {
564 BITSET_SET(ssa_used, psrc->val);
565 }
566 }
567
568 /* Remove instructions with unused SSA destinations (if they also have no
569 * side-effects).
570 */
571 pco_foreach_instr_in_func_safe (instr, func) {
572 bool has_ssa_dests = false;
573 bool dests_used = false;
574
575 pco_foreach_instr_dest_ssa (pdest, instr) {
576 has_ssa_dests = true;
577 dests_used |= BITSET_TEST(ssa_used, pdest->val);
578 }
579
580 if (has_ssa_dests && !dests_used && !instr_has_side_effects(instr)) {
581 pco_instr_delete(instr);
582 progress = true;
583 }
584 }
585
586 ralloc_free(ssa_used);
587 }
588
589 return progress;
590 }
591