xref: /aosp_15_r20/external/mesa3d/src/nouveau/compiler/nak/liveness.rs (revision 6104692788411f58d303aa86923a9ff6ecaded22)
1 // Copyright © 2022 Collabora, Ltd.
2 // SPDX-License-Identifier: MIT
3 
4 use crate::ir::*;
5 
6 use compiler::bitset::BitSet;
7 use std::cell::RefCell;
8 use std::cmp::{max, Ord, Ordering};
9 use std::collections::{hash_set, HashMap, HashSet};
10 
11 #[derive(Clone)]
12 pub struct LiveSet {
13     live: PerRegFile<u32>,
14     set: HashSet<SSAValue>,
15 }
16 
17 impl LiveSet {
new() -> LiveSet18     pub fn new() -> LiveSet {
19         LiveSet {
20             live: Default::default(),
21             set: HashSet::new(),
22         }
23     }
24 
contains(&self, ssa: &SSAValue) -> bool25     pub fn contains(&self, ssa: &SSAValue) -> bool {
26         self.set.contains(ssa)
27     }
28 
count(&self, file: RegFile) -> u3229     pub fn count(&self, file: RegFile) -> u32 {
30         self.live[file]
31     }
32 
insert(&mut self, ssa: SSAValue) -> bool33     pub fn insert(&mut self, ssa: SSAValue) -> bool {
34         if self.set.insert(ssa) {
35             self.live[ssa.file()] += 1;
36             true
37         } else {
38             false
39         }
40     }
41 
iter(&self) -> hash_set::Iter<SSAValue>42     pub fn iter(&self) -> hash_set::Iter<SSAValue> {
43         self.set.iter()
44     }
45 
remove(&mut self, ssa: &SSAValue) -> bool46     pub fn remove(&mut self, ssa: &SSAValue) -> bool {
47         if self.set.remove(ssa) {
48             self.live[ssa.file()] -= 1;
49             true
50         } else {
51             false
52         }
53     }
54 
insert_instr_top_down<L: BlockLiveness>( &mut self, ip: usize, instr: &Instr, bl: &L, ) -> PerRegFile<u32>55     pub fn insert_instr_top_down<L: BlockLiveness>(
56         &mut self,
57         ip: usize,
58         instr: &Instr,
59         bl: &L,
60     ) -> PerRegFile<u32> {
61         // Vector destinations go live before sources are killed.  Even
62         // in the case where the destination is immediately killed, it
63         // still may contribute to pressure temporarily.
64         for dst in instr.dsts() {
65             if let Dst::SSA(vec) = dst {
66                 if vec.comps() > 1 {
67                     for ssa in vec.iter() {
68                         self.insert(*ssa);
69                     }
70                 }
71             }
72         }
73 
74         let after_dsts_live = self.live;
75 
76         instr.for_each_ssa_use(|ssa| {
77             if !bl.is_live_after_ip(ssa, ip) {
78                 self.remove(ssa);
79             }
80         });
81 
82         // Scalar destinations are allocated last
83         for dst in instr.dsts() {
84             if let Dst::SSA(vec) = dst {
85                 if vec.comps() == 1 {
86                     self.insert(vec[0]);
87                 }
88             }
89         }
90 
91         let max_live = PerRegFile::new_with(|file| {
92             max(self.live[file], after_dsts_live[file])
93         });
94 
95         // It's possible (but unlikely) that a destination is immediately
96         // killed. Remove any which are killed by this instruction.
97         instr.for_each_ssa_def(|ssa| {
98             debug_assert!(self.contains(ssa));
99             if !bl.is_live_after_ip(ssa, ip) {
100                 self.remove(ssa);
101             }
102         });
103 
104         max_live
105     }
106 }
107 
108 impl FromIterator<SSAValue> for LiveSet {
from_iter<T: IntoIterator<Item = SSAValue>>(iter: T) -> Self109     fn from_iter<T: IntoIterator<Item = SSAValue>>(iter: T) -> Self {
110         let mut set = LiveSet::new();
111         for ssa in iter {
112             set.insert(ssa);
113         }
114         set
115     }
116 }
117 
118 impl Extend<SSAValue> for LiveSet {
extend<T: IntoIterator<Item = SSAValue>>(&mut self, iter: T)119     fn extend<T: IntoIterator<Item = SSAValue>>(&mut self, iter: T) {
120         for ssa in iter {
121             self.insert(ssa);
122         }
123     }
124 }
125 
126 pub trait BlockLiveness {
127     /// Returns true if @val is still live after @ip
is_live_after_ip(&self, val: &SSAValue, ip: usize) -> bool128     fn is_live_after_ip(&self, val: &SSAValue, ip: usize) -> bool;
129 
130     /// Returns true if @val is live-in to this block
is_live_in(&self, val: &SSAValue) -> bool131     fn is_live_in(&self, val: &SSAValue) -> bool;
132 
133     /// Returns true if @val is live-out of this block
is_live_out(&self, val: &SSAValue) -> bool134     fn is_live_out(&self, val: &SSAValue) -> bool;
135 
get_instr_pressure(&self, ip: usize, instr: &Instr) -> PerRegFile<u8>136     fn get_instr_pressure(&self, ip: usize, instr: &Instr) -> PerRegFile<u8> {
137         let mut live = PerRegFile::new_with(|_| 0_i8);
138 
139         // Vector destinations go live before sources are killed.
140         for dst in instr.dsts() {
141             if let Dst::SSA(vec) = dst {
142                 if vec.comps() > 1 {
143                     for ssa in vec.iter() {
144                         live[ssa.file()] += 1;
145                     }
146                 }
147             }
148         }
149 
150         // This is the first high point
151         let vec_dst_live = live;
152 
153         // Use a hash set because sources may occur more than once
154         let mut killed = HashSet::new();
155         instr.for_each_ssa_use(|ssa| {
156             if !self.is_live_after_ip(ssa, ip) {
157                 killed.insert(*ssa);
158             }
159         });
160         for ssa in killed.drain() {
161             live[ssa.file()] -= 1;
162         }
163 
164         // Scalar destinations are allocated last
165         for dst in instr.dsts() {
166             if let Dst::SSA(vec) = dst {
167                 if vec.comps() == 1 {
168                     live[vec[0].file()] += 1;
169                 }
170             }
171         }
172 
173         PerRegFile::new_with(|file| {
174             max(0, max(vec_dst_live[file], live[file]))
175                 .try_into()
176                 .unwrap()
177         })
178     }
179 }
180 
181 pub trait Liveness {
182     type PerBlock: BlockLiveness;
183 
block_live(&self, idx: usize) -> &Self::PerBlock184     fn block_live(&self, idx: usize) -> &Self::PerBlock;
185 
calc_max_live(&self, f: &Function) -> PerRegFile<u32>186     fn calc_max_live(&self, f: &Function) -> PerRegFile<u32> {
187         let mut max_live: PerRegFile<u32> = Default::default();
188         let mut block_live_out: Vec<LiveSet> = Vec::new();
189 
190         for (bb_idx, bb) in f.blocks.iter().enumerate() {
191             let bl = self.block_live(bb_idx);
192 
193             let mut live = LiveSet::new();
194 
195             // Predecessors are added block order so we can just grab the first
196             // one (if any) and it will be a block we've processed.
197             if let Some(pred_idx) = f.blocks.pred_indices(bb_idx).first() {
198                 let pred_out = &block_live_out[*pred_idx];
199                 for ssa in pred_out.iter() {
200                     if bl.is_live_in(ssa) {
201                         live.insert(*ssa);
202                     }
203                 }
204             }
205 
206             for (ip, instr) in bb.instrs.iter().enumerate() {
207                 let live_at_instr = live.insert_instr_top_down(ip, instr, bl);
208                 max_live = PerRegFile::new_with(|file| {
209                     max(max_live[file], live_at_instr[file])
210                 });
211 
212                 if let Op::RegOut(reg_out) = &instr.op {
213                     // This should be the last instruction.  Everything should
214                     // be dead once we've processed it.
215                     debug_assert!(live.count(RegFile::GPR) == 0);
216                     let num_gprs_out = reg_out.srcs.len().try_into().unwrap();
217                     max_live[RegFile::GPR] =
218                         max(max_live[RegFile::GPR], num_gprs_out);
219                 }
220             }
221 
222             assert!(block_live_out.len() == bb_idx);
223             block_live_out.push(live);
224         }
225 
226         max_live
227     }
228 }
229 
230 pub struct SimpleBlockLiveness {
231     defs: BitSet,
232     uses: BitSet,
233     last_use: HashMap<u32, usize>,
234     live_in: BitSet,
235     live_out: BitSet,
236 }
237 
238 impl SimpleBlockLiveness {
new() -> Self239     fn new() -> Self {
240         Self {
241             defs: BitSet::new(),
242             uses: BitSet::new(),
243             last_use: HashMap::new(),
244             live_in: BitSet::new(),
245             live_out: BitSet::new(),
246         }
247     }
248 
add_def(&mut self, ssa: SSAValue)249     fn add_def(&mut self, ssa: SSAValue) {
250         self.defs.insert(ssa.idx().try_into().unwrap());
251     }
252 
add_use(&mut self, ssa: SSAValue, ip: usize)253     fn add_use(&mut self, ssa: SSAValue, ip: usize) {
254         self.uses.insert(ssa.idx().try_into().unwrap());
255         self.last_use.insert(ssa.idx(), ip);
256     }
257 }
258 
259 impl BlockLiveness for SimpleBlockLiveness {
is_live_after_ip(&self, val: &SSAValue, ip: usize) -> bool260     fn is_live_after_ip(&self, val: &SSAValue, ip: usize) -> bool {
261         if self.live_out.get(val.idx().try_into().unwrap()) {
262             true
263         } else {
264             if let Some(last_use_ip) = self.last_use.get(&val.idx()) {
265                 *last_use_ip > ip
266             } else {
267                 false
268             }
269         }
270     }
271 
is_live_in(&self, val: &SSAValue) -> bool272     fn is_live_in(&self, val: &SSAValue) -> bool {
273         self.live_in.get(val.idx().try_into().unwrap())
274     }
275 
is_live_out(&self, val: &SSAValue) -> bool276     fn is_live_out(&self, val: &SSAValue) -> bool {
277         self.live_out.get(val.idx().try_into().unwrap())
278     }
279 }
280 
281 pub struct SimpleLiveness {
282     ssa_block_ip: HashMap<SSAValue, (usize, usize)>,
283     blocks: Vec<SimpleBlockLiveness>,
284 }
285 
286 impl SimpleLiveness {
for_function(func: &Function) -> SimpleLiveness287     pub fn for_function(func: &Function) -> SimpleLiveness {
288         let mut l = SimpleLiveness {
289             ssa_block_ip: HashMap::new(),
290             blocks: Vec::new(),
291         };
292         let mut live_in = Vec::new();
293 
294         for (bi, b) in func.blocks.iter().enumerate() {
295             let mut bl = SimpleBlockLiveness::new();
296 
297             for (ip, instr) in b.instrs.iter().enumerate() {
298                 instr.for_each_ssa_use(|ssa| {
299                     bl.add_use(*ssa, ip);
300                 });
301                 instr.for_each_ssa_def(|ssa| {
302                     l.ssa_block_ip.insert(*ssa, (bi, ip));
303                     bl.add_def(*ssa);
304                 });
305             }
306 
307             l.blocks.push(bl);
308             live_in.push(BitSet::new());
309         }
310         assert!(l.blocks.len() == func.blocks.len());
311         assert!(live_in.len() == func.blocks.len());
312 
313         let num_ssa = usize::try_from(func.ssa_alloc.max_idx() + 1).unwrap();
314         let mut tmp = BitSet::new();
315         tmp.reserve(num_ssa);
316 
317         let mut to_do = true;
318         while to_do {
319             to_do = false;
320             for (b_idx, bl) in l.blocks.iter_mut().enumerate().rev() {
321                 // Compute live-out
322                 for sb_idx in func.blocks.succ_indices(b_idx) {
323                     to_do |= bl.live_out.union_with(&live_in[*sb_idx]);
324                 }
325 
326                 tmp.clear();
327                 tmp.set_words(0..num_ssa, |w| {
328                     (bl.live_out.get_word(w) | bl.uses.get_word(w))
329                         & !bl.defs.get_word(w)
330                 });
331 
332                 to_do |= live_in[b_idx].union_with(&tmp);
333             }
334         }
335 
336         for (bl, b_live_in) in l.blocks.iter_mut().zip(live_in.into_iter()) {
337             bl.live_in = b_live_in;
338         }
339 
340         l
341     }
342 }
343 
344 impl SimpleLiveness {
def_block_ip(&self, ssa: &SSAValue) -> (usize, usize)345     pub fn def_block_ip(&self, ssa: &SSAValue) -> (usize, usize) {
346         *self.ssa_block_ip.get(ssa).unwrap()
347     }
348 
interferes(&self, a: &SSAValue, b: &SSAValue) -> bool349     pub fn interferes(&self, a: &SSAValue, b: &SSAValue) -> bool {
350         let (ab, ai) = self.def_block_ip(a);
351         let (bb, bi) = self.def_block_ip(b);
352 
353         match ab.cmp(&bb).then(ai.cmp(&bi)) {
354             Ordering::Equal => true,
355             Ordering::Less => self.block_live(bb).is_live_after_ip(a, bi),
356             Ordering::Greater => self.block_live(ab).is_live_after_ip(b, ai),
357         }
358     }
359 }
360 
361 impl Liveness for SimpleLiveness {
362     type PerBlock = SimpleBlockLiveness;
363 
block_live(&self, idx: usize) -> &SimpleBlockLiveness364     fn block_live(&self, idx: usize) -> &SimpleBlockLiveness {
365         &self.blocks[idx]
366     }
367 }
368 
369 struct SSAUseDef {
370     defined: bool,
371     uses: Vec<usize>,
372 }
373 
374 impl SSAUseDef {
add_def(&mut self)375     fn add_def(&mut self) {
376         self.defined = true;
377     }
378 
add_in_block_use(&mut self, use_ip: usize)379     fn add_in_block_use(&mut self, use_ip: usize) {
380         self.uses.push(use_ip);
381     }
382 
add_successor_use( &mut self, num_block_instrs: usize, use_ip: usize, ) -> bool383     fn add_successor_use(
384         &mut self,
385         num_block_instrs: usize,
386         use_ip: usize,
387     ) -> bool {
388         // IPs are relative to the start of their block
389         let use_ip = num_block_instrs + use_ip;
390 
391         if let Some(last_use_ip) = self.uses.last_mut() {
392             if *last_use_ip < num_block_instrs {
393                 // We've never seen a successor use before
394                 self.uses.push(use_ip);
395                 true
396             } else if *last_use_ip > use_ip {
397                 // Otherwise, we want the minimum next use
398                 *last_use_ip = use_ip;
399                 true
400             } else {
401                 false
402             }
403         } else {
404             self.uses.push(use_ip);
405             true
406         }
407     }
408 }
409 
410 pub struct NextUseBlockLiveness {
411     num_instrs: usize,
412     ssa_map: HashMap<SSAValue, SSAUseDef>,
413 }
414 
415 impl NextUseBlockLiveness {
new(num_instrs: usize) -> Self416     fn new(num_instrs: usize) -> Self {
417         Self {
418             num_instrs: num_instrs,
419             ssa_map: HashMap::new(),
420         }
421     }
422 
entry_mut(&mut self, ssa: SSAValue) -> &mut SSAUseDef423     fn entry_mut(&mut self, ssa: SSAValue) -> &mut SSAUseDef {
424         self.ssa_map.entry(ssa).or_insert_with(|| SSAUseDef {
425             defined: false,
426             uses: Vec::new(),
427         })
428     }
429 
add_def(&mut self, ssa: SSAValue)430     fn add_def(&mut self, ssa: SSAValue) {
431         self.entry_mut(ssa).add_def();
432     }
433 
add_use(&mut self, ssa: SSAValue, ip: usize)434     fn add_use(&mut self, ssa: SSAValue, ip: usize) {
435         self.entry_mut(ssa).add_in_block_use(ip);
436     }
437 
438     /// Returns an iterator over all the values which are live-in to this block
iter_live_in(&self) -> impl Iterator<Item = &SSAValue>439     pub fn iter_live_in(&self) -> impl Iterator<Item = &SSAValue> {
440         self.ssa_map.iter().filter_map(|(ssa, entry)| {
441             if entry.defined || entry.uses.is_empty() {
442                 None
443             } else {
444                 Some(ssa)
445             }
446         })
447     }
448 
449     /// Returns the IP of the first use of @val
450     ///
451     /// The returned IP is relative to the start of this block.  If the next use
452     /// is in some successor block, the returned IP is relative to the start of
453     /// this block.  If @val is not used in this block and is not live-out, None
454     /// is returned.
first_use(&self, val: &SSAValue) -> Option<usize>455     pub fn first_use(&self, val: &SSAValue) -> Option<usize> {
456         if let Some(entry) = self.ssa_map.get(val) {
457             entry.uses.first().cloned()
458         } else {
459             None
460         }
461     }
462 
463     /// Returns the IP of the first use of @val which is greater than or equal
464     /// to @ip
465     ///
466     /// All IPs are relative to the start of the block.  If the next use is some
467     /// successor block, the returned IP is relative to the start of this block.
next_use_after_or_at_ip( &self, val: &SSAValue, ip: usize, ) -> Option<usize>468     pub fn next_use_after_or_at_ip(
469         &self,
470         val: &SSAValue,
471         ip: usize,
472     ) -> Option<usize> {
473         if let Some(entry) = self.ssa_map.get(val) {
474             let i = entry.uses.partition_point(|u| *u < ip);
475             if i < entry.uses.len() {
476                 Some(entry.uses[i])
477             } else {
478                 None
479             }
480         } else {
481             None
482         }
483     }
484 }
485 
486 impl BlockLiveness for NextUseBlockLiveness {
is_live_after_ip(&self, val: &SSAValue, ip: usize) -> bool487     fn is_live_after_ip(&self, val: &SSAValue, ip: usize) -> bool {
488         if let Some(entry) = self.ssa_map.get(val) {
489             if let Some(last_use_ip) = entry.uses.last() {
490                 *last_use_ip > ip
491             } else {
492                 false
493             }
494         } else {
495             false
496         }
497     }
498 
is_live_in(&self, val: &SSAValue) -> bool499     fn is_live_in(&self, val: &SSAValue) -> bool {
500         if let Some(entry) = self.ssa_map.get(val) {
501             !entry.defined && !entry.uses.is_empty()
502         } else {
503             false
504         }
505     }
506 
is_live_out(&self, val: &SSAValue) -> bool507     fn is_live_out(&self, val: &SSAValue) -> bool {
508         if let Some(entry) = self.ssa_map.get(val) {
509             if let Some(last_use_ip) = entry.uses.last() {
510                 *last_use_ip >= self.num_instrs
511             } else {
512                 false
513             }
514         } else {
515             false
516         }
517     }
518 }
519 
520 /// An implementation of Liveness that tracks next-use IPs for each SSAValue
521 ///
522 /// Along with the usual liveness information, this tracks next-use IPs for each
523 /// SSAValue.  Cross-block next-use IPs computed are as per the global next-use
524 /// distance algorithm described in "Register Spilling and Live-Range Splitting
525 /// for SSA-Form Programs" by Braun and Hack.
526 pub struct NextUseLiveness {
527     blocks: Vec<NextUseBlockLiveness>,
528 }
529 
530 impl NextUseLiveness {
for_function( func: &Function, files: &RegFileSet, ) -> NextUseLiveness531     pub fn for_function(
532         func: &Function,
533         files: &RegFileSet,
534     ) -> NextUseLiveness {
535         let mut blocks = Vec::new();
536         for (bi, b) in func.blocks.iter().enumerate() {
537             let mut bl = NextUseBlockLiveness::new(b.instrs.len());
538 
539             for (ip, instr) in b.instrs.iter().enumerate() {
540                 instr.for_each_ssa_use(|ssa| {
541                     if files.contains(ssa.file()) {
542                         bl.add_use(*ssa, ip);
543                     }
544                 });
545 
546                 instr.for_each_ssa_def(|ssa| {
547                     if files.contains(ssa.file()) {
548                         bl.add_def(*ssa);
549                     }
550                 });
551             }
552 
553             debug_assert!(bi == blocks.len());
554             blocks.push(RefCell::new(bl));
555         }
556 
557         let mut to_do = true;
558         while to_do {
559             to_do = false;
560             for (b_idx, b) in func.blocks.iter().enumerate().rev() {
561                 let num_instrs = b.instrs.len();
562                 let mut bl = blocks[b_idx].borrow_mut();
563 
564                 // Compute live-out
565                 for sb_idx in func.blocks.succ_indices(b_idx) {
566                     if *sb_idx == b_idx {
567                         for entry in bl.ssa_map.values_mut() {
568                             if entry.defined {
569                                 continue;
570                             }
571 
572                             let Some(first_use_ip) = entry.uses.first() else {
573                                 continue;
574                             };
575 
576                             to_do |= entry
577                                 .add_successor_use(num_instrs, *first_use_ip);
578                         }
579                     } else {
580                         let sbl = blocks[*sb_idx].borrow();
581                         for (ssa, entry) in sbl.ssa_map.iter() {
582                             if entry.defined {
583                                 continue;
584                             }
585 
586                             let Some(first_use_ip) = entry.uses.first() else {
587                                 continue;
588                             };
589 
590                             to_do |= bl
591                                 .entry_mut(*ssa)
592                                 .add_successor_use(num_instrs, *first_use_ip);
593                         }
594                     }
595                 }
596             }
597         }
598 
599         NextUseLiveness {
600             blocks: blocks.into_iter().map(|bl| bl.into_inner()).collect(),
601         }
602     }
603 }
604 
605 impl Liveness for NextUseLiveness {
606     type PerBlock = NextUseBlockLiveness;
607 
block_live(&self, idx: usize) -> &NextUseBlockLiveness608     fn block_live(&self, idx: usize) -> &NextUseBlockLiveness {
609         &self.blocks[idx]
610     }
611 }
612