1use rucc_ir::{Block, Def, Extra, Flags, Func, Inst, Opcode, Type, Value};
39
40use crate::{Fuel, Pass};
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub struct Simplify;
45
46impl Pass for Simplify {
47 fn name(&self) -> &'static str {
48 "simplify"
49 }
50
51 fn describe(&self) -> &'static str {
52 "a negated comparison becomes the comparison with the opposite predicate"
53 }
54
55 fn run(&self, func: &mut Func, fuel: &mut Fuel) -> bool {
56 let mut changed = false;
57 for block in func.blocks().collect::<Vec<Block>>() {
58 for inst in func.insts(block).collect::<Vec<Inst>>() {
59 let Some(flip) = negated_comparison(func, inst) else { continue };
60 if !fuel.take() {
61 continue;
65 }
66 let args = func.push_values(&[flip.lhs, flip.rhs]);
67 let data = &mut func[inst];
68 data.opcode = flip.opcode;
69 data.flags = flip.flags;
70 data.args = args;
71 data.extra = flip.extra;
72 changed = true;
73 }
74 }
75 changed
76 }
77}
78
79struct Flip {
81 opcode: Opcode,
83 flags: Flags,
85 extra: Extra,
87 lhs: Value,
89 rhs: Value,
91}
92
93fn negated_comparison(func: &Func, inst: Inst) -> Option<Flip> {
100 let data = &func[inst];
101 if data.opcode != Opcode::Xor {
102 return None;
103 }
104 let args = &func[data.args];
105 let (&first, &second) = (args.first()?, args.get(1)?);
106 if func[first].ty != Type::int(1) {
107 return None;
108 }
109 let cmp = match (all_ones(func, first), all_ones(func, second)) {
110 (true, false) => second,
111 (false, true) => first,
112 _ => return None,
115 };
116 let Def::Result { inst: cmp, .. } = func[cmp].def else { return None };
117 let data = &func[cmp];
118 let extra = match (data.opcode, data.extra) {
119 (Opcode::ICmp, Extra::IntPred(pred)) => Extra::IntPred(pred.inverse()),
120 (Opcode::FCmp, Extra::FloatPred(pred)) => Extra::FloatPred(pred.inverse()),
121 _ => return None,
122 };
123 let args = &func[data.args];
124 Some(Flip {
125 opcode: data.opcode,
126 flags: data.flags,
127 extra,
128 lhs: *args.first()?,
129 rhs: *args.get(1)?,
130 })
131}
132
133fn all_ones(func: &Func, value: Value) -> bool {
135 let ty = func[value].ty;
136 let Def::Result { inst, .. } = func[value].def else { return false };
137 let data = &func[inst];
138 let Extra::Imm(at) = data.extra else { return false };
139 if data.opcode != Opcode::IConst {
140 return false;
141 }
142 func[at].signed(ty) == -1
145}
146
147#[cfg(test)]
148mod tests {
149 use rucc_base::Interner;
150 use rucc_ir::{
151 Block, Builder, Extra, Flags, Float, FloatPred, Func, IntPred, Opcode, Signature, Type,
152 };
153
154 use crate::{Fuel, Pass, simplify::Simplify};
155
156 fn blank() -> (Interner, Func, Block) {
158 let mut names = Interner::new();
159 let name = names.intern("f");
160 let mut func = Func::new(name, Signature::new().with_returns(&[Type::int(1)]));
161 let block = func.create_block();
162 (names, func, block)
163 }
164
165 fn simplify(func: &mut Func) -> bool {
167 Simplify.run(func, &mut Fuel::unlimited())
168 }
169
170 fn came_from(func: &Func, value: rucc_ir::Value) -> (Opcode, Extra) {
172 let rucc_ir::Def::Result { inst, .. } = func[value].def else { panic!("not a result") };
173 (func[inst].opcode, func[inst].extra)
174 }
175
176 #[test]
177 fn a_negated_float_comparison_becomes_the_opposite_predicate() {
178 for pred in FloatPred::all() {
181 let (_, mut func, block) = blank();
182 let mut build = Builder::new(&mut func, block);
183 let x = build.iconst(Type::int(64), 0);
184 let x = build.unary(Opcode::Bitcast, x, Type::float(Float::F64));
185 let cmp = build.fcmp(pred, x, x, Flags::NONE);
186 let ones = build.iconst(Type::int(1), -1);
187 let not = build.binary(Opcode::Xor, cmp, ones, Flags::NONE);
188 build.ret(&[not]);
189 assert!(simplify(&mut func), "{pred:?}");
190 assert_eq!(
191 came_from(&func, not),
192 (Opcode::FCmp, Extra::FloatPred(pred.inverse())),
193 "{pred:?}"
194 );
195 }
196 }
197
198 #[test]
199 fn a_negated_integer_comparison_becomes_the_opposite_predicate() {
200 for pred in IntPred::all() {
201 let (_, mut func, block) = blank();
202 let mut build = Builder::new(&mut func, block);
203 let x = build.iconst(Type::int(32), 3);
204 let cmp = build.icmp(pred, x, x);
205 let ones = build.iconst(Type::int(1), -1);
206 let not = build.binary(Opcode::Xor, cmp, ones, Flags::NONE);
207 build.ret(&[not]);
208 assert!(simplify(&mut func), "{pred:?}");
209 assert_eq!(
210 came_from(&func, not),
211 (Opcode::ICmp, Extra::IntPred(pred.inverse())),
212 "{pred:?}"
213 );
214 }
215 }
216
217 #[test]
218 fn the_constant_is_found_on_either_side() {
219 for swapped in [false, true] {
220 let (_, mut func, block) = blank();
221 let mut build = Builder::new(&mut func, block);
222 let x = build.iconst(Type::int(32), 3);
223 let cmp = build.icmp(IntPred::Slt, x, x);
224 let ones = build.iconst(Type::int(1), -1);
225 let (lhs, rhs) = if swapped { (ones, cmp) } else { (cmp, ones) };
226 let not = build.binary(Opcode::Xor, lhs, rhs, Flags::NONE);
227 build.ret(&[not]);
228 assert!(simplify(&mut func), "swapped {swapped}");
229 assert_eq!(came_from(&func, not).1, Extra::IntPred(IntPred::Sge));
230 }
231 }
232
233 #[test]
234 fn an_exclusive_or_of_two_comparisons_is_left_alone() {
235 let (_, mut func, block) = blank();
236 let mut build = Builder::new(&mut func, block);
237 let x = build.iconst(Type::int(32), 3);
238 let a = build.icmp(IntPred::Slt, x, x);
239 let b = build.icmp(IntPred::Sgt, x, x);
240 let differ = build.binary(Opcode::Xor, a, b, Flags::NONE);
241 build.ret(&[differ]);
242 assert!(!simplify(&mut func));
243 assert_eq!(came_from(&func, differ).0, Opcode::Xor);
244 }
245
246 #[test]
247 fn an_exclusive_or_of_something_that_is_not_a_comparison_is_left_alone() {
248 let (_, mut func, block) = blank();
249 let mut build = Builder::new(&mut func, block);
250 let x = build.iconst(Type::int(32), 3);
251 let narrow = build.unary(Opcode::Trunc, x, Type::int(1));
252 let ones = build.iconst(Type::int(1), -1);
253 let not = build.binary(Opcode::Xor, narrow, ones, Flags::NONE);
254 build.ret(&[not]);
255 assert!(!simplify(&mut func));
256 assert_eq!(came_from(&func, not).0, Opcode::Xor);
257 }
258
259 #[test]
260 fn a_wider_exclusive_or_with_one_is_not_a_negation_and_is_left_alone() {
261 let (_, mut func, block) = blank();
262 let mut build = Builder::new(&mut func, block);
263 let x = build.iconst(Type::int(32), 3);
264 let cmp = build.icmp(IntPred::Slt, x, x);
265 let wide = build.unary(Opcode::ZExt, cmp, Type::int(32));
266 let one = build.iconst(Type::int(32), 1);
267 let flipped = build.binary(Opcode::Xor, wide, one, Flags::NONE);
268 let narrow = build.unary(Opcode::Trunc, flipped, Type::int(1));
269 build.ret(&[narrow]);
270 assert!(!simplify(&mut func), "an i32 xor 1 flips one bit of thirty two");
271 assert_eq!(came_from(&func, flipped).0, Opcode::Xor);
272 }
273
274 #[test]
275 fn the_comparisons_flags_travel_with_the_predicate() {
276 let (_, mut func, block) = blank();
277 let mut build = Builder::new(&mut func, block);
278 let x = build.iconst(Type::int(64), 0);
279 let x = build.unary(Opcode::Bitcast, x, Type::float(Float::F64));
280 let cmp = build.fcmp(FloatPred::Olt, x, x, Flags::FAST);
281 let ones = build.iconst(Type::int(1), -1);
282 let not = build.binary(Opcode::Xor, cmp, ones, Flags::NONE);
283 build.ret(&[not]);
284 assert!(simplify(&mut func));
285 let rucc_ir::Def::Result { inst, .. } = func[not].def else { panic!("not a result") };
286 assert_eq!(func[inst].flags, Flags::FAST);
289 }
290
291 #[test]
292 fn fuel_stops_the_transformation_and_not_the_walk() {
293 let (_, mut func, block) = blank();
294 let mut build = Builder::new(&mut func, block);
295 let x = build.iconst(Type::int(32), 3);
296 let a = build.icmp(IntPred::Slt, x, x);
297 let b = build.icmp(IntPred::Sgt, x, x);
298 let ones = build.iconst(Type::int(1), -1);
299 let first = build.binary(Opcode::Xor, a, ones, Flags::NONE);
300 let second = build.binary(Opcode::Xor, b, ones, Flags::NONE);
301 let both = build.binary(Opcode::And, first, second, Flags::NONE);
302 build.ret(&[both]);
303 assert!(Simplify.run(&mut func, &mut Fuel::of(1)));
304 assert_eq!(came_from(&func, first).0, Opcode::ICmp);
305 assert_eq!(came_from(&func, second).0, Opcode::Xor);
306 }
307}