| Conal Elliott  <[EMAIL PROTECTED]> asks:
| > Re: ERROR: Control stack overflow
| > Would it be reasonably easy for Hugs to give some more information in
| > these situations?  - Conal
| 
| I think this would be pretty hard to do.
| To make sense of the control stack, I think you need to look at the 
|  return stack.  The return stack in Hugs is just the C return stack
|  and I don't know any way of examining that other than just viewing
|  the stack in a C debugger.

Not so fast!  When Hugs displays "ERROR: Control stack overflow", it
is not referring to the C stack but to its own internal stack that is
used for evaluation.  This is the good news, because it means that
there is something we can do without resorting to specifics of particular
C compilers or debuggers.

Stack overflows are most likely to occur when the evaluator is called
recursively.  A classic example would be x where x = 1 + x; we try to
evaluate x, which in turn requires that we evaluate x, which in turn
requires ...  Very soon, the stack overflows.  As a quick experiment,
I hacked the most recent beta version of Hugs 1.4 to keep a record
of the current list of expressions to be evaluated.  These are kept in
a stack, pushed at the beginning of a call to the evaluator, and popped
when the evaluator returns.  When a stack overflow occurs, the routine
that used to just print the error message above now displays the top
and bottom portions of the stack too.  This can provide some useful
context information.

However, the bad news is that the expressions on the stack may not
be as close to your program as you might like.  They will reflect,
for example, the fact that some parts of the program have been
evaluated while others are still delayed.  And they will probably
make use of primitives or compiler-generated symbols that the user
might not recognize.

Here's an example.  Each line beginning "while evaluating" signals
an entry on the stack, starting at the top (most recent) and working
down to the bottom (earliest).  The line with just "..." represents
the middle portion of the stack.  Expressions on each line are printed
to a maximum depth of 15 (set at compile-time); anything more complicated
than this is truncated and displayed as "...".  Even so, some of the lines
can be quite long

  Prelude> foldr (+) 1 [1..10000]


  ERROR: Control stack overflow
  while evaluating: v40 5326 10000
  while evaluating: takeWhile (flip v40 10000) (5326 : _strict numericEnum
  From (v95 5326 (v102 1)))
  while evaluating: foldr v95 (v102 1) (takeWhile (flip v40 10000) (5326 :
   _strict numericEnumFrom (v95 5326 (v102 1))))
  while evaluating: primPlusInt 5325 (foldr v95 (v102 1) (takeWhile (flip 
  v40 10000) (5326 : _strict numericEnumFrom (v95 5326 (v102 1)))))
  while evaluating: primPlusInt 5324 (primPlusInt 5325 (foldr v95 (v102 1)
   (takeWhile (flip v40 10000) (5326 : _strict numericEnumFrom (v95 5326 (
  v102 1))))))
  ...
  while evaluating: primPlusInt 2 (primPlusInt 3 (primPlusInt 4 (primPlusI
  nt 5 (primPlusInt 6 (primPlusInt 7 (primPlusInt 8 (primPlusInt 9 (primPl
  usInt 10 (primPlusInt 11 (primPlusInt 12 (primPlusInt 13 (primPlusInt 14
   (primPlusInt 15 (primPlusInt 16 (... ... ...)))))))))))))))
  while evaluating: primPlusInt 1 (primPlusInt 2 (primPlusInt 3 (primPlusI
  nt 4 (primPlusInt 5 (primPlusInt 6 (primPlusInt 7 (primPlusInt 8 (primPl
  usInt 9 (primPlusInt 10 (primPlusInt 11 (primPlusInt 12 (primPlusInt 13 
  (primPlusInt 14 (primPlusInt 15 (... ... ...)))))))))))))))
  while evaluating: primShowsInt 0 (primPlusInt 1 (primPlusInt 2 (primPlus
  Int 3 (primPlusInt 4 (primPlusInt 5 (primPlusInt 6 (primPlusInt 7 (primP
  lusInt 8 (primPlusInt 9 (primPlusInt 10 (primPlusInt 11 (primPlusInt 12 
  (primPlusInt 13 (primPlusInt 14 (... ... ...))))))))))))))) []
  while evaluating: _Gc Black Hole _Gc Black Hole
  while evaluating: v631 (_Gc Black Hole _Gc Black Hole)

  Prelude>

It is highly likely that the quality of this display could be
improved.  Let's see, however, what we can get out of the display as it
is.  Starting at the bottom, v631 is (probably) one of the internal
functions used for printing (generated from an instance of Show) and
has been applied to an expression whose value in the heap has been
stubbed out with black hole markers to avoid a space leak.  You can see
that argument value being evaluated on the second from last stack
entry.  Above that we see the beginning of a large expression.  It
starts with 0+(1+(2+...)) and then above that we see the subexpression
1+(2+...), and above that, further subexpressions of a similar form,
each of which will need to be evaluated before the final result will be
known.

Shifting attention to the top of the stack, and mindful of the way that
constructs like [1..10000] are implemented, we can see the leading
edge in the creation of this monster expression.  We can see, for example,
that the generator has got upto the 5,326th element in the list.  The
v40 symbol is almost certainly the integer comparison operator <=, and
the "flip" appears because the definition of enumFromTo uses that operator
in a section of the form (<=10000).  Just a little bit further down the
stack, we can see the effects of a "foldr" step when the number 5326 is
taken off the front of the list being generated by a _strict version of
numericEnumFrom.

Overall, this gives an interesting insight into the evaluation mechanisms,
and also tells me how far the calculation had got before an error occured.
If, for example, I subsequently try the following:

  Prelude> foldr (+) 1 [1..5324]
  14175151
  Prelude>

then Hugs produces a successful result.  It hasn't told us, but we know
that this evaluation got about as close to overflowing the stack as it
could, without actually exceeding the limit.  It might also suggest
(although I haven't tried it) that, if we were to recompile Hugs with an
approximately doubled stack size, then our original program would run
without a problem.  Or it might encourage us to look for a different
way to write that program that would avoid the stack overflow altogether:

  Prelude> foldl' (+) 1 [1..10000]
  50005001
  Prelude> 

In summary, it is possible to give more information than just a "stack
overflow" message, but you'll need to know quite a lot about what's
going on to make much sense of the extra details.  In a real example,
the root of the expression that caused the problem is unlikely to be
at the very bottom of the stack, so you might need to browse the whole
stack trace to find the relevant portion.  To support this, Hugs could
be made to dump a stack trace into a file chosen by the user when an
overflow occurs.  In my current implementation, there is an overhead
for maintaining the stack trace information, even if it is not required.
It is possible that this could be avoided by using some cunning methods
to reinterpret the contents of the stack when an error occurs, but I'm
not sure about that.

I'll send patches for the changes that I made if people are interested.
I would, however, repeat that this was just a quick hack, known more
elegantly as a "proof of concept", and could surely be improved and
refined ...

All the best,
Mark

Reply via email to