1use rucc_base::Interner;
60use rucc_ir::{
61 Abi, CallInfo, Extra, Flags, Float, FloatPred, Func, Imm, Inst, InstData, IntPred, MemInfo,
62 MemOrder, Opcode, Param, Restrict, Signature, Type, Value,
63};
64use rucc_target::AbiDescription;
65
66use crate::capability;
67
68fn routine(opcode: Opcode, mode: &str) -> &'static str {
74 capability::libcall(opcode, mode)
75 .unwrap_or_else(|| panic!("no routine for `{}` at `{mode}`", opcode.name()))
76}
77
78const QUAD: Float = Float::F128;
80
81const MODE: &str = "f128";
83
84const BITS: u32 = 128;
86const BYTES: u64 = (BITS / 8) as u64;
87
88const NARROW: u32 = 32;
91const WORD: u32 = 64;
92
93pub fn calls(func: &mut Func, names: &mut Interner, abi: &'static AbiDescription) {
98 let found: Vec<Inst> =
99 func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
100 for inst in found {
101 match func[inst].opcode {
102 Opcode::FAdd | Opcode::FSub | Opcode::FMul | Opcode::FDiv => {
103 arithmetic(func, names, abi, inst);
104 }
105 Opcode::FNeg => negate(func, names, abi, inst),
106 Opcode::FCmp => compare(func, names, abi, inst),
107 Opcode::FConst => constant(func, inst),
108 Opcode::FPExt => widen(func, names, abi, inst),
109 Opcode::FPTrunc => narrow(func, names, abi, inst),
110 Opcode::SIToFP | Opcode::UIToFP => from_integer(func, names, abi, inst),
111 Opcode::FPToSI | Opcode::FPToUI => to_integer(func, names, abi, inst),
112 _ => {}
113 }
114 }
115}
116
117fn quad(ty: Type) -> bool {
119 ty.is_scalar() && ty.format() == Some(QUAD)
120}
121
122fn produced(func: &Func, inst: Inst) -> Option<Type> {
124 func[inst].first_result.map(|value| func[value].ty)
125}
126
127fn arithmetic(func: &mut Func, names: &mut Interner, abi: &'static AbiDescription, inst: Inst) {
134 let Some(ty) = produced(func, inst) else { return };
135 if !quad(ty) {
136 return;
137 }
138 let args = func[func[inst].args].to_vec();
139 let [a, b] = args[..] else { return };
140 let opcode = func[inst].opcode;
143 let (Opcode::FAdd | Opcode::FSub | Opcode::FMul | Opcode::FDiv) = opcode else { return };
144 let Some(routine) = capability::libcall(opcode, MODE) else { return };
145 into_call(func, names, abi, inst, routine, &[a, b]);
146}
147
148fn negate(func: &mut Func, names: &mut Interner, abi: &'static AbiDescription, inst: Inst) {
155 let Some(ty) = produced(func, inst) else { return };
156 let Some(&arg) = func[func[inst].args].first() else { return };
157 if !quad(ty) {
158 return;
159 }
160 into_call(func, names, abi, inst, routine(Opcode::FNeg, MODE), &[arg]);
161}
162
163fn compare(func: &mut Func, names: &mut Interner, abi: &'static AbiDescription, inst: Inst) {
184 let args = func[func[inst].args].to_vec();
185 let [a, b] = args[..] else { return };
186 if !quad(func[a].ty) || !quad(func[b].ty) {
187 return;
188 }
189 let Extra::FloatPred(pred) = func[inst].extra else { return };
190 if let Some((routine, test)) = single(pred) {
191 let answer = call(func, names, abi, inst, routine, &[a, b], Type::int(NARROW));
192 let zero = ahead_const(func, inst, Imm::int(0, Type::int(NARROW)), Type::int(NARROW));
193 let extra = Extra::IntPred(test);
194 becomes(func, inst, Opcode::ICmp, extra, &[answer, zero]);
195 return;
196 }
197 if let FloatPred::False | FloatPred::True = pred {
201 let bits = u128::from(pred == FloatPred::True);
202 let extra = Extra::Imm(func.add_imm(Imm::int(bits as i128, Type::I1)));
203 becomes(func, inst, Opcode::IConst, extra, &[]);
204 return;
205 }
206 let (FloatPred::One | FloatPred::Ueq) = pred else { return };
207 let uno = routine(Opcode::FCmp, "uno.f128");
208 let une = routine(Opcode::FCmp, "une.f128");
209 let ordered = pair(func, names, abi, inst, uno, &[a, b], IntPred::Eq);
210 let different = pair(func, names, abi, inst, une, &[a, b], IntPred::Ne);
211 let (opcode, args) = if pred == FloatPred::One {
213 (Opcode::And, [ordered, different])
214 } else {
215 let unordered = flipped(func, inst, ordered);
216 let same = flipped(func, inst, different);
217 (Opcode::Or, [unordered, same])
218 };
219 becomes(func, inst, opcode, Extra::None, &args);
220}
221
222fn single(pred: FloatPred) -> Option<(&'static str, IntPred)> {
224 let (named, test) = match pred {
228 FloatPred::Oeq => ("oeq.f128", IntPred::Eq),
229 FloatPred::Une => ("une.f128", IntPred::Ne),
230 FloatPred::Olt => ("olt.f128", IntPred::Slt),
231 FloatPred::Ole => ("ole.f128", IntPred::Sle),
232 FloatPred::Ogt => ("ogt.f128", IntPred::Sgt),
233 FloatPred::Oge => ("oge.f128", IntPred::Sge),
234 FloatPred::Uno => ("uno.f128", IntPred::Ne),
235 FloatPred::Ord => ("uno.f128", IntPred::Eq),
236 FloatPred::Ult => ("oge.f128", IntPred::Slt),
238 FloatPred::Ule => ("ogt.f128", IntPred::Sle),
239 FloatPred::Ugt => ("ole.f128", IntPred::Sgt),
240 FloatPred::Uge => ("olt.f128", IntPred::Sge),
241 _ => return None,
242 };
243 Some((routine(Opcode::FCmp, named), test))
244}
245
246fn pair(
248 func: &mut Func,
249 names: &mut Interner,
250 abi: &'static AbiDescription,
251 inst: Inst,
252 routine: &str,
253 args: &[Value],
254 test: IntPred,
255) -> Value {
256 let answer = call(func, names, abi, inst, routine, args, Type::int(NARROW));
257 let zero = ahead_const(func, inst, Imm::int(0, Type::int(NARROW)), Type::int(NARROW));
258 let args = func.push_values(&[answer, zero]);
259 let extra = Extra::IntPred(test);
260 written(func, inst, InstData { args, extra, ..InstData::new(Opcode::ICmp) }, Type::I1)
261}
262
263fn flipped(func: &mut Func, inst: Inst, value: Value) -> Value {
265 let one = ahead_const(func, inst, Imm::int(1, Type::I1), Type::I1);
266 let args = func.push_values(&[value, one]);
267 written(func, inst, InstData { args, ..InstData::new(Opcode::Xor) }, Type::I1)
268}
269
270fn constant(func: &mut Func, inst: Inst) {
287 let Some(ty) = produced(func, inst) else { return };
288 let Extra::Imm(imm) = func[inst].extra else { return };
289 if !quad(ty) {
290 return;
291 }
292 let bits = func[imm].bits();
293 let whole = whole();
294 let slot = slot(func, inst);
295 let half = u64::from(WORD / 8);
296 let word = Type::int(WORD);
297 let low = ahead_const(func, inst, Imm::int(bits as i128, word), word);
298 write(func, inst, low, slot, MemInfo { size: half, ..whole });
299 let step = ahead_const(func, inst, Imm::int(half as i128, word), word);
300 let args = func.push_values(&[slot, step]);
301 let above = written(func, inst, InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR);
302 let high = ahead_const(func, inst, Imm::int((bits >> WORD) as i128, word), word);
303 write(func, inst, high, above, MemInfo { size: half, align: WORD / 8, ..whole });
304 let extra = Extra::Mem(func.add_mem(whole));
305 becomes(func, inst, Opcode::Load, extra, &[slot]);
306}
307
308fn widen(func: &mut Func, names: &mut Interner, abi: &'static AbiDescription, inst: Inst) {
314 let Some(ty) = produced(func, inst) else { return };
315 let Some(&arg) = func[func[inst].args].first() else { return };
316 if !quad(ty) {
317 return;
318 }
319 let mode = match func[arg].ty.format() {
320 Some(Float::F32) => "f32.f128",
321 Some(Float::F64) => "f64.f128",
322 _ => return,
323 };
324 let routine = routine(Opcode::FPExt, mode);
325 into_call(func, names, abi, inst, routine, &[arg]);
326}
327
328fn narrow(func: &mut Func, names: &mut Interner, abi: &'static AbiDescription, inst: Inst) {
330 let Some(ty) = produced(func, inst) else { return };
331 let Some(&arg) = func[func[inst].args].first() else { return };
332 if !quad(func[arg].ty) {
333 return;
334 }
335 let mode = match ty.format() {
336 Some(Float::F32) => "f128.f32",
337 Some(Float::F64) => "f128.f64",
338 _ => return,
339 };
340 let routine = routine(Opcode::FPTrunc, mode);
341 into_call(func, names, abi, inst, routine, &[arg]);
342}
343
344fn from_integer(func: &mut Func, names: &mut Interner, abi: &'static AbiDescription, inst: Inst) {
362 let Some(ty) = produced(func, inst) else { return };
363 let Some(&arg) = func[func[inst].args].first() else { return };
364 let from = func[arg].ty;
365 if !quad(ty) || !from.is_int() || !from.is_scalar() {
366 return;
367 }
368 let signed = func[inst].opcode == Opcode::SIToFP;
369 let Some(width) = holder(from.bits()) else { return };
370 let opcode = if signed { Opcode::SIToFP } else { Opcode::UIToFP };
371 let routine = routine(opcode, if width == NARROW { "i32.f128" } else { "i64.f128" });
372 let value = if from.bits() == width {
373 arg
374 } else {
375 let opcode = if signed { Opcode::SExt } else { Opcode::ZExt };
376 let args = func.push_values(&[arg]);
377 written(func, inst, InstData { args, ..InstData::new(opcode) }, Type::int(width))
378 };
379 into_call(func, names, abi, inst, routine, &[value]);
380}
381
382fn to_integer(func: &mut Func, names: &mut Interner, abi: &'static AbiDescription, inst: Inst) {
396 let Some(ty) = produced(func, inst) else { return };
397 let Some(&arg) = func[func[inst].args].first() else { return };
398 if !quad(func[arg].ty) || !ty.is_int() || !ty.is_scalar() {
399 return;
400 }
401 let signed = func[inst].opcode == Opcode::FPToSI;
402 let Some(width) = holder(ty.bits()) else { return };
403 let opcode = if signed { Opcode::FPToSI } else { Opcode::FPToUI };
404 let routine = routine(opcode, if width == NARROW { "f128.i32" } else { "f128.i64" });
405 if ty.bits() == width {
406 into_call(func, names, abi, inst, routine, &[arg]);
407 return;
408 }
409 let answer = call(func, names, abi, inst, routine, &[arg], Type::int(width));
410 becomes(func, inst, Opcode::Trunc, Extra::None, &[answer]);
411}
412
413fn holder(bits: u32) -> Option<u32> {
422 match bits {
423 0..=NARROW => Some(NARROW),
424 33..=WORD => Some(WORD),
425 _ => None,
426 }
427}
428
429fn into_call(
440 func: &mut Func,
441 names: &mut Interner,
442 abi: &'static AbiDescription,
443 inst: Inst,
444 routine: &str,
445 args: &[Value],
446) {
447 let Some(ty) = produced(func, inst) else { return };
448 let shape = shaped(func, abi, inst, args, ty);
449 let extra = signature(func, names, routine, &shape, ty);
450 let Some(out) = shape.out else {
451 becomes(func, inst, Opcode::Call, extra, &shape.values);
452 return;
453 };
454 made(func, inst, extra, &shape.values);
455 let read = Extra::Mem(func.add_mem(whole()));
456 becomes(func, inst, Opcode::Load, read, &[out]);
457}
458
459fn call(
461 func: &mut Func,
462 names: &mut Interner,
463 abi: &'static AbiDescription,
464 inst: Inst,
465 routine: &str,
466 args: &[Value],
467 ty: Type,
468) -> Value {
469 let shape = shaped(func, abi, inst, args, ty);
470 let extra = signature(func, names, routine, &shape, ty);
471 let Some(out) = shape.out else {
472 let args = func.push_values(&shape.values);
473 return written(func, inst, InstData { args, extra, ..InstData::new(Opcode::Call) }, ty);
474 };
475 made(func, inst, extra, &shape.values);
476 let extra = Extra::Mem(func.add_mem(whole()));
477 let args = func.push_values(&[out]);
478 written(func, inst, InstData { args, extra, ..InstData::new(Opcode::Load) }, ty)
479}
480
481fn made(func: &mut Func, inst: Inst, extra: Extra, values: &[Value]) {
487 let span = func.span(inst);
488 let args = func.push_values(values);
489 let data = InstData { args, extra, ..InstData::new(Opcode::Call) };
490 let call = func.create_inst(data, &[], span);
491 func.insert_before(call, inst);
492}
493
494struct Shape {
503 params: Vec<Param>,
505 values: Vec<Value>,
507 out: Option<Value>,
509}
510
511fn shaped(
518 func: &mut Func,
519 abi: &'static AbiDescription,
520 inst: Inst,
521 args: &[Value],
522 ty: Type,
523) -> Shape {
524 let mut shape = Shape { params: Vec::new(), values: Vec::new(), out: None };
525 if quad(ty) && abi.scalar_is_by_reference(BYTES) {
526 let out = slot(func, inst);
527 shape.params.push(Param::with_abi(Type::PTR, Abi::Sret { size: BYTES, align: BITS / 8 }));
528 shape.values.push(out);
529 shape.out = Some(out);
530 }
531 for &value in args {
532 let ty = func[value].ty;
533 let size = u64::from(ty.bits().div_ceil(8));
534 if quad(ty) && abi.scalar_is_by_reference(size) {
535 let copy = slot(func, inst);
536 write(func, inst, value, copy, whole());
537 shape.params.push(Param::new(Type::PTR));
538 shape.values.push(copy);
539 } else {
540 shape.params.push(Param::new(ty));
541 shape.values.push(value);
542 }
543 }
544 shape
545}
546
547fn signature(
549 func: &mut Func,
550 names: &mut Interner,
551 routine: &str,
552 shape: &Shape,
553 ty: Type,
554) -> Extra {
555 let mut built = Signature::new();
556 built.params = shape.params.clone();
557 if shape.out.is_none() {
558 built.returns = vec![Param::new(ty)];
559 }
560 let signature = func.add_signature(built);
561 let callee = Some(names.intern(routine));
562 let varargs = func.push_abis(&[]);
563 Extra::Call(func.add_call(CallInfo { callee, signature, varargs }))
564}
565
566fn slot(func: &mut Func, inst: Inst) -> Value {
568 let extra = Extra::Mem(func.add_mem(whole()));
569 written(func, inst, InstData { extra, ..InstData::new(Opcode::Alloca) }, Type::PTR)
570}
571
572fn whole() -> MemInfo {
574 MemInfo {
575 size: BYTES,
576 align: BITS / 8,
577 order: MemOrder::NotAtomic,
578 tbaa: None,
579 owns: 0,
580 restrict: Restrict::NONE,
581 }
582}
583
584fn ahead_const(func: &mut Func, inst: Inst, imm: Imm, ty: Type) -> Value {
586 let extra = Extra::Imm(func.add_imm(imm));
587 written(func, inst, InstData { extra, ..InstData::new(Opcode::IConst) }, ty)
588}
589
590fn write(func: &mut Func, inst: Inst, value: Value, into: Value, info: MemInfo) {
592 let span = func.span(inst);
593 let extra = Extra::Mem(func.add_mem(info));
594 let args = func.push_values(&[value, into]);
595 let data = InstData { args, extra, ..InstData::new(Opcode::Store) };
596 let made = func.create_inst(data, &[], span);
597 func.insert_before(made, inst);
598}
599
600fn written(func: &mut Func, inst: Inst, data: InstData, ty: Type) -> Value {
602 let span = func.span(inst);
603 let made = func.create_inst(data, &[ty], span);
604 func.insert_before(made, inst);
605 func[made].first_result.expect("an instruction created with one result has one")
606}
607
608fn becomes(func: &mut Func, inst: Inst, opcode: Opcode, extra: Extra, args: &[Value]) {
610 let args = func.push_values(args);
611 let data = &mut func[inst];
612 data.opcode = opcode;
613 data.args = args;
614 data.extra = extra;
615 data.flags = data.flags.intersection(Flags::legal_on(opcode));
616}
617
618#[cfg(test)]
619mod tests {
620 use rucc_base::Interner;
621 use rucc_ir::{Block, Builder, Module, Signature};
622 use rucc_target::{AbiDescription, Arch, Env, Os, TargetInfo, Triple, x86_64};
623
624 use super::{BITS, Flags, Float, FloatPred, Func, Opcode, Type, Value, calls};
625
626 fn sysv() -> &'static AbiDescription {
629 x86_64::SYSV.abi
630 }
631
632 fn win64() -> &'static AbiDescription {
634 x86_64::WIN64.abi
635 }
636
637 fn quad() -> Type {
639 Type::float(Float::F128)
640 }
641
642 fn target() -> TargetInfo {
643 TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu))
644 }
645
646 fn printed(func: &Func, names: &mut Interner) -> String {
647 let module = Module::new(names.intern("q.c"), &target());
648 rucc_ir::print_func(&module, func, names)
649 }
650
651 fn shell(names: &mut Interner, params: &[Type], returns: &[Type]) -> (Func, Block, Vec<Value>) {
653 let signature = Signature::new().with_params(params).with_returns(returns);
654 let mut func = Func::new(names.intern("f"), signature);
655 let entry = func.create_block();
656 let values = params.iter().map(|&ty| func.append_param(entry, ty)).collect();
657 (func, entry, values)
658 }
659
660 fn binary(opcode: Opcode) -> String {
662 binary_on(opcode, sysv())
663 }
664
665 fn binary_on(opcode: Opcode, abi: &'static AbiDescription) -> String {
667 let mut names = Interner::new();
668 let (mut func, entry, params) = shell(&mut names, &[quad(), quad()], &[quad()]);
669 let mut build = Builder::new(&mut func, entry);
670 let answer = build.binary(opcode, params[0], params[1], Flags::NONE);
671 build.ret(&[answer]);
672 calls(&mut func, &mut names, abi);
673 printed(&func, &mut names)
674 }
675
676 fn compared(pred: FloatPred) -> String {
678 compared_on(pred, sysv())
679 }
680
681 fn compared_on(pred: FloatPred, abi: &'static AbiDescription) -> String {
683 let mut names = Interner::new();
684 let (mut func, entry, params) = shell(&mut names, &[quad(), quad()], &[Type::I1]);
685 let mut build = Builder::new(&mut func, entry);
686 let answer = build.fcmp(pred, params[0], params[1], Flags::NONE);
687 build.ret(&[answer]);
688 calls(&mut func, &mut names, abi);
689 printed(&func, &mut names)
690 }
691
692 #[test]
693 fn the_four_operations_are_the_four_routines() {
694 for (opcode, routine) in [
695 (Opcode::FAdd, "__addtf3"),
696 (Opcode::FSub, "__subtf3"),
697 (Opcode::FMul, "__multf3"),
698 (Opcode::FDiv, "__divtf3"),
699 ] {
700 let text = binary(opcode);
701 assert!(text.contains(&format!("@{routine}")), "{routine}: {text}");
702 assert_eq!(text.matches(" = f").count(), 0, "no float arithmetic left: {text}");
705 assert_eq!(text.matches(" = call").count(), 1, "one call: {text}");
706 }
707 }
708
709 #[test]
710 fn a_negation_is_the_routine_rather_than_a_sign_flip() {
711 let mut names = Interner::new();
712 let (mut func, entry, params) = shell(&mut names, &[quad()], &[quad()]);
713 let mut build = Builder::new(&mut func, entry);
714 let answer = build.unary(Opcode::FNeg, params[0], quad());
715 build.ret(&[answer]);
716 calls(&mut func, &mut names, sysv());
717 let text = printed(&func, &mut names);
718 assert!(text.contains("@__negtf2"), "{text}");
719 assert!(!text.contains("xor"), "no sign flip in a register: {text}");
720 }
721
722 #[test]
725 fn an_ordered_comparison_is_its_own_routine_tested_against_zero() {
726 for (pred, routine, test) in [
727 (FloatPred::Oeq, "__eqtf2", "icmp eq"),
728 (FloatPred::Une, "__netf2", "icmp ne"),
729 (FloatPred::Olt, "__lttf2", "icmp slt"),
730 (FloatPred::Ole, "__letf2", "icmp sle"),
731 (FloatPred::Ogt, "__gttf2", "icmp sgt"),
732 (FloatPred::Oge, "__getf2", "icmp sge"),
733 ] {
734 let text = compared(pred);
735 assert!(text.contains(&format!("@{routine}")), "{routine}: {text}");
736 assert!(text.contains(test), "{test}: {text}");
737 assert!(!text.contains("fcmp"), "the comparison is gone: {text}");
738 }
739 }
740
741 #[test]
747 fn an_unordered_comparison_is_the_opposite_routine_read_the_same_way() {
748 for (pred, routine, test) in [
749 (FloatPred::Ult, "__getf2", "icmp slt"),
750 (FloatPred::Ule, "__gttf2", "icmp sle"),
751 (FloatPred::Ugt, "__letf2", "icmp sgt"),
752 (FloatPred::Uge, "__lttf2", "icmp sge"),
753 ] {
754 let text = compared(pred);
755 assert!(text.contains(&format!("@{routine}")), "{routine}: {text}");
756 assert!(text.contains(test), "{test}: {text}");
757 }
758 }
759
760 #[test]
761 fn whether_two_values_can_be_ordered_at_all_is_one_routine_either_way_round() {
762 let unordered = compared(FloatPred::Uno);
763 assert!(unordered.contains("@__unordtf2"), "{unordered}");
764 assert!(unordered.contains("icmp ne"), "{unordered}");
765 let ordered = compared(FloatPred::Ord);
766 assert!(ordered.contains("@__unordtf2"), "{ordered}");
767 assert!(ordered.contains("icmp eq"), "{ordered}");
768 }
769
770 #[test]
772 fn ordered_and_different_is_two_calls_joined() {
773 let text = compared(FloatPred::One);
774 assert!(text.contains("@__unordtf2"), "{text}");
775 assert!(text.contains("@__netf2"), "{text}");
776 assert_eq!(text.matches(" = call").count(), 2, "both calls: {text}");
777 assert_eq!(text.matches(" = and").count(), 1, "joined: {text}");
778 assert!(!text.contains("xor"), "nothing is negated: {text}");
779 }
780
781 #[test]
783 fn unordered_or_equal_is_the_negation_of_it() {
784 let text = compared(FloatPred::Ueq);
785 assert_eq!(text.matches(" = call").count(), 2, "both calls: {text}");
786 assert_eq!(text.matches(" = or").count(), 1, "joined the other way: {text}");
787 assert_eq!(text.matches(" = xor").count(), 2, "both answers negated: {text}");
788 }
789
790 #[test]
791 fn the_two_comparisons_with_no_operands_to_read_are_constants() {
792 let never = compared(FloatPred::False);
793 assert!(never.contains("iconst.i1 0"), "{never}");
794 assert!(!never.contains("call"), "nothing is called: {never}");
795 let always = compared(FloatPred::True);
798 assert!(always.contains("iconst.i1 -1"), "{always}");
799 }
800
801 #[test]
803 fn a_constant_goes_through_the_frame_a_word_at_a_time() {
804 let mut names = Interner::new();
805 let (mut func, entry, _) = shell(&mut names, &[], &[quad()]);
806 let mut build = Builder::new(&mut func, entry);
807 let value = build.fconst(quad(), (3u128 << 64) | 5);
810 build.ret(&[value]);
811 calls(&mut func, &mut names, sysv());
812 let text = printed(&func, &mut names);
813 assert!(!text.contains("fconst"), "the constant is gone: {text}");
814 assert_eq!(text.matches("alloca").count(), 1, "one slot: {text}");
815 assert_eq!(text.matches("store").count(), 2, "a word at a time: {text}");
816 assert!(text.contains("iconst.i64 5"), "the low word first: {text}");
817 assert!(text.contains("iconst.i64 3"), "the high word above it: {text}");
818 assert_eq!(text.matches("ptr_add").count(), 1, "the high word is eight bytes up: {text}");
819 assert_eq!(text.matches(" = load").count(), 1, "read back as one value: {text}");
820 }
821
822 #[test]
823 fn the_two_narrower_formats_are_a_routine_each_way() {
824 for (from, to, routine) in [
825 (Float::F32, Float::F128, "__extendsftf2"),
826 (Float::F64, Float::F128, "__extenddftf2"),
827 (Float::F128, Float::F32, "__trunctfsf2"),
828 (Float::F128, Float::F64, "__trunctfdf2"),
829 ] {
830 let mut names = Interner::new();
831 let (mut func, entry, params) =
832 shell(&mut names, &[Type::float(from)], &[Type::float(to)]);
833 let mut build = Builder::new(&mut func, entry);
834 let opcode = if to == Float::F128 { Opcode::FPExt } else { Opcode::FPTrunc };
835 let answer = build.unary(opcode, params[0], Type::float(to));
836 build.ret(&[answer]);
837 calls(&mut func, &mut names, sysv());
838 let text = printed(&func, &mut names);
839 assert!(text.contains(&format!("@{routine}")), "{routine}: {text}");
840 }
841 }
842
843 #[test]
846 fn a_narrow_integer_is_widened_before_the_conversion() {
847 for (opcode, bits, extend, routine) in [
848 (Opcode::SIToFP, 16, " = sext", "__floatsitf"),
849 (Opcode::UIToFP, 16, " = zext", "__floatunsitf"),
850 (Opcode::SIToFP, 32, "", "__floatsitf"),
851 (Opcode::UIToFP, 64, "", "__floatunditf"),
852 ] {
853 let mut names = Interner::new();
854 let (mut func, entry, params) = shell(&mut names, &[Type::int(bits)], &[quad()]);
855 let mut build = Builder::new(&mut func, entry);
856 let answer = build.unary(opcode, params[0], quad());
857 build.ret(&[answer]);
858 calls(&mut func, &mut names, sysv());
859 let text = printed(&func, &mut names);
860 assert!(text.contains(&format!("@{routine}")), "{routine}: {text}");
861 if extend.is_empty() {
862 assert!(!text.contains(" = sext"), "nothing to widen: {text}");
863 assert!(!text.contains(" = zext"), "nothing to widen: {text}");
864 } else {
865 assert!(text.contains(extend), "{extend}: {text}");
866 }
867 }
868 }
869
870 #[test]
872 fn a_narrow_answer_is_the_wider_routine_and_a_truncation() {
873 let mut names = Interner::new();
874 let (mut func, entry, params) = shell(&mut names, &[quad()], &[Type::int(16)]);
875 let mut build = Builder::new(&mut func, entry);
876 let answer = build.unary(Opcode::FPToSI, params[0], Type::int(16));
877 build.ret(&[answer]);
878 calls(&mut func, &mut names, sysv());
879 let text = printed(&func, &mut names);
880 assert!(text.contains("@__fixtfsi"), "{text}");
881 assert_eq!(text.matches(" = trunc").count(), 1, "cut down afterwards: {text}");
882 }
883
884 #[test]
885 fn a_conversion_against_a_wide_integer_is_left_exactly_as_it_was() {
886 let mut names = Interner::new();
887 let (mut func, entry, params) = shell(&mut names, &[Type::int(BITS)], &[quad()]);
888 let mut build = Builder::new(&mut func, entry);
889 let answer = build.unary(Opcode::SIToFP, params[0], quad());
890 build.ret(&[answer]);
891 calls(&mut func, &mut names, sysv());
892 let text = printed(&func, &mut names);
893 assert!(!text.contains("call"), "no routine is called: {text}");
894 assert!(text.contains("sitofp"), "the conversion is still there to be refused: {text}");
895 }
896
897 #[test]
899 fn the_narrower_formats_go_past_untouched() {
900 let mut names = Interner::new();
901 let double = Type::float(Float::F64);
902 let (mut func, entry, params) = shell(&mut names, &[double, double], &[double]);
903 let mut build = Builder::new(&mut func, entry);
904 let sum = build.binary(Opcode::FAdd, params[0], params[1], Flags::NONE);
905 let answer = build.fcmp(FloatPred::Olt, sum, params[1], Flags::NONE);
906 build.ret(&[sum]);
907 let _ = answer;
908 calls(&mut func, &mut names, sysv());
909 let text = printed(&func, &mut names);
910 assert!(!text.contains("call"), "nothing became a call: {text}");
911 assert!(text.contains("fadd"), "the add is still an add: {text}");
912 assert!(text.contains("fcmp"), "the comparison is still a comparison: {text}");
913 }
914
915 #[test]
923 fn on_windows_the_operands_and_the_answer_all_travel_as_addresses() {
924 let text = binary_on(Opcode::FAdd, win64());
925 assert!(text.contains("@__addtf3"), "{text}");
926 assert_eq!(text.matches("alloca").count(), 3, "three slots: {text}");
929 assert_eq!(text.matches("store").count(), 2, "a copy of each operand: {text}");
930 assert_eq!(text.matches(" = call").count(), 0, "the call answers nothing: {text}");
933 assert_eq!(text.matches("call ").count(), 1, "and there is one of them: {text}");
934 assert_eq!(text.matches(" = load").count(), 1, "read back out of the slot: {text}");
935 }
936
937 #[test]
940 fn on_windows_a_comparison_hands_over_its_operands_and_keeps_its_answer() {
941 let text = compared_on(FloatPred::Oeq, win64());
942 assert!(text.contains("@__eqtf2"), "{text}");
943 assert_eq!(text.matches("alloca").count(), 2, "one slot per operand: {text}");
944 assert_eq!(text.matches("store").count(), 2, "and a copy into each: {text}");
945 assert_eq!(text.matches(" = call").count(), 1, "the answer is still a result: {text}");
946 assert!(text.contains("icmp eq"), "read the same way: {text}");
947 }
948
949 #[test]
951 fn the_convention_that_holds_one_in_a_register_puts_nothing_on_the_frame() {
952 let text = binary_on(Opcode::FAdd, sysv());
953 assert!(text.contains("@__addtf3"), "{text}");
954 assert!(!text.contains("alloca"), "nothing goes through the frame: {text}");
955 assert!(!text.contains("store"), "nothing is copied: {text}");
956 assert_eq!(text.matches(" = call").count(), 1, "the call is the value: {text}");
957 }
958}