Zulip Chat Archive
Stream: std4
Topic: tail recursion
Mario Carneiro (Nov 28 2023 at 00:51):
@Scott Morrison said:
:light_bulb: Interesting. I wish we had an annotation to require / check tail-recursiveness...
You mean like this?
import Lean
open Lean
partial def visitBody (f : IR.FunId) : IR.FnBody → StateM (NameSet × Bool) Unit
| .vdecl x _ e b => do
let tailcall := match b with | .ret (.var y) => x == y | _ => false
match e with
| .fap c _ =>
if tailcall && c == f then modify fun (s, _) => (s, true)
else modify fun (s, b) => (s.insert c, b)
| .pap c _ => modify fun (s, b) => (s.insert c, b)
| _ => pure ()
visitBody f b
| .jdecl _ _ v b => do visitBody f v; visitBody f b
| .case _ _ _ cs => for c in cs do visitBody f c.body
| e => if e.isTerminal then pure () else visitBody f e.body
elab "#ir_stats " decl:ident : command => do
let name := decl.getId
let some decl := (IR.declMapExt.getState (← getEnv)).find? name
| throwError "IR declaration {name} not found"
match decl with
| .extern .. => logInfo m!"{name} is external"
| .fdecl (f := f) (body := b) .. =>
let (_, calls, tailRec) := (visitBody f b).run ({}, false)
if !calls.isEmpty then
logInfo m!"{name} calls (recursively) {calls.toList}"
if tailRec then
logInfo m!"{name} is tail-recursive"
else if calls.contains f then
logInfo m!"{name} is properly recursive"
else
logInfo m!"{name} is not recursive"
@[specialize] def map {α β} (f : α → β) : List α → List β
| [] => []
| a::as => f a :: map f as
@[inline] def mapTR {α β} (f : α → β) (as : List α) : List β :=
loop as []
where
@[specialize] loop : List α → List β → List β
| [], bs => bs.reverse
| a::as, bs => loop as (f a :: bs)
#ir_stats map._rarg -- map._rarg is properly recursive
#ir_stats mapTR.loop._rarg -- mapTR.loop._rarg is tail-recursive
Scott Morrison (Nov 28 2023 at 03:27):
Nice!
Perhaps the name should just be #check_recursive
?
Scott Morrison (Nov 28 2023 at 03:27):
Could this go in Lean.Util.Recursion
?
Scott Morrison (Nov 28 2023 at 03:28):
Would it make sense to allow leaving off the ._rarg
?
Mario Carneiro (Nov 28 2023 at 03:32):
The idea behind this interface was more to give general information about IR declarations, kind of like #print
but for IR decls instead of regular decls. For checking for bounded stack space, I was thinking of a more all-in-one solution that traverses the whole dag of function declarations starting at the one you specify, and reports if any of them is properly recursive (or mutually recursive). That way you don't have to put the _rarg
. That would also presumably work better as an attribute like you originally suggested.
There is a limitation though: we can only inspect the definitions of declarations in the same file - the IR for other files is not available, only the signatures, so we have to treat any calls to upstream functions as opaque.
Mario Carneiro (Nov 28 2023 at 03:34):
I didn't show it above but the reason #ir_stats
prints the direct calls is so that you can be lead to inspect the _rarg
function, I didn't just guess those names above
Last updated: Dec 20 2023 at 11:08 UTC