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>
401 lines
11 KiB
Markdown
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
|