Skip to content

NaN constants lose their sign bit and signaling bit in the generated C #443

Open

Description

@mborland

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.txt file in my .zip or .tar.gz upload. If I included a LICENSE.txt file, any tests created using the reproducing source code will be governed by the terms of the license I provided instead.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions