Compare commits

...

6 Commits

11 changed files with 1200 additions and 12 deletions

7
Cargo.lock generated
View File

@ -524,6 +524,7 @@ dependencies = [
"parking_lot 0.11.2",
"rayon",
"regex",
"slab",
"test-case",
]
@ -1003,6 +1004,12 @@ version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de"
[[package]]
name = "slab"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32"
[[package]]
name = "smallvec"
version = "1.8.0"

View File

@ -9,6 +9,7 @@ itertools = "0.10.1"
crossbeam = "0.8.1"
parking_lot = "0.11.1"
rayon = "1.5.1"
slab = "0.4.6"
nac3parser = { path = "../nac3parser" }
[dependencies.inkwell]

View File

@ -4,7 +4,7 @@ use std::rc::Rc;
use crate::{
codegen::{expr::get_subst_key, stmt::exn_constructor},
symbol_resolver::SymbolValue,
typecheck::type_inferencer::{FunctionData, Inferencer},
typecheck::{type_inferencer::{FunctionData, Inferencer}, escape_analysis::EscapeAnalyzer},
};
use super::*;
@ -1792,6 +1792,7 @@ impl TopLevelComposer {
result
};
let mut calls: HashMap<CodeLocation, CallId> = HashMap::new();
let mut args = vec![];
let mut inferencer = Inferencer {
top_level: ctx.as_ref(),
defined_identifiers: identifiers.clone(),
@ -1812,6 +1813,7 @@ impl TopLevelComposer {
result.insert("self".into(), self_ty);
}
result.extend(inst_args.iter().map(|x| (x.name, x.ty)));
args.extend(result.iter().map(|(&a, &b)| (a, b)));
result
},
primitives: primitives_ty,
@ -1917,6 +1919,18 @@ impl TopLevelComposer {
));
}
if simple_name.to_string() != "__init__" {
EscapeAnalyzer::check_function_lifetime(
unifier,
&primitives_ty,
resolver.as_ref().unwrap().clone(),
ctx.as_ref(),
&args,
&fun_body,
ast.as_ref().unwrap().location,
).map_err(|e| format!("Escape analysis error: {}\n ... in function {}", e, name))?;
}
instance_to_stmt.insert(
get_subst_key(unifier, self_type, &subst, Some(&vars.keys().cloned().collect())),
FunInstance {

View File

@ -0,0 +1,515 @@
use slab::Slab;
use std::borrow::Cow;
use std::collections::{HashMap, HashSet};
use nac3parser::ast::{Location, StrRef};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LifetimeKind {
Static,
NonLocal,
Unknown,
PreciseLocal,
ImpreciseLocal,
}
impl std::ops::BitAnd for LifetimeKind {
type Output = Self;
fn bitand(self, rhs: Self) -> Self::Output {
use LifetimeKind::*;
match (self, rhs) {
(x, y) if x == y => x,
(PreciseLocal, ImpreciseLocal) | (ImpreciseLocal, PreciseLocal) => ImpreciseLocal,
(Static, NonLocal) | (NonLocal, Static) => NonLocal,
_ => Unknown,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct LifetimeId(usize);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct BasicBlockId(usize);
#[derive(Debug, Clone)]
pub enum LifetimeIR {
VarAssign { var: StrRef, lifetime: LifetimeId },
VarAccess { var: StrRef },
FieldAssign { obj: LifetimeId, field: StrRef, new: LifetimeId, is_init: bool },
FieldAccess { obj: LifetimeId, field: StrRef },
CreateLifetime { kind: LifetimeKind },
PassedToFunc { param_lifetimes: Vec<LifetimeId> },
UnifyLifetimes { lifetimes: Vec<LifetimeId> },
Branch { targets: Vec<BasicBlockId> },
Return { val: Option<LifetimeId> },
}
pub struct LifetimeIRBuilder {
irs: Vec<Option<(LifetimeIR, Location)>>,
basic_blocks: Vec<Vec<usize>>,
current_block: BasicBlockId,
}
impl LifetimeIRBuilder {
pub fn new() -> Self {
LifetimeIRBuilder {
irs: vec![None],
basic_blocks: vec![vec![]],
current_block: BasicBlockId(0),
}
}
pub fn print_ir(&self) -> String {
let mut lines = vec![];
for (i, bb) in self.basic_blocks.iter().enumerate() {
if bb.is_empty() {
continue;
}
lines.push(format!("{}:", i));
for ir in bb.iter() {
if let Some((inst, loc)) = &self.irs[*ir] {
lines.push(format!(" {}: {:?} ({})", *ir, inst, loc));
}
}
}
lines.join("\n")
}
pub fn append_ir(&mut self, inst: LifetimeIR, loc: Location) -> LifetimeId {
let id = self.irs.len();
self.irs.push(Some((inst, loc)));
self.basic_blocks[self.current_block.0].push(id);
LifetimeId(id)
}
pub fn append_block(&mut self) -> BasicBlockId {
let id = self.basic_blocks.len();
self.basic_blocks.push(vec![]);
BasicBlockId(id)
}
pub fn get_current_block(&self) -> BasicBlockId {
self.current_block
}
pub fn position_at_end(&mut self, id: BasicBlockId) {
self.current_block = id;
}
pub fn is_terminated(&self, id: BasicBlockId) -> bool {
let bb = &self.basic_blocks[id.0];
if bb.is_empty() {
false
} else {
matches!(
self.irs[*bb.last().unwrap()],
Some((LifetimeIR::Return { .. }, _)) | Some((LifetimeIR::Branch { .. }, _))
)
}
}
pub fn remove_empty_bb(&mut self) {
let mut destination_mapping = HashMap::new();
let basic_blocks = &mut self.basic_blocks;
let irs = &mut self.irs;
for (i, bb) in basic_blocks.iter_mut().enumerate() {
bb.retain(|&id| irs[id].is_some());
if bb.len() == 1 {
let id = bb.pop().unwrap();
let ir = irs[id].take().unwrap();
match ir.0 {
LifetimeIR::Branch { targets } => {
destination_mapping.insert(i, targets);
}
_ => (),
}
}
}
let mut buffer = HashSet::new();
for bb in basic_blocks.iter_mut() {
if bb.is_empty() {
continue;
}
if let LifetimeIR::Branch { targets } =
&mut irs[*bb.last().unwrap()].as_mut().unwrap().0
{
buffer.clear();
let mut updated = false;
for target in targets.iter() {
if let Some(dest) = destination_mapping.get(&target.0) {
buffer.extend(dest.iter().cloned());
updated = true;
} else {
buffer.insert(*target);
}
}
if updated {
targets.clear();
targets.extend(buffer.iter().cloned());
}
}
}
}
pub fn analyze(&self) -> Result<(), String> {
let mut analyzers = HashMap::new();
analyzers.insert(0, (0, true, LifetimeAnalyzer::new()));
let mut worklist = vec![0];
while let Some(bb) = worklist.pop() {
let (counter, updated, analyzer) = analyzers.get_mut(&bb).unwrap();
*counter += 1;
if *counter > 100 {
return Err(format!("infinite loop detected at basic block {}", bb));
}
*updated = false;
let mut analyzer = analyzer.clone();
let block = &self.basic_blocks[bb];
let ir_iter = block.iter().filter_map(|&id| {
self.irs[id].as_ref().map(|(ir, loc)| (LifetimeId(id), ir, *loc))
});
if let Some(branch) = analyzer.analyze_basic_block(ir_iter)? {
for &target in branch.iter() {
if let Some((_, updated, successor)) = analyzers.get_mut(&target.0) {
if successor.merge(&analyzer) && !*updated {
// changed
worklist.push(target.0);
*updated = true;
}
} else {
analyzers.insert(target.0, (0, true, analyzer.clone()));
worklist.push(target.0);
}
}
}
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct LifetimeStore {
kind: LifetimeKind,
fields: HashMap<StrRef, LifetimeId>,
lifetimes: HashSet<LifetimeId>,
}
#[derive(Debug, Clone)]
pub struct LifetimeAnalyzer<'a> {
lifetime_to_id: HashMap<LifetimeId, usize>,
lifetime_stores: Slab<Cow<'a, LifetimeStore>>,
variable_assignment: HashMap<StrRef, LifetimeId>,
}
impl<'a> LifetimeAnalyzer<'a> {
pub fn new() -> Self {
let mut zelf = LifetimeAnalyzer {
lifetime_to_id: HashMap::new(),
lifetime_stores: Default::default(),
variable_assignment: HashMap::new(),
};
zelf.add_lifetime(LifetimeId(0), LifetimeKind::Unknown);
zelf
}
pub fn merge(&mut self, other: &LifetimeAnalyzer) -> bool {
let mut to_be_merged = other.lifetime_to_id.keys().cloned().collect::<Vec<_>>();
let mut updated = false;
let mut lifetime_merge_list = vec![];
for (&var_name, &lifetime) in other.variable_assignment.iter() {
if let Some(&our_lifetime) = self.variable_assignment.get(&var_name) {
if our_lifetime != lifetime {
lifetime_merge_list.push((our_lifetime, lifetime));
}
} else {
self.variable_assignment.insert(var_name, lifetime);
updated = true;
}
}
while let Some(lifetime) = to_be_merged.pop() {
let other_store_id = *other.lifetime_to_id.get(&lifetime).unwrap();
if let Some(&self_store_id) = self.lifetime_to_id.get(&lifetime) {
let self_store = self.lifetime_stores.get_mut(self_store_id).unwrap();
let other_store = other.lifetime_stores.get(other_store_id).unwrap();
let self_store = self_store.to_mut();
// merge them
for (&field, &other_lifetime) in other_store.fields.iter() {
if let Some(&self_lifetime) = self_store.fields.get(&field) {
if self_lifetime != other_lifetime {
lifetime_merge_list.push((self_lifetime, other_lifetime));
}
} else {
self_store.fields.insert(field, other_lifetime);
updated = true;
}
}
let zelf_lifetimes = &mut self_store.lifetimes;
for &other_lifetime in other_store.lifetimes.iter() {
if zelf_lifetimes.insert(other_lifetime) {
lifetime_merge_list.push((lifetime, other_lifetime));
}
}
let result_kind = self_store.kind & other_store.kind;
if self_store.kind != result_kind {
self_store.kind = result_kind;
}
} else {
let store = other.lifetime_stores.get(other_store_id).unwrap().as_ref().clone();
let store = self.lifetime_stores.insert(Cow::Owned(store));
self.lifetime_to_id.insert(lifetime, store);
updated = true;
}
}
for (a, b) in lifetime_merge_list.into_iter() {
self.unify(a, b);
}
updated
}
pub fn add_lifetime(&mut self, lifetime: LifetimeId, kind: LifetimeKind) {
let id = self.lifetime_stores.insert(Cow::Owned(LifetimeStore {
kind,
fields: HashMap::new(),
lifetimes: [lifetime].iter().cloned().collect(),
}));
let old_store_id = self.lifetime_to_id.insert(lifetime, id);
if let Some(old_store_id) = old_store_id {
let old_lifetime_store = self.lifetime_stores.get_mut(old_store_id).unwrap().to_mut();
old_lifetime_store.lifetimes.remove(&lifetime);
if old_lifetime_store.lifetimes.is_empty() {
self.lifetime_stores.remove(old_store_id);
}
}
}
pub fn set_lifetime(&mut self, lifetime: LifetimeId, to: LifetimeId) {
let id = *self.lifetime_to_id.get(&to).unwrap();
let store = self.lifetime_stores.get_mut(id).unwrap();
store.to_mut().lifetimes.insert(lifetime);
let old_store_id = self.lifetime_to_id.insert(lifetime, id);
if let Some(old_store_id) = old_store_id {
let old_lifetime_store = self.lifetime_stores.get_mut(old_store_id).unwrap().to_mut();
old_lifetime_store.lifetimes.remove(&lifetime);
if old_lifetime_store.lifetimes.is_empty() {
self.lifetime_stores.remove(old_store_id);
}
}
}
fn unify(&mut self, lhs: LifetimeId, rhs: LifetimeId) {
use LifetimeKind::{ImpreciseLocal, PreciseLocal};
let lhs_id = *self.lifetime_to_id.get(&lhs).unwrap();
let rhs_id = *self.lifetime_to_id.get(&rhs).unwrap();
if lhs_id == rhs_id {
return;
}
let lhs_store = self.lifetime_stores.get(lhs_id).unwrap();
let rhs_store = self.lifetime_stores.get(rhs_id).unwrap();
let all_lifetimes: HashSet<_> =
lhs_store.lifetimes.union(&rhs_store.lifetimes).cloned().collect();
let result_kind = lhs_store.kind & rhs_store.kind;
let fields = if matches!(result_kind, PreciseLocal | ImpreciseLocal) {
let mut need_union = vec![];
let mut fields = lhs_store.fields.clone();
for (k, v) in rhs_store.fields.iter() {
if let Some(old) = fields.insert(*k, *v) {
need_union.push((old, *v));
}
}
drop(lhs_store);
drop(rhs_store);
for (lhs, rhs) in need_union {
self.unify(lhs, rhs);
}
fields
} else {
Default::default()
};
// unify them, slow
for lifetime in all_lifetimes.iter() {
self.lifetime_to_id.insert(*lifetime, lhs_id);
}
*self.lifetime_stores.get_mut(lhs_id).unwrap() =
Cow::Owned(LifetimeStore { kind: result_kind, fields, lifetimes: all_lifetimes });
self.lifetime_stores.remove(rhs_id);
}
fn get_field_lifetime(&mut self, obj: LifetimeId, field: StrRef) -> LifetimeId {
use LifetimeKind::*;
let id = *self.lifetime_to_id.get(&obj).unwrap();
let store = self.lifetime_stores.get(id).unwrap();
if matches!(store.kind, PreciseLocal | ImpreciseLocal) {
if let Some(&lifetime) = store.fields.get(&field) {
let field_lifetime_kind = self.get_lifetime_kind(lifetime);
if field_lifetime_kind == PreciseLocal
&& (store.kind == ImpreciseLocal || field == "$elem".into())
{
let id = *self.lifetime_to_id.get(&lifetime).unwrap();
self.lifetime_stores.get_mut(id).unwrap().to_mut().kind = ImpreciseLocal;
}
lifetime
} else {
LifetimeId(0)
}
} else {
obj
}
}
fn set_field_lifetime(
&mut self,
obj: LifetimeId,
field: StrRef,
field_lifetime: LifetimeId,
is_init: bool,
) -> Result<(), String> {
use LifetimeKind::*;
let obj_id = *self.lifetime_to_id.get(&obj).unwrap();
let field_id = *self.lifetime_to_id.get(&field_lifetime).unwrap();
let field_lifetime_kind = self.lifetime_stores.get(field_id).unwrap().kind;
let obj_store = self.lifetime_stores.get_mut(obj_id).unwrap();
if !matches!(
(obj_store.kind, field_lifetime_kind),
(PreciseLocal, _) | (ImpreciseLocal, _) | (_, Static)
) {
return Err("field lifetime error".into());
}
match obj_store.kind {
// $elem means list elements
PreciseLocal if field != "$elem".into() => {
// strong update
obj_store.to_mut().fields.insert(field, field_lifetime);
}
PreciseLocal | ImpreciseLocal => {
// weak update
let old_lifetime = obj_store.to_mut().fields.get(&field).copied();
if let Some(old_lifetime) = old_lifetime {
self.unify(old_lifetime, field_lifetime);
} else {
obj_store.to_mut().fields.insert(field, field_lifetime);
if !is_init {
// unify with unknown lifetime
self.unify(LifetimeId(0), field_lifetime);
}
if field == "$elem".into() {
let field_lifetime_id = *self.lifetime_to_id.get(&field_lifetime).unwrap();
let field_lifetime = self.lifetime_stores.get_mut(field_lifetime_id).unwrap();
if field_lifetime.kind == PreciseLocal {
field_lifetime.to_mut().kind = ImpreciseLocal;
}
}
}
}
_ => (),
}
Ok(())
}
fn get_lifetime_kind(&self, lifetime: LifetimeId) -> LifetimeKind {
self.lifetime_stores.get(*self.lifetime_to_id.get(&lifetime).unwrap()).unwrap().kind
}
fn pass_function_params(&mut self, lifetimes: &[LifetimeId]) {
use LifetimeKind::*;
let mut visited = HashSet::new();
let mut worklist = vec![];
fn add_fields_to_worklist(
visited: &mut HashSet<LifetimeId>,
worklist: &mut Vec<(LifetimeId, bool)>,
fields: &HashMap<StrRef, LifetimeId>,
) {
for (&name, &field) in fields.iter() {
if visited.insert(field) {
// not visited previously
let name = name.to_string();
let mutable = !(name.starts_with("$elem") && name.len() != "$elem".len());
worklist.push((field, mutable));
}
}
}
for lifetime in lifetimes.iter() {
let lifetime =
self.lifetime_stores.get_mut(*self.lifetime_to_id.get(lifetime).unwrap()).unwrap();
add_fields_to_worklist(&mut visited, &mut worklist, &lifetime.fields);
}
while let Some((item, mutable)) = worklist.pop() {
let lifetime =
self.lifetime_stores.get_mut(*self.lifetime_to_id.get(&item).unwrap()).unwrap();
if matches!(lifetime.kind, Unknown | Static) {
continue;
}
add_fields_to_worklist(&mut visited, &mut worklist, &lifetime.fields);
if mutable {
// we may assign values with static lifetime to function params
lifetime.to_mut().kind = lifetime.kind & Static;
}
}
}
pub fn analyze_basic_block<'b, I: Iterator<Item = (LifetimeId, &'b LifetimeIR, Location)>>(
&mut self,
instructions: I,
) -> Result<Option<&'b [BasicBlockId]>, String> {
use LifetimeIR::*;
for (id, inst, loc) in instructions {
match inst {
VarAssign { var, lifetime } => {
self.variable_assignment.insert(*var, *lifetime);
}
VarAccess { var } => {
let lifetime = self.variable_assignment.get(var).cloned();
if let Some(lifetime) = lifetime {
self.set_lifetime(id, lifetime);
} else {
// should be static lifetime
self.add_lifetime(id, LifetimeKind::Static)
}
}
FieldAssign { obj, field, new, is_init } => {
self.set_field_lifetime(*obj, *field, *new, *is_init)
.map_err(|e| format!("{} in {}", e, loc))?;
}
FieldAccess { obj, field } => {
let lifetime = self.get_field_lifetime(*obj, *field);
self.set_lifetime(id, lifetime);
}
CreateLifetime { kind } => {
if *kind == LifetimeKind::Unknown {
self.set_lifetime(id, LifetimeId(0));
} else {
self.add_lifetime(id, *kind);
}
}
PassedToFunc { param_lifetimes } => {
self.pass_function_params(param_lifetimes);
}
UnifyLifetimes { lifetimes } => {
assert!(!lifetimes.is_empty());
let lhs = lifetimes[0];
for rhs in lifetimes[1..].iter() {
self.unify(lhs, *rhs);
}
self.set_lifetime(id, lhs);
}
Return { val } => {
if let Some(val) = val {
let kind = self.get_lifetime_kind(*val);
if !matches!(kind, LifetimeKind::Static | LifetimeKind::NonLocal) {
return Err(format!("return value lifetime error in {}", loc));
}
}
return Ok(None);
}
Branch { targets } => return Ok(Some(targets)),
}
}
Ok(None)
}
}

View File

@ -0,0 +1,580 @@
use std::sync::Arc;
use itertools::chain;
use nac3parser::ast::{Comprehension, Constant, Expr, ExprKind, Location, Stmt, StmtKind, StrRef};
use lifetime::{BasicBlockId, LifetimeIR, LifetimeIRBuilder, LifetimeId, LifetimeKind};
use crate::{
symbol_resolver::SymbolResolver,
toplevel::{TopLevelContext, TopLevelDef},
};
use super::{
type_inferencer::PrimitiveStore,
typedef::{Type, TypeEnum, Unifier},
};
#[cfg(test)]
mod test;
mod lifetime;
pub struct EscapeAnalyzer<'a> {
builder: LifetimeIRBuilder,
loop_head: Option<BasicBlockId>,
loop_tail: Option<BasicBlockId>,
unifier: &'a mut Unifier,
primitive_store: &'a PrimitiveStore,
resolver: Arc<dyn SymbolResolver + Send + Sync>,
top_level: &'a TopLevelContext,
}
impl<'a> EscapeAnalyzer<'a> {
pub fn new(
unifier: &'a mut Unifier,
primitive_store: &'a PrimitiveStore,
resolver: Arc<dyn SymbolResolver + Send + Sync>,
top_level: &'a TopLevelContext,
) -> Self {
Self {
builder: LifetimeIRBuilder::new(),
loop_head: None,
loop_tail: None,
primitive_store,
unifier,
resolver,
top_level,
}
}
pub fn check_function_lifetime(
unifier: &'a mut Unifier,
primitive_store: &'a PrimitiveStore,
resolver: Arc<dyn SymbolResolver + Send + Sync>,
top_level: &'a TopLevelContext,
args: &[(StrRef, Type)],
body: &[Stmt<Option<Type>>],
loc: Location,
) -> Result<(), String> {
use LifetimeIR::{CreateLifetime, VarAssign};
let mut zelf = Self::new(unifier, primitive_store, resolver, top_level);
let nonlocal_lifetime =
zelf.builder.append_ir(CreateLifetime { kind: LifetimeKind::NonLocal }, loc);
for (name, ty) in args.iter().copied() {
if zelf.need_alloca(ty) {
zelf.builder.append_ir(VarAssign { var: name, lifetime: nonlocal_lifetime }, loc);
}
}
zelf.handle_statements(body)?;
zelf.builder.remove_empty_bb();
zelf.builder.analyze().map_err(|e| {
format!("{}\nIR: {}", e, zelf.builder.print_ir())
})
}
fn need_alloca(&mut self, ty: Type) -> bool {
!(self.unifier.unioned(ty, self.primitive_store.int32)
|| self.unifier.unioned(ty, self.primitive_store.int64)
|| self.unifier.unioned(ty, self.primitive_store.uint32)
|| self.unifier.unioned(ty, self.primitive_store.uint64)
|| self.unifier.unioned(ty, self.primitive_store.float)
|| self.unifier.unioned(ty, self.primitive_store.bool)
|| self.unifier.unioned(ty, self.primitive_store.none)
|| self.unifier.unioned(ty, self.primitive_store.range))
}
fn is_terminated(&self) -> bool {
self.builder.is_terminated(self.builder.get_current_block())
}
fn handle_unknown_function_call<P: std::borrow::Borrow<Expr<Option<Type>>>>(
&mut self,
params: &[P],
ret_need_alloca: bool,
loc: Location,
) -> Result<Option<LifetimeId>, String> {
let param_lifetimes = params
.iter()
.filter_map(|p| self.handle_expr(p.borrow()).transpose())
.collect::<Result<Vec<_>, _>>()?;
self.builder.append_ir(LifetimeIR::PassedToFunc { param_lifetimes }, loc);
if ret_need_alloca {
Ok(Some(
self.builder
.append_ir(LifetimeIR::CreateLifetime { kind: LifetimeKind::Unknown }, loc),
))
} else {
Ok(None)
}
}
fn handle_expr(&mut self, expr: &Expr<Option<Type>>) -> Result<Option<LifetimeId>, String> {
use LifetimeIR::*;
use LifetimeKind::*;
let need_alloca = self.need_alloca(expr.custom.unwrap());
let loc = expr.location;
Ok(match &expr.node {
ExprKind::Name { id, .. } => {
if need_alloca {
Some(self.builder.append_ir(VarAccess { var: *id }, loc))
} else {
None
}
}
ExprKind::Attribute { value, attr, .. } => {
if need_alloca {
let val = self.handle_expr(value)?.unwrap();
Some(self.builder.append_ir(FieldAccess { obj: val, field: *attr }, loc))
} else {
self.handle_expr(value)?;
None
}
}
ExprKind::Constant { .. } => {
if need_alloca {
Some(self.builder.append_ir(CreateLifetime { kind: Static }, loc))
} else {
None
}
}
ExprKind::List { elts, .. } => {
let elems =
elts.iter().map(|e| self.handle_expr(e)).collect::<Result<Vec<_>, _>>()?;
let list_lifetime =
self.builder.append_ir(CreateLifetime { kind: PreciseLocal }, loc);
if !elems.is_empty() {
if elems[0].is_some() {
let elems = elems.into_iter().map(|e| e.unwrap()).collect::<Vec<_>>();
let elem_lifetime =
self.builder.append_ir(UnifyLifetimes { lifetimes: elems }, loc);
self.builder.append_ir(
FieldAssign {
obj: list_lifetime,
field: "$elem".into(),
new: elem_lifetime,
is_init: true,
},
loc,
);
}
} else {
let elem_lifetime =
self.builder.append_ir(CreateLifetime { kind: PreciseLocal }, loc);
self.builder.append_ir(
FieldAssign {
obj: list_lifetime,
field: "$elem".into(),
new: elem_lifetime,
is_init: true,
},
loc,
);
}
Some(list_lifetime)
}
ExprKind::Tuple { elts, .. } => {
let elems =
elts.iter().map(|e| self.handle_expr(e)).collect::<Result<Vec<_>, _>>()?;
let tuple_lifetime =
self.builder.append_ir(CreateLifetime { kind: PreciseLocal }, loc);
for (i, lifetime) in elems.into_iter().enumerate() {
if let Some(lifetime) = lifetime {
self.builder.append_ir(
FieldAssign {
obj: tuple_lifetime,
field: format!("$elem{}", i).into(),
new: lifetime,
is_init: true,
},
loc,
);
}
}
Some(tuple_lifetime)
}
ExprKind::Subscript { value, slice, .. } => {
let value_lifetime = self.handle_expr(value)?.unwrap();
match &slice.node {
ExprKind::Slice { lower, upper, step } => {
for expr in [lower, upper, step].iter().filter_map(|x| x.as_ref()) {
self.handle_expr(expr)?;
}
let slice_lifetime =
self.builder.append_ir(CreateLifetime { kind: PreciseLocal }, loc);
let slice_elem = self.builder.append_ir(
FieldAccess { obj: value_lifetime, field: "$elem".into() },
loc,
);
self.builder.append_ir(
FieldAssign {
obj: slice_lifetime,
field: "$elem".into(),
new: slice_elem,
is_init: true
},
loc,
);
Some(slice_lifetime)
}
ExprKind::Constant { value: Constant::Int(v), .. }
if matches!(
&*self.unifier.get_ty(value.custom.unwrap()),
TypeEnum::TTuple { .. }
) =>
{
Some(self.builder.append_ir(
FieldAccess {
obj: value_lifetime,
field: format!("$elem{}", v).into(),
},
loc,
))
}
_ => {
self.handle_expr(slice)?;
if need_alloca {
Some(self.builder.append_ir(
FieldAccess { obj: value_lifetime, field: "$elem".into() },
loc,
))
} else {
None
}
}
}
}
ExprKind::Call { func, args, keywords } => {
let mut lifetimes = vec![];
for arg in chain!(args.iter(), keywords.iter().map(|k| k.node.value.as_ref())) {
if let Some(lifetime) = self.handle_expr(arg)? {
lifetimes.push(lifetime);
}
}
match &func.node {
ExprKind::Name { id, .. } => {
if !lifetimes.is_empty() {
self.builder.append_ir(PassedToFunc { param_lifetimes: lifetimes }, loc);
}
if need_alloca {
let id = self
.resolver
.get_identifier_def(*id)
.map_err(|e| format!("{} (at {})", e, func.location))?;
if let TopLevelDef::Class { .. } =
&*self.top_level.definitions.read()[id.0].read()
{
Some(
self.builder
.append_ir(CreateLifetime { kind: PreciseLocal }, loc),
)
} else {
Some(self.builder.append_ir(CreateLifetime { kind: Unknown }, loc))
}
} else {
None
}
}
ExprKind::Attribute { value, .. } => {
let obj_lifetime = self.handle_expr(value)?.unwrap();
lifetimes.push(obj_lifetime);
self.builder.append_ir(PassedToFunc { param_lifetimes: lifetimes }, loc);
if need_alloca {
Some(self.builder.append_ir(CreateLifetime { kind: Unknown }, loc))
} else {
None
}
}
_ => unimplemented!(),
}
}
ExprKind::BinOp { left, right, .. } => self.handle_unknown_function_call(
&[left.as_ref(), right.as_ref()],
need_alloca,
loc,
)?,
ExprKind::BoolOp { values, .. } => {
self.handle_unknown_function_call(&values, need_alloca, loc)?
}
ExprKind::UnaryOp { operand, .. } => {
self.handle_unknown_function_call(&[operand.as_ref()], need_alloca, loc)?
}
ExprKind::Compare { left, comparators, .. } => {
self.handle_unknown_function_call(&[left.as_ref()], false, loc)?;
self.handle_unknown_function_call(&comparators, need_alloca, loc)?
}
ExprKind::IfExp { test, body, orelse } => {
self.handle_expr(test)?;
let body_bb = self.builder.append_block();
let else_bb = self.builder.append_block();
let tail_bb = self.builder.append_block();
self.builder.append_ir(Branch { targets: vec![body_bb, else_bb] }, test.location);
self.builder.position_at_end(body_bb);
let body_lifetime = self.handle_expr(body)?;
self.builder.append_ir(Branch { targets: vec![tail_bb] }, body.location);
self.builder.position_at_end(else_bb);
let else_lifetime = self.handle_expr(body)?;
self.builder.append_ir(Branch { targets: vec![tail_bb] }, orelse.location);
self.builder.position_at_end(tail_bb);
if let (Some(body_lifetime), Some(else_lifetime)) = (body_lifetime, else_lifetime) {
Some(self.builder.append_ir(
UnifyLifetimes { lifetimes: vec![body_lifetime, else_lifetime] },
loc,
))
} else {
None
}
}
ExprKind::ListComp { elt, generators } => {
let Comprehension { target, iter, ifs, .. } = &generators[0];
let list_lifetime =
self.builder.append_ir(CreateLifetime { kind: PreciseLocal }, loc);
let iter_elem_lifetime = self.handle_expr(iter)?.map(|obj| {
self.builder
.append_ir(FieldAccess { obj, field: "$elem".into() }, iter.location)
});
let loop_body = self.builder.append_block();
let loop_tail = self.builder.append_block();
self.builder.append_ir(Branch { targets: vec![loop_body] }, loc);
self.builder.position_at_end(loop_body);
self.handle_assignment(target, iter_elem_lifetime)?;
for ifexpr in ifs.iter() {
self.handle_expr(ifexpr)?;
}
let elem_lifetime = self.handle_expr(elt)?;
if let Some(elem_lifetime) = elem_lifetime {
self.builder.append_ir(
FieldAssign {
obj: list_lifetime,
field: "$elem".into(),
new: elem_lifetime,
is_init: true
},
elt.location,
);
}
self.builder.append_ir(Branch { targets: vec![loop_body, loop_tail] }, loc);
self.builder.position_at_end(loop_tail);
Some(list_lifetime)
}
_ => unimplemented!(),
})
}
fn handle_assignment(
&mut self,
lhs: &Expr<Option<Type>>,
rhs_lifetime: Option<LifetimeId>,
) -> Result<(), String> {
use LifetimeIR::*;
match &lhs.node {
ExprKind::Attribute { value, attr, .. } => {
let value_lifetime = self.handle_expr(value)?.unwrap();
if let Some(field_lifetime) = rhs_lifetime {
self.builder.append_ir(
FieldAssign { obj: value_lifetime, field: *attr, new: field_lifetime, is_init: false },
lhs.location,
);
}
}
ExprKind::Subscript { value, slice, .. } => {
let value_lifetime = self.handle_expr(value)?.unwrap();
let elem_lifetime = if let ExprKind::Slice { lower, upper, step } = &slice.node {
for expr in [lower, upper, step].iter().filter_map(|x| x.as_ref()) {
self.handle_expr(expr)?;
}
if let Some(rhs_lifetime) = rhs_lifetime {
// must be a list
Some(self.builder.append_ir(
FieldAccess { obj: rhs_lifetime, field: "$elem".into() },
lhs.location,
))
} else {
None
}
} else {
self.handle_expr(slice)?;
rhs_lifetime
};
// must be a list
if let Some(elem_lifetime) = elem_lifetime {
self.builder.append_ir(
FieldAssign {
obj: value_lifetime,
field: "$elem".into(),
new: elem_lifetime,
is_init: false
},
lhs.location,
);
}
}
ExprKind::Name { id, .. } => {
if let Some(lifetime) = rhs_lifetime {
self.builder.append_ir(VarAssign { var: *id, lifetime }, lhs.location);
}
}
ExprKind::Tuple { elts, .. } => {
let rhs_lifetime = rhs_lifetime.unwrap();
for (i, e) in elts.iter().enumerate() {
let elem_lifetime = self.builder.append_ir(
FieldAccess { obj: rhs_lifetime, field: format!("$elem{}", i).into() },
e.location,
);
self.handle_assignment(e, Some(elem_lifetime))?;
}
}
_ => unreachable!(),
}
Ok(())
}
fn handle_statement(&mut self, stmt: &Stmt<Option<Type>>) -> Result<(), String> {
use LifetimeIR::*;
match &stmt.node {
StmtKind::Expr { value, .. } => {
self.handle_expr(value)?;
}
StmtKind::Assign { targets, value, .. } => {
let rhs_lifetime = self.handle_expr(value)?;
for target in targets {
self.handle_assignment(target, rhs_lifetime)?;
}
}
StmtKind::If { test, body, orelse, .. } => {
// test should return bool
self.handle_expr(test)?;
let body_bb = self.builder.append_block();
let else_bb = self.builder.append_block();
self.builder.append_ir(Branch { targets: vec![body_bb, else_bb] }, stmt.location);
self.builder.position_at_end(body_bb);
self.handle_statements(&body)?;
let body_terminated = self.is_terminated();
if orelse.is_empty() {
if !body_terminated {
// else_bb is the basic block after this if statement
self.builder.append_ir(Branch { targets: vec![else_bb] }, stmt.location);
self.builder.position_at_end(else_bb);
}
} else {
let tail_bb = self.builder.append_block();
if !body_terminated {
self.builder.append_ir(Branch { targets: vec![tail_bb] }, stmt.location);
}
self.builder.position_at_end(else_bb);
self.handle_statements(&orelse)?;
if !self.is_terminated() {
self.builder.append_ir(Branch { targets: vec![tail_bb] }, stmt.location);
}
self.builder.position_at_end(tail_bb);
}
}
StmtKind::While { test, body, orelse, .. } => {
let old_loop_head = self.loop_head;
let old_loop_tail = self.loop_tail;
let loop_head = self.builder.append_block();
let loop_body = self.builder.append_block();
let loop_else =
if orelse.is_empty() { None } else { Some(self.builder.append_block()) };
let loop_tail = self.builder.append_block();
self.loop_head = Some(loop_head);
self.loop_tail = Some(loop_tail);
self.builder.append_ir(Branch { targets: vec![loop_head] }, stmt.location);
self.builder.position_at_end(loop_head);
self.handle_expr(test)?;
self.builder.append_ir(
Branch { targets: vec![loop_body, loop_else.unwrap_or(loop_tail)] },
stmt.location,
);
self.builder.position_at_end(loop_body);
self.handle_statements(&body)?;
if !self.is_terminated() {
self.builder.append_ir(Branch { targets: vec![loop_head] }, stmt.location);
}
self.loop_head = old_loop_head;
self.loop_tail = old_loop_tail;
if let Some(loop_else) = loop_else {
self.builder.position_at_end(loop_else);
self.handle_statements(&orelse)?;
if !self.is_terminated() {
self.builder.append_ir(Branch { targets: vec![loop_tail] }, stmt.location);
}
}
self.builder.position_at_end(loop_tail);
}
StmtKind::For { target, iter, body, orelse, .. } => {
let old_loop_head = self.loop_head;
let old_loop_tail = self.loop_tail;
let loop_head = self.builder.append_block();
let loop_body = self.builder.append_block();
let loop_else =
if orelse.is_empty() { None } else { Some(self.builder.append_block()) };
let loop_tail = self.builder.append_block();
self.loop_head = Some(loop_head);
self.loop_tail = Some(loop_tail);
let iter_lifetime = self.handle_expr(iter)?.map(|obj| {
self.builder
.append_ir(FieldAccess { obj, field: "$elem".into() }, iter.location)
});
self.builder.append_ir(Branch { targets: vec![loop_head] }, stmt.location);
self.builder.position_at_end(loop_head);
if let Some(iter_lifetime) = iter_lifetime {
self.handle_assignment(target, Some(iter_lifetime))?;
}
self.builder.append_ir(
Branch { targets: vec![loop_body, loop_else.unwrap_or(loop_tail)] },
stmt.location,
);
self.builder.position_at_end(loop_body);
self.handle_statements(&body)?;
if !self.is_terminated() {
self.builder.append_ir(Branch { targets: vec![loop_head] }, stmt.location);
}
self.loop_head = old_loop_head;
self.loop_tail = old_loop_tail;
if let Some(loop_else) = loop_else {
self.builder.position_at_end(loop_else);
self.handle_statements(&orelse)?;
if !self.is_terminated() {
self.builder.append_ir(Branch { targets: vec![loop_tail] }, stmt.location);
}
}
self.builder.position_at_end(loop_tail);
}
StmtKind::Continue { .. } => {
if let Some(loop_head) = self.loop_head {
self.builder.append_ir(Branch { targets: vec![loop_head] }, stmt.location);
} else {
return Err(format!("break outside loop"));
}
}
StmtKind::Break { .. } => {
if let Some(loop_tail) = self.loop_tail {
self.builder.append_ir(Branch { targets: vec![loop_tail] }, stmt.location);
} else {
return Err(format!("break outside loop"));
}
}
StmtKind::Return { value, .. } => {
let val = if let Some(value) = value { self.handle_expr(value)? } else { None };
self.builder.append_ir(Return { val }, stmt.location);
}
StmtKind::Pass { .. } => {}
_ => unimplemented!("{:?}", stmt.node),
}
Ok(())
}
fn handle_statements(&mut self, stmts: &[Stmt<Option<Type>>]) -> Result<(), String> {
for stmt in stmts.iter() {
if self.builder.is_terminated(self.builder.get_current_block()) {
break;
}
self.handle_statement(stmt)?;
}
Ok(())
}
}

View File

@ -0,0 +1,60 @@
use super::EscapeAnalyzer;
use crate::typecheck::{type_inferencer::test::TestEnvironment, typedef::TypeEnum};
use indoc::indoc;
use nac3parser::ast::fold::Fold;
use std::collections::hash_set::HashSet;
use test_case::test_case;
use nac3parser::parser::parse_program;
#[test_case(indoc! {"
# a: list[list[int32]]
b = [1]
a[0] = b
"}, Err("field lifetime error in unknown: line 3 column 2".into())
; "assign global elem")]
#[test_case(indoc! {"
# a: list[list[int32]]
b = [[], []]
b[1] = a
b[0][0] = [0]
"}, Err("field lifetime error in unknown: line 4 column 5".into())
; "global unify")]
#[test_case(indoc! {"
b = [1, 2, 3]
c = [a]
c[0][0] = b
"}, Err("field lifetime error in unknown: line 3 column 5".into())
; "global unify 2")]
fn test_simple(source: &str, expected_result: Result<(), String>) {
let mut env = TestEnvironment::basic_test_env();
let mut defined_identifiers: HashSet<_> = env.identifier_mapping.keys().cloned().collect();
defined_identifiers.insert("a".into());
let mut inferencer = env.get_inferencer();
inferencer.defined_identifiers = defined_identifiers.clone();
let list_int = inferencer.unifier.add_ty(TypeEnum::TList { ty: inferencer.primitives.int32 });
let list_list_int = inferencer.unifier.add_ty(TypeEnum::TList { ty: list_int });
inferencer.variable_mapping.insert("a".into(), list_list_int);
let statements = parse_program(source, Default::default()).unwrap();
let statements = statements
.into_iter()
.map(|v| inferencer.fold_stmt(v))
.collect::<Result<Vec<_>, _>>()
.unwrap();
inferencer.check_block(&statements, &mut defined_identifiers).unwrap();
let mut lifetime_ctx = EscapeAnalyzer::new(
&mut inferencer.unifier,
&mut inferencer.primitives,
inferencer.function_data.resolver.clone(),
&inferencer.top_level,
);
lifetime_ctx.handle_statements(&statements).unwrap();
lifetime_ctx.builder.remove_empty_bb();
let result = lifetime_ctx.builder.analyze();
assert_eq!(result, expected_result);
}

View File

@ -4,3 +4,4 @@ pub mod type_error;
pub mod type_inferencer;
pub mod typedef;
mod unification_table;
pub mod escape_analysis;

View File

@ -14,7 +14,7 @@ use nac3parser::ast::{
};
#[cfg(test)]
mod test;
pub(crate) mod test;
#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
pub struct CodeLocation {

View File

@ -11,7 +11,7 @@ use nac3parser::parser::parse_program;
use parking_lot::RwLock;
use test_case::test_case;
struct Resolver {
pub(crate) struct Resolver {
id_to_type: HashMap<StrRef, Type>,
id_to_def: HashMap<StrRef, DefinitionId>,
class_names: HashMap<StrRef, Type>,
@ -56,7 +56,7 @@ impl SymbolResolver for Resolver {
}
}
struct TestEnvironment {
pub(crate) struct TestEnvironment {
pub unifier: Unifier,
pub function_data: FunctionData,
pub primitives: PrimitiveStore,
@ -192,7 +192,7 @@ impl TestEnvironment {
}
}
fn new() -> TestEnvironment {
pub fn new() -> TestEnvironment {
let mut unifier = Unifier::new();
let mut identifier_mapping = HashMap::new();
let mut top_level_defs: Vec<Arc<RwLock<TopLevelDef>>> = Vec::new();
@ -447,7 +447,7 @@ impl TestEnvironment {
}
}
fn get_inferencer(&mut self) -> Inferencer {
pub fn get_inferencer(&mut self) -> Inferencer {
Inferencer {
top_level: &self.top_level,
function_data: &mut self.function_data,

View File

@ -3,7 +3,7 @@ use std::rc::Rc;
use itertools::izip;
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
pub struct UnificationKey(usize);
pub struct UnificationKey(pub(crate) usize);
#[derive(Clone)]
pub struct UnificationTable<V> {
@ -44,6 +44,12 @@ impl<V> UnificationTable<V> {
UnificationTable { parents: Vec::new(), ranks: Vec::new(), values: Vec::new(), log: Vec::new(), generation: 0 }
}
fn log_action(&mut self, action: Action<V>) {
if !self.log.is_empty() {
self.log.push(action);
}
}
pub fn new_key(&mut self, v: V) -> UnificationKey {
let index = self.parents.len();
self.parents.push(index);
@ -61,10 +67,10 @@ impl<V> UnificationTable<V> {
if self.ranks[a] < self.ranks[b] {
std::mem::swap(&mut a, &mut b);
}
self.log.push(Action::Parent { key: b, original_parent: self.parents[b] });
self.log_action(Action::Parent { key: b, original_parent: self.parents[b] });
self.parents[b] = a;
if self.ranks[a] == self.ranks[b] {
self.log.push(Action::Rank { key: a, original_rank: self.ranks[a] });
self.log_action(Action::Rank { key: a, original_rank: self.ranks[a] });
self.ranks[a] += 1;
}
}
@ -88,7 +94,7 @@ impl<V> UnificationTable<V> {
pub fn set_value(&mut self, a: UnificationKey, v: V) {
let index = self.find(a);
let original_value = self.values[index].replace(v);
self.log.push(Action::Value { key: index, original_value });
self.log_action(Action::Value { key: index, original_value });
}
pub fn unioned(&mut self, a: UnificationKey, b: UnificationKey) -> bool {
@ -106,7 +112,7 @@ impl<V> UnificationTable<V> {
// a = parent.parent
let a = self.parents[parent];
// root.parent = parent.parent
self.log.push(Action::Parent { key: root, original_parent: self.parents[root] });
self.log_action(Action::Parent { key: root, original_parent: self.parents[root] });
self.parents[root] = a;
root = parent;
// parent = root.parent

View File

@ -220,7 +220,11 @@ fn main() {
let signature = store.from_signature(&mut composer.unifier, &primitive, &signature, &mut cache);
let signature = store.add_cty(signature);
composer.start_analysis(true).unwrap();
if let Err(e) = composer.start_analysis(true) {
eprintln!("{}", e);
std::process::exit(1);
}
let top_level = Arc::new(composer.make_top_level_context());