1use crate::mir::{BlockId, Function, InstId, Terminator, Value, ValueId};
14use smallvec::SmallVec;
15use solar_data_structures::{bit_set::GrowableBitSet, map::FxHashMap};
16use std::collections::VecDeque;
17
18pub type LiveSet = GrowableBitSet<ValueId>;
20
21#[derive(Clone, Debug)]
23pub struct LivenessInfo {
24 pub live_before: LiveSet,
26 pub live_after: LiveSet,
28}
29
30#[derive(Clone, Debug)]
32struct BlockLiveness {
33 live_in: LiveSet,
35 live_out: LiveSet,
37}
38
39#[derive(Debug)]
41pub struct Liveness {
42 block_liveness: Vec<BlockLiveness>,
44 last_use_in_block: FxHashMap<(ValueId, BlockId), Option<usize>>,
48 pub(crate) inst_to_value: FxHashMap<InstId, ValueId>,
51 #[allow(dead_code)]
53 num_values: usize,
54}
55
56impl Liveness {
57 #[must_use]
59 pub fn compute(func: &Function) -> Self {
60 let num_values = func.values.len();
61 let num_blocks = func.blocks.len();
62
63 let mut inst_to_value: FxHashMap<InstId, ValueId> = FxHashMap::default();
66 for (val_id, val) in func.values.iter_enumerated() {
67 if let Value::Inst(inst_id) = val {
68 inst_to_value.insert(*inst_id, val_id);
69 }
70 }
71
72 let mut block_liveness: Vec<BlockLiveness> = (0..num_blocks)
74 .map(|_| BlockLiveness {
75 live_in: LiveSet::with_capacity(num_values),
76 live_out: LiveSet::with_capacity(num_values),
77 })
78 .collect();
79
80 let mut block_defs: Vec<LiveSet> =
82 (0..num_blocks).map(|_| LiveSet::with_capacity(num_values)).collect();
83 let mut block_uses: Vec<LiveSet> =
84 (0..num_blocks).map(|_| LiveSet::with_capacity(num_values)).collect();
85
86 let mut operand_buf = SmallVec::<[ValueId; 8]>::new();
87
88 for (block_id, block) in func.blocks.iter_enumerated() {
89 let bidx = block_id.index();
90 for &inst_id in &block.instructions {
92 let inst = func.instruction(inst_id);
93
94 operand_buf.clear();
96 inst.kind.collect_operands(&mut operand_buf);
97 for &operand in &operand_buf {
98 if !block_defs[bidx].contains(operand) {
99 block_uses[bidx].insert(operand);
100 }
101 }
102
103 if let Some(&val_id) = inst_to_value.get(&inst_id) {
105 block_defs[bidx].insert(val_id);
106 }
107 }
108
109 if let Some(term) = &block.terminator {
111 operand_buf.clear();
112 collect_terminator_uses(term, &mut operand_buf);
113 for &operand in &operand_buf {
114 if !block_defs[bidx].contains(operand) {
115 block_uses[bidx].insert(operand);
116 }
117 }
118 }
119 }
120
121 let mut worklist: VecDeque<BlockId> = func.blocks.indices().collect();
126
127 while let Some(block_id) = worklist.pop_front() {
128 let bidx = block_id.index();
129 let block = &func.blocks[block_id];
130
131 let mut new_live_out = LiveSet::with_capacity(num_values);
132 let successors =
133 block.terminator.as_ref().map(Terminator::successors).unwrap_or_default();
134 for succ in successors {
135 new_live_out.union(&block_liveness[succ.index()].live_in);
136 }
137
138 let mut new_live_in = block_uses[bidx].clone();
140 for val in new_live_out.iter() {
141 if !block_defs[bidx].contains(val) {
142 new_live_in.insert(val);
143 }
144 }
145
146 if new_live_out != block_liveness[bidx].live_out
147 || new_live_in != block_liveness[bidx].live_in
148 {
149 block_liveness[bidx].live_out = new_live_out;
150 block_liveness[bidx].live_in = new_live_in;
151
152 for &pred in &block.predecessors {
154 worklist.push_back(pred);
155 }
156 }
157 }
158
159 let mut last_use_in_block: FxHashMap<(ValueId, BlockId), Option<usize>> =
162 FxHashMap::default();
163 for (block_id, block) in func.blocks.iter_enumerated() {
164 if let Some(term) = &block.terminator {
166 operand_buf.clear();
167 collect_terminator_uses(term, &mut operand_buf);
168 for &operand in &operand_buf {
169 last_use_in_block.entry((operand, block_id)).or_insert(None);
171 }
172 }
173
174 for (inst_idx, &inst_id) in block.instructions.iter().enumerate().rev() {
177 let inst = func.instruction(inst_id);
178 operand_buf.clear();
179 inst.kind.collect_operands(&mut operand_buf);
180 for &operand in &operand_buf {
181 last_use_in_block.entry((operand, block_id)).or_insert(Some(inst_idx));
182 }
183 }
184 }
185
186 Self { block_liveness, last_use_in_block, inst_to_value, num_values }
187 }
188
189 #[must_use]
191 pub fn live_in(&self, block: BlockId) -> &LiveSet {
192 &self.block_liveness[block.index()].live_in
193 }
194
195 #[must_use]
197 pub fn live_out(&self, block: BlockId) -> &LiveSet {
198 &self.block_liveness[block.index()].live_out
199 }
200
201 #[must_use]
204 pub fn live_at_inst(
205 &self,
206 func: &Function,
207 block_id: BlockId,
208 inst_idx: usize,
209 ) -> LivenessInfo {
210 let bidx = block_id.index();
211 let block = &func.blocks[block_id];
212
213 let mut live = self.block_liveness[bidx].live_out.clone();
215
216 if let Some(term) = &block.terminator {
218 let mut term_uses = SmallVec::<[ValueId; 8]>::new();
219 collect_terminator_uses(term, &mut term_uses);
220 for operand in term_uses {
221 live.insert(operand);
222 }
223 }
224
225 let mut operand_buf = SmallVec::<[ValueId; 8]>::new();
227 let mut live_after = None;
228
229 for (idx, &inst_id) in block.instructions.iter().enumerate().rev() {
230 if idx == inst_idx {
231 live_after = Some(live.clone());
232 }
233
234 if let Some(&val_id) = self.inst_to_value.get(&inst_id) {
236 live.remove(val_id);
237 }
238
239 operand_buf.clear();
241 func.instruction(inst_id).kind.collect_operands(&mut operand_buf);
242 for &operand in &operand_buf {
243 live.insert(operand);
244 }
245
246 if idx == inst_idx {
247 return LivenessInfo { live_before: live, live_after: live_after.unwrap() };
248 }
249 }
250
251 LivenessInfo { live_before: live.clone(), live_after: live }
253 }
254
255 #[must_use]
260 pub fn last_use_in_block(&self, val: ValueId, block: BlockId) -> Option<Option<usize>> {
261 self.last_use_in_block.get(&(val, block)).copied()
262 }
263
264 #[must_use]
270 pub fn is_dead_after(&self, val: ValueId, block: BlockId, inst_idx: usize) -> bool {
271 if self.block_liveness[block.index()].live_out.contains(val) {
273 return false;
274 }
275
276 match self.last_use_in_block.get(&(val, block)) {
278 Some(&Some(last_idx)) => last_idx == inst_idx,
279 Some(&None) => false,
281 None => true,
284 }
285 }
286}
287
288fn collect_terminator_uses(term: &Terminator, out: &mut SmallVec<[ValueId; 8]>) {
290 match term {
291 Terminator::Jump(_) => {}
292 Terminator::Branch { condition, .. } => {
293 out.push(*condition);
294 }
295 Terminator::Switch { value, cases, .. } => {
296 out.push(*value);
297 for (case_val, _) in cases {
298 out.push(*case_val);
299 }
300 }
301 Terminator::Return { values } => {
302 out.extend(values.iter().copied());
303 }
304 Terminator::Revert { offset, size } | Terminator::ReturnData { offset, size } => {
305 out.push(*offset);
306 out.push(*size);
307 }
308 Terminator::Stop | Terminator::Invalid => {}
309 Terminator::SelfDestruct { recipient } => {
310 out.push(*recipient);
311 }
312 }
313}
314
315#[cfg(test)]
316mod tests {
317 use super::*;
318
319 #[test]
320 fn test_liveset_basic() {
321 let mut set = LiveSet::with_capacity(100);
322 let v0 = ValueId::from_usize(0);
323 let v42 = ValueId::from_usize(42);
324 let v99 = ValueId::from_usize(99);
325
326 assert!(!set.contains(v0));
327 assert!(!set.contains(v42));
328
329 assert!(set.insert(v0));
330 assert!(set.contains(v0));
331 assert!(!set.insert(v0)); assert!(set.insert(v42));
334 assert!(set.contains(v42));
335
336 assert!(set.insert(v99));
337 assert_eq!(set.count(), 3);
338
339 set.remove(v42);
340 assert!(!set.contains(v42));
341 assert_eq!(set.count(), 2);
342 }
343
344 #[test]
345 fn test_liveset_union() {
346 let mut set1 = LiveSet::with_capacity(64);
347 let mut set2 = LiveSet::with_capacity(64);
348
349 set1.insert(ValueId::from_usize(1));
350 set1.insert(ValueId::from_usize(3));
351 set2.insert(ValueId::from_usize(2));
352 set2.insert(ValueId::from_usize(3));
353
354 assert!(set1.union(&set2));
355 assert!(set1.contains(ValueId::from_usize(1)));
356 assert!(set1.contains(ValueId::from_usize(2)));
357 assert!(set1.contains(ValueId::from_usize(3)));
358 assert_eq!(set1.count(), 3);
359
360 assert!(!set1.union(&set2));
362 }
363
364 #[test]
365 fn test_liveset_boundary() {
366 let mut set = LiveSet::with_capacity(200);
367 for i in [0, 1, 62, 63, 64, 65, 126, 127, 128, 129, 199] {
368 assert!(set.insert(ValueId::from_usize(i)));
369 assert!(set.contains(ValueId::from_usize(i)));
370 }
371 assert_eq!(set.count(), 11);
372 }
373
374 #[test]
375 fn test_liveset_clear() {
376 let mut set = LiveSet::with_capacity(128);
377 set.insert(ValueId::from_usize(0));
378 set.insert(ValueId::from_usize(63));
379 set.insert(ValueId::from_usize(64));
380 set.insert(ValueId::from_usize(127));
381 assert_eq!(set.count(), 4);
382 set.clear();
383 assert_eq!(set.count(), 0);
384 assert!(!set.contains(ValueId::from_usize(0)));
385 }
386
387 #[test]
388 fn test_liveset_iter() {
389 let mut set = LiveSet::with_capacity(200);
390 let indices = [0, 5, 63, 64, 127, 128, 199];
391 for &i in &indices {
392 set.insert(ValueId::from_usize(i));
393 }
394 let collected: Vec<usize> = set.iter().map(|v| v.index()).collect();
395 assert_eq!(collected, indices);
396 }
397
398 use crate::mir::{Function, FunctionBuilder, MirType};
401 use solar_interface::Ident;
402
403 fn make_func() -> Function {
404 Function::new(Ident::DUMMY)
405 }
406
407 #[test]
408 fn test_linear_code() {
409 let mut func = make_func();
411 let mut b = FunctionBuilder::new(&mut func);
412 let x = b.add_param(MirType::uint256());
413 let one = b.imm_u64(1);
414 let sum = b.add(x, one);
415 b.ret([sum]);
416
417 let liveness = Liveness::compute(&func);
418 let entry = func.entry_block;
419
420 assert!(liveness.live_in(entry).contains(x));
422 assert!(liveness.live_in(entry).contains(one));
423 assert!(!liveness.live_out(entry).contains(sum));
425 assert_eq!(liveness.live_out(entry).count(), 0);
427 }
428
429 #[test]
430 fn test_diamond_cfg() {
431 let mut func = make_func();
436 let mut b = FunctionBuilder::new(&mut func);
437 let x = b.add_param(MirType::uint256());
438 let cond = b.add_param(MirType::Bool);
439
440 let then_bb = b.create_block();
441 let else_bb = b.create_block();
442 let merge = b.create_block();
443
444 b.branch(cond, then_bb, else_bb);
445
446 b.switch_to_block(then_bb);
447 let c1 = b.imm_u64(1);
448 let v_then = b.add(x, c1);
449 b.jump(merge);
450
451 b.switch_to_block(else_bb);
452 let c2 = b.imm_u64(1);
453 let v_else = b.sub(x, c2);
454 b.jump(merge);
455
456 b.switch_to_block(merge);
458 b.ret([v_then]);
459
460 let liveness = Liveness::compute(&func);
461
462 assert!(liveness.live_in(then_bb).contains(x));
464 assert!(liveness.live_in(else_bb).contains(x));
465 assert!(liveness.live_out(func.entry_block).contains(x));
467 assert!(liveness.live_out(then_bb).contains(v_then));
469 assert!(!liveness.live_out(else_bb).contains(v_else));
471 }
472
473 #[test]
474 fn test_simple_loop() {
475 let mut func = make_func();
482 let mut b = FunctionBuilder::new(&mut func);
483 let i = b.add_param(MirType::uint256());
484 let limit = b.imm_u64(10);
485
486 let header = b.create_block();
487 let body = b.create_block();
488 let exit = b.create_block();
489
490 b.jump(header);
491
492 b.switch_to_block(header);
493 let cond = b.lt(i, limit);
494 b.branch(cond, body, exit);
495
496 b.switch_to_block(body);
497 let step = b.imm_u64(1);
498 let _i_next = b.add(i, step);
499 b.jump(header);
500
501 b.switch_to_block(exit);
502 b.ret([i]);
503
504 let liveness = Liveness::compute(&func);
505
506 assert!(liveness.live_in(header).contains(i), "i live-in to header");
508 assert!(liveness.live_in(body).contains(i), "i live-in to body");
509 assert!(liveness.live_in(exit).contains(i), "i live-in to exit");
510 assert!(liveness.live_out(header).contains(i), "i live-out of header");
511 assert!(liveness.live_out(body).contains(i), "i live-out of body");
512 }
513
514 #[test]
515 fn test_dead_instruction_result() {
516 let mut func = make_func();
520 let mut b = FunctionBuilder::new(&mut func);
521 let x = b.add_param(MirType::uint256());
522 let y = b.add_param(MirType::uint256());
523 let dead = b.add(x, y); b.ret([x]);
525
526 let liveness = Liveness::compute(&func);
527 let entry = func.entry_block;
528
529 assert!(liveness.live_in(entry).contains(x));
530 assert!(!liveness.live_out(entry).contains(dead));
532 assert!(liveness.is_dead_after(dead, entry, 0), "dead inst result should be dead");
534 }
535
536 #[test]
537 fn test_unused_param() {
538 let mut func = make_func();
539 let mut b = FunctionBuilder::new(&mut func);
540 let _x = b.add_param(MirType::uint256()); let y = b.add_param(MirType::uint256());
542 b.ret([y]);
543
544 let liveness = Liveness::compute(&func);
545 let entry = func.entry_block;
546
547 assert!(!liveness.live_in(entry).contains(_x), "unused param not live");
548 assert!(liveness.live_in(entry).contains(y), "used param is live");
549 }
550
551 #[test]
552 fn test_value_used_in_two_successors() {
553 let mut func = make_func();
557 let mut b = FunctionBuilder::new(&mut func);
558 let x = b.add_param(MirType::uint256());
559 let cond = b.add_param(MirType::Bool);
560
561 let left = b.create_block();
562 let right = b.create_block();
563
564 b.branch(cond, left, right);
565
566 b.switch_to_block(left);
567 b.ret([x]);
568
569 b.switch_to_block(right);
570 b.ret([x]);
571
572 let liveness = Liveness::compute(&func);
573
574 assert!(liveness.live_in(left).contains(x));
575 assert!(liveness.live_in(right).contains(x));
576 assert!(liveness.live_out(func.entry_block).contains(x));
577 }
578
579 #[test]
580 fn test_empty_function() {
581 let mut func = make_func();
582 let mut b = FunctionBuilder::new(&mut func);
583 b.stop();
584
585 let liveness = Liveness::compute(&func);
586 assert_eq!(liveness.live_in(func.entry_block).count(), 0);
587 assert_eq!(liveness.live_out(func.entry_block).count(), 0);
588 }
589
590 #[test]
591 fn test_side_effect_op_keeps_operands_live() {
592 let mut func = make_func();
594 let mut b = FunctionBuilder::new(&mut func);
595 let slot = b.add_param(MirType::uint256());
596 let val = b.add_param(MirType::uint256());
597 b.sstore(slot, val);
598 let loaded = b.sload(slot);
599 b.ret([loaded]);
600
601 let liveness = Liveness::compute(&func);
602 let entry = func.entry_block;
603
604 let info_0 = liveness.live_at_inst(&func, entry, 0);
606 assert!(info_0.live_before.contains(slot), "slot live before sstore");
607 assert!(info_0.live_before.contains(val), "val live before sstore");
608
609 let info_1 = liveness.live_at_inst(&func, entry, 1);
611 assert!(info_1.live_before.contains(slot), "slot live before sload");
612 assert!(!info_1.live_before.contains(val), "val not live before sload");
613 }
614
615 #[test]
616 fn test_live_at_inst() {
617 let mut func = make_func();
619 let mut b = FunctionBuilder::new(&mut func);
620 let v0 = b.add_param(MirType::uint256());
621 let v1 = b.add_param(MirType::uint256());
622 let v2 = b.add(v0, v1);
623 let v3 = b.mul(v2, v0);
624 b.ret([v3]);
625
626 let liveness = Liveness::compute(&func);
627 let entry = func.entry_block;
628
629 let info_0 = liveness.live_at_inst(&func, entry, 0);
631 assert!(info_0.live_before.contains(v0));
632 assert!(info_0.live_before.contains(v1));
633 assert!(!info_0.live_before.contains(v2));
634 assert!(!info_0.live_before.contains(v3));
635
636 assert!(info_0.live_after.contains(v0));
638 assert!(info_0.live_after.contains(v2));
639 assert!(!info_0.live_after.contains(v1), "v1 dead after add");
640
641 let info_1 = liveness.live_at_inst(&func, entry, 1);
643 assert!(info_1.live_before.contains(v0));
644 assert!(info_1.live_before.contains(v2));
645
646 assert!(info_1.live_after.contains(v3));
648 assert!(!info_1.live_after.contains(v0));
649 assert!(!info_1.live_after.contains(v2));
650 }
651
652 #[test]
653 fn test_is_dead_after() {
654 let mut func = make_func();
656 let mut b = FunctionBuilder::new(&mut func);
657 let v0 = b.add_param(MirType::uint256());
658 let v1 = b.add_param(MirType::uint256());
659 let v2 = b.add(v0, v1);
660 b.ret([v2]);
661
662 let liveness = Liveness::compute(&func);
663 let entry = func.entry_block;
664
665 assert!(liveness.is_dead_after(v0, entry, 0), "v0 dead after add");
667 assert!(liveness.is_dead_after(v1, entry, 0), "v1 dead after add");
668 assert!(!liveness.is_dead_after(v2, entry, 0), "v2 alive after add");
670 }
671
672 #[test]
673 fn test_last_use_in_block() {
674 let mut func = make_func();
676 let mut b = FunctionBuilder::new(&mut func);
677 let v0 = b.add_param(MirType::uint256());
678 let v1 = b.add_param(MirType::uint256());
679 let _v2 = b.add(v0, v1);
680 let v3 = b.mul(_v2, v0);
681 b.ret([v3]);
682
683 let liveness = Liveness::compute(&func);
684 let entry = func.entry_block;
685
686 assert_eq!(liveness.last_use_in_block(v0, entry), Some(Some(1)));
688 assert_eq!(liveness.last_use_in_block(v1, entry), Some(Some(0)));
690 assert_eq!(liveness.last_use_in_block(v3, entry), Some(None));
692 }
693
694 #[test]
695 fn test_value_live_across_multiple_blocks() {
696 let mut func = make_func();
700 let mut b = FunctionBuilder::new(&mut func);
701 let v0 = b.add_param(MirType::uint256());
702 let v1 = b.add_param(MirType::uint256());
703
704 let bb1 = b.create_block();
705 let bb2 = b.create_block();
706
707 b.jump(bb1);
708
709 b.switch_to_block(bb1);
710 let _v2 = b.add(v0, v1);
711 b.jump(bb2);
712
713 b.switch_to_block(bb2);
714 b.ret([v0]);
715
716 let liveness = Liveness::compute(&func);
717
718 assert!(liveness.live_out(bb1).contains(v0));
720 assert!(!liveness.live_out(bb1).contains(v1));
722 assert!(liveness.live_in(bb2).contains(v0));
724 }
725
726 #[test]
727 fn test_multiple_returns() {
728 let mut func = make_func();
732 let mut b = FunctionBuilder::new(&mut func);
733 let v0 = b.add_param(MirType::uint256());
734 let v1 = b.add_param(MirType::uint256());
735 let cond = b.add_param(MirType::Bool);
736
737 let left = b.create_block();
738 let right = b.create_block();
739
740 b.branch(cond, left, right);
741
742 b.switch_to_block(left);
743 b.ret([v0]);
744
745 b.switch_to_block(right);
746 b.ret([v1]);
747
748 let liveness = Liveness::compute(&func);
749
750 assert!(liveness.live_in(left).contains(v0));
752 assert!(!liveness.live_in(right).contains(v0));
753 assert!(liveness.live_in(right).contains(v1));
755 assert!(!liveness.live_in(left).contains(v1));
756 }
757
758 #[test]
759 fn test_phi_liveness() {
760 let mut func = make_func();
770
771 let entry;
773 let header;
774 let body;
775 let exit;
776 let init;
777 let updated;
778 let phi_placeholder; {
780 let mut b = FunctionBuilder::new(&mut func);
781 init = b.imm_u64(0);
782 entry = b.current_block();
783 header = b.create_block();
784 body = b.create_block();
785 exit = b.create_block();
786
787 b.jump(header);
788
789 b.switch_to_block(header);
790 phi_placeholder = b.undef(MirType::uint256());
792 let limit = b.imm_u64(10);
793 let cond = b.lt(phi_placeholder, limit);
794 b.branch(cond, body, exit);
795
796 b.switch_to_block(body);
797 let step = b.imm_u64(1);
798 updated = b.add(phi_placeholder, step);
799 b.jump(header);
800
801 b.switch_to_block(exit);
802 b.ret([phi_placeholder]);
803 }
804
805 let phi_val = phi_placeholder;
808 let phi_inst = func.alloc_inst(crate::mir::Instruction::new(
809 crate::mir::InstKind::Phi(vec![(entry, init), (body, updated)]),
810 Some(MirType::uint256()),
811 ));
812 func.blocks[header].instructions.insert(0, phi_inst);
813 func.values[phi_val] = crate::mir::Value::Inst(phi_inst);
814
815 let liveness = Liveness::compute(&func);
816
817 assert!(liveness.live_out(entry).contains(init), "init live-out of entry");
819 assert!(liveness.live_out(body).contains(updated), "updated live-out of body");
821 assert!(liveness.live_in(body).contains(phi_val), "phi_val live-in to body");
823 assert!(liveness.live_in(exit).contains(phi_val), "phi_val live-in to exit");
824 assert!(!liveness.live_in(entry).contains(phi_val), "phi_val not live-in to entry");
826 }
827
828 #[test]
829 fn test_inst_to_value_map_consistency() {
830 let mut func = make_func();
833 {
834 let mut b = FunctionBuilder::new(&mut func);
835 let x = b.add_param(MirType::uint256());
836 let y = b.add_param(MirType::uint256());
837 let sum = b.add(x, y);
838 let product = b.mul(sum, x);
839 b.sstore(x, product); let loaded = b.sload(y);
841 b.ret([loaded]);
842 }
843 let liveness = Liveness::compute(&func);
844
845 use solar_data_structures::map::FxHashMap;
847 let mut expected: FxHashMap<crate::mir::InstId, ValueId> = FxHashMap::default();
848 for (val_id, val) in func.values.iter_enumerated() {
849 if let crate::mir::Value::Inst(inst_id) = val {
850 expected.insert(*inst_id, val_id);
851 }
852 }
853 assert_eq!(liveness.inst_to_value, expected, "inst_to_value map diverged from linear scan");
854 }
855}