Skip to content

transpile: Implement __builtin_nans* and honor NaN payloads - #2049

Open
shashikanth-a wants to merge 3 commits into
immunant:masterfrom
shashikanth-a:transpile-builtin-nans
Open

shashikanth-a wants to merge 3 commits into
immunant:masterfrom
shashikanth-a:transpile-builtin-nans

Conversation

@shashikanth-a

Copy link
Copy Markdown

Fixes #1800.

__builtin_nans{,f,l} were unimplemented, __builtin_nan* ignored their payload argument, and the __float128 variants (__builtin_{nan,nans,inf,huge_val}f128) were unimplemented too.

All NaN builtins now translate to the bits clang's constant folding produces:

  • The payload is parsed like LLVM's StringRef::getAsInteger with an auto-detected radix ("", decimal, leading-0 octal, 0x, 0b, 0o), and the NaN is built like APFloat::makeNaN. A signaling NaN clears the quiet bit, and sets the next bit down if the payload is then empty.
  • Where clang doesn't fold (a non-literal or malformed payload), it emits a libm call. The quiet variants become the default NaN with a warning, keeping the argument's side effects. libm has no nans, so an unfoldable signaling NaN is an error.
  • Edition 2024 emits f64::from_bits(..). Older editions emit transmute, since from_bits isn't const on their pinned toolchain (1.70) and these can appear in static initializers. The default quiet NaN still becomes f32::NAN/f64::NAN.
  • long double and __float128 use transmute::<u128, f128::f128>, as the f128 crate has no public bit-pattern constructor.

One behaviour change: __builtin_nanl("") used to be f128::f128::NAN, whose payload (all ones) differs from clang's. It now gets clang's bits.

The second commit adds the __float128 builtins separately, in case you'd rather review or take it on its own.

Testing

  • New snapshot tests nan.c and f128_builtins.c (translated for x86_64-unknown-linux-gnu), plus unit tests for payload parsing and NaN bits.
  • Checked against clang outside the test suite: for 25 payload strings with each of nan, nans, nanf and nansf, I compiled and ran both the C and the translated Rust (editions 2021 and 2024) and compared the bits. The f128 variants were compared against clang's x86_64 constants, including payloads wider than 128 bits.
  • Ran the f128 output on x86_64 Linux against the real f128 crate, and compared it with GCC.

Limitations

  • long double is translated as binary128, so on x86 the bits differ from x87's 80-bit format. This is the existing mapping.
  • A payload only known at runtime gets the default NaN.
  • _Float16 isn't supported by the transpiler at all, so __builtin_nanf16 isn't handled here.


/// The IEEE format of the result of a `__builtin_nan*` builtin.
#[derive(Copy, Clone)]
enum NanFloat {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems superfluous to the FloatingKind enum?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. Removed NanFloat and moved the helpers onto CFloatingKind in 932c3bb.

Comment thread c2rust-transpile/src/c_ast/mod.rs Outdated
// Both are translated to `f128::f128` (binary128).
LongDouble | Float128 => Some((112, 15)),

Half | BFloat16 => None,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really that important, but since the values needed for 16-bit floats are known too, they might as well be added? Then it doesn't have to return Option and neither does nan_bits.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 8226c99. layout() and nan_bits() no longer return Option, and 16-bit floats are rejected once at the start of convert_builtin_nan. The new test values match clang (Half) and LLVM's APFloat (BFloat16), since clang has no bf16 NaN builtins

@Rua

Rua commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

Not a maintainer myself, but the maintainers of c2rust seem to generally prefer rebasing and editing existing commits when changes are requested in a review, rather than adding new commits.

@shashikanth-a
shashikanth-a force-pushed the transpile-builtin-nans branch from 8226c99 to a54d797 Compare November 3, 2026 19:44
@shashikanth-a

Copy link
Copy Markdown
Author

Thanks! I squashed the fix into the CFloatingKind commit and force-pushed.

`__builtin_nans`, `__builtin_nansf` and `__builtin_nansl` were
unimplemented, and `__builtin_nan*` ignored their payload argument.

Translate both to the bits clang's constant folding produces: the
payload string is parsed like LLVM's `StringRef::getAsInteger` with an
auto-detected radix, and the NaN is built like `APFloat::makeNaN`.
`long double` uses binary128, matching its translation to `f128::f128`.

Where clang doesn't fold (a non-literal or malformed payload), it emits
a libm call instead. For the quiet variants, emit the default NaN with a
warning, keeping the argument's side effects. libm has no `nans`, so an
unfoldable signaling NaN is an error.

Edition 2024 uses `f32::from_bits`/`f64::from_bits`. Older editions use
`transmute`, as `from_bits` isn't `const` on their pinned toolchain and
these can appear in `static` initializers.

Fixes immunant#1800.
`__builtin_nanf128`, `__builtin_nansf128`, `__builtin_inff128` and
`__builtin_huge_valf128` were unimplemented. `__float128` is translated
to `f128::f128` like `long double`, so handle them like the `l` variants.
@shashikanth-a
shashikanth-a force-pushed the transpile-builtin-nans branch from a54d797 to 8f1aff4 Compare November 8, 2026 21:02
@shashikanth-a

Copy link
Copy Markdown
Author

Rebased onto latest master to resolve conflicts with the convert_builtin changes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

C2Rust fails to translate __builtin_nans with “Unimplemented builtin __builtin_nans”

2 participants