1use rucc_base::Interner;
235use rucc_mir::{Amode, Func, Inst, Opcode, Operand, Reg};
236use rucc_target::MachineInsts;
237
238use crate::changes::{Changes, Plan, Reads};
239use crate::fold::Pending;
240
241pub const WINDOW: usize = 16;
258
259#[derive(Debug, Clone, Copy, PartialEq, Eq)]
267pub struct Fold {
268 pub from: &'static str,
270 pub into: &'static str,
272 pub load: &'static str,
274 pub swapped: Option<&'static str>,
288}
289
290pub static FOLDS: &[Fold] = &[
301 Fold { from: "add_rr_8", into: "add_rm_8", load: "mov_rm_8", swapped: Some("add_rm_8") },
302 Fold { from: "add_rr_16", into: "add_rm_16", load: "mov_rm_16", swapped: Some("add_rm_16") },
303 Fold { from: "add_rr_32", into: "add_rm_32", load: "mov_rm_32", swapped: Some("add_rm_32") },
304 Fold { from: "add_rr_64", into: "add_rm_64", load: "mov_rm_64", swapped: Some("add_rm_64") },
305 Fold { from: "sub_rr_8", into: "sub_rm_8", load: "mov_rm_8", swapped: None },
306 Fold { from: "sub_rr_16", into: "sub_rm_16", load: "mov_rm_16", swapped: None },
307 Fold { from: "sub_rr_32", into: "sub_rm_32", load: "mov_rm_32", swapped: None },
308 Fold { from: "sub_rr_64", into: "sub_rm_64", load: "mov_rm_64", swapped: None },
309 Fold { from: "and_rr_8", into: "and_rm_8", load: "mov_rm_8", swapped: Some("and_rm_8") },
310 Fold { from: "and_rr_16", into: "and_rm_16", load: "mov_rm_16", swapped: Some("and_rm_16") },
311 Fold { from: "and_rr_32", into: "and_rm_32", load: "mov_rm_32", swapped: Some("and_rm_32") },
312 Fold { from: "and_rr_64", into: "and_rm_64", load: "mov_rm_64", swapped: Some("and_rm_64") },
313 Fold { from: "or_rr_8", into: "or_rm_8", load: "mov_rm_8", swapped: Some("or_rm_8") },
314 Fold { from: "or_rr_16", into: "or_rm_16", load: "mov_rm_16", swapped: Some("or_rm_16") },
315 Fold { from: "or_rr_32", into: "or_rm_32", load: "mov_rm_32", swapped: Some("or_rm_32") },
316 Fold { from: "or_rr_64", into: "or_rm_64", load: "mov_rm_64", swapped: Some("or_rm_64") },
317 Fold { from: "xor_rr_8", into: "xor_rm_8", load: "mov_rm_8", swapped: Some("xor_rm_8") },
318 Fold { from: "xor_rr_16", into: "xor_rm_16", load: "mov_rm_16", swapped: Some("xor_rm_16") },
319 Fold { from: "xor_rr_32", into: "xor_rm_32", load: "mov_rm_32", swapped: Some("xor_rm_32") },
320 Fold { from: "xor_rr_64", into: "xor_rm_64", load: "mov_rm_64", swapped: Some("xor_rm_64") },
321 Fold { from: "imul_rr_16", into: "imul_rm_16", load: "mov_rm_16", swapped: Some("imul_rm_16") },
322 Fold { from: "imul_rr_32", into: "imul_rm_32", load: "mov_rm_32", swapped: Some("imul_rm_32") },
323 Fold { from: "imul_rr_64", into: "imul_rm_64", load: "mov_rm_64", swapped: Some("imul_rm_64") },
324 Fold {
325 from: "cmp_set_e_8",
326 into: "cmp_set_e_rm_8",
327 load: "mov_rm_8",
328 swapped: Some("cmp_set_e_rm_8"),
329 },
330 Fold {
331 from: "cmp_set_e_16",
332 into: "cmp_set_e_rm_16",
333 load: "mov_rm_16",
334 swapped: Some("cmp_set_e_rm_16"),
335 },
336 Fold {
337 from: "cmp_set_e_32",
338 into: "cmp_set_e_rm_32",
339 load: "mov_rm_32",
340 swapped: Some("cmp_set_e_rm_32"),
341 },
342 Fold {
343 from: "cmp_set_e_64",
344 into: "cmp_set_e_rm_64",
345 load: "mov_rm_64",
346 swapped: Some("cmp_set_e_rm_64"),
347 },
348 Fold {
349 from: "cmp_set_ne_8",
350 into: "cmp_set_ne_rm_8",
351 load: "mov_rm_8",
352 swapped: Some("cmp_set_ne_rm_8"),
353 },
354 Fold {
355 from: "cmp_set_ne_16",
356 into: "cmp_set_ne_rm_16",
357 load: "mov_rm_16",
358 swapped: Some("cmp_set_ne_rm_16"),
359 },
360 Fold {
361 from: "cmp_set_ne_32",
362 into: "cmp_set_ne_rm_32",
363 load: "mov_rm_32",
364 swapped: Some("cmp_set_ne_rm_32"),
365 },
366 Fold {
367 from: "cmp_set_ne_64",
368 into: "cmp_set_ne_rm_64",
369 load: "mov_rm_64",
370 swapped: Some("cmp_set_ne_rm_64"),
371 },
372 Fold {
373 from: "cmp_set_l_8",
374 into: "cmp_set_l_rm_8",
375 load: "mov_rm_8",
376 swapped: Some("cmp_set_g_rm_8"),
377 },
378 Fold {
379 from: "cmp_set_l_16",
380 into: "cmp_set_l_rm_16",
381 load: "mov_rm_16",
382 swapped: Some("cmp_set_g_rm_16"),
383 },
384 Fold {
385 from: "cmp_set_l_32",
386 into: "cmp_set_l_rm_32",
387 load: "mov_rm_32",
388 swapped: Some("cmp_set_g_rm_32"),
389 },
390 Fold {
391 from: "cmp_set_l_64",
392 into: "cmp_set_l_rm_64",
393 load: "mov_rm_64",
394 swapped: Some("cmp_set_g_rm_64"),
395 },
396 Fold {
397 from: "cmp_set_le_8",
398 into: "cmp_set_le_rm_8",
399 load: "mov_rm_8",
400 swapped: Some("cmp_set_ge_rm_8"),
401 },
402 Fold {
403 from: "cmp_set_le_16",
404 into: "cmp_set_le_rm_16",
405 load: "mov_rm_16",
406 swapped: Some("cmp_set_ge_rm_16"),
407 },
408 Fold {
409 from: "cmp_set_le_32",
410 into: "cmp_set_le_rm_32",
411 load: "mov_rm_32",
412 swapped: Some("cmp_set_ge_rm_32"),
413 },
414 Fold {
415 from: "cmp_set_le_64",
416 into: "cmp_set_le_rm_64",
417 load: "mov_rm_64",
418 swapped: Some("cmp_set_ge_rm_64"),
419 },
420 Fold {
421 from: "cmp_set_g_8",
422 into: "cmp_set_g_rm_8",
423 load: "mov_rm_8",
424 swapped: Some("cmp_set_l_rm_8"),
425 },
426 Fold {
427 from: "cmp_set_g_16",
428 into: "cmp_set_g_rm_16",
429 load: "mov_rm_16",
430 swapped: Some("cmp_set_l_rm_16"),
431 },
432 Fold {
433 from: "cmp_set_g_32",
434 into: "cmp_set_g_rm_32",
435 load: "mov_rm_32",
436 swapped: Some("cmp_set_l_rm_32"),
437 },
438 Fold {
439 from: "cmp_set_g_64",
440 into: "cmp_set_g_rm_64",
441 load: "mov_rm_64",
442 swapped: Some("cmp_set_l_rm_64"),
443 },
444 Fold {
445 from: "cmp_set_ge_8",
446 into: "cmp_set_ge_rm_8",
447 load: "mov_rm_8",
448 swapped: Some("cmp_set_le_rm_8"),
449 },
450 Fold {
451 from: "cmp_set_ge_16",
452 into: "cmp_set_ge_rm_16",
453 load: "mov_rm_16",
454 swapped: Some("cmp_set_le_rm_16"),
455 },
456 Fold {
457 from: "cmp_set_ge_32",
458 into: "cmp_set_ge_rm_32",
459 load: "mov_rm_32",
460 swapped: Some("cmp_set_le_rm_32"),
461 },
462 Fold {
463 from: "cmp_set_ge_64",
464 into: "cmp_set_ge_rm_64",
465 load: "mov_rm_64",
466 swapped: Some("cmp_set_le_rm_64"),
467 },
468 Fold {
469 from: "cmp_set_b_8",
470 into: "cmp_set_b_rm_8",
471 load: "mov_rm_8",
472 swapped: Some("cmp_set_a_rm_8"),
473 },
474 Fold {
475 from: "cmp_set_b_16",
476 into: "cmp_set_b_rm_16",
477 load: "mov_rm_16",
478 swapped: Some("cmp_set_a_rm_16"),
479 },
480 Fold {
481 from: "cmp_set_b_32",
482 into: "cmp_set_b_rm_32",
483 load: "mov_rm_32",
484 swapped: Some("cmp_set_a_rm_32"),
485 },
486 Fold {
487 from: "cmp_set_b_64",
488 into: "cmp_set_b_rm_64",
489 load: "mov_rm_64",
490 swapped: Some("cmp_set_a_rm_64"),
491 },
492 Fold {
493 from: "cmp_set_be_8",
494 into: "cmp_set_be_rm_8",
495 load: "mov_rm_8",
496 swapped: Some("cmp_set_ae_rm_8"),
497 },
498 Fold {
499 from: "cmp_set_be_16",
500 into: "cmp_set_be_rm_16",
501 load: "mov_rm_16",
502 swapped: Some("cmp_set_ae_rm_16"),
503 },
504 Fold {
505 from: "cmp_set_be_32",
506 into: "cmp_set_be_rm_32",
507 load: "mov_rm_32",
508 swapped: Some("cmp_set_ae_rm_32"),
509 },
510 Fold {
511 from: "cmp_set_be_64",
512 into: "cmp_set_be_rm_64",
513 load: "mov_rm_64",
514 swapped: Some("cmp_set_ae_rm_64"),
515 },
516 Fold {
517 from: "cmp_set_a_8",
518 into: "cmp_set_a_rm_8",
519 load: "mov_rm_8",
520 swapped: Some("cmp_set_b_rm_8"),
521 },
522 Fold {
523 from: "cmp_set_a_16",
524 into: "cmp_set_a_rm_16",
525 load: "mov_rm_16",
526 swapped: Some("cmp_set_b_rm_16"),
527 },
528 Fold {
529 from: "cmp_set_a_32",
530 into: "cmp_set_a_rm_32",
531 load: "mov_rm_32",
532 swapped: Some("cmp_set_b_rm_32"),
533 },
534 Fold {
535 from: "cmp_set_a_64",
536 into: "cmp_set_a_rm_64",
537 load: "mov_rm_64",
538 swapped: Some("cmp_set_b_rm_64"),
539 },
540 Fold {
541 from: "cmp_set_ae_8",
542 into: "cmp_set_ae_rm_8",
543 load: "mov_rm_8",
544 swapped: Some("cmp_set_be_rm_8"),
545 },
546 Fold {
547 from: "cmp_set_ae_16",
548 into: "cmp_set_ae_rm_16",
549 load: "mov_rm_16",
550 swapped: Some("cmp_set_be_rm_16"),
551 },
552 Fold {
553 from: "cmp_set_ae_32",
554 into: "cmp_set_ae_rm_32",
555 load: "mov_rm_32",
556 swapped: Some("cmp_set_be_rm_32"),
557 },
558 Fold {
559 from: "cmp_set_ae_64",
560 into: "cmp_set_ae_rm_64",
561 load: "mov_rm_64",
562 swapped: Some("cmp_set_be_rm_64"),
563 },
564];
565
566#[derive(Debug, Clone, Copy, PartialEq, Eq)]
573pub struct Update {
574 pub from: &'static str,
576 pub into: &'static str,
578 pub load: &'static str,
580 pub store: &'static str,
582 pub commutes: bool,
584}
585
586pub static UPDATES: &[Update] = &[
597 Update {
598 from: "add_rr_8",
599 into: "add_mr_8",
600 load: "mov_rm_8",
601 store: "mov_mr_8",
602 commutes: true,
603 },
604 Update {
605 from: "add_rr_16",
606 into: "add_mr_16",
607 load: "mov_rm_16",
608 store: "mov_mr_16",
609 commutes: true,
610 },
611 Update {
612 from: "add_rr_32",
613 into: "add_mr_32",
614 load: "mov_rm_32",
615 store: "mov_mr_32",
616 commutes: true,
617 },
618 Update {
619 from: "add_rr_64",
620 into: "add_mr_64",
621 load: "mov_rm_64",
622 store: "mov_mr_64",
623 commutes: true,
624 },
625 Update {
626 from: "sub_rr_8",
627 into: "sub_mr_8",
628 load: "mov_rm_8",
629 store: "mov_mr_8",
630 commutes: false,
631 },
632 Update {
633 from: "sub_rr_16",
634 into: "sub_mr_16",
635 load: "mov_rm_16",
636 store: "mov_mr_16",
637 commutes: false,
638 },
639 Update {
640 from: "sub_rr_32",
641 into: "sub_mr_32",
642 load: "mov_rm_32",
643 store: "mov_mr_32",
644 commutes: false,
645 },
646 Update {
647 from: "sub_rr_64",
648 into: "sub_mr_64",
649 load: "mov_rm_64",
650 store: "mov_mr_64",
651 commutes: false,
652 },
653 Update {
654 from: "and_rr_8",
655 into: "and_mr_8",
656 load: "mov_rm_8",
657 store: "mov_mr_8",
658 commutes: true,
659 },
660 Update {
661 from: "and_rr_16",
662 into: "and_mr_16",
663 load: "mov_rm_16",
664 store: "mov_mr_16",
665 commutes: true,
666 },
667 Update {
668 from: "and_rr_32",
669 into: "and_mr_32",
670 load: "mov_rm_32",
671 store: "mov_mr_32",
672 commutes: true,
673 },
674 Update {
675 from: "and_rr_64",
676 into: "and_mr_64",
677 load: "mov_rm_64",
678 store: "mov_mr_64",
679 commutes: true,
680 },
681 Update {
682 from: "or_rr_8",
683 into: "or_mr_8",
684 load: "mov_rm_8",
685 store: "mov_mr_8",
686 commutes: true,
687 },
688 Update {
689 from: "or_rr_16",
690 into: "or_mr_16",
691 load: "mov_rm_16",
692 store: "mov_mr_16",
693 commutes: true,
694 },
695 Update {
696 from: "or_rr_32",
697 into: "or_mr_32",
698 load: "mov_rm_32",
699 store: "mov_mr_32",
700 commutes: true,
701 },
702 Update {
703 from: "or_rr_64",
704 into: "or_mr_64",
705 load: "mov_rm_64",
706 store: "mov_mr_64",
707 commutes: true,
708 },
709 Update {
710 from: "xor_rr_8",
711 into: "xor_mr_8",
712 load: "mov_rm_8",
713 store: "mov_mr_8",
714 commutes: true,
715 },
716 Update {
717 from: "xor_rr_16",
718 into: "xor_mr_16",
719 load: "mov_rm_16",
720 store: "mov_mr_16",
721 commutes: true,
722 },
723 Update {
724 from: "xor_rr_32",
725 into: "xor_mr_32",
726 load: "mov_rm_32",
727 store: "mov_mr_32",
728 commutes: true,
729 },
730 Update {
731 from: "xor_rr_64",
732 into: "xor_mr_64",
733 load: "mov_rm_64",
734 store: "mov_mr_64",
735 commutes: true,
736 },
737];
738
739#[derive(Debug, Clone, Copy, PartialEq, Eq)]
748pub struct Bump {
749 pub from: &'static str,
751 pub into: &'static str,
753 pub load: &'static str,
755 pub store: &'static str,
757}
758
759pub static BUMPS: &[Bump] = &[
774 Bump { from: "add_ri_8", into: "add_mi_8", load: "mov_rm_8", store: "mov_mr_8" },
775 Bump { from: "add_ri_16", into: "add_mi_16", load: "mov_rm_16", store: "mov_mr_16" },
776 Bump { from: "add_ri_32", into: "add_mi_32", load: "mov_rm_32", store: "mov_mr_32" },
777 Bump { from: "add_ri_64", into: "add_mi_64", load: "mov_rm_64", store: "mov_mr_64" },
778 Bump { from: "sub_ri_8", into: "sub_mi_8", load: "mov_rm_8", store: "mov_mr_8" },
779 Bump { from: "sub_ri_16", into: "sub_mi_16", load: "mov_rm_16", store: "mov_mr_16" },
780 Bump { from: "sub_ri_32", into: "sub_mi_32", load: "mov_rm_32", store: "mov_mr_32" },
781 Bump { from: "sub_ri_64", into: "sub_mi_64", load: "mov_rm_64", store: "mov_mr_64" },
782 Bump { from: "and_ri_8", into: "and_mi_8", load: "mov_rm_8", store: "mov_mr_8" },
783 Bump { from: "and_ri_16", into: "and_mi_16", load: "mov_rm_16", store: "mov_mr_16" },
784 Bump { from: "and_ri_32", into: "and_mi_32", load: "mov_rm_32", store: "mov_mr_32" },
785 Bump { from: "and_ri_64", into: "and_mi_64", load: "mov_rm_64", store: "mov_mr_64" },
786 Bump { from: "or_ri_8", into: "or_mi_8", load: "mov_rm_8", store: "mov_mr_8" },
787 Bump { from: "or_ri_16", into: "or_mi_16", load: "mov_rm_16", store: "mov_mr_16" },
788 Bump { from: "or_ri_32", into: "or_mi_32", load: "mov_rm_32", store: "mov_mr_32" },
789 Bump { from: "or_ri_64", into: "or_mi_64", load: "mov_rm_64", store: "mov_mr_64" },
790 Bump { from: "xor_ri_8", into: "xor_mi_8", load: "mov_rm_8", store: "mov_mr_8" },
791 Bump { from: "xor_ri_16", into: "xor_mi_16", load: "mov_rm_16", store: "mov_mr_16" },
792 Bump { from: "xor_ri_32", into: "xor_mi_32", load: "mov_rm_32", store: "mov_mr_32" },
793 Bump { from: "xor_ri_64", into: "xor_mi_64", load: "mov_rm_64", store: "mov_mr_64" },
794];
795
796#[derive(Debug, Clone, Copy)]
801struct Waiting {
802 inst: Inst,
804 reg: Reg,
806 load: &'static str,
808 at: usize,
810}
811
812pub fn loads(
823 func: &mut Func,
824 machine: &MachineInsts,
825 names: &mut Interner,
826 pending: &mut Pending<'_>,
827) -> usize {
828 let mut reads = Reads::of(func);
829 let mut done = 0;
830 for block in func.blocks().collect::<Vec<_>>() {
831 let mut waiting: Option<Waiting> = None;
832 for (at, inst) in func.insts(block).collect::<Vec<_>>().into_iter().enumerate() {
833 let name = names.resolve(func[inst].opcode.name()).to_owned();
834 let bare = machine.bare(&name).to_owned();
835 let barrier = machine.calls(&name) || !machine.has(&name) || machine.touches_mem(&name);
841 if let Some(carried) = waiting {
842 if let Some(plan) = joined(func, &reads, carried, machine, names, inst, &bare) {
843 let mut set = Changes::new();
844 set.rewrite(inst, plan);
845 set.remove(carried.inst);
846 if set.commit(func, &mut reads, names, machine).is_ok() {
847 pending.moved(carried.inst, &[inst]);
848 waiting = None;
849 done += 1;
850 }
851 }
852 }
853 if barrier {
854 waiting = None;
855 }
856 if let Some(carried) = waiting {
857 if at - carried.at >= WINDOW || writes_what_it_reads(func, inst, &carried) {
858 waiting = None;
859 }
860 }
861 if let Some(load) = FOLDS.iter().find(|fold| fold.load == bare).map(|fold| fold.load) {
862 let operands = &func[func[inst].operands];
863 if let Some(first) = operands.first().filter(|operand| operand.role.is_def()) {
864 waiting = Some(Waiting { inst, reg: first.reg, load, at });
865 }
866 }
867 }
868 }
869 done
870}
871
872#[derive(Debug, Clone, Copy)]
874struct Run {
875 load: Inst,
877 alu: Inst,
879 store: Inst,
881 update: &'static Update,
883 kept: Operand,
885}
886
887#[derive(Debug, Clone, Copy)]
893struct Bumped {
894 load: Inst,
896 alu: Inst,
898 store: Inst,
900 bump: &'static Bump,
902 imm: i64,
904}
905
906pub fn stores(
923 func: &mut Func,
924 machine: &MachineInsts,
925 names: &mut Interner,
926 pending: &mut Pending<'_>,
927) -> usize {
928 let mut reads = Reads::of(func);
929 let mut done = 0;
930 for block in func.blocks().collect::<Vec<_>>() {
931 let insts: Vec<Inst> = func.insts(block).collect();
932 for at in 0..insts.len() {
933 let found = match run(func, &reads, machine, names, &insts, at) {
934 Some(found) => Some((
935 found.load,
936 found.alu,
937 found.store,
938 updated(func, machine, names, &found),
939 )),
940 None => constant(func, &reads, machine, names, &insts, at).map(|found| {
941 (found.load, found.alu, found.store, bumped(func, machine, names, &found))
942 }),
943 };
944 let Some((load, alu, store, plan)) = found else { continue };
945 if !pending.alike(load, store) {
946 continue;
947 }
948 let mut set = Changes::new();
949 set.rewrite(store, plan);
950 set.remove(alu);
951 set.remove(load);
952 if set.commit(func, &mut reads, names, machine).is_ok() {
953 pending.moved(load, &[]);
954 done += 1;
955 }
956 }
957 }
958 done
959}
960
961fn run(
973 func: &Func,
974 reads: &Reads,
975 machine: &MachineInsts,
976 names: &Interner,
977 insts: &[Inst],
978 at: usize,
979) -> Option<Run> {
980 let store = insts[at];
981 let stored = machine.bare(names.resolve(func[store].opcode.name())).to_owned();
982 let value = *func[func[store].operands].first()?;
983 if value.role.is_def() || reads.count(value.reg) != 1 {
984 return None;
985 }
986 let earliest = at.saturating_sub(WINDOW);
989 let alu = (earliest..at).rev().find(|&k| writes(func, insts[k], value.reg))?;
990 let bare = machine.bare(names.resolve(func[insts[alu]].opcode.name())).to_owned();
991 let update = UPDATES.iter().find(|row| row.from == bare && row.store == stored)?;
992 let operands = func[func[insts[alu]].operands].to_vec();
993 let [_, first, second] = operands[..] else { return None };
994 let both = [(first, second), (second, first)];
999 let tried = if update.commutes { &both[..] } else { &both[..1] };
1000 for &(source, kept) in tried {
1001 if reads.count(source.reg) != 1 {
1002 continue;
1003 }
1004 let Some(from) = (earliest..alu).rev().find(|&k| writes(func, insts[k], source.reg)) else {
1005 continue;
1006 };
1007 let load = insts[from];
1008 if machine.bare(names.resolve(func[load].opcode.name())) != update.load {
1009 continue;
1010 }
1011 if !same_place(func, load, store) {
1012 continue;
1013 }
1014 let mut wanted: Vec<Reg> =
1019 func[func[store].operands][1..].iter().map(|operand| operand.reg).collect();
1020 wanted.push(kept.reg);
1021 if !clear(func, machine, names, insts, (from, at), &wanted) {
1022 continue;
1023 }
1024 return Some(Run { load, alu: insts[alu], store, update, kept });
1025 }
1026 None
1027}
1028
1029fn constant(
1043 func: &Func,
1044 reads: &Reads,
1045 machine: &MachineInsts,
1046 names: &Interner,
1047 insts: &[Inst],
1048 at: usize,
1049) -> Option<Bumped> {
1050 let store = insts[at];
1051 let stored = machine.bare(names.resolve(func[store].opcode.name())).to_owned();
1052 let value = *func[func[store].operands].first()?;
1053 if value.role.is_def() || reads.count(value.reg) != 1 {
1054 return None;
1055 }
1056 let mem = func[func[store].mem?];
1057 if mem.base == Some(0) || mem.index == Some(0) {
1058 return None;
1059 }
1060 let earliest = at.saturating_sub(WINDOW);
1061 let alu = (earliest..at).rev().find(|&k| writes(func, insts[k], value.reg))?;
1062 let bare = machine.bare(names.resolve(func[insts[alu]].opcode.name())).to_owned();
1063 let bump = BUMPS.iter().find(|row| row.from == bare && row.store == stored)?;
1064 let operands = func[func[insts[alu]].operands].to_vec();
1065 let [_, source] = operands[..] else { return None };
1066 let imm = func[func[insts[alu]].imm?].0;
1067 if reads.count(source.reg) != 1 {
1068 return None;
1069 }
1070 let from = (earliest..alu).rev().find(|&k| writes(func, insts[k], source.reg))?;
1071 let load = insts[from];
1072 if machine.bare(names.resolve(func[load].opcode.name())) != bump.load {
1073 return None;
1074 }
1075 if !same_place(func, load, store) {
1076 return None;
1077 }
1078 let wanted: Vec<Reg> =
1081 func[func[store].operands][1..].iter().map(|operand| operand.reg).collect();
1082 if !clear(func, machine, names, insts, (from, at), &wanted) {
1083 return None;
1084 }
1085 Some(Bumped { load, alu: insts[alu], store, bump, imm })
1086}
1087
1088fn writes(func: &Func, inst: Inst, reg: Reg) -> bool {
1090 func[func[inst].operands].iter().any(|operand| operand.role.is_def() && operand.reg == reg)
1091}
1092
1093fn same_place(func: &Func, one: Inst, other: Inst) -> bool {
1100 let (Some(here), Some(there)) = (func[one].mem, func[other].mem) else { return false };
1101 let (here, there) = (func[here], func[there]);
1102 if func[one].symbol != func[other].symbol {
1103 return false;
1104 }
1105 let bare = |amode: Amode| Amode { base: None, index: None, ..amode };
1106 if bare(here) != bare(there) {
1107 return false;
1108 }
1109 let same = |left: Option<u8>, right: Option<u8>| match (left, right) {
1110 (None, None) => true,
1111 (Some(left), Some(right)) => {
1112 func[func[one].operands][usize::from(left)].reg
1113 == func[func[other].operands][usize::from(right)].reg
1114 }
1115 _ => false,
1116 };
1117 same(here.base, there.base) && same(here.index, there.index)
1118}
1119
1120fn clear(
1127 func: &Func,
1128 machine: &MachineInsts,
1129 names: &Interner,
1130 insts: &[Inst],
1131 span: (usize, usize),
1132 wanted: &[Reg],
1133) -> bool {
1134 let (from, to) = span;
1135 insts[from + 1..to].iter().all(|&inst| {
1136 let name = names.resolve(func[inst].opcode.name());
1137 if machine.calls(name) || !machine.has(name) || machine.touches_mem(name) {
1138 return false;
1139 }
1140 !func[func[inst].operands]
1141 .iter()
1142 .any(|operand| operand.role.is_def() && wanted.contains(&operand.reg))
1143 })
1144}
1145
1146fn updated(func: &Func, machine: &MachineInsts, names: &mut Interner, run: &Run) -> Plan {
1152 let operands = func[func[run.store].operands].to_vec();
1153 let into = names.intern(&format!("{}{}", machine.prefix, run.update.into));
1154 Plan {
1155 opcode: Opcode::new(into),
1156 operands: [run.kept].into_iter().chain(operands[1..].iter().copied()).collect(),
1157 imm: None,
1158 amode: func[run.store].mem.map(|mem| func[mem]),
1159 symbol: func[run.store].symbol,
1160 }
1161}
1162
1163fn bumped(func: &Func, machine: &MachineInsts, names: &mut Interner, run: &Bumped) -> Plan {
1171 let operands = func[func[run.store].operands][1..].to_vec();
1172 let into = names.intern(&format!("{}{}", machine.prefix, run.bump.into));
1173 let back = |at: Option<u8>| at.map(|at| at - 1);
1174 Plan {
1175 opcode: Opcode::new(into),
1176 operands,
1177 imm: Some(run.imm),
1178 amode: func[run.store].mem.map(|mem| {
1179 let mem = func[mem];
1180 Amode { base: back(mem.base), index: back(mem.index), ..mem }
1181 }),
1182 symbol: func[run.store].symbol,
1183 }
1184}
1185
1186fn writes_what_it_reads(func: &Func, inst: Inst, carried: &Waiting) -> bool {
1192 let written: Vec<Reg> = func[func[inst].operands]
1193 .iter()
1194 .filter(|operand| operand.role.is_def())
1195 .map(|operand| operand.reg)
1196 .collect();
1197 func[func[carried.inst].operands].iter().any(|operand| written.contains(&operand.reg))
1198}
1199
1200fn joined(
1205 func: &Func,
1206 reads: &Reads,
1207 carried: Waiting,
1208 machine: &MachineInsts,
1209 names: &mut Interner,
1210 inst: Inst,
1211 bare: &str,
1212) -> Option<Plan> {
1213 let fold = FOLDS.iter().find(|fold| fold.from == bare)?;
1214 if carried.load != fold.load || reads.count(carried.reg) != 1 {
1215 return None;
1216 }
1217 let operands = func[func[inst].operands].to_vec();
1218 let [answer, first, second] = operands[..] else { return None };
1219 let (kept, into) = if second.reg == carried.reg {
1226 (first, fold.into)
1227 } else if first.reg == carried.reg {
1228 (second, fold.swapped?)
1229 } else {
1230 return None;
1231 };
1232 let load = carried.inst;
1233 let address = func[func[load].operands][1..].to_vec();
1234 let mut amode = func[func[load].mem?];
1235 amode.base = amode.base.map(|at| at + 1);
1239 amode.index = amode.index.map(|at| at + 1);
1240 let into = names.intern(&format!("{}{}", machine.prefix, into));
1241 Some(Plan {
1242 opcode: Opcode::new(into),
1243 operands: [answer, kept].into_iter().chain(address).collect(),
1244 imm: None,
1245 amode: Some(amode),
1246 symbol: func[load].symbol,
1247 })
1248}
1249
1250#[cfg(test)]
1251mod tests {
1252 use rucc_mir::{self as mir, Constraint, Mem, Operand};
1253 use rucc_target::x86_64::{GPR, MACHINE};
1254
1255 use super::*;
1256
1257 fn empty() -> (Interner, Func, mir::Block) {
1259 let mut names = Interner::new();
1260 let mut func = Func::new(names.intern("f"));
1261 let block = func.create_block();
1262 (names, func, block)
1263 }
1264
1265 fn op(names: &mut Interner, name: &str) -> Opcode {
1267 Opcode::new(names.intern(&format!("{}{name}", MACHINE.prefix)))
1268 }
1269
1270 fn load(func: &mut Func, names: &mut Interner, block: mir::Block, base: Reg) -> Reg {
1272 let into = func.new_vreg(GPR);
1273 let mov = op(names, "mov_rm_64");
1274 func.build(block, mov)
1275 .def(into, GPR)
1276 .mem(Mem { disp: 16, ..Mem::at(Operand::read(base, GPR)) })
1277 .finish();
1278 into
1279 }
1280
1281 fn alu(
1283 func: &mut Func,
1284 names: &mut Interner,
1285 block: mir::Block,
1286 name: &str,
1287 first: Reg,
1288 second: Reg,
1289 ) -> Reg {
1290 let answer = func.new_vreg(GPR);
1291 let opcode = op(names, name);
1292 func.build(block, opcode)
1293 .operand(Operand::write(answer, GPR).with(Constraint::Reuse(1)))
1294 .uses(first, GPR)
1295 .uses(second, GPR)
1296 .finish();
1297 answer
1298 }
1299
1300 fn compare(
1303 func: &mut Func,
1304 names: &mut Interner,
1305 block: mir::Block,
1306 name: &str,
1307 first: Reg,
1308 second: Reg,
1309 ) -> Reg {
1310 let byte = func.new_vreg(GPR);
1311 let opcode = op(names, name);
1312 func.build(block, opcode).def(byte, GPR).uses(first, GPR).uses(second, GPR).finish();
1313 byte
1314 }
1315
1316 fn shape(func: &Func, names: &Interner, block: mir::Block) -> Vec<String> {
1318 func.insts(block).map(|inst| names.resolve(func[inst].opcode.name()).to_owned()).collect()
1319 }
1320
1321 fn combine(func: &mut Func, names: &mut Interner) -> usize {
1323 let mut addresses = Vec::new();
1324 let mut arguments = Vec::new();
1325 let mut dynamic = Vec::new();
1326 let mut pending =
1327 Pending { addresses: &mut addresses, arguments: &mut arguments, dynamic: &mut dynamic };
1328 loads(func, &MACHINE, names, &mut pending)
1329 }
1330
1331 fn store(func: &mut Func, names: &mut Interner, block: mir::Block, base: Reg, value: Reg) {
1333 let mov = op(names, "mov_mr_64");
1334 func.build(block, mov)
1335 .uses(value, GPR)
1336 .mem(Mem { disp: 16, ..Mem::at(Operand::read(base, GPR)) })
1337 .finish();
1338 }
1339
1340 fn update(func: &mut Func, names: &mut Interner) -> usize {
1342 let mut addresses = Vec::new();
1343 let mut arguments = Vec::new();
1344 let mut dynamic = Vec::new();
1345 let mut pending =
1346 Pending { addresses: &mut addresses, arguments: &mut arguments, dynamic: &mut dynamic };
1347 stores(func, &MACHINE, names, &mut pending)
1348 }
1349
1350 #[test]
1352 fn a_word_read_changed_and_written_back_becomes_one_instruction() {
1353 let (mut names, mut func, block) = empty();
1354 let base = func.new_vreg(GPR);
1355 let other = func.new_vreg(GPR);
1356 let word = load(&mut func, &mut names, block, base);
1357 let sum = alu(&mut func, &mut names, block, "add_rr_64", word, other);
1358 store(&mut func, &mut names, block, base, sum);
1359
1360 assert_eq!(update(&mut func, &mut names), 1);
1361 assert_eq!(shape(&func, &names, block), ["x64.add_mr_64"]);
1362 let inst = func.insts(block).next().expect("the addition");
1363 let mem = func[inst].mem.expect("it writes memory");
1364 assert_eq!(func[mem].disp, 16, "the address came from the store");
1365 assert_eq!(func[mem].base, Some(1), "and names the operand behind the source");
1366 assert_eq!(func[func[inst].operands].len(), 2, "one source and the base of the address");
1367 assert_eq!(func[func[inst].operands][0].reg, other, "the source it kept");
1368 assert_eq!(func[func[inst].operands][1].reg, base, "the address");
1369 }
1370
1371 #[test]
1375 fn a_word_read_into_the_right_source_of_an_addition_is_still_one_instruction() {
1376 let (mut names, mut func, block) = empty();
1377 let base = func.new_vreg(GPR);
1378 let other = func.new_vreg(GPR);
1379 let word = load(&mut func, &mut names, block, base);
1380 let sum = alu(&mut func, &mut names, block, "add_rr_64", other, word);
1381 store(&mut func, &mut names, block, base, sum);
1382
1383 assert_eq!(update(&mut func, &mut names), 1);
1384 assert_eq!(shape(&func, &names, block), ["x64.add_mr_64"]);
1385 assert_eq!(func[func[func.insts(block).next().expect("it")].operands][0].reg, other);
1386 }
1387
1388 #[test]
1391 fn a_subtraction_taking_a_register_away_from_memory_becomes_one_instruction() {
1392 let (mut names, mut func, block) = empty();
1393 let base = func.new_vreg(GPR);
1394 let other = func.new_vreg(GPR);
1395 let word = load(&mut func, &mut names, block, base);
1396 let left = alu(&mut func, &mut names, block, "sub_rr_64", word, other);
1397 store(&mut func, &mut names, block, base, left);
1398
1399 assert_eq!(update(&mut func, &mut names), 1);
1400 assert_eq!(shape(&func, &names, block), ["x64.sub_mr_64"]);
1401 }
1402
1403 #[test]
1406 fn a_subtraction_taking_memory_away_from_a_register_stays_three_instructions() {
1407 let (mut names, mut func, block) = empty();
1408 let base = func.new_vreg(GPR);
1409 let other = func.new_vreg(GPR);
1410 let word = load(&mut func, &mut names, block, base);
1411 let left = alu(&mut func, &mut names, block, "sub_rr_64", other, word);
1412 store(&mut func, &mut names, block, base, left);
1413
1414 assert_eq!(update(&mut func, &mut names), 0);
1415 assert_eq!(
1416 shape(&func, &names, block),
1417 ["x64.mov_rm_64", "x64.sub_rr_64", "x64.mov_mr_64"]
1418 );
1419 }
1420
1421 #[test]
1424 fn a_store_to_another_address_stays_three_instructions() {
1425 let (mut names, mut func, block) = empty();
1426 let base = func.new_vreg(GPR);
1427 let elsewhere = func.new_vreg(GPR);
1428 let other = func.new_vreg(GPR);
1429 let word = load(&mut func, &mut names, block, base);
1430 let sum = alu(&mut func, &mut names, block, "add_rr_64", word, other);
1431 store(&mut func, &mut names, block, elsewhere, sum);
1432
1433 assert_eq!(update(&mut func, &mut names), 0);
1434 }
1435
1436 #[test]
1439 fn a_store_at_another_displacement_stays_three_instructions() {
1440 let (mut names, mut func, block) = empty();
1441 let base = func.new_vreg(GPR);
1442 let other = func.new_vreg(GPR);
1443 let word = load(&mut func, &mut names, block, base);
1444 let sum = alu(&mut func, &mut names, block, "add_rr_64", word, other);
1445 let mov = op(&mut names, "mov_mr_64");
1446 func.build(block, mov)
1447 .uses(sum, GPR)
1448 .mem(Mem { disp: 24, ..Mem::at(Operand::read(base, GPR)) })
1449 .finish();
1450
1451 assert_eq!(update(&mut func, &mut names), 0);
1452 }
1453
1454 #[test]
1457 fn a_word_two_instructions_read_stays_three_instructions() {
1458 let (mut names, mut func, block) = empty();
1459 let base = func.new_vreg(GPR);
1460 let other = func.new_vreg(GPR);
1461 let word = load(&mut func, &mut names, block, base);
1462 let sum = alu(&mut func, &mut names, block, "add_rr_64", word, other);
1463 alu(&mut func, &mut names, block, "xor_rr_64", word, other);
1464 store(&mut func, &mut names, block, base, sum);
1465
1466 assert_eq!(update(&mut func, &mut names), 0);
1467 }
1468
1469 #[test]
1472 fn an_answer_something_else_reads_stays_three_instructions() {
1473 let (mut names, mut func, block) = empty();
1474 let base = func.new_vreg(GPR);
1475 let other = func.new_vreg(GPR);
1476 let word = load(&mut func, &mut names, block, base);
1477 let sum = alu(&mut func, &mut names, block, "add_rr_64", word, other);
1478 store(&mut func, &mut names, block, base, sum);
1479 alu(&mut func, &mut names, block, "xor_rr_64", sum, other);
1480
1481 assert_eq!(update(&mut func, &mut names), 0);
1482 }
1483
1484 #[test]
1487 fn a_run_with_another_access_in_the_middle_stays_three_instructions() {
1488 let (mut names, mut func, block) = empty();
1489 let base = func.new_vreg(GPR);
1490 let other = func.new_vreg(GPR);
1491 let word = load(&mut func, &mut names, block, base);
1492 load(&mut func, &mut names, block, other);
1493 let sum = alu(&mut func, &mut names, block, "add_rr_64", word, other);
1494 store(&mut func, &mut names, block, base, sum);
1495
1496 assert_eq!(update(&mut func, &mut names), 0);
1497 }
1498
1499 #[test]
1502 fn a_run_whose_address_register_is_written_in_the_middle_stays_three_instructions() {
1503 let (mut names, mut func, block) = empty();
1504 let base = Reg::physical(rucc_target::x86_64::RSP);
1505 let other = func.new_vreg(GPR);
1506 let word = load(&mut func, &mut names, block, base);
1507 let sub = op(&mut names, "sub_ri_64");
1508 func.build(block, sub)
1509 .operand(Operand::write(base, GPR).with(Constraint::Reuse(1)))
1510 .uses(base, GPR)
1511 .imm(32)
1512 .finish();
1513 let sum = alu(&mut func, &mut names, block, "add_rr_64", word, other);
1514 store(&mut func, &mut names, block, base, sum);
1515
1516 assert_eq!(update(&mut func, &mut names), 0);
1517 }
1518
1519 #[test]
1523 fn two_locals_the_layout_has_not_placed_yet_are_not_the_same_place() {
1524 let (mut names, mut func, block) = empty();
1525 let base = Reg::physical(rucc_target::x86_64::RSP);
1526 let other = func.new_vreg(GPR);
1527 let mov = op(&mut names, "mov_rm_64");
1528 let word = func.new_vreg(GPR);
1529 func.build(block, mov).def(word, GPR).mem(Mem::at(Operand::read(base, GPR))).finish();
1530 let read = func.insts(block).next().expect("the load");
1531 let sum = alu(&mut func, &mut names, block, "add_rr_64", word, other);
1532 let put = op(&mut names, "mov_mr_64");
1533 func.build(block, put).uses(sum, GPR).mem(Mem::at(Operand::read(base, GPR))).finish();
1534 let written = func.insts(block).nth(2).expect("the store");
1535
1536 let mut addresses = vec![(read, 3usize), (written, 4usize)];
1537 let mut arguments = Vec::new();
1538 let mut dynamic = Vec::new();
1539 let mut pending =
1540 Pending { addresses: &mut addresses, arguments: &mut arguments, dynamic: &mut dynamic };
1541 assert_eq!(stores(&mut func, &MACHINE, &mut names, &mut pending), 0);
1542 }
1543
1544 #[test]
1548 fn the_frame_entry_of_a_load_that_goes_comes_off_the_list() {
1549 let (mut names, mut func, block) = empty();
1550 let base = Reg::physical(rucc_target::x86_64::RSP);
1551 let other = func.new_vreg(GPR);
1552 let mov = op(&mut names, "mov_rm_64");
1553 let word = func.new_vreg(GPR);
1554 func.build(block, mov).def(word, GPR).mem(Mem::at(Operand::read(base, GPR))).finish();
1555 let read = func.insts(block).next().expect("the load");
1556 let sum = alu(&mut func, &mut names, block, "add_rr_64", word, other);
1557 let put = op(&mut names, "mov_mr_64");
1558 func.build(block, put).uses(sum, GPR).mem(Mem::at(Operand::read(base, GPR))).finish();
1559 let written = func.insts(block).nth(2).expect("the store");
1560
1561 let mut addresses = vec![(read, 3usize), (written, 3usize)];
1562 let mut arguments = Vec::new();
1563 let mut dynamic = Vec::new();
1564 let mut pending =
1565 Pending { addresses: &mut addresses, arguments: &mut arguments, dynamic: &mut dynamic };
1566 assert_eq!(stores(&mut func, &MACHINE, &mut names, &mut pending), 1);
1567
1568 let inst = func.insts(block).next().expect("the addition");
1569 assert_eq!(addresses, [(inst, 3usize)], "one entry, on the instruction that is left");
1570 }
1571
1572 #[test]
1574 fn a_run_whose_widths_disagree_stays_three_instructions() {
1575 let (mut names, mut func, block) = empty();
1576 let base = func.new_vreg(GPR);
1577 let other = func.new_vreg(GPR);
1578 let into = func.new_vreg(GPR);
1579 let narrow = op(&mut names, "mov_rm_32");
1580 func.build(block, narrow)
1581 .def(into, GPR)
1582 .mem(Mem { disp: 16, ..Mem::at(Operand::read(base, GPR)) })
1583 .finish();
1584 let sum = alu(&mut func, &mut names, block, "add_rr_64", into, other);
1585 store(&mut func, &mut names, block, base, sum);
1586
1587 assert_eq!(update(&mut func, &mut names), 0);
1588 }
1589
1590 #[test]
1592 fn every_row_of_the_update_table_is_four_instructions_this_target_has() {
1593 for update in UPDATES {
1594 for name in [update.from, update.into, update.load, update.store] {
1595 assert!(MACHINE.has(name), "{name} is not an instruction");
1596 }
1597 let width = |name: &str| name.rsplit_once('_').map(|(_, width)| width.to_owned());
1598 assert_eq!(width(update.from), width(update.into), "{} changes width", update.from);
1599 assert_eq!(
1600 width(update.from),
1601 width(update.load),
1602 "{} loads another width",
1603 update.from
1604 );
1605 assert_eq!(
1606 width(update.from),
1607 width(update.store),
1608 "{} stores another width",
1609 update.from
1610 );
1611 assert!((MACHINE.takes_mem)(update.into), "{} reaches no memory", update.into);
1612 assert!(!(MACHINE.takes_mem)(update.from), "{} already reaches memory", update.from);
1613 }
1614 }
1615
1616 #[test]
1619 fn the_update_table_covers_the_arithmetic_this_target_can_do_in_place() {
1620 assert_eq!(UPDATES.len(), 20, "five operations at four widths, and no multiply");
1621 let commuting = UPDATES.iter().filter(|update| update.commutes).count();
1622 assert_eq!(commuting, 16, "everything but the four subtractions");
1623 }
1624
1625 fn alu_imm(
1627 func: &mut Func,
1628 names: &mut Interner,
1629 block: mir::Block,
1630 name: &str,
1631 source: Reg,
1632 value: i64,
1633 ) -> Reg {
1634 let answer = func.new_vreg(GPR);
1635 let opcode = op(names, name);
1636 func.build(block, opcode)
1637 .operand(Operand::write(answer, GPR).with(Constraint::Reuse(1)))
1638 .uses(source, GPR)
1639 .imm(value)
1640 .finish();
1641 answer
1642 }
1643
1644 #[test]
1646 fn a_word_read_changed_by_a_constant_and_written_back_becomes_one_instruction() {
1647 let (mut names, mut func, block) = empty();
1648 let base = func.new_vreg(GPR);
1649 let word = load(&mut func, &mut names, block, base);
1650 let sum = alu_imm(&mut func, &mut names, block, "add_ri_64", word, 1);
1651 store(&mut func, &mut names, block, base, sum);
1652
1653 assert_eq!(update(&mut func, &mut names), 1);
1654 assert_eq!(shape(&func, &names, block), ["x64.add_mi_64"]);
1655 let inst = func.insts(block).next().expect("the addition");
1656 let mem = func[inst].mem.expect("it writes memory");
1657 assert_eq!(func[mem].disp, 16, "the address came from the store");
1658 assert_eq!(func[mem].base, Some(0), "which is now the first operand and not the second");
1659 assert_eq!(func[func[inst].operands].len(), 1, "the base of the address and nothing else");
1660 assert_eq!(func[func[inst].operands][0].reg, base, "the address");
1661 assert_eq!(func[func[inst].imm.expect("the constant")].0, 1);
1662 }
1663
1664 #[test]
1667 fn a_constant_taken_away_from_a_place_becomes_one_instruction() {
1668 let (mut names, mut func, block) = empty();
1669 let base = func.new_vreg(GPR);
1670 let word = load(&mut func, &mut names, block, base);
1671 let left = alu_imm(&mut func, &mut names, block, "sub_ri_64", word, 7);
1672 store(&mut func, &mut names, block, base, left);
1673
1674 assert_eq!(update(&mut func, &mut names), 1);
1675 assert_eq!(shape(&func, &names, block), ["x64.sub_mi_64"]);
1676 assert_eq!(func[func[func.insts(block).next().expect("it")].imm.expect("it")].0, 7);
1677 }
1678
1679 #[test]
1682 fn a_byte_read_changed_by_a_constant_and_written_back_becomes_one_instruction() {
1683 let (mut names, mut func, block) = empty();
1684 let base = func.new_vreg(GPR);
1685 let word = func.new_vreg(GPR);
1686 let mov = op(&mut names, "mov_rm_8");
1687 func.build(block, mov)
1688 .def(word, GPR)
1689 .mem(Mem { disp: 16, ..Mem::at(Operand::read(base, GPR)) })
1690 .finish();
1691 let sum = alu_imm(&mut func, &mut names, block, "or_ri_8", word, 4);
1692 let put = op(&mut names, "mov_mr_8");
1693 func.build(block, put)
1694 .uses(sum, GPR)
1695 .mem(Mem { disp: 16, ..Mem::at(Operand::read(base, GPR)) })
1696 .finish();
1697
1698 assert_eq!(update(&mut func, &mut names), 1);
1699 assert_eq!(shape(&func, &names, block), ["x64.or_mi_8"]);
1700 }
1701
1702 #[test]
1705 fn a_word_a_constant_changes_and_something_else_reads_stays_three_instructions() {
1706 let (mut names, mut func, block) = empty();
1707 let base = func.new_vreg(GPR);
1708 let other = func.new_vreg(GPR);
1709 let word = load(&mut func, &mut names, block, base);
1710 let sum = alu_imm(&mut func, &mut names, block, "add_ri_64", word, 1);
1711 alu(&mut func, &mut names, block, "xor_rr_64", word, other);
1712 store(&mut func, &mut names, block, base, sum);
1713
1714 assert_eq!(update(&mut func, &mut names), 0);
1715 }
1716
1717 #[test]
1720 fn a_constant_run_with_another_access_in_the_middle_stays_three_instructions() {
1721 let (mut names, mut func, block) = empty();
1722 let base = func.new_vreg(GPR);
1723 let elsewhere = func.new_vreg(GPR);
1724 let word = load(&mut func, &mut names, block, base);
1725 let sum = alu_imm(&mut func, &mut names, block, "add_ri_64", word, 1);
1726 load(&mut func, &mut names, block, elsewhere);
1727 store(&mut func, &mut names, block, base, sum);
1728
1729 assert_eq!(update(&mut func, &mut names), 0);
1730 }
1731
1732 #[test]
1735 fn a_constant_run_whose_address_register_is_written_in_the_middle_stays_three_instructions() {
1736 let (mut names, mut func, block) = empty();
1737 let base = Reg::physical(rucc_target::x86_64::RAX);
1738 let word = load(&mut func, &mut names, block, base);
1739 let sum = alu_imm(&mut func, &mut names, block, "add_ri_64", word, 1);
1740 let mov = op(&mut names, "mov_ri_64");
1741 func.build(block, mov).def(base, GPR).imm(0).finish();
1742 store(&mut func, &mut names, block, base, sum);
1743
1744 assert_eq!(update(&mut func, &mut names), 0);
1745 }
1746
1747 #[test]
1749 fn a_constant_written_to_another_address_stays_three_instructions() {
1750 let (mut names, mut func, block) = empty();
1751 let base = func.new_vreg(GPR);
1752 let elsewhere = func.new_vreg(GPR);
1753 let word = load(&mut func, &mut names, block, base);
1754 let sum = alu_imm(&mut func, &mut names, block, "add_ri_64", word, 1);
1755 store(&mut func, &mut names, block, elsewhere, sum);
1756
1757 assert_eq!(update(&mut func, &mut names), 0);
1758 }
1759
1760 #[test]
1762 fn a_constant_run_whose_widths_disagree_stays_three_instructions() {
1763 let (mut names, mut func, block) = empty();
1764 let base = func.new_vreg(GPR);
1765 let into = func.new_vreg(GPR);
1766 let narrow = op(&mut names, "mov_rm_32");
1767 func.build(block, narrow)
1768 .def(into, GPR)
1769 .mem(Mem { disp: 16, ..Mem::at(Operand::read(base, GPR)) })
1770 .finish();
1771 let sum = alu_imm(&mut func, &mut names, block, "add_ri_64", into, 1);
1772 store(&mut func, &mut names, block, base, sum);
1773
1774 assert_eq!(update(&mut func, &mut names), 0);
1775 }
1776
1777 #[test]
1780 fn a_place_multiplied_by_a_constant_stays_three_instructions() {
1781 let (mut names, mut func, block) = empty();
1782 let base = func.new_vreg(GPR);
1783 let word = load(&mut func, &mut names, block, base);
1784 let product = alu_imm(&mut func, &mut names, block, "imul_ri_64", word, 3);
1785 store(&mut func, &mut names, block, base, product);
1786
1787 assert_eq!(update(&mut func, &mut names), 0);
1788 }
1789
1790 #[test]
1793 fn the_frame_entry_of_a_load_a_constant_run_takes_comes_off_the_list() {
1794 let (mut names, mut func, block) = empty();
1795 let base = Reg::physical(rucc_target::x86_64::RSP);
1796 let mov = op(&mut names, "mov_rm_64");
1797 let word = func.new_vreg(GPR);
1798 func.build(block, mov).def(word, GPR).mem(Mem::at(Operand::read(base, GPR))).finish();
1799 let read = func.insts(block).next().expect("the load");
1800 let sum = alu_imm(&mut func, &mut names, block, "add_ri_64", word, 1);
1801 let put = op(&mut names, "mov_mr_64");
1802 func.build(block, put).uses(sum, GPR).mem(Mem::at(Operand::read(base, GPR))).finish();
1803 let written = func.insts(block).nth(2).expect("the store");
1804
1805 let mut addresses = vec![(read, 3usize), (written, 3usize)];
1806 let mut arguments = Vec::new();
1807 let mut dynamic = Vec::new();
1808 let mut pending =
1809 Pending { addresses: &mut addresses, arguments: &mut arguments, dynamic: &mut dynamic };
1810 assert_eq!(stores(&mut func, &MACHINE, &mut names, &mut pending), 1);
1811
1812 let inst = func.insts(block).next().expect("the addition");
1813 assert_eq!(addresses, [(inst, 3usize)], "one entry, on the instruction that is left");
1814 }
1815
1816 #[test]
1819 fn two_locals_a_constant_run_would_join_are_not_the_same_place() {
1820 let (mut names, mut func, block) = empty();
1821 let base = Reg::physical(rucc_target::x86_64::RSP);
1822 let mov = op(&mut names, "mov_rm_64");
1823 let word = func.new_vreg(GPR);
1824 func.build(block, mov).def(word, GPR).mem(Mem::at(Operand::read(base, GPR))).finish();
1825 let read = func.insts(block).next().expect("the load");
1826 let sum = alu_imm(&mut func, &mut names, block, "add_ri_64", word, 1);
1827 let put = op(&mut names, "mov_mr_64");
1828 func.build(block, put).uses(sum, GPR).mem(Mem::at(Operand::read(base, GPR))).finish();
1829 let written = func.insts(block).nth(2).expect("the store");
1830
1831 let mut addresses = vec![(read, 3usize), (written, 4usize)];
1832 let mut arguments = Vec::new();
1833 let mut dynamic = Vec::new();
1834 let mut pending =
1835 Pending { addresses: &mut addresses, arguments: &mut arguments, dynamic: &mut dynamic };
1836 assert_eq!(stores(&mut func, &MACHINE, &mut names, &mut pending), 0);
1837 }
1838
1839 #[test]
1841 fn every_row_of_the_bump_table_is_four_instructions_this_target_has() {
1842 for bump in BUMPS {
1843 for name in [bump.from, bump.into, bump.load, bump.store] {
1844 assert!(MACHINE.has(name), "{name} is not an instruction");
1845 }
1846 let width = |name: &str| name.rsplit_once('_').map(|(_, width)| width.to_owned());
1847 assert_eq!(width(bump.from), width(bump.into), "{} changes width", bump.from);
1848 assert_eq!(width(bump.from), width(bump.load), "{} loads another width", bump.from);
1849 assert_eq!(width(bump.from), width(bump.store), "{} stores another width", bump.from);
1850 assert!((MACHINE.takes_mem)(bump.into), "{} reaches no memory", bump.into);
1851 assert!(!(MACHINE.takes_mem)(bump.from), "{} already reaches memory", bump.from);
1852 assert!((MACHINE.takes_imm)(bump.into), "{} carries no constant", bump.into);
1853 }
1854 }
1855
1856 #[test]
1859 fn the_bump_table_covers_the_arithmetic_this_target_can_do_in_place_against_a_constant() {
1860 assert_eq!(BUMPS.len(), 20, "five operations at four widths, and no multiply");
1861 let register: Vec<&str> = UPDATES.iter().map(|update| update.from).collect();
1862 for bump in BUMPS {
1863 let same = bump.from.replace("_ri_", "_rr_");
1864 assert!(register.contains(&same.as_str()), "{} has no register row", bump.from);
1865 }
1866 }
1867
1868 #[test]
1871 fn nothing_is_both_a_register_run_and_a_constant_run() {
1872 for bump in BUMPS {
1873 assert!(
1874 !UPDATES.iter().any(|update| update.from == bump.from),
1875 "{} starts both kinds of run",
1876 bump.from
1877 );
1878 }
1879 }
1880
1881 #[test]
1883 fn a_load_read_once_by_an_addition_becomes_its_memory_operand() {
1884 let (mut names, mut func, block) = empty();
1885 let base = func.new_vreg(GPR);
1886 let other = func.new_vreg(GPR);
1887 let word = load(&mut func, &mut names, block, base);
1888 alu(&mut func, &mut names, block, "add_rr_64", other, word);
1889
1890 assert_eq!(combine(&mut func, &mut names), 1);
1891 assert_eq!(shape(&func, &names, block), ["x64.add_rm_64"]);
1892 let inst = func.insts(block).next().expect("the addition");
1893 let mem = func[inst].mem.expect("the addition reads memory now");
1894 assert_eq!(func[mem].disp, 16, "the load's displacement came with it");
1895 assert_eq!(func[mem].base, Some(2), "and names the operand behind the source it kept");
1896 assert_eq!(func[func[inst].operands][1].reg, other, "the source it kept");
1897 assert_eq!(func[func[inst].operands][2].reg, base, "the address it took on");
1898 }
1899
1900 #[test]
1903 fn a_load_feeding_the_first_source_of_an_addition_is_swapped_and_folded() {
1904 let (mut names, mut func, block) = empty();
1905 let base = func.new_vreg(GPR);
1906 let other = func.new_vreg(GPR);
1907 let word = load(&mut func, &mut names, block, base);
1908 alu(&mut func, &mut names, block, "add_rr_64", word, other);
1909
1910 assert_eq!(combine(&mut func, &mut names), 1);
1911 assert_eq!(shape(&func, &names, block), ["x64.add_rm_64"]);
1912 let inst = func.insts(block).next().expect("the addition");
1913 assert_eq!(func[func[inst].operands][1].reg, other);
1914 }
1915
1916 #[test]
1919 fn a_load_feeding_the_left_of_a_subtraction_stays_a_load() {
1920 let (mut names, mut func, block) = empty();
1921 let base = func.new_vreg(GPR);
1922 let other = func.new_vreg(GPR);
1923 let word = load(&mut func, &mut names, block, base);
1924 alu(&mut func, &mut names, block, "sub_rr_64", word, other);
1925
1926 assert_eq!(combine(&mut func, &mut names), 0);
1927 assert_eq!(shape(&func, &names, block), ["x64.mov_rm_64", "x64.sub_rr_64"]);
1928 }
1929
1930 #[test]
1932 fn a_load_feeding_the_right_of_a_subtraction_folds() {
1933 let (mut names, mut func, block) = empty();
1934 let base = func.new_vreg(GPR);
1935 let other = func.new_vreg(GPR);
1936 let word = load(&mut func, &mut names, block, base);
1937 alu(&mut func, &mut names, block, "sub_rr_64", other, word);
1938
1939 assert_eq!(combine(&mut func, &mut names), 1);
1940 assert_eq!(shape(&func, &names, block), ["x64.sub_rm_64"]);
1941 }
1942
1943 #[test]
1946 fn a_load_two_instructions_read_stays_a_load() {
1947 let (mut names, mut func, block) = empty();
1948 let base = func.new_vreg(GPR);
1949 let other = func.new_vreg(GPR);
1950 let word = load(&mut func, &mut names, block, base);
1951 alu(&mut func, &mut names, block, "add_rr_64", other, word);
1952 alu(&mut func, &mut names, block, "xor_rr_64", other, word);
1953
1954 assert_eq!(combine(&mut func, &mut names), 0);
1955 assert_eq!(
1956 shape(&func, &names, block),
1957 ["x64.mov_rm_64", "x64.add_rr_64", "x64.xor_rr_64"]
1958 );
1959 }
1960
1961 #[test]
1964 fn a_load_with_a_store_between_it_and_its_reader_stays_a_load() {
1965 let (mut names, mut func, block) = empty();
1966 let base = func.new_vreg(GPR);
1967 let other = func.new_vreg(GPR);
1968 let word = load(&mut func, &mut names, block, base);
1969 let store = op(&mut names, "mov_mr_64");
1970 func.build(block, store).uses(other, GPR).mem(Mem::at(Operand::read(base, GPR))).finish();
1971 alu(&mut func, &mut names, block, "add_rr_64", other, word);
1972
1973 assert_eq!(combine(&mut func, &mut names), 0);
1974 assert_eq!(
1975 shape(&func, &names, block),
1976 ["x64.mov_rm_64", "x64.mov_mr_64", "x64.add_rr_64"]
1977 );
1978 }
1979
1980 #[test]
1986 fn a_load_with_another_load_between_it_and_its_reader_stays_a_load() {
1987 let (mut names, mut func, block) = empty();
1988 let base = func.new_vreg(GPR);
1989 let other = func.new_vreg(GPR);
1990 let word = load(&mut func, &mut names, block, base);
1991 load(&mut func, &mut names, block, other);
1992 alu(&mut func, &mut names, block, "add_rr_64", other, word);
1993
1994 assert_eq!(combine(&mut func, &mut names), 0);
1995 assert_eq!(
1996 shape(&func, &names, block),
1997 ["x64.mov_rm_64", "x64.mov_rm_64", "x64.add_rr_64"]
1998 );
1999 }
2000
2001 #[test]
2004 fn the_later_of_two_loads_is_the_one_that_folds() {
2005 let (mut names, mut func, block) = empty();
2006 let base = func.new_vreg(GPR);
2007 let other = func.new_vreg(GPR);
2008 let first = load(&mut func, &mut names, block, base);
2009 let second = load(&mut func, &mut names, block, other);
2010 alu(&mut func, &mut names, block, "add_rr_64", first, second);
2011
2012 assert_eq!(combine(&mut func, &mut names), 1);
2013 assert_eq!(shape(&func, &names, block), ["x64.mov_rm_64", "x64.add_rm_64"]);
2014 let addition = func.insts(block).nth(1).expect("the addition");
2015 assert_eq!(func[func[addition].operands][1].reg, first, "the earlier load is still read");
2016 assert_eq!(func[func[addition].operands][2].reg, other, "and the later one is the address");
2017 }
2018
2019 #[test]
2022 fn a_load_with_a_call_between_it_and_its_reader_stays_a_load() {
2023 let (mut names, mut func, block) = empty();
2024 let base = func.new_vreg(GPR);
2025 let other = func.new_vreg(GPR);
2026 let word = load(&mut func, &mut names, block, base);
2027 let call = op(&mut names, "call");
2028 func.build(block, call).finish();
2029 alu(&mut func, &mut names, block, "add_rr_64", other, word);
2030
2031 assert_eq!(combine(&mut func, &mut names), 0);
2032 assert_eq!(shape(&func, &names, block), ["x64.mov_rm_64", "x64.call", "x64.add_rr_64"]);
2033 }
2034
2035 #[test]
2038 fn a_load_whose_address_register_is_written_between_the_two_stays_a_load() {
2039 let (mut names, mut func, block) = empty();
2040 let base = Reg::physical(rucc_target::x86_64::RSP);
2041 let other = func.new_vreg(GPR);
2042 let word = load(&mut func, &mut names, block, base);
2043 let sub = op(&mut names, "sub_ri_64");
2044 func.build(block, sub)
2045 .operand(Operand::write(base, GPR).with(Constraint::Reuse(1)))
2046 .uses(base, GPR)
2047 .imm(32)
2048 .finish();
2049 alu(&mut func, &mut names, block, "add_rr_64", other, word);
2050
2051 assert_eq!(combine(&mut func, &mut names), 0);
2052 }
2053
2054 #[test]
2057 fn a_load_of_the_wrong_width_stays_a_load() {
2058 let (mut names, mut func, block) = empty();
2059 let base = func.new_vreg(GPR);
2060 let other = func.new_vreg(GPR);
2061 let into = func.new_vreg(GPR);
2062 let narrow = op(&mut names, "mov_rm_32");
2063 func.build(block, narrow).def(into, GPR).mem(Mem::at(Operand::read(base, GPR))).finish();
2064 alu(&mut func, &mut names, block, "add_rr_64", other, into);
2065
2066 assert_eq!(combine(&mut func, &mut names), 0);
2067 assert_eq!(shape(&func, &names, block), ["x64.mov_rm_32", "x64.add_rr_64"]);
2068 }
2069
2070 #[test]
2073 fn a_load_whose_value_an_edge_carries_stays_a_load() {
2074 let (mut names, mut func, block) = empty();
2075 let next = func.create_block();
2076 let base = func.new_vreg(GPR);
2077 let other = func.new_vreg(GPR);
2078 let word = load(&mut func, &mut names, block, base);
2079 alu(&mut func, &mut names, block, "add_rr_64", other, word);
2080 let arrived = func.new_vreg(GPR);
2081 func.params_mut(next).push(mir::Param { reg: arrived, class: GPR });
2082 *func.succs_mut(block) = vec![mir::BlockCall::with(next, vec![word])];
2083
2084 assert_eq!(combine(&mut func, &mut names), 0);
2085 assert_eq!(shape(&func, &names, block), ["x64.mov_rm_64", "x64.add_rr_64"]);
2086 }
2087
2088 #[test]
2090 fn a_reader_in_another_block_stays_where_it_is() {
2091 let (mut names, mut func, block) = empty();
2092 let next = func.create_block();
2093 let base = func.new_vreg(GPR);
2094 let other = func.new_vreg(GPR);
2095 let word = load(&mut func, &mut names, block, base);
2096 alu(&mut func, &mut names, next, "add_rr_64", other, word);
2097
2098 assert_eq!(combine(&mut func, &mut names), 0);
2099 assert_eq!(shape(&func, &names, block), ["x64.mov_rm_64"]);
2100 assert_eq!(shape(&func, &names, next), ["x64.add_rr_64"]);
2101 }
2102
2103 #[test]
2105 fn a_reader_past_the_window_stays_where_it_is() {
2106 let (mut names, mut func, block) = empty();
2107 let base = func.new_vreg(GPR);
2108 let other = func.new_vreg(GPR);
2109 let word = load(&mut func, &mut names, block, base);
2110 let nop = op(&mut names, "nop");
2111 for _ in 0..WINDOW {
2112 func.build(block, nop).finish();
2113 }
2114 alu(&mut func, &mut names, block, "add_rr_64", other, word);
2115
2116 assert_eq!(combine(&mut func, &mut names), 0);
2117 }
2118
2119 #[test]
2121 fn a_reader_at_the_edge_of_the_window_folds() {
2122 let (mut names, mut func, block) = empty();
2123 let base = func.new_vreg(GPR);
2124 let other = func.new_vreg(GPR);
2125 let word = load(&mut func, &mut names, block, base);
2126 let nop = op(&mut names, "nop");
2127 for _ in 0..WINDOW - 1 {
2128 func.build(block, nop).finish();
2129 }
2130 alu(&mut func, &mut names, block, "add_rr_64", other, word);
2131
2132 assert_eq!(combine(&mut func, &mut names), 1);
2133 }
2134
2135 #[test]
2138 fn the_frame_entry_of_a_load_that_moves_goes_with_it() {
2139 let (mut names, mut func, block) = empty();
2140 let base = Reg::physical(rucc_target::x86_64::RSP);
2141 let other = func.new_vreg(GPR);
2142 let word = load(&mut func, &mut names, block, base);
2143 let reader = func.insts(block).nth(1);
2144 assert!(reader.is_none(), "the block holds the load alone so far");
2145 alu(&mut func, &mut names, block, "add_rr_64", other, word);
2146 let held = func.insts(block).next().expect("the load");
2147
2148 let mut addresses = vec![(held, 3usize)];
2149 let mut arguments = Vec::new();
2150 let mut dynamic = Vec::new();
2151 let mut pending =
2152 Pending { addresses: &mut addresses, arguments: &mut arguments, dynamic: &mut dynamic };
2153 assert_eq!(loads(&mut func, &MACHINE, &mut names, &mut pending), 1);
2154
2155 let inst = func.insts(block).next().expect("the addition");
2156 assert_eq!(addresses, [(inst, 3usize)], "the entry names the instruction that took it");
2157 }
2158
2159 #[test]
2163 fn a_comparison_against_a_word_that_was_just_loaded_becomes_one_instruction() {
2164 let (mut names, mut func, block) = empty();
2165 let base = func.new_vreg(GPR);
2166 let other = func.new_vreg(GPR);
2167 let word = load(&mut func, &mut names, block, base);
2168 compare(&mut func, &mut names, block, "cmp_set_l_64", other, word);
2169
2170 assert_eq!(combine(&mut func, &mut names), 1);
2171 assert_eq!(shape(&func, &names, block), ["x64.cmp_set_l_rm_64"]);
2172 let inst = func.insts(block).next().expect("the comparison");
2173 let mem = func[inst].mem.expect("it reads memory");
2174 assert_eq!(func[mem].disp, 16, "the address came from the load");
2175 assert_eq!(func[mem].base, Some(2), "and names the operand behind the byte and the source");
2176 assert_eq!(func[func[inst].operands][1].reg, other, "the side it kept");
2177 assert_eq!(func[func[inst].operands][2].reg, base, "the address");
2178 }
2179
2180 #[test]
2184 fn a_comparison_whose_left_hand_side_was_just_loaded_turns_the_condition_over() {
2185 let (mut names, mut func, block) = empty();
2186 let base = func.new_vreg(GPR);
2187 let other = func.new_vreg(GPR);
2188 let word = load(&mut func, &mut names, block, base);
2189 compare(&mut func, &mut names, block, "cmp_set_l_64", word, other);
2190
2191 assert_eq!(combine(&mut func, &mut names), 1);
2192 assert_eq!(shape(&func, &names, block), ["x64.cmp_set_g_rm_64"]);
2193 let inst = func.insts(block).next().expect("the comparison");
2194 assert_eq!(func[func[inst].operands][1].reg, other, "the side it kept");
2195 }
2196
2197 #[test]
2201 fn an_equality_folded_on_either_side_is_the_same_comparison() {
2202 for (first, second) in [(true, false), (false, true)] {
2203 let (mut names, mut func, block) = empty();
2204 let base = func.new_vreg(GPR);
2205 let other = func.new_vreg(GPR);
2206 let word = load(&mut func, &mut names, block, base);
2207 let left = if first { word } else { other };
2208 let right = if second { word } else { other };
2209 compare(&mut func, &mut names, block, "cmp_set_e_64", left, right);
2210
2211 assert_eq!(combine(&mut func, &mut names), 1);
2212 assert_eq!(shape(&func, &names, block), ["x64.cmp_set_e_rm_64"]);
2213 }
2214 }
2215
2216 #[test]
2219 fn a_comparison_against_a_constant_is_left_where_it_is() {
2220 let (mut names, mut func, block) = empty();
2221 let base = func.new_vreg(GPR);
2222 let byte = func.new_vreg(GPR);
2223 let word = load(&mut func, &mut names, block, base);
2224 let opcode = op(&mut names, "cmp_set_l_ri_64");
2225 func.build(block, opcode).def(byte, GPR).uses(word, GPR).imm(7).finish();
2226
2227 assert_eq!(combine(&mut func, &mut names), 0);
2228 assert_eq!(shape(&func, &names, block), ["x64.mov_rm_64", "x64.cmp_set_l_ri_64"]);
2229 }
2230
2231 #[test]
2235 fn every_row_of_the_table_is_three_instructions_this_target_has() {
2236 for fold in FOLDS {
2237 assert!(MACHINE.has(fold.from), "{} is not an instruction", fold.from);
2238 assert!(MACHINE.has(fold.into), "{} is not an instruction", fold.into);
2239 assert!(MACHINE.has(fold.load), "{} is not an instruction", fold.load);
2240 let width = |name: &str| name.rsplit_once('_').map(|(_, width)| width.to_owned());
2241 assert_eq!(width(fold.from), width(fold.into), "{} changes width", fold.from);
2242 assert_eq!(width(fold.from), width(fold.load), "{} loads another width", fold.from);
2243 assert!((MACHINE.takes_mem)(fold.into), "{} reads no memory", fold.into);
2244 assert!(!(MACHINE.takes_mem)(fold.from), "{} already reads memory", fold.from);
2245 let Some(swapped) = fold.swapped else { continue };
2246 assert!(MACHINE.has(swapped), "{swapped} is not an instruction");
2247 assert_eq!(width(fold.from), width(swapped), "{} changes width", fold.from);
2248 assert!((MACHINE.takes_mem)(swapped), "{swapped} reads no memory");
2249 }
2250 }
2251
2252 #[test]
2256 fn the_table_covers_the_arithmetic_and_the_comparisons_this_target_has() {
2257 let compares = FOLDS.iter().filter(|fold| fold.from.starts_with("cmp_set_")).count();
2258 assert_eq!(compares, 40, "ten conditions at four widths");
2259 let arithmetic = FOLDS.len() - compares;
2260 assert_eq!(arithmetic, 23, "six operations at four widths, less the eight bit multiply");
2261 let swapped = FOLDS.iter().filter(|fold| fold.swapped.is_some()).count();
2262 assert_eq!(swapped, 59, "everything but the four subtractions");
2263 }
2264
2265 #[test]
2273 fn a_comparison_folded_on_its_left_hand_side_asks_the_same_question_backwards() {
2274 let turned = |condition: &str| match condition {
2275 "e" => "e",
2276 "ne" => "ne",
2277 "l" => "g",
2278 "g" => "l",
2279 "le" => "ge",
2280 "ge" => "le",
2281 "b" => "a",
2282 "a" => "b",
2283 "be" => "ae",
2284 "ae" => "be",
2285 other => panic!("{other} is not a condition this machine has"),
2286 };
2287 let compares = FOLDS.iter().filter(|fold| fold.from.starts_with("cmp_set_"));
2288 for fold in compares {
2289 let (front, width) = fold.from.rsplit_once('_').expect("a name ending in a width");
2290 let condition = front.strip_prefix("cmp_set_").expect("a name with a condition");
2291 assert_eq!(fold.into, format!("cmp_set_{condition}_rm_{width}"));
2292 let wanted = format!("cmp_set_{}_rm_{width}", turned(condition));
2293 assert_eq!(fold.swapped, Some(wanted.as_str()), "{} turns over wrongly", fold.from);
2294 }
2295 }
2296}