use rucc_base::Interner;
use rucc_ir::{
Builder, Def, Extra, Flags, Func, Global, InstData, MemInfo, MemOrder, Meta, MetaNode, Module,
Opcode, Restrict, Signature, TbaaNode, Type, Value,
};
use rucc_opt::{Access, Alias, Answer, Reason};
use rucc_target::{TargetInfo, Triple};
const OBJECT: u64 = 64;
const PARAMS: usize = 4;
const LOCALS: usize = 3;
const ACCESSES: usize = 12;
#[test]
fn a_no_is_only_ever_said_about_two_references_that_really_do_not_meet() {
let mut random = Random::new(0x5ce7_c0ff_ee00_0002);
let mut nos = 0;
let mut asked = 0;
for _ in 0..400 {
let case = Case::new(&mut random);
let (module, func, accesses) = case.build();
let mut alias = Alias::new(&func, &module);
for (i, (a, truth_a)) in accesses.iter().enumerate() {
for (b, truth_b) in accesses.iter().skip(i) {
let answer = alias.query(a, b);
asked += 1;
if answer.is_no() {
nos += 1;
assert!(
!truth_a.meets(truth_b),
"said {answer:?} about {truth_a:?} and {truth_b:?} in {case:?}"
);
}
assert_eq!(
alias.query(b, a).is_no(),
answer.is_no(),
"the answer changed when the two were swapped, about {truth_a:?} and \
{truth_b:?} in {case:?}"
);
}
}
let counts = alias.counts();
let total: u64 = Reason::ALL.iter().map(|&r| counts.answered(r)).sum();
assert_eq!(total, counts.total());
assert!(counts.queries() >= total);
let mut again = Alias::new(&func, &module);
for (a, _) in &accesses {
for (b, _) in &accesses {
assert_eq!(again.query(a, b), alias.query(a, b), "not repeatable in {case:?}");
}
}
}
assert!(nos * 4 > asked, "only {nos} of {asked} pairs were disambiguated, which is too few");
}
#[derive(Clone, Debug)]
struct Case {
points_at: [Object; PARAMS],
escapes: [bool; LOCALS],
accesses: Vec<Plan>,
}
#[derive(Clone, Copy, Debug)]
struct Plan {
base: Base,
offset: u64,
size: u64,
tagged: bool,
bytewise: bool,
writes: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Base {
Param(usize),
Local(usize),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Object {
Hidden(usize),
Local(usize),
}
impl Object {
fn ty(self) -> usize {
match self {
Object::Hidden(n) | Object::Local(n) => n % 3,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct Truth {
object: Object,
start: u64,
end: u64,
}
impl Truth {
fn meets(&self, other: &Self) -> bool {
self.object == other.object && self.start < other.end && other.start < self.end
}
}
impl Case {
fn new(random: &mut Random) -> Self {
let points_at = [Object::Hidden(0), Object::Hidden(1), Object::Hidden(2), Object::Local(0)];
let mut escapes = [false; LOCALS];
escapes[0] = true;
for slot in escapes.iter_mut().skip(1) {
*slot = random.below(4) == 0;
}
let accesses = (0..ACCESSES)
.map(|_| {
let base = if random.below(2) == 0 {
Base::Param(random.below(PARAMS as u64) as usize)
} else {
Base::Local(random.below(LOCALS as u64) as usize)
};
let bytewise = random.below(4) == 0;
let size = if bytewise { 1 } else { 1 << random.below(4) };
Plan {
base,
offset: random.below(OBJECT - size + 1),
size,
tagged: random.below(4) != 0,
bytewise,
writes: random.below(2) == 0,
}
})
.collect();
Self { points_at, escapes, accesses }
}
fn object(&self, plan: &Plan) -> Object {
match plan.base {
Base::Param(n) => self.points_at[n],
Base::Local(n) => Object::Local(n),
}
}
fn build(&self) -> (Module, Func, Vec<(Access, Truth)>) {
let mut names = Interner::new();
let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
let mut module = Module::new(names.intern("t.c"), &target);
let root = module.add_meta(MetaNode::Tbaa(TbaaNode {
name: names.intern("char"),
parent: None,
offset: 0,
}));
let types: [Meta; 3] = [
root,
module.add_meta(MetaNode::Tbaa(TbaaNode {
name: names.intern("int"),
parent: Some(root),
offset: 0,
})),
module.add_meta(MetaNode::Tbaa(TbaaNode {
name: names.intern("float"),
parent: Some(root),
offset: 0,
})),
];
module.add_global(Global::new(names.intern("keep"), 8, 8));
let params = [Type::PTR; PARAMS];
let mut func = Func::new(names.intern("f"), Signature::new().with_params(¶ms));
let entry = func.create_block();
for ty in params {
func.append_param(entry, ty);
}
let incoming: Vec<Value> = func[entry].params.to_vec();
let mut build = Builder::new(&mut func, entry);
let locals: Vec<Value> = (0..LOCALS)
.map(|_| {
let mem = build.func().add_mem(MemInfo {
size: OBJECT,
align: 16,
order: MemOrder::NotAtomic,
tbaa: None,
restrict: Restrict::NONE,
});
build.value(
InstData { extra: Extra::Mem(mem), ..InstData::new(Opcode::Alloca) },
Type::PTR,
)
})
.collect();
for (index, &local) in locals.iter().enumerate() {
if self.escapes[index] {
build.store(local, incoming[0], mem_of(8, None, Restrict::NONE), Flags::NONE);
}
}
let mut out = Vec::with_capacity(self.accesses.len());
for plan in &self.accesses {
let object = self.object(plan);
let pointer = match plan.base {
Base::Param(n) => incoming[n],
Base::Local(n) => locals[n],
};
let at = build.iconst(Type::int(64), i128::from(plan.offset));
let address = build.binary(Opcode::PtrAdd, pointer, at, Flags::NONE);
let restrict = match (plan.tagged, plan.base) {
(true, Base::Param(n)) => Restrict { clique: 1, base: n as u16 + 1 },
_ => Restrict::NONE,
};
let tbaa = plan.tagged.then(|| if plan.bytewise { root } else { types[object.ty()] });
let info = mem_of(plan.size, tbaa, restrict);
let ty = Type::int(u32::try_from(plan.size).unwrap() * 8);
let inst = if plan.writes {
let value = build.iconst(ty, 1);
build.store(value, address, info, Flags::NONE)
} else {
let value = build.load(ty, address, info, Flags::NONE);
match build.func()[value].def {
Def::Result { inst, .. } => inst,
Def::Param { .. } => unreachable!("a load is not a block parameter"),
}
};
out.push((inst, object, plan));
}
build.ret(&[]);
let alias = Alias::new(&func, &module);
let accesses = out
.into_iter()
.map(|(inst, object, plan)| {
let access = if plan.writes {
alias.writes(inst).expect("a store writes")
} else {
alias.reads(inst).expect("a load reads")
};
assert_eq!(access.size, Some(plan.size), "the access is not the size asked for");
let truth = Truth { object, start: plan.offset, end: plan.offset + plan.size };
(access, truth)
})
.collect();
drop(alias);
(module, func, accesses)
}
}
fn mem_of(size: u64, tbaa: Option<Meta>, restrict: Restrict) -> MemInfo {
MemInfo {
size,
align: u32::try_from(size).unwrap(),
order: MemOrder::NotAtomic,
tbaa,
restrict,
}
}
#[test]
fn an_answer_names_the_layer_that_gave_it_and_the_layer_has_something_to_say() {
for reason in Reason::ALL {
assert!(!reason.name().is_empty());
assert!(reason.describe().len() > reason.name().len());
assert_eq!(Answer::No(reason).reason(), Some(reason));
}
}
struct Random(u64);
impl Random {
fn new(seed: u64) -> Self {
Self(seed)
}
fn bits(&mut self) -> u64 {
self.0 ^= self.0 << 13;
self.0 ^= self.0 >> 7;
self.0 ^= self.0 << 17;
self.0
}
fn below(&mut self, bound: u64) -> u64 {
self.bits() % bound
}
}