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 const UNROLL: usize = 32;
253
254pub fn bulk(func: &mut Func, names: &mut Interner, word: u32) {
265 let found: Vec<Inst> =
266 func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
267 for inst in found {
268 match func[inst].opcode {
269 Opcode::Memcpy => copy(func, names, inst, word),
270 Opcode::Memset => fill(func, names, inst, word),
271 Opcode::Memmove => library(func, names, inst, "memmove", word),
272 _ => {}
273 }
274 }
275}
276
277fn copy(func: &mut Func, names: &mut Interner, inst: Inst, word: u32) {
285 let [into, from] = func[func[inst].args] else { return };
286 let Extra::Mem(mem) = func[inst].extra else { return };
287 let info = func[mem];
288 let Some(plan) = chunks(info, word) else { return library(func, names, inst, "memcpy", word) };
289 for (at, width) in plan {
290 let ty = Type::int(width * 8);
291 let access = MemInfo { size: u64::from(width), align: width.min(info.align), ..info };
292 let there = stepped(func, inst, from, at);
293 let word = read(func, inst, there, access, ty);
294 let here = stepped(func, inst, into, at);
295 write(func, inst, word, here, access);
296 }
297 func.remove_inst(inst);
298}
299
300fn fill(func: &mut Func, names: &mut Interner, inst: Inst, word: u32) {
307 let [into, byte] = func[func[inst].args] else { return };
308 let Extra::Mem(mem) = func[inst].extra else { return };
309 let info = func[mem];
310 let Some(spelled) = literal(func, byte) else {
311 return library(func, names, inst, "memset", word);
312 };
313 let Some(plan) = chunks(info, word) else { return library(func, names, inst, "memset", word) };
314 for (at, width) in plan {
315 let ty = Type::int(width * 8);
316 let access = MemInfo { size: u64::from(width), align: width.min(info.align), ..info };
317 let value = ahead_const(func, inst, Imm::int(spread(spelled, width) as i128, ty), ty);
318 let here = stepped(func, inst, into, at);
319 write(func, inst, value, here, access);
320 }
321 func.remove_inst(inst);
322}
323
324fn library(func: &mut Func, names: &mut Interner, inst: Inst, routine: &str, word: u32) {
337 let [into, second] = func[func[inst].args] else { return };
338 let Extra::Mem(mem) = func[inst].extra else { return };
339 let size = func[mem].size;
340
341 let words = Type::int(word * 8);
345 let count = ahead_const(func, inst, Imm::int(i128::from(size), words), words);
346 let second = match routine {
349 "memset" => widened(func, inst, second),
350 _ => second,
351 };
352
353 let sig = func.add_signature(Signature::new().with_params(&[
354 Type::PTR,
355 if routine == "memset" { Type::int(32) } else { Type::PTR },
356 words,
357 ]));
358 let callee = names.intern(routine);
359 let varargs = func.push_abis(&[]);
360 let info = func.add_call(CallInfo { callee: Some(callee), signature: sig, varargs });
361 let args = func.push_values(&[into, second, count]);
362 let data = &mut func[inst];
363 data.opcode = Opcode::Call;
364 data.args = args;
365 data.extra = Extra::Call(info);
366 data.flags = data.flags.intersection(Flags::legal_on(Opcode::Call));
367}
368
369fn widened(func: &mut Func, inst: Inst, value: Value) -> Value {
371 let int = Type::int(32);
372 let ty = func[value].ty;
373 if ty == int {
374 return value;
375 }
376 ahead(func, inst, Opcode::ZExt, &[value], int)
377}
378
379fn chunks(info: MemInfo, word: u32) -> Option<Vec<(u64, u32)>> {
392 plan(info.size, info.align, word)
393}
394
395pub(crate) fn plan(size: u64, align: u32, word: u32) -> Option<Vec<(u64, u32)>> {
403 let widest = word.min(align).max(1);
404 if !widest.is_power_of_two() {
405 return None;
406 }
407 let mut plan = Vec::new();
408 let mut at = 0;
409 let mut width = u64::from(widest);
410 while at < size {
411 while width > size - at {
412 width /= 2;
413 }
414 plan.push((at, u32::try_from(width).ok()?));
415 at += width;
416 if plan.len() > UNROLL {
417 return None;
418 }
419 }
420 Some(plan)
421}
422
423fn literal(func: &Func, value: Value) -> Option<u8> {
425 let Def::Result { inst, .. } = func[value].def else { return None };
426 if func[inst].opcode != Opcode::IConst {
427 return None;
428 }
429 let Extra::Imm(imm) = func[inst].extra else { return None };
430 u8::try_from(func[imm].bits() & 0xff).ok()
431}
432
433fn spread(byte: u8, width: u32) -> u64 {
435 (0..width).fold(0, |word, at| word | u64::from(byte) << (at * 8))
436}
437
438fn stepped(func: &mut Func, inst: Inst, block: Value, at: u64) -> Value {
441 if at == 0 {
442 return block;
443 }
444 let step = ahead_const(func, inst, Imm::int(i128::from(at), Type::int(64)), Type::int(64));
445 ahead(func, inst, Opcode::PtrAdd, &[block, step], Type::PTR)
446}
447
448fn read(func: &mut Func, inst: Inst, from: Value, info: MemInfo, ty: Type) -> Value {
450 let extra = Extra::Mem(func.add_mem(info));
451 let args = func.push_values(&[from]);
452 written(func, inst, InstData { args, extra, ..InstData::new(Opcode::Load) }, ty)
453}
454
455fn write(func: &mut Func, inst: Inst, value: Value, into: Value, info: MemInfo) {
457 let span = func.span(inst);
458 let extra = Extra::Mem(func.add_mem(info));
459 let args = func.push_values(&[value, into]);
460 let data = InstData { args, extra, ..InstData::new(Opcode::Store) };
461 let made = func.create_inst(data, &[], span);
462 func.insert_before(made, inst);
463}
464
465fn holder(bits: u32, signed: bool) -> Option<u32> {
474 match if signed { bits } else { bits + 1 } {
475 ..=32 => Some(32),
476 33..=64 => Some(64),
477 _ => None,
478 }
479}
480
481fn produced(func: &Func, inst: Inst) -> Type {
486 func[inst].first_result.map_or(Type::VOID, |value| func[value].ty)
487}
488
489fn ahead(func: &mut Func, inst: Inst, opcode: Opcode, args: &[Value], ty: Type) -> Value {
491 let args = func.push_values(args);
492 written(func, inst, InstData { args, ..InstData::new(opcode) }, ty)
493}
494
495fn ahead_const(func: &mut Func, inst: Inst, imm: Imm, ty: Type) -> Value {
497 let extra = Extra::Imm(func.add_imm(imm));
498 written(func, inst, InstData { extra, ..InstData::new(Opcode::IConst) }, ty)
499}
500
501fn written(func: &mut Func, inst: Inst, data: InstData, ty: Type) -> Value {
503 let span = func.span(inst);
504 let made = func.create_inst(data, &[ty], span);
505 func.insert_before(made, inst);
506 func[made].first_result.expect("an instruction created with one result has one")
507}
508
509fn becomes(func: &mut Func, inst: Inst, opcode: Opcode, args: &[Value]) {
516 let args = func.push_values(args);
517 let data = &mut func[inst];
518 data.opcode = opcode;
519 data.args = args;
520 data.extra = Extra::None;
521 data.flags = data.flags.intersection(Flags::legal_on(opcode));
524}
525
526#[must_use]
531pub fn blocks_for(cases: usize) -> usize {
532 cases.saturating_sub(1)
533}
534
535#[cfg(test)]
536mod tests {
537 use rucc_base::Interner;
538 use rucc_ir::{Builder, Flags, Float, Func, Module, Opcode, Signature, Type};
539 use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
540
541 use rucc_ir::{Extra, InstData, MemInfo, MemOrder};
542
543 use super::{UNROLL, blocks_for, bulk, chunks, floats, spread, switches};
544
545 fn target() -> TargetInfo {
546 TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu))
547 }
548
549 fn built(cases: &[i128]) -> (Interner, Func) {
552 let mut names = Interner::new();
553 let int = Type::int(32);
554 let mut func = Func::new(
555 names.intern("sw"),
556 Signature::new().with_params(&[int]).with_returns(&[int]),
557 );
558 let entry = func.create_block();
559 let x = func.append_param(entry, int);
560
561 let default = func.create_block();
562 let arms: Vec<_> = cases.iter().map(|_| func.create_block()).collect();
563 let table: Vec<(i128, rucc_ir::Block)> =
564 cases.iter().copied().zip(arms.iter().copied()).collect();
565 Builder::new(&mut func, entry).switch(x, default, &table);
566
567 for (index, &arm) in arms.iter().enumerate() {
568 let mut build = Builder::new(&mut func, arm);
569 let what = i128::try_from(index).expect("a small number of cases");
570 let v = build.iconst(int, (what + 1) * 10);
571 build.ret(&[v]);
572 }
573 let mut build = Builder::new(&mut func, default);
574 let v = build.iconst(int, 30);
575 build.ret(&[v]);
576 (names, func)
577 }
578
579 fn count(func: &Func) -> usize {
580 func.blocks().count()
581 }
582
583 fn printed(func: &Func, names: &mut Interner) -> String {
584 let module = Module::new(names.intern("sw.c"), &target());
585 rucc_ir::print_func(&module, func, names)
586 }
587
588 #[test]
589 fn a_switch_becomes_a_compare_and_a_branch_for_each_case() {
590 let (mut names, mut func) = built(&[1, 2]);
591 let before = count(&func);
592 switches(&mut func);
593 assert_eq!(count(&func), before + blocks_for(2));
594
595 let text = printed(&func, &mut names);
596 assert!(!text.contains("switch"), "the switch is gone: {text}");
597 assert_eq!(text.matches("icmp eq").count(), 2, "one compare per case: {text}");
598 assert_eq!(text.matches("br_if").count(), 2, "one branch per case: {text}");
599 }
600
601 #[test]
602 fn the_last_case_falls_to_the_default_rather_than_to_a_block_of_its_own() {
603 let (_, mut func) = built(&[7]);
604 let before = count(&func);
605 switches(&mut func);
606 assert_eq!(count(&func), before);
608 assert_eq!(blocks_for(1), 0);
609 }
610
611 #[test]
612 fn a_switch_with_only_a_default_is_a_jump() {
613 let (_, mut func) = built(&[]);
614 switches(&mut func);
615 let entry = func.entry().expect("an entry block");
616 let term = func.terminator(entry).expect("a terminator");
617 assert_eq!(func[term].opcode, Opcode::Jump);
618 }
619
620 #[test]
623 fn what_comes_out_is_valid_ir() {
624 let (mut names, mut func) = built(&[1, 2, 3, 4]);
625 switches(&mut func);
626 let module = Module::new(names.intern("sw.c"), &target());
627 rucc_ir::verify_func(&module, &func, &names).expect("the rewrite builds valid IR");
628 }
629
630 #[test]
633 fn a_function_with_no_switch_is_left_exactly_as_it_was() {
634 let mut names = Interner::new();
635 let int = Type::int(32);
636 let mut func =
637 Func::new(names.intern("f"), Signature::new().with_params(&[int]).with_returns(&[int]));
638 let entry = func.create_block();
639 let x = func.append_param(entry, int);
640 Builder::new(&mut func, entry).ret(&[x]);
641
642 let before = printed(&func, &mut names);
643 switches(&mut func);
644 assert_eq!(printed(&func, &mut names), before);
645 }
646
647 fn one(
652 params: &[Type],
653 returns: &[Type],
654 body: impl FnOnce(&mut Builder<'_>, &[rucc_ir::Value]),
655 ) -> (Interner, Func) {
656 let mut names = Interner::new();
657 let mut func = Func::new(
658 names.intern("f"),
659 Signature::new().with_params(params).with_returns(returns),
660 );
661 let entry = func.create_block();
662 let args: Vec<_> = params.iter().map(|&ty| func.append_param(entry, ty)).collect();
663 let mut build = Builder::new(&mut func, entry);
664 body(&mut build, &args);
665 (names, func)
666 }
667
668 fn f64() -> Type {
669 Type::float(Float::F64)
670 }
671
672 fn f32() -> Type {
673 Type::float(Float::F32)
674 }
675
676 #[test]
678 fn a_float_constant_becomes_the_integer_that_spells_it_and_a_reading_of_those_bits() {
679 let (mut names, mut func) = one(&[], &[f64()], |build, _| {
680 let k = build.fconst(f64(), 0x3ff8_0000_0000_0000);
681 build.ret(&[k]);
682 });
683 floats(&mut func);
684
685 let text = printed(&func, &mut names);
686 assert!(!text.contains("fconst"), "the float constant is gone: {text}");
687 assert!(text.contains("iconst.i64 4609434218613702656"), "the bits, as an integer: {text}");
688 assert!(text.contains("bitcast"), "read back as the float: {text}");
689 }
690
691 #[test]
694 fn a_constant_at_the_narrow_format_is_an_integer_of_the_narrow_width() {
695 let (mut names, mut func) = one(&[], &[f32()], |build, _| {
696 let k = build.fconst(f32(), 0x4020_0000);
697 build.ret(&[k]);
698 });
699 floats(&mut func);
700 assert!(printed(&func, &mut names).contains("iconst.i32"), "an i32, not an i64");
701 }
702
703 #[test]
706 fn a_negation_flips_the_sign_bit_and_touches_no_other() {
707 let (mut names, mut func) = one(&[f64()], &[f64()], |build, args| {
708 let n = build.unary(Opcode::FNeg, args[0], f64());
709 build.ret(&[n]);
710 });
711 floats(&mut func);
712
713 let text = printed(&func, &mut names);
714 assert!(!text.contains("fneg"), "the negation is gone: {text}");
715 assert!(!text.contains("fsub"), "and it did not become a subtraction: {text}");
716 assert!(text.contains("iconst.i64 -9223372036854775808"), "the sign bit alone: {text}");
717 assert_eq!(text.matches("xor").count(), 1, "one exclusive or: {text}");
718 assert_eq!(text.matches("bitcast").count(), 2, "there and back: {text}");
719 }
720
721 #[test]
723 fn an_unsigned_integer_becoming_a_float_widens_first_and_then_converts_as_signed() {
724 let (mut names, mut func) = one(&[Type::int(32)], &[f64()], |build, args| {
725 let d = build.unary(Opcode::UIToFP, args[0], f64());
726 build.ret(&[d]);
727 });
728 floats(&mut func);
729
730 let text = printed(&func, &mut names);
731 assert!(!text.contains("uitofp"), "the unsigned conversion is gone: {text}");
732 assert!(text.contains("zext.i64"), "widened with zeroes: {text}");
733 assert!(text.contains("sitofp.f64"), "converted as signed: {text}");
734 }
735
736 #[test]
738 fn a_float_becoming_an_unsigned_integer_converts_as_signed_first_and_then_narrows() {
739 let (mut names, mut func) = one(&[f64()], &[Type::int(32)], |build, args| {
740 let n = build.unary(Opcode::FPToUI, args[0], Type::int(32));
741 build.ret(&[n]);
742 });
743 floats(&mut func);
744
745 let text = printed(&func, &mut names);
746 assert!(!text.contains("fptoui"), "the unsigned conversion is gone: {text}");
747 assert!(text.contains("fptosi.i64"), "converted as signed: {text}");
748 assert!(text.contains("trunc.i32"), "and narrowed to what was asked: {text}");
749 }
750
751 #[test]
754 fn a_conversion_narrower_than_the_machine_has_is_one_it_has_and_a_narrowing() {
755 let (mut names, mut func) = one(&[f64()], &[Type::int(8)], |build, args| {
756 let n = build.unary(Opcode::FPToSI, args[0], Type::int(8));
757 build.ret(&[n]);
758 });
759 floats(&mut func);
760
761 let text = printed(&func, &mut names);
762 assert!(text.contains("fptosi.i32"), "converted at a width there is one at: {text}");
763 assert!(text.contains("trunc.i8"), "and narrowed to what was asked: {text}");
764 }
765
766 #[test]
768 fn a_signed_integer_narrower_than_the_machine_converts_from_is_widened_with_its_sign() {
769 let (mut names, mut func) = one(&[Type::int(8)], &[f64()], |build, args| {
770 let d = build.unary(Opcode::SIToFP, args[0], f64());
771 build.ret(&[d]);
772 });
773 floats(&mut func);
774
775 let text = printed(&func, &mut names);
776 assert!(text.contains("sext.i32"), "widened with the sign and not with zeroes: {text}");
777 assert!(!text.contains("zext"), "widened with the sign and not with zeroes: {text}");
778 assert!(text.contains("sitofp.f64"), "converted at a width there is one at: {text}");
779 }
780
781 #[test]
783 fn the_width_a_conversion_happens_at_is_the_narrowest_one_that_holds_the_values() {
784 use super::holder;
785 for bits in [1, 8, 16, 32] {
786 assert_eq!(holder(bits, true), Some(32), "a signed {bits} bit value fits in an int");
787 }
788 assert_eq!(holder(64, true), Some(64));
789 for bits in [1, 8, 16, 31] {
790 assert_eq!(holder(bits, false), Some(32), "an unsigned {bits} bit value does too");
791 }
792 assert_eq!(holder(32, false), Some(64));
794 assert_eq!(holder(64, false), None);
795 }
796
797 #[test]
801 fn the_unsigned_conversions_at_the_widest_width_are_left_alone() {
802 let (mut names, mut func) = one(&[Type::int(64)], &[f64()], |build, args| {
803 let d = build.unary(Opcode::UIToFP, args[0], f64());
804 build.ret(&[d]);
805 });
806 let before = printed(&func, &mut names);
807 floats(&mut func);
808 assert_eq!(printed(&func, &mut names), before);
809
810 let (mut names, mut func) = one(&[f64()], &[Type::int(64)], |build, args| {
811 let n = build.unary(Opcode::FPToUI, args[0], Type::int(64));
812 build.ret(&[n]);
813 });
814 let before = printed(&func, &mut names);
815 floats(&mut func);
816 assert_eq!(printed(&func, &mut names), before);
817 }
818
819 #[test]
822 fn what_the_float_rewrites_leave_is_valid_ir() {
823 let (mut names, mut func) = one(&[Type::int(32)], &[f64()], |build, args| {
824 let k = build.fconst(f64(), 0x3ff8_0000_0000_0000);
825 let d = build.unary(Opcode::UIToFP, args[0], f64());
826 let n = build.unary(Opcode::FNeg, d, f64());
827 let s = build.binary(Opcode::FAdd, n, k, Flags::NONE);
828 build.ret(&[s]);
829 });
830 floats(&mut func);
831 let module = Module::new(names.intern("f.c"), &target());
832 rucc_ir::verify_func(&module, &func, &names).expect("the rewrite builds valid IR");
833 }
834
835 #[test]
838 fn a_function_with_no_floats_in_it_is_left_exactly_as_it_was() {
839 let (mut names, mut func) = one(&[Type::int(32)], &[Type::int(32)], |build, args| {
840 build.ret(&[args[0]]);
841 });
842 let before = printed(&func, &mut names);
843 floats(&mut func);
844 assert_eq!(printed(&func, &mut names), before);
845 }
846 fn access(size: u64, align: u32) -> MemInfo {
847 MemInfo { size, align, order: MemOrder::NotAtomic, tbaa: None }
848 }
849
850 fn moving(opcode: Opcode, size: u64, align: u32, byte: Option<i128>) -> (Interner, Func) {
853 one(&[Type::PTR, Type::PTR], &[], |build, args| {
854 let second = match byte {
855 Some(value) => build.iconst(Type::int(8), value),
856 None => args[1],
857 };
858 let mem = build.func().add_mem(access(size, align));
859 let operands = build.func().push_values(&[args[0], second]);
860 let data = InstData { args: operands, extra: Extra::Mem(mem), ..InstData::new(opcode) };
861 build.inst(data, &[]);
862 build.ret(&[]);
863 })
864 }
865
866 fn copying(size: u64, align: u32) -> (Interner, Func) {
867 moving(Opcode::Memcpy, size, align, None)
868 }
869
870 fn filling(size: u64, align: u32, byte: i128) -> (Interner, Func) {
871 moving(Opcode::Memset, size, align, Some(byte))
872 }
873
874 fn widths(size: u64, align: u32) -> Option<Vec<u32>> {
877 Some(chunks(access(size, align), 8)?.into_iter().map(|(_, width)| width).collect())
878 }
879
880 #[test]
882 fn a_copy_becomes_a_load_and_a_store_for_each_word_of_it() {
883 let (mut names, mut func) = copying(16, 8);
884 bulk(&mut func, &mut names, 8);
885
886 let text = printed(&func, &mut names);
887 assert!(!text.contains("memcpy"), "the copy is gone: {text}");
888 assert_eq!(text.matches("load.i64").count(), 2, "a load per word: {text}");
889 assert_eq!(text.matches("store").count(), 2, "a store per word: {text}");
890 assert_eq!(
891 text.matches("ptr_add").count(),
892 2,
893 "no offset for the word at the front: {text}"
894 );
895 }
896
897 #[test]
901 fn a_word_is_as_wide_as_the_block_is_aligned_to() {
902 assert_eq!(widths(16, 8), Some(vec![8, 8]));
903 assert_eq!(widths(16, 4), Some(vec![4, 4, 4, 4]));
904 assert_eq!(widths(4, 1), Some(vec![1, 1, 1, 1]));
905 }
906
907 #[test]
910 fn what_is_left_over_is_narrower_words_and_not_a_run_of_bytes() {
911 assert_eq!(widths(13, 8), Some(vec![8, 4, 1]));
912 assert_eq!(widths(3, 8), Some(vec![2, 1]));
913 assert_eq!(widths(1, 8), Some(vec![1]));
914 }
915
916 #[test]
919 fn every_word_starts_somewhere_it_is_aligned_for() {
920 for (at, width) in chunks(access(13, 8), 8).expect("a plan for thirteen bytes") {
921 assert_eq!(at % u64::from(width), 0, "{at} is a multiple of {width}");
922 }
923 }
924
925 #[test]
927 fn a_fill_is_the_byte_spread_across_each_word() {
928 let (mut names, mut func) = filling(16, 8, 0);
929 bulk(&mut func, &mut names, 8);
930
931 let text = printed(&func, &mut names);
932 assert!(!text.contains("memset"), "the fill is gone: {text}");
933 assert_eq!(text.matches("store").count(), 2, "a store per word: {text}");
934 assert!(!text.contains("load"), "a fill reads nothing: {text}");
935 }
936
937 #[test]
940 fn the_byte_is_repeated_across_the_word_it_is_stored_as() {
941 assert_eq!(spread(0, 8), 0);
942 assert_eq!(spread(0xff, 1), 0xff);
943 assert_eq!(spread(0xff, 4), 0xffff_ffff);
944 assert_eq!(spread(0xab, 2), 0xabab);
945 assert_eq!(spread(0xab, 8), 0xabab_abab_abab_abab);
946 }
947
948 #[test]
950 fn a_copy_too_large_to_unroll_becomes_a_call_to_the_runtime() {
951 let size = u64::try_from(UNROLL).expect("a small threshold") + 1;
952 let (mut names, mut func) = copying(size, 1);
953 bulk(&mut func, &mut names, 8);
954 let text = printed(&func, &mut names);
955 assert!(text.contains("call @memcpy"), "a call and not a bulk move: {text}");
956
957 let (mut names, mut func) = copying(size - 1, 1);
960 bulk(&mut func, &mut names, 8);
961 assert!(!printed(&func, &mut names).contains("memcpy"), "one word under it is unrolled");
962 }
963
964 #[test]
967 fn the_call_passes_the_size_that_the_instruction_carried_beside_it() {
968 let size = u64::try_from(UNROLL).expect("a small threshold") + 1;
969 let (mut names, mut func) = copying(size, 1);
970 bulk(&mut func, &mut names, 8);
971 let text = printed(&func, &mut names);
972 assert!(text.contains(&format!("{size}")), "the size is an argument now: {text}");
973 }
974
975 #[test]
978 fn a_move_is_a_call_however_small_it_is() {
979 let (mut names, mut func) = moving(Opcode::Memmove, 8, 8, None);
980 bulk(&mut func, &mut names, 8);
981 let text = printed(&func, &mut names);
982 assert!(text.contains("call @memmove"), "a call and not a run of moves: {text}");
983 }
984
985 #[test]
988 fn a_fill_whose_byte_is_not_a_constant_becomes_a_call() {
989 let (mut names, mut func) = one(&[Type::PTR, Type::int(8)], &[], |build, args| {
990 let mem = build.func().add_mem(access(8, 8));
991 let operands = build.func().push_values(&[args[0], args[1]]);
992 let data = InstData {
993 args: operands,
994 extra: Extra::Mem(mem),
995 ..InstData::new(Opcode::Memset)
996 };
997 build.inst(data, &[]);
998 build.ret(&[]);
999 });
1000 bulk(&mut func, &mut names, 8);
1001 let text = printed(&func, &mut names);
1002 assert!(text.contains("call @memset"), "a call and not a run of stores: {text}");
1003 assert!(text.contains("zext.i32"), "the byte is widened to what C passes: {text}");
1005 }
1006
1007 #[test]
1010 fn no_word_is_wider_than_the_machine_moves_at_once() {
1011 assert_eq!(chunks(access(8, 8), 4).map(|plan| plan.len()), Some(2));
1012 assert_eq!(chunks(access(8, 8), 8).map(|plan| plan.len()), Some(1));
1013 }
1014
1015 #[test]
1016 fn what_a_copy_becomes_is_ir_that_verifies() {
1017 let (mut names, mut func) = copying(13, 8);
1018 bulk(&mut func, &mut names, 8);
1019 let module = Module::new(names.intern("c.c"), &target());
1020 rucc_ir::verify_func(&module, &func, &names).expect("the rewrite builds valid IR");
1021 }
1022
1023 #[test]
1024 fn what_a_fill_becomes_is_ir_that_verifies() {
1025 let (mut names, mut func) = filling(13, 8, 0xff);
1026 bulk(&mut func, &mut names, 8);
1027 let module = Module::new(names.intern("f.c"), &target());
1028 rucc_ir::verify_func(&module, &func, &names).expect("the rewrite builds valid IR");
1029 }
1030
1031 #[test]
1032 fn what_a_copy_too_large_to_unroll_becomes_is_ir_that_verifies() {
1033 let size = u64::try_from(UNROLL).expect("a small threshold") + 1;
1034 let (mut names, mut func) = copying(size, 1);
1035 bulk(&mut func, &mut names, 8);
1036 let module = Module::new(names.intern("c.c"), &target());
1037 rucc_ir::verify_func(&module, &func, &names).expect("the call is valid IR");
1038 }
1039
1040 #[test]
1042 fn a_function_with_no_bulk_move_in_it_is_left_exactly_as_it_was() {
1043 let (mut names, mut func) = one(&[Type::int(32)], &[Type::int(32)], |build, args| {
1044 build.ret(&[args[0]]);
1045 });
1046 let before = printed(&func, &mut names);
1047 bulk(&mut func, &mut names, 8);
1048 assert_eq!(printed(&func, &mut names), before);
1049 }
1050}