Wednesday, January 17, 2007

Broken optimizers in C/C++ compilers

I've got a sizable chunk of working C code now in my ongoing OBJ hacking project and I decided to see how it worked on various different compilers on different platforms.

To date, the project has been built as an ordinary "large model" DOS app using several different versions of Borland C compilers ranging from Turbo C 2.0 through Borland C/C++ 4.52 all of which have performed OK and produced seemingly correct code.

Next I took it (don't laugh) onto an OS/2 2.0 machine and built it as a 32 bit app with the Borland C/C++ v1.5 OS/2 compiler. A few nits shook out when I started running it against its own code, but nothing major, just oversights on my part in the support of some of the newer 32 bit OMF record types. Again, the 32 bit Borland OS/2 compiler seemed to be producing apparently correct code.

Now I move it over to a Windows NT 4 platform and build the code with one of the early Borland Win32 compilers. All hell breaks loose and NOTHING WORKS.

OK, time to look at what's going on at the assembler code level with a debugger. This version of the Borland compiler, a couple of years newer than the OS/2 version, apparently got a new code generator and optimizer -- one which turned correct C code into INCORRECT machine code. The damned thing was optimizing away some rather important shifting and masking operations in my lowest level routines that read record fields out of OBJ records -- and I hadn't even throw any fancy optimization switches yet. I was just going with the standard defaults.

These routines involved weren't terribly complex either - none of them should generate more than about 10 machines even in completely unoptimized code. Simple stuff like:

int lowbyte, hibyte;

lowbyte = getbyte();
hibyte = getbyte();
return ((hibyte << 8) | lowbyte);

Simple constructs like this completely spazed that Borland compiler. OMG - how did Borland ever get this compiler to build itself with egregious code generation bugs like this? How did any customer with a sizable code base ever do anything useful with it?

The golden rule of optimizers in a compiler is "do no harm". Breaking correct code is a major sin.
Microsoft took a pretty hard PR hit over this with their C 5.0 compiler when it first came out. C 5.0 introduced a bunch of new optimizations, but the loop optimizations were busted, and people noticed it right away.

No comments: