use rucc_ir::{
Block, Builder, Extra, Flags, Func, Imm, Inst, InstData, IntPred, MemInfo, MemOrder, Opcode,
Type, Value,
};
use rucc_target::{CallRegs, Slot};
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 ends_at(self, float: bool) -> u32 {
if float { self.size } else { self.floats_at }
}
#[must_use]
fn holds(self, float: bool) -> u32 {
if float { self.counts.1 } else { self.counts.0 }
}
#[must_use]
pub fn last(self, float: bool) -> Option<u32> {
let last = self.holds(float).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::VaObject => object(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 object(func: &mut Func, inst: Inst, area: Area) {
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() || !fits(&slots, area) {
return;
}
let span = func.span(inst);
let reach = slots.iter().map(|&slot| slot.offset() + width(slot)).max().unwrap_or(0);
let room = buffer(func, inst, reach.max(size), align);
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);
}
let (ends, address) = match room {
Some(room) if !slots.is_empty() => {
registers(func, block, inst, Read { list, area, slots: &slots, size, align, room })
}
_ => {
let mut build = Builder::new(func, block).at(span);
(block, overflow(&mut build, list, area, size, align))
}
};
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(ends, inst);
for at in rest {
func.append_inst(ends, at);
}
}
#[derive(Clone, Copy)]
struct Read<'a> {
list: Value,
area: Area,
slots: &'a [Slot],
size: u64,
align: u32,
room: Value,
}
fn fits(slots: &[Slot], area: Area) -> bool {
let mut counts = [0, 0];
for &slot in slots {
if width(slot) > u64::from(area.word) {
return false;
}
counts[usize::from(is_float(slot))] += 1;
}
counts[0] <= area.holds(false) && counts[1] <= area.holds(true)
}
fn registers(func: &mut Func, block: Block, inst: Inst, read: Read<'_>) -> (Block, Value) {
let span = func.span(inst);
let area = read.area;
let counts = [taken_of(read.slots, false), taken_of(read.slots, true)];
let saved = func.create_block();
let overflowed = func.create_block();
let join = func.create_block();
let address = func.append_param(join, Type::int(64));
let asked: Vec<bool> =
[false, true].into_iter().filter(|&float| counts[usize::from(float)] > 0).collect();
let mut at = block;
for (index, &float) in asked.iter().enumerate() {
let next = if index + 1 == asked.len() { saved } else { func.create_block() };
let room =
area.ends_at(float).saturating_sub(area.stride(float) * counts[usize::from(float)]);
let mut build = Builder::new(func, at).at(span);
let counter = offset(&mut build, read.list, field_of(float));
let walked = build.load(Type::int(32), counter, info(4, 4), Flags::default());
let end = build.iconst(Type::int(32), i128::from(room));
let inside = build.icmp(IntPred::Ule, walked, end);
build.br_if(inside, next, &[], overflowed, &[]);
at = next;
}
let mut build = Builder::new(func, saved).at(span);
let found = copied(&mut build, read, counts);
build.jump(join, &[found]);
let mut build = Builder::new(func, overflowed).at(span);
let here = overflow(&mut build, read.list, area, read.size, read.align);
build.jump(join, &[here]);
(join, address)
}
fn copied(build: &mut Builder<'_>, read: Read<'_>, counts: [u32; 2]) -> Value {
let area = read.area;
let base = offset(build, read.list, SAVE_AREA);
let save = build.load(Type::PTR, base, info(8, 8), Flags::default());
let mut walked = [None, None];
let mut nexts = [None, None];
for float in [false, true] {
let file = usize::from(float);
if counts[file] == 0 {
continue;
}
let counter = offset(build, read.list, field_of(float));
let read = build.load(Type::int(32), counter, info(4, 4), Flags::default());
let wide = build.unary(Opcode::ZExt, read, Type::int(64));
walked[file] = Some(read);
nexts[file] = Some(added(build, save, wide));
}
let mut seen = [0, 0];
for &slot in read.slots {
let float = is_float(slot);
let file = usize::from(float);
let Some(from) = nexts[file] else { continue };
let step = i64::from(area.stride(float) * seen[file]);
seen[file] += 1;
let bytes = width(slot);
let ty = Type::int(u32::try_from(bytes).unwrap_or(1) * 8);
let at = offset(build, from, step);
let value =
build.load(ty, at, info(bytes, area.stride(float).min(area.word)), Flags::default());
let into = offset(build, read.room, i64::try_from(slot.offset()).unwrap_or(0));
let holds = info(bytes, part(read.align, slot.offset()));
build.store(value, into, holds, Flags::default());
}
for float in [false, true] {
let file = usize::from(float);
let Some(counter) = walked[file] else { continue };
let by = build.iconst(Type::int(32), i128::from(area.stride(float) * counts[file]));
let stepped = build.binary(Opcode::Add, counter, by, Flags::default());
let at = offset(build, read.list, field_of(float));
build.store(stepped, at, info(4, 4), Flags::default());
}
build.unary(Opcode::PtrToInt, read.room, Type::int(64))
}
fn buffer(func: &mut Func, inst: Inst, size: u64, align: u32) -> Option<Value> {
let entry = func.entry()?;
let span = func.span(inst);
let mem = func.add_mem(info(size, align.max(1)));
let data = InstData { extra: Extra::Mem(mem), ..InstData::new(Opcode::Alloca) };
let made = func.create_inst(data, &[Type::PTR], span);
let first = func.insts(entry).next();
match first {
Some(first) => func.insert_before(made, first),
None => func.append_inst(entry, made),
}
func[made].first_result
}
fn overflow(build: &mut Builder<'_>, list: Value, area: Area, size: u64, align: u32) -> Value {
let word = u64::from(area.word);
let wide = Type::int(64);
let pointer = offset(build, list, OVERFLOW);
let here = build.load(Type::PTR, pointer, info(word, area.word), Flags::default());
let mut at = build.unary(Opcode::PtrToInt, here, wide);
if u64::from(align) > word {
let bump = build.iconst(wide, i128::from(align) - 1);
at = build.binary(Opcode::Add, at, bump, Flags::default());
let mask = build.iconst(wide, -i128::from(align));
at = build.binary(Opcode::And, at, mask, Flags::default());
}
let by = build.iconst(wide, i128::from(size.next_multiple_of(word)));
let onward = build.binary(Opcode::Add, at, by, Flags::default());
let onward = build.unary(Opcode::IntToPtr, onward, Type::PTR);
build.store(onward, pointer, info(word, area.word), Flags::default());
at
}
fn field_of(float: bool) -> i64 {
if float { FP_OFFSET } else { GP_OFFSET }
}
fn is_float(slot: Slot) -> bool {
matches!(slot, Slot::Float { .. })
}
fn taken_of(slots: &[Slot], float: bool) -> u32 {
u32::try_from(slots.iter().filter(|&&slot| is_float(slot) == float).count()).unwrap_or(0)
}
fn width(slot: Slot) -> u64 {
match slot {
Slot::Integer { size, .. } => u64::from(size.next_power_of_two().clamp(1, 8)),
Slot::Float { format, .. } => u64::from(format.width()).div_ceil(8),
}
}
fn part(align: u32, offset: u64) -> u32 {
let align = align.max(1);
if offset == 0 {
return align;
}
u32::try_from(1_u64 << offset.trailing_zeros()).unwrap_or(align).min(align)
}
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_base::float::Format;
use rucc_ir::{Builder, Extra, Func, InstData, Module, Opcode, Signature, Type, VaInfo};
use rucc_target::x86_64::{SYSV, WIN64};
use rucc_target::{Arch, Env, Os, Slot, 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);
}
fn object(size: u64, align: u32, slots: &[Slot]) -> (Interner, Func) {
let mut names = Interner::new();
let signature = Signature::new().with_params(&[Type::PTR]).with_returns(&[Type::PTR]);
let mut func = Func::new(names.intern("f"), signature);
let entry = func.create_block();
let list = func.append_param(entry, Type::PTR);
let mem = func.add_mem(super::info(size, align));
let slots = func.push_slots(slots);
let at = func.add_va_object(VaInfo { mem, slots });
let mut build = Builder::new(&mut func, entry);
let args = build.func().push_values(&[list]);
let data = InstData { args, extra: Extra::VaObject(at), ..InstData::new(Opcode::VaObject) };
let got = build.value(data, Type::PTR);
build.ret(&[got]);
(names, func)
}
fn gpr(offset: u64, size: u32) -> Slot {
Slot::Integer { offset, size }
}
fn sse(offset: u64) -> Slot {
Slot::Float { offset, format: Format::Double }
}
#[test]
fn an_object_too_big_for_the_registers_is_read_out_of_the_caller_s_memory() {
let (mut names, mut func) = object(24, 8, &[]);
lists(&mut func, &SYSV);
let text = printed(&func, &mut names);
assert!(!text.contains("va_object"), "{text}");
assert_eq!(func.blocks().count(), 1, "no branch, so no new block: {text}");
assert!(text.contains("iconst.i64 8"), "the overflow field is at eight: {text}");
assert!(text.contains("iconst.i64 24"), "and the pointer steps past the object: {text}");
assert!(!text.contains("gp_offset"), "{text}");
valid(&func, &mut names);
}
#[test]
fn a_size_that_is_not_a_whole_number_of_words_steps_on_by_the_next_one() {
let (mut names, mut func) = object(28, 4, &[]);
lists(&mut func, &SYSV);
let text = printed(&func, &mut names);
assert!(text.contains("iconst.i64 32"), "twenty eight bytes step on by thirty two: {text}");
valid(&func, &mut names);
}
#[test]
fn an_object_wanting_more_alignment_than_a_word_is_rounded_up_to_it() {
let (mut names, mut func) = object(32, 16, &[]);
lists(&mut func, &SYSV);
let text = printed(&func, &mut names);
assert!(text.contains("iconst.i64 15"), "up to the next sixteen: {text}");
assert!(text.contains("iconst.i64 -16"), "and down to a multiple of it: {text}");
assert!(text.contains(" = and "), "which is an add and a mask: {text}");
valid(&func, &mut names);
let (mut names, mut func) = object(24, 8, &[]);
lists(&mut func, &SYSV);
assert!(!printed(&func, &mut names).contains(" = and "), "a word wants no rounding");
}
#[test]
fn an_object_that_arrived_in_registers_is_copied_out_of_the_save_area() {
let (mut names, mut func) = object(16, 8, &[gpr(0, 8), gpr(8, 8)]);
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_object"), "{text}");
assert!(text.contains("iconst.i32 32"), "forty eight less two slots: {text}");
assert!(text.contains("icmp ule"), "which is the threshold: {text}");
assert!(text.contains("alloca, size 16"), "the object lands in a buffer: {text}");
assert!(text.contains("iconst.i32 16"), "and the counter steps by both slots: {text}");
valid(&func, &mut names);
}
#[test]
fn an_object_in_both_files_asks_about_both_of_them() {
let (mut names, mut func) = object(16, 8, &[gpr(0, 8), sse(8)]);
lists(&mut func, &SYSV);
let text = printed(&func, &mut names);
assert_eq!(text.matches("br_if").count(), 2, "one question per file: {text}");
assert!(text.contains("iconst.i32 40"), "forty eight less one slot: {text}");
assert!(text.contains("iconst.i32 160"), "and a hundred and seventy six less one: {text}");
assert!(text.contains("iconst.i32 8"), "each counter steps by its own slot: {text}");
valid(&func, &mut names);
}
#[test]
fn the_buffer_is_as_big_as_the_registers_reach() {
let (mut names, mut func) = object(5, 1, &[gpr(0, 5)]);
lists(&mut func, &SYSV);
let text = printed(&func, &mut names);
assert!(text.contains("alloca, size 8"), "five bytes travel in a whole register: {text}");
valid(&func, &mut names);
}
#[test]
fn a_classification_that_does_not_fit_the_area_is_left_alone() {
let wide = [Slot::Float { offset: 0, format: Format::Quad }];
let (mut names, mut func) = object(16, 16, &wide);
let before = printed(&func, &mut names);
lists(&mut func, &SYSV);
assert_eq!(printed(&func, &mut names), before);
}
#[test]
fn what_reads_the_object_reads_the_same_value_it_did_before() {
for slots in [&[][..], &[gpr(0, 8), gpr(8, 8)][..]] {
let (mut names, mut func) = object(if slots.is_empty() { 24 } else { 16 }, 8, slots);
let entry = func.entry().expect("an entry block");
let inst = func.insts(entry).next().expect("the va_object is first");
let read = func[inst].first_result.expect("it answers an address");
lists(&mut func, &SYSV);
assert_eq!(func[inst].opcode, Opcode::IntToPtr, "the same instruction, lowered");
assert_eq!(func[inst].first_result, Some(read), "producing the same value");
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);
}
}