use std::collections::HashSet;
use rucc_base::Symbol;
use rucc_ir::{
AttrSet, Attrs, DataLayout, Def, Extra, Flags, Func, Imm, Inst, MemInfo, Meta, Module, Opcode,
Restrict, SymbolRef, Type, Value,
};
const CHASE_LIMIT: u32 = 64;
const TREE_LIMIT: u32 = 32;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Reason {
Distinct,
Escape,
Offset,
Tbaa,
Restrict,
Attribute,
}
impl Reason {
pub const ALL: [Self; 6] =
[Self::Distinct, Self::Escape, Self::Offset, Self::Tbaa, Self::Restrict, Self::Attribute];
pub const COUNT: usize = Self::ALL.len();
#[must_use]
pub const fn index(self) -> usize {
match self {
Self::Distinct => 0,
Self::Escape => 1,
Self::Offset => 2,
Self::Tbaa => 3,
Self::Restrict => 4,
Self::Attribute => 5,
}
}
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Self::Distinct => "distinct",
Self::Escape => "escape",
Self::Offset => "offset",
Self::Tbaa => "tbaa",
Self::Restrict => "restrict",
Self::Attribute => "attribute",
}
}
#[must_use]
pub const fn describe(self) -> &'static str {
match self {
Self::Distinct => "they are two different objects",
Self::Escape => "the address of that local never leaves this function",
Self::Offset => "they are parts of one object that do not overlap",
Self::Tbaa => "no object has both of those types",
Self::Restrict => "restrict says those two pointers do not reach the same object",
Self::Attribute => "the callee is declared not to touch memory that way",
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Answer {
May,
No(Reason),
}
impl Answer {
#[must_use]
pub const fn is_no(self) -> bool {
matches!(self, Self::No(_))
}
#[must_use]
pub const fn reason(self) -> Option<Reason> {
match self {
Self::No(reason) => Some(reason),
Self::May => None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Options {
pub strict_aliasing: bool,
}
impl Default for Options {
fn default() -> Self {
Self { strict_aliasing: true }
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Origin {
Local(Inst),
Global(Symbol),
Unknown(Value),
}
impl Origin {
#[must_use]
pub const fn is_object(self) -> bool {
matches!(self, Self::Local(_) | Self::Global(_))
}
}
#[must_use]
pub fn origin(func: &Func, mut value: Value) -> (Origin, Option<i64>) {
let mut offset = Some(0i64);
for _ in 0..CHASE_LIMIT {
let Def::Result { inst, .. } = func[value].def else {
return (Origin::Unknown(value), offset);
};
let data = func[inst];
match data.opcode {
Opcode::Alloca => return (Origin::Local(inst), offset),
Opcode::GlobalAddr => {
let Extra::Symbol(name) = data.extra else {
return (Origin::Unknown(value), offset);
};
return (Origin::Global(name), offset);
}
Opcode::PtrAdd => {
let args = &func[data.args];
let (base, by) = (args[0], args[1]);
offset = offset
.and_then(|so_far| Some((so_far, constant(func, by)?)))
.and_then(|(so_far, by)| so_far.checked_add(by));
value = base;
}
Opcode::Bitcast => value = func[data.args][0],
_ => return (Origin::Unknown(value), offset),
}
}
(Origin::Unknown(value), None)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Access {
pub origin: Origin,
pub offset: Option<i64>,
pub size: Option<u64>,
pub tbaa: Option<Meta>,
pub restrict: Restrict,
pub volatile: bool,
}
impl Access {
#[must_use]
pub fn through(func: &Func, pointer: Value) -> Self {
let (origin, offset) = origin(func, pointer);
Self { origin, offset, size: None, tbaa: None, restrict: Restrict::NONE, volatile: false }
}
#[must_use]
pub fn range(&self) -> Option<(i128, i128)> {
let (offset, size) = (self.offset?, self.size?);
let start = i128::from(offset);
Some((start, start + i128::from(size)))
}
}
#[derive(Clone, Debug, Default)]
pub struct Escapes {
escaped: HashSet<Inst>,
}
impl Escapes {
#[must_use]
pub fn of(func: &Func) -> Self {
let mut escaped = HashSet::new();
for block in func.blocks() {
for inst in func.insts(block) {
let data = func[inst];
for (index, &arg) in func[data.args].iter().enumerate() {
if keeps_address(data.opcode, index) {
continue;
}
if let (Origin::Local(local), _) = origin(func, arg) {
escaped.insert(local);
}
}
for call in func.successors(inst) {
for &arg in &func[call.args] {
if let (Origin::Local(local), _) = origin(func, arg) {
escaped.insert(local);
}
}
}
}
}
Self { escaped }
}
#[must_use]
pub fn escaped(&self, local: Inst) -> bool {
self.escaped.contains(&local)
}
#[must_use]
pub fn count(&self) -> usize {
self.escaped.len()
}
}
#[must_use]
pub const fn keeps_address(opcode: Opcode, index: usize) -> bool {
match (opcode, index) {
(Opcode::Load | Opcode::AtomicLoad, 0)
| (Opcode::Store | Opcode::AtomicStore, 1)
| (Opcode::AtomicRmw | Opcode::Cmpxchg, 0)
| (Opcode::Memcpy | Opcode::Memmove, 0 | 1)
| (Opcode::Memset | Opcode::Prefetch, 0) => true,
(Opcode::PtrAdd | Opcode::Bitcast, 0) => true,
(Opcode::ICmp, 0 | 1) => true,
_ => false,
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Counts {
queries: u64,
answered: [u64; Reason::COUNT],
}
impl Counts {
#[must_use]
pub const fn queries(&self) -> u64 {
self.queries
}
#[must_use]
pub const fn answered(&self, reason: Reason) -> u64 {
self.answered[reason.index()]
}
#[must_use]
pub fn total(&self) -> u64 {
self.answered.iter().sum()
}
}
#[derive(Debug)]
pub struct Alias<'a> {
func: &'a Func,
module: &'a Module,
options: Options,
escapes: Escapes,
counts: Counts,
}
impl<'a> Alias<'a> {
#[must_use]
pub fn new(func: &'a Func, module: &'a Module) -> Self {
Self::with(func, module, Options::default())
}
#[must_use]
pub fn with(func: &'a Func, module: &'a Module, options: Options) -> Self {
Self { func, module, options, escapes: Escapes::of(func), counts: Counts::default() }
}
#[must_use]
pub const fn escapes(&self) -> &Escapes {
&self.escapes
}
#[must_use]
pub const fn counts(&self) -> &Counts {
&self.counts
}
#[must_use]
pub fn reads(&self, inst: Inst) -> Option<Access> {
let data = self.func[inst];
let args = &self.func[data.args];
let info = self.mem(inst);
let (pointer, size) = match data.opcode {
Opcode::Load | Opcode::AtomicLoad => (args[0], self.width(self.result_type(inst)?)),
Opcode::Memcpy | Opcode::Memmove => (args[1], Some(info?.size)),
Opcode::AtomicRmw => (args[0], self.width(self.func[args[1]].ty)),
Opcode::Cmpxchg => (args[0], self.width(self.func[args[1]].ty)),
Opcode::VaObject => (args[0], Some(info?.size)),
_ => return None,
};
Some(self.access(pointer, size, info, data.flags))
}
#[must_use]
pub fn writes(&self, inst: Inst) -> Option<Access> {
let data = self.func[inst];
let args = &self.func[data.args];
let info = self.mem(inst);
let (pointer, size) = match data.opcode {
Opcode::Store | Opcode::AtomicStore => (args[1], self.width(self.func[args[0]].ty)),
Opcode::Memcpy | Opcode::Memmove | Opcode::Memset => (args[0], Some(info?.size)),
Opcode::AtomicRmw | Opcode::Cmpxchg => (args[0], self.width(self.func[args[1]].ty)),
_ => return None,
};
Some(self.access(pointer, size, info, data.flags))
}
pub fn query(&mut self, a: &Access, b: &Access) -> Answer {
self.counts.queries += 1;
let answer = self.decide(a, b);
if let Answer::No(reason) = answer {
self.counts.answered[reason.index()] += 1;
}
answer
}
pub fn clobbered_by(&mut self, reference: &Access, call: Inst) -> Answer {
self.touched_by(reference, call, true)
}
pub fn read_by(&mut self, reference: &Access, call: Inst) -> Answer {
self.touched_by(reference, call, false)
}
fn decide(&self, a: &Access, b: &Access) -> Answer {
if a.volatile && b.volatile {
return Answer::May;
}
if a.origin.is_object() && b.origin.is_object() {
if self.distinct(a.origin, b.origin) {
return Answer::No(Reason::Distinct);
}
if a.origin == b.origin {
return by_offset(a, b);
}
return Answer::May;
}
if let Some(local) = self.private(a).or_else(|| self.private(b)) {
let _ = local;
return Answer::No(Reason::Escape);
}
if a.restrict.disjoint(b.restrict) {
return Answer::No(Reason::Restrict);
}
if self.options.strict_aliasing {
if let (Some(one), Some(other)) = (a.tbaa, b.tbaa) {
if !self.types_conflict(one, other) {
return Answer::No(Reason::Tbaa);
}
}
}
if a.origin == b.origin {
return by_offset(a, b);
}
Answer::May
}
fn private(&self, reference: &Access) -> Option<Inst> {
match reference.origin {
Origin::Local(local) if !self.escapes.escaped(local) => Some(local),
_ => None,
}
}
fn distinct(&self, a: Origin, b: Origin) -> bool {
match (a, b) {
(Origin::Local(one), Origin::Local(other)) => one != other,
(Origin::Local(_), Origin::Global(_)) | (Origin::Global(_), Origin::Local(_)) => true,
(Origin::Global(one), Origin::Global(other)) => {
one != other && self.one_object(one) && self.one_object(other)
}
_ => false,
}
}
fn one_object(&self, name: Symbol) -> bool {
matches!(self.module.lookup(name), Some(SymbolRef::Func(_) | SymbolRef::Global(_)))
}
fn types_conflict(&self, one: Meta, other: Meta) -> bool {
self.at_or_below(one, other) || self.at_or_below(other, one)
}
fn at_or_below(&self, mut node: Meta, ancestor: Meta) -> bool {
for _ in 0..TREE_LIMIT {
if node == ancestor {
return true;
}
match self.module[node].parent() {
Some(up) => node = up,
None => return false,
}
}
true
}
fn touched_by(&mut self, reference: &Access, call: Inst, writing: bool) -> Answer {
self.counts.queries += 1;
let answer = self.decide_call(reference, call, writing);
if let Answer::No(reason) = answer {
self.counts.answered[reason.index()] += 1;
}
answer
}
fn decide_call(&self, reference: &Access, call: Inst, writing: bool) -> Answer {
if self.private(reference).is_some() {
return Answer::No(Reason::Escape);
}
let Some(attrs) = self.callee(call) else {
return Answer::May;
};
if attrs.set.contains(AttrSet::READNONE)
|| (writing && attrs.set.contains(AttrSet::READONLY))
{
return Answer::No(Reason::Attribute);
}
if attrs.set.contains(AttrSet::ARGMEM_ONLY) {
let args = &self.func[self.func[call].args];
let mut all = true;
for &arg in args {
if !self.func[arg].ty.is_ptr() {
continue;
}
let through = Access::through(self.func, arg);
all &= self.decide(reference, &through).is_no();
}
if all {
return Answer::No(Reason::Attribute);
}
}
Answer::May
}
fn callee(&self, call: Inst) -> Option<Attrs> {
let Extra::Call(info) = self.func[call].extra else {
return None;
};
let name = self.func[info].callee?;
match self.module.lookup(name)? {
SymbolRef::Func(id) => Some(self.module[id].attrs),
_ => None,
}
}
fn mem(&self, inst: Inst) -> Option<MemInfo> {
match self.func[inst].extra {
Extra::Mem(info) | Extra::Rmw(_, info) => Some(self.func[info]),
Extra::VaObject(object) => Some(self.func[self.func[object].mem]),
_ => None,
}
}
fn result_type(&self, inst: Inst) -> Option<Type> {
self.func[inst].results().next().map(|value| self.func[value].ty)
}
fn access(
&self,
pointer: Value,
size: Option<u64>,
info: Option<MemInfo>,
flags: Flags,
) -> Access {
let (origin, offset) = origin(self.func, pointer);
Access {
origin,
offset,
size,
tbaa: info.and_then(|info| info.tbaa),
restrict: info.map_or(Restrict::NONE, |info| info.restrict),
volatile: flags.contains(Flags::VOLATILE),
}
}
fn width(&self, ty: Type) -> Option<u64> {
let layout: &DataLayout = &self.module.datalayout;
if ty.is_ptr() {
return Some(u64::from(layout.pointer_bits).div_ceil(8));
}
let bits = u64::from(ty.bits()) * u64::from(ty.lanes());
(bits > 0).then(|| bits.div_ceil(8))
}
}
fn by_offset(a: &Access, b: &Access) -> Answer {
let (Some((a_start, a_end)), Some((b_start, b_end))) = (a.range(), b.range()) else {
return Answer::May;
};
if a_end <= b_start || b_end <= a_start {
return Answer::No(Reason::Offset);
}
Answer::May
}
fn constant(func: &Func, value: Value) -> Option<i64> {
let Def::Result { inst, .. } = func[value].def else {
return None;
};
let data = func[inst];
if data.opcode != Opcode::IConst {
return None;
}
let Extra::Imm(imm) = data.extra else {
return None;
};
i64::try_from(Imm::signed(func[imm], func[value].ty)).ok()
}
#[cfg(test)]
mod tests {
use rucc_base::{Interner, Symbol};
use rucc_ir::{
AttrSet, Attrs, Builder, CallInfo, Extra, Flags, Func, Global, InstData, IntPred, MemInfo,
MemOrder, MetaNode, Module, Opcode, Restrict, Signature, TbaaNode, Type, Value,
};
use rucc_target::{TargetInfo, Triple};
use super::*;
fn module(names: &mut Interner) -> Module {
let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
Module::new(names.intern("t.c"), &target)
}
fn func(names: &mut Interner, params: &[Type]) -> Func {
let mut func = Func::new(names.intern("f"), Signature::new().with_params(params));
let entry = func.create_block();
for &ty in params {
func.append_param(entry, ty);
}
func
}
fn builder(func: &mut Func) -> Builder<'_> {
let entry = func.entry().expect("the function has an entry block");
Builder::new(func, entry)
}
fn param(func: &Func, index: usize) -> Value {
let entry = func.entry().expect("the function has an entry block");
func[entry].params[index]
}
fn plain(align: u32) -> MemInfo {
MemInfo { size: 0, align, order: MemOrder::NotAtomic, tbaa: None, restrict: Restrict::NONE }
}
fn sized(size: u64, align: u32) -> MemInfo {
MemInfo { size, ..plain(align) }
}
fn local(build: &mut Builder<'_>, size: u64) -> Value {
let mem = build.func().add_mem(sized(size, 8));
build.value(InstData { extra: Extra::Mem(mem), ..InstData::new(Opcode::Alloca) }, Type::PTR)
}
fn at(build: &mut Builder<'_>, base: Value, offset: i64) -> Value {
let by = build.iconst(Type::int(64), i128::from(offset));
build.binary(Opcode::PtrAdd, base, by, Flags::NONE)
}
fn global(build: &mut Builder<'_>, module: &mut Module, name: Symbol) -> Value {
module.add_global(Global::new(name, 16, 8));
build.value(
InstData { extra: Extra::Symbol(name), ..InstData::new(Opcode::GlobalAddr) },
Type::PTR,
)
}
#[test]
fn two_different_locals_are_two_objects() {
let mut names = Interner::new();
let module = module(&mut names);
let mut f = func(&mut names, &[]);
let mut build = builder(&mut f);
let one = local(&mut build, 16);
let other = local(&mut build, 16);
let read = build.load(Type::int(32), one, plain(4), Flags::NONE);
build.store(read, other, plain(4), Flags::NONE);
build.ret(&[]);
let mut alias = Alias::new(&f, &module);
let (a, b) = two(&alias, &f);
assert_eq!(alias.query(&a, &b), Answer::No(Reason::Distinct));
assert_eq!(alias.counts().answered(Reason::Distinct), 1);
assert_eq!(alias.counts().queries(), 1);
}
fn two(alias: &Alias<'_>, func: &Func) -> (Access, Access) {
let mut read = None;
let mut written = None;
for block in func.blocks() {
for inst in func.insts(block) {
if read.is_none() {
read = alias.reads(inst);
}
if written.is_none() {
written = alias.writes(inst);
}
}
}
(read.expect("a read"), written.expect("a write"))
}
#[test]
fn a_local_and_a_global_are_two_objects() {
let mut names = Interner::new();
let mut module = module(&mut names);
let x = names.intern("x");
let mut f = func(&mut names, &[]);
let mut build = builder(&mut f);
let one = local(&mut build, 16);
let other = global(&mut build, &mut module, x);
let read = build.load(Type::int(32), one, plain(4), Flags::NONE);
build.store(read, other, plain(4), Flags::NONE);
build.ret(&[]);
let mut alias = Alias::new(&f, &module);
let (a, b) = two(&alias, &f);
assert_eq!(alias.query(&a, &b), Answer::No(Reason::Distinct));
}
#[test]
fn two_different_globals_are_two_objects() {
let mut names = Interner::new();
let mut module = module(&mut names);
let (x, y) = (names.intern("x"), names.intern("y"));
let mut f = func(&mut names, &[]);
let mut build = builder(&mut f);
let one = global(&mut build, &mut module, x);
let other = global(&mut build, &mut module, y);
let read = build.load(Type::int(32), one, plain(4), Flags::NONE);
build.store(read, other, plain(4), Flags::NONE);
build.ret(&[]);
let mut alias = Alias::new(&f, &module);
let (a, b) = two(&alias, &f);
assert_eq!(alias.query(&a, &b), Answer::No(Reason::Distinct));
}
#[test]
fn a_global_the_module_does_not_have_is_not_argued_about() {
let mut names = Interner::new();
let mut module = module(&mut names);
let (x, y) = (names.intern("x"), names.intern("y"));
let mut f = func(&mut names, &[]);
let mut build = builder(&mut f);
let one = global(&mut build, &mut module, x);
let other = build.value(
InstData { extra: Extra::Symbol(y), ..InstData::new(Opcode::GlobalAddr) },
Type::PTR,
);
let read = build.load(Type::int(32), one, plain(4), Flags::NONE);
build.store(read, other, plain(4), Flags::NONE);
build.ret(&[]);
let mut alias = Alias::new(&f, &module);
let (a, b) = two(&alias, &f);
assert_eq!(alias.query(&a, &b), Answer::May);
}
#[test]
fn two_parts_of_one_object_that_do_not_overlap_are_disjoint() {
let mut names = Interner::new();
let module = module(&mut names);
let mut f = func(&mut names, &[]);
let mut build = builder(&mut f);
let object = local(&mut build, 16);
let first = at(&mut build, object, 0);
let second = at(&mut build, object, 4);
let read = build.load(Type::int(32), first, plain(4), Flags::NONE);
build.store(read, second, plain(4), Flags::NONE);
build.ret(&[]);
let mut alias = Alias::new(&f, &module);
let (a, b) = two(&alias, &f);
assert_eq!(alias.query(&a, &b), Answer::No(Reason::Offset));
}
#[test]
fn two_parts_of_one_object_that_do_overlap_are_not() {
let mut names = Interner::new();
let module = module(&mut names);
let mut f = func(&mut names, &[]);
let mut build = builder(&mut f);
let object = local(&mut build, 16);
let first = at(&mut build, object, 0);
let second = at(&mut build, object, 2);
let read = build.load(Type::int(32), first, plain(4), Flags::NONE);
build.store(read, second, plain(4), Flags::NONE);
build.ret(&[]);
let mut alias = Alias::new(&f, &module);
let (a, b) = two(&alias, &f);
assert_eq!(alias.query(&a, &b), Answer::May);
}
#[test]
fn an_offset_nobody_knows_gives_up_the_offset_and_keeps_the_object() {
let mut names = Interner::new();
let module = module(&mut names);
let mut f = func(&mut names, &[Type::int(64)]);
let n = param(&f, 0);
let mut build = builder(&mut f);
let object = local(&mut build, 16);
let somewhere = build.binary(Opcode::PtrAdd, object, n, Flags::NONE);
let read = build.load(Type::int(32), somewhere, plain(4), Flags::NONE);
build.store(read, object, plain(4), Flags::NONE);
build.ret(&[]);
let mut alias = Alias::new(&f, &module);
let (a, b) = two(&alias, &f);
assert_eq!(a.origin, b.origin, "both are still that one object");
assert_eq!(a.offset, None);
assert_eq!(alias.query(&a, &b), Answer::May);
}
#[test]
fn a_local_whose_address_stays_here_is_not_what_a_parameter_points_at() {
let mut names = Interner::new();
let module = module(&mut names);
let mut f = func(&mut names, &[Type::PTR]);
let outside = param(&f, 0);
let mut build = builder(&mut f);
let object = local(&mut build, 16);
let read = build.load(Type::int(32), object, plain(4), Flags::NONE);
build.store(read, outside, plain(4), Flags::NONE);
build.ret(&[]);
let mut alias = Alias::new(&f, &module);
assert_eq!(alias.escapes().count(), 0);
let (a, b) = two(&alias, &f);
assert_eq!(alias.query(&a, &b), Answer::No(Reason::Escape));
}
#[test]
fn a_local_whose_address_was_stored_somewhere_is() {
let mut names = Interner::new();
let module = module(&mut names);
let mut f = func(&mut names, &[Type::PTR]);
let outside = param(&f, 0);
let mut build = builder(&mut f);
let object = local(&mut build, 16);
build.store(object, outside, plain(8), Flags::NONE);
let read = build.load(Type::int(32), object, plain(4), Flags::NONE);
build.store(read, outside, plain(4), Flags::NONE);
build.ret(&[]);
let mut alias = Alias::new(&f, &module);
assert_eq!(alias.escapes().count(), 1);
let read = first(&f, Opcode::Load);
let write = last(&f, Opcode::Store);
let a = alias.reads(read).unwrap();
let b = alias.writes(write).unwrap();
assert_eq!(alias.query(&a, &b), Answer::May);
}
fn first(func: &Func, opcode: Opcode) -> Inst {
func.blocks()
.flat_map(|block| func.insts(block))
.find(|&inst| func[inst].opcode == opcode)
.expect("an instruction with that opcode")
}
fn last(func: &Func, opcode: Opcode) -> Inst {
func.blocks()
.flat_map(|block| func.insts(block))
.filter(|&inst| func[inst].opcode == opcode)
.last()
.expect("an instruction with that opcode")
}
#[test]
fn an_address_carried_through_a_block_parameter_has_left_the_function() {
let mut names = Interner::new();
let module = module(&mut names);
let mut f = func(&mut names, &[]);
let start = f.entry().expect("an entry block");
let next = f.create_block();
f.append_param(next, Type::PTR);
let mut build = Builder::new(&mut f, start);
let object = local(&mut build, 16);
build.jump(next, &[object]);
let mut build = Builder::new(&mut f, next);
build.ret(&[]);
let alias = Alias::new(&f, &module);
assert!(alias.escapes().escaped(first(&f, Opcode::Alloca)));
}
#[test]
fn comparing_two_addresses_does_not_let_either_of_them_out() {
let mut names = Interner::new();
let module = module(&mut names);
let mut f = func(&mut names, &[Type::PTR]);
let outside = param(&f, 0);
let mut build = builder(&mut f);
let object = local(&mut build, 16);
build.icmp(IntPred::Eq, object, outside);
build.ret(&[]);
let alias = Alias::new(&f, &module);
assert_eq!(alias.escapes().count(), 0);
}
#[test]
fn an_address_turned_into_a_number_has_left_the_function() {
let mut names = Interner::new();
let module = module(&mut names);
let mut f = func(&mut names, &[]);
let mut build = builder(&mut f);
let object = local(&mut build, 16);
build.unary(Opcode::PtrToInt, object, Type::int(64));
build.ret(&[]);
let alias = Alias::new(&f, &module);
assert!(alias.escapes().escaped(first(&f, Opcode::Alloca)));
}
#[test]
fn two_restrict_pointers_in_one_scope_do_not_reach_the_same_object() {
let mut names = Interner::new();
let module = module(&mut names);
let mut f = func(&mut names, &[Type::PTR, Type::PTR]);
let (one, other) = (param(&f, 0), param(&f, 1));
let mut build = builder(&mut f);
let mut info = plain(4);
info.restrict = Restrict { clique: 1, base: 1 };
let read = build.load(Type::int(32), one, info, Flags::NONE);
info.restrict = Restrict { clique: 1, base: 2 };
build.store(read, other, info, Flags::NONE);
build.ret(&[]);
let mut alias = Alias::new(&f, &module);
let (a, b) = two(&alias, &f);
assert_eq!(alias.query(&a, &b), Answer::No(Reason::Restrict));
}
#[test]
fn two_restrict_pointers_in_different_scopes_say_nothing_about_each_other() {
let mut names = Interner::new();
let module = module(&mut names);
let mut f = func(&mut names, &[Type::PTR, Type::PTR]);
let (one, other) = (param(&f, 0), param(&f, 1));
let mut build = builder(&mut f);
let mut info = plain(4);
info.restrict = Restrict { clique: 1, base: 1 };
let read = build.load(Type::int(32), one, info, Flags::NONE);
info.restrict = Restrict { clique: 2, base: 1 };
build.store(read, other, info, Flags::NONE);
build.ret(&[]);
let mut alias = Alias::new(&f, &module);
let (a, b) = two(&alias, &f);
assert_eq!(alias.query(&a, &b), Answer::May);
}
fn types(module: &mut Module, names: &mut Interner) -> (Meta, Meta, Meta) {
let root = module.add_meta(MetaNode::Tbaa(TbaaNode {
name: names.intern("char"),
parent: None,
offset: 0,
}));
let int = module.add_meta(MetaNode::Tbaa(TbaaNode {
name: names.intern("int"),
parent: Some(root),
offset: 0,
}));
let float = module.add_meta(MetaNode::Tbaa(TbaaNode {
name: names.intern("float"),
parent: Some(root),
offset: 0,
}));
(root, int, float)
}
#[test]
fn two_unrelated_types_describe_no_object_in_common() {
let mut names = Interner::new();
let mut module = module(&mut names);
let (_, int, float) = types(&mut module, &mut names);
let mut f = func(&mut names, &[Type::PTR, Type::PTR]);
let (one, other) = (param(&f, 0), param(&f, 1));
let mut build = builder(&mut f);
let mut info = plain(4);
info.tbaa = Some(int);
let read = build.load(Type::int(32), one, info, Flags::NONE);
info.tbaa = Some(float);
build.store(read, other, info, Flags::NONE);
build.ret(&[]);
let mut alias = Alias::new(&f, &module);
let (a, b) = two(&alias, &f);
assert_eq!(alias.query(&a, &b), Answer::No(Reason::Tbaa));
}
#[test]
fn an_access_through_char_conflicts_with_everything() {
let mut names = Interner::new();
let mut module = module(&mut names);
let (root, int, _) = types(&mut module, &mut names);
let mut f = func(&mut names, &[Type::PTR, Type::PTR]);
let (one, other) = (param(&f, 0), param(&f, 1));
let mut build = builder(&mut f);
let mut info = plain(4);
info.tbaa = Some(int);
let read = build.load(Type::int(32), one, info, Flags::NONE);
info.tbaa = Some(root);
build.store(read, other, info, Flags::NONE);
build.ret(&[]);
let mut alias = Alias::new(&f, &module);
let (a, b) = two(&alias, &f);
assert_eq!(alias.query(&a, &b), Answer::May);
}
#[test]
fn turning_strict_aliasing_off_turns_off_that_layer_and_no_other() {
let mut names = Interner::new();
let mut module = module(&mut names);
let (_, int, float) = types(&mut module, &mut names);
let mut f = func(&mut names, &[Type::PTR, Type::PTR]);
let (one, other) = (param(&f, 0), param(&f, 1));
let mut build = builder(&mut f);
let mut info = plain(4);
info.tbaa = Some(int);
info.restrict = Restrict { clique: 1, base: 1 };
let read = build.load(Type::int(32), one, info, Flags::NONE);
info.tbaa = Some(float);
info.restrict = Restrict { clique: 1, base: 2 };
build.store(read, other, info, Flags::NONE);
build.ret(&[]);
let options = Options { strict_aliasing: false };
let mut alias = Alias::with(&f, &module, options);
let (a, b) = two(&alias, &f);
assert_eq!(alias.query(&a, &b), Answer::No(Reason::Restrict));
let mut without = Alias::with(&f, &module, options);
let plainer = Access { restrict: Restrict::NONE, ..a };
let other = Access { restrict: Restrict::NONE, ..b };
assert_eq!(without.query(&plainer, &other), Answer::May);
let mut with = Alias::new(&f, &module);
assert_eq!(with.query(&plainer, &other), Answer::No(Reason::Tbaa));
}
#[test]
fn writing_one_member_of_a_union_and_reading_another_is_one_object() {
let mut names = Interner::new();
let mut module = module(&mut names);
let (_, int, float) = types(&mut module, &mut names);
let mut f = func(&mut names, &[]);
let mut build = builder(&mut f);
let object = local(&mut build, 4);
let mut info = plain(4);
info.tbaa = Some(float);
let read = build.load(Type::int(32), object, info, Flags::NONE);
info.tbaa = Some(int);
build.store(read, object, info, Flags::NONE);
build.ret(&[]);
let mut alias = Alias::new(&f, &module);
let (a, b) = two(&alias, &f);
assert_eq!(alias.query(&a, &b), Answer::May);
}
#[test]
fn two_volatile_accesses_conflict_whatever_else_is_true_of_them() {
let mut names = Interner::new();
let module = module(&mut names);
let mut f = func(&mut names, &[]);
let mut build = builder(&mut f);
let one = local(&mut build, 16);
let other = local(&mut build, 16);
let read = build.load(Type::int(32), one, plain(4), Flags::VOLATILE);
build.store(read, other, plain(4), Flags::VOLATILE);
build.ret(&[]);
let mut alias = Alias::new(&f, &module);
let (a, b) = two(&alias, &f);
assert_eq!(alias.query(&a, &b), Answer::May);
}
#[test]
fn one_volatile_access_and_one_ordinary_one_are_argued_about_as_usual() {
let mut names = Interner::new();
let module = module(&mut names);
let mut f = func(&mut names, &[]);
let mut build = builder(&mut f);
let one = local(&mut build, 16);
let other = local(&mut build, 16);
let read = build.load(Type::int(32), one, plain(4), Flags::VOLATILE);
build.store(read, other, plain(4), Flags::NONE);
build.ret(&[]);
let mut alias = Alias::new(&f, &module);
let (a, b) = two(&alias, &f);
assert_eq!(alias.query(&a, &b), Answer::No(Reason::Distinct));
}
#[test]
fn a_copy_reads_its_source_and_writes_its_destination() {
let mut names = Interner::new();
let module = module(&mut names);
let mut f = func(&mut names, &[]);
let mut build = builder(&mut f);
let to = local(&mut build, 16);
let from = local(&mut build, 16);
let mem = build.func().add_mem(sized(16, 8));
let args = build.func().push_values(&[to, from]);
build.inst(InstData { args, extra: Extra::Mem(mem), ..InstData::new(Opcode::Memcpy) }, &[]);
build.ret(&[]);
let alias = Alias::new(&f, &module);
let copy = first(&f, Opcode::Memcpy);
let read = alias.reads(copy).expect("a copy reads");
let written = alias.writes(copy).expect("a copy writes");
assert_eq!(read.size, Some(16));
assert_eq!(written.size, Some(16));
assert_ne!(read.origin, written.origin);
}
fn call_to(
names: &mut Interner,
module: &mut Module,
f: &mut Func,
attrs: Attrs,
args: &[Value],
) -> Inst {
let name = names.intern("g");
let params: Vec<Type> = args.iter().map(|_| Type::PTR).collect();
let mut callee = Func::new(name, Signature::new().with_params(¶ms));
callee.attrs = attrs;
module.add_func(callee);
let signature = f.add_signature(Signature::new().with_params(¶ms));
let mut build = builder(f);
build.call(name, signature, args)
}
fn attrs(set: AttrSet) -> Attrs {
Attrs { set, ..Attrs::NONE }
}
#[test]
fn a_call_cannot_touch_a_local_whose_address_stayed_here() {
let mut names = Interner::new();
let mut module = module(&mut names);
let mut f = func(&mut names, &[Type::PTR]);
let outside = param(&f, 0);
let mut build = builder(&mut f);
let object = local(&mut build, 16);
let read = build.load(Type::int(32), object, plain(4), Flags::NONE);
let _ = read;
let call = call_to(&mut names, &mut module, &mut f, Attrs::NONE, &[outside]);
let mut build = builder(&mut f);
build.ret(&[]);
let mut alias = Alias::new(&f, &module);
let reference = alias.reads(first(&f, Opcode::Load)).unwrap();
assert_eq!(alias.clobbered_by(&reference, call), Answer::No(Reason::Escape));
assert_eq!(alias.read_by(&reference, call), Answer::No(Reason::Escape));
}
#[test]
fn a_call_can_touch_a_local_it_was_handed() {
let mut names = Interner::new();
let mut module = module(&mut names);
let mut f = func(&mut names, &[]);
let mut build = builder(&mut f);
let object = local(&mut build, 16);
build.load(Type::int(32), object, plain(4), Flags::NONE);
let call = call_to(&mut names, &mut module, &mut f, Attrs::NONE, &[object]);
let mut build = builder(&mut f);
build.ret(&[]);
let mut alias = Alias::new(&f, &module);
let reference = alias.reads(first(&f, Opcode::Load)).unwrap();
assert_eq!(alias.clobbered_by(&reference, call), Answer::May);
}
#[test]
fn a_pure_callee_reads_memory_and_writes_none() {
let mut names = Interner::new();
let mut module = module(&mut names);
let mut f = func(&mut names, &[Type::PTR]);
let outside = param(&f, 0);
let mut build = builder(&mut f);
build.load(Type::int(32), outside, plain(4), Flags::NONE);
let call = call_to(&mut names, &mut module, &mut f, attrs(AttrSet::READONLY), &[outside]);
let mut build = builder(&mut f);
build.ret(&[]);
let mut alias = Alias::new(&f, &module);
let reference = alias.reads(first(&f, Opcode::Load)).unwrap();
assert_eq!(alias.clobbered_by(&reference, call), Answer::No(Reason::Attribute));
assert_eq!(alias.read_by(&reference, call), Answer::May);
}
#[test]
fn a_const_callee_touches_no_memory_at_all() {
let mut names = Interner::new();
let mut module = module(&mut names);
let mut f = func(&mut names, &[Type::PTR]);
let outside = param(&f, 0);
let mut build = builder(&mut f);
build.load(Type::int(32), outside, plain(4), Flags::NONE);
let call = call_to(&mut names, &mut module, &mut f, attrs(AttrSet::READNONE), &[outside]);
let mut build = builder(&mut f);
build.ret(&[]);
let mut alias = Alias::new(&f, &module);
let reference = alias.reads(first(&f, Opcode::Load)).unwrap();
assert_eq!(alias.clobbered_by(&reference, call), Answer::No(Reason::Attribute));
assert_eq!(alias.read_by(&reference, call), Answer::No(Reason::Attribute));
}
#[test]
fn a_callee_that_touches_only_its_arguments_leaves_a_global_it_was_not_passed_alone() {
let mut names = Interner::new();
let mut module = module(&mut names);
let x = names.intern("x");
let mut f = func(&mut names, &[Type::PTR]);
let outside = param(&f, 0);
let mut build = builder(&mut f);
let object = global(&mut build, &mut module, x);
build.load(Type::int(32), object, plain(4), Flags::NONE);
let call =
call_to(&mut names, &mut module, &mut f, attrs(AttrSet::ARGMEM_ONLY), &[outside]);
let mut build = builder(&mut f);
build.ret(&[]);
let mut alias = Alias::new(&f, &module);
let reference = alias.reads(first(&f, Opcode::Load)).unwrap();
assert_eq!(alias.clobbered_by(&reference, call), Answer::May);
}
#[test]
fn a_callee_that_touches_only_its_arguments_and_was_handed_one_object_leaves_the_other() {
let mut names = Interner::new();
let mut module = module(&mut names);
let (x, y) = (names.intern("x"), names.intern("y"));
let mut f = func(&mut names, &[]);
let mut build = builder(&mut f);
let watched = global(&mut build, &mut module, x);
let handed = global(&mut build, &mut module, y);
build.load(Type::int(32), watched, plain(4), Flags::NONE);
let call = call_to(&mut names, &mut module, &mut f, attrs(AttrSet::ARGMEM_ONLY), &[handed]);
let mut build = builder(&mut f);
build.ret(&[]);
let mut alias = Alias::new(&f, &module);
let reference = alias.reads(first(&f, Opcode::Load)).unwrap();
assert_eq!(alias.clobbered_by(&reference, call), Answer::No(Reason::Attribute));
}
#[test]
fn an_indirect_call_is_not_argued_about() {
let mut names = Interner::new();
let module = module(&mut names);
let mut f = func(&mut names, &[Type::PTR, Type::PTR]);
let (target, outside) = (param(&f, 0), param(&f, 1));
let mut build = builder(&mut f);
build.load(Type::int(32), outside, plain(4), Flags::NONE);
let signature = build.func().add_signature(Signature::new().with_params(&[Type::PTR]));
let varargs = build.func().push_abis(&[]);
let info = build.func().add_call(CallInfo { callee: None, signature, varargs });
let args = build.func().push_values(&[target, outside]);
let call = build.inst(
InstData { args, extra: Extra::Call(info), ..InstData::new(Opcode::CallIndirect) },
&[],
);
build.ret(&[]);
let mut alias = Alias::new(&f, &module);
let reference = alias.reads(first(&f, Opcode::Load)).unwrap();
assert_eq!(alias.clobbered_by(&reference, call), Answer::May);
}
#[test]
fn every_reason_has_a_name_and_a_sentence() {
for reason in Reason::ALL {
assert!(!reason.name().is_empty());
assert!(!reason.describe().is_empty());
assert_eq!(Reason::ALL[reason.index()], reason);
}
assert_eq!(Reason::ALL.len(), Reason::COUNT);
assert_eq!(Answer::No(Reason::Offset).reason(), Some(Reason::Offset));
assert!(Answer::No(Reason::Offset).is_no());
assert_eq!(Answer::May.reason(), None);
assert!(!Answer::May.is_no());
}
}