Files
lux/docs/COMPILER_OPTIMIZATIONS.md
Brandon Lucas 7e76acab18 feat: rebuild website with full learning funnel
Website rebuilt from scratch based on analysis of 11 beloved language
websites (Elm, Zig, Gleam, Swift, Kotlin, Haskell, OCaml, Crystal, Roc,
Rust, Go).

New website structure:
- Homepage with hero, playground, three pillars, install guide
- Language Tour with interactive lessons (hello world, types, effects)
- Examples cookbook with categorized sidebar
- API documentation index
- Installation guide (Nix and source)
- Sleek/noble design (black/gold, serif typography)

Also includes:
- New stdlib/json.lux module for JSON serialization
- Enhanced stdlib/http.lux with middleware and routing
- New string functions (charAt, indexOf, lastIndexOf, repeat)
- LSP improvements (rename, signature help, formatting)
- Package manager transitive dependency resolution
- Updated documentation for effects and stdlib
- New showcase example (task_manager.lux)

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

401 lines
11 KiB
Markdown

# Compiler Optimizations from Behavioral Types
This document describes optimization opportunities enabled by Lux's behavioral type system. When functions are annotated with properties like `is pure`, `is total`, `is idempotent`, `is deterministic`, or `is commutative`, the compiler gains knowledge that enables aggressive optimizations.
## Overview
| Property | Key Optimizations |
|----------|-------------------|
| `is pure` | Memoization, CSE, dead code elimination, auto-parallelization |
| `is total` | No exception handling, aggressive inlining, loop unrolling |
| `is deterministic` | Result caching, test reproducibility, parallel execution |
| `is idempotent` | Duplicate call elimination, retry optimization |
| `is commutative` | Argument reordering, parallel reduction, algebraic simplification |
## Pure Function Optimizations
When a function is marked `is pure`:
### 1. Memoization (Automatic Caching)
```lux
fn fib(n: Int): Int is pure =
if n <= 1 then n else fib(n - 1) + fib(n - 2)
```
**Optimization**: The compiler can automatically memoize results. Since `fib` is pure, `fib(10)` will always return the same value, so we can cache it.
**Implementation approach**:
- Maintain a hash map of argument → result mappings
- Before computing, check if result exists
- Store results after computation
- Use LRU eviction for memory management
**Impact**: Reduces exponential recursive calls to linear time.
### 2. Common Subexpression Elimination (CSE)
```lux
fn compute(x: Int): Int is pure =
expensive(x) + expensive(x) // Same call twice
```
**Optimization**: The compiler recognizes both calls are identical and computes `expensive(x)` only once.
**Transformed to**:
```lux
fn compute(x: Int): Int is pure =
let temp = expensive(x)
temp + temp
```
**Impact**: Eliminates redundant computation.
### 3. Dead Code Elimination
```lux
fn example(): Int is pure = {
let unused = expensiveComputation() // Result not used
42
}
```
**Optimization**: Since `expensiveComputation` is pure (no side effects), and its result is unused, the entire call can be eliminated.
**Impact**: Removes unnecessary work.
### 4. Auto-Parallelization
```lux
fn processAll(items: List<Item>): List<Result> is pure =
List.map(items, processItem) // processItem is pure
```
**Optimization**: Since `processItem` is pure, each invocation is independent. The compiler can automatically parallelize the map operation.
**Implementation approach**:
- Detect pure functions in map/filter/fold operations
- Split work across available cores
- Merge results (order-preserving for map)
**Impact**: Linear speedup with core count for CPU-bound operations.
### 5. Speculative Execution
```lux
fn decide(cond: Bool, a: Int, b: Int): Int is pure =
if cond then computeA(a) else computeB(b)
```
**Optimization**: Both branches can be computed in parallel before the condition is known, since neither has side effects.
**Impact**: Reduced latency when condition evaluation is slow.
## Total Function Optimizations
When a function is marked `is total`:
### 1. Exception Handling Elimination
```lux
fn safeCompute(x: Int): Int is total =
complexCalculation(x)
```
**Optimization**: No try/catch blocks needed around calls to `safeCompute`. The compiler knows it will never throw or fail.
**Generated code difference**:
```c
// Without is total - needs error checking
Result result = safeCompute(x);
if (result.is_error) { handle_error(); }
// With is total - direct call
int result = safeCompute(x);
```
**Impact**: Reduced code size, better branch prediction.
### 2. Aggressive Inlining
```lux
fn square(x: Int): Int is total = x * x
fn sumOfSquares(a: Int, b: Int): Int is total =
square(a) + square(b)
```
**Optimization**: Total functions are safe to inline aggressively because:
- They won't change control flow unexpectedly
- They won't introduce exception handling complexity
- Their termination is guaranteed
**Impact**: Eliminates function call overhead, enables further optimizations.
### 3. Loop Unrolling
```lux
fn sumList(xs: List<Int>): Int is total =
List.fold(xs, 0, fn(acc: Int, x: Int): Int is total => acc + x)
```
**Optimization**: When the list size is known at compile time and the fold function is total, the loop can be fully unrolled.
**Impact**: Eliminates loop overhead, enables vectorization.
### 4. Termination Assumptions
```lux
fn processRecursive(data: Tree): Result is total =
match data {
Leaf(v) => Result.single(v),
Node(left, right) => {
let l = processRecursive(left)
let r = processRecursive(right)
Result.merge(l, r)
}
}
```
**Optimization**: The compiler can assume this recursion terminates, allowing optimizations like:
- Converting recursion to iteration
- Allocating fixed stack space
- Tail call optimization
**Impact**: Stack safety, predictable memory usage.
## Deterministic Function Optimizations
When a function is marked `is deterministic`:
### 1. Compile-Time Evaluation
```lux
fn hashConstant(s: String): Int is deterministic = computeHash(s)
let key = hashConstant("api_key") // Constant input
```
**Optimization**: Since the input is a compile-time constant and the function is deterministic, the result can be computed at compile time.
**Transformed to**:
```lux
let key = 7823491 // Pre-computed
```
**Impact**: Zero runtime cost for constant computations.
### 2. Result Caching Across Runs
```lux
fn parseConfig(path: String): Config is deterministic with {File} =
Json.parse(File.read(path))
```
**Optimization**: Results can be cached persistently. If the file hasn't changed, the cached result is valid.
**Implementation approach**:
- Hash inputs (including file contents)
- Store results in persistent cache
- Validate cache on next run
**Impact**: Faster startup times, reduced I/O.
### 3. Reproducible Parallel Execution
```lux
fn renderImages(images: List<Image>): List<Bitmap> is deterministic =
List.map(images, render)
```
**Optimization**: Deterministic parallel execution guarantees same results regardless of scheduling order. This enables:
- Work stealing without synchronization concerns
- Speculative execution without rollback complexity
- Distributed computation across machines
**Impact**: Easier parallelization, simpler distributed systems.
## Idempotent Function Optimizations
When a function is marked `is idempotent`:
### 1. Duplicate Call Elimination
```lux
fn setFlag(config: Config, flag: Bool): Config is idempotent =
{ ...config, enabled: flag }
fn configure(c: Config): Config is idempotent =
c |> setFlag(true) |> setFlag(true) |> setFlag(true)
```
**Optimization**: Multiple consecutive calls with the same arguments can be collapsed to one.
**Transformed to**:
```lux
fn configure(c: Config): Config is idempotent =
setFlag(c, true)
```
**Impact**: Eliminates redundant operations.
### 2. Retry Optimization
```lux
fn sendRequest(data: Request): Response is idempotent with {Http} =
Http.put("/api/resource", data)
fn reliableSend(data: Request): Response with {Http} =
retry(3, fn(): Response => sendRequest(data))
```
**Optimization**: The retry mechanism knows the operation is safe to retry without side effects accumulating.
**Implementation approach**:
- No need for transaction logs
- No need for "already processed" checks
- Simple retry loop
**Impact**: Simpler error recovery, reduced complexity.
### 3. Convergent Computation
```lux
fn normalize(value: Float): Float is idempotent =
clamp(round(value, 2), 0.0, 1.0)
```
**Optimization**: In iterative algorithms, the compiler can detect when a value has converged (applying the function no longer changes it).
```lux
// Can terminate early when values stop changing
fn iterateUntilStable(values: List<Float>): List<Float> =
let normalized = List.map(values, normalize)
if normalized == values then values
else iterateUntilStable(normalized)
```
**Impact**: Early termination of iterative algorithms.
## Commutative Function Optimizations
When a function is marked `is commutative`:
### 1. Argument Reordering
```lux
fn multiply(a: Int, b: Int): Int is commutative = a * b
// In a computation
multiply(expensiveA(), cheapB())
```
**Optimization**: Evaluate the cheaper argument first to enable short-circuit optimizations or better register allocation.
**Impact**: Improved instruction scheduling.
### 2. Parallel Reduction
```lux
fn add(a: Int, b: Int): Int is commutative = a + b
fn sum(xs: List<Int>): Int =
List.fold(xs, 0, add)
```
**Optimization**: Since `add` is commutative (and associative), the fold can be parallelized:
```
[1, 2, 3, 4, 5, 6, 7, 8]
↓ parallel reduce
[(1+2), (3+4), (5+6), (7+8)]
↓ parallel reduce
[(3+7), (11+15)]
↓ parallel reduce
[36]
```
**Impact**: O(log n) parallel reduction instead of O(n) sequential.
### 3. Algebraic Simplification
```lux
fn add(a: Int, b: Int): Int is commutative = a + b
// Expression: add(x, add(y, z))
```
**Optimization**: Commutative operations can be reordered for simplification:
- `add(x, 0)``x`
- `add(add(x, 1), add(y, 1))``add(add(x, y), 2)`
**Impact**: Constant folding, strength reduction.
## Combined Property Optimizations
Properties can be combined for even more powerful optimizations:
### Pure + Deterministic + Total
```lux
fn computeKey(data: String): Int
is pure
is deterministic
is total = {
// Hash computation
List.fold(String.chars(data), 0, fn(acc: Int, c: Char): Int =>
acc * 31 + Char.code(c))
}
```
**Enabled optimizations**:
- Compile-time evaluation for constants
- Automatic memoization at runtime
- Parallel execution in batch operations
- No exception handling needed
- Safe to inline anywhere
### Idempotent + Commutative
```lux
fn setUnionItem<T>(set: Set<T>, item: T): Set<T>
is idempotent
is commutative = {
Set.add(set, item)
}
```
**Enabled optimizations**:
- Parallel set building (order doesn't matter)
- Duplicate insertions are free (idempotent)
- Reorder insertions for cache locality
## Implementation Status
| Optimization | Status |
|--------------|--------|
| Pure: CSE | Planned |
| Pure: Dead code elimination | Partial (basic) |
| Pure: Auto-parallelization | Planned |
| Total: Exception elimination | Planned |
| Total: Aggressive inlining | Partial |
| Deterministic: Compile-time eval | Planned |
| Idempotent: Duplicate elimination | Planned |
| Commutative: Parallel reduction | Planned |
## Adding New Optimizations
When implementing new optimizations based on behavioral types:
1. **Verify the property is correct**: The optimization is only valid if the property holds
2. **Consider combinations**: Multiple properties together enable more optimizations
3. **Measure impact**: Profile before and after to ensure benefit
4. **Handle `assume`**: Functions using `assume` bypass verification but still enable optimizations (risk is on the programmer)
## Future Work
1. **Inter-procedural analysis**: Track properties across function boundaries
2. **Automatic property inference**: Derive properties when not explicitly stated
3. **Profile-guided optimization**: Use runtime data to decide when to apply optimizations
4. **LLVM integration**: Pass behavioral hints to LLVM for backend optimizations