1use rucc_base::Interner;
60use rucc_ir::{
61 CallInfo, Extra, Flags, Float, FloatPred, Func, Imm, Inst, InstData, IntPred, MemInfo,
62 MemOrder, Opcode, Restrict, Signature, Type, Value,
63};
64
65use crate::capability;
66
67fn routine(opcode: Opcode, mode: &str) -> &'static str {
73 capability::libcall(opcode, mode)
74 .unwrap_or_else(|| panic!("no routine for `{}` at `{mode}`", opcode.name()))
75}
76
77const QUAD: Float = Float::F128;
79
80const MODE: &str = "f128";
82
83const BITS: u32 = 128;
85
86const NARROW: u32 = 32;
89const WORD: u32 = 64;
90
91pub fn calls(func: &mut Func, names: &mut Interner) {
96 let found: Vec<Inst> =
97 func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
98 for inst in found {
99 match func[inst].opcode {
100 Opcode::FAdd | Opcode::FSub | Opcode::FMul | Opcode::FDiv => {
101 arithmetic(func, names, inst);
102 }
103 Opcode::FNeg => negate(func, names, inst),
104 Opcode::FCmp => compare(func, names, inst),
105 Opcode::FConst => constant(func, inst),
106 Opcode::FPExt => widen(func, names, inst),
107 Opcode::FPTrunc => narrow(func, names, inst),
108 Opcode::SIToFP | Opcode::UIToFP => from_integer(func, names, inst),
109 Opcode::FPToSI | Opcode::FPToUI => to_integer(func, names, inst),
110 _ => {}
111 }
112 }
113}
114
115fn quad(ty: Type) -> bool {
117 ty.is_scalar() && ty.format() == Some(QUAD)
118}
119
120fn produced(func: &Func, inst: Inst) -> Option<Type> {
122 func[inst].first_result.map(|value| func[value].ty)
123}
124
125fn arithmetic(func: &mut Func, names: &mut Interner, inst: Inst) {
132 let Some(ty) = produced(func, inst) else { return };
133 if !quad(ty) {
134 return;
135 }
136 let args = func[func[inst].args].to_vec();
137 let [a, b] = args[..] else { return };
138 let opcode = func[inst].opcode;
141 let (Opcode::FAdd | Opcode::FSub | Opcode::FMul | Opcode::FDiv) = opcode else { return };
142 let Some(routine) = capability::libcall(opcode, MODE) else { return };
143 into_call(func, names, inst, routine, &[a, b]);
144}
145
146fn negate(func: &mut Func, names: &mut Interner, inst: Inst) {
153 let Some(ty) = produced(func, inst) else { return };
154 let Some(&arg) = func[func[inst].args].first() else { return };
155 if !quad(ty) {
156 return;
157 }
158 into_call(func, names, inst, routine(Opcode::FNeg, MODE), &[arg]);
159}
160
161fn compare(func: &mut Func, names: &mut Interner, inst: Inst) {
182 let args = func[func[inst].args].to_vec();
183 let [a, b] = args[..] else { return };
184 if !quad(func[a].ty) || !quad(func[b].ty) {
185 return;
186 }
187 let Extra::FloatPred(pred) = func[inst].extra else { return };
188 if let Some((routine, test)) = single(pred) {
189 let answer = call(func, names, inst, routine, &[a, b], Type::int(NARROW));
190 let zero = ahead_const(func, inst, Imm::int(0, Type::int(NARROW)), Type::int(NARROW));
191 let extra = Extra::IntPred(test);
192 becomes(func, inst, Opcode::ICmp, extra, &[answer, zero]);
193 return;
194 }
195 if let FloatPred::False | FloatPred::True = pred {
199 let bits = u128::from(pred == FloatPred::True);
200 let extra = Extra::Imm(func.add_imm(Imm::int(bits as i128, Type::I1)));
201 becomes(func, inst, Opcode::IConst, extra, &[]);
202 return;
203 }
204 let (FloatPred::One | FloatPred::Ueq) = pred else { return };
205 let ordered = pair(func, names, inst, routine(Opcode::FCmp, "uno.f128"), a, b, IntPred::Eq);
206 let different = pair(func, names, inst, routine(Opcode::FCmp, "une.f128"), a, b, IntPred::Ne);
207 let (opcode, args) = if pred == FloatPred::One {
209 (Opcode::And, [ordered, different])
210 } else {
211 let unordered = flipped(func, inst, ordered);
212 let same = flipped(func, inst, different);
213 (Opcode::Or, [unordered, same])
214 };
215 becomes(func, inst, opcode, Extra::None, &args);
216}
217
218fn single(pred: FloatPred) -> Option<(&'static str, IntPred)> {
220 let (named, test) = match pred {
224 FloatPred::Oeq => ("oeq.f128", IntPred::Eq),
225 FloatPred::Une => ("une.f128", IntPred::Ne),
226 FloatPred::Olt => ("olt.f128", IntPred::Slt),
227 FloatPred::Ole => ("ole.f128", IntPred::Sle),
228 FloatPred::Ogt => ("ogt.f128", IntPred::Sgt),
229 FloatPred::Oge => ("oge.f128", IntPred::Sge),
230 FloatPred::Uno => ("uno.f128", IntPred::Ne),
231 FloatPred::Ord => ("uno.f128", IntPred::Eq),
232 FloatPred::Ult => ("oge.f128", IntPred::Slt),
234 FloatPred::Ule => ("ogt.f128", IntPred::Sle),
235 FloatPred::Ugt => ("ole.f128", IntPred::Sgt),
236 FloatPred::Uge => ("olt.f128", IntPred::Sge),
237 _ => return None,
238 };
239 Some((routine(Opcode::FCmp, named), test))
240}
241
242fn pair(
244 func: &mut Func,
245 names: &mut Interner,
246 inst: Inst,
247 routine: &str,
248 a: Value,
249 b: Value,
250 test: IntPred,
251) -> Value {
252 let answer = call(func, names, inst, routine, &[a, b], Type::int(NARROW));
253 let zero = ahead_const(func, inst, Imm::int(0, Type::int(NARROW)), Type::int(NARROW));
254 let args = func.push_values(&[answer, zero]);
255 let extra = Extra::IntPred(test);
256 written(func, inst, InstData { args, extra, ..InstData::new(Opcode::ICmp) }, Type::I1)
257}
258
259fn flipped(func: &mut Func, inst: Inst, value: Value) -> Value {
261 let one = ahead_const(func, inst, Imm::int(1, Type::I1), Type::I1);
262 let args = func.push_values(&[value, one]);
263 written(func, inst, InstData { args, ..InstData::new(Opcode::Xor) }, Type::I1)
264}
265
266fn constant(func: &mut Func, inst: Inst) {
283 let Some(ty) = produced(func, inst) else { return };
284 let Extra::Imm(imm) = func[inst].extra else { return };
285 if !quad(ty) {
286 return;
287 }
288 let bits = func[imm].bits();
289 let bytes = u64::from(BITS / 8);
290 let whole = MemInfo {
291 size: bytes,
292 align: BITS / 8,
293 order: MemOrder::NotAtomic,
294 tbaa: None,
295 owns: 0,
296 restrict: Restrict::NONE,
297 };
298 let slot = {
299 let extra = Extra::Mem(func.add_mem(whole));
300 written(func, inst, InstData { extra, ..InstData::new(Opcode::Alloca) }, Type::PTR)
301 };
302 let half = u64::from(WORD / 8);
303 let word = Type::int(WORD);
304 let low = ahead_const(func, inst, Imm::int(bits as i128, word), word);
305 write(func, inst, low, slot, MemInfo { size: half, ..whole });
306 let step = ahead_const(func, inst, Imm::int(half as i128, word), word);
307 let args = func.push_values(&[slot, step]);
308 let above = written(func, inst, InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR);
309 let high = ahead_const(func, inst, Imm::int((bits >> WORD) as i128, word), word);
310 write(func, inst, high, above, MemInfo { size: half, align: WORD / 8, ..whole });
311 let extra = Extra::Mem(func.add_mem(whole));
312 becomes(func, inst, Opcode::Load, extra, &[slot]);
313}
314
315fn widen(func: &mut Func, names: &mut Interner, inst: Inst) {
321 let Some(ty) = produced(func, inst) else { return };
322 let Some(&arg) = func[func[inst].args].first() else { return };
323 if !quad(ty) {
324 return;
325 }
326 let mode = match func[arg].ty.format() {
327 Some(Float::F32) => "f32.f128",
328 Some(Float::F64) => "f64.f128",
329 _ => return,
330 };
331 let routine = routine(Opcode::FPExt, mode);
332 into_call(func, names, inst, routine, &[arg]);
333}
334
335fn narrow(func: &mut Func, names: &mut Interner, inst: Inst) {
337 let Some(ty) = produced(func, inst) else { return };
338 let Some(&arg) = func[func[inst].args].first() else { return };
339 if !quad(func[arg].ty) {
340 return;
341 }
342 let mode = match ty.format() {
343 Some(Float::F32) => "f128.f32",
344 Some(Float::F64) => "f128.f64",
345 _ => return,
346 };
347 let routine = routine(Opcode::FPTrunc, mode);
348 into_call(func, names, inst, routine, &[arg]);
349}
350
351fn from_integer(func: &mut Func, names: &mut Interner, inst: Inst) {
369 let Some(ty) = produced(func, inst) else { return };
370 let Some(&arg) = func[func[inst].args].first() else { return };
371 let from = func[arg].ty;
372 if !quad(ty) || !from.is_int() || !from.is_scalar() {
373 return;
374 }
375 let signed = func[inst].opcode == Opcode::SIToFP;
376 let Some(width) = holder(from.bits()) else { return };
377 let opcode = if signed { Opcode::SIToFP } else { Opcode::UIToFP };
378 let routine = routine(opcode, if width == NARROW { "i32.f128" } else { "i64.f128" });
379 let value = if from.bits() == width {
380 arg
381 } else {
382 let opcode = if signed { Opcode::SExt } else { Opcode::ZExt };
383 let args = func.push_values(&[arg]);
384 written(func, inst, InstData { args, ..InstData::new(opcode) }, Type::int(width))
385 };
386 into_call(func, names, inst, routine, &[value]);
387}
388
389fn to_integer(func: &mut Func, names: &mut Interner, inst: Inst) {
403 let Some(ty) = produced(func, inst) else { return };
404 let Some(&arg) = func[func[inst].args].first() else { return };
405 if !quad(func[arg].ty) || !ty.is_int() || !ty.is_scalar() {
406 return;
407 }
408 let signed = func[inst].opcode == Opcode::FPToSI;
409 let Some(width) = holder(ty.bits()) else { return };
410 let opcode = if signed { Opcode::FPToSI } else { Opcode::FPToUI };
411 let routine = routine(opcode, if width == NARROW { "f128.i32" } else { "f128.i64" });
412 if ty.bits() == width {
413 into_call(func, names, inst, routine, &[arg]);
414 return;
415 }
416 let answer = call(func, names, inst, routine, &[arg], Type::int(width));
417 becomes(func, inst, Opcode::Trunc, Extra::None, &[answer]);
418}
419
420fn holder(bits: u32) -> Option<u32> {
429 match bits {
430 0..=NARROW => Some(NARROW),
431 33..=WORD => Some(WORD),
432 _ => None,
433 }
434}
435
436fn into_call(func: &mut Func, names: &mut Interner, inst: Inst, routine: &str, args: &[Value]) {
443 let Some(ty) = produced(func, inst) else { return };
444 let params: Vec<Type> = args.iter().map(|&value| func[value].ty).collect();
445 let signature = func.add_signature(Signature::new().with_params(¶ms).with_returns(&[ty]));
446 let callee = Some(names.intern(routine));
447 let varargs = func.push_abis(&[]);
448 let extra = Extra::Call(func.add_call(CallInfo { callee, signature, varargs }));
449 becomes(func, inst, Opcode::Call, extra, args);
450}
451
452fn call(
454 func: &mut Func,
455 names: &mut Interner,
456 inst: Inst,
457 routine: &str,
458 args: &[Value],
459 ty: Type,
460) -> Value {
461 let params: Vec<Type> = args.iter().map(|&value| func[value].ty).collect();
462 let signature = func.add_signature(Signature::new().with_params(¶ms).with_returns(&[ty]));
463 let callee = Some(names.intern(routine));
464 let varargs = func.push_abis(&[]);
465 let extra = Extra::Call(func.add_call(CallInfo { callee, signature, varargs }));
466 let args = func.push_values(args);
467 written(func, inst, InstData { args, extra, ..InstData::new(Opcode::Call) }, ty)
468}
469
470fn ahead_const(func: &mut Func, inst: Inst, imm: Imm, ty: Type) -> Value {
472 let extra = Extra::Imm(func.add_imm(imm));
473 written(func, inst, InstData { extra, ..InstData::new(Opcode::IConst) }, ty)
474}
475
476fn write(func: &mut Func, inst: Inst, value: Value, into: Value, info: MemInfo) {
478 let span = func.span(inst);
479 let extra = Extra::Mem(func.add_mem(info));
480 let args = func.push_values(&[value, into]);
481 let data = InstData { args, extra, ..InstData::new(Opcode::Store) };
482 let made = func.create_inst(data, &[], span);
483 func.insert_before(made, inst);
484}
485
486fn written(func: &mut Func, inst: Inst, data: InstData, ty: Type) -> Value {
488 let span = func.span(inst);
489 let made = func.create_inst(data, &[ty], span);
490 func.insert_before(made, inst);
491 func[made].first_result.expect("an instruction created with one result has one")
492}
493
494fn becomes(func: &mut Func, inst: Inst, opcode: Opcode, extra: Extra, args: &[Value]) {
496 let args = func.push_values(args);
497 let data = &mut func[inst];
498 data.opcode = opcode;
499 data.args = args;
500 data.extra = extra;
501 data.flags = data.flags.intersection(Flags::legal_on(opcode));
502}
503
504#[cfg(test)]
505mod tests {
506 use rucc_base::Interner;
507 use rucc_ir::{Block, Builder, Module, Signature};
508 use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
509
510 use super::{BITS, Flags, Float, FloatPred, Func, Opcode, Type, Value, calls};
511
512 fn quad() -> Type {
514 Type::float(Float::F128)
515 }
516
517 fn target() -> TargetInfo {
518 TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu))
519 }
520
521 fn printed(func: &Func, names: &mut Interner) -> String {
522 let module = Module::new(names.intern("q.c"), &target());
523 rucc_ir::print_func(&module, func, names)
524 }
525
526 fn shell(names: &mut Interner, params: &[Type], returns: &[Type]) -> (Func, Block, Vec<Value>) {
528 let signature = Signature::new().with_params(params).with_returns(returns);
529 let mut func = Func::new(names.intern("f"), signature);
530 let entry = func.create_block();
531 let values = params.iter().map(|&ty| func.append_param(entry, ty)).collect();
532 (func, entry, values)
533 }
534
535 fn binary(opcode: Opcode) -> String {
537 let mut names = Interner::new();
538 let (mut func, entry, params) = shell(&mut names, &[quad(), quad()], &[quad()]);
539 let mut build = Builder::new(&mut func, entry);
540 let answer = build.binary(opcode, params[0], params[1], Flags::NONE);
541 build.ret(&[answer]);
542 calls(&mut func, &mut names);
543 printed(&func, &mut names)
544 }
545
546 fn compared(pred: FloatPred) -> String {
548 let mut names = Interner::new();
549 let (mut func, entry, params) = shell(&mut names, &[quad(), quad()], &[Type::I1]);
550 let mut build = Builder::new(&mut func, entry);
551 let answer = build.fcmp(pred, params[0], params[1], Flags::NONE);
552 build.ret(&[answer]);
553 calls(&mut func, &mut names);
554 printed(&func, &mut names)
555 }
556
557 #[test]
558 fn the_four_operations_are_the_four_routines() {
559 for (opcode, routine) in [
560 (Opcode::FAdd, "__addtf3"),
561 (Opcode::FSub, "__subtf3"),
562 (Opcode::FMul, "__multf3"),
563 (Opcode::FDiv, "__divtf3"),
564 ] {
565 let text = binary(opcode);
566 assert!(text.contains(&format!("@{routine}")), "{routine}: {text}");
567 assert_eq!(text.matches(" = f").count(), 0, "no float arithmetic left: {text}");
570 assert_eq!(text.matches(" = call").count(), 1, "one call: {text}");
571 }
572 }
573
574 #[test]
575 fn a_negation_is_the_routine_rather_than_a_sign_flip() {
576 let mut names = Interner::new();
577 let (mut func, entry, params) = shell(&mut names, &[quad()], &[quad()]);
578 let mut build = Builder::new(&mut func, entry);
579 let answer = build.unary(Opcode::FNeg, params[0], quad());
580 build.ret(&[answer]);
581 calls(&mut func, &mut names);
582 let text = printed(&func, &mut names);
583 assert!(text.contains("@__negtf2"), "{text}");
584 assert!(!text.contains("xor"), "no sign flip in a register: {text}");
585 }
586
587 #[test]
590 fn an_ordered_comparison_is_its_own_routine_tested_against_zero() {
591 for (pred, routine, test) in [
592 (FloatPred::Oeq, "__eqtf2", "icmp eq"),
593 (FloatPred::Une, "__netf2", "icmp ne"),
594 (FloatPred::Olt, "__lttf2", "icmp slt"),
595 (FloatPred::Ole, "__letf2", "icmp sle"),
596 (FloatPred::Ogt, "__gttf2", "icmp sgt"),
597 (FloatPred::Oge, "__getf2", "icmp sge"),
598 ] {
599 let text = compared(pred);
600 assert!(text.contains(&format!("@{routine}")), "{routine}: {text}");
601 assert!(text.contains(test), "{test}: {text}");
602 assert!(!text.contains("fcmp"), "the comparison is gone: {text}");
603 }
604 }
605
606 #[test]
612 fn an_unordered_comparison_is_the_opposite_routine_read_the_same_way() {
613 for (pred, routine, test) in [
614 (FloatPred::Ult, "__getf2", "icmp slt"),
615 (FloatPred::Ule, "__gttf2", "icmp sle"),
616 (FloatPred::Ugt, "__letf2", "icmp sgt"),
617 (FloatPred::Uge, "__lttf2", "icmp sge"),
618 ] {
619 let text = compared(pred);
620 assert!(text.contains(&format!("@{routine}")), "{routine}: {text}");
621 assert!(text.contains(test), "{test}: {text}");
622 }
623 }
624
625 #[test]
626 fn whether_two_values_can_be_ordered_at_all_is_one_routine_either_way_round() {
627 let unordered = compared(FloatPred::Uno);
628 assert!(unordered.contains("@__unordtf2"), "{unordered}");
629 assert!(unordered.contains("icmp ne"), "{unordered}");
630 let ordered = compared(FloatPred::Ord);
631 assert!(ordered.contains("@__unordtf2"), "{ordered}");
632 assert!(ordered.contains("icmp eq"), "{ordered}");
633 }
634
635 #[test]
637 fn ordered_and_different_is_two_calls_joined() {
638 let text = compared(FloatPred::One);
639 assert!(text.contains("@__unordtf2"), "{text}");
640 assert!(text.contains("@__netf2"), "{text}");
641 assert_eq!(text.matches(" = call").count(), 2, "both calls: {text}");
642 assert_eq!(text.matches(" = and").count(), 1, "joined: {text}");
643 assert!(!text.contains("xor"), "nothing is negated: {text}");
644 }
645
646 #[test]
648 fn unordered_or_equal_is_the_negation_of_it() {
649 let text = compared(FloatPred::Ueq);
650 assert_eq!(text.matches(" = call").count(), 2, "both calls: {text}");
651 assert_eq!(text.matches(" = or").count(), 1, "joined the other way: {text}");
652 assert_eq!(text.matches(" = xor").count(), 2, "both answers negated: {text}");
653 }
654
655 #[test]
656 fn the_two_comparisons_with_no_operands_to_read_are_constants() {
657 let never = compared(FloatPred::False);
658 assert!(never.contains("iconst.i1 0"), "{never}");
659 assert!(!never.contains("call"), "nothing is called: {never}");
660 let always = compared(FloatPred::True);
663 assert!(always.contains("iconst.i1 -1"), "{always}");
664 }
665
666 #[test]
668 fn a_constant_goes_through_the_frame_a_word_at_a_time() {
669 let mut names = Interner::new();
670 let (mut func, entry, _) = shell(&mut names, &[], &[quad()]);
671 let mut build = Builder::new(&mut func, entry);
672 let value = build.fconst(quad(), (3u128 << 64) | 5);
675 build.ret(&[value]);
676 calls(&mut func, &mut names);
677 let text = printed(&func, &mut names);
678 assert!(!text.contains("fconst"), "the constant is gone: {text}");
679 assert_eq!(text.matches("alloca").count(), 1, "one slot: {text}");
680 assert_eq!(text.matches("store").count(), 2, "a word at a time: {text}");
681 assert!(text.contains("iconst.i64 5"), "the low word first: {text}");
682 assert!(text.contains("iconst.i64 3"), "the high word above it: {text}");
683 assert_eq!(text.matches("ptr_add").count(), 1, "the high word is eight bytes up: {text}");
684 assert_eq!(text.matches(" = load").count(), 1, "read back as one value: {text}");
685 }
686
687 #[test]
688 fn the_two_narrower_formats_are_a_routine_each_way() {
689 for (from, to, routine) in [
690 (Float::F32, Float::F128, "__extendsftf2"),
691 (Float::F64, Float::F128, "__extenddftf2"),
692 (Float::F128, Float::F32, "__trunctfsf2"),
693 (Float::F128, Float::F64, "__trunctfdf2"),
694 ] {
695 let mut names = Interner::new();
696 let (mut func, entry, params) =
697 shell(&mut names, &[Type::float(from)], &[Type::float(to)]);
698 let mut build = Builder::new(&mut func, entry);
699 let opcode = if to == Float::F128 { Opcode::FPExt } else { Opcode::FPTrunc };
700 let answer = build.unary(opcode, params[0], Type::float(to));
701 build.ret(&[answer]);
702 calls(&mut func, &mut names);
703 let text = printed(&func, &mut names);
704 assert!(text.contains(&format!("@{routine}")), "{routine}: {text}");
705 }
706 }
707
708 #[test]
711 fn a_narrow_integer_is_widened_before_the_conversion() {
712 for (opcode, bits, extend, routine) in [
713 (Opcode::SIToFP, 16, " = sext", "__floatsitf"),
714 (Opcode::UIToFP, 16, " = zext", "__floatunsitf"),
715 (Opcode::SIToFP, 32, "", "__floatsitf"),
716 (Opcode::UIToFP, 64, "", "__floatunditf"),
717 ] {
718 let mut names = Interner::new();
719 let (mut func, entry, params) = shell(&mut names, &[Type::int(bits)], &[quad()]);
720 let mut build = Builder::new(&mut func, entry);
721 let answer = build.unary(opcode, params[0], quad());
722 build.ret(&[answer]);
723 calls(&mut func, &mut names);
724 let text = printed(&func, &mut names);
725 assert!(text.contains(&format!("@{routine}")), "{routine}: {text}");
726 if extend.is_empty() {
727 assert!(!text.contains(" = sext"), "nothing to widen: {text}");
728 assert!(!text.contains(" = zext"), "nothing to widen: {text}");
729 } else {
730 assert!(text.contains(extend), "{extend}: {text}");
731 }
732 }
733 }
734
735 #[test]
737 fn a_narrow_answer_is_the_wider_routine_and_a_truncation() {
738 let mut names = Interner::new();
739 let (mut func, entry, params) = shell(&mut names, &[quad()], &[Type::int(16)]);
740 let mut build = Builder::new(&mut func, entry);
741 let answer = build.unary(Opcode::FPToSI, params[0], Type::int(16));
742 build.ret(&[answer]);
743 calls(&mut func, &mut names);
744 let text = printed(&func, &mut names);
745 assert!(text.contains("@__fixtfsi"), "{text}");
746 assert_eq!(text.matches(" = trunc").count(), 1, "cut down afterwards: {text}");
747 }
748
749 #[test]
750 fn a_conversion_against_a_wide_integer_is_left_exactly_as_it_was() {
751 let mut names = Interner::new();
752 let (mut func, entry, params) = shell(&mut names, &[Type::int(BITS)], &[quad()]);
753 let mut build = Builder::new(&mut func, entry);
754 let answer = build.unary(Opcode::SIToFP, params[0], quad());
755 build.ret(&[answer]);
756 calls(&mut func, &mut names);
757 let text = printed(&func, &mut names);
758 assert!(!text.contains("call"), "no routine is called: {text}");
759 assert!(text.contains("sitofp"), "the conversion is still there to be refused: {text}");
760 }
761
762 #[test]
764 fn the_narrower_formats_go_past_untouched() {
765 let mut names = Interner::new();
766 let double = Type::float(Float::F64);
767 let (mut func, entry, params) = shell(&mut names, &[double, double], &[double]);
768 let mut build = Builder::new(&mut func, entry);
769 let sum = build.binary(Opcode::FAdd, params[0], params[1], Flags::NONE);
770 let answer = build.fcmp(FloatPred::Olt, sum, params[1], Flags::NONE);
771 build.ret(&[sum]);
772 let _ = answer;
773 calls(&mut func, &mut names);
774 let text = printed(&func, &mut names);
775 assert!(!text.contains("call"), "nothing became a call: {text}");
776 assert!(text.contains("fadd"), "the add is still an add: {text}");
777 assert!(text.contains("fcmp"), "the comparison is still a comparison: {text}");
778 }
779}