use rucc_ir::{
Def, Extra, Func, FuncId, Imm, Inst, InstData, Module, Opcode, Pic, SymbolRef, Value,
};
use crate::Cfg;
use crate::extents::vouched;
const DEPTH: u32 = 16;
pub fn answer(module: &mut Module, pic: Pic, look: bool) -> usize {
let mut answered = 0;
for id in module.funcs().collect::<Vec<FuncId>>() {
if module[id].is_declaration() {
continue;
}
let asked = questions(&module[id]);
if asked.is_empty() {
continue;
}
let answers: Vec<(Inst, i128)> = {
let func = &module[id];
let walk = Walk { module, func, cfg: &Cfg::new(func), pic };
asked
.iter()
.map(|&inst| {
let Extra::Question(kind) = func[inst].extra else { return (inst, 0) };
let address = func[func[inst].args][0];
let largest = kind & 2 == 0;
let known = match (look, kind) {
(false, _) | (_, 3) => None,
_ => walk.left(address, largest, DEPTH, &mut Vec::new()).ok().flatten(),
};
(inst, known.map_or(if largest { -1 } else { 0 }, i128::from))
})
.collect()
};
let func = &mut module[id];
for (inst, number) in answers {
write(func, inst, number);
answered += 1;
}
}
answered
}
fn questions(func: &Func) -> Vec<Inst> {
func.blocks()
.flat_map(|block| func.insts(block))
.filter(|&inst| func[inst].opcode == Opcode::ObjectSize)
.collect()
}
fn write(func: &mut Func, inst: Inst, number: i128) {
let result = func[inst].results().next().expect("an object size is one value");
let ty = func[result].ty;
let span = func.span(inst);
let imm = func.add_imm(Imm::int(number, ty.lane()));
let data = InstData { extra: Extra::Imm(imm), ..InstData::new(Opcode::IConst) };
let made = func.create_inst(data, &[ty], span);
func.insert_before(made, inst);
let value = func[made].results().next().expect("a constant is one value");
let forward = [(result, value)].into_iter().collect();
crate::uses::substitute(func, &forward);
func.remove_inst(inst);
}
struct Walk<'a> {
module: &'a Module,
func: &'a Func,
cfg: &'a Cfg,
pic: Pic,
}
type Left = Result<Option<u64>, ()>;
impl Walk<'_> {
fn left(&self, value: Value, largest: bool, depth: u32, on: &mut Vec<Value>) -> Left {
let depth = depth.checked_sub(1).ok_or(())?;
match self.func[value].def {
Def::Param { block, index } => {
if on.contains(&value) {
return Ok(None);
}
let preds = self.cfg.predecessors(block);
if preds.is_empty() {
return Err(());
}
on.push(value);
let mut all = Ok(None);
for &pred in preds {
let term = self.func.terminator(pred).ok_or(())?;
for call in self.func.successors(term).collect::<Vec<_>>() {
if call.block != block {
continue;
}
let arg = *self.func[call.args].get(index as usize).ok_or(())?;
all = both(all, self.left(arg, largest, depth, on), largest);
}
}
on.pop();
all
}
Def::Result { inst, .. } => {
let data = &self.func[inst];
let args = &self.func[data.args];
match data.opcode {
Opcode::Select => {
let (then, other) = (*args.get(1).ok_or(())?, *args.get(2).ok_or(())?);
let then = self.left(then, largest, depth, on);
both(then, self.left(other, largest, depth, on), largest)
}
Opcode::PtrAdd => {
let base = *args.first().ok_or(())?;
let (imm, ty) =
crate::fold::evaluated(self.func, *args.get(1).ok_or(())?, 4)
.ok_or(())?;
let step = u64::try_from(imm.signed(ty)).map_err(|_| ())?;
match self.left(base, largest, depth, on)? {
Some(left) => Ok(Some(left.saturating_sub(step))),
None if !largest && step != 0 => Err(()),
None => Ok(None),
}
}
Opcode::Alloca => match args.first() {
None => {
let Extra::Mem(mem) = data.extra else { return Err(()) };
Ok(Some(self.func[mem].size))
}
Some(&count) => {
let (imm, _) = crate::fold::evaluated(self.func, count, 4).ok_or(())?;
Ok(Some(u64::try_from(imm.unsigned()).map_err(|_| ())?))
}
},
Opcode::GlobalAddr => {
let Extra::Symbol(name) = data.extra else { return Err(()) };
let Some(SymbolRef::Global(id)) = self.module.lookup(name) else {
return Err(());
};
let global = &self.module[id];
if !vouched(global, self.pic) {
return Err(());
}
Ok(Some(global.size))
}
_ => Err(()),
}
}
}
}
}
fn both(one: Left, other: Left, largest: bool) -> Left {
Ok(match (one?, other?) {
(Some(one), Some(other)) if largest => Some(one.max(other)),
(Some(one), Some(other)) => Some(one.min(other)),
(Some(one), None) | (None, Some(one)) => Some(one),
(None, None) => None,
})
}
#[cfg(test)]
mod tests {
use rucc_base::Interner;
use super::*;
const HEAD: &str = "\
; ModuleID = 't.c'
; format 0
target triple = \"x86_64-unknown-linux-gnu\"
target datalayout = \"e-p:64:64-i64:64-f80:128-S128\"
";
fn answers(body: &str, look: bool) -> Vec<i128> {
let mut names = Interner::new();
let text = format!("{HEAD}{body}");
let mut module = rucc_ir::parse(&text, &mut names).expect("the fixture parses");
answer(&mut module, Pic::Executable, look);
if let Err(errors) = rucc_ir::verify(&module, &names) {
panic!("the answers left invalid IR, {errors:?}\n{}", rucc_ir::print(&module, &names));
}
let mut found = Vec::new();
for id in module.funcs() {
let func = &module[id];
for block in func.blocks() {
for inst in func.insts(block) {
assert_ne!(func[inst].opcode, Opcode::ObjectSize, "a question was left");
if func[inst].opcode != Opcode::Call {
continue;
}
let &[value] = &func[func[inst].args] else { continue };
let Def::Result { inst: def, .. } = func[value].def else { continue };
let Extra::Imm(imm) = func[def].extra else { continue };
found.push(func[imm].signed(func[value].ty));
}
}
}
found
}
#[test]
fn a_choice_of_two_objects_is_the_larger_or_the_smaller_of_what_each_has_left() {
let body = "
global @g : bytes 32 = { zero 32 }, align 1, linkage(external)
func @f(i32), linkage(external) {
block0(%0: i32):
%1 = alloca, size 20, align 16
%2 = iconst.i32 0
%3 = icmp ne %0, %2
br_if %3, block1, block2
block1:
%4 = iconst.i32 5
%5 = sext.i64 %4
%6 = ptr_add %1, %5
jump block3(%6)
block2:
%7 = global_addr @g
%8 = iconst.i64 4
%9 = ptr_add %7, %8
jump block3(%9)
block3(%10: ptr):
%11 = object_size.i64 %10, kind 0
call @use(%11) : (i64)
%12 = object_size.i64 %10, kind 1
call @use(%12) : (i64)
%13 = object_size.i64 %10, kind 2
call @use(%13) : (i64)
%14 = object_size.i64 %10, kind 3
call @use(%14) : (i64)
return
}
";
assert_eq!(answers(body, true), [28, 28, 15, 0]);
assert_eq!(answers(body, false), [-1, -1, 0, 0]);
}
#[test]
fn a_pointer_a_loop_leaves_alone_is_what_it_was_given() {
let body = "
func @f(i32), linkage(external) {
block0(%0: i32):
%1 = alloca, size 20, align 16
%2 = iconst.i32 0
jump block1(%1, %2)
block1(%3: ptr, %4: i32):
%5 = icmp eq %4, %0
%6 = iconst.i64 7
%7 = ptr_add %1, %6
%8 = select.ptr %5, %7, %3
%9 = iconst.i32 1
%10 = add %4, %9
%11 = icmp slt %10, %0
br_if %11, block1(%8, %10), block2
block2:
%12 = object_size.i64 %8, kind 0
call @use(%12) : (i64)
%13 = object_size.i64 %8, kind 2
call @use(%13) : (i64)
return
}
";
assert_eq!(answers(body, true), [20, 13]);
}
#[test]
fn a_pointer_a_loop_moves_is_known_only_where_that_can_only_leave_less() {
let forward = "
func @f(i32), linkage(external) {
block0(%0: i32):
%1 = alloca, size 20, align 16
%2 = iconst.i32 0
jump block1(%1, %2)
block1(%3: ptr, %4: i32):
%5 = iconst.i64 STEP
%6 = ptr_add %3, %5
%7 = iconst.i32 1
%8 = add %4, %7
%9 = icmp slt %8, %0
br_if %9, block1(%6, %8), block2
block2:
%10 = object_size.i64 %6, kind 0
call @use(%10) : (i64)
%11 = object_size.i64 %6, kind 2
call @use(%11) : (i64)
return
}
";
assert_eq!(answers(&forward.replace("STEP", "1"), true), [19, 0]);
assert_eq!(answers(&forward.replace("STEP", "-1"), true), [-1, 0]);
}
#[test]
fn past_the_end_is_nothing_and_a_weak_global_is_not_known() {
let body = "
global @g : bytes 8 = { zero 8 }, align 1, linkage(external)
global @w : bytes 8 = { zero 8 }, align 1, linkage(weak)
func @f(), linkage(external) {
block0:
%0 = global_addr @g
%1 = iconst.i64 12
%2 = ptr_add %0, %1
%3 = object_size.i64 %2, kind 0
call @use(%3) : (i64)
%4 = global_addr @w
%5 = object_size.i64 %4, kind 0
call @use(%5) : (i64)
return
}
";
assert_eq!(answers(body, true), [0, -1]);
}
}