You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Host-side autofixer + VM closures + direct-call benchmarks
Three more architectural moves:
Track 1 — Direct-call benchmark variant:
examples/benchmarks.omc gains a second loop that calls each
benchmark function directly (bench_int_add(N)) instead of
through call(fn, args). Comparison reveals exactly where the
Rust VM advantage lives.
Headline number: recursive fib(22) drops from 2.3 ms/op
(tree-walk) to 0.95 ms/op (VM direct call) — a 2.42x speedup
where Op::Call dispatch dominates. Reflective dispatch via
call(fn, args) is ~identical between tree-walk and VM because
it always routes through invoke_user_function (tree-walk).
The benchmark suite now produces actionable signal for future
VM work.
Track 4 — Host-side autofixer (OMC_HEAL=1):
The H.1-H.5 healing logic was previously stuck inside OMC demo
files. Now it's a toolchain feature: OMC_HEAL=1 walks the AST
after parsing, applies four classes of rewrites, prints
diagnostics to stderr, and runs the healed AST.
Classes:
Harmonic: numeric literal close to Fibonacci (|Δ| ≤ 3)
→ rewrite to nearest attractor
Typo: call to unknown name, Levenshtein within 2
→ resolve to best match (user fns preferred over
builtins as tiebreaker — fbi → fib, not fbi → pi)
/0: literal divide-by-zero
→ Call("safe_divide", [x, 0])
Arity: user-fn call with wrong arity
→ pad with 0 literals (too few)
→ truncate (too many)
Only fires on user fns whose declared arity we know.
~250 lines added to interpreter.rs: heal_ast, heal_stmt,
heal_expr, plus module-level helpers (edit_distance,
closest_name, is_on_fibonacci_attractor, HEAL_BUILTIN_NAMES).
OMC_HEAL_QUIET=1 suppresses the diagnostic preamble.
Demo: examples/heal_pass_demo.omc — 8 diagnostics, all four
classes exercised, healed program executes cleanly.
Track 2 — Closures on the Rust VM (MVP):
Lambdas previously errored under OMC_VM=1. Now they compile:
New Op::Lambda(name) opcode. Compile-time: Expression::Lambda
registers body as CompiledFunction in module.functions AND
stashes the AST body in a new module.lambda_asts field.
Runtime: pushes Value::Function with name + captured = current
scope (Rc). Sibling lambdas share captured Rc.
main.rs registers every module.lambda_asts entry into the
interpreter's function table before vm.run_module(). Closure
invocation routes through call_first_class_function →
invoke_user_function (tree-walk for body); registration makes
the AST body discoverable.
Body execution still tree-walks — fast bytecode-VM body
execution is future work. But the CREATE step is bytecode-
native, and OMC_VM=1 works end-to-end on programs that use
lambdas now.
Verified: examples/test_runner.omc (uses inline lambdas for
arr_filter) runs cleanly under OMC_VM=1 — 5/6 tests pass.
Bank-account pattern: identical output on both paths.
Architectural side-effects:
Module gains lambda_asts: Vec<(String, Vec<String>,
Vec<Statement>)>. Default empty so existing callers don't break.
Compiler gains pending_lambda_asts; nested compilers drain
into their parent.
Interpreter gains pub register_lambda(name, params, body).
Op::Lambda(String) has a disassembly form.
Regression: V.9b ✓✓✓ unchanged. H.5: 6/6 converge. test_runner:
5/6 pass on BOTH tree-walk and OMC_VM=1. safe_keyword_host,
module_demo, mutable_closure_test, benchmarks all good.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
🎯 **The healer becomes a toolchain feature; lambdas work on the Rust VM; direct-call benchmark variant reveals the VM's 2.4× speedup on recursion.**
10
+
11
+
#### Track 1 — Direct-call benchmark variant
12
+
13
+
Added a second benchmark loop to `examples/benchmarks.omc` that calls each function directly (`bench_int_add(N)`) instead of through `call(fn, args)`. The two loops together reveal exactly where the Rust VM advantage lives.
14
+
15
+
**Result on a modern laptop:**
16
+
17
+
| Operation | Tree-walk | VM reflective | VM direct | Speedup |
|`recursive fib(22)`| 2.3 ms | 2.3 ms |**0.95 ms**|**2.42×**|
23
+
24
+
The big finding: reflective dispatch (`call(fn, args)`) routes through tree-walk regardless of `OMC_VM`. **Direct calls hit the bytecode VM hot path** — and `recursive fib(22)` shows a 2.4× speedup, where the Op::Call cycle dominates. The benchmark suite now produces actionable signal for future VM work.
25
+
26
+
#### Track 4 — Host-side autofixer (`OMC_HEAL=1`)
27
+
28
+
The H.1–H.5 self-healing demos lived inside OMC programs — you'd run `self_healing_h5.omc` and it healed a hardcoded broken-source string. Useful as a research demonstration, but you couldn't apply it to your own code.
29
+
30
+
This commit lifts the healing pass into the **host toolchain**. `OMC_HEAL=1` walks the AST after parsing, applies four classes of rewrites, prints diagnostics to stderr, then executes the healed AST.
-**Harmonic** (literal close to Fibonacci): rewrite to nearest attractor when `|Δ| ≤ 3`.
52
+
-**Identifier typo at call site**: Levenshtein within distance 2; tiebreaker prefers user-defined functions over builtins. This catches `fbi → fib` (not `fbi → pi`, which is also distance 2 but is a builtin).
-**Arity auto-pad / truncate (H.6)**: user-fn call with too few args → pad with `0` literals; too many → truncate. Only fires on user functions (we know their declared arity).
55
+
56
+
The implementation is ~250 lines in `interpreter.rs` — `heal_ast`, `heal_stmt`, `heal_expr`, plus module-level helpers `edit_distance`, `closest_name`, `is_on_fibonacci_attractor`, and the `HEAL_BUILTIN_NAMES` static slice that keeps the typo-checker from flagging real builtins.
57
+
58
+
`OMC_HEAL_QUIET=1` suppresses the diagnostic preamble — heal still happens silently.
59
+
60
+
#### Track 2 — Closures on the Rust VM (MVP)
61
+
62
+
Lambdas previously errored under `OMC_VM=1` with "Lambda expressions require tree-walk." Now they compile:
63
+
64
+
- New `Op::Lambda(name)` opcode. Compile-time: `Expression::Lambda { params, body }` registers the body as a `CompiledFunction` in `module.functions` under a fresh `__lambda_N` name AND stashes the AST body in a new `module.lambda_asts` field. Runtime: pushes a `Value::Function` with `name` and `captured = Some(self.locals.last().cloned())` — sibling lambdas share the captured Rc.
65
+
-`main.rs` registers every entry in `module.lambda_asts` into the interpreter's function table before `vm.run_module(...)`. Closure invocation routes through `call_first_class_function → invoke_user_function` (tree-walk semantics for the body), so this registration makes the body discoverable.
66
+
- Body execution still routes through tree-walk — fast bytecode-VM body execution is future work. But the COMPILE and CREATE steps are now bytecode-native, and `OMC_VM=1` works end-to-end on programs that use lambdas.
67
+
68
+
Verified: `examples/test_runner.omc` (which uses inline lambdas for `arr_filter`) runs cleanly under `OMC_VM=1` — 5/6 tests pass (the intentional failure still fires).
69
+
70
+
Bank-account pattern produces identical output on both interpretation paths (100, 150, 120, 120).
71
+
72
+
#### Architectural side-effects
73
+
74
+
-`Module` gained a `lambda_asts: Vec<(String, Vec<String>, Vec<Statement>)>` field. Doesn't break existing callers because `Module::default()` returns empty.
75
+
-`Compiler` gained a `pending_lambda_asts` field that nested compilers drain into their parent.
76
+
-`Interpreter` gained a public `register_lambda(name, params, body)` method, used by `main.rs` when running in VM mode.
77
+
- New `Op::Lambda(String)` disassembly form.
78
+
79
+
#### Regression
80
+
81
+
V.9b ✓✓✓ unchanged. H.5: 6/6 demos converge. Test runner: 5/6 (1 intentional failure) on BOTH tree-walk and `OMC_VM=1`. `safe_keyword_host`, `module_demo`, `mutable_closure_test`, `benchmarks` all produce expected output. `heal_pass_demo` heals 8 issues and runs to completion.
🎯 **Three more architectural moves: closures gain shared mutable state, the module system gets namespaced imports, and OMC has its first benchmark suite.**
Copy file name to clipboardExpand all lines: README.md
+3-1Lines changed: 3 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -50,7 +50,9 @@ What this is **not**: a fast runtime, a production toolchain, a stable API, a de
50
50
| Built-in test runner |`examples/test_runner.omc`|`fn test_*()` functions auto-discovered via `defined_functions()` and dispatched via `call(name, args)`. `assert_eq` / `assert_array_eq` etc. record failures in host-side state |
51
51
| Mutable closures (Rc<RefCell> shared capture) |`examples/test_runner.omc`| Bank-account pattern: multiple closures from `make_account(100)` share the same `balance` binding; mutations propagate across all of them |
52
52
| Module system with namespace aliasing |`examples/module_demo.omc`|`import "math_module.omc" as math` then `math.fib_up_to(100)` → `0,1,1,2,3,5,8,13,21,34,55,89`. Idempotent re-import; literal-path resolution |
53
-
| Benchmark suite |`examples/benchmarks.omc`| Times `int_add` / `str_concat` / `arr_push` / `recursive fib(22)` / `is_fibonacci` etc. with per-op ns. Run with `OMC_VM=1` to compare against the bytecode VM |
53
+
| Benchmark suite |`examples/benchmarks.omc`| Times `int_add` / `str_concat` / `arr_push` / `recursive fib(22)` / `is_fibonacci` etc. with per-op ns. Run with `OMC_VM=1` to compare against the bytecode VM. Direct-call variant shows the VM's 2.4× speedup on `recursive_fib`. |
54
+
| Host-side self-healing pass (`OMC_HEAL=1`) |`examples/heal_pass_demo.omc`| Any OMC program benefits: harmonic-violation rewrites, typo correction with user-fn-preferred tiebreaker, literal `/0` → `safe_divide`, and arity auto-pad/truncate at call sites |
55
+
| Closures on the bytecode VM |`examples/test_runner.omc` (run with `OMC_VM=1`) | Lambda expressions now compile under VM. Bank-account pattern produces identical output on tree-walk and VM. Test runner runs cleanly via `OMC_VM=1`|
54
56
55
57
Run any of these with the binary built from this repo:
Copy file name to clipboardExpand all lines: STDLIB.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -329,7 +329,7 @@ fn make_account(balance) {
329
329
}
330
330
```
331
331
332
-
VM caveat: lambdas execute via tree-walk, not the Rust bytecode VM. The VM's `OMC_VM=1` path bails with an error on `Expression::Lambda` because the bytecode VM has no captured-scope plumbing. Tree-walk works cleanly.
332
+
**VM update (2026-05-14):** lambdas now compile on the Rust VM. `Op::Lambda(name)` creates a `Value::Function` at runtime with the current scope captured. Body execution still routes through tree-walk via `call_first_class_function`, so closures aren't VM-fast yet — but they no longer error under `OMC_VM=1`. The test runner runs cleanly via the VM now.
0 commit comments