Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions c2rust-transpile/src/c_ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<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;
Some(exponent | significand)
}
}

impl Display for CFloatingKind {
Expand Down Expand Up @@ -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);
}
}
}
256 changes: 241 additions & 15 deletions c2rust-transpile/src/translator/builtins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,30 @@ enum LibcFnArgType {
Int,
}

/// 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<u128> {
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(
Expand All @@ -42,6 +66,127 @@ impl<'c> Translation<'c> {
}))
}

/// 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
/// 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<SrcSpan>,
builtin_name: &str,
args: &[CExprId],
float: CFloatingKind,
signaling: bool,
) -> TranslationResult<WithStmts<Box<Expr>>> {
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 self
.convert_nan_bits(float, payload, signaling)
.ok_or_else(unsupported);
}

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)?;
let nan = self
.convert_nan_bits(float, 0, false)
.ok_or_else(unsupported)?;
Ok(payload.and_then(|_| nan))
}

/// 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<WithStmts<Box<Expr>>> {
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`).
CFloatingKind::Float if bits == 0x7fc0_0000 => {
WithStmts::new_val(mk().abs_path_expr(vec!["core", "f32", "NAN"]))
}
CFloatingKind::Double if bits == 0x7ff8_0000_0000_0000 => {
WithStmts::new_val(mk().abs_path_expr(vec!["core", "f64", "NAN"]))
}
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
// 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()
}
CFloatingKind::Half | CFloatingKind::BFloat16 => return None,
})
}

/// 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<Box<Expr>> {
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,
Expand Down Expand Up @@ -76,7 +221,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(
Expand All @@ -89,26 +234,77 @@ 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(
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_nanl" => {
self.use_crate(ExternCrate::F128);

Ok(WithStmts::new_val(
mk().abs_path_expr(vec!["f128", "f128", "NAN"]),
))
}
"__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])?;

Expand Down Expand Up @@ -839,3 +1035,33 @@ 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);
}
}
Loading