use rucc_cost::Goal;
use rucc_ir::{Def, Extra, Flags, Func, Imm, Inst, Opcode, Type, Value};
use crate::expand::{ahead, ahead_const, becomes};
pub fn divisions(func: &mut Func, goal: Goal) {
if matches!(goal, Goal::Size) {
return;
}
let found: Vec<Inst> =
func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
for inst in found {
let Some(division) = division(func, inst) else { continue };
let Some(program) = program(division) else { continue };
write(func, inst, &program);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Range {
Unsigned(u32),
Signed(u32),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Division {
pub signed: bool,
pub remainder: bool,
pub exact: bool,
pub width: u32,
pub range: Range,
pub divisor: i128,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Step {
Const(i128, u32),
Op(Opcode, [usize; 2], u32),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Program {
pub width: u32,
pub steps: Vec<Step>,
}
const DIVIDEND: usize = 0;
impl Program {
fn new(width: u32) -> Self {
Self { width, steps: Vec::new() }
}
fn push(&mut self, step: Step) -> usize {
self.steps.push(step);
self.steps.len()
}
fn constant(&mut self, value: i128, bits: u32) -> usize {
self.push(Step::Const(value, bits))
}
fn by(&mut self, opcode: Opcode, value: usize, constant: i128, bits: u32) -> usize {
let constant = self.constant(constant, bits);
self.push(Step::Op(opcode, [value, constant], bits))
}
fn op(&mut self, opcode: Opcode, lhs: usize, rhs: usize, bits: u32) -> usize {
self.push(Step::Op(opcode, [lhs, rhs], bits))
}
fn convert(&mut self, opcode: Opcode, value: usize, bits: u32) -> usize {
self.push(Step::Op(opcode, [value, value], bits))
}
fn widened(&mut self, extend: Opcode) -> usize {
if self.width == 64 { DIVIDEND } else { self.convert(extend, DIVIDEND, 64) }
}
fn narrowed(&mut self, value: usize) -> usize {
if self.width == 64 { value } else { self.convert(Opcode::Trunc, value, self.width) }
}
fn negated(&mut self, value: usize) -> usize {
let zero = self.constant(0, self.width);
self.op(Opcode::Sub, zero, value, self.width)
}
fn left_over(&mut self, quotient: usize, divisor: i128) -> usize {
let back = self.by(Opcode::Mul, quotient, divisor, self.width);
self.op(Opcode::Sub, DIVIDEND, back, self.width)
}
}
#[must_use]
pub fn program(division: Division) -> Option<Program> {
let Division { signed, remainder, exact, width, range, divisor } = division;
if !matches!(width, 8 | 16 | 32 | 64) || matches!(divisor, 0 | 1) || (signed && divisor == -1) {
return None;
}
let mut program = Program::new(width);
if exact && !remainder {
exactly(&mut program, signed, divisor);
return Some(program);
}
let size = divisor.unsigned_abs();
match range {
Range::Unsigned(bits) => {
let quotient = unsigned(&mut program, size, bits)?;
if remainder {
if size.is_power_of_two() {
let mask = i128::try_from(size - 1).ok()?;
program.by(Opcode::And, DIVIDEND, mask, width);
} else {
program.left_over(quotient, i128::try_from(size).ok()?);
}
} else if divisor < 0 {
program.negated(quotient);
}
}
Range::Signed(_) if size.is_power_of_two() => biased(&mut program, divisor, remainder),
Range::Signed(bits) => {
let quotient = rounded(&mut program, divisor, bits)?;
if remainder {
program.left_over(quotient, divisor);
}
}
}
Some(program)
}
#[must_use]
pub fn multiplier(divisor: u128, bits: u32, precision: u32) -> (u128, u32) {
assert!(divisor > 0, "a divisor of zero has no reciprocal");
let up = 128 - (divisor - 1).leading_zeros();
assert!(bits + up < 128, "a divisor and a width this can work with");
let mut shift = up;
let low = (1u128 << (bits + up)) / divisor;
let high = ((1u128 << (bits + up)) + (1u128 << (bits + up - precision))) / divisor;
let (mut low, mut high) = (low, high);
while shift > 0 && low / 2 < high / 2 {
low /= 2;
high /= 2;
shift -= 1;
}
(high, shift)
}
fn unsigned(program: &mut Program, divisor: u128, bits: u32) -> Option<usize> {
let width = program.width;
if bits >= 128 || divisor >= 1u128 << bits {
return None;
}
if divisor.is_power_of_two() {
let shift = i128::from(divisor.trailing_zeros());
return Some(program.by(Opcode::LShr, DIVIDEND, shift, width));
}
if bits > 32 {
return None;
}
let (magic, shift) = multiplier(divisor, bits, bits);
let wide = program.widened(Opcode::ZExt);
let shifted = if magic < 1u128 << bits || bits < 32 {
let product = program.by(Opcode::Mul, wide, i128::try_from(magic).ok()?, 64);
program.by(Opcode::LShr, product, i128::from(bits + shift), 64)
} else {
let magic = i128::try_from(magic - (1u128 << 32)).ok()?;
let product = program.by(Opcode::Mul, wide, magic, 64);
let high = program.by(Opcode::LShr, product, 32, 64);
let sum = program.op(Opcode::Add, high, wide, 64);
if shift == 0 { sum } else { program.by(Opcode::LShr, sum, i128::from(shift), 64) }
};
Some(program.narrowed(shifted))
}
fn rounded(program: &mut Program, divisor: i128, bits: u32) -> Option<usize> {
let width = program.width;
let size = divisor.unsigned_abs();
if bits > 32 || size > 1u128 << (bits - 1) {
return None;
}
let (magic, shift) = multiplier(size, bits, bits - 1);
let wide = program.widened(Opcode::SExt);
let product = program.by(Opcode::Mul, wide, i128::try_from(magic).ok()?, 64);
let shifted = program.by(Opcode::AShr, product, i128::from(bits + shift), 64);
let low = program.narrowed(shifted);
let sign = program.by(Opcode::AShr, DIVIDEND, i128::from(width - 1), width);
Some(if divisor > 0 {
program.op(Opcode::Sub, low, sign, width)
} else {
program.op(Opcode::Sub, sign, low, width)
})
}
fn biased(program: &mut Program, divisor: i128, remainder: bool) {
let width = program.width;
let size = divisor.unsigned_abs();
let power = size.trailing_zeros();
let bias = if power == 1 {
program.by(Opcode::LShr, DIVIDEND, i128::from(width - 1), width)
} else {
let sign = program.by(Opcode::AShr, DIVIDEND, i128::from(width - 1), width);
program.by(Opcode::LShr, sign, i128::from(width - power), width)
};
let sum = program.op(Opcode::Add, DIVIDEND, bias, width);
if remainder {
let mask = (1i128 << power) - 1;
let low = program.by(Opcode::And, sum, mask, width);
program.op(Opcode::Sub, low, bias, width);
return;
}
let quotient = program.by(Opcode::AShr, sum, i128::from(power), width);
if divisor < 0 {
program.negated(quotient);
}
}
fn exactly(program: &mut Program, signed: bool, divisor: i128) {
let width = program.width;
let power = divisor.trailing_zeros();
let mut value = DIVIDEND;
if power > 0 {
let shift = if signed { Opcode::AShr } else { Opcode::LShr };
value = program.by(shift, DIVIDEND, i128::from(power), width);
}
let odd = divisor >> power;
if odd != 1 {
program.by(Opcode::Mul, value, signed_at(inverse(odd, width), width), width);
}
}
fn inverse(odd: i128, width: u32) -> u128 {
let odd = odd as u128;
let mut inverse = odd;
for _ in 0..6 {
inverse = inverse.wrapping_mul(2u128.wrapping_sub(odd.wrapping_mul(inverse)));
}
inverse & mask(width)
}
fn signed_at(bits: u128, width: u32) -> i128 {
let spare = 128 - width;
((bits << spare) as i128) >> spare
}
fn mask(width: u32) -> u128 {
if width >= 128 { u128::MAX } else { (1u128 << width) - 1 }
}
fn division(func: &Func, inst: Inst) -> Option<Division> {
let data = &func[inst];
let (signed, remainder) = match data.opcode {
Opcode::SDiv => (true, false),
Opcode::UDiv => (false, false),
Opcode::SRem => (true, true),
Opcode::URem => (false, true),
_ => return None,
};
let &[dividend, by] = &func[data.args] else { return None };
let ty = func[dividend].ty;
if !ty.is_int() || ty.lanes() != 1 {
return None;
}
let imm = constant(func, by)?;
let divisor = if signed { imm.signed(ty) } else { i128::try_from(imm.unsigned()).ok()? };
Some(Division {
signed,
remainder,
exact: data.flags.contains(Flags::EXACT),
width: ty.bits(),
range: range(func, dividend, ty.bits(), signed),
divisor,
})
}
fn constant(func: &Func, value: Value) -> Option<Imm> {
let Def::Result { inst, .. } = func[value].def else { return None };
if func[inst].opcode != Opcode::IConst {
return None;
}
let Extra::Imm(imm) = func[inst].extra else { return None };
Some(func[imm])
}
fn range(func: &Func, value: Value, width: u32, signed: bool) -> Range {
let whole = if signed { Range::Signed(width) } else { Range::Unsigned(width) };
let Def::Result { inst, .. } = func[value].def else { return whole };
let Some(&from) = func[func[inst].args].first() else { return whole };
let bits = func[from].ty.bits();
if bits == 0 || bits >= width {
return whole;
}
match func[inst].opcode {
Opcode::ZExt => Range::Unsigned(bits),
Opcode::SExt if signed => Range::Signed(bits),
_ => whole,
}
}
fn write(func: &mut Func, inst: Inst, program: &Program) {
let Some((&Step::Op(opcode, args, _), before)) = program.steps.split_last() else { return };
let mut values = vec![func[func[inst].args][0]];
for &step in before {
let value = match step {
Step::Const(value, bits) => {
let ty = Type::int(bits);
ahead_const(func, inst, Imm::int(value, ty), ty)
}
Step::Op(opcode, args, bits) => {
let operands: Vec<Value> =
args[..arity(opcode)].iter().map(|&at| values[at]).collect();
ahead(func, inst, opcode, &operands, Type::int(bits))
}
};
values.push(value);
}
let operands: Vec<Value> = args[..arity(opcode)].iter().map(|&at| values[at]).collect();
becomes(func, inst, opcode, &operands);
}
fn arity(opcode: Opcode) -> usize {
match opcode {
Opcode::Trunc | Opcode::ZExt | Opcode::SExt => 1,
_ => 2,
}
}
#[cfg(test)]
mod tests {
use rucc_base::Interner;
use rucc_cost::Goal;
use rucc_ir::{Builder, Flags, Func, Opcode, Signature, Type, Value};
use super::{Division, Program, Range, Step, divisions, mask, multiplier, program, signed_at};
fn run(program: &Program, dividend: u128, values: &mut Vec<(u128, u32)>) -> u128 {
values.clear();
values.push((dividend & mask(program.width), program.width));
for &step in &program.steps {
let value = match step {
Step::Const(value, bits) => (value as u128 & mask(bits), bits),
Step::Op(opcode, [lhs, rhs], bits) => {
let (a, from) = values[lhs];
let (b, other) = values[rhs];
let answer = match opcode {
Opcode::ZExt | Opcode::Trunc | Opcode::SExt => {
let right =
if opcode == Opcode::Trunc { from > bits } else { from < bits };
assert!(right, "{opcode:?} from {from} bits to {bits}");
if opcode == Opcode::SExt { signed_at(a, from) as u128 } else { a }
}
_ => {
assert_eq!((from, other), (bits, bits), "{opcode:?} at {bits} bits");
match opcode {
Opcode::Add => a.wrapping_add(b),
Opcode::Sub => a.wrapping_sub(b),
Opcode::Mul => a.wrapping_mul(b),
Opcode::And => a & b,
Opcode::LShr | Opcode::AShr => {
assert!(b < u128::from(bits), "a shift by {b} at {bits} bits");
if opcode == Opcode::LShr {
a >> b
} else {
(signed_at(a, bits) >> b) as u128
}
}
_ => panic!("{opcode:?} is not something a program writes"),
}
}
};
(answer & mask(bits), bits)
}
};
values.push(value);
}
let (answer, bits) = *values.last().expect("a program has steps");
assert_eq!(bits, program.width, "the answer is at the width of the division");
answer
}
fn truth(division: &Division, dividend: u128) -> u128 {
let answer = if division.signed {
let x = signed_at(dividend, division.width);
if division.remainder { x % division.divisor } else { x / division.divisor }
} else {
let d = division.divisor as u128;
(if division.remainder { dividend % d } else { dividend / d }) as i128
};
answer as u128 & mask(division.width)
}
fn ends(range: Range) -> (i128, i128) {
match range {
Range::Unsigned(bits) => (0, (1 << bits) - 1),
Range::Signed(bits) => (-(1 << (bits - 1)), (1 << (bits - 1)) - 1),
}
}
fn division(
signed: bool,
remainder: bool,
width: u32,
range: Range,
divisor: i128,
) -> Division {
Division { signed, remainder, exact: false, width, range, divisor }
}
fn left(division: &Division) -> bool {
let size = division.divisor.unsigned_abs();
let (low, high) = ends(division.range);
let most = low.unsigned_abs().max(high.unsigned_abs());
let bits = match division.range {
Range::Unsigned(bits) | Range::Signed(bits) => bits,
};
let signed_range = matches!(division.range, Range::Signed(_));
matches!(division.divisor, 0 | 1)
|| (division.signed && division.divisor == -1)
|| (!signed_range && size > most)
|| (signed_range && !size.is_power_of_two() && (size > most || bits > 32))
|| (!signed_range && !size.is_power_of_two() && bits > 32)
}
fn check(
division: Division,
dividends: impl IntoIterator<Item = i128>,
values: &mut Vec<(u128, u32)>,
) {
let Some(program) = program(division) else {
assert!(left(&division), "{division:?} was left as a div");
return;
};
assert!(!left(&division), "{division:?} was rewritten");
for x in dividends {
let x = x as u128 & mask(division.width);
assert_eq!(run(&program, x, values), truth(&division, x), "{division:?} of {x:#x}");
}
}
fn edges(range: Range, divisor: i128) -> Vec<i128> {
let (low, high) = ends(range);
let size = divisor.abs().max(1);
let mut all = vec![low, high, -1, 0, 1];
let mut at = 0;
while at <= high + 1 || -at >= low - 1 {
all.extend([at - 1, at, at + 1, -at - 1, -at, -at + 1]);
at += size;
}
all.retain(|&x| (low..=high).contains(&x));
all
}
fn random(state: &mut u64) -> u64 {
*state ^= *state << 13;
*state ^= *state >> 7;
*state ^= *state << 17;
*state
}
fn both() -> [(bool, bool); 4] {
[(false, false), (false, true), (true, false), (true, true)]
}
#[test]
fn the_magic_numbers_are_the_ones_gcc_writes() {
assert_eq!(multiplier(100, 32, 32), (1_374_389_535, 5));
assert_eq!(multiplier(7, 32, 32), ((1 << 32) + 613_566_757, 3));
assert_eq!(multiplier(7, 32, 31), (2_454_267_027, 2));
assert_eq!(multiplier(3, 32, 31), (1_431_655_766, 0));
assert_eq!(multiplier(10, 16, 16), (52_429, 3));
}
#[test]
fn every_eight_bit_division_by_every_divisor_is_right() {
let mut values = Vec::new();
for (signed, remainder) in both() {
for width in [8, 16, 32, 64] {
let mut ranges = vec![Range::Unsigned(8)];
if signed {
ranges.push(Range::Signed(8));
}
for range in ranges {
if width == 8
&& range != (if signed { Range::Signed(8) } else { Range::Unsigned(8) })
{
continue;
}
let (low, high) = ends(range);
let divisors: Vec<i128> =
if signed { (-300..=300).collect() } else { (0..=300).collect() };
for divisor in divisors {
let divisor = match (width, signed) {
(8, true) => signed_at(divisor as u128 & 0xff, 8),
(8, false) => divisor & 0xff,
_ => divisor,
};
check(
division(signed, remainder, width, range, divisor),
low..=high,
&mut values,
);
}
}
}
}
}
#[test]
fn every_sixteen_bit_quotient_by_every_divisor_is_right() {
let mut values = Vec::new();
for signed in [false, true] {
for width in [16, 32] {
let range = if signed { Range::Signed(16) } else { Range::Unsigned(16) };
let divisors: Vec<i128> =
if signed { (-32_768..=32_767).collect() } else { (0..=65_535).collect() };
for divisor in divisors {
check(
division(signed, false, width, range, divisor),
edges(range, divisor),
&mut values,
);
}
}
}
let range = Range::Unsigned(16);
for divisor in -70_000..=70_000 {
check(division(true, false, 32, range, divisor), edges(range, divisor), &mut values);
}
}
#[test]
fn every_sixteen_bit_remainder_by_a_power_of_two_is_right() {
let mut values = Vec::new();
for (signed, remainder) in both() {
let range = if signed { Range::Signed(16) } else { Range::Unsigned(16) };
let (low, high) = ends(range);
for power in 1..16 {
for divisor in [1i128 << power, -(1i128 << power)] {
if !signed && divisor < 0 {
continue;
}
check(division(signed, remainder, 16, range, divisor), low..=high, &mut values);
}
}
}
}
#[test]
fn thirty_two_bit_divisions_are_right_at_the_edges_and_on_a_sample() {
let mut values = Vec::new();
let mut state = 0x9e37_79b9_7f4a_7c15;
let mut divisors: Vec<i128> = (-2_000..=2_000).collect();
for power in 1..=32 {
let at = 1i128 << power;
divisors.extend([at - 1, at, at + 1, -at + 1, -at, -at - 1]);
}
for _ in 0..2_000 {
divisors.push(i128::from(random(&mut state) as u32));
divisors.push(i128::from(random(&mut state) as i32));
}
for (signed, remainder) in both() {
for (width, range) in [
(32, if signed { Range::Signed(32) } else { Range::Unsigned(32) }),
(64, Range::Unsigned(32)),
(64, if signed { Range::Signed(32) } else { Range::Unsigned(32) }),
] {
let (low, high) = ends(range);
for &divisor in &divisors {
let divisor = if width == 32 {
let bits = divisor as u128 & mask(32);
if signed { signed_at(bits, 32) } else { bits as i128 }
} else {
divisor
};
if !signed && divisor < 0 {
continue;
}
let size = divisor.abs().max(1);
let mut dividends = vec![low, low + 1, -1, 0, 1, 2, high - 1, high];
for end in [low, high] {
let near = end / size * size;
dividends.extend([near - 1, near, near + 1, near - size, near + size]);
}
for _ in 0..64 {
let x = random(&mut state) as u128 & mask(32);
dividends.push(if matches!(range, Range::Signed(_)) {
signed_at(x, 32)
} else {
x as i128
});
}
dividends.retain(|x| (low..=high).contains(x));
check(
division(signed, remainder, width, range, divisor),
dividends,
&mut values,
);
}
}
}
}
#[test]
fn sixty_four_bits_rewrite_only_a_power_of_two() {
let mut values = Vec::new();
let mut state = 0x2545_f491_4f6c_dd1d;
for (signed, remainder) in both() {
let range = if signed { Range::Signed(64) } else { Range::Unsigned(64) };
let (low, high) = ends(range);
let mut dividends = vec![low, low + 1, -1, 0, 1, high - 1, high];
for _ in 0..512 {
let x = u128::from(random(&mut state));
dividends.push(if signed { signed_at(x, 64) } else { x as i128 });
}
dividends.retain(|x| (low..=high).contains(x));
for power in 1..64 {
for divisor in [1i128 << power, -(1i128 << power)] {
if !signed && divisor < 0 {
continue;
}
check(
division(signed, remainder, 64, range, divisor),
dividends.clone(),
&mut values,
);
}
}
for divisor in [3, 7, 10, 1_000_000_007] {
check(
division(signed, remainder, 64, range, divisor),
dividends.clone(),
&mut values,
);
}
}
}
#[test]
fn an_exact_division_is_right_over_every_multiple_it_is_given() {
let mut values = Vec::new();
let mut state = 0x1234_5678_9abc_def1;
for signed in [false, true] {
for width in [8, 16, 32, 64] {
let range = if signed { Range::Signed(width) } else { Range::Unsigned(width) };
let (low, high) = ends(range);
for divisor in (-100i128..=100).chain([12, 24, 40, 56, 1 << 20, 3 << 30]) {
if (!signed && divisor < 0) || divisor < low || divisor > high {
continue;
}
let exact =
Division { exact: true, ..division(signed, false, width, range, divisor) };
let Some(program) = program(exact) else {
assert!(
matches!(divisor, 0 | 1) || (signed && divisor == -1),
"{exact:?} was left"
);
continue;
};
let size = divisor.abs();
let mut dividends: Vec<i128> = if width <= 16 {
(low / size..=high / size).map(|times| times * divisor).collect()
} else {
(0..256)
.map(|_| {
let times = signed_at(u128::from(random(&mut state)), 64)
% (high / size).max(1);
if signed { times * divisor } else { times.abs() * divisor }
})
.collect()
};
dividends.extend([0, divisor, high / size * divisor]);
dividends.retain(|x| (low..=high).contains(x));
for x in dividends {
let x = x as u128 & mask(width);
assert_eq!(
run(&program, x, &mut values),
truth(&exact, x),
"{exact:?} of {x:#x}"
);
}
}
}
}
}
fn one(param: Type, ret: Type, body: impl FnOnce(&mut Builder<'_>, Value) -> Value) -> Func {
let mut names = Interner::new();
let mut func = Func::new(
names.intern("f"),
Signature::new().with_params(&[param]).with_returns(&[ret]),
);
let entry = func.create_block();
let x = func.append_param(entry, param);
let mut build = Builder::new(&mut func, entry);
let answer = body(&mut build, x);
build.ret(&[answer]);
func
}
fn opcodes(func: &Func) -> Vec<Opcode> {
let entry = func.blocks().next().expect("an entry");
func.insts(entry).map(|inst| func[inst].opcode).filter(|&op| op != Opcode::IConst).collect()
}
fn divided(
opcode: Opcode,
divisor: i128,
flags: Flags,
) -> impl FnOnce(&mut Builder<'_>, Value) -> Value {
move |build, x| {
let ty = build.func()[x].ty;
let by = build.iconst(ty, divisor);
build.binary(opcode, x, by, flags)
}
}
#[test]
fn an_unsigned_division_by_seven_is_a_multiply_an_add_and_two_shifts() {
let i32 = Type::int(32);
let mut func = one(i32, i32, divided(Opcode::UDiv, 7, Flags::NONE));
divisions(&mut func, Goal::Speed);
let want = [
Opcode::ZExt,
Opcode::Mul,
Opcode::LShr,
Opcode::Add,
Opcode::LShr,
Opcode::Trunc,
Opcode::Return,
];
assert_eq!(opcodes(&func), want);
}
#[test]
fn a_signed_remainder_is_the_quotient_multiplied_back() {
let i32 = Type::int(32);
let mut func = one(i32, i32, divided(Opcode::SRem, 7, Flags::NONE));
divisions(&mut func, Goal::Speed);
let want = [
Opcode::SExt,
Opcode::Mul,
Opcode::AShr,
Opcode::Trunc,
Opcode::AShr,
Opcode::Sub,
Opcode::Mul,
Opcode::Sub,
Opcode::Return,
];
assert_eq!(opcodes(&func), want);
}
#[test]
fn a_widened_unsigned_short_takes_the_short_number_and_no_correction() {
let (i16, i32) = (Type::int(16), Type::int(32));
let mut func = one(i16, i32, |build, x| {
let wide = build.unary(Opcode::ZExt, x, i32);
divided(Opcode::SDiv, 10, Flags::NONE)(build, wide)
});
divisions(&mut func, Goal::Speed);
let want =
[Opcode::ZExt, Opcode::ZExt, Opcode::Mul, Opcode::LShr, Opcode::Trunc, Opcode::Return];
assert_eq!(opcodes(&func), want);
}
#[test]
fn an_exact_division_is_a_shift_and_a_multiply() {
let i64 = Type::int(64);
let mut func = one(i64, i64, divided(Opcode::SDiv, 12, Flags::EXACT));
divisions(&mut func, Goal::Speed);
assert_eq!(opcodes(&func), [Opcode::AShr, Opcode::Mul, Opcode::Return]);
}
#[test]
fn size_a_variable_divisor_and_a_wide_dividend_keep_the_div() {
let i32 = Type::int(32);
let mut func = one(i32, i32, divided(Opcode::UDiv, 7, Flags::NONE));
divisions(&mut func, Goal::Size);
assert_eq!(opcodes(&func), [Opcode::UDiv, Opcode::Return]);
let mut func = one(i32, i32, |build, x| build.binary(Opcode::SDiv, x, x, Flags::NONE));
divisions(&mut func, Goal::Speed);
assert_eq!(opcodes(&func), [Opcode::SDiv, Opcode::Return]);
let i64 = Type::int(64);
let mut func = one(i64, i64, divided(Opcode::UDiv, 10, Flags::NONE));
divisions(&mut func, Goal::Speed);
assert_eq!(opcodes(&func), [Opcode::UDiv, Opcode::Return]);
}
}