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
263pub(crate) fn 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) && !(ty.is_scalar() && ty.format() == Some(Float::D128)) {
292 return;
293 }
294 let bits = func[imm].bits();
295 let whole = whole();
296 let slot = slot(func, inst);
297 let half = u64::from(WORD / 8);
298 let word = Type::int(WORD);
299 let low = ahead_const(func, inst, Imm::int(bits as i128, word), word);
300 write(func, inst, low, slot, MemInfo { size: half, ..whole });
301 let step = ahead_const(func, inst, Imm::int(half as i128, word), word);
302 let args = func.push_values(&[slot, step]);
303 let above = written(func, inst, InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR);
304 let high = ahead_const(func, inst, Imm::int((bits >> WORD) as i128, word), word);
305 write(func, inst, high, above, MemInfo { size: half, align: WORD / 8, ..whole });
306 let extra = Extra::Mem(func.add_mem(whole));
307 becomes(func, inst, Opcode::Load, extra, &[slot]);
308}
309
310fn widen(func: &mut Func, names: &mut Interner, abi: &'static AbiDescription, inst: Inst) {
316 let Some(ty) = produced(func, inst) else { return };
317 let Some(&arg) = func[func[inst].args].first() else { return };
318 if !quad(ty) {
319 return;
320 }
321 let mode = match func[arg].ty.format() {
322 Some(Float::F32) => "f32.f128",
323 Some(Float::F64) => "f64.f128",
324 _ => return,
325 };
326 let routine = routine(Opcode::FPExt, mode);
327 into_call(func, names, abi, inst, routine, &[arg]);
328}
329
330fn narrow(func: &mut Func, names: &mut Interner, abi: &'static AbiDescription, inst: Inst) {
332 let Some(ty) = produced(func, inst) else { return };
333 let Some(&arg) = func[func[inst].args].first() else { return };
334 if !quad(func[arg].ty) {
335 return;
336 }
337 let mode = match ty.format() {
338 Some(Float::F32) => "f128.f32",
339 Some(Float::F64) => "f128.f64",
340 _ => return,
341 };
342 let routine = routine(Opcode::FPTrunc, mode);
343 into_call(func, names, abi, inst, routine, &[arg]);
344}
345
346fn from_integer(func: &mut Func, names: &mut Interner, abi: &'static AbiDescription, inst: Inst) {
364 let Some(ty) = produced(func, inst) else { return };
365 let Some(&arg) = func[func[inst].args].first() else { return };
366 let from = func[arg].ty;
367 if !quad(ty) || !from.is_int() || !from.is_scalar() {
368 return;
369 }
370 let signed = func[inst].opcode == Opcode::SIToFP;
371 let Some(width) = holder(from.bits()) else { return };
372 let opcode = if signed { Opcode::SIToFP } else { Opcode::UIToFP };
373 let routine = routine(opcode, if width == NARROW { "i32.f128" } else { "i64.f128" });
374 let value = if from.bits() == width {
375 arg
376 } else {
377 let opcode = if signed { Opcode::SExt } else { Opcode::ZExt };
378 let args = func.push_values(&[arg]);
379 written(func, inst, InstData { args, ..InstData::new(opcode) }, Type::int(width))
380 };
381 into_call(func, names, abi, inst, routine, &[value]);
382}
383
384fn to_integer(func: &mut Func, names: &mut Interner, abi: &'static AbiDescription, inst: Inst) {
398 let Some(ty) = produced(func, inst) else { return };
399 let Some(&arg) = func[func[inst].args].first() else { return };
400 if !quad(func[arg].ty) || !ty.is_int() || !ty.is_scalar() {
401 return;
402 }
403 let signed = func[inst].opcode == Opcode::FPToSI;
404 let Some(width) = holder(ty.bits()) else { return };
405 let opcode = if signed { Opcode::FPToSI } else { Opcode::FPToUI };
406 let routine = routine(opcode, if width == NARROW { "f128.i32" } else { "f128.i64" });
407 if ty.bits() == width {
408 into_call(func, names, abi, inst, routine, &[arg]);
409 return;
410 }
411 let answer = call(func, names, abi, inst, routine, &[arg], Type::int(width));
412 becomes(func, inst, Opcode::Trunc, Extra::None, &[answer]);
413}
414
415fn holder(bits: u32) -> Option<u32> {
424 match bits {
425 0..=NARROW => Some(NARROW),
426 33..=WORD => Some(WORD),
427 _ => None,
428 }
429}
430
431pub(crate) fn into_call(
442 func: &mut Func,
443 names: &mut Interner,
444 abi: &'static AbiDescription,
445 inst: Inst,
446 routine: &str,
447 args: &[Value],
448) {
449 let Some(ty) = produced(func, inst) else { return };
450 let shape = shaped(func, abi, inst, args, ty);
451 let extra = signature(func, names, routine, &shape, ty);
452 let Some(out) = shape.out else {
453 becomes(func, inst, Opcode::Call, extra, &shape.values);
454 return;
455 };
456 made(func, inst, extra, &shape.values);
457 let read = Extra::Mem(func.add_mem(whole()));
458 becomes(func, inst, Opcode::Load, read, &[out]);
459}
460
461pub(crate) fn call(
463 func: &mut Func,
464 names: &mut Interner,
465 abi: &'static AbiDescription,
466 inst: Inst,
467 routine: &str,
468 args: &[Value],
469 ty: Type,
470) -> Value {
471 let shape = shaped(func, abi, inst, args, ty);
472 let extra = signature(func, names, routine, &shape, ty);
473 let Some(out) = shape.out else {
474 let args = func.push_values(&shape.values);
475 return written(func, inst, InstData { args, extra, ..InstData::new(Opcode::Call) }, ty);
476 };
477 made(func, inst, extra, &shape.values);
478 let extra = Extra::Mem(func.add_mem(whole()));
479 let args = func.push_values(&[out]);
480 written(func, inst, InstData { args, extra, ..InstData::new(Opcode::Load) }, ty)
481}
482
483fn made(func: &mut Func, inst: Inst, extra: Extra, values: &[Value]) {
489 let span = func.span(inst);
490 let args = func.push_values(values);
491 let data = InstData { args, extra, ..InstData::new(Opcode::Call) };
492 let call = func.create_inst(data, &[], span);
493 func.insert_before(call, inst);
494}
495
496struct Shape {
505 params: Vec<Param>,
507 values: Vec<Value>,
509 out: Option<Value>,
511}
512
513fn shaped(
520 func: &mut Func,
521 abi: &'static AbiDescription,
522 inst: Inst,
523 args: &[Value],
524 ty: Type,
525) -> Shape {
526 let mut shape = Shape { params: Vec::new(), values: Vec::new(), out: None };
527 if quad(ty) && abi.scalar_is_by_reference(BYTES) {
528 let out = slot(func, inst);
529 shape.params.push(Param::with_abi(Type::PTR, Abi::Sret { size: BYTES, align: BITS / 8 }));
530 shape.values.push(out);
531 shape.out = Some(out);
532 }
533 for &value in args {
534 let ty = func[value].ty;
535 let size = u64::from(ty.bits().div_ceil(8));
536 if quad(ty) && abi.scalar_is_by_reference(size) {
537 let copy = slot(func, inst);
538 write(func, inst, value, copy, whole());
539 shape.params.push(Param::new(Type::PTR));
540 shape.values.push(copy);
541 } else {
542 shape.params.push(Param::new(ty));
543 shape.values.push(value);
544 }
545 }
546 shape
547}
548
549fn signature(
551 func: &mut Func,
552 names: &mut Interner,
553 routine: &str,
554 shape: &Shape,
555 ty: Type,
556) -> Extra {
557 let mut built = Signature::new();
558 built.params = shape.params.clone();
559 if shape.out.is_none() {
560 built.returns = vec![Param::new(ty)];
561 }
562 let signature = func.add_signature(built);
563 let callee = Some(names.intern(routine));
564 let varargs = func.push_abis(&[]);
565 Extra::Call(func.add_call(CallInfo { callee, signature, varargs }))
566}
567
568fn slot(func: &mut Func, inst: Inst) -> Value {
570 let extra = Extra::Mem(func.add_mem(whole()));
571 written(func, inst, InstData { extra, ..InstData::new(Opcode::Alloca) }, Type::PTR)
572}
573
574fn whole() -> MemInfo {
576 MemInfo {
577 size: BYTES,
578 align: BITS / 8,
579 order: MemOrder::NotAtomic,
580 tbaa: None,
581 owns: 0,
582 restrict: Restrict::NONE,
583 }
584}
585
586pub(crate) fn ahead_const(func: &mut Func, inst: Inst, imm: Imm, ty: Type) -> Value {
588 let extra = Extra::Imm(func.add_imm(imm));
589 written(func, inst, InstData { extra, ..InstData::new(Opcode::IConst) }, ty)
590}
591
592fn write(func: &mut Func, inst: Inst, value: Value, into: Value, info: MemInfo) {
594 let span = func.span(inst);
595 let extra = Extra::Mem(func.add_mem(info));
596 let args = func.push_values(&[value, into]);
597 let data = InstData { args, extra, ..InstData::new(Opcode::Store) };
598 let made = func.create_inst(data, &[], span);
599 func.insert_before(made, inst);
600}
601
602pub(crate) fn written(func: &mut Func, inst: Inst, data: InstData, ty: Type) -> Value {
604 let span = func.span(inst);
605 let made = func.create_inst(data, &[ty], span);
606 func.insert_before(made, inst);
607 func[made].first_result.expect("an instruction created with one result has one")
608}
609
610pub(crate) fn becomes(func: &mut Func, inst: Inst, opcode: Opcode, extra: Extra, args: &[Value]) {
612 let args = func.push_values(args);
613 let data = &mut func[inst];
614 data.opcode = opcode;
615 data.args = args;
616 data.extra = extra;
617 data.flags = data.flags.intersection(Flags::legal_on(opcode));
618}
619
620#[cfg(test)]
621mod tests {
622 use rucc_base::Interner;
623 use rucc_ir::{Block, Builder, Module, Signature};
624 use rucc_target::{AbiDescription, Arch, Env, Os, TargetInfo, Triple, x86_64};
625
626 use super::{BITS, Flags, Float, FloatPred, Func, Opcode, Type, Value, calls};
627
628 fn sysv() -> &'static AbiDescription {
631 x86_64::SYSV.abi
632 }
633
634 fn win64() -> &'static AbiDescription {
636 x86_64::WIN64.abi
637 }
638
639 fn quad() -> Type {
641 Type::float(Float::F128)
642 }
643
644 fn target() -> TargetInfo {
645 TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu))
646 }
647
648 fn printed(func: &Func, names: &mut Interner) -> String {
649 let module = Module::new(names.intern("q.c"), &target());
650 rucc_ir::print_func(&module, func, names)
651 }
652
653 fn shell(names: &mut Interner, params: &[Type], returns: &[Type]) -> (Func, Block, Vec<Value>) {
655 let signature = Signature::new().with_params(params).with_returns(returns);
656 let mut func = Func::new(names.intern("f"), signature);
657 let entry = func.create_block();
658 let values = params.iter().map(|&ty| func.append_param(entry, ty)).collect();
659 (func, entry, values)
660 }
661
662 fn binary(opcode: Opcode) -> String {
664 binary_on(opcode, sysv())
665 }
666
667 fn binary_on(opcode: Opcode, abi: &'static AbiDescription) -> String {
669 let mut names = Interner::new();
670 let (mut func, entry, params) = shell(&mut names, &[quad(), quad()], &[quad()]);
671 let mut build = Builder::new(&mut func, entry);
672 let answer = build.binary(opcode, params[0], params[1], Flags::NONE);
673 build.ret(&[answer]);
674 calls(&mut func, &mut names, abi);
675 printed(&func, &mut names)
676 }
677
678 fn compared(pred: FloatPred) -> String {
680 compared_on(pred, sysv())
681 }
682
683 fn compared_on(pred: FloatPred, abi: &'static AbiDescription) -> String {
685 let mut names = Interner::new();
686 let (mut func, entry, params) = shell(&mut names, &[quad(), quad()], &[Type::I1]);
687 let mut build = Builder::new(&mut func, entry);
688 let answer = build.fcmp(pred, params[0], params[1], Flags::NONE);
689 build.ret(&[answer]);
690 calls(&mut func, &mut names, abi);
691 printed(&func, &mut names)
692 }
693
694 #[test]
695 fn the_four_operations_are_the_four_routines() {
696 for (opcode, routine) in [
697 (Opcode::FAdd, "__addtf3"),
698 (Opcode::FSub, "__subtf3"),
699 (Opcode::FMul, "__multf3"),
700 (Opcode::FDiv, "__divtf3"),
701 ] {
702 let text = binary(opcode);
703 assert!(text.contains(&format!("@{routine}")), "{routine}: {text}");
704 assert_eq!(text.matches(" = f").count(), 0, "no float arithmetic left: {text}");
707 assert_eq!(text.matches(" = call").count(), 1, "one call: {text}");
708 }
709 }
710
711 #[test]
712 fn a_negation_is_the_routine_rather_than_a_sign_flip() {
713 let mut names = Interner::new();
714 let (mut func, entry, params) = shell(&mut names, &[quad()], &[quad()]);
715 let mut build = Builder::new(&mut func, entry);
716 let answer = build.unary(Opcode::FNeg, params[0], quad());
717 build.ret(&[answer]);
718 calls(&mut func, &mut names, sysv());
719 let text = printed(&func, &mut names);
720 assert!(text.contains("@__negtf2"), "{text}");
721 assert!(!text.contains("xor"), "no sign flip in a register: {text}");
722 }
723
724 #[test]
727 fn an_ordered_comparison_is_its_own_routine_tested_against_zero() {
728 for (pred, routine, test) in [
729 (FloatPred::Oeq, "__eqtf2", "icmp eq"),
730 (FloatPred::Une, "__netf2", "icmp ne"),
731 (FloatPred::Olt, "__lttf2", "icmp slt"),
732 (FloatPred::Ole, "__letf2", "icmp sle"),
733 (FloatPred::Ogt, "__gttf2", "icmp sgt"),
734 (FloatPred::Oge, "__getf2", "icmp sge"),
735 ] {
736 let text = compared(pred);
737 assert!(text.contains(&format!("@{routine}")), "{routine}: {text}");
738 assert!(text.contains(test), "{test}: {text}");
739 assert!(!text.contains("fcmp"), "the comparison is gone: {text}");
740 }
741 }
742
743 #[test]
749 fn an_unordered_comparison_is_the_opposite_routine_read_the_same_way() {
750 for (pred, routine, test) in [
751 (FloatPred::Ult, "__getf2", "icmp slt"),
752 (FloatPred::Ule, "__gttf2", "icmp sle"),
753 (FloatPred::Ugt, "__letf2", "icmp sgt"),
754 (FloatPred::Uge, "__lttf2", "icmp sge"),
755 ] {
756 let text = compared(pred);
757 assert!(text.contains(&format!("@{routine}")), "{routine}: {text}");
758 assert!(text.contains(test), "{test}: {text}");
759 }
760 }
761
762 #[test]
763 fn whether_two_values_can_be_ordered_at_all_is_one_routine_either_way_round() {
764 let unordered = compared(FloatPred::Uno);
765 assert!(unordered.contains("@__unordtf2"), "{unordered}");
766 assert!(unordered.contains("icmp ne"), "{unordered}");
767 let ordered = compared(FloatPred::Ord);
768 assert!(ordered.contains("@__unordtf2"), "{ordered}");
769 assert!(ordered.contains("icmp eq"), "{ordered}");
770 }
771
772 #[test]
774 fn ordered_and_different_is_two_calls_joined() {
775 let text = compared(FloatPred::One);
776 assert!(text.contains("@__unordtf2"), "{text}");
777 assert!(text.contains("@__netf2"), "{text}");
778 assert_eq!(text.matches(" = call").count(), 2, "both calls: {text}");
779 assert_eq!(text.matches(" = and").count(), 1, "joined: {text}");
780 assert!(!text.contains("xor"), "nothing is negated: {text}");
781 }
782
783 #[test]
785 fn unordered_or_equal_is_the_negation_of_it() {
786 let text = compared(FloatPred::Ueq);
787 assert_eq!(text.matches(" = call").count(), 2, "both calls: {text}");
788 assert_eq!(text.matches(" = or").count(), 1, "joined the other way: {text}");
789 assert_eq!(text.matches(" = xor").count(), 2, "both answers negated: {text}");
790 }
791
792 #[test]
793 fn the_two_comparisons_with_no_operands_to_read_are_constants() {
794 let never = compared(FloatPred::False);
795 assert!(never.contains("iconst.i1 0"), "{never}");
796 assert!(!never.contains("call"), "nothing is called: {never}");
797 let always = compared(FloatPred::True);
800 assert!(always.contains("iconst.i1 -1"), "{always}");
801 }
802
803 #[test]
805 fn a_constant_goes_through_the_frame_a_word_at_a_time() {
806 let mut names = Interner::new();
807 let (mut func, entry, _) = shell(&mut names, &[], &[quad()]);
808 let mut build = Builder::new(&mut func, entry);
809 let value = build.fconst(quad(), (3u128 << 64) | 5);
812 build.ret(&[value]);
813 calls(&mut func, &mut names, sysv());
814 let text = printed(&func, &mut names);
815 assert!(!text.contains("fconst"), "the constant is gone: {text}");
816 assert_eq!(text.matches("alloca").count(), 1, "one slot: {text}");
817 assert_eq!(text.matches("store").count(), 2, "a word at a time: {text}");
818 assert!(text.contains("iconst.i64 5"), "the low word first: {text}");
819 assert!(text.contains("iconst.i64 3"), "the high word above it: {text}");
820 assert_eq!(text.matches("ptr_add").count(), 1, "the high word is eight bytes up: {text}");
821 assert_eq!(text.matches(" = load").count(), 1, "read back as one value: {text}");
822 }
823
824 #[test]
825 fn the_two_narrower_formats_are_a_routine_each_way() {
826 for (from, to, routine) in [
827 (Float::F32, Float::F128, "__extendsftf2"),
828 (Float::F64, Float::F128, "__extenddftf2"),
829 (Float::F128, Float::F32, "__trunctfsf2"),
830 (Float::F128, Float::F64, "__trunctfdf2"),
831 ] {
832 let mut names = Interner::new();
833 let (mut func, entry, params) =
834 shell(&mut names, &[Type::float(from)], &[Type::float(to)]);
835 let mut build = Builder::new(&mut func, entry);
836 let opcode = if to == Float::F128 { Opcode::FPExt } else { Opcode::FPTrunc };
837 let answer = build.unary(opcode, params[0], Type::float(to));
838 build.ret(&[answer]);
839 calls(&mut func, &mut names, sysv());
840 let text = printed(&func, &mut names);
841 assert!(text.contains(&format!("@{routine}")), "{routine}: {text}");
842 }
843 }
844
845 #[test]
848 fn a_narrow_integer_is_widened_before_the_conversion() {
849 for (opcode, bits, extend, routine) in [
850 (Opcode::SIToFP, 16, " = sext", "__floatsitf"),
851 (Opcode::UIToFP, 16, " = zext", "__floatunsitf"),
852 (Opcode::SIToFP, 32, "", "__floatsitf"),
853 (Opcode::UIToFP, 64, "", "__floatunditf"),
854 ] {
855 let mut names = Interner::new();
856 let (mut func, entry, params) = shell(&mut names, &[Type::int(bits)], &[quad()]);
857 let mut build = Builder::new(&mut func, entry);
858 let answer = build.unary(opcode, params[0], quad());
859 build.ret(&[answer]);
860 calls(&mut func, &mut names, sysv());
861 let text = printed(&func, &mut names);
862 assert!(text.contains(&format!("@{routine}")), "{routine}: {text}");
863 if extend.is_empty() {
864 assert!(!text.contains(" = sext"), "nothing to widen: {text}");
865 assert!(!text.contains(" = zext"), "nothing to widen: {text}");
866 } else {
867 assert!(text.contains(extend), "{extend}: {text}");
868 }
869 }
870 }
871
872 #[test]
874 fn a_narrow_answer_is_the_wider_routine_and_a_truncation() {
875 let mut names = Interner::new();
876 let (mut func, entry, params) = shell(&mut names, &[quad()], &[Type::int(16)]);
877 let mut build = Builder::new(&mut func, entry);
878 let answer = build.unary(Opcode::FPToSI, params[0], Type::int(16));
879 build.ret(&[answer]);
880 calls(&mut func, &mut names, sysv());
881 let text = printed(&func, &mut names);
882 assert!(text.contains("@__fixtfsi"), "{text}");
883 assert_eq!(text.matches(" = trunc").count(), 1, "cut down afterwards: {text}");
884 }
885
886 #[test]
887 fn a_conversion_against_a_wide_integer_is_left_exactly_as_it_was() {
888 let mut names = Interner::new();
889 let (mut func, entry, params) = shell(&mut names, &[Type::int(BITS)], &[quad()]);
890 let mut build = Builder::new(&mut func, entry);
891 let answer = build.unary(Opcode::SIToFP, params[0], quad());
892 build.ret(&[answer]);
893 calls(&mut func, &mut names, sysv());
894 let text = printed(&func, &mut names);
895 assert!(!text.contains("call"), "no routine is called: {text}");
896 assert!(text.contains("sitofp"), "the conversion is still there to be refused: {text}");
897 }
898
899 #[test]
901 fn the_narrower_formats_go_past_untouched() {
902 let mut names = Interner::new();
903 let double = Type::float(Float::F64);
904 let (mut func, entry, params) = shell(&mut names, &[double, double], &[double]);
905 let mut build = Builder::new(&mut func, entry);
906 let sum = build.binary(Opcode::FAdd, params[0], params[1], Flags::NONE);
907 let answer = build.fcmp(FloatPred::Olt, sum, params[1], Flags::NONE);
908 build.ret(&[sum]);
909 let _ = answer;
910 calls(&mut func, &mut names, sysv());
911 let text = printed(&func, &mut names);
912 assert!(!text.contains("call"), "nothing became a call: {text}");
913 assert!(text.contains("fadd"), "the add is still an add: {text}");
914 assert!(text.contains("fcmp"), "the comparison is still a comparison: {text}");
915 }
916
917 #[test]
925 fn on_windows_the_operands_and_the_answer_all_travel_as_addresses() {
926 let text = binary_on(Opcode::FAdd, win64());
927 assert!(text.contains("@__addtf3"), "{text}");
928 assert_eq!(text.matches("alloca").count(), 3, "three slots: {text}");
931 assert_eq!(text.matches("store").count(), 2, "a copy of each operand: {text}");
932 assert_eq!(text.matches(" = call").count(), 0, "the call answers nothing: {text}");
935 assert_eq!(text.matches("call ").count(), 1, "and there is one of them: {text}");
936 assert_eq!(text.matches(" = load").count(), 1, "read back out of the slot: {text}");
937 }
938
939 #[test]
942 fn on_windows_a_comparison_hands_over_its_operands_and_keeps_its_answer() {
943 let text = compared_on(FloatPred::Oeq, win64());
944 assert!(text.contains("@__eqtf2"), "{text}");
945 assert_eq!(text.matches("alloca").count(), 2, "one slot per operand: {text}");
946 assert_eq!(text.matches("store").count(), 2, "and a copy into each: {text}");
947 assert_eq!(text.matches(" = call").count(), 1, "the answer is still a result: {text}");
948 assert!(text.contains("icmp eq"), "read the same way: {text}");
949 }
950
951 #[test]
953 fn the_convention_that_holds_one_in_a_register_puts_nothing_on_the_frame() {
954 let text = binary_on(Opcode::FAdd, sysv());
955 assert!(text.contains("@__addtf3"), "{text}");
956 assert!(!text.contains("alloca"), "nothing goes through the frame: {text}");
957 assert!(!text.contains("store"), "nothing is copied: {text}");
958 assert_eq!(text.matches(" = call").count(), 1, "the call is the value: {text}");
959 }
960}