1use rucc_ir::{
36 BlockCall, Builder, Extra, Flags, Func, Imm, Inst, InstData, IntPred, Opcode, Type, Value,
37};
38
39pub fn switches(func: &mut Func) {
45 let found: Vec<Inst> = func
46 .blocks()
47 .filter_map(|block| func.terminator(block))
48 .filter(|&inst| func[inst].opcode == Opcode::Switch)
49 .collect();
50 for inst in found {
51 chain(func, inst);
52 }
53}
54
55fn chain(func: &mut Func, inst: Inst) {
67 let block = func.block_of(inst).expect("a terminator is in a block");
68 let span = func.span(inst);
69 let Extra::Switch(info) = func[inst].extra else { return };
70 let info = func[info];
71 let value = func[func[inst].args][0];
72 let ty = func[value].ty.lane();
75 let calls: Vec<BlockCall> = func[info.targets].to_vec();
76 let cases: Vec<Imm> = func[info.cases].to_vec();
77 let Some((default, arms)) = calls.split_first() else { return };
78
79 func.remove_inst(inst);
82
83 let Some((first, rest)) = arms.split_first() else {
87 let args: Vec<Value> = func[default.args].to_vec();
88 Builder::new(func, block).at(span).jump(default.block, &args);
89 return;
90 };
91
92 let mut at = block;
93 for (index, arm) in std::iter::once(first).chain(rest).enumerate() {
94 let last = index + 1 == arms.len();
95 let next = if last { default.block } else { func.create_block() };
96 let onward: Vec<Value> = if last { func[default.args].to_vec() } else { Vec::new() };
97 let taken: Vec<Value> = func[arm.args].to_vec();
98 let case = cases[index].signed(ty);
99
100 let mut build = Builder::new(func, at).at(span);
101 let want = build.iconst(ty, case);
102 let same = build.icmp(IntPred::Eq, value, want);
103 build.br_if(same, arm.block, &taken, next, &onward);
104 at = next;
105 }
106}
107
108pub fn floats(func: &mut Func) {
123 let found: Vec<Inst> =
124 func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
125 for inst in found {
126 match func[inst].opcode {
127 Opcode::FConst => constant(func, inst),
128 Opcode::FNeg => negate(func, inst),
129 Opcode::SIToFP | Opcode::UIToFP => widen_then_convert(func, inst),
130 Opcode::FPToSI | Opcode::FPToUI => convert_then_narrow(func, inst),
131 _ => {}
132 }
133 }
134}
135
136fn constant(func: &mut Func, inst: Inst) {
143 let ty = produced(func, inst);
144 let Extra::Imm(imm) = func[inst].extra else { return };
145 if !ty.is_float() || !ty.is_scalar() {
146 return;
147 }
148 let int = Type::int(ty.bits());
149 let bits = func[imm].bits();
150 let spelled = ahead_const(func, inst, Imm::int(bits as i128, int), int);
153 becomes(func, inst, Opcode::Bitcast, &[spelled]);
154}
155
156fn negate(func: &mut Func, inst: Inst) {
167 let ty = produced(func, inst);
168 let Some(&arg) = func[func[inst].args].first() else { return };
169 if !ty.is_float() || !ty.is_scalar() {
170 return;
171 }
172 let int = Type::int(ty.bits());
173 let bits = ahead(func, inst, Opcode::Bitcast, &[arg], int);
174 let mask = ahead_const(func, inst, Imm::int(1i128 << (ty.bits() - 1), int), int);
175 let flipped = ahead(func, inst, Opcode::Xor, &[bits, mask], int);
176 becomes(func, inst, Opcode::Bitcast, &[flipped]);
177}
178
179fn widen_then_convert(func: &mut Func, inst: Inst) {
186 let signed = func[inst].opcode == Opcode::SIToFP;
187 let Some(&arg) = func[func[inst].args].first() else { return };
188 let from = func[arg].ty;
189 if !from.is_int() || !from.is_scalar() {
190 return;
191 }
192 let Some(width) = holder(from.bits(), signed) else { return };
193 if width == from.bits() {
194 return;
195 }
196 let widen = if signed { Opcode::SExt } else { Opcode::ZExt };
197 let wide = ahead(func, inst, widen, &[arg], Type::int(width));
198 becomes(func, inst, Opcode::SIToFP, &[wide]);
199}
200
201fn convert_then_narrow(func: &mut Func, inst: Inst) {
208 let signed = func[inst].opcode == Opcode::FPToSI;
209 let ty = produced(func, inst);
210 let Some(&arg) = func[func[inst].args].first() else { return };
211 if !ty.is_int() || !ty.is_scalar() {
212 return;
213 }
214 let Some(width) = holder(ty.bits(), signed) else { return };
215 if width == ty.bits() {
216 return;
217 }
218 let wide = ahead(func, inst, Opcode::FPToSI, &[arg], Type::int(width));
219 becomes(func, inst, Opcode::Trunc, &[wide]);
220}
221
222fn holder(bits: u32, signed: bool) -> Option<u32> {
231 match if signed { bits } else { bits + 1 } {
232 ..=32 => Some(32),
233 33..=64 => Some(64),
234 _ => None,
235 }
236}
237
238fn produced(func: &Func, inst: Inst) -> Type {
243 func[inst].first_result.map_or(Type::VOID, |value| func[value].ty)
244}
245
246fn ahead(func: &mut Func, inst: Inst, opcode: Opcode, args: &[Value], ty: Type) -> Value {
248 let args = func.push_values(args);
249 written(func, inst, InstData { args, ..InstData::new(opcode) }, ty)
250}
251
252fn ahead_const(func: &mut Func, inst: Inst, imm: Imm, ty: Type) -> Value {
254 let extra = Extra::Imm(func.add_imm(imm));
255 written(func, inst, InstData { extra, ..InstData::new(Opcode::IConst) }, ty)
256}
257
258fn written(func: &mut Func, inst: Inst, data: InstData, ty: Type) -> Value {
260 let span = func.span(inst);
261 let made = func.create_inst(data, &[ty], span);
262 func.insert_before(made, inst);
263 func[made].first_result.expect("an instruction created with one result has one")
264}
265
266fn becomes(func: &mut Func, inst: Inst, opcode: Opcode, args: &[Value]) {
273 let args = func.push_values(args);
274 let data = &mut func[inst];
275 data.opcode = opcode;
276 data.args = args;
277 data.extra = Extra::None;
278 data.flags = data.flags.intersection(Flags::legal_on(opcode));
281}
282
283#[must_use]
288pub fn blocks_for(cases: usize) -> usize {
289 cases.saturating_sub(1)
290}
291
292#[cfg(test)]
293mod tests {
294 use rucc_base::Interner;
295 use rucc_ir::{Builder, Flags, Float, Func, Module, Opcode, Signature, Type};
296 use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
297
298 use super::{blocks_for, floats, switches};
299
300 fn target() -> TargetInfo {
301 TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu))
302 }
303
304 fn built(cases: &[i128]) -> (Interner, Func) {
307 let mut names = Interner::new();
308 let int = Type::int(32);
309 let mut func = Func::new(
310 names.intern("sw"),
311 Signature::new().with_params(&[int]).with_returns(&[int]),
312 );
313 let entry = func.create_block();
314 let x = func.append_param(entry, int);
315
316 let default = func.create_block();
317 let arms: Vec<_> = cases.iter().map(|_| func.create_block()).collect();
318 let table: Vec<(i128, rucc_ir::Block)> =
319 cases.iter().copied().zip(arms.iter().copied()).collect();
320 Builder::new(&mut func, entry).switch(x, default, &table);
321
322 for (index, &arm) in arms.iter().enumerate() {
323 let mut build = Builder::new(&mut func, arm);
324 let what = i128::try_from(index).expect("a small number of cases");
325 let v = build.iconst(int, (what + 1) * 10);
326 build.ret(&[v]);
327 }
328 let mut build = Builder::new(&mut func, default);
329 let v = build.iconst(int, 30);
330 build.ret(&[v]);
331 (names, func)
332 }
333
334 fn count(func: &Func) -> usize {
335 func.blocks().count()
336 }
337
338 fn printed(func: &Func, names: &mut Interner) -> String {
339 let module = Module::new(names.intern("sw.c"), &target());
340 rucc_ir::print_func(&module, func, names)
341 }
342
343 #[test]
344 fn a_switch_becomes_a_compare_and_a_branch_for_each_case() {
345 let (mut names, mut func) = built(&[1, 2]);
346 let before = count(&func);
347 switches(&mut func);
348 assert_eq!(count(&func), before + blocks_for(2));
349
350 let text = printed(&func, &mut names);
351 assert!(!text.contains("switch"), "the switch is gone: {text}");
352 assert_eq!(text.matches("icmp eq").count(), 2, "one compare per case: {text}");
353 assert_eq!(text.matches("br_if").count(), 2, "one branch per case: {text}");
354 }
355
356 #[test]
357 fn the_last_case_falls_to_the_default_rather_than_to_a_block_of_its_own() {
358 let (_, mut func) = built(&[7]);
359 let before = count(&func);
360 switches(&mut func);
361 assert_eq!(count(&func), before);
363 assert_eq!(blocks_for(1), 0);
364 }
365
366 #[test]
367 fn a_switch_with_only_a_default_is_a_jump() {
368 let (_, mut func) = built(&[]);
369 switches(&mut func);
370 let entry = func.entry().expect("an entry block");
371 let term = func.terminator(entry).expect("a terminator");
372 assert_eq!(func[term].opcode, Opcode::Jump);
373 }
374
375 #[test]
378 fn what_comes_out_is_valid_ir() {
379 let (mut names, mut func) = built(&[1, 2, 3, 4]);
380 switches(&mut func);
381 let module = Module::new(names.intern("sw.c"), &target());
382 rucc_ir::verify_func(&module, &func, &names).expect("the rewrite builds valid IR");
383 }
384
385 #[test]
388 fn a_function_with_no_switch_is_left_exactly_as_it_was() {
389 let mut names = Interner::new();
390 let int = Type::int(32);
391 let mut func =
392 Func::new(names.intern("f"), Signature::new().with_params(&[int]).with_returns(&[int]));
393 let entry = func.create_block();
394 let x = func.append_param(entry, int);
395 Builder::new(&mut func, entry).ret(&[x]);
396
397 let before = printed(&func, &mut names);
398 switches(&mut func);
399 assert_eq!(printed(&func, &mut names), before);
400 }
401
402 fn one(
407 params: &[Type],
408 returns: &[Type],
409 body: impl FnOnce(&mut Builder<'_>, &[rucc_ir::Value]),
410 ) -> (Interner, Func) {
411 let mut names = Interner::new();
412 let mut func = Func::new(
413 names.intern("f"),
414 Signature::new().with_params(params).with_returns(returns),
415 );
416 let entry = func.create_block();
417 let args: Vec<_> = params.iter().map(|&ty| func.append_param(entry, ty)).collect();
418 let mut build = Builder::new(&mut func, entry);
419 body(&mut build, &args);
420 (names, func)
421 }
422
423 fn f64() -> Type {
424 Type::float(Float::F64)
425 }
426
427 fn f32() -> Type {
428 Type::float(Float::F32)
429 }
430
431 #[test]
433 fn a_float_constant_becomes_the_integer_that_spells_it_and_a_reading_of_those_bits() {
434 let (mut names, mut func) = one(&[], &[f64()], |build, _| {
435 let k = build.fconst(f64(), 0x3ff8_0000_0000_0000);
436 build.ret(&[k]);
437 });
438 floats(&mut func);
439
440 let text = printed(&func, &mut names);
441 assert!(!text.contains("fconst"), "the float constant is gone: {text}");
442 assert!(text.contains("iconst.i64 4609434218613702656"), "the bits, as an integer: {text}");
443 assert!(text.contains("bitcast"), "read back as the float: {text}");
444 }
445
446 #[test]
449 fn a_constant_at_the_narrow_format_is_an_integer_of_the_narrow_width() {
450 let (mut names, mut func) = one(&[], &[f32()], |build, _| {
451 let k = build.fconst(f32(), 0x4020_0000);
452 build.ret(&[k]);
453 });
454 floats(&mut func);
455 assert!(printed(&func, &mut names).contains("iconst.i32"), "an i32, not an i64");
456 }
457
458 #[test]
461 fn a_negation_flips_the_sign_bit_and_touches_no_other() {
462 let (mut names, mut func) = one(&[f64()], &[f64()], |build, args| {
463 let n = build.unary(Opcode::FNeg, args[0], f64());
464 build.ret(&[n]);
465 });
466 floats(&mut func);
467
468 let text = printed(&func, &mut names);
469 assert!(!text.contains("fneg"), "the negation is gone: {text}");
470 assert!(!text.contains("fsub"), "and it did not become a subtraction: {text}");
471 assert!(text.contains("iconst.i64 -9223372036854775808"), "the sign bit alone: {text}");
472 assert_eq!(text.matches("xor").count(), 1, "one exclusive or: {text}");
473 assert_eq!(text.matches("bitcast").count(), 2, "there and back: {text}");
474 }
475
476 #[test]
478 fn an_unsigned_integer_becoming_a_float_widens_first_and_then_converts_as_signed() {
479 let (mut names, mut func) = one(&[Type::int(32)], &[f64()], |build, args| {
480 let d = build.unary(Opcode::UIToFP, args[0], f64());
481 build.ret(&[d]);
482 });
483 floats(&mut func);
484
485 let text = printed(&func, &mut names);
486 assert!(!text.contains("uitofp"), "the unsigned conversion is gone: {text}");
487 assert!(text.contains("zext.i64"), "widened with zeroes: {text}");
488 assert!(text.contains("sitofp.f64"), "converted as signed: {text}");
489 }
490
491 #[test]
493 fn a_float_becoming_an_unsigned_integer_converts_as_signed_first_and_then_narrows() {
494 let (mut names, mut func) = one(&[f64()], &[Type::int(32)], |build, args| {
495 let n = build.unary(Opcode::FPToUI, args[0], Type::int(32));
496 build.ret(&[n]);
497 });
498 floats(&mut func);
499
500 let text = printed(&func, &mut names);
501 assert!(!text.contains("fptoui"), "the unsigned conversion is gone: {text}");
502 assert!(text.contains("fptosi.i64"), "converted as signed: {text}");
503 assert!(text.contains("trunc.i32"), "and narrowed to what was asked: {text}");
504 }
505
506 #[test]
509 fn a_conversion_narrower_than_the_machine_has_is_one_it_has_and_a_narrowing() {
510 let (mut names, mut func) = one(&[f64()], &[Type::int(8)], |build, args| {
511 let n = build.unary(Opcode::FPToSI, args[0], Type::int(8));
512 build.ret(&[n]);
513 });
514 floats(&mut func);
515
516 let text = printed(&func, &mut names);
517 assert!(text.contains("fptosi.i32"), "converted at a width there is one at: {text}");
518 assert!(text.contains("trunc.i8"), "and narrowed to what was asked: {text}");
519 }
520
521 #[test]
523 fn a_signed_integer_narrower_than_the_machine_converts_from_is_widened_with_its_sign() {
524 let (mut names, mut func) = one(&[Type::int(8)], &[f64()], |build, args| {
525 let d = build.unary(Opcode::SIToFP, args[0], f64());
526 build.ret(&[d]);
527 });
528 floats(&mut func);
529
530 let text = printed(&func, &mut names);
531 assert!(text.contains("sext.i32"), "widened with the sign and not with zeroes: {text}");
532 assert!(!text.contains("zext"), "widened with the sign and not with zeroes: {text}");
533 assert!(text.contains("sitofp.f64"), "converted at a width there is one at: {text}");
534 }
535
536 #[test]
538 fn the_width_a_conversion_happens_at_is_the_narrowest_one_that_holds_the_values() {
539 use super::holder;
540 for bits in [1, 8, 16, 32] {
541 assert_eq!(holder(bits, true), Some(32), "a signed {bits} bit value fits in an int");
542 }
543 assert_eq!(holder(64, true), Some(64));
544 for bits in [1, 8, 16, 31] {
545 assert_eq!(holder(bits, false), Some(32), "an unsigned {bits} bit value does too");
546 }
547 assert_eq!(holder(32, false), Some(64));
549 assert_eq!(holder(64, false), None);
550 }
551
552 #[test]
556 fn the_unsigned_conversions_at_the_widest_width_are_left_alone() {
557 let (mut names, mut func) = one(&[Type::int(64)], &[f64()], |build, args| {
558 let d = build.unary(Opcode::UIToFP, args[0], f64());
559 build.ret(&[d]);
560 });
561 let before = printed(&func, &mut names);
562 floats(&mut func);
563 assert_eq!(printed(&func, &mut names), before);
564
565 let (mut names, mut func) = one(&[f64()], &[Type::int(64)], |build, args| {
566 let n = build.unary(Opcode::FPToUI, args[0], Type::int(64));
567 build.ret(&[n]);
568 });
569 let before = printed(&func, &mut names);
570 floats(&mut func);
571 assert_eq!(printed(&func, &mut names), before);
572 }
573
574 #[test]
577 fn what_the_float_rewrites_leave_is_valid_ir() {
578 let (mut names, mut func) = one(&[Type::int(32)], &[f64()], |build, args| {
579 let k = build.fconst(f64(), 0x3ff8_0000_0000_0000);
580 let d = build.unary(Opcode::UIToFP, args[0], f64());
581 let n = build.unary(Opcode::FNeg, d, f64());
582 let s = build.binary(Opcode::FAdd, n, k, Flags::NONE);
583 build.ret(&[s]);
584 });
585 floats(&mut func);
586 let module = Module::new(names.intern("f.c"), &target());
587 rucc_ir::verify_func(&module, &func, &names).expect("the rewrite builds valid IR");
588 }
589
590 #[test]
593 fn a_function_with_no_floats_in_it_is_left_exactly_as_it_was() {
594 let (mut names, mut func) = one(&[Type::int(32)], &[Type::int(32)], |build, args| {
595 build.ret(&[args[0]]);
596 });
597 let before = printed(&func, &mut names);
598 floats(&mut func);
599 assert_eq!(printed(&func, &mut names), before);
600 }
601}