debuggingcommon-lispsbclslime

How to interpret this stack frame in my control stack?


I am trying to learn Common Lisp with the book Common Lisp: A gentle introduction to Symbolic Computation. In addition, I am using SBCL, Emacs, and Slime.

In the end of chapter 8, the author presents the debugger as one of the great tools for lisp programming. Then, to showcase it he uses the break command inside a factorial-like function definition:

(defun fact-debugging (n)
  (cond ((zerop n) (break "N is zero."))
        (t (* n (fact-debugging (- n 1))))))

After calling the function in the REPL with:

CL-USER> (fact-debugging 4)

I get the the control stack. . I am specially curious about the backtrace part:

N is zero.
   [Condition of type SIMPLE-CONDITION]

Restarts:
 0: [CONTINUE] Return from BREAK.
 1: [RETRY] Retry SLIME REPL evaluation request.
 2: [*ABORT] Return to SLIME's top level.
 3: [ABORT] abort thread (#<THREAD "repl-thread" RUNNING {10024B9BC3}>)

Backtrace:
  0: (FACT-DEBUGGING 1)
  1: (FACT-DEBUGGING 2)
  2: (FACT-DEBUGGING 3)
  3: (FACT-DEBUGGING 4)
  4: (SB-INT:SIMPLE-EVAL-IN-LEXENV (FACT-DEBUGGING 4) #<NULL-LEXENV>)
  5: (EVAL (FACT-DEBUGGING 4))

I can understand all stack frames except for the number 4: (SB-INT...

Interestingly enough the author does not get such a message. He gets something lighter:

enter image description here

Hence, I would like to ask:

1 - Why does that stack frame happen?

2 - Why it happens after eval and before the stack-frame of (fact-debugging 4)?

3 - What does it actually mean? It is full of unseen terms like LEXENV or #<NULL LEXENV>.


Solution

  • When you enter the form (fact-debugging 4) in the REPL, the form is evaluated using eval, hence: 5: (EVAL (FACT-DEBUGGING 4)).

    If you move the emacs point to EVAL in 5 and press M-. (using Slime), you will find that eval is calling eval-in-lexenv, which is itself calling simple-eval-in-lexenv, hence: 4: (SB-INT:SIMPLE-EVAL-IN-LEXENV (FACT-DEBUGGING 4) #<NULL-LEXENV>).

    If you move the point to the 4th frame and press ENTER, you will see something like this:

    4: (SB-INT:SIMPLE-EVAL-IN-LEXENV (FACT-DEBUGGING 4) #<NULL-LEXENV>)
        Locals:
          SB-KERNEL:LEXENV = #<NULL-LEXENV>
          SB-IMPL::ORIGINAL-EXP = (FACT-DEBUGGING 4)
    

    Moving the point to the line that says SB-KERNEL:LEXENV = #<NULL-LEXENV> and pressing ENTER reveals:

    #<SB-KERNEL:LEXENV {1003FD12E3}>
    --------------------
    The object is a STRUCTURE-OBJECT of type SB-KERNEL:LEXENV.
    

    So, the initial call to eval was made by the REPL, and eval called eval-in-lexenv, which called simple-eval-in-lexenv with a lexenv structure. The lexenv structure is just a representation of the lexical environment in which eval should operate. You can move the emacs point to SB-KERNEL:LEXENV to see that definition, too. This definition is pretty long, but here is the beginning:

    ;;; The LEXENV represents the lexical environment used for IR1 conversion.
    ;;; (This is also what shows up as an ENVIRONMENT value in macroexpansion.)
    (declaim (inline internal-make-lexenv))
    (defstruct (lexenv
                (:include abstract-lexenv)
    ;;;
    ;;; and so on....
    ;;;
    

    Once eval has the lexical environment, evaluation of (FACT-DEBUGGING 4) can proceed. Of course, all of these details are SBCL-specific. In this particular case, the lexical environment, #<NULL-LEXENV>, is the null lexical environment, i.e., the global environment.