use std::collections::{HashMap, HashSet};
use rucc_ir::{
Abi, Block, BlockCall, CallInfo, Def, Extra, Flags, Func, Imm, Inst, InstData, IntPred,
MemInfo, Opcode, Param, Signature, Type, Value,
};
use rucc_target::{CallRegs, Places, Where};
use crate::expand;
const WIDE: u32 = 128;
const HALF: u32 = 64;
const STEP: u64 = 8;
fn is_wide(ty: Type) -> bool {
ty.is_int() && ty.is_scalar() && ty.bits() == WIDE
}
fn half() -> Type {
Type::int(HALF)
}
pub fn halves(func: &mut Func, conv: &CallRegs) -> bool {
if !func.values().any(|value| is_wide(func[value].ty)) {
return false;
}
let insts: Vec<Inst> =
walk(func).into_iter().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
let order: HashMap<Inst, usize> =
insts.iter().enumerate().map(|(at, &inst)| (inst, at)).collect();
if !insts.iter().enumerate().all(|(at, &inst)| can_split(func, &order, at, inst)) {
return false;
}
if !func.signatures().all(|signature| fits(signature, conv)) {
return false;
}
let mut halves: Halves = HashMap::new();
let mut forward: HashMap<Value, Value> = HashMap::new();
for block in func.blocks().collect::<Vec<_>>() {
params(func, block, &mut halves, &mut forward);
}
for &inst in &insts {
rewrite(func, &mut halves, &mut forward, inst);
}
substitute(func, &forward);
let signature = split_signature(func.signature());
func.set_signature(signature);
true
}
fn walk(func: &Func) -> Vec<Block> {
let Some(entry) = func.entry() else { return func.blocks().collect() };
let mut seen: HashSet<Block> = HashSet::new();
let mut order: Vec<Block> = Vec::new();
let mut stack: Vec<(Block, bool)> = vec![(entry, false)];
seen.insert(entry);
while let Some((block, done)) = stack.pop() {
if done {
order.push(block);
continue;
}
stack.push((block, true));
let Some(term) = func.terminator(block) else { continue };
for call in func.successors(term) {
if seen.insert(call.block) {
stack.push((call.block, false));
}
}
}
order.reverse();
order.extend(func.blocks().filter(|block| !seen.contains(block)));
order
}
type Halves = HashMap<Value, (Value, Value)>;
fn understood(opcode: Opcode) -> bool {
matches!(
opcode,
Opcode::IConst
| Opcode::Load
| Opcode::Store
| Opcode::Add
| Opcode::Sub
| Opcode::Mul
| Opcode::Shl
| Opcode::LShr
| Opcode::AShr
| Opcode::And
| Opcode::Or
| Opcode::Xor
| Opcode::ICmp
| Opcode::Select
| Opcode::Trunc
| Opcode::SExt
| Opcode::ZExt
| Opcode::Call
| Opcode::CallIndirect
| Opcode::Return
| Opcode::Jump
| Opcode::BrIf
)
}
fn can_split(func: &Func, order: &HashMap<Inst, usize>, at: usize, inst: Inst) -> bool {
let data = func[inst];
let reads = operands(func, inst);
let wide = |&value: &Value| is_wide(func[value].ty);
if !reads.iter().any(wide) && !data.results().any(|value| is_wide(func[value].ty)) {
return true;
}
if !understood(data.opcode) {
return false;
}
if func.carries_mem(inst) {
return false;
}
if data.opcode == Opcode::SExt && reads.iter().any(|&value| func[value].ty.bits() < 8) {
return false;
}
if matches!(data.opcode, Opcode::Call | Opcode::CallIndirect) {
let Extra::Call(info) = data.extra else { return false };
if func[func[info].signature].variadic {
return false;
}
}
reads.iter().filter(|value| wide(value)).all(|&value| match func[value].def {
Def::Result { inst, .. } => order.get(&inst).is_some_and(|&def| def < at),
Def::Param { .. } => true,
})
}
fn operands(func: &Func, inst: Inst) -> Vec<Value> {
let mut reads = func[func[inst].args].to_vec();
for call in func.successors(inst).collect::<Vec<_>>() {
reads.extend_from_slice(&func[call.args]);
}
reads
}
fn fits(signature: &Signature, conv: &CallRegs) -> bool {
let mut places = Places::new(conv);
for param in &signature.params {
if let Abi::ByVal { size, align } = param.abi {
places.on_stack(u32::try_from(size).unwrap_or(u32::MAX), align);
} else if crate::abi::on_the_stack(param.ty) {
let (size, align) = crate::abi::X87_AREA;
places.on_stack(size, align);
} else if is_wide(param.ty) {
let low = places.integer();
let high = places.integer();
if !matches!((low, high), (Where::Reg(_), Where::Reg(_))) {
return false;
}
} else if param.ty.is_float() {
places.float();
} else {
places.integer();
}
}
true
}
fn params(func: &mut Func, block: Block, halves: &mut Halves, forward: &mut HashMap<Value, Value>) {
let old: Vec<Value> = func[block].params.clone();
if !old.iter().any(|&value| is_wide(func[value].ty)) {
return;
}
for &value in &old {
if is_wide(func[value].ty) {
let low = func.append_param(block, half());
let high = func.append_param(block, half());
halves.insert(value, (low, high));
} else {
let again = func.append_param(block, func[value].ty);
forward.insert(value, again);
}
}
func.retain_params(block, |value| !old.contains(&value));
}
fn rewrite(func: &mut Func, halves: &mut Halves, forward: &mut HashMap<Value, Value>, inst: Inst) {
let data = func[inst];
let produces = data.results().any(|value| is_wide(func[value].ty));
let takes = func[data.args].iter().any(|&value| is_wide(func[value].ty));
match data.opcode {
Opcode::IConst if produces => constant(func, halves, inst),
Opcode::Load if produces => load(func, halves, inst),
Opcode::Store if takes => store(func, halves, inst),
Opcode::Add | Opcode::Sub if produces => carried(func, halves, inst, data.opcode),
Opcode::Mul if produces => multiply(func, halves, inst),
Opcode::Shl | Opcode::LShr | Opcode::AShr if produces => {
shifted(func, halves, inst, data.opcode);
}
Opcode::And | Opcode::Or | Opcode::Xor if produces => {
bitwise(func, halves, inst, data.opcode);
}
Opcode::ICmp if takes => compare(func, halves, forward, inst),
Opcode::Select if produces => choose(func, halves, inst),
Opcode::Trunc if takes => truncate(func, halves, forward, inst),
Opcode::SExt | Opcode::ZExt if produces => {
extend(func, halves, inst, data.opcode == Opcode::SExt);
}
Opcode::Call | Opcode::CallIndirect if produces || takes => {
call(func, halves, forward, inst);
}
Opcode::Return if takes => flatten(func, halves, inst),
Opcode::Jump | Opcode::BrIf => edges(func, halves, inst),
_ => {}
}
}
fn constant(func: &mut Func, halves: &mut Halves, inst: Inst) {
let Extra::Imm(imm) = func[inst].extra else { return };
let bits = func[imm].unsigned();
#[expect(clippy::cast_possible_truncation, reason = "the halves are what this is taking")]
let (low, high) = (bits as u64, (bits >> HALF) as u64);
let low = ahead_const(func, inst, i128::from(low));
let high = ahead_const(func, inst, i128::from(high));
replace(func, halves, inst, low, high);
}
fn load(func: &mut Func, halves: &mut Halves, inst: Inst) {
let data = func[inst];
let Extra::Mem(mem) = data.extra else { return };
let info = func[mem];
let Some(&from) = func[data.args].first() else { return };
let low = read(func, inst, from, word(info, 0), data.flags);
let up = stepped(func, inst, from);
let high = read(func, inst, up, word(info, STEP), data.flags);
replace(func, halves, inst, low, high);
}
fn store(func: &mut Func, halves: &mut Halves, inst: Inst) {
let data = func[inst];
let Extra::Mem(mem) = data.extra else { return };
let info = func[mem];
let args = func[data.args].to_vec();
let [value, into] = args[..] else { return };
let Some(&(low, high)) = halves.get(&value) else { return };
write(func, inst, low, into, word(info, 0), data.flags);
let up = stepped(func, inst, into);
write(func, inst, high, up, word(info, STEP), data.flags);
func.remove_inst(inst);
}
fn carried(func: &mut Func, halves: &mut Halves, inst: Inst, opcode: Opcode) {
let args = func[func[inst].args].to_vec();
let [a, b] = args[..] else { return };
let (Some(&(a_low, a_high)), Some(&(b_low, b_high))) = (halves.get(&a), halves.get(&b)) else {
return;
};
let low = ahead(func, inst, opcode, &[a_low, b_low]);
let carried = if opcode == Opcode::Add {
compared(func, inst, IntPred::Ult, low, a_low)
} else {
compared(func, inst, IntPred::Ult, a_low, b_low)
};
let carry = ahead(func, inst, Opcode::ZExt, &[carried]);
let high = ahead(func, inst, opcode, &[a_high, b_high]);
let high = ahead(func, inst, opcode, &[high, carry]);
replace(func, halves, inst, low, high);
}
fn multiply(func: &mut Func, halves: &mut Halves, inst: Inst) {
let args = func[func[inst].args].to_vec();
let [a, b] = args[..] else { return };
let (Some(&(a_low, a_high)), Some(&(b_low, b_high))) = (halves.get(&a), halves.get(&b)) else {
return;
};
let low = ahead(func, inst, Opcode::Mul, &[a_low, b_low]);
let carried = expand::high_half(func, inst, a_low, b_low, false, half());
let cross = ahead(func, inst, Opcode::Mul, &[a_low, b_high]);
let other = ahead(func, inst, Opcode::Mul, &[a_high, b_low]);
let high = ahead(func, inst, Opcode::Add, &[carried, cross]);
let high = ahead(func, inst, Opcode::Add, &[high, other]);
replace(func, halves, inst, low, high);
}
fn shifted(func: &mut Func, halves: &mut Halves, inst: Inst, opcode: Opcode) {
let args = func[func[inst].args].to_vec();
let [a, b] = args[..] else { return };
let (Some(&(a_low, a_high)), Some(&(count, _))) = (halves.get(&a), halves.get(&b)) else {
return;
};
let top = ahead_const(func, inst, i128::from(HALF - 1));
let places = ahead(func, inst, Opcode::And, &[count, top]);
let back = ahead(func, inst, Opcode::Sub, &[top, places]);
let one = ahead_const(func, inst, 1);
let zero = ahead_const(func, inst, 0);
let bit = ahead_const(func, inst, i128::from(HALF));
let reach = ahead(func, inst, Opcode::And, &[count, bit]);
let whole = compared(func, inst, IntPred::Ne, reach, zero);
let (low, high) = if opcode == Opcode::Shl {
let moved = ahead(func, inst, Opcode::Shl, &[a_low, places]);
let edge = ahead(func, inst, Opcode::LShr, &[a_low, one]);
let across = ahead(func, inst, Opcode::LShr, &[edge, back]);
let above = ahead(func, inst, Opcode::Shl, &[a_high, places]);
let joined = ahead(func, inst, Opcode::Or, &[above, across]);
let low = ahead(func, inst, Opcode::Select, &[whole, zero, moved]);
let high = ahead(func, inst, Opcode::Select, &[whole, moved, joined]);
(low, high)
} else {
let moved = ahead(func, inst, opcode, &[a_high, places]);
let edge = ahead(func, inst, Opcode::Shl, &[a_high, one]);
let across = ahead(func, inst, Opcode::Shl, &[edge, back]);
let below = ahead(func, inst, Opcode::LShr, &[a_low, places]);
let joined = ahead(func, inst, Opcode::Or, &[below, across]);
let spent = if opcode == Opcode::AShr {
ahead(func, inst, Opcode::AShr, &[a_high, top])
} else {
zero
};
let low = ahead(func, inst, Opcode::Select, &[whole, moved, joined]);
let high = ahead(func, inst, Opcode::Select, &[whole, spent, moved]);
(low, high)
};
replace(func, halves, inst, low, high);
}
fn bitwise(func: &mut Func, halves: &mut Halves, inst: Inst, opcode: Opcode) {
let args = func[func[inst].args].to_vec();
let [a, b] = args[..] else { return };
let (Some(&(a_low, a_high)), Some(&(b_low, b_high))) = (halves.get(&a), halves.get(&b)) else {
return;
};
let low = ahead(func, inst, opcode, &[a_low, b_low]);
let high = ahead(func, inst, opcode, &[a_high, b_high]);
replace(func, halves, inst, low, high);
}
fn compare(func: &mut Func, halves: &Halves, forward: &mut HashMap<Value, Value>, inst: Inst) {
let Extra::IntPred(pred) = func[inst].extra else { return };
let args = func[func[inst].args].to_vec();
let [a, b] = args[..] else { return };
let (Some(&(a_low, a_high)), Some(&(b_low, b_high))) = (halves.get(&a), halves.get(&b)) else {
return;
};
let answer = if matches!(pred, IntPred::Eq | IntPred::Ne) {
let low = ahead(func, inst, Opcode::Xor, &[a_low, b_low]);
let high = ahead(func, inst, Opcode::Xor, &[a_high, b_high]);
let both = ahead(func, inst, Opcode::Or, &[low, high]);
let zero = ahead_const(func, inst, 0);
compared(func, inst, pred, both, zero)
} else {
let above = compared(func, inst, strict(pred), a_high, b_high);
let below = compared(func, inst, unsigned(pred), a_low, b_low);
let same = compared(func, inst, IntPred::Eq, a_high, b_high);
let tail = bit(func, inst, Opcode::And, same, below);
bit(func, inst, Opcode::Or, above, tail)
};
if let Some(result) = func[inst].first_result {
forward.insert(result, answer);
}
func.remove_inst(inst);
}
fn strict(pred: IntPred) -> IntPred {
match pred {
IntPred::Sle => IntPred::Slt,
IntPred::Sge => IntPred::Sgt,
IntPred::Ule => IntPred::Ult,
IntPred::Uge => IntPred::Ugt,
other => other,
}
}
fn unsigned(pred: IntPred) -> IntPred {
match pred {
IntPred::Slt => IntPred::Ult,
IntPred::Sle => IntPred::Ule,
IntPred::Sgt => IntPred::Ugt,
IntPred::Sge => IntPred::Uge,
other => other,
}
}
fn choose(func: &mut Func, halves: &mut Halves, inst: Inst) {
let args = func[func[inst].args].to_vec();
let [cond, then, other] = args[..] else { return };
let (Some(&(then_low, then_high)), Some(&(other_low, other_high))) =
(halves.get(&then), halves.get(&other))
else {
return;
};
let low = ahead(func, inst, Opcode::Select, &[cond, then_low, other_low]);
let high = ahead(func, inst, Opcode::Select, &[cond, then_high, other_high]);
replace(func, halves, inst, low, high);
}
fn truncate(func: &mut Func, halves: &Halves, forward: &mut HashMap<Value, Value>, inst: Inst) {
let Some(&arg) = func[func[inst].args].first() else { return };
let Some(&(low, _)) = halves.get(&arg) else { return };
let Some(result) = func[inst].first_result else { return };
if func[result].ty.bits() == HALF {
forward.insert(result, low);
func.remove_inst(inst);
return;
}
becomes(func, inst, Opcode::Trunc, &[low]);
}
fn extend(func: &mut Func, halves: &mut Halves, inst: Inst, signed: bool) {
let Some(&arg) = func[func[inst].args].first() else { return };
let low = if func[arg].ty.bits() == HALF {
arg
} else {
let opcode = if signed { Opcode::SExt } else { Opcode::ZExt };
ahead(func, inst, opcode, &[arg])
};
let high = if signed {
let top = ahead_const(func, inst, i128::from(HALF - 1));
ahead(func, inst, Opcode::AShr, &[low, top])
} else {
ahead_const(func, inst, 0)
};
replace(func, halves, inst, low, high);
}
fn call(func: &mut Func, halves: &mut Halves, forward: &mut HashMap<Value, Value>, inst: Inst) {
let data = func[inst];
let Extra::Call(info) = data.extra else { return };
let info = func[info];
let args = spread(&func[data.args], halves);
let results: Vec<Type> = data
.results()
.map(|value| func[value].ty)
.flat_map(|ty| if is_wide(ty) { vec![half(), half()] } else { vec![ty] })
.collect();
let signature = func.add_signature(split_signature(&func[info.signature]));
let extra = Extra::Call(func.add_call(CallInfo { signature, ..info }));
let args = func.push_values(&args);
let span = func.span(inst);
let made = func.create_inst(InstData { args, extra, ..data }, &results, span);
func.insert_before(made, inst);
let mut fresh = func[made].results();
for old in data.results() {
if is_wide(func[old].ty) {
let (Some(low), Some(high)) = (fresh.next(), fresh.next()) else { return };
halves.insert(old, (low, high));
} else if let Some(again) = fresh.next() {
forward.insert(old, again);
}
}
func.remove_inst(inst);
}
fn flatten(func: &mut Func, halves: &Halves, inst: Inst) {
let args = spread(&func[func[inst].args], halves);
func[inst].args = func.push_values(&args);
}
fn edges(func: &mut Func, halves: &Halves, inst: Inst) {
for at in func.target_list(inst).iter() {
let call = func[at];
let args = func[call.args].to_vec();
if !args.iter().any(|value| halves.contains_key(value)) {
continue;
}
let args = func.push_values(&spread(&args, halves));
func.set_block_call(at, BlockCall { block: call.block, args });
}
}
fn spread(args: &[Value], halves: &Halves) -> Vec<Value> {
args.iter()
.flat_map(|value| match halves.get(value) {
Some(&(low, high)) => vec![low, high],
None => vec![*value],
})
.collect()
}
fn split_signature(signature: &Signature) -> Signature {
let split = |params: &[Param]| -> Vec<Param> {
params
.iter()
.flat_map(|param| {
if is_wide(param.ty) {
vec![Param::new(half()), Param::new(half())]
} else {
vec![*param]
}
})
.collect()
};
Signature {
params: split(&signature.params),
returns: split(&signature.returns),
variadic: signature.variadic,
}
}
fn replace(func: &mut Func, halves: &mut Halves, inst: Inst, low: Value, high: Value) {
if let Some(result) = func[inst].first_result {
halves.insert(result, (low, high));
}
func.remove_inst(inst);
}
fn substitute(func: &mut Func, forward: &HashMap<Value, Value>) {
if forward.is_empty() {
return;
}
let with = |value: Value| forward.get(&value).copied().unwrap_or(value);
for block in func.blocks().collect::<Vec<_>>() {
for inst in func.insts(block).collect::<Vec<Inst>>() {
let args = func[inst].args;
func.rewrite(args, with);
for call in func.successors(inst).collect::<Vec<_>>() {
func.rewrite(call.args, with);
}
}
}
}
fn word(info: MemInfo, at: u64) -> MemInfo {
let align = if at == 0 { info.align } else { info.align.min(8) };
MemInfo { size: STEP, align, ..info }
}
fn stepped(func: &mut Func, inst: Inst, from: Value) -> Value {
let step = ahead_const(func, inst, i128::from(STEP));
let args = func.push_values(&[from, step]);
written(func, inst, InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR)
}
fn read(func: &mut Func, inst: Inst, from: Value, info: MemInfo, flags: Flags) -> Value {
let extra = Extra::Mem(func.add_mem(info));
let args = func.push_values(&[from]);
let data = InstData { args, flags, extra, ..InstData::new(Opcode::Load) };
written(func, inst, data, half())
}
fn write(func: &mut Func, inst: Inst, value: Value, into: Value, info: MemInfo, flags: Flags) {
let span = func.span(inst);
let extra = Extra::Mem(func.add_mem(info));
let args = func.push_values(&[value, into]);
let data = InstData { args, flags, extra, ..InstData::new(Opcode::Store) };
let made = func.create_inst(data, &[], span);
func.insert_before(made, inst);
}
fn compared(func: &mut Func, inst: Inst, pred: IntPred, lhs: Value, rhs: Value) -> Value {
let args = func.push_values(&[lhs, rhs]);
let extra = Extra::IntPred(pred);
written(func, inst, InstData { args, extra, ..InstData::new(Opcode::ICmp) }, Type::I1)
}
fn bit(func: &mut Func, inst: Inst, opcode: Opcode, lhs: Value, rhs: Value) -> Value {
let args = func.push_values(&[lhs, rhs]);
written(func, inst, InstData { args, ..InstData::new(opcode) }, Type::I1)
}
fn ahead(func: &mut Func, inst: Inst, opcode: Opcode, args: &[Value]) -> Value {
let args = func.push_values(args);
written(func, inst, InstData { args, ..InstData::new(opcode) }, half())
}
fn ahead_const(func: &mut Func, inst: Inst, value: i128) -> Value {
let extra = Extra::Imm(func.add_imm(Imm::int(value, half())));
written(func, inst, InstData { extra, ..InstData::new(Opcode::IConst) }, half())
}
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::{
Block, Builder, Flags, Func, MemOrder, Module, Restrict, Signature, Type, Value,
};
use rucc_target::x86_64::SYSV;
use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
use super::{HALF, IntPred, MemInfo, Opcode, halves};
fn wide() -> Type {
Type::int(super::WIDE)
}
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, params: &[Type], returns: &[Type]) -> (Func, Block, Vec<Value>) {
let signature = Signature::new().with_params(params).with_returns(returns);
let mut func = Func::new(names.intern("f"), signature);
let entry = func.create_block();
let values = params.iter().map(|&ty| func.append_param(entry, ty)).collect();
(func, entry, values)
}
fn info(size: u64, align: u32) -> MemInfo {
MemInfo {
size,
align,
order: MemOrder::NotAtomic,
tbaa: None,
owns: 0,
restrict: Restrict::NONE,
}
}
#[test]
fn an_add_carries_from_the_low_half_into_the_high_one() {
let mut names = Interner::new();
let (mut func, entry, params) = shell(&mut names, &[wide(), wide()], &[wide()]);
let mut build = Builder::new(&mut func, entry);
let sum = build.binary(Opcode::Add, params[0], params[1], Flags::NONE);
build.ret(&[sum]);
assert!(halves(&mut func, &SYSV), "there is a width to split");
let text = printed(&func, &mut names);
assert!(!text.contains("i128"), "nothing that wide is left: {text}");
assert_eq!(text.matches(" = add ").count(), 3, "three adds: {text}");
assert_eq!(text.matches("icmp ult").count(), 1, "one carry: {text}");
assert_eq!(text.matches(" = zext.i64 ").count(), 1, "the carry as a number: {text}");
}
#[test]
fn a_subtract_borrows_the_other_way_round() {
let mut names = Interner::new();
let (mut func, entry, params) = shell(&mut names, &[wide(), wide()], &[wide()]);
let mut build = Builder::new(&mut func, entry);
let difference = build.binary(Opcode::Sub, params[0], params[1], Flags::NONE);
build.ret(&[difference]);
assert!(halves(&mut func, &SYSV), "there is a width to split");
let text = printed(&func, &mut names);
assert_eq!(text.matches(" = sub ").count(), 3, "three subtracts: {text}");
assert!(text.contains("icmp ult %0, %2"), "the operands are compared: {text}");
}
#[test]
fn the_signature_and_the_entry_block_say_the_same_thing() {
let mut names = Interner::new();
let (mut func, entry, params) = shell(&mut names, &[Type::int(32), wide()], &[wide()]);
let mut build = Builder::new(&mut func, entry);
build.ret(&[params[1]]);
assert!(halves(&mut func, &SYSV), "there is a width to split");
assert_eq!(
func.signature().param_types().collect::<Vec<_>>(),
[Type::int(32), Type::int(HALF), Type::int(HALF)],
"the wide parameter became two where it stood"
);
assert_eq!(
func.signature().return_types().collect::<Vec<_>>(),
[Type::int(HALF), Type::int(HALF)],
"and so did what comes back"
);
let text = printed(&func, &mut names);
assert!(text.contains("block0(%0: i32, %1: i64, %2: i64)"), "the block agrees: {text}");
assert!(text.contains("return %1, %2"), "both halves go back: {text}");
let _ = entry;
}
#[test]
fn a_read_takes_the_high_word_a_word_above_the_low_one() {
let mut names = Interner::new();
let (mut func, entry, params) = shell(&mut names, &[Type::PTR], &[wide()]);
let mut build = Builder::new(&mut func, entry);
let value = build.load(wide(), params[0], info(16, 16), Flags::NONE);
build.ret(&[value]);
assert!(halves(&mut func, &SYSV), "there is a width to split");
let text = printed(&func, &mut names);
assert_eq!(text.matches(" = load.i64 ").count(), 2, "two reads: {text}");
assert!(text.contains("ptr_add"), "the high word is a word up: {text}");
assert!(text.contains("align 16"), "the low word keeps what the object had: {text}");
assert!(text.contains("align 8"), "the high word knows less: {text}");
}
#[test]
fn an_equality_asks_once_about_both_halves() {
let mut names = Interner::new();
let (mut func, entry, params) = shell(&mut names, &[wide(), wide()], &[Type::int(32)]);
let mut build = Builder::new(&mut func, entry);
let same = build.icmp(IntPred::Eq, params[0], params[1]);
let answer = build.unary(Opcode::ZExt, same, Type::int(32));
build.ret(&[answer]);
assert!(halves(&mut func, &SYSV), "there is a width to split");
let text = printed(&func, &mut names);
assert_eq!(text.matches("icmp").count(), 1, "one comparison: {text}");
assert_eq!(text.matches(" = xor ").count(), 2, "the halves differ or they do not: {text}");
}
#[test]
fn an_ordering_reads_the_low_halves_without_a_sign() {
let mut names = Interner::new();
let (mut func, entry, params) = shell(&mut names, &[wide(), wide()], &[Type::int(32)]);
let mut build = Builder::new(&mut func, entry);
let below = build.icmp(IntPred::Slt, params[0], params[1]);
let answer = build.unary(Opcode::ZExt, below, Type::int(32));
build.ret(&[answer]);
assert!(halves(&mut func, &SYSV), "there is a width to split");
let text = printed(&func, &mut names);
assert!(text.contains("icmp slt"), "the high halves keep the sign: {text}");
assert!(text.contains("icmp ult"), "the low halves have none: {text}");
assert!(
text.contains("icmp eq"),
"and the low halves only matter when the high tie: {text}"
);
}
#[test]
fn an_ordering_that_allows_equality_asks_the_high_halves_a_strict_question() {
let mut names = Interner::new();
let (mut func, entry, params) = shell(&mut names, &[wide(), wide()], &[Type::int(32)]);
let mut build = Builder::new(&mut func, entry);
let at_least = build.icmp(IntPred::Sge, params[0], params[1]);
let answer = build.unary(Opcode::ZExt, at_least, Type::int(32));
build.ret(&[answer]);
assert!(halves(&mut func, &SYSV), "there is a width to split");
let text = printed(&func, &mut names);
assert!(text.contains("icmp sgt"), "the high halves settle it outright: {text}");
assert!(!text.contains("icmp sge"), "a tie in the high halves settles nothing: {text}");
assert!(text.contains("icmp uge"), "the low halves are the ones allowed to tie: {text}");
}
#[test]
fn a_widening_puts_the_sign_of_the_value_in_the_high_half() {
let mut names = Interner::new();
let (mut func, entry, params) = shell(&mut names, &[Type::int(32)], &[wide()]);
let mut build = Builder::new(&mut func, entry);
let value = build.unary(Opcode::SExt, params[0], wide());
build.ret(&[value]);
assert!(halves(&mut func, &SYSV), "there is a width to split");
let text = printed(&func, &mut names);
assert!(text.contains("sext.i64"), "the value fills the low half: {text}");
assert!(text.contains("ashr"), "and its sign fills the high one: {text}");
}
#[test]
fn a_block_parameter_becomes_two_and_every_branch_passes_two() {
let mut names = Interner::new();
let (mut func, entry, params) = shell(&mut names, &[wide(), Type::int(32)], &[wide()]);
let tail = func.create_block();
let carried = func.append_param(tail, wide());
let mut build = Builder::new(&mut func, entry);
let zero = build.iconst(Type::int(32), 0);
let taken = build.icmp(IntPred::Ne, params[1], zero);
let other = build.iconst(wide(), 7);
build.br_if(taken, tail, &[params[0]], tail, &[other]);
let mut build = Builder::new(&mut func, tail);
build.ret(&[carried]);
assert!(halves(&mut func, &SYSV), "there is a width to split");
let text = printed(&func, &mut names);
assert!(!text.contains("i128"), "nothing that wide is left: {text}");
assert!(text.contains("block1(%7: i64, %8: i64)"), "the block takes two: {text}");
assert_eq!(text.matches("block1(").count(), 3, "and both edges pass two: {text}");
}
#[test]
fn a_multiply_is_three_multiplies_and_the_carry_out_of_the_low_ones() {
let mut names = Interner::new();
let (mut func, entry, params) = shell(&mut names, &[wide(), wide()], &[wide()]);
let mut build = Builder::new(&mut func, entry);
let product = build.binary(Opcode::Mul, params[0], params[1], Flags::NONE);
build.ret(&[product]);
assert!(halves(&mut func, &SYSV), "there is a width to split");
let text = printed(&func, &mut names);
assert!(!text.contains("i128"), "nothing that wide is left: {text}");
assert_eq!(text.matches(" = mul ").count(), 7, "three and the carry's four: {text}");
}
#[test]
fn a_shift_left_chooses_between_a_count_that_crossed_a_half_and_one_that_did_not() {
let mut names = Interner::new();
let (mut func, entry, params) = shell(&mut names, &[wide(), wide()], &[wide()]);
let mut build = Builder::new(&mut func, entry);
let moved = build.binary(Opcode::Shl, params[0], params[1], Flags::NONE);
build.ret(&[moved]);
assert!(halves(&mut func, &SYSV), "there is a width to split");
let text = printed(&func, &mut names);
assert!(!text.contains("i128"), "nothing that wide is left: {text}");
assert_eq!(
text.matches(" = shl ").count(),
2,
"one per half, and the far case reuses one: {text}"
);
assert_eq!(text.matches(" = select.i64 ").count(), 2, "one choice per half: {text}");
assert_eq!(text.matches(" = lshr ").count(), 2, "the crossing bits, in two steps: {text}");
}
#[test]
fn the_bits_that_cross_move_one_place_and_then_the_rest_of_the_way() {
let mut names = Interner::new();
let (mut func, entry, params) = shell(&mut names, &[wide(), wide()], &[wide()]);
let mut build = Builder::new(&mut func, entry);
let moved = build.binary(Opcode::LShr, params[0], params[1], Flags::NONE);
build.ret(&[moved]);
assert!(halves(&mut func, &SYSV), "there is a width to split");
let text = printed(&func, &mut names);
assert!(text.contains("iconst.i64 63"), "sixty three is the distance left: {text}");
assert!(text.contains("iconst.i64 1"), "after the one place that comes first: {text}");
assert!(text.contains(" = sub "), "the rest of the way is worked out: {text}");
assert!(
!text.contains("iconst.i64 127"),
"and the count is not masked to the width: {text}"
);
}
#[test]
fn an_arithmetic_shift_right_leaves_the_sign_bit_where_a_logical_one_leaves_zeroes() {
let mut names = Interner::new();
let (mut func, entry, params) = shell(&mut names, &[wide(), wide()], &[wide()]);
let mut build = Builder::new(&mut func, entry);
let moved = build.binary(Opcode::AShr, params[0], params[1], Flags::NONE);
build.ret(&[moved]);
assert!(halves(&mut func, &SYSV), "there is a width to split");
let text = printed(&func, &mut names);
assert!(!text.contains("i128"), "nothing that wide is left: {text}");
assert_eq!(text.matches(" = ashr ").count(), 2, "the count and the sign: {text}");
assert_eq!(text.matches(" = lshr ").count(), 1, "the low half is not signed: {text}");
assert_eq!(text.matches(" = select.i64 ").count(), 2, "one choice per half: {text}");
}
#[test]
fn a_parameter_with_one_register_left_leaves_the_function_alone() {
let mut names = Interner::new();
let word = Type::int(HALF);
let params = [word, word, word, word, word, wide()];
let (mut func, entry, values) = shell(&mut names, ¶ms, &[word]);
let mut build = Builder::new(&mut func, entry);
let low = build.unary(Opcode::Trunc, values[5], word);
build.ret(&[low]);
let before = printed(&func, &mut names);
assert!(!halves(&mut func, &SYSV), "one of the halves has no register");
assert_eq!(printed(&func, &mut names), before, "so nothing moved");
}
#[test]
fn a_block_made_after_the_one_it_runs_before_is_still_split() {
let mut names = Interner::new();
let (mut func, entry, params) = shell(&mut names, &[wide()], &[wide()]);
let tail = func.create_block();
let middle = func.create_block();
let mut build = Builder::new(&mut func, entry);
build.jump(middle, &[]);
let mut build = Builder::new(&mut func, middle);
let doubled = build.binary(Opcode::Add, params[0], params[0], Flags::NONE);
build.jump(tail, &[]);
let mut build = Builder::new(&mut func, tail);
let again = build.binary(Opcode::Add, doubled, doubled, Flags::NONE);
build.ret(&[again]);
assert!(
halves(&mut func, &SYSV),
"the definition runs before the use whatever the list says"
);
let text = printed(&func, &mut names);
assert!(!text.contains("i128"), "nothing that wide is left: {text}");
}
#[test]
fn a_function_with_nothing_that_wide_is_not_touched() {
let mut names = Interner::new();
let word = Type::int(HALF);
let (mut func, entry, params) = shell(&mut names, &[word, word], &[word]);
let mut build = Builder::new(&mut func, entry);
let sum = build.binary(Opcode::Add, params[0], params[1], Flags::NONE);
build.ret(&[sum]);
assert!(!halves(&mut func, &SYSV), "there is nothing to split");
}
}