Wednesday, June 04, 2003

Whither Debugging?

Went to an interesting talk today by David Levine, formerly of Analog Devices, among other places. His interest is in how you use source-level debuggers for highly optimized code, or, in other words, what you do when you compile with -O and -g at the same time.

The problem, briefly, is that the sorts of things you want to do with a debugger -- set breakpoints, print the values of variables, set the values of variables, set watches, etc. -- become extremely difficult in highly optimized programs. As the optimizer works, it is moving code around, possibly removing it, using the same memory location for two variables, etc. A "breakpoint" that you set may correspond to no single valid point in the code by the time the optimizer is done. In fact, a variable that you think has a value at that point may already be overwritten by something completely different. This is especially true for optimizations like loop unrolling, code hoisting, and common subexpression elimination.

The traditional solution, he points out, is to debug heavily with optimization turned off. But that's silly for a couple reasons. One, you really ought to debug the optimized version if you're planning on using it. Two, the optimized version might be necessary to run the program correctly, for example if timing (such as in a DSP) or footprint (if it is supposed to run in an embedded device) are issues.

It was a neat talk and he had some interesting suggestions, but I disagree with some of his basic assumptions. It seems that underneath it all, he's making an implicit claim that lines of code should be treated like transactions - atomic, consistent, independent, durable. And the point of forcing debuggers and optimizers to co-exist is to preserve these properties. I say, faugh! The ACIDity of lines of code is an illusion. It always was, and especially with superscalar multiple dispatch speculative executing hardware, it's even more of an illusion now. The right answer is not to scrabble with our fingernails to preserve this, but to abandon it and move on.

This begs a more important and fundamental question, which is how do we debug programs for which we do not have entirely reliable source? Highly optimized code is only one instance of this; another is automatically generated code (for example by biological processes). As Caltech's John Doyle is fond of pointing out, if we can't figure out how to debug this stuff (or to prove it safe and correct), then civilization is liable to end. Once we move completely into the Media Lab future where everything is networked and running on embedded chips, we become extraordinarily vulnerable to system errors. And it will be very hard to reboot the world, so we better either get it perfect the first time or have a strong capability to debug it in real time. This will become even more difficult as we start running into higher order emergent behaviors.