From 0d6d0bf4390e2d32ebb89bd43235d5f6420cb88b Mon Sep 17 00:00:00 2001 From: giles Date: Sat, 25 Apr 2026 04:29:57 +0000 Subject: [PATCH] forth: TCO at colon-def endings (no extra frame on tail-call ops) --- lib/forth/compiler.sx | 18 +++++++++++++++--- plans/forth-on-sx.md | 12 +++++++++++- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/lib/forth/compiler.sx b/lib/forth/compiler.sx index 1d744ce3..34adf066 100644 --- a/lib/forth/compiler.sx +++ b/lib/forth/compiler.sx @@ -274,15 +274,27 @@ (first cs) (forth-find-do (rest cs)))))) +;; Run a colon-def body. The PC is a one-cell dict so step-op can mutate +;; it for branches/loops/exit. As an explicit TCO, when we reach the +;; final op AND it's a plain function (not a branch dict), call it in +;; tail position — no recurse, no post-increment — so chains of +;; colon-def `: A ... B ;` calls don't accumulate continuation frames. (define forth-run-body (fn (s ops pc n) (when (< (get pc "v") n) - (begin - (forth-step-op s (nth ops (get pc "v")) pc) - (forth-run-body s ops pc n))))) + (let + ((cur (get pc "v"))) + (let + ((op (nth ops cur))) + (if + (and (not (dict? op)) (= (+ cur 1) n)) + (op s) + (begin + (forth-step-op s op pc) + (forth-run-body s ops pc n)))))))) ;; Override forth-interpret-token to branch on compile mode. (define diff --git a/plans/forth-on-sx.md b/plans/forth-on-sx.md index 243830a2..7476f759 100644 --- a/plans/forth-on-sx.md +++ b/plans/forth-on-sx.md @@ -99,13 +99,23 @@ Representation: ### Phase 6 — speed - [x] Inline primitive calls during compile (skip dict lookup) -- [ ] Tail-call optimise colon-def endings +- [x] Tail-call optimise colon-def endings - [ ] JIT cooperation: mark compiled colon-defs as VM-eligible ## Progress log _Newest first._ +- **Phase 6 — TCO at colon-def endings (Hayes unchanged at 618/638).** + `forth-run-body` now special-cases the final op when it's a plain + function (not a branch dict): we call it in tail position with no + pc-increment and no recursive `forth-run-body` call. This means + the SX CEK can collapse the continuation frame, so chains like + `: A ... B ; : B ... C ; …` and `RECURSE` deep-recursion test + cases run without piling up frames at each colon-def boundary. + All 306 internal tests still green; verified 5000-deep + `COUNTDOWN RECURSE` still terminates fine. + - **Phase 6 — inline primitive calls (Hayes unchanged at 618/638).** `forth-compile-call` now appends the looked-up word's body fn directly to the colon-def body instead of wrapping it in