use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
use super::ir::*;
const MAX_ALIAS_GROUP_SIZE: usize = 16;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct BaseAliasResult {
canonical: HashMap<String, String>,
members: HashMap<String, SmallVec<[String; 4]>>,
}
impl BaseAliasResult {
pub fn empty() -> Self {
Self {
canonical: HashMap::new(),
members: HashMap::new(),
}
}
pub fn is_empty(&self) -> bool {
self.members.is_empty()
}
pub fn aliases_of(&self, base: &str) -> Option<&[String]> {
let canon = self.canonical.get(base)?;
self.members.get(canon).map(|v| v.as_slice())
}
pub fn are_aliases(&self, a: &str, b: &str) -> bool {
if a == b {
return true;
}
match (self.canonical.get(a), self.canonical.get(b)) {
(Some(ca), Some(cb)) => ca == cb,
_ => false,
}
}
}
pub fn compute_base_aliases(
copy_map: &HashMap<SsaValue, SsaValue>,
body: &SsaBody,
) -> BaseAliasResult {
if copy_map.is_empty() {
return BaseAliasResult::empty();
}
let mut parent: HashMap<String, String> = HashMap::new();
fn find(parent: &mut HashMap<String, String>, x: &str) -> String {
if !parent.contains_key(x) {
return x.to_string();
}
let mut root = x.to_string();
for _ in 0..100 {
match parent.get(&root) {
Some(p) if p != &root => root = p.clone(),
_ => break,
}
}
let mut cur = x.to_string();
for _ in 0..100 {
match parent.get(&cur) {
Some(p) if p != &root => {
let next = p.clone();
parent.insert(cur, root.clone());
cur = next;
}
_ => break,
}
}
root
}
fn union(parent: &mut HashMap<String, String>, a: &str, b: &str) {
let ra = find(parent, a);
let rb = find(parent, b);
if ra != rb {
if ra < rb {
parent.insert(rb, ra);
} else {
parent.insert(ra, rb);
}
}
}
for (&dst, &src) in copy_map {
let dst_idx = dst.0 as usize;
let src_idx = src.0 as usize;
if dst_idx >= body.value_defs.len() || src_idx >= body.value_defs.len() {
continue;
}
let dst_name = match &body.value_defs[dst_idx].var_name {
Some(n) => n.as_str(),
None => continue,
};
let src_name = match &body.value_defs[src_idx].var_name {
Some(n) => n.as_str(),
None => continue,
};
if dst_name.contains('.') || src_name.contains('.') {
continue;
}
if dst_name == src_name {
continue;
}
parent
.entry(dst_name.to_string())
.or_insert_with(|| dst_name.to_string());
parent
.entry(src_name.to_string())
.or_insert_with(|| src_name.to_string());
union(&mut parent, dst_name, src_name);
}
if parent.is_empty() {
return BaseAliasResult::empty();
}
let mut groups: HashMap<String, SmallVec<[String; 4]>> = HashMap::new();
let all_names: Vec<String> = parent.keys().cloned().collect();
for name in &all_names {
let root = find(&mut parent, name);
groups.entry(root).or_default().push(name.clone());
}
groups.retain(|_, members| members.len() > 1);
for members in groups.values_mut() {
members.sort();
members.truncate(MAX_ALIAS_GROUP_SIZE);
}
let mut canonical: HashMap<String, String> = HashMap::new();
for (root, members) in &groups {
for member in members {
canonical.insert(member.clone(), root.clone());
}
}
BaseAliasResult {
canonical,
members: groups,
}
}
#[cfg(test)]
mod tests {
use super::*;
use petgraph::graph::NodeIndex;
use std::collections::HashMap;
fn vdef(name: &str) -> ValueDef {
ValueDef {
var_name: Some(name.to_string()),
cfg_node: NodeIndex::new(0),
block: BlockId(0),
}
}
fn vdef_none() -> ValueDef {
ValueDef {
var_name: None,
cfg_node: NodeIndex::new(0),
block: BlockId(0),
}
}
fn make_body(defs: Vec<ValueDef>) -> SsaBody {
SsaBody {
blocks: vec![],
entry: BlockId(0),
value_defs: defs,
cfg_node_map: HashMap::new(),
exception_edges: vec![],
field_interner: crate::ssa::ir::FieldInterner::default(),
field_writes: std::collections::HashMap::new(),
synthetic_externals: std::collections::HashSet::new(),
}
}
#[test]
fn test_simple_alias_detection() {
let body = make_body(vec![vdef("a"), vdef("b")]);
let mut copy_map = HashMap::new();
copy_map.insert(SsaValue(1), SsaValue(0));
let result = compute_base_aliases(©_map, &body);
assert!(!result.is_empty());
assert!(result.are_aliases("a", "b"));
assert!(result.are_aliases("b", "a"));
let aliases = result.aliases_of("a").unwrap();
assert_eq!(aliases.len(), 2);
assert!(aliases.contains(&"a".to_string()));
assert!(aliases.contains(&"b".to_string()));
}
#[test]
fn test_transitive_aliases() {
let body = make_body(vec![vdef("a"), vdef("b"), vdef("c")]);
let mut copy_map = HashMap::new();
copy_map.insert(SsaValue(1), SsaValue(0));
copy_map.insert(SsaValue(2), SsaValue(1));
let result = compute_base_aliases(©_map, &body);
assert!(result.are_aliases("a", "b"));
assert!(result.are_aliases("b", "c"));
assert!(result.are_aliases("a", "c"));
let aliases = result.aliases_of("c").unwrap();
assert_eq!(aliases.len(), 3);
}
#[test]
fn test_no_alias_for_none_names() {
let body = make_body(vec![vdef_none(), vdef("b")]);
let mut copy_map = HashMap::new();
copy_map.insert(SsaValue(1), SsaValue(0));
let result = compute_base_aliases(©_map, &body);
assert!(result.is_empty());
}
#[test]
fn test_dotted_paths_ignored() {
let body = make_body(vec![vdef("a.x"), vdef("b.x")]);
let mut copy_map = HashMap::new();
copy_map.insert(SsaValue(1), SsaValue(0));
let result = compute_base_aliases(©_map, &body);
assert!(result.is_empty());
}
#[test]
fn test_alias_group_size_limit() {
let mut defs = vec![vdef("v0")];
let mut copy_map = HashMap::new();
for i in 1..20u32 {
defs.push(vdef(&format!("v{}", i)));
copy_map.insert(SsaValue(i), SsaValue(0));
}
let body = make_body(defs);
let result = compute_base_aliases(©_map, &body);
let aliases = result.aliases_of("v0").unwrap();
assert_eq!(aliases.len(), MAX_ALIAS_GROUP_SIZE);
}
#[test]
fn test_empty_copy_map() {
let body = make_body(vec![vdef("a"), vdef("b")]);
let copy_map = HashMap::new();
let result = compute_base_aliases(©_map, &body);
assert!(result.is_empty());
}
#[test]
fn test_self_alias_ignored() {
let body = make_body(vec![vdef("a")]);
let mut copy_map = HashMap::new();
copy_map.insert(SsaValue(0), SsaValue(0));
let result = compute_base_aliases(©_map, &body);
assert!(result.is_empty());
}
#[test]
fn test_are_aliases_same_name() {
let result = BaseAliasResult::empty();
assert!(result.are_aliases("x", "x"));
}
}