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