4 ms·
In C++, the snippet of code: double x; double y = std::nan(""); double z = std::fmax(x, y); is undefined behaviour. So the current transformation
by quietbritishjim 6y ago
In C++, the snippet of code:
double x;
double y = std::nan("");
double z = std::fmax(x, y);
is undefined behaviour. So the current transformation in LLVM looks correct to my C++-coloured eyes. (Incidentally, std::fmax(x, nan) is supposed to return x, but even if it was defined to return nan then this would still be undefined behaviour, because passing an uninitialised variable to a function is undefined behaviour even if the parameter is ultimately ignored.)
Could someone explain a practical situation where the listed transformation causes incorrect compilation of some code in a programming language (maybe not the right term but I mean as opposed to LLVM IR)?
- tsukikage 6y agoI mean, it's undefined behaviour. The current transformation is correct, but so is the one proposed in OP's link. In fact, any transformation would be correct, as would nasal demons. An actually interesting question might be: which of the correct transformations is the more useful?
- devit 6y agoLLVM undef does not mean "undefined behavior", it means that it's OK to transform any use of it into the use of an arbitrary value (potentially different, unless the freeze instruction is used). To get undefined behavior, you need to do something like use the unreachable instruction, or call a noreturn function that does in fact return. You may also have "poison values", which trigger undefined behavior on certain uses.
- jcranmer 6y agostd::fmax is not the same as llvm.maximum, it's the same as llvm.maxnum. fmax in C/C++ specifies that the non-NaN argument is returned if one argument is NaN. The llvm.maxnum intrinsic does likewise, although it has more clarity on what it does with signaling versus non-signaling NaN. The llvm.maximum intrinsic specifies that NaN is returned if one argument is NaN. Edit: Here's a longer discussion of undefined behavior in LLVM: Undefined behavior in LLVM is more complicated than C/C++, and this is one of the areas where assuming C/C++ semantics are going to confuse you more than help. Unlike C/C++, there are several gradations of undefined behavior, and what is UB in C/C++ may be weaker in LLVM. Strongest is undefined behavior. Like C/C++, the optimizer will optimize under the assumption that it does not occur, and can even draw inferences that it will not occur. Undefined behavior largely arises out of cases where you add annotations that are incorrect (e.g., a readnone function reads memory), but it is also retained in a few basic situations: dereferencing null pointer, branching on undef, divide by 0, unaligned memory accesses [1]. Somewhat weaker is the poison value. This is effectively delayed undefined behavior. Poison originates from violating nuw/nsw flags (basically, C/C++'s signed integer overflow rule), as well as out-of-bounds pointer arithmetic, and maybe a few more cases I don't remember off-hand. Poison propagates through most instructions, and becomes undefined behavior if you use it to control a branch or access memory. Weaker still is the undef value. This value can take on any bit pattern, and--crucially and confusingly--it need not take on a consistent bit pattern. So in this code: %x = undef %y = sub i32 %x, %x The value of %y is undef, because you can "read" different values of %x in the two arguments of %y. It is undefined behavior to dereference an undef pointer, or branch on an undef value. Weakest is the frozen values. The LLVM freeze instruction converts its input to an undefined, but stable value. If we were to consider the equivalent of the above program: %x = freeze i32 undef %y = sub i32 %x, %x Then we can say that %y = 0 here, since the two values of %x must read the same value. Frozen values are still undefined behavior value to access memory, but they are no longer undefined behavior if used as a branch condition. [1] Note that, unlike C/C++, LLVM does allow underaligned accesses, so you can specify a load of a 4-byte value that is 1-byte aligned, and this is not undefined behavior.
- quietbritishjim 6y agoI get that. std::fmax in C++ is not llvm.maximum. The question I was trying to ask was: so what in C++ is llvm.maximum? (Or what in some other LLVM front-end language instead of C++, e.g. Rust or Fortran, is llvm.maximum?) And having answered that, when looking at that C++ code, would I expect it to be well defined even though this bug (if it is a bug) makes it undefined?
- jcranmer 6y agoSo, IEEE 754 took the very unusual step of tossing their old definition of minimum and maximum and replacing them with a new definition. This is effected by deprecating the operations under their old name and introducing new operations with new names. Here, LLVM's intrinsic names are following the IEEE 754 standard names. C and C++ only has the old definition provided in their standard library, although there is a proposal for C2x to add the new comparisons under the names "fmaximum" and "fminimum" (see http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2489.pdf http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2489.pdf).