Live data from Hacker News

GCC proves an uninitialized variable must be 0 and doesn't warn about it

lkml.org

31–40 of 67 posts

Re: GCC proves an uninitialized variable must be 0 and doesn't warn about it

#31
post #12

Earlier quoted context omitted.

The compiler is allowed to do that. Since it would be undefined behavior to read the undefined variable, the compiler gets to pick whatever value it wants for it. So after the if, the compiler sees that ret is either 0, or an undefined value. It picks 0 as the undefined value since that's a good optimization, so ret is now always 0, and (in this case) there is no warning.

> It picks 0 as the undefined value since that's a good optimization. No it isn't; it costs an extra instruction to initialize that register or memory location to zero. How C compilers optimize situations involving uninitialized variables is by leaving those variables alone. They do so with the standard's blessing, which grants them that uninitialized automatic variables are "indeterminately-valued" and that if the p…

Actuallym it is: You're right to need an extra instruction, but you gain a register. Storing and reloading this register would take instructions too.

I threw this in godbolt, so play around, e.g. initialize ret or invert the condition. Linus' version has 36 lines, most other things I tried end up with 40 or more.

https://godbolt.org/z/xiDGam

Update: Added -Wall to godbolt

Re: GCC proves an uninitialized variable must be 0 and doesn't warn about it

#32

Earlier quoted context omitted.

Note he uses both, even in the same paragraph - asterisks typically mean "bold," whereas underscores typically mean "underlined."

Except in Slack, which somehow interprets this to be italics.

Underlining was a substitute for italics before everyone had fancy word processors.

Re: GCC proves an uninitialized variable must be 0 and doesn't warn about it

#33

Random question - what are the _underscores_ that show up frequently in Linus's responses?

An old way to underline.

To cite the jargon file:

  Also, it is common to use bracketing with unusual
  characters to signify emphasis. The asterisk is most
  common, as in “What the *hell*?” even though this
  interferes with the common use of the asterisk suffix
  as a footnote mark. The underscore is also common,
  suggesting underlining 
http://catb.org/~esr/jargon/html/writing-style.html

Re: GCC proves an uninitialized variable must be 0 and doesn't warn about it

#34

Earlier quoted context omitted.

> It picks 0 as the undefined value since that's a good optimization. No it isn't; it costs an extra instruction to initialize that register or memory location to zero. How C compilers optimize situations involving uninitialized variables is by leaving those variables alone. They do so with the standard's blessing, which grants them that uninitialized automatic variables are "indeterminately-valued" and that if the p…

Actuallym it is: You're right to need an extra instruction, but you gain a register. Storing and reloading this register would take instructions too. I threw this in godbolt, so play around, e.g. initialize ret or invert the condition. Linus' version has 36 lines, most other things I tried end up with 40 or more. https://godbolt.org/z/xiDGam Update: Added -Wall to godbolt

How do you gain a register by emitting xorl %eax, %eax versus simply not emitting that and simply letting the code access the garbage value that's already in %eax?

Re: GCC proves an uninitialized variable must be 0 and doesn't warn about it

#35
post #12

Earlier quoted context omitted.

The compiler is allowed to do that. Since it would be undefined behavior to read the undefined variable, the compiler gets to pick whatever value it wants for it. So after the if, the compiler sees that ret is either 0, or an undefined value. It picks 0 as the undefined value since that's a good optimization, so ret is now always 0, and (in this case) there is no warning.

> It picks 0 as the undefined value since that's a good optimization. No it isn't; it costs an extra instruction to initialize that register or memory location to zero. How C compilers optimize situations involving uninitialized variables is by leaving those variables alone. They do so with the standard's blessing, which grants them that uninitialized automatic variables are "indeterminately-valued" and that if the p…

>No it isn't; it costs an extra instruction to initialize that register or memory location to zero.

If you're talking about the "xor eax, eax" that was emitted, then absolutely not. GCC is doing the most efficient thing possible with eax here.

That xor is not inserted because GCC kindly initializes the variable for you to save you from your mistakes, but because it has to initialize eax before returning (look, there's a function call before in the code example). And after deciding the return value to be 0 it knows "xor eax, eax" is always going to be faster than actually loading the real value of "ret". (In fact it's a zero-idiom, even a NOP instruction is slower than that!)

What I was really trying to talk about is the SSA semantics and not the actual emitted code. Now in fairness I'm not familiar with GCC's IR, but I'm going to blithely assume what GCC does with unitialized variables somewhat ressembles LLVM's undef semantics.

When I say that picking 0 is a good optimization, I mean that at an SSA level, the compiler is going have a Phi between Undef and the range [0, 0]. If by "leaving those variables alone" you mean that the compiler should have returned Undef as the result of that Phi, that would be a horrible miscompilation! And of course picking something else than 0 would just be silly.

>It's not correct for diagnostics to pertain to versions of the program that were logically altered by the compiler, rather than to the original program.

Agreed. As a user, I am not happy about this lack of diagnostics and I'm happy to call that a bug if you want. And furthermore, I don't think performing the optimizations above is fundamentally incompatible with good diagnostics. Probably just hard to implement for GCC.

Disclaimer: I am not actually a compiler engineer and this probably contains mistakes.

Re: GCC proves an uninitialized variable must be 0 and doesn't warn about it

#36
post #35

Earlier quoted context omitted.

> It picks 0 as the undefined value since that's a good optimization. No it isn't; it costs an extra instruction to initialize that register or memory location to zero. How C compilers optimize situations involving uninitialized variables is by leaving those variables alone. They do so with the standard's blessing, which grants them that uninitialized automatic variables are "indeterminately-valued" and that if the p…

>No it isn't; it costs an extra instruction to initialize that register or memory location to zero. If you're talking about the "xor eax, eax" that was emitted, then absolutely not . GCC is doing the most efficient thing possible with eax here. That xor is not inserted because GCC kindly initializes the variable for you to save you from your mistakes, but because it has to initialize eax before returning (look, there…

> GCC is doing the most efficient thing possible with eax here.

It seems that the most efficient thing with %eax is not to mention it in an instruction.

There may be some quirk/feature of modern Intel processors that clearing a register will dis-entangle it from considerations of prior hazards. So that is to say, when we execute 'xorl %eax, %eax', the processor knows that any prior value in `%eax` is no longer required by subsequent code. A new lifetime has begun for that register. Therefore, there is no need to execute a pipeline stall to wait for that prior value, if it so happens that that prior value is not yet ready. Internally to the processor, the ISA-level register `%eax` can be assigned to a different internal register at that point, under the discipline of "register renaming".

In the absence of any such hardware considerations, the clearing of %eax is pure waste.

> it has to initialize eax before returning

Where is that requirement coming from? Not from ISO C, certainly. The behavior is undefined. It was the programmer's job to ensure that the return value is calculated by a well-defined expression, not that of the compiler. From that standpoint alone, it's perfectly conforming to emit code that just leaves the existing content in %eax and returns.

Re: GCC proves an uninitialized variable must be 0 and doesn't warn about it

#37

Earlier quoted context omitted.

Actuallym it is: You're right to need an extra instruction, but you gain a register. Storing and reloading this register would take instructions too. I threw this in godbolt, so play around, e.g. initialize ret or invert the condition. Linus' version has 36 lines, most other things I tried end up with 40 or more. https://godbolt.org/z/xiDGam Update: Added -Wall to godbolt

How do you gain a register by emitting xorl %eax, %eax versus simply not emitting that and simply letting the code access the garbage value that's already in %eax ?

So, if the if branch is taken, then the function could really return 0, right?

So GCC has to have some instruction somewhere that ensures eax is 0 at the end of the function in case the if was taken. That's why it can't just leave a garbage value in eax. Sometimes it's not actually garbage.

And when it really is garbage? Well who cares, might as well return 0.

Re: GCC proves an uninitialized variable must be 0 and doesn't warn about it

#38
post #35

Earlier quoted context omitted.

>No it isn't; it costs an extra instruction to initialize that register or memory location to zero. If you're talking about the "xor eax, eax" that was emitted, then absolutely not . GCC is doing the most efficient thing possible with eax here. That xor is not inserted because GCC kindly initializes the variable for you to save you from your mistakes, but because it has to initialize eax before returning (look, there…

> GCC is doing the most efficient thing possible with eax here. It seems that the most efficient thing with %eax is not to mention it in an instruction. There may be some quirk/feature of modern Intel processors that clearing a register will dis-entangle it from considerations of prior hazards. So that is to say, when we execute 'xorl %eax, %eax', the processor knows that any prior value in `%eax` is no longer requir…

>It seems that the most efficient thing with %eax is not to mention it in an instruction.

I think what you're missing is that UB is a property of the execution of the program, and not just of the code that was compiled.

If in practice the if branch is always taken, then there is NO undefined behavior (surprisingly, perhaps)!

That means GCC has to be prepared to handle that case, and has to set eax to 0 when the if branch is taken.

>Where is that requirement coming from? Not from ISO C, certainly. The behavior is undefined.

Not so! You don't know until the program is run.

-

Edit: (removed some speculation about assuming the branch is always taken, that was not relevant)

>There may be some quirk/feature of modern Intel processors that clearing a register will dis-entangle it from considerations of prior hazards. So that is to say, when we execute 'xorl %eax, %eax', the processor knows that any prior value in `%eax` is no longer required by subsequent code. A new lifetime has begun for that register.

That's absolutely right, by the way. I think this was introduced with Sandy Bridge, in 2011.

Re: GCC proves an uninitialized variable must be 0 and doesn't warn about it

#39
post #38

Earlier quoted context omitted.

> GCC is doing the most efficient thing possible with eax here. It seems that the most efficient thing with %eax is not to mention it in an instruction. There may be some quirk/feature of modern Intel processors that clearing a register will dis-entangle it from considerations of prior hazards. So that is to say, when we execute 'xorl %eax, %eax', the processor knows that any prior value in `%eax` is no longer requir…

>It seems that the most efficient thing with %eax is not to mention it in an instruction. I think what you're missing is that UB is a property of the execution of the program , and not just of the code that was compiled. If in practice the if branch is always taken, then there is NO undefined behavior (surprisingly, perhaps)! That means GCC has to be prepared to handle that case, and has to set eax to 0 when the if b…

No, I mean the behavior is undefined in that case when the function returns the indeterminate value, which was initialized anyway.

Look, GCC (what I have here: 7.3.0) is doing this even for the following trivial function:

    int undef(void)
    {
      int ret;
      return ret;
    }
With -O2 this turns out:

  xorl %eax, %eax
  ret
do we still know until run-time that this has UB?

Re: GCC proves an uninitialized variable must be 0 and doesn't warn about it

#40
post #38

Earlier quoted context omitted.

> GCC is doing the most efficient thing possible with eax here. It seems that the most efficient thing with %eax is not to mention it in an instruction. There may be some quirk/feature of modern Intel processors that clearing a register will dis-entangle it from considerations of prior hazards. So that is to say, when we execute 'xorl %eax, %eax', the processor knows that any prior value in `%eax` is no longer requir…

>It seems that the most efficient thing with %eax is not to mention it in an instruction. I think what you're missing is that UB is a property of the execution of the program , and not just of the code that was compiled. If in practice the if branch is always taken, then there is NO undefined behavior (surprisingly, perhaps)! That means GCC has to be prepared to handle that case, and has to set eax to 0 when the if b…

[deleted]
Post reply on HN