Live data from Hacker News

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

lkml.org

51–60 of 67 posts

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

#51
post #28
post #23

Earlier quoted context omitted.

By msan, are you referring to this? https://clang.llvm.org/docs/MemorySanitizer.html (I'd never heard of the tool before.)

Yes, this is msan. All the clang sanitizers have handy names, like tsan, msan, asan, ubsan, ksan. And they even made it to gcc.

IIRC they were in GCC first and later ported to clang because it was too annoying for Google to continue to maintain them in GCC.

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

#52

Earlier quoted context omitted.

It's for emphasis. Some people use asterisks. He uses underscores.

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

Back in the days of usenet, and way before markdown, we always used bold, /italic/, and _underline_. You were just supposed to mentally interpret them in your head.

Edit: Ironically, HN is translating my asterisk-bolds into italic.

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

#53

Earlier quoted context omitted.

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

They were always meant for emphasis, which takes on variable definitions over time. Terminals were bad at italics due to character box layouts and so the literary approach of emphasis through italics mutated into emphasis through underlines. Now that we’re all free of monospaced terminal grids, it looks like we’re drifting back to emphasis through italics.

Italics were still used though.

Like /this/.

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

#54
post #12

It look like a bug to me, since inode->i_nlink may be zero, and then ret is uninitialized (optimizing the final "return ret" to "return 0" should be safe (but is not necessarily worth it, depending on the target instruction set and ABI), although if warnings are enabled, it seem like the warning should still be displayed).

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.

No, it picks 0 because that's required for the code path where the "if (ret) return ret;" doesn't return, for all the other code paths where it reaches the end of the subroutine ret is undefined so 0 is good for all possible paths to that point

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

#55
post #20

What I don't understand is why it doesn't optimize away the check on (inode->i_nlink), since it is assuming the code in the dependent block always runs. It has to be assuming that, because it is the only path that doesn't lead to undefined behavior.

The optimizer isn't required to eliminate code paths encountering dynamic UB; it is allowed to. It also doesn't have to be consistent in its "assumptions", although inconsistency may be a sign of missing efficiency somewhere

> The optimizer isn't required to eliminate code paths encountering dynamic UB; it is allowed to.

If I understood correctly, Linus had some colorful thoughts[1] on that:

If you know something is undefined, you warn about it. You don't silently generate random code that doesn't match the source code just because some paper standard says you "can".

[1] https://gcc.gnu.org/bugzilla/show_bug.cgi?id=89501#c12

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

#56
post #54
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.

No, it picks 0 because that's required for the code path where the "if (ret) return ret;" doesn't return, for all the other code paths where it reaches the end of the subroutine ret is undefined so 0 is good for all possible paths to that point

Since I'm on a comment spree today: I think we mostly agree, actually, but note that the compiler doesn't have to pick 0 for the undefined value at all. It only does that because that's the most convenient value to pick.

(And for the code path where it's deduced to be 0, well it's not really picking, it doesn't have a choice =] )

A compiler with less optimizations could chose to spill the "ret" variable and not pick a value at all, for example. It would just return whatever happens to be lying on the stack if you took the unitialized path.

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

#57
post #9

Explanation here (from 2004): https://gcc.gnu.org/ml/gcc/2004-12/msg00681.html It's not that GCC proves the uninitialized variable must be 0, it's just that CCP sets it to 0 and everything happens to work out in specific cases.

This is not a correct interpretation (I worked on CCP and related optimizations for years :P)

It does in fact prove that it is zero, because it is the meet of lattice values (undefined, 0).

You can actually solve this particular case by interpreting phi nodes differently than CCP does. CCP does not generally care about what blocks things occur in - it doesn't have to, all definitions dominate all uses, so it is safe to evaluate things in any order. The only thing you stand to lose is optimality because things only go one direction on the lattice (to ensure that it reaches a fixed point).

However, in this particular case, you can see if that if you treated the phi nodes as the equivalent form (assignments occurring on the edge of the previous block), and then processed the loop blocks in dominance order, it is obvious the use is uninitialized.

It is only when looking at phi nodes as an unordered black box (as ccp does) that you get this particular variant of the issue.

This is, of course, just a very cheap form of flow sensitivity.

You can also get the same effect by using SSI form in a lot of cases.

Most compilers do not bother doing real expensive flow sensitive analysis to try to analyze stuff like this, because almost everything you can do comes with its own fun source of false positives and negatives.

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

#58
post #28

Earlier quoted context omitted.

Yes, this is msan. All the clang sanitizers have handy names, like tsan, msan, asan, ubsan, ksan. And they even made it to gcc.

IIRC they were in GCC first and later ported to clang because it was too annoying for Google to continue to maintain them in GCC.

As far as I know Kostya he started by improving Helgrind as valgrind plugin in 2008: the first ThreadSanitizer (as project data-race-test) https://storage.googleapis.com/pub-tools-public-publication-... which was a harder problem than oob checks. But it was still too slow, and they started with tsan and asan as clang plugin. Years later they ported it to gcc. gcc started then becoming more plugin friendly.

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

#59
A simple way to avoid this problem is prohibit, in that code base, local variable declarations without initializers. If the initializer truly was unneeded the compiler would be able to eliminate it in almost all cases. Risking undefined behavior for hypothetical microspeedups is just dumb. If a case occurs where leaving out the initializer is both useful and safe then the initializer could be removed there, after deliberate review.

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

#60
post #9

Explanation here (from 2004): https://gcc.gnu.org/ml/gcc/2004-12/msg00681.html It's not that GCC proves the uninitialized variable must be 0, it's just that CCP sets it to 0 and everything happens to work out in specific cases.

This is not a correct interpretation (I worked on CCP and related optimizations for years :P) It does in fact prove that it is zero, because it is the meet of lattice values (undefined, 0). You can actually solve this particular case by interpreting phi nodes differently than CCP does. CCP does not generally care about what blocks things occur in - it doesn't have to, all definitions dominate all uses, so it is safe…

Just curious. I’m learning about compilers and would be interested in which compilers most make the trade-off in favor of optimal code generation over compilation speed.
Post reply on HN