Skip to content

Implement comparesf2/comparedf2 intrinsics #222

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

Merged
merged 7 commits into from
Jan 13, 2018
Merged
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
20 changes: 12 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -52,16 +52,20 @@ features = ["c"]
## Contributing

1. Pick one or more intrinsics from the [pending list](#progress).
2. Fork this repository
3. Port the intrinsic(s) and their corresponding [unit tests][1] from their [C implementation][2] to
Rust.
4. Send a Pull Request (PR)
5. Once the PR passes our extensive [testing infrastructure][3], we'll merge it!
2. Fork this repository.
3. Port the intrinsic(s) and their corresponding [unit tests][1] from their
[C implementation][2] to Rust.
4. Implement a [test generator][3] to compare the behavior of the ported intrinsic(s)
with their implementation on the testing host. Note that randomized compiler-builtin tests
should be run using `cargo test --features gen-tests`.
4. Send a Pull Request (PR).
5. Once the PR passes our extensive [testing infrastructure][4], we'll merge it!
6. Celebrate :tada:

[1]: https://github.com/rust-lang/compiler-rt/tree/8598065bd965d9713bfafb6c1e766d63a7b17b89/test/builtins/Unit
[2]: https://github.com/rust-lang/compiler-rt/tree/8598065bd965d9713bfafb6c1e766d63a7b17b89/lib/builtins
[3]: https://travis-ci.org/rust-lang-nursery/compiler-builtins
[3]: https://github.com/rust-lang-nursery/compiler-builtins/blob/0ba07e49264a54cb5bbd4856fcea083bb3fbec15/build.rs#L180-L265
[4]: https://travis-ci.org/rust-lang-nursery/compiler-builtins

### Porting Reminders

@@ -133,6 +137,8 @@ features = ["c"]
- [ ] arm/unordsf2vfp.S
- [x] ashldi3.c
- [x] ashrdi3.c
- [x] comparedf2.c
- [x] comparesf2.c
- [x] divdf3.c
- [x] divdi3.c
- [x] divmoddi4.c
@@ -301,8 +307,6 @@ These builtins are never called by LLVM.
- ~~clzti2.c~~
- ~~cmpdi2.c~~
- ~~cmpti2.c~~
- ~~comparedf2.c~~
- ~~comparesf2.c~~
- ~~ctzdi2.c~~
- ~~ctzsi2.c~~
- ~~ctzti2.c~~
321 changes: 318 additions & 3 deletions build.rs
Original file line number Diff line number Diff line change
@@ -89,6 +89,12 @@ mod tests {
Adddf3,
Addsf3,

// float/cmp.rs
Gedf2,
Gesf2,
Ledf2,
Lesf2,

// float/conv.rs
Fixdfdi,
Fixdfsi,
@@ -2529,6 +2535,318 @@ fn floatuntidf() {
}
}

#[derive(Eq, Hash, PartialEq)]
pub struct Gedf2 {
a: u64,
b: u64,
c: i32,
}

impl TestCase for Gedf2 {
fn name() -> &'static str {
"gedf2"
}

fn generate<R>(rng: &mut R) -> Option<Self>
where
R: Rng,
Self: Sized,
{
let a = gen_f64(rng);
let b = gen_f64(rng);
// TODO accept NaNs. We don't do that right now because we can't check
// for NaN-ness on the thumb targets (due to missing intrinsics)
if a.is_nan() || b.is_nan() {
return None;
}

let c;
if a.is_nan() || b.is_nan() {
c = -1;
} else if a < b {
c = -1;
} else if a > b {
c = 1;
} else {
c = 0;
}

Some(Gedf2 { a: to_u64(a), b: to_u64(b), c })
}

fn to_string(&self, buffer: &mut String) {
writeln!(
buffer,
"(({a}, {b}), {c}),",
a = self.a,
b = self.b,
c = self.c
)
.unwrap();
}

fn prologue() -> &'static str {
"
use std::mem;
use compiler_builtins::float::cmp::__gedf2;

fn to_f64(x: u64) -> f64 {
unsafe { mem::transmute(x) }
}

static TEST_CASES: &[((u64, u64), i32)] = &[
"
}

fn epilogue() -> &'static str {
"
];

#[test]
fn gedf2() {
for &((a, b), c) in TEST_CASES {
let c_ = __gedf2(to_f64(a), to_f64(b));
assert_eq!(((a, b), c), ((a, b), c_));
}
}
"
}
}

#[derive(Eq, Hash, PartialEq)]
pub struct Gesf2 {
a: u32,
b: u32,
c: i32,
}

impl TestCase for Gesf2 {
fn name() -> &'static str {
"gesf2"
}

fn generate<R>(rng: &mut R) -> Option<Self>
where
R: Rng,
Self: Sized,
{
let a = gen_f32(rng);
let b = gen_f32(rng);
// TODO accept NaNs. We don't do that right now because we can't check
// for NaN-ness on the thumb targets (due to missing intrinsics)
if a.is_nan() || b.is_nan() {
return None;
}

let c;
if a.is_nan() || b.is_nan() {
c = -1;
} else if a < b {
c = -1;
} else if a > b {
c = 1;
} else {
c = 0;
}

Some(Gesf2 { a: to_u32(a), b: to_u32(b), c })
}

fn to_string(&self, buffer: &mut String) {
writeln!(
buffer,
"(({a}, {b}), {c}),",
a = self.a,
b = self.b,
c = self.c
)
.unwrap();
}

fn prologue() -> &'static str {
"
use std::mem;
use compiler_builtins::float::cmp::__gesf2;

fn to_f32(x: u32) -> f32 {
unsafe { mem::transmute(x) }
}

static TEST_CASES: &[((u32, u32), i32)] = &[
"
}

fn epilogue() -> &'static str {
"
];

#[test]
fn gesf2() {
for &((a, b), c) in TEST_CASES {
let c_ = __gesf2(to_f32(a), to_f32(b));
assert_eq!(((a, b), c), ((a, b), c_));
}
}
"
}
}

#[derive(Eq, Hash, PartialEq)]
pub struct Ledf2 {
a: u64,
b: u64,
c: i32,
}

impl TestCase for Ledf2 {
fn name() -> &'static str {
"ledf2"
}

fn generate<R>(rng: &mut R) -> Option<Self>
where
R: Rng,
Self: Sized,
{
let a = gen_f64(rng);
let b = gen_f64(rng);
// TODO accept NaNs. We don't do that right now because we can't check
// for NaN-ness on the thumb targets (due to missing intrinsics)
if a.is_nan() || b.is_nan() {
return None;
}

let c;
if a.is_nan() || b.is_nan() {
c = 1;
} else if a < b {
c = -1;
} else if a > b {
c = 1;
} else {
c = 0;
}

Some(Ledf2 { a: to_u64(a), b: to_u64(b), c })
}

fn to_string(&self, buffer: &mut String) {
writeln!(
buffer,
"(({a}, {b}), {c}),",
a = self.a,
b = self.b,
c = self.c
)
.unwrap();
}

fn prologue() -> &'static str {
"
use std::mem;
use compiler_builtins::float::cmp::__ledf2;

fn to_f64(x: u64) -> f64 {
unsafe { mem::transmute(x) }
}

static TEST_CASES: &[((u64, u64), i32)] = &[
"
}

fn epilogue() -> &'static str {
"
];

#[test]
fn ledf2() {
for &((a, b), c) in TEST_CASES {
let c_ = __ledf2(to_f64(a), to_f64(b));
assert_eq!(((a, b), c), ((a, b), c_));
}
}
"
}
}

#[derive(Eq, Hash, PartialEq)]
pub struct Lesf2 {
a: u32,
b: u32,
c: i32,
}

impl TestCase for Lesf2 {
fn name() -> &'static str {
"lesf2"
}

fn generate<R>(rng: &mut R) -> Option<Self>
where
R: Rng,
Self: Sized,
{
let a = gen_f32(rng);
let b = gen_f32(rng);
// TODO accept NaNs. We don't do that right now because we can't check
// for NaN-ness on the thumb targets (due to missing intrinsics)
if a.is_nan() || b.is_nan() {
return None;
}

let c;
if a.is_nan() || b.is_nan() {
c = 1;
} else if a < b {
c = -1;
} else if a > b {
c = 1;
} else {
c = 0;
}

Some(Lesf2 { a: to_u32(a), b: to_u32(b), c })
}

fn to_string(&self, buffer: &mut String) {
writeln!(
buffer,
"(({a}, {b}), {c}),",
a = self.a,
b = self.b,
c = self.c
)
.unwrap();
}

fn prologue() -> &'static str {
"
use std::mem;
use compiler_builtins::float::cmp::__lesf2;

fn to_f32(x: u32) -> f32 {
unsafe { mem::transmute(x) }
}

static TEST_CASES: &[((u32, u32), i32)] = &[
"
}

fn epilogue() -> &'static str {
"
];

#[test]
fn lesf2() {
for &((a, b), c) in TEST_CASES {
let c_ = __lesf2(to_f32(a), to_f32(b));
assert_eq!(((a, b), c), ((a, b), c_));
}
}
"
}
}

#[derive(Eq, Hash, PartialEq)]
pub struct Moddi3 {
a: i64,
@@ -4982,8 +5300,6 @@ mod c {
"clzdi2.c",
"clzsi2.c",
"cmpdi2.c",
"comparedf2.c",
"comparesf2.c",
"ctzdi2.c",
"ctzsi2.c",
"divdc3.c",
@@ -5127,7 +5443,6 @@ mod c {
"arm/bswapsi2.S",
"arm/clzdi2.S",
"arm/clzsi2.S",
"arm/comparesf2.S",
"arm/divmodsi4.S",
"arm/modsi3.S",
"arm/switch16.S",
172 changes: 172 additions & 0 deletions src/float/cmp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
#![allow(unreachable_code)]

use int::{Int, CastInto};
use float::Float;

#[derive(Clone, Copy)]
enum Result {
Less,
Equal,
Greater,
Unordered
}

impl Result {
fn to_le_abi(self) -> i32 {
match self {
Result::Less => -1,
Result::Equal => 0,
Result::Greater => 1,
Result::Unordered => 1
}
}

fn to_ge_abi(self) -> i32 {
match self {
Result::Less => -1,
Result::Equal => 0,
Result::Greater => 1,
Result::Unordered => -1
}
}
}

fn cmp<F: Float>(a: F, b: F) -> Result where
u32: CastInto<F::Int>,
F::Int: CastInto<u32>,
i32: CastInto<F::Int>,
F::Int: CastInto<i32>,
{
let one = F::Int::ONE;
let zero = F::Int::ZERO;
let szero = F::SignedInt::ZERO;

let sign_bit = F::SIGN_MASK as F::Int;
let abs_mask = sign_bit - one;
let exponent_mask = F::EXPONENT_MASK;
let inf_rep = exponent_mask;

let a_rep = a.repr();
let b_rep = b.repr();
let a_abs = a_rep & abs_mask;
let b_abs = b_rep & abs_mask;

// If either a or b is NaN, they are unordered.
if a_abs > inf_rep || b_abs > inf_rep {
return Result::Unordered
}

// If a and b are both zeros, they are equal.
if a_abs | b_abs == zero {
return Result::Equal
}

let a_srep = a.signed_repr();
let b_srep = b.signed_repr();

// If at least one of a and b is positive, we get the same result comparing
// a and b as signed integers as we would with a fp_ting-point compare.
if a_srep & b_srep >= szero {
if a_srep < b_srep {
return Result::Less
} else if a_srep == b_srep {
return Result::Equal
} else {
return Result::Greater
}
}

// Otherwise, both are negative, so we need to flip the sense of the
// comparison to get the correct result. (This assumes a twos- or ones-
// complement integer representation; if integers are represented in a
// sign-magnitude representation, then this flip is incorrect).
else {
if a_srep > b_srep {
return Result::Less
} else if a_srep == b_srep {
return Result::Equal
} else {
return Result::Greater
}
}
}
fn unord<F: Float>(a: F, b: F) -> bool where
u32: CastInto<F::Int>,
F::Int: CastInto<u32>,
i32: CastInto<F::Int>,
F::Int: CastInto<i32>,
{
let one = F::Int::ONE;

let sign_bit = F::SIGN_MASK as F::Int;
let abs_mask = sign_bit - one;
let exponent_mask = F::EXPONENT_MASK;
let inf_rep = exponent_mask;

let a_rep = a.repr();
let b_rep = b.repr();
let a_abs = a_rep & abs_mask;
let b_abs = b_rep & abs_mask;

a_abs > inf_rep || b_abs > inf_rep
}

intrinsics! {
pub extern "C" fn __lesf2(a: f32, b: f32) -> i32 {
cmp(a, b).to_le_abi()
}

pub extern "C" fn __gesf2(a: f32, b: f32) -> i32 {
cmp(a, b).to_ge_abi()
}

#[arm_aeabi_alias = fcmpun]
pub extern "C" fn __unordsf2(a: f32, b: f32) -> i32 {
unord(a, b) as i32
}

pub extern "C" fn __eqsf2(a: f32, b: f32) -> i32 {
cmp(a, b).to_le_abi()
}

pub extern "C" fn __ltsf2(a: f32, b: f32) -> i32 {
cmp(a, b).to_le_abi()
}

pub extern "C" fn __nesf2(a: f32, b: f32) -> i32 {
cmp(a, b).to_le_abi()
}

pub extern "C" fn __gtsf2(a: f32, b: f32) -> i32 {
cmp(a, b).to_ge_abi()
}

pub extern "C" fn __ledf2(a: f64, b: f64) -> i32 {
cmp(a, b).to_le_abi()
}

pub extern "C" fn __gedf2(a: f64, b: f64) -> i32 {
cmp(a, b).to_ge_abi()
}

#[arm_aeabi_alias = dcmpun]
pub extern "C" fn __unorddf2(a: f64, b: f64) -> i32 {
unord(a, b) as i32
}

pub extern "C" fn __eqdf2(a: f64, b: f64) -> i32 {
cmp(a, b).to_le_abi()
}

pub extern "C" fn __ltdf2(a: f64, b: f64) -> i32 {
cmp(a, b).to_le_abi()
}

pub extern "C" fn __nedf2(a: f64, b: f64) -> i32 {
cmp(a, b).to_le_abi()
}

pub extern "C" fn __gtdf2(a: f32, b: f32) -> i32 {
cmp(a, b).to_ge_abi()
}
}
17 changes: 14 additions & 3 deletions src/float/mod.rs
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@ use core::ops;
use super::int::Int;

pub mod conv;
pub mod cmp;
pub mod add;
pub mod pow;
pub mod sub;
@@ -25,6 +26,9 @@ pub trait Float:
/// A uint of the same with as the float
type Int: Int;

/// A int of the same with as the float
type SignedInt: Int;

const ZERO: Self;
const ONE: Self;

@@ -58,6 +62,9 @@ pub trait Float:
/// Returns `self` transmuted to `Self::Int`
fn repr(self) -> Self::Int;

/// Returns `self` transmuted to `Self::SignedInt`
fn signed_repr(self) -> Self::SignedInt;

#[cfg(test)]
/// Checks if two floats have the same bit representation. *Except* for NaNs! NaN can be
/// represented in multiple different ways. This method returns `true` if two NaNs are
@@ -77,9 +84,10 @@ pub trait Float:
// FIXME: Some of this can be removed if RFC Issue #1424 is resolved
// https://github.com/rust-lang/rfcs/issues/1424
macro_rules! float_impl {
($ty:ident, $ity:ident, $bits:expr, $significand_bits:expr) => {
($ty:ident, $ity:ident, $sity:ident, $bits:expr, $significand_bits:expr) => {
impl Float for $ty {
type Int = $ity;
type SignedInt = $sity;
const ZERO: Self = 0.0;
const ONE: Self = 1.0;

@@ -94,6 +102,9 @@ macro_rules! float_impl {
fn repr(self) -> Self::Int {
unsafe { mem::transmute(self) }
}
fn signed_repr(self) -> Self::SignedInt {
unsafe { mem::transmute(self) }
}
#[cfg(test)]
fn eq_repr(self, rhs: Self) -> bool {
if self.is_nan() && rhs.is_nan() {
@@ -119,5 +130,5 @@ macro_rules! float_impl {
}
}

float_impl!(f32, u32, 32, 23);
float_impl!(f64, u64, 64, 52);
float_impl!(f32, u32, i32, 32, 23);
float_impl!(f64, u64, i64, 64, 52);
7 changes: 7 additions & 0 deletions tests/gedf2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#![feature(compiler_builtins_lib)]
#![feature(i128_type)]
#![cfg_attr(all(target_arch = "arm", not(any(target_env = "gnu", target_env = "musl")),
target_os = "linux", test),
no_std)]

include!(concat!(env!("OUT_DIR"), "/gedf2.rs"));
7 changes: 7 additions & 0 deletions tests/gesf2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#![feature(compiler_builtins_lib)]
#![feature(i128_type)]
#![cfg_attr(all(target_arch = "arm", not(any(target_env = "gnu", target_env = "musl")),
target_os = "linux", test),
no_std)]

include!(concat!(env!("OUT_DIR"), "/gesf2.rs"));
7 changes: 7 additions & 0 deletions tests/ledf2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#![feature(compiler_builtins_lib)]
#![feature(i128_type)]
#![cfg_attr(all(target_arch = "arm", not(any(target_env = "gnu", target_env = "musl")),
target_os = "linux", test),
no_std)]

include!(concat!(env!("OUT_DIR"), "/ledf2.rs"));
7 changes: 7 additions & 0 deletions tests/lesf2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#![feature(compiler_builtins_lib)]
#![feature(i128_type)]
#![cfg_attr(all(target_arch = "arm", not(any(target_env = "gnu", target_env = "musl")),
target_os = "linux", test),
no_std)]

include!(concat!(env!("OUT_DIR"), "/lesf2.rs"));