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