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>
6.1 KiB
Evidence Passing for Algebraic Effects
Overview
Evidence passing is a compilation technique that transforms O(n) runtime effect handler lookup into O(1) direct function calls. This document describes the implementation in Lux.
Background: The Problem
Current Approach (Stack-Based)
When an effect operation is performed, Lux searches the handler stack:
// interpreter.rs:2898-2903
let handler = self
.handler_stack
.iter()
.rev()
.find(|h| h.effect == request.effect) // O(n) linear search
For deeply nested handlers or effect-heavy code, this becomes a bottleneck.
Evidence Passing Solution
Instead of searching at runtime, pass handler references directly to functions:
Before (stack lookup):
┌─────────────┐
│ greet() │ ──► search handler_stack for "Console" ──► call handler
└─────────────┘
After (evidence passing):
┌─────────────┐
│ greet(ev) │ ──► ev.console.print() (direct call)
└─────────────┘
Design
1. Effect Index Assignment
Each effect type gets a compile-time index:
| Effect | Index |
|---|---|
| Console | 0 |
| State | 1 |
| Reader | 2 |
| Fail | 3 |
| ... | ... |
2. Evidence Structure
// Interpreter representation
struct Evidence {
handlers: HashMap<String, Rc<HandlerValue>>,
}
// C backend representation
typedef struct {
LuxConsoleHandler* console;
LuxStateHandler* state;
LuxReaderHandler* reader;
// ... other effects
} LuxEvidence;
3. Handler Structures (C Backend)
Each effect becomes a struct with function pointers:
// Console effect handler
typedef struct {
void (*print)(void* env, LuxString msg);
LuxString (*readLine)(void* env);
LuxInt (*readInt)(void* env);
void* env; // Captured environment
} LuxConsoleHandler;
// State effect handler
typedef struct {
void* (*get)(void* env);
void (*put)(void* env, void* value);
void* env;
} LuxStateHandler;
4. Function Signature Transformation
Functions that use effects receive an implicit evidence parameter:
Lux source:
fn greet(name: String): Unit with {Console} =
Console.print("Hello, " + name)
Generated C:
void greet_lux(LuxEvidence* ev, LuxString name) {
LuxString msg = lux_string_concat("Hello, ", name);
ev->console->print(ev->console->env, msg);
}
5. Handler Installation
The run ... with {} expression:
Lux source:
run greet("World") with {
Console {
print(msg) => resume(builtin_print(msg))
}
}
Generated C:
// Create handler
LuxConsoleHandler console_handler = {
.print = my_print_impl,
.readLine = builtin_readLine,
.readInt = builtin_readInt,
.env = captured_env
};
// Create evidence with this handler
LuxEvidence ev = outer_ev; // Copy outer evidence
ev.console = &console_handler;
// Run body with new evidence
greet_lux(&ev, "World");
Implementation Status
Phase 1: Interpreter Optimization ✅ COMPLETE
Goal: Replace stack search with HashMap lookup.
Changes made to interpreter.rs:
- Added
evidence: HashMap<String, Rc<HandlerValue>>field to Interpreter - Modified
handle_effectto use O(1) evidence lookup instead of O(n) stack search - Updated
eval_runto manage evidence:- Save previous evidence values before entering run block
- Insert new handlers into evidence
- Restore previous evidence after exiting run block
Key code change:
// Before (O(n) search):
let handler = self.handler_stack.iter().rev().find(|h| h.effect == request.effect)
// After (O(1) lookup via evidence):
let handler = self.evidence.get(&request.effect)
Phase 2: C Backend Evidence Infrastructure ✅ COMPLETE
Goal: Generate evidence types for future use.
Changes made to c_backend.rs:
-
Added handler struct types to prelude:
LuxConsoleHandlerwith print/readLine function pointersLuxStateHandlerwith get/put function pointersLuxReaderHandlerwith ask function pointer
-
Added
LuxEvidencestruct containing pointers to handlers -
Added default handlers that delegate to built-in implementations
-
Added
default_evidenceglobal with built-in handlers
Phase 3: C Backend Evidence Threading ✅ COMPLETE
Goal: Thread evidence through effectful function calls.
Changes made to c_backend.rs:
- Track effectful functions in
effectful_functions: HashSet<String> - Add
has_evidence: boolflag to track context - For effectful functions:
- Add
LuxEvidence* evparameter to forward declarations - Add
LuxEvidence* evparameter to function definitions
- Add
- Transform effect operations:
- When
has_evidenceis true:ev->console->print(ev->console->env, msg) - When
has_evidenceis false:lux_console_print(msg)(fallback)
- When
- Update function calls:
- Effectful calls inside effectful functions: pass
ev - Effectful calls outside: pass
&default_evidence
- Effectful calls inside effectful functions: pass
- Update main function generation to pass
&default_evidence
Example generated code:
// 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
| Metric | Stack-Based | Evidence Passing |
|---|---|---|
| Handler lookup | O(n) | O(1) |
| Memory overhead | Stack grows | Fixed-size struct |
| Function call overhead | None | +1 pointer parameter |
| Effect invocation | Search + indirect call | Direct call |
Expected speedup: 10-20x for effect-heavy code (based on Koka benchmarks).
References
- Generalized Evidence Passing for Effect Handlers - ICFP 2021
- Effect Handlers, Evidently - ICFP 2020
- Koka Language