use rucc_base::Idx;
use rucc_ir::{Def, Extra, Flags, Func, Imm, Inst, InstData, IntPred, Opcode, Type, Value};
#[must_use]
fn container(ty: Type) -> Option<u32> {
if !ty.is_int() || !ty.is_scalar() {
return None;
}
let bits = ty.bits();
if bits == 1 || bits > 64 {
return None;
}
let held = bits.next_power_of_two().max(8);
(held != bits).then_some(held)
}
#[must_use]
fn understood(opcode: Opcode) -> bool {
matches!(
opcode,
Opcode::IConst
| Opcode::Add
| Opcode::Sub
| Opcode::Mul
| Opcode::SDiv
| Opcode::UDiv
| Opcode::SRem
| Opcode::URem
| Opcode::And
| Opcode::Or
| Opcode::Xor
| Opcode::Shl
| Opcode::LShr
| Opcode::AShr
| Opcode::ICmp
| Opcode::Trunc
| Opcode::SExt
| Opcode::ZExt
| Opcode::Load
| Opcode::Store
| Opcode::Jump
| Opcode::BrIf
)
}
pub fn integers(func: &mut Func) -> bool {
let narrow: Vec<Option<u32>> = func
.values()
.map(|value| container(func[value].ty).map(|_| func[value].ty.bits()))
.collect();
if narrow.iter().all(Option::is_none) {
return false;
}
if !every_width_is_one_the_signature_has(func) {
return false;
}
let insts: Vec<Inst> =
func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
if !insts.iter().all(|&inst| touches_nothing_it_does_not_understand(func, &narrow, inst)) {
return false;
}
let values: Vec<Value> = func.values().collect();
for value in values {
let ty = func[value].ty;
if let Some(held) = container(ty) {
func.retype(value, Type::int(held));
}
}
for &inst in &insts {
if func[inst].opcode == Opcode::IConst {
constant(func, &narrow, inst);
}
}
for inst in insts {
rewrite(func, &narrow, inst);
}
true
}
fn every_width_is_one_the_signature_has(func: &Func) -> bool {
let signature = func.signature();
let crossing = signature.params.iter().chain(signature.returns.iter());
if crossing.map(|param| param.ty).any(|ty| container(ty).is_some()) {
return false;
}
let Some(entry) = func.entry() else { return true };
func[entry].params.iter().all(|&value| container(func[value].ty).is_none())
}
fn touches_nothing_it_does_not_understand(func: &Func, narrow: &[Option<u32>], inst: Inst) -> bool {
let data = &func[inst];
let touched = results(func, inst).any(|value| at(narrow, value).is_some())
|| func[data.args].iter().any(|&value| at(narrow, value).is_some());
!touched || understood(data.opcode)
}
#[must_use]
fn at(narrow: &[Option<u32>], value: Value) -> Option<u32> {
narrow.get(value.index()).copied().flatten()
}
fn results(func: &Func, inst: Inst) -> impl Iterator<Item = Value> + use<'_> {
let first = func[inst].first_result.map_or(0, Idx::index);
let count = usize::from(func[inst].results);
(first..first + count).map(Idx::from_usize)
}
fn rewrite(func: &mut Func, narrow: &[Option<u32>], inst: Inst) {
match func[inst].opcode {
Opcode::Add | Opcode::Sub | Opcode::Mul | Opcode::And | Opcode::Or | Opcode::Xor => {
forget_flags(func, narrow, inst);
}
Opcode::SDiv | Opcode::SRem => shape_both(func, narrow, inst, true),
Opcode::UDiv | Opcode::URem => shape_both(func, narrow, inst, false),
Opcode::Shl => shape_count(func, narrow, inst),
Opcode::LShr => {
shape_operand(func, narrow, inst, 0, false);
shape_count(func, narrow, inst);
}
Opcode::AShr => {
shape_operand(func, narrow, inst, 0, true);
shape_count(func, narrow, inst);
}
Opcode::ICmp => compare(func, narrow, inst),
Opcode::Trunc => truncate(func, narrow, inst),
Opcode::SExt => extend(func, narrow, inst, true),
Opcode::ZExt => extend(func, narrow, inst, false),
Opcode::Store => shape_operand(func, narrow, inst, 0, false),
_ => {}
}
}
fn constant(func: &mut Func, narrow: &[Option<u32>], inst: Inst) {
let Some(ty) = produced(func, inst) else { return };
let Some(was) = produced_narrow(func, narrow, inst) else { return };
let Extra::Imm(imm) = func[inst].extra else { return };
let value = func[imm].signed(Type::int(was));
let imm = func.add_imm(Imm::int(value, ty));
func[inst].extra = Extra::Imm(imm);
}
fn forget_flags(func: &mut Func, narrow: &[Option<u32>], inst: Inst) {
if produced_narrow(func, narrow, inst).is_none() {
return;
}
func[inst].flags = func[inst].flags.without(Flags::NSW.union(Flags::NUW).union(Flags::EXACT));
}
fn shape_both(func: &mut Func, narrow: &[Option<u32>], inst: Inst, signed: bool) {
shape_operand(func, narrow, inst, 0, signed);
shape_operand(func, narrow, inst, 1, signed);
if produced_narrow(func, narrow, inst).is_some() {
func[inst].flags = func[inst].flags.without(Flags::EXACT);
}
}
fn shape_count(func: &mut Func, narrow: &[Option<u32>], inst: Inst) {
shape_operand(func, narrow, inst, 1, false);
if produced_narrow(func, narrow, inst).is_some() {
func[inst].flags =
func[inst].flags.without(Flags::NSW.union(Flags::NUW).union(Flags::EXACT));
}
}
fn compare(func: &mut Func, narrow: &[Option<u32>], inst: Inst) {
let Extra::IntPred(pred) = func[inst].extra else { return };
let signed = matches!(pred, IntPred::Slt | IntPred::Sle | IntPred::Sgt | IntPred::Sge);
shape_operand(func, narrow, inst, 0, signed);
shape_operand(func, narrow, inst, 1, signed);
}
fn truncate(func: &mut Func, narrow: &[Option<u32>], inst: Inst) {
let Some(to) = produced_narrow(func, narrow, inst) else { return };
let args = func[inst].args;
let Some(&arg) = func[args].first() else { return };
let ty = func[arg].ty;
if produced(func, inst) != Some(ty) {
return;
}
let mask = ahead_const(func, inst, Imm::int(low_bits(to), ty), ty);
becomes(func, inst, Opcode::And, &[arg, mask]);
}
fn extend(func: &mut Func, narrow: &[Option<u32>], inst: Inst, signed: bool) {
let args = func[inst].args;
let Some(&arg) = func[args].first() else { return };
let Some(from) = at(narrow, arg) else { return };
let ty = func[arg].ty;
let Some(wide) = produced(func, inst) else { return };
if wide != ty {
let shaped = shaped(func, inst, arg, from, signed);
let opcode = if signed { Opcode::SExt } else { Opcode::ZExt };
becomes(func, inst, opcode, &[shaped]);
return;
}
if signed {
let spare = ahead_const(func, inst, Imm::int(i128::from(ty.bits() - from), ty), ty);
let up = ahead(func, inst, Opcode::Shl, &[arg, spare], ty);
becomes(func, inst, Opcode::AShr, &[up, spare]);
return;
}
let mask = ahead_const(func, inst, Imm::int(low_bits(from), ty), ty);
becomes(func, inst, Opcode::And, &[arg, mask]);
}
fn shape_operand(func: &mut Func, narrow: &[Option<u32>], inst: Inst, index: usize, signed: bool) {
let list = func[inst].args;
let mut args: Vec<Value> = func[list].to_vec();
let Some(&arg) = args.get(index) else { return };
let Some(width) = at(narrow, arg) else { return };
let shaped = shaped(func, inst, arg, width, signed);
if shaped == arg {
return;
}
args[index] = shaped;
let list = func.push_values(&args);
func[inst].args = list;
}
fn shaped(func: &mut Func, inst: Inst, value: Value, width: u32, signed: bool) -> Value {
let ty = func[value].ty;
if already(func, value, width, signed) {
return value;
}
if signed {
let spare = ahead_const(func, inst, Imm::int(i128::from(ty.bits() - width), ty), ty);
let up = ahead(func, inst, Opcode::Shl, &[value, spare], ty);
return ahead(func, inst, Opcode::AShr, &[up, spare], ty);
}
let mask = ahead_const(func, inst, Imm::int(low_bits(width), ty), ty);
ahead(func, inst, Opcode::And, &[value, mask], ty)
}
fn already(func: &Func, value: Value, width: u32, signed: bool) -> bool {
let Def::Result { inst, .. } = func[value].def else { return false };
let ty = func[value].ty;
match func[inst].opcode {
Opcode::IConst => {
let Extra::Imm(imm) = func[inst].extra else { return false };
let held = func[imm].signed(ty);
if signed {
let spare = 128 - width;
return (held << spare) >> spare == held;
}
held >= 0 && held == held & low_bits(width)
}
Opcode::And if !signed => {
let args = func[inst].args;
func[args].iter().any(|&arg| keeps_no_more_than(func, arg, width))
}
_ => false,
}
}
fn keeps_no_more_than(func: &Func, value: Value, width: u32) -> bool {
let Def::Result { inst, .. } = func[value].def else { return false };
if func[inst].opcode != Opcode::IConst {
return false;
}
let Extra::Imm(imm) = func[inst].extra else { return false };
let held = func[imm].signed(func[value].ty);
held >= 0 && held & !low_bits(width) == 0
}
#[must_use]
fn low_bits(width: u32) -> i128 {
(1i128 << width) - 1
}
fn produced(func: &Func, inst: Inst) -> Option<Type> {
func[inst].first_result.map(|value| func[value].ty)
}
fn produced_narrow(func: &Func, narrow: &[Option<u32>], inst: Inst) -> Option<u32> {
at(narrow, func[inst].first_result?)
}
fn ahead(func: &mut Func, inst: Inst, opcode: Opcode, args: &[Value], ty: Type) -> Value {
let args = func.push_values(args);
written(func, inst, InstData { args, ..InstData::new(opcode) }, ty)
}
fn ahead_const(func: &mut Func, inst: Inst, imm: Imm, ty: Type) -> Value {
let extra = Extra::Imm(func.add_imm(imm));
written(func, inst, InstData { extra, ..InstData::new(Opcode::IConst) }, ty)
}
fn written(func: &mut Func, inst: Inst, data: InstData, ty: Type) -> Value {
let span = func.span(inst);
let made = func.create_inst(data, &[ty], span);
func.insert_before(made, inst);
func[made].first_result.expect("an instruction created with one result has one")
}
fn becomes(func: &mut Func, inst: Inst, opcode: Opcode, args: &[Value]) {
let args = func.push_values(args);
let data = &mut func[inst];
data.opcode = opcode;
data.args = args;
data.extra = Extra::None;
data.flags = data.flags.intersection(Flags::legal_on(opcode));
}
#[cfg(test)]
mod tests {
use rucc_base::Interner;
use rucc_ir::{Builder, Flags, Func, IntPred, Module, Opcode, Signature, Type};
use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
use super::{container, integers};
fn target() -> TargetInfo {
TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu))
}
fn printed(func: &Func, names: &mut Interner) -> String {
let module = Module::new(names.intern("w.c"), &target());
rucc_ir::print_func(&module, func, names)
}
fn shell(names: &mut Interner) -> (Func, rucc_ir::Block) {
let int = Type::int(32);
let mut func = Func::new(names.intern("f"), Signature::new().with_returns(&[int]));
let entry = func.create_block();
(func, entry)
}
#[test]
fn a_width_is_held_in_the_narrowest_register_that_fits_it() {
assert_eq!(container(Type::int(40)), Some(64));
assert_eq!(container(Type::int(33)), Some(64));
assert_eq!(container(Type::int(17)), Some(32));
assert_eq!(container(Type::int(9)), Some(16));
assert_eq!(container(Type::int(3)), Some(8));
for bits in [1, 8, 16, 32, 64] {
assert_eq!(container(Type::int(bits)), None, "{bits} is a width the machine has");
}
assert_eq!(container(Type::int(65)), None);
assert_eq!(container(Type::int(128)), None);
assert_eq!(container(Type::vector(Type::int(40), 2)), None);
assert_eq!(container(Type::PTR), None);
}
#[test]
fn a_shift_at_a_width_the_machine_lacks_keeps_the_bits_the_width_has() {
let mut names = Interner::new();
let (mut func, entry) = shell(&mut names);
let narrow = Type::int(40);
let mut build = Builder::new(&mut func, entry);
let value = build.iconst(narrow, 0x100);
let count = build.iconst(narrow, 32);
let shifted = build.binary(Opcode::Shl, value, count, Flags::NONE);
let wide = build.unary(Opcode::ZExt, shifted, Type::int(64));
let answer = build.unary(Opcode::Trunc, wide, Type::int(32));
build.ret(&[answer]);
assert!(integers(&mut func), "there is a width to widen");
let text = printed(&func, &mut names);
assert!(!text.contains("i40"), "no forty bit value is left: {text}");
assert_eq!(text.matches(" = and ").count(), 1, "the widening became a mask: {text}");
assert!(!text.contains("zext"), "and is no longer a widening: {text}");
}
fn seed(build: &mut Builder<'_>, narrow: Type) -> rucc_ir::Value {
let wide = build.iconst(Type::int(64), 5);
build.unary(Opcode::Trunc, wide, narrow)
}
#[test]
fn a_signed_shift_right_spreads_the_sign_the_narrow_value_has() {
let mut names = Interner::new();
let (mut func, entry) = shell(&mut names);
let narrow = Type::int(40);
let mut build = Builder::new(&mut func, entry);
let value = seed(&mut build, narrow);
let count = build.iconst(narrow, 3);
let shifted = build.binary(Opcode::AShr, value, count, Flags::NONE);
let answer = build.unary(Opcode::Trunc, shifted, Type::int(32));
build.ret(&[answer]);
assert!(integers(&mut func), "there is a width to widen");
let text = printed(&func, &mut names);
assert!(!text.contains("i40"), "no forty bit value is left: {text}");
assert!(text.contains("iconst.i64 24"), "the spare bits are counted: {text}");
assert_eq!(text.matches(" = shl ").count(), 1, "shifted up once: {text}");
assert_eq!(text.matches(" = ashr ").count(), 2, "and back down, then by three: {text}");
}
#[test]
fn a_comparison_shapes_its_operands_the_way_its_predicate_reads_them() {
for (pred, shifts) in [(IntPred::Ult, 0), (IntPred::Eq, 0), (IntPred::Slt, 1)] {
let mut names = Interner::new();
let (mut func, entry) = shell(&mut names);
let narrow = Type::int(33);
let mut build = Builder::new(&mut func, entry);
let left = seed(&mut build, narrow);
let right = build.iconst(narrow, 7);
let same = build.icmp(pred, left, right);
let answer = build.unary(Opcode::ZExt, same, Type::int(32));
build.ret(&[answer]);
assert!(integers(&mut func), "there is a width to widen");
let text = printed(&func, &mut names);
assert!(!text.contains("i33"), "no thirty three bit value is left: {text}");
assert_eq!(text.matches(" = and ").count(), 1, "{pred:?} masks once: {text}");
assert_eq!(text.matches(" = shl ").count(), shifts, "{pred:?} shifts up: {text}");
}
}
#[test]
fn a_width_that_crosses_the_boundary_is_left_for_the_abi() {
let mut names = Interner::new();
let narrow = Type::int(40);
let mut func = Func::new(
names.intern("f"),
Signature::new().with_params(&[narrow]).with_returns(&[narrow]),
);
let entry = func.create_block();
let x = func.append_param(entry, narrow);
let mut build = Builder::new(&mut func, entry);
let one = build.iconst(narrow, 1);
let sum = build.binary(Opcode::Add, x, one, Flags::NONE);
build.ret(&[sum]);
assert!(!integers(&mut func), "a parameter at that width is not this pass's to move");
let text = printed(&func, &mut names);
assert!(text.contains("i40"), "the function is exactly as it was: {text}");
}
#[test]
fn a_width_reaching_an_opcode_this_does_not_understand_is_left_alone() {
let mut names = Interner::new();
let (mut func, entry) = shell(&mut names);
let narrow = Type::int(40);
let mut build = Builder::new(&mut func, entry);
let value = build.iconst(narrow, 3);
let counted = build.unary(Opcode::Ctpop, value, narrow);
let answer = build.unary(Opcode::Trunc, counted, Type::int(32));
build.ret(&[answer]);
assert!(!integers(&mut func), "an opcode this has not thought about stops it");
let text = printed(&func, &mut names);
assert!(text.contains("i40"), "the function is exactly as it was: {text}");
}
#[test]
fn a_function_with_nothing_at_such_a_width_is_not_touched() {
let mut names = Interner::new();
let (mut func, entry) = shell(&mut names);
let mut build = Builder::new(&mut func, entry);
let value = build.iconst(Type::int(32), 3);
build.ret(&[value]);
assert!(!integers(&mut func), "there is nothing to widen");
}
}