Repository navigation
NaN constants lose their sign bit and signaling bit in the generated C #443
Description
Issue Summary
The C-generating back end writes a NaN constant either as the
__builtin_nan*("...") call it came from, when the constant's backing
expression is exactly such a call, or otherwise as __builtin_nan(""). The
constant's sign bit and quiet bit are never looked at. So a negative NaN
(-__builtin_nan(""), -std::numeric_limits<T>::quiet_NaN()) comes out
positive, and a signaling NaN produced through a constexpr function (for
example std::numeric_limits<T>::signaling_NaN()) comes out quiet. The front
end's own value is correct: a static_assert on __builtin_signbit passes.
This happens for float, double and long double. In Boost, the
Boost.Charconv NaN formatting tests fail (github_issue_152,
github_issue_152_float128, to_chars_float): they expect -nan(ind) and
nan(snan) but get nan.
Reproducing Source Code
constexpr double make_snan() { return __builtin_nans(""); }
constexpr double neg_qnan = -__builtin_nan("");
constexpr double snan = make_snan();
/* The front end's values are correct ... */
static_assert(__builtin_signbit(neg_qnan), "");
int main() {
unsigned long long a, b;
__builtin_memcpy(&a, &neg_qnan, sizeof a);
__builtin_memcpy(&b, &snan, sizeof b);
/* ... but the generated C loses them. Expected: a == 0xfff8000000000000
(sign bit set) and b == 0x7ff4000000000000 (quiet bit clear). */
return (a == 0xfff8000000000000ull ? 0 : 1) |
(b == 0x7ff4000000000000ull ? 0 : 2);
}Command-line Options
EDG_USE_SYSTEM_HEADERS=1 eccp --c++17 nan_constants.cpp -o nan_constants && ./nan_constants
Type of Issue
[Back End] There is a problem with the generated C code.
Additional Details
Expected: exit status 0 (g++ 15.3.1: 0).
Actual (linux-gcc-release and linux-gcc-debug): exit status 3, so both
values are wrong (3 of 3 runs).
Generated C (cpfe --c++17 --gnu_version=150301 --gen_c_file_name=nan_constants.int.c nan_constants.cpp):
static const double neg_qnan = ((__builtin_nan("")));
static const double snan = ((__builtin_nan("")));Plain (non-constant) expressions are fine: return -__builtin_nan(""); is
emitted as return -((__builtin_nan("")));.
Through std::numeric_limits (libstdc++ 15 headers, -O0), bytes of each
value, most significant first, with signbit:
quiet_NaN() |
-quiet_NaN() |
signaling_NaN() |
-signaling_NaN() |
|
|---|---|---|---|---|
g++ float |
7fc00000 |
ffc00000 (sb=1) |
7fa00000 |
ffa00000 (sb=1) |
EDG float |
7fc00000 |
7fc00000 (sb=0) |
7fc00000 |
7fc00000 (sb=0) |
g++ double |
7ff8000000000000 |
fff8000000000000 (sb=1) |
7ff4000000000000 |
fff4000000000000 (sb=1) |
EDG double |
7ff8000000000000 |
7ff8000000000000 (sb=0) |
7ff8000000000000 |
7ff8000000000000 (sb=0) |
g++ long double |
7fffc000000000000000 |
ffffc000000000000000 (sb=1) |
7fffa000000000000000 |
ffffa000000000000000 (sb=1) |
EDG long double |
7fffc000000000000000 |
7fffc000000000000000 (sb=0) |
7fffc000000000000000 |
7fffc000000000000000 (sb=0) |
Cause: src/il_to_str.c:6095-6177. For a NaN in compilable output, the
code strips compiler-generated casts and follows the constant's backing
expression (6109-6126). If that is a GNU builtin call with a string-literal
argument it is reproduced (6127-6153), otherwise (__builtin_nan("") is
written (6163). A negated NaN's backing expression is the negation, not a builtin call, and a
NaN returned by a constexpr function has a call to that function as its
backing expression, so both fall through to __builtin_nan(""). A fix could
prefix - when the sign bit of the value is set and use __builtin_nans when
the quiet bit is clear, or reproduce the exact bit pattern. The same function
serves the C++-generating back end (BACK_END_IS_CP_GEN_BE), which I haven't
checked.
Boost: libs/charconv/test/github_issue_152.cpp:89,95,
github_issue_152_float128.cpp:64, to_chars_float.cpp:86,92,98, e.g.
test 'buffer6 == "-nan(snan)"' ('nan' == '-nan(snan)') failed.
Environment: edgcpp/dev-env Docker image (Fedora 43, GCC 15.3.1) on
x86-64, boost branch at c36e03a67c. EDG_USE_SYSTEM_HEADERS=1 makes eccp
use GCC 15's headers and pass --gnu_version=150301
(bases/docker/dev-env/gcc/edg_eccp_config).
Reproducing (Standard) Configuration(s)
linux-gcc-release
Reproducing (Non-standard) Configuration
No response
I acknowledge that:
- I have checked open issues and existing discussions before filing this report.
- The reproducing source code that I have provided does not depend on headers that I have not included with my submission.
- I have reduced the reproducing source code to the best of my ability.
- I have selected at least one standard configuration or uploaded a non-standard configuration dump.
- I understand code shared via this form is NOT confidential.
- I have permission to share the reproducing source code and grant permission for the project to use the source code (or derivatives of the source code) to create tests.
- I agree that any tests created using the reproducing source code (or deriatives of it) will be licensed under project license unless I included a
LICENSE.txtfile in my.zipor.tar.gzupload. If I included aLICENSE.txtfile, any tests created using the reproducing source code will be governed by the terms of the license I provided instead.
Activity