feat: complete evidence threading in C backend

C backend now fully threads evidence through effectful function calls:

- Track effectful functions via effectful_functions HashSet
- Add has_evidence flag to track context during code generation
- Add LuxEvidence* ev parameter to effectful function signatures
- Transform effect operations to use ev->console->print() when evidence available
- Update function calls to pass evidence (ev or &default_evidence)
- Update main entry point to pass &default_evidence

Generated code now uses zero-cost evidence passing:
  void greet_lux(LuxEvidence* ev) {
      ev->console->print(ev->console->env, "Hello!");
  }

This completes the evidence passing implementation for both interpreter
(O(1) HashMap lookup) and C backend (direct function pointer calls).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-14 11:58:26 -05:00
parent ce4ab45651
commit 52cb38805a
3 changed files with 160 additions and 48 deletions

View File

@@ -95,6 +95,10 @@ pub struct CBackend {
variant_to_type: HashMap<String, String>,
/// Mapping from (type_name, variant_name) to field types
variant_field_types: HashMap<(String, String), Vec<String>>,
/// Functions that use effects (have evidence parameter)
effectful_functions: HashSet<String>,
/// 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<Vec<_>, _> = 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));
}
}
}
}