1use std::cmp::Ordering;
41use std::collections::HashMap;
42
43use rucc_base::Interner;
44use rucc_ir::{
45 CallInfo, Def, Extra, Flags, FloatPred, Func, Imm, Inst, InstData, IntPred, MemInfo, MemOrder,
46 Opcode, Signature, Type, Value,
47};
48
49pub fn orderings(func: &mut Func, word: u32) {
86 let found: Vec<Inst> =
87 func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
88 for inst in found {
89 match func[inst].opcode {
90 Opcode::AtomicLoad => relaxed(func, inst, Opcode::Load, word),
91 Opcode::AtomicStore => relaxed(func, inst, Opcode::Store, word),
92 _ => {}
93 }
94 }
95}
96
97fn relaxed(func: &mut Func, inst: Inst, plain: Opcode, word: u32) {
111 let Extra::Mem(mem) = func[inst].extra else { return };
112 let info = func[mem];
113 let ty = match plain {
114 Opcode::Store => match func[func[inst].args].first() {
115 Some(&value) => func[value].ty,
116 None => return,
117 },
118 _ => produced(func, inst),
119 };
120 if !indivisible(ty, info, word) {
121 return;
122 }
123 let unordered = MemInfo { order: MemOrder::NotAtomic, ..info };
124
125 if plain == Opcode::Store && info.order == MemOrder::SeqCst {
126 let [value, addr] = func[func[inst].args] else { return };
127 write(func, inst, value, addr, unordered);
128 let none = func.push_values(&[]);
129 let data = &mut func[inst];
130 data.opcode = Opcode::Fence;
131 data.args = none;
132 data.extra = Extra::Order(MemOrder::SeqCst);
133 data.flags = data.flags.intersection(Flags::legal_on(Opcode::Fence));
134 return;
135 }
136
137 let plainly = func.add_mem(unordered);
138 let data = &mut func[inst];
139 data.opcode = plain;
140 data.extra = Extra::Mem(plainly);
141 data.flags = data.flags.intersection(Flags::legal_on(plain));
142}
143
144fn indivisible(ty: Type, info: MemInfo, word: u32) -> bool {
157 let bytes = if ty.is_ptr() { word } else { ty.bits().div_ceil(8) };
158 ty.is_scalar() && bytes.is_power_of_two() && bytes <= word && info.align >= bytes
159}
160
161pub fn floats(func: &mut Func) {
175 let found: Vec<Inst> =
176 func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
177 for inst in found {
178 match func[inst].opcode {
179 Opcode::FConst => constant(func, inst),
180 Opcode::FNeg => negate(func, inst),
181 Opcode::SIToFP | Opcode::UIToFP => widen_then_convert(func, inst),
182 Opcode::FPToSI | Opcode::FPToUI => convert_then_narrow(func, inst),
183 _ => {}
184 }
185 }
186}
187
188fn constant(func: &mut Func, inst: Inst) {
199 let ty = produced(func, inst);
200 let Extra::Imm(imm) = func[inst].extra else { return };
201 if !ty.is_float() || !ty.is_scalar() || ty.bits() > 64 {
202 return;
203 }
204 let int = Type::int(ty.bits());
205 let bits = func[imm].bits();
206 let spelled = ahead_const(func, inst, Imm::int(bits as i128, int), int);
209 becomes(func, inst, Opcode::Bitcast, &[spelled]);
210}
211
212fn negate(func: &mut Func, inst: Inst) {
228 let ty = produced(func, inst);
229 let Some(&arg) = func[func[inst].args].first() else { return };
230 if !ty.is_float() || !ty.is_scalar() || ty.bits() > 64 {
231 return;
232 }
233 let int = Type::int(ty.bits());
234 let bits = ahead(func, inst, Opcode::Bitcast, &[arg], int);
235 let mask = ahead_const(func, inst, Imm::int(1i128 << (ty.bits() - 1), int), int);
236 let flipped = ahead(func, inst, Opcode::Xor, &[bits, mask], int);
237 becomes(func, inst, Opcode::Bitcast, &[flipped]);
238}
239
240fn widen_then_convert(func: &mut Func, inst: Inst) {
247 let signed = func[inst].opcode == Opcode::SIToFP;
248 let Some(&arg) = func[func[inst].args].first() else { return };
249 let from = func[arg].ty;
250 if !from.is_int() || !from.is_scalar() {
251 return;
252 }
253 let Some(width) = holder(from.bits(), signed) else {
254 from_unsigned_word(func, inst, arg, from);
255 return;
256 };
257 if width == from.bits() {
258 return;
259 }
260 let widen = if signed { Opcode::SExt } else { Opcode::ZExt };
261 let wide = ahead(func, inst, widen, &[arg], Type::int(width));
262 becomes(func, inst, Opcode::SIToFP, &[wide]);
263}
264
265fn convert_then_narrow(func: &mut Func, inst: Inst) {
272 let signed = func[inst].opcode == Opcode::FPToSI;
273 let ty = produced(func, inst);
274 let Some(&arg) = func[func[inst].args].first() else { return };
275 if !ty.is_int() || !ty.is_scalar() {
276 return;
277 }
278 let Some(width) = holder(ty.bits(), signed) else {
279 to_unsigned_word(func, inst, arg, ty);
280 return;
281 };
282 if width == ty.bits() {
283 return;
284 }
285 let wide = ahead(func, inst, Opcode::FPToSI, &[arg], Type::int(width));
286 becomes(func, inst, Opcode::Trunc, &[wide]);
287}
288
289fn from_unsigned_word(func: &mut Func, inst: Inst, arg: Value, from: Type) {
311 let ty = produced(func, inst);
312 if !ty.is_float() || !ty.is_scalar() {
313 return;
314 }
315 if ty.bits() > 64 {
316 from_unsigned_word_wide(func, inst, arg, from);
317 return;
318 }
319 let spread = spread_top_bit(func, inst, arg, from);
320
321 let one = ahead_const(func, inst, Imm::int(1, from), from);
323 let lost = ahead(func, inst, Opcode::And, &[arg, one], from);
324 let half = ahead(func, inst, Opcode::LShr, &[arg, one], from);
325 let odd = ahead(func, inst, Opcode::Or, &[half, lost], from);
326
327 let differ = ahead(func, inst, Opcode::Xor, &[arg, odd], from);
329 let taken = ahead(func, inst, Opcode::And, &[differ, spread], from);
330 let source = ahead(func, inst, Opcode::Xor, &[arg, taken], from);
331 let converted = ahead(func, inst, Opcode::SIToFP, &[source], ty);
332
333 let bits = Type::int(ty.bits());
336 let narrow = same_width(func, inst, spread, from, bits);
337 let raw = ahead(func, inst, Opcode::Bitcast, &[converted], bits);
338 let again = ahead(func, inst, Opcode::And, &[raw, narrow], bits);
339 let addend = ahead(func, inst, Opcode::Bitcast, &[again], ty);
340 becomes(func, inst, Opcode::FAdd, &[converted, addend]);
341}
342
343fn to_unsigned_word(func: &mut Func, inst: Inst, arg: Value, ty: Type) {
356 let from = func[arg].ty;
357 if !from.is_float() || !from.is_scalar() {
358 return;
359 }
360 if from.bits() > 64 {
361 to_unsigned_word_wide(func, inst, arg, ty);
362 return;
363 }
364 let bits = Type::int(from.bits());
366 let pattern = Imm::int(half_the_range(from.bits()), bits);
367 let spelled = ahead_const(func, inst, pattern, bits);
368 let half = ahead(func, inst, Opcode::Bitcast, &[spelled], from);
369
370 let over = ahead_cmp(func, inst, Opcode::FCmp, Extra::FloatPred(FloatPred::Oge), &[arg, half]);
371 let wide = ahead(func, inst, Opcode::ZExt, &[over], bits);
372 let zero = ahead_const(func, inst, Imm::int(0, bits), bits);
373 let spread = ahead(func, inst, Opcode::Sub, &[zero, wide], bits);
374
375 let amount = ahead(func, inst, Opcode::And, &[spread, spelled], bits);
376 let taken = ahead(func, inst, Opcode::Bitcast, &[amount], from);
377 let under = ahead(func, inst, Opcode::FSub, &[arg, taken], from);
378 let low = ahead(func, inst, Opcode::FPToSI, &[under], ty);
379
380 let again = ahead(func, inst, Opcode::ZExt, &[over], ty);
382 let up = ahead_const(func, inst, Imm::int(i128::from(ty.bits() - 1), ty), ty);
383 let top = ahead(func, inst, Opcode::Shl, &[again, up], ty);
384 becomes(func, inst, Opcode::Xor, &[low, top]);
385}
386
387fn from_unsigned_word_wide(func: &mut Func, inst: Inst, arg: Value, from: Type) {
409 let ty = produced(func, inst);
410 let zero = ahead_const(func, inst, Imm::int(0, from), from);
411 let over = ahead_cmp(func, inst, Opcode::ICmp, Extra::IntPred(IntPred::Slt), &[arg, zero]);
412
413 let signed = ahead(func, inst, Opcode::SIToFP, &[arg], ty);
414 let range = ahead_float(func, inst, two_to_the(64), ty);
415 let flag = flag_as_float(func, inst, over, ty);
416 let addend = ahead(func, inst, Opcode::FMul, &[range, flag], ty);
417 becomes(func, inst, Opcode::FAdd, &[signed, addend]);
418}
419
420fn to_unsigned_word_wide(func: &mut Func, inst: Inst, arg: Value, ty: Type) {
432 let from = func[arg].ty;
433 let half = ahead_float(func, inst, two_to_the(63), from);
434 let over = ahead_cmp(func, inst, Opcode::FCmp, Extra::FloatPred(FloatPred::Oge), &[arg, half]);
435
436 let flag = flag_as_float(func, inst, over, from);
437 let taken = ahead(func, inst, Opcode::FMul, &[half, flag], from);
438 let under = ahead(func, inst, Opcode::FSub, &[arg, taken], from);
439 let low = ahead(func, inst, Opcode::FPToSI, &[under], ty);
440
441 let again = ahead(func, inst, Opcode::ZExt, &[over], ty);
443 let up = ahead_const(func, inst, Imm::int(i128::from(ty.bits() - 1), ty), ty);
444 let top = ahead(func, inst, Opcode::Shl, &[again, up], ty);
445 becomes(func, inst, Opcode::Xor, &[low, top]);
446}
447
448fn flag_as_float(func: &mut Func, inst: Inst, cond: Value, ty: Type) -> Value {
454 let wide = ahead(func, inst, Opcode::ZExt, &[cond], Type::int(64));
455 ahead(func, inst, Opcode::SIToFP, &[wide], ty)
456}
457
458const fn two_to_the(power: u32) -> u128 {
463 ((0x3fff + power as u128) << 64) | 0x8000_0000_0000_0000
464}
465
466fn spread_top_bit(func: &mut Func, inst: Inst, arg: Value, ty: Type) -> Value {
472 let zero = ahead_const(func, inst, Imm::int(0, ty), ty);
473 let set = ahead_cmp(func, inst, Opcode::ICmp, Extra::IntPred(IntPred::Slt), &[arg, zero]);
474 let wide = ahead(func, inst, Opcode::ZExt, &[set], ty);
475 ahead(func, inst, Opcode::Sub, &[zero, wide], ty)
476}
477
478fn same_width(func: &mut Func, inst: Inst, value: Value, from: Type, to: Type) -> Value {
480 match to.bits().cmp(&from.bits()) {
481 Ordering::Equal => value,
482 Ordering::Less => ahead(func, inst, Opcode::Trunc, &[value], to),
483 Ordering::Greater => ahead(func, inst, Opcode::SExt, &[value], to),
484 }
485}
486
487fn half_the_range(width: u32) -> i128 {
493 match width {
494 32 => 0x5F00_0000,
495 _ => 0x43E0_0000_0000_0000,
496 }
497}
498
499pub fn bytes(func: &mut Func) {
513 let found: Vec<Inst> =
514 func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
515 for inst in found {
516 if func[inst].opcode == Opcode::Bswap {
517 swap(func, inst);
518 }
519 }
520}
521
522fn swap(func: &mut Func, inst: Inst) {
539 let ty = produced(func, inst);
540 let Some(&arg) = func[func[inst].args].first() else { return };
541 if !ty.is_int() || !ty.is_scalar() || ty.bits() < 16 || ty.bits() % 8 != 0 {
542 return;
543 }
544
545 let mut value = arg;
546 let mut group = ty.bits() / 2;
547 while group >= 8 {
548 let mask = alternating(ty.bits(), group);
551 let keep = ahead_const(func, inst, Imm::int(mask, ty), ty);
552 let count = ahead_const(func, inst, Imm::int(i128::from(group), ty), ty);
553 let low = ahead(func, inst, Opcode::And, &[value, keep], ty);
554 let up = ahead(func, inst, Opcode::Shl, &[low, count], ty);
555 let down = ahead(func, inst, Opcode::LShr, &[value, count], ty);
556 let high = ahead(func, inst, Opcode::And, &[down, keep], ty);
557 if group == 8 {
560 becomes(func, inst, Opcode::Or, &[up, high]);
561 return;
562 }
563 value = ahead(func, inst, Opcode::Or, &[up, high], ty);
564 group /= 2;
565 }
566}
567
568fn alternating(width: u32, group: u32) -> i128 {
579 every(width, group * 2, group)
580}
581
582fn every(width: u32, step: u32, run: u32) -> i128 {
591 let ones = (1i128 << run) - 1;
592 let mut mask = 0i128;
593 let mut at = 0;
594 while at < width {
595 mask |= ones << at;
596 at += step;
597 }
598 mask
599}
600
601pub fn counts(func: &mut Func) {
615 let found: Vec<Inst> =
616 func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
617 for inst in found {
618 match func[inst].opcode {
619 Opcode::Ctlz => searched(func, inst, true),
620 Opcode::Cttz => searched(func, inst, false),
621 _ => {}
622 }
623 }
624 let found: Vec<Inst> =
625 func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
626 for inst in found {
627 if func[inst].opcode == Opcode::Ctpop {
628 counted(func, inst);
629 }
630 }
631}
632
633fn searched(func: &mut Func, inst: Inst, leading: bool) {
651 let ty = produced(func, inst);
652 let Some(&arg) = func[func[inst].args].first() else { return };
653 if !countable(ty) {
654 return;
655 }
656 let ones = ahead_const(func, inst, Imm::int(-1, ty), ty);
657 if leading {
658 let mut value = arg;
659 let mut by = 1;
660 while by < ty.bits() {
661 let count = ahead_const(func, inst, Imm::int(i128::from(by), ty), ty);
662 let down = ahead(func, inst, Opcode::LShr, &[value, count], ty);
663 value = ahead(func, inst, Opcode::Or, &[value, down], ty);
664 by *= 2;
665 }
666 let above = ahead(func, inst, Opcode::Xor, &[value, ones], ty);
667 becomes(func, inst, Opcode::Ctpop, &[above]);
668 return;
669 }
670 let missing = ahead(func, inst, Opcode::Xor, &[arg, ones], ty);
671 let less = ahead(func, inst, Opcode::Add, &[arg, ones], ty);
672 let below = ahead(func, inst, Opcode::And, &[missing, less], ty);
673 becomes(func, inst, Opcode::Ctpop, &[below]);
674}
675
676fn counted(func: &mut Func, inst: Inst) {
690 let ty = produced(func, inst);
691 let Some(&arg) = func[func[inst].args].first() else { return };
692 if !countable(ty) {
693 return;
694 }
695 let width = ty.bits();
696 let pairs = ahead_const(func, inst, Imm::int(alternating(width, 1), ty), ty);
697 let two = ahead_const(func, inst, Imm::int(2, ty), ty);
698 let one = ahead_const(func, inst, Imm::int(1, ty), ty);
699 let high = ahead(func, inst, Opcode::LShr, &[arg, one], ty);
700 let odd = ahead(func, inst, Opcode::And, &[high, pairs], ty);
701 let bits = ahead(func, inst, Opcode::Sub, &[arg, odd], ty);
702
703 let quads = ahead_const(func, inst, Imm::int(alternating(width, 2), ty), ty);
704 let low = ahead(func, inst, Opcode::And, &[bits, quads], ty);
705 let up = ahead(func, inst, Opcode::LShr, &[bits, two], ty);
706 let rest = ahead(func, inst, Opcode::And, &[up, quads], ty);
707 let nibbles = ahead(func, inst, Opcode::Add, &[low, rest], ty);
708
709 let four = ahead_const(func, inst, Imm::int(4, ty), ty);
710 let bytes = ahead_const(func, inst, Imm::int(alternating(width, 4), ty), ty);
711 let folded = ahead(func, inst, Opcode::LShr, &[nibbles, four], ty);
712 let summed = ahead(func, inst, Opcode::Add, &[nibbles, folded], ty);
713 if width == 8 {
714 becomes(func, inst, Opcode::And, &[summed, bytes]);
715 return;
716 }
717 let held = ahead(func, inst, Opcode::And, &[summed, bytes], ty);
718
719 let spread = ahead_const(func, inst, Imm::int(every(width, 8, 1), ty), ty);
720 let top = ahead_const(func, inst, Imm::int(i128::from(width - 8), ty), ty);
721 let total = ahead(func, inst, Opcode::Mul, &[held, spread], ty);
722 becomes(func, inst, Opcode::LShr, &[total, top]);
723}
724
725pub fn overflows(func: &mut Func) {
738 let found: Vec<Inst> =
739 func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
740 let mut forward = HashMap::new();
741 for inst in found {
742 let checked = match func[inst].opcode {
743 Opcode::UAddOverflow => Checked::Add(false),
744 Opcode::SAddOverflow => Checked::Add(true),
745 Opcode::USubOverflow => Checked::Sub(false),
746 Opcode::SSubOverflow => Checked::Sub(true),
747 Opcode::UMulOverflow => Checked::Mul(false),
748 Opcode::SMulOverflow => Checked::Mul(true),
749 _ => continue,
750 };
751 overflowed(func, inst, checked, &mut forward);
752 }
753 if !forward.is_empty() {
754 substitute(func, &forward);
755 }
756}
757
758#[derive(Debug, Clone, Copy)]
760enum Checked {
761 Add(bool),
763 Sub(bool),
765 Mul(bool),
767}
768
769fn overflowed(func: &mut Func, inst: Inst, checked: Checked, forward: &mut HashMap<Value, Value>) {
786 let ty = produced(func, inst);
787 let [a, b] = func[func[inst].args] else { return };
788 if !checkable(ty) {
789 return;
790 }
791 let (value, bit) = match checked {
792 Checked::Add(signed) => {
793 let value = ahead(func, inst, Opcode::Add, &[a, b], ty);
794 let bit = if signed {
795 let left = ahead(func, inst, Opcode::Xor, &[a, value], ty);
796 let right = ahead(func, inst, Opcode::Xor, &[b, value], ty);
797 let both = ahead(func, inst, Opcode::And, &[left, right], ty);
798 negative(func, inst, both, ty)
799 } else {
800 compared(func, inst, IntPred::Ult, value, a)
801 };
802 (value, bit)
803 }
804 Checked::Sub(signed) => {
805 let value = ahead(func, inst, Opcode::Sub, &[a, b], ty);
806 let bit = if signed {
807 let apart = ahead(func, inst, Opcode::Xor, &[a, b], ty);
808 let moved = ahead(func, inst, Opcode::Xor, &[a, value], ty);
809 let both = ahead(func, inst, Opcode::And, &[apart, moved], ty);
810 negative(func, inst, both, ty)
811 } else {
812 compared(func, inst, IntPred::Ult, a, b)
813 };
814 (value, bit)
815 }
816 Checked::Mul(signed) => {
817 let value = ahead(func, inst, Opcode::Mul, &[a, b], ty);
818 let high = high_half(func, inst, a, b, signed, ty);
819 let bit = if signed {
820 let sign = ahead_const(func, inst, Imm::int(i128::from(ty.bits() - 1), ty), ty);
821 let wanted = ahead(func, inst, Opcode::AShr, &[value, sign], ty);
822 compared(func, inst, IntPred::Ne, high, wanted)
823 } else {
824 let zero = ahead_const(func, inst, Imm::int(0, ty), ty);
825 compared(func, inst, IntPred::Ne, high, zero)
826 };
827 (value, bit)
828 }
829 };
830 let mut answers = func[inst].results();
831 if let (Some(wrapped), Some(flag)) = (answers.next(), answers.next()) {
832 forward.insert(wrapped, value);
833 forward.insert(flag, bit);
834 }
835 func.remove_inst(inst);
836}
837
838pub(crate) fn high_half(
860 func: &mut Func,
861 inst: Inst,
862 a: Value,
863 b: Value,
864 signed: bool,
865 ty: Type,
866) -> Value {
867 let width = ty.bits();
868 let half = width / 2;
869 let shift = ahead_const(func, inst, Imm::int(i128::from(half), ty), ty);
870 let mask = ahead_const(func, inst, Imm::int((1i128 << half) - 1, ty), ty);
871
872 let al = ahead(func, inst, Opcode::And, &[a, mask], ty);
873 let ah = ahead(func, inst, Opcode::LShr, &[a, shift], ty);
874 let bl = ahead(func, inst, Opcode::And, &[b, mask], ty);
875 let bh = ahead(func, inst, Opcode::LShr, &[b, shift], ty);
876
877 let ll = ahead(func, inst, Opcode::Mul, &[al, bl], ty);
878 let lh = ahead(func, inst, Opcode::Mul, &[al, bh], ty);
879 let hl = ahead(func, inst, Opcode::Mul, &[ah, bl], ty);
880 let hh = ahead(func, inst, Opcode::Mul, &[ah, bh], ty);
881
882 let over = ahead(func, inst, Opcode::LShr, &[ll, shift], ty);
885 let lh_low = ahead(func, inst, Opcode::And, &[lh, mask], ty);
886 let hl_low = ahead(func, inst, Opcode::And, &[hl, mask], ty);
887 let some = ahead(func, inst, Opcode::Add, &[over, lh_low], ty);
888 let carry = ahead(func, inst, Opcode::Add, &[some, hl_low], ty);
889
890 let lh_high = ahead(func, inst, Opcode::LShr, &[lh, shift], ty);
891 let hl_high = ahead(func, inst, Opcode::LShr, &[hl, shift], ty);
892 let up = ahead(func, inst, Opcode::LShr, &[carry, shift], ty);
893 let first = ahead(func, inst, Opcode::Add, &[hh, lh_high], ty);
894 let second = ahead(func, inst, Opcode::Add, &[first, hl_high], ty);
895 let high = ahead(func, inst, Opcode::Add, &[second, up], ty);
896 if !signed {
897 return high;
898 }
899 let top = ahead_const(func, inst, Imm::int(i128::from(width - 1), ty), ty);
900 let a_sign = ahead(func, inst, Opcode::AShr, &[a, top], ty);
901 let b_sign = ahead(func, inst, Opcode::AShr, &[b, top], ty);
902 let a_owes = ahead(func, inst, Opcode::And, &[a_sign, b], ty);
903 let b_owes = ahead(func, inst, Opcode::And, &[b_sign, a], ty);
904 let once = ahead(func, inst, Opcode::Sub, &[high, a_owes], ty);
905 ahead(func, inst, Opcode::Sub, &[once, b_owes], ty)
906}
907
908fn negative(func: &mut Func, inst: Inst, value: Value, ty: Type) -> Value {
910 let zero = ahead_const(func, inst, Imm::int(0, ty), ty);
911 compared(func, inst, IntPred::Slt, value, zero)
912}
913
914fn compared(func: &mut Func, inst: Inst, pred: IntPred, lhs: Value, rhs: Value) -> Value {
917 let ty = func[lhs].ty.with_lane(Type::I1);
918 let args = func.push_values(&[lhs, rhs]);
919 let extra = Extra::IntPred(pred);
920 written(func, inst, InstData { args, extra, ..InstData::new(Opcode::ICmp) }, ty)
921}
922
923fn substitute(func: &mut Func, forward: &HashMap<Value, Value>) {
930 let with = |value: Value| forward.get(&value).copied().unwrap_or(value);
931 for block in func.blocks().collect::<Vec<_>>() {
932 for inst in func.insts(block).collect::<Vec<Inst>>() {
933 let args = func[inst].args;
934 func.rewrite(args, with);
935 for call in func.successors(inst).collect::<Vec<_>>() {
936 func.rewrite(call.args, with);
937 }
938 }
939 }
940}
941
942fn countable(ty: Type) -> bool {
952 ty.is_int()
953 && ty.is_scalar()
954 && ty.bits() >= 8
955 && ty.bits() <= 64
956 && ty.bits().is_power_of_two()
957}
958
959fn checkable(ty: Type) -> bool {
967 countable(ty) || (ty.is_int() && ty.is_scalar() && ty.bits() == 128)
968}
969
970pub fn rounds(func: &mut Func, to: u32) {
990 let found: Vec<Inst> =
991 func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
992 for inst in found {
993 if func[inst].opcode != Opcode::Alloca {
994 continue;
995 }
996 let Some(&size) = func[func[inst].args].first() else { continue };
997 let ty = func[size].ty;
998 if !ty.is_int() {
999 continue;
1000 }
1001 let up = ahead_const(func, inst, Imm::int(i128::from(to) - 1, ty), ty);
1002 let mask = ahead_const(func, inst, Imm::int(-i128::from(to), ty), ty);
1003 let over = ahead(func, inst, Opcode::Add, &[size, up], ty);
1004 let rounded = ahead(func, inst, Opcode::And, &[over, mask], ty);
1005 let args = func.push_values(&[rounded]);
1006 func[inst].args = args;
1007 }
1008}
1009
1010pub const UNROLL: usize = 32;
1023
1024pub fn bulk(func: &mut Func, names: &mut Interner, word: u32) {
1035 let found: Vec<Inst> =
1036 func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
1037 for inst in found {
1038 match func[inst].opcode {
1039 Opcode::Memcpy => copy(func, names, inst, word),
1040 Opcode::Memset => fill(func, names, inst, word),
1041 Opcode::Memmove => library(func, names, inst, "memmove", word),
1042 _ => {}
1043 }
1044 }
1045}
1046
1047fn copy(func: &mut Func, names: &mut Interner, inst: Inst, word: u32) {
1055 let [into, from] = func[func[inst].args] else { return };
1056 let Extra::Mem(mem) = func[inst].extra else { return };
1057 let info = func[mem];
1058 let Some(plan) = chunks(info, word) else { return library(func, names, inst, "memcpy", word) };
1059 for (at, width) in plan {
1060 let ty = Type::int(width * 8);
1061 let access = MemInfo { size: u64::from(width), align: width.min(info.align), ..info };
1062 let there = stepped(func, inst, from, at);
1063 let word = read(func, inst, there, access, ty);
1064 let here = stepped(func, inst, into, at);
1065 write(func, inst, word, here, access);
1066 }
1067 func.remove_inst(inst);
1068}
1069
1070fn fill(func: &mut Func, names: &mut Interner, inst: Inst, word: u32) {
1077 let [into, byte] = func[func[inst].args] else { return };
1078 let Extra::Mem(mem) = func[inst].extra else { return };
1079 let info = func[mem];
1080 let Some(spelled) = literal(func, byte) else {
1081 return library(func, names, inst, "memset", word);
1082 };
1083 let Some(plan) = chunks(info, word) else { return library(func, names, inst, "memset", word) };
1084 for (at, width) in plan {
1085 let ty = Type::int(width * 8);
1086 let access = MemInfo { size: u64::from(width), align: width.min(info.align), ..info };
1087 let value = ahead_const(func, inst, Imm::int(spread(spelled, width) as i128, ty), ty);
1088 let here = stepped(func, inst, into, at);
1089 write(func, inst, value, here, access);
1090 }
1091 func.remove_inst(inst);
1092}
1093
1094fn library(func: &mut Func, names: &mut Interner, inst: Inst, routine: &str, word: u32) {
1107 let [into, second] = func[func[inst].args] else { return };
1108 let Extra::Mem(mem) = func[inst].extra else { return };
1109 let size = func[mem].size;
1110
1111 let words = Type::int(word * 8);
1115 let count = ahead_const(func, inst, Imm::int(i128::from(size), words), words);
1116 let second = match routine {
1119 "memset" => widened(func, inst, second),
1120 _ => second,
1121 };
1122
1123 let sig = func.add_signature(Signature::new().with_params(&[
1124 Type::PTR,
1125 if routine == "memset" { Type::int(32) } else { Type::PTR },
1126 words,
1127 ]));
1128 let callee = names.intern(routine);
1129 let varargs = func.push_abis(&[]);
1130 let info = func.add_call(CallInfo { callee: Some(callee), signature: sig, varargs });
1131 let args = func.push_values(&[into, second, count]);
1132 let data = &mut func[inst];
1133 data.opcode = Opcode::Call;
1134 data.args = args;
1135 data.extra = Extra::Call(info);
1136 data.flags = data.flags.intersection(Flags::legal_on(Opcode::Call));
1137}
1138
1139fn widened(func: &mut Func, inst: Inst, value: Value) -> Value {
1141 let int = Type::int(32);
1142 let ty = func[value].ty;
1143 if ty == int {
1144 return value;
1145 }
1146 ahead(func, inst, Opcode::ZExt, &[value], int)
1147}
1148
1149fn chunks(info: MemInfo, word: u32) -> Option<Vec<(u64, u32)>> {
1162 plan(info.size, info.align, word)
1163}
1164
1165pub(crate) fn plan(size: u64, align: u32, word: u32) -> Option<Vec<(u64, u32)>> {
1173 let widest = word.min(align).max(1);
1174 if !widest.is_power_of_two() {
1175 return None;
1176 }
1177 let mut plan = Vec::new();
1178 let mut at = 0;
1179 let mut width = u64::from(widest);
1180 while at < size {
1181 while width > size - at {
1182 width /= 2;
1183 }
1184 plan.push((at, u32::try_from(width).ok()?));
1185 at += width;
1186 if plan.len() > UNROLL {
1187 return None;
1188 }
1189 }
1190 Some(plan)
1191}
1192
1193fn literal(func: &Func, value: Value) -> Option<u8> {
1195 let Def::Result { inst, .. } = func[value].def else { return None };
1196 if func[inst].opcode != Opcode::IConst {
1197 return None;
1198 }
1199 let Extra::Imm(imm) = func[inst].extra else { return None };
1200 u8::try_from(func[imm].bits() & 0xff).ok()
1201}
1202
1203fn spread(byte: u8, width: u32) -> u64 {
1205 (0..width).fold(0, |word, at| word | u64::from(byte) << (at * 8))
1206}
1207
1208fn stepped(func: &mut Func, inst: Inst, block: Value, at: u64) -> Value {
1211 if at == 0 {
1212 return block;
1213 }
1214 let step = ahead_const(func, inst, Imm::int(i128::from(at), Type::int(64)), Type::int(64));
1215 ahead(func, inst, Opcode::PtrAdd, &[block, step], Type::PTR)
1216}
1217
1218fn read(func: &mut Func, inst: Inst, from: Value, info: MemInfo, ty: Type) -> Value {
1220 let extra = Extra::Mem(func.add_mem(info));
1221 let args = func.push_values(&[from]);
1222 written(func, inst, InstData { args, extra, ..InstData::new(Opcode::Load) }, ty)
1223}
1224
1225fn write(func: &mut Func, inst: Inst, value: Value, into: Value, info: MemInfo) {
1227 let span = func.span(inst);
1228 let extra = Extra::Mem(func.add_mem(info));
1229 let args = func.push_values(&[value, into]);
1230 let data = InstData { args, extra, ..InstData::new(Opcode::Store) };
1231 let made = func.create_inst(data, &[], span);
1232 func.insert_before(made, inst);
1233}
1234
1235fn holder(bits: u32, signed: bool) -> Option<u32> {
1244 match if signed { bits } else { bits + 1 } {
1245 ..=32 => Some(32),
1246 33..=64 => Some(64),
1247 _ => None,
1248 }
1249}
1250
1251fn produced(func: &Func, inst: Inst) -> Type {
1256 func[inst].first_result.map_or(Type::VOID, |value| func[value].ty)
1257}
1258
1259fn ahead(func: &mut Func, inst: Inst, opcode: Opcode, args: &[Value], ty: Type) -> Value {
1261 let args = func.push_values(args);
1262 written(func, inst, InstData { args, ..InstData::new(opcode) }, ty)
1263}
1264
1265fn ahead_cmp(func: &mut Func, inst: Inst, opcode: Opcode, extra: Extra, args: &[Value]) -> Value {
1267 let args = func.push_values(args);
1268 written(func, inst, InstData { args, extra, ..InstData::new(opcode) }, Type::I1)
1269}
1270
1271fn ahead_const(func: &mut Func, inst: Inst, imm: Imm, ty: Type) -> Value {
1273 let extra = Extra::Imm(func.add_imm(imm));
1274 written(func, inst, InstData { extra, ..InstData::new(Opcode::IConst) }, ty)
1275}
1276
1277fn ahead_float(func: &mut Func, inst: Inst, bits: u128, ty: Type) -> Value {
1279 let extra = Extra::Imm(func.add_imm(Imm::from_bits(bits)));
1280 written(func, inst, InstData { extra, ..InstData::new(Opcode::FConst) }, ty)
1281}
1282
1283fn written(func: &mut Func, inst: Inst, data: InstData, ty: Type) -> Value {
1285 let span = func.span(inst);
1286 let made = func.create_inst(data, &[ty], span);
1287 func.insert_before(made, inst);
1288 func[made].first_result.expect("an instruction created with one result has one")
1289}
1290
1291fn becomes(func: &mut Func, inst: Inst, opcode: Opcode, args: &[Value]) {
1298 let args = func.push_values(args);
1299 let data = &mut func[inst];
1300 data.opcode = opcode;
1301 data.args = args;
1302 data.extra = Extra::None;
1303 data.flags = data.flags.intersection(Flags::legal_on(opcode));
1306}
1307
1308#[cfg(test)]
1309mod tests {
1310 use rucc_base::Interner;
1311 use rucc_ir::{Builder, Flags, Float, Func, Module, Opcode, Signature, Type};
1312 use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
1313
1314 use rucc_ir::{Extra, InstData, MemInfo, MemOrder, Restrict};
1315
1316 use super::{
1317 UNROLL, alternating, bulk, bytes, chunks, counts, every, floats, orderings, overflows,
1318 spread,
1319 };
1320
1321 fn target() -> TargetInfo {
1322 TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu))
1323 }
1324
1325 fn printed(func: &Func, names: &mut Interner) -> String {
1326 let module = Module::new(names.intern("sw.c"), &target());
1327 rucc_ir::print_func(&module, func, names)
1328 }
1329
1330 fn one(
1335 params: &[Type],
1336 returns: &[Type],
1337 body: impl FnOnce(&mut Builder<'_>, &[rucc_ir::Value]),
1338 ) -> (Interner, Func) {
1339 let mut names = Interner::new();
1340 let mut func = Func::new(
1341 names.intern("f"),
1342 Signature::new().with_params(params).with_returns(returns),
1343 );
1344 let entry = func.create_block();
1345 let args: Vec<_> = params.iter().map(|&ty| func.append_param(entry, ty)).collect();
1346 let mut build = Builder::new(&mut func, entry);
1347 body(&mut build, &args);
1348 (names, func)
1349 }
1350
1351 fn f64() -> Type {
1352 Type::float(Float::F64)
1353 }
1354
1355 fn f32() -> Type {
1356 Type::float(Float::F32)
1357 }
1358
1359 fn f80() -> Type {
1360 Type::float(Float::F80)
1361 }
1362
1363 const CASES: &[u64] = &[
1368 0,
1369 1,
1370 2,
1371 0x7FFF_FFFF,
1372 0x8000_0000,
1373 0xFFFF_FFFF,
1374 0x0020_0000_0000_0000,
1375 0x0020_0000_0000_0001,
1376 0x7FFF_FFFF_FFFF_FFFF,
1377 0x8000_0000_0000_0000,
1378 0x8000_0000_0000_0001,
1379 0x8000_0000_0000_0400,
1380 0xFFFF_FFFF_FFFF_F800,
1381 0xFFFF_FFFF_FFFF_FFFF,
1382 ];
1383
1384 fn valid(func: &Func, names: &mut Interner) {
1386 let module = Module::new(names.intern("f.c"), &target());
1387 rucc_ir::verify_func(&module, func, names).expect("the rewrite builds valid IR");
1388 }
1389
1390 #[test]
1392 fn a_float_constant_becomes_the_integer_that_spells_it_and_a_reading_of_those_bits() {
1393 let (mut names, mut func) = one(&[], &[f64()], |build, _| {
1394 let k = build.fconst(f64(), 0x3ff8_0000_0000_0000);
1395 build.ret(&[k]);
1396 });
1397 floats(&mut func);
1398
1399 let text = printed(&func, &mut names);
1400 assert!(!text.contains("fconst"), "the float constant is gone: {text}");
1401 assert!(text.contains("iconst.i64 4609434218613702656"), "the bits, as an integer: {text}");
1402 assert!(text.contains("bitcast"), "read back as the float: {text}");
1403 }
1404
1405 #[test]
1408 fn a_constant_at_the_narrow_format_is_an_integer_of_the_narrow_width() {
1409 let (mut names, mut func) = one(&[], &[f32()], |build, _| {
1410 let k = build.fconst(f32(), 0x4020_0000);
1411 build.ret(&[k]);
1412 });
1413 floats(&mut func);
1414 assert!(printed(&func, &mut names).contains("iconst.i32"), "an i32, not an i64");
1415 }
1416
1417 #[test]
1420 fn a_negation_flips_the_sign_bit_and_touches_no_other() {
1421 let (mut names, mut func) = one(&[f64()], &[f64()], |build, args| {
1422 let n = build.unary(Opcode::FNeg, args[0], f64());
1423 build.ret(&[n]);
1424 });
1425 floats(&mut func);
1426
1427 let text = printed(&func, &mut names);
1428 assert!(!text.contains("fneg"), "the negation is gone: {text}");
1429 assert!(!text.contains("fsub"), "and it did not become a subtraction: {text}");
1430 assert!(text.contains("iconst.i64 -9223372036854775808"), "the sign bit alone: {text}");
1431 assert_eq!(text.matches("xor").count(), 1, "one exclusive or: {text}");
1432 assert_eq!(text.matches("bitcast").count(), 2, "there and back: {text}");
1433 }
1434
1435 #[test]
1437 fn an_unsigned_integer_becoming_a_float_widens_first_and_then_converts_as_signed() {
1438 let (mut names, mut func) = one(&[Type::int(32)], &[f64()], |build, args| {
1439 let d = build.unary(Opcode::UIToFP, args[0], f64());
1440 build.ret(&[d]);
1441 });
1442 floats(&mut func);
1443
1444 let text = printed(&func, &mut names);
1445 assert!(!text.contains("uitofp"), "the unsigned conversion is gone: {text}");
1446 assert!(text.contains("zext.i64"), "widened with zeroes: {text}");
1447 assert!(text.contains("sitofp.f64"), "converted as signed: {text}");
1448 }
1449
1450 #[test]
1452 fn a_float_becoming_an_unsigned_integer_converts_as_signed_first_and_then_narrows() {
1453 let (mut names, mut func) = one(&[f64()], &[Type::int(32)], |build, args| {
1454 let n = build.unary(Opcode::FPToUI, args[0], Type::int(32));
1455 build.ret(&[n]);
1456 });
1457 floats(&mut func);
1458
1459 let text = printed(&func, &mut names);
1460 assert!(!text.contains("fptoui"), "the unsigned conversion is gone: {text}");
1461 assert!(text.contains("fptosi.i64"), "converted as signed: {text}");
1462 assert!(text.contains("trunc.i32"), "and narrowed to what was asked: {text}");
1463 }
1464
1465 #[test]
1468 fn a_conversion_narrower_than_the_machine_has_is_one_it_has_and_a_narrowing() {
1469 let (mut names, mut func) = one(&[f64()], &[Type::int(8)], |build, args| {
1470 let n = build.unary(Opcode::FPToSI, args[0], Type::int(8));
1471 build.ret(&[n]);
1472 });
1473 floats(&mut func);
1474
1475 let text = printed(&func, &mut names);
1476 assert!(text.contains("fptosi.i32"), "converted at a width there is one at: {text}");
1477 assert!(text.contains("trunc.i8"), "and narrowed to what was asked: {text}");
1478 }
1479
1480 #[test]
1482 fn a_signed_integer_narrower_than_the_machine_converts_from_is_widened_with_its_sign() {
1483 let (mut names, mut func) = one(&[Type::int(8)], &[f64()], |build, args| {
1484 let d = build.unary(Opcode::SIToFP, args[0], f64());
1485 build.ret(&[d]);
1486 });
1487 floats(&mut func);
1488
1489 let text = printed(&func, &mut names);
1490 assert!(text.contains("sext.i32"), "widened with the sign and not with zeroes: {text}");
1491 assert!(!text.contains("zext"), "widened with the sign and not with zeroes: {text}");
1492 assert!(text.contains("sitofp.f64"), "converted at a width there is one at: {text}");
1493 }
1494
1495 #[test]
1497 fn the_width_a_conversion_happens_at_is_the_narrowest_one_that_holds_the_values() {
1498 use super::holder;
1499 for bits in [1, 8, 16, 32] {
1500 assert_eq!(holder(bits, true), Some(32), "a signed {bits} bit value fits in an int");
1501 }
1502 assert_eq!(holder(64, true), Some(64));
1503 for bits in [1, 8, 16, 31] {
1504 assert_eq!(holder(bits, false), Some(32), "an unsigned {bits} bit value does too");
1505 }
1506 assert_eq!(holder(32, false), Some(64));
1508 assert_eq!(holder(64, false), None);
1509 }
1510
1511 #[test]
1515 fn the_unsigned_conversions_at_the_widest_width_become_the_signed_one_and_a_correction() {
1516 for float in [f32(), f64()] {
1517 let (mut names, mut func) = one(&[Type::int(64)], &[float], |build, args| {
1518 let d = build.unary(Opcode::UIToFP, args[0], float);
1519 build.ret(&[d]);
1520 });
1521 floats(&mut func);
1522 let text = printed(&func, &mut names);
1523 assert!(!text.contains("uitofp"), "the unsigned conversion is gone: {text}");
1524 assert!(text.contains("sitofp"), "the signed one is what is left: {text}");
1525 assert!(text.contains("lshr"), "the value is halved: {text}");
1528 assert!(text.contains("fadd"), "and doubled again afterwards: {text}");
1529 valid(&func, &mut names);
1530 }
1531
1532 for float in [f32(), f64()] {
1533 let (mut names, mut func) = one(&[float], &[Type::int(64)], |build, args| {
1534 let n = build.unary(Opcode::FPToUI, args[0], Type::int(64));
1535 build.ret(&[n]);
1536 });
1537 floats(&mut func);
1538 let text = printed(&func, &mut names);
1539 assert!(!text.contains("fptoui"), "the unsigned conversion is gone: {text}");
1540 assert!(text.contains("fptosi"), "the signed one is what is left: {text}");
1541 assert!(text.contains("fsub"), "the value is brought down: {text}");
1543 assert!(text.contains("shl"), "and the top bit goes back on: {text}");
1544 valid(&func, &mut names);
1545 }
1546 }
1547
1548 #[test]
1552 fn the_widest_unsigned_conversions_are_written_without_a_branch() {
1553 let (_, mut func) = one(&[Type::int(64)], &[f64()], |build, args| {
1554 let d = build.unary(Opcode::UIToFP, args[0], f64());
1555 build.ret(&[d]);
1556 });
1557 floats(&mut func);
1558 assert_eq!(func.blocks().count(), 1, "the conversion did not split the block");
1559
1560 let (_, mut func) = one(&[f64()], &[Type::int(64)], |build, args| {
1561 let n = build.unary(Opcode::FPToUI, args[0], Type::int(64));
1562 build.ret(&[n]);
1563 });
1564 floats(&mut func);
1565 assert_eq!(func.blocks().count(), 1, "nor did the other one");
1566 }
1567
1568 #[test]
1575 fn the_arithmetic_the_widest_unsigned_conversions_do_is_the_conversion() {
1576 for &x in CASES {
1577 let mask = if (x as i64) < 0 { u64::MAX } else { 0 };
1579 let odd = (x >> 1) | (x & 1);
1580 let source = x ^ ((x ^ odd) & mask);
1581 let converted = source as i64 as f64;
1582 let addend = f64::from_bits(converted.to_bits() & mask);
1583 assert_eq!(converted + addend, x as f64, "converting {x:#x} into a double");
1584 }
1585
1586 for &x in CASES {
1587 let d = x as f64;
1589 if d >= 18_446_744_073_709_551_616.0 {
1590 continue;
1591 }
1592 let half = f64::from_bits(0x43E0_0000_0000_0000);
1593 let mask = if d >= half { u64::MAX } else { 0 };
1594 let taken = f64::from_bits(half.to_bits() & mask);
1595 let low = (d - taken) as i64;
1596 let top = u64::from(d >= half) << 63;
1597 assert_eq!(low as u64 ^ top, d as u64, "converting {d} into an unsigned word");
1598 }
1599 }
1600
1601 #[test]
1608 fn the_unsigned_conversions_at_eighty_bits_correct_with_a_multiply_instead_of_a_mask() {
1609 let (mut names, mut func) = one(&[Type::int(64)], &[f80()], |build, args| {
1610 let d = build.unary(Opcode::UIToFP, args[0], f80());
1611 build.ret(&[d]);
1612 });
1613 floats(&mut func);
1614 let text = printed(&func, &mut names);
1615 assert!(!text.contains("uitofp"), "the unsigned conversion is gone: {text}");
1616 assert!(text.contains("sitofp.f80"), "the signed one is what is left: {text}");
1617 assert!(!text.contains("bitcast"), "and nothing reads the float as an integer: {text}");
1618 assert!(!text.contains("lshr"), "nor is the value halved, since nothing rounds: {text}");
1619 assert!(text.contains("fmul "), "the constant is taken or not by a multiply: {text}");
1620 assert!(text.contains("fadd "), "and added to what the conversion gave: {text}");
1621 assert_eq!(func.blocks().count(), 1, "the conversion did not split the block");
1622 valid(&func, &mut names);
1623
1624 let (mut names, mut func) = one(&[f80()], &[Type::int(64)], |build, args| {
1625 let n = build.unary(Opcode::FPToUI, args[0], Type::int(64));
1626 build.ret(&[n]);
1627 });
1628 floats(&mut func);
1629 let text = printed(&func, &mut names);
1630 assert!(!text.contains("fptoui"), "the unsigned conversion is gone: {text}");
1631 assert!(text.contains("fptosi.i64"), "the signed one is what is left: {text}");
1632 assert!(!text.contains("bitcast"), "and nothing reads the float as an integer: {text}");
1633 assert!(text.contains("fmul "), "the constant is taken or not by a multiply: {text}");
1634 assert!(text.contains("fsub "), "and subtracted before the conversion: {text}");
1635 assert!(text.contains("shl"), "with the top bit going back on after it: {text}");
1636 assert_eq!(func.blocks().count(), 1, "nor did the other one");
1637 valid(&func, &mut names);
1638 }
1639
1640 #[test]
1650 fn nothing_in_either_conversion_at_eighty_bits_rounds() {
1651 fn exact(v: i128) -> bool {
1653 let mag = v.unsigned_abs();
1654 mag == 0 || (mag >> mag.trailing_zeros()) < 1 << 64
1655 }
1656
1657 for &x in CASES {
1658 let signed = i128::from(x as i64);
1660 let addend = if (x as i64) < 0 { 1i128 << 64 } else { 0 };
1661 assert!(exact(signed), "the conversion of {x:#x} read as signed is exact");
1662 assert!(exact(addend), "and so is the constant it gets");
1663 assert!(exact(signed + addend), "and so is the sum");
1664 assert_eq!(signed + addend, i128::from(x), "converting {x:#x} into a long double");
1665 }
1666
1667 for &x in CASES {
1668 let value = i128::from(x);
1670 let taken = if value >= 1 << 63 { 1i128 << 63 } else { 0 };
1671 let under = value - taken;
1672 assert!(exact(under), "the subtraction that brings {x:#x} into range is exact");
1673 let top = u64::from(value >= 1 << 63) << 63;
1674 assert_eq!(under as u64 ^ top, x, "converting {x:#x} back into an unsigned word");
1675 }
1676 }
1677
1678 #[test]
1681 fn what_the_float_rewrites_leave_is_valid_ir() {
1682 let (mut names, mut func) = one(&[Type::int(32)], &[f64()], |build, args| {
1683 let k = build.fconst(f64(), 0x3ff8_0000_0000_0000);
1684 let d = build.unary(Opcode::UIToFP, args[0], f64());
1685 let n = build.unary(Opcode::FNeg, d, f64());
1686 let s = build.binary(Opcode::FAdd, n, k, Flags::NONE);
1687 build.ret(&[s]);
1688 });
1689 floats(&mut func);
1690 let module = Module::new(names.intern("f.c"), &target());
1691 rucc_ir::verify_func(&module, &func, &names).expect("the rewrite builds valid IR");
1692 }
1693
1694 #[test]
1697 fn a_function_with_no_floats_in_it_is_left_exactly_as_it_was() {
1698 let (mut names, mut func) = one(&[Type::int(32)], &[Type::int(32)], |build, args| {
1699 build.ret(&[args[0]]);
1700 });
1701 let before = printed(&func, &mut names);
1702 floats(&mut func);
1703 assert_eq!(printed(&func, &mut names), before);
1704 }
1705 fn access(size: u64, align: u32) -> MemInfo {
1706 MemInfo {
1707 size,
1708 align,
1709 order: MemOrder::NotAtomic,
1710 tbaa: None,
1711 owns: 0,
1712 restrict: Restrict::NONE,
1713 }
1714 }
1715
1716 fn moving(opcode: Opcode, size: u64, align: u32, byte: Option<i128>) -> (Interner, Func) {
1719 one(&[Type::PTR, Type::PTR], &[], |build, args| {
1720 let second = match byte {
1721 Some(value) => build.iconst(Type::int(8), value),
1722 None => args[1],
1723 };
1724 let mem = build.func().add_mem(access(size, align));
1725 let operands = build.func().push_values(&[args[0], second]);
1726 let data = InstData { args: operands, extra: Extra::Mem(mem), ..InstData::new(opcode) };
1727 build.inst(data, &[]);
1728 build.ret(&[]);
1729 })
1730 }
1731
1732 fn copying(size: u64, align: u32) -> (Interner, Func) {
1733 moving(Opcode::Memcpy, size, align, None)
1734 }
1735
1736 fn filling(size: u64, align: u32, byte: i128) -> (Interner, Func) {
1737 moving(Opcode::Memset, size, align, Some(byte))
1738 }
1739
1740 fn widths(size: u64, align: u32) -> Option<Vec<u32>> {
1743 Some(chunks(access(size, align), 8)?.into_iter().map(|(_, width)| width).collect())
1744 }
1745
1746 #[test]
1748 fn a_copy_becomes_a_load_and_a_store_for_each_word_of_it() {
1749 let (mut names, mut func) = copying(16, 8);
1750 bulk(&mut func, &mut names, 8);
1751
1752 let text = printed(&func, &mut names);
1753 assert!(!text.contains("memcpy"), "the copy is gone: {text}");
1754 assert_eq!(text.matches("load.i64").count(), 2, "a load per word: {text}");
1755 assert_eq!(text.matches("store").count(), 2, "a store per word: {text}");
1756 assert_eq!(
1757 text.matches("ptr_add").count(),
1758 2,
1759 "no offset for the word at the front: {text}"
1760 );
1761 }
1762
1763 #[test]
1767 fn a_word_is_as_wide_as_the_block_is_aligned_to() {
1768 assert_eq!(widths(16, 8), Some(vec![8, 8]));
1769 assert_eq!(widths(16, 4), Some(vec![4, 4, 4, 4]));
1770 assert_eq!(widths(4, 1), Some(vec![1, 1, 1, 1]));
1771 }
1772
1773 #[test]
1776 fn what_is_left_over_is_narrower_words_and_not_a_run_of_bytes() {
1777 assert_eq!(widths(13, 8), Some(vec![8, 4, 1]));
1778 assert_eq!(widths(3, 8), Some(vec![2, 1]));
1779 assert_eq!(widths(1, 8), Some(vec![1]));
1780 }
1781
1782 #[test]
1785 fn every_word_starts_somewhere_it_is_aligned_for() {
1786 for (at, width) in chunks(access(13, 8), 8).expect("a plan for thirteen bytes") {
1787 assert_eq!(at % u64::from(width), 0, "{at} is a multiple of {width}");
1788 }
1789 }
1790
1791 #[test]
1793 fn a_fill_is_the_byte_spread_across_each_word() {
1794 let (mut names, mut func) = filling(16, 8, 0);
1795 bulk(&mut func, &mut names, 8);
1796
1797 let text = printed(&func, &mut names);
1798 assert!(!text.contains("memset"), "the fill is gone: {text}");
1799 assert_eq!(text.matches("store").count(), 2, "a store per word: {text}");
1800 assert!(!text.contains("load"), "a fill reads nothing: {text}");
1801 }
1802
1803 #[test]
1806 fn the_byte_is_repeated_across_the_word_it_is_stored_as() {
1807 assert_eq!(spread(0, 8), 0);
1808 assert_eq!(spread(0xff, 1), 0xff);
1809 assert_eq!(spread(0xff, 4), 0xffff_ffff);
1810 assert_eq!(spread(0xab, 2), 0xabab);
1811 assert_eq!(spread(0xab, 8), 0xabab_abab_abab_abab);
1812 }
1813
1814 #[test]
1816 fn a_copy_too_large_to_unroll_becomes_a_call_to_the_runtime() {
1817 let size = u64::try_from(UNROLL).expect("a small threshold") + 1;
1818 let (mut names, mut func) = copying(size, 1);
1819 bulk(&mut func, &mut names, 8);
1820 let text = printed(&func, &mut names);
1821 assert!(text.contains("call @memcpy"), "a call and not a bulk move: {text}");
1822
1823 let (mut names, mut func) = copying(size - 1, 1);
1826 bulk(&mut func, &mut names, 8);
1827 assert!(!printed(&func, &mut names).contains("memcpy"), "one word under it is unrolled");
1828 }
1829
1830 #[test]
1833 fn the_call_passes_the_size_that_the_instruction_carried_beside_it() {
1834 let size = u64::try_from(UNROLL).expect("a small threshold") + 1;
1835 let (mut names, mut func) = copying(size, 1);
1836 bulk(&mut func, &mut names, 8);
1837 let text = printed(&func, &mut names);
1838 assert!(text.contains(&format!("{size}")), "the size is an argument now: {text}");
1839 }
1840
1841 #[test]
1844 fn a_move_is_a_call_however_small_it_is() {
1845 let (mut names, mut func) = moving(Opcode::Memmove, 8, 8, None);
1846 bulk(&mut func, &mut names, 8);
1847 let text = printed(&func, &mut names);
1848 assert!(text.contains("call @memmove"), "a call and not a run of moves: {text}");
1849 }
1850
1851 #[test]
1854 fn a_fill_whose_byte_is_not_a_constant_becomes_a_call() {
1855 let (mut names, mut func) = one(&[Type::PTR, Type::int(8)], &[], |build, args| {
1856 let mem = build.func().add_mem(access(8, 8));
1857 let operands = build.func().push_values(&[args[0], args[1]]);
1858 let data = InstData {
1859 args: operands,
1860 extra: Extra::Mem(mem),
1861 ..InstData::new(Opcode::Memset)
1862 };
1863 build.inst(data, &[]);
1864 build.ret(&[]);
1865 });
1866 bulk(&mut func, &mut names, 8);
1867 let text = printed(&func, &mut names);
1868 assert!(text.contains("call @memset"), "a call and not a run of stores: {text}");
1869 assert!(text.contains("zext.i32"), "the byte is widened to what C passes: {text}");
1871 }
1872
1873 #[test]
1876 fn no_word_is_wider_than_the_machine_moves_at_once() {
1877 assert_eq!(chunks(access(8, 8), 4).map(|plan| plan.len()), Some(2));
1878 assert_eq!(chunks(access(8, 8), 8).map(|plan| plan.len()), Some(1));
1879 }
1880
1881 #[test]
1882 fn what_a_copy_becomes_is_ir_that_verifies() {
1883 let (mut names, mut func) = copying(13, 8);
1884 bulk(&mut func, &mut names, 8);
1885 let module = Module::new(names.intern("c.c"), &target());
1886 rucc_ir::verify_func(&module, &func, &names).expect("the rewrite builds valid IR");
1887 }
1888
1889 #[test]
1890 fn what_a_fill_becomes_is_ir_that_verifies() {
1891 let (mut names, mut func) = filling(13, 8, 0xff);
1892 bulk(&mut func, &mut names, 8);
1893 let module = Module::new(names.intern("f.c"), &target());
1894 rucc_ir::verify_func(&module, &func, &names).expect("the rewrite builds valid IR");
1895 }
1896
1897 #[test]
1898 fn what_a_copy_too_large_to_unroll_becomes_is_ir_that_verifies() {
1899 let size = u64::try_from(UNROLL).expect("a small threshold") + 1;
1900 let (mut names, mut func) = copying(size, 1);
1901 bulk(&mut func, &mut names, 8);
1902 let module = Module::new(names.intern("c.c"), &target());
1903 rucc_ir::verify_func(&module, &func, &names).expect("the call is valid IR");
1904 }
1905
1906 #[test]
1908 fn a_function_with_no_bulk_move_in_it_is_left_exactly_as_it_was() {
1909 let (mut names, mut func) = one(&[Type::int(32)], &[Type::int(32)], |build, args| {
1910 build.ret(&[args[0]]);
1911 });
1912 let before = printed(&func, &mut names);
1913 bulk(&mut func, &mut names, 8);
1914 assert_eq!(printed(&func, &mut names), before);
1915 }
1916
1917 fn swapping(width: u32) -> (Interner, Func) {
1920 let ty = Type::int(width);
1921 one(&[ty], &[ty], |build, args| {
1922 let s = build.unary(Opcode::Bswap, args[0], ty);
1923 build.ret(&[s]);
1924 })
1925 }
1926
1927 #[test]
1934 fn the_masks_are_the_alternating_runs_of_the_group_being_swapped() {
1935 assert_eq!(alternating(32, 16), 0x0000_ffff);
1936 assert_eq!(alternating(32, 8), 0x00ff_00ff);
1937 assert_eq!(alternating(16, 8), 0x00ff);
1938 assert_eq!(alternating(64, 32), 0x0000_0000_ffff_ffff);
1939 assert_eq!(alternating(64, 16), 0x0000_ffff_0000_ffff);
1940 assert_eq!(alternating(64, 8), 0x00ff_00ff_00ff_00ff);
1941 }
1942
1943 #[test]
1945 fn a_two_byte_swap_is_one_exchange_of_neighbouring_bytes() {
1946 let (mut names, mut func) = swapping(16);
1947 bytes(&mut func);
1948
1949 let text = printed(&func, &mut names);
1950 assert!(!text.contains("bswap"), "the instruction is gone: {text}");
1951 assert!(text.contains("iconst.i16 255"), "the low byte of the pair: {text}");
1952 assert_eq!(text.matches("shl").count(), 1, "one shift up: {text}");
1953 assert_eq!(text.matches("lshr").count(), 1, "one shift down: {text}");
1954 assert_eq!(text.matches(" or ").count(), 1, "and the two put together: {text}");
1955 }
1956
1957 #[test]
1960 fn a_wider_swap_is_the_same_exchange_once_per_halving() {
1961 for (width, steps) in [(16u32, 1usize), (32, 2), (64, 3)] {
1962 let (mut names, mut func) = swapping(width);
1963 bytes(&mut func);
1964 let text = printed(&func, &mut names);
1965 assert_eq!(text.matches("shl").count(), steps, "at {width}: {text}");
1966 assert_eq!(text.matches("lshr").count(), steps, "at {width}: {text}");
1967 assert_eq!(text.matches(" and ").count(), steps * 2, "at {width}: {text}");
1968 assert_eq!(text.matches(" or ").count(), steps, "at {width}: {text}");
1969 }
1970 }
1971
1972 #[test]
1975 fn the_shift_counts_are_the_group_width_halving_as_it_goes() {
1976 let (mut names, mut func) = swapping(64);
1977 bytes(&mut func);
1978 let text = printed(&func, &mut names);
1979 for count in ["iconst.i64 32", "iconst.i64 16", "iconst.i64 8"] {
1980 assert!(text.contains(count), "{count} is a step: {text}");
1981 }
1982 }
1983
1984 #[test]
1987 fn what_a_byte_swap_becomes_is_ir_that_verifies() {
1988 let (mut names, mut func) = swapping(32);
1989 bytes(&mut func);
1990 let module = Module::new(names.intern("b.c"), &target());
1991 rucc_ir::verify_func(&module, &func, &names).expect("the rewrite builds valid IR");
1992 }
1993
1994 #[test]
1997 fn a_function_with_no_byte_swap_in_it_is_left_exactly_as_it_was() {
1998 let (mut names, mut func) = one(&[Type::int(32)], &[Type::int(32)], |build, args| {
1999 build.ret(&[args[0]]);
2000 });
2001 let before = printed(&func, &mut names);
2002 bytes(&mut func);
2003 assert_eq!(printed(&func, &mut names), before);
2004 }
2005
2006 fn counting(op: Opcode, width: u32) -> (Interner, Func) {
2008 let ty = Type::int(width);
2009 one(&[ty], &[ty], |build, args| {
2010 let c = build.unary(op, args[0], ty);
2011 build.ret(&[c]);
2012 })
2013 }
2014
2015 #[test]
2018 fn the_counting_masks_are_the_ones_the_halving_sum_is_written_with() {
2019 assert_eq!(alternating(32, 1), 0x5555_5555);
2020 assert_eq!(alternating(32, 2), 0x3333_3333);
2021 assert_eq!(alternating(32, 4), 0x0f0f_0f0f);
2022 assert_eq!(every(32, 8, 1), 0x0101_0101);
2023 assert_eq!(every(64, 8, 1), 0x0101_0101_0101_0101);
2024 }
2025
2026 #[test]
2029 fn a_set_bit_count_is_the_halving_sum_and_a_multiply_that_adds_the_bytes() {
2030 let (mut names, mut func) = counting(Opcode::Ctpop, 32);
2031 counts(&mut func);
2032
2033 let text = printed(&func, &mut names);
2034 assert!(!text.contains("ctpop"), "the instruction is gone: {text}");
2035 assert!(text.contains("iconst.i32 1431655765"), "the pairs mask: {text}");
2036 assert!(text.contains("iconst.i32 858993459"), "the nibbles mask: {text}");
2037 assert!(text.contains("iconst.i32 252645135"), "the bytes mask: {text}");
2038 assert_eq!(text.matches(" mul ").count(), 1, "one multiply: {text}");
2039 assert!(text.contains("iconst.i32 24"), "and the top byte is the answer: {text}");
2040 }
2041
2042 #[test]
2044 fn a_count_of_one_byte_stops_before_the_multiply() {
2045 let (mut names, mut func) = counting(Opcode::Ctpop, 8);
2046 counts(&mut func);
2047 let text = printed(&func, &mut names);
2048 assert!(!text.contains("ctpop"), "{text}");
2049 assert!(!text.contains(" mul "), "nothing to add together: {text}");
2050 }
2051
2052 #[test]
2055 fn a_leading_zero_count_smears_the_value_down_and_counts_the_complement() {
2056 let (mut names, mut func) = counting(Opcode::Ctlz, 32);
2057 counts(&mut func);
2058
2059 let text = printed(&func, &mut names);
2060 assert!(!text.contains("ctlz"), "the instruction is gone: {text}");
2061 assert!(!text.contains("ctpop"), "and so is the count it became: {text}");
2062 for by in ["iconst.i32 1", "iconst.i32 2", "iconst.i32 4", "iconst.i32 8", "iconst.i32 16"]
2063 {
2064 assert!(text.contains(by), "{by} is a smearing step: {text}");
2065 }
2066 assert_eq!(text.matches(" xor ").count(), 1, "one complement: {text}");
2067 }
2068
2069 #[test]
2071 fn a_trailing_zero_count_masks_the_bits_below_the_lowest_set_one() {
2072 let (mut names, mut func) = counting(Opcode::Cttz, 32);
2073 counts(&mut func);
2074
2075 let text = printed(&func, &mut names);
2076 assert!(!text.contains("cttz"), "the instruction is gone: {text}");
2077 assert!(!text.contains("ctpop"), "and so is the count it became: {text}");
2078 assert!(text.contains("iconst.i32 -1"), "the complement and the decrement: {text}");
2079 assert_eq!(text.matches(" xor ").count(), 1, "one complement: {text}");
2080 assert!(text.matches(" or ").count() <= 1, "no smearing run: {text}");
2082 }
2083
2084 #[test]
2087 fn what_a_bit_count_becomes_is_ir_that_verifies() {
2088 for op in [Opcode::Ctpop, Opcode::Ctlz, Opcode::Cttz] {
2089 for width in [8u32, 16, 32, 64] {
2090 let (mut names, mut func) = counting(op, width);
2091 counts(&mut func);
2092 let module = Module::new(names.intern("c.c"), &target());
2093 rucc_ir::verify_func(&module, &func, &names)
2094 .unwrap_or_else(|e| panic!("{op:?} at {width}: {e:?}"));
2095 }
2096 }
2097 }
2098
2099 #[test]
2103 fn a_width_the_halving_sum_is_not_written_for_is_left_alone() {
2104 let (mut names, mut func) = counting(Opcode::Ctpop, 24);
2105 counts(&mut func);
2106 assert!(printed(&func, &mut names).contains("ctpop"), "left as it was");
2107 }
2108
2109 #[test]
2111 fn a_function_with_no_bit_count_in_it_is_left_exactly_as_it_was() {
2112 let (mut names, mut func) = one(&[Type::int(32)], &[Type::int(32)], |build, args| {
2113 build.ret(&[args[0]]);
2114 });
2115 let before = printed(&func, &mut names);
2116 counts(&mut func);
2117 assert_eq!(printed(&func, &mut names), before);
2118 }
2119
2120 fn checking(op: Opcode, width: u32) -> (Interner, Func) {
2123 let ty = Type::int(width);
2124 let bit = ty.with_lane(Type::I1);
2125 one(&[ty, ty], &[ty, bit], |build, args| {
2126 let (value, flag) = build.checked(op, args[0], args[1]);
2127 build.ret(&[value, flag]);
2128 })
2129 }
2130
2131 #[test]
2134 fn a_checked_unsigned_add_becomes_an_add_and_one_comparison() {
2135 let (mut names, mut func) = checking(Opcode::UAddOverflow, 32);
2136 overflows(&mut func);
2137
2138 let text = printed(&func, &mut names);
2139 assert!(!text.contains("uadd_overflow"), "the instruction is gone: {text}");
2140 assert_eq!(text.matches(" add ").count(), 1, "one add: {text}");
2141 assert_eq!(text.matches("icmp ult").count(), 1, "and one comparison: {text}");
2142 assert!(!text.contains(" xor "), "nothing about sign bits: {text}");
2143 }
2144
2145 #[test]
2148 fn a_checked_signed_add_becomes_an_add_and_the_sign_bit_of_two_exclusive_ors() {
2149 let (mut names, mut func) = checking(Opcode::SAddOverflow, 32);
2150 overflows(&mut func);
2151
2152 let text = printed(&func, &mut names);
2153 assert!(!text.contains("sadd_overflow"), "the instruction is gone: {text}");
2154 assert_eq!(text.matches(" add ").count(), 1, "one add: {text}");
2155 assert_eq!(text.matches(" xor ").count(), 2, "the answer against each operand: {text}");
2156 assert_eq!(text.matches(" and ").count(), 1, "both at once: {text}");
2157 assert!(text.contains("icmp slt"), "and its sign bit: {text}");
2158 }
2159
2160 #[test]
2163 fn a_checked_unsigned_subtract_compares_the_operands_and_not_the_answer() {
2164 let (mut names, mut func) = checking(Opcode::USubOverflow, 64);
2165 overflows(&mut func);
2166
2167 let text = printed(&func, &mut names);
2168 assert!(!text.contains("usub_overflow"), "the instruction is gone: {text}");
2169 assert_eq!(text.matches(" sub ").count(), 1, "one subtract: {text}");
2170 assert!(text.contains("icmp ult %0, %1"), "the operands, in order: {text}");
2171 }
2172
2173 #[test]
2180 fn a_checked_multiply_becomes_a_multiply_and_the_high_half_of_the_product() {
2181 let (mut names, mut func) = checking(Opcode::UMulOverflow, 64);
2182 overflows(&mut func);
2183
2184 let text = printed(&func, &mut names);
2185 assert!(!text.contains("umul_overflow"), "the instruction is gone: {text}");
2186 assert_eq!(text.matches(" mul ").count(), 5, "the answer and the four halves: {text}");
2187 assert!(text.contains("iconst.i64 32"), "split at half the width: {text}");
2188 assert!(text.contains("iconst.i64 4294967295"), "and masked to it: {text}");
2189 assert!(text.contains("icmp ne"), "the high half against zero: {text}");
2190 assert!(!text.contains("ashr"), "and nothing corrected for sign: {text}");
2191 }
2192
2193 #[test]
2196 fn a_checked_signed_multiply_corrects_the_high_half_for_each_negative_operand() {
2197 let (mut names, mut func) = checking(Opcode::SMulOverflow, 64);
2198 overflows(&mut func);
2199
2200 let text = printed(&func, &mut names);
2201 assert!(!text.contains("smul_overflow"), "the instruction is gone: {text}");
2202 assert_eq!(
2203 text.matches(" ashr ").count(),
2204 3,
2205 "each operand's sign, and the answer: {text}"
2206 );
2207 assert!(text.contains("iconst.i64 63"), "spread from the top bit: {text}");
2208 assert_eq!(text.matches(" sub ").count(), 2, "one correction per operand: {text}");
2209 }
2210
2211 #[test]
2215 fn both_results_are_substituted_into_whoever_was_reading_them() {
2216 let (mut names, mut func) = checking(Opcode::SAddOverflow, 32);
2217 overflows(&mut func);
2218
2219 let text = printed(&func, &mut names);
2223 assert_eq!(
2224 text,
2225 concat!(
2226 "func @f(i32, i32) -> (i32, i1), linkage(external) {\n",
2227 "block0(%0: i32, %1: i32):\n",
2228 " %2 = add %0, %1\n",
2229 " %3 = xor %0, %2\n",
2230 " %4 = xor %1, %2\n",
2231 " %5 = and %3, %4\n",
2232 " %6 = iconst.i32 0\n",
2233 " %7 = icmp slt %5, %6\n",
2234 " return %2, %7\n",
2235 "}\n",
2236 ),
2237 );
2238 }
2239
2240 #[test]
2243 fn what_an_overflow_check_becomes_is_ir_that_verifies() {
2244 let all = [
2245 Opcode::UAddOverflow,
2246 Opcode::SAddOverflow,
2247 Opcode::USubOverflow,
2248 Opcode::SSubOverflow,
2249 Opcode::UMulOverflow,
2250 Opcode::SMulOverflow,
2251 ];
2252 for op in all {
2253 for width in [8u32, 16, 32, 64, 128] {
2254 let (mut names, mut func) = checking(op, width);
2255 overflows(&mut func);
2256 let module = Module::new(names.intern("c.c"), &target());
2257 rucc_ir::verify_func(&module, &func, &names)
2258 .unwrap_or_else(|e| panic!("{op:?} at {width}: {e:?}"));
2259 }
2260 }
2261 }
2262
2263 #[test]
2267 fn a_width_the_split_is_not_written_for_is_left_alone() {
2268 let (mut names, mut func) = checking(Opcode::UMulOverflow, 24);
2269 overflows(&mut func);
2270 assert!(printed(&func, &mut names).contains("umul_overflow"), "left as it was");
2271 }
2272
2273 #[test]
2280 fn a_check_at_the_width_no_register_holds_is_rewritten_here() {
2281 let (mut names, mut func) = checking(Opcode::UAddOverflow, 128);
2282 overflows(&mut func);
2283 let text = printed(&func, &mut names);
2284 assert!(!text.contains("uadd_overflow"), "the check is gone: {text}");
2285 assert!(text.contains(" = add "), "into the arithmetic it is: {text}");
2286 assert!(text.contains("icmp ult"), "and the test that says it wrapped: {text}");
2287 }
2288
2289 #[test]
2291 fn a_function_with_no_overflow_check_in_it_is_left_exactly_as_it_was() {
2292 let (mut names, mut func) = one(&[Type::int(32)], &[Type::int(32)], |build, args| {
2293 build.ret(&[args[0]]);
2294 });
2295 let before = printed(&func, &mut names);
2296 overflows(&mut func);
2297 assert_eq!(printed(&func, &mut names), before);
2298 }
2299
2300 fn reading(ty: Type, align: u32, order: MemOrder) -> (Interner, Func) {
2302 one(&[Type::PTR], &[ty], |build, args| {
2303 let info = MemInfo { order, ..access(0, align) };
2304 let value = build.atomic_load(ty, args[0], info, Flags::NONE);
2305 build.ret(&[value]);
2306 })
2307 }
2308
2309 fn writing(ty: Type, align: u32, order: MemOrder) -> (Interner, Func) {
2311 one(&[Type::PTR, ty], &[], |build, args| {
2312 let info = MemInfo { order, ..access(0, align) };
2313 build.atomic_store(args[1], args[0], info, Flags::NONE);
2314 build.ret(&[]);
2315 })
2316 }
2317
2318 #[test]
2325 fn an_ordered_access_becomes_the_plain_one_this_machine_already_orders() {
2326 for order in [MemOrder::Relaxed, MemOrder::Acquire, MemOrder::SeqCst] {
2327 let (mut names, mut func) = reading(Type::int(32), 4, order);
2328 orderings(&mut func, 8);
2329 let text = printed(&func, &mut names);
2330 assert!(text.contains("load.i32"), "{order:?}: {text}");
2331 assert!(!text.contains("atomic_load"), "{order:?}: {text}");
2332 assert!(!text.contains(order.name()), "the ordering came off: {text}");
2333 }
2334
2335 for order in [MemOrder::Relaxed, MemOrder::Release] {
2336 let (mut names, mut func) = writing(Type::int(32), 4, order);
2337 orderings(&mut func, 8);
2338 let text = printed(&func, &mut names);
2339 assert!(text.contains("store %1 -> %0"), "{order:?}: {text}");
2340 assert!(!text.contains("atomic_store"), "{order:?}: {text}");
2341 assert!(!text.contains("fence"), "{order:?} costs nothing here: {text}");
2342 }
2343 }
2344
2345 #[test]
2351 fn the_strongest_store_keeps_a_barrier_behind_it() {
2352 let (mut names, mut func) = writing(Type::int(32), 4, MemOrder::SeqCst);
2353 orderings(&mut func, 8);
2354 let text = printed(&func, &mut names);
2355 let (before, after) = text.split_once("fence seq_cst").expect("a barrier");
2356 assert!(before.contains("store %1 -> %0"), "the store comes first: {text}");
2357 assert!(!after.contains("store"), "and nothing is between them: {text}");
2358 assert!(!text.contains("atomic_store"), "{text}");
2359 }
2360
2361 #[test]
2364 fn a_barrier_is_left_for_the_place_that_knows_what_one_costs() {
2365 for order in MemOrder::all().filter(|&order| order != MemOrder::NotAtomic) {
2366 let (mut names, mut func) = one(&[], &[], |build, _| {
2367 build.fence(order);
2368 build.ret(&[]);
2369 });
2370 let before = printed(&func, &mut names);
2371 orderings(&mut func, 8);
2372 assert_eq!(printed(&func, &mut names), before, "{order:?}");
2373 }
2374 }
2375
2376 #[test]
2382 fn an_access_this_machine_cannot_do_in_one_go_is_left_alone() {
2383 for (ty, align) in [(Type::int(128), 16), (Type::int(64), 4)] {
2384 let (mut names, mut func) = reading(ty, align, MemOrder::SeqCst);
2385 orderings(&mut func, 8);
2386 assert!(printed(&func, &mut names).contains("atomic_load"), "left as it was");
2387 }
2388 }
2389
2390 #[test]
2393 fn what_the_ordered_accesses_become_verifies() {
2394 for order in MemOrder::all().filter(|&order| order != MemOrder::NotAtomic) {
2395 for (mut names, mut func) in
2396 [reading(Type::int(32), 4, order), writing(Type::int(32), 4, order)]
2397 {
2398 if !order.is_valid_for_load() && !order.is_valid_for_store() {
2399 continue;
2400 }
2401 orderings(&mut func, 8);
2402 let module = Module::new(names.intern("a.c"), &target());
2403 rucc_ir::verify_func(&module, &func, &names)
2404 .unwrap_or_else(|e| panic!("{order:?}: {e:?}"));
2405 }
2406 }
2407 }
2408
2409 #[test]
2411 fn a_function_with_no_ordered_access_in_it_is_left_exactly_as_it_was() {
2412 let (mut names, mut func) = one(&[Type::int(32)], &[Type::int(32)], |build, args| {
2413 build.ret(&[args[0]]);
2414 });
2415 let before = printed(&func, &mut names);
2416 orderings(&mut func, 8);
2417 assert_eq!(printed(&func, &mut names), before);
2418 }
2419}