use rucc_ir::{
Builder, Extra, Flags, Func, Imm, Inst, InstData, IntPred, MemInfo, MemOrder, Opcode, Type,
Value,
};
use rucc_target::CallRegs;
pub const GP_OFFSET: i64 = 0;
pub const FP_OFFSET: i64 = 4;
pub const OVERFLOW: i64 = 8;
pub const SAVE_AREA: i64 = 16;
pub const SIZE: u64 = 24;
pub const VECTOR_SLOT: u32 = 16;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Area {
pub floats_at: u32,
pub size: u32,
counts: (u32, u32),
word: u32,
}
impl Area {
#[must_use]
pub fn of(conv: &CallRegs) -> Self {
let ints = u32::try_from(conv.int_args.len()).unwrap_or(0);
let floats = u32::try_from(conv.sse_args.len()).unwrap_or(0);
let floats_at = conv.word * ints;
Self {
floats_at,
size: floats_at + VECTOR_SLOT * floats,
counts: (ints, floats),
word: conv.word,
}
}
#[must_use]
pub fn stride(self, float: bool) -> u32 {
if float { VECTOR_SLOT } else { self.word }
}
#[must_use]
pub fn starts_at(self, float: bool) -> u32 {
if float { self.floats_at } else { 0 }
}
#[must_use]
pub fn last(self, float: bool) -> Option<u32> {
let count = if float { self.counts.1 } else { self.counts.0 };
let last = count.checked_sub(1)?;
Some(self.starts_at(float) + self.stride(float) * last)
}
}
pub fn lists(func: &mut Func, conv: &CallRegs) {
if conv.shared_positions {
return;
}
let area = Area::of(conv);
let found: Vec<Inst> =
func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
for inst in found {
match func[inst].opcode {
Opcode::VaArg => next(func, inst, area),
Opcode::VaCopy => copy(func, inst),
Opcode::VaEnd => func.remove_inst(inst),
_ => {}
}
}
}
fn next(func: &mut Func, inst: Inst, area: Area) {
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 };
let span = func.span(inst);
if !ty.is_scalar() || ty.bits() > 64 || !(ty.is_int() || ty.is_float() || ty.is_ptr()) {
return;
}
let float = ty.is_float();
let Some(last) = area.last(float) else { return };
let field = if float { FP_OFFSET } else { GP_OFFSET };
let rest: Vec<Inst> = func.insts(block).skip_while(|&at| at != inst).skip(1).collect();
let taken = func.create_block();
let overflowed = func.create_block();
let join = func.create_block();
let addr = func.append_param(join, Type::PTR);
func.remove_inst(inst);
for &at in &rest {
func.remove_inst(at);
}
let mut build = Builder::new(func, block).at(span);
let counter = offset(&mut build, list, field);
let walked = build.load(Type::int(32), counter, info(4, 4), Flags::default());
let end = build.iconst(Type::int(32), i128::from(last));
let inside = build.icmp(IntPred::Ule, walked, end);
build.br_if(inside, taken, &[], overflowed, &[]);
let mut build = Builder::new(func, taken).at(span);
let base = offset(&mut build, list, SAVE_AREA);
let save = build.load(Type::PTR, base, info(8, 8), Flags::default());
let wide = build.unary(Opcode::ZExt, walked, Type::int(64));
let found = added(&mut build, save, wide);
let stride = build.iconst(Type::int(32), i128::from(area.stride(float)));
let stepped = build.binary(Opcode::Add, walked, stride, Flags::default());
let counter = offset(&mut build, list, field);
build.store(stepped, counter, info(4, 4), Flags::default());
build.jump(join, &[found]);
let mut build = Builder::new(func, overflowed).at(span);
let pointer = offset(&mut build, list, OVERFLOW);
let here = build.load(Type::PTR, pointer, info(8, 8), Flags::default());
let word = build.iconst(Type::int(64), i128::from(area.word));
let onward = added(&mut build, here, word);
build.store(onward, pointer, info(8, 8), Flags::default());
build.jump(join, &[here]);
let bytes = ty.bits() / 8;
let mem = func.add_mem(info(u64::from(bytes), bytes));
let args = func.push_values(&[addr]);
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);
}
}
fn copy(func: &mut Func, inst: Inst) {
let [into, from] = func[func[inst].args] else { return };
let mut moved = Vec::new();
for word in 0..SIZE / 8 {
let step = i64::try_from(word * 8).unwrap_or(0);
let there = field(func, inst, from, step);
let mem = func.add_mem(info(8, 8));
let args = func.push_values(&[there]);
let data = InstData { args, extra: Extra::Mem(mem), ..InstData::new(Opcode::Load) };
moved.push((ahead(func, inst, data, Type::int(64)), step));
}
for (read, step) in moved {
let here = field(func, inst, into, step);
let mem = func.add_mem(info(8, 8));
let args = func.push_values(&[read, here]);
let data = InstData { args, extra: Extra::Mem(mem), ..InstData::new(Opcode::Store) };
let span = func.span(inst);
let made = func.create_inst(data, &[], span);
func.insert_before(made, inst);
}
func.remove_inst(inst);
}
fn field(func: &mut Func, inst: Inst, list: Value, at: i64) -> Value {
if at == 0 {
return list;
}
let extra = Extra::Imm(func.add_imm(Imm::int(i128::from(at), Type::int(64))));
let step =
ahead(func, inst, InstData { extra, ..InstData::new(Opcode::IConst) }, Type::int(64));
let args = func.push_values(&[list, step]);
ahead(func, inst, InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR)
}
fn ahead(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 offset(build: &mut Builder<'_>, list: Value, at: i64) -> Value {
if at == 0 {
return list;
}
let step = build.iconst(Type::int(64), i128::from(at));
added(build, list, step)
}
fn added(build: &mut Builder<'_>, pointer: Value, by: Value) -> Value {
let args = build.func().push_values(&[pointer, by]);
build.value(InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR)
}
fn info(size: u64, align: u32) -> MemInfo {
MemInfo { size, align, order: MemOrder::NotAtomic, tbaa: None }
}
#[cfg(test)]
mod tests {
use rucc_base::Interner;
use rucc_ir::{Builder, Func, InstData, Module, Opcode, Signature, Type};
use rucc_target::x86_64::{SYSV, WIN64};
use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
use super::{Area, FP_OFFSET, GP_OFFSET, OVERFLOW, SAVE_AREA, SIZE, VECTOR_SLOT, lists};
fn target() -> TargetInfo {
TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu))
}
fn built(opcode: Opcode, ty: Type, lists: usize) -> (Interner, Func) {
let mut names = Interner::new();
let params = vec![Type::PTR; lists];
let mut signature = Signature::new().with_params(¶ms);
if !ty.is_void() {
signature = signature.with_returns(&[ty]);
}
let mut func = Func::new(names.intern("f"), signature);
let entry = func.create_block();
let args: Vec<_> = params.iter().map(|&ty| func.append_param(entry, ty)).collect();
let mut build = Builder::new(&mut func, entry);
let list = build.func().push_values(&args);
if ty.is_void() {
build.inst(InstData { args: list, ..InstData::new(opcode) }, &[]);
build.ret(&[]);
} else {
let got = build.value(InstData { args: list, ..InstData::new(opcode) }, ty);
build.ret(&[got]);
}
(names, func)
}
fn printed(func: &Func, names: &mut Interner) -> String {
let module = Module::new(names.intern("va.c"), &target());
rucc_ir::print_func(&module, func, names)
}
fn valid(func: &Func, names: &mut Interner) {
let module = Module::new(names.intern("va.c"), &target());
rucc_ir::verify_func(&module, func, names).expect("the rewrite builds valid IR");
}
#[test]
fn the_save_area_is_the_one_the_document_describes() {
let area = Area::of(&SYSV);
assert_eq!(area.floats_at, 48, "six general purpose registers of eight bytes");
assert_eq!(area.size, 176, "and eight vector ones of sixteen");
assert_eq!(area.stride(false), 8);
assert_eq!(area.stride(true), VECTOR_SLOT);
assert_eq!(area.starts_at(false), 0);
assert_eq!(area.starts_at(true), 48);
assert_eq!(area.last(false), Some(40));
assert_eq!(area.last(true), Some(160));
}
#[test]
fn a_list_is_the_four_fields_the_document_describes() {
assert_eq!((GP_OFFSET, FP_OFFSET, OVERFLOW, SAVE_AREA), (0, 4, 8, 16));
assert_eq!(SIZE, 24);
}
#[test]
fn a_va_arg_becomes_the_branch_on_whether_the_argument_is_still_in_the_save_area() {
let (mut names, mut func) = built(Opcode::VaArg, Type::int(32), 1);
let before = func.blocks().count();
lists(&mut func, &SYSV);
assert_eq!(func.blocks().count(), before + 3, "one for each path and one they meet at");
let text = printed(&func, &mut names);
assert!(!text.contains("va_arg"), "the va_arg is gone: {text}");
assert!(text.contains("icmp ule"), "the threshold is a comparison: {text}");
assert!(text.contains("br_if"), "and it is branched on: {text}");
valid(&func, &mut names);
}
#[test]
fn which_half_of_the_area_is_walked_is_the_type_s_answer() {
for (ty, last, stride) in
[(Type::int(64), 40, 8), (Type::float(rucc_ir::Float::F64), 160, 16)]
{
let (mut names, mut func) = built(Opcode::VaArg, ty, 1);
lists(&mut func, &SYSV);
let text = printed(&func, &mut names);
assert!(text.contains(&format!("iconst.i32 {last}")), "{ty:?} stops at {last}: {text}");
assert!(text.contains(&format!("iconst.i32 {stride}")), "and steps by it: {text}");
}
}
#[test]
fn what_reads_the_argument_reads_the_same_value_it_did_before() {
let (mut names, mut func) = built(Opcode::VaArg, Type::int(32), 1);
let entry = func.entry().expect("an entry block");
let inst = func.insts(entry).next().expect("the va_arg is first");
let read = func[inst].first_result.expect("it produces the argument");
lists(&mut func, &SYSV);
assert_eq!(func[inst].opcode, Opcode::Load, "the same instruction, lowered");
assert_eq!(func[inst].first_result, Some(read), "producing the same value");
assert_ne!(func.block_of(inst), Some(entry), "in the block the two paths meet at");
valid(&func, &mut names);
}
#[test]
fn a_va_end_is_nothing_at_all() {
let (mut names, mut func) = built(Opcode::VaEnd, Type::VOID, 1);
lists(&mut func, &SYSV);
let text = printed(&func, &mut names);
assert!(!text.contains("va_end"), "{text}");
assert_eq!(func.blocks().count(), 1, "and needs no block: {text}");
valid(&func, &mut names);
}
#[test]
fn a_va_copy_is_the_list_moved_a_word_at_a_time() {
let (mut names, mut func) = built(Opcode::VaCopy, Type::VOID, 2);
lists(&mut func, &SYSV);
let text = printed(&func, &mut names);
assert!(!text.contains("va_copy"), "{text}");
assert_eq!(text.matches("load.i64").count(), 3, "{text}");
assert_eq!(text.matches("store").count(), 3, "{text}");
assert_eq!(func.blocks().count(), 1, "and needs no block: {text}");
valid(&func, &mut names);
}
#[test]
fn a_list_copied_onto_itself_moves_what_it_held() {
let (mut names, mut func) = built(Opcode::VaCopy, Type::VOID, 1);
let entry = func.entry().expect("an entry block");
let inst = func.insts(entry).next().expect("the copy is first");
let list = func[func[inst].args][0];
let args = func.push_values(&[list, list]);
func[inst].args = args;
lists(&mut func, &SYSV);
let text = printed(&func, &mut names);
let first = text.find("store").expect("a write");
let last = text.rfind("load.i64").expect("a read");
assert!(last < first, "every read is above every write: {text}");
valid(&func, &mut names);
}
#[test]
fn a_convention_whose_list_is_not_this_one_is_left_alone() {
let (mut names, mut func) = built(Opcode::VaArg, Type::int(32), 1);
let before = printed(&func, &mut names);
lists(&mut func, &WIN64);
assert_eq!(printed(&func, &mut names), before);
}
#[test]
fn a_type_that_does_not_travel_in_one_slot_is_left_alone() {
for ty in [Type::float(rucc_ir::Float::F80), Type::int(128)] {
let (mut names, mut func) = built(Opcode::VaArg, ty, 1);
let before = printed(&func, &mut names);
lists(&mut func, &SYSV);
assert_eq!(printed(&func, &mut names), before, "{ty:?}");
}
}
#[test]
fn a_function_with_no_list_in_it_is_left_exactly_as_it_was() {
let mut names = Interner::new();
let int = Type::int(32);
let mut func =
Func::new(names.intern("f"), Signature::new().with_params(&[int]).with_returns(&[int]));
let entry = func.create_block();
let x = func.append_param(entry, int);
Builder::new(&mut func, entry).ret(&[x]);
let before = printed(&func, &mut names);
lists(&mut func, &SYSV);
assert_eq!(printed(&func, &mut names), before);
}
}