janet-lang / janet

A dynamic language and bytecode vm
https://janet-lang.org
MIT License
3.47k stars 224 forks source link

`try` and `defer` break tail call optimization. #1406

Closed amano-kenji closed 7 months ago

amano-kenji commented 7 months ago
(defn recurse
  []
  (ev/sleep 1)
  (try
    (do
      (debug/stacktrace (fiber/current) "print stack" "")
      (recurse))
    ([_])))

(defn recurse2
  []
  (ev/sleep 1)
  (with [proc (os/spawn ["ls"] :p {:out :pipe})]
    (debug/stacktrace (fiber/current) "print stack" "")
    (recurse2)))

I don't know whether this is intended...

llmII commented 7 months ago

For reference, macex against recurse2's usage of the with macro produces the following:

(do
  (def proc (os/spawn ["ls"] :p {:out :pipe}))
  (do
    (def _00000C
      (fiber/new
        (fn []
          (debug/stacktrace (fiber/current) "print stack" "")
          (recurse))
        :ti))
    (def _00000D (resume _00000C))
    (:close proc)
    (if (= (fiber/status _00000C) :dead)
      _00000D
      (propagate _00000D _00000C))))

I've cleaned it up to change <.function \(.*\)> to \1.

llmII commented 7 months ago

Anything introducing a new fiber will mean there is a new stack. Error handling is based upon fibers and you can't tail call between fibers (they have separate stacks).

If you want the stack information from current fiber and it's ancestors, you can use debug/lineage and apply debug/stacktrace to each in whatever order you please.

If you want tail calls while dealing with error handling, that's not possible. Tail calls involve a stack, but each fiber has it's own stack, so it can't possibly tail call. If you can move the call outside of the mechanism introducing a fiber, you could do a tail call, but in most cases, you're not going to do that when error handling.

llmII commented 7 months ago

Correction - recursion within the body of an error handling mechanism (not the part that handles the error, but the code that might error) is non-recursive (and can't be, since that part is in a fiber).

bakpakin commented 7 months ago

Yeah, this is a cannot fix, as tail calls as a concept loose contextual information, making try-catch or any kind of error catching mechanism that uses a stack not an option.