1use rucc_base::Idx;
51use rucc_ir::{Def, Extra, Flags, Func, Imm, Inst, InstData, IntPred, Opcode, Type, Value};
52
53#[must_use]
60fn container(ty: Type) -> Option<u32> {
61 if !ty.is_int() || !ty.is_scalar() {
62 return None;
63 }
64 let bits = ty.bits();
65 if bits == 1 || bits > 64 {
66 return None;
67 }
68 let held = bits.next_power_of_two().max(8);
69 (held != bits).then_some(held)
70}
71
72#[must_use]
78fn understood(opcode: Opcode) -> bool {
79 matches!(
80 opcode,
81 Opcode::IConst
82 | Opcode::Add
83 | Opcode::Sub
84 | Opcode::Mul
85 | Opcode::SDiv
86 | Opcode::UDiv
87 | Opcode::SRem
88 | Opcode::URem
89 | Opcode::And
90 | Opcode::Or
91 | Opcode::Xor
92 | Opcode::Shl
93 | Opcode::LShr
94 | Opcode::AShr
95 | Opcode::ICmp
96 | Opcode::Trunc
97 | Opcode::SExt
98 | Opcode::ZExt
99 | Opcode::Load
100 | Opcode::Store
101 | Opcode::Jump
102 | Opcode::BrIf
103 )
104}
105
106pub fn integers(func: &mut Func) -> bool {
116 let narrow: Vec<Option<u32>> = func
117 .values()
118 .map(|value| container(func[value].ty).map(|_| func[value].ty.bits()))
119 .collect();
120 if narrow.iter().all(Option::is_none) {
121 return false;
122 }
123 if !every_width_is_one_the_signature_has(func) {
124 return false;
125 }
126
127 let insts: Vec<Inst> =
128 func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
129 if !insts.iter().all(|&inst| touches_nothing_it_does_not_understand(func, &narrow, inst)) {
130 return false;
131 }
132
133 let values: Vec<Value> = func.values().collect();
134 for value in values {
135 let ty = func[value].ty;
136 if let Some(held) = container(ty) {
137 func.retype(value, Type::int(held));
138 }
139 }
140 for &inst in &insts {
145 if func[inst].opcode == Opcode::IConst {
146 constant(func, &narrow, inst);
147 }
148 }
149 for inst in insts {
150 rewrite(func, &narrow, inst);
151 }
152 true
153}
154
155fn every_width_is_one_the_signature_has(func: &Func) -> bool {
163 let signature = func.signature();
164 let crossing = signature.params.iter().chain(signature.returns.iter());
165 if crossing.map(|param| param.ty).any(|ty| container(ty).is_some()) {
166 return false;
167 }
168 let Some(entry) = func.entry() else { return true };
169 func[entry].params.iter().all(|&value| container(func[value].ty).is_none())
170}
171
172fn touches_nothing_it_does_not_understand(func: &Func, narrow: &[Option<u32>], inst: Inst) -> bool {
174 let data = &func[inst];
175 let touched = results(func, inst).any(|value| at(narrow, value).is_some())
176 || func[data.args].iter().any(|&value| at(narrow, value).is_some());
177 !touched || understood(data.opcode)
178}
179
180#[must_use]
185fn at(narrow: &[Option<u32>], value: Value) -> Option<u32> {
186 narrow.get(value.index()).copied().flatten()
187}
188
189fn results(func: &Func, inst: Inst) -> impl Iterator<Item = Value> + use<'_> {
191 let first = func[inst].first_result.map_or(0, Idx::index);
192 let count = usize::from(func[inst].results);
193 (first..first + count).map(Idx::from_usize)
194}
195
196fn rewrite(func: &mut Func, narrow: &[Option<u32>], inst: Inst) {
198 match func[inst].opcode {
199 Opcode::Add | Opcode::Sub | Opcode::Mul | Opcode::And | Opcode::Or | Opcode::Xor => {
204 forget_flags(func, narrow, inst);
205 }
206 Opcode::SDiv | Opcode::SRem => shape_both(func, narrow, inst, true),
207 Opcode::UDiv | Opcode::URem => shape_both(func, narrow, inst, false),
208 Opcode::Shl => shape_count(func, narrow, inst),
209 Opcode::LShr => {
210 shape_operand(func, narrow, inst, 0, false);
211 shape_count(func, narrow, inst);
212 }
213 Opcode::AShr => {
214 shape_operand(func, narrow, inst, 0, true);
215 shape_count(func, narrow, inst);
216 }
217 Opcode::ICmp => compare(func, narrow, inst),
218 Opcode::Trunc => truncate(func, narrow, inst),
219 Opcode::SExt => extend(func, narrow, inst, true),
220 Opcode::ZExt => extend(func, narrow, inst, false),
221 Opcode::Store => shape_operand(func, narrow, inst, 0, false),
226 _ => {}
227 }
228}
229
230fn constant(func: &mut Func, narrow: &[Option<u32>], inst: Inst) {
238 let Some(ty) = produced(func, inst) else { return };
239 let Some(was) = produced_narrow(func, narrow, inst) else { return };
240 let Extra::Imm(imm) = func[inst].extra else { return };
241 let value = func[imm].signed(Type::int(was));
242 let imm = func.add_imm(Imm::int(value, ty));
243 func[inst].extra = Extra::Imm(imm);
244}
245
246fn forget_flags(func: &mut Func, narrow: &[Option<u32>], inst: Inst) {
248 if produced_narrow(func, narrow, inst).is_none() {
249 return;
250 }
251 func[inst].flags = func[inst].flags.without(Flags::NSW.union(Flags::NUW).union(Flags::EXACT));
252}
253
254fn shape_both(func: &mut Func, narrow: &[Option<u32>], inst: Inst, signed: bool) {
256 shape_operand(func, narrow, inst, 0, signed);
257 shape_operand(func, narrow, inst, 1, signed);
258 if produced_narrow(func, narrow, inst).is_some() {
259 func[inst].flags = func[inst].flags.without(Flags::EXACT);
260 }
261}
262
263fn shape_count(func: &mut Func, narrow: &[Option<u32>], inst: Inst) {
265 shape_operand(func, narrow, inst, 1, false);
266 if produced_narrow(func, narrow, inst).is_some() {
267 func[inst].flags =
268 func[inst].flags.without(Flags::NSW.union(Flags::NUW).union(Flags::EXACT));
269 }
270}
271
272fn compare(func: &mut Func, narrow: &[Option<u32>], inst: Inst) {
276 let Extra::IntPred(pred) = func[inst].extra else { return };
277 let signed = matches!(pred, IntPred::Slt | IntPred::Sle | IntPred::Sgt | IntPred::Sge);
278 shape_operand(func, narrow, inst, 0, signed);
279 shape_operand(func, narrow, inst, 1, signed);
280}
281
282fn truncate(func: &mut Func, narrow: &[Option<u32>], inst: Inst) {
289 let Some(to) = produced_narrow(func, narrow, inst) else { return };
290 let args = func[inst].args;
291 let Some(&arg) = func[args].first() else { return };
292 let ty = func[arg].ty;
293 if produced(func, inst) != Some(ty) {
294 return;
295 }
296 let mask = ahead_const(func, inst, Imm::int(low_bits(to), ty), ty);
297 becomes(func, inst, Opcode::And, &[arg, mask]);
298}
299
300fn extend(func: &mut Func, narrow: &[Option<u32>], inst: Inst, signed: bool) {
308 let args = func[inst].args;
309 let Some(&arg) = func[args].first() else { return };
310 let Some(from) = at(narrow, arg) else { return };
311 let ty = func[arg].ty;
312 let Some(wide) = produced(func, inst) else { return };
313 if wide != ty {
314 let shaped = shaped(func, inst, arg, from, signed);
315 let opcode = if signed { Opcode::SExt } else { Opcode::ZExt };
316 becomes(func, inst, opcode, &[shaped]);
317 return;
318 }
319 if signed {
320 let spare = ahead_const(func, inst, Imm::int(i128::from(ty.bits() - from), ty), ty);
321 let up = ahead(func, inst, Opcode::Shl, &[arg, spare], ty);
322 becomes(func, inst, Opcode::AShr, &[up, spare]);
323 return;
324 }
325 let mask = ahead_const(func, inst, Imm::int(low_bits(from), ty), ty);
326 becomes(func, inst, Opcode::And, &[arg, mask]);
327}
328
329fn shape_operand(func: &mut Func, narrow: &[Option<u32>], inst: Inst, index: usize, signed: bool) {
331 let list = func[inst].args;
332 let mut args: Vec<Value> = func[list].to_vec();
333 let Some(&arg) = args.get(index) else { return };
334 let Some(width) = at(narrow, arg) else { return };
335 let shaped = shaped(func, inst, arg, width, signed);
336 if shaped == arg {
337 return;
338 }
339 args[index] = shaped;
340 let list = func.push_values(&args);
341 func[inst].args = list;
342}
343
344fn shaped(func: &mut Func, inst: Inst, value: Value, width: u32, signed: bool) -> Value {
350 let ty = func[value].ty;
351 if already(func, value, width, signed) {
352 return value;
353 }
354 if signed {
355 let spare = ahead_const(func, inst, Imm::int(i128::from(ty.bits() - width), ty), ty);
356 let up = ahead(func, inst, Opcode::Shl, &[value, spare], ty);
357 return ahead(func, inst, Opcode::AShr, &[up, spare], ty);
358 }
359 let mask = ahead_const(func, inst, Imm::int(low_bits(width), ty), ty);
360 ahead(func, inst, Opcode::And, &[value, mask], ty)
361}
362
363fn already(func: &Func, value: Value, width: u32, signed: bool) -> bool {
372 let Def::Result { inst, .. } = func[value].def else { return false };
373 let ty = func[value].ty;
374 match func[inst].opcode {
375 Opcode::IConst => {
376 let Extra::Imm(imm) = func[inst].extra else { return false };
377 let held = func[imm].signed(ty);
378 if signed {
379 let spare = 128 - width;
380 return (held << spare) >> spare == held;
381 }
382 held >= 0 && held == held & low_bits(width)
383 }
384 Opcode::And if !signed => {
387 let args = func[inst].args;
388 func[args].iter().any(|&arg| keeps_no_more_than(func, arg, width))
389 }
390 _ => false,
391 }
392}
393
394fn keeps_no_more_than(func: &Func, value: Value, width: u32) -> bool {
396 let Def::Result { inst, .. } = func[value].def else { return false };
397 if func[inst].opcode != Opcode::IConst {
398 return false;
399 }
400 let Extra::Imm(imm) = func[inst].extra else { return false };
401 let held = func[imm].signed(func[value].ty);
402 held >= 0 && held & !low_bits(width) == 0
403}
404
405#[must_use]
407fn low_bits(width: u32) -> i128 {
408 (1i128 << width) - 1
409}
410
411fn produced(func: &Func, inst: Inst) -> Option<Type> {
413 func[inst].first_result.map(|value| func[value].ty)
414}
415
416fn produced_narrow(func: &Func, narrow: &[Option<u32>], inst: Inst) -> Option<u32> {
418 at(narrow, func[inst].first_result?)
419}
420
421fn ahead(func: &mut Func, inst: Inst, opcode: Opcode, args: &[Value], ty: Type) -> Value {
423 let args = func.push_values(args);
424 written(func, inst, InstData { args, ..InstData::new(opcode) }, ty)
425}
426
427fn ahead_const(func: &mut Func, inst: Inst, imm: Imm, ty: Type) -> Value {
429 let extra = Extra::Imm(func.add_imm(imm));
430 written(func, inst, InstData { extra, ..InstData::new(Opcode::IConst) }, ty)
431}
432
433fn written(func: &mut Func, inst: Inst, data: InstData, ty: Type) -> Value {
435 let span = func.span(inst);
436 let made = func.create_inst(data, &[ty], span);
437 func.insert_before(made, inst);
438 func[made].first_result.expect("an instruction created with one result has one")
439}
440
441fn becomes(func: &mut Func, inst: Inst, opcode: Opcode, args: &[Value]) {
446 let args = func.push_values(args);
447 let data = &mut func[inst];
448 data.opcode = opcode;
449 data.args = args;
450 data.extra = Extra::None;
451 data.flags = data.flags.intersection(Flags::legal_on(opcode));
452}
453
454#[cfg(test)]
455mod tests {
456 use rucc_base::Interner;
457 use rucc_ir::{Builder, Flags, Func, IntPred, Module, Opcode, Signature, Type};
458 use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
459
460 use super::{container, integers};
461
462 fn target() -> TargetInfo {
463 TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu))
464 }
465
466 fn printed(func: &Func, names: &mut Interner) -> String {
467 let module = Module::new(names.intern("w.c"), &target());
468 rucc_ir::print_func(&module, func, names)
469 }
470
471 fn shell(names: &mut Interner) -> (Func, rucc_ir::Block) {
473 let int = Type::int(32);
474 let mut func = Func::new(names.intern("f"), Signature::new().with_returns(&[int]));
475 let entry = func.create_block();
476 (func, entry)
477 }
478
479 #[test]
480 fn a_width_is_held_in_the_narrowest_register_that_fits_it() {
481 assert_eq!(container(Type::int(40)), Some(64));
482 assert_eq!(container(Type::int(33)), Some(64));
483 assert_eq!(container(Type::int(17)), Some(32));
484 assert_eq!(container(Type::int(9)), Some(16));
485 assert_eq!(container(Type::int(3)), Some(8));
486 for bits in [1, 8, 16, 32, 64] {
488 assert_eq!(container(Type::int(bits)), None, "{bits} is a width the machine has");
489 }
490 assert_eq!(container(Type::int(65)), None);
492 assert_eq!(container(Type::int(128)), None);
493 assert_eq!(container(Type::vector(Type::int(40), 2)), None);
494 assert_eq!(container(Type::PTR), None);
495 }
496
497 #[test]
500 fn a_shift_at_a_width_the_machine_lacks_keeps_the_bits_the_width_has() {
501 let mut names = Interner::new();
502 let (mut func, entry) = shell(&mut names);
503 let narrow = Type::int(40);
504 let mut build = Builder::new(&mut func, entry);
505 let value = build.iconst(narrow, 0x100);
506 let count = build.iconst(narrow, 32);
507 let shifted = build.binary(Opcode::Shl, value, count, Flags::NONE);
508 let wide = build.unary(Opcode::ZExt, shifted, Type::int(64));
509 let answer = build.unary(Opcode::Trunc, wide, Type::int(32));
510 build.ret(&[answer]);
511
512 assert!(integers(&mut func), "there is a width to widen");
513 let text = printed(&func, &mut names);
514 assert!(!text.contains("i40"), "no forty bit value is left: {text}");
515 assert_eq!(text.matches(" = and ").count(), 1, "the widening became a mask: {text}");
518 assert!(!text.contains("zext"), "and is no longer a widening: {text}");
519 }
520
521 fn seed(build: &mut Builder<'_>, narrow: Type) -> rucc_ir::Value {
526 let wide = build.iconst(Type::int(64), 5);
527 build.unary(Opcode::Trunc, wide, narrow)
528 }
529
530 #[test]
533 fn a_signed_shift_right_spreads_the_sign_the_narrow_value_has() {
534 let mut names = Interner::new();
535 let (mut func, entry) = shell(&mut names);
536 let narrow = Type::int(40);
537 let mut build = Builder::new(&mut func, entry);
538 let value = seed(&mut build, narrow);
539 let count = build.iconst(narrow, 3);
540 let shifted = build.binary(Opcode::AShr, value, count, Flags::NONE);
541 let answer = build.unary(Opcode::Trunc, shifted, Type::int(32));
542 build.ret(&[answer]);
543
544 assert!(integers(&mut func), "there is a width to widen");
545 let text = printed(&func, &mut names);
546 assert!(!text.contains("i40"), "no forty bit value is left: {text}");
547 assert!(text.contains("iconst.i64 24"), "the spare bits are counted: {text}");
551 assert_eq!(text.matches(" = shl ").count(), 1, "shifted up once: {text}");
552 assert_eq!(text.matches(" = ashr ").count(), 2, "and back down, then by three: {text}");
553 }
554
555 #[test]
558 fn a_comparison_shapes_its_operands_the_way_its_predicate_reads_them() {
559 for (pred, shifts) in [(IntPred::Ult, 0), (IntPred::Eq, 0), (IntPred::Slt, 1)] {
563 let mut names = Interner::new();
564 let (mut func, entry) = shell(&mut names);
565 let narrow = Type::int(33);
566 let mut build = Builder::new(&mut func, entry);
567 let left = seed(&mut build, narrow);
568 let right = build.iconst(narrow, 7);
569 let same = build.icmp(pred, left, right);
570 let answer = build.unary(Opcode::ZExt, same, Type::int(32));
571 build.ret(&[answer]);
572
573 assert!(integers(&mut func), "there is a width to widen");
574 let text = printed(&func, &mut names);
575 assert!(!text.contains("i33"), "no thirty three bit value is left: {text}");
576 assert_eq!(text.matches(" = and ").count(), 1, "{pred:?} masks once: {text}");
577 assert_eq!(text.matches(" = shl ").count(), shifts, "{pred:?} shifts up: {text}");
578 }
579 }
580
581 #[test]
582 fn a_width_that_crosses_the_boundary_is_left_for_the_abi() {
583 let mut names = Interner::new();
584 let narrow = Type::int(40);
585 let mut func = Func::new(
586 names.intern("f"),
587 Signature::new().with_params(&[narrow]).with_returns(&[narrow]),
588 );
589 let entry = func.create_block();
590 let x = func.append_param(entry, narrow);
591 let mut build = Builder::new(&mut func, entry);
592 let one = build.iconst(narrow, 1);
593 let sum = build.binary(Opcode::Add, x, one, Flags::NONE);
594 build.ret(&[sum]);
595
596 assert!(!integers(&mut func), "a parameter at that width is not this pass's to move");
597 let text = printed(&func, &mut names);
598 assert!(text.contains("i40"), "the function is exactly as it was: {text}");
599 }
600
601 #[test]
602 fn a_width_reaching_an_opcode_this_does_not_understand_is_left_alone() {
603 let mut names = Interner::new();
604 let (mut func, entry) = shell(&mut names);
605 let narrow = Type::int(40);
606 let mut build = Builder::new(&mut func, entry);
607 let value = build.iconst(narrow, 3);
608 let counted = build.unary(Opcode::Ctpop, value, narrow);
611 let answer = build.unary(Opcode::Trunc, counted, Type::int(32));
612 build.ret(&[answer]);
613
614 assert!(!integers(&mut func), "an opcode this has not thought about stops it");
615 let text = printed(&func, &mut names);
616 assert!(text.contains("i40"), "the function is exactly as it was: {text}");
617 }
618
619 #[test]
620 fn a_function_with_nothing_at_such_a_width_is_not_touched() {
621 let mut names = Interner::new();
622 let (mut func, entry) = shell(&mut names);
623 let mut build = Builder::new(&mut func, entry);
624 let value = build.iconst(Type::int(32), 3);
625 build.ret(&[value]);
626
627 assert!(!integers(&mut func), "there is nothing to widen");
628 }
629}