use rucc_ir::{
Block, Builder, Extra, Flags, Float, Func, Inst, IntPred, MemInfo, Opcode, Type, Value,
};
use rucc_target::Slot;
use super::{added, buffer, info, is_float, offset, width};
pub const STACK: i64 = 0;
pub const GR_TOP: i64 = 8;
pub const VR_TOP: i64 = 16;
pub const GR_OFFS: i64 = 24;
pub const VR_OFFS: i64 = 28;
pub const SIZE: u64 = 32;
const WORD: u32 = 8;
const VECTOR: u32 = 16;
const IN_REGISTERS: u64 = 16;
#[derive(Clone, Copy)]
struct Wants {
float: bool,
slots: u32,
even: bool,
size: u64,
wide: bool,
}
pub(super) fn next(func: &mut Func, inst: Inst) {
let Some(result) = func[inst].first_result else { return };
let Some(&list) = func[func[inst].args].first() else { return };
let ty = func[result].ty;
let Some(block) = func.block_of(inst) else { return };
if !ty.is_scalar() || !(ty.is_int() || ty.is_float() || ty.is_ptr()) || ty.bits() > 128 {
return;
}
if ty.is_int() && ty.bits() > 64 {
return;
}
let bytes = u64::from(ty.bits().div_ceil(8)).max(1);
let float = ty.is_float();
let wants = Wants { float, slots: 1, even: false, size: bytes, wide: bytes > 8 };
let rest = cut(func, block, inst);
let (join, address) = walk(func, block, inst, list, wants, |_, found| found);
let span = func.span(inst);
let mut build = Builder::new(func, join).at(span);
let from = build.unary(Opcode::IntToPtr, address, Type::PTR);
let align = u32::try_from(bytes.next_power_of_two()).unwrap_or(1);
let mem = func.add_mem(info(bytes, align));
let args = func.push_values(&[from]);
let data = &mut func[inst];
data.opcode = Opcode::Load;
data.args = args;
data.extra = Extra::Mem(mem);
data.flags = data.flags.intersection(Flags::legal_on(Opcode::Load));
func.append_inst(join, inst);
for at in rest {
func.append_inst(join, at);
}
}
pub(super) fn object(func: &mut Func, inst: Inst) {
let Extra::VaObject(at) = func[inst].extra else { return };
let object = func[at];
let MemInfo { size, align, .. } = func[object.mem];
let slots: Vec<Slot> = func[object.slots].to_vec();
let Some(&list) = func[func[inst].args].first() else { return };
let Some(block) = func.block_of(inst) else { return };
if func[inst].first_result.is_none() {
return;
}
let floats = slots.iter().filter(|&&slot| is_float(slot)).count();
let by_reference = slots.is_empty() && size > IN_REGISTERS;
let wants = if by_reference {
Wants { float: false, slots: 1, even: false, size: u64::from(WORD), wide: false }
} else if floats == 0 {
let slots = u32::try_from(size.div_ceil(u64::from(WORD))).unwrap_or(0);
if slots == 0 || size > IN_REGISTERS {
return;
}
Wants { float: false, slots, even: align >= 16, size, wide: align >= 16 }
} else if floats == slots.len() && floats <= 4 {
let wide = slots.iter().any(|&slot| width(slot) > u64::from(WORD));
let slots = u32::try_from(floats).unwrap_or(0);
Wants { float: true, slots, even: false, size, wide }
} else {
return;
};
let room = if wants.float && wants.slots > 1 {
let Some(room) = buffer(func, inst, size, align.max(WORD)) else { return };
Some(room)
} else {
None
};
let rest = cut(func, block, inst);
let copy = |build: &mut Builder<'_>, found: Value| match room {
Some(room) => copied(build, found, &slots, room, align.max(WORD)),
None => found,
};
let (join, mut address) = walk(func, block, inst, list, wants, copy);
if by_reference {
let span = func.span(inst);
let mut build = Builder::new(func, join).at(span);
let slot = build.unary(Opcode::IntToPtr, address, Type::PTR);
let held = build.load(Type::PTR, slot, info(8, 8), Flags::default());
address = build.unary(Opcode::PtrToInt, held, Type::int(64));
}
let args = func.push_values(&[address]);
let data = &mut func[inst];
data.opcode = Opcode::IntToPtr;
data.args = args;
data.extra = Extra::None;
data.flags = data.flags.intersection(Flags::legal_on(Opcode::IntToPtr));
func.append_inst(join, inst);
for at in rest {
func.append_inst(join, at);
}
}
pub(super) fn stacked(func: &mut Func, inst: Inst) {
let Extra::VaObject(at) = func[inst].extra else { return };
let object = func[at];
let MemInfo { size, align, .. } = func[object.mem];
let by_reference = func[object.slots].is_empty() && size > IN_REGISTERS;
let Some(&list) = func[func[inst].args].first() else { return };
let Some(block) = func.block_of(inst) else { return };
if func[inst].first_result.is_none() {
return;
}
let wants = if by_reference {
Wants { float: false, slots: 1, even: false, size: u64::from(WORD), wide: false }
} else {
Wants { float: false, slots: 1, even: false, size, wide: align >= 16 }
};
let rest = cut(func, block, inst);
let span = func.span(inst);
let mut build = Builder::new(func, block).at(span);
let mut address = in_memory(&mut build, list, wants);
if by_reference {
let slot = build.unary(Opcode::IntToPtr, address, Type::PTR);
let held = build.load(Type::PTR, slot, info(8, 8), Flags::default());
address = build.unary(Opcode::PtrToInt, held, Type::int(64));
}
let args = func.push_values(&[address]);
let data = &mut func[inst];
data.opcode = Opcode::IntToPtr;
data.args = args;
data.extra = Extra::None;
data.flags = data.flags.intersection(Flags::legal_on(Opcode::IntToPtr));
func.append_inst(block, inst);
for at in rest {
func.append_inst(block, at);
}
}
fn cut(func: &mut Func, block: Block, inst: Inst) -> Vec<Inst> {
let rest: Vec<Inst> = func.insts(block).skip_while(|&at| at != inst).skip(1).collect();
func.remove_inst(inst);
for &at in &rest {
func.remove_inst(at);
}
rest
}
fn walk(
func: &mut Func,
block: Block,
inst: Inst,
list: Value,
wants: Wants,
found: impl FnOnce(&mut Builder<'_>, Value) -> Value,
) -> (Block, Value) {
let span = func.span(inst);
let wide = Type::int(64);
let word = Type::int(32);
let (field, top, stride) =
if wants.float { (VR_OFFS, VR_TOP, VECTOR) } else { (GR_OFFS, GR_TOP, WORD) };
let fits = func.create_block();
let saved = func.create_block();
let stack = func.create_block();
let join = func.create_block();
let address = func.append_param(join, wide);
let mut build = Builder::new(func, block).at(span);
let counter = offset(&mut build, list, field);
let mut walked = build.load(word, counter, info(4, 4), Flags::default());
let zero = build.iconst(word, 0);
let out = build.icmp(IntPred::Sge, walked, zero);
build.br_if(out, stack, &[], fits, &[]);
let mut build = Builder::new(func, fits).at(span);
if wants.even {
let bump = build.iconst(word, 15);
walked = build.binary(Opcode::Add, walked, bump, Flags::default());
let mask = build.iconst(word, -16);
walked = build.binary(Opcode::And, walked, mask, Flags::default());
}
let by = build.iconst(word, i128::from(stride * wants.slots));
let stepped = build.binary(Opcode::Add, walked, by, Flags::default());
let counter = offset(&mut build, list, field);
build.store(stepped, counter, info(4, 4), Flags::default());
let zero = build.iconst(word, 0);
let past = build.icmp(IntPred::Sgt, stepped, zero);
build.br_if(past, stack, &[], saved, &[]);
let mut build = Builder::new(func, saved).at(span);
let at = offset(&mut build, list, top);
let top = build.load(Type::PTR, at, info(8, 8), Flags::default());
let back = build.unary(Opcode::SExt, walked, wide);
let here = added(&mut build, top, back);
let here = build.unary(Opcode::PtrToInt, here, wide);
let here = found(&mut build, here);
build.jump(join, &[here]);
let mut build = Builder::new(func, stack).at(span);
let there = in_memory(&mut build, list, wants);
build.jump(join, &[there]);
(join, address)
}
fn in_memory(build: &mut Builder<'_>, list: Value, wants: Wants) -> Value {
let wide = Type::int(64);
let pointer = offset(build, list, STACK);
let there = build.load(Type::PTR, pointer, info(8, 8), Flags::default());
let mut there = build.unary(Opcode::PtrToInt, there, wide);
if wants.wide {
let bump = build.iconst(wide, 15);
there = build.binary(Opcode::Add, there, bump, Flags::default());
let mask = build.iconst(wide, -16);
there = build.binary(Opcode::And, there, mask, Flags::default());
}
let by = build.iconst(wide, i128::from(wants.size.next_multiple_of(u64::from(WORD))));
let onward = build.binary(Opcode::Add, there, by, Flags::default());
let onward = build.unary(Opcode::IntToPtr, onward, Type::PTR);
build.store(onward, pointer, info(8, 8), Flags::default());
there
}
fn copied(build: &mut Builder<'_>, from: Value, slots: &[Slot], room: Value, align: u32) -> Value {
let from = build.unary(Opcode::IntToPtr, from, Type::PTR);
for (index, &slot) in slots.iter().enumerate() {
let bytes = width(slot);
let ty = if bytes > u64::from(WORD) {
Type::float(Float::F128)
} else {
Type::int(u32::try_from(bytes).unwrap_or(1) * 8)
};
let step = i64::from(VECTOR) * i64::try_from(index).unwrap_or(0);
let at = offset(build, from, step);
let aligned = u32::try_from(bytes).unwrap_or(1);
let value = build.load(ty, at, info(bytes, aligned), Flags::default());
let into = offset(build, room, i64::try_from(slot.offset()).unwrap_or(0));
let holds = info(bytes, super::part(align, slot.offset()));
build.store(value, into, holds, Flags::default());
}
build.unary(Opcode::PtrToInt, room, Type::int(64))
}