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
65const QUAD: Float = Float::F128;
67
68const BITS: u32 = 128;
70
71const NARROW: u32 = 32;
74const WORD: u32 = 64;
75
76pub fn calls(func: &mut Func, names: &mut Interner) {
81 let found: Vec<Inst> =
82 func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
83 for inst in found {
84 match func[inst].opcode {
85 Opcode::FAdd | Opcode::FSub | Opcode::FMul | Opcode::FDiv => {
86 arithmetic(func, names, inst);
87 }
88 Opcode::FNeg => negate(func, names, inst),
89 Opcode::FCmp => compare(func, names, inst),
90 Opcode::FConst => constant(func, inst),
91 Opcode::FPExt => widen(func, names, inst),
92 Opcode::FPTrunc => narrow(func, names, inst),
93 Opcode::SIToFP | Opcode::UIToFP => from_integer(func, names, inst),
94 Opcode::FPToSI | Opcode::FPToUI => to_integer(func, names, inst),
95 _ => {}
96 }
97 }
98}
99
100fn quad(ty: Type) -> bool {
102 ty.is_scalar() && ty.format() == Some(QUAD)
103}
104
105fn produced(func: &Func, inst: Inst) -> Option<Type> {
107 func[inst].first_result.map(|value| func[value].ty)
108}
109
110fn arithmetic(func: &mut Func, names: &mut Interner, inst: Inst) {
117 let Some(ty) = produced(func, inst) else { return };
118 if !quad(ty) {
119 return;
120 }
121 let args = func[func[inst].args].to_vec();
122 let [a, b] = args[..] else { return };
123 let routine = match func[inst].opcode {
124 Opcode::FAdd => "__addtf3",
125 Opcode::FSub => "__subtf3",
126 Opcode::FMul => "__multf3",
127 Opcode::FDiv => "__divtf3",
128 _ => return,
129 };
130 into_call(func, names, inst, routine, &[a, b]);
131}
132
133fn negate(func: &mut Func, names: &mut Interner, inst: Inst) {
140 let Some(ty) = produced(func, inst) else { return };
141 let Some(&arg) = func[func[inst].args].first() else { return };
142 if !quad(ty) {
143 return;
144 }
145 into_call(func, names, inst, "__negtf2", &[arg]);
146}
147
148fn compare(func: &mut Func, names: &mut Interner, inst: Inst) {
169 let args = func[func[inst].args].to_vec();
170 let [a, b] = args[..] else { return };
171 if !quad(func[a].ty) || !quad(func[b].ty) {
172 return;
173 }
174 let Extra::FloatPred(pred) = func[inst].extra else { return };
175 if let Some((routine, test)) = single(pred) {
176 let answer = call(func, names, inst, routine, &[a, b], Type::int(NARROW));
177 let zero = ahead_const(func, inst, Imm::int(0, Type::int(NARROW)), Type::int(NARROW));
178 let extra = Extra::IntPred(test);
179 becomes(func, inst, Opcode::ICmp, extra, &[answer, zero]);
180 return;
181 }
182 if let FloatPred::False | FloatPred::True = pred {
186 let bits = u128::from(pred == FloatPred::True);
187 let extra = Extra::Imm(func.add_imm(Imm::int(bits as i128, Type::I1)));
188 becomes(func, inst, Opcode::IConst, extra, &[]);
189 return;
190 }
191 let (FloatPred::One | FloatPred::Ueq) = pred else { return };
192 let ordered = pair(func, names, inst, "__unordtf2", a, b, IntPred::Eq);
193 let different = pair(func, names, inst, "__netf2", a, b, IntPred::Ne);
194 let (opcode, args) = if pred == FloatPred::One {
196 (Opcode::And, [ordered, different])
197 } else {
198 let unordered = flipped(func, inst, ordered);
199 let same = flipped(func, inst, different);
200 (Opcode::Or, [unordered, same])
201 };
202 becomes(func, inst, opcode, Extra::None, &args);
203}
204
205fn single(pred: FloatPred) -> Option<(&'static str, IntPred)> {
207 Some(match pred {
208 FloatPred::Oeq => ("__eqtf2", IntPred::Eq),
209 FloatPred::Une => ("__netf2", IntPred::Ne),
210 FloatPred::Olt => ("__lttf2", IntPred::Slt),
211 FloatPred::Ole => ("__letf2", IntPred::Sle),
212 FloatPred::Ogt => ("__gttf2", IntPred::Sgt),
213 FloatPred::Oge => ("__getf2", IntPred::Sge),
214 FloatPred::Uno => ("__unordtf2", IntPred::Ne),
215 FloatPred::Ord => ("__unordtf2", IntPred::Eq),
216 FloatPred::Ult => ("__getf2", IntPred::Slt),
218 FloatPred::Ule => ("__gttf2", IntPred::Sle),
219 FloatPred::Ugt => ("__letf2", IntPred::Sgt),
220 FloatPred::Uge => ("__lttf2", IntPred::Sge),
221 _ => return None,
222 })
223}
224
225fn pair(
227 func: &mut Func,
228 names: &mut Interner,
229 inst: Inst,
230 routine: &str,
231 a: Value,
232 b: Value,
233 test: IntPred,
234) -> Value {
235 let answer = call(func, names, inst, routine, &[a, b], Type::int(NARROW));
236 let zero = ahead_const(func, inst, Imm::int(0, Type::int(NARROW)), Type::int(NARROW));
237 let args = func.push_values(&[answer, zero]);
238 let extra = Extra::IntPred(test);
239 written(func, inst, InstData { args, extra, ..InstData::new(Opcode::ICmp) }, Type::I1)
240}
241
242fn flipped(func: &mut Func, inst: Inst, value: Value) -> Value {
244 let one = ahead_const(func, inst, Imm::int(1, Type::I1), Type::I1);
245 let args = func.push_values(&[value, one]);
246 written(func, inst, InstData { args, ..InstData::new(Opcode::Xor) }, Type::I1)
247}
248
249fn constant(func: &mut Func, inst: Inst) {
266 let Some(ty) = produced(func, inst) else { return };
267 let Extra::Imm(imm) = func[inst].extra else { return };
268 if !quad(ty) {
269 return;
270 }
271 let bits = func[imm].bits();
272 let bytes = u64::from(BITS / 8);
273 let whole = MemInfo {
274 size: bytes,
275 align: BITS / 8,
276 order: MemOrder::NotAtomic,
277 tbaa: None,
278 owns: 0,
279 restrict: Restrict::NONE,
280 };
281 let slot = {
282 let extra = Extra::Mem(func.add_mem(whole));
283 written(func, inst, InstData { extra, ..InstData::new(Opcode::Alloca) }, Type::PTR)
284 };
285 let half = u64::from(WORD / 8);
286 let word = Type::int(WORD);
287 let low = ahead_const(func, inst, Imm::int(bits as i128, word), word);
288 write(func, inst, low, slot, MemInfo { size: half, ..whole });
289 let step = ahead_const(func, inst, Imm::int(half as i128, word), word);
290 let args = func.push_values(&[slot, step]);
291 let above = written(func, inst, InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR);
292 let high = ahead_const(func, inst, Imm::int((bits >> WORD) as i128, word), word);
293 write(func, inst, high, above, MemInfo { size: half, align: WORD / 8, ..whole });
294 let extra = Extra::Mem(func.add_mem(whole));
295 becomes(func, inst, Opcode::Load, extra, &[slot]);
296}
297
298fn widen(func: &mut Func, names: &mut Interner, inst: Inst) {
304 let Some(ty) = produced(func, inst) else { return };
305 let Some(&arg) = func[func[inst].args].first() else { return };
306 if !quad(ty) {
307 return;
308 }
309 let routine = match func[arg].ty.format() {
310 Some(Float::F32) => "__extendsftf2",
311 Some(Float::F64) => "__extenddftf2",
312 _ => return,
313 };
314 into_call(func, names, inst, routine, &[arg]);
315}
316
317fn narrow(func: &mut Func, names: &mut Interner, inst: Inst) {
319 let Some(ty) = produced(func, inst) else { return };
320 let Some(&arg) = func[func[inst].args].first() else { return };
321 if !quad(func[arg].ty) {
322 return;
323 }
324 let routine = match ty.format() {
325 Some(Float::F32) => "__trunctfsf2",
326 Some(Float::F64) => "__trunctfdf2",
327 _ => return,
328 };
329 into_call(func, names, inst, routine, &[arg]);
330}
331
332fn from_integer(func: &mut Func, names: &mut Interner, inst: Inst) {
350 let Some(ty) = produced(func, inst) else { return };
351 let Some(&arg) = func[func[inst].args].first() else { return };
352 let from = func[arg].ty;
353 if !quad(ty) || !from.is_int() || !from.is_scalar() {
354 return;
355 }
356 let signed = func[inst].opcode == Opcode::SIToFP;
357 let Some(width) = holder(from.bits()) else { return };
358 let routine = match (signed, width) {
359 (true, NARROW) => "__floatsitf",
360 (false, NARROW) => "__floatunsitf",
361 (true, _) => "__floatditf",
362 (false, _) => "__floatunditf",
363 };
364 let value = if from.bits() == width {
365 arg
366 } else {
367 let opcode = if signed { Opcode::SExt } else { Opcode::ZExt };
368 let args = func.push_values(&[arg]);
369 written(func, inst, InstData { args, ..InstData::new(opcode) }, Type::int(width))
370 };
371 into_call(func, names, inst, routine, &[value]);
372}
373
374fn to_integer(func: &mut Func, names: &mut Interner, inst: Inst) {
388 let Some(ty) = produced(func, inst) else { return };
389 let Some(&arg) = func[func[inst].args].first() else { return };
390 if !quad(func[arg].ty) || !ty.is_int() || !ty.is_scalar() {
391 return;
392 }
393 let signed = func[inst].opcode == Opcode::FPToSI;
394 let Some(width) = holder(ty.bits()) else { return };
395 let routine = match (signed, width) {
396 (true, NARROW) => "__fixtfsi",
397 (false, NARROW) => "__fixunstfsi",
398 (true, _) => "__fixtfdi",
399 (false, _) => "__fixunstfdi",
400 };
401 if ty.bits() == width {
402 into_call(func, names, inst, routine, &[arg]);
403 return;
404 }
405 let answer = call(func, names, inst, routine, &[arg], Type::int(width));
406 becomes(func, inst, Opcode::Trunc, Extra::None, &[answer]);
407}
408
409fn holder(bits: u32) -> Option<u32> {
418 match bits {
419 0..=NARROW => Some(NARROW),
420 33..=WORD => Some(WORD),
421 _ => None,
422 }
423}
424
425fn into_call(func: &mut Func, names: &mut Interner, inst: Inst, routine: &str, args: &[Value]) {
432 let Some(ty) = produced(func, inst) else { return };
433 let params: Vec<Type> = args.iter().map(|&value| func[value].ty).collect();
434 let signature = func.add_signature(Signature::new().with_params(¶ms).with_returns(&[ty]));
435 let callee = Some(names.intern(routine));
436 let varargs = func.push_abis(&[]);
437 let extra = Extra::Call(func.add_call(CallInfo { callee, signature, varargs }));
438 becomes(func, inst, Opcode::Call, extra, args);
439}
440
441fn call(
443 func: &mut Func,
444 names: &mut Interner,
445 inst: Inst,
446 routine: &str,
447 args: &[Value],
448 ty: Type,
449) -> Value {
450 let params: Vec<Type> = args.iter().map(|&value| func[value].ty).collect();
451 let signature = func.add_signature(Signature::new().with_params(¶ms).with_returns(&[ty]));
452 let callee = Some(names.intern(routine));
453 let varargs = func.push_abis(&[]);
454 let extra = Extra::Call(func.add_call(CallInfo { callee, signature, varargs }));
455 let args = func.push_values(args);
456 written(func, inst, InstData { args, extra, ..InstData::new(Opcode::Call) }, ty)
457}
458
459fn ahead_const(func: &mut Func, inst: Inst, imm: Imm, ty: Type) -> Value {
461 let extra = Extra::Imm(func.add_imm(imm));
462 written(func, inst, InstData { extra, ..InstData::new(Opcode::IConst) }, ty)
463}
464
465fn write(func: &mut Func, inst: Inst, value: Value, into: Value, info: MemInfo) {
467 let span = func.span(inst);
468 let extra = Extra::Mem(func.add_mem(info));
469 let args = func.push_values(&[value, into]);
470 let data = InstData { args, extra, ..InstData::new(Opcode::Store) };
471 let made = func.create_inst(data, &[], span);
472 func.insert_before(made, inst);
473}
474
475fn written(func: &mut Func, inst: Inst, data: InstData, ty: Type) -> Value {
477 let span = func.span(inst);
478 let made = func.create_inst(data, &[ty], span);
479 func.insert_before(made, inst);
480 func[made].first_result.expect("an instruction created with one result has one")
481}
482
483fn becomes(func: &mut Func, inst: Inst, opcode: Opcode, extra: Extra, args: &[Value]) {
485 let args = func.push_values(args);
486 let data = &mut func[inst];
487 data.opcode = opcode;
488 data.args = args;
489 data.extra = extra;
490 data.flags = data.flags.intersection(Flags::legal_on(opcode));
491}
492
493#[cfg(test)]
494mod tests {
495 use rucc_base::Interner;
496 use rucc_ir::{Block, Builder, Module, Signature};
497 use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
498
499 use super::{BITS, Flags, Float, FloatPred, Func, Opcode, Type, Value, calls};
500
501 fn quad() -> Type {
503 Type::float(Float::F128)
504 }
505
506 fn target() -> TargetInfo {
507 TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu))
508 }
509
510 fn printed(func: &Func, names: &mut Interner) -> String {
511 let module = Module::new(names.intern("q.c"), &target());
512 rucc_ir::print_func(&module, func, names)
513 }
514
515 fn shell(names: &mut Interner, params: &[Type], returns: &[Type]) -> (Func, Block, Vec<Value>) {
517 let signature = Signature::new().with_params(params).with_returns(returns);
518 let mut func = Func::new(names.intern("f"), signature);
519 let entry = func.create_block();
520 let values = params.iter().map(|&ty| func.append_param(entry, ty)).collect();
521 (func, entry, values)
522 }
523
524 fn binary(opcode: Opcode) -> String {
526 let mut names = Interner::new();
527 let (mut func, entry, params) = shell(&mut names, &[quad(), quad()], &[quad()]);
528 let mut build = Builder::new(&mut func, entry);
529 let answer = build.binary(opcode, params[0], params[1], Flags::NONE);
530 build.ret(&[answer]);
531 calls(&mut func, &mut names);
532 printed(&func, &mut names)
533 }
534
535 fn compared(pred: FloatPred) -> String {
537 let mut names = Interner::new();
538 let (mut func, entry, params) = shell(&mut names, &[quad(), quad()], &[Type::I1]);
539 let mut build = Builder::new(&mut func, entry);
540 let answer = build.fcmp(pred, params[0], params[1], Flags::NONE);
541 build.ret(&[answer]);
542 calls(&mut func, &mut names);
543 printed(&func, &mut names)
544 }
545
546 #[test]
547 fn the_four_operations_are_the_four_routines() {
548 for (opcode, routine) in [
549 (Opcode::FAdd, "__addtf3"),
550 (Opcode::FSub, "__subtf3"),
551 (Opcode::FMul, "__multf3"),
552 (Opcode::FDiv, "__divtf3"),
553 ] {
554 let text = binary(opcode);
555 assert!(text.contains(&format!("@{routine}")), "{routine}: {text}");
556 assert_eq!(text.matches(" = f").count(), 0, "no float arithmetic left: {text}");
559 assert_eq!(text.matches(" = call").count(), 1, "one call: {text}");
560 }
561 }
562
563 #[test]
564 fn a_negation_is_the_routine_rather_than_a_sign_flip() {
565 let mut names = Interner::new();
566 let (mut func, entry, params) = shell(&mut names, &[quad()], &[quad()]);
567 let mut build = Builder::new(&mut func, entry);
568 let answer = build.unary(Opcode::FNeg, params[0], quad());
569 build.ret(&[answer]);
570 calls(&mut func, &mut names);
571 let text = printed(&func, &mut names);
572 assert!(text.contains("@__negtf2"), "{text}");
573 assert!(!text.contains("xor"), "no sign flip in a register: {text}");
574 }
575
576 #[test]
579 fn an_ordered_comparison_is_its_own_routine_tested_against_zero() {
580 for (pred, routine, test) in [
581 (FloatPred::Oeq, "__eqtf2", "icmp eq"),
582 (FloatPred::Une, "__netf2", "icmp ne"),
583 (FloatPred::Olt, "__lttf2", "icmp slt"),
584 (FloatPred::Ole, "__letf2", "icmp sle"),
585 (FloatPred::Ogt, "__gttf2", "icmp sgt"),
586 (FloatPred::Oge, "__getf2", "icmp sge"),
587 ] {
588 let text = compared(pred);
589 assert!(text.contains(&format!("@{routine}")), "{routine}: {text}");
590 assert!(text.contains(test), "{test}: {text}");
591 assert!(!text.contains("fcmp"), "the comparison is gone: {text}");
592 }
593 }
594
595 #[test]
601 fn an_unordered_comparison_is_the_opposite_routine_read_the_same_way() {
602 for (pred, routine, test) in [
603 (FloatPred::Ult, "__getf2", "icmp slt"),
604 (FloatPred::Ule, "__gttf2", "icmp sle"),
605 (FloatPred::Ugt, "__letf2", "icmp sgt"),
606 (FloatPred::Uge, "__lttf2", "icmp sge"),
607 ] {
608 let text = compared(pred);
609 assert!(text.contains(&format!("@{routine}")), "{routine}: {text}");
610 assert!(text.contains(test), "{test}: {text}");
611 }
612 }
613
614 #[test]
615 fn whether_two_values_can_be_ordered_at_all_is_one_routine_either_way_round() {
616 let unordered = compared(FloatPred::Uno);
617 assert!(unordered.contains("@__unordtf2"), "{unordered}");
618 assert!(unordered.contains("icmp ne"), "{unordered}");
619 let ordered = compared(FloatPred::Ord);
620 assert!(ordered.contains("@__unordtf2"), "{ordered}");
621 assert!(ordered.contains("icmp eq"), "{ordered}");
622 }
623
624 #[test]
626 fn ordered_and_different_is_two_calls_joined() {
627 let text = compared(FloatPred::One);
628 assert!(text.contains("@__unordtf2"), "{text}");
629 assert!(text.contains("@__netf2"), "{text}");
630 assert_eq!(text.matches(" = call").count(), 2, "both calls: {text}");
631 assert_eq!(text.matches(" = and").count(), 1, "joined: {text}");
632 assert!(!text.contains("xor"), "nothing is negated: {text}");
633 }
634
635 #[test]
637 fn unordered_or_equal_is_the_negation_of_it() {
638 let text = compared(FloatPred::Ueq);
639 assert_eq!(text.matches(" = call").count(), 2, "both calls: {text}");
640 assert_eq!(text.matches(" = or").count(), 1, "joined the other way: {text}");
641 assert_eq!(text.matches(" = xor").count(), 2, "both answers negated: {text}");
642 }
643
644 #[test]
645 fn the_two_comparisons_with_no_operands_to_read_are_constants() {
646 let never = compared(FloatPred::False);
647 assert!(never.contains("iconst.i1 0"), "{never}");
648 assert!(!never.contains("call"), "nothing is called: {never}");
649 let always = compared(FloatPred::True);
652 assert!(always.contains("iconst.i1 -1"), "{always}");
653 }
654
655 #[test]
657 fn a_constant_goes_through_the_frame_a_word_at_a_time() {
658 let mut names = Interner::new();
659 let (mut func, entry, _) = shell(&mut names, &[], &[quad()]);
660 let mut build = Builder::new(&mut func, entry);
661 let value = build.fconst(quad(), (3u128 << 64) | 5);
664 build.ret(&[value]);
665 calls(&mut func, &mut names);
666 let text = printed(&func, &mut names);
667 assert!(!text.contains("fconst"), "the constant is gone: {text}");
668 assert_eq!(text.matches("alloca").count(), 1, "one slot: {text}");
669 assert_eq!(text.matches("store").count(), 2, "a word at a time: {text}");
670 assert!(text.contains("iconst.i64 5"), "the low word first: {text}");
671 assert!(text.contains("iconst.i64 3"), "the high word above it: {text}");
672 assert_eq!(text.matches("ptr_add").count(), 1, "the high word is eight bytes up: {text}");
673 assert_eq!(text.matches(" = load").count(), 1, "read back as one value: {text}");
674 }
675
676 #[test]
677 fn the_two_narrower_formats_are_a_routine_each_way() {
678 for (from, to, routine) in [
679 (Float::F32, Float::F128, "__extendsftf2"),
680 (Float::F64, Float::F128, "__extenddftf2"),
681 (Float::F128, Float::F32, "__trunctfsf2"),
682 (Float::F128, Float::F64, "__trunctfdf2"),
683 ] {
684 let mut names = Interner::new();
685 let (mut func, entry, params) =
686 shell(&mut names, &[Type::float(from)], &[Type::float(to)]);
687 let mut build = Builder::new(&mut func, entry);
688 let opcode = if to == Float::F128 { Opcode::FPExt } else { Opcode::FPTrunc };
689 let answer = build.unary(opcode, params[0], Type::float(to));
690 build.ret(&[answer]);
691 calls(&mut func, &mut names);
692 let text = printed(&func, &mut names);
693 assert!(text.contains(&format!("@{routine}")), "{routine}: {text}");
694 }
695 }
696
697 #[test]
700 fn a_narrow_integer_is_widened_before_the_conversion() {
701 for (opcode, bits, extend, routine) in [
702 (Opcode::SIToFP, 16, " = sext", "__floatsitf"),
703 (Opcode::UIToFP, 16, " = zext", "__floatunsitf"),
704 (Opcode::SIToFP, 32, "", "__floatsitf"),
705 (Opcode::UIToFP, 64, "", "__floatunditf"),
706 ] {
707 let mut names = Interner::new();
708 let (mut func, entry, params) = shell(&mut names, &[Type::int(bits)], &[quad()]);
709 let mut build = Builder::new(&mut func, entry);
710 let answer = build.unary(opcode, params[0], quad());
711 build.ret(&[answer]);
712 calls(&mut func, &mut names);
713 let text = printed(&func, &mut names);
714 assert!(text.contains(&format!("@{routine}")), "{routine}: {text}");
715 if extend.is_empty() {
716 assert!(!text.contains(" = sext"), "nothing to widen: {text}");
717 assert!(!text.contains(" = zext"), "nothing to widen: {text}");
718 } else {
719 assert!(text.contains(extend), "{extend}: {text}");
720 }
721 }
722 }
723
724 #[test]
726 fn a_narrow_answer_is_the_wider_routine_and_a_truncation() {
727 let mut names = Interner::new();
728 let (mut func, entry, params) = shell(&mut names, &[quad()], &[Type::int(16)]);
729 let mut build = Builder::new(&mut func, entry);
730 let answer = build.unary(Opcode::FPToSI, params[0], Type::int(16));
731 build.ret(&[answer]);
732 calls(&mut func, &mut names);
733 let text = printed(&func, &mut names);
734 assert!(text.contains("@__fixtfsi"), "{text}");
735 assert_eq!(text.matches(" = trunc").count(), 1, "cut down afterwards: {text}");
736 }
737
738 #[test]
739 fn a_conversion_against_a_wide_integer_is_left_exactly_as_it_was() {
740 let mut names = Interner::new();
741 let (mut func, entry, params) = shell(&mut names, &[Type::int(BITS)], &[quad()]);
742 let mut build = Builder::new(&mut func, entry);
743 let answer = build.unary(Opcode::SIToFP, params[0], quad());
744 build.ret(&[answer]);
745 calls(&mut func, &mut names);
746 let text = printed(&func, &mut names);
747 assert!(!text.contains("call"), "no routine is called: {text}");
748 assert!(text.contains("sitofp"), "the conversion is still there to be refused: {text}");
749 }
750
751 #[test]
753 fn the_narrower_formats_go_past_untouched() {
754 let mut names = Interner::new();
755 let double = Type::float(Float::F64);
756 let (mut func, entry, params) = shell(&mut names, &[double, double], &[double]);
757 let mut build = Builder::new(&mut func, entry);
758 let sum = build.binary(Opcode::FAdd, params[0], params[1], Flags::NONE);
759 let answer = build.fcmp(FloatPred::Olt, sum, params[1], Flags::NONE);
760 build.ret(&[sum]);
761 let _ = answer;
762 calls(&mut func, &mut names);
763 let text = printed(&func, &mut names);
764 assert!(!text.contains("call"), "nothing became a call: {text}");
765 assert!(text.contains("fadd"), "the add is still an add: {text}");
766 assert!(text.contains("fcmp"), "the comparison is still a comparison: {text}");
767 }
768}