wip: compile to c

This commit is contained in:
2026-02-14 00:12:28 -05:00
parent d9be70b021
commit 9a42a7f540
3 changed files with 979 additions and 0 deletions

View File

@@ -0,0 +1,345 @@
# Lux Compilation Strategy
## Vision
Lux should compile to native code with zero-cost effects AND compile to JavaScript for frontend development - all while eliminating runtime overhead like Svelte does.
## Current State
| Component | Status |
|-----------|--------|
| Interpreter | Full-featured, all language constructs |
| JIT (Cranelift) | Integer arithmetic only, ~160x speedup |
| Targets | Native (via Cranelift JIT) |
## Target Architecture
```
┌──→ C Backend ──→ GCC/Clang ──→ Native Binary (servers, CLI)
Lux Source ──→ HIR ──→ MIR ──→ JS Backend ──→ Optimized JS (no runtime, like Svelte)
└──→ Wasm Backend ──→ WebAssembly (browsers, edge)
```
## Compilation Targets Comparison
### What Other Languages Do
| Language | Backend | Runtime | Effect Strategy |
|----------|---------|---------|-----------------|
| **Koka** | C (Perceus) | None | Evidence passing |
| **Elm** | JS | Small runtime | No effects (pure) |
| **Gleam** | Erlang/JS | BEAM/JS runtime | No effects |
| **Svelte** | JS | **No runtime** | Compile-time reactivity |
| **ReScript** | JS | Minimal | No effects |
| **PureScript** | JS | Small runtime | Monad transformers |
### Key Insight: Svelte's No-Runtime Approach
Svelte compiles components to vanilla JavaScript that directly manipulates the DOM. No virtual DOM diffing, no framework code shipped to the browser.
```svelte
<!-- Svelte component -->
<script>
let count = 0;
</script>
<button on:click={() => count++}>{count}</button>
```
Compiles to:
```javascript
// Direct DOM manipulation, no runtime
function create_fragment(ctx) {
let button;
return {
c() {
button = element("button");
button.textContent = ctx[0];
},
m(target, anchor) {
insert(target, button, anchor);
listen(button, "click", ctx[1]);
},
p(ctx, dirty) {
if (dirty & 1) set_data(button, ctx[0]);
}
};
}
```
**We can do the same for Lux effects on the frontend.**
## Phase 1: C Backend (Native Compilation)
### Goal
Compile Lux to C code that can be compiled by GCC/Clang for native execution.
### Why C?
1. **Portability**: C compilers exist for every platform
2. **Performance**: Leverage decades of GCC/Clang optimizations
3. **No runtime**: Like Koka, compile effects away
4. **Proven path**: Koka, Nim, Chicken Scheme all do this successfully
### Implementation Plan
#### Step 1.1: Basic C Code Generation
- Integer arithmetic and comparisons
- Function definitions and calls
- If/else expressions
- Let bindings
#### Step 1.2: Data Structures
- Records → C structs
- ADTs → Tagged unions
- Lists → Linked lists or arrays
- Strings → UTF-8 byte arrays
#### Step 1.3: Effect Compilation (Evidence Passing)
Transform:
```lux
fn greet(name: String): Unit with {Console} =
Console.print("Hello, " + name)
```
To:
```c
void greet(Evidence* ev, LuxString name) {
LuxString msg = lux_string_concat("Hello, ", name);
ev->console->print(ev, msg);
}
```
#### Step 1.4: Memory Management (Perceus-style)
- Compile-time reference counting insertion
- Reuse analysis for in-place updates
- No garbage collector needed
### File Structure
```
src/
codegen/
mod.rs # Code generation module
c_backend.rs # C code generation
c_runtime.h # Minimal C runtime header
c_runtime.c # Runtime support (RC, strings, etc.)
```
## Phase 2: JavaScript Backend (Frontend)
### Goal
Compile Lux to optimized JavaScript with NO runtime, like Svelte.
### Effect Mapping
| Lux Effect | JS Compilation |
|------------|----------------|
| `Console.print` | `console.log()` |
| `Dom.getElementById` | `document.getElementById()` |
| `Dom.addEventListener` | Direct event binding |
| `Http.get` | `fetch()` with async/await |
| `State.get/set` | Compile-time reactivity |
### No-Runtime Strategy
Instead of shipping a runtime, compile effects to direct DOM manipulation:
```lux
effect Dom {
fn getElementById(id: String): Element
fn setTextContent(el: Element, text: String): Unit
fn addEventListener(el: Element, event: String, handler: fn(): Unit): Unit
}
fn counter(): Unit with {Dom} = {
let btn = Dom.getElementById("btn")
let count = 0
Dom.addEventListener(btn, "click", fn() => {
count = count + 1
Dom.setTextContent(btn, toString(count))
})
}
```
Compiles to:
```javascript
// No runtime needed - direct DOM calls
function counter() {
const btn = document.getElementById("btn");
let count = 0;
btn.addEventListener("click", () => {
count = count + 1;
btn.textContent = String(count);
});
}
```
### Reactive State (Like Svelte)
For reactive state, compile to fine-grained updates:
```lux
effect Reactive {
fn signal<T>(initial: T): Signal<T>
fn derived<T>(compute: fn(): T): Signal<T>
}
let count = Reactive.signal(0)
let doubled = Reactive.derived(fn() => count.get() * 2)
```
Compiles to:
```javascript
// Compile-time reactivity, no virtual DOM
let count = 0;
let doubled;
const count_subscribers = new Set();
function set_count(value) {
count = value;
doubled = count * 2; // Statically known dependency
count_subscribers.forEach(fn => fn());
}
```
## Phase 3: WebAssembly Backend
### Goal
Compile to Wasm for browser and edge deployment.
### Strategy
- Reuse C backend: `C → Emscripten → Wasm`
- Or direct Wasm generation via Cranelift
## Phase 4: Zero-Cost Effects (Evidence Passing)
### Current Problem
Effects use runtime handler lookup - O(n) per effect operation.
### Solution: Evidence Passing
Transform effect operations to direct function calls at compile time.
Before (runtime lookup):
```
performEffect("Console", "print", [msg])
→ search handler stack
→ invoke handler
```
After (compile-time):
```
evidence.console.print(msg)
→ direct function call
```
### Implementation
```rust
// Transform AST to evidence-passing form
fn transform_to_evidence(expr: Expr, effects: &[Effect]) -> Expr {
match expr {
Expr::EffectOp { effect, operation, args } => {
// Transform Console.print(x) to ev.console.print(x)
Expr::Call {
func: Expr::Field {
object: Expr::Field {
object: Expr::Var("__evidence__"),
field: effect.to_lowercase(),
},
field: operation,
},
args,
}
}
// ... recurse on other expressions
}
}
```
## Phase 5: FBIP (Functional But In-Place)
### Goal
Detect when functional updates can be performed in-place.
### Example
```lux
fn increment(tree: Tree): Tree =
match tree {
Leaf(n) => Leaf(n + 1),
Node(l, r) => Node(increment(l), increment(r))
}
```
With FBIP analysis, if `tree` has refcount 1, reuse the memory:
```c
Tree increment(Tree tree) {
if (tree->tag == LEAF) {
if (is_unique(tree)) {
tree->leaf.value += 1; // In-place!
return tree;
} else {
return make_leaf(tree->leaf.value + 1);
}
}
// ...
}
```
## Performance Targets
| Metric | Target | How |
|--------|--------|-----|
| Effect overhead | 0% | Evidence passing |
| Memory allocation | Minimal | Perceus RC + FBIP |
| JS bundle size | No runtime | Direct compilation |
| Native vs C | <5% overhead | Good C codegen |
## Milestones
### v0.2.0 - C Backend (Basic)
- [ ] Integer/bool expressions → C
- [ ] Functions → C functions
- [ ] If/else → C conditionals
- [ ] Let bindings → C variables
- [ ] Basic main() generation
- [ ] Build with GCC/Clang
### v0.3.0 - C Backend (Full)
- [ ] Strings → C strings
- [ ] Records → C structs
- [ ] ADTs → Tagged unions
- [ ] Pattern matching → Switch/if chains
- [ ] Lists → Linked structures
- [ ] Effect compilation (basic)
### v0.4.0 - Evidence Passing
- [ ] Effect analysis
- [ ] Evidence vector generation
- [ ] Transform effect ops to direct calls
- [ ] Handler compilation
### v0.5.0 - JavaScript Backend
- [ ] Basic expressions → JS
- [ ] Functions → JS functions
- [ ] Effects → Direct DOM/API calls
- [ ] No runtime bundle
### v0.6.0 - Reactive Frontend
- [ ] Reactive primitives
- [ ] Fine-grained DOM updates
- [ ] Compile-time dependency tracking
- [ ] Svelte-like output
### v0.7.0 - Memory Optimization
- [ ] Reference counting insertion
- [ ] Reuse analysis
- [ ] FBIP detection
- [ ] In-place updates
## References
- [Koka: Evidence Passing](https://github.com/koka-lang/koka)
- [Perceus: Garbage Free Reference Counting](https://www.microsoft.com/en-us/research/publication/perceus-garbage-free-reference-counting-with-reuse/)
- [FBIP: Functional But In-Place](https://www.microsoft.com/en-us/research/publication/fp2-fully-in-place-functional-programming/)
- [Svelte: No Virtual DOM](https://svelte.dev/blog/virtual-dom-is-pure-overhead)
- [Eff: Efficient Compilation of Handlers](https://dl.acm.org/doi/10.1145/3485479)