From d73a67d2e9e258d3b4b8fdf36c3258161a2284bc Mon Sep 17 00:00:00 2001 From: shashikanth Date: Mon, 26 Oct 2026 18:33:12 +0530 Subject: [PATCH 1/3] transpile: Implement `__builtin_nans*` and honor NaN payloads `__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 #1800. --- c2rust-transpile/src/translator/builtins.rs | 235 +++++++++++++++++- c2rust-transpile/tests/snapshots.rs | 5 + c2rust-transpile/tests/snapshots/nan.c | 26 ++ ...apshots__transpile@nan.c.2021.clang15.snap | 41 +++ ...apshots__transpile@nan.c.2024.clang15.snap | 40 +++ 5 files changed, 336 insertions(+), 11 deletions(-) create mode 100644 c2rust-transpile/tests/snapshots/nan.c create mode 100644 c2rust-transpile/tests/snapshots/snapshots__transpile@nan.c.2021.clang15.snap create mode 100644 c2rust-transpile/tests/snapshots/snapshots__transpile@nan.c.2024.clang15.snap diff --git a/c2rust-transpile/src/translator/builtins.rs b/c2rust-transpile/src/translator/builtins.rs index 84f40e6a4c..54af17ff8d 100644 --- a/c2rust-transpile/src/translator/builtins.rs +++ b/c2rust-transpile/src/translator/builtins.rs @@ -24,6 +24,70 @@ enum LibcFnArgType { Int, } +/// The IEEE format of the result of a `__builtin_nan*` builtin. +#[derive(Copy, Clone)] +enum NanFloat { + /// `float` + F32, + /// `double` + F64, + /// `long double`, which we translate to `f128::f128` (IEEE binary128) + F128, +} + +impl NanFloat { + /// The number of explicitly stored significand bits and of exponent bits. + fn layout(self) -> (u32, u32) { + match self { + NanFloat::F32 => (23, 8), + NanFloat::F64 => (52, 11), + NanFloat::F128 => (112, 15), + } + } + + /// The bits of the NaN with this payload, as computed by LLVM's `APFloat::makeNaN`. + fn nan_bits(self, payload: u128, signaling: bool) -> u128 { + let (significand_bits, exponent_bits) = self.layout(); + let quiet_bit = 1 << (significand_bits - 1); + let mut significand = payload & ((1 << significand_bits) - 1); + if signaling { + significand &= !quiet_bit; + // An all-zero significand would be an infinity, so set the next bit down. + if significand == 0 { + significand = quiet_bit >> 1; + } + } else { + significand |= quiet_bit; + } + let exponent = ((1 << exponent_bits) - 1) << significand_bits; + exponent | significand + } +} + +/// Parse a `__builtin_nan*` payload the way clang does: as an integer with an +/// auto-detected radix (LLVM's `StringRef::getAsInteger(0, ..)`), with the empty +/// string meaning 0. Only the low 128 bits are kept, which is all a payload can use. +/// Returns `None` where clang would not fold the builtin to a constant. +fn parse_nan_payload(s: &[u8]) -> Option { + if s.is_empty() { + return Some(0); + } + let (radix, digits) = match s { + [b'0', b'x' | b'X', rest @ ..] => (16, rest), + [b'0', b'b' | b'B', rest @ ..] => (2, rest), + [b'0', b'o', rest @ ..] => (8, rest), + [b'0', d, ..] if d.is_ascii_digit() => (8, &s[1..]), + _ => (10, s), + }; + if digits.is_empty() { + return None; + } + digits.iter().try_fold(0u128, |acc, &c| { + let digit = char::from(c).to_digit(radix)?; + Some(acc.wrapping_mul(radix.into()).wrapping_add(digit.into())) + }) +} + impl<'c> Translation<'c> { /// Convert a call to a rotate builtin. fn convert_builtin_rotate( @@ -42,6 +106,104 @@ impl<'c> Translation<'c> { })) } + /// Convert a call to `__builtin_nan{,f,l}` or `__builtin_nans{,f,l}`. + /// + /// Clang folds these to a constant when the payload is a string literal it can parse, + /// and otherwise emits a call to the libm function of the same name. We fold the same + /// cases to the same bits, in the format we translate the type to (binary128 for + /// `long double`, which isn't what e.g. x87 uses). Otherwise, the quiet variants fall + /// back to the default NaN: that is what libm returns for a malformed payload, but not + /// necessarily for one only known at runtime. There is no libm `nans`, so an unfoldable + /// signaling NaN is an error. + fn convert_builtin_nan( + &self, + ctx: ExprContext, + src_loc: &Option, + builtin_name: &str, + args: &[CExprId], + float: NanFloat, + signaling: bool, + ) -> TranslationResult>> { + let arg = self.ast_context.unwrap_cast_expr(args[0]); + let payload = match self.ast_context[arg].kind { + CExprKind::Literal(_, CLiteral::String(ref bytes, 1)) => parse_nan_payload(bytes), + _ => None, + }; + if let Some(payload) = payload { + return Ok(self.convert_nan_bits(float, float.nan_bits(payload, signaling))); + } + + let msg = "payload is not a string literal clang can fold"; + if signaling { + return Err(format_translation_err!( + self.ast_context.display_loc(src_loc), + "{builtin_name} {msg}", + )); + } + if let Some(loc) = self.ast_context.display_loc(src_loc) { + warn!("{loc}: {builtin_name} {msg}; emitting the default NaN"); + } else { + warn!("{builtin_name} {msg}; emitting the default NaN"); + } + // Keep any side effects of the payload expression. + let payload = self.convert_expr(ctx.unused(), args[0], None)?; + Ok(payload.and_then(|_| self.convert_nan_bits(float, float.nan_bits(0, false)))) + } + + /// Build a NaN of the given format from its bits. + fn convert_nan_bits(&self, float: NanFloat, bits: u128) -> WithStmts> { + match float { + // Rust doesn't guarantee the bits of `NAN`, but in practice it is the default + // quiet NaN, and it is more readable for the common case (e.g. C's `NAN`). + NanFloat::F32 if bits == 0x7fc0_0000 => { + WithStmts::new_val(mk().abs_path_expr(vec!["core", "f32", "NAN"])) + } + NanFloat::F64 if bits == 0x7ff8_0000_0000_0000 => { + WithStmts::new_val(mk().abs_path_expr(vec!["core", "f64", "NAN"])) + } + NanFloat::F32 => self.convert_float_from_bits("f32", "u32", bits), + NanFloat::F64 => self.convert_float_from_bits("f64", "u64", bits), + NanFloat::F128 => { + self.use_crate(ExternCrate::F128); + + // `f128::f128` has no public bit-pattern constructor (and its `NAN` has a + // different payload than clang's), but it is a `#[repr(C)]` wrapper around + // its native-endian bytes. + let bits = mk().lit_expr(mk().int_unsuffixed_lit(format!("{bits:#x}"))); + WithStmts::new_val(transmute_expr( + mk().path_ty(vec!["u128"]), + mk().abs_path_ty(vec!["f128", "f128"]), + bits, + )) + .set_unsafe() + } + } + } + + /// Build a float from its bit pattern, usable in `static` initializers. + /// `from_bits` is only `const` since Rust 1.83, so older editions, whose pinned toolchain + /// predates that, use a `transmute`. + fn convert_float_from_bits( + &self, + float_ty: &'static str, + bits_ty: &'static str, + bits: u128, + ) -> WithStmts> { + let bits = mk().lit_expr(mk().int_unsuffixed_lit(format!("{bits:#x}"))); + if self.tcfg.edition < Edition2024 { + WithStmts::new_val(transmute_expr( + mk().path_ty(vec![bits_ty]), + mk().path_ty(vec![float_ty]), + bits, + )) + .set_unsafe() + } else { + WithStmts::new_val( + mk().call_expr(mk().path_expr(vec![float_ty, "from_bits"]), vec![bits]), + ) + } + } + /// Convert a call to a builtin function to a Rust expression pub fn convert_builtin( &self, @@ -96,18 +258,23 @@ impl<'c> Translation<'c> { mk().abs_path_expr(vec!["f128", "f128", "INFINITY"]), )) } - "__builtin_nanf" => Ok(WithStmts::new_val( - mk().abs_path_expr(vec!["core", "f32", "NAN"]), - )), - "__builtin_nan" => Ok(WithStmts::new_val( - mk().abs_path_expr(vec!["core", "f64", "NAN"]), - )), + "__builtin_nanf" => { + self.convert_builtin_nan(ctx, src_loc, builtin_name, args, NanFloat::F32, false) + } + "__builtin_nan" => { + self.convert_builtin_nan(ctx, src_loc, builtin_name, args, NanFloat::F64, false) + } "__builtin_nanl" => { - self.use_crate(ExternCrate::F128); - - Ok(WithStmts::new_val( - mk().abs_path_expr(vec!["f128", "f128", "NAN"]), - )) + self.convert_builtin_nan(ctx, src_loc, builtin_name, args, NanFloat::F128, false) + } + "__builtin_nansf" => { + self.convert_builtin_nan(ctx, src_loc, builtin_name, args, NanFloat::F32, true) + } + "__builtin_nans" => { + self.convert_builtin_nan(ctx, src_loc, builtin_name, args, NanFloat::F64, true) + } + "__builtin_nansl" => { + self.convert_builtin_nan(ctx, src_loc, builtin_name, args, NanFloat::F128, true) } "__builtin_signbit" | "__builtin_signbitf" | "__builtin_signbitl" => { self.import_num_traits(args[0])?; @@ -839,3 +1006,49 @@ impl<'c> Translation<'c> { }) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn nan_payloads_parse_like_clang() { + assert_eq!(parse_nan_payload(b""), Some(0)); + assert_eq!(parse_nan_payload(b"0"), Some(0)); + assert_eq!(parse_nan_payload(b"12"), Some(12)); + assert_eq!(parse_nan_payload(b"010"), Some(8)); + assert_eq!(parse_nan_payload(b"0x1F"), Some(31)); + assert_eq!(parse_nan_payload(b"0X1f"), Some(31)); + assert_eq!(parse_nan_payload(b"0b101"), Some(5)); + assert_eq!(parse_nan_payload(b"0o17"), Some(15)); + assert_eq!(parse_nan_payload(b"0B11"), Some(3)); + // Only the low 128 bits are kept. + assert_eq!( + parse_nan_payload(b"0x1ffffffffffffffffffffffffffffffff"), + Some(u128::MAX) + ); + assert_eq!(parse_nan_payload(b"0x"), None); + assert_eq!(parse_nan_payload(b"08"), None); + assert_eq!(parse_nan_payload(b"0O17"), None); + assert_eq!(parse_nan_payload(b"-1"), None); + assert_eq!(parse_nan_payload(b" 1"), None); + assert_eq!(parse_nan_payload(b"xyz"), None); + assert_eq!(parse_nan_payload(b"1\x002"), None); + } + + #[test] + fn nan_bits_match_clang() { + use NanFloat::*; + assert_eq!(F32.nan_bits(0, false), 0x7fc00000); + assert_eq!(F64.nan_bits(0, false), 0x7ff8000000000000); + assert_eq!(F64.nan_bits(1, false), 0x7ff8000000000001); + assert_eq!(F32.nan_bits(0, true), 0x7fa00000); + assert_eq!(F32.nan_bits(7, true), 0x7f800007); + assert_eq!(F64.nan_bits(0, true), 0x7ff4000000000000); + assert_eq!(F64.nan_bits(1, true), 0x7ff0000000000001); + // The quiet bit is cleared, leaving an all-zero significand. + assert_eq!(F64.nan_bits(0x8000000000000, true), 0x7ff4000000000000); + assert_eq!(F128.nan_bits(0, false), 0x7fff8000000000000000000000000000); + assert_eq!(F128.nan_bits(0, true), 0x7fff4000000000000000000000000000); + } +} diff --git a/c2rust-transpile/tests/snapshots.rs b/c2rust-transpile/tests/snapshots.rs index d9d5736a4d..291d60e88d 100644 --- a/c2rust-transpile/tests/snapshots.rs +++ b/c2rust-transpile/tests/snapshots.rs @@ -448,6 +448,11 @@ fn test_main_fn() { transpile("main_fn.c").run(); } +#[test] +fn test_nan() { + transpile("nan.c").expect_unresolved_import("f128").run(); +} + #[test] fn test_opaque_pointer_casts() { transpile("opaque_pointer_casts.c").run(); diff --git a/c2rust-transpile/tests/snapshots/nan.c b/c2rust-transpile/tests/snapshots/nan.c new file mode 100644 index 0000000000..eab3250b73 --- /dev/null +++ b/c2rust-transpile/tests/snapshots/nan.c @@ -0,0 +1,26 @@ +float static_nansf = __builtin_nansf(""); +double static_nans = __builtin_nans(""); + +const char *next_payload(void); + +void nan_payloads(void) { + float qf = __builtin_nanf(""); + double qd = __builtin_nan(""); + long double ql = __builtin_nanl(""); + float sf = __builtin_nansf(""); + double sd = __builtin_nans(""); + long double sl = __builtin_nansl(""); + + // Payloads are integers in any radix clang accepts. + double q1 = __builtin_nan("1"); + double s1 = __builtin_nans("0x1f"); + float s2 = __builtin_nansf("010"); + // Clearing the quiet bit leaves an empty payload, so the default is used. + double s3 = __builtin_nans("0x8000000000000"); + // How macOS defines `NAN`. + float q2 = __builtin_nanf("0x7fc00000"); + + // Clang doesn't fold these; libm returns the default NaN for a malformed payload. + double q3 = __builtin_nan("xyz"); + double q4 = __builtin_nan(next_payload()); +} diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@nan.c.2021.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@nan.c.2021.clang15.snap new file mode 100644 index 0000000000..b02c73bf3e --- /dev/null +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@nan.c.2021.clang15.snap @@ -0,0 +1,41 @@ +--- +source: c2rust-transpile/tests/snapshots.rs +expression: cat tests/snapshots/nan.2021.clang15.rs +--- +#![allow( + clippy::missing_safety_doc, + dead_code, + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unused_assignments, + unused_mut +)] +extern "C" { + fn next_payload() -> *const ::core::ffi::c_char; +} +#[no_mangle] +pub static mut static_nansf: ::core::ffi::c_float = + unsafe { ::core::mem::transmute::(0x7fa00000) }; +#[no_mangle] +pub static mut static_nans: ::core::ffi::c_double = + unsafe { ::core::mem::transmute::(0x7ff4000000000000) }; +#[no_mangle] +pub unsafe extern "C" fn nan_payloads() { + let mut qf: ::core::ffi::c_float = ::core::f32::NAN; + let mut qd: ::core::ffi::c_double = ::core::f64::NAN; + let mut ql: ::f128::f128 = + ::core::mem::transmute::(0x7fff8000000000000000000000000000); + let mut sf: ::core::ffi::c_float = ::core::mem::transmute::(0x7fa00000); + let mut sd: ::core::ffi::c_double = ::core::mem::transmute::(0x7ff4000000000000); + let mut sl: ::f128::f128 = + ::core::mem::transmute::(0x7fff4000000000000000000000000000); + let mut q1: ::core::ffi::c_double = ::core::mem::transmute::(0x7ff8000000000001); + let mut s1: ::core::ffi::c_double = ::core::mem::transmute::(0x7ff000000000001f); + let mut s2: ::core::ffi::c_float = ::core::mem::transmute::(0x7f800008); + let mut s3: ::core::ffi::c_double = ::core::mem::transmute::(0x7ff4000000000000); + let mut q2: ::core::ffi::c_float = ::core::f32::NAN; + let mut q3: ::core::ffi::c_double = ::core::f64::NAN; + next_payload(); + let mut q4: ::core::ffi::c_double = ::core::f64::NAN; +} diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@nan.c.2024.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@nan.c.2024.clang15.snap new file mode 100644 index 0000000000..95a68edbce --- /dev/null +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@nan.c.2024.clang15.snap @@ -0,0 +1,40 @@ +--- +source: c2rust-transpile/tests/snapshots.rs +expression: cat tests/snapshots/nan.2024.clang15.rs +--- +#![allow( + clippy::missing_safety_doc, + dead_code, + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unsafe_op_in_unsafe_fn, + unused_assignments, + unused_mut +)] +unsafe extern "C" { + unsafe fn next_payload() -> *const ::core::ffi::c_char; +} +#[unsafe(no_mangle)] +pub static mut static_nansf: ::core::ffi::c_float = f32::from_bits(0x7fa00000); +#[unsafe(no_mangle)] +pub static mut static_nans: ::core::ffi::c_double = f64::from_bits(0x7ff4000000000000); +#[unsafe(no_mangle)] +pub unsafe extern "C" fn nan_payloads() { + let mut qf: ::core::ffi::c_float = ::core::f32::NAN; + let mut qd: ::core::ffi::c_double = ::core::f64::NAN; + let mut ql: ::f128::f128 = + ::core::mem::transmute::(0x7fff8000000000000000000000000000); + let mut sf: ::core::ffi::c_float = f32::from_bits(0x7fa00000); + let mut sd: ::core::ffi::c_double = f64::from_bits(0x7ff4000000000000); + let mut sl: ::f128::f128 = + ::core::mem::transmute::(0x7fff4000000000000000000000000000); + let mut q1: ::core::ffi::c_double = f64::from_bits(0x7ff8000000000001); + let mut s1: ::core::ffi::c_double = f64::from_bits(0x7ff000000000001f); + let mut s2: ::core::ffi::c_float = f32::from_bits(0x7f800008); + let mut s3: ::core::ffi::c_double = f64::from_bits(0x7ff4000000000000); + let mut q2: ::core::ffi::c_float = ::core::f32::NAN; + let mut q3: ::core::ffi::c_double = ::core::f64::NAN; + next_payload(); + let mut q4: ::core::ffi::c_double = ::core::f64::NAN; +} From a681ae4cf5ea339a06c25e020a57343079d4a1a6 Mon Sep 17 00:00:00 2001 From: shashikanth Date: Mon, 26 Oct 2026 18:33:21 +0530 Subject: [PATCH 2/3] transpile: Add `__float128` NaN and infinity builtins `__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. --- c2rust-transpile/src/translator/builtins.rs | 12 +++--- c2rust-transpile/tests/snapshots.rs | 8 ++++ .../tests/snapshots/f128_builtins.c | 12 ++++++ ...ranspile@f128_builtins.c.2021.clang15.snap | 37 ++++++++++++++++++ ...ranspile@f128_builtins.c.2024.clang15.snap | 38 +++++++++++++++++++ 5 files changed, 101 insertions(+), 6 deletions(-) create mode 100644 c2rust-transpile/tests/snapshots/f128_builtins.c create mode 100644 c2rust-transpile/tests/snapshots/snapshots__transpile@f128_builtins.c.2021.clang15.snap create mode 100644 c2rust-transpile/tests/snapshots/snapshots__transpile@f128_builtins.c.2024.clang15.snap diff --git a/c2rust-transpile/src/translator/builtins.rs b/c2rust-transpile/src/translator/builtins.rs index 54af17ff8d..34599b5927 100644 --- a/c2rust-transpile/src/translator/builtins.rs +++ b/c2rust-transpile/src/translator/builtins.rs @@ -31,7 +31,7 @@ enum NanFloat { F32, /// `double` F64, - /// `long double`, which we translate to `f128::f128` (IEEE binary128) + /// `long double` and `__float128`, which we translate to `f128::f128` (IEEE binary128) F128, } @@ -106,7 +106,7 @@ impl<'c> Translation<'c> { })) } - /// Convert a call to `__builtin_nan{,f,l}` or `__builtin_nans{,f,l}`. + /// Convert a call to `__builtin_nan{,f,l,f128}` or `__builtin_nans{,f,l,f128}`. /// /// Clang folds these to a constant when the payload is a string literal it can parse, /// and otherwise emits a call to the libm function of the same name. We fold the same @@ -238,7 +238,7 @@ impl<'c> Translation<'c> { "__builtin_huge_val" => Ok(WithStmts::new_val( mk().abs_path_expr(vec!["core", "f64", "INFINITY"]), )), - "__builtin_huge_vall" => { + "__builtin_huge_vall" | "__builtin_huge_valf128" => { self.use_crate(ExternCrate::F128); Ok(WithStmts::new_val( @@ -251,7 +251,7 @@ impl<'c> Translation<'c> { "__builtin_inf" => Ok(WithStmts::new_val( mk().abs_path_expr(vec!["core", "f64", "INFINITY"]), )), - "__builtin_infl" => { + "__builtin_infl" | "__builtin_inff128" => { self.use_crate(ExternCrate::F128); Ok(WithStmts::new_val( @@ -264,7 +264,7 @@ impl<'c> Translation<'c> { "__builtin_nan" => { self.convert_builtin_nan(ctx, src_loc, builtin_name, args, NanFloat::F64, false) } - "__builtin_nanl" => { + "__builtin_nanl" | "__builtin_nanf128" => { self.convert_builtin_nan(ctx, src_loc, builtin_name, args, NanFloat::F128, false) } "__builtin_nansf" => { @@ -273,7 +273,7 @@ impl<'c> Translation<'c> { "__builtin_nans" => { self.convert_builtin_nan(ctx, src_loc, builtin_name, args, NanFloat::F64, true) } - "__builtin_nansl" => { + "__builtin_nansl" | "__builtin_nansf128" => { self.convert_builtin_nan(ctx, src_loc, builtin_name, args, NanFloat::F128, true) } "__builtin_signbit" | "__builtin_signbitf" | "__builtin_signbitl" => { diff --git a/c2rust-transpile/tests/snapshots.rs b/c2rust-transpile/tests/snapshots.rs index 291d60e88d..545ab9a2e6 100644 --- a/c2rust-transpile/tests/snapshots.rs +++ b/c2rust-transpile/tests/snapshots.rs @@ -382,6 +382,14 @@ fn test_exprs() { transpile("exprs.c").run(); } +#[test] +fn test_f128_builtins() { + transpile("f128_builtins.c") + .extra_clang_args(&["-target", "x86_64-unknown-linux-gnu"]) + .expect_unresolved_import("f128") + .run(); +} + #[test] fn test_factorial() { transpile("factorial.c").run(); diff --git a/c2rust-transpile/tests/snapshots/f128_builtins.c b/c2rust-transpile/tests/snapshots/f128_builtins.c new file mode 100644 index 0000000000..607633ca90 --- /dev/null +++ b/c2rust-transpile/tests/snapshots/f128_builtins.c @@ -0,0 +1,12 @@ +// `__float128` is only available on some targets, such as x86_64. +__float128 static_nansf128 = __builtin_nansf128(""); + +void f128_builtins(void) { + __float128 inf = __builtin_inff128(); + __float128 huge = __builtin_huge_valf128(); + + __float128 q = __builtin_nanf128(""); + __float128 s = __builtin_nansf128(""); + __float128 q1 = __builtin_nanf128("1"); + __float128 s1 = __builtin_nansf128("0x1f"); +} diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@f128_builtins.c.2021.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@f128_builtins.c.2021.clang15.snap new file mode 100644 index 0000000000..62db67f1e6 --- /dev/null +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@f128_builtins.c.2021.clang15.snap @@ -0,0 +1,37 @@ +--- +source: c2rust-transpile/tests/snapshots.rs +expression: cat tests/snapshots/f128_builtins.2021.clang15.rs +--- +#![allow( + clippy::missing_safety_doc, + dead_code, + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unused_assignments, + unused_mut +)] +#[no_mangle] +pub static mut static_nansf128: ::f128::f128 = ::f128::f128::ZERO; +#[no_mangle] +pub unsafe extern "C" fn f128_builtins() { + let mut inf: ::f128::f128 = ::f128::f128::INFINITY; + let mut huge: ::f128::f128 = ::f128::f128::INFINITY; + let mut q: ::f128::f128 = + ::core::mem::transmute::(0x7fff8000000000000000000000000000); + let mut s: ::f128::f128 = + ::core::mem::transmute::(0x7fff4000000000000000000000000000); + let mut q1: ::f128::f128 = + ::core::mem::transmute::(0x7fff8000000000000000000000000001); + let mut s1: ::f128::f128 = + ::core::mem::transmute::(0x7fff000000000000000000000000001f); +} +unsafe extern "C" fn c2rust_run_static_initializers() { + static_nansf128 = + ::core::mem::transmute::(0x7fff4000000000000000000000000000); +} +#[used] +#[cfg_attr(target_os = "linux", link_section = ".init_array")] +#[cfg_attr(target_os = "windows", link_section = ".CRT$XIB")] +#[cfg_attr(target_os = "macos", link_section = "__DATA,__mod_init_func")] +static INIT_ARRAY: [unsafe extern "C" fn(); 1] = [c2rust_run_static_initializers]; diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@f128_builtins.c.2024.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@f128_builtins.c.2024.clang15.snap new file mode 100644 index 0000000000..768033cfe4 --- /dev/null +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@f128_builtins.c.2024.clang15.snap @@ -0,0 +1,38 @@ +--- +source: c2rust-transpile/tests/snapshots.rs +expression: cat tests/snapshots/f128_builtins.2024.clang15.rs +--- +#![allow( + clippy::missing_safety_doc, + dead_code, + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unsafe_op_in_unsafe_fn, + unused_assignments, + unused_mut +)] +#[unsafe(no_mangle)] +pub static mut static_nansf128: ::f128::f128 = ::f128::f128::ZERO; +#[unsafe(no_mangle)] +pub unsafe extern "C" fn f128_builtins() { + let mut inf: ::f128::f128 = ::f128::f128::INFINITY; + let mut huge: ::f128::f128 = ::f128::f128::INFINITY; + let mut q: ::f128::f128 = + ::core::mem::transmute::(0x7fff8000000000000000000000000000); + let mut s: ::f128::f128 = + ::core::mem::transmute::(0x7fff4000000000000000000000000000); + let mut q1: ::f128::f128 = + ::core::mem::transmute::(0x7fff8000000000000000000000000001); + let mut s1: ::f128::f128 = + ::core::mem::transmute::(0x7fff000000000000000000000000001f); +} +unsafe extern "C" fn c2rust_run_static_initializers() { + static_nansf128 = + ::core::mem::transmute::(0x7fff4000000000000000000000000000); +} +#[used] +#[cfg_attr(target_os = "linux", unsafe(link_section = ".init_array"))] +#[cfg_attr(target_os = "windows", unsafe(link_section = ".CRT$XIB"))] +#[cfg_attr(target_os = "macos", unsafe(link_section = "__DATA,__mod_init_func"))] +static INIT_ARRAY: [unsafe extern "C" fn(); 1] = [c2rust_run_static_initializers]; From 932c3bbd31086e983e5241d0bc23498ded9085b5 Mon Sep 17 00:00:00 2001 From: shashikanth Date: Mon, 2 Nov 2026 10:22:21 +0530 Subject: [PATCH 3/3] transpile: use CFloatingKind instead of NanFloat --- c2rust-transpile/src/c_ast/mod.rs | 74 ++++++++ c2rust-transpile/src/translator/builtins.rs | 185 +++++++++++--------- 2 files changed, 173 insertions(+), 86 deletions(-) diff --git a/c2rust-transpile/src/c_ast/mod.rs b/c2rust-transpile/src/c_ast/mod.rs index 0cbfdca7bd..77991500e2 100644 --- a/c2rust-transpile/src/c_ast/mod.rs +++ b/c2rust-transpile/src/c_ast/mod.rs @@ -3447,6 +3447,43 @@ impl CFloatingKind { BFloat16 => todo!("bf16 range"), } } + + /// The number of explicitly stored significand bits and of exponent bits + /// of the format this floating-point type is translated to. + /// + /// Returns [`None`] for the 16-bit types, whose NaNs aren't supported. + pub(crate) fn layout(self) -> Option<(u32, u32)> { + use CFloatingKind::*; + match self { + Float => Some((23, 8)), + Double => Some((52, 11)), + + // Both are translated to `f128::f128` (binary128). + LongDouble | Float128 => Some((112, 15)), + + Half | BFloat16 => None, + } + } + + /// The bits of the NaN with this payload, as computed by LLVM's `APFloat::makeNaN`. + /// + /// Returns [`None`] if [`Self::layout`] does. + pub(crate) fn nan_bits(self, payload: u128, signaling: bool) -> Option { + let (significand_bits, exponent_bits) = self.layout()?; + let quiet_bit = 1 << (significand_bits - 1); + let mut significand = payload & ((1 << significand_bits) - 1); + if signaling { + significand &= !quiet_bit; + // An all-zero significand would be an infinity, so set the next bit down. + if significand == 0 { + significand = quiet_bit >> 1; + } + } else { + significand |= quiet_bit; + } + let exponent = ((1 << exponent_bits) - 1) << significand_bits; + Some(exponent | significand) + } } impl Display for CFloatingKind { @@ -3767,4 +3804,41 @@ c = {c} // This should not panic. locs.sort_unstable_by_key(|&loc| ctx.cmp_loc_include(loc)); } + + #[test] + fn nan_bits_match_clang() { + use CFloatingKind::*; + assert_eq!(Float.nan_bits(0, false), Some(0x7fc00000)); + assert_eq!(Double.nan_bits(0, false), Some(0x7ff8000000000000)); + assert_eq!(Double.nan_bits(1, false), Some(0x7ff8000000000001)); + assert_eq!(Float.nan_bits(0, true), Some(0x7fa00000)); + assert_eq!(Float.nan_bits(7, true), Some(0x7f800007)); + assert_eq!(Double.nan_bits(0, true), Some(0x7ff4000000000000)); + assert_eq!(Double.nan_bits(1, true), Some(0x7ff0000000000001)); + // The quiet bit is cleared, leaving an all-zero significand. + assert_eq!( + Double.nan_bits(0x8000000000000, true), + Some(0x7ff4000000000000) + ); + for binary128 in [LongDouble, Float128] { + assert_eq!( + binary128.nan_bits(0, false), + Some(0x7fff8000000000000000000000000000) + ); + assert_eq!( + binary128.nan_bits(0, true), + Some(0x7fff4000000000000000000000000000) + ); + } + } + + #[test] + fn nan_bits_unsupported_for_16_bit_floats() { + use CFloatingKind::*; + for float16 in [Half, BFloat16] { + assert_eq!(float16.layout(), None); + assert_eq!(float16.nan_bits(0, false), None); + assert_eq!(float16.nan_bits(0, true), None); + } + } } diff --git a/c2rust-transpile/src/translator/builtins.rs b/c2rust-transpile/src/translator/builtins.rs index 34599b5927..c1413b1b58 100644 --- a/c2rust-transpile/src/translator/builtins.rs +++ b/c2rust-transpile/src/translator/builtins.rs @@ -24,46 +24,6 @@ enum LibcFnArgType { Int, } -/// The IEEE format of the result of a `__builtin_nan*` builtin. -#[derive(Copy, Clone)] -enum NanFloat { - /// `float` - F32, - /// `double` - F64, - /// `long double` and `__float128`, which we translate to `f128::f128` (IEEE binary128) - F128, -} - -impl NanFloat { - /// The number of explicitly stored significand bits and of exponent bits. - fn layout(self) -> (u32, u32) { - match self { - NanFloat::F32 => (23, 8), - NanFloat::F64 => (52, 11), - NanFloat::F128 => (112, 15), - } - } - - /// The bits of the NaN with this payload, as computed by LLVM's `APFloat::makeNaN`. - fn nan_bits(self, payload: u128, signaling: bool) -> u128 { - let (significand_bits, exponent_bits) = self.layout(); - let quiet_bit = 1 << (significand_bits - 1); - let mut significand = payload & ((1 << significand_bits) - 1); - if signaling { - significand &= !quiet_bit; - // An all-zero significand would be an infinity, so set the next bit down. - if significand == 0 { - significand = quiet_bit >> 1; - } - } else { - significand |= quiet_bit; - } - let exponent = ((1 << exponent_bits) - 1) << significand_bits; - exponent | significand - } -} - /// Parse a `__builtin_nan*` payload the way clang does: as an integer with an /// auto-detected radix (LLVM's `StringRef::getAsInteger(0, ..)`), with the empty /// string meaning 0. Only the low 128 bits are kept, which is all a payload can use. @@ -121,16 +81,28 @@ impl<'c> Translation<'c> { src_loc: &Option, builtin_name: &str, args: &[CExprId], - float: NanFloat, + float: CFloatingKind, signaling: bool, ) -> TranslationResult>> { + let unsupported = || { + format_translation_err!( + self.ast_context.display_loc(src_loc), + "Unimplemented builtin {builtin_name}", + ) + }; + if float.layout().is_none() { + return Err(unsupported()); + } + let arg = self.ast_context.unwrap_cast_expr(args[0]); let payload = match self.ast_context[arg].kind { CExprKind::Literal(_, CLiteral::String(ref bytes, 1)) => parse_nan_payload(bytes), _ => None, }; if let Some(payload) = payload { - return Ok(self.convert_nan_bits(float, float.nan_bits(payload, signaling))); + return self + .convert_nan_bits(float, payload, signaling) + .ok_or_else(unsupported); } let msg = "payload is not a string literal clang can fold"; @@ -147,23 +119,33 @@ impl<'c> Translation<'c> { } // Keep any side effects of the payload expression. let payload = self.convert_expr(ctx.unused(), args[0], None)?; - Ok(payload.and_then(|_| self.convert_nan_bits(float, float.nan_bits(0, false)))) + let nan = self + .convert_nan_bits(float, 0, false) + .ok_or_else(unsupported)?; + Ok(payload.and_then(|_| nan)) } - /// Build a NaN of the given format from its bits. - fn convert_nan_bits(&self, float: NanFloat, bits: u128) -> WithStmts> { - match float { + /// Build a NaN of the given type with this payload, + /// or [`None`] if [`CFloatingKind::nan_bits`] can't compute its bits. + fn convert_nan_bits( + &self, + float: CFloatingKind, + payload: u128, + signaling: bool, + ) -> Option>> { + let bits = float.nan_bits(payload, signaling)?; + Some(match float { // Rust doesn't guarantee the bits of `NAN`, but in practice it is the default // quiet NaN, and it is more readable for the common case (e.g. C's `NAN`). - NanFloat::F32 if bits == 0x7fc0_0000 => { + CFloatingKind::Float if bits == 0x7fc0_0000 => { WithStmts::new_val(mk().abs_path_expr(vec!["core", "f32", "NAN"])) } - NanFloat::F64 if bits == 0x7ff8_0000_0000_0000 => { + CFloatingKind::Double if bits == 0x7ff8_0000_0000_0000 => { WithStmts::new_val(mk().abs_path_expr(vec!["core", "f64", "NAN"])) } - NanFloat::F32 => self.convert_float_from_bits("f32", "u32", bits), - NanFloat::F64 => self.convert_float_from_bits("f64", "u64", bits), - NanFloat::F128 => { + CFloatingKind::Float => self.convert_float_from_bits("f32", "u32", bits), + CFloatingKind::Double => self.convert_float_from_bits("f64", "u64", bits), + CFloatingKind::LongDouble | CFloatingKind::Float128 => { self.use_crate(ExternCrate::F128); // `f128::f128` has no public bit-pattern constructor (and its `NAN` has a @@ -177,7 +159,8 @@ impl<'c> Translation<'c> { )) .set_unsafe() } - } + CFloatingKind::Half | CFloatingKind::BFloat16 => return None, + }) } /// Build a float from its bit pattern, usable in `static` initializers. @@ -258,24 +241,70 @@ impl<'c> Translation<'c> { mk().abs_path_expr(vec!["f128", "f128", "INFINITY"]), )) } - "__builtin_nanf" => { - self.convert_builtin_nan(ctx, src_loc, builtin_name, args, NanFloat::F32, false) - } - "__builtin_nan" => { - self.convert_builtin_nan(ctx, src_loc, builtin_name, args, NanFloat::F64, false) - } - "__builtin_nanl" | "__builtin_nanf128" => { - self.convert_builtin_nan(ctx, src_loc, builtin_name, args, NanFloat::F128, false) - } - "__builtin_nansf" => { - self.convert_builtin_nan(ctx, src_loc, builtin_name, args, NanFloat::F32, true) - } - "__builtin_nans" => { - self.convert_builtin_nan(ctx, src_loc, builtin_name, args, NanFloat::F64, true) - } - "__builtin_nansl" | "__builtin_nansf128" => { - self.convert_builtin_nan(ctx, src_loc, builtin_name, args, NanFloat::F128, true) - } + "__builtin_nanf" => self.convert_builtin_nan( + ctx, + src_loc, + builtin_name, + args, + CFloatingKind::Float, + false, + ), + "__builtin_nan" => self.convert_builtin_nan( + ctx, + src_loc, + builtin_name, + args, + CFloatingKind::Double, + false, + ), + "__builtin_nanl" => self.convert_builtin_nan( + ctx, + src_loc, + builtin_name, + args, + CFloatingKind::LongDouble, + false, + ), + "__builtin_nanf128" => self.convert_builtin_nan( + ctx, + src_loc, + builtin_name, + args, + CFloatingKind::Float128, + false, + ), + "__builtin_nansf" => self.convert_builtin_nan( + ctx, + src_loc, + builtin_name, + args, + CFloatingKind::Float, + true, + ), + "__builtin_nans" => self.convert_builtin_nan( + ctx, + src_loc, + builtin_name, + args, + CFloatingKind::Double, + true, + ), + "__builtin_nansl" => self.convert_builtin_nan( + ctx, + src_loc, + builtin_name, + args, + CFloatingKind::LongDouble, + true, + ), + "__builtin_nansf128" => self.convert_builtin_nan( + ctx, + src_loc, + builtin_name, + args, + CFloatingKind::Float128, + true, + ), "__builtin_signbit" | "__builtin_signbitf" | "__builtin_signbitl" => { self.import_num_traits(args[0])?; @@ -1035,20 +1064,4 @@ mod tests { assert_eq!(parse_nan_payload(b"xyz"), None); assert_eq!(parse_nan_payload(b"1\x002"), None); } - - #[test] - fn nan_bits_match_clang() { - use NanFloat::*; - assert_eq!(F32.nan_bits(0, false), 0x7fc00000); - assert_eq!(F64.nan_bits(0, false), 0x7ff8000000000000); - assert_eq!(F64.nan_bits(1, false), 0x7ff8000000000001); - assert_eq!(F32.nan_bits(0, true), 0x7fa00000); - assert_eq!(F32.nan_bits(7, true), 0x7f800007); - assert_eq!(F64.nan_bits(0, true), 0x7ff4000000000000); - assert_eq!(F64.nan_bits(1, true), 0x7ff0000000000001); - // The quiet bit is cleared, leaving an all-zero significand. - assert_eq!(F64.nan_bits(0x8000000000000, true), 0x7ff4000000000000); - assert_eq!(F128.nan_bits(0, false), 0x7fff8000000000000000000000000000); - assert_eq!(F128.nan_bits(0, true), 0x7fff4000000000000000000000000000); - } }