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