examples: lisp — proper tail calls in eval
This commit is contained in:
@@ -94,6 +94,25 @@ always one of the above and shows up under valgrind/gdb the same
|
||||
way it did the first time: silently dropped store, missing field
|
||||
read, garbage payload after a tagged-union return.
|
||||
|
||||
## Tail-call optimization
|
||||
|
||||
`eval` is a single `for(true)` trampoline; a tail position rewrites
|
||||
`v` (current expr) and `cure` (current env) in place and `continue`s
|
||||
instead of recursing. Tail positions are:
|
||||
|
||||
- the chosen branch of `if`
|
||||
- the last form of `begin` / `let` / a lambda body
|
||||
- a direct call in any of the above
|
||||
|
||||
A `tailed` flag flips on the first jump into a fresh scope (LET
|
||||
binding, lambda body) so subsequent `define`/`set!` mutate the local
|
||||
`cure` chain rather than the caller's `ein` slot — same scoping the
|
||||
old recursive `run_body(_, &pe)` path gave.
|
||||
|
||||
Mutual recursion still doesn't work — that's a `define` tie-back
|
||||
limitation, not a TCO one. Defining `evn?` before `od?` captures an
|
||||
env where `od?` is unbound; the tie-back only adds the self-binding.
|
||||
|
||||
## Interpreter limitations (design, not bug)
|
||||
|
||||
- **No GC.** Every cons / value / env frame is `mmap`'d via
|
||||
@@ -101,10 +120,9 @@ read, garbage payload after a tagged-union return.
|
||||
the process exits. The test_huge demo peaks at ~595 MB under
|
||||
`--pages-as-heap=yes`. Per-top-level-form arena reset would cut
|
||||
this by ~100× — see the closing note in `repl()` for the hook
|
||||
point.
|
||||
- **No tail-call optimization.** Recursion grows the C-side stack
|
||||
one frame per Lisp call. `(spin 100000 0)` will eventually stack-
|
||||
overflow even though it's tail-recursive in source.
|
||||
point. Note: `rt_alloc` is one mmap syscall per call returning a
|
||||
whole 4KB page, so even tail-recursive loops are bottlenecked on
|
||||
allocation, not on Lisp work — `(spin 100000 0)` runs in ~4s.
|
||||
- **No bigints.** `i64` wraps silently on overflow. `(fact 21)`
|
||||
rolls over.
|
||||
- **Float printing is fixed `%.6f`.** `1.0` prints as `1.000000`.
|
||||
|
||||
Reference in New Issue
Block a user