53 comments

[ 2.0 ms ] story [ 101 ms ] thread
If anything's messed up, then that would be my doing.

I thought at first that some of the words in the piece were distorted as a joke about undefined behavior, but as I look at the source, I think something is just broken with the custom fonts.

If signed overflow were defined, then 12 * i + 8 would have to have correct overflow behaviour for 32-bit values and so this trick couldn't be done.

I feel like this misses the difference between "undefined" and "implementation defined". Despite the example given, those of us (like me) who complain about compilers removing safety checks rarely claim that signed 32-bit ints must have guaranteed behavior on overflow. Overflowing might be easiest, if that's what the hardware does naturally, but it doesn't have to be defined to do this.

In our minds, it would be just fine for the compiler to generate an "add" instruction and let the registers fall where they may. What we object to is the compiler's reasoning that since "undefined behavior can be presumed not to occur, I'll just delete this spurious NULL check that the programmer accidentally put here".

Granted, there are cases where a compiler can take proper advantage of such optimizations, but it's emphatically not the case that the only alternative to the current "nasal daemon" approach is disabling all loop optimizations. Simply changing "undefined" to "implementation defined" would satisfy a majority of the objections.

This post, written by someone who is or has been on the standardization committee gives a different point of view:

http://shape-of-code.coding-guidelines.com/2014/08/31/undefi...

What always surprises me is that, when a new optimization comes out justified by a sentence in a dark corner of the C standard, you can usually trace the sentence back all the way to C90.

>the members of the committee were rubbing their hands at the thought of all the optimizations the poorly specified ambiguous successive versions of the document would allow:

I couldn't find that in the blog post you linked?

(comment deleted)
You are right. Either I was thinking of another comment by Derek Jones (one of the two persons I know on the C standardization committee, which is otherwise quite opaque) or I projected my own image of a patiently malevolent committee on the phrase “doing stuff that we could only dream about back in the day” that he did not mean this way.

I have edited my comment.

> Granted, there are cases where a compiler can take proper advantage of such optimizations, but it's emphatically not the case that the only alternative to the current "nasal daemon" approach is disabling all loop optimizations. Simply changing "undefined" to "implementation defined" would satisfy a majority of the objections.

No. Since a huge number of loop optimizations are dependent on trip count detection, this is not in fact the case. Loop trip count detection is ruined by making signed overflow implementation defined.

See this thread from just last week explaining this: https://news.ycombinator.com/item?id=11653940

Additionally, there's less of a difference between "undefined" and "implementation defined" than you might think. Consider the example in the original post:

    if (x + y < x) {
        return 0; /* Overflow */
    }
    length = x + y;
With implementation defined overflow, the compiler is still free to remove this check, because the value of "x + y" would become an undefined value on overflow, and comparisons on undefined values are themselves undefined. If you want the compiler to do anything else, then you're proposing a drastic overhaul of (for example) LLVM's notion of "undef" [1], one that would have massive consequences for optimization.

[1]: http://llvm.org/docs/LangRef.html#undefined-values

Since a huge number of loop optimizations are dependent on trip count detection, this is not in fact the case. Loop trip count detection is ruined by making signed overflow implementation defined.

Could you offer a good example of a loop where performance of a human written lop would suffer if the loop counter was changed from int64_t to uint64_t? I'm sure they exist, but I wonder if I'm underestimating the severity.

With implementation defined overflow, the compiler is still free to remove this check, because the value of "x + y" would become an undefined value on overflow

Well, no, at least the way I'm viewing "implementation defined". If the signed addition is implementation defined, I'd think that by definition "x + y" never becomes an undefined value.

As I'm sure you realize (but others might not), x64 doesn't have separate add-signed-32-bit and add-unsigned-32-bit instructions. So on x64, I'd hope that a sane implementation would simply define signed addition as "ask the processor to add x to y and interpret the result as signed".

I don't think this would require any overhaul of the notion of "undef"?

> Well, no, at least the way I'm viewing "implementation defined". If the signed addition is implementation defined, I'd think that by definition "x + y" never becomes an undefined value.

See my other reply. A compiler can legally rewrite the expression into "(uint64_t)x + (uint64_t)y > UINT_MAX ? undef : true" per your rule.

Am I the only one who thinks that writing

    if (x + y < x)
is utterly insane? It completely breaks the abstraction of algebra that's being used in the language. Much better would be:

    if (ADDITION_WOULD_OVERFLOW_INT(x, y))
    {
       ...
    }
This is (a) far easier to read, and (b) more likely to be more correctly implemented as something like:

    #define ADDITION_WOULD_OVERFLOW_INT(a, b)            \
        (((a) > 0 && (b) > 0 && (b) > INT_MAX - (a)) ||  \
         ((a) < 0 && (b) < 0 && (b) < INT_MIN - (a)))
For bonus points you could write this as part of the compiler support library to derive the types and limits automatically, or even define it as part of the compiler. Why do GCC/clang not have:

    __builtin_addition_would_overflow(a, b)

?
> Why do GCC/clang not have: __builtin_addition_would_overflow(a, b)

Both GCC and Clang have __builtin_add_overflow, which as far as I can tell do exactly what you're proposing.

A comment below reminded me of these: https://gcc.gnu.org/onlinedocs/gcc/Integer-Overflow-Builtins..., which are close, but have an insane API as they replace your arithmetic operations with ugliness.

I think they can be fixed with sensible macros, though:

    #define ADDITION_WOULD_OVERFLOW(a, b) \
      ({ typeof((a) + (b)) __r; __builtin_add_overflow((a), (b), &__r); })
Hopefully the compiler would optimise:

    if (ADDITION_WOULD_OVERFLOW(a, b))
    {
       return 0;
    }
    return a + b;
to only do the addition once.
I just tried this, and GCC6 does indeed optimise this correctly:

    0000000000400560 <maybe_overflow>:
      400560:	01 f7                	add    %esi,%edi
      400562:	70 03                	jo     400567 <maybe_overflow+0x7>
      400564:	89 f8                	mov    %edi,%eax
      400566:	c3                   	retq   
      400567:	31 c0                	xor    %eax,%eax
      400569:	c3                   	retq
How about:

  #define ADD_OR_DEFAULT(a, b, d) \
      ({ typeof((a) + (b)) r; __builtin_add_overflow((a), (b), &r) ? (d) : r; })
and then:

  return ADD_OR_DEFAULT(a, b, 0);
P.S: Names beginning with two underscores are reserved for the implementation.
The problem with solutions like this is that they break the abstraction of using algebraic notation for calculations. We're conditioned to expect calculations to be written in algebraic notation, and indeed one of the original selling points of C is that it lets you express calculations in that form. To undo that is a massive shame.

The calculation itself absolutely MUST look like this:

    a + b
While maybe it's not too bad for simple addition, think of compound expressions with multiple terms.
You didn't consider y < 0
I did. Look at the second line of the macro expansion.
> Additionally, there's less of a difference between "undefined" and "implementation defined" than you might think.

No. If `x + y < x` is "implementation defined", then an "undef" value would never even come into existence!

A compiler can legally rewrite the expression into "(uint64_t)x + (uint64_t)y > UINT_MAX ? undef : true" per the rule suggested by nkurz. This makes the undef value show up.
If signed addition were implementation defined to be saturating, then if (x + y < x) could still be removed as impossible.
Yes, but in that case there wouldn't actually be an overflow either.
> Given that the size of a Point is 12 bytes, the ref­er­ence to the z mem­bers of points is turned into points + (12 * i + 8).

No, it's not. In the C abstract machine, arithmetic on pointers is not the same as arithmetic on integers.

This really is "points + i", but on pointers to an object with sizeof 12, and then a dereference with an offset of 8. These pointers don't have the overflow undefined behavior (but do have other undefined behavior if you have a pointer outside a C object). As implemented in assembly, yes, it comes to a multiplying by 12, but that's not a C int at that point. (And can be done with addressing trickery, as the article says.)

Perhaps someone who has a better grasp of the standard can correct me, but as far as I can tell, your interpretation is correct and the given example is not undefined behavior.

From 6.5.2.1 (Array Subscripting) of the C11 draft:

"A postfix expression followed by an expression in square brackets [] is a subscripted designation of an element of an array object. The definition of the subscript operator [] is that E1[E2] is identical to (*((E1)+(E2))). Because of the conversion rules that apply to the binary + operator, if E1 is an array object (equivalently, a pointer to the initial element of an array object) and E2 is an integer, E1[E2] designates the E2-th element of E1 (counting from zero)."

From 6.5.6 (Additive Operators):

"When an expression that has integer type is added to or subtracted from a pointer, the result has the type of the pointer operand. If the pointer operand points to an element of an array object, and the array is large enough, the result points to an element offset from the original element such that the difference of the subscripts of the resulting and original array elements equals the integer expression. [...] If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined."

To me, those sections together read that there should be no overflow as long as i is smaller than the length of `points` + 1, and thus no undefined behavior. Does anyone have another interpretation?

Nope, you're correct. On the other hand, earlier today I read a post about a pretty similar-seeming phenomenon (signed overflow UB reducing the number of lea instructions that have to be generated):

https://gist.github.com/rygorous/e0f055bfb74e3d5f0af20690759...

Perhaps the author had heard that -fwrapv causes extra 'lea's but got mixed up as to why. (TL;DR: it makes it harder for the compiler to prove that you're not a sadist using negative array indices.)

I was talking about the addressing at the assembly level, because that's what LEA and addressing modes deal with. However, your references to a spec make a very good point. Maybe I misunderstood Chandler in the first place.

I've pulled the post (since it may be incorrect) until I can sort it out with a compiler person.

I think when the author writes points + (12 * i + 8), it is meant to be a calculation as done in assembly, not in C.
Sure, but at that point what relationship does it have to the semantics of C ints?
I work on a piece of high-performance C++ code. Anything that can make us 1% faster than the competition would be welcome, given that our product often runs number crunching tasks for months. We compile all our code with -fwrapv, because fuck trying to guess how the compiler would "optimise" the code.
Friendly reminder to use clang's and gcc's intrinsics to check for integer overflow (signed or unsigned):

https://gcc.gnu.org/onlinedocs/gcc/Integer-Overflow-Builtins...

http://clang.llvm.org/docs/LanguageExtensions.html#checked-a...

On the other hand, using nonstandard extensions makes it harder to move away from a compiler the direction of which you are not happy with, and can make your situation worse in the long term.
Just put the intrinsics into their own functions and re-implement those when switching compilers.
Wouldn't this be a classic case where you'd encyst the nonstandard code in a library, making it simple to swap out the implementation if required?
What's going on with the zalgo text? Is this an aesthetic choice, or is this a legitimate rending issue?

http://i.imgur.com/tryjhMi.png

Interesting. Looks normal for me on Chrome 50.0.2661.102m, Win10.
Doesn't happen here (Firefox). Seems indeed like a rendering issue in your browser.
Hmm, must be Safari then.
Looks like Safari is no longer rendering the fonts correctly. (It used to work but I hesitate to blame Safari because font files are complex and maybe there's a problem with the ones that I'm using.) I've dropped the custom font now.
(comment deleted)
Yes (x - y < x) can't detect overflow because it overflows too, but what's wrong with (UINT_MAX - x < y)?
What do you mean?

(UINT_MAX - x < y) is wrong, if x and y are both of type int, it should be: (INT_MAX - x < y), which is defined and will yield true, if an overflow on x+y would happen.

Is there a request overflow? This page just won't load and gives no error, either.
Signed overflow optimization is just a cheap trick for avoiding the need to do real bounds analysis. Every single loop optimization possible by abusing signed overflow is also possible with unsigned values (and thus implementation-defined signed overflow) if you can prove your bounds.

I'm sick and tired of compiler authors whining about how we "need" undefined signed overflow for optimization, particularly because I usually use unsigned loop counters and so don't benefit from this optimization, but would benefit from real, robust bounds analysis that worked for both signed and unsigned types.

GCC and clang are open source. Please contribute your patches.
An interesting pet project for someone to attempt would be a language with fully-specified arithmetic.

So rather than declaring vaguely that you want an "integer", you'd specify bounds, signed-ness (two or one's complement), and what happens on overflowing the bounds (wrap / saturate / exception / trap / set flag / set NAN etc.)

Saturated arithmetic intrinsics are widely available but generally not used by generated code. Similarly I've never seen an attempt at doing "integer NaN", which could be quite useful in some situations.

You'd probably also want some flags to say "give me a compiler error or warning if this code doesn't compile to intrinsics and is being emulated instead".

Generic (compound) constraints could also be useful.

Like this:

(Value >= 0 && Value <= 1000 && (Value & 1) == 0) || Value in(value_enum)

Would limit Value to even numbers between [0 .. 1000] except if it's an enum (for error values).

Hmm, I can see why that would be useful but that makes it much harder to reason about in order to automatically eliminate checks wherever possible; you can guarantee that adding a number in [0,100) to [0,100) will produce a number in [0,200) without needing to apply checks, but one of your Values may generate an error if you just increment it.
Compilers already reason about values like that. They can prove certain bits in an integer to have certain values, etc, not just about ranges. Actual checks could be optimized away pretty often.

Simple example: Say, you multiply a number by two. From that, the compiler data flow analysis knows the lowest bit can only be zero. So it can safely optimize away anything that checks that bit.