7 ms·
Dropping "explicit error checks" is no doubt bad from the user's point of view, but the primary difficulty is that to the compiler, working at the abstract leve
by BeeOnRope 8y ago
Dropping "explicit error checks" is no doubt bad from the user's point of view, but the primary difficulty is that to the compiler, working at the abstract level of multi-pass optimization of the IR, the good and bad cases all look the same. Pruning paths that lead to UB leads to some significant actual real-world optimizations (and these extend beyond these various signed-wrapping examples), and people want those whether they know it or not. Unfortunately, to the compilers, your overflow check just looks like another one of these cases.
That is, it is very hard for the compiler to guess the programmer's intent, and say "oh, this is actually a check which is trying to avoid overflow, and despite that it uses UB, we should compile this part with wrapping semantics instead", and yet apply the optimization in all the other places you would want it. You could probably come up with some type of pattern recognition, or some heuristics like "if the optimization makes some of the original source code dead unconditionally, don't make it", but I think this is both very difficult and in many cases worse than the current disease.
I think you can make a strong argument that the optimizations enabled by some types of UB exploitation are not worth the bugs and vulnerabilities they cause, and so the UB should be removed entirely, making it defined or implementation defined. Trying to have your cake (yes, it should still make these optimizations x and y) but eating it too (oh, but case z is different, see?) is a tougher proposition.
- nkurz 8y agoDropping "explicit error checks" is no doubt bad from the user's point of view Only from the user's point of view? No, I'd say it's bad in some absolute sense as well. Can we at least agree that it would be better for the compiler to do what the programmer clearly intended? Yes, given current limitations, it can be difficult to see how a compiler has enough information to always recognize this intent. But I hope we at least both agree that if all else doing equal, doing what the programmer meant is the preferred choice? As a parallel, I think the approach of having -Wall flag "if (a = b)" as a likely error but be overrideable with "if ((a = b))" is a great solution. Maybe this one can be solved with an extra block, or with a better #pragma, or just really good heuristics. But the current approach of silent deletion of explicit code for undefined behavior is a terrible pattern!
- BeeOnRope 8y ago> Only from the user's point of view? No, I'd say it's bad in some absolute sense as well. I didn't say _only_, but I'm not sure about "bad" an an "absolute" sense. I think it would take a lot of defining the exact question to debate that. For me if it is a bad outcome from the user's point of view, it is already worth discussing. > Can we at least agree that it would be better for the compiler to do what the programmer clearly intended? I guess? It would be pretty neat to work in a language that did what I intended rather than what I wrote, but it will quickly become unsustainable when two people write the same code but intended different things. That problem doesn't apply exactly here since the code is just UB, so the intent doesn't exactly conflict with the actual semantics since there are no semantics. However, the compiler writers have decided to chose the behavior which produces the fastest code. > Maybe this one can be solved with an extra block, or with a better #pragma, or just really good heuristics. IMO, it is largely already solved. If you want signed overflow to behave as, well, signed overflow, use -fwrapv. You pay the small price in reduced optimization opportunities, but you get well-defined behavior on wrapping always (note: wrapping is still almost always a bug!). I think you can make a good argument that this flag should be the default behavior. Certainly, if you were designing a new language you might want to make it the default and maybe C and/or C++ will get there some day. > But the current approach of silent deletion of explicit code for undefined behavior is a terrible pattern! This sentence embodies my main complaint with the endless discussions on this topic. There is no optimization that just removes signed overflows checks, inserted there by a malicious compiler writer. There is a big family of interlocking optimizations, which as a whole are responsible for some spectacular gains in compiled code efficiency. For example there is dead code elimination, which is really important. There is treating code that does something impossible or UB as dead, which is also important (it extends way beyond this signedness stuff). All this conspires to eliminate the _explicit_ overflow check which is the poster-child for compiler misbehavior - but the same optimizations do all sorts of other stuff that you wouldn't have a problem with. Everyone points there finger at this case that looks bad and says "obviously don't do that", but very few explain how. Deep within the guts of the optimizer, you just have IR which probably looks nothing like the code you wrote, there is no distinction between explicit and other code, and no "annotation of intent" to tell anyone what the programmer _really meant_. You have most or all of the major compilers implementing this optimization, yet none of them have apparently solved this simple case of not messing up what the programmer wrote? Probably because it is not at all simple. The solution I'm aware of is to just always treat overflow as defined. I.e., the -fwrapv solution. I'd be fine if compiler makes made it the default - but I guess the competitive pressures mean that compilers generally leave all legal optimizations on at the highest levels, to not be slower than the other guy. I'm OK with that also - at least it's a consistent rule. The compiler-writers position is if you write broken code that invokes UB, you don't get the complain. I think that's often a reasonable approach. In other languages it's just the obvious default: it's just a bit annoying in C/C++ because there are so many things that people think should work, and did work, but are actually UB.
- vyodaiken 8y agoWhy do you assume we want either Pascal ("wrapping semantics") or random-"optimizations". Those are not the choices. It's actually very easy for the compiler to detect that it is a check to avoid overflow, it's the text of the program. You don't need to compile with wrapping semantics: the compiler generates code that actually does overflow the counter. All you need to do is to stop using invalid reasoning of the type: We assume that overflow cannot happen, yet generate code in which it does happen.
- BeeOnRope 8y ago> Why do you assume we want either Pascal ("wrapping semantics") or random-"optimizations". I don't think I assumed that. I think there are a variety of ways of dealing with the overflow issue, which fall at a variety of points within the multi-dimensional design space that includes considerations like (a) comprehensibility by the average programmer and principle of least surprise (b) performance (c) fast failure (d) portability across hardware. I don't know if C and C++ made the right decision with leaving this UB, nor am I convinced of the compiler writers method of handling the UB, but they are at least consistent in usually favoring performance and portability above other concerns. That it, is it is not obviously wrong to me or, nor dominated with another obviously better behavior. > It's actually very easy for the compiler to detect that it is a check to avoid overflow, it's the text of the program. I guess you are suggesting some type of idiom recognition, which will preserve these checks, but still allow full optimization in other cases where overflow is not expected or the intent of the programmer was different? I don't think that will ever happen. The best you can hope for is that the involved compilers simply decide to define signed overflow as wrapping, i.e., make the -fwrapv behavior the default. Personally I think that's a reasonable choice.
- vyodaiken 8y agoI can't imagine it is ever the intent of the programmer that if(i+1 < i)x be there simply as decoration to be removed by the compiler. If the compiler can deduce that the test will evaluate as false (or true), it is perfectly reasonable to optimize by deleting the line (or the check, in case of true). But this should be sound deduction. For example, if the target processor traps on overflow, the compiler can deduct that the conditional is always false. But what GCC/Clang do now is (a) generate machine code on x86 and ARM that does wrap, and (b) transform code on the axiom that ints will not wrap!That is, they take "false" for a theorem.