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, loop_tail: Option, unifier: &'a mut Unifier, primitive_store: &'a PrimitiveStore, resolver: Arc, top_level: &'a TopLevelContext, } impl<'a> EscapeAnalyzer<'a> { pub fn new( unifier: &'a mut Unifier, primitive_store: &'a PrimitiveStore, resolver: Arc, 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, top_level: &'a TopLevelContext, args: &[(StrRef, Type)], body: &[Stmt>], 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>>>( &mut self, params: &[P], ret_need_alloca: bool, loc: Location, ) -> Result, String> { let param_lifetimes = params .iter() .filter_map(|p| self.handle_expr(p.borrow()).transpose()) .collect::, _>>()?; 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>) -> Result, 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::, _>>()?; 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::>(); 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::, _>>()?; 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>, rhs_lifetime: Option, ) -> 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>) -> 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>]) -> Result<(), String> { for stmt in stmts.iter() { if self.builder.is_terminated(self.builder.get_current_block()) { break; } self.handle_statement(stmt)?; } Ok(()) } }