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