1use rucc_base::Interner;
44use rucc_ir::{
45 CallInfo, Extra, Flags, Float, FloatPred, Func, Imm, Inst, InstData, IntPred, MemInfo,
46 MemOrder, Opcode, Restrict, Signature, Type, Value,
47};
48
49const QUAD: Float = Float::F128;
51
52const BITS: u32 = 128;
54
55const NARROW: u32 = 32;
58const WORD: u32 = 64;
59
60pub fn calls(func: &mut Func, names: &mut Interner) {
65 let found: Vec<Inst> =
66 func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
67 for inst in found {
68 match func[inst].opcode {
69 Opcode::FAdd | Opcode::FSub | Opcode::FMul | Opcode::FDiv => {
70 arithmetic(func, names, inst);
71 }
72 Opcode::FNeg => negate(func, names, inst),
73 Opcode::FCmp => compare(func, names, inst),
74 Opcode::FConst => constant(func, inst),
75 Opcode::FPExt => widen(func, names, inst),
76 Opcode::FPTrunc => narrow(func, names, inst),
77 Opcode::SIToFP | Opcode::UIToFP => from_integer(func, names, inst),
78 Opcode::FPToSI | Opcode::FPToUI => to_integer(func, names, inst),
79 _ => {}
80 }
81 }
82}
83
84fn quad(ty: Type) -> bool {
86 ty.is_scalar() && ty.format() == Some(QUAD)
87}
88
89fn produced(func: &Func, inst: Inst) -> Option<Type> {
91 func[inst].first_result.map(|value| func[value].ty)
92}
93
94fn arithmetic(func: &mut Func, names: &mut Interner, inst: Inst) {
101 let Some(ty) = produced(func, inst) else { return };
102 if !quad(ty) {
103 return;
104 }
105 let args = func[func[inst].args].to_vec();
106 let [a, b] = args[..] else { return };
107 let routine = match func[inst].opcode {
108 Opcode::FAdd => "__addtf3",
109 Opcode::FSub => "__subtf3",
110 Opcode::FMul => "__multf3",
111 Opcode::FDiv => "__divtf3",
112 _ => return,
113 };
114 into_call(func, names, inst, routine, &[a, b]);
115}
116
117fn negate(func: &mut Func, names: &mut Interner, inst: Inst) {
124 let Some(ty) = produced(func, inst) else { return };
125 let Some(&arg) = func[func[inst].args].first() else { return };
126 if !quad(ty) {
127 return;
128 }
129 into_call(func, names, inst, "__negtf2", &[arg]);
130}
131
132fn compare(func: &mut Func, names: &mut Interner, inst: Inst) {
153 let args = func[func[inst].args].to_vec();
154 let [a, b] = args[..] else { return };
155 if !quad(func[a].ty) || !quad(func[b].ty) {
156 return;
157 }
158 let Extra::FloatPred(pred) = func[inst].extra else { return };
159 if let Some((routine, test)) = single(pred) {
160 let answer = call(func, names, inst, routine, &[a, b], Type::int(NARROW));
161 let zero = ahead_const(func, inst, Imm::int(0, Type::int(NARROW)), Type::int(NARROW));
162 let extra = Extra::IntPred(test);
163 becomes(func, inst, Opcode::ICmp, extra, &[answer, zero]);
164 return;
165 }
166 if let FloatPred::False | FloatPred::True = pred {
170 let bits = u128::from(pred == FloatPred::True);
171 let extra = Extra::Imm(func.add_imm(Imm::int(bits as i128, Type::I1)));
172 becomes(func, inst, Opcode::IConst, extra, &[]);
173 return;
174 }
175 let (FloatPred::One | FloatPred::Ueq) = pred else { return };
176 let ordered = pair(func, names, inst, "__unordtf2", a, b, IntPred::Eq);
177 let different = pair(func, names, inst, "__netf2", a, b, IntPred::Ne);
178 let (opcode, args) = if pred == FloatPred::One {
180 (Opcode::And, [ordered, different])
181 } else {
182 let unordered = flipped(func, inst, ordered);
183 let same = flipped(func, inst, different);
184 (Opcode::Or, [unordered, same])
185 };
186 becomes(func, inst, opcode, Extra::None, &args);
187}
188
189fn single(pred: FloatPred) -> Option<(&'static str, IntPred)> {
191 Some(match pred {
192 FloatPred::Oeq => ("__eqtf2", IntPred::Eq),
193 FloatPred::Une => ("__netf2", IntPred::Ne),
194 FloatPred::Olt => ("__lttf2", IntPred::Slt),
195 FloatPred::Ole => ("__letf2", IntPred::Sle),
196 FloatPred::Ogt => ("__gttf2", IntPred::Sgt),
197 FloatPred::Oge => ("__getf2", IntPred::Sge),
198 FloatPred::Uno => ("__unordtf2", IntPred::Ne),
199 FloatPred::Ord => ("__unordtf2", IntPred::Eq),
200 FloatPred::Ult => ("__getf2", IntPred::Slt),
202 FloatPred::Ule => ("__gttf2", IntPred::Sle),
203 FloatPred::Ugt => ("__letf2", IntPred::Sgt),
204 FloatPred::Uge => ("__lttf2", IntPred::Sge),
205 _ => return None,
206 })
207}
208
209fn pair(
211 func: &mut Func,
212 names: &mut Interner,
213 inst: Inst,
214 routine: &str,
215 a: Value,
216 b: Value,
217 test: IntPred,
218) -> Value {
219 let answer = call(func, names, inst, routine, &[a, b], Type::int(NARROW));
220 let zero = ahead_const(func, inst, Imm::int(0, Type::int(NARROW)), Type::int(NARROW));
221 let args = func.push_values(&[answer, zero]);
222 let extra = Extra::IntPred(test);
223 written(func, inst, InstData { args, extra, ..InstData::new(Opcode::ICmp) }, Type::I1)
224}
225
226fn flipped(func: &mut Func, inst: Inst, value: Value) -> Value {
228 let one = ahead_const(func, inst, Imm::int(1, Type::I1), Type::I1);
229 let args = func.push_values(&[value, one]);
230 written(func, inst, InstData { args, ..InstData::new(Opcode::Xor) }, Type::I1)
231}
232
233fn constant(func: &mut Func, inst: Inst) {
250 let Some(ty) = produced(func, inst) else { return };
251 let Extra::Imm(imm) = func[inst].extra else { return };
252 if !quad(ty) {
253 return;
254 }
255 let bits = func[imm].bits();
256 let bytes = u64::from(BITS / 8);
257 let whole = MemInfo {
258 size: bytes,
259 align: BITS / 8,
260 order: MemOrder::NotAtomic,
261 tbaa: None,
262 owns: 0,
263 restrict: Restrict::NONE,
264 };
265 let slot = {
266 let extra = Extra::Mem(func.add_mem(whole));
267 written(func, inst, InstData { extra, ..InstData::new(Opcode::Alloca) }, Type::PTR)
268 };
269 let half = u64::from(WORD / 8);
270 let word = Type::int(WORD);
271 let low = ahead_const(func, inst, Imm::int(bits as i128, word), word);
272 write(func, inst, low, slot, MemInfo { size: half, ..whole });
273 let step = ahead_const(func, inst, Imm::int(half as i128, word), word);
274 let args = func.push_values(&[slot, step]);
275 let above = written(func, inst, InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR);
276 let high = ahead_const(func, inst, Imm::int((bits >> WORD) as i128, word), word);
277 write(func, inst, high, above, MemInfo { size: half, align: WORD / 8, ..whole });
278 let extra = Extra::Mem(func.add_mem(whole));
279 becomes(func, inst, Opcode::Load, extra, &[slot]);
280}
281
282fn widen(func: &mut Func, names: &mut Interner, inst: Inst) {
288 let Some(ty) = produced(func, inst) else { return };
289 let Some(&arg) = func[func[inst].args].first() else { return };
290 if !quad(ty) {
291 return;
292 }
293 let routine = match func[arg].ty.format() {
294 Some(Float::F32) => "__extendsftf2",
295 Some(Float::F64) => "__extenddftf2",
296 _ => return,
297 };
298 into_call(func, names, inst, routine, &[arg]);
299}
300
301fn narrow(func: &mut Func, names: &mut Interner, inst: Inst) {
303 let Some(ty) = produced(func, inst) else { return };
304 let Some(&arg) = func[func[inst].args].first() else { return };
305 if !quad(func[arg].ty) {
306 return;
307 }
308 let routine = match ty.format() {
309 Some(Float::F32) => "__trunctfsf2",
310 Some(Float::F64) => "__trunctfdf2",
311 _ => return,
312 };
313 into_call(func, names, inst, routine, &[arg]);
314}
315
316fn from_integer(func: &mut Func, names: &mut Interner, inst: Inst) {
333 let Some(ty) = produced(func, inst) else { return };
334 let Some(&arg) = func[func[inst].args].first() else { return };
335 let from = func[arg].ty;
336 if !quad(ty) || !from.is_int() || !from.is_scalar() {
337 return;
338 }
339 let signed = func[inst].opcode == Opcode::SIToFP;
340 let Some(width) = holder(from.bits()) else { return };
341 let routine = match (signed, width) {
342 (true, NARROW) => "__floatsitf",
343 (false, NARROW) => "__floatunsitf",
344 (true, _) => "__floatditf",
345 (false, _) => "__floatunditf",
346 };
347 let value = if from.bits() == width {
348 arg
349 } else {
350 let opcode = if signed { Opcode::SExt } else { Opcode::ZExt };
351 let args = func.push_values(&[arg]);
352 written(func, inst, InstData { args, ..InstData::new(opcode) }, Type::int(width))
353 };
354 into_call(func, names, inst, routine, &[value]);
355}
356
357fn to_integer(func: &mut Func, names: &mut Interner, inst: Inst) {
371 let Some(ty) = produced(func, inst) else { return };
372 let Some(&arg) = func[func[inst].args].first() else { return };
373 if !quad(func[arg].ty) || !ty.is_int() || !ty.is_scalar() {
374 return;
375 }
376 let signed = func[inst].opcode == Opcode::FPToSI;
377 let Some(width) = holder(ty.bits()) else { return };
378 let routine = match (signed, width) {
379 (true, NARROW) => "__fixtfsi",
380 (false, NARROW) => "__fixunstfsi",
381 (true, _) => "__fixtfdi",
382 (false, _) => "__fixunstfdi",
383 };
384 if ty.bits() == width {
385 into_call(func, names, inst, routine, &[arg]);
386 return;
387 }
388 let answer = call(func, names, inst, routine, &[arg], Type::int(width));
389 becomes(func, inst, Opcode::Trunc, Extra::None, &[answer]);
390}
391
392fn holder(bits: u32) -> Option<u32> {
400 match bits {
401 0..=NARROW => Some(NARROW),
402 33..=WORD => Some(WORD),
403 _ => None,
404 }
405}
406
407fn into_call(func: &mut Func, names: &mut Interner, inst: Inst, routine: &str, args: &[Value]) {
414 let Some(ty) = produced(func, inst) else { return };
415 let params: Vec<Type> = args.iter().map(|&value| func[value].ty).collect();
416 let signature = func.add_signature(Signature::new().with_params(¶ms).with_returns(&[ty]));
417 let callee = Some(names.intern(routine));
418 let varargs = func.push_abis(&[]);
419 let extra = Extra::Call(func.add_call(CallInfo { callee, signature, varargs }));
420 becomes(func, inst, Opcode::Call, extra, args);
421}
422
423fn call(
425 func: &mut Func,
426 names: &mut Interner,
427 inst: Inst,
428 routine: &str,
429 args: &[Value],
430 ty: Type,
431) -> Value {
432 let params: Vec<Type> = args.iter().map(|&value| func[value].ty).collect();
433 let signature = func.add_signature(Signature::new().with_params(¶ms).with_returns(&[ty]));
434 let callee = Some(names.intern(routine));
435 let varargs = func.push_abis(&[]);
436 let extra = Extra::Call(func.add_call(CallInfo { callee, signature, varargs }));
437 let args = func.push_values(args);
438 written(func, inst, InstData { args, extra, ..InstData::new(Opcode::Call) }, ty)
439}
440
441fn ahead_const(func: &mut Func, inst: Inst, imm: Imm, ty: Type) -> Value {
443 let extra = Extra::Imm(func.add_imm(imm));
444 written(func, inst, InstData { extra, ..InstData::new(Opcode::IConst) }, ty)
445}
446
447fn write(func: &mut Func, inst: Inst, value: Value, into: Value, info: MemInfo) {
449 let span = func.span(inst);
450 let extra = Extra::Mem(func.add_mem(info));
451 let args = func.push_values(&[value, into]);
452 let data = InstData { args, extra, ..InstData::new(Opcode::Store) };
453 let made = func.create_inst(data, &[], span);
454 func.insert_before(made, inst);
455}
456
457fn written(func: &mut Func, inst: Inst, data: InstData, ty: Type) -> Value {
459 let span = func.span(inst);
460 let made = func.create_inst(data, &[ty], span);
461 func.insert_before(made, inst);
462 func[made].first_result.expect("an instruction created with one result has one")
463}
464
465fn becomes(func: &mut Func, inst: Inst, opcode: Opcode, extra: Extra, args: &[Value]) {
467 let args = func.push_values(args);
468 let data = &mut func[inst];
469 data.opcode = opcode;
470 data.args = args;
471 data.extra = extra;
472 data.flags = data.flags.intersection(Flags::legal_on(opcode));
473}
474
475#[cfg(test)]
476mod tests {
477 use rucc_base::Interner;
478 use rucc_ir::{Block, Builder, Module, Signature};
479 use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
480
481 use super::{BITS, Flags, Float, FloatPred, Func, Opcode, Type, Value, calls};
482
483 fn quad() -> Type {
485 Type::float(Float::F128)
486 }
487
488 fn target() -> TargetInfo {
489 TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu))
490 }
491
492 fn printed(func: &Func, names: &mut Interner) -> String {
493 let module = Module::new(names.intern("q.c"), &target());
494 rucc_ir::print_func(&module, func, names)
495 }
496
497 fn shell(names: &mut Interner, params: &[Type], returns: &[Type]) -> (Func, Block, Vec<Value>) {
499 let signature = Signature::new().with_params(params).with_returns(returns);
500 let mut func = Func::new(names.intern("f"), signature);
501 let entry = func.create_block();
502 let values = params.iter().map(|&ty| func.append_param(entry, ty)).collect();
503 (func, entry, values)
504 }
505
506 fn binary(opcode: Opcode) -> String {
508 let mut names = Interner::new();
509 let (mut func, entry, params) = shell(&mut names, &[quad(), quad()], &[quad()]);
510 let mut build = Builder::new(&mut func, entry);
511 let answer = build.binary(opcode, params[0], params[1], Flags::NONE);
512 build.ret(&[answer]);
513 calls(&mut func, &mut names);
514 printed(&func, &mut names)
515 }
516
517 fn compared(pred: FloatPred) -> String {
519 let mut names = Interner::new();
520 let (mut func, entry, params) = shell(&mut names, &[quad(), quad()], &[Type::I1]);
521 let mut build = Builder::new(&mut func, entry);
522 let answer = build.fcmp(pred, params[0], params[1], Flags::NONE);
523 build.ret(&[answer]);
524 calls(&mut func, &mut names);
525 printed(&func, &mut names)
526 }
527
528 #[test]
529 fn the_four_operations_are_the_four_routines() {
530 for (opcode, routine) in [
531 (Opcode::FAdd, "__addtf3"),
532 (Opcode::FSub, "__subtf3"),
533 (Opcode::FMul, "__multf3"),
534 (Opcode::FDiv, "__divtf3"),
535 ] {
536 let text = binary(opcode);
537 assert!(text.contains(&format!("@{routine}")), "{routine}: {text}");
538 assert_eq!(text.matches(" = f").count(), 0, "no float arithmetic left: {text}");
541 assert_eq!(text.matches(" = call").count(), 1, "one call: {text}");
542 }
543 }
544
545 #[test]
546 fn a_negation_is_the_routine_rather_than_a_sign_flip() {
547 let mut names = Interner::new();
548 let (mut func, entry, params) = shell(&mut names, &[quad()], &[quad()]);
549 let mut build = Builder::new(&mut func, entry);
550 let answer = build.unary(Opcode::FNeg, params[0], quad());
551 build.ret(&[answer]);
552 calls(&mut func, &mut names);
553 let text = printed(&func, &mut names);
554 assert!(text.contains("@__negtf2"), "{text}");
555 assert!(!text.contains("xor"), "no sign flip in a register: {text}");
556 }
557
558 #[test]
561 fn an_ordered_comparison_is_its_own_routine_tested_against_zero() {
562 for (pred, routine, test) in [
563 (FloatPred::Oeq, "__eqtf2", "icmp eq"),
564 (FloatPred::Une, "__netf2", "icmp ne"),
565 (FloatPred::Olt, "__lttf2", "icmp slt"),
566 (FloatPred::Ole, "__letf2", "icmp sle"),
567 (FloatPred::Ogt, "__gttf2", "icmp sgt"),
568 (FloatPred::Oge, "__getf2", "icmp sge"),
569 ] {
570 let text = compared(pred);
571 assert!(text.contains(&format!("@{routine}")), "{routine}: {text}");
572 assert!(text.contains(test), "{test}: {text}");
573 assert!(!text.contains("fcmp"), "the comparison is gone: {text}");
574 }
575 }
576
577 #[test]
583 fn an_unordered_comparison_is_the_opposite_routine_read_the_same_way() {
584 for (pred, routine, test) in [
585 (FloatPred::Ult, "__getf2", "icmp slt"),
586 (FloatPred::Ule, "__gttf2", "icmp sle"),
587 (FloatPred::Ugt, "__letf2", "icmp sgt"),
588 (FloatPred::Uge, "__lttf2", "icmp sge"),
589 ] {
590 let text = compared(pred);
591 assert!(text.contains(&format!("@{routine}")), "{routine}: {text}");
592 assert!(text.contains(test), "{test}: {text}");
593 }
594 }
595
596 #[test]
597 fn whether_two_values_can_be_ordered_at_all_is_one_routine_either_way_round() {
598 let unordered = compared(FloatPred::Uno);
599 assert!(unordered.contains("@__unordtf2"), "{unordered}");
600 assert!(unordered.contains("icmp ne"), "{unordered}");
601 let ordered = compared(FloatPred::Ord);
602 assert!(ordered.contains("@__unordtf2"), "{ordered}");
603 assert!(ordered.contains("icmp eq"), "{ordered}");
604 }
605
606 #[test]
608 fn ordered_and_different_is_two_calls_joined() {
609 let text = compared(FloatPred::One);
610 assert!(text.contains("@__unordtf2"), "{text}");
611 assert!(text.contains("@__netf2"), "{text}");
612 assert_eq!(text.matches(" = call").count(), 2, "both calls: {text}");
613 assert_eq!(text.matches(" = and").count(), 1, "joined: {text}");
614 assert!(!text.contains("xor"), "nothing is negated: {text}");
615 }
616
617 #[test]
619 fn unordered_or_equal_is_the_negation_of_it() {
620 let text = compared(FloatPred::Ueq);
621 assert_eq!(text.matches(" = call").count(), 2, "both calls: {text}");
622 assert_eq!(text.matches(" = or").count(), 1, "joined the other way: {text}");
623 assert_eq!(text.matches(" = xor").count(), 2, "both answers negated: {text}");
624 }
625
626 #[test]
627 fn the_two_comparisons_with_no_operands_to_read_are_constants() {
628 let never = compared(FloatPred::False);
629 assert!(never.contains("iconst.i1 0"), "{never}");
630 assert!(!never.contains("call"), "nothing is called: {never}");
631 let always = compared(FloatPred::True);
634 assert!(always.contains("iconst.i1 -1"), "{always}");
635 }
636
637 #[test]
639 fn a_constant_goes_through_the_frame_a_word_at_a_time() {
640 let mut names = Interner::new();
641 let (mut func, entry, _) = shell(&mut names, &[], &[quad()]);
642 let mut build = Builder::new(&mut func, entry);
643 let value = build.fconst(quad(), (3u128 << 64) | 5);
646 build.ret(&[value]);
647 calls(&mut func, &mut names);
648 let text = printed(&func, &mut names);
649 assert!(!text.contains("fconst"), "the constant is gone: {text}");
650 assert_eq!(text.matches("alloca").count(), 1, "one slot: {text}");
651 assert_eq!(text.matches("store").count(), 2, "a word at a time: {text}");
652 assert!(text.contains("iconst.i64 5"), "the low word first: {text}");
653 assert!(text.contains("iconst.i64 3"), "the high word above it: {text}");
654 assert_eq!(text.matches("ptr_add").count(), 1, "the high word is eight bytes up: {text}");
655 assert_eq!(text.matches(" = load").count(), 1, "read back as one value: {text}");
656 }
657
658 #[test]
659 fn the_two_narrower_formats_are_a_routine_each_way() {
660 for (from, to, routine) in [
661 (Float::F32, Float::F128, "__extendsftf2"),
662 (Float::F64, Float::F128, "__extenddftf2"),
663 (Float::F128, Float::F32, "__trunctfsf2"),
664 (Float::F128, Float::F64, "__trunctfdf2"),
665 ] {
666 let mut names = Interner::new();
667 let (mut func, entry, params) =
668 shell(&mut names, &[Type::float(from)], &[Type::float(to)]);
669 let mut build = Builder::new(&mut func, entry);
670 let opcode = if to == Float::F128 { Opcode::FPExt } else { Opcode::FPTrunc };
671 let answer = build.unary(opcode, params[0], Type::float(to));
672 build.ret(&[answer]);
673 calls(&mut func, &mut names);
674 let text = printed(&func, &mut names);
675 assert!(text.contains(&format!("@{routine}")), "{routine}: {text}");
676 }
677 }
678
679 #[test]
682 fn a_narrow_integer_is_widened_before_the_conversion() {
683 for (opcode, bits, extend, routine) in [
684 (Opcode::SIToFP, 16, " = sext", "__floatsitf"),
685 (Opcode::UIToFP, 16, " = zext", "__floatunsitf"),
686 (Opcode::SIToFP, 32, "", "__floatsitf"),
687 (Opcode::UIToFP, 64, "", "__floatunditf"),
688 ] {
689 let mut names = Interner::new();
690 let (mut func, entry, params) = shell(&mut names, &[Type::int(bits)], &[quad()]);
691 let mut build = Builder::new(&mut func, entry);
692 let answer = build.unary(opcode, params[0], quad());
693 build.ret(&[answer]);
694 calls(&mut func, &mut names);
695 let text = printed(&func, &mut names);
696 assert!(text.contains(&format!("@{routine}")), "{routine}: {text}");
697 if extend.is_empty() {
698 assert!(!text.contains(" = sext"), "nothing to widen: {text}");
699 assert!(!text.contains(" = zext"), "nothing to widen: {text}");
700 } else {
701 assert!(text.contains(extend), "{extend}: {text}");
702 }
703 }
704 }
705
706 #[test]
708 fn a_narrow_answer_is_the_wider_routine_and_a_truncation() {
709 let mut names = Interner::new();
710 let (mut func, entry, params) = shell(&mut names, &[quad()], &[Type::int(16)]);
711 let mut build = Builder::new(&mut func, entry);
712 let answer = build.unary(Opcode::FPToSI, params[0], Type::int(16));
713 build.ret(&[answer]);
714 calls(&mut func, &mut names);
715 let text = printed(&func, &mut names);
716 assert!(text.contains("@__fixtfsi"), "{text}");
717 assert_eq!(text.matches(" = trunc").count(), 1, "cut down afterwards: {text}");
718 }
719
720 #[test]
721 fn a_conversion_against_a_wide_integer_is_left_exactly_as_it_was() {
722 let mut names = Interner::new();
723 let (mut func, entry, params) = shell(&mut names, &[Type::int(BITS)], &[quad()]);
724 let mut build = Builder::new(&mut func, entry);
725 let answer = build.unary(Opcode::SIToFP, params[0], quad());
726 build.ret(&[answer]);
727 calls(&mut func, &mut names);
728 let text = printed(&func, &mut names);
729 assert!(!text.contains("call"), "no routine is called: {text}");
730 assert!(text.contains("sitofp"), "the conversion is still there to be refused: {text}");
731 }
732
733 #[test]
735 fn the_narrower_formats_go_past_untouched() {
736 let mut names = Interner::new();
737 let double = Type::float(Float::F64);
738 let (mut func, entry, params) = shell(&mut names, &[double, double], &[double]);
739 let mut build = Builder::new(&mut func, entry);
740 let sum = build.binary(Opcode::FAdd, params[0], params[1], Flags::NONE);
741 let answer = build.fcmp(FloatPred::Olt, sum, params[1], Flags::NONE);
742 build.ret(&[sum]);
743 let _ = answer;
744 calls(&mut func, &mut names);
745 let text = printed(&func, &mut names);
746 assert!(!text.contains("call"), "nothing became a call: {text}");
747 assert!(text.contains("fadd"), "the add is still an add: {text}");
748 assert!(text.contains("fcmp"), "the comparison is still a comparison: {text}");
749 }
750}