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>
This commit is contained in:
400
docs/COMPILER_OPTIMIZATIONS.md
Normal file
400
docs/COMPILER_OPTIMIZATIONS.md
Normal file
@@ -0,0 +1,400 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user