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
49use crate::capability;
50
51pub fn orderings(func: &mut Func, word: u32) {
88 let found: Vec<Inst> =
89 func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
90 for inst in found {
91 match func[inst].opcode {
92 Opcode::AtomicLoad => relaxed(func, inst, Opcode::Load, word),
93 Opcode::AtomicStore => relaxed(func, inst, Opcode::Store, word),
94 _ => {}
95 }
96 }
97}
98
99fn relaxed(func: &mut Func, inst: Inst, plain: Opcode, word: u32) {
113 let Extra::Mem(mem) = func[inst].extra else { return };
114 let info = func[mem];
115 let ty = match plain {
116 Opcode::Store => match func[func[inst].args].first() {
117 Some(&value) => func[value].ty,
118 None => return,
119 },
120 _ => produced(func, inst),
121 };
122 if !indivisible(ty, info, word) {
123 return;
124 }
125 let unordered = MemInfo { order: MemOrder::NotAtomic, ..info };
126
127 if plain == Opcode::Store && info.order == MemOrder::SeqCst {
128 let [value, addr] = func[func[inst].args] else { return };
129 write(func, inst, value, addr, unordered);
130 let none = func.push_values(&[]);
131 let data = &mut func[inst];
132 data.opcode = Opcode::Fence;
133 data.args = none;
134 data.extra = Extra::Order(MemOrder::SeqCst);
135 data.flags = data.flags.intersection(Flags::legal_on(Opcode::Fence));
136 return;
137 }
138
139 let plainly = func.add_mem(unordered);
140 let data = &mut func[inst];
141 data.opcode = plain;
142 data.extra = Extra::Mem(plainly);
143 data.flags = data.flags.intersection(Flags::legal_on(plain));
144}
145
146fn indivisible(ty: Type, info: MemInfo, word: u32) -> bool {
159 let bytes = if ty.is_ptr() { word } else { ty.bits().div_ceil(8) };
160 ty.is_scalar() && bytes.is_power_of_two() && bytes <= word && info.align >= bytes
161}
162
163pub fn floats(func: &mut Func) {
177 let found: Vec<Inst> =
178 func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
179 for inst in found {
180 match func[inst].opcode {
181 Opcode::FConst => constant(func, inst),
182 Opcode::FNeg => negate(func, inst),
183 Opcode::SIToFP | Opcode::UIToFP => widen_then_convert(func, inst),
184 Opcode::FPToSI | Opcode::FPToUI => convert_then_narrow(func, inst),
185 _ => {}
186 }
187 }
188}
189
190fn constant(func: &mut Func, inst: Inst) {
201 let ty = produced(func, inst);
202 let Extra::Imm(imm) = func[inst].extra else { return };
203 if !ty.is_float() || !ty.is_scalar() || ty.bits() > 64 {
204 return;
205 }
206 let int = Type::int(ty.bits());
207 let bits = func[imm].bits();
208 let spelled = ahead_const(func, inst, Imm::int(bits as i128, int), int);
211 becomes(func, inst, Opcode::Bitcast, &[spelled]);
212}
213
214fn negate(func: &mut Func, inst: Inst) {
230 let ty = produced(func, inst);
231 let Some(&arg) = func[func[inst].args].first() else { return };
232 if !ty.is_float() || !ty.is_scalar() || ty.bits() > 64 {
233 return;
234 }
235 let int = Type::int(ty.bits());
236 let bits = ahead(func, inst, Opcode::Bitcast, &[arg], int);
237 let mask = ahead_const(func, inst, Imm::int(1i128 << (ty.bits() - 1), int), int);
238 let flipped = ahead(func, inst, Opcode::Xor, &[bits, mask], int);
239 becomes(func, inst, Opcode::Bitcast, &[flipped]);
240}
241
242fn widen_then_convert(func: &mut Func, inst: Inst) {
249 let signed = func[inst].opcode == Opcode::SIToFP;
250 let Some(&arg) = func[func[inst].args].first() else { return };
251 let from = func[arg].ty;
252 if !from.is_int() || !from.is_scalar() {
253 return;
254 }
255 let Some(width) = holder(from.bits(), signed) else {
256 from_unsigned_word(func, inst, arg, from);
257 return;
258 };
259 if width == from.bits() {
260 return;
261 }
262 let widen = if signed { Opcode::SExt } else { Opcode::ZExt };
263 let wide = ahead(func, inst, widen, &[arg], Type::int(width));
264 becomes(func, inst, Opcode::SIToFP, &[wide]);
265}
266
267fn convert_then_narrow(func: &mut Func, inst: Inst) {
274 let signed = func[inst].opcode == Opcode::FPToSI;
275 let ty = produced(func, inst);
276 let Some(&arg) = func[func[inst].args].first() else { return };
277 if !ty.is_int() || !ty.is_scalar() {
278 return;
279 }
280 let Some(width) = holder(ty.bits(), signed) else {
281 to_unsigned_word(func, inst, arg, ty);
282 return;
283 };
284 if width == ty.bits() {
285 return;
286 }
287 let wide = ahead(func, inst, Opcode::FPToSI, &[arg], Type::int(width));
288 becomes(func, inst, Opcode::Trunc, &[wide]);
289}
290
291fn from_unsigned_word(func: &mut Func, inst: Inst, arg: Value, from: Type) {
313 let ty = produced(func, inst);
314 if !ty.is_float() || !ty.is_scalar() {
315 return;
316 }
317 if ty.bits() > 64 {
318 from_unsigned_word_wide(func, inst, arg, from);
319 return;
320 }
321 let spread = spread_top_bit(func, inst, arg, from);
322
323 let one = ahead_const(func, inst, Imm::int(1, from), from);
325 let lost = ahead(func, inst, Opcode::And, &[arg, one], from);
326 let half = ahead(func, inst, Opcode::LShr, &[arg, one], from);
327 let odd = ahead(func, inst, Opcode::Or, &[half, lost], from);
328
329 let differ = ahead(func, inst, Opcode::Xor, &[arg, odd], from);
331 let taken = ahead(func, inst, Opcode::And, &[differ, spread], from);
332 let source = ahead(func, inst, Opcode::Xor, &[arg, taken], from);
333 let converted = ahead(func, inst, Opcode::SIToFP, &[source], ty);
334
335 let bits = Type::int(ty.bits());
338 let narrow = same_width(func, inst, spread, from, bits);
339 let raw = ahead(func, inst, Opcode::Bitcast, &[converted], bits);
340 let again = ahead(func, inst, Opcode::And, &[raw, narrow], bits);
341 let addend = ahead(func, inst, Opcode::Bitcast, &[again], ty);
342 becomes(func, inst, Opcode::FAdd, &[converted, addend]);
343}
344
345fn to_unsigned_word(func: &mut Func, inst: Inst, arg: Value, ty: Type) {
358 let from = func[arg].ty;
359 if !from.is_float() || !from.is_scalar() {
360 return;
361 }
362 if from.bits() > 64 {
363 to_unsigned_word_wide(func, inst, arg, ty);
364 return;
365 }
366 let bits = Type::int(from.bits());
368 let pattern = Imm::int(half_the_range(from.bits()), bits);
369 let spelled = ahead_const(func, inst, pattern, bits);
370 let half = ahead(func, inst, Opcode::Bitcast, &[spelled], from);
371
372 let over = ahead_cmp(func, inst, Opcode::FCmp, Extra::FloatPred(FloatPred::Oge), &[arg, half]);
373 let wide = ahead(func, inst, Opcode::ZExt, &[over], bits);
374 let zero = ahead_const(func, inst, Imm::int(0, bits), bits);
375 let spread = ahead(func, inst, Opcode::Sub, &[zero, wide], bits);
376
377 let amount = ahead(func, inst, Opcode::And, &[spread, spelled], bits);
378 let taken = ahead(func, inst, Opcode::Bitcast, &[amount], from);
379 let under = ahead(func, inst, Opcode::FSub, &[arg, taken], from);
380 let low = ahead(func, inst, Opcode::FPToSI, &[under], ty);
381
382 let again = ahead(func, inst, Opcode::ZExt, &[over], ty);
384 let up = ahead_const(func, inst, Imm::int(i128::from(ty.bits() - 1), ty), ty);
385 let top = ahead(func, inst, Opcode::Shl, &[again, up], ty);
386 becomes(func, inst, Opcode::Xor, &[low, top]);
387}
388
389fn from_unsigned_word_wide(func: &mut Func, inst: Inst, arg: Value, from: Type) {
411 let ty = produced(func, inst);
412 let zero = ahead_const(func, inst, Imm::int(0, from), from);
413 let over = ahead_cmp(func, inst, Opcode::ICmp, Extra::IntPred(IntPred::Slt), &[arg, zero]);
414
415 let signed = ahead(func, inst, Opcode::SIToFP, &[arg], ty);
416 let range = ahead_float(func, inst, two_to_the(64), ty);
417 let flag = flag_as_float(func, inst, over, ty);
418 let addend = ahead(func, inst, Opcode::FMul, &[range, flag], ty);
419 becomes(func, inst, Opcode::FAdd, &[signed, addend]);
420}
421
422fn to_unsigned_word_wide(func: &mut Func, inst: Inst, arg: Value, ty: Type) {
434 let from = func[arg].ty;
435 let half = ahead_float(func, inst, two_to_the(63), from);
436 let over = ahead_cmp(func, inst, Opcode::FCmp, Extra::FloatPred(FloatPred::Oge), &[arg, half]);
437
438 let flag = flag_as_float(func, inst, over, from);
439 let taken = ahead(func, inst, Opcode::FMul, &[half, flag], from);
440 let under = ahead(func, inst, Opcode::FSub, &[arg, taken], from);
441 let low = ahead(func, inst, Opcode::FPToSI, &[under], ty);
442
443 let again = ahead(func, inst, Opcode::ZExt, &[over], ty);
445 let up = ahead_const(func, inst, Imm::int(i128::from(ty.bits() - 1), ty), ty);
446 let top = ahead(func, inst, Opcode::Shl, &[again, up], ty);
447 becomes(func, inst, Opcode::Xor, &[low, top]);
448}
449
450fn flag_as_float(func: &mut Func, inst: Inst, cond: Value, ty: Type) -> Value {
456 let wide = ahead(func, inst, Opcode::ZExt, &[cond], Type::int(64));
457 ahead(func, inst, Opcode::SIToFP, &[wide], ty)
458}
459
460const fn two_to_the(power: u32) -> u128 {
465 ((0x3fff + power as u128) << 64) | 0x8000_0000_0000_0000
466}
467
468fn spread_top_bit(func: &mut Func, inst: Inst, arg: Value, ty: Type) -> Value {
474 let zero = ahead_const(func, inst, Imm::int(0, ty), ty);
475 let set = ahead_cmp(func, inst, Opcode::ICmp, Extra::IntPred(IntPred::Slt), &[arg, zero]);
476 let wide = ahead(func, inst, Opcode::ZExt, &[set], ty);
477 ahead(func, inst, Opcode::Sub, &[zero, wide], ty)
478}
479
480fn same_width(func: &mut Func, inst: Inst, value: Value, from: Type, to: Type) -> Value {
482 match to.bits().cmp(&from.bits()) {
483 Ordering::Equal => value,
484 Ordering::Less => ahead(func, inst, Opcode::Trunc, &[value], to),
485 Ordering::Greater => ahead(func, inst, Opcode::SExt, &[value], to),
486 }
487}
488
489fn half_the_range(width: u32) -> i128 {
495 match width {
496 32 => 0x5F00_0000,
497 _ => 0x43E0_0000_0000_0000,
498 }
499}
500
501pub fn bytes(func: &mut Func) {
515 let found: Vec<Inst> =
516 func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
517 for inst in found {
518 if func[inst].opcode == Opcode::Bswap {
519 swap(func, inst);
520 }
521 }
522}
523
524fn swap(func: &mut Func, inst: Inst) {
541 let ty = produced(func, inst);
542 let Some(&arg) = func[func[inst].args].first() else { return };
543 if !ty.is_int() || !ty.is_scalar() || ty.bits() < 16 || ty.bits() % 8 != 0 {
544 return;
545 }
546
547 let mut value = arg;
548 let mut group = ty.bits() / 2;
549 while group >= 8 {
550 let mask = alternating(ty.bits(), group);
553 let keep = ahead_const(func, inst, Imm::int(mask, ty), ty);
554 let count = ahead_const(func, inst, Imm::int(i128::from(group), ty), ty);
555 let low = ahead(func, inst, Opcode::And, &[value, keep], ty);
556 let up = ahead(func, inst, Opcode::Shl, &[low, count], ty);
557 let down = ahead(func, inst, Opcode::LShr, &[value, count], ty);
558 let high = ahead(func, inst, Opcode::And, &[down, keep], ty);
559 if group == 8 {
562 becomes(func, inst, Opcode::Or, &[up, high]);
563 return;
564 }
565 value = ahead(func, inst, Opcode::Or, &[up, high], ty);
566 group /= 2;
567 }
568}
569
570fn alternating(width: u32, group: u32) -> i128 {
581 every(width, group * 2, group)
582}
583
584fn every(width: u32, step: u32, run: u32) -> i128 {
593 let ones = (1i128 << run) - 1;
594 let mut mask = 0i128;
595 let mut at = 0;
596 while at < width {
597 mask |= ones << at;
598 at += step;
599 }
600 mask
601}
602
603pub fn counts(func: &mut Func) {
617 let found: Vec<Inst> =
618 func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
619 for inst in found {
620 match func[inst].opcode {
621 Opcode::Ctlz => searched(func, inst, true),
622 Opcode::Cttz => searched(func, inst, false),
623 _ => {}
624 }
625 }
626 let found: Vec<Inst> =
627 func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
628 for inst in found {
629 if func[inst].opcode == Opcode::Ctpop {
630 counted(func, inst);
631 }
632 }
633}
634
635fn searched(func: &mut Func, inst: Inst, leading: bool) {
653 let ty = produced(func, inst);
654 let Some(&arg) = func[func[inst].args].first() else { return };
655 if !countable(ty) {
656 return;
657 }
658 let ones = ahead_const(func, inst, Imm::int(-1, ty), ty);
659 if leading {
660 let mut value = arg;
661 let mut by = 1;
662 while by < ty.bits() {
663 let count = ahead_const(func, inst, Imm::int(i128::from(by), ty), ty);
664 let down = ahead(func, inst, Opcode::LShr, &[value, count], ty);
665 value = ahead(func, inst, Opcode::Or, &[value, down], ty);
666 by *= 2;
667 }
668 let above = ahead(func, inst, Opcode::Xor, &[value, ones], ty);
669 becomes(func, inst, Opcode::Ctpop, &[above]);
670 return;
671 }
672 let missing = ahead(func, inst, Opcode::Xor, &[arg, ones], ty);
673 let less = ahead(func, inst, Opcode::Add, &[arg, ones], ty);
674 let below = ahead(func, inst, Opcode::And, &[missing, less], ty);
675 becomes(func, inst, Opcode::Ctpop, &[below]);
676}
677
678fn counted(func: &mut Func, inst: Inst) {
692 let ty = produced(func, inst);
693 let Some(&arg) = func[func[inst].args].first() else { return };
694 if !countable(ty) {
695 return;
696 }
697 let width = ty.bits();
698 let pairs = ahead_const(func, inst, Imm::int(alternating(width, 1), ty), ty);
699 let two = ahead_const(func, inst, Imm::int(2, ty), ty);
700 let one = ahead_const(func, inst, Imm::int(1, ty), ty);
701 let high = ahead(func, inst, Opcode::LShr, &[arg, one], ty);
702 let odd = ahead(func, inst, Opcode::And, &[high, pairs], ty);
703 let bits = ahead(func, inst, Opcode::Sub, &[arg, odd], ty);
704
705 let quads = ahead_const(func, inst, Imm::int(alternating(width, 2), ty), ty);
706 let low = ahead(func, inst, Opcode::And, &[bits, quads], ty);
707 let up = ahead(func, inst, Opcode::LShr, &[bits, two], ty);
708 let rest = ahead(func, inst, Opcode::And, &[up, quads], ty);
709 let nibbles = ahead(func, inst, Opcode::Add, &[low, rest], ty);
710
711 let four = ahead_const(func, inst, Imm::int(4, ty), ty);
712 let bytes = ahead_const(func, inst, Imm::int(alternating(width, 4), ty), ty);
713 let folded = ahead(func, inst, Opcode::LShr, &[nibbles, four], ty);
714 let summed = ahead(func, inst, Opcode::Add, &[nibbles, folded], ty);
715 if width == 8 {
716 becomes(func, inst, Opcode::And, &[summed, bytes]);
717 return;
718 }
719 let held = ahead(func, inst, Opcode::And, &[summed, bytes], ty);
720
721 let spread = ahead_const(func, inst, Imm::int(every(width, 8, 1), ty), ty);
722 let top = ahead_const(func, inst, Imm::int(i128::from(width - 8), ty), ty);
723 let total = ahead(func, inst, Opcode::Mul, &[held, spread], ty);
724 becomes(func, inst, Opcode::LShr, &[total, top]);
725}
726
727pub fn overflows(func: &mut Func) {
740 let found: Vec<Inst> =
741 func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
742 let mut forward = HashMap::new();
743 for inst in found {
744 let checked = match func[inst].opcode {
745 Opcode::UAddOverflow => Checked::Add(false),
746 Opcode::SAddOverflow => Checked::Add(true),
747 Opcode::USubOverflow => Checked::Sub(false),
748 Opcode::SSubOverflow => Checked::Sub(true),
749 Opcode::UMulOverflow => Checked::Mul(false),
750 Opcode::SMulOverflow => Checked::Mul(true),
751 _ => continue,
752 };
753 overflowed(func, inst, checked, &mut forward);
754 }
755 if !forward.is_empty() {
756 substitute(func, &forward);
757 }
758}
759
760#[derive(Debug, Clone, Copy)]
762enum Checked {
763 Add(bool),
765 Sub(bool),
767 Mul(bool),
769}
770
771fn overflowed(func: &mut Func, inst: Inst, checked: Checked, forward: &mut HashMap<Value, Value>) {
788 let ty = produced(func, inst);
789 let [a, b] = func[func[inst].args] else { return };
790 if !checkable(ty) {
791 return;
792 }
793 let (value, bit) = match checked {
794 Checked::Add(signed) => {
795 let value = ahead(func, inst, Opcode::Add, &[a, b], ty);
796 let bit = if signed {
797 let left = ahead(func, inst, Opcode::Xor, &[a, value], ty);
798 let right = ahead(func, inst, Opcode::Xor, &[b, value], ty);
799 let both = ahead(func, inst, Opcode::And, &[left, right], ty);
800 negative(func, inst, both, ty)
801 } else {
802 compared(func, inst, IntPred::Ult, value, a)
803 };
804 (value, bit)
805 }
806 Checked::Sub(signed) => {
807 let value = ahead(func, inst, Opcode::Sub, &[a, b], ty);
808 let bit = if signed {
809 let apart = ahead(func, inst, Opcode::Xor, &[a, b], ty);
810 let moved = ahead(func, inst, Opcode::Xor, &[a, value], ty);
811 let both = ahead(func, inst, Opcode::And, &[apart, moved], ty);
812 negative(func, inst, both, ty)
813 } else {
814 compared(func, inst, IntPred::Ult, a, b)
815 };
816 (value, bit)
817 }
818 Checked::Mul(signed) => {
819 let value = ahead(func, inst, Opcode::Mul, &[a, b], ty);
820 let high = high_half(func, inst, a, b, signed, ty);
821 let bit = if signed {
822 let sign = ahead_const(func, inst, Imm::int(i128::from(ty.bits() - 1), ty), ty);
823 let wanted = ahead(func, inst, Opcode::AShr, &[value, sign], ty);
824 compared(func, inst, IntPred::Ne, high, wanted)
825 } else {
826 let zero = ahead_const(func, inst, Imm::int(0, ty), ty);
827 compared(func, inst, IntPred::Ne, high, zero)
828 };
829 (value, bit)
830 }
831 };
832 let mut answers = func[inst].results();
833 if let (Some(wrapped), Some(flag)) = (answers.next(), answers.next()) {
834 forward.insert(wrapped, value);
835 forward.insert(flag, bit);
836 }
837 func.remove_inst(inst);
838}
839
840pub(crate) fn high_half(
862 func: &mut Func,
863 inst: Inst,
864 a: Value,
865 b: Value,
866 signed: bool,
867 ty: Type,
868) -> Value {
869 let width = ty.bits();
870 let half = width / 2;
871 let shift = ahead_const(func, inst, Imm::int(i128::from(half), ty), ty);
872 let mask = ahead_const(func, inst, Imm::int((1i128 << half) - 1, ty), ty);
873
874 let al = ahead(func, inst, Opcode::And, &[a, mask], ty);
875 let ah = ahead(func, inst, Opcode::LShr, &[a, shift], ty);
876 let bl = ahead(func, inst, Opcode::And, &[b, mask], ty);
877 let bh = ahead(func, inst, Opcode::LShr, &[b, shift], ty);
878
879 let ll = ahead(func, inst, Opcode::Mul, &[al, bl], ty);
880 let lh = ahead(func, inst, Opcode::Mul, &[al, bh], ty);
881 let hl = ahead(func, inst, Opcode::Mul, &[ah, bl], ty);
882 let hh = ahead(func, inst, Opcode::Mul, &[ah, bh], ty);
883
884 let over = ahead(func, inst, Opcode::LShr, &[ll, shift], ty);
887 let lh_low = ahead(func, inst, Opcode::And, &[lh, mask], ty);
888 let hl_low = ahead(func, inst, Opcode::And, &[hl, mask], ty);
889 let some = ahead(func, inst, Opcode::Add, &[over, lh_low], ty);
890 let carry = ahead(func, inst, Opcode::Add, &[some, hl_low], ty);
891
892 let lh_high = ahead(func, inst, Opcode::LShr, &[lh, shift], ty);
893 let hl_high = ahead(func, inst, Opcode::LShr, &[hl, shift], ty);
894 let up = ahead(func, inst, Opcode::LShr, &[carry, shift], ty);
895 let first = ahead(func, inst, Opcode::Add, &[hh, lh_high], ty);
896 let second = ahead(func, inst, Opcode::Add, &[first, hl_high], ty);
897 let high = ahead(func, inst, Opcode::Add, &[second, up], ty);
898 if !signed {
899 return high;
900 }
901 let top = ahead_const(func, inst, Imm::int(i128::from(width - 1), ty), ty);
902 let a_sign = ahead(func, inst, Opcode::AShr, &[a, top], ty);
903 let b_sign = ahead(func, inst, Opcode::AShr, &[b, top], ty);
904 let a_owes = ahead(func, inst, Opcode::And, &[a_sign, b], ty);
905 let b_owes = ahead(func, inst, Opcode::And, &[b_sign, a], ty);
906 let once = ahead(func, inst, Opcode::Sub, &[high, a_owes], ty);
907 ahead(func, inst, Opcode::Sub, &[once, b_owes], ty)
908}
909
910fn negative(func: &mut Func, inst: Inst, value: Value, ty: Type) -> Value {
912 let zero = ahead_const(func, inst, Imm::int(0, ty), ty);
913 compared(func, inst, IntPred::Slt, value, zero)
914}
915
916fn compared(func: &mut Func, inst: Inst, pred: IntPred, lhs: Value, rhs: Value) -> Value {
919 let ty = func[lhs].ty.with_lane(Type::I1);
920 let args = func.push_values(&[lhs, rhs]);
921 let extra = Extra::IntPred(pred);
922 written(func, inst, InstData { args, extra, ..InstData::new(Opcode::ICmp) }, ty)
923}
924
925fn substitute(func: &mut Func, forward: &HashMap<Value, Value>) {
932 let with = |value: Value| forward.get(&value).copied().unwrap_or(value);
933 for block in func.blocks().collect::<Vec<_>>() {
934 for inst in func.insts(block).collect::<Vec<Inst>>() {
935 let args = func[inst].args;
936 func.rewrite(args, with);
937 for call in func.successors(inst).collect::<Vec<_>>() {
938 func.rewrite(call.args, with);
939 }
940 }
941 }
942}
943
944fn countable(ty: Type) -> bool {
954 ty.is_int()
955 && ty.is_scalar()
956 && ty.bits() >= 8
957 && ty.bits() <= 64
958 && ty.bits().is_power_of_two()
959}
960
961fn checkable(ty: Type) -> bool {
969 countable(ty) || (ty.is_int() && ty.is_scalar() && ty.bits() == 128)
970}
971
972pub fn rounds(func: &mut Func, to: u32) {
992 let found: Vec<Inst> =
993 func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
994 for inst in found {
995 if func[inst].opcode != Opcode::Alloca {
996 continue;
997 }
998 let Some(&size) = func[func[inst].args].first() else { continue };
999 let ty = func[size].ty;
1000 if !ty.is_int() {
1001 continue;
1002 }
1003 let up = ahead_const(func, inst, Imm::int(i128::from(to) - 1, ty), ty);
1004 let mask = ahead_const(func, inst, Imm::int(-i128::from(to), ty), ty);
1005 let over = ahead(func, inst, Opcode::Add, &[size, up], ty);
1006 let rounded = ahead(func, inst, Opcode::And, &[over, mask], ty);
1007 let args = func.push_values(&[rounded]);
1008 func[inst].args = args;
1009 }
1010}
1011
1012pub const UNROLL: usize = 32;
1025
1026pub fn bulk(func: &mut Func, names: &mut Interner, word: u32) {
1037 let found: Vec<Inst> =
1038 func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
1039 for inst in found {
1040 match func[inst].opcode {
1041 Opcode::Memcpy => copy(func, names, inst, word),
1042 Opcode::Memset => fill(func, names, inst, word),
1043 Opcode::Memmove => library(func, names, inst, Opcode::Memmove, word),
1044 _ => {}
1045 }
1046 }
1047}
1048
1049fn copy(func: &mut Func, names: &mut Interner, inst: Inst, word: u32) {
1057 let Some(bulk) = func.bulk(inst) else { return };
1058 let (into, from) = (bulk.to, bulk.with);
1059 let Extra::Mem(mem) = func[inst].extra else { return };
1060 let info = func[mem];
1061 let Some(plan) = chunks(info, word).filter(|_| bulk.length.is_none()) else {
1063 return library(func, names, inst, Opcode::Memcpy, word);
1064 };
1065 for (at, width) in plan {
1066 let ty = Type::int(width * 8);
1067 let access = MemInfo { size: u64::from(width), align: width.min(info.align), ..info };
1068 let there = stepped(func, inst, from, at);
1069 let word = read(func, inst, there, access, ty);
1070 let here = stepped(func, inst, into, at);
1071 write(func, inst, word, here, access);
1072 }
1073 func.remove_inst(inst);
1074}
1075
1076fn fill(func: &mut Func, names: &mut Interner, inst: Inst, word: u32) {
1083 let Some(bulk) = func.bulk(inst) else { return };
1084 let (into, byte) = (bulk.to, bulk.with);
1085 let Extra::Mem(mem) = func[inst].extra else { return };
1086 let info = func[mem];
1087 let Some(spelled) = literal(func, byte) else {
1088 return library(func, names, inst, Opcode::Memset, word);
1089 };
1090 let Some(plan) = chunks(info, word).filter(|_| bulk.length.is_none()) else {
1092 return library(func, names, inst, Opcode::Memset, word);
1093 };
1094 for (at, width) in plan {
1095 let ty = Type::int(width * 8);
1096 let access = MemInfo { size: u64::from(width), align: width.min(info.align), ..info };
1097 let value = ahead_const(func, inst, Imm::int(spread(spelled, width) as i128, ty), ty);
1098 let here = stepped(func, inst, into, at);
1099 write(func, inst, value, here, access);
1100 }
1101 func.remove_inst(inst);
1102}
1103
1104fn library(func: &mut Func, names: &mut Interner, inst: Inst, opcode: Opcode, word: u32) {
1117 let mode = if opcode == Opcode::Memmove { "any" } else { "big" };
1122 let Some(routine) = capability::libcall(opcode, mode) else { return };
1123 let Some(bulk) = func.bulk(inst) else { return };
1124 let (into, second) = (bulk.to, bulk.with);
1125 let Extra::Mem(mem) = func[inst].extra else { return };
1126 let size = func[mem].size;
1127
1128 let words = Type::int(word * 8);
1132 let count = match bulk.length {
1136 Some(length) => fitted(func, inst, length, words),
1137 None => ahead_const(func, inst, Imm::int(i128::from(size), words), words),
1138 };
1139 let second = if opcode == Opcode::Memset { widened(func, inst, second) } else { second };
1142
1143 let sig = func.add_signature(Signature::new().with_params(&[
1144 Type::PTR,
1145 if opcode == Opcode::Memset { Type::int(32) } else { Type::PTR },
1146 words,
1147 ]));
1148 let callee = names.intern(routine);
1149 let varargs = func.push_abis(&[]);
1150 let info = func.add_call(CallInfo { callee: Some(callee), signature: sig, varargs });
1151 let args = func.push_values(&[into, second, count]);
1152 let data = &mut func[inst];
1153 data.opcode = Opcode::Call;
1154 data.args = args;
1155 data.extra = Extra::Call(info);
1156 data.flags = data.flags.intersection(Flags::legal_on(Opcode::Call));
1157}
1158
1159fn fitted(func: &mut Func, inst: Inst, value: Value, want: Type) -> Value {
1167 let ty = func[value].ty;
1168 if ty == want {
1169 return value;
1170 }
1171 let opcode = if ty.bits() < want.bits() { Opcode::ZExt } else { Opcode::Trunc };
1172 ahead(func, inst, opcode, &[value], want)
1173}
1174
1175fn widened(func: &mut Func, inst: Inst, value: Value) -> Value {
1177 let int = Type::int(32);
1178 let ty = func[value].ty;
1179 if ty == int {
1180 return value;
1181 }
1182 ahead(func, inst, Opcode::ZExt, &[value], int)
1183}
1184
1185fn chunks(info: MemInfo, word: u32) -> Option<Vec<(u64, u32)>> {
1198 plan(info.size, info.align, word)
1199}
1200
1201pub(crate) fn plan(size: u64, align: u32, word: u32) -> Option<Vec<(u64, u32)>> {
1209 let widest = word.min(align).max(1);
1210 if !widest.is_power_of_two() {
1211 return None;
1212 }
1213 let mut plan = Vec::new();
1214 let mut at = 0;
1215 let mut width = u64::from(widest);
1216 while at < size {
1217 while width > size - at {
1218 width /= 2;
1219 }
1220 plan.push((at, u32::try_from(width).ok()?));
1221 at += width;
1222 if plan.len() > UNROLL {
1223 return None;
1224 }
1225 }
1226 Some(plan)
1227}
1228
1229fn literal(func: &Func, value: Value) -> Option<u8> {
1231 let Def::Result { inst, .. } = func[value].def else { return None };
1232 if func[inst].opcode != Opcode::IConst {
1233 return None;
1234 }
1235 let Extra::Imm(imm) = func[inst].extra else { return None };
1236 u8::try_from(func[imm].bits() & 0xff).ok()
1237}
1238
1239fn spread(byte: u8, width: u32) -> u64 {
1241 (0..width).fold(0, |word, at| word | u64::from(byte) << (at * 8))
1242}
1243
1244fn stepped(func: &mut Func, inst: Inst, block: Value, at: u64) -> Value {
1247 if at == 0 {
1248 return block;
1249 }
1250 let step = ahead_const(func, inst, Imm::int(i128::from(at), Type::int(64)), Type::int(64));
1251 ahead(func, inst, Opcode::PtrAdd, &[block, step], Type::PTR)
1252}
1253
1254fn read(func: &mut Func, inst: Inst, from: Value, info: MemInfo, ty: Type) -> Value {
1256 let extra = Extra::Mem(func.add_mem(info));
1257 let args = func.push_values(&[from]);
1258 written(func, inst, InstData { args, extra, ..InstData::new(Opcode::Load) }, ty)
1259}
1260
1261fn write(func: &mut Func, inst: Inst, value: Value, into: Value, info: MemInfo) {
1263 let span = func.span(inst);
1264 let extra = Extra::Mem(func.add_mem(info));
1265 let args = func.push_values(&[value, into]);
1266 let data = InstData { args, extra, ..InstData::new(Opcode::Store) };
1267 let made = func.create_inst(data, &[], span);
1268 func.insert_before(made, inst);
1269}
1270
1271fn holder(bits: u32, signed: bool) -> Option<u32> {
1280 match if signed { bits } else { bits + 1 } {
1281 ..=32 => Some(32),
1282 33..=64 => Some(64),
1283 _ => None,
1284 }
1285}
1286
1287fn produced(func: &Func, inst: Inst) -> Type {
1292 func[inst].first_result.map_or(Type::VOID, |value| func[value].ty)
1293}
1294
1295fn ahead(func: &mut Func, inst: Inst, opcode: Opcode, args: &[Value], ty: Type) -> Value {
1297 let args = func.push_values(args);
1298 written(func, inst, InstData { args, ..InstData::new(opcode) }, ty)
1299}
1300
1301fn ahead_cmp(func: &mut Func, inst: Inst, opcode: Opcode, extra: Extra, args: &[Value]) -> Value {
1303 let args = func.push_values(args);
1304 written(func, inst, InstData { args, extra, ..InstData::new(opcode) }, Type::I1)
1305}
1306
1307fn ahead_const(func: &mut Func, inst: Inst, imm: Imm, ty: Type) -> Value {
1309 let extra = Extra::Imm(func.add_imm(imm));
1310 written(func, inst, InstData { extra, ..InstData::new(Opcode::IConst) }, ty)
1311}
1312
1313fn ahead_float(func: &mut Func, inst: Inst, bits: u128, ty: Type) -> Value {
1315 let extra = Extra::Imm(func.add_imm(Imm::from_bits(bits)));
1316 written(func, inst, InstData { extra, ..InstData::new(Opcode::FConst) }, ty)
1317}
1318
1319fn written(func: &mut Func, inst: Inst, data: InstData, ty: Type) -> Value {
1321 let span = func.span(inst);
1322 let made = func.create_inst(data, &[ty], span);
1323 func.insert_before(made, inst);
1324 func[made].first_result.expect("an instruction created with one result has one")
1325}
1326
1327fn becomes(func: &mut Func, inst: Inst, opcode: Opcode, args: &[Value]) {
1334 let args = func.push_values(args);
1335 let data = &mut func[inst];
1336 data.opcode = opcode;
1337 data.args = args;
1338 data.extra = Extra::None;
1339 data.flags = data.flags.intersection(Flags::legal_on(opcode));
1342}
1343
1344#[cfg(test)]
1345mod tests {
1346 use rucc_base::Interner;
1347 use rucc_ir::{Builder, Flags, Float, Func, Module, Opcode, Signature, Type};
1348 use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
1349
1350 use rucc_ir::{Extra, InstData, MemInfo, MemOrder, Restrict};
1351
1352 use super::{
1353 UNROLL, alternating, bulk, bytes, chunks, counts, every, floats, orderings, overflows,
1354 spread,
1355 };
1356
1357 fn target() -> TargetInfo {
1358 TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu))
1359 }
1360
1361 fn printed(func: &Func, names: &mut Interner) -> String {
1362 let module = Module::new(names.intern("sw.c"), &target());
1363 rucc_ir::print_func(&module, func, names)
1364 }
1365
1366 fn one(
1371 params: &[Type],
1372 returns: &[Type],
1373 body: impl FnOnce(&mut Builder<'_>, &[rucc_ir::Value]),
1374 ) -> (Interner, Func) {
1375 let mut names = Interner::new();
1376 let mut func = Func::new(
1377 names.intern("f"),
1378 Signature::new().with_params(params).with_returns(returns),
1379 );
1380 let entry = func.create_block();
1381 let args: Vec<_> = params.iter().map(|&ty| func.append_param(entry, ty)).collect();
1382 let mut build = Builder::new(&mut func, entry);
1383 body(&mut build, &args);
1384 (names, func)
1385 }
1386
1387 fn f64() -> Type {
1388 Type::float(Float::F64)
1389 }
1390
1391 fn f32() -> Type {
1392 Type::float(Float::F32)
1393 }
1394
1395 fn f80() -> Type {
1396 Type::float(Float::F80)
1397 }
1398
1399 const CASES: &[u64] = &[
1404 0,
1405 1,
1406 2,
1407 0x7FFF_FFFF,
1408 0x8000_0000,
1409 0xFFFF_FFFF,
1410 0x0020_0000_0000_0000,
1411 0x0020_0000_0000_0001,
1412 0x7FFF_FFFF_FFFF_FFFF,
1413 0x8000_0000_0000_0000,
1414 0x8000_0000_0000_0001,
1415 0x8000_0000_0000_0400,
1416 0xFFFF_FFFF_FFFF_F800,
1417 0xFFFF_FFFF_FFFF_FFFF,
1418 ];
1419
1420 fn valid(func: &Func, names: &mut Interner) {
1422 let module = Module::new(names.intern("f.c"), &target());
1423 rucc_ir::verify_func(&module, func, names).expect("the rewrite builds valid IR");
1424 }
1425
1426 #[test]
1428 fn a_float_constant_becomes_the_integer_that_spells_it_and_a_reading_of_those_bits() {
1429 let (mut names, mut func) = one(&[], &[f64()], |build, _| {
1430 let k = build.fconst(f64(), 0x3ff8_0000_0000_0000);
1431 build.ret(&[k]);
1432 });
1433 floats(&mut func);
1434
1435 let text = printed(&func, &mut names);
1436 assert!(!text.contains("fconst"), "the float constant is gone: {text}");
1437 assert!(text.contains("iconst.i64 4609434218613702656"), "the bits, as an integer: {text}");
1438 assert!(text.contains("bitcast"), "read back as the float: {text}");
1439 }
1440
1441 #[test]
1444 fn a_constant_at_the_narrow_format_is_an_integer_of_the_narrow_width() {
1445 let (mut names, mut func) = one(&[], &[f32()], |build, _| {
1446 let k = build.fconst(f32(), 0x4020_0000);
1447 build.ret(&[k]);
1448 });
1449 floats(&mut func);
1450 assert!(printed(&func, &mut names).contains("iconst.i32"), "an i32, not an i64");
1451 }
1452
1453 #[test]
1456 fn a_negation_flips_the_sign_bit_and_touches_no_other() {
1457 let (mut names, mut func) = one(&[f64()], &[f64()], |build, args| {
1458 let n = build.unary(Opcode::FNeg, args[0], f64());
1459 build.ret(&[n]);
1460 });
1461 floats(&mut func);
1462
1463 let text = printed(&func, &mut names);
1464 assert!(!text.contains("fneg"), "the negation is gone: {text}");
1465 assert!(!text.contains("fsub"), "and it did not become a subtraction: {text}");
1466 assert!(text.contains("iconst.i64 -9223372036854775808"), "the sign bit alone: {text}");
1467 assert_eq!(text.matches("xor").count(), 1, "one exclusive or: {text}");
1468 assert_eq!(text.matches("bitcast").count(), 2, "there and back: {text}");
1469 }
1470
1471 #[test]
1473 fn an_unsigned_integer_becoming_a_float_widens_first_and_then_converts_as_signed() {
1474 let (mut names, mut func) = one(&[Type::int(32)], &[f64()], |build, args| {
1475 let d = build.unary(Opcode::UIToFP, args[0], f64());
1476 build.ret(&[d]);
1477 });
1478 floats(&mut func);
1479
1480 let text = printed(&func, &mut names);
1481 assert!(!text.contains("uitofp"), "the unsigned conversion is gone: {text}");
1482 assert!(text.contains("zext.i64"), "widened with zeroes: {text}");
1483 assert!(text.contains("sitofp.f64"), "converted as signed: {text}");
1484 }
1485
1486 #[test]
1488 fn a_float_becoming_an_unsigned_integer_converts_as_signed_first_and_then_narrows() {
1489 let (mut names, mut func) = one(&[f64()], &[Type::int(32)], |build, args| {
1490 let n = build.unary(Opcode::FPToUI, args[0], Type::int(32));
1491 build.ret(&[n]);
1492 });
1493 floats(&mut func);
1494
1495 let text = printed(&func, &mut names);
1496 assert!(!text.contains("fptoui"), "the unsigned conversion is gone: {text}");
1497 assert!(text.contains("fptosi.i64"), "converted as signed: {text}");
1498 assert!(text.contains("trunc.i32"), "and narrowed to what was asked: {text}");
1499 }
1500
1501 #[test]
1504 fn a_conversion_narrower_than_the_machine_has_is_one_it_has_and_a_narrowing() {
1505 let (mut names, mut func) = one(&[f64()], &[Type::int(8)], |build, args| {
1506 let n = build.unary(Opcode::FPToSI, args[0], Type::int(8));
1507 build.ret(&[n]);
1508 });
1509 floats(&mut func);
1510
1511 let text = printed(&func, &mut names);
1512 assert!(text.contains("fptosi.i32"), "converted at a width there is one at: {text}");
1513 assert!(text.contains("trunc.i8"), "and narrowed to what was asked: {text}");
1514 }
1515
1516 #[test]
1518 fn a_signed_integer_narrower_than_the_machine_converts_from_is_widened_with_its_sign() {
1519 let (mut names, mut func) = one(&[Type::int(8)], &[f64()], |build, args| {
1520 let d = build.unary(Opcode::SIToFP, args[0], f64());
1521 build.ret(&[d]);
1522 });
1523 floats(&mut func);
1524
1525 let text = printed(&func, &mut names);
1526 assert!(text.contains("sext.i32"), "widened with the sign and not with zeroes: {text}");
1527 assert!(!text.contains("zext"), "widened with the sign and not with zeroes: {text}");
1528 assert!(text.contains("sitofp.f64"), "converted at a width there is one at: {text}");
1529 }
1530
1531 #[test]
1533 fn the_width_a_conversion_happens_at_is_the_narrowest_one_that_holds_the_values() {
1534 use super::holder;
1535 for bits in [1, 8, 16, 32] {
1536 assert_eq!(holder(bits, true), Some(32), "a signed {bits} bit value fits in an int");
1537 }
1538 assert_eq!(holder(64, true), Some(64));
1539 for bits in [1, 8, 16, 31] {
1540 assert_eq!(holder(bits, false), Some(32), "an unsigned {bits} bit value does too");
1541 }
1542 assert_eq!(holder(32, false), Some(64));
1544 assert_eq!(holder(64, false), None);
1545 }
1546
1547 #[test]
1551 fn the_unsigned_conversions_at_the_widest_width_become_the_signed_one_and_a_correction() {
1552 for float in [f32(), f64()] {
1553 let (mut names, mut func) = one(&[Type::int(64)], &[float], |build, args| {
1554 let d = build.unary(Opcode::UIToFP, args[0], float);
1555 build.ret(&[d]);
1556 });
1557 floats(&mut func);
1558 let text = printed(&func, &mut names);
1559 assert!(!text.contains("uitofp"), "the unsigned conversion is gone: {text}");
1560 assert!(text.contains("sitofp"), "the signed one is what is left: {text}");
1561 assert!(text.contains("lshr"), "the value is halved: {text}");
1564 assert!(text.contains("fadd"), "and doubled again afterwards: {text}");
1565 valid(&func, &mut names);
1566 }
1567
1568 for float in [f32(), f64()] {
1569 let (mut names, mut func) = one(&[float], &[Type::int(64)], |build, args| {
1570 let n = build.unary(Opcode::FPToUI, args[0], Type::int(64));
1571 build.ret(&[n]);
1572 });
1573 floats(&mut func);
1574 let text = printed(&func, &mut names);
1575 assert!(!text.contains("fptoui"), "the unsigned conversion is gone: {text}");
1576 assert!(text.contains("fptosi"), "the signed one is what is left: {text}");
1577 assert!(text.contains("fsub"), "the value is brought down: {text}");
1579 assert!(text.contains("shl"), "and the top bit goes back on: {text}");
1580 valid(&func, &mut names);
1581 }
1582 }
1583
1584 #[test]
1588 fn the_widest_unsigned_conversions_are_written_without_a_branch() {
1589 let (_, mut func) = one(&[Type::int(64)], &[f64()], |build, args| {
1590 let d = build.unary(Opcode::UIToFP, args[0], f64());
1591 build.ret(&[d]);
1592 });
1593 floats(&mut func);
1594 assert_eq!(func.blocks().count(), 1, "the conversion did not split the block");
1595
1596 let (_, mut func) = one(&[f64()], &[Type::int(64)], |build, args| {
1597 let n = build.unary(Opcode::FPToUI, args[0], Type::int(64));
1598 build.ret(&[n]);
1599 });
1600 floats(&mut func);
1601 assert_eq!(func.blocks().count(), 1, "nor did the other one");
1602 }
1603
1604 #[test]
1611 fn the_arithmetic_the_widest_unsigned_conversions_do_is_the_conversion() {
1612 for &x in CASES {
1613 let mask = if (x as i64) < 0 { u64::MAX } else { 0 };
1615 let odd = (x >> 1) | (x & 1);
1616 let source = x ^ ((x ^ odd) & mask);
1617 let converted = source as i64 as f64;
1618 let addend = f64::from_bits(converted.to_bits() & mask);
1619 assert_eq!(converted + addend, x as f64, "converting {x:#x} into a double");
1620 }
1621
1622 for &x in CASES {
1623 let d = x as f64;
1625 if d >= 18_446_744_073_709_551_616.0 {
1626 continue;
1627 }
1628 let half = f64::from_bits(0x43E0_0000_0000_0000);
1629 let mask = if d >= half { u64::MAX } else { 0 };
1630 let taken = f64::from_bits(half.to_bits() & mask);
1631 let low = (d - taken) as i64;
1632 let top = u64::from(d >= half) << 63;
1633 assert_eq!(low as u64 ^ top, d as u64, "converting {d} into an unsigned word");
1634 }
1635 }
1636
1637 #[test]
1644 fn the_unsigned_conversions_at_eighty_bits_correct_with_a_multiply_instead_of_a_mask() {
1645 let (mut names, mut func) = one(&[Type::int(64)], &[f80()], |build, args| {
1646 let d = build.unary(Opcode::UIToFP, args[0], f80());
1647 build.ret(&[d]);
1648 });
1649 floats(&mut func);
1650 let text = printed(&func, &mut names);
1651 assert!(!text.contains("uitofp"), "the unsigned conversion is gone: {text}");
1652 assert!(text.contains("sitofp.f80"), "the signed one is what is left: {text}");
1653 assert!(!text.contains("bitcast"), "and nothing reads the float as an integer: {text}");
1654 assert!(!text.contains("lshr"), "nor is the value halved, since nothing rounds: {text}");
1655 assert!(text.contains("fmul "), "the constant is taken or not by a multiply: {text}");
1656 assert!(text.contains("fadd "), "and added to what the conversion gave: {text}");
1657 assert_eq!(func.blocks().count(), 1, "the conversion did not split the block");
1658 valid(&func, &mut names);
1659
1660 let (mut names, mut func) = one(&[f80()], &[Type::int(64)], |build, args| {
1661 let n = build.unary(Opcode::FPToUI, args[0], Type::int(64));
1662 build.ret(&[n]);
1663 });
1664 floats(&mut func);
1665 let text = printed(&func, &mut names);
1666 assert!(!text.contains("fptoui"), "the unsigned conversion is gone: {text}");
1667 assert!(text.contains("fptosi.i64"), "the signed one is what is left: {text}");
1668 assert!(!text.contains("bitcast"), "and nothing reads the float as an integer: {text}");
1669 assert!(text.contains("fmul "), "the constant is taken or not by a multiply: {text}");
1670 assert!(text.contains("fsub "), "and subtracted before the conversion: {text}");
1671 assert!(text.contains("shl"), "with the top bit going back on after it: {text}");
1672 assert_eq!(func.blocks().count(), 1, "nor did the other one");
1673 valid(&func, &mut names);
1674 }
1675
1676 #[test]
1686 fn nothing_in_either_conversion_at_eighty_bits_rounds() {
1687 fn exact(v: i128) -> bool {
1689 let mag = v.unsigned_abs();
1690 mag == 0 || (mag >> mag.trailing_zeros()) < 1 << 64
1691 }
1692
1693 for &x in CASES {
1694 let signed = i128::from(x as i64);
1696 let addend = if (x as i64) < 0 { 1i128 << 64 } else { 0 };
1697 assert!(exact(signed), "the conversion of {x:#x} read as signed is exact");
1698 assert!(exact(addend), "and so is the constant it gets");
1699 assert!(exact(signed + addend), "and so is the sum");
1700 assert_eq!(signed + addend, i128::from(x), "converting {x:#x} into a long double");
1701 }
1702
1703 for &x in CASES {
1704 let value = i128::from(x);
1706 let taken = if value >= 1 << 63 { 1i128 << 63 } else { 0 };
1707 let under = value - taken;
1708 assert!(exact(under), "the subtraction that brings {x:#x} into range is exact");
1709 let top = u64::from(value >= 1 << 63) << 63;
1710 assert_eq!(under as u64 ^ top, x, "converting {x:#x} back into an unsigned word");
1711 }
1712 }
1713
1714 #[test]
1717 fn what_the_float_rewrites_leave_is_valid_ir() {
1718 let (mut names, mut func) = one(&[Type::int(32)], &[f64()], |build, args| {
1719 let k = build.fconst(f64(), 0x3ff8_0000_0000_0000);
1720 let d = build.unary(Opcode::UIToFP, args[0], f64());
1721 let n = build.unary(Opcode::FNeg, d, f64());
1722 let s = build.binary(Opcode::FAdd, n, k, Flags::NONE);
1723 build.ret(&[s]);
1724 });
1725 floats(&mut func);
1726 let module = Module::new(names.intern("f.c"), &target());
1727 rucc_ir::verify_func(&module, &func, &names).expect("the rewrite builds valid IR");
1728 }
1729
1730 #[test]
1733 fn a_function_with_no_floats_in_it_is_left_exactly_as_it_was() {
1734 let (mut names, mut func) = one(&[Type::int(32)], &[Type::int(32)], |build, args| {
1735 build.ret(&[args[0]]);
1736 });
1737 let before = printed(&func, &mut names);
1738 floats(&mut func);
1739 assert_eq!(printed(&func, &mut names), before);
1740 }
1741 fn access(size: u64, align: u32) -> MemInfo {
1742 MemInfo {
1743 size,
1744 align,
1745 order: MemOrder::NotAtomic,
1746 tbaa: None,
1747 owns: 0,
1748 restrict: Restrict::NONE,
1749 }
1750 }
1751
1752 fn moving(opcode: Opcode, size: u64, align: u32, byte: Option<i128>) -> (Interner, Func) {
1755 one(&[Type::PTR, Type::PTR], &[], |build, args| {
1756 let second = match byte {
1757 Some(value) => build.iconst(Type::int(8), value),
1758 None => args[1],
1759 };
1760 let mem = build.func().add_mem(access(size, align));
1761 let operands = build.func().push_values(&[args[0], second]);
1762 let data = InstData { args: operands, extra: Extra::Mem(mem), ..InstData::new(opcode) };
1763 build.inst(data, &[]);
1764 build.ret(&[]);
1765 })
1766 }
1767
1768 fn copying(size: u64, align: u32) -> (Interner, Func) {
1769 moving(Opcode::Memcpy, size, align, None)
1770 }
1771
1772 fn filling(size: u64, align: u32, byte: i128) -> (Interner, Func) {
1773 moving(Opcode::Memset, size, align, Some(byte))
1774 }
1775
1776 fn widths(size: u64, align: u32) -> Option<Vec<u32>> {
1779 Some(chunks(access(size, align), 8)?.into_iter().map(|(_, width)| width).collect())
1780 }
1781
1782 #[test]
1784 fn a_copy_becomes_a_load_and_a_store_for_each_word_of_it() {
1785 let (mut names, mut func) = copying(16, 8);
1786 bulk(&mut func, &mut names, 8);
1787
1788 let text = printed(&func, &mut names);
1789 assert!(!text.contains("memcpy"), "the copy is gone: {text}");
1790 assert_eq!(text.matches("load.i64").count(), 2, "a load per word: {text}");
1791 assert_eq!(text.matches("store").count(), 2, "a store per word: {text}");
1792 assert_eq!(
1793 text.matches("ptr_add").count(),
1794 2,
1795 "no offset for the word at the front: {text}"
1796 );
1797 }
1798
1799 #[test]
1803 fn a_word_is_as_wide_as_the_block_is_aligned_to() {
1804 assert_eq!(widths(16, 8), Some(vec![8, 8]));
1805 assert_eq!(widths(16, 4), Some(vec![4, 4, 4, 4]));
1806 assert_eq!(widths(4, 1), Some(vec![1, 1, 1, 1]));
1807 }
1808
1809 #[test]
1812 fn what_is_left_over_is_narrower_words_and_not_a_run_of_bytes() {
1813 assert_eq!(widths(13, 8), Some(vec![8, 4, 1]));
1814 assert_eq!(widths(3, 8), Some(vec![2, 1]));
1815 assert_eq!(widths(1, 8), Some(vec![1]));
1816 }
1817
1818 #[test]
1821 fn every_word_starts_somewhere_it_is_aligned_for() {
1822 for (at, width) in chunks(access(13, 8), 8).expect("a plan for thirteen bytes") {
1823 assert_eq!(at % u64::from(width), 0, "{at} is a multiple of {width}");
1824 }
1825 }
1826
1827 #[test]
1829 fn a_fill_is_the_byte_spread_across_each_word() {
1830 let (mut names, mut func) = filling(16, 8, 0);
1831 bulk(&mut func, &mut names, 8);
1832
1833 let text = printed(&func, &mut names);
1834 assert!(!text.contains("memset"), "the fill is gone: {text}");
1835 assert_eq!(text.matches("store").count(), 2, "a store per word: {text}");
1836 assert!(!text.contains("load"), "a fill reads nothing: {text}");
1837 }
1838
1839 #[test]
1842 fn the_byte_is_repeated_across_the_word_it_is_stored_as() {
1843 assert_eq!(spread(0, 8), 0);
1844 assert_eq!(spread(0xff, 1), 0xff);
1845 assert_eq!(spread(0xff, 4), 0xffff_ffff);
1846 assert_eq!(spread(0xab, 2), 0xabab);
1847 assert_eq!(spread(0xab, 8), 0xabab_abab_abab_abab);
1848 }
1849
1850 #[test]
1852 fn a_copy_too_large_to_unroll_becomes_a_call_to_the_runtime() {
1853 let size = u64::try_from(UNROLL).expect("a small threshold") + 1;
1854 let (mut names, mut func) = copying(size, 1);
1855 bulk(&mut func, &mut names, 8);
1856 let text = printed(&func, &mut names);
1857 assert!(text.contains("call @memcpy"), "a call and not a bulk move: {text}");
1858
1859 let (mut names, mut func) = copying(size - 1, 1);
1862 bulk(&mut func, &mut names, 8);
1863 assert!(!printed(&func, &mut names).contains("memcpy"), "one word under it is unrolled");
1864 }
1865
1866 #[test]
1869 fn the_call_passes_the_size_that_the_instruction_carried_beside_it() {
1870 let size = u64::try_from(UNROLL).expect("a small threshold") + 1;
1871 let (mut names, mut func) = copying(size, 1);
1872 bulk(&mut func, &mut names, 8);
1873 let text = printed(&func, &mut names);
1874 assert!(text.contains(&format!("{size}")), "the size is an argument now: {text}");
1875 }
1876
1877 #[test]
1880 fn a_move_is_a_call_however_small_it_is() {
1881 let (mut names, mut func) = moving(Opcode::Memmove, 8, 8, None);
1882 bulk(&mut func, &mut names, 8);
1883 let text = printed(&func, &mut names);
1884 assert!(text.contains("call @memmove"), "a call and not a run of moves: {text}");
1885 }
1886
1887 #[test]
1890 fn a_fill_whose_byte_is_not_a_constant_becomes_a_call() {
1891 let (mut names, mut func) = one(&[Type::PTR, Type::int(8)], &[], |build, args| {
1892 let mem = build.func().add_mem(access(8, 8));
1893 let operands = build.func().push_values(&[args[0], args[1]]);
1894 let data = InstData {
1895 args: operands,
1896 extra: Extra::Mem(mem),
1897 ..InstData::new(Opcode::Memset)
1898 };
1899 build.inst(data, &[]);
1900 build.ret(&[]);
1901 });
1902 bulk(&mut func, &mut names, 8);
1903 let text = printed(&func, &mut names);
1904 assert!(text.contains("call @memset"), "a call and not a run of stores: {text}");
1905 assert!(text.contains("zext.i32"), "the byte is widened to what C passes: {text}");
1907 }
1908
1909 fn computing(opcode: Opcode, byte: Option<i128>) -> (Interner, Func) {
1912 one(&[Type::PTR, Type::PTR, Type::int(64)], &[], |build, args| {
1913 let second = match byte {
1914 Some(value) => build.iconst(Type::int(8), value),
1915 None => args[1],
1916 };
1917 let mem = build.func().add_mem(access(0, 4));
1918 let operands = build.func().push_values(&[args[0], second, args[2]]);
1919 let data = InstData { args: operands, extra: Extra::Mem(mem), ..InstData::new(opcode) };
1920 build.inst(data, &[]);
1921 build.ret(&[]);
1922 })
1923 }
1924
1925 #[test]
1928 fn a_bulk_move_of_a_length_the_program_works_out_is_a_call_whatever_the_payload_says() {
1929 for (opcode, name) in
1930 [(Opcode::Memcpy, "memcpy"), (Opcode::Memmove, "memmove"), (Opcode::Memset, "memset")]
1931 {
1932 let byte = (opcode == Opcode::Memset).then_some(0);
1933 let (mut names, mut func) = computing(opcode, byte);
1934 bulk(&mut func, &mut names, 8);
1935 let text = printed(&func, &mut names);
1936 assert!(text.contains(&format!("call @{name}")), "a call and not a plan: {text}");
1937 assert!(!text.contains("iconst.i64"), "no size was invented: {text}");
1940 }
1941 }
1942
1943 #[test]
1944 fn what_a_bulk_move_of_a_length_the_program_works_out_becomes_is_ir_that_verifies() {
1945 for opcode in [Opcode::Memcpy, Opcode::Memmove, Opcode::Memset] {
1946 let byte = (opcode == Opcode::Memset).then_some(0);
1947 let (mut names, mut func) = computing(opcode, byte);
1948 bulk(&mut func, &mut names, 8);
1949 valid(&func, &mut names);
1950 }
1951 }
1952
1953 #[test]
1956 fn no_word_is_wider_than_the_machine_moves_at_once() {
1957 assert_eq!(chunks(access(8, 8), 4).map(|plan| plan.len()), Some(2));
1958 assert_eq!(chunks(access(8, 8), 8).map(|plan| plan.len()), Some(1));
1959 }
1960
1961 #[test]
1962 fn what_a_copy_becomes_is_ir_that_verifies() {
1963 let (mut names, mut func) = copying(13, 8);
1964 bulk(&mut func, &mut names, 8);
1965 let module = Module::new(names.intern("c.c"), &target());
1966 rucc_ir::verify_func(&module, &func, &names).expect("the rewrite builds valid IR");
1967 }
1968
1969 #[test]
1970 fn what_a_fill_becomes_is_ir_that_verifies() {
1971 let (mut names, mut func) = filling(13, 8, 0xff);
1972 bulk(&mut func, &mut names, 8);
1973 let module = Module::new(names.intern("f.c"), &target());
1974 rucc_ir::verify_func(&module, &func, &names).expect("the rewrite builds valid IR");
1975 }
1976
1977 #[test]
1978 fn what_a_copy_too_large_to_unroll_becomes_is_ir_that_verifies() {
1979 let size = u64::try_from(UNROLL).expect("a small threshold") + 1;
1980 let (mut names, mut func) = copying(size, 1);
1981 bulk(&mut func, &mut names, 8);
1982 let module = Module::new(names.intern("c.c"), &target());
1983 rucc_ir::verify_func(&module, &func, &names).expect("the call is valid IR");
1984 }
1985
1986 #[test]
1988 fn a_function_with_no_bulk_move_in_it_is_left_exactly_as_it_was() {
1989 let (mut names, mut func) = one(&[Type::int(32)], &[Type::int(32)], |build, args| {
1990 build.ret(&[args[0]]);
1991 });
1992 let before = printed(&func, &mut names);
1993 bulk(&mut func, &mut names, 8);
1994 assert_eq!(printed(&func, &mut names), before);
1995 }
1996
1997 fn swapping(width: u32) -> (Interner, Func) {
2000 let ty = Type::int(width);
2001 one(&[ty], &[ty], |build, args| {
2002 let s = build.unary(Opcode::Bswap, args[0], ty);
2003 build.ret(&[s]);
2004 })
2005 }
2006
2007 #[test]
2014 fn the_masks_are_the_alternating_runs_of_the_group_being_swapped() {
2015 assert_eq!(alternating(32, 16), 0x0000_ffff);
2016 assert_eq!(alternating(32, 8), 0x00ff_00ff);
2017 assert_eq!(alternating(16, 8), 0x00ff);
2018 assert_eq!(alternating(64, 32), 0x0000_0000_ffff_ffff);
2019 assert_eq!(alternating(64, 16), 0x0000_ffff_0000_ffff);
2020 assert_eq!(alternating(64, 8), 0x00ff_00ff_00ff_00ff);
2021 }
2022
2023 #[test]
2025 fn a_two_byte_swap_is_one_exchange_of_neighbouring_bytes() {
2026 let (mut names, mut func) = swapping(16);
2027 bytes(&mut func);
2028
2029 let text = printed(&func, &mut names);
2030 assert!(!text.contains("bswap"), "the instruction is gone: {text}");
2031 assert!(text.contains("iconst.i16 255"), "the low byte of the pair: {text}");
2032 assert_eq!(text.matches("shl").count(), 1, "one shift up: {text}");
2033 assert_eq!(text.matches("lshr").count(), 1, "one shift down: {text}");
2034 assert_eq!(text.matches(" or ").count(), 1, "and the two put together: {text}");
2035 }
2036
2037 #[test]
2040 fn a_wider_swap_is_the_same_exchange_once_per_halving() {
2041 for (width, steps) in [(16u32, 1usize), (32, 2), (64, 3)] {
2042 let (mut names, mut func) = swapping(width);
2043 bytes(&mut func);
2044 let text = printed(&func, &mut names);
2045 assert_eq!(text.matches("shl").count(), steps, "at {width}: {text}");
2046 assert_eq!(text.matches("lshr").count(), steps, "at {width}: {text}");
2047 assert_eq!(text.matches(" and ").count(), steps * 2, "at {width}: {text}");
2048 assert_eq!(text.matches(" or ").count(), steps, "at {width}: {text}");
2049 }
2050 }
2051
2052 #[test]
2055 fn the_shift_counts_are_the_group_width_halving_as_it_goes() {
2056 let (mut names, mut func) = swapping(64);
2057 bytes(&mut func);
2058 let text = printed(&func, &mut names);
2059 for count in ["iconst.i64 32", "iconst.i64 16", "iconst.i64 8"] {
2060 assert!(text.contains(count), "{count} is a step: {text}");
2061 }
2062 }
2063
2064 #[test]
2067 fn what_a_byte_swap_becomes_is_ir_that_verifies() {
2068 let (mut names, mut func) = swapping(32);
2069 bytes(&mut func);
2070 let module = Module::new(names.intern("b.c"), &target());
2071 rucc_ir::verify_func(&module, &func, &names).expect("the rewrite builds valid IR");
2072 }
2073
2074 #[test]
2077 fn a_function_with_no_byte_swap_in_it_is_left_exactly_as_it_was() {
2078 let (mut names, mut func) = one(&[Type::int(32)], &[Type::int(32)], |build, args| {
2079 build.ret(&[args[0]]);
2080 });
2081 let before = printed(&func, &mut names);
2082 bytes(&mut func);
2083 assert_eq!(printed(&func, &mut names), before);
2084 }
2085
2086 fn counting(op: Opcode, width: u32) -> (Interner, Func) {
2088 let ty = Type::int(width);
2089 one(&[ty], &[ty], |build, args| {
2090 let c = build.unary(op, args[0], ty);
2091 build.ret(&[c]);
2092 })
2093 }
2094
2095 #[test]
2098 fn the_counting_masks_are_the_ones_the_halving_sum_is_written_with() {
2099 assert_eq!(alternating(32, 1), 0x5555_5555);
2100 assert_eq!(alternating(32, 2), 0x3333_3333);
2101 assert_eq!(alternating(32, 4), 0x0f0f_0f0f);
2102 assert_eq!(every(32, 8, 1), 0x0101_0101);
2103 assert_eq!(every(64, 8, 1), 0x0101_0101_0101_0101);
2104 }
2105
2106 #[test]
2109 fn a_set_bit_count_is_the_halving_sum_and_a_multiply_that_adds_the_bytes() {
2110 let (mut names, mut func) = counting(Opcode::Ctpop, 32);
2111 counts(&mut func);
2112
2113 let text = printed(&func, &mut names);
2114 assert!(!text.contains("ctpop"), "the instruction is gone: {text}");
2115 assert!(text.contains("iconst.i32 1431655765"), "the pairs mask: {text}");
2116 assert!(text.contains("iconst.i32 858993459"), "the nibbles mask: {text}");
2117 assert!(text.contains("iconst.i32 252645135"), "the bytes mask: {text}");
2118 assert_eq!(text.matches(" mul ").count(), 1, "one multiply: {text}");
2119 assert!(text.contains("iconst.i32 24"), "and the top byte is the answer: {text}");
2120 }
2121
2122 #[test]
2124 fn a_count_of_one_byte_stops_before_the_multiply() {
2125 let (mut names, mut func) = counting(Opcode::Ctpop, 8);
2126 counts(&mut func);
2127 let text = printed(&func, &mut names);
2128 assert!(!text.contains("ctpop"), "{text}");
2129 assert!(!text.contains(" mul "), "nothing to add together: {text}");
2130 }
2131
2132 #[test]
2135 fn a_leading_zero_count_smears_the_value_down_and_counts_the_complement() {
2136 let (mut names, mut func) = counting(Opcode::Ctlz, 32);
2137 counts(&mut func);
2138
2139 let text = printed(&func, &mut names);
2140 assert!(!text.contains("ctlz"), "the instruction is gone: {text}");
2141 assert!(!text.contains("ctpop"), "and so is the count it became: {text}");
2142 for by in ["iconst.i32 1", "iconst.i32 2", "iconst.i32 4", "iconst.i32 8", "iconst.i32 16"]
2143 {
2144 assert!(text.contains(by), "{by} is a smearing step: {text}");
2145 }
2146 assert_eq!(text.matches(" xor ").count(), 1, "one complement: {text}");
2147 }
2148
2149 #[test]
2151 fn a_trailing_zero_count_masks_the_bits_below_the_lowest_set_one() {
2152 let (mut names, mut func) = counting(Opcode::Cttz, 32);
2153 counts(&mut func);
2154
2155 let text = printed(&func, &mut names);
2156 assert!(!text.contains("cttz"), "the instruction is gone: {text}");
2157 assert!(!text.contains("ctpop"), "and so is the count it became: {text}");
2158 assert!(text.contains("iconst.i32 -1"), "the complement and the decrement: {text}");
2159 assert_eq!(text.matches(" xor ").count(), 1, "one complement: {text}");
2160 assert!(text.matches(" or ").count() <= 1, "no smearing run: {text}");
2162 }
2163
2164 #[test]
2167 fn what_a_bit_count_becomes_is_ir_that_verifies() {
2168 for op in [Opcode::Ctpop, Opcode::Ctlz, Opcode::Cttz] {
2169 for width in [8u32, 16, 32, 64] {
2170 let (mut names, mut func) = counting(op, width);
2171 counts(&mut func);
2172 let module = Module::new(names.intern("c.c"), &target());
2173 rucc_ir::verify_func(&module, &func, &names)
2174 .unwrap_or_else(|e| panic!("{op:?} at {width}: {e:?}"));
2175 }
2176 }
2177 }
2178
2179 #[test]
2183 fn a_width_the_halving_sum_is_not_written_for_is_left_alone() {
2184 let (mut names, mut func) = counting(Opcode::Ctpop, 24);
2185 counts(&mut func);
2186 assert!(printed(&func, &mut names).contains("ctpop"), "left as it was");
2187 }
2188
2189 #[test]
2191 fn a_function_with_no_bit_count_in_it_is_left_exactly_as_it_was() {
2192 let (mut names, mut func) = one(&[Type::int(32)], &[Type::int(32)], |build, args| {
2193 build.ret(&[args[0]]);
2194 });
2195 let before = printed(&func, &mut names);
2196 counts(&mut func);
2197 assert_eq!(printed(&func, &mut names), before);
2198 }
2199
2200 fn checking(op: Opcode, width: u32) -> (Interner, Func) {
2203 let ty = Type::int(width);
2204 let bit = ty.with_lane(Type::I1);
2205 one(&[ty, ty], &[ty, bit], |build, args| {
2206 let (value, flag) = build.checked(op, args[0], args[1]);
2207 build.ret(&[value, flag]);
2208 })
2209 }
2210
2211 #[test]
2214 fn a_checked_unsigned_add_becomes_an_add_and_one_comparison() {
2215 let (mut names, mut func) = checking(Opcode::UAddOverflow, 32);
2216 overflows(&mut func);
2217
2218 let text = printed(&func, &mut names);
2219 assert!(!text.contains("uadd_overflow"), "the instruction is gone: {text}");
2220 assert_eq!(text.matches(" add ").count(), 1, "one add: {text}");
2221 assert_eq!(text.matches("icmp ult").count(), 1, "and one comparison: {text}");
2222 assert!(!text.contains(" xor "), "nothing about sign bits: {text}");
2223 }
2224
2225 #[test]
2228 fn a_checked_signed_add_becomes_an_add_and_the_sign_bit_of_two_exclusive_ors() {
2229 let (mut names, mut func) = checking(Opcode::SAddOverflow, 32);
2230 overflows(&mut func);
2231
2232 let text = printed(&func, &mut names);
2233 assert!(!text.contains("sadd_overflow"), "the instruction is gone: {text}");
2234 assert_eq!(text.matches(" add ").count(), 1, "one add: {text}");
2235 assert_eq!(text.matches(" xor ").count(), 2, "the answer against each operand: {text}");
2236 assert_eq!(text.matches(" and ").count(), 1, "both at once: {text}");
2237 assert!(text.contains("icmp slt"), "and its sign bit: {text}");
2238 }
2239
2240 #[test]
2243 fn a_checked_unsigned_subtract_compares_the_operands_and_not_the_answer() {
2244 let (mut names, mut func) = checking(Opcode::USubOverflow, 64);
2245 overflows(&mut func);
2246
2247 let text = printed(&func, &mut names);
2248 assert!(!text.contains("usub_overflow"), "the instruction is gone: {text}");
2249 assert_eq!(text.matches(" sub ").count(), 1, "one subtract: {text}");
2250 assert!(text.contains("icmp ult %0, %1"), "the operands, in order: {text}");
2251 }
2252
2253 #[test]
2260 fn a_checked_multiply_becomes_a_multiply_and_the_high_half_of_the_product() {
2261 let (mut names, mut func) = checking(Opcode::UMulOverflow, 64);
2262 overflows(&mut func);
2263
2264 let text = printed(&func, &mut names);
2265 assert!(!text.contains("umul_overflow"), "the instruction is gone: {text}");
2266 assert_eq!(text.matches(" mul ").count(), 5, "the answer and the four halves: {text}");
2267 assert!(text.contains("iconst.i64 32"), "split at half the width: {text}");
2268 assert!(text.contains("iconst.i64 4294967295"), "and masked to it: {text}");
2269 assert!(text.contains("icmp ne"), "the high half against zero: {text}");
2270 assert!(!text.contains("ashr"), "and nothing corrected for sign: {text}");
2271 }
2272
2273 #[test]
2276 fn a_checked_signed_multiply_corrects_the_high_half_for_each_negative_operand() {
2277 let (mut names, mut func) = checking(Opcode::SMulOverflow, 64);
2278 overflows(&mut func);
2279
2280 let text = printed(&func, &mut names);
2281 assert!(!text.contains("smul_overflow"), "the instruction is gone: {text}");
2282 assert_eq!(
2283 text.matches(" ashr ").count(),
2284 3,
2285 "each operand's sign, and the answer: {text}"
2286 );
2287 assert!(text.contains("iconst.i64 63"), "spread from the top bit: {text}");
2288 assert_eq!(text.matches(" sub ").count(), 2, "one correction per operand: {text}");
2289 }
2290
2291 #[test]
2295 fn both_results_are_substituted_into_whoever_was_reading_them() {
2296 let (mut names, mut func) = checking(Opcode::SAddOverflow, 32);
2297 overflows(&mut func);
2298
2299 let text = printed(&func, &mut names);
2303 assert_eq!(
2304 text,
2305 concat!(
2306 "func @f(i32, i32) -> (i32, i1), linkage(external) {\n",
2307 "block0(%0: i32, %1: i32):\n",
2308 " %2 = add %0, %1\n",
2309 " %3 = xor %0, %2\n",
2310 " %4 = xor %1, %2\n",
2311 " %5 = and %3, %4\n",
2312 " %6 = iconst.i32 0\n",
2313 " %7 = icmp slt %5, %6\n",
2314 " return %2, %7\n",
2315 "}\n",
2316 ),
2317 );
2318 }
2319
2320 #[test]
2323 fn what_an_overflow_check_becomes_is_ir_that_verifies() {
2324 let all = [
2325 Opcode::UAddOverflow,
2326 Opcode::SAddOverflow,
2327 Opcode::USubOverflow,
2328 Opcode::SSubOverflow,
2329 Opcode::UMulOverflow,
2330 Opcode::SMulOverflow,
2331 ];
2332 for op in all {
2333 for width in [8u32, 16, 32, 64, 128] {
2334 let (mut names, mut func) = checking(op, width);
2335 overflows(&mut func);
2336 let module = Module::new(names.intern("c.c"), &target());
2337 rucc_ir::verify_func(&module, &func, &names)
2338 .unwrap_or_else(|e| panic!("{op:?} at {width}: {e:?}"));
2339 }
2340 }
2341 }
2342
2343 #[test]
2347 fn a_width_the_split_is_not_written_for_is_left_alone() {
2348 let (mut names, mut func) = checking(Opcode::UMulOverflow, 24);
2349 overflows(&mut func);
2350 assert!(printed(&func, &mut names).contains("umul_overflow"), "left as it was");
2351 }
2352
2353 #[test]
2360 fn a_check_at_the_width_no_register_holds_is_rewritten_here() {
2361 let (mut names, mut func) = checking(Opcode::UAddOverflow, 128);
2362 overflows(&mut func);
2363 let text = printed(&func, &mut names);
2364 assert!(!text.contains("uadd_overflow"), "the check is gone: {text}");
2365 assert!(text.contains(" = add "), "into the arithmetic it is: {text}");
2366 assert!(text.contains("icmp ult"), "and the test that says it wrapped: {text}");
2367 }
2368
2369 #[test]
2371 fn a_function_with_no_overflow_check_in_it_is_left_exactly_as_it_was() {
2372 let (mut names, mut func) = one(&[Type::int(32)], &[Type::int(32)], |build, args| {
2373 build.ret(&[args[0]]);
2374 });
2375 let before = printed(&func, &mut names);
2376 overflows(&mut func);
2377 assert_eq!(printed(&func, &mut names), before);
2378 }
2379
2380 fn reading(ty: Type, align: u32, order: MemOrder) -> (Interner, Func) {
2382 one(&[Type::PTR], &[ty], |build, args| {
2383 let info = MemInfo { order, ..access(0, align) };
2384 let value = build.atomic_load(ty, args[0], info, Flags::NONE);
2385 build.ret(&[value]);
2386 })
2387 }
2388
2389 fn writing(ty: Type, align: u32, order: MemOrder) -> (Interner, Func) {
2391 one(&[Type::PTR, ty], &[], |build, args| {
2392 let info = MemInfo { order, ..access(0, align) };
2393 build.atomic_store(args[1], args[0], info, Flags::NONE);
2394 build.ret(&[]);
2395 })
2396 }
2397
2398 #[test]
2405 fn an_ordered_access_becomes_the_plain_one_this_machine_already_orders() {
2406 for order in [MemOrder::Relaxed, MemOrder::Acquire, MemOrder::SeqCst] {
2407 let (mut names, mut func) = reading(Type::int(32), 4, order);
2408 orderings(&mut func, 8);
2409 let text = printed(&func, &mut names);
2410 assert!(text.contains("load.i32"), "{order:?}: {text}");
2411 assert!(!text.contains("atomic_load"), "{order:?}: {text}");
2412 assert!(!text.contains(order.name()), "the ordering came off: {text}");
2413 }
2414
2415 for order in [MemOrder::Relaxed, MemOrder::Release] {
2416 let (mut names, mut func) = writing(Type::int(32), 4, order);
2417 orderings(&mut func, 8);
2418 let text = printed(&func, &mut names);
2419 assert!(text.contains("store %1 -> %0"), "{order:?}: {text}");
2420 assert!(!text.contains("atomic_store"), "{order:?}: {text}");
2421 assert!(!text.contains("fence"), "{order:?} costs nothing here: {text}");
2422 }
2423 }
2424
2425 #[test]
2431 fn the_strongest_store_keeps_a_barrier_behind_it() {
2432 let (mut names, mut func) = writing(Type::int(32), 4, MemOrder::SeqCst);
2433 orderings(&mut func, 8);
2434 let text = printed(&func, &mut names);
2435 let (before, after) = text.split_once("fence seq_cst").expect("a barrier");
2436 assert!(before.contains("store %1 -> %0"), "the store comes first: {text}");
2437 assert!(!after.contains("store"), "and nothing is between them: {text}");
2438 assert!(!text.contains("atomic_store"), "{text}");
2439 }
2440
2441 #[test]
2444 fn a_barrier_is_left_for_the_place_that_knows_what_one_costs() {
2445 for order in MemOrder::all().filter(|&order| order != MemOrder::NotAtomic) {
2446 let (mut names, mut func) = one(&[], &[], |build, _| {
2447 build.fence(order);
2448 build.ret(&[]);
2449 });
2450 let before = printed(&func, &mut names);
2451 orderings(&mut func, 8);
2452 assert_eq!(printed(&func, &mut names), before, "{order:?}");
2453 }
2454 }
2455
2456 #[test]
2462 fn an_access_this_machine_cannot_do_in_one_go_is_left_alone() {
2463 for (ty, align) in [(Type::int(128), 16), (Type::int(64), 4)] {
2464 let (mut names, mut func) = reading(ty, align, MemOrder::SeqCst);
2465 orderings(&mut func, 8);
2466 assert!(printed(&func, &mut names).contains("atomic_load"), "left as it was");
2467 }
2468 }
2469
2470 #[test]
2473 fn what_the_ordered_accesses_become_verifies() {
2474 for order in MemOrder::all().filter(|&order| order != MemOrder::NotAtomic) {
2475 for (mut names, mut func) in
2476 [reading(Type::int(32), 4, order), writing(Type::int(32), 4, order)]
2477 {
2478 if !order.is_valid_for_load() && !order.is_valid_for_store() {
2479 continue;
2480 }
2481 orderings(&mut func, 8);
2482 let module = Module::new(names.intern("a.c"), &target());
2483 rucc_ir::verify_func(&module, &func, &names)
2484 .unwrap_or_else(|e| panic!("{order:?}: {e:?}"));
2485 }
2486 }
2487 }
2488
2489 #[test]
2491 fn a_function_with_no_ordered_access_in_it_is_left_exactly_as_it_was() {
2492 let (mut names, mut func) = one(&[Type::int(32)], &[Type::int(32)], |build, args| {
2493 build.ret(&[args[0]]);
2494 });
2495 let before = printed(&func, &mut names);
2496 orderings(&mut func, 8);
2497 assert_eq!(printed(&func, &mut names), before);
2498 }
2499}