5 ms·
> could be considered fully defined since there is no optimization the compiler can do based on it being undefined UB means the compiler is free to perform wha
by KMag 3y ago
> could be considered fully defined since there is no optimization the compiler can do based on it being undefined
UB means the compiler is free to perform whatever optimizations it wants. Are you really saying that no optimizations are allowed once the compiler notices UB?
Michael Chastain (GDB developer, one of the few people with commit rights to the root of Google's monorepo) told me about the worst bug he ever helped a Googler debug, eventually boiling it down to:
static const double y = 1.0;
static const double x = 2 * y;
printf("Before\n");
if (x > 0.0) {
printf("x\n");
} else {
printf("Not x\n"):
}
printf("After\n");
It was printing "Before\nAfter\n", with nothing in-between.
It turns out that the value of x depended upon the initialization order of statically-scoped doubles. There was no malicious optimization in GCC that got rid of both branches, but both branches got optimized away through a series of intermediate steps where the compiler basically split the control flow graph into something equivalent to:
static const double y = 1.0;
static const double x = 2 * y;
printf("Before\n");
if (x > 0.0) {
printf("x\n");
}
if (!(x > 0.0)) {
printf("Not x\n"):
}
printf("After\n");
GCC determined that x was statically determinied and UB. In both places an optimization pass determined that the fastest choice for if(UB) is to branch over the code in question. Since it's UB, the compiler wasn't required to pick some self-consistent value for x. It's free to optimize away all if(UB) blocks to if(false) blocks.
(Note that this particular case of UB is specific to floating point types and would not occur with integer types. I believe the intention of the spec is to avoid the requirement for floating point hardware to behave identically, including the state bits that affect rounding etc., to behave identically on the machine where the compiler is run and on the machine where the program is run. My naive hot take would be that this should be ID, not UB, but I'm sure the standards committee put a lot more thought into this than I have.)
It wasn't some malicious or two-clever optimization. It was the emergent behavior of a series of optimizations, each one correct.
Anyway, long story short, once the compiler notices UB, all optimizations are on the table. You seem to be implying that compilers need to be conservative when they detect UB. Compilers are generally recklessly aggressive in optimizing when they detect UB.
- vardump 3y agoThat UB is so hard to see, even when you know about static initialization order indeterminism. Static initialization is such a bug magnet in general. Can make builds ”work” randomly, when static object constructors are ran in more or less random order, decided by the compiler. Change something unrelated and kaboom. (Needless to say, that statically initializing anything with a complex constructor is a bad idea, especially if it refers to another statically initialized object.)
- usefulcat 3y agoI don't see the UB. Are you implying or assuming that x and y are defined in different translation units? That would be certainly be UB, but what you wrote doesn't look like that. ETA: from https://en.cppreference.com/w/cpp/language/siof https://en.cppreference.com/w/cpp/language/siof "The static initialization order fiasco refers to the ambiguity in the order that objects with static storage duration in different translation units are initialized in." "Within a single translation unit, the fiasco does not apply because the objects are initialized from top to bottom."
- KMag 3y agoSorry, from the context, you could assume the above was C++. At least in C89, floating point literals aren't constant expressions, and static initialization order is only defined for constant expressions. I distinctly remember getting burned by this in 2003. GCC was acting sanely, but Sun Studio was initializing two static const floats in successive lines in the same file in reverse order they were defined, resulting in 0.0 for the product of two nonzero constants. Tracking that down was rather painful. Switching the file to C++ solved the issue, but this was radio network simulation code that took several days to run on my client's $250,000 Sunfire V1280 with 96 GB of RAM (quite a large amount of RAM for 2003), and Sun's C++ compiler generated slower code for this same file. I presume some amount of monkeying with compiler flags would have brought the C++ performance back in line with the C performance, but I had more important things to do. So, I changed the static const doubles to #defines and that also fixed the bug.
- neuromanser 3y agoI want to say "Static Initialization Order Fiasco!" but that only applies in cross-translation unit scenarios. Were x and y defined in different TUs? It's either that or a compiler bug. https://en.cppreference.com/w/cpp/language/siof https://en.cppreference.com/w/cpp/language/siof