Support constexpr version of __builtin_ilogb and its variations.
Details
Diff Detail
- Repository
- rG LLVM Github Monorepo
Event Timeline
This patch is similar to __bultin_fmax: https://reviews.llvm.org/D134369
The constexpr version of ilogb matches the libc realization, this is verified with the same tests:
https://github.com/llvm/llvm-project/blob/main/libc/test/src/math/ILogbTest.h
test_special_numbers -> ILOGB_TEST_SPECIAL_NUMBERS test_powers_of_two -> ILOGB_TEST_POWERS_OF_TWO test_some_integers -> ILOGB_TEST_SOME_INTEGERS
https://eel.is/c++draft/library.c#3 says that a floating-point exception other than FE_INEXACT causes it to not be a constant expression.
I check it with small ilog function refactoring and the new function isConstantOpStatus.
The online documentation (https://en.cppreference.com/w/cpp/numeric/math/ilogb) says:
1. If the correct result is greater than INT_MAX or smaller than INT_MIN, FE_INVALID is raised. 2. If arg is ±0, ±∞, or NaN, FE_INVALID is raised. 3. In all other cases, the result is exact (FE_INEXACT is never raised) and the current rounding mode is ignored
The first point seemingly never occur, because llvm's ilogb return type is int.
The second point is handled as expected (APFloatTest.cpp checks it)
FWIW, I would be slightly wary of relying on cppreference as definitive for niche semantic issues like this, although it is correct in this case. C2x is actually pretty well-organized in Annex F, and enumerates most of the corner cases specifically in every function.
clang/lib/AST/ExprConstant.cpp | ||
---|---|---|
12452 | long double is ppc_fp128 on at least some PPC targets, and while I'm not entirely certain of what ilogb properly returns in the corner cases of the ppc_fp128, I'm not entirely confident that the implementation of APFloat is correct in those cases. I'd like someone with PPC background to comment in here. | |
clang/test/Sema/constant-builtins-ilogb.cpp | ||
14 | Test of smallest subnormal and largest finite number would also be useful. |
+1 to this point; the edge cases are pretty tricky and the standard is the source of truth for how we should be implementing this stuff.
clang/lib/AST/ExprConstant.cpp | ||
---|---|---|
12452 | Paging @hubert.reinterpretcast for help finding a good person to comment on the PPC questions. | |
llvm/include/llvm/ADT/APFloat.h | ||
450–454 | Is this part of the comment still accurate? |
clang/lib/AST/ExprConstant.cpp | ||
---|---|---|
12452 | @jcranmer-intel constexpr evaluation is quite machine-/target-independent. Clang evaluates it based on its internal representation of float variables. int ilogb uses Arg.getIEEE(), that returns Clang's internal float representation. Whichever float semantics is being used, minExponent and maxExponent are representable as APFloatBase::ExponentType, which is int32_t: /// A signed type to represent a floating point numbers unbiased exponent. typedef int32_t ExponentType; We already use int ilogb in some constexpr evaluation code: link, it is working correct because it is working on Clang's float representations. |
clang/lib/AST/ExprConstant.cpp | ||
---|---|---|
12452 |
APFloat::getIEEE(), if I'm following it correctly, only returns the details of the high double in ppc_fp128 floats, and I'm not sufficiently well-versed in the ppc_fp128 format to establish whether or not the low double comes into play here. glibc seems to think that the low double comes into play in at least one case: https://github.com/bminor/glibc/blob/30891f35fa7da832b66d80d0807610df361851f3/sysdeps/ieee754/ldbl-128ibm/e_ilogbl.c |
clang/lib/AST/ExprConstant.cpp | ||
---|---|---|
12452 | Thanks for the link to the glibc code! It helped me to understand the IEEE754 standard better. I did some research and it seems like AST supports a fixed set of float types, each working good with ilogb: half (__fp16, only for OpenCL), float16, float, double, long double, float128 It means that the constant evaluator doesn't deal with other float types including ppc_fp128. I checked the list of builtins - each builtin argument of float type also supports only common float types: |
clang/lib/AST/ExprConstant.cpp | ||
---|---|---|
12452 | Won't long double map to ppc_fp128 for some targets? |
clang/lib/AST/ExprConstant.cpp | ||
---|---|---|
12452 | Hi! It will map, but only after all the constant (constexpr) calculations are done (that is, after the AST parsing stage) - in the Codegen stage. The Clang's constant evaluator itself never deals with ppc_fp128 and doesn't care about the target. The Codegen can map the AST's long double to ppc_fp128 on some targets. I'm sorry if I'm not describing it clearly. It's important to me that everyone understands what the trick is =) |
clang/lib/AST/ExprConstant.cpp | ||
---|---|---|
12452 | That's kind-of terrible, but at least that means that what ilogb can do within the scope of this patch is very clear. |
clang/lib/AST/ExprConstant.cpp | ||
---|---|---|
12452 | Of course I should have used ilogbl... |
clang/lib/AST/ExprConstant.cpp | ||
---|---|---|
12452 |
How did you use ilogbl? I built local Clang with PowerPC support, this worked fine to me: static_assert(__builtin_ilogbl(-0x1p-64) == -64); static_assert(__builtin_ilogbl(-0x1p-1074L) == -1074); // smaller exponents (< -1074) don't work, too small number I would be glad for any example where constant ilogb would work incorrectly but runtime ilogb correctly. Unfortunately I can't make up such number because I navigate not so good enough in IEEE754 corner cases =(
I just checked with the debugger, the -target ppc64le parameter actually makes the constant evaluator use internal 128-bit format for long double. Yeah, this contradicts to some points what I wrote earlier... =( |
clang/test/Sema/constant-builtins-ilogb.cpp | ||
---|---|---|
53–63 | You can simplify at least the float, double, and especially long double tests using something like this: static_assert(__builtin_ilogbf(__FLT_MIN__) == __FLT_MIN_EXP__ + 1); static_assert(__builtin_ilogbf(__FLT_MAX__) == __FLT_MAX_EXP__ - 1); |
clang/lib/AST/ExprConstant.cpp | ||
---|---|---|
12452 |
By ilogbl({2, -0x1p-64}), I meant ilogbl(2.L + -0x1p-64). The answer I got at runtime is 0 (which is what I expected). It seems the ilogbl implementation at compile time does not return 0 though. Here, I try the largest number in the format that is smaller than 2: $ cat ilogb.cc #include <math.h> #include <stdio.h> constexpr int comptime = __builtin_ilogbl(2.L - __DBL_DENORM_MIN__); constinit volatile long double rtinput = 2.L - __DBL_DENORM_MIN__; int runtime; int main(void) { runtime = ilogbl(rtinput); printf("comptime(%d)\nruntime(%d)\n", comptime, runtime); } $ clang++ -target powerpc64le-unknown-linux-gnu -std=c++20 ilogb.cc && ./a.out comptime(1) runtime(0) | |
clang/test/Sema/constant-builtins-ilogb.cpp | ||
1 | There seems to be no C language test in the patch (although the builtin presumably is okay at least as part of arithmetic constant expressions). @aaron.ballman, what are your thoughts re: integer constant expression contexts? For example: struct C { int x : __builtin_ilogb(1. + 1.); }; | |
60 | This test also fails for PPC double-double (which does not increase the range of exponents). With C++, it is presumably possible for you to verify using predefined floating point constants with platform-specific values, e.g., __FLT_DENORM_MIN__, and checking the answer by dividing or multiplying the number by 2 (or more generally, the base) in a constexpr function repeatedly. |
clang/test/Sema/constant-builtins-ilogb.cpp | ||
---|---|---|
1 | WG14 adopted https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2713.htm for C2x to clarify that implementations are not allowed to extend what they consider to be an integer constant expression. The operand in this case is a function call expression, which is not one of the permissible things in an ICE, so the standard doesn't want us to make it one. I believe that Clang's response to that paper is to implement it to the letter rather than to the intent. e.g., issue a warning that a constant expression is being folded into an ICE but otherwise accept the code. We have too many situations already in which we fold a constant expression to an ICE and changing that behavior would be observable (and a performance regression). So I think it's fine for us to treat the builtin call as an ICE so long as we issue a (pedantic) warning about folding it. (FWIW, I don't think it qualifies as an arithmetic constant expression because that also doesn't allow function call expressions. But we can extend the definition of an arithmetic constant expression.) |
clang/test/Sema/constant-builtins-ilogb.cpp | ||
---|---|---|
1 | Thanks Aaron. I think a C language test for this patch may be a good way to explore the extension space (and any pedantic conformance diagnostics). Particular to the example above and ICEs is that the argument involves floating-point operations. Would the desired model be that the call is considered a literal for the purposes of the language requirements? It seems that built-ins that are accepted in constant expressions in C are a more general issue though (and I am not sure if, for this patch, we defer the issue for some general approach that would take care of the class of problems as a whole). |
clang/test/Sema/constant-builtins-ilogb.cpp | ||
---|---|---|
1 |
+1, especially because C2x has support for constexpr object definitions (which we've not implemented in Clang yet).
I think that model makes the most sense to me. If we can compute the correct answer at compile time, it seems a bit silly to defer until runtime just because the builtin looks like a function call expression.
Yeah, I think it is a more general issue with builtins in C (and how we handle constant expression evaluation in C). I don't think this patch will make the problem significantly *worse*, so I think I'd be fine not addressing the specific concern for this builtin in this patch. But it would probably make a lot of sense for us to fix up the builtin situation before converting a ton of math library functions to builtins since all of those are going to have the same class of concern. |
long double is ppc_fp128 on at least some PPC targets, and while I'm not entirely certain of what ilogb properly returns in the corner cases of the ppc_fp128, I'm not entirely confident that the implementation of APFloat is correct in those cases. I'd like someone with PPC background to comment in here.