Line numbers in stack traces

Richard A. O'Keefe <>
Wed Aug 10 07:16:09 CEST 2005


David Hopwood <> continues:
	> There are a number of assumptions hidden here:
	> (1) all VM code comes from source code
	> (2) all source code is hand-written
	
	I wasn't assuming either of these.

Not deliberately, perhaps, but these are extremely common assumptions
amongst programmers in general.  It is *important* to remember that
programs are DATA.

	I said that the VM format should be *designed to allow this*.

But that is to assume that this is more important than other things
(like compact code).  For example, I just compiled a C program with
(129k) and without (68k) debugging information.  The debugging
information pretty much doubled the size of the file.

Doubtless you will retort that this cost comes from *having* debugging
information, not from *being able to have* debugging information.  But
there is a cost:  a considerably more complicated object code format
is required to support the debugging info.

	I.e. where source line numbers exist (not necessarily Erlang
	source; the concept of line numbers applies to any textual
	source language),

This isn't actually true, and I have used just such a language.
Interlisp-D on the Xerox D-machines.  There *was* a textual form in
files, and there *was* a "textual" editor.  And while debugging
support on the D-machines was excellent, I can't for the life of me
think of any reason why anyone would ever have wanted to know a line
number.  Line numbers were totally under the control of the pretty-printer.

Oh yeah, another such language I use a lot:  Smalltalk.  When you edit
code, what you see is text.  But (a) you can flip between text as you
typed it, pretty-printed text, pretty-printed text with colouring, or
a non-traditional syntax, and (b) the code doesn't *have* line numbers,
even though it eventually ends up in a file.  This is not a barrier to
debugging, in fact although I have debugged more Smalltalk code than I
like to admit getting wrong, it only just occurred to me that there was
no way of finding out about line numbers.  Smalltalk doesn't have macros,
which helps a lot when debugging it.

	the VM format should provide a way of encoding them, at least in
	a debugging mode.

You are confusing a *source position* (which makes sense even if, as in
Interlisp-D and Smalltalk, the code is technically held in a data base)
with *line numbers*.

	And line numbers are still useful if the source is
	machine-generated, to the writer of the generator program.
	
I was assuming the fairly common situation in which the person debugging
the code is NOT the person who wrote the generator.

Note that if a programming language is intended for use with tail
recursion optimisation, as Prolog and Scheme and Erlang are, then
a debugger *must* discard most dynamic line number information or
else choke on its own stack.  (Prolog debuggers commonly choose the
choke-on-your-own-stack option.)

	> (3) therefore all VM code *has* line numbers
	>     which it is simply incompetent of the VM not to retain.
	
	I didn't say that it was an incompetent decision, either. It is a
	design limitation, not shared by many other VM designs (e.g. typical
	Java and Smalltalk VMs).
	
The only Smalltalk VMs I am familiar with are the ones described in
the Blue Book (which just happens to be on my desk right beside my
terminal at the moment), the closely related one in Squeak, and a couple
of new ones described in the Squeak mailing list.  (Oh, and the one
used in a CIT thesis in the 80s.)  NONE of them have any provision for
line number information.  I don't know what Ambrai Smalltalk's VM looks
like, but I can't find anything in its CompiledCode class that refers
to line numbers.  Basically, Smalltalk debuggers rely on finding 'call'
instructions in the byte code and matching them up to message sends in
the source.  (In Smalltalk-80, Squeak, and Ambrai, stack traces do NOT
include line numbers.)

This points the way to an Erlang debugger which doesn't have and
doesn't need line number information:  all it needs is "which clause
of which function in which module?"  Given that, when you want to
display the right position in a debugger window, you pick up the
function from the window, find the clause, compile it, and match
the VM instruction position to the position in the clause.

If the compiler recorded the source position of the first token
of each clause in each function, then knowing which clause of which
function you were in would get you very close in terms of line numbers.
Like well-written Smalltalk, well-written Erlang is supposed to have
lots of *small* clauses, no?

Of course, function inlining would make that problematic.

	> (4) the relationship between source lines and VM code is easy to maintain.
	
	It's not trivial, but it's not rocket science either.
	
It's sufficiently difficult that people have earned PhDs for doing it.
I think there are much better things to spend the time and money on.

	True, but there is only so much code on a line, even if it
	expands to "hundreds of tokens".

I have had to debug C code where one line, looking like a function call,
expanded via a cascade of macros, to the equivalent of several hundred
*lines*.

	The size of the expansion isn't actually very important; what
	matters is whether a line number identifies the point at which
	the exception occurred in a way that is usefully more precise
	than the current stack traces.

But those are the *same* question.  The expansion is not only very large,
it is inaccessible.  In debugging other people's C code I have several
times been reduced to

	cc -E ... foobar.c | cb >foobar-i.c
	cc -g ... foobar-i.c
	mv foobar-i.o foobar.o

in order to get something "usefully more precise".  (Oddly enough,
the program that gave me the most trouble in this regard was written
by a full Professor of Software Engineering...)

	Stepping into the code isn't what we were talking about; line
	numbers in stack traces do not replace a debugger.
	
Nobody ever said they did.

	> and the line where the error *is* may be in a completely different file
	> from the reported line.  Erlang macros are regrettably similar to C macros.
	
	This is true, but it doesn't seriously interfere with the
	utility of line numbers,

This has not been my experience.

	especially for programmers who make little or no use of macros.

	In any case, if the particular line that is reported contains a
	macro application, then it is obvious that the problem might be
	with the macro definition.  I don't see why that would be
	surprising or difficult to deal with.

Because it isn't "obvious" *where* the problem is within the cascade of
macro applications.

	JVM classfiles have a way of specifying line numbers in multiple
	levels of source language, e.g. both the input and the output of
	a code generator.
	
	<http://java.sun.com/j2se/1.4.2/docs/
	guide/jpda/enhancements.html#debugotherlanguages>
	
Good for them.  (Although the relevant word here is 'enhancements'.)

However, the fact that there is an interface is no guarantee that there
is anything at the other end of it.  See the Location interface, where
we learn
	The availability of a line number for a location will
	depend on the level of debugging information from the
	target VM.
We also learn if we read further that there is no way of passing this
information through Java *source* code, which is a pity.

There is one important difference between Erlang and Java:
the level of support.
There is so much money and manpower behind Java that Sun can afford
to do things (and so much muscle behind C# that Sun cannot afford NOT
to do things) that are not necessarily a good use of resources for
enhancing Erlang.

	[Modern functional compilers can restructure the code in
	 truly major ways.]
	
	That's one reason why you might only want to support this in a
	debugging mode.

Note, above all, that

    A DEBUGGING MODE NEED NOT USE THE VM.

For example, the Quintus Prolog debugger was *not* based in any way on
the WAM:  debugged code was interpreted from abstract syntax trees,
rather like Lisp.  (For that matter, in order to get source information,
later versions of QP even used a special version of 'read'.)

So in order to support line number information (should that prove to be
usefully more precise than {module,function,arity,clause}) in a debugging
mode, it is NOT necessary to have any support for line numbers in the VM.
(As noted above, QP was able to provide source positions in debugging mode
without having any support for it in the WAM.)

	I am skeptical that the size of this information would be larger
	than the actual code except in contrived pathological cases, though.
	
It is large, because the information required basically amounts to undoing
the transformations.  Inlining is just the beginning.

My earlier mention of TRO doesn't seem to have sunk in.
Iterative code in Erlang relies on turning the dynamically last
call in a function into a jump.

When an error (badmatch, badarith, badarg, &c) is reported, the line
number of the actual error report doesn't tell you very much.  Quite
often it's inside some system function.  *Your* function call which
contains the error has very often disappeared completely from the stack.


The important question is not "is adding line numbers a good way to
improve Erlang stack traces" but "what is the best use of Erlang
development resources to help people get their Erlang programs right?"

As I think I've said, I'd rather have QuickCheck.




More information about the erlang-questions mailing list