1use crate::ir::*;
27
28pub fn optimize_instruction(instr: &mut Instruction, arch: Arch, level: OptLevel) -> bool {
38 if !matches!(arch, Arch::X86 | Arch::X86_64) || level == OptLevel::None {
39 return false;
40 }
41
42 let mut changed = false;
43
44 if level == OptLevel::Aggressive {
46 changed |= try_zero_idiom(instr);
47 }
48
49 changed |= try_mov_imm32_narrow(instr, arch);
51 changed |= try_rex_elimination(instr, arch);
52 changed |= try_test_conversion(instr);
53
54 changed
55}
56
57fn try_zero_idiom(instr: &mut Instruction) -> bool {
65 if instr.mnemonic != "mov" {
66 return false;
67 }
68 if instr.operands.len() != 2 {
69 return false;
70 }
71
72 let dst_reg = match &instr.operands[0] {
74 Operand::Register(r) => *r,
75 _ => return false,
76 };
77
78 let is_zero = matches!(&instr.operands[1], Operand::Immediate(0));
80 if !is_zero {
81 return false;
82 }
83
84 let bits = dst_reg.size_bits();
85 if bits != 32 && bits != 64 {
87 return false;
88 }
89
90 let xor_reg = if bits == 64 {
92 dst_reg.to_32bit()
93 } else {
94 Some(dst_reg)
95 };
96
97 if let Some(r32) = xor_reg {
98 instr.mnemonic = Mnemonic::from("xor");
99 instr.operands =
100 OperandList::from(alloc::vec![Operand::Register(r32), Operand::Register(r32)]);
101 instr.size_hint = None;
103 return true;
104 }
105
106 false
107}
108
109fn try_mov_imm32_narrow(instr: &mut Instruction, arch: Arch) -> bool {
115 if arch != Arch::X86_64 {
116 return false;
117 }
118 if instr.mnemonic != "mov" {
119 return false;
120 }
121 if instr.operands.len() != 2 {
122 return false;
123 }
124
125 let dst_reg = match &instr.operands[0] {
126 Operand::Register(r) => *r,
127 _ => return false,
128 };
129
130 if dst_reg.size_bits() != 64 {
131 return false;
132 }
133
134 let imm = match &instr.operands[1] {
135 Operand::Immediate(v) => *v,
136 _ => return false,
137 };
138
139 if !(0..=0xFFFF_FFFF).contains(&imm) {
142 return false;
143 }
144
145 if let Some(r32) = dst_reg.to_32bit() {
146 instr.operands[0] = Operand::Register(r32);
147 return true;
148 }
149
150 false
151}
152
153fn try_rex_elimination(instr: &mut Instruction, arch: Arch) -> bool {
164 if arch != Arch::X86_64 {
165 return false;
166 }
167 if instr.mnemonic != "and" {
168 return false;
169 }
170 if instr.operands.len() != 2 {
171 return false;
172 }
173
174 let dst_reg = match &instr.operands[0] {
175 Operand::Register(r) => *r,
176 _ => return false,
177 };
178
179 if dst_reg.size_bits() != 64 {
180 return false;
181 }
182
183 let imm = match &instr.operands[1] {
184 Operand::Immediate(v) => *v,
185 _ => return false,
186 };
187
188 if !(0..=0xFFFF_FFFF).contains(&imm) {
191 return false;
192 }
193
194 if let Some(r32) = dst_reg.to_32bit() {
195 instr.operands[0] = Operand::Register(r32);
196 return true;
197 }
198
199 false
200}
201
202fn try_test_conversion(instr: &mut Instruction) -> bool {
207 if instr.mnemonic != "and" {
208 return false;
209 }
210 if instr.operands.len() != 2 {
211 return false;
212 }
213
214 let r1 = match &instr.operands[0] {
215 Operand::Register(r) => r,
216 _ => return false,
217 };
218 let r2 = match &instr.operands[1] {
219 Operand::Register(r) => r,
220 _ => return false,
221 };
222
223 if r1 == r2 {
224 instr.mnemonic = Mnemonic::from("test");
225 return true;
226 }
227
228 false
229}
230
231#[cfg(test)]
232mod tests {
233 use super::*;
234 use crate::error::Span;
235
236 fn make_instr(mnemonic: &str, ops: Vec<Operand>) -> Instruction {
237 Instruction {
238 mnemonic: Mnemonic::from(mnemonic),
239 operands: OperandList::from(ops),
240 size_hint: None,
241 prefixes: PrefixList::new(),
242 opmask: None,
243 zeroing: false,
244 broadcast: None,
245 span: Span::dummy(),
246 }
247 }
248
249 #[test]
250 fn zero_idiom_mov_eax_0() {
251 let mut instr = make_instr(
252 "mov",
253 alloc::vec![Operand::Register(Register::Eax), Operand::Immediate(0)],
254 );
255 assert!(optimize_instruction(
256 &mut instr,
257 Arch::X86_64,
258 OptLevel::Aggressive
259 ));
260 assert_eq!(instr.mnemonic, "xor");
261 assert_eq!(instr.operands[0], Operand::Register(Register::Eax));
262 assert_eq!(instr.operands[1], Operand::Register(Register::Eax));
263 }
264
265 #[test]
266 fn zero_idiom_mov_rax_0() {
267 let mut instr = make_instr(
268 "mov",
269 alloc::vec![Operand::Register(Register::Rax), Operand::Immediate(0)],
270 );
271 assert!(optimize_instruction(
272 &mut instr,
273 Arch::X86_64,
274 OptLevel::Aggressive
275 ));
276 assert_eq!(instr.mnemonic, "xor");
277 assert_eq!(instr.operands[0], Operand::Register(Register::Eax));
279 }
280
281 #[test]
282 fn zero_idiom_mov_r12_0() {
283 let mut instr = make_instr(
284 "mov",
285 alloc::vec![Operand::Register(Register::R12), Operand::Immediate(0)],
286 );
287 assert!(optimize_instruction(
288 &mut instr,
289 Arch::X86_64,
290 OptLevel::Aggressive
291 ));
292 assert_eq!(instr.mnemonic, "xor");
293 assert_eq!(instr.operands[0], Operand::Register(Register::R12d));
294 }
295
296 #[test]
297 fn zero_idiom_not_applied_nonzero() {
298 let mut instr = make_instr(
299 "mov",
300 alloc::vec![Operand::Register(Register::Eax), Operand::Immediate(1)],
301 );
302 assert!(!optimize_instruction(
303 &mut instr,
304 Arch::X86_64,
305 OptLevel::Aggressive
306 ));
307 assert_eq!(instr.mnemonic, "mov");
308 }
309
310 #[test]
311 fn zero_idiom_not_applied_8bit() {
312 let mut instr = make_instr(
313 "mov",
314 alloc::vec![Operand::Register(Register::Al), Operand::Immediate(0)],
315 );
316 assert!(!optimize_instruction(
317 &mut instr,
318 Arch::X86_64,
319 OptLevel::Aggressive
320 ));
321 assert_eq!(instr.mnemonic, "mov");
322 }
323
324 #[test]
325 fn mov_imm32_narrow_rax_1() {
326 let mut instr = make_instr(
327 "mov",
328 alloc::vec![Operand::Register(Register::Rax), Operand::Immediate(1)],
329 );
330 assert!(optimize_instruction(
331 &mut instr,
332 Arch::X86_64,
333 OptLevel::Size
334 ));
335 assert_eq!(instr.operands[0], Operand::Register(Register::Eax));
336 assert_eq!(instr.operands[1], Operand::Immediate(1));
337 }
338
339 #[test]
340 fn mov_imm32_narrow_rax_max_u32() {
341 let mut instr = make_instr(
342 "mov",
343 alloc::vec![
344 Operand::Register(Register::Rax),
345 Operand::Immediate(0xFFFF_FFFF),
346 ],
347 );
348 assert!(optimize_instruction(
349 &mut instr,
350 Arch::X86_64,
351 OptLevel::Size
352 ));
353 assert_eq!(instr.operands[0], Operand::Register(Register::Eax));
354 }
355
356 #[test]
357 fn mov_imm32_narrow_not_applied_negative() {
358 let mut instr = make_instr(
359 "mov",
360 alloc::vec![Operand::Register(Register::Rax), Operand::Immediate(-1)],
361 );
362 assert!(!optimize_instruction(
365 &mut instr,
366 Arch::X86_64,
367 OptLevel::Size
368 ));
369 }
370
371 #[test]
372 fn mov_imm32_narrow_not_applied_large() {
373 let mut instr = make_instr(
374 "mov",
375 alloc::vec![
376 Operand::Register(Register::Rax),
377 Operand::Immediate(0x1_0000_0000),
378 ],
379 );
380 assert!(!optimize_instruction(
381 &mut instr,
382 Arch::X86_64,
383 OptLevel::Size
384 ));
385 }
386
387 #[test]
388 fn test_conversion_and_self() {
389 let mut instr = make_instr(
390 "and",
391 alloc::vec![
392 Operand::Register(Register::Eax),
393 Operand::Register(Register::Eax),
394 ],
395 );
396 assert!(optimize_instruction(
397 &mut instr,
398 Arch::X86_64,
399 OptLevel::Size
400 ));
401 assert_eq!(instr.mnemonic, "test");
402 }
403
404 #[test]
405 fn test_conversion_not_applied_different_regs() {
406 let mut instr = make_instr(
407 "and",
408 alloc::vec![
409 Operand::Register(Register::Eax),
410 Operand::Register(Register::Ebx),
411 ],
412 );
413 assert!(!optimize_instruction(
414 &mut instr,
415 Arch::X86_64,
416 OptLevel::Size
417 ));
418 assert_eq!(instr.mnemonic, "and");
419 }
420
421 #[test]
423 fn rex_elim_and_rax_0xff() {
424 let mut instr = make_instr(
426 "and",
427 alloc::vec![Operand::Register(Register::Rax), Operand::Immediate(0xFF)],
428 );
429 assert!(optimize_instruction(
430 &mut instr,
431 Arch::X86_64,
432 OptLevel::Size
433 ));
434 assert_eq!(instr.mnemonic, "and");
435 assert_eq!(instr.operands[0], Operand::Register(Register::Eax));
436 assert_eq!(instr.operands[1], Operand::Immediate(0xFF));
437 }
438
439 #[test]
440 fn rex_elim_and_r12_u32_max() {
441 let mut instr = make_instr(
443 "and",
444 alloc::vec![
445 Operand::Register(Register::R12),
446 Operand::Immediate(0xFFFF_FFFF),
447 ],
448 );
449 assert!(optimize_instruction(
450 &mut instr,
451 Arch::X86_64,
452 OptLevel::Size
453 ));
454 assert_eq!(instr.operands[0], Operand::Register(Register::R12d));
455 }
456
457 #[test]
458 fn rex_elim_and_not_applied_negative() {
459 let mut instr = make_instr(
461 "and",
462 alloc::vec![Operand::Register(Register::Rax), Operand::Immediate(-1)],
463 );
464 assert!(!optimize_instruction(
465 &mut instr,
466 Arch::X86_64,
467 OptLevel::Size
468 ));
469 }
470
471 #[test]
472 fn rex_elim_and_not_applied_large() {
473 let mut instr = make_instr(
475 "and",
476 alloc::vec![
477 Operand::Register(Register::Rax),
478 Operand::Immediate(0x1_0000_0000),
479 ],
480 );
481 assert!(!optimize_instruction(
482 &mut instr,
483 Arch::X86_64,
484 OptLevel::Size
485 ));
486 }
487
488 #[test]
489 fn rex_elim_not_applied_to_add() {
490 let mut instr = make_instr(
492 "and", alloc::vec![Operand::Register(Register::Rax), Operand::Immediate(5)],
494 );
495 assert!(optimize_instruction(
497 &mut instr,
498 Arch::X86_64,
499 OptLevel::Size
500 ));
501
502 let mut instr2 = make_instr(
504 "add",
505 alloc::vec![Operand::Register(Register::Rax), Operand::Immediate(5)],
506 );
507 assert!(!optimize_instruction(
508 &mut instr2,
509 Arch::X86_64,
510 OptLevel::Size
511 ));
512 }
513
514 #[test]
515 fn zero_idiom_not_applied_at_default_level() {
516 let mut instr = make_instr(
519 "mov",
520 alloc::vec![Operand::Register(Register::Eax), Operand::Immediate(0)],
521 );
522 assert!(!optimize_instruction(
523 &mut instr,
524 Arch::X86_64,
525 OptLevel::Size
526 ));
527 assert_eq!(instr.mnemonic, "mov");
528 }
529
530 #[test]
531 fn opt_level_none_disables_everything() {
532 let mut instr = make_instr(
533 "mov",
534 alloc::vec![Operand::Register(Register::Rax), Operand::Immediate(1)],
535 );
536 assert!(!optimize_instruction(
537 &mut instr,
538 Arch::X86_64,
539 OptLevel::None
540 ));
541 assert_eq!(instr.operands[0], Operand::Register(Register::Rax));
542 }
543
544 #[test]
545 fn rex_elim_not_applied_32bit_arch() {
546 let mut instr = make_instr(
547 "and",
548 alloc::vec![Operand::Register(Register::Eax), Operand::Immediate(0xFF)],
549 );
550 assert!(!optimize_instruction(&mut instr, Arch::X86, OptLevel::Size));
551 }
552}