use crate::parser::*;
use std::collections::HashSet;
#[cfg(test)]
use crate::parser::{Literal, MacroDelimiter};
#[derive(Debug, Clone, Default)]
pub struct EscapeAnalysisStats {
pub vectors_stack_allocated: usize,
pub strings_inlined: usize,
pub boxes_unboxed: usize,
pub total_optimizations: usize,
}
impl EscapeAnalysisStats {
pub fn add(&mut self, other: &EscapeAnalysisStats) {
self.vectors_stack_allocated += other.vectors_stack_allocated;
self.strings_inlined += other.strings_inlined;
self.boxes_unboxed += other.boxes_unboxed;
self.total_optimizations += other.total_optimizations;
}
}
pub fn optimize_escape_analysis<'ast>(
program: &Program<'ast>,
optimizer: &crate::optimizer::Optimizer,
) -> (Program<'ast>, EscapeAnalysisStats) {
let mut stats = EscapeAnalysisStats::default();
let mut new_items = Vec::new();
for item in &program.items {
let new_item = match item {
Item::Function { decl: func, .. } => {
let (new_func, func_stats) = optimize_function_escape_analysis(func, optimizer);
stats.add(&func_stats);
Item::Function {
decl: new_func,
location: None,
}
}
Item::Impl {
block: impl_block, ..
} => {
let (new_impl, impl_stats) = optimize_impl_escape_analysis(impl_block, optimizer);
stats.add(&impl_stats);
Item::Impl {
block: new_impl,
location: None,
}
}
_ => item.clone(),
};
new_items.push(new_item);
}
(Program { items: new_items }, stats)
}
fn optimize_function_escape_analysis<'ast>(
func: &FunctionDecl<'ast>,
optimizer: &crate::optimizer::Optimizer,
) -> (FunctionDecl<'ast>, EscapeAnalysisStats) {
let mut stats = EscapeAnalysisStats::default();
let escape_info = analyze_escapes(&func.body, &func.parameters);
let new_body =
optimize_statements_escape_analysis(&func.body, &escape_info, &mut stats, optimizer);
(
FunctionDecl {
body: new_body,
..func.clone()
},
stats,
)
}
fn optimize_impl_escape_analysis<'ast>(
impl_block: &ImplBlock<'ast>,
optimizer: &crate::optimizer::Optimizer,
) -> (ImplBlock<'ast>, EscapeAnalysisStats) {
let mut stats = EscapeAnalysisStats::default();
let mut new_functions = Vec::new();
for func in &impl_block.functions {
let (new_func, func_stats) = optimize_function_escape_analysis(func, optimizer);
stats.add(&func_stats);
new_functions.push(new_func);
}
(
ImplBlock {
functions: new_functions,
..impl_block.clone()
},
stats,
)
}
struct EscapeInfo {
escaped_vars: HashSet<String>,
returned_vars: HashSet<String>,
stored_vars: HashSet<String>,
closure_captured_vars: HashSet<String>,
}
fn analyze_escapes<'ast>(
body: &[&'ast Statement<'ast>],
parameters: &[Parameter<'ast>],
) -> EscapeInfo {
let mut info = EscapeInfo {
escaped_vars: HashSet::new(),
returned_vars: HashSet::new(),
stored_vars: HashSet::new(),
closure_captured_vars: HashSet::new(),
};
for param in parameters {
info.escaped_vars.insert(param.name.clone());
}
analyze_statements_for_escapes(body, &mut info);
info.escaped_vars.extend(info.returned_vars.iter().cloned());
info.escaped_vars.extend(info.stored_vars.iter().cloned());
info.escaped_vars
.extend(info.closure_captured_vars.iter().cloned());
info
}
fn analyze_statements_for_escapes<'ast>(stmts: &[&'ast Statement<'ast>], info: &mut EscapeInfo) {
for stmt in stmts {
match stmt {
Statement::Return {
value: Some(expr), ..
} => {
collect_variables_in_expression(expr, &mut info.returned_vars);
}
Statement::Let { value, .. } | Statement::Const { value, .. } => {
if let Expression::Identifier { name, .. } = value {
info.escaped_vars.insert(name.clone());
}
}
Statement::Expression { expr, .. } => {
if let Expression::FieldAccess { .. } = expr {
collect_variables_in_expression(expr, &mut info.stored_vars);
}
}
Statement::If {
then_block,
else_block,
..
} => {
analyze_statements_for_escapes(then_block, info);
if let Some(else_stmts) = else_block {
analyze_statements_for_escapes(else_stmts, info);
}
}
Statement::While { body, .. } | Statement::For { body, .. } => {
analyze_statements_for_escapes(body, info);
}
Statement::Match { arms, .. } => {
for arm in arms {
collect_variables_in_expression(arm.body, &mut info.returned_vars);
}
}
_ => {}
}
}
}
fn collect_variables_in_expression<'ast>(expr: &'ast Expression<'ast>, vars: &mut HashSet<String>) {
match expr {
Expression::Identifier { name, .. } => {
vars.insert(name.clone());
}
Expression::Binary { left, right, .. } => {
collect_variables_in_expression(left, vars);
collect_variables_in_expression(right, vars);
}
Expression::Unary { operand, .. } => {
collect_variables_in_expression(operand, vars);
}
Expression::MethodCall {
object, arguments, ..
} => {
collect_variables_in_expression(object, vars);
for (_, arg) in arguments {
collect_variables_in_expression(arg, vars);
}
}
Expression::FieldAccess { object, .. } => {
collect_variables_in_expression(object, vars);
}
Expression::Index { object, index, .. } => {
collect_variables_in_expression(object, vars);
collect_variables_in_expression(index, vars);
}
Expression::Call { arguments, .. } => {
for (_, arg) in arguments {
collect_variables_in_expression(arg, vars);
}
}
Expression::Closure { body, .. } => {
collect_variables_in_expression(body, vars);
}
_ => {}
}
}
#[allow(clippy::only_used_in_recursion)]
fn optimize_statements_escape_analysis<'ast>(
stmts: &[&'ast Statement<'ast>],
escape_info: &EscapeInfo,
stats: &mut EscapeAnalysisStats,
optimizer: &crate::optimizer::Optimizer,
) -> Vec<&'ast Statement<'ast>> {
stmts
.iter()
.map(|stmt| optimize_statement_escape_analysis(stmt, escape_info, stats, optimizer))
.collect()
}
fn optimize_statement_escape_analysis<'a, 'ast>(
stmt: &'a Statement<'a>,
escape_info: &EscapeInfo,
stats: &mut EscapeAnalysisStats,
optimizer: &crate::optimizer::Optimizer,
) -> &'ast Statement<'ast> {
match stmt {
Statement::Let {
pattern,
mutable,
type_,
value,
else_block,
..
} => {
let var_name = match pattern {
Pattern::Identifier(name) => Some(name.as_str()),
_ => None,
};
if let Some(name) = var_name {
if !escape_info.escaped_vars.contains(name) {
if let Some(new_value) = try_optimize_vec_to_smallvec(value, optimizer) {
stats.vectors_stack_allocated += 1;
stats.total_optimizations += 1;
return optimizer.alloc_stmt(unsafe {
std::mem::transmute::<Statement<'_>, Statement<'_>>(Statement::Let {
pattern: Pattern::Identifier(name.to_string()),
mutable: *mutable,
type_: type_.clone(),
value: new_value,
else_block: else_block.clone(),
location: None,
})
});
}
}
}
optimizer.alloc_stmt(unsafe {
std::mem::transmute::<Statement<'_>, Statement<'_>>(Statement::Let {
pattern: pattern.clone(),
mutable: *mutable,
type_: type_.clone(),
value: optimize_expression_escape_analysis(
value,
escape_info,
stats,
optimizer,
),
else_block: else_block.as_ref().map(|stmts| {
optimize_statements_escape_analysis(stmts, escape_info, stats, optimizer)
}),
location: None,
})
})
}
Statement::If {
condition,
then_block,
else_block,
..
} => optimizer.alloc_stmt(unsafe {
std::mem::transmute::<Statement<'_>, Statement<'_>>(Statement::If {
condition: optimize_expression_escape_analysis(
condition,
escape_info,
stats,
optimizer,
),
then_block: optimize_statements_escape_analysis(
then_block,
escape_info,
stats,
optimizer,
),
else_block: else_block.as_ref().map(|stmts| {
optimize_statements_escape_analysis(stmts, escape_info, stats, optimizer)
}),
location: None,
})
}),
Statement::While {
condition, body, ..
} => optimizer.alloc_stmt(unsafe {
std::mem::transmute::<Statement<'_>, Statement<'_>>(Statement::While {
condition: optimize_expression_escape_analysis(
condition,
escape_info,
stats,
optimizer,
),
body: optimize_statements_escape_analysis(body, escape_info, stats, optimizer),
location: None,
})
}),
Statement::For {
pattern,
iterable,
body,
..
} => optimizer.alloc_stmt(unsafe {
std::mem::transmute::<Statement<'_>, Statement<'_>>(Statement::For {
pattern: pattern.clone(),
iterable: optimize_expression_escape_analysis(
iterable,
escape_info,
stats,
optimizer,
),
body: optimize_statements_escape_analysis(body, escape_info, stats, optimizer),
location: None,
})
}),
_ => unsafe { std::mem::transmute::<&Statement<'_>, &Statement<'_>>(stmt) }, }
}
#[allow(clippy::only_used_in_recursion)]
fn optimize_expression_escape_analysis<'a: 'ast, 'ast>(
expr: &'a Expression<'a>,
escape_info: &EscapeInfo,
stats: &mut EscapeAnalysisStats,
optimizer: &crate::optimizer::Optimizer,
) -> &'ast Expression<'ast> {
match expr {
Expression::Binary {
left, op, right, ..
} => optimizer.alloc_expr(unsafe {
std::mem::transmute::<Expression<'_>, Expression<'_>>(Expression::Binary {
left: optimize_expression_escape_analysis(left, escape_info, stats, optimizer),
op: *op,
right: optimize_expression_escape_analysis(right, escape_info, stats, optimizer),
location: None,
})
}),
Expression::Unary { op, operand, .. } => optimizer.alloc_expr(unsafe {
std::mem::transmute::<Expression<'_>, Expression<'_>>(Expression::Unary {
op: *op,
operand: optimize_expression_escape_analysis(
operand,
escape_info,
stats,
optimizer,
),
location: None,
})
}),
_ => expr,
}
}
fn try_optimize_vec_to_smallvec<'ast>(
expr: &'ast Expression<'ast>,
optimizer: &crate::optimizer::Optimizer,
) -> Option<&'ast Expression<'ast>> {
match expr {
Expression::MacroInvocation { name, args, .. }
if name == "vec"
&& args.len() < 8 && !args.is_empty() =>
{
return Some(optimizer.alloc_expr(unsafe {
std::mem::transmute::<Expression<'_>, Expression<'_>>(Expression::MacroInvocation {
name: "smallvec".to_string(),
args: args.clone(),
delimiter: MacroDelimiter::Brackets,
is_repeat: false, location: None,
})
}));
}
_ => {}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::*;
#[test]
fn test_escape_analysis_basic() {
let program = Program {
items: vec![Item::Function {
decl: FunctionDecl {
is_pub: false,
is_extern: false,
name: "test".to_string(),
parameters: vec![],
return_type: None,
return_decorators: Vec::new(),
body: vec![test_alloc_stmt(Statement::Let {
pattern: Pattern::Identifier("temp".to_string()),
mutable: false,
type_: None,
value: test_alloc_expr(Expression::MacroInvocation {
name: "vec".to_string(),
args: vec![
test_alloc_expr(Expression::Literal {
value: Literal::Int(1),
location: None,
}),
test_alloc_expr(Expression::Literal {
value: Literal::Int(2),
location: None,
}),
test_alloc_expr(Expression::Literal {
value: Literal::Int(3),
location: None,
}),
],
delimiter: MacroDelimiter::Brackets,
is_repeat: false,
location: None,
}),
else_block: None,
location: None,
})],
type_params: vec![],
where_clause: vec![],
is_async: false,
decorators: vec![],
parent_type: None,
impl_trait: None,
doc_comment: None,
},
location: None,
}],
};
let optimizer = crate::optimizer::Optimizer::with_defaults();
let (optimized, stats) = optimize_escape_analysis(&program, &optimizer);
assert_eq!(stats.vectors_stack_allocated, 1);
assert_eq!(stats.total_optimizations, 1);
#[allow(clippy::collapsible_match)]
if let Item::Function { decl: func, .. } = &optimized.items[0] {
if let Statement::Let { value, .. } = func.body[0] {
if let Expression::MacroInvocation { name, .. } = value {
assert_eq!(name, "smallvec");
}
}
}
}
#[test]
fn test_escape_analysis_returned_var() {
let program = Program {
items: vec![Item::Function {
decl: FunctionDecl {
is_pub: false,
is_extern: false,
name: "test".to_string(),
parameters: vec![],
return_type: None,
return_decorators: Vec::new(),
body: vec![
test_alloc_stmt(Statement::Let {
pattern: Pattern::Identifier("temp".to_string()),
mutable: false,
type_: None,
value: test_alloc_expr(Expression::MacroInvocation {
name: "vec".to_string(),
args: vec![test_alloc_expr(Expression::Literal {
value: Literal::Int(1),
location: None,
})],
delimiter: MacroDelimiter::Brackets,
is_repeat: false,
location: None,
}),
else_block: None,
location: None,
}),
test_alloc_stmt(Statement::Return {
value: Some(test_alloc_expr(Expression::Identifier {
name: "temp".to_string(),
location: None,
})),
location: None,
}),
],
type_params: vec![],
where_clause: vec![],
is_async: false,
decorators: vec![],
parent_type: None,
impl_trait: None,
doc_comment: None,
},
location: None,
}],
};
let optimizer = crate::optimizer::Optimizer::with_defaults();
let (_, stats) = optimize_escape_analysis(&program, &optimizer);
assert_eq!(stats.vectors_stack_allocated, 0);
}
}