Crunch time compiling: Q&A with Joe Drzewiecki, Manager of Compilers and Software Development, Microchip Technology
May 24, 2017
Nobody likes a complainer, especially when you have to live with them day-in and day-out, and it seems that modern compilers spend a lot of time compl...
ECD: How can designers give themselves the best chance of having their compiler properly realize the intent of their source code?
DRZEWIECKI: I’ve met a lot of engineers who thought that compiler error messages and warnings were criticisms of their coding style or practice. Now that I’ve been working with compiler developers for a while, I can tell you that an error or warning is most probably because the compiler doesn’t understand the source code that you’ve written. Now, that should strike you immediately as a big red flag, ensuring that what you wanted to happen is probably not going to happen with the code the compiler generates. So, turn on the errors and warnings and deal with them.
ECD: Even good coders can routinely get the compiler to generate thousands of errors and warnings. Are you telling me that they have to wade through that mess and fix them all?
DRZEWIECKI: Only if you want your code to work! Every single one of those messages is the compiler telling you that it’s not sure how to interpret what you’ve written, or worse, that the compiler is pretty sure that what you said is not what you meant. As miserable as cleaning up compiler errors and warnings is, I guarantee that finding the misbehaving code on your board is 10x worse. I’m sure many developers have found code that they meant to work one way and it worked another – usually after painstaking hours of debugging, late nights, and the boss breathing down your neck. That’s no fun!
There are a couple of techniques that you can use to deal with the whiny compiler. First, write small chunks of code and compile them frequently to make sure the compiler understands them. Some developers may be saying, “Yeah, yeah, I’ve heard it all before, but I’m late, and ...” It’s your choice. Like the old oil-filter commercial said, “You can pay me now or pay me later.” If you think you’re late now, just wait.
The second tip is to start at the very beginning, clean up the first error or so, and then recompile. I’ve seen instances where a single missing semicolon can generate so many error messages that the compiler stops printing them. Fixing that one missing semicolon cleared up a thousand errors. It’s not as bad as it seems, and turning off compiler errors is the epitome of being penny wise and pound foolish with your time (see Figure 1).
ECD: Is there anything else that can be done, besides ensuring that the compiler can properly interpret the source code?
DRZEWIECKI: Almost everyone knows what they should do when coding – short functions with a single purpose, clean control flow, good encapsulation of data (no global variables), and limited pointer/array usage. If you have a tough time being disciplined in what you know would make your code work better, I strongly suggest using a Motor Industry Software Reliability Association (MISRA) checker. MISRA has a number of rules that limit the most often abused features of the C programming language. If you stick with MISRA, you’ll get more deterministic execution and much less opportunity for code that gets “lost in space.” You may even be surprised to see your code size shrink after you’ve coded things so cleanly.
ECD: Even with clean coding and following best practices, the compiled code often still won’t fit in the designer’s chosen device. This often leads to the use of optimizations, which can break the code. Is there any way to avoid creating problems with the code when using optimizations?
DRZEWIECKI: A compiler’s most important responsibility is to generate “correct” code based on the source you give it. You know what you wrote. The compiler knows what it read. Since they’re the same thing, how can they be different? One of several answers lies in the mystery of code that has “no effect.” When the compiler reduces the size of your code, it can’t arbitrarily throw things away, so one of the tricks the compiler uses is to look for code that has “no effect.” It’s important that you understand what a compiler means by this. You may have a timing loop that looks like this:
long long i;
for(i = 0; i < 5280 * NumberOfMilliseconds; i++)
In this case, you have empirically and painstakingly determined that 5,280 (obviously a silly example) was the number of No Operations Performed (NOPs) that your machine needed to tie itself up for a millisecond (bonus points if you counted loop overhead, the long math, the function entry/exit, and the like). You know that a millisecond is an important amount of time, but the compiler doesn’t have any concept of the passage of time, so this entire construct (and every place that calls it and expects time to pass) will be eliminated by the optimizer because this code has “no effect.”
For anyone who has ever used such software timing loops and had them optimized out by a compiler, you know how badly this makes your code malfunction, especially at the last minute, where the final change has made the image too big for the device and the customer, VP, or regulatory agency is coming to inspect your work, forcing you to use more optimizations in a hurry. It’s not because your intent is wrong. It’s not that the compiler is misreading your code. The real misconception here is that the compiler has an understanding of time; it doesn’t. It only has an understanding of code that has “no effect,” and can therefore be safely removed.
There are many ways to work around this, some better and some worse, but that’s beyond the scope of this interview. The fact I’d like to consider here is that the compiler’s concept of code that has “no effect” had better be reflected in your thinking, or there will be severe consequences when you use the optimizer. Writing optimizer-safe code takes some doing and is another one of the signs of coding mastery, as it allows you to be a craftsperson with your tools and generate what you want every time.