Skip to content

Faster float conversion operations #370

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
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
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ test = false
# For more information on this dependency see rust-lang/rust's
# `src/tools/rustc-std-workspace` folder
core = { version = "1.0.0", optional = true, package = 'rustc-std-workspace-core' }
floatconv = "0.2.8"

[build-dependencies]
cc = { optional = true, version = "1.0" }
Expand Down
12 changes: 1 addition & 11 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,21 +263,15 @@ mod c {

if target_env == "msvc" {
if target_arch == "x86_64" {
sources.extend(&[
("__floatdisf", "x86_64/floatdisf.c"),
("__floatdixf", "x86_64/floatdixf.c"),
]);
sources.extend(&[("__floatdixf", "x86_64/floatdixf.c")]);
}
} else {
// None of these seem to be used on x86_64 windows, and they've all
// got the wrong ABI anyway, so we want to avoid them.
if target_os != "windows" {
if target_arch == "x86_64" {
sources.extend(&[
("__floatdisf", "x86_64/floatdisf.c"),
("__floatdixf", "x86_64/floatdixf.c"),
("__floatundidf", "x86_64/floatundidf.S"),
("__floatundisf", "x86_64/floatundisf.S"),
("__floatundixf", "x86_64/floatundixf.S"),
]);
}
Expand All @@ -288,11 +282,7 @@ mod c {
("__ashldi3", "i386/ashldi3.S"),
("__ashrdi3", "i386/ashrdi3.S"),
("__divdi3", "i386/divdi3.S"),
("__floatdidf", "i386/floatdidf.S"),
("__floatdisf", "i386/floatdisf.S"),
("__floatdixf", "i386/floatdixf.S"),
("__floatundidf", "i386/floatundidf.S"),
("__floatundisf", "i386/floatundisf.S"),
("__floatundixf", "i386/floatundixf.S"),
("__lshrdi3", "i386/lshrdi3.S"),
("__moddi3", "i386/moddi3.S"),
Expand Down
214 changes: 24 additions & 190 deletions src/float/conv.rs
Original file line number Diff line number Diff line change
@@ -1,287 +1,121 @@
use float::Float;
use int::Int;

macro_rules! int_to_float {
($i:expr, $ity:ty, $fty:ty) => {{
let i = $i;
if i == 0 {
return 0.0;
}

let mant_dig = <$fty>::SIGNIFICAND_BITS + 1;
let exponent_bias = <$fty>::EXPONENT_BIAS;

let n = <$ity>::BITS;
let (s, a) = i.extract_sign();
let mut a = a;

// number of significant digits
let sd = n - a.leading_zeros();

// exponent
let mut e = sd - 1;

if <$ity>::BITS < mant_dig {
return <$fty>::from_parts(
s,
(e + exponent_bias) as <$fty as Float>::Int,
(a as <$fty as Float>::Int) << (mant_dig - e - 1),
);
}

a = if sd > mant_dig {
/* start: 0000000000000000000001xxxxxxxxxxxxxxxxxxxxxxPQxxxxxxxxxxxxxxxxxx
* finish: 000000000000000000000000000000000000001xxxxxxxxxxxxxxxxxxxxxxPQR
* 12345678901234567890123456
* 1 = msb 1 bit
* P = bit MANT_DIG-1 bits to the right of 1
* Q = bit MANT_DIG bits to the right of 1
* R = "or" of all bits to the right of Q
*/
let mant_dig_plus_one = mant_dig + 1;
let mant_dig_plus_two = mant_dig + 2;
a = if sd == mant_dig_plus_one {
a << 1
} else if sd == mant_dig_plus_two {
a
} else {
(a >> (sd - mant_dig_plus_two)) as <$ity as Int>::UnsignedInt
| ((a & <$ity as Int>::UnsignedInt::max_value())
.wrapping_shl((n + mant_dig_plus_two) - sd)
!= 0) as <$ity as Int>::UnsignedInt
};

/* finish: */
a |= ((a & 4) != 0) as <$ity as Int>::UnsignedInt; /* Or P into R */
a += 1; /* round - this step may add a significant bit */
a >>= 2; /* dump Q and R */

/* a is now rounded to mant_dig or mant_dig+1 bits */
if (a & (1 << mant_dig)) != 0 {
a >>= 1;
e += 1;
}
a
/* a is now rounded to mant_dig bits */
} else {
a.wrapping_shl(mant_dig - sd)
/* a is now rounded to mant_dig bits */
};

<$fty>::from_parts(
s,
(e + exponent_bias) as <$fty as Float>::Int,
a as <$fty as Float>::Int,
)
}};
}

intrinsics! {
#[arm_aeabi_alias = __aeabi_i2f]
pub extern "C" fn __floatsisf(i: i32) -> f32 {
int_to_float!(i, i32, f32)
floatconv::fast::i32_to_f32_round(i)
}

#[arm_aeabi_alias = __aeabi_i2d]
pub extern "C" fn __floatsidf(i: i32) -> f64 {
int_to_float!(i, i32, f64)
floatconv::fast::i32_to_f64(i)
}

#[maybe_use_optimized_c_shim]
#[arm_aeabi_alias = __aeabi_l2f]
pub extern "C" fn __floatdisf(i: i64) -> f32 {
// On x86_64 LLVM will use native instructions for this conversion, we
// can just do it directly
if cfg!(target_arch = "x86_64") {
i as f32
} else {
int_to_float!(i, i64, f32)
}
floatconv::fast::i64_to_f32_round(i)
}

#[maybe_use_optimized_c_shim]
#[arm_aeabi_alias = __aeabi_l2d]
pub extern "C" fn __floatdidf(i: i64) -> f64 {
// On x86_64 LLVM will use native instructions for this conversion, we
// can just do it directly
if cfg!(target_arch = "x86_64") {
i as f64
} else {
int_to_float!(i, i64, f64)
}
floatconv::fast::i64_to_f64_round(i)
}

#[unadjusted_on_win64]
pub extern "C" fn __floattisf(i: i128) -> f32 {
int_to_float!(i, i128, f32)
floatconv::fast::i128_to_f32_round(i)
}

#[unadjusted_on_win64]
pub extern "C" fn __floattidf(i: i128) -> f64 {
int_to_float!(i, i128, f64)
floatconv::fast::i128_to_f64_round(i)
}

#[arm_aeabi_alias = __aeabi_ui2f]
pub extern "C" fn __floatunsisf(i: u32) -> f32 {
int_to_float!(i, u32, f32)
floatconv::fast::u32_to_f32_round(i)
}

#[arm_aeabi_alias = __aeabi_ui2d]
pub extern "C" fn __floatunsidf(i: u32) -> f64 {
int_to_float!(i, u32, f64)
floatconv::fast::u32_to_f64(i)
}

#[maybe_use_optimized_c_shim]
#[arm_aeabi_alias = __aeabi_ul2f]
pub extern "C" fn __floatundisf(i: u64) -> f32 {
int_to_float!(i, u64, f32)
floatconv::fast::u64_to_f32_round(i)
}

#[maybe_use_optimized_c_shim]
#[arm_aeabi_alias = __aeabi_ul2d]
pub extern "C" fn __floatundidf(i: u64) -> f64 {
int_to_float!(i, u64, f64)
floatconv::fast::u64_to_f64_round(i)
}

#[unadjusted_on_win64]
pub extern "C" fn __floatuntisf(i: u128) -> f32 {
int_to_float!(i, u128, f32)
floatconv::fast::u128_to_f32_round(i)
}

#[unadjusted_on_win64]
pub extern "C" fn __floatuntidf(i: u128) -> f64 {
int_to_float!(i, u128, f64)
floatconv::fast::u128_to_f64_round(i)
}
}

#[derive(PartialEq)]
enum Sign {
Positive,
Negative,
}

macro_rules! float_to_int {
($f:expr, $fty:ty, $ity:ty) => {{
let f = $f;
let fixint_min = <$ity>::min_value();
let fixint_max = <$ity>::max_value();
let fixint_bits = <$ity>::BITS as usize;
let fixint_unsigned = fixint_min == 0;

let sign_bit = <$fty>::SIGN_MASK;
let significand_bits = <$fty>::SIGNIFICAND_BITS as usize;
let exponent_bias = <$fty>::EXPONENT_BIAS as usize;
//let exponent_max = <$fty>::exponent_max() as usize;

// Break a into sign, exponent, significand
let a_rep = <$fty>::repr(f);
let a_abs = a_rep & !sign_bit;

// this is used to work around -1 not being available for unsigned
let sign = if (a_rep & sign_bit) == 0 {
Sign::Positive
} else {
Sign::Negative
};
let mut exponent = (a_abs >> significand_bits) as usize;
let significand = (a_abs & <$fty>::SIGNIFICAND_MASK) | <$fty>::IMPLICIT_BIT;

// if < 1 or unsigned & negative
if exponent < exponent_bias || fixint_unsigned && sign == Sign::Negative {
return 0;
}
exponent -= exponent_bias;

// If the value is infinity, saturate.
// If the value is too large for the integer type, 0.
if exponent
>= (if fixint_unsigned {
fixint_bits
} else {
fixint_bits - 1
})
{
return if sign == Sign::Positive {
fixint_max
} else {
fixint_min
};
}
// If 0 <= exponent < significand_bits, right shift to get the result.
// Otherwise, shift left.
// (sign - 1) will never overflow as negative signs are already returned as 0 for unsigned
let r = if exponent < significand_bits {
(significand >> (significand_bits - exponent)) as $ity
} else {
(significand as $ity) << (exponent - significand_bits)
};

if sign == Sign::Negative {
(!r).wrapping_add(1)
} else {
r
}
}};
}

intrinsics! {
#[arm_aeabi_alias = __aeabi_f2iz]
pub extern "C" fn __fixsfsi(f: f32) -> i32 {
float_to_int!(f, f32, i32)
floatconv::fast::f32_to_i32(f)
}

#[arm_aeabi_alias = __aeabi_f2lz]
pub extern "C" fn __fixsfdi(f: f32) -> i64 {
float_to_int!(f, f32, i64)
floatconv::fast::f32_to_i64(f)
}

#[unadjusted_on_win64]
pub extern "C" fn __fixsfti(f: f32) -> i128 {
float_to_int!(f, f32, i128)
floatconv::fast::f32_to_i128(f)
}

#[arm_aeabi_alias = __aeabi_d2iz]
pub extern "C" fn __fixdfsi(f: f64) -> i32 {
float_to_int!(f, f64, i32)
floatconv::fast::f64_to_i32(f)
}

#[arm_aeabi_alias = __aeabi_d2lz]
pub extern "C" fn __fixdfdi(f: f64) -> i64 {
float_to_int!(f, f64, i64)
floatconv::fast::f64_to_i64(f)
}

#[unadjusted_on_win64]
pub extern "C" fn __fixdfti(f: f64) -> i128 {
float_to_int!(f, f64, i128)
floatconv::fast::f64_to_i128(f)
}

#[arm_aeabi_alias = __aeabi_f2uiz]
pub extern "C" fn __fixunssfsi(f: f32) -> u32 {
float_to_int!(f, f32, u32)
floatconv::fast::f32_to_u32(f)
}

#[arm_aeabi_alias = __aeabi_f2ulz]
pub extern "C" fn __fixunssfdi(f: f32) -> u64 {
float_to_int!(f, f32, u64)
floatconv::fast::f32_to_u64(f)
}

#[unadjusted_on_win64]
pub extern "C" fn __fixunssfti(f: f32) -> u128 {
float_to_int!(f, f32, u128)
floatconv::fast::f32_to_u128(f)
}

#[arm_aeabi_alias = __aeabi_d2uiz]
pub extern "C" fn __fixunsdfsi(f: f64) -> u32 {
float_to_int!(f, f64, u32)
floatconv::fast::f64_to_u32(f)
}

#[arm_aeabi_alias = __aeabi_d2ulz]
pub extern "C" fn __fixunsdfdi(f: f64) -> u64 {
float_to_int!(f, f64, u64)
floatconv::fast::f64_to_u64(f)
}

#[unadjusted_on_win64]
pub extern "C" fn __fixunsdfti(f: f64) -> u128 {
float_to_int!(f, f64, u128)
floatconv::fast::f64_to_u128(f)
}
}