1use rucc_base::Interner;
52use rucc_ir::{
53 BlockCall, Builder, CallInfo, Def, Extra, Flags, Func, Imm, Inst, InstData, IntPred, MemInfo,
54 Opcode, Signature, Type, Value,
55};
56
57pub fn switches(func: &mut Func) {
63 let found: Vec<Inst> = func
64 .blocks()
65 .filter_map(|block| func.terminator(block))
66 .filter(|&inst| func[inst].opcode == Opcode::Switch)
67 .collect();
68 for inst in found {
69 chain(func, inst);
70 }
71}
72
73fn chain(func: &mut Func, inst: Inst) {
85 let block = func.block_of(inst).expect("a terminator is in a block");
86 let span = func.span(inst);
87 let Extra::Switch(info) = func[inst].extra else { return };
88 let info = func[info];
89 let value = func[func[inst].args][0];
90 let ty = func[value].ty.lane();
93 let calls: Vec<BlockCall> = func[info.targets].to_vec();
94 let cases: Vec<Imm> = func[info.cases].to_vec();
95 let Some((default, arms)) = calls.split_first() else { return };
96
97 func.remove_inst(inst);
100
101 let Some((first, rest)) = arms.split_first() else {
105 let args: Vec<Value> = func[default.args].to_vec();
106 Builder::new(func, block).at(span).jump(default.block, &args);
107 return;
108 };
109
110 let mut at = block;
111 for (index, arm) in std::iter::once(first).chain(rest).enumerate() {
112 let last = index + 1 == arms.len();
113 let next = if last { default.block } else { func.create_block() };
114 let onward: Vec<Value> = if last { func[default.args].to_vec() } else { Vec::new() };
115 let taken: Vec<Value> = func[arm.args].to_vec();
116 let case = cases[index].signed(ty);
117
118 let mut build = Builder::new(func, at).at(span);
119 let want = build.iconst(ty, case);
120 let same = build.icmp(IntPred::Eq, value, want);
121 build.br_if(same, arm.block, &taken, next, &onward);
122 at = next;
123 }
124}
125
126pub fn floats(func: &mut Func) {
141 let found: Vec<Inst> =
142 func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
143 for inst in found {
144 match func[inst].opcode {
145 Opcode::FConst => constant(func, inst),
146 Opcode::FNeg => negate(func, inst),
147 Opcode::SIToFP | Opcode::UIToFP => widen_then_convert(func, inst),
148 Opcode::FPToSI | Opcode::FPToUI => convert_then_narrow(func, inst),
149 _ => {}
150 }
151 }
152}
153
154fn constant(func: &mut Func, inst: Inst) {
161 let ty = produced(func, inst);
162 let Extra::Imm(imm) = func[inst].extra else { return };
163 if !ty.is_float() || !ty.is_scalar() {
164 return;
165 }
166 let int = Type::int(ty.bits());
167 let bits = func[imm].bits();
168 let spelled = ahead_const(func, inst, Imm::int(bits as i128, int), int);
171 becomes(func, inst, Opcode::Bitcast, &[spelled]);
172}
173
174fn negate(func: &mut Func, inst: Inst) {
185 let ty = produced(func, inst);
186 let Some(&arg) = func[func[inst].args].first() else { return };
187 if !ty.is_float() || !ty.is_scalar() {
188 return;
189 }
190 let int = Type::int(ty.bits());
191 let bits = ahead(func, inst, Opcode::Bitcast, &[arg], int);
192 let mask = ahead_const(func, inst, Imm::int(1i128 << (ty.bits() - 1), int), int);
193 let flipped = ahead(func, inst, Opcode::Xor, &[bits, mask], int);
194 becomes(func, inst, Opcode::Bitcast, &[flipped]);
195}
196
197fn widen_then_convert(func: &mut Func, inst: Inst) {
204 let signed = func[inst].opcode == Opcode::SIToFP;
205 let Some(&arg) = func[func[inst].args].first() else { return };
206 let from = func[arg].ty;
207 if !from.is_int() || !from.is_scalar() {
208 return;
209 }
210 let Some(width) = holder(from.bits(), signed) else { return };
211 if width == from.bits() {
212 return;
213 }
214 let widen = if signed { Opcode::SExt } else { Opcode::ZExt };
215 let wide = ahead(func, inst, widen, &[arg], Type::int(width));
216 becomes(func, inst, Opcode::SIToFP, &[wide]);
217}
218
219fn convert_then_narrow(func: &mut Func, inst: Inst) {
226 let signed = func[inst].opcode == Opcode::FPToSI;
227 let ty = produced(func, inst);
228 let Some(&arg) = func[func[inst].args].first() else { return };
229 if !ty.is_int() || !ty.is_scalar() {
230 return;
231 }
232 let Some(width) = holder(ty.bits(), signed) else { return };
233 if width == ty.bits() {
234 return;
235 }
236 let wide = ahead(func, inst, Opcode::FPToSI, &[arg], Type::int(width));
237 becomes(func, inst, Opcode::Trunc, &[wide]);
238}
239
240pub fn bytes(func: &mut Func) {
254 let found: Vec<Inst> =
255 func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
256 for inst in found {
257 if func[inst].opcode == Opcode::Bswap {
258 swap(func, inst);
259 }
260 }
261}
262
263fn swap(func: &mut Func, inst: Inst) {
280 let ty = produced(func, inst);
281 let Some(&arg) = func[func[inst].args].first() else { return };
282 if !ty.is_int() || !ty.is_scalar() || ty.bits() < 16 || ty.bits() % 8 != 0 {
283 return;
284 }
285
286 let mut value = arg;
287 let mut group = ty.bits() / 2;
288 while group >= 8 {
289 let mask = alternating(ty.bits(), group);
292 let keep = ahead_const(func, inst, Imm::int(mask, ty), ty);
293 let count = ahead_const(func, inst, Imm::int(i128::from(group), ty), ty);
294 let low = ahead(func, inst, Opcode::And, &[value, keep], ty);
295 let up = ahead(func, inst, Opcode::Shl, &[low, count], ty);
296 let down = ahead(func, inst, Opcode::LShr, &[value, count], ty);
297 let high = ahead(func, inst, Opcode::And, &[down, keep], ty);
298 if group == 8 {
301 becomes(func, inst, Opcode::Or, &[up, high]);
302 return;
303 }
304 value = ahead(func, inst, Opcode::Or, &[up, high], ty);
305 group /= 2;
306 }
307}
308
309fn alternating(width: u32, group: u32) -> i128 {
320 every(width, group * 2, group)
321}
322
323fn every(width: u32, step: u32, run: u32) -> i128 {
332 let ones = (1i128 << run) - 1;
333 let mut mask = 0i128;
334 let mut at = 0;
335 while at < width {
336 mask |= ones << at;
337 at += step;
338 }
339 mask
340}
341
342pub fn counts(func: &mut Func) {
356 let found: Vec<Inst> =
357 func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
358 for inst in found {
359 match func[inst].opcode {
360 Opcode::Ctlz => searched(func, inst, true),
361 Opcode::Cttz => searched(func, inst, false),
362 _ => {}
363 }
364 }
365 let found: Vec<Inst> =
366 func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
367 for inst in found {
368 if func[inst].opcode == Opcode::Ctpop {
369 counted(func, inst);
370 }
371 }
372}
373
374fn searched(func: &mut Func, inst: Inst, leading: bool) {
392 let ty = produced(func, inst);
393 let Some(&arg) = func[func[inst].args].first() else { return };
394 if !countable(ty) {
395 return;
396 }
397 let ones = ahead_const(func, inst, Imm::int(-1, ty), ty);
398 if leading {
399 let mut value = arg;
400 let mut by = 1;
401 while by < ty.bits() {
402 let count = ahead_const(func, inst, Imm::int(i128::from(by), ty), ty);
403 let down = ahead(func, inst, Opcode::LShr, &[value, count], ty);
404 value = ahead(func, inst, Opcode::Or, &[value, down], ty);
405 by *= 2;
406 }
407 let above = ahead(func, inst, Opcode::Xor, &[value, ones], ty);
408 becomes(func, inst, Opcode::Ctpop, &[above]);
409 return;
410 }
411 let missing = ahead(func, inst, Opcode::Xor, &[arg, ones], ty);
412 let less = ahead(func, inst, Opcode::Add, &[arg, ones], ty);
413 let below = ahead(func, inst, Opcode::And, &[missing, less], ty);
414 becomes(func, inst, Opcode::Ctpop, &[below]);
415}
416
417fn counted(func: &mut Func, inst: Inst) {
431 let ty = produced(func, inst);
432 let Some(&arg) = func[func[inst].args].first() else { return };
433 if !countable(ty) {
434 return;
435 }
436 let width = ty.bits();
437 let pairs = ahead_const(func, inst, Imm::int(alternating(width, 1), ty), ty);
438 let two = ahead_const(func, inst, Imm::int(2, ty), ty);
439 let one = ahead_const(func, inst, Imm::int(1, ty), ty);
440 let high = ahead(func, inst, Opcode::LShr, &[arg, one], ty);
441 let odd = ahead(func, inst, Opcode::And, &[high, pairs], ty);
442 let bits = ahead(func, inst, Opcode::Sub, &[arg, odd], ty);
443
444 let quads = ahead_const(func, inst, Imm::int(alternating(width, 2), ty), ty);
445 let low = ahead(func, inst, Opcode::And, &[bits, quads], ty);
446 let up = ahead(func, inst, Opcode::LShr, &[bits, two], ty);
447 let rest = ahead(func, inst, Opcode::And, &[up, quads], ty);
448 let nibbles = ahead(func, inst, Opcode::Add, &[low, rest], ty);
449
450 let four = ahead_const(func, inst, Imm::int(4, ty), ty);
451 let bytes = ahead_const(func, inst, Imm::int(alternating(width, 4), ty), ty);
452 let folded = ahead(func, inst, Opcode::LShr, &[nibbles, four], ty);
453 let summed = ahead(func, inst, Opcode::Add, &[nibbles, folded], ty);
454 if width == 8 {
455 becomes(func, inst, Opcode::And, &[summed, bytes]);
456 return;
457 }
458 let held = ahead(func, inst, Opcode::And, &[summed, bytes], ty);
459
460 let spread = ahead_const(func, inst, Imm::int(every(width, 8, 1), ty), ty);
461 let top = ahead_const(func, inst, Imm::int(i128::from(width - 8), ty), ty);
462 let total = ahead(func, inst, Opcode::Mul, &[held, spread], ty);
463 becomes(func, inst, Opcode::LShr, &[total, top]);
464}
465
466fn countable(ty: Type) -> bool {
472 ty.is_int()
473 && ty.is_scalar()
474 && ty.bits() >= 8
475 && ty.bits() <= 64
476 && ty.bits().is_power_of_two()
477}
478
479pub const UNROLL: usize = 32;
492
493pub fn bulk(func: &mut Func, names: &mut Interner, word: u32) {
504 let found: Vec<Inst> =
505 func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
506 for inst in found {
507 match func[inst].opcode {
508 Opcode::Memcpy => copy(func, names, inst, word),
509 Opcode::Memset => fill(func, names, inst, word),
510 Opcode::Memmove => library(func, names, inst, "memmove", word),
511 _ => {}
512 }
513 }
514}
515
516fn copy(func: &mut Func, names: &mut Interner, inst: Inst, word: u32) {
524 let [into, from] = func[func[inst].args] else { return };
525 let Extra::Mem(mem) = func[inst].extra else { return };
526 let info = func[mem];
527 let Some(plan) = chunks(info, word) else { return library(func, names, inst, "memcpy", word) };
528 for (at, width) in plan {
529 let ty = Type::int(width * 8);
530 let access = MemInfo { size: u64::from(width), align: width.min(info.align), ..info };
531 let there = stepped(func, inst, from, at);
532 let word = read(func, inst, there, access, ty);
533 let here = stepped(func, inst, into, at);
534 write(func, inst, word, here, access);
535 }
536 func.remove_inst(inst);
537}
538
539fn fill(func: &mut Func, names: &mut Interner, inst: Inst, word: u32) {
546 let [into, byte] = func[func[inst].args] else { return };
547 let Extra::Mem(mem) = func[inst].extra else { return };
548 let info = func[mem];
549 let Some(spelled) = literal(func, byte) else {
550 return library(func, names, inst, "memset", word);
551 };
552 let Some(plan) = chunks(info, word) else { return library(func, names, inst, "memset", word) };
553 for (at, width) in plan {
554 let ty = Type::int(width * 8);
555 let access = MemInfo { size: u64::from(width), align: width.min(info.align), ..info };
556 let value = ahead_const(func, inst, Imm::int(spread(spelled, width) as i128, ty), ty);
557 let here = stepped(func, inst, into, at);
558 write(func, inst, value, here, access);
559 }
560 func.remove_inst(inst);
561}
562
563fn library(func: &mut Func, names: &mut Interner, inst: Inst, routine: &str, word: u32) {
576 let [into, second] = func[func[inst].args] else { return };
577 let Extra::Mem(mem) = func[inst].extra else { return };
578 let size = func[mem].size;
579
580 let words = Type::int(word * 8);
584 let count = ahead_const(func, inst, Imm::int(i128::from(size), words), words);
585 let second = match routine {
588 "memset" => widened(func, inst, second),
589 _ => second,
590 };
591
592 let sig = func.add_signature(Signature::new().with_params(&[
593 Type::PTR,
594 if routine == "memset" { Type::int(32) } else { Type::PTR },
595 words,
596 ]));
597 let callee = names.intern(routine);
598 let varargs = func.push_abis(&[]);
599 let info = func.add_call(CallInfo { callee: Some(callee), signature: sig, varargs });
600 let args = func.push_values(&[into, second, count]);
601 let data = &mut func[inst];
602 data.opcode = Opcode::Call;
603 data.args = args;
604 data.extra = Extra::Call(info);
605 data.flags = data.flags.intersection(Flags::legal_on(Opcode::Call));
606}
607
608fn widened(func: &mut Func, inst: Inst, value: Value) -> Value {
610 let int = Type::int(32);
611 let ty = func[value].ty;
612 if ty == int {
613 return value;
614 }
615 ahead(func, inst, Opcode::ZExt, &[value], int)
616}
617
618fn chunks(info: MemInfo, word: u32) -> Option<Vec<(u64, u32)>> {
631 plan(info.size, info.align, word)
632}
633
634pub(crate) fn plan(size: u64, align: u32, word: u32) -> Option<Vec<(u64, u32)>> {
642 let widest = word.min(align).max(1);
643 if !widest.is_power_of_two() {
644 return None;
645 }
646 let mut plan = Vec::new();
647 let mut at = 0;
648 let mut width = u64::from(widest);
649 while at < size {
650 while width > size - at {
651 width /= 2;
652 }
653 plan.push((at, u32::try_from(width).ok()?));
654 at += width;
655 if plan.len() > UNROLL {
656 return None;
657 }
658 }
659 Some(plan)
660}
661
662fn literal(func: &Func, value: Value) -> Option<u8> {
664 let Def::Result { inst, .. } = func[value].def else { return None };
665 if func[inst].opcode != Opcode::IConst {
666 return None;
667 }
668 let Extra::Imm(imm) = func[inst].extra else { return None };
669 u8::try_from(func[imm].bits() & 0xff).ok()
670}
671
672fn spread(byte: u8, width: u32) -> u64 {
674 (0..width).fold(0, |word, at| word | u64::from(byte) << (at * 8))
675}
676
677fn stepped(func: &mut Func, inst: Inst, block: Value, at: u64) -> Value {
680 if at == 0 {
681 return block;
682 }
683 let step = ahead_const(func, inst, Imm::int(i128::from(at), Type::int(64)), Type::int(64));
684 ahead(func, inst, Opcode::PtrAdd, &[block, step], Type::PTR)
685}
686
687fn read(func: &mut Func, inst: Inst, from: Value, info: MemInfo, ty: Type) -> Value {
689 let extra = Extra::Mem(func.add_mem(info));
690 let args = func.push_values(&[from]);
691 written(func, inst, InstData { args, extra, ..InstData::new(Opcode::Load) }, ty)
692}
693
694fn write(func: &mut Func, inst: Inst, value: Value, into: Value, info: MemInfo) {
696 let span = func.span(inst);
697 let extra = Extra::Mem(func.add_mem(info));
698 let args = func.push_values(&[value, into]);
699 let data = InstData { args, extra, ..InstData::new(Opcode::Store) };
700 let made = func.create_inst(data, &[], span);
701 func.insert_before(made, inst);
702}
703
704fn holder(bits: u32, signed: bool) -> Option<u32> {
713 match if signed { bits } else { bits + 1 } {
714 ..=32 => Some(32),
715 33..=64 => Some(64),
716 _ => None,
717 }
718}
719
720fn produced(func: &Func, inst: Inst) -> Type {
725 func[inst].first_result.map_or(Type::VOID, |value| func[value].ty)
726}
727
728fn ahead(func: &mut Func, inst: Inst, opcode: Opcode, args: &[Value], ty: Type) -> Value {
730 let args = func.push_values(args);
731 written(func, inst, InstData { args, ..InstData::new(opcode) }, ty)
732}
733
734fn ahead_const(func: &mut Func, inst: Inst, imm: Imm, ty: Type) -> Value {
736 let extra = Extra::Imm(func.add_imm(imm));
737 written(func, inst, InstData { extra, ..InstData::new(Opcode::IConst) }, ty)
738}
739
740fn written(func: &mut Func, inst: Inst, data: InstData, ty: Type) -> Value {
742 let span = func.span(inst);
743 let made = func.create_inst(data, &[ty], span);
744 func.insert_before(made, inst);
745 func[made].first_result.expect("an instruction created with one result has one")
746}
747
748fn becomes(func: &mut Func, inst: Inst, opcode: Opcode, args: &[Value]) {
755 let args = func.push_values(args);
756 let data = &mut func[inst];
757 data.opcode = opcode;
758 data.args = args;
759 data.extra = Extra::None;
760 data.flags = data.flags.intersection(Flags::legal_on(opcode));
763}
764
765#[must_use]
770pub fn blocks_for(cases: usize) -> usize {
771 cases.saturating_sub(1)
772}
773
774#[cfg(test)]
775mod tests {
776 use rucc_base::Interner;
777 use rucc_ir::{Builder, Flags, Float, Func, Module, Opcode, Signature, Type};
778 use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
779
780 use rucc_ir::{Extra, InstData, MemInfo, MemOrder, Restrict};
781
782 use super::{
783 UNROLL, alternating, blocks_for, bulk, bytes, chunks, counts, every, floats, spread,
784 switches,
785 };
786
787 fn target() -> TargetInfo {
788 TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu))
789 }
790
791 fn built(cases: &[i128]) -> (Interner, Func) {
794 let mut names = Interner::new();
795 let int = Type::int(32);
796 let mut func = Func::new(
797 names.intern("sw"),
798 Signature::new().with_params(&[int]).with_returns(&[int]),
799 );
800 let entry = func.create_block();
801 let x = func.append_param(entry, int);
802
803 let default = func.create_block();
804 let arms: Vec<_> = cases.iter().map(|_| func.create_block()).collect();
805 let table: Vec<(i128, rucc_ir::Block)> =
806 cases.iter().copied().zip(arms.iter().copied()).collect();
807 Builder::new(&mut func, entry).switch(x, default, &table);
808
809 for (index, &arm) in arms.iter().enumerate() {
810 let mut build = Builder::new(&mut func, arm);
811 let what = i128::try_from(index).expect("a small number of cases");
812 let v = build.iconst(int, (what + 1) * 10);
813 build.ret(&[v]);
814 }
815 let mut build = Builder::new(&mut func, default);
816 let v = build.iconst(int, 30);
817 build.ret(&[v]);
818 (names, func)
819 }
820
821 fn count(func: &Func) -> usize {
822 func.blocks().count()
823 }
824
825 fn printed(func: &Func, names: &mut Interner) -> String {
826 let module = Module::new(names.intern("sw.c"), &target());
827 rucc_ir::print_func(&module, func, names)
828 }
829
830 #[test]
831 fn a_switch_becomes_a_compare_and_a_branch_for_each_case() {
832 let (mut names, mut func) = built(&[1, 2]);
833 let before = count(&func);
834 switches(&mut func);
835 assert_eq!(count(&func), before + blocks_for(2));
836
837 let text = printed(&func, &mut names);
838 assert!(!text.contains("switch"), "the switch is gone: {text}");
839 assert_eq!(text.matches("icmp eq").count(), 2, "one compare per case: {text}");
840 assert_eq!(text.matches("br_if").count(), 2, "one branch per case: {text}");
841 }
842
843 #[test]
844 fn the_last_case_falls_to_the_default_rather_than_to_a_block_of_its_own() {
845 let (_, mut func) = built(&[7]);
846 let before = count(&func);
847 switches(&mut func);
848 assert_eq!(count(&func), before);
850 assert_eq!(blocks_for(1), 0);
851 }
852
853 #[test]
854 fn a_switch_with_only_a_default_is_a_jump() {
855 let (_, mut func) = built(&[]);
856 switches(&mut func);
857 let entry = func.entry().expect("an entry block");
858 let term = func.terminator(entry).expect("a terminator");
859 assert_eq!(func[term].opcode, Opcode::Jump);
860 }
861
862 #[test]
865 fn what_comes_out_is_valid_ir() {
866 let (mut names, mut func) = built(&[1, 2, 3, 4]);
867 switches(&mut func);
868 let module = Module::new(names.intern("sw.c"), &target());
869 rucc_ir::verify_func(&module, &func, &names).expect("the rewrite builds valid IR");
870 }
871
872 #[test]
875 fn a_function_with_no_switch_is_left_exactly_as_it_was() {
876 let mut names = Interner::new();
877 let int = Type::int(32);
878 let mut func =
879 Func::new(names.intern("f"), Signature::new().with_params(&[int]).with_returns(&[int]));
880 let entry = func.create_block();
881 let x = func.append_param(entry, int);
882 Builder::new(&mut func, entry).ret(&[x]);
883
884 let before = printed(&func, &mut names);
885 switches(&mut func);
886 assert_eq!(printed(&func, &mut names), before);
887 }
888
889 fn one(
894 params: &[Type],
895 returns: &[Type],
896 body: impl FnOnce(&mut Builder<'_>, &[rucc_ir::Value]),
897 ) -> (Interner, Func) {
898 let mut names = Interner::new();
899 let mut func = Func::new(
900 names.intern("f"),
901 Signature::new().with_params(params).with_returns(returns),
902 );
903 let entry = func.create_block();
904 let args: Vec<_> = params.iter().map(|&ty| func.append_param(entry, ty)).collect();
905 let mut build = Builder::new(&mut func, entry);
906 body(&mut build, &args);
907 (names, func)
908 }
909
910 fn f64() -> Type {
911 Type::float(Float::F64)
912 }
913
914 fn f32() -> Type {
915 Type::float(Float::F32)
916 }
917
918 #[test]
920 fn a_float_constant_becomes_the_integer_that_spells_it_and_a_reading_of_those_bits() {
921 let (mut names, mut func) = one(&[], &[f64()], |build, _| {
922 let k = build.fconst(f64(), 0x3ff8_0000_0000_0000);
923 build.ret(&[k]);
924 });
925 floats(&mut func);
926
927 let text = printed(&func, &mut names);
928 assert!(!text.contains("fconst"), "the float constant is gone: {text}");
929 assert!(text.contains("iconst.i64 4609434218613702656"), "the bits, as an integer: {text}");
930 assert!(text.contains("bitcast"), "read back as the float: {text}");
931 }
932
933 #[test]
936 fn a_constant_at_the_narrow_format_is_an_integer_of_the_narrow_width() {
937 let (mut names, mut func) = one(&[], &[f32()], |build, _| {
938 let k = build.fconst(f32(), 0x4020_0000);
939 build.ret(&[k]);
940 });
941 floats(&mut func);
942 assert!(printed(&func, &mut names).contains("iconst.i32"), "an i32, not an i64");
943 }
944
945 #[test]
948 fn a_negation_flips_the_sign_bit_and_touches_no_other() {
949 let (mut names, mut func) = one(&[f64()], &[f64()], |build, args| {
950 let n = build.unary(Opcode::FNeg, args[0], f64());
951 build.ret(&[n]);
952 });
953 floats(&mut func);
954
955 let text = printed(&func, &mut names);
956 assert!(!text.contains("fneg"), "the negation is gone: {text}");
957 assert!(!text.contains("fsub"), "and it did not become a subtraction: {text}");
958 assert!(text.contains("iconst.i64 -9223372036854775808"), "the sign bit alone: {text}");
959 assert_eq!(text.matches("xor").count(), 1, "one exclusive or: {text}");
960 assert_eq!(text.matches("bitcast").count(), 2, "there and back: {text}");
961 }
962
963 #[test]
965 fn an_unsigned_integer_becoming_a_float_widens_first_and_then_converts_as_signed() {
966 let (mut names, mut func) = one(&[Type::int(32)], &[f64()], |build, args| {
967 let d = build.unary(Opcode::UIToFP, args[0], f64());
968 build.ret(&[d]);
969 });
970 floats(&mut func);
971
972 let text = printed(&func, &mut names);
973 assert!(!text.contains("uitofp"), "the unsigned conversion is gone: {text}");
974 assert!(text.contains("zext.i64"), "widened with zeroes: {text}");
975 assert!(text.contains("sitofp.f64"), "converted as signed: {text}");
976 }
977
978 #[test]
980 fn a_float_becoming_an_unsigned_integer_converts_as_signed_first_and_then_narrows() {
981 let (mut names, mut func) = one(&[f64()], &[Type::int(32)], |build, args| {
982 let n = build.unary(Opcode::FPToUI, args[0], Type::int(32));
983 build.ret(&[n]);
984 });
985 floats(&mut func);
986
987 let text = printed(&func, &mut names);
988 assert!(!text.contains("fptoui"), "the unsigned conversion is gone: {text}");
989 assert!(text.contains("fptosi.i64"), "converted as signed: {text}");
990 assert!(text.contains("trunc.i32"), "and narrowed to what was asked: {text}");
991 }
992
993 #[test]
996 fn a_conversion_narrower_than_the_machine_has_is_one_it_has_and_a_narrowing() {
997 let (mut names, mut func) = one(&[f64()], &[Type::int(8)], |build, args| {
998 let n = build.unary(Opcode::FPToSI, args[0], Type::int(8));
999 build.ret(&[n]);
1000 });
1001 floats(&mut func);
1002
1003 let text = printed(&func, &mut names);
1004 assert!(text.contains("fptosi.i32"), "converted at a width there is one at: {text}");
1005 assert!(text.contains("trunc.i8"), "and narrowed to what was asked: {text}");
1006 }
1007
1008 #[test]
1010 fn a_signed_integer_narrower_than_the_machine_converts_from_is_widened_with_its_sign() {
1011 let (mut names, mut func) = one(&[Type::int(8)], &[f64()], |build, args| {
1012 let d = build.unary(Opcode::SIToFP, args[0], f64());
1013 build.ret(&[d]);
1014 });
1015 floats(&mut func);
1016
1017 let text = printed(&func, &mut names);
1018 assert!(text.contains("sext.i32"), "widened with the sign and not with zeroes: {text}");
1019 assert!(!text.contains("zext"), "widened with the sign and not with zeroes: {text}");
1020 assert!(text.contains("sitofp.f64"), "converted at a width there is one at: {text}");
1021 }
1022
1023 #[test]
1025 fn the_width_a_conversion_happens_at_is_the_narrowest_one_that_holds_the_values() {
1026 use super::holder;
1027 for bits in [1, 8, 16, 32] {
1028 assert_eq!(holder(bits, true), Some(32), "a signed {bits} bit value fits in an int");
1029 }
1030 assert_eq!(holder(64, true), Some(64));
1031 for bits in [1, 8, 16, 31] {
1032 assert_eq!(holder(bits, false), Some(32), "an unsigned {bits} bit value does too");
1033 }
1034 assert_eq!(holder(32, false), Some(64));
1036 assert_eq!(holder(64, false), None);
1037 }
1038
1039 #[test]
1043 fn the_unsigned_conversions_at_the_widest_width_are_left_alone() {
1044 let (mut names, mut func) = one(&[Type::int(64)], &[f64()], |build, args| {
1045 let d = build.unary(Opcode::UIToFP, args[0], f64());
1046 build.ret(&[d]);
1047 });
1048 let before = printed(&func, &mut names);
1049 floats(&mut func);
1050 assert_eq!(printed(&func, &mut names), before);
1051
1052 let (mut names, mut func) = one(&[f64()], &[Type::int(64)], |build, args| {
1053 let n = build.unary(Opcode::FPToUI, args[0], Type::int(64));
1054 build.ret(&[n]);
1055 });
1056 let before = printed(&func, &mut names);
1057 floats(&mut func);
1058 assert_eq!(printed(&func, &mut names), before);
1059 }
1060
1061 #[test]
1064 fn what_the_float_rewrites_leave_is_valid_ir() {
1065 let (mut names, mut func) = one(&[Type::int(32)], &[f64()], |build, args| {
1066 let k = build.fconst(f64(), 0x3ff8_0000_0000_0000);
1067 let d = build.unary(Opcode::UIToFP, args[0], f64());
1068 let n = build.unary(Opcode::FNeg, d, f64());
1069 let s = build.binary(Opcode::FAdd, n, k, Flags::NONE);
1070 build.ret(&[s]);
1071 });
1072 floats(&mut func);
1073 let module = Module::new(names.intern("f.c"), &target());
1074 rucc_ir::verify_func(&module, &func, &names).expect("the rewrite builds valid IR");
1075 }
1076
1077 #[test]
1080 fn a_function_with_no_floats_in_it_is_left_exactly_as_it_was() {
1081 let (mut names, mut func) = one(&[Type::int(32)], &[Type::int(32)], |build, args| {
1082 build.ret(&[args[0]]);
1083 });
1084 let before = printed(&func, &mut names);
1085 floats(&mut func);
1086 assert_eq!(printed(&func, &mut names), before);
1087 }
1088 fn access(size: u64, align: u32) -> MemInfo {
1089 MemInfo { size, align, order: MemOrder::NotAtomic, tbaa: None, restrict: Restrict::NONE }
1090 }
1091
1092 fn moving(opcode: Opcode, size: u64, align: u32, byte: Option<i128>) -> (Interner, Func) {
1095 one(&[Type::PTR, Type::PTR], &[], |build, args| {
1096 let second = match byte {
1097 Some(value) => build.iconst(Type::int(8), value),
1098 None => args[1],
1099 };
1100 let mem = build.func().add_mem(access(size, align));
1101 let operands = build.func().push_values(&[args[0], second]);
1102 let data = InstData { args: operands, extra: Extra::Mem(mem), ..InstData::new(opcode) };
1103 build.inst(data, &[]);
1104 build.ret(&[]);
1105 })
1106 }
1107
1108 fn copying(size: u64, align: u32) -> (Interner, Func) {
1109 moving(Opcode::Memcpy, size, align, None)
1110 }
1111
1112 fn filling(size: u64, align: u32, byte: i128) -> (Interner, Func) {
1113 moving(Opcode::Memset, size, align, Some(byte))
1114 }
1115
1116 fn widths(size: u64, align: u32) -> Option<Vec<u32>> {
1119 Some(chunks(access(size, align), 8)?.into_iter().map(|(_, width)| width).collect())
1120 }
1121
1122 #[test]
1124 fn a_copy_becomes_a_load_and_a_store_for_each_word_of_it() {
1125 let (mut names, mut func) = copying(16, 8);
1126 bulk(&mut func, &mut names, 8);
1127
1128 let text = printed(&func, &mut names);
1129 assert!(!text.contains("memcpy"), "the copy is gone: {text}");
1130 assert_eq!(text.matches("load.i64").count(), 2, "a load per word: {text}");
1131 assert_eq!(text.matches("store").count(), 2, "a store per word: {text}");
1132 assert_eq!(
1133 text.matches("ptr_add").count(),
1134 2,
1135 "no offset for the word at the front: {text}"
1136 );
1137 }
1138
1139 #[test]
1143 fn a_word_is_as_wide_as_the_block_is_aligned_to() {
1144 assert_eq!(widths(16, 8), Some(vec![8, 8]));
1145 assert_eq!(widths(16, 4), Some(vec![4, 4, 4, 4]));
1146 assert_eq!(widths(4, 1), Some(vec![1, 1, 1, 1]));
1147 }
1148
1149 #[test]
1152 fn what_is_left_over_is_narrower_words_and_not_a_run_of_bytes() {
1153 assert_eq!(widths(13, 8), Some(vec![8, 4, 1]));
1154 assert_eq!(widths(3, 8), Some(vec![2, 1]));
1155 assert_eq!(widths(1, 8), Some(vec![1]));
1156 }
1157
1158 #[test]
1161 fn every_word_starts_somewhere_it_is_aligned_for() {
1162 for (at, width) in chunks(access(13, 8), 8).expect("a plan for thirteen bytes") {
1163 assert_eq!(at % u64::from(width), 0, "{at} is a multiple of {width}");
1164 }
1165 }
1166
1167 #[test]
1169 fn a_fill_is_the_byte_spread_across_each_word() {
1170 let (mut names, mut func) = filling(16, 8, 0);
1171 bulk(&mut func, &mut names, 8);
1172
1173 let text = printed(&func, &mut names);
1174 assert!(!text.contains("memset"), "the fill is gone: {text}");
1175 assert_eq!(text.matches("store").count(), 2, "a store per word: {text}");
1176 assert!(!text.contains("load"), "a fill reads nothing: {text}");
1177 }
1178
1179 #[test]
1182 fn the_byte_is_repeated_across_the_word_it_is_stored_as() {
1183 assert_eq!(spread(0, 8), 0);
1184 assert_eq!(spread(0xff, 1), 0xff);
1185 assert_eq!(spread(0xff, 4), 0xffff_ffff);
1186 assert_eq!(spread(0xab, 2), 0xabab);
1187 assert_eq!(spread(0xab, 8), 0xabab_abab_abab_abab);
1188 }
1189
1190 #[test]
1192 fn a_copy_too_large_to_unroll_becomes_a_call_to_the_runtime() {
1193 let size = u64::try_from(UNROLL).expect("a small threshold") + 1;
1194 let (mut names, mut func) = copying(size, 1);
1195 bulk(&mut func, &mut names, 8);
1196 let text = printed(&func, &mut names);
1197 assert!(text.contains("call @memcpy"), "a call and not a bulk move: {text}");
1198
1199 let (mut names, mut func) = copying(size - 1, 1);
1202 bulk(&mut func, &mut names, 8);
1203 assert!(!printed(&func, &mut names).contains("memcpy"), "one word under it is unrolled");
1204 }
1205
1206 #[test]
1209 fn the_call_passes_the_size_that_the_instruction_carried_beside_it() {
1210 let size = u64::try_from(UNROLL).expect("a small threshold") + 1;
1211 let (mut names, mut func) = copying(size, 1);
1212 bulk(&mut func, &mut names, 8);
1213 let text = printed(&func, &mut names);
1214 assert!(text.contains(&format!("{size}")), "the size is an argument now: {text}");
1215 }
1216
1217 #[test]
1220 fn a_move_is_a_call_however_small_it_is() {
1221 let (mut names, mut func) = moving(Opcode::Memmove, 8, 8, None);
1222 bulk(&mut func, &mut names, 8);
1223 let text = printed(&func, &mut names);
1224 assert!(text.contains("call @memmove"), "a call and not a run of moves: {text}");
1225 }
1226
1227 #[test]
1230 fn a_fill_whose_byte_is_not_a_constant_becomes_a_call() {
1231 let (mut names, mut func) = one(&[Type::PTR, Type::int(8)], &[], |build, args| {
1232 let mem = build.func().add_mem(access(8, 8));
1233 let operands = build.func().push_values(&[args[0], args[1]]);
1234 let data = InstData {
1235 args: operands,
1236 extra: Extra::Mem(mem),
1237 ..InstData::new(Opcode::Memset)
1238 };
1239 build.inst(data, &[]);
1240 build.ret(&[]);
1241 });
1242 bulk(&mut func, &mut names, 8);
1243 let text = printed(&func, &mut names);
1244 assert!(text.contains("call @memset"), "a call and not a run of stores: {text}");
1245 assert!(text.contains("zext.i32"), "the byte is widened to what C passes: {text}");
1247 }
1248
1249 #[test]
1252 fn no_word_is_wider_than_the_machine_moves_at_once() {
1253 assert_eq!(chunks(access(8, 8), 4).map(|plan| plan.len()), Some(2));
1254 assert_eq!(chunks(access(8, 8), 8).map(|plan| plan.len()), Some(1));
1255 }
1256
1257 #[test]
1258 fn what_a_copy_becomes_is_ir_that_verifies() {
1259 let (mut names, mut func) = copying(13, 8);
1260 bulk(&mut func, &mut names, 8);
1261 let module = Module::new(names.intern("c.c"), &target());
1262 rucc_ir::verify_func(&module, &func, &names).expect("the rewrite builds valid IR");
1263 }
1264
1265 #[test]
1266 fn what_a_fill_becomes_is_ir_that_verifies() {
1267 let (mut names, mut func) = filling(13, 8, 0xff);
1268 bulk(&mut func, &mut names, 8);
1269 let module = Module::new(names.intern("f.c"), &target());
1270 rucc_ir::verify_func(&module, &func, &names).expect("the rewrite builds valid IR");
1271 }
1272
1273 #[test]
1274 fn what_a_copy_too_large_to_unroll_becomes_is_ir_that_verifies() {
1275 let size = u64::try_from(UNROLL).expect("a small threshold") + 1;
1276 let (mut names, mut func) = copying(size, 1);
1277 bulk(&mut func, &mut names, 8);
1278 let module = Module::new(names.intern("c.c"), &target());
1279 rucc_ir::verify_func(&module, &func, &names).expect("the call is valid IR");
1280 }
1281
1282 #[test]
1284 fn a_function_with_no_bulk_move_in_it_is_left_exactly_as_it_was() {
1285 let (mut names, mut func) = one(&[Type::int(32)], &[Type::int(32)], |build, args| {
1286 build.ret(&[args[0]]);
1287 });
1288 let before = printed(&func, &mut names);
1289 bulk(&mut func, &mut names, 8);
1290 assert_eq!(printed(&func, &mut names), before);
1291 }
1292
1293 fn swapping(width: u32) -> (Interner, Func) {
1296 let ty = Type::int(width);
1297 one(&[ty], &[ty], |build, args| {
1298 let s = build.unary(Opcode::Bswap, args[0], ty);
1299 build.ret(&[s]);
1300 })
1301 }
1302
1303 #[test]
1310 fn the_masks_are_the_alternating_runs_of_the_group_being_swapped() {
1311 assert_eq!(alternating(32, 16), 0x0000_ffff);
1312 assert_eq!(alternating(32, 8), 0x00ff_00ff);
1313 assert_eq!(alternating(16, 8), 0x00ff);
1314 assert_eq!(alternating(64, 32), 0x0000_0000_ffff_ffff);
1315 assert_eq!(alternating(64, 16), 0x0000_ffff_0000_ffff);
1316 assert_eq!(alternating(64, 8), 0x00ff_00ff_00ff_00ff);
1317 }
1318
1319 #[test]
1321 fn a_two_byte_swap_is_one_exchange_of_neighbouring_bytes() {
1322 let (mut names, mut func) = swapping(16);
1323 bytes(&mut func);
1324
1325 let text = printed(&func, &mut names);
1326 assert!(!text.contains("bswap"), "the instruction is gone: {text}");
1327 assert!(text.contains("iconst.i16 255"), "the low byte of the pair: {text}");
1328 assert_eq!(text.matches("shl").count(), 1, "one shift up: {text}");
1329 assert_eq!(text.matches("lshr").count(), 1, "one shift down: {text}");
1330 assert_eq!(text.matches(" or ").count(), 1, "and the two put together: {text}");
1331 }
1332
1333 #[test]
1336 fn a_wider_swap_is_the_same_exchange_once_per_halving() {
1337 for (width, steps) in [(16u32, 1usize), (32, 2), (64, 3)] {
1338 let (mut names, mut func) = swapping(width);
1339 bytes(&mut func);
1340 let text = printed(&func, &mut names);
1341 assert_eq!(text.matches("shl").count(), steps, "at {width}: {text}");
1342 assert_eq!(text.matches("lshr").count(), steps, "at {width}: {text}");
1343 assert_eq!(text.matches(" and ").count(), steps * 2, "at {width}: {text}");
1344 assert_eq!(text.matches(" or ").count(), steps, "at {width}: {text}");
1345 }
1346 }
1347
1348 #[test]
1351 fn the_shift_counts_are_the_group_width_halving_as_it_goes() {
1352 let (mut names, mut func) = swapping(64);
1353 bytes(&mut func);
1354 let text = printed(&func, &mut names);
1355 for count in ["iconst.i64 32", "iconst.i64 16", "iconst.i64 8"] {
1356 assert!(text.contains(count), "{count} is a step: {text}");
1357 }
1358 }
1359
1360 #[test]
1363 fn what_a_byte_swap_becomes_is_ir_that_verifies() {
1364 let (mut names, mut func) = swapping(32);
1365 bytes(&mut func);
1366 let module = Module::new(names.intern("b.c"), &target());
1367 rucc_ir::verify_func(&module, &func, &names).expect("the rewrite builds valid IR");
1368 }
1369
1370 #[test]
1373 fn a_function_with_no_byte_swap_in_it_is_left_exactly_as_it_was() {
1374 let (mut names, mut func) = one(&[Type::int(32)], &[Type::int(32)], |build, args| {
1375 build.ret(&[args[0]]);
1376 });
1377 let before = printed(&func, &mut names);
1378 bytes(&mut func);
1379 assert_eq!(printed(&func, &mut names), before);
1380 }
1381
1382 fn counting(op: Opcode, width: u32) -> (Interner, Func) {
1384 let ty = Type::int(width);
1385 one(&[ty], &[ty], |build, args| {
1386 let c = build.unary(op, args[0], ty);
1387 build.ret(&[c]);
1388 })
1389 }
1390
1391 #[test]
1394 fn the_counting_masks_are_the_ones_the_halving_sum_is_written_with() {
1395 assert_eq!(alternating(32, 1), 0x5555_5555);
1396 assert_eq!(alternating(32, 2), 0x3333_3333);
1397 assert_eq!(alternating(32, 4), 0x0f0f_0f0f);
1398 assert_eq!(every(32, 8, 1), 0x0101_0101);
1399 assert_eq!(every(64, 8, 1), 0x0101_0101_0101_0101);
1400 }
1401
1402 #[test]
1405 fn a_set_bit_count_is_the_halving_sum_and_a_multiply_that_adds_the_bytes() {
1406 let (mut names, mut func) = counting(Opcode::Ctpop, 32);
1407 counts(&mut func);
1408
1409 let text = printed(&func, &mut names);
1410 assert!(!text.contains("ctpop"), "the instruction is gone: {text}");
1411 assert!(text.contains("iconst.i32 1431655765"), "the pairs mask: {text}");
1412 assert!(text.contains("iconst.i32 858993459"), "the nibbles mask: {text}");
1413 assert!(text.contains("iconst.i32 252645135"), "the bytes mask: {text}");
1414 assert_eq!(text.matches(" mul ").count(), 1, "one multiply: {text}");
1415 assert!(text.contains("iconst.i32 24"), "and the top byte is the answer: {text}");
1416 }
1417
1418 #[test]
1420 fn a_count_of_one_byte_stops_before_the_multiply() {
1421 let (mut names, mut func) = counting(Opcode::Ctpop, 8);
1422 counts(&mut func);
1423 let text = printed(&func, &mut names);
1424 assert!(!text.contains("ctpop"), "{text}");
1425 assert!(!text.contains(" mul "), "nothing to add together: {text}");
1426 }
1427
1428 #[test]
1431 fn a_leading_zero_count_smears_the_value_down_and_counts_the_complement() {
1432 let (mut names, mut func) = counting(Opcode::Ctlz, 32);
1433 counts(&mut func);
1434
1435 let text = printed(&func, &mut names);
1436 assert!(!text.contains("ctlz"), "the instruction is gone: {text}");
1437 assert!(!text.contains("ctpop"), "and so is the count it became: {text}");
1438 for by in ["iconst.i32 1", "iconst.i32 2", "iconst.i32 4", "iconst.i32 8", "iconst.i32 16"]
1439 {
1440 assert!(text.contains(by), "{by} is a smearing step: {text}");
1441 }
1442 assert_eq!(text.matches(" xor ").count(), 1, "one complement: {text}");
1443 }
1444
1445 #[test]
1447 fn a_trailing_zero_count_masks_the_bits_below_the_lowest_set_one() {
1448 let (mut names, mut func) = counting(Opcode::Cttz, 32);
1449 counts(&mut func);
1450
1451 let text = printed(&func, &mut names);
1452 assert!(!text.contains("cttz"), "the instruction is gone: {text}");
1453 assert!(!text.contains("ctpop"), "and so is the count it became: {text}");
1454 assert!(text.contains("iconst.i32 -1"), "the complement and the decrement: {text}");
1455 assert_eq!(text.matches(" xor ").count(), 1, "one complement: {text}");
1456 assert!(text.matches(" or ").count() <= 1, "no smearing run: {text}");
1458 }
1459
1460 #[test]
1463 fn what_a_bit_count_becomes_is_ir_that_verifies() {
1464 for op in [Opcode::Ctpop, Opcode::Ctlz, Opcode::Cttz] {
1465 for width in [8u32, 16, 32, 64] {
1466 let (mut names, mut func) = counting(op, width);
1467 counts(&mut func);
1468 let module = Module::new(names.intern("c.c"), &target());
1469 rucc_ir::verify_func(&module, &func, &names)
1470 .unwrap_or_else(|e| panic!("{op:?} at {width}: {e:?}"));
1471 }
1472 }
1473 }
1474
1475 #[test]
1479 fn a_width_the_halving_sum_is_not_written_for_is_left_alone() {
1480 let (mut names, mut func) = counting(Opcode::Ctpop, 24);
1481 counts(&mut func);
1482 assert!(printed(&func, &mut names).contains("ctpop"), "left as it was");
1483 }
1484
1485 #[test]
1487 fn a_function_with_no_bit_count_in_it_is_left_exactly_as_it_was() {
1488 let (mut names, mut func) = one(&[Type::int(32)], &[Type::int(32)], |build, args| {
1489 build.ret(&[args[0]]);
1490 });
1491 let before = printed(&func, &mut names);
1492 counts(&mut func);
1493 assert_eq!(printed(&func, &mut names), before);
1494 }
1495}