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
170 changes: 170 additions & 0 deletions cc_bindings_from_rs/generate_bindings/generate_bindings_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2534,6 +2534,176 @@ fn test_const_non_aggregate_struct_fails() {
});
}

#[gtest]
fn test_const_float() {
let test_src = r#"
pub const WHOLE_F32: f32 = 64.0;
pub const LARGE_WHOLE_F32: f32 = 12583039.0;
pub const SCI_F32: f32 = 1e-7;
pub const NEG_ZERO_F32: f32 = -0.0;
pub const INF_F32: f32 = f32::INFINITY;
pub const NEG_INF_F32: f32 = f32::NEG_INFINITY;
pub const NAN_F32: f32 = f32::NAN;
pub const NEG_NAN_F32: f32 = -f32::NAN;
pub const PAYLOAD_NAN_F32: f32 = f32::from_bits(0x7fc0_0042);
pub const NEG_PAYLOAD_NAN_F32: f32 = f32::from_bits(0xffc0_0042);
pub const MAX_PAYLOAD_NAN_F32: f32 = f32::from_bits(0x7fff_ffff);
pub const SIGNALING_NAN_F32: f32 = f32::from_bits(0x7f80_0001);
pub const NEG_SIGNALING_NAN_F32: f32 = f32::from_bits(0xff80_0001);
pub const WHOLE_F64: f64 = 64.0;
pub const INF_F64: f64 = f64::INFINITY;
pub const NEG_INF_F64: f64 = f64::NEG_INFINITY;
pub const NAN_F64: f64 = f64::NAN;
pub const NEG_NAN_F64: f64 = -f64::NAN;
pub const PAYLOAD_NAN_F64: f64 = f64::from_bits(0x7ff8_0000_0000_0042);
pub const NEG_PAYLOAD_NAN_F64: f64 = f64::from_bits(0xfff8_0000_0000_0042);
pub const MAX_PAYLOAD_NAN_F64: f64 = f64::from_bits(0x7fff_ffff_ffff_ffff);
pub const SIGNALING_NAN_F64: f64 = f64::from_bits(0x7ff0_0000_0000_0001);
pub const NEG_SIGNALING_NAN_F64: f64 = f64::from_bits(0xfff0_0000_0000_0001);
"#;
test_generated_bindings(test_src, |bindings| {
let cc_api = &bindings.unwrap().cc_api;
assert_cc_matches!(
cc_api,
quote! {
static constexpr float WHOLE_F32 = 64.0f;
}
);
assert_cc_matches!(
cc_api,
quote! {
static constexpr float LARGE_WHOLE_F32 = 12583039.0f;
}
);
assert_cc_matches!(
cc_api,
quote! {
static constexpr float SCI_F32 = 1e-7f;
}
);
assert_cc_matches!(
cc_api,
quote! {
static constexpr float NEG_ZERO_F32 = -0.0f;
}
);
assert_cc_matches!(
cc_api,
quote! {
static constexpr float INF_F32 = __builtin_inff();
}
);
assert_cc_matches!(
cc_api,
quote! {
static constexpr float NEG_INF_F32 = -__builtin_inff();
}
);
assert_cc_matches!(
cc_api,
quote! {
static constexpr float NAN_F32 = __builtin_nanf("");
}
);
assert_cc_matches!(
cc_api,
quote! {
static constexpr float NEG_NAN_F32 = -__builtin_nanf("");
}
);
assert_cc_matches!(
cc_api,
quote! {
static constexpr float PAYLOAD_NAN_F32 = __builtin_nanf("0x42");
}
);
assert_cc_matches!(
cc_api,
quote! {
static constexpr float NEG_PAYLOAD_NAN_F32 = -__builtin_nanf("0x42");
}
);
assert_cc_matches!(
cc_api,
quote! {
static constexpr float MAX_PAYLOAD_NAN_F32 = __builtin_nanf("0x3fffff");
}
);
assert_cc_matches!(
cc_api,
quote! {
static constexpr float SIGNALING_NAN_F32 = __builtin_nansf("0x1");
}
);
assert_cc_matches!(
cc_api,
quote! {
static constexpr float NEG_SIGNALING_NAN_F32 = -__builtin_nansf("0x1");
}
);
assert_cc_matches!(
cc_api,
quote! {
static constexpr double WHOLE_F64 = 64.0;
}
);
assert_cc_matches!(
cc_api,
quote! {
static constexpr double INF_F64 = __builtin_inf();
}
);
assert_cc_matches!(
cc_api,
quote! {
static constexpr double NEG_INF_F64 = -__builtin_inf();
}
);
assert_cc_matches!(
cc_api,
quote! {
static constexpr double NAN_F64 = __builtin_nan("");
}
);
assert_cc_matches!(
cc_api,
quote! {
static constexpr double NEG_NAN_F64 = -__builtin_nan("");
}
);
assert_cc_matches!(
cc_api,
quote! {
static constexpr double PAYLOAD_NAN_F64 = __builtin_nan("0x42");
}
);
assert_cc_matches!(
cc_api,
quote! {
static constexpr double NEG_PAYLOAD_NAN_F64 = -__builtin_nan("0x42");
}
);
assert_cc_matches!(
cc_api,
quote! {
static constexpr double MAX_PAYLOAD_NAN_F64 = __builtin_nan("0x7ffffffffffff");
}
);
assert_cc_matches!(
cc_api,
quote! {
static constexpr double SIGNALING_NAN_F64 = __builtin_nans("0x1");
}
);
assert_cc_matches!(
cc_api,
quote! {
static constexpr double NEG_SIGNALING_NAN_F64 = -__builtin_nans("0x1");
}
);
});
}

#[gtest]
fn test_unbindable_types_warning() {
let test_src = r#"
Expand Down
69 changes: 57 additions & 12 deletions cc_bindings_from_rs/generate_bindings/generate_enum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,31 @@ fn generate_variant<'tcx>(
}
}

#[derive(Copy, Clone, Debug)]
struct CcNan {
is_negative: bool,
is_quiet: bool,
payload: u64,
is_f32: bool,
}

/// Formats an IEEE-754 NaN into a Clang/GCC `constexpr` builtin call that preserves the sign bit,
/// quiet vs. signaling bit, and NaN payload.
fn format_cc_nan(nan: CcNan) -> String {
let sign = if nan.is_negative { "-" } else { "" };
let builtin = match (nan.is_quiet, nan.is_f32) {
(true, true) => "__builtin_nanf",
(true, false) => "__builtin_nan",
(false, true) => "__builtin_nansf",
(false, false) => "__builtin_nans",
};
if nan.is_quiet && nan.payload == 0 {
format!(r#"{sign}{builtin}("")"#)
} else {
format!(r#"{sign}{builtin}("0x{:x}")"#, nan.payload)
}
}

/// Returns a string representation of the value of a given numeric Scalar having a given TyKind.
pub fn scalar_value_to_string(tcx: TyCtxt, scalar: Scalar, kind: TyKind) -> Result<String> {
let scalar = match scalar {
Expand All @@ -699,14 +724,6 @@ pub fn scalar_value_to_string(tcx: TyCtxt, scalar: Scalar, kind: TyKind) -> Resu
use ty::TyKind;
use ty::UintTy::*;

fn format_float(value: String, suffix: &str) -> String {
if value.contains('.') || value.contains('e') || value.contains('E') {
format!("{value}{suffix}")
} else {
format!("{value}.0{suffix}")
}
}

Ok(match kind {
TyKind::Bool => scalar.try_to_bool().unwrap().to_string(),
TyKind::Int(I8) => scalar.to_i8().to_string(),
Expand All @@ -717,12 +734,40 @@ pub fn scalar_value_to_string(tcx: TyCtxt, scalar: Scalar, kind: TyKind) -> Resu
TyKind::Uint(U32) => format!("UINT32_C({})", scalar.to_u32()),
TyKind::Uint(U64) => format!("UINT64_C({})", scalar.to_u64()),
TyKind::Float(F32) => {
let s = scalar.to_f32().to_string();
format_float(s, "f")
let bits = scalar.to_u32();
let value = f32::from_bits(bits);
if value.is_nan() {
format_cc_nan(CcNan {
is_negative: (bits & 0x8000_0000) != 0,
is_quiet: (bits & 0x0040_0000) != 0,
payload: (bits & 0x003f_ffff) as u64,
is_f32: true,
})
} else if value == f32::INFINITY {
"__builtin_inff()".to_string()
} else if value == f32::NEG_INFINITY {
"-__builtin_inff()".to_string()
} else {
format!("{value:?}f")
}
}
TyKind::Float(F64) => {
let s = scalar.to_f64().to_string();
format_float(s, "L")
let bits = scalar.to_u64();
let value = f64::from_bits(bits);
if value.is_nan() {
format_cc_nan(CcNan {
is_negative: (bits & 0x8000_0000_0000_0000) != 0,
is_quiet: (bits & 0x0008_0000_0000_0000) != 0,
payload: bits & 0x0007_ffff_ffff_ffff,
is_f32: false,
})
} else if value == f64::INFINITY {
"__builtin_inf()".to_string()
} else if value == f64::NEG_INFINITY {
"-__builtin_inf()".to_string()
} else {
format!("{value:?}")
}
}
TyKind::Uint(Usize) => format!("UINT64_C({})", scalar.to_target_usize(tcx)),

Expand Down
19 changes: 2 additions & 17 deletions cc_bindings_from_rs/generate_bindings/generate_struct_and_union.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ use itertools::Itertools;
use proc_macro2::{Ident, Literal, TokenStream};
use query_compiler::{
as_ref_or_pinned_ref, field_def_is_pub_and_stable, get_layout, get_tag_size_with_padding,
get_validated_layout, is_copy, is_std_ptr_non_null, matches_qualified_name, normalize_ty,
should_receive_bindings, SortedByDef,
get_validated_layout, is_copy, is_std_ptr_non_null, normalize_ty, should_receive_bindings,
SortedByDef,
};
use quote::{format_ident, quote};
#[rustversion::since(2026-05-18)]
Expand Down Expand Up @@ -4365,21 +4365,6 @@ pub fn generate_const<'tcx>(
bail!("Associated constants with their own predicates are not supported");
}
}
// TODO: b/457843120 - Remove this workaround once we can properly support float constants.
let unsupported_consts = [
["core", "f32", "INFINITY"],
["core", "f64", "INFINITY"],
["core", "f32", "NEG_INFINITY"],
["core", "f64", "NEG_INFINITY"],
["core", "f32", "NAN"],
["core", "f64", "NAN"],
];
if unsupported_consts.iter().any(|const_path| matches_qualified_name(db, def_id, const_path)) {
bail!(
"Cannot generate bindings to unsupported constant: {}",
tcx.item_name(def_id).as_str()
)
}
let (ty, const_value) = match generic_args {
Some(args) => {
let typing_env = ty::TypingEnv::fully_monomorphized();
Expand Down
25 changes: 25 additions & 0 deletions cc_bindings_from_rs/test/bazel/rs_core/rs_core_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

// Verify that <cmath> macros (INFINITY, NAN) do not collide with rs_core.h.
// clang-format off
#include <cmath>
#include "support/rs_std/rs_core.h"
// clang-format on

#include "gtest/gtest.h"

Expand All @@ -14,5 +18,26 @@ TEST(RsCoreTest, Duration) {
EXPECT_EQ(d.as_secs(), 10);
}

TEST(RsCoreTest, FloatConstants) {
static_assert(rs::core::f32::INFINITY_ == __builtin_inff());
static_assert(rs::core::f32::NEG_INFINITY == -__builtin_inff());
static_assert(rs::core::f32::NAN_ != rs::core::f32::NAN_);
static_assert(rs::core::f64::INFINITY_ == __builtin_inf());
static_assert(rs::core::f64::NEG_INFINITY == -__builtin_inf());
static_assert(rs::core::f64::NAN_ != rs::core::f64::NAN_);

EXPECT_TRUE(std::isinf(rs::core::f32::INFINITY_));
EXPECT_GT(rs::core::f32::INFINITY_, 0.0f);
EXPECT_TRUE(std::isinf(rs::core::f32::NEG_INFINITY));
EXPECT_LT(rs::core::f32::NEG_INFINITY, 0.0f);
EXPECT_TRUE(std::isnan(rs::core::f32::NAN_));

EXPECT_TRUE(std::isinf(rs::core::f64::INFINITY_));
EXPECT_GT(rs::core::f64::INFINITY_, 0.0);
EXPECT_TRUE(std::isinf(rs::core::f64::NEG_INFINITY));
EXPECT_LT(rs::core::f64::NEG_INFINITY, 0.0);
EXPECT_TRUE(std::isnan(rs::core::f64::NAN_));
}

} // namespace
} // namespace crubit
Loading
Loading