Emit direct self-calls for named-fn self-recursion

A self-recursive call to a named fn compiled to (jolt-invoke fib ...) instead of
a direct (fib ...): emit-invoke handled a :local callee only when it was NOT a
known proc, so a :local that IS in *known-procs* (the letrec-bound self-name) fell
through to the :else jolt-invoke branch. Now a :local known proc emits a direct
Scheme call — no jolt-invoke, no per-call arg-list consing; case-lambda handles
arity.

fib 30: 63.3ms -> 4.7ms (faster than JVM Clojure's 7.1ms; was 9x slower). The win
is on every self-recursive non-loop fn, including the compiler's own. No semantic
change — selfhost holds, make test green, shakesmoke/buildsmoke byte-identical.

Re-mint (backend is seed). Corpus rows pin self-recursion across fixed/multi/
variadic arities.
This commit is contained in:
Yogthos 2026-06-26 00:34:29 -04:00
parent 5a5ea8058a
commit 63a8cb1cb2
4 changed files with 696 additions and 686 deletions

View file

@ -520,9 +520,15 @@
(if (empty? as) "" (str " " (str/join " " as))) ")")))
(= :host (:op fnode))
(throw (ex-info (str "emit: unsupported host call `" (:name fnode) "`") {}))
;; a :local callee that isn't a known procedure -> dynamic IFn dispatch.
(and (= :local (:op fnode)) (not (*known-procs* (munge-name (:name fnode)))))
(invoke)
;; a :local callee: a known procedure (the letrec-bound self-name of a named
;; fn — i.e. self-recursion) is a real Scheme proc, so call it directly with
;; no jolt-invoke / arg consing; case-lambda handles arity. Any other local
;; holds an arbitrary IFn -> dynamic dispatch.
(= :local (:op fnode))
(if (*known-procs* (munge-name (:name fnode)))
(order-args (fn [as] (str "(" (munge-name (:name fnode))
(if (seq as) (str " " (str/join " " as)) "") ")")))
(invoke))
;; closed-world direct call: the callee var is an app fn def already emitted
;; with a Scheme binding — apply it directly, no var lookup, no jolt-invoke.
;; Only fn-valued defs qualify; a non-fn invokable value (a map/set/keyword