1use rucc_base::Interner;
44use rucc_base::hash::Map;
45use rucc_mir::{self as mir, Role};
46use rucc_target::{BranchInsts, FlagInsts, Fusion, MachineInsts};
47
48use crate::changes::{self, Changes, Plan};
49
50#[must_use]
60pub fn fusable(
61 func: &mir::Func,
62 insts: &BranchInsts,
63 names: &mut Interner,
64) -> Map<mir::Inst, usize> {
65 let compares = compares(insts, names);
66 let selects = selects(insts, names);
67 let reads = changes::Reads::of(func);
68 let mut found = Map::default();
69 for block in func.blocks() {
70 let mut waiting: Map<mir::Reg, (mir::Inst, usize)> = Map::default();
71 for inst in func.insts(block) {
72 let data = &func[inst];
73 let operands = &func[data.operands];
74 if selects.contains(&data.opcode) {
75 let condition = operands.get(3).map(|operand| operand.reg);
76 if let Some(entry) = condition.and_then(|reg| waiting.get_mut(®)) {
77 entry.1 += 1;
78 }
79 }
80 if compares.contains_key(&data.opcode) {
81 let byte = operands.first().filter(|operand| operand.role != Role::Use);
82 if let Some(byte) = byte.filter(|byte| byte.reg.is_virtual()) {
83 waiting.insert(byte.reg, (inst, 0));
84 }
85 }
86 }
87 for (reg, (compare, count)) in waiting {
88 if count > 0 && reads.count(reg) == count {
89 found.insert(compare, count);
90 }
91 }
92 }
93 found
94}
95
96pub fn moves(
101 func: &mut mir::Func,
102 insts: &BranchInsts,
103 flags: &FlagInsts,
104 machine: &MachineInsts,
105 names: &mut Interner,
106 fusable: &Map<mir::Inst, usize>,
107) -> usize {
108 if fusable.is_empty() {
109 return 0;
110 }
111 let compares = compares(insts, names);
114 let kept: Map<&str, mir::Opcode> =
115 insts.fused.iter().map(|fusion| (fusion.cmp, opcode(insts, names, fusion.cmp))).collect();
116 let chosen: Map<(mir::Opcode, &str), mir::Opcode> = insts
117 .moves
118 .iter()
119 .map(|entry| {
120 let select = opcode(insts, names, entry.select);
121 ((select, entry.when), opcode(insts, names, entry.cmov))
122 })
123 .collect();
124 let names = &*names;
125 let mut counts = changes::Reads::of(func);
126 let mut made = 0;
127 for block in func.blocks().collect::<Vec<_>>() {
128 let sequence: Vec<mir::Inst> = func.insts(block).collect();
129 for (at, &compare) in sequence.iter().enumerate() {
130 let Some(&wanted) = fusable.get(&compare) else { continue };
131 let Some(&fusion) = compares.get(&func[compare].opcode) else { continue };
132 let Some(&byte) = func[func[compare].operands].first() else { continue };
133 let found = reached(func, flags, names, &chosen, fusion, byte, &sequence[at + 1..]);
134 if found.len() != wanted {
135 continue;
136 }
137 let Some(&cmp) = kept.get(fusion.cmp) else { continue };
138 let mut set = Changes::new();
139 set.rewrite(compare, flags_only(func, compare, cmp));
140 for (select, cmov) in found {
141 let mut plan = Plan::of(func, select);
142 plan.operands.truncate(3);
143 set.rewrite(select, Plan { opcode: cmov, ..plan });
144 }
145 if set.commit(func, &mut counts, names, machine).is_ok() {
146 made += 1;
147 }
148 }
149 }
150 made
151}
152
153fn reached(
160 func: &mir::Func,
161 flags: &FlagInsts,
162 names: &Interner,
163 chosen: &Map<(mir::Opcode, &str), mir::Opcode>,
164 fusion: &Fusion,
165 byte: mir::Operand,
166 after: &[mir::Inst],
167) -> Vec<(mir::Inst, mir::Opcode)> {
168 let place = (byte.class, byte.reg);
169 let mut found = Vec::new();
170 for &inst in after {
171 let data = &func[inst];
172 let operands = &func[data.operands];
173 let writes = operands
174 .iter()
175 .any(|operand| operand.role != Role::Use && (operand.class, operand.reg) == place);
176 if let Some(&cmov) = chosen.get(&(data.opcode, fusion.if_true)) {
177 let [_, false_arm, true_arm, condition] = operands else { break };
178 let arms = [false_arm, true_arm];
179 if (condition.class, condition.reg) == place
180 && arms.iter().all(|arm| (arm.class, arm.reg) != place)
181 {
182 found.push((inst, cmov));
183 if writes {
184 break;
185 }
186 continue;
187 }
188 }
189 let Some(name) = names.resolve(data.opcode.name()).strip_prefix(flags.prefix) else {
190 break;
191 };
192 if (flags.writes)(name) || writes {
193 break;
194 }
195 }
196 found
197}
198
199fn flags_only(func: &mir::Func, compare: mir::Inst, cmp: mir::Opcode) -> Plan {
204 let mut plan = Plan::of(func, compare);
205 plan.operands.remove(0);
206 plan.amode = plan.amode.map(|mut amode| {
207 amode.base = amode.base.map(|position| position - 1);
208 amode.index = amode.index.map(|position| position - 1);
209 amode
210 });
211 Plan { opcode: cmp, ..plan }
212}
213
214fn compares(insts: &BranchInsts, names: &mut Interner) -> Map<mir::Opcode, &'static Fusion> {
216 insts.fused.iter().map(|fusion| (opcode(insts, names, fusion.set), fusion)).collect()
217}
218
219fn selects(insts: &BranchInsts, names: &mut Interner) -> Vec<mir::Opcode> {
221 let mut found: Vec<mir::Opcode> =
222 insts.moves.iter().map(|entry| opcode(insts, names, entry.select)).collect();
223 found.dedup();
224 found
225}
226
227fn opcode(insts: &BranchInsts, names: &mut Interner, name: &str) -> mir::Opcode {
229 mir::Opcode::new(names.intern(&format!("{}{name}", insts.prefix)))
230}
231
232#[cfg(test)]
233mod tests {
234 use rucc_target::x86_64::{BRANCH, FLAGS, GPR, MACHINE};
235
236 use super::*;
237
238 fn empty() -> (Interner, mir::Func, mir::Block) {
240 let mut names = Interner::new();
241 let mut func = mir::Func::new(names.intern("f"));
242 let block = func.create_block();
243 (names, func, block)
244 }
245
246 fn op(names: &mut Interner, name: &str) -> mir::Opcode {
248 opcode(&BRANCH, names, name)
249 }
250
251 fn fuse(func: &mut mir::Func, names: &mut Interner) -> usize {
253 let found = fusable(func, &BRANCH, names);
254 moves(func, &BRANCH, &FLAGS, &MACHINE, names, &found)
255 }
256
257 fn shape(func: &mir::Func, names: &Interner, block: mir::Block) -> Vec<String> {
259 func.insts(block)
260 .map(|inst| {
261 let name = names.resolve(func[inst].opcode.name());
262 name.strip_prefix(BRANCH.prefix).unwrap_or("").to_owned()
263 })
264 .collect()
265 }
266
267 fn tied(f: mir::Reg) -> mir::Operand {
270 mir::Operand::write(f, GPR).with(rucc_mir::Constraint::Reuse(1))
271 }
272
273 fn compare_and_select(
275 func: &mut mir::Func,
276 names: &mut Interner,
277 block: mir::Block,
278 condition: &str,
279 ) -> [mir::Reg; 2] {
280 let [x, y, byte, t, f] = [(); 5].map(|()| func.new_vreg(GPR));
281 let cmp = op(names, &format!("cmp_set_{condition}_32"));
282 let select = op(names, "test_cmov_ne_32");
283 func.build(block, cmp).def(byte, GPR).uses(x, GPR).uses(y, GPR).finish();
284 func.build(block, select)
285 .operand(tied(f))
286 .uses(f, GPR)
287 .uses(t, GPR)
288 .uses(byte, GPR)
289 .finish();
290 [byte, t]
291 }
292
293 #[test]
296 fn a_select_on_a_comparison_becomes_a_move_on_its_condition() {
297 for condition in ["e", "l", "ge", "b", "a"] {
298 let (mut names, mut func, block) = empty();
299 compare_and_select(&mut func, &mut names, block, condition);
300
301 assert_eq!(fuse(&mut func, &mut names), 1, "{condition}");
302 let expected = ["cmp_rr_32".to_owned(), format!("cmov_{condition}_32")];
303 assert_eq!(shape(&func, &names, block), expected);
304 let last = func.insts(block).last().expect("the move");
305 assert_eq!(func[func[last].operands].len(), 3);
306 }
307 }
308
309 #[test]
312 fn two_selects_on_one_comparison_both_become_moves() {
313 let (mut names, mut func, block) = empty();
314 let [byte, _] = compare_and_select(&mut func, &mut names, block, "g");
315 let [t, f] = [(); 2].map(|()| func.new_vreg(GPR));
316 let select = op(&mut names, "test_cmov_ne_64");
317 func.build(block, select)
318 .operand(tied(f))
319 .uses(f, GPR)
320 .uses(t, GPR)
321 .uses(byte, GPR)
322 .finish();
323
324 assert_eq!(fuse(&mut func, &mut names), 1);
325 assert_eq!(shape(&func, &names, block), ["cmp_rr_32", "cmov_g_32", "cmov_g_64"]);
326 }
327
328 #[test]
331 fn a_copy_between_the_comparison_and_the_select_does_not_stop_it() {
332 let (mut names, mut func, block) = empty();
333 let [x, y, byte, t, f, spare] = [(); 6].map(|()| func.new_vreg(GPR));
334 let cmp = op(&mut names, "cmp_set_l_32");
335 let copy = op(&mut names, "mov_rr_64");
336 let select = op(&mut names, "test_cmov_ne_32");
337 func.build(block, cmp).def(byte, GPR).uses(x, GPR).uses(y, GPR).finish();
338 func.build(block, copy).def(spare, GPR).uses(f, GPR).finish();
339 func.build(block, select)
340 .operand(tied(f))
341 .uses(f, GPR)
342 .uses(t, GPR)
343 .uses(byte, GPR)
344 .finish();
345
346 assert_eq!(fuse(&mut func, &mut names), 1);
347 assert_eq!(shape(&func, &names, block), ["cmp_rr_32", "mov_rr_64", "cmov_l_32"]);
348 }
349
350 #[test]
353 fn arithmetic_between_the_comparison_and_the_select_keeps_the_test() {
354 let (mut names, mut func, block) = empty();
355 let [x, y, byte, t, f] = [(); 5].map(|()| func.new_vreg(GPR));
356 let cmp = op(&mut names, "cmp_set_l_32");
357 let add = op(&mut names, "add_rr_32");
358 let select = op(&mut names, "test_cmov_ne_32");
359 func.build(block, cmp).def(byte, GPR).uses(x, GPR).uses(y, GPR).finish();
360 func.build(block, add).def(t, GPR).uses(t, GPR).uses(x, GPR).finish();
361 func.build(block, select)
362 .operand(tied(f))
363 .uses(f, GPR)
364 .uses(t, GPR)
365 .uses(byte, GPR)
366 .finish();
367
368 assert_eq!(fuse(&mut func, &mut names), 0);
369 assert_eq!(shape(&func, &names, block), ["cmp_set_l_32", "add_rr_32", "test_cmov_ne_32"]);
370 }
371
372 #[test]
375 fn a_byte_something_else_reads_is_kept_and_so_is_the_test() {
376 let (mut names, mut func, block) = empty();
377 let [byte, _] = compare_and_select(&mut func, &mut names, block, "e");
378 let spare = func.new_vreg(GPR);
379 let copy = op(&mut names, "mov_rr_64");
380 func.build(block, copy).def(spare, GPR).uses(byte, GPR).finish();
381
382 assert_eq!(fuse(&mut func, &mut names), 0);
383 assert_eq!(shape(&func, &names, block), ["cmp_set_e_32", "test_cmov_ne_32", "mov_rr_64"]);
384 }
385
386 #[test]
389 fn a_select_that_chooses_the_byte_itself_keeps_the_test() {
390 let (mut names, mut func, block) = empty();
391 let [x, y, byte, f] = [(); 4].map(|()| func.new_vreg(GPR));
392 let cmp = op(&mut names, "cmp_set_ne_32");
393 let select = op(&mut names, "test_cmov_ne_32");
394 func.build(block, cmp).def(byte, GPR).uses(x, GPR).uses(y, GPR).finish();
395 func.build(block, select)
396 .operand(tied(f))
397 .uses(f, GPR)
398 .uses(byte, GPR)
399 .uses(byte, GPR)
400 .finish();
401
402 assert_eq!(fuse(&mut func, &mut names), 0);
403 }
404
405 #[test]
409 fn a_register_written_again_before_the_select_keeps_the_test() {
410 let (mut names, mut func, block) = empty();
411 let byte = mir::Reg::physical(rucc_target::x86_64::RAX);
412 let [x, y, t, f, other] = [(); 5].map(|()| func.new_vreg(GPR));
413 let cmp = op(&mut names, "cmp_set_l_32");
414 let copy = op(&mut names, "mov_rr_64");
415 let select = op(&mut names, "test_cmov_ne_32");
416 func.build(block, cmp).def(byte, GPR).uses(x, GPR).uses(y, GPR).finish();
417 func.build(block, copy).def(byte, GPR).uses(other, GPR).finish();
418 func.build(block, select)
419 .operand(tied(f))
420 .uses(f, GPR)
421 .uses(t, GPR)
422 .uses(byte, GPR)
423 .finish();
424 let found: Map<_, _> =
425 [(func.insts(block).next().expect("the comparison"), 1)].into_iter().collect();
426
427 assert_eq!(moves(&mut func, &BRANCH, &FLAGS, &MACHINE, &mut names, &found), 0);
428 assert_eq!(shape(&func, &names, block), ["cmp_set_l_32", "mov_rr_64", "test_cmov_ne_32"]);
429 }
430
431 #[test]
434 fn a_comparison_against_memory_keeps_its_address() {
435 let (mut names, mut func, block) = empty();
436 let [x, base, byte, t, f] = [(); 5].map(|()| func.new_vreg(GPR));
437 let cmp = op(&mut names, "cmp_set_l_rm_32");
438 let select = op(&mut names, "test_cmov_ne_32");
439 let place = mir::Mem { disp: 8, ..mir::Mem::at(mir::Operand::read(base, GPR)) };
440 let compare = func.build(block, cmp).def(byte, GPR).uses(x, GPR).mem(place).finish();
441 func.build(block, select)
442 .operand(tied(f))
443 .uses(f, GPR)
444 .uses(t, GPR)
445 .uses(byte, GPR)
446 .finish();
447
448 assert_eq!(fuse(&mut func, &mut names), 1);
449 assert_eq!(shape(&func, &names, block), ["cmp_rm_32", "cmov_l_32"]);
450 let mode = func[func[compare].mem.expect("the address")];
451 assert_eq!((mode.base, mode.disp), (Some(1), 8));
452 }
453}