4 ms·
You are strawmanning the parent, he did not claim that compilers will go "oh, you wrote to this variable without synchronization! Now I'll show you, hehehe!". Y
by dooglius 5y ago
You are strawmanning the parent, he did not claim that compilers will go "oh, you wrote to this variable without synchronization! Now I'll show you, hehehe!". Your description of the compiler's behavior matches his description; the compilers detect a case where the program might exhibit behavior defined ISO C spec (signed integer overflow), see it as a potential for optimization because they are allowed to do anything (certain passes can be made if overflow is assumed not to happen), then do that optimization.
- MauranKilom 5y agoYou can cast literally any optimization into this shape. For starters, virtually any optimization pass requires sane assumptions about (absence of) data races and/or out of bounds writes. The point you are arguing (or claiming OP argued) boils down to "compilers behave as I want when I turn all optimization passes off". No, the OP wrote specifically that "the compiler detects [UB]" and then does optimizations exploiting that. Not "detects potential UB". The former is a common misconception, the latter is basically a truism.
- dooglius 5y ago> You can cast literally any optimization into this shape Tell me how these use UB: integer constant folding, unused load removal, optimal use of GP registers to minimize stack spilling >sane assumptions "sane" here is doing a lot of work. Assuming overflow won't happen is not a sane assumption, assuming some other thread won't randomly write into your stack memory is.
- MauranKilom 5y ago> integer constant folding int a = 10; int var1; int b = 20; foo(&var1); int c = a + b; You'd like to constant fold c? Better assume no UB: void foo(int* x) { *(x-1) = 0; } > unused load removal Same idea as above. > Assuming overflow won't happen is not a sane assumption If the alternative is loops being much slower when you request optimizations, then maybe it is. Consult your compiler manuals if this default irks you.
- dooglius 5y agoOkay, yes, you are technically right. What I mean is, making an assumption _other than_ that the code won't alter stack/heap/instruction memory that the implementation is using (e.g. aliasing, overflow, use of NULL)
- gpderetta 5y agoIsn't TBAA exactly the assumption that the program won't alter the stack/head/instruction memory in an illegal way?
- MauranKilom 5y agoSorry, I'm tired of playing whack-a-mole with what are obviously sane assumptions to you. I literally mentioned out-of-bounds writes as a "universal UB" example, you asked me to demonstrate how UB is relevant in a few optimizations, I did, and now you shift goalposts. My original point was that compilers don't specifically abuse UB to sleight you. You're trying hard to draw a line in the sand between different degrees of UB so that you can label one side as "not sane", with (what appears to be) the sole intent of concluding malice on the compiler author side. Please accept that the related tradeoffs have been discussed, and that a default was chosen (if you are curious for a recent instance of this, see [0]). If you are ok with leaving performance on the table by disabling overflow being UB, you are literally free to do that. You're also free to lobby the standard bodies to alter their carefully considered choices. But you're not going to get anywhere by labeling any of this "insane". [0]: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0907r4.html#r0r1 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p090...
- jcranmer 5y ago> unused load removal Oh come on, this is like the most UB-centric thing in the entire compiler. The compiler uses UB to know that it has enumerated all uses of a memory location, as any pointer that didn't get its value from the data dependency tree that compiler used had to use UB to compute the address. (Be it from buffer overflow, use after end-of-lifetime, random integer converted to pointer, etc.)
- dooglius 5y agoI might be using the wrong terminology, what I'm talking about is removing e.g. `int x = *ptr` where x is then not used in scope (more realistically, `(*struct_inst).member` shouldn't bother to load the other members of the struct). What you're doing sounds like removing a global it detects is never used, which I agree relies too much on UB and should not be done.
- gpderetta 5y agoRemoving a load is visible behaviour if the load would have trapped for example. Everybody agrees that compilers should only do sane optimizations. Nobody agrees on which subset is sane.
- dooglius 5y agoThat's fair, I'll concede that I make some implicit assumptions. But the magnitude of people who hit problems and accidentally hit UB should give a strong indication that a lot of things done now are not close to sane.
- gpderetta 5y agoLobbying the standard body to reduce the amount of UB in the C standard is a perfectly reasonable position. Requiring compilers to DWIM is not.
- puffoflogic 5y agoAside from the possible trap ("segfault") mention in the sibling comment, the first example relies on absence of UB because with UB you could enumerate the contents of the stack to find where `x` is living, and discover it isn't there, contra the spec. Even allocating variables directly into registers and never onto the stack relies on UB.
- jcranmer 5y agoHere is an actual example of taking advantage of UB behavior in a compiler. The compiler sees a variable x that's allocated on the stack. It looks at all the uses of the address of x, and sees that they are all loads and stores. It therefore decides to promote x into a register variable. Where's the UB, you ask. Well, since the address of x was never leaked, it is UB to compute another pointer to that address (say, via an integer-to-pointer conversion). The compiler never checked for that possibility to affirm that it was UB; it knew that any code that did that would be UB and simply ignored the possibility. This makes arithmetic overflow a very poor vehicle for UB because it's unusual in that you can't really take advantage of the UB without pointing to the specific operation that caused the UB to occur. This is why I believe that arithmetic overflow UB is gratuitous, and people objecting to UB because of what happens specifically with arithmetic overflow go on to make suggestions that are completely untenable because they don't have familiarity with how most UB works.
- astrange 5y agoUndefined signed overflow isn’t great (and it doesn’t help that C’s implicit integer promotion rules are bad), but it’s important because it allows loop optimizations, which really are important cross-platform, eg Intel is fine with loops counting up but PPC would like them to count down. The problem causing this is that C loops are overspecified. If there was a “for…in” loop that didn’t make you declare and increment a variable, then manual increments could trap on overflow without causing so many problems.
- hramrach 5y agoI don't understand. If you can turn the loop from upward to downward then you do have a bound for it, and then you can tell if it overflows or not. Also why is signed overflow a problem and not unsigned? Surely you want unsigned loop optimized also?
- astrange 5y agoUnsigned overflow is not UB (it wraps) so it has to be preserved more often, which means you have a loop bounds less often, which means those loops can't be optimized. Typically not a problem for i=0...n loops but eg `for (i=0;i<n;i+=2)` could overflow.
- maxlybbert 5y agoThe parent said > UB problems happen only when compilers detect it, understand it is an opportunity for optimization, because they are allowed to do anything, then do that optimization. Perhaps I'm misunderstanding the comment, but I took "detect it, understand it is an opportunity for optimization, because they are allowed to do anything, ..." to mean "detect that undefined behavior occurs and then ..." Instead, it should be "detect that an optimization can be applied and that the optimized code will do the right thing in all cases that undefined behavior does not occur, and apply the optimization." I've met programmers who seem to believe that compilers intentionally apply weird transformations when they detect undefined behavior. But really, the compilers are applying sensible transformations for well-defined code and ignoring whether those transformations are sensible for code that isn't well-defined.