1 // Copyright © 2022 Collabora, Ltd. 2 // SPDX-License-Identifier: MIT 3 4 use crate::bitset::BitSet; 5 use crate::ir::*; 6 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 pub trait BlockLiveness { 119 /// Returns true if @val is still live after @ip is_live_after_ip(&self, val: &SSAValue, ip: usize) -> bool120 fn is_live_after_ip(&self, val: &SSAValue, ip: usize) -> bool; 121 122 /// Returns true if @val is live-in to this block is_live_in(&self, val: &SSAValue) -> bool123 fn is_live_in(&self, val: &SSAValue) -> bool; 124 125 /// Returns true if @val is live-out of this block is_live_out(&self, val: &SSAValue) -> bool126 fn is_live_out(&self, val: &SSAValue) -> bool; 127 get_instr_pressure(&self, ip: usize, instr: &Instr) -> PerRegFile<u8>128 fn get_instr_pressure(&self, ip: usize, instr: &Instr) -> PerRegFile<u8> { 129 let mut live = PerRegFile::new_with(|_| 0_i8); 130 131 // Vector destinations go live before sources are killed. 132 for dst in instr.dsts() { 133 if let Dst::SSA(vec) = dst { 134 if vec.comps() > 1 { 135 for ssa in vec.iter() { 136 live[ssa.file()] += 1; 137 } 138 } 139 } 140 } 141 142 // This is the first high point 143 let vec_dst_live = live; 144 145 // Use a hash set because sources may occur more than once 146 let mut killed = HashSet::new(); 147 instr.for_each_ssa_use(|ssa| { 148 if !self.is_live_after_ip(ssa, ip) { 149 killed.insert(*ssa); 150 } 151 }); 152 for ssa in killed.drain() { 153 live[ssa.file()] -= 1; 154 } 155 156 // Scalar destinations are allocated last 157 for dst in instr.dsts() { 158 if let Dst::SSA(vec) = dst { 159 if vec.comps() == 1 { 160 live[vec[0].file()] += 1; 161 } 162 } 163 } 164 165 PerRegFile::new_with(|file| { 166 max(0, max(vec_dst_live[file], live[file])) 167 .try_into() 168 .unwrap() 169 }) 170 } 171 } 172 173 pub trait Liveness { 174 type PerBlock: BlockLiveness; 175 block_live(&self, idx: usize) -> &Self::PerBlock176 fn block_live(&self, idx: usize) -> &Self::PerBlock; 177 calc_max_live(&self, f: &Function) -> PerRegFile<u32>178 fn calc_max_live(&self, f: &Function) -> PerRegFile<u32> { 179 let mut max_live: PerRegFile<u32> = Default::default(); 180 let mut block_live_out: Vec<LiveSet> = Vec::new(); 181 182 for (bb_idx, bb) in f.blocks.iter().enumerate() { 183 let bl = self.block_live(bb_idx); 184 185 let mut live = LiveSet::new(); 186 187 // Predecessors are added block order so we can just grab the first 188 // one (if any) and it will be a block we've processed. 189 if let Some(pred_idx) = f.blocks.pred_indices(bb_idx).first() { 190 let pred_out = &block_live_out[*pred_idx]; 191 for ssa in pred_out.iter() { 192 if bl.is_live_in(ssa) { 193 live.insert(*ssa); 194 } 195 } 196 } 197 198 for (ip, instr) in bb.instrs.iter().enumerate() { 199 let live_at_instr = live.insert_instr_top_down(ip, instr, bl); 200 max_live = PerRegFile::new_with(|file| { 201 max(max_live[file], live_at_instr[file]) 202 }); 203 204 if let Op::FSOut(fs_out) = &instr.op { 205 // This should be the last instruction. Everything should 206 // be dead once we've processed it. 207 debug_assert!(live.count(RegFile::GPR) == 0); 208 let num_gprs_out = fs_out.srcs.len().try_into().unwrap(); 209 max_live[RegFile::GPR] = 210 max(max_live[RegFile::GPR], num_gprs_out); 211 } 212 } 213 214 assert!(block_live_out.len() == bb_idx); 215 block_live_out.push(live); 216 } 217 218 max_live 219 } 220 } 221 222 pub struct SimpleBlockLiveness { 223 defs: BitSet, 224 uses: BitSet, 225 last_use: HashMap<u32, usize>, 226 live_in: BitSet, 227 live_out: BitSet, 228 } 229 230 impl SimpleBlockLiveness { new() -> Self231 fn new() -> Self { 232 Self { 233 defs: BitSet::new(), 234 uses: BitSet::new(), 235 last_use: HashMap::new(), 236 live_in: BitSet::new(), 237 live_out: BitSet::new(), 238 } 239 } 240 add_def(&mut self, ssa: SSAValue)241 fn add_def(&mut self, ssa: SSAValue) { 242 self.defs.insert(ssa.idx().try_into().unwrap()); 243 } 244 add_use(&mut self, ssa: SSAValue, ip: usize)245 fn add_use(&mut self, ssa: SSAValue, ip: usize) { 246 self.uses.insert(ssa.idx().try_into().unwrap()); 247 self.last_use.insert(ssa.idx(), ip); 248 } 249 } 250 251 impl BlockLiveness for SimpleBlockLiveness { is_live_after_ip(&self, val: &SSAValue, ip: usize) -> bool252 fn is_live_after_ip(&self, val: &SSAValue, ip: usize) -> bool { 253 if self.live_out.get(val.idx().try_into().unwrap()) { 254 true 255 } else { 256 if let Some(last_use_ip) = self.last_use.get(&val.idx()) { 257 *last_use_ip > ip 258 } else { 259 false 260 } 261 } 262 } 263 is_live_in(&self, val: &SSAValue) -> bool264 fn is_live_in(&self, val: &SSAValue) -> bool { 265 self.live_in.get(val.idx().try_into().unwrap()) 266 } 267 is_live_out(&self, val: &SSAValue) -> bool268 fn is_live_out(&self, val: &SSAValue) -> bool { 269 self.live_out.get(val.idx().try_into().unwrap()) 270 } 271 } 272 273 pub struct SimpleLiveness { 274 ssa_block_ip: HashMap<SSAValue, (usize, usize)>, 275 blocks: Vec<SimpleBlockLiveness>, 276 } 277 278 impl SimpleLiveness { for_function(func: &Function) -> SimpleLiveness279 pub fn for_function(func: &Function) -> SimpleLiveness { 280 let mut l = SimpleLiveness { 281 ssa_block_ip: HashMap::new(), 282 blocks: Vec::new(), 283 }; 284 let mut live_in = Vec::new(); 285 286 for (bi, b) in func.blocks.iter().enumerate() { 287 let mut bl = SimpleBlockLiveness::new(); 288 289 for (ip, instr) in b.instrs.iter().enumerate() { 290 instr.for_each_ssa_use(|ssa| { 291 bl.add_use(*ssa, ip); 292 }); 293 instr.for_each_ssa_def(|ssa| { 294 l.ssa_block_ip.insert(*ssa, (bi, ip)); 295 bl.add_def(*ssa); 296 }); 297 } 298 299 l.blocks.push(bl); 300 live_in.push(BitSet::new()); 301 } 302 assert!(l.blocks.len() == func.blocks.len()); 303 assert!(live_in.len() == func.blocks.len()); 304 305 let num_ssa = usize::try_from(func.ssa_alloc.max_idx() + 1).unwrap(); 306 let mut tmp = BitSet::new(); 307 tmp.reserve(num_ssa); 308 309 let mut to_do = true; 310 while to_do { 311 to_do = false; 312 for (b_idx, bl) in l.blocks.iter_mut().enumerate().rev() { 313 // Compute live-out 314 for sb_idx in func.blocks.succ_indices(b_idx) { 315 to_do |= bl.live_out.union_with(&live_in[*sb_idx]); 316 } 317 318 tmp.clear(); 319 tmp.set_words(0..num_ssa, |w| { 320 (bl.live_out.get_word(w) | bl.uses.get_word(w)) 321 & !bl.defs.get_word(w) 322 }); 323 324 to_do |= live_in[b_idx].union_with(&tmp); 325 } 326 } 327 328 for (bl, b_live_in) in l.blocks.iter_mut().zip(live_in.into_iter()) { 329 bl.live_in = b_live_in; 330 } 331 332 l 333 } 334 } 335 336 impl SimpleLiveness { def_block_ip(&self, ssa: &SSAValue) -> (usize, usize)337 pub fn def_block_ip(&self, ssa: &SSAValue) -> (usize, usize) { 338 *self.ssa_block_ip.get(ssa).unwrap() 339 } 340 interferes(&self, a: &SSAValue, b: &SSAValue) -> bool341 pub fn interferes(&self, a: &SSAValue, b: &SSAValue) -> bool { 342 let (ab, ai) = self.def_block_ip(a); 343 let (bb, bi) = self.def_block_ip(b); 344 345 match ab.cmp(&bb).then(ai.cmp(&bi)) { 346 Ordering::Equal => true, 347 Ordering::Less => self.block_live(bb).is_live_after_ip(a, bi), 348 Ordering::Greater => self.block_live(ab).is_live_after_ip(b, ai), 349 } 350 } 351 } 352 353 impl Liveness for SimpleLiveness { 354 type PerBlock = SimpleBlockLiveness; 355 block_live(&self, idx: usize) -> &SimpleBlockLiveness356 fn block_live(&self, idx: usize) -> &SimpleBlockLiveness { 357 &self.blocks[idx] 358 } 359 } 360 361 struct SSAUseDef { 362 defined: bool, 363 uses: Vec<usize>, 364 } 365 366 impl SSAUseDef { add_def(&mut self)367 fn add_def(&mut self) { 368 self.defined = true; 369 } 370 add_in_block_use(&mut self, use_ip: usize)371 fn add_in_block_use(&mut self, use_ip: usize) { 372 self.uses.push(use_ip); 373 } 374 add_successor_use( &mut self, num_block_instrs: usize, use_ip: usize, ) -> bool375 fn add_successor_use( 376 &mut self, 377 num_block_instrs: usize, 378 use_ip: usize, 379 ) -> bool { 380 // IPs are relative to the start of their block 381 let use_ip = num_block_instrs + use_ip; 382 383 if let Some(last_use_ip) = self.uses.last_mut() { 384 if *last_use_ip < num_block_instrs { 385 // We've never seen a successor use before 386 self.uses.push(use_ip); 387 true 388 } else if *last_use_ip > use_ip { 389 // Otherwise, we want the minimum next use 390 *last_use_ip = use_ip; 391 true 392 } else { 393 false 394 } 395 } else { 396 self.uses.push(use_ip); 397 true 398 } 399 } 400 } 401 402 pub struct NextUseBlockLiveness { 403 num_instrs: usize, 404 ssa_map: HashMap<SSAValue, SSAUseDef>, 405 } 406 407 impl NextUseBlockLiveness { new(num_instrs: usize) -> Self408 fn new(num_instrs: usize) -> Self { 409 Self { 410 num_instrs: num_instrs, 411 ssa_map: HashMap::new(), 412 } 413 } 414 entry_mut(&mut self, ssa: SSAValue) -> &mut SSAUseDef415 fn entry_mut(&mut self, ssa: SSAValue) -> &mut SSAUseDef { 416 self.ssa_map.entry(ssa).or_insert_with(|| SSAUseDef { 417 defined: false, 418 uses: Vec::new(), 419 }) 420 } 421 add_def(&mut self, ssa: SSAValue)422 fn add_def(&mut self, ssa: SSAValue) { 423 self.entry_mut(ssa).add_def(); 424 } 425 add_use(&mut self, ssa: SSAValue, ip: usize)426 fn add_use(&mut self, ssa: SSAValue, ip: usize) { 427 self.entry_mut(ssa).add_in_block_use(ip); 428 } 429 430 /// Returns an iterator over all the values which are live-in to this block iter_live_in(&self) -> impl Iterator<Item = &SSAValue>431 pub fn iter_live_in(&self) -> impl Iterator<Item = &SSAValue> { 432 self.ssa_map.iter().filter_map(|(ssa, entry)| { 433 if entry.defined || entry.uses.is_empty() { 434 None 435 } else { 436 Some(ssa) 437 } 438 }) 439 } 440 441 /// Returns the IP of the first use of @val 442 /// 443 /// The returned IP is relative to the start of this block. If the next use 444 /// is in some successor block, the returned IP is relative to the start of 445 /// this block. If @val is not used in this block and is not live-out, None 446 /// is returned. first_use(&self, val: &SSAValue) -> Option<usize>447 pub fn first_use(&self, val: &SSAValue) -> Option<usize> { 448 if let Some(entry) = self.ssa_map.get(val) { 449 entry.uses.first().cloned() 450 } else { 451 None 452 } 453 } 454 455 /// Returns the IP of the first use of @val which is greater than or equal 456 /// to @ip 457 /// 458 /// All IPs are relative to the start of the block. If the next use is some 459 /// 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>460 pub fn next_use_after_or_at_ip( 461 &self, 462 val: &SSAValue, 463 ip: usize, 464 ) -> Option<usize> { 465 if let Some(entry) = self.ssa_map.get(val) { 466 let i = entry.uses.partition_point(|u| *u < ip); 467 if i < entry.uses.len() { 468 Some(entry.uses[i]) 469 } else { 470 None 471 } 472 } else { 473 None 474 } 475 } 476 } 477 478 impl BlockLiveness for NextUseBlockLiveness { is_live_after_ip(&self, val: &SSAValue, ip: usize) -> bool479 fn is_live_after_ip(&self, val: &SSAValue, ip: usize) -> bool { 480 if let Some(entry) = self.ssa_map.get(val) { 481 if let Some(last_use_ip) = entry.uses.last() { 482 *last_use_ip > ip 483 } else { 484 false 485 } 486 } else { 487 false 488 } 489 } 490 is_live_in(&self, val: &SSAValue) -> bool491 fn is_live_in(&self, val: &SSAValue) -> bool { 492 if let Some(entry) = self.ssa_map.get(val) { 493 !entry.defined && !entry.uses.is_empty() 494 } else { 495 false 496 } 497 } 498 is_live_out(&self, val: &SSAValue) -> bool499 fn is_live_out(&self, val: &SSAValue) -> bool { 500 if let Some(entry) = self.ssa_map.get(val) { 501 if let Some(last_use_ip) = entry.uses.last() { 502 *last_use_ip >= self.num_instrs 503 } else { 504 false 505 } 506 } else { 507 false 508 } 509 } 510 } 511 512 /// An implementation of Liveness that tracks next-use IPs for each SSAValue 513 /// 514 /// Along with the usual liveness information, this tracks next-use IPs for each 515 /// SSAValue. Cross-block next-use IPs computed are as per the global next-use 516 /// distance algorithm described in "Register Spilling and Live-Range Splitting 517 /// for SSA-Form Programs" by Braun and Hack. 518 pub struct NextUseLiveness { 519 blocks: Vec<NextUseBlockLiveness>, 520 } 521 522 impl NextUseLiveness { for_function( func: &Function, files: &RegFileSet, ) -> NextUseLiveness523 pub fn for_function( 524 func: &Function, 525 files: &RegFileSet, 526 ) -> NextUseLiveness { 527 let mut blocks = Vec::new(); 528 for (bi, b) in func.blocks.iter().enumerate() { 529 let mut bl = NextUseBlockLiveness::new(b.instrs.len()); 530 531 for (ip, instr) in b.instrs.iter().enumerate() { 532 instr.for_each_ssa_use(|ssa| { 533 if files.contains(ssa.file()) { 534 bl.add_use(*ssa, ip); 535 } 536 }); 537 538 instr.for_each_ssa_def(|ssa| { 539 if files.contains(ssa.file()) { 540 bl.add_def(*ssa); 541 } 542 }); 543 } 544 545 debug_assert!(bi == blocks.len()); 546 blocks.push(RefCell::new(bl)); 547 } 548 549 let mut to_do = true; 550 while to_do { 551 to_do = false; 552 for (b_idx, b) in func.blocks.iter().enumerate().rev() { 553 let num_instrs = b.instrs.len(); 554 let mut bl = blocks[b_idx].borrow_mut(); 555 556 // Compute live-out 557 for sb_idx in func.blocks.succ_indices(b_idx) { 558 if *sb_idx == b_idx { 559 for entry in bl.ssa_map.values_mut() { 560 if entry.defined { 561 continue; 562 } 563 564 let Some(first_use_ip) = entry.uses.first() else { 565 continue; 566 }; 567 568 to_do |= entry 569 .add_successor_use(num_instrs, *first_use_ip); 570 } 571 } else { 572 let sbl = blocks[*sb_idx].borrow(); 573 for (ssa, entry) in sbl.ssa_map.iter() { 574 if entry.defined { 575 continue; 576 } 577 578 let Some(first_use_ip) = entry.uses.first() else { 579 continue; 580 }; 581 582 to_do |= bl 583 .entry_mut(*ssa) 584 .add_successor_use(num_instrs, *first_use_ip); 585 } 586 } 587 } 588 } 589 } 590 591 NextUseLiveness { 592 blocks: blocks.into_iter().map(|bl| bl.into_inner()).collect(), 593 } 594 } 595 } 596 597 impl Liveness for NextUseLiveness { 598 type PerBlock = NextUseBlockLiveness; 599 block_live(&self, idx: usize) -> &NextUseBlockLiveness600 fn block_live(&self, idx: usize) -> &NextUseBlockLiveness { 601 &self.blocks[idx] 602 } 603 } 604