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 [into, from] = func[func[inst].args] else { return };
1058 let Extra::Mem(mem) = func[inst].extra else { return };
1059 let info = func[mem];
1060 let Some(plan) = chunks(info, word) else {
1061 return library(func, names, inst, Opcode::Memcpy, word);
1062 };
1063 for (at, width) in plan {
1064 let ty = Type::int(width * 8);
1065 let access = MemInfo { size: u64::from(width), align: width.min(info.align), ..info };
1066 let there = stepped(func, inst, from, at);
1067 let word = read(func, inst, there, access, ty);
1068 let here = stepped(func, inst, into, at);
1069 write(func, inst, word, here, access);
1070 }
1071 func.remove_inst(inst);
1072}
1073
1074fn fill(func: &mut Func, names: &mut Interner, inst: Inst, word: u32) {
1081 let [into, byte] = func[func[inst].args] else { return };
1082 let Extra::Mem(mem) = func[inst].extra else { return };
1083 let info = func[mem];
1084 let Some(spelled) = literal(func, byte) else {
1085 return library(func, names, inst, Opcode::Memset, word);
1086 };
1087 let Some(plan) = chunks(info, word) else {
1088 return library(func, names, inst, Opcode::Memset, word);
1089 };
1090 for (at, width) in plan {
1091 let ty = Type::int(width * 8);
1092 let access = MemInfo { size: u64::from(width), align: width.min(info.align), ..info };
1093 let value = ahead_const(func, inst, Imm::int(spread(spelled, width) as i128, ty), ty);
1094 let here = stepped(func, inst, into, at);
1095 write(func, inst, value, here, access);
1096 }
1097 func.remove_inst(inst);
1098}
1099
1100fn library(func: &mut Func, names: &mut Interner, inst: Inst, opcode: Opcode, word: u32) {
1113 let mode = if opcode == Opcode::Memmove { "any" } else { "big" };
1118 let Some(routine) = capability::libcall(opcode, mode) else { return };
1119 let [into, second] = func[func[inst].args] else { return };
1120 let Extra::Mem(mem) = func[inst].extra else { return };
1121 let size = func[mem].size;
1122
1123 let words = Type::int(word * 8);
1127 let count = ahead_const(func, inst, Imm::int(i128::from(size), words), words);
1128 let second = if opcode == Opcode::Memset { widened(func, inst, second) } else { second };
1131
1132 let sig = func.add_signature(Signature::new().with_params(&[
1133 Type::PTR,
1134 if opcode == Opcode::Memset { Type::int(32) } else { Type::PTR },
1135 words,
1136 ]));
1137 let callee = names.intern(routine);
1138 let varargs = func.push_abis(&[]);
1139 let info = func.add_call(CallInfo { callee: Some(callee), signature: sig, varargs });
1140 let args = func.push_values(&[into, second, count]);
1141 let data = &mut func[inst];
1142 data.opcode = Opcode::Call;
1143 data.args = args;
1144 data.extra = Extra::Call(info);
1145 data.flags = data.flags.intersection(Flags::legal_on(Opcode::Call));
1146}
1147
1148fn widened(func: &mut Func, inst: Inst, value: Value) -> Value {
1150 let int = Type::int(32);
1151 let ty = func[value].ty;
1152 if ty == int {
1153 return value;
1154 }
1155 ahead(func, inst, Opcode::ZExt, &[value], int)
1156}
1157
1158fn chunks(info: MemInfo, word: u32) -> Option<Vec<(u64, u32)>> {
1171 plan(info.size, info.align, word)
1172}
1173
1174pub(crate) fn plan(size: u64, align: u32, word: u32) -> Option<Vec<(u64, u32)>> {
1182 let widest = word.min(align).max(1);
1183 if !widest.is_power_of_two() {
1184 return None;
1185 }
1186 let mut plan = Vec::new();
1187 let mut at = 0;
1188 let mut width = u64::from(widest);
1189 while at < size {
1190 while width > size - at {
1191 width /= 2;
1192 }
1193 plan.push((at, u32::try_from(width).ok()?));
1194 at += width;
1195 if plan.len() > UNROLL {
1196 return None;
1197 }
1198 }
1199 Some(plan)
1200}
1201
1202fn literal(func: &Func, value: Value) -> Option<u8> {
1204 let Def::Result { inst, .. } = func[value].def else { return None };
1205 if func[inst].opcode != Opcode::IConst {
1206 return None;
1207 }
1208 let Extra::Imm(imm) = func[inst].extra else { return None };
1209 u8::try_from(func[imm].bits() & 0xff).ok()
1210}
1211
1212fn spread(byte: u8, width: u32) -> u64 {
1214 (0..width).fold(0, |word, at| word | u64::from(byte) << (at * 8))
1215}
1216
1217fn stepped(func: &mut Func, inst: Inst, block: Value, at: u64) -> Value {
1220 if at == 0 {
1221 return block;
1222 }
1223 let step = ahead_const(func, inst, Imm::int(i128::from(at), Type::int(64)), Type::int(64));
1224 ahead(func, inst, Opcode::PtrAdd, &[block, step], Type::PTR)
1225}
1226
1227fn read(func: &mut Func, inst: Inst, from: Value, info: MemInfo, ty: Type) -> Value {
1229 let extra = Extra::Mem(func.add_mem(info));
1230 let args = func.push_values(&[from]);
1231 written(func, inst, InstData { args, extra, ..InstData::new(Opcode::Load) }, ty)
1232}
1233
1234fn write(func: &mut Func, inst: Inst, value: Value, into: Value, info: MemInfo) {
1236 let span = func.span(inst);
1237 let extra = Extra::Mem(func.add_mem(info));
1238 let args = func.push_values(&[value, into]);
1239 let data = InstData { args, extra, ..InstData::new(Opcode::Store) };
1240 let made = func.create_inst(data, &[], span);
1241 func.insert_before(made, inst);
1242}
1243
1244fn holder(bits: u32, signed: bool) -> Option<u32> {
1253 match if signed { bits } else { bits + 1 } {
1254 ..=32 => Some(32),
1255 33..=64 => Some(64),
1256 _ => None,
1257 }
1258}
1259
1260fn produced(func: &Func, inst: Inst) -> Type {
1265 func[inst].first_result.map_or(Type::VOID, |value| func[value].ty)
1266}
1267
1268fn ahead(func: &mut Func, inst: Inst, opcode: Opcode, args: &[Value], ty: Type) -> Value {
1270 let args = func.push_values(args);
1271 written(func, inst, InstData { args, ..InstData::new(opcode) }, ty)
1272}
1273
1274fn ahead_cmp(func: &mut Func, inst: Inst, opcode: Opcode, extra: Extra, args: &[Value]) -> Value {
1276 let args = func.push_values(args);
1277 written(func, inst, InstData { args, extra, ..InstData::new(opcode) }, Type::I1)
1278}
1279
1280fn ahead_const(func: &mut Func, inst: Inst, imm: Imm, ty: Type) -> Value {
1282 let extra = Extra::Imm(func.add_imm(imm));
1283 written(func, inst, InstData { extra, ..InstData::new(Opcode::IConst) }, ty)
1284}
1285
1286fn ahead_float(func: &mut Func, inst: Inst, bits: u128, ty: Type) -> Value {
1288 let extra = Extra::Imm(func.add_imm(Imm::from_bits(bits)));
1289 written(func, inst, InstData { extra, ..InstData::new(Opcode::FConst) }, ty)
1290}
1291
1292fn written(func: &mut Func, inst: Inst, data: InstData, ty: Type) -> Value {
1294 let span = func.span(inst);
1295 let made = func.create_inst(data, &[ty], span);
1296 func.insert_before(made, inst);
1297 func[made].first_result.expect("an instruction created with one result has one")
1298}
1299
1300fn becomes(func: &mut Func, inst: Inst, opcode: Opcode, args: &[Value]) {
1307 let args = func.push_values(args);
1308 let data = &mut func[inst];
1309 data.opcode = opcode;
1310 data.args = args;
1311 data.extra = Extra::None;
1312 data.flags = data.flags.intersection(Flags::legal_on(opcode));
1315}
1316
1317#[cfg(test)]
1318mod tests {
1319 use rucc_base::Interner;
1320 use rucc_ir::{Builder, Flags, Float, Func, Module, Opcode, Signature, Type};
1321 use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
1322
1323 use rucc_ir::{Extra, InstData, MemInfo, MemOrder, Restrict};
1324
1325 use super::{
1326 UNROLL, alternating, bulk, bytes, chunks, counts, every, floats, orderings, overflows,
1327 spread,
1328 };
1329
1330 fn target() -> TargetInfo {
1331 TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu))
1332 }
1333
1334 fn printed(func: &Func, names: &mut Interner) -> String {
1335 let module = Module::new(names.intern("sw.c"), &target());
1336 rucc_ir::print_func(&module, func, names)
1337 }
1338
1339 fn one(
1344 params: &[Type],
1345 returns: &[Type],
1346 body: impl FnOnce(&mut Builder<'_>, &[rucc_ir::Value]),
1347 ) -> (Interner, Func) {
1348 let mut names = Interner::new();
1349 let mut func = Func::new(
1350 names.intern("f"),
1351 Signature::new().with_params(params).with_returns(returns),
1352 );
1353 let entry = func.create_block();
1354 let args: Vec<_> = params.iter().map(|&ty| func.append_param(entry, ty)).collect();
1355 let mut build = Builder::new(&mut func, entry);
1356 body(&mut build, &args);
1357 (names, func)
1358 }
1359
1360 fn f64() -> Type {
1361 Type::float(Float::F64)
1362 }
1363
1364 fn f32() -> Type {
1365 Type::float(Float::F32)
1366 }
1367
1368 fn f80() -> Type {
1369 Type::float(Float::F80)
1370 }
1371
1372 const CASES: &[u64] = &[
1377 0,
1378 1,
1379 2,
1380 0x7FFF_FFFF,
1381 0x8000_0000,
1382 0xFFFF_FFFF,
1383 0x0020_0000_0000_0000,
1384 0x0020_0000_0000_0001,
1385 0x7FFF_FFFF_FFFF_FFFF,
1386 0x8000_0000_0000_0000,
1387 0x8000_0000_0000_0001,
1388 0x8000_0000_0000_0400,
1389 0xFFFF_FFFF_FFFF_F800,
1390 0xFFFF_FFFF_FFFF_FFFF,
1391 ];
1392
1393 fn valid(func: &Func, names: &mut Interner) {
1395 let module = Module::new(names.intern("f.c"), &target());
1396 rucc_ir::verify_func(&module, func, names).expect("the rewrite builds valid IR");
1397 }
1398
1399 #[test]
1401 fn a_float_constant_becomes_the_integer_that_spells_it_and_a_reading_of_those_bits() {
1402 let (mut names, mut func) = one(&[], &[f64()], |build, _| {
1403 let k = build.fconst(f64(), 0x3ff8_0000_0000_0000);
1404 build.ret(&[k]);
1405 });
1406 floats(&mut func);
1407
1408 let text = printed(&func, &mut names);
1409 assert!(!text.contains("fconst"), "the float constant is gone: {text}");
1410 assert!(text.contains("iconst.i64 4609434218613702656"), "the bits, as an integer: {text}");
1411 assert!(text.contains("bitcast"), "read back as the float: {text}");
1412 }
1413
1414 #[test]
1417 fn a_constant_at_the_narrow_format_is_an_integer_of_the_narrow_width() {
1418 let (mut names, mut func) = one(&[], &[f32()], |build, _| {
1419 let k = build.fconst(f32(), 0x4020_0000);
1420 build.ret(&[k]);
1421 });
1422 floats(&mut func);
1423 assert!(printed(&func, &mut names).contains("iconst.i32"), "an i32, not an i64");
1424 }
1425
1426 #[test]
1429 fn a_negation_flips_the_sign_bit_and_touches_no_other() {
1430 let (mut names, mut func) = one(&[f64()], &[f64()], |build, args| {
1431 let n = build.unary(Opcode::FNeg, args[0], f64());
1432 build.ret(&[n]);
1433 });
1434 floats(&mut func);
1435
1436 let text = printed(&func, &mut names);
1437 assert!(!text.contains("fneg"), "the negation is gone: {text}");
1438 assert!(!text.contains("fsub"), "and it did not become a subtraction: {text}");
1439 assert!(text.contains("iconst.i64 -9223372036854775808"), "the sign bit alone: {text}");
1440 assert_eq!(text.matches("xor").count(), 1, "one exclusive or: {text}");
1441 assert_eq!(text.matches("bitcast").count(), 2, "there and back: {text}");
1442 }
1443
1444 #[test]
1446 fn an_unsigned_integer_becoming_a_float_widens_first_and_then_converts_as_signed() {
1447 let (mut names, mut func) = one(&[Type::int(32)], &[f64()], |build, args| {
1448 let d = build.unary(Opcode::UIToFP, args[0], f64());
1449 build.ret(&[d]);
1450 });
1451 floats(&mut func);
1452
1453 let text = printed(&func, &mut names);
1454 assert!(!text.contains("uitofp"), "the unsigned conversion is gone: {text}");
1455 assert!(text.contains("zext.i64"), "widened with zeroes: {text}");
1456 assert!(text.contains("sitofp.f64"), "converted as signed: {text}");
1457 }
1458
1459 #[test]
1461 fn a_float_becoming_an_unsigned_integer_converts_as_signed_first_and_then_narrows() {
1462 let (mut names, mut func) = one(&[f64()], &[Type::int(32)], |build, args| {
1463 let n = build.unary(Opcode::FPToUI, args[0], Type::int(32));
1464 build.ret(&[n]);
1465 });
1466 floats(&mut func);
1467
1468 let text = printed(&func, &mut names);
1469 assert!(!text.contains("fptoui"), "the unsigned conversion is gone: {text}");
1470 assert!(text.contains("fptosi.i64"), "converted as signed: {text}");
1471 assert!(text.contains("trunc.i32"), "and narrowed to what was asked: {text}");
1472 }
1473
1474 #[test]
1477 fn a_conversion_narrower_than_the_machine_has_is_one_it_has_and_a_narrowing() {
1478 let (mut names, mut func) = one(&[f64()], &[Type::int(8)], |build, args| {
1479 let n = build.unary(Opcode::FPToSI, args[0], Type::int(8));
1480 build.ret(&[n]);
1481 });
1482 floats(&mut func);
1483
1484 let text = printed(&func, &mut names);
1485 assert!(text.contains("fptosi.i32"), "converted at a width there is one at: {text}");
1486 assert!(text.contains("trunc.i8"), "and narrowed to what was asked: {text}");
1487 }
1488
1489 #[test]
1491 fn a_signed_integer_narrower_than_the_machine_converts_from_is_widened_with_its_sign() {
1492 let (mut names, mut func) = one(&[Type::int(8)], &[f64()], |build, args| {
1493 let d = build.unary(Opcode::SIToFP, args[0], f64());
1494 build.ret(&[d]);
1495 });
1496 floats(&mut func);
1497
1498 let text = printed(&func, &mut names);
1499 assert!(text.contains("sext.i32"), "widened with the sign and not with zeroes: {text}");
1500 assert!(!text.contains("zext"), "widened with the sign and not with zeroes: {text}");
1501 assert!(text.contains("sitofp.f64"), "converted at a width there is one at: {text}");
1502 }
1503
1504 #[test]
1506 fn the_width_a_conversion_happens_at_is_the_narrowest_one_that_holds_the_values() {
1507 use super::holder;
1508 for bits in [1, 8, 16, 32] {
1509 assert_eq!(holder(bits, true), Some(32), "a signed {bits} bit value fits in an int");
1510 }
1511 assert_eq!(holder(64, true), Some(64));
1512 for bits in [1, 8, 16, 31] {
1513 assert_eq!(holder(bits, false), Some(32), "an unsigned {bits} bit value does too");
1514 }
1515 assert_eq!(holder(32, false), Some(64));
1517 assert_eq!(holder(64, false), None);
1518 }
1519
1520 #[test]
1524 fn the_unsigned_conversions_at_the_widest_width_become_the_signed_one_and_a_correction() {
1525 for float in [f32(), f64()] {
1526 let (mut names, mut func) = one(&[Type::int(64)], &[float], |build, args| {
1527 let d = build.unary(Opcode::UIToFP, args[0], float);
1528 build.ret(&[d]);
1529 });
1530 floats(&mut func);
1531 let text = printed(&func, &mut names);
1532 assert!(!text.contains("uitofp"), "the unsigned conversion is gone: {text}");
1533 assert!(text.contains("sitofp"), "the signed one is what is left: {text}");
1534 assert!(text.contains("lshr"), "the value is halved: {text}");
1537 assert!(text.contains("fadd"), "and doubled again afterwards: {text}");
1538 valid(&func, &mut names);
1539 }
1540
1541 for float in [f32(), f64()] {
1542 let (mut names, mut func) = one(&[float], &[Type::int(64)], |build, args| {
1543 let n = build.unary(Opcode::FPToUI, args[0], Type::int(64));
1544 build.ret(&[n]);
1545 });
1546 floats(&mut func);
1547 let text = printed(&func, &mut names);
1548 assert!(!text.contains("fptoui"), "the unsigned conversion is gone: {text}");
1549 assert!(text.contains("fptosi"), "the signed one is what is left: {text}");
1550 assert!(text.contains("fsub"), "the value is brought down: {text}");
1552 assert!(text.contains("shl"), "and the top bit goes back on: {text}");
1553 valid(&func, &mut names);
1554 }
1555 }
1556
1557 #[test]
1561 fn the_widest_unsigned_conversions_are_written_without_a_branch() {
1562 let (_, mut func) = one(&[Type::int(64)], &[f64()], |build, args| {
1563 let d = build.unary(Opcode::UIToFP, args[0], f64());
1564 build.ret(&[d]);
1565 });
1566 floats(&mut func);
1567 assert_eq!(func.blocks().count(), 1, "the conversion did not split the block");
1568
1569 let (_, mut func) = one(&[f64()], &[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 assert_eq!(func.blocks().count(), 1, "nor did the other one");
1575 }
1576
1577 #[test]
1584 fn the_arithmetic_the_widest_unsigned_conversions_do_is_the_conversion() {
1585 for &x in CASES {
1586 let mask = if (x as i64) < 0 { u64::MAX } else { 0 };
1588 let odd = (x >> 1) | (x & 1);
1589 let source = x ^ ((x ^ odd) & mask);
1590 let converted = source as i64 as f64;
1591 let addend = f64::from_bits(converted.to_bits() & mask);
1592 assert_eq!(converted + addend, x as f64, "converting {x:#x} into a double");
1593 }
1594
1595 for &x in CASES {
1596 let d = x as f64;
1598 if d >= 18_446_744_073_709_551_616.0 {
1599 continue;
1600 }
1601 let half = f64::from_bits(0x43E0_0000_0000_0000);
1602 let mask = if d >= half { u64::MAX } else { 0 };
1603 let taken = f64::from_bits(half.to_bits() & mask);
1604 let low = (d - taken) as i64;
1605 let top = u64::from(d >= half) << 63;
1606 assert_eq!(low as u64 ^ top, d as u64, "converting {d} into an unsigned word");
1607 }
1608 }
1609
1610 #[test]
1617 fn the_unsigned_conversions_at_eighty_bits_correct_with_a_multiply_instead_of_a_mask() {
1618 let (mut names, mut func) = one(&[Type::int(64)], &[f80()], |build, args| {
1619 let d = build.unary(Opcode::UIToFP, args[0], f80());
1620 build.ret(&[d]);
1621 });
1622 floats(&mut func);
1623 let text = printed(&func, &mut names);
1624 assert!(!text.contains("uitofp"), "the unsigned conversion is gone: {text}");
1625 assert!(text.contains("sitofp.f80"), "the signed one is what is left: {text}");
1626 assert!(!text.contains("bitcast"), "and nothing reads the float as an integer: {text}");
1627 assert!(!text.contains("lshr"), "nor is the value halved, since nothing rounds: {text}");
1628 assert!(text.contains("fmul "), "the constant is taken or not by a multiply: {text}");
1629 assert!(text.contains("fadd "), "and added to what the conversion gave: {text}");
1630 assert_eq!(func.blocks().count(), 1, "the conversion did not split the block");
1631 valid(&func, &mut names);
1632
1633 let (mut names, mut func) = one(&[f80()], &[Type::int(64)], |build, args| {
1634 let n = build.unary(Opcode::FPToUI, args[0], Type::int(64));
1635 build.ret(&[n]);
1636 });
1637 floats(&mut func);
1638 let text = printed(&func, &mut names);
1639 assert!(!text.contains("fptoui"), "the unsigned conversion is gone: {text}");
1640 assert!(text.contains("fptosi.i64"), "the signed one is what is left: {text}");
1641 assert!(!text.contains("bitcast"), "and nothing reads the float as an integer: {text}");
1642 assert!(text.contains("fmul "), "the constant is taken or not by a multiply: {text}");
1643 assert!(text.contains("fsub "), "and subtracted before the conversion: {text}");
1644 assert!(text.contains("shl"), "with the top bit going back on after it: {text}");
1645 assert_eq!(func.blocks().count(), 1, "nor did the other one");
1646 valid(&func, &mut names);
1647 }
1648
1649 #[test]
1659 fn nothing_in_either_conversion_at_eighty_bits_rounds() {
1660 fn exact(v: i128) -> bool {
1662 let mag = v.unsigned_abs();
1663 mag == 0 || (mag >> mag.trailing_zeros()) < 1 << 64
1664 }
1665
1666 for &x in CASES {
1667 let signed = i128::from(x as i64);
1669 let addend = if (x as i64) < 0 { 1i128 << 64 } else { 0 };
1670 assert!(exact(signed), "the conversion of {x:#x} read as signed is exact");
1671 assert!(exact(addend), "and so is the constant it gets");
1672 assert!(exact(signed + addend), "and so is the sum");
1673 assert_eq!(signed + addend, i128::from(x), "converting {x:#x} into a long double");
1674 }
1675
1676 for &x in CASES {
1677 let value = i128::from(x);
1679 let taken = if value >= 1 << 63 { 1i128 << 63 } else { 0 };
1680 let under = value - taken;
1681 assert!(exact(under), "the subtraction that brings {x:#x} into range is exact");
1682 let top = u64::from(value >= 1 << 63) << 63;
1683 assert_eq!(under as u64 ^ top, x, "converting {x:#x} back into an unsigned word");
1684 }
1685 }
1686
1687 #[test]
1690 fn what_the_float_rewrites_leave_is_valid_ir() {
1691 let (mut names, mut func) = one(&[Type::int(32)], &[f64()], |build, args| {
1692 let k = build.fconst(f64(), 0x3ff8_0000_0000_0000);
1693 let d = build.unary(Opcode::UIToFP, args[0], f64());
1694 let n = build.unary(Opcode::FNeg, d, f64());
1695 let s = build.binary(Opcode::FAdd, n, k, Flags::NONE);
1696 build.ret(&[s]);
1697 });
1698 floats(&mut func);
1699 let module = Module::new(names.intern("f.c"), &target());
1700 rucc_ir::verify_func(&module, &func, &names).expect("the rewrite builds valid IR");
1701 }
1702
1703 #[test]
1706 fn a_function_with_no_floats_in_it_is_left_exactly_as_it_was() {
1707 let (mut names, mut func) = one(&[Type::int(32)], &[Type::int(32)], |build, args| {
1708 build.ret(&[args[0]]);
1709 });
1710 let before = printed(&func, &mut names);
1711 floats(&mut func);
1712 assert_eq!(printed(&func, &mut names), before);
1713 }
1714 fn access(size: u64, align: u32) -> MemInfo {
1715 MemInfo {
1716 size,
1717 align,
1718 order: MemOrder::NotAtomic,
1719 tbaa: None,
1720 owns: 0,
1721 restrict: Restrict::NONE,
1722 }
1723 }
1724
1725 fn moving(opcode: Opcode, size: u64, align: u32, byte: Option<i128>) -> (Interner, Func) {
1728 one(&[Type::PTR, Type::PTR], &[], |build, args| {
1729 let second = match byte {
1730 Some(value) => build.iconst(Type::int(8), value),
1731 None => args[1],
1732 };
1733 let mem = build.func().add_mem(access(size, align));
1734 let operands = build.func().push_values(&[args[0], second]);
1735 let data = InstData { args: operands, extra: Extra::Mem(mem), ..InstData::new(opcode) };
1736 build.inst(data, &[]);
1737 build.ret(&[]);
1738 })
1739 }
1740
1741 fn copying(size: u64, align: u32) -> (Interner, Func) {
1742 moving(Opcode::Memcpy, size, align, None)
1743 }
1744
1745 fn filling(size: u64, align: u32, byte: i128) -> (Interner, Func) {
1746 moving(Opcode::Memset, size, align, Some(byte))
1747 }
1748
1749 fn widths(size: u64, align: u32) -> Option<Vec<u32>> {
1752 Some(chunks(access(size, align), 8)?.into_iter().map(|(_, width)| width).collect())
1753 }
1754
1755 #[test]
1757 fn a_copy_becomes_a_load_and_a_store_for_each_word_of_it() {
1758 let (mut names, mut func) = copying(16, 8);
1759 bulk(&mut func, &mut names, 8);
1760
1761 let text = printed(&func, &mut names);
1762 assert!(!text.contains("memcpy"), "the copy is gone: {text}");
1763 assert_eq!(text.matches("load.i64").count(), 2, "a load per word: {text}");
1764 assert_eq!(text.matches("store").count(), 2, "a store per word: {text}");
1765 assert_eq!(
1766 text.matches("ptr_add").count(),
1767 2,
1768 "no offset for the word at the front: {text}"
1769 );
1770 }
1771
1772 #[test]
1776 fn a_word_is_as_wide_as_the_block_is_aligned_to() {
1777 assert_eq!(widths(16, 8), Some(vec![8, 8]));
1778 assert_eq!(widths(16, 4), Some(vec![4, 4, 4, 4]));
1779 assert_eq!(widths(4, 1), Some(vec![1, 1, 1, 1]));
1780 }
1781
1782 #[test]
1785 fn what_is_left_over_is_narrower_words_and_not_a_run_of_bytes() {
1786 assert_eq!(widths(13, 8), Some(vec![8, 4, 1]));
1787 assert_eq!(widths(3, 8), Some(vec![2, 1]));
1788 assert_eq!(widths(1, 8), Some(vec![1]));
1789 }
1790
1791 #[test]
1794 fn every_word_starts_somewhere_it_is_aligned_for() {
1795 for (at, width) in chunks(access(13, 8), 8).expect("a plan for thirteen bytes") {
1796 assert_eq!(at % u64::from(width), 0, "{at} is a multiple of {width}");
1797 }
1798 }
1799
1800 #[test]
1802 fn a_fill_is_the_byte_spread_across_each_word() {
1803 let (mut names, mut func) = filling(16, 8, 0);
1804 bulk(&mut func, &mut names, 8);
1805
1806 let text = printed(&func, &mut names);
1807 assert!(!text.contains("memset"), "the fill is gone: {text}");
1808 assert_eq!(text.matches("store").count(), 2, "a store per word: {text}");
1809 assert!(!text.contains("load"), "a fill reads nothing: {text}");
1810 }
1811
1812 #[test]
1815 fn the_byte_is_repeated_across_the_word_it_is_stored_as() {
1816 assert_eq!(spread(0, 8), 0);
1817 assert_eq!(spread(0xff, 1), 0xff);
1818 assert_eq!(spread(0xff, 4), 0xffff_ffff);
1819 assert_eq!(spread(0xab, 2), 0xabab);
1820 assert_eq!(spread(0xab, 8), 0xabab_abab_abab_abab);
1821 }
1822
1823 #[test]
1825 fn a_copy_too_large_to_unroll_becomes_a_call_to_the_runtime() {
1826 let size = u64::try_from(UNROLL).expect("a small threshold") + 1;
1827 let (mut names, mut func) = copying(size, 1);
1828 bulk(&mut func, &mut names, 8);
1829 let text = printed(&func, &mut names);
1830 assert!(text.contains("call @memcpy"), "a call and not a bulk move: {text}");
1831
1832 let (mut names, mut func) = copying(size - 1, 1);
1835 bulk(&mut func, &mut names, 8);
1836 assert!(!printed(&func, &mut names).contains("memcpy"), "one word under it is unrolled");
1837 }
1838
1839 #[test]
1842 fn the_call_passes_the_size_that_the_instruction_carried_beside_it() {
1843 let size = u64::try_from(UNROLL).expect("a small threshold") + 1;
1844 let (mut names, mut func) = copying(size, 1);
1845 bulk(&mut func, &mut names, 8);
1846 let text = printed(&func, &mut names);
1847 assert!(text.contains(&format!("{size}")), "the size is an argument now: {text}");
1848 }
1849
1850 #[test]
1853 fn a_move_is_a_call_however_small_it_is() {
1854 let (mut names, mut func) = moving(Opcode::Memmove, 8, 8, None);
1855 bulk(&mut func, &mut names, 8);
1856 let text = printed(&func, &mut names);
1857 assert!(text.contains("call @memmove"), "a call and not a run of moves: {text}");
1858 }
1859
1860 #[test]
1863 fn a_fill_whose_byte_is_not_a_constant_becomes_a_call() {
1864 let (mut names, mut func) = one(&[Type::PTR, Type::int(8)], &[], |build, args| {
1865 let mem = build.func().add_mem(access(8, 8));
1866 let operands = build.func().push_values(&[args[0], args[1]]);
1867 let data = InstData {
1868 args: operands,
1869 extra: Extra::Mem(mem),
1870 ..InstData::new(Opcode::Memset)
1871 };
1872 build.inst(data, &[]);
1873 build.ret(&[]);
1874 });
1875 bulk(&mut func, &mut names, 8);
1876 let text = printed(&func, &mut names);
1877 assert!(text.contains("call @memset"), "a call and not a run of stores: {text}");
1878 assert!(text.contains("zext.i32"), "the byte is widened to what C passes: {text}");
1880 }
1881
1882 #[test]
1885 fn no_word_is_wider_than_the_machine_moves_at_once() {
1886 assert_eq!(chunks(access(8, 8), 4).map(|plan| plan.len()), Some(2));
1887 assert_eq!(chunks(access(8, 8), 8).map(|plan| plan.len()), Some(1));
1888 }
1889
1890 #[test]
1891 fn what_a_copy_becomes_is_ir_that_verifies() {
1892 let (mut names, mut func) = copying(13, 8);
1893 bulk(&mut func, &mut names, 8);
1894 let module = Module::new(names.intern("c.c"), &target());
1895 rucc_ir::verify_func(&module, &func, &names).expect("the rewrite builds valid IR");
1896 }
1897
1898 #[test]
1899 fn what_a_fill_becomes_is_ir_that_verifies() {
1900 let (mut names, mut func) = filling(13, 8, 0xff);
1901 bulk(&mut func, &mut names, 8);
1902 let module = Module::new(names.intern("f.c"), &target());
1903 rucc_ir::verify_func(&module, &func, &names).expect("the rewrite builds valid IR");
1904 }
1905
1906 #[test]
1907 fn what_a_copy_too_large_to_unroll_becomes_is_ir_that_verifies() {
1908 let size = u64::try_from(UNROLL).expect("a small threshold") + 1;
1909 let (mut names, mut func) = copying(size, 1);
1910 bulk(&mut func, &mut names, 8);
1911 let module = Module::new(names.intern("c.c"), &target());
1912 rucc_ir::verify_func(&module, &func, &names).expect("the call is valid IR");
1913 }
1914
1915 #[test]
1917 fn a_function_with_no_bulk_move_in_it_is_left_exactly_as_it_was() {
1918 let (mut names, mut func) = one(&[Type::int(32)], &[Type::int(32)], |build, args| {
1919 build.ret(&[args[0]]);
1920 });
1921 let before = printed(&func, &mut names);
1922 bulk(&mut func, &mut names, 8);
1923 assert_eq!(printed(&func, &mut names), before);
1924 }
1925
1926 fn swapping(width: u32) -> (Interner, Func) {
1929 let ty = Type::int(width);
1930 one(&[ty], &[ty], |build, args| {
1931 let s = build.unary(Opcode::Bswap, args[0], ty);
1932 build.ret(&[s]);
1933 })
1934 }
1935
1936 #[test]
1943 fn the_masks_are_the_alternating_runs_of_the_group_being_swapped() {
1944 assert_eq!(alternating(32, 16), 0x0000_ffff);
1945 assert_eq!(alternating(32, 8), 0x00ff_00ff);
1946 assert_eq!(alternating(16, 8), 0x00ff);
1947 assert_eq!(alternating(64, 32), 0x0000_0000_ffff_ffff);
1948 assert_eq!(alternating(64, 16), 0x0000_ffff_0000_ffff);
1949 assert_eq!(alternating(64, 8), 0x00ff_00ff_00ff_00ff);
1950 }
1951
1952 #[test]
1954 fn a_two_byte_swap_is_one_exchange_of_neighbouring_bytes() {
1955 let (mut names, mut func) = swapping(16);
1956 bytes(&mut func);
1957
1958 let text = printed(&func, &mut names);
1959 assert!(!text.contains("bswap"), "the instruction is gone: {text}");
1960 assert!(text.contains("iconst.i16 255"), "the low byte of the pair: {text}");
1961 assert_eq!(text.matches("shl").count(), 1, "one shift up: {text}");
1962 assert_eq!(text.matches("lshr").count(), 1, "one shift down: {text}");
1963 assert_eq!(text.matches(" or ").count(), 1, "and the two put together: {text}");
1964 }
1965
1966 #[test]
1969 fn a_wider_swap_is_the_same_exchange_once_per_halving() {
1970 for (width, steps) in [(16u32, 1usize), (32, 2), (64, 3)] {
1971 let (mut names, mut func) = swapping(width);
1972 bytes(&mut func);
1973 let text = printed(&func, &mut names);
1974 assert_eq!(text.matches("shl").count(), steps, "at {width}: {text}");
1975 assert_eq!(text.matches("lshr").count(), steps, "at {width}: {text}");
1976 assert_eq!(text.matches(" and ").count(), steps * 2, "at {width}: {text}");
1977 assert_eq!(text.matches(" or ").count(), steps, "at {width}: {text}");
1978 }
1979 }
1980
1981 #[test]
1984 fn the_shift_counts_are_the_group_width_halving_as_it_goes() {
1985 let (mut names, mut func) = swapping(64);
1986 bytes(&mut func);
1987 let text = printed(&func, &mut names);
1988 for count in ["iconst.i64 32", "iconst.i64 16", "iconst.i64 8"] {
1989 assert!(text.contains(count), "{count} is a step: {text}");
1990 }
1991 }
1992
1993 #[test]
1996 fn what_a_byte_swap_becomes_is_ir_that_verifies() {
1997 let (mut names, mut func) = swapping(32);
1998 bytes(&mut func);
1999 let module = Module::new(names.intern("b.c"), &target());
2000 rucc_ir::verify_func(&module, &func, &names).expect("the rewrite builds valid IR");
2001 }
2002
2003 #[test]
2006 fn a_function_with_no_byte_swap_in_it_is_left_exactly_as_it_was() {
2007 let (mut names, mut func) = one(&[Type::int(32)], &[Type::int(32)], |build, args| {
2008 build.ret(&[args[0]]);
2009 });
2010 let before = printed(&func, &mut names);
2011 bytes(&mut func);
2012 assert_eq!(printed(&func, &mut names), before);
2013 }
2014
2015 fn counting(op: Opcode, width: u32) -> (Interner, Func) {
2017 let ty = Type::int(width);
2018 one(&[ty], &[ty], |build, args| {
2019 let c = build.unary(op, args[0], ty);
2020 build.ret(&[c]);
2021 })
2022 }
2023
2024 #[test]
2027 fn the_counting_masks_are_the_ones_the_halving_sum_is_written_with() {
2028 assert_eq!(alternating(32, 1), 0x5555_5555);
2029 assert_eq!(alternating(32, 2), 0x3333_3333);
2030 assert_eq!(alternating(32, 4), 0x0f0f_0f0f);
2031 assert_eq!(every(32, 8, 1), 0x0101_0101);
2032 assert_eq!(every(64, 8, 1), 0x0101_0101_0101_0101);
2033 }
2034
2035 #[test]
2038 fn a_set_bit_count_is_the_halving_sum_and_a_multiply_that_adds_the_bytes() {
2039 let (mut names, mut func) = counting(Opcode::Ctpop, 32);
2040 counts(&mut func);
2041
2042 let text = printed(&func, &mut names);
2043 assert!(!text.contains("ctpop"), "the instruction is gone: {text}");
2044 assert!(text.contains("iconst.i32 1431655765"), "the pairs mask: {text}");
2045 assert!(text.contains("iconst.i32 858993459"), "the nibbles mask: {text}");
2046 assert!(text.contains("iconst.i32 252645135"), "the bytes mask: {text}");
2047 assert_eq!(text.matches(" mul ").count(), 1, "one multiply: {text}");
2048 assert!(text.contains("iconst.i32 24"), "and the top byte is the answer: {text}");
2049 }
2050
2051 #[test]
2053 fn a_count_of_one_byte_stops_before_the_multiply() {
2054 let (mut names, mut func) = counting(Opcode::Ctpop, 8);
2055 counts(&mut func);
2056 let text = printed(&func, &mut names);
2057 assert!(!text.contains("ctpop"), "{text}");
2058 assert!(!text.contains(" mul "), "nothing to add together: {text}");
2059 }
2060
2061 #[test]
2064 fn a_leading_zero_count_smears_the_value_down_and_counts_the_complement() {
2065 let (mut names, mut func) = counting(Opcode::Ctlz, 32);
2066 counts(&mut func);
2067
2068 let text = printed(&func, &mut names);
2069 assert!(!text.contains("ctlz"), "the instruction is gone: {text}");
2070 assert!(!text.contains("ctpop"), "and so is the count it became: {text}");
2071 for by in ["iconst.i32 1", "iconst.i32 2", "iconst.i32 4", "iconst.i32 8", "iconst.i32 16"]
2072 {
2073 assert!(text.contains(by), "{by} is a smearing step: {text}");
2074 }
2075 assert_eq!(text.matches(" xor ").count(), 1, "one complement: {text}");
2076 }
2077
2078 #[test]
2080 fn a_trailing_zero_count_masks_the_bits_below_the_lowest_set_one() {
2081 let (mut names, mut func) = counting(Opcode::Cttz, 32);
2082 counts(&mut func);
2083
2084 let text = printed(&func, &mut names);
2085 assert!(!text.contains("cttz"), "the instruction is gone: {text}");
2086 assert!(!text.contains("ctpop"), "and so is the count it became: {text}");
2087 assert!(text.contains("iconst.i32 -1"), "the complement and the decrement: {text}");
2088 assert_eq!(text.matches(" xor ").count(), 1, "one complement: {text}");
2089 assert!(text.matches(" or ").count() <= 1, "no smearing run: {text}");
2091 }
2092
2093 #[test]
2096 fn what_a_bit_count_becomes_is_ir_that_verifies() {
2097 for op in [Opcode::Ctpop, Opcode::Ctlz, Opcode::Cttz] {
2098 for width in [8u32, 16, 32, 64] {
2099 let (mut names, mut func) = counting(op, width);
2100 counts(&mut func);
2101 let module = Module::new(names.intern("c.c"), &target());
2102 rucc_ir::verify_func(&module, &func, &names)
2103 .unwrap_or_else(|e| panic!("{op:?} at {width}: {e:?}"));
2104 }
2105 }
2106 }
2107
2108 #[test]
2112 fn a_width_the_halving_sum_is_not_written_for_is_left_alone() {
2113 let (mut names, mut func) = counting(Opcode::Ctpop, 24);
2114 counts(&mut func);
2115 assert!(printed(&func, &mut names).contains("ctpop"), "left as it was");
2116 }
2117
2118 #[test]
2120 fn a_function_with_no_bit_count_in_it_is_left_exactly_as_it_was() {
2121 let (mut names, mut func) = one(&[Type::int(32)], &[Type::int(32)], |build, args| {
2122 build.ret(&[args[0]]);
2123 });
2124 let before = printed(&func, &mut names);
2125 counts(&mut func);
2126 assert_eq!(printed(&func, &mut names), before);
2127 }
2128
2129 fn checking(op: Opcode, width: u32) -> (Interner, Func) {
2132 let ty = Type::int(width);
2133 let bit = ty.with_lane(Type::I1);
2134 one(&[ty, ty], &[ty, bit], |build, args| {
2135 let (value, flag) = build.checked(op, args[0], args[1]);
2136 build.ret(&[value, flag]);
2137 })
2138 }
2139
2140 #[test]
2143 fn a_checked_unsigned_add_becomes_an_add_and_one_comparison() {
2144 let (mut names, mut func) = checking(Opcode::UAddOverflow, 32);
2145 overflows(&mut func);
2146
2147 let text = printed(&func, &mut names);
2148 assert!(!text.contains("uadd_overflow"), "the instruction is gone: {text}");
2149 assert_eq!(text.matches(" add ").count(), 1, "one add: {text}");
2150 assert_eq!(text.matches("icmp ult").count(), 1, "and one comparison: {text}");
2151 assert!(!text.contains(" xor "), "nothing about sign bits: {text}");
2152 }
2153
2154 #[test]
2157 fn a_checked_signed_add_becomes_an_add_and_the_sign_bit_of_two_exclusive_ors() {
2158 let (mut names, mut func) = checking(Opcode::SAddOverflow, 32);
2159 overflows(&mut func);
2160
2161 let text = printed(&func, &mut names);
2162 assert!(!text.contains("sadd_overflow"), "the instruction is gone: {text}");
2163 assert_eq!(text.matches(" add ").count(), 1, "one add: {text}");
2164 assert_eq!(text.matches(" xor ").count(), 2, "the answer against each operand: {text}");
2165 assert_eq!(text.matches(" and ").count(), 1, "both at once: {text}");
2166 assert!(text.contains("icmp slt"), "and its sign bit: {text}");
2167 }
2168
2169 #[test]
2172 fn a_checked_unsigned_subtract_compares_the_operands_and_not_the_answer() {
2173 let (mut names, mut func) = checking(Opcode::USubOverflow, 64);
2174 overflows(&mut func);
2175
2176 let text = printed(&func, &mut names);
2177 assert!(!text.contains("usub_overflow"), "the instruction is gone: {text}");
2178 assert_eq!(text.matches(" sub ").count(), 1, "one subtract: {text}");
2179 assert!(text.contains("icmp ult %0, %1"), "the operands, in order: {text}");
2180 }
2181
2182 #[test]
2189 fn a_checked_multiply_becomes_a_multiply_and_the_high_half_of_the_product() {
2190 let (mut names, mut func) = checking(Opcode::UMulOverflow, 64);
2191 overflows(&mut func);
2192
2193 let text = printed(&func, &mut names);
2194 assert!(!text.contains("umul_overflow"), "the instruction is gone: {text}");
2195 assert_eq!(text.matches(" mul ").count(), 5, "the answer and the four halves: {text}");
2196 assert!(text.contains("iconst.i64 32"), "split at half the width: {text}");
2197 assert!(text.contains("iconst.i64 4294967295"), "and masked to it: {text}");
2198 assert!(text.contains("icmp ne"), "the high half against zero: {text}");
2199 assert!(!text.contains("ashr"), "and nothing corrected for sign: {text}");
2200 }
2201
2202 #[test]
2205 fn a_checked_signed_multiply_corrects_the_high_half_for_each_negative_operand() {
2206 let (mut names, mut func) = checking(Opcode::SMulOverflow, 64);
2207 overflows(&mut func);
2208
2209 let text = printed(&func, &mut names);
2210 assert!(!text.contains("smul_overflow"), "the instruction is gone: {text}");
2211 assert_eq!(
2212 text.matches(" ashr ").count(),
2213 3,
2214 "each operand's sign, and the answer: {text}"
2215 );
2216 assert!(text.contains("iconst.i64 63"), "spread from the top bit: {text}");
2217 assert_eq!(text.matches(" sub ").count(), 2, "one correction per operand: {text}");
2218 }
2219
2220 #[test]
2224 fn both_results_are_substituted_into_whoever_was_reading_them() {
2225 let (mut names, mut func) = checking(Opcode::SAddOverflow, 32);
2226 overflows(&mut func);
2227
2228 let text = printed(&func, &mut names);
2232 assert_eq!(
2233 text,
2234 concat!(
2235 "func @f(i32, i32) -> (i32, i1), linkage(external) {\n",
2236 "block0(%0: i32, %1: i32):\n",
2237 " %2 = add %0, %1\n",
2238 " %3 = xor %0, %2\n",
2239 " %4 = xor %1, %2\n",
2240 " %5 = and %3, %4\n",
2241 " %6 = iconst.i32 0\n",
2242 " %7 = icmp slt %5, %6\n",
2243 " return %2, %7\n",
2244 "}\n",
2245 ),
2246 );
2247 }
2248
2249 #[test]
2252 fn what_an_overflow_check_becomes_is_ir_that_verifies() {
2253 let all = [
2254 Opcode::UAddOverflow,
2255 Opcode::SAddOverflow,
2256 Opcode::USubOverflow,
2257 Opcode::SSubOverflow,
2258 Opcode::UMulOverflow,
2259 Opcode::SMulOverflow,
2260 ];
2261 for op in all {
2262 for width in [8u32, 16, 32, 64, 128] {
2263 let (mut names, mut func) = checking(op, width);
2264 overflows(&mut func);
2265 let module = Module::new(names.intern("c.c"), &target());
2266 rucc_ir::verify_func(&module, &func, &names)
2267 .unwrap_or_else(|e| panic!("{op:?} at {width}: {e:?}"));
2268 }
2269 }
2270 }
2271
2272 #[test]
2276 fn a_width_the_split_is_not_written_for_is_left_alone() {
2277 let (mut names, mut func) = checking(Opcode::UMulOverflow, 24);
2278 overflows(&mut func);
2279 assert!(printed(&func, &mut names).contains("umul_overflow"), "left as it was");
2280 }
2281
2282 #[test]
2289 fn a_check_at_the_width_no_register_holds_is_rewritten_here() {
2290 let (mut names, mut func) = checking(Opcode::UAddOverflow, 128);
2291 overflows(&mut func);
2292 let text = printed(&func, &mut names);
2293 assert!(!text.contains("uadd_overflow"), "the check is gone: {text}");
2294 assert!(text.contains(" = add "), "into the arithmetic it is: {text}");
2295 assert!(text.contains("icmp ult"), "and the test that says it wrapped: {text}");
2296 }
2297
2298 #[test]
2300 fn a_function_with_no_overflow_check_in_it_is_left_exactly_as_it_was() {
2301 let (mut names, mut func) = one(&[Type::int(32)], &[Type::int(32)], |build, args| {
2302 build.ret(&[args[0]]);
2303 });
2304 let before = printed(&func, &mut names);
2305 overflows(&mut func);
2306 assert_eq!(printed(&func, &mut names), before);
2307 }
2308
2309 fn reading(ty: Type, align: u32, order: MemOrder) -> (Interner, Func) {
2311 one(&[Type::PTR], &[ty], |build, args| {
2312 let info = MemInfo { order, ..access(0, align) };
2313 let value = build.atomic_load(ty, args[0], info, Flags::NONE);
2314 build.ret(&[value]);
2315 })
2316 }
2317
2318 fn writing(ty: Type, align: u32, order: MemOrder) -> (Interner, Func) {
2320 one(&[Type::PTR, ty], &[], |build, args| {
2321 let info = MemInfo { order, ..access(0, align) };
2322 build.atomic_store(args[1], args[0], info, Flags::NONE);
2323 build.ret(&[]);
2324 })
2325 }
2326
2327 #[test]
2334 fn an_ordered_access_becomes_the_plain_one_this_machine_already_orders() {
2335 for order in [MemOrder::Relaxed, MemOrder::Acquire, MemOrder::SeqCst] {
2336 let (mut names, mut func) = reading(Type::int(32), 4, order);
2337 orderings(&mut func, 8);
2338 let text = printed(&func, &mut names);
2339 assert!(text.contains("load.i32"), "{order:?}: {text}");
2340 assert!(!text.contains("atomic_load"), "{order:?}: {text}");
2341 assert!(!text.contains(order.name()), "the ordering came off: {text}");
2342 }
2343
2344 for order in [MemOrder::Relaxed, MemOrder::Release] {
2345 let (mut names, mut func) = writing(Type::int(32), 4, order);
2346 orderings(&mut func, 8);
2347 let text = printed(&func, &mut names);
2348 assert!(text.contains("store %1 -> %0"), "{order:?}: {text}");
2349 assert!(!text.contains("atomic_store"), "{order:?}: {text}");
2350 assert!(!text.contains("fence"), "{order:?} costs nothing here: {text}");
2351 }
2352 }
2353
2354 #[test]
2360 fn the_strongest_store_keeps_a_barrier_behind_it() {
2361 let (mut names, mut func) = writing(Type::int(32), 4, MemOrder::SeqCst);
2362 orderings(&mut func, 8);
2363 let text = printed(&func, &mut names);
2364 let (before, after) = text.split_once("fence seq_cst").expect("a barrier");
2365 assert!(before.contains("store %1 -> %0"), "the store comes first: {text}");
2366 assert!(!after.contains("store"), "and nothing is between them: {text}");
2367 assert!(!text.contains("atomic_store"), "{text}");
2368 }
2369
2370 #[test]
2373 fn a_barrier_is_left_for_the_place_that_knows_what_one_costs() {
2374 for order in MemOrder::all().filter(|&order| order != MemOrder::NotAtomic) {
2375 let (mut names, mut func) = one(&[], &[], |build, _| {
2376 build.fence(order);
2377 build.ret(&[]);
2378 });
2379 let before = printed(&func, &mut names);
2380 orderings(&mut func, 8);
2381 assert_eq!(printed(&func, &mut names), before, "{order:?}");
2382 }
2383 }
2384
2385 #[test]
2391 fn an_access_this_machine_cannot_do_in_one_go_is_left_alone() {
2392 for (ty, align) in [(Type::int(128), 16), (Type::int(64), 4)] {
2393 let (mut names, mut func) = reading(ty, align, MemOrder::SeqCst);
2394 orderings(&mut func, 8);
2395 assert!(printed(&func, &mut names).contains("atomic_load"), "left as it was");
2396 }
2397 }
2398
2399 #[test]
2402 fn what_the_ordered_accesses_become_verifies() {
2403 for order in MemOrder::all().filter(|&order| order != MemOrder::NotAtomic) {
2404 for (mut names, mut func) in
2405 [reading(Type::int(32), 4, order), writing(Type::int(32), 4, order)]
2406 {
2407 if !order.is_valid_for_load() && !order.is_valid_for_store() {
2408 continue;
2409 }
2410 orderings(&mut func, 8);
2411 let module = Module::new(names.intern("a.c"), &target());
2412 rucc_ir::verify_func(&module, &func, &names)
2413 .unwrap_or_else(|e| panic!("{order:?}: {e:?}"));
2414 }
2415 }
2416 }
2417
2418 #[test]
2420 fn a_function_with_no_ordered_access_in_it_is_left_exactly_as_it_was() {
2421 let (mut names, mut func) = one(&[Type::int(32)], &[Type::int(32)], |build, args| {
2422 build.ret(&[args[0]]);
2423 });
2424 let before = printed(&func, &mut names);
2425 orderings(&mut func, 8);
2426 assert_eq!(printed(&func, &mut names), before);
2427 }
2428}