diff --git a/docs/C_BACKEND.md b/docs/C_BACKEND.md index 65b6f41..4b276a8 100644 --- a/docs/C_BACKEND.md +++ b/docs/C_BACKEND.md @@ -251,13 +251,23 @@ Koka also compiles to C with algebraic effects. Key differences: ## Current Progress -### Evidence Passing (Zero-Cost Effects) - PARTIALLY COMPLETE +### Evidence Passing (Zero-Cost Effects) ✅ COMPLETE **Interpreter:** ✅ Complete - O(1) HashMap lookup instead of O(n) stack search. -**C Backend Infrastructure:** ✅ Complete - Handler structs and evidence types generated. +**C Backend:** ✅ Complete - Full evidence threading through function calls. -**C Backend Threading:** 🔜 Planned - Passing evidence through function calls. +**Generated code example:** +```c +void greet_lux(LuxEvidence* ev) { + ev->console->print(ev->console->env, "Hello!"); +} + +int main(int argc, char** argv) { + greet_lux(&default_evidence); + return 0; +} +``` See [docs/EVIDENCE_PASSING.md](EVIDENCE_PASSING.md) for details. @@ -265,28 +275,6 @@ See [docs/EVIDENCE_PASSING.md](EVIDENCE_PASSING.md) for details. ## Future Roadmap -### Phase 1: Complete Evidence Passing in C Backend - -**Goal:** Thread evidence through all effectful function calls. - -**Current state:** Handler types (`LuxConsoleHandler`, etc.) and `LuxEvidence` struct -are generated, but not yet used. Effect calls still use hardcoded `lux_console_print()`. - -**Required changes:** -```c -// Current (hardcoded): -void greet_lux(LuxString name) { - lux_console_print(name); -} - -// Target (evidence passing): -void greet_lux(LuxEvidence* ev, LuxString name) { - ev->console->print(ev->console->env, name); -} -``` - -**Expected speedup:** 10-20x for effect-heavy code (based on Koka benchmarks). - ### Phase 2: Perceus Reference Counting **Goal:** Deterministic memory management without GC pauses. diff --git a/docs/EVIDENCE_PASSING.md b/docs/EVIDENCE_PASSING.md index 2c2a4f8..7960a51 100644 --- a/docs/EVIDENCE_PASSING.md +++ b/docs/EVIDENCE_PASSING.md @@ -181,16 +181,39 @@ let handler = self.evidence.get(&request.effect) 4. Added `default_evidence` global with built-in handlers -### Phase 3: C Backend Evidence Threading 🔜 PLANNED +### Phase 3: C Backend Evidence Threading ✅ COMPLETE **Goal:** Thread evidence through effectful function calls. -**Required changes:** +**Changes made to `c_backend.rs`:** -1. Add `LuxEvidence* ev` parameter to effectful functions -2. Transform effect operations to use `ev->console->print(...)` -3. Generate handler structs for user-defined handlers in `run` blocks -4. Pass evidence through call chain +1. Track effectful functions in `effectful_functions: HashSet` +2. Add `has_evidence: bool` flag to track context +3. For effectful functions: + - Add `LuxEvidence* ev` parameter to forward declarations + - Add `LuxEvidence* ev` parameter to function definitions +4. Transform effect operations: + - When `has_evidence` is true: `ev->console->print(ev->console->env, msg)` + - When `has_evidence` is false: `lux_console_print(msg)` (fallback) +5. Update function calls: + - Effectful calls inside effectful functions: pass `ev` + - Effectful calls outside: pass `&default_evidence` +6. Update main function generation to pass `&default_evidence` + +**Example generated code:** +```c +// Function signature includes evidence +void greet_lux(LuxEvidence* ev) { + // Effect operations use evidence + ev->console->print(ev->console->env, "Hello, World!"); +} + +int main(int argc, char** argv) { + // Entry point uses default evidence + greet_lux(&default_evidence); + return 0; +} +``` ## Performance Characteristics diff --git a/src/codegen/c_backend.rs b/src/codegen/c_backend.rs index 174a01b..8f562f2 100644 --- a/src/codegen/c_backend.rs +++ b/src/codegen/c_backend.rs @@ -95,6 +95,10 @@ pub struct CBackend { variant_to_type: HashMap, /// Mapping from (type_name, variant_name) to field types variant_field_types: HashMap<(String, String), Vec>, + /// Functions that use effects (have evidence parameter) + effectful_functions: HashSet, + /// Whether we're currently inside an effectful function (has evidence available) + has_evidence: bool, } impl CBackend { @@ -111,6 +115,8 @@ impl CBackend { closure_returning_functions: HashSet::new(), variant_to_type: HashMap::new(), variant_field_types: HashMap::new(), + effectful_functions: HashSet::new(), + has_evidence: false, } } @@ -120,7 +126,7 @@ impl CBackend { self.closures.clear(); self.emit_prelude(); - // First pass: collect all function names and types + // First pass: collect all function names, types, and effects for decl in &program.declarations { match decl { Declaration::Function(f) => { @@ -129,6 +135,10 @@ impl CBackend { if matches!(&f.return_type, TypeExpr::Function { .. }) { self.closure_returning_functions.insert(f.name.name.clone()); } + // Check if this function uses effects (evidence passing) + if !f.effects.is_empty() { + self.effectful_functions.insert(f.name.name.clone()); + } } Declaration::Type(t) => { self.collect_type(t)?; @@ -305,7 +315,18 @@ impl CBackend { match func.as_ref() { Expr::Var(ident) if self.functions.contains(&ident.name) => { let c_func_name = self.mangle_name(&ident.name); - Ok(format!("{}({})", c_func_name, args_str)) + // Pass evidence if this function uses effects + let is_effectful = self.effectful_functions.contains(&ident.name); + if is_effectful { + // Inside closures, use default evidence + if args_str.is_empty() { + Ok(format!("{}(&default_evidence)", c_func_name)) + } else { + Ok(format!("{}(&default_evidence, {})", c_func_name, args_str)) + } + } else { + Ok(format!("{}({})", c_func_name, args_str)) + } } _ => { let closure_expr = self.emit_expr_with_env(func, captured)?; @@ -678,7 +699,19 @@ impl CBackend { let ret_type = self.type_expr_to_c(&f.return_type)?; let params = self.emit_params(&f.params)?; let mangled = self.mangle_name(&f.name.name); - self.writeln(&format!("{} {}({});", ret_type, mangled, params)); + + // Add evidence parameter for effectful functions + let full_params = if !f.effects.is_empty() { + if params == "void" { + "LuxEvidence* ev".to_string() + } else { + format!("LuxEvidence* ev, {}", params) + } + } else { + params + }; + + self.writeln(&format!("{} {}({});", ret_type, mangled, full_params)); } } self.writeln(""); @@ -690,12 +723,35 @@ impl CBackend { let params = self.emit_params(&func.params)?; let mangled = self.mangle_name(&func.name.name); - self.writeln(&format!("{} {}({}) {{", ret_type, mangled, params)); + // Check if this function uses effects (needs evidence parameter) + let is_effectful = !func.effects.is_empty(); + + // Build parameter list with evidence if needed + let full_params = if is_effectful { + if params == "void" { + "LuxEvidence* ev".to_string() + } else { + format!("LuxEvidence* ev, {}", params) + } + } else { + params + }; + + self.writeln(&format!("{} {}({}) {{", ret_type, mangled, full_params)); self.indent += 1; + // Set evidence availability for expression generation + let prev_has_evidence = self.has_evidence; + if is_effectful { + self.has_evidence = true; + } + // Emit function body let result = self.emit_expr(&func.body)?; + // Restore previous evidence state + self.has_evidence = prev_has_evidence; + if ret_type != "void" { self.writeln(&format!("return {};", result)); } @@ -809,7 +865,24 @@ impl CBackend { Expr::Var(ident) if self.functions.contains(&ident.name) => { // Direct call to a known function let c_func_name = self.mangle_name(&ident.name); - Ok(format!("{}({})", c_func_name, args_str)) + // Pass evidence if this function uses effects and we have evidence + let is_effectful = self.effectful_functions.contains(&ident.name); + if is_effectful && self.has_evidence { + if args_str.is_empty() { + Ok(format!("{}(ev)", c_func_name)) + } else { + Ok(format!("{}(ev, {})", c_func_name, args_str)) + } + } else if is_effectful { + // Calling effectful function but don't have evidence - use default + if args_str.is_empty() { + Ok(format!("{}(&default_evidence)", c_func_name)) + } else { + Ok(format!("{}(&default_evidence, {})", c_func_name, args_str)) + } + } else { + Ok(format!("{}({})", c_func_name, args_str)) + } } Expr::Var(ident) if self.variant_to_type.contains_key(&ident.name) => { // ADT constructor call - create struct with tag and data @@ -965,24 +1038,47 @@ impl CBackend { Expr::EffectOp { effect, operation, args, .. } => { self.effects_used.insert(effect.name.clone()); - // Built-in effects - if effect.name == "Console" && operation.name == "print" { - let arg = self.emit_expr(&args[0])?; - self.writeln(&format!("lux_console_print({});", arg)); - return Ok("NULL".to_string()); - } - // List module operations (treated as effect by parser but handled specially) if effect.name == "List" { return self.emit_list_operation(&operation.name, args); } + // Built-in Console effect + if effect.name == "Console" { + if operation.name == "print" { + let arg = self.emit_expr(&args[0])?; + if self.has_evidence { + // Use evidence passing + self.writeln(&format!("ev->console->print(ev->console->env, {});", arg)); + } else { + // Fallback to direct call + self.writeln(&format!("lux_console_print({});", arg)); + } + return Ok("NULL".to_string()); + } else if operation.name == "readLine" { + if self.has_evidence { + return Ok("ev->console->readLine(ev->console->env)".to_string()); + } else { + return Ok("lux_console_readLine()".to_string()); + } + } + } + // For other effects, emit evidence-passing call let arg_strs: Result, _> = args.iter().map(|a| self.emit_expr(a)).collect(); - Ok(format!("ev_{}__{}({})", - effect.name.to_lowercase(), - operation.name, - arg_strs?.join(", "))) + if self.has_evidence { + Ok(format!("ev->{}->{}(ev->{}->env{}{})", + effect.name.to_lowercase(), + operation.name, + effect.name.to_lowercase(), + if arg_strs.as_ref().map_or(true, |v| v.is_empty()) { "" } else { ", " }, + arg_strs?.join(", "))) + } else { + Ok(format!("lux_{}__{}({})", + effect.name.to_lowercase(), + operation.name, + arg_strs?.join(", "))) + } } Expr::Record { fields, .. } => { @@ -1606,7 +1702,12 @@ impl CBackend { if let Expr::Call { func, .. } = expr.as_ref() { if let Expr::Var(fn_name) = func.as_ref() { let mangled = self.mangle_name(&fn_name.name); - self.writeln(&format!("{}();", mangled)); + // Pass default evidence if function uses effects + if self.effectful_functions.contains(&fn_name.name) { + self.writeln(&format!("{}(&default_evidence);", mangled)); + } else { + self.writeln(&format!("{}();", mangled)); + } } } }