○
Planned This feature is planned but not yet implemented.
OWED: checker-event + label-loop lowers to a native loop.
Error Details
output_emitted.zig:166:59: error: type 'u64' does not support field access
Failure Output
🎯 Compiler coordination: Passes: 20 (flow-based: elaborate, analysis, emission)
Error: output_emitted.zig:166:59: error: type 'u64' does not support field access
_ = main_module.print_event.handler(.{ .result = d.result });
~^~~~~~~
referenced by:
main: output_emitted.zig:214:22
callMain [inlined]: /opt/homebrew/Cellar/zig/0.15.2_1/lib/zig/std/start.zig:618:22
callMainWithArgs [inlined]: /opt/homebrew/Cellar/zig/0.15.2_1/lib/zig/std/start.zig:587:20
main: /opt/homebrew/Cellar/zig/0.15.2_1/lib/zig/std/start.zig:602:28
1 reference(s) hidden; use '-freference-trace=5' to see all references Code
// PERFORMANCE TEST: Loop Optimization - Basic Checker Event Pattern
// Goal: Prove compiler detects checker event pattern and transforms to native loop
// Pattern: Checker event (two branches: continue/done) + label loop with recursive jump
// Baseline: Hand-written Zig for loop
// Threshold: 1.05x (5% overhead max)
//
// This tests the CORE loop optimization:
// - Detect checker event with if/else returning continue vs done
// - Detect label loop with recursive jump that increments counter
// - Transform to NativeLoop IR node
// - Emit as native for loop with inlined body
const std = @import("std");
// Checker event: Two branches (continue/done), proc with if/else
~tor loop-step { i: u64, limit: u64, sum: u64 }
| continue { i: u64, sum: u64 }
| done u64
~proc loop-step|zig {
if (i < limit) {
const new_sum = sum + i;
return .{ .continue = .{ .i = i, .sum = new_sum } };
} else {
return .{ .done = sum };
}
}
// Print result
~tor print { result: u64 }
~proc print|zig {
std.debug.print("Sum: {}\n", .{result});
}
// Main flow: Label loop with recursive jump
// Pattern the optimizer should detect:
// - Initial call: loop-step(i: 0, ...)
// - Continue branch: @loop(i: i + 1, ...) - RECURSIVE JUMP with increment
// - Done branch: Exit loop
//
// Expected transformation:
// for (0..10_000_000) |i| {
// sum += i;
// }
~#main_loop loop-step(i: 0, limit: 10_000_000, sum: 0)
| continue c |> @main_loop(i: c.i + 1, limit: 10_000_000, c.sum)
| done d |> print(d.result)
Supporting Files
# Loop Optimization Analysis - FINDINGS
**Date**: 2025-11-01
**Test**: 2007_loop_optimization_basic
**Method**: Scientific benchmarking with incremental hand-optimizations
## Summary
We tested 5 versions to isolate where the 7x performance gap comes from:
| Version | Time | Ratio vs Baseline | What Changed |
|---------|------|-------------------|--------------|
| 1. Baseline (for loop) | 1.1ms | 1.00x | ✅ Native Zig for loop |
| 2. Koru Current | 8.3ms | 7.33x | ❌ Handler calls in loop |
| 3. HandOpt1 (no dead code) | 8.3ms | 7.36x | Removed intermediate variables |
| 4. HandOpt2 (inline handler) | 1.2ms | 1.04x | ✅ Inlined handler body |
| 5. HandOpt3 (for loop) | 1.5ms | 1.35x | Converted while → for |
## Key Findings
### 1. Dead Code Removal: NO IMPACT ❌
**Impact**: 0% (8.3ms → 8.3ms)
Removing intermediate variable unpacking had ZERO effect. Zig's optimizer already eliminates:
```zig
const c = result_0.@"continue"; // ← Zig eliminates this
main_loop_i = c.i + 1; // ← And this becomes direct access
```
**Conclusion**: Don't waste time on dead code elimination pass. Zig handles it!
### 2. Handler Inlining: THE ENTIRE GAP! ✅
**Impact**: 691% speedup (8.3ms → 1.2ms)
Inlining the handler body eliminates:
- **Function call overhead** (10M calls × ~0.7ns each)
- **Struct packing/unpacking** (Input → handler → Output)
- **Union creation and pattern matching** (every iteration checks `.continue` vs `.done`)
**Before (slow):**
```zig
var result = loop_step_event.handler(.{ .i = i, .limit = limit, .sum = sum });
while (result == .@"continue") {
main_loop_i = result.@"continue".i + 1;
main_loop_sum = result.@"continue".sum;
result = loop_step_event.handler(.{ .i = i, .limit = limit, .sum = sum });
}
```
**After (fast):**
```zig
var i: u64 = 0;
var sum: u64 = 0;
while (i < 10_000_000) { // ← Inlined condition check
sum += i; // ← Inlined body
i += 1;
}
```
**Conclusion**: This is THE critical optimization! Everything else is noise.
### 3. Loop Form: While BEATS For! 🤯
**Impact**: -25% (for is SLOWER!)
Converting to native `for (0..N)` loops actually made things WORSE:
- HandOpt2 (while): 1.2ms ✅
- HandOpt3 (for): 1.5ms ❌
**Why?** The `for (0..N)` syntax creates a range object with bounds checking. The `while` loop with direct counter increments is more direct.
**Conclusion**: Keep emitting while loops! Don't transform to for!
## What We Need to Build
### Loop Handler Inlining Transform
**Pattern to detect:**
1. Label loop: `#label event(...) | continue c |> @label(...) | done d |> ...`
2. Checker event: Event with `if/else` returning `continue` vs `done`
3. Simple body: Arithmetic/state updates (no complex control flow)
**Transformation:**
1. Extract condition from handler's if-check
2. Extract body from continue-branch logic
3. Replace while+handler loop with while+inlined body
4. Preserve done-branch handling after loop
**Pseudocode:**
```
if (flow is label_loop &&
continue_branch jumps to same label &&
invoked_event is checker_pattern) {
inline handler body into while loop
}
```
## Infrastructure We Have
✅ **Transform framework**: `transforms/inline_small_events.zig` shows the pattern
✅ **Functional transforms**: `transform_functional.zig` for AST manipulation
✅ **Compiler hooks**: `main.zig` line 2680 shows where transforms apply
✅ **Benchmarking**: hyperfine setup in `benchmark.sh`
## Next Steps
1. **Create `transforms/inline_loop_handlers.zig`**
- Detect the pattern (checker event + label loop)
- Extract condition and body from handler
- Generate inlined while loop
2. **Register transform in main.zig**
- Add after inline_small_events
- Apply before code generation
3. **Verify with test 2007**
- Should go from 8.3ms → ~1.2ms
- Threshold is 1.05x, we'll hit 1.04x!
4. **Apply to nbody**
- Test 2101c has recursive event loops
- Should close significant performance gap
## Performance Prediction
**Test 2007 (loop):**
- Current: 8.3ms (7.3x slow)
- With inlining: ~1.2ms (1.04x) ✅ PASS
**Test 2101c (nbody extreme):**
- Current: 0.214s (1.56x slow vs hand-opt)
- With inlining: ~0.14s (1.09x) ✅ Near Rust!
## Why This Matters
This single optimization closes:
- **7x gap** in simple loops
- **~30-40% gap** in complex numerical code (nbody)
- Makes event-driven loops **competitive with imperative code**
Without this, Koru loops are impractical. With it, they're zero-cost!
---
**The Data Speaks**: Inline handlers in loops. Nothing else matters.
// Hand-written Zig baseline for loop optimization test
// This is what the Koru optimizer SHOULD generate from the checker event pattern
const std = @import("std");
pub fn main() !void {
var sum: u64 = 0;
// Native for loop - what the optimizer should emit
for (0..10_000_000) |i| {
sum += i;
}
std.debug.print("Sum: {}\n", .{sum});
}
#!/bin/bash
# Benchmark: Loop Optimization - Basic Checker Event Pattern
# Compare multiple optimization levels to isolate performance impact
set -e
echo "Building all versions..."
echo " 1. Baseline (native for loop)..."
zig build-exe baseline.zig -O ReleaseFast -femit-bin=baseline
echo " 2. Koru current (from compiler)..."
if [ -f "output_emitted.zig" ]; then
zig build-exe output_emitted.zig -O ReleaseFast -femit-bin=koru_current
elif [ -f "output" ]; then
cp output koru_current
chmod +x koru_current
else
echo "ERROR: No Koru output found (output_emitted.zig or output)"
exit 1
fi
echo " 3. HandOpt1 (remove dead unpacking)..."
zig build-exe handopt1.zig -O ReleaseFast -femit-bin=handopt1
echo " 4. HandOpt2 (inline handler body)..."
zig build-exe handopt2.zig -O ReleaseFast -femit-bin=handopt2
echo " 5. HandOpt3 (native for loop)..."
zig build-exe handopt3.zig -O ReleaseFast -femit-bin=handopt3
echo ""
echo "Running benchmarks with hyperfine..."
# Check if hyperfine is installed
if ! command -v hyperfine &> /dev/null; then
echo "ERROR: hyperfine not installed"
echo "Install with: brew install hyperfine (macOS) or cargo install hyperfine"
exit 1
fi
# Run benchmark comparing all versions
hyperfine --warmup 5 --runs 30 --shell=none \
--export-json results.json \
--command-name "1. Baseline (for loop)" './baseline' \
--command-name "2. Koru Current" './koru_current' \
--command-name "3. HandOpt1 (no dead code)" './handopt1' \
--command-name "4. HandOpt2 (inline handler)" './handopt2' \
--command-name "5. HandOpt3 (for loop)" './handopt3'
echo ""
echo "Benchmark complete! Results saved to results.json"
echo ""
echo "Analysis:"
echo " Baseline vs Koru: Shows total gap"
echo " Koru vs HandOpt1: Dead code removal impact"
echo " HandOpt1 vs HandOpt2: Handler inlining impact"
echo " HandOpt2 vs HandOpt3: Loop form impact (while vs for)"
#!/bin/bash
# Benchmark script for loop optimization variants
set -e
echo "═══════════════════════════════════════════════════════════"
echo " LOOP OPTIMIZATION BENCHMARK - Scientific Comparison"
echo "═══════════════════════════════════════════════════════════"
echo ""
# Build all variants
echo "🔨 Building variants..."
echo ""
VARIANTS=(
"v0_theoretical_max"
"v1_baseline"
"v2_inline_keyword"
"v3_inline_force"
"v4_manual_inline"
"v5_native_for"
"v6_no_struct_input"
"v7_no_union_output"
"v8_inline_plus_no_struct"
"v9_inline_body_only"
"v10_combined_all"
)
for variant in "${VARIANTS[@]}"; do
echo " Building $variant..."
zig build-exe "$variant.zig" -O ReleaseFast -femit-bin="$variant" 2>/dev/null
done
echo ""
echo "✅ All variants built"
echo ""
# Check if hyperfine is installed
if ! command -v hyperfine &> /dev/null; then
echo "❌ hyperfine not found. Install with: brew install hyperfine"
exit 1
fi
echo "🏃 Running benchmarks..."
echo ""
# Run benchmarks
hyperfine --warmup 3 --runs 10 \
--export-json results.json \
--export-markdown results.md \
"./v0_theoretical_max" \
"./v1_baseline" \
"./v2_inline_keyword" \
"./v3_inline_force" \
"./v4_manual_inline" \
"./v5_native_for" \
"./v6_no_struct_input" \
"./v7_no_union_output" \
"./v8_inline_plus_no_struct" \
"./v9_inline_body_only" \
"./v10_combined_all"
echo ""
echo "═══════════════════════════════════════════════════════════"
echo " RESULTS"
echo "═══════════════════════════════════════════════════════════"
echo ""
# Show the markdown table
if [ -f results.md ]; then
cat results.md
fi
echo ""
echo "✅ Benchmark complete"
echo "📊 Results saved to:"
echo " - results.json (machine-readable)"
echo " - results.md (human-readable)"
echo ""
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|:---|---:|---:|---:|---:|
| `./v0_theoretical_max` | 1.2 ± 0.2 | 1.0 | 1.5 | 1.08 ± 0.20 |
| `./v1_baseline` | 8.8 ± 0.5 | 8.3 | 9.6 | 7.95 ± 1.05 |
| `./v2_inline_keyword` | 4.0 ± 0.1 | 3.9 | 4.2 | 3.67 ± 0.44 |
| `./v3_inline_force` | 4.0 ± 0.2 | 3.8 | 4.3 | 3.63 ± 0.46 |
| `./v4_manual_inline` | 1.1 ± 0.1 | 1.0 | 1.4 | 1.00 |
| `./v5_native_for` | 1.3 ± 0.4 | 0.9 | 1.9 | 1.18 ± 0.39 |
| `./v6_no_struct_input` | 8.4 ± 0.2 | 8.1 | 8.8 | 7.61 ± 0.92 |
| `./v7_no_union_output` | 8.7 ± 0.8 | 8.0 | 10.3 | 7.88 ± 1.16 |
| `./v8_inline_plus_no_struct` | 4.1 ± 0.1 | 4.0 | 4.4 | 3.75 ± 0.45 |
| `./v9_inline_body_only` | 1.1 ± 0.1 | 0.9 | 1.3 | 1.02 ± 0.16 |
| `./v10_combined_all` | 5.0 ± 0.8 | 3.8 | 5.9 | 4.50 ± 0.90 |
Flows
flow ~loop-step click a branch to expand · @labels scroll to their anchor
#main_loop loop-step (i: 0, limit: 10_000_000, sum: 0)
Test Configuration
MUST_RUN THRESHOLD 1.05
Post-validation Script:
#!/bin/bash
# Post-validation: Check performance is within threshold
set -e
if [ ! -f "results.json" ]; then
echo "⚠️ No benchmark results found (results.json missing)"
echo " Running benchmark..."
bash benchmark.sh
fi
if [ ! -f "results.json" ]; then
echo "❌ FAIL: Benchmark did not produce results.json"
exit 1
fi
# Check if jq is installed
if ! command -v jq &> /dev/null; then
echo "⚠️ jq not installed (needed to parse benchmark results)"
echo " Install with: brew install jq (macOS) or apt install jq (Linux)"
echo " Skipping performance validation..."
exit 0
fi
THRESHOLD=$(cat THRESHOLD)
# Parse results (hyperfine format)
BASELINE_TIME=$(jq -r '.results[0].mean' results.json)
KORU_TIME=$(jq -r '.results[1].mean' results.json)
# Calculate ratio (Koru / Baseline)
RATIO=$(echo "scale=4; $KORU_TIME / $BASELINE_TIME" | bc -l)
echo ""
echo "Performance Results:"
echo " Baseline (Zig): ${BASELINE_TIME}s"
echo " Koru (Optimized): ${KORU_TIME}s"
echo " Ratio: ${RATIO}x"
echo " Threshold: ${THRESHOLD}x"
echo ""
# Compare to threshold
if (( $(echo "$RATIO > $THRESHOLD" | bc -l) )); then
echo "❌ PERFORMANCE REGRESSION!"
echo " Koru is ${RATIO}x slower than baseline"
echo " Threshold is ${THRESHOLD}x"
echo " Regression: $(echo "scale=1; ($RATIO - 1) * 100" | bc -l)%"
exit 1
elif (( $(echo "$RATIO < 0.95" | bc -l) )); then
echo "✅ PERFORMANCE IMPROVED!"
echo " Koru is FASTER than baseline (${RATIO}x)"
else
echo "✅ Performance within threshold"
echo " Overhead: $(echo "scale=1; ($RATIO - 1) * 100" | bc -l)%"
fi
exit 0