Files
lux/docs/COMPILATION_STRATEGY.md
Brandon Lucas 33b4f57faf fix: C backend String functions, record type aliases, docs cleanup
- Add String.fromChar, chars, substring, toUpper, toLower, replace,
  startsWith, endsWith, join to C backend
- Fix record type alias unification by adding expand_type_alias and
  unify_with_env functions
- Update docs to reflect current implementation status
- Clean up outdated roadmap items and fix inconsistencies
- Add comprehensive language comparison document

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-16 01:06:20 -05:00

8.9 KiB

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
C Backend Complete (functions, closures, pattern matching, lists, RC)
JS Backend Complete (full language, browser & Node.js, DOM, TEA)
JIT (Cranelift) Integer arithmetic only, ~160x speedup
Targets Native (via C), JavaScript, 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 component -->
<script>
  let count = 0;
</script>
<button on:click={() => count++}>{count}</button>

Compiles to:

// 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:

fn greet(name: String): Unit with {Console} =
    Console.print("Hello, " + name)

To:

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:

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:

// 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:

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:

// 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

// 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

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:

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

C Backend - COMPLETE

  • Integer/bool expressions → C
  • Functions → C functions
  • If/else → C conditionals
  • Let bindings → C variables
  • Basic main() generation
  • Build with GCC/Clang
  • Strings → C strings
  • Pattern matching → Switch/if chains
  • Lists → Linked structures
  • Closures
  • Reference counting (lists, boxed values)

JavaScript Backend - COMPLETE

  • Basic expressions → JS
  • Functions → JS functions
  • Effects → Direct DOM/API calls
  • Standard library (String, List, Option, Result, Math, JSON)
  • DOM effect (40+ operations)
  • Html module (type-safe HTML)
  • TEA runtime (Elm Architecture)
  • Browser & Node.js support

Remaining Work

  • Evidence passing for zero-cost effects
  • FBIP (Functional But In-Place) optimization
  • WASM backend (deprioritized)

References