llvm_transforms/
loop_unroll.rs1use crate::pass::FunctionPass;
9use llvm_analysis::{Cfg, DomTree, LoopInfo};
10use llvm_ir::{BlockId, ConstantData, Context, Function, InstrKind, ValueRef};
11
12pub struct LoopUnroll {
14 pub factor: usize,
16 pub max_trip_count: usize,
18}
19
20impl Default for LoopUnroll {
21 fn default() -> Self {
22 Self {
23 factor: 4,
24 max_trip_count: 16,
25 }
26 }
27}
28
29impl FunctionPass for LoopUnroll {
30 fn name(&self) -> &'static str {
31 "loop-unroll"
32 }
33
34 fn run_on_function(&mut self, ctx: &mut Context, func: &mut Function) -> bool {
35 if func.blocks.is_empty() {
36 return false;
37 }
38
39 let cfg = Cfg::compute(func);
40 let dom = DomTree::compute(func, &cfg);
41 let li = LoopInfo::compute(func, &cfg, &dom);
42
43 let mut changed = false;
44 for lp in li.loops() {
45 let Some(trip_count) = constant_trip_count(ctx, func, lp.header) else {
46 continue;
47 };
48 if trip_count == 0 || trip_count > self.max_trip_count {
49 continue;
50 }
51
52 if lp.body.len() != 1 {
55 continue;
56 }
57 let hb = &func.blocks[lp.header.0 as usize];
58 let Some(tid) = hb.terminator else { continue };
59 let InstrKind::CondBr {
60 then_dest,
61 else_dest,
62 ..
63 } = func.instr(tid).kind
64 else {
65 continue;
66 };
67
68 let (loop_edge, exit_edge) = if then_dest == lp.header {
70 (then_dest, else_dest)
71 } else if else_dest == lp.header {
72 (else_dest, then_dest)
73 } else {
74 continue;
75 };
76 if loop_edge != lp.header {
77 continue;
78 }
79
80 if trip_count == 1 {
81 func.instr_mut(tid).kind = InstrKind::Br { dest: exit_edge };
82 changed = true;
83 }
84 }
85
86 changed
87 }
88}
89
90fn const_i64(ctx: &Context, v: ValueRef) -> Option<i64> {
91 let ValueRef::Constant(cid) = v else {
92 return None;
93 };
94 match ctx.get_const(cid) {
95 ConstantData::Int { val, .. } => Some(*val as i64),
96 _ => None,
97 }
98}
99
100pub(crate) fn constant_trip_count(ctx: &Context, func: &Function, header: BlockId) -> Option<usize> {
108 let hb = &func.blocks[header.0 as usize];
109 let tid = hb.terminator?;
110 let InstrKind::CondBr { cond, .. } = func.instr(tid).kind else {
111 return None;
112 };
113 let ValueRef::Instruction(cmp_iid) = cond else {
114 return None;
115 };
116 let InstrKind::ICmp { pred, lhs: _, rhs } = func.instr(cmp_iid).kind else {
117 return None;
118 };
119
120 let c = const_i64(ctx, rhs)?;
121 if c < 0 {
122 return None;
123 }
124
125 let tc = match pred {
126 llvm_ir::IntPredicate::Slt | llvm_ir::IntPredicate::Ult => c,
127 llvm_ir::IntPredicate::Sle | llvm_ir::IntPredicate::Ule => c + 1,
128 _ => return None,
129 };
130 usize::try_from(tc).ok()
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136 use llvm_ir::{Builder, Linkage, Module};
137
138 fn make_counted_loop_guard(pred: llvm_ir::IntPredicate, n: i64) -> (Context, Function) {
139 let mut ctx = Context::new();
140 let mut module = Module::new("m");
141 let mut b = Builder::new(&mut ctx, &mut module);
142
143 b.add_function("f", b.ctx.i32_ty, vec![], vec![], false, Linkage::External);
144 let entry = b.add_block("entry");
145 let header = b.add_block("header");
146 let exit = b.add_block("exit");
147
148 b.position_at_end(entry);
149 b.build_br(header);
150
151 b.position_at_end(header);
152 let zero = b.const_int(b.ctx.i32_ty, 0);
153 let i = b.build_phi(
154 "i",
155 b.ctx.i32_ty,
156 vec![(zero, entry), (zero, header)],
157 );
158 let c = b.const_int(b.ctx.i32_ty, n as u64);
159 let cmp = b.build_icmp("cmp", pred, i, c);
160 b.build_cond_br(cmp, header, exit);
161
162 b.position_at_end(exit);
163 let ret = b.const_int(b.ctx.i32_ty, 0);
164 b.build_ret(ret);
165
166 (ctx, module.functions.remove(0))
167 }
168
169 #[test]
170 fn trip_count_slt_1_to_16() {
171 for n in 1..=16 {
172 let (ctx, f) = make_counted_loop_guard(llvm_ir::IntPredicate::Slt, n);
173 assert_eq!(constant_trip_count(&ctx, &f, BlockId(1)), Some(n as usize));
174 }
175 }
176
177 #[test]
178 fn trip_count_ult_1_to_16() {
179 for n in 1..=16 {
180 let (ctx, f) = make_counted_loop_guard(llvm_ir::IntPredicate::Ult, n);
181 assert_eq!(constant_trip_count(&ctx, &f, BlockId(1)), Some(n as usize));
182 }
183 }
184
185 #[test]
186 fn trip_count_sle_1_to_16() {
187 for n in 1..=16 {
188 let (ctx, f) = make_counted_loop_guard(llvm_ir::IntPredicate::Sle, n - 1);
189 assert_eq!(constant_trip_count(&ctx, &f, BlockId(1)), Some(n as usize));
190 }
191 }
192
193 #[test]
194 fn trip_count_ule_1_to_16() {
195 for n in 1..=16 {
196 let (ctx, f) = make_counted_loop_guard(llvm_ir::IntPredicate::Ule, n - 1);
197 assert_eq!(constant_trip_count(&ctx, &f, BlockId(1)), Some(n as usize));
198 }
199 }
200}