wip: compile to c
This commit is contained in:
345
docs/COMPILATION_STRATEGY.md
Normal file
345
docs/COMPILATION_STRATEGY.md
Normal 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)
|
||||
Reference in New Issue
Block a user