|Debugging of optimized code SAND_DUANE@tandem.com (1995-01-09)|
|Re: Debugging of optimized code firstname.lastname@example.org (1995-01-23)|
|Re: Debugging of optimized code email@example.com (1995-01-13)|
|Re: Debugging of optimized code milt@Eng.Sun.COM (Milton Barber) (1995-01-23)|
|Re: Debugging of optimized code snl@RIF.ndim.edrc.cmu.edu (Sean Levy) (1995-01-23)|
|Re: Debugging of optimized code firstname.lastname@example.org.OZ.AU (1995-01-24)|
|Re: Debugging of optimized code email@example.com (1995-01-24)|
|Re: Debugging of optimized code firstname.lastname@example.org (1995-01-26)|
|Re: Debugging of optimized code email@example.com (1995-01-26)|
|Re: Debugging of optimized code firstname.lastname@example.org (1995-01-27)|
|[18 later articles]|
|From:||Milton Barber <milt@Eng.Sun.COM>|
|Organization:||Sun Microsystems Inc., Mountain View, CA|
|Date:||Mon, 23 Jan 1995 01:18:17 GMT|
> A major irritant with all the optimizing compilers I know of, is that
> symbolic source-level debugging of the optimized version of released
> products becomes impossible or very very flaky.
> ... deletion ...
> I'm interested in hearing
> * Are improvements in this area practical?
> * Are there any production compilers now in use or under further
> development, which support reliable symbolic debugging of
> maximally or not-quite-maximally optimized code?
> * Have any research compilers solved this problem adequately?
> * Are we fundamentally limited by the existing "standard" codefile
> debugger info formats?
> * Is DWARF2, whatever that is, going to solve that problem? When?
> * What are some clear ways of describing the "current location
> in code" in source terms, when code motion and cross-statement
> scheduling has blurred the statement boundaries?
> * Just how much added performance is gained, by those code
> optimizations that make source-level debugging particularly hard?
> ... deletion
> I'm asking for reliable debugging support of these essentials:
> * Where am I, in the program?
> * Stmt breakpoints are possible at some (if not most) statement
> boundaries, with at least one point avaiable in each basic block.
> * Displaying of variable values, with warning if the value is
> not current for the current claimed stmt location.
> * Stack traceback of callers.
I would like to address one tiny little part of this big area, namely
the issue of "Where am I, in the program?".
Assume we have a very thoroughgoing optimizer, and let's watch some
one particular statement as it passes through the compiler.
First, the routine containing the statement is inlined in several
places (and, in some of these places, but not all, some higher-
order inlining takes place, i.e. the inlined code is again inlined),
leaving our statement now duplicated in a number of places throughout
the program, in a number of different program contexts. Furthermore,
lets assume that our statement contains some references to the
formal parameters of its containing routine, so, in each of these
places where it has been inlined, the actual parameters of the call
have been substituted, thus our statement is now not only duplicated
in a number of different contexts, it is actually a different
statement in each of those contexts.
Second, we apply loop-level transformations, such as loop inversion
or cache-blocking, drastically changing the looping control flow
in which some of the statement instances are involved.
Third, we apply normal global optimization, which will proceed to
"spread out" the effects of each instance of our statement via such
things as cse, forward and backward code motion, etc. Of course,
since each of the instances of our statement are different and in a
different context, different effects will apply at each place.
Fourth, we apply loop unrolling and/or software pipelining, (which
will apply to some of the parts of some of the instances of our
statement, but probably not all parts, or all instances, because of
the differing environments).
Fifth, we apply various control-flow rearrangement schemes, say
for example, tail splitting, so there is duplication of some parts
of some instances of code derived from the statement.
Sixth, we apply global scheduling, which will take the instructions
of the program and move them around among the basic blocks of the
program, possibly further changing the control-flow and possibly
introducing compensation code.
Seventh, we apply code order selection, which might result in
"later" parts of code occurring earlier in the program than "earlier"
parts of code.
Now, let's invoke the debugger, set a breakpoint on this statement
and start the run.
Just what do you expect the debugger to do?
The routine that contained the original statement is never invoked
because of inlining. The variables referenced in the original
statement may or may not be referenced and may or may not ever be
resident in memory because of parameter substitution and various
sorts of optimization. The control flow context of the original
statement may not survive in any recognizable form due such things
as loop inversion, loop unrolling or pipelining, unreachable code
elimination, global scheduling with compensation code, etc. Some
code that might conceivably be regarded as being derived from the
statement might be the first instruction in the program (say if a
constant from the statement was loaded globally) and/or might be
last instruction in the program (forward code motion). If a loop
containing some instance of the statement has been pipelined, parts
of the statement instance from different iterations of the loop might
be active simultaneously (and, the loop itself might not be anything
recognizable from the original program because of a loop-level
transformation such as cache blocking). Furthermore, you can't even
think of a statement instance as occupying some region of the code,
from some start address to some ending address, because of the code
All of the optimizations discussed here occur in commercial compilers
and have sufficient payoff that they are worth doing in any compiler
aimed at a high-performance market. You cannot solve the problem of
debugging a program passed through a highly optimizing compiler
by telling the compiler writer he was wrong to put in all of these
optimizations. The compiler writer just won't listen and will
respond to the commercial pressures and put them in. Instead, you
have to try to do SOMETHING to help the poor user faced with the need
to debug a highly optimized program.
I think a key part of a solution to this problem is recognition of
the fact that, above a certain level of optimization, the very idea
of a positional correspondence between the original source program
and the object program is broken so many transformations have
occurred to the program that any attempt to utilize this
correspondence will mislead more than it will help.
One possible alternative is to construct for the user a program
that does have a strong positional correspondence to the object
program and looks something like a source program because it uses
terminology borrowed from his program (variable names) and syntactic
forms borrowed from his program's language. (Although this sounds
hard to do, it turns out to be quite easy to do in many optimizing
compilers.) With such a program, you can show the user "where" he
is when he is stepping through the program, and/or you can let him
place a breakpoint in this program and stop his run "there".
Any other ideas or comments?
Return to the
Search the comp.compilers archives again.