diff options
Diffstat (limited to 'doc/ref/api-debug.texi')
-rw-r--r-- | doc/ref/api-debug.texi | 285 |
1 files changed, 206 insertions, 79 deletions
diff --git a/doc/ref/api-debug.texi b/doc/ref/api-debug.texi index 619629b56..9b0e56448 100644 --- a/doc/ref/api-debug.texi +++ b/doc/ref/api-debug.texi @@ -342,6 +342,7 @@ library, or from Guile itself. * Catching Exceptions:: Handling errors after the stack is unwound. * Capturing Stacks:: Capturing the stack at the time of error. * Pre-Unwind Debugging:: Debugging before the exception is thrown. +* Stack Overflow:: Detecting and handling runaway recursion. * Debug Options:: A historical interface to debugging. @end menu @@ -600,10 +601,12 @@ These procedures are available for use by user programs, in the @deffn {Scheme Procedure} call-with-error-handling thunk @ [#:on-error on-error='debug] [#:post-error post-error='catch] @ - [#:pass-keys pass-keys='(quit)] [#:trap-handler trap-handler='debug] + [#:pass-keys pass-keys='(quit)] @ + [#:report-keys report-keys='(stack-overflow)] @ + [#:trap-handler trap-handler='debug] Call a thunk in a context in which errors are handled. -There are four keyword arguments: +There are five keyword arguments: @table @var @item on-error @@ -630,9 +633,185 @@ traps entirely. @xref{Traps}, for more information. @item pass-keys A set of keys to ignore, as a list. + +@item report-keys +A set of keys to always report even if the post-error handler is +@code{catch}, as a list. @end table @end deffn +@node Stack Overflow +@subsubsection Stack Overflow + +@cindex overflow, stack +@cindex stack overflow +Every time a Scheme program makes a call that is not in tail position, +it pushes a new frame onto the stack. Returning a value from a function +pops the top frame off the stack. Stack frames take up memory, and as +nobody has an infinite amount of memory, deep recursion could cause +Guile to run out of memory. Running out of stack memory is called +@dfn{stack overflow}. + +@subsubheading Stack Limits + +Most languages have a terrible stack overflow story. For example, in C, +if you use too much stack, your program will exhibit ``undefined +behavior'', which if you are lucky means that it will crash. It's +especially bad in C, as you neither know ahead of time how much stack +your functions use, nor the stack limit imposed by the user's system, +and the stack limit is often quite small relative to the total memory +size. + +Managed languages like Python have a better error story, as they are +defined to raise an exception on stack overflow -- but like C, Python +and most dynamic languages still have a fixed stack size limit that is +usually much smaller than the heap. + +Arbitrary stack limits would have an unfortunate effect on Guile +programs. For example, the following implementation of the inner loop +of @code{map} is clean and elegant: + +@example +(define (map f l) + (if (pair? l) + (cons (f (car l)) + (map f (cdr l))) + '())) +@end example + +However, if there were a stack limit, that would limit the size of lists +that can be processed with this @code{map}. Eventually, you would have +to rewrite it to use iteration with an accumulator: + +@example +(define (map f l) + (let lp ((l l) (out '())) + (if (pair? l) + (lp (cdr l) (cons (f (car l)) out)) + (reverse out)))) +@end example + +This second version is sadly not as clear, and it also allocates more +heap memory (once to build the list in reverse, and then again to +reverse the list). You would be tempted to use the destructive +@code{reverse!} to save memory and time, but then your code would not be +continuation-safe -- if @var{f} returned again after the map had +finished, it would see an @var{out} list that had already been +reversed. The recursive @code{map} has none of these problems. + +Guile has no stack limit for Scheme code. When a thread makes its first +Guile call, a small stack is allocated -- just one page of memory. +Whenever that memory limit would be reached, Guile arranges to grow the +stack by a factor of two. When garbage collection happens, Guile +arranges to return the unused part of the stack to the operating system, +but without causing the stack to shrink. In this way, the stack can +grow to consume up to all memory available to the Guile process, and +when the recursive computation eventually finishes, that stack memory is +returned to the system. + +@subsubheading Exceptional Situations + +Of course, it's still possible to run out of stack memory. The most +common cause of this is program bugs that cause unbounded recursion, as +in: + +@example +(define (faulty-map f l) + (if (pair? l) + (cons (f (car l)) (faulty-map f l)) + '())) +@end example + +Did you spot the bug? The recursive call to @code{faulty-map} recursed +on @var{l}, not @code{(cdr @var{l})}. Running this program would cause +Guile to use up all memory in your system, and eventually Guile would +fail to grow the stack. At that point you have a problem: Guile needs +to raise an exception to unwind the stack and return memory to the +system, but the user might have throw handlers in place (@pxref{Throw +Handlers}) that want to run before the stack is unwound, and we don't +have any stack in which to run them. + +Therefore in this case, Guile throws an unwind-only exception that does +not run pre-unwind handlers. Because this is such an odd case, Guile +prints out a message on the console, in case the user was expecting to +be able to get a backtrace from any pre-unwind handler. + +@subsubheading Runaway Recursion + +Still, this failure mode is not so nice. If you are running an +environment in which you are interactively building a program while it +is running, such as at a REPL, you might want to impose an artificial +stack limit on the part of your program that you are building to detect +accidental runaway recursion. For that purpose, there is +@code{call-with-stack-overflow-handler}, from @code{(system vm vm)}. + +@example +(use-module (system vm vm)) +@end example + +@deffn {Scheme Procedure} call-with-stack-overflow-handler limit thunk handler +Call @var{thunk} in an environment in which the stack limit has been +reduced to @var{limit} additional words. If the limit is reached, +@var{handler} (a thunk) will be invoked in the dynamic environment of +the error. For the extent of the call to @var{handler}, the stack limit +and handler are restored to the values that were in place when +@code{call-with-stack-overflow-handler} was called. + +Usually, @var{handler} should raise an exception or abort to an outer +prompt. However if @var{handler} does return, it should return a number +of additional words of stack space to allow to the inner environment. +@end deffn + +A stack overflow handler may only ever ``credit'' the inner thunk with +stack space that was available when the handler was instated. When +Guile first starts, there is no stack limit in place, so the outer +handler may allow the inner thunk an arbitrary amount of space, but any +nested stack overflow handler will not be able to consume more than its +limit. + +Unlike the unwind-only exception that is thrown if Guile is unable to +grow its stack, any exception thrown by a stack overflow handler might +invoke pre-unwind handlers. Indeed, the stack overflow handler is +itself a pre-unwind handler of sorts. If the code imposing the stack +limit wants to protect itself against malicious pre-unwind handlers from +the inner thunk, it should abort to a prompt of its own making instead +of throwing an exception that might be caught by the inner thunk. + +@subsubheading C Stack Usage + +It is also possible for Guile to run out of space on the C stack. If +you call a primitive procedure which then calls a Scheme procedure in a +loop, you will consume C stack space. Guile tries to detect excessive +consumption of C stack space, throwing an error when you have hit 80% of +the process' available stack (as allocated by the operating system), or +160 kilowords in the absence of a strict limit. + +For example, looping through @code{call-with-vm}, a primitive that calls +a thunk, gives us the following: + +@lisp +scheme@@(guile-user)> (use-modules (system vm vm)) +scheme@@(guile-user)> (let lp () (call-with-vm lp)) +ERROR: Stack overflow +@end lisp + +Unfortunately, that's all the information we get. Overrunning the C +stack will throw an unwind-only exception, because it's not safe to +do very much when you are close to the C stack limit. + +If you get an error like this, you can either try rewriting your code to +use less stack space, or increase the maximum stack size. To increase +the maximum stack size, use @code{debug-set!}, for example: + +@lisp +(debug-set! stack 200000) +@end lisp + +The next section describes @code{debug-set!} more thoroughly. Of course +the best thing is to have your code operate without so much resource +consumption by avoiding loops through C trampolines. + + @node Debug Options @subsubsection Debug options @@ -666,8 +845,8 @@ warn-deprecated no Warn when deprecated features are used. @end smallexample The boolean options may be toggled with @code{debug-enable} and -@code{debug-disable}. The non-boolean @code{keywords} option must be set -using @code{debug-set!}. +@code{debug-disable}. The non-boolean options must be set using +@code{debug-set!}. @deffn {Scheme Procedure} debug-enable option-name @deffnx {Scheme Procedure} debug-disable option-name @@ -680,59 +859,6 @@ to historical oddities, it is a macro that expects an unquoted option name. @end deffn -@subsubheading Stack overflow - -@cindex overflow, stack -@cindex stack overflow -Stack overflow errors are caused by a computation trying to use more -stack space than has been enabled by the @code{stack} option. There are -actually two kinds of stack that can overflow, the C stack and the -Scheme stack. - -Scheme stack overflows can occur if Scheme procedures recurse too far -deeply. An example would be the following recursive loop: - -@lisp -scheme@@(guile-user)> (let lp () (+ 1 (lp))) -<unnamed port>:8:17: In procedure vm-run: -<unnamed port>:8:17: VM: Stack overflow -@end lisp - -The default stack size should allow for about 10000 frames or so, so one -usually doesn't hit this level of recursion. Unfortunately there is no -way currently to make a VM with a bigger stack. If you are in this -unfortunate situation, please file a bug, and in the meantime, rewrite -your code to be tail-recursive (@pxref{Tail Calls}). - -The other limit you might hit would be C stack overflows. If you call a -primitive procedure which then calls a Scheme procedure in a loop, you -will consume C stack space. Guile tries to detect excessive consumption -of C stack space, throwing an error when you have hit 80% of the -process' available stack (as allocated by the operating system), or 160 -kilowords in the absence of a strict limit. - -For example, looping through @code{call-with-vm}, a primitive that calls -a thunk, gives us the following: - -@lisp -scheme@@(guile-user)> (use-modules (system vm vm)) -scheme@@(guile-user)> (debug-set! stack 10000) -scheme@@(guile-user)> (let lp () (call-with-vm (the-vm) lp)) -ERROR: In procedure call-with-vm: -ERROR: Stack overflow -@end lisp - -If you get an error like this, you can either try rewriting your code to -use less stack space, or increase the maximum stack size. To increase -the maximum stack size, use @code{debug-set!}, for example: - -@lisp -(debug-set! stack 200000) -@end lisp - -But of course it's better to have your code operate without so much -resource consumption, avoiding loops through C trampolines. - @node Traps @subsection Traps @@ -800,10 +926,11 @@ To digress, Guile's VM has 6 different hooks (@pxref{Hooks}) that can be fired at different times, which may be accessed with the following procedures. -All hooks are called with one argument, the frame in -question. @xref{Frames}. Since these hooks may be fired very -frequently, Guile does a terrible thing: it allocates the frames on the -C stack instead of the garbage-collected heap. +The first argument of calls to these hooks is the frame in question. +@xref{Frames}. Some hooks may call their procedures with more +arguments. Since these hooks may be fired very frequently, Guile does a +terrible thing: it allocates the frames on the C stack instead of the +garbage-collected heap. The upshot here is that the frames are only valid within the dynamic extent of the call to the hook. If a hook procedure keeps a reference to @@ -816,31 +943,28 @@ The interface to hooks is provided by the @code{(system vm vm)} module: @end example @noindent -The result of calling @code{the-vm} is usually passed as the @var{vm} -argument to all of these procedures. +All of these functions implicitly act on the VM for the current thread +only. -@deffn {Scheme Procedure} vm-next-hook vm +@deffn {Scheme Procedure} vm-next-hook The hook that will be fired before an instruction is retired (and executed). @end deffn -@deffn {Scheme Procedure} vm-push-continuation-hook vm +@deffn {Scheme Procedure} vm-push-continuation-hook The hook that will be fired after preparing a new frame. Fires just before applying a procedure in a non-tail context, just before the corresponding apply-hook. @end deffn -@deffn {Scheme Procedure} vm-pop-continuation-hook vm +@deffn {Scheme Procedure} vm-pop-continuation-hook The hook that will be fired before returning from a frame. -This hook is a bit trickier than the rest, in that there is a particular -interpretation of the values on the stack. Specifically, the top value -on the stack is the number of values being returned, and the next -@var{n} values are the actual values being returned, with the last value -highest on the stack. +This hook fires with a variable number of arguments, corresponding to +the values that the frame returns to its continuation. @end deffn -@deffn {Scheme Procedure} vm-apply-hook vm +@deffn {Scheme Procedure} vm-apply-hook The hook that will be fired before a procedure is applied. The frame's procedure will have already been set to the new procedure. @@ -851,13 +975,16 @@ whereas a tail call will run without having fired a push-continuation hook. @end deffn -@deffn {Scheme Procedure} vm-abort-continuation-hook vm +@deffn {Scheme Procedure} vm-abort-continuation-hook The hook that will be called after aborting to a -prompt. @xref{Prompts}. The stack will be in the same state as for -@code{vm-pop-continuation-hook}. +prompt. @xref{Prompts}. + +Like the pop-continuation hook, this hook fires with a variable number +of arguments, corresponding to the values that returned to the +continuation. @end deffn -@deffn {Scheme Procedure} vm-restore-continuation-hook vm +@deffn {Scheme Procedure} vm-restore-continuation-hook The hook that will be called after restoring an undelimited continuation. Unfortunately it's not currently possible to introspect on the values that were given to the continuation. @@ -875,12 +1002,12 @@ level temporarily set to 0. That way the hooks don't fire while you're handling a hook. The trace level is restored to whatever it was once the hook procedure finishes. -@deffn {Scheme Procedure} vm-trace-level vm +@deffn {Scheme Procedure} vm-trace-level Retrieve the ``trace level'' of the VM. If positive, the trace hooks associated with @var{vm} will be run. The initial trace level is 0. @end deffn -@deffn {Scheme Procedure} set-vm-trace-level! vm level +@deffn {Scheme Procedure} set-vm-trace-level! level Set the ``trace level'' of the VM. @end deffn @@ -1178,7 +1305,7 @@ procedure calls and returns within the thunk. @deffn {Scheme Procedure} call-with-trace thunk [#:calls?=#t] @ [#:instructions?=#f] @ - [#:width=80] [#:vm=(the-vm)] + [#:width=80] Call @var{thunk}, tracing all execution within its dynamic extent. If @var{calls?} is true, Guile will print a brief report at each |