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:
2026-02-14 13:53:28 -05:00
parent 5098104aaf
commit f6569f1821
2 changed files with 100 additions and 13 deletions

View File

@@ -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
}