1use std::cmp::Ordering;
52use std::collections::HashMap;
53
54use rucc_base::Interner;
55use rucc_ir::{
56 BlockCall, Builder, CallInfo, Def, Extra, Flags, FloatPred, Func, Imm, Inst, InstData, IntPred,
57 MemInfo, MemOrder, Opcode, Signature, Type, Value,
58};
59
60pub fn switches(func: &mut Func) {
66 let found: Vec<Inst> = func
67 .blocks()
68 .filter_map(|block| func.terminator(block))
69 .filter(|&inst| func[inst].opcode == Opcode::Switch)
70 .collect();
71 for inst in found {
72 chain(func, inst);
73 }
74}
75
76fn chain(func: &mut Func, inst: Inst) {
88 let block = func.block_of(inst).expect("a terminator is in a block");
89 let span = func.span(inst);
90 let Extra::Switch(info) = func[inst].extra else { return };
91 let info = func[info];
92 let value = func[func[inst].args][0];
93 let ty = func[value].ty.lane();
96 let calls: Vec<BlockCall> = func[info.targets].to_vec();
97 let cases: Vec<Imm> = func[info.cases].to_vec();
98 let Some((default, arms)) = calls.split_first() else { return };
99
100 func.remove_inst(inst);
103
104 let Some((first, rest)) = arms.split_first() else {
108 let args: Vec<Value> = func[default.args].to_vec();
109 Builder::new(func, block).at(span).jump(default.block, &args);
110 return;
111 };
112
113 let mut at = block;
114 for (index, arm) in std::iter::once(first).chain(rest).enumerate() {
115 let last = index + 1 == arms.len();
116 let next = if last { default.block } else { func.create_block() };
117 let onward: Vec<Value> = if last { func[default.args].to_vec() } else { Vec::new() };
118 let taken: Vec<Value> = func[arm.args].to_vec();
119 let case = cases[index].signed(ty);
120
121 let mut build = Builder::new(func, at).at(span);
122 let want = build.iconst(ty, case);
123 let same = build.icmp(IntPred::Eq, value, want);
124 build.br_if(same, arm.block, &taken, next, &onward);
125 at = next;
126 }
127}
128
129pub fn orderings(func: &mut Func, word: u32) {
166 let found: Vec<Inst> =
167 func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
168 for inst in found {
169 match func[inst].opcode {
170 Opcode::AtomicLoad => relaxed(func, inst, Opcode::Load, word),
171 Opcode::AtomicStore => relaxed(func, inst, Opcode::Store, word),
172 _ => {}
173 }
174 }
175}
176
177fn relaxed(func: &mut Func, inst: Inst, plain: Opcode, word: u32) {
191 let Extra::Mem(mem) = func[inst].extra else { return };
192 let info = func[mem];
193 let ty = match plain {
194 Opcode::Store => match func[func[inst].args].first() {
195 Some(&value) => func[value].ty,
196 None => return,
197 },
198 _ => produced(func, inst),
199 };
200 if !indivisible(ty, info, word) {
201 return;
202 }
203 let unordered = MemInfo { order: MemOrder::NotAtomic, ..info };
204
205 if plain == Opcode::Store && info.order == MemOrder::SeqCst {
206 let [value, addr] = func[func[inst].args] else { return };
207 write(func, inst, value, addr, unordered);
208 let none = func.push_values(&[]);
209 let data = &mut func[inst];
210 data.opcode = Opcode::Fence;
211 data.args = none;
212 data.extra = Extra::Order(MemOrder::SeqCst);
213 data.flags = data.flags.intersection(Flags::legal_on(Opcode::Fence));
214 return;
215 }
216
217 let plainly = func.add_mem(unordered);
218 let data = &mut func[inst];
219 data.opcode = plain;
220 data.extra = Extra::Mem(plainly);
221 data.flags = data.flags.intersection(Flags::legal_on(plain));
222}
223
224fn indivisible(ty: Type, info: MemInfo, word: u32) -> bool {
237 let bytes = if ty.is_ptr() { word } else { ty.bits().div_ceil(8) };
238 ty.is_scalar() && bytes.is_power_of_two() && bytes <= word && info.align >= bytes
239}
240
241pub fn floats(func: &mut Func) {
255 let found: Vec<Inst> =
256 func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
257 for inst in found {
258 match func[inst].opcode {
259 Opcode::FConst => constant(func, inst),
260 Opcode::FNeg => negate(func, inst),
261 Opcode::SIToFP | Opcode::UIToFP => widen_then_convert(func, inst),
262 Opcode::FPToSI | Opcode::FPToUI => convert_then_narrow(func, inst),
263 _ => {}
264 }
265 }
266}
267
268fn constant(func: &mut Func, inst: Inst) {
275 let ty = produced(func, inst);
276 let Extra::Imm(imm) = func[inst].extra else { return };
277 if !ty.is_float() || !ty.is_scalar() {
278 return;
279 }
280 let int = Type::int(ty.bits());
281 let bits = func[imm].bits();
282 let spelled = ahead_const(func, inst, Imm::int(bits as i128, int), int);
285 becomes(func, inst, Opcode::Bitcast, &[spelled]);
286}
287
288fn negate(func: &mut Func, inst: Inst) {
299 let ty = produced(func, inst);
300 let Some(&arg) = func[func[inst].args].first() else { return };
301 if !ty.is_float() || !ty.is_scalar() {
302 return;
303 }
304 let int = Type::int(ty.bits());
305 let bits = ahead(func, inst, Opcode::Bitcast, &[arg], int);
306 let mask = ahead_const(func, inst, Imm::int(1i128 << (ty.bits() - 1), int), int);
307 let flipped = ahead(func, inst, Opcode::Xor, &[bits, mask], int);
308 becomes(func, inst, Opcode::Bitcast, &[flipped]);
309}
310
311fn widen_then_convert(func: &mut Func, inst: Inst) {
318 let signed = func[inst].opcode == Opcode::SIToFP;
319 let Some(&arg) = func[func[inst].args].first() else { return };
320 let from = func[arg].ty;
321 if !from.is_int() || !from.is_scalar() {
322 return;
323 }
324 let Some(width) = holder(from.bits(), signed) else {
325 from_unsigned_word(func, inst, arg, from);
326 return;
327 };
328 if width == from.bits() {
329 return;
330 }
331 let widen = if signed { Opcode::SExt } else { Opcode::ZExt };
332 let wide = ahead(func, inst, widen, &[arg], Type::int(width));
333 becomes(func, inst, Opcode::SIToFP, &[wide]);
334}
335
336fn convert_then_narrow(func: &mut Func, inst: Inst) {
343 let signed = func[inst].opcode == Opcode::FPToSI;
344 let ty = produced(func, inst);
345 let Some(&arg) = func[func[inst].args].first() else { return };
346 if !ty.is_int() || !ty.is_scalar() {
347 return;
348 }
349 let Some(width) = holder(ty.bits(), signed) else {
350 to_unsigned_word(func, inst, arg, ty);
351 return;
352 };
353 if width == ty.bits() {
354 return;
355 }
356 let wide = ahead(func, inst, Opcode::FPToSI, &[arg], Type::int(width));
357 becomes(func, inst, Opcode::Trunc, &[wide]);
358}
359
360fn from_unsigned_word(func: &mut Func, inst: Inst, arg: Value, from: Type) {
382 let ty = produced(func, inst);
383 if !ty.is_float() || !ty.is_scalar() {
384 return;
385 }
386 let spread = spread_top_bit(func, inst, arg, from);
387
388 let one = ahead_const(func, inst, Imm::int(1, from), from);
390 let lost = ahead(func, inst, Opcode::And, &[arg, one], from);
391 let half = ahead(func, inst, Opcode::LShr, &[arg, one], from);
392 let odd = ahead(func, inst, Opcode::Or, &[half, lost], from);
393
394 let differ = ahead(func, inst, Opcode::Xor, &[arg, odd], from);
396 let taken = ahead(func, inst, Opcode::And, &[differ, spread], from);
397 let source = ahead(func, inst, Opcode::Xor, &[arg, taken], from);
398 let converted = ahead(func, inst, Opcode::SIToFP, &[source], ty);
399
400 let bits = Type::int(ty.bits());
403 let narrow = same_width(func, inst, spread, from, bits);
404 let raw = ahead(func, inst, Opcode::Bitcast, &[converted], bits);
405 let again = ahead(func, inst, Opcode::And, &[raw, narrow], bits);
406 let addend = ahead(func, inst, Opcode::Bitcast, &[again], ty);
407 becomes(func, inst, Opcode::FAdd, &[converted, addend]);
408}
409
410fn to_unsigned_word(func: &mut Func, inst: Inst, arg: Value, ty: Type) {
423 let from = func[arg].ty;
424 if !from.is_float() || !from.is_scalar() {
425 return;
426 }
427 let bits = Type::int(from.bits());
429 let pattern = Imm::int(half_the_range(from.bits()), bits);
430 let spelled = ahead_const(func, inst, pattern, bits);
431 let half = ahead(func, inst, Opcode::Bitcast, &[spelled], from);
432
433 let over = ahead_cmp(func, inst, Opcode::FCmp, Extra::FloatPred(FloatPred::Oge), &[arg, half]);
434 let wide = ahead(func, inst, Opcode::ZExt, &[over], bits);
435 let zero = ahead_const(func, inst, Imm::int(0, bits), bits);
436 let spread = ahead(func, inst, Opcode::Sub, &[zero, wide], bits);
437
438 let amount = ahead(func, inst, Opcode::And, &[spread, spelled], bits);
439 let taken = ahead(func, inst, Opcode::Bitcast, &[amount], 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 spread_top_bit(func: &mut Func, inst: Inst, arg: Value, ty: Type) -> Value {
456 let zero = ahead_const(func, inst, Imm::int(0, ty), ty);
457 let set = ahead_cmp(func, inst, Opcode::ICmp, Extra::IntPred(IntPred::Slt), &[arg, zero]);
458 let wide = ahead(func, inst, Opcode::ZExt, &[set], ty);
459 ahead(func, inst, Opcode::Sub, &[zero, wide], ty)
460}
461
462fn same_width(func: &mut Func, inst: Inst, value: Value, from: Type, to: Type) -> Value {
464 match to.bits().cmp(&from.bits()) {
465 Ordering::Equal => value,
466 Ordering::Less => ahead(func, inst, Opcode::Trunc, &[value], to),
467 Ordering::Greater => ahead(func, inst, Opcode::SExt, &[value], to),
468 }
469}
470
471fn half_the_range(width: u32) -> i128 {
477 match width {
478 32 => 0x5F00_0000,
479 _ => 0x43E0_0000_0000_0000,
480 }
481}
482
483pub fn bytes(func: &mut Func) {
497 let found: Vec<Inst> =
498 func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
499 for inst in found {
500 if func[inst].opcode == Opcode::Bswap {
501 swap(func, inst);
502 }
503 }
504}
505
506fn swap(func: &mut Func, inst: Inst) {
523 let ty = produced(func, inst);
524 let Some(&arg) = func[func[inst].args].first() else { return };
525 if !ty.is_int() || !ty.is_scalar() || ty.bits() < 16 || ty.bits() % 8 != 0 {
526 return;
527 }
528
529 let mut value = arg;
530 let mut group = ty.bits() / 2;
531 while group >= 8 {
532 let mask = alternating(ty.bits(), group);
535 let keep = ahead_const(func, inst, Imm::int(mask, ty), ty);
536 let count = ahead_const(func, inst, Imm::int(i128::from(group), ty), ty);
537 let low = ahead(func, inst, Opcode::And, &[value, keep], ty);
538 let up = ahead(func, inst, Opcode::Shl, &[low, count], ty);
539 let down = ahead(func, inst, Opcode::LShr, &[value, count], ty);
540 let high = ahead(func, inst, Opcode::And, &[down, keep], ty);
541 if group == 8 {
544 becomes(func, inst, Opcode::Or, &[up, high]);
545 return;
546 }
547 value = ahead(func, inst, Opcode::Or, &[up, high], ty);
548 group /= 2;
549 }
550}
551
552fn alternating(width: u32, group: u32) -> i128 {
563 every(width, group * 2, group)
564}
565
566fn every(width: u32, step: u32, run: u32) -> i128 {
575 let ones = (1i128 << run) - 1;
576 let mut mask = 0i128;
577 let mut at = 0;
578 while at < width {
579 mask |= ones << at;
580 at += step;
581 }
582 mask
583}
584
585pub fn counts(func: &mut Func) {
599 let found: Vec<Inst> =
600 func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
601 for inst in found {
602 match func[inst].opcode {
603 Opcode::Ctlz => searched(func, inst, true),
604 Opcode::Cttz => searched(func, inst, false),
605 _ => {}
606 }
607 }
608 let found: Vec<Inst> =
609 func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
610 for inst in found {
611 if func[inst].opcode == Opcode::Ctpop {
612 counted(func, inst);
613 }
614 }
615}
616
617fn searched(func: &mut Func, inst: Inst, leading: bool) {
635 let ty = produced(func, inst);
636 let Some(&arg) = func[func[inst].args].first() else { return };
637 if !countable(ty) {
638 return;
639 }
640 let ones = ahead_const(func, inst, Imm::int(-1, ty), ty);
641 if leading {
642 let mut value = arg;
643 let mut by = 1;
644 while by < ty.bits() {
645 let count = ahead_const(func, inst, Imm::int(i128::from(by), ty), ty);
646 let down = ahead(func, inst, Opcode::LShr, &[value, count], ty);
647 value = ahead(func, inst, Opcode::Or, &[value, down], ty);
648 by *= 2;
649 }
650 let above = ahead(func, inst, Opcode::Xor, &[value, ones], ty);
651 becomes(func, inst, Opcode::Ctpop, &[above]);
652 return;
653 }
654 let missing = ahead(func, inst, Opcode::Xor, &[arg, ones], ty);
655 let less = ahead(func, inst, Opcode::Add, &[arg, ones], ty);
656 let below = ahead(func, inst, Opcode::And, &[missing, less], ty);
657 becomes(func, inst, Opcode::Ctpop, &[below]);
658}
659
660fn counted(func: &mut Func, inst: Inst) {
674 let ty = produced(func, inst);
675 let Some(&arg) = func[func[inst].args].first() else { return };
676 if !countable(ty) {
677 return;
678 }
679 let width = ty.bits();
680 let pairs = ahead_const(func, inst, Imm::int(alternating(width, 1), ty), ty);
681 let two = ahead_const(func, inst, Imm::int(2, ty), ty);
682 let one = ahead_const(func, inst, Imm::int(1, ty), ty);
683 let high = ahead(func, inst, Opcode::LShr, &[arg, one], ty);
684 let odd = ahead(func, inst, Opcode::And, &[high, pairs], ty);
685 let bits = ahead(func, inst, Opcode::Sub, &[arg, odd], ty);
686
687 let quads = ahead_const(func, inst, Imm::int(alternating(width, 2), ty), ty);
688 let low = ahead(func, inst, Opcode::And, &[bits, quads], ty);
689 let up = ahead(func, inst, Opcode::LShr, &[bits, two], ty);
690 let rest = ahead(func, inst, Opcode::And, &[up, quads], ty);
691 let nibbles = ahead(func, inst, Opcode::Add, &[low, rest], ty);
692
693 let four = ahead_const(func, inst, Imm::int(4, ty), ty);
694 let bytes = ahead_const(func, inst, Imm::int(alternating(width, 4), ty), ty);
695 let folded = ahead(func, inst, Opcode::LShr, &[nibbles, four], ty);
696 let summed = ahead(func, inst, Opcode::Add, &[nibbles, folded], ty);
697 if width == 8 {
698 becomes(func, inst, Opcode::And, &[summed, bytes]);
699 return;
700 }
701 let held = ahead(func, inst, Opcode::And, &[summed, bytes], ty);
702
703 let spread = ahead_const(func, inst, Imm::int(every(width, 8, 1), ty), ty);
704 let top = ahead_const(func, inst, Imm::int(i128::from(width - 8), ty), ty);
705 let total = ahead(func, inst, Opcode::Mul, &[held, spread], ty);
706 becomes(func, inst, Opcode::LShr, &[total, top]);
707}
708
709pub fn overflows(func: &mut Func) {
722 let found: Vec<Inst> =
723 func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
724 let mut forward = HashMap::new();
725 for inst in found {
726 let checked = match func[inst].opcode {
727 Opcode::UAddOverflow => Checked::Add(false),
728 Opcode::SAddOverflow => Checked::Add(true),
729 Opcode::USubOverflow => Checked::Sub(false),
730 Opcode::SSubOverflow => Checked::Sub(true),
731 Opcode::UMulOverflow => Checked::Mul(false),
732 Opcode::SMulOverflow => Checked::Mul(true),
733 _ => continue,
734 };
735 overflowed(func, inst, checked, &mut forward);
736 }
737 if !forward.is_empty() {
738 substitute(func, &forward);
739 }
740}
741
742#[derive(Debug, Clone, Copy)]
744enum Checked {
745 Add(bool),
747 Sub(bool),
749 Mul(bool),
751}
752
753fn overflowed(func: &mut Func, inst: Inst, checked: Checked, forward: &mut HashMap<Value, Value>) {
770 let ty = produced(func, inst);
771 let [a, b] = func[func[inst].args] else { return };
772 if !countable(ty) {
773 return;
774 }
775 let (value, bit) = match checked {
776 Checked::Add(signed) => {
777 let value = ahead(func, inst, Opcode::Add, &[a, b], ty);
778 let bit = if signed {
779 let left = ahead(func, inst, Opcode::Xor, &[a, value], ty);
780 let right = ahead(func, inst, Opcode::Xor, &[b, value], ty);
781 let both = ahead(func, inst, Opcode::And, &[left, right], ty);
782 negative(func, inst, both, ty)
783 } else {
784 compared(func, inst, IntPred::Ult, value, a)
785 };
786 (value, bit)
787 }
788 Checked::Sub(signed) => {
789 let value = ahead(func, inst, Opcode::Sub, &[a, b], ty);
790 let bit = if signed {
791 let apart = ahead(func, inst, Opcode::Xor, &[a, b], ty);
792 let moved = ahead(func, inst, Opcode::Xor, &[a, value], ty);
793 let both = ahead(func, inst, Opcode::And, &[apart, moved], ty);
794 negative(func, inst, both, ty)
795 } else {
796 compared(func, inst, IntPred::Ult, a, b)
797 };
798 (value, bit)
799 }
800 Checked::Mul(signed) => {
801 let value = ahead(func, inst, Opcode::Mul, &[a, b], ty);
802 let high = high_half(func, inst, a, b, signed, ty);
803 let bit = if signed {
804 let sign = ahead_const(func, inst, Imm::int(i128::from(ty.bits() - 1), ty), ty);
805 let wanted = ahead(func, inst, Opcode::AShr, &[value, sign], ty);
806 compared(func, inst, IntPred::Ne, high, wanted)
807 } else {
808 let zero = ahead_const(func, inst, Imm::int(0, ty), ty);
809 compared(func, inst, IntPred::Ne, high, zero)
810 };
811 (value, bit)
812 }
813 };
814 let mut answers = func[inst].results();
815 if let (Some(wrapped), Some(flag)) = (answers.next(), answers.next()) {
816 forward.insert(wrapped, value);
817 forward.insert(flag, bit);
818 }
819 func.remove_inst(inst);
820}
821
822fn high_half(func: &mut Func, inst: Inst, a: Value, b: Value, signed: bool, ty: Type) -> Value {
839 let width = ty.bits();
840 let half = width / 2;
841 let shift = ahead_const(func, inst, Imm::int(i128::from(half), ty), ty);
842 let mask = ahead_const(func, inst, Imm::int((1i128 << half) - 1, ty), ty);
843
844 let al = ahead(func, inst, Opcode::And, &[a, mask], ty);
845 let ah = ahead(func, inst, Opcode::LShr, &[a, shift], ty);
846 let bl = ahead(func, inst, Opcode::And, &[b, mask], ty);
847 let bh = ahead(func, inst, Opcode::LShr, &[b, shift], ty);
848
849 let ll = ahead(func, inst, Opcode::Mul, &[al, bl], ty);
850 let lh = ahead(func, inst, Opcode::Mul, &[al, bh], ty);
851 let hl = ahead(func, inst, Opcode::Mul, &[ah, bl], ty);
852 let hh = ahead(func, inst, Opcode::Mul, &[ah, bh], ty);
853
854 let over = ahead(func, inst, Opcode::LShr, &[ll, shift], ty);
857 let lh_low = ahead(func, inst, Opcode::And, &[lh, mask], ty);
858 let hl_low = ahead(func, inst, Opcode::And, &[hl, mask], ty);
859 let some = ahead(func, inst, Opcode::Add, &[over, lh_low], ty);
860 let carry = ahead(func, inst, Opcode::Add, &[some, hl_low], ty);
861
862 let lh_high = ahead(func, inst, Opcode::LShr, &[lh, shift], ty);
863 let hl_high = ahead(func, inst, Opcode::LShr, &[hl, shift], ty);
864 let up = ahead(func, inst, Opcode::LShr, &[carry, shift], ty);
865 let first = ahead(func, inst, Opcode::Add, &[hh, lh_high], ty);
866 let second = ahead(func, inst, Opcode::Add, &[first, hl_high], ty);
867 let high = ahead(func, inst, Opcode::Add, &[second, up], ty);
868 if !signed {
869 return high;
870 }
871 let top = ahead_const(func, inst, Imm::int(i128::from(width - 1), ty), ty);
872 let a_sign = ahead(func, inst, Opcode::AShr, &[a, top], ty);
873 let b_sign = ahead(func, inst, Opcode::AShr, &[b, top], ty);
874 let a_owes = ahead(func, inst, Opcode::And, &[a_sign, b], ty);
875 let b_owes = ahead(func, inst, Opcode::And, &[b_sign, a], ty);
876 let once = ahead(func, inst, Opcode::Sub, &[high, a_owes], ty);
877 ahead(func, inst, Opcode::Sub, &[once, b_owes], ty)
878}
879
880fn negative(func: &mut Func, inst: Inst, value: Value, ty: Type) -> Value {
882 let zero = ahead_const(func, inst, Imm::int(0, ty), ty);
883 compared(func, inst, IntPred::Slt, value, zero)
884}
885
886fn compared(func: &mut Func, inst: Inst, pred: IntPred, lhs: Value, rhs: Value) -> Value {
889 let ty = func[lhs].ty.with_lane(Type::I1);
890 let args = func.push_values(&[lhs, rhs]);
891 let extra = Extra::IntPred(pred);
892 written(func, inst, InstData { args, extra, ..InstData::new(Opcode::ICmp) }, ty)
893}
894
895fn substitute(func: &mut Func, forward: &HashMap<Value, Value>) {
902 let with = |value: Value| forward.get(&value).copied().unwrap_or(value);
903 for block in func.blocks().collect::<Vec<_>>() {
904 for inst in func.insts(block).collect::<Vec<Inst>>() {
905 let args = func[inst].args;
906 func.rewrite(args, with);
907 for call in func.successors(inst).collect::<Vec<_>>() {
908 func.rewrite(call.args, with);
909 }
910 }
911 }
912}
913
914fn countable(ty: Type) -> bool {
924 ty.is_int()
925 && ty.is_scalar()
926 && ty.bits() >= 8
927 && ty.bits() <= 64
928 && ty.bits().is_power_of_two()
929}
930
931pub const UNROLL: usize = 32;
944
945pub fn bulk(func: &mut Func, names: &mut Interner, word: u32) {
956 let found: Vec<Inst> =
957 func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
958 for inst in found {
959 match func[inst].opcode {
960 Opcode::Memcpy => copy(func, names, inst, word),
961 Opcode::Memset => fill(func, names, inst, word),
962 Opcode::Memmove => library(func, names, inst, "memmove", word),
963 _ => {}
964 }
965 }
966}
967
968fn copy(func: &mut Func, names: &mut Interner, inst: Inst, word: u32) {
976 let [into, from] = func[func[inst].args] else { return };
977 let Extra::Mem(mem) = func[inst].extra else { return };
978 let info = func[mem];
979 let Some(plan) = chunks(info, word) else { return library(func, names, inst, "memcpy", word) };
980 for (at, width) in plan {
981 let ty = Type::int(width * 8);
982 let access = MemInfo { size: u64::from(width), align: width.min(info.align), ..info };
983 let there = stepped(func, inst, from, at);
984 let word = read(func, inst, there, access, ty);
985 let here = stepped(func, inst, into, at);
986 write(func, inst, word, here, access);
987 }
988 func.remove_inst(inst);
989}
990
991fn fill(func: &mut Func, names: &mut Interner, inst: Inst, word: u32) {
998 let [into, byte] = func[func[inst].args] else { return };
999 let Extra::Mem(mem) = func[inst].extra else { return };
1000 let info = func[mem];
1001 let Some(spelled) = literal(func, byte) else {
1002 return library(func, names, inst, "memset", word);
1003 };
1004 let Some(plan) = chunks(info, word) else { return library(func, names, inst, "memset", word) };
1005 for (at, width) in plan {
1006 let ty = Type::int(width * 8);
1007 let access = MemInfo { size: u64::from(width), align: width.min(info.align), ..info };
1008 let value = ahead_const(func, inst, Imm::int(spread(spelled, width) as i128, ty), ty);
1009 let here = stepped(func, inst, into, at);
1010 write(func, inst, value, here, access);
1011 }
1012 func.remove_inst(inst);
1013}
1014
1015fn library(func: &mut Func, names: &mut Interner, inst: Inst, routine: &str, word: u32) {
1028 let [into, second] = func[func[inst].args] else { return };
1029 let Extra::Mem(mem) = func[inst].extra else { return };
1030 let size = func[mem].size;
1031
1032 let words = Type::int(word * 8);
1036 let count = ahead_const(func, inst, Imm::int(i128::from(size), words), words);
1037 let second = match routine {
1040 "memset" => widened(func, inst, second),
1041 _ => second,
1042 };
1043
1044 let sig = func.add_signature(Signature::new().with_params(&[
1045 Type::PTR,
1046 if routine == "memset" { Type::int(32) } else { Type::PTR },
1047 words,
1048 ]));
1049 let callee = names.intern(routine);
1050 let varargs = func.push_abis(&[]);
1051 let info = func.add_call(CallInfo { callee: Some(callee), signature: sig, varargs });
1052 let args = func.push_values(&[into, second, count]);
1053 let data = &mut func[inst];
1054 data.opcode = Opcode::Call;
1055 data.args = args;
1056 data.extra = Extra::Call(info);
1057 data.flags = data.flags.intersection(Flags::legal_on(Opcode::Call));
1058}
1059
1060fn widened(func: &mut Func, inst: Inst, value: Value) -> Value {
1062 let int = Type::int(32);
1063 let ty = func[value].ty;
1064 if ty == int {
1065 return value;
1066 }
1067 ahead(func, inst, Opcode::ZExt, &[value], int)
1068}
1069
1070fn chunks(info: MemInfo, word: u32) -> Option<Vec<(u64, u32)>> {
1083 plan(info.size, info.align, word)
1084}
1085
1086pub(crate) fn plan(size: u64, align: u32, word: u32) -> Option<Vec<(u64, u32)>> {
1094 let widest = word.min(align).max(1);
1095 if !widest.is_power_of_two() {
1096 return None;
1097 }
1098 let mut plan = Vec::new();
1099 let mut at = 0;
1100 let mut width = u64::from(widest);
1101 while at < size {
1102 while width > size - at {
1103 width /= 2;
1104 }
1105 plan.push((at, u32::try_from(width).ok()?));
1106 at += width;
1107 if plan.len() > UNROLL {
1108 return None;
1109 }
1110 }
1111 Some(plan)
1112}
1113
1114fn literal(func: &Func, value: Value) -> Option<u8> {
1116 let Def::Result { inst, .. } = func[value].def else { return None };
1117 if func[inst].opcode != Opcode::IConst {
1118 return None;
1119 }
1120 let Extra::Imm(imm) = func[inst].extra else { return None };
1121 u8::try_from(func[imm].bits() & 0xff).ok()
1122}
1123
1124fn spread(byte: u8, width: u32) -> u64 {
1126 (0..width).fold(0, |word, at| word | u64::from(byte) << (at * 8))
1127}
1128
1129fn stepped(func: &mut Func, inst: Inst, block: Value, at: u64) -> Value {
1132 if at == 0 {
1133 return block;
1134 }
1135 let step = ahead_const(func, inst, Imm::int(i128::from(at), Type::int(64)), Type::int(64));
1136 ahead(func, inst, Opcode::PtrAdd, &[block, step], Type::PTR)
1137}
1138
1139fn read(func: &mut Func, inst: Inst, from: Value, info: MemInfo, ty: Type) -> Value {
1141 let extra = Extra::Mem(func.add_mem(info));
1142 let args = func.push_values(&[from]);
1143 written(func, inst, InstData { args, extra, ..InstData::new(Opcode::Load) }, ty)
1144}
1145
1146fn write(func: &mut Func, inst: Inst, value: Value, into: Value, info: MemInfo) {
1148 let span = func.span(inst);
1149 let extra = Extra::Mem(func.add_mem(info));
1150 let args = func.push_values(&[value, into]);
1151 let data = InstData { args, extra, ..InstData::new(Opcode::Store) };
1152 let made = func.create_inst(data, &[], span);
1153 func.insert_before(made, inst);
1154}
1155
1156fn holder(bits: u32, signed: bool) -> Option<u32> {
1165 match if signed { bits } else { bits + 1 } {
1166 ..=32 => Some(32),
1167 33..=64 => Some(64),
1168 _ => None,
1169 }
1170}
1171
1172fn produced(func: &Func, inst: Inst) -> Type {
1177 func[inst].first_result.map_or(Type::VOID, |value| func[value].ty)
1178}
1179
1180fn ahead(func: &mut Func, inst: Inst, opcode: Opcode, args: &[Value], ty: Type) -> Value {
1182 let args = func.push_values(args);
1183 written(func, inst, InstData { args, ..InstData::new(opcode) }, ty)
1184}
1185
1186fn ahead_cmp(func: &mut Func, inst: Inst, opcode: Opcode, extra: Extra, args: &[Value]) -> Value {
1188 let args = func.push_values(args);
1189 written(func, inst, InstData { args, extra, ..InstData::new(opcode) }, Type::I1)
1190}
1191
1192fn ahead_const(func: &mut Func, inst: Inst, imm: Imm, ty: Type) -> Value {
1194 let extra = Extra::Imm(func.add_imm(imm));
1195 written(func, inst, InstData { extra, ..InstData::new(Opcode::IConst) }, ty)
1196}
1197
1198fn written(func: &mut Func, inst: Inst, data: InstData, ty: Type) -> Value {
1200 let span = func.span(inst);
1201 let made = func.create_inst(data, &[ty], span);
1202 func.insert_before(made, inst);
1203 func[made].first_result.expect("an instruction created with one result has one")
1204}
1205
1206fn becomes(func: &mut Func, inst: Inst, opcode: Opcode, args: &[Value]) {
1213 let args = func.push_values(args);
1214 let data = &mut func[inst];
1215 data.opcode = opcode;
1216 data.args = args;
1217 data.extra = Extra::None;
1218 data.flags = data.flags.intersection(Flags::legal_on(opcode));
1221}
1222
1223#[must_use]
1228pub fn blocks_for(cases: usize) -> usize {
1229 cases.saturating_sub(1)
1230}
1231
1232#[cfg(test)]
1233mod tests {
1234 use rucc_base::Interner;
1235 use rucc_ir::{Builder, Flags, Float, Func, Module, Opcode, Signature, Type};
1236 use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
1237
1238 use rucc_ir::{Extra, InstData, MemInfo, MemOrder, Restrict};
1239
1240 use super::{
1241 UNROLL, alternating, blocks_for, bulk, bytes, chunks, counts, every, floats, orderings,
1242 overflows, spread, switches,
1243 };
1244
1245 fn target() -> TargetInfo {
1246 TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu))
1247 }
1248
1249 fn built(cases: &[i128]) -> (Interner, Func) {
1252 let mut names = Interner::new();
1253 let int = Type::int(32);
1254 let mut func = Func::new(
1255 names.intern("sw"),
1256 Signature::new().with_params(&[int]).with_returns(&[int]),
1257 );
1258 let entry = func.create_block();
1259 let x = func.append_param(entry, int);
1260
1261 let default = func.create_block();
1262 let arms: Vec<_> = cases.iter().map(|_| func.create_block()).collect();
1263 let table: Vec<(i128, rucc_ir::Block)> =
1264 cases.iter().copied().zip(arms.iter().copied()).collect();
1265 Builder::new(&mut func, entry).switch(x, default, &table);
1266
1267 for (index, &arm) in arms.iter().enumerate() {
1268 let mut build = Builder::new(&mut func, arm);
1269 let what = i128::try_from(index).expect("a small number of cases");
1270 let v = build.iconst(int, (what + 1) * 10);
1271 build.ret(&[v]);
1272 }
1273 let mut build = Builder::new(&mut func, default);
1274 let v = build.iconst(int, 30);
1275 build.ret(&[v]);
1276 (names, func)
1277 }
1278
1279 fn count(func: &Func) -> usize {
1280 func.blocks().count()
1281 }
1282
1283 fn printed(func: &Func, names: &mut Interner) -> String {
1284 let module = Module::new(names.intern("sw.c"), &target());
1285 rucc_ir::print_func(&module, func, names)
1286 }
1287
1288 #[test]
1289 fn a_switch_becomes_a_compare_and_a_branch_for_each_case() {
1290 let (mut names, mut func) = built(&[1, 2]);
1291 let before = count(&func);
1292 switches(&mut func);
1293 assert_eq!(count(&func), before + blocks_for(2));
1294
1295 let text = printed(&func, &mut names);
1296 assert!(!text.contains("switch"), "the switch is gone: {text}");
1297 assert_eq!(text.matches("icmp eq").count(), 2, "one compare per case: {text}");
1298 assert_eq!(text.matches("br_if").count(), 2, "one branch per case: {text}");
1299 }
1300
1301 #[test]
1302 fn the_last_case_falls_to_the_default_rather_than_to_a_block_of_its_own() {
1303 let (_, mut func) = built(&[7]);
1304 let before = count(&func);
1305 switches(&mut func);
1306 assert_eq!(count(&func), before);
1308 assert_eq!(blocks_for(1), 0);
1309 }
1310
1311 #[test]
1312 fn a_switch_with_only_a_default_is_a_jump() {
1313 let (_, mut func) = built(&[]);
1314 switches(&mut func);
1315 let entry = func.entry().expect("an entry block");
1316 let term = func.terminator(entry).expect("a terminator");
1317 assert_eq!(func[term].opcode, Opcode::Jump);
1318 }
1319
1320 #[test]
1323 fn what_comes_out_is_valid_ir() {
1324 let (mut names, mut func) = built(&[1, 2, 3, 4]);
1325 switches(&mut func);
1326 let module = Module::new(names.intern("sw.c"), &target());
1327 rucc_ir::verify_func(&module, &func, &names).expect("the rewrite builds valid IR");
1328 }
1329
1330 #[test]
1333 fn a_function_with_no_switch_is_left_exactly_as_it_was() {
1334 let mut names = Interner::new();
1335 let int = Type::int(32);
1336 let mut func =
1337 Func::new(names.intern("f"), Signature::new().with_params(&[int]).with_returns(&[int]));
1338 let entry = func.create_block();
1339 let x = func.append_param(entry, int);
1340 Builder::new(&mut func, entry).ret(&[x]);
1341
1342 let before = printed(&func, &mut names);
1343 switches(&mut func);
1344 assert_eq!(printed(&func, &mut names), before);
1345 }
1346
1347 fn one(
1352 params: &[Type],
1353 returns: &[Type],
1354 body: impl FnOnce(&mut Builder<'_>, &[rucc_ir::Value]),
1355 ) -> (Interner, Func) {
1356 let mut names = Interner::new();
1357 let mut func = Func::new(
1358 names.intern("f"),
1359 Signature::new().with_params(params).with_returns(returns),
1360 );
1361 let entry = func.create_block();
1362 let args: Vec<_> = params.iter().map(|&ty| func.append_param(entry, ty)).collect();
1363 let mut build = Builder::new(&mut func, entry);
1364 body(&mut build, &args);
1365 (names, func)
1366 }
1367
1368 fn f64() -> Type {
1369 Type::float(Float::F64)
1370 }
1371
1372 fn f32() -> Type {
1373 Type::float(Float::F32)
1374 }
1375
1376 fn valid(func: &Func, names: &mut Interner) {
1378 let module = Module::new(names.intern("f.c"), &target());
1379 rucc_ir::verify_func(&module, func, names).expect("the rewrite builds valid IR");
1380 }
1381
1382 #[test]
1384 fn a_float_constant_becomes_the_integer_that_spells_it_and_a_reading_of_those_bits() {
1385 let (mut names, mut func) = one(&[], &[f64()], |build, _| {
1386 let k = build.fconst(f64(), 0x3ff8_0000_0000_0000);
1387 build.ret(&[k]);
1388 });
1389 floats(&mut func);
1390
1391 let text = printed(&func, &mut names);
1392 assert!(!text.contains("fconst"), "the float constant is gone: {text}");
1393 assert!(text.contains("iconst.i64 4609434218613702656"), "the bits, as an integer: {text}");
1394 assert!(text.contains("bitcast"), "read back as the float: {text}");
1395 }
1396
1397 #[test]
1400 fn a_constant_at_the_narrow_format_is_an_integer_of_the_narrow_width() {
1401 let (mut names, mut func) = one(&[], &[f32()], |build, _| {
1402 let k = build.fconst(f32(), 0x4020_0000);
1403 build.ret(&[k]);
1404 });
1405 floats(&mut func);
1406 assert!(printed(&func, &mut names).contains("iconst.i32"), "an i32, not an i64");
1407 }
1408
1409 #[test]
1412 fn a_negation_flips_the_sign_bit_and_touches_no_other() {
1413 let (mut names, mut func) = one(&[f64()], &[f64()], |build, args| {
1414 let n = build.unary(Opcode::FNeg, args[0], f64());
1415 build.ret(&[n]);
1416 });
1417 floats(&mut func);
1418
1419 let text = printed(&func, &mut names);
1420 assert!(!text.contains("fneg"), "the negation is gone: {text}");
1421 assert!(!text.contains("fsub"), "and it did not become a subtraction: {text}");
1422 assert!(text.contains("iconst.i64 -9223372036854775808"), "the sign bit alone: {text}");
1423 assert_eq!(text.matches("xor").count(), 1, "one exclusive or: {text}");
1424 assert_eq!(text.matches("bitcast").count(), 2, "there and back: {text}");
1425 }
1426
1427 #[test]
1429 fn an_unsigned_integer_becoming_a_float_widens_first_and_then_converts_as_signed() {
1430 let (mut names, mut func) = one(&[Type::int(32)], &[f64()], |build, args| {
1431 let d = build.unary(Opcode::UIToFP, args[0], f64());
1432 build.ret(&[d]);
1433 });
1434 floats(&mut func);
1435
1436 let text = printed(&func, &mut names);
1437 assert!(!text.contains("uitofp"), "the unsigned conversion is gone: {text}");
1438 assert!(text.contains("zext.i64"), "widened with zeroes: {text}");
1439 assert!(text.contains("sitofp.f64"), "converted as signed: {text}");
1440 }
1441
1442 #[test]
1444 fn a_float_becoming_an_unsigned_integer_converts_as_signed_first_and_then_narrows() {
1445 let (mut names, mut func) = one(&[f64()], &[Type::int(32)], |build, args| {
1446 let n = build.unary(Opcode::FPToUI, args[0], Type::int(32));
1447 build.ret(&[n]);
1448 });
1449 floats(&mut func);
1450
1451 let text = printed(&func, &mut names);
1452 assert!(!text.contains("fptoui"), "the unsigned conversion is gone: {text}");
1453 assert!(text.contains("fptosi.i64"), "converted as signed: {text}");
1454 assert!(text.contains("trunc.i32"), "and narrowed to what was asked: {text}");
1455 }
1456
1457 #[test]
1460 fn a_conversion_narrower_than_the_machine_has_is_one_it_has_and_a_narrowing() {
1461 let (mut names, mut func) = one(&[f64()], &[Type::int(8)], |build, args| {
1462 let n = build.unary(Opcode::FPToSI, args[0], Type::int(8));
1463 build.ret(&[n]);
1464 });
1465 floats(&mut func);
1466
1467 let text = printed(&func, &mut names);
1468 assert!(text.contains("fptosi.i32"), "converted at a width there is one at: {text}");
1469 assert!(text.contains("trunc.i8"), "and narrowed to what was asked: {text}");
1470 }
1471
1472 #[test]
1474 fn a_signed_integer_narrower_than_the_machine_converts_from_is_widened_with_its_sign() {
1475 let (mut names, mut func) = one(&[Type::int(8)], &[f64()], |build, args| {
1476 let d = build.unary(Opcode::SIToFP, args[0], f64());
1477 build.ret(&[d]);
1478 });
1479 floats(&mut func);
1480
1481 let text = printed(&func, &mut names);
1482 assert!(text.contains("sext.i32"), "widened with the sign and not with zeroes: {text}");
1483 assert!(!text.contains("zext"), "widened with the sign and not with zeroes: {text}");
1484 assert!(text.contains("sitofp.f64"), "converted at a width there is one at: {text}");
1485 }
1486
1487 #[test]
1489 fn the_width_a_conversion_happens_at_is_the_narrowest_one_that_holds_the_values() {
1490 use super::holder;
1491 for bits in [1, 8, 16, 32] {
1492 assert_eq!(holder(bits, true), Some(32), "a signed {bits} bit value fits in an int");
1493 }
1494 assert_eq!(holder(64, true), Some(64));
1495 for bits in [1, 8, 16, 31] {
1496 assert_eq!(holder(bits, false), Some(32), "an unsigned {bits} bit value does too");
1497 }
1498 assert_eq!(holder(32, false), Some(64));
1500 assert_eq!(holder(64, false), None);
1501 }
1502
1503 #[test]
1507 fn the_unsigned_conversions_at_the_widest_width_become_the_signed_one_and_a_correction() {
1508 for float in [f32(), f64()] {
1509 let (mut names, mut func) = one(&[Type::int(64)], &[float], |build, args| {
1510 let d = build.unary(Opcode::UIToFP, args[0], float);
1511 build.ret(&[d]);
1512 });
1513 floats(&mut func);
1514 let text = printed(&func, &mut names);
1515 assert!(!text.contains("uitofp"), "the unsigned conversion is gone: {text}");
1516 assert!(text.contains("sitofp"), "the signed one is what is left: {text}");
1517 assert!(text.contains("lshr"), "the value is halved: {text}");
1520 assert!(text.contains("fadd"), "and doubled again afterwards: {text}");
1521 valid(&func, &mut names);
1522 }
1523
1524 for float in [f32(), f64()] {
1525 let (mut names, mut func) = one(&[float], &[Type::int(64)], |build, args| {
1526 let n = build.unary(Opcode::FPToUI, args[0], Type::int(64));
1527 build.ret(&[n]);
1528 });
1529 floats(&mut func);
1530 let text = printed(&func, &mut names);
1531 assert!(!text.contains("fptoui"), "the unsigned conversion is gone: {text}");
1532 assert!(text.contains("fptosi"), "the signed one is what is left: {text}");
1533 assert!(text.contains("fsub"), "the value is brought down: {text}");
1535 assert!(text.contains("shl"), "and the top bit goes back on: {text}");
1536 valid(&func, &mut names);
1537 }
1538 }
1539
1540 #[test]
1544 fn the_widest_unsigned_conversions_are_written_without_a_branch() {
1545 let (_, mut func) = one(&[Type::int(64)], &[f64()], |build, args| {
1546 let d = build.unary(Opcode::UIToFP, args[0], f64());
1547 build.ret(&[d]);
1548 });
1549 floats(&mut func);
1550 assert_eq!(func.blocks().count(), 1, "the conversion did not split the block");
1551
1552 let (_, mut func) = one(&[f64()], &[Type::int(64)], |build, args| {
1553 let n = build.unary(Opcode::FPToUI, args[0], Type::int(64));
1554 build.ret(&[n]);
1555 });
1556 floats(&mut func);
1557 assert_eq!(func.blocks().count(), 1, "nor did the other one");
1558 }
1559
1560 #[test]
1568 fn the_arithmetic_the_widest_unsigned_conversions_do_is_the_conversion() {
1569 const CASES: &[u64] = &[
1570 0,
1571 1,
1572 2,
1573 0x7FFF_FFFF,
1574 0x8000_0000,
1575 0xFFFF_FFFF,
1576 0x0020_0000_0000_0000,
1577 0x0020_0000_0000_0001,
1578 0x7FFF_FFFF_FFFF_FFFF,
1579 0x8000_0000_0000_0000,
1580 0x8000_0000_0000_0001,
1581 0x8000_0000_0000_0400,
1582 0xFFFF_FFFF_FFFF_F800,
1583 0xFFFF_FFFF_FFFF_FFFF,
1584 ];
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]
1613 fn what_the_float_rewrites_leave_is_valid_ir() {
1614 let (mut names, mut func) = one(&[Type::int(32)], &[f64()], |build, args| {
1615 let k = build.fconst(f64(), 0x3ff8_0000_0000_0000);
1616 let d = build.unary(Opcode::UIToFP, args[0], f64());
1617 let n = build.unary(Opcode::FNeg, d, f64());
1618 let s = build.binary(Opcode::FAdd, n, k, Flags::NONE);
1619 build.ret(&[s]);
1620 });
1621 floats(&mut func);
1622 let module = Module::new(names.intern("f.c"), &target());
1623 rucc_ir::verify_func(&module, &func, &names).expect("the rewrite builds valid IR");
1624 }
1625
1626 #[test]
1629 fn a_function_with_no_floats_in_it_is_left_exactly_as_it_was() {
1630 let (mut names, mut func) = one(&[Type::int(32)], &[Type::int(32)], |build, args| {
1631 build.ret(&[args[0]]);
1632 });
1633 let before = printed(&func, &mut names);
1634 floats(&mut func);
1635 assert_eq!(printed(&func, &mut names), before);
1636 }
1637 fn access(size: u64, align: u32) -> MemInfo {
1638 MemInfo { size, align, order: MemOrder::NotAtomic, tbaa: None, restrict: Restrict::NONE }
1639 }
1640
1641 fn moving(opcode: Opcode, size: u64, align: u32, byte: Option<i128>) -> (Interner, Func) {
1644 one(&[Type::PTR, Type::PTR], &[], |build, args| {
1645 let second = match byte {
1646 Some(value) => build.iconst(Type::int(8), value),
1647 None => args[1],
1648 };
1649 let mem = build.func().add_mem(access(size, align));
1650 let operands = build.func().push_values(&[args[0], second]);
1651 let data = InstData { args: operands, extra: Extra::Mem(mem), ..InstData::new(opcode) };
1652 build.inst(data, &[]);
1653 build.ret(&[]);
1654 })
1655 }
1656
1657 fn copying(size: u64, align: u32) -> (Interner, Func) {
1658 moving(Opcode::Memcpy, size, align, None)
1659 }
1660
1661 fn filling(size: u64, align: u32, byte: i128) -> (Interner, Func) {
1662 moving(Opcode::Memset, size, align, Some(byte))
1663 }
1664
1665 fn widths(size: u64, align: u32) -> Option<Vec<u32>> {
1668 Some(chunks(access(size, align), 8)?.into_iter().map(|(_, width)| width).collect())
1669 }
1670
1671 #[test]
1673 fn a_copy_becomes_a_load_and_a_store_for_each_word_of_it() {
1674 let (mut names, mut func) = copying(16, 8);
1675 bulk(&mut func, &mut names, 8);
1676
1677 let text = printed(&func, &mut names);
1678 assert!(!text.contains("memcpy"), "the copy is gone: {text}");
1679 assert_eq!(text.matches("load.i64").count(), 2, "a load per word: {text}");
1680 assert_eq!(text.matches("store").count(), 2, "a store per word: {text}");
1681 assert_eq!(
1682 text.matches("ptr_add").count(),
1683 2,
1684 "no offset for the word at the front: {text}"
1685 );
1686 }
1687
1688 #[test]
1692 fn a_word_is_as_wide_as_the_block_is_aligned_to() {
1693 assert_eq!(widths(16, 8), Some(vec![8, 8]));
1694 assert_eq!(widths(16, 4), Some(vec![4, 4, 4, 4]));
1695 assert_eq!(widths(4, 1), Some(vec![1, 1, 1, 1]));
1696 }
1697
1698 #[test]
1701 fn what_is_left_over_is_narrower_words_and_not_a_run_of_bytes() {
1702 assert_eq!(widths(13, 8), Some(vec![8, 4, 1]));
1703 assert_eq!(widths(3, 8), Some(vec![2, 1]));
1704 assert_eq!(widths(1, 8), Some(vec![1]));
1705 }
1706
1707 #[test]
1710 fn every_word_starts_somewhere_it_is_aligned_for() {
1711 for (at, width) in chunks(access(13, 8), 8).expect("a plan for thirteen bytes") {
1712 assert_eq!(at % u64::from(width), 0, "{at} is a multiple of {width}");
1713 }
1714 }
1715
1716 #[test]
1718 fn a_fill_is_the_byte_spread_across_each_word() {
1719 let (mut names, mut func) = filling(16, 8, 0);
1720 bulk(&mut func, &mut names, 8);
1721
1722 let text = printed(&func, &mut names);
1723 assert!(!text.contains("memset"), "the fill is gone: {text}");
1724 assert_eq!(text.matches("store").count(), 2, "a store per word: {text}");
1725 assert!(!text.contains("load"), "a fill reads nothing: {text}");
1726 }
1727
1728 #[test]
1731 fn the_byte_is_repeated_across_the_word_it_is_stored_as() {
1732 assert_eq!(spread(0, 8), 0);
1733 assert_eq!(spread(0xff, 1), 0xff);
1734 assert_eq!(spread(0xff, 4), 0xffff_ffff);
1735 assert_eq!(spread(0xab, 2), 0xabab);
1736 assert_eq!(spread(0xab, 8), 0xabab_abab_abab_abab);
1737 }
1738
1739 #[test]
1741 fn a_copy_too_large_to_unroll_becomes_a_call_to_the_runtime() {
1742 let size = u64::try_from(UNROLL).expect("a small threshold") + 1;
1743 let (mut names, mut func) = copying(size, 1);
1744 bulk(&mut func, &mut names, 8);
1745 let text = printed(&func, &mut names);
1746 assert!(text.contains("call @memcpy"), "a call and not a bulk move: {text}");
1747
1748 let (mut names, mut func) = copying(size - 1, 1);
1751 bulk(&mut func, &mut names, 8);
1752 assert!(!printed(&func, &mut names).contains("memcpy"), "one word under it is unrolled");
1753 }
1754
1755 #[test]
1758 fn the_call_passes_the_size_that_the_instruction_carried_beside_it() {
1759 let size = u64::try_from(UNROLL).expect("a small threshold") + 1;
1760 let (mut names, mut func) = copying(size, 1);
1761 bulk(&mut func, &mut names, 8);
1762 let text = printed(&func, &mut names);
1763 assert!(text.contains(&format!("{size}")), "the size is an argument now: {text}");
1764 }
1765
1766 #[test]
1769 fn a_move_is_a_call_however_small_it_is() {
1770 let (mut names, mut func) = moving(Opcode::Memmove, 8, 8, None);
1771 bulk(&mut func, &mut names, 8);
1772 let text = printed(&func, &mut names);
1773 assert!(text.contains("call @memmove"), "a call and not a run of moves: {text}");
1774 }
1775
1776 #[test]
1779 fn a_fill_whose_byte_is_not_a_constant_becomes_a_call() {
1780 let (mut names, mut func) = one(&[Type::PTR, Type::int(8)], &[], |build, args| {
1781 let mem = build.func().add_mem(access(8, 8));
1782 let operands = build.func().push_values(&[args[0], args[1]]);
1783 let data = InstData {
1784 args: operands,
1785 extra: Extra::Mem(mem),
1786 ..InstData::new(Opcode::Memset)
1787 };
1788 build.inst(data, &[]);
1789 build.ret(&[]);
1790 });
1791 bulk(&mut func, &mut names, 8);
1792 let text = printed(&func, &mut names);
1793 assert!(text.contains("call @memset"), "a call and not a run of stores: {text}");
1794 assert!(text.contains("zext.i32"), "the byte is widened to what C passes: {text}");
1796 }
1797
1798 #[test]
1801 fn no_word_is_wider_than_the_machine_moves_at_once() {
1802 assert_eq!(chunks(access(8, 8), 4).map(|plan| plan.len()), Some(2));
1803 assert_eq!(chunks(access(8, 8), 8).map(|plan| plan.len()), Some(1));
1804 }
1805
1806 #[test]
1807 fn what_a_copy_becomes_is_ir_that_verifies() {
1808 let (mut names, mut func) = copying(13, 8);
1809 bulk(&mut func, &mut names, 8);
1810 let module = Module::new(names.intern("c.c"), &target());
1811 rucc_ir::verify_func(&module, &func, &names).expect("the rewrite builds valid IR");
1812 }
1813
1814 #[test]
1815 fn what_a_fill_becomes_is_ir_that_verifies() {
1816 let (mut names, mut func) = filling(13, 8, 0xff);
1817 bulk(&mut func, &mut names, 8);
1818 let module = Module::new(names.intern("f.c"), &target());
1819 rucc_ir::verify_func(&module, &func, &names).expect("the rewrite builds valid IR");
1820 }
1821
1822 #[test]
1823 fn what_a_copy_too_large_to_unroll_becomes_is_ir_that_verifies() {
1824 let size = u64::try_from(UNROLL).expect("a small threshold") + 1;
1825 let (mut names, mut func) = copying(size, 1);
1826 bulk(&mut func, &mut names, 8);
1827 let module = Module::new(names.intern("c.c"), &target());
1828 rucc_ir::verify_func(&module, &func, &names).expect("the call is valid IR");
1829 }
1830
1831 #[test]
1833 fn a_function_with_no_bulk_move_in_it_is_left_exactly_as_it_was() {
1834 let (mut names, mut func) = one(&[Type::int(32)], &[Type::int(32)], |build, args| {
1835 build.ret(&[args[0]]);
1836 });
1837 let before = printed(&func, &mut names);
1838 bulk(&mut func, &mut names, 8);
1839 assert_eq!(printed(&func, &mut names), before);
1840 }
1841
1842 fn swapping(width: u32) -> (Interner, Func) {
1845 let ty = Type::int(width);
1846 one(&[ty], &[ty], |build, args| {
1847 let s = build.unary(Opcode::Bswap, args[0], ty);
1848 build.ret(&[s]);
1849 })
1850 }
1851
1852 #[test]
1859 fn the_masks_are_the_alternating_runs_of_the_group_being_swapped() {
1860 assert_eq!(alternating(32, 16), 0x0000_ffff);
1861 assert_eq!(alternating(32, 8), 0x00ff_00ff);
1862 assert_eq!(alternating(16, 8), 0x00ff);
1863 assert_eq!(alternating(64, 32), 0x0000_0000_ffff_ffff);
1864 assert_eq!(alternating(64, 16), 0x0000_ffff_0000_ffff);
1865 assert_eq!(alternating(64, 8), 0x00ff_00ff_00ff_00ff);
1866 }
1867
1868 #[test]
1870 fn a_two_byte_swap_is_one_exchange_of_neighbouring_bytes() {
1871 let (mut names, mut func) = swapping(16);
1872 bytes(&mut func);
1873
1874 let text = printed(&func, &mut names);
1875 assert!(!text.contains("bswap"), "the instruction is gone: {text}");
1876 assert!(text.contains("iconst.i16 255"), "the low byte of the pair: {text}");
1877 assert_eq!(text.matches("shl").count(), 1, "one shift up: {text}");
1878 assert_eq!(text.matches("lshr").count(), 1, "one shift down: {text}");
1879 assert_eq!(text.matches(" or ").count(), 1, "and the two put together: {text}");
1880 }
1881
1882 #[test]
1885 fn a_wider_swap_is_the_same_exchange_once_per_halving() {
1886 for (width, steps) in [(16u32, 1usize), (32, 2), (64, 3)] {
1887 let (mut names, mut func) = swapping(width);
1888 bytes(&mut func);
1889 let text = printed(&func, &mut names);
1890 assert_eq!(text.matches("shl").count(), steps, "at {width}: {text}");
1891 assert_eq!(text.matches("lshr").count(), steps, "at {width}: {text}");
1892 assert_eq!(text.matches(" and ").count(), steps * 2, "at {width}: {text}");
1893 assert_eq!(text.matches(" or ").count(), steps, "at {width}: {text}");
1894 }
1895 }
1896
1897 #[test]
1900 fn the_shift_counts_are_the_group_width_halving_as_it_goes() {
1901 let (mut names, mut func) = swapping(64);
1902 bytes(&mut func);
1903 let text = printed(&func, &mut names);
1904 for count in ["iconst.i64 32", "iconst.i64 16", "iconst.i64 8"] {
1905 assert!(text.contains(count), "{count} is a step: {text}");
1906 }
1907 }
1908
1909 #[test]
1912 fn what_a_byte_swap_becomes_is_ir_that_verifies() {
1913 let (mut names, mut func) = swapping(32);
1914 bytes(&mut func);
1915 let module = Module::new(names.intern("b.c"), &target());
1916 rucc_ir::verify_func(&module, &func, &names).expect("the rewrite builds valid IR");
1917 }
1918
1919 #[test]
1922 fn a_function_with_no_byte_swap_in_it_is_left_exactly_as_it_was() {
1923 let (mut names, mut func) = one(&[Type::int(32)], &[Type::int(32)], |build, args| {
1924 build.ret(&[args[0]]);
1925 });
1926 let before = printed(&func, &mut names);
1927 bytes(&mut func);
1928 assert_eq!(printed(&func, &mut names), before);
1929 }
1930
1931 fn counting(op: Opcode, width: u32) -> (Interner, Func) {
1933 let ty = Type::int(width);
1934 one(&[ty], &[ty], |build, args| {
1935 let c = build.unary(op, args[0], ty);
1936 build.ret(&[c]);
1937 })
1938 }
1939
1940 #[test]
1943 fn the_counting_masks_are_the_ones_the_halving_sum_is_written_with() {
1944 assert_eq!(alternating(32, 1), 0x5555_5555);
1945 assert_eq!(alternating(32, 2), 0x3333_3333);
1946 assert_eq!(alternating(32, 4), 0x0f0f_0f0f);
1947 assert_eq!(every(32, 8, 1), 0x0101_0101);
1948 assert_eq!(every(64, 8, 1), 0x0101_0101_0101_0101);
1949 }
1950
1951 #[test]
1954 fn a_set_bit_count_is_the_halving_sum_and_a_multiply_that_adds_the_bytes() {
1955 let (mut names, mut func) = counting(Opcode::Ctpop, 32);
1956 counts(&mut func);
1957
1958 let text = printed(&func, &mut names);
1959 assert!(!text.contains("ctpop"), "the instruction is gone: {text}");
1960 assert!(text.contains("iconst.i32 1431655765"), "the pairs mask: {text}");
1961 assert!(text.contains("iconst.i32 858993459"), "the nibbles mask: {text}");
1962 assert!(text.contains("iconst.i32 252645135"), "the bytes mask: {text}");
1963 assert_eq!(text.matches(" mul ").count(), 1, "one multiply: {text}");
1964 assert!(text.contains("iconst.i32 24"), "and the top byte is the answer: {text}");
1965 }
1966
1967 #[test]
1969 fn a_count_of_one_byte_stops_before_the_multiply() {
1970 let (mut names, mut func) = counting(Opcode::Ctpop, 8);
1971 counts(&mut func);
1972 let text = printed(&func, &mut names);
1973 assert!(!text.contains("ctpop"), "{text}");
1974 assert!(!text.contains(" mul "), "nothing to add together: {text}");
1975 }
1976
1977 #[test]
1980 fn a_leading_zero_count_smears_the_value_down_and_counts_the_complement() {
1981 let (mut names, mut func) = counting(Opcode::Ctlz, 32);
1982 counts(&mut func);
1983
1984 let text = printed(&func, &mut names);
1985 assert!(!text.contains("ctlz"), "the instruction is gone: {text}");
1986 assert!(!text.contains("ctpop"), "and so is the count it became: {text}");
1987 for by in ["iconst.i32 1", "iconst.i32 2", "iconst.i32 4", "iconst.i32 8", "iconst.i32 16"]
1988 {
1989 assert!(text.contains(by), "{by} is a smearing step: {text}");
1990 }
1991 assert_eq!(text.matches(" xor ").count(), 1, "one complement: {text}");
1992 }
1993
1994 #[test]
1996 fn a_trailing_zero_count_masks_the_bits_below_the_lowest_set_one() {
1997 let (mut names, mut func) = counting(Opcode::Cttz, 32);
1998 counts(&mut func);
1999
2000 let text = printed(&func, &mut names);
2001 assert!(!text.contains("cttz"), "the instruction is gone: {text}");
2002 assert!(!text.contains("ctpop"), "and so is the count it became: {text}");
2003 assert!(text.contains("iconst.i32 -1"), "the complement and the decrement: {text}");
2004 assert_eq!(text.matches(" xor ").count(), 1, "one complement: {text}");
2005 assert!(text.matches(" or ").count() <= 1, "no smearing run: {text}");
2007 }
2008
2009 #[test]
2012 fn what_a_bit_count_becomes_is_ir_that_verifies() {
2013 for op in [Opcode::Ctpop, Opcode::Ctlz, Opcode::Cttz] {
2014 for width in [8u32, 16, 32, 64] {
2015 let (mut names, mut func) = counting(op, width);
2016 counts(&mut func);
2017 let module = Module::new(names.intern("c.c"), &target());
2018 rucc_ir::verify_func(&module, &func, &names)
2019 .unwrap_or_else(|e| panic!("{op:?} at {width}: {e:?}"));
2020 }
2021 }
2022 }
2023
2024 #[test]
2028 fn a_width_the_halving_sum_is_not_written_for_is_left_alone() {
2029 let (mut names, mut func) = counting(Opcode::Ctpop, 24);
2030 counts(&mut func);
2031 assert!(printed(&func, &mut names).contains("ctpop"), "left as it was");
2032 }
2033
2034 #[test]
2036 fn a_function_with_no_bit_count_in_it_is_left_exactly_as_it_was() {
2037 let (mut names, mut func) = one(&[Type::int(32)], &[Type::int(32)], |build, args| {
2038 build.ret(&[args[0]]);
2039 });
2040 let before = printed(&func, &mut names);
2041 counts(&mut func);
2042 assert_eq!(printed(&func, &mut names), before);
2043 }
2044
2045 fn checking(op: Opcode, width: u32) -> (Interner, Func) {
2048 let ty = Type::int(width);
2049 let bit = ty.with_lane(Type::I1);
2050 one(&[ty, ty], &[ty, bit], |build, args| {
2051 let (value, flag) = build.checked(op, args[0], args[1]);
2052 build.ret(&[value, flag]);
2053 })
2054 }
2055
2056 #[test]
2059 fn a_checked_unsigned_add_becomes_an_add_and_one_comparison() {
2060 let (mut names, mut func) = checking(Opcode::UAddOverflow, 32);
2061 overflows(&mut func);
2062
2063 let text = printed(&func, &mut names);
2064 assert!(!text.contains("uadd_overflow"), "the instruction is gone: {text}");
2065 assert_eq!(text.matches(" add ").count(), 1, "one add: {text}");
2066 assert_eq!(text.matches("icmp ult").count(), 1, "and one comparison: {text}");
2067 assert!(!text.contains(" xor "), "nothing about sign bits: {text}");
2068 }
2069
2070 #[test]
2073 fn a_checked_signed_add_becomes_an_add_and_the_sign_bit_of_two_exclusive_ors() {
2074 let (mut names, mut func) = checking(Opcode::SAddOverflow, 32);
2075 overflows(&mut func);
2076
2077 let text = printed(&func, &mut names);
2078 assert!(!text.contains("sadd_overflow"), "the instruction is gone: {text}");
2079 assert_eq!(text.matches(" add ").count(), 1, "one add: {text}");
2080 assert_eq!(text.matches(" xor ").count(), 2, "the answer against each operand: {text}");
2081 assert_eq!(text.matches(" and ").count(), 1, "both at once: {text}");
2082 assert!(text.contains("icmp slt"), "and its sign bit: {text}");
2083 }
2084
2085 #[test]
2088 fn a_checked_unsigned_subtract_compares_the_operands_and_not_the_answer() {
2089 let (mut names, mut func) = checking(Opcode::USubOverflow, 64);
2090 overflows(&mut func);
2091
2092 let text = printed(&func, &mut names);
2093 assert!(!text.contains("usub_overflow"), "the instruction is gone: {text}");
2094 assert_eq!(text.matches(" sub ").count(), 1, "one subtract: {text}");
2095 assert!(text.contains("icmp ult %0, %1"), "the operands, in order: {text}");
2096 }
2097
2098 #[test]
2105 fn a_checked_multiply_becomes_a_multiply_and_the_high_half_of_the_product() {
2106 let (mut names, mut func) = checking(Opcode::UMulOverflow, 64);
2107 overflows(&mut func);
2108
2109 let text = printed(&func, &mut names);
2110 assert!(!text.contains("umul_overflow"), "the instruction is gone: {text}");
2111 assert_eq!(text.matches(" mul ").count(), 5, "the answer and the four halves: {text}");
2112 assert!(text.contains("iconst.i64 32"), "split at half the width: {text}");
2113 assert!(text.contains("iconst.i64 4294967295"), "and masked to it: {text}");
2114 assert!(text.contains("icmp ne"), "the high half against zero: {text}");
2115 assert!(!text.contains("ashr"), "and nothing corrected for sign: {text}");
2116 }
2117
2118 #[test]
2121 fn a_checked_signed_multiply_corrects_the_high_half_for_each_negative_operand() {
2122 let (mut names, mut func) = checking(Opcode::SMulOverflow, 64);
2123 overflows(&mut func);
2124
2125 let text = printed(&func, &mut names);
2126 assert!(!text.contains("smul_overflow"), "the instruction is gone: {text}");
2127 assert_eq!(
2128 text.matches(" ashr ").count(),
2129 3,
2130 "each operand's sign, and the answer: {text}"
2131 );
2132 assert!(text.contains("iconst.i64 63"), "spread from the top bit: {text}");
2133 assert_eq!(text.matches(" sub ").count(), 2, "one correction per operand: {text}");
2134 }
2135
2136 #[test]
2140 fn both_results_are_substituted_into_whoever_was_reading_them() {
2141 let (mut names, mut func) = checking(Opcode::SAddOverflow, 32);
2142 overflows(&mut func);
2143
2144 let text = printed(&func, &mut names);
2148 assert_eq!(
2149 text,
2150 concat!(
2151 "func @f(i32, i32) -> (i32, i1), linkage(external) {\n",
2152 "block0(%0: i32, %1: i32):\n",
2153 " %2 = add %0, %1\n",
2154 " %3 = xor %0, %2\n",
2155 " %4 = xor %1, %2\n",
2156 " %5 = and %3, %4\n",
2157 " %6 = iconst.i32 0\n",
2158 " %7 = icmp slt %5, %6\n",
2159 " return %2, %7\n",
2160 "}\n",
2161 ),
2162 );
2163 }
2164
2165 #[test]
2168 fn what_an_overflow_check_becomes_is_ir_that_verifies() {
2169 let all = [
2170 Opcode::UAddOverflow,
2171 Opcode::SAddOverflow,
2172 Opcode::USubOverflow,
2173 Opcode::SSubOverflow,
2174 Opcode::UMulOverflow,
2175 Opcode::SMulOverflow,
2176 ];
2177 for op in all {
2178 for width in [8u32, 16, 32, 64] {
2179 let (mut names, mut func) = checking(op, width);
2180 overflows(&mut func);
2181 let module = Module::new(names.intern("c.c"), &target());
2182 rucc_ir::verify_func(&module, &func, &names)
2183 .unwrap_or_else(|e| panic!("{op:?} at {width}: {e:?}"));
2184 }
2185 }
2186 }
2187
2188 #[test]
2192 fn a_width_the_split_is_not_written_for_is_left_alone() {
2193 let (mut names, mut func) = checking(Opcode::UMulOverflow, 24);
2194 overflows(&mut func);
2195 assert!(printed(&func, &mut names).contains("umul_overflow"), "left as it was");
2196 }
2197
2198 #[test]
2200 fn a_function_with_no_overflow_check_in_it_is_left_exactly_as_it_was() {
2201 let (mut names, mut func) = one(&[Type::int(32)], &[Type::int(32)], |build, args| {
2202 build.ret(&[args[0]]);
2203 });
2204 let before = printed(&func, &mut names);
2205 overflows(&mut func);
2206 assert_eq!(printed(&func, &mut names), before);
2207 }
2208
2209 fn reading(ty: Type, align: u32, order: MemOrder) -> (Interner, Func) {
2211 one(&[Type::PTR], &[ty], |build, args| {
2212 let info = MemInfo { order, ..access(0, align) };
2213 let value = build.atomic_load(ty, args[0], info, Flags::NONE);
2214 build.ret(&[value]);
2215 })
2216 }
2217
2218 fn writing(ty: Type, align: u32, order: MemOrder) -> (Interner, Func) {
2220 one(&[Type::PTR, ty], &[], |build, args| {
2221 let info = MemInfo { order, ..access(0, align) };
2222 build.atomic_store(args[1], args[0], info, Flags::NONE);
2223 build.ret(&[]);
2224 })
2225 }
2226
2227 #[test]
2234 fn an_ordered_access_becomes_the_plain_one_this_machine_already_orders() {
2235 for order in [MemOrder::Relaxed, MemOrder::Acquire, MemOrder::SeqCst] {
2236 let (mut names, mut func) = reading(Type::int(32), 4, order);
2237 orderings(&mut func, 8);
2238 let text = printed(&func, &mut names);
2239 assert!(text.contains("load.i32"), "{order:?}: {text}");
2240 assert!(!text.contains("atomic_load"), "{order:?}: {text}");
2241 assert!(!text.contains(order.name()), "the ordering came off: {text}");
2242 }
2243
2244 for order in [MemOrder::Relaxed, MemOrder::Release] {
2245 let (mut names, mut func) = writing(Type::int(32), 4, order);
2246 orderings(&mut func, 8);
2247 let text = printed(&func, &mut names);
2248 assert!(text.contains("store %1 -> %0"), "{order:?}: {text}");
2249 assert!(!text.contains("atomic_store"), "{order:?}: {text}");
2250 assert!(!text.contains("fence"), "{order:?} costs nothing here: {text}");
2251 }
2252 }
2253
2254 #[test]
2260 fn the_strongest_store_keeps_a_barrier_behind_it() {
2261 let (mut names, mut func) = writing(Type::int(32), 4, MemOrder::SeqCst);
2262 orderings(&mut func, 8);
2263 let text = printed(&func, &mut names);
2264 let (before, after) = text.split_once("fence seq_cst").expect("a barrier");
2265 assert!(before.contains("store %1 -> %0"), "the store comes first: {text}");
2266 assert!(!after.contains("store"), "and nothing is between them: {text}");
2267 assert!(!text.contains("atomic_store"), "{text}");
2268 }
2269
2270 #[test]
2273 fn a_barrier_is_left_for_the_place_that_knows_what_one_costs() {
2274 for order in MemOrder::all().filter(|&order| order != MemOrder::NotAtomic) {
2275 let (mut names, mut func) = one(&[], &[], |build, _| {
2276 build.fence(order);
2277 build.ret(&[]);
2278 });
2279 let before = printed(&func, &mut names);
2280 orderings(&mut func, 8);
2281 assert_eq!(printed(&func, &mut names), before, "{order:?}");
2282 }
2283 }
2284
2285 #[test]
2291 fn an_access_this_machine_cannot_do_in_one_go_is_left_alone() {
2292 for (ty, align) in [(Type::int(128), 16), (Type::int(64), 4)] {
2293 let (mut names, mut func) = reading(ty, align, MemOrder::SeqCst);
2294 orderings(&mut func, 8);
2295 assert!(printed(&func, &mut names).contains("atomic_load"), "left as it was");
2296 }
2297 }
2298
2299 #[test]
2302 fn what_the_ordered_accesses_become_verifies() {
2303 for order in MemOrder::all().filter(|&order| order != MemOrder::NotAtomic) {
2304 for (mut names, mut func) in
2305 [reading(Type::int(32), 4, order), writing(Type::int(32), 4, order)]
2306 {
2307 if !order.is_valid_for_load() && !order.is_valid_for_store() {
2308 continue;
2309 }
2310 orderings(&mut func, 8);
2311 let module = Module::new(names.intern("a.c"), &target());
2312 rucc_ir::verify_func(&module, &func, &names)
2313 .unwrap_or_else(|e| panic!("{order:?}: {e:?}"));
2314 }
2315 }
2316 }
2317
2318 #[test]
2320 fn a_function_with_no_ordered_access_in_it_is_left_exactly_as_it_was() {
2321 let (mut names, mut func) = one(&[Type::int(32)], &[Type::int(32)], |build, args| {
2322 build.ret(&[args[0]]);
2323 });
2324 let before = printed(&func, &mut names);
2325 orderings(&mut func, 8);
2326 assert_eq!(printed(&func, &mut names), before);
2327 }
2328}