nac3_sca/nac3core/src/typecheck/escape_analysis/lifetime.rs

277 lines
8.8 KiB
Rust

use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use std::rc::Rc;
use crate::typecheck::unification_table::{UnificationKey, UnificationTable};
use itertools::Itertools;
use nac3parser::ast::StrRef;
// change this to enum, only local needs unification key
pub type Lifetime = UnificationKey;
#[derive(Copy, Debug, Clone, PartialEq, Eq, Hash)]
pub enum LifetimeKind {
// can be assigned to fields of anything
// can be returned
// lifetime of static values
Global,
// can only be assigned to fields of objects with local lifetime
// can be returned
// lifetime of parameters
NonLocal,
// can only be assigned to fields of objects with local lifetime
// cannot be returned
// lifetime of constructor return values
Local,
// can only be assigned to fields of objects with local lifetime
// cannot be returned
// lifetime of function return values
Unknown,
}
impl std::ops::BitAnd for LifetimeKind {
type Output = Self;
fn bitand(self, other: Self) -> Self {
use LifetimeKind::*;
match (self, other) {
(x, y) if x == y => x,
(Global, NonLocal) | (NonLocal, Global) => NonLocal,
_ => Unknown,
}
}
}
impl std::cmp::PartialOrd for LifetimeKind {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
use LifetimeKind::*;
match (*self, *other) {
(x, y) if x == y => Some(std::cmp::Ordering::Equal),
(Local, _) | (_, Global) => Some(std::cmp::Ordering::Less),
(_, Local) | (Global, _) => Some(std::cmp::Ordering::Greater),
_ => None,
}
}
}
pub struct BlockLifetimeContext {
mapping: Vec<(Option<Lifetime>, Lifetime)>,
}
impl BlockLifetimeContext {
pub fn new() -> Self {
BlockLifetimeContext { mapping: Vec::new() }
}
pub fn add_fresh(&mut self, lifetime: Lifetime) {
self.mapping.push((None, lifetime));
}
}
struct LifetimeEntry {
kind: LifetimeKind,
fields: RefCell<HashMap<StrRef, Lifetime>>,
}
pub struct LifetimeTable {
table: UnificationTable<Rc<LifetimeEntry>>,
cache: HashSet<(Lifetime, Lifetime)>,
}
impl LifetimeTable {
pub fn new() -> Self {
let mut zelf = Self { table: UnificationTable::new(), cache: Default::default() };
zelf.table.new_key(Rc::new(LifetimeEntry {
kind: LifetimeKind::Unknown,
fields: Default::default(),
}));
zelf
}
pub fn add_lifetime(&mut self, kind: LifetimeKind) -> Lifetime {
self.table.new_key(Rc::new(LifetimeEntry { kind, fields: Default::default() }))
}
pub fn unify(&mut self, a: Lifetime, b: Lifetime, ctx: &mut BlockLifetimeContext) {
self.cache.clear();
self.unify_impl(a, b, ctx);
}
fn get_scoped<const N: usize>(
&mut self,
mut lifetimes: [Lifetime; N],
ctx: &mut BlockLifetimeContext,
) -> [Lifetime; N] {
for l in lifetimes.iter_mut() {
let mut result = None;
for (k, v) in ctx.mapping.iter() {
if self.table.unioned(*v, *l) || k.map_or(false, |k| self.table.unioned(k, *l)) {
// already fresh
result = Some(*v);
break;
}
}
if let Some(result) = result {
*l = result;
} else {
let lifetime = self.table.probe_value(*l).clone();
*l = if lifetime.kind == LifetimeKind::Unknown {
UnificationKey(0)
} else {
let k = self.table.new_key(lifetime);
ctx.mapping.push((Some(*l), k));
k
}
}
}
lifetimes
}
fn unify_impl(&mut self, a: Lifetime, b: Lifetime, ctx: &mut BlockLifetimeContext) {
use LifetimeKind::*;
let [a, b] = self.get_scoped([a, b], ctx);
let a = self.table.get_representative(a);
let b = self.table.get_representative(b);
if a == b || self.cache.contains(&(a, b)) || self.cache.contains(&(b, a)) {
return;
}
self.cache.insert((a, b));
let v_a = self.table.probe_value(a).clone();
let v_b = self.table.probe_value(b).clone();
let result_kind = v_a.kind & v_b.kind;
let fields = if result_kind == Local {
// we only need to track fields lifetime for objects with local lifetime
let fields = v_a.fields.clone();
{
let mut fields_ref = fields.borrow_mut();
for (k, v) in v_b.fields.borrow().iter() {
if let Some(old) = fields_ref.insert(k.clone(), *v) {
self.unify_impl(old, *v, ctx);
}
}
}
fields
} else {
Default::default()
};
self.table.unify(a, b);
self.table.set_value(a, Rc::new(LifetimeEntry { kind: result_kind, fields }));
}
pub fn get_field_lifetime(
&mut self,
lifetime: Lifetime,
field: StrRef,
ctx: &mut BlockLifetimeContext,
) -> Lifetime {
use LifetimeKind::*;
let [lifetime] = self.get_scoped([lifetime], ctx);
if let LifetimeEntry { kind: Local, fields } = &*self.table.probe_value(lifetime).clone() {
if let Some(lifetime) = fields.borrow().get(&field) {
*lifetime
} else {
// unknown lifetime
// we can reuse this lifetime because it will never be unified to something else
UnificationKey(0)
}
} else {
lifetime
}
}
pub fn set_field_lifetime(
&mut self,
obj: Lifetime,
field: StrRef,
lifetime: Lifetime,
is_strong_update: bool,
ctx: &mut BlockLifetimeContext,
) -> Result<(), String> {
let [obj, lifetime] = self.get_scoped([obj, lifetime], ctx);
let obj_lifetime = self.table.probe_value(obj).clone();
let field_lifetime = self.table.probe_value(lifetime).clone();
if !(obj_lifetime.kind <= field_lifetime.kind) {
return Err("lifetime error".to_string());
}
let mut fields = obj_lifetime.fields.borrow_mut();
if is_strong_update {
fields.insert(field, lifetime);
} else {
if let Some(old) = fields.insert(field, lifetime) {
self.unify(old, lifetime, ctx);
}
}
Ok(())
}
pub fn get_lifetime_kind(
&mut self,
lifetime: Lifetime,
ctx: &mut BlockLifetimeContext,
) -> LifetimeKind {
let [lifetime] = self.get_scoped([lifetime], ctx);
self.table.probe_value(lifetime).kind
}
pub fn set_function_params(&mut self, lifetime: Lifetime, ctx: &mut BlockLifetimeContext) {
use LifetimeKind::*;
// unify each field with global
let [lifetime] = self.get_scoped([lifetime], ctx);
let lifetime = self.table.probe_value(lifetime).clone();
let mut worklist = lifetime.fields.borrow().values().copied().collect_vec();
while let Some(item) = worklist.pop() {
let [item] = self.get_scoped([item], ctx);
let lifetime = self.table.probe_value(item).clone();
if lifetime.kind == Unknown || lifetime.kind == Global {
continue;
}
let fields = lifetime.fields.borrow().clone();
for (_, v) in fields.iter() {
worklist.push(*v);
}
self.table.set_value(
item,
Rc::new(LifetimeEntry {
kind: lifetime.kind & Global,
fields: RefCell::new(fields),
}),
);
}
}
pub fn get_unknown_lifetime(&self) -> Lifetime {
UnificationKey(0)
}
pub fn equiv(&mut self, a: Lifetime, b: Lifetime, ctx: &mut BlockLifetimeContext) -> bool {
use LifetimeKind::Local;
let [a, b] = self.get_scoped([a, b], ctx);
if self.table.unioned(a, b) {
return true;
}
let lifetime_a = self.table.probe_value(a).clone();
let lifetime_b = self.table.probe_value(b).clone();
if lifetime_a.kind == Local && lifetime_b.kind == Local {
let fields_a = lifetime_a.fields.borrow();
let fields_b = lifetime_b.fields.borrow();
for (k, v) in fields_a.iter() {
if fields_b.get(k).map(|v1| self.equiv(*v, *v1, ctx)) != Some(true) {
return false;
}
}
// they are just equivalent
// this can avoid infinite recursion
self.table.unify(a, b);
true
} else {
lifetime_a.kind == lifetime_b.kind
}
}
}