# 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: ```rust // 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 ```rust // Interpreter representation struct Evidence { handlers: HashMap>, } ``` ```c // 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: ```c // 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:** ```lux fn greet(name: String): Unit with {Console} = Console.print("Hello, " + name) ``` **Generated C:** ```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:** ```lux run greet("World") with { Console { print(msg) => resume(builtin_print(msg)) } } ``` **Generated C:** ```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`:** 1. Added `evidence: HashMap>` field to Interpreter 2. Modified `handle_effect` to use O(1) evidence lookup instead of O(n) stack search 3. Updated `eval_run` to 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:** ```rust // 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`:** 1. Added handler struct types to prelude: - `LuxConsoleHandler` with print/readLine function pointers - `LuxStateHandler` with get/put function pointers - `LuxReaderHandler` with ask function pointer 2. Added `LuxEvidence` struct containing pointers to handlers 3. Added default handlers that delegate to built-in implementations 4. Added `default_evidence` global with built-in handlers ### Phase 3: C Backend Evidence Threading ✅ COMPLETE **Goal:** Thread evidence through effectful function calls. **Changes made to `c_backend.rs`:** 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 | 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](https://xnning.github.io/papers/multip.pdf) - ICFP 2021 - [Effect Handlers, Evidently](https://www.dhil.net/research/papers/effect_handlers_evidently-draft-march2020.pdf) - ICFP 2020 - [Koka Language](https://koka-lang.github.io/koka/doc/book.html)