I agree about the unpleasant tone of the paper, putting "facts" and "optimizations" in quotes, implying they are not real, and talking about "claims" of GCC maintainers, and cracks about psychic powers.
But there is a good underlying point to the paper, and one I tend to agree with - a compiler's behavior should give considerable weight to how the language is used more than taking a literal view of what the words mean in the spec.
Taking things to an absurd extreme to make the point, "undefined behavior" implies that a compiler writer can insert code to reformat the hard disk if it encountered such code. Granted, the point of a spec is to define things so that compiler writers don't have to divine intent, but perfection is never attained and hence compiler writers have to use their judgment.
I know in my compiler work, I have backed out some optimizations that were allowed by the spec, but broke too many programs. The inconsequential speed improvement was just not worth it.
It may be unpleasant, but the reality is that many programmers do feel that way. I can say that as a kernel programmer, I approach using a new version of gcc with a particular version of dread. What sort of random bugs that might end up causing kernel crashes or data corruption will arise when I try using a new compiler?
As a result, I'm quite sympathetic to the attitude of "any sufficiently advanced compiler is indistinguishable from a malicious adversary"....
I'm just a crummy engineer that writes a fair amount of embedded firmware. When this subject comes up I've found mainstream programmers attitude towards these sorts of concerns to be actually hostile. A lot of programmers think when specification gives the compile maintainers a choice between making the code do something platform specific or something silently malicious, to do the malicious thing in order to 'teach the programmer a lesson'
I'm with you. These guys aren't helping us, our customers or the people that pay the bills.
These guys put thousands of hours to maintain and improve a compiler you get for free. Thousands of programmers welcome each and every release jumping to a change log. Every major GCC version lately introduced significant optimizer improvements, new diagnostics, new warnings, faster compile time etc.
How can you claim these guys aren't helping "us" is beyond me. I am developing a small project with just a little over 1000 customers. GCC team efforts over last few years saved thousands of dollars in hardware and electricity costs as well as hundred of hours of computation just because GCC 5.3 produced very significantly faster code than GCC 4.x for low x.
Bugs are different issue than optimizer being more aggressive. We would all like to avoid bugs but it's hard. There will never be improvement if we don't allow for the possibility of bugs being introduced.
I immediately thought about the same language. It's clean, safe, fast and explicit, even if a bit verbose. With a quite nice compiler, last time I checked (some 10 years ago), and without "undefined behaviour".
Optimally a compiler's version of a language is better defined simply by it's definitions. It's just problematic if the behavior is changed between versions, eg. for the sake of optimizations. It's also bad if undefined behavior is expected to be reliable where the original implementation wasn't intended to be meaningful in the first place.
If those two cases are hard to tell apart, the process of announcing this is at fault, I guess, e.g. the error messages and warnings, the documentation, the info hidden in discussions on the mailinglists or in bug reports, but the gcc docs seem fairly extensive and I can't claim to have read it completely or the c standard.
"a compiler's behavior should give considerable weight to how the language is used more than taking a literal view of what the words mean in the spec."
This is generally true, but the problem in this case is the committee history of a lot of what is being complained about shows it was added specifically to let compilers reason the way they do.
The committee didn't sit down and say "hey, this should be undefined, because it sucked". Instead, what happened is committee members and compiler writers sat down and said the compiler guys said "if you make this undefined, we can optimize this thing over here better", and the committee said "great, sounds awesome".
Complaining that the compiler writers then go and do that is misguided :)
(This covers most of the specific examples given, BTW. There are other examples where he dismissively claims that what should happen is obvious.)
The whole thing is like complaining that people follow the law, instead of the spirit of the law.
The question I have is whether committee members are sufficiently representative of programmers in the trenches, and whether committee members are sufficiently informed about the impact of their saying, "great, sounds awesome".
Sometimes yes, sometimes no. But in some sense, it doesn't matter.
They are the ones defining C. If people don't like it, they should create Cgood or whatever.
This has been tried before, and it has never caught on.
I'm not worried. If the programmers in the trenches really hate it so much, C will eventually die off in favor of something better :)
To be fair Cgood doesn't catch on because no compiler vendors implement it, and the C standards committe is at least somewhat beholden to the compiler vendors because if the compiler vendors en-masse decided not to implement their standard, then the standard isn't worth the paper it's printed on.
Compiler vendors are people, and in fact, if you don't like that set of people, there are plenty of vendors who have forks :)
IE if you don't like gcc 4.8's treatment of behavior, it's not like there aren't 50 different arm forks from different vendors you can use, with or without their SOC's.
Yet none of these vendors have apparently heard enough from customers that they have made what are pretty simple patches.
It's really, really hard to sell someone on a non-standard implementation of a popular language like C. For example, I've had customers beg me to add an extension to C. I implement it, and present it to them. They refuse to use it - because then their code would be dependent on a non-Standard feature.
The solution (for me, anyway) was to create an entirely new language. The changes that nobody wanted in C (not because they were bad, but because they were non-Standard) found plenty of traction and users in D.
This is a very good observation. The exception is strict subsets though; there are plenty of people using MISRA, and that's an extremely restrictive subset of C. Similarly this would be a dialect of C that is identical in behavior for conforming C programs, rather than C with some extensions.
> This is entirely true, but at the same time:
Compiler vendors are people, and in fact, if you don't like that set of people, there are plenty of vendors who have forks :)
With all due respect, but that sounds a bit like "if you don't like this particular law, you're always free to try a coup/revolution/run your own country".
I don't know much about the situation in standardized C, but I would argue that the point of standards is to reduce fragmentation and discuss language features in a way that all implementation can benefit from it. Demanding that you break the standard if you don't like a part of it kind of defeats that point, I think.
A different (and IMO more sensible) approach is shown by the WHATWG developing HTML5. Even though input primarily comes from browser vendors (which would be the equivalent to compiler vendors here), there is a rather strict requirement that new features must break a little as possible existing usage of HTML and should never introduce new security vulnerabilities. (Where those requirements collide, the second one takes precedence). This is done by extensive studies of how HTML is used "in the wild".
"With all due respect, but that sounds a bit like "if you don't like this particular law, you're always free to try a coup/revolution/run your own country".
"
IMHO, it's closer to, if you don't like SF, you can move to any of the cities around it :)
"Demanding that you break the standard if you don't like a part of it kind of defeats that point, I think."
Which, humorously, is precisely what Anton wants in some cases.
"A different (and IMO more sensible) approach is shown by the WHATWG developing HTML5. Even though input primarily comes from browser vendors (which would be the equivalent to compiler vendors here), there is a rather strict requirement that new features must break a little as possible existing usage of HTML and should never introduce new security vulnerabilities. (Where those requirements collide, the second one takes precedence). This is done by extensive studies of how HTML is used "in the wild"."
This is in actuality, how most of C/C++ is developed. With a bit more formality due to it being an international standard.
The truth is this code has pretty much never had defined behavior.
As far as I'm aware, the author was using optimizations in quotes ("optimizations") to parallel the previously defined C* and "C" distinctions. Hence why section 2.1 is called 'Optimization* and "Optimization".' He's not suggesting they are not real optimizations, but that they are a class of optimizations which target the "C" subset rather than the C* superset.
At least, that was my understanding of it. People seem to be reading into the quotes as indicative of tone rather than notation, so I wanted to put a different perspective out there.
You read one intention of the quotes right. But they are also intended to be scare quotes; if the result of an "optimization" is equivalent to the behaviour intended by the programmer that also worked with earlier versions of the compiler, it's a real optimization, if not, it's miscompilation. E.g., "optimizing" a bounded loop into an endless loop is not a real optimization.
The problem is that many programmers who are actually competent and care about speed welcome each and every new GCC release with joy checking the change-log for optimizer improvements. I mean, people who actually check the docs instead of ranting about how int overflow doesn't behave according to some wishful thinking.
>>The inconsequential speed improvement was just not worth it.
Yeah, but the author if taking about very significant speed improvements (often bigger than 5%) which only brake really bad code (relying on behavior of int overflow).
> really bad code (relying on behavior of int overflow).
How's that "bad code"? On most desktop architecture, integers are coded as 2's complement, and wrap around. Modulo arithmetic is weird, but it does have some uses. Then on DSP, integers generally saturate upon overflow.
Those behaviours are both perfectly well defined. Why can't one expect to be able to rely on them? Why integer overflow can't simply be "implementation defined"?
Because of traps, such as we see upon divide by zero? Because some architecture might produce nonsensical results upon overflow? Those are put into the "undefined" bucket, but they're quite different from "the compiler can assume it never happens, and treat the alternative as dead code".
>>How's that "bad code"? On most desktop architecture, integers are coded as 2's complement, and wrap around.
It doesn't matter, the language specs are very clear about it.
Think about, if you write a piece of code using unsigned ints which you know is going to overflow, what is the first thing you do? You check the docs for what happens. Does it cap? Does it wrap around? You check the standard and it says that they wrap around. Now you use that in your code because maybe that's a useful behavior.
You do the same thing with signed ints, you check the standard and it says that it's undefined which means you, as a programmer promise you won't do that so you don't.
>>Those behaviours are both perfectly well defined. Why can't one expect to be able to rely on them?
Because language specs says so.
>>Why integer overflow can't simply be "implementation defined"?
Maybe it could and maybe it would be a better language. It is undefined behavior since long time ago though and that means you end of the contract as a programmer is that you won't invoke it.
Your argument just comes down: "Gee, maybe C would be a better language if signed overflow was dependent of implementation and not UB". Maybe that's true, I don't know. It doesn't matter though, it's similar to saying Python would be better language if it didn't have an interpreter lock and then complaining your multithreading program doesn't use all the cores as it should.
You sound like programming languages are a Given™, never to be modified —or at least not by us lowly programmers. That's too pessimistic for my taste.
As for whether C would be better off… that's besides the point. Taken to its extreme, undefined behaviour upon signed integer overflow breaks C's own philosophy.
C is supposed to be closed to the metal. Originally, it was. So you'd expect that if some `ADD R1 R2` instruction triggers an overflow, you might observe funny results that might have funny consequences down the line. You'd further expect that the `+` operator has the exact same consequences —because C is close to the metal.
What you do not expect, is this (imagine you're writing a PID for a DSP, where integer arithmetic saturates):
int signal = input + 1;
if (signal == input) { // tests for saturation
// dead code, because undefined behaviour
//
// The only way for this test to be true is
// to overflow signed integer addition.
// Signed integer overflow is undefined, so
// the compiler is allowed to assume it never
// happens.
//
// Therefore, the test above is always false,
// and the code right here is marked as dead.
}
Dead code is not whatever funny behaviour that might have risen from the `ADD R1 R2` above. That's the compiler doing weird stuff because "undefined behaviour" allowed it to go crazy. This is not what you'd expect of a low level language. Craziness is supposed to come from the platform, not the compiler.
Now C being what it is, the quick fix would be to use INT_MAX instead. It's the portable way to do this test, and would avoid that crazy dead code elimination. But this is not enough: if `input == INT_MAX`, we still have an overflow, and who knows what would happen. The real fix would be something like this:
int signal = input + (input == INT_MAX ? 0 : 1);
if (signal == INT_MAX) {
// live code, yay!
}
I have to emulate saturation in software! Why?!? The platform does this already with the `ADD` instruction, why can't I just use it? Why am I even using a low level language?
In this particular case, the spirit of the C standard was clearly to have addition map to whatever hardware instruction was available. If the `ADD` wrapped around, so would `+`. If it saturated, so would `+`. And if it trapped like a divide by zero, so would `+`.
Instead, the compiler writers took the standard to the letter, and saw "undefined" as a license to eliminate code that wasn't dead by any such low level assumption. Doing this clearly violates the idea that C is close to the metal.
Is your positive experience with C, or C++? I think a lot of the friction is that the two languages often share a compiler but not a philosophy. Modern templated C++ depends heavily on the compiler's ability to optimize out redundant code, while many C programmers still want to more of a WYSIWYG compiler. I often get the feeling that many people involved with GCC would be happier if they could drop C compatibility and just work on a C++ compiler.
Ertl's work is at a low enough level where he knows in advance what the assembly of the optimized loops should look like. For example, here's a big improvement to the Python interpreter that's based on his work: https://bugs.python.org/issue4753. What he, and I, and some small but significant number of others, wish for is that there was a way to write C that will reliably produce the output we envision. The chances that an optimizer will make better code than this is low, whereas the chance that attempts to optimize the code will produce worse code is very high.
I'd be interested to see an "out of sample" longitudinal look at performance of some optimized C code with different versions of GCC. That is, ignore the code used in the standard benchmarks, since the optimization strategies have been consciously chosen to do well on that code, and instead look at code that was written for high performance with older compilers but hasn't yet been tested with current compilers. I don't know for sure what it would show, but I'm guessing it would be more of a mixed bag rather than a monotonic improvement.
It's very positive. Python used to be my favorite programming language, now it's C. I don't code anything in C++ so I don't know, the experience was very negative when I've learnt some of it long time ago but maybe it would be different today.
>>What he, and I, and some small but significant number of others, wish for is that there was a way to write C that will reliably produce the output we envision. The chances that an optimizer will make better code than this is low, whereas the chance that attempts to optimize the code will produce worse code is very high.
Ok, I can definitely see the point. Still it irks me when something as simple as integer overflow is the reason source of rants about UB. I do agree that it would be nice to have some universal way to drop to assembly. I am in probably rare position that I write code which sells because of performance but I am not that good with assembly (the reason is that I am relatively new and work in a niche). That means that optimizer improvements are more important for me than "stick to what is written even if you don't understand it" kind of compiler.
>>. I don't know for sure what it would show, but I'm guessing it would be more of a mixed bag rather than a monotonic improvement.
It could be because programmers fit what they do to the compiler. My approach is to just test a lot of things and leave what sticks so my code is naturally a better fit to a compiler I work with. Still, updating my old GCC to 4.8 was a huge boost in speed and updating to 5.3 recently gave me additional free 4%-5% while compiling faster and offering some new warnings.
"facts" absolutely should be in quotes there. He's describing assertions like "x cannot be null", something that is inferred from the code and treated as fact but turns out not to be true at runtime.
> I know in my compiler work, I have backed out some optimizations that were allowed by the spec, but broke too many programs. The inconsequential speed improvement was just not worth it.
Then perhaps those cases should be reported to the spec commitee to perhaps make the optimization not allowed?
But there is a good underlying point to the paper, and one I tend to agree with - a compiler's behavior should give considerable weight to how the language is used more than taking a literal view of what the words mean in the spec.
Taking things to an absurd extreme to make the point, "undefined behavior" implies that a compiler writer can insert code to reformat the hard disk if it encountered such code. Granted, the point of a spec is to define things so that compiler writers don't have to divine intent, but perfection is never attained and hence compiler writers have to use their judgment.
I know in my compiler work, I have backed out some optimizations that were allowed by the spec, but broke too many programs. The inconsequential speed improvement was just not worth it.