feat: implement early return handling for RC values
- Add pop_rc_scope_except() to skip decref'ing returned variables - Block expressions now properly preserve returned RC variables - Function returns skip cleanup for variables being returned - Track function return types for call expression type inference - Function calls returning RC types now register for cleanup - Fix main() entry point to call main_lux() when present Test result: [RC] No leaks: 17 allocs, 17 frees Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -111,6 +111,8 @@ pub struct CBackend {
|
||||
rc_scopes: Vec<Vec<RcVariable>>,
|
||||
/// Whether to emit memory tracking code for debugging
|
||||
debug_rc: bool,
|
||||
/// Mapping from function names to their C return types
|
||||
function_return_types: HashMap<String, String>,
|
||||
/// Type tags for ADT types (starting at 100)
|
||||
adt_type_tags: HashMap<String, i32>,
|
||||
/// Next available ADT type tag
|
||||
@@ -137,6 +139,7 @@ impl CBackend {
|
||||
has_evidence: false,
|
||||
rc_scopes: Vec::new(),
|
||||
debug_rc: true, // Enable memory tracking for now
|
||||
function_return_types: HashMap::new(),
|
||||
adt_type_tags: HashMap::new(),
|
||||
next_adt_tag: 100, // ADT tags start at 100
|
||||
adt_with_pointers: HashSet::new(),
|
||||
@@ -154,6 +157,10 @@ impl CBackend {
|
||||
match decl {
|
||||
Declaration::Function(f) => {
|
||||
self.functions.insert(f.name.name.clone());
|
||||
// Store function return type for call inference
|
||||
if let Ok(ret_type) = self.type_expr_to_c(&f.return_type) {
|
||||
self.function_return_types.insert(f.name.name.clone(), ret_type);
|
||||
}
|
||||
// Check if this function returns a closure
|
||||
if matches!(&f.return_type, TypeExpr::Function { .. }) {
|
||||
self.closure_returning_functions.insert(f.name.name.clone());
|
||||
@@ -1438,10 +1445,38 @@ impl CBackend {
|
||||
|
||||
// For non-void functions, we need to save result, decref locals, then return
|
||||
if ret_type != "void" && ret_type != "LuxUnit" {
|
||||
// Check if result is a local RC variable we should skip decref'ing
|
||||
let skip_var = if let Expr::Var(ident) = &func.body {
|
||||
if self.is_var_in_current_rc_scope(&ident.name) {
|
||||
Some(ident.name.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else if let Expr::Block { result: block_result, .. } = &func.body {
|
||||
// For blocks, check if the result expression is a var in scope
|
||||
if let Expr::Var(ident) = block_result.as_ref() {
|
||||
if self.is_var_in_current_rc_scope(&ident.name) {
|
||||
Some(ident.name.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Check if result is an RC type that we need to keep alive
|
||||
let is_rc_result = self.is_rc_type(&ret_type);
|
||||
if is_rc_result && !self.rc_scopes.last().map_or(true, |s| s.is_empty()) {
|
||||
// Save result, incref to keep it alive through cleanup
|
||||
let has_rc_locals = !self.rc_scopes.last().map_or(true, |s| s.is_empty());
|
||||
|
||||
if let Some(ref var_name) = skip_var {
|
||||
// Result is a local variable - skip decref'ing it and just return
|
||||
self.pop_rc_scope_except(Some(var_name));
|
||||
self.writeln(&format!("return {};", result));
|
||||
} else if is_rc_result && has_rc_locals {
|
||||
// Result is from a call or complex expression - use incref/decref pattern
|
||||
self.writeln(&format!("{} _result = {};", ret_type, result));
|
||||
self.writeln("lux_incref(_result);");
|
||||
self.pop_rc_scope(); // Emit decrefs for all local RC vars
|
||||
@@ -1770,8 +1805,17 @@ impl CBackend {
|
||||
let result_val = self.emit_expr(result)?;
|
||||
|
||||
// Pop scope and emit decrefs for block-local variables
|
||||
// Note: We don't decref the result variable itself if it's being returned
|
||||
self.pop_rc_scope();
|
||||
// Check if the result is a variable from this scope - if so, skip decref'ing it
|
||||
let skip_var = if let Expr::Var(ident) = result.as_ref() {
|
||||
if self.is_var_in_current_rc_scope(&ident.name) {
|
||||
Some(ident.name.as_str())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
self.pop_rc_scope_except(skip_var);
|
||||
|
||||
Ok(result_val)
|
||||
}
|
||||
@@ -2456,6 +2500,10 @@ impl CBackend {
|
||||
if let Some(type_name) = self.variant_to_type.get(&ident.name) {
|
||||
return Some(type_name.clone());
|
||||
}
|
||||
// Check if calling a known function - use its return type
|
||||
if let Some(ret_type) = self.function_return_types.get(&ident.name) {
|
||||
return Some(ret_type.clone());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
@@ -2675,6 +2723,16 @@ impl CBackend {
|
||||
}
|
||||
}
|
||||
|
||||
// If there's a main function, call it
|
||||
if has_main {
|
||||
// Check if main uses effects (Console typically)
|
||||
if self.effectful_functions.contains("main") {
|
||||
self.writeln("main_lux(&default_evidence);");
|
||||
} else {
|
||||
self.writeln("main_lux();");
|
||||
}
|
||||
}
|
||||
|
||||
// Check for memory leaks in debug mode
|
||||
if self.debug_rc {
|
||||
self.writeln("lux_rc_check_leaks();");
|
||||
@@ -2748,12 +2806,25 @@ impl CBackend {
|
||||
|
||||
/// Pop the current scope and emit decref calls for all variables
|
||||
fn pop_rc_scope(&mut self) {
|
||||
self.pop_rc_scope_except(None);
|
||||
}
|
||||
|
||||
/// Pop the current scope and emit decref calls, skipping the named variable
|
||||
/// This is used when returning a variable from a block - we don't want to decref it
|
||||
fn pop_rc_scope_except(&mut self, skip_var: Option<&str>) {
|
||||
if let Some(scope) = self.rc_scopes.pop() {
|
||||
// Clone data we need to avoid borrow issues
|
||||
let variant_field_types = self.variant_field_types.clone();
|
||||
|
||||
// Decref in reverse order (LIFO - last allocated, first freed)
|
||||
for var in scope.iter().rev() {
|
||||
// Skip the variable we're returning (if any)
|
||||
if let Some(skip) = skip_var {
|
||||
if var.name == skip {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(adt_name) = &var.adt_type_name {
|
||||
// ADT with pointer fields - need to decref the fields
|
||||
self.emit_adt_field_cleanup(&var.name, adt_name, &variant_field_types);
|
||||
@@ -2840,6 +2911,15 @@ impl CBackend {
|
||||
// Note: LuxString (char*) needs special handling - only dynamic strings are RC
|
||||
}
|
||||
|
||||
/// Check if a variable name is in the current RC scope
|
||||
fn is_var_in_current_rc_scope(&self, name: &str) -> bool {
|
||||
if let Some(scope) = self.rc_scopes.last() {
|
||||
scope.iter().any(|var| var.name == name)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if an expression creates a new RC-managed value that needs tracking
|
||||
fn expr_creates_rc_value(&self, expr: &Expr) -> bool {
|
||||
match expr {
|
||||
@@ -2849,7 +2929,7 @@ impl CBackend {
|
||||
// Lambdas create RC-managed closures
|
||||
Expr::Lambda { .. } => true,
|
||||
|
||||
// Calls to List.* that return lists
|
||||
// Calls to List.* that return lists or user functions returning RC types
|
||||
Expr::Call { func, .. } => {
|
||||
if let Expr::Field { object, field, .. } = func.as_ref() {
|
||||
if let Expr::Var(module) = object.as_ref() {
|
||||
@@ -2862,6 +2942,12 @@ impl CBackend {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check if calling a user-defined function that returns an RC type
|
||||
if let Expr::Var(fn_name) = func.as_ref() {
|
||||
if let Some(ret_type) = self.function_return_types.get(&fn_name.name) {
|
||||
return ret_type.ends_with('*') && ret_type != "LuxString";
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user