@@ -2060,10 +2060,98 @@ Like `cl-flet' but the definitions can refer to previous ones.
20602060 ((null (cdr bindings)) `(cl-flet ,bindings ,@body ))
20612061 (t `(cl-flet (,(pop bindings)) (cl-flet* ,bindings ,@body )))))
20622062
2063+ (defun cl--self-tco (var fargs body )
2064+ ; ; This tries to "optimize" tail calls for the specific case
2065+ ; ; of recursive self-calls by replacing them with a `while' loop.
2066+ ; ; It is quite far from a general tail-call optimization, since it doesn't
2067+ ; ; even handle mutually recursive functions.
2068+ (letrec
2069+ ((done nil ) ; ; Non-nil if some TCO happened.
2070+ (retvar (make-symbol " retval" ))
2071+ (ofargs (mapcar (lambda (s ) (if (memq s cl--lambda-list-keywords) s
2072+ (make-symbol (symbol-name s))))
2073+ fargs))
2074+ (opt-exps (lambda (exps ) ; ; `exps' is in tail position!
2075+ (append (butlast exps)
2076+ (list (funcall opt (car (last exps)))))))
2077+ (opt
2078+ (lambda (exp ) ; ; `exp' is in tail position!
2079+ (pcase exp
2080+ ; ; FIXME: Optimize `apply' ?
2081+ (`(funcall ,(pred (eq var)) . ,aargs )
2082+ ; ; This is a self-recursive call in tail position.
2083+ (let ((sets nil )
2084+ (fargs ofargs))
2085+ (while fargs
2086+ (pcase (pop fargs)
2087+ ('&rest
2088+ (push (pop fargs) sets)
2089+ (push `(list . ,aargs ) sets)
2090+ ; ; (cl-assert (null fargs))
2091+ )
2092+ ('&optional nil )
2093+ (farg
2094+ (push farg sets)
2095+ (push (pop aargs) sets))))
2096+ (setq done t )
2097+ `(progn (setq . ,(nreverse sets))
2098+ :recurse )))
2099+ (`(progn . ,exps ) `(progn . ,(funcall opt-exps exps)))
2100+ (`(if ,cond ,then . ,else )
2101+ `(if ,cond ,(funcall opt then) . ,(funcall opt-exps else)))
2102+ (`(cond . ,conds )
2103+ (let ((cs '()))
2104+ (while conds
2105+ (pcase (pop conds)
2106+ (`(,exp )
2107+ (push (if conds
2108+ ; ; This returns the value of `exp' but it's
2109+ ; ; only in tail position if it's the
2110+ ; ; last condition.
2111+ `((setq ,retvar ,exp ) nil )
2112+ `(,(funcall opt exp)))
2113+ cs))
2114+ (exps
2115+ (push (funcall opt-exps exps) cs))))
2116+ (if (eq t (caar cs))
2117+ `(cond . ,(nreverse cs))
2118+ `(cond ,@(nreverse cs) (t (setq ,retvar nil ))))))
2119+ ((and `(,(or 'let 'let* ) ,bindings . ,exps )
2120+ (guard
2121+ ; ; Note: it's OK for this `let' to shadow any
2122+ ; ; of the formal arguments since we will only
2123+ ; ; setq the fresh new `ofargs' vars instead ;-)
2124+ (let ((shadowings (mapcar #'car bindings)))
2125+ ; ; If `var' is shadowed, then it clearly can't be
2126+ ; ; tail-called any more.
2127+ (not (memq var shadowings)))))
2128+ `(,(car exp) ,bindings . ,(funcall opt-exps exps)))
2129+ (_
2130+ `(progn (setq ,retvar ,exp ) nil ))))))
2131+
2132+ (let ((optimized-body (funcall opt-exps body)))
2133+ (if (not done)
2134+ (cons fargs body)
2135+ ; ; We use two sets of vars: `ofargs' and `fargs' because we need
2136+ ; ; to be careful that if a closure captures a formal argument
2137+ ; ; in one iteration, it needs to capture a different binding
2138+ ; ; then that of other iterations, e.g.
2139+ (cons
2140+ ofargs
2141+ `((let (,retvar )
2142+ (while (let ,(delq nil
2143+ (cl-mapcar
2144+ (lambda (a oa )
2145+ (unless (memq a cl--lambda-list-keywords)
2146+ (list a oa)))
2147+ fargs ofargs))
2148+ . ,optimized-body ))
2149+ ,retvar )))))))
2150+
20632151;;;### autoload
20642152(defmacro cl-labels (bindings &rest body )
2065- " Make local (recursive) function definitions.
2066- Each definition can take the form (FUNC ARGLIST BODY...) where
2153+ " Make local (recursive) function definitions.
2154+ +BINDINGS is a list of definitions of the form (FUNC ARGLIST BODY...) where
20672155FUNC is the function name, ARGLIST its arguments, and BODY the
20682156forms of the function body. FUNC is defined in any BODY, as well
20692157as FORM, so you can write recursive and mutually recursive
@@ -2075,17 +2163,33 @@ details.
20752163 (let ((binds ()) (newenv macroexpand-all-environment))
20762164 (dolist (binding bindings)
20772165 (let ((var (make-symbol (format " --cl-%s -- " (car binding)))))
2078- (push (list var `( cl-function ( lambda . ,( cdr binding)) )) binds)
2166+ (push (cons var ( cdr binding)) binds)
20792167 (push (cons (car binding)
20802168 (lambda (&rest args )
20812169 (if (eq (car args) cl--labels-magic)
20822170 (list cl--labels-magic var)
20832171 (cl-list* 'funcall var args))))
20842172 newenv)))
2085- (macroexpand-all `(letrec ,(nreverse binds) ,@body )
2086- ; ; Don't override lexical-let's macro-expander.
2087- (if (assq 'function newenv) newenv
2088- (cons (cons 'function #'cl--labels-convert ) newenv)))))
2173+ ; ; Don't override lexical-let's macro-expander.
2174+ (unless (assq 'function newenv)
2175+ (push (cons 'function #'cl--labels-convert ) newenv))
2176+ ; ; Perform self-tail call elimination.
2177+ (setq binds (mapcar
2178+ (lambda (bind )
2179+ (pcase-let*
2180+ ((`(,var ,sargs . ,sbody ) bind)
2181+ (`(function (lambda ,fargs . ,ebody ))
2182+ (macroexpand-all `(cl-function (lambda ,sargs . ,sbody ))
2183+ newenv))
2184+ (`(,ofargs . ,obody )
2185+ (cl--self-tco var fargs ebody)))
2186+ `(,var (function (lambda ,ofargs . ,obody )))))
2187+ (nreverse binds)))
2188+ `(letrec ,binds
2189+ . ,(macroexp-unprogn
2190+ (macroexpand-all
2191+ (macroexp-progn body)
2192+ newenv)))))
20892193
20902194; ; The following ought to have a better definition for use with newer
20912195; ; byte compilers.
0 commit comments