Skip to content
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

float tests fails on wasm32-unknown-emscripten #42630

Closed
malbarbo opened this issue Jun 13, 2017 · 7 comments
Closed

float tests fails on wasm32-unknown-emscripten #42630

malbarbo opened this issue Jun 13, 2017 · 7 comments
Labels
A-testsuite Area: The testsuite used to check the correctness of rustc C-bug Category: This is a bug. O-wasm Target: WASM (WebAssembly), http://webassembly.org/

Comments

@malbarbo
Copy link
Contributor

This tests (taken from rust tests):

#![feature(dec2flt)]

#![allow(unused_macros, unused_imports, dead_code)]

extern crate core;

use std::num::FpCategory::*;
use std::num::FpCategory as Fp;
use core::num::dec2flt::rawfp::{RawFloat, Unpacked};

const SOME_FLOATS: [f64; 9] =
    [0.1f64, 33.568, 42.1e-5, 777.0e9, 1.1111, 0.347997,
     9843579834.35892, 12456.0e-150, 54389573.0e-150];

// src/libcore/tests/num/dec2flt/mod.rs

// Take a float literal, turn it into a string in various ways (that are all trusted
// to be correct) and see if those strings are parsed back to the value of the literal.
// Requires a *polymorphic literal*, i.e. one that can serve as f64 as well as f32.
macro_rules! test_literal {
    ($x: expr) => ({
        let x32: f32 = $x;
        let x64: f64 = $x;
        let inputs = &[stringify!($x).into(), format!("{:?}", x64), format!("{:e}", x64)];
        for input in inputs {
            assert_eq!(input.parse(), Ok(x64));
            assert_eq!(input.parse(), Ok(x32));
            let neg_input = &format!("-{}", input);
            assert_eq!(neg_input.parse(), Ok(-x64));
            assert_eq!(neg_input.parse(), Ok(-x32));
        }
    })
}

#[test]
fn ordinary() {
    test_literal!(1.0);
    test_literal!(3e-5);
    test_literal!(0.1);
    test_literal!(12345.);
    test_literal!(0.9999999);
    test_literal!(2.2250738585072014e-308);
}

#[test]
fn special_code_paths() {
    test_literal!(36893488147419103229.0); // 2^65 - 3, triggers half-to-even with even significand
    test_literal!(101e-33); // Triggers the tricky underflow case in AlgorithmM (for f32)
    test_literal!(1e23); // Triggers AlgorithmR
    test_literal!(2075e23); // Triggers another path through AlgorithmR
    test_literal!(8713e-23); // ... and yet another.
}


// src/libcore/tests/num/dec2flt/rawfp.rs

#[test]
fn prev_float_monotonic() {
    let mut x = 1.0;
    for _ in 0..100 {
        let x1 = prev_float(x);
        assert!(x1 < x);
        assert!(x - x1 < 1e-15);
        x = x1;
    }
}

#[test]
fn next_prev_identity() {
    for &x in &SOME_FLOATS {
        assert_eq!(prev_float(next_float(x)), x);
        assert_eq!(prev_float(prev_float(next_float(next_float(x)))), x);
        assert_eq!(next_float(prev_float(x)), x);
        assert_eq!(next_float(next_float(prev_float(prev_float(x)))), x);
    }
}


/// Inverse of `RawFloat::unpack()` for normalized numbers.
/// Panics if the significand or exponent are not valid for normalized numbers.
pub fn encode_normal<T: RawFloat>(x: Unpacked) -> T {
    debug_assert!(T::MIN_SIG <= x.sig && x.sig <= T::MAX_SIG,
        "encode_normal: significand not normalized");
    // Remove the hidden bit
    let sig_enc = x.sig & !(1 << T::EXPLICIT_SIG_BITS);
    // Adjust the exponent for exponent bias and mantissa shift
    let k_enc = x.k + T::MAX_EXP + T::EXPLICIT_SIG_BITS as i16;
    debug_assert!(k_enc != 0 && k_enc < T::MAX_ENCODED_EXP,
        "encode_normal: exponent out of range");
    // Leave sign bit at 0 ("+"), our numbers are all positive
    let bits = (k_enc as u64) << T::EXPLICIT_SIG_BITS | sig_enc;
    T::from_bits(bits)
}

/// Find the largest floating point number strictly smaller than the argument.
/// Does not handle subnormals, zero, or exponent underflow.
pub fn prev_float<T: RawFloat>(x: T) -> T {
    match x.classify() {
        Infinite => panic!("prev_float: argument is infinite"),
        Nan => panic!("prev_float: argument is NaN"),
        Subnormal => panic!("prev_float: argument is subnormal"),
        Zero => panic!("prev_float: argument is zero"),
        Normal => {
            let Unpacked { sig, k } = x.unpack();
            if sig == T::MIN_SIG {
                encode_normal(Unpacked::new(T::MAX_SIG, k - 1))
            } else {
                encode_normal(Unpacked::new(sig - 1, k))
            }
        }
    }
}

// Find the smallest floating point number strictly larger than the argument.
// This operation is saturating, i.e. next_float(inf) == inf.
// Unlike most code in this module, this function does handle zero, subnormals, and infinities.
// However, like all other code here, it does not deal with NaN and negative numbers.
pub fn next_float<T: RawFloat>(x: T) -> T {
    match x.classify() {
        Nan => panic!("next_float: argument is NaN"),
        Infinite => T::INFINITY,
        // This seems too good to be true, but it works.
        // 0.0 is encoded as the all-zero word. Subnormals are 0x000m...m where m is the mantissa.
        // In particular, the smallest subnormal is 0x0...01 and the largest is 0x000F...F.
        // The smallest normal number is 0x0010...0, so this corner case works as well.
        // If the increment overflows the mantissa, the carry bit increments the exponent as we
        // want, and the mantissa bits become zero. Because of the hidden bit convention, this
        // too is exactly what we want!
        // Finally, f64::MAX + 1 = 7eff...f + 1 = 7ff0...0 = f64::INFINITY.
        Zero | Subnormal | Normal => {
            let bits: u64 = x.transmute();
            T::from_bits(bits + 1)
        }
    }
}


// src/libcore/tests/num/mod.rs
#[test]
fn test_f32f64() {
    use core::f32;

    let max: f64 = f32::MAX.into();
    assert_eq!(max as f32, f32::MAX);
    assert!(max.is_normal());

    let min: f64 = f32::MIN.into();
    assert_eq!(min as f32, f32::MIN);
    assert!(min.is_normal());

    let min_positive: f64 = f32::MIN_POSITIVE.into();
    assert_eq!(min_positive as f32, f32::MIN_POSITIVE);
    assert!(min_positive.is_normal());

    let epsilon: f64 = f32::EPSILON.into();
    assert_eq!(epsilon as f32, f32::EPSILON);
    assert!(epsilon.is_normal());

    let zero: f64 = (0.0f32).into();
    assert_eq!(zero as f32, 0.0f32);
    assert!(zero.is_sign_positive());

    let neg_zero: f64 = (-0.0f32).into();
    assert_eq!(neg_zero as f32, -0.0f32);
    assert!(neg_zero.is_sign_negative());

    let infinity: f64 = f32::INFINITY.into();
    assert_eq!(infinity as f32, f32::INFINITY);
    assert!(infinity.is_infinite());
    assert!(infinity.is_sign_positive());

    let neg_infinity: f64 = f32::NEG_INFINITY.into();
    assert_eq!(neg_infinity as f32, f32::NEG_INFINITY);
    assert!(neg_infinity.is_infinite());
    assert!(neg_infinity.is_sign_negative());

    let nan: f64 = f32::NAN.into();
    assert!(nan.is_nan());
}


// src/libstd/f64.rs
#[test]
fn test_one() {
    let one: f64 = 1.0f64;
    assert_eq!(1.0, one);
    assert!(!one.is_infinite());
    assert!(one.is_finite());
    assert!(one.is_sign_positive());
    assert!(!one.is_sign_negative());
    assert!(!one.is_nan());
    assert!(one.is_normal());
    assert_eq!(Fp::Normal, one.classify());
}

#[test]
fn test_is_normal() {
    use std::f64::*;
    let nan: f64 = NAN;
    let inf: f64 = INFINITY;
    let neg_inf: f64 = NEG_INFINITY;
    let zero: f64 = 0.0f64;
    let neg_zero: f64 = -0.0;
    assert!(!nan.is_normal());
    assert!(!inf.is_normal());
    assert!(!neg_inf.is_normal());
    assert!(!zero.is_normal());
    assert!(!neg_zero.is_normal());
    assert!(1f64.is_normal());
    assert!(1e-307f64.is_normal());
    assert!(!1e-308f64.is_normal());
}

#[test]
fn test_classify() {
    use std::f64::*;
    let nan: f64 = NAN;
    let inf: f64 = INFINITY;
    let neg_inf: f64 = NEG_INFINITY;
    let zero: f64 = 0.0f64;
    let neg_zero: f64 = -0.0;
    assert_eq!(nan.classify(), Fp::Nan);
    assert_eq!(inf.classify(), Fp::Infinite);
    assert_eq!(neg_inf.classify(), Fp::Infinite);
    assert_eq!(zero.classify(), Fp::Zero);
    assert_eq!(neg_zero.classify(), Fp::Zero);
    assert_eq!(1e-307f64.classify(), Fp::Normal);
    assert_eq!(1e-308f64.classify(), Fp::Subnormal);
}

fails on wasm32-unknown-emscripten. I think that this as bug in asm2wasm because the same tests works on asmjs-unknown-emscripten. I used cross to run the tests (it is necessary to create a project and put the code in lib.rs):

cross test --target asmjs-unknown-emscripten # works
cross test --target wasm32-unknown-emscripten # fails

Tests results:

running 8 tests
test next_prev_identity ... FAILED
test ordinary ... FAILED
test prev_float_monotonic ... FAILED
test special_code_paths ... FAILED
test test_classify ... FAILED
test test_f32f64 ... FAILED
test test_is_normal ... FAILED
test test_one ... FAILED

failures:

---- next_prev_identity stdout ----
	thread 'main' panicked at 'prev_float: argument is subnormal', src/lib.rs:101
note: Run with `RUST_BACKTRACE=1` for a backtrace.

---- ordinary stdout ----
	thread 'main' panicked at 'assertion failed: `(left == right)` (left: `Ok(0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002225073858507201)`, right: `Ok(0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002225073858507201)`)', src/lib.rs:42

---- prev_float_monotonic stdout ----
	thread 'main' panicked at 'prev_float: argument is subnormal', src/lib.rs:101

---- special_code_paths stdout ----
	thread 'main' panicked at 'assertion failed: `(left == right)` (left: `Ok(36893488147419100000)`, right: `Ok(36893488147419100000)`)', src/lib.rs:47

---- test_classify stdout ----
	thread 'main' panicked at 'assertion failed: `(left == right)` (left: `Subnormal`, right: `Normal`)', src/lib.rs:227

---- test_f32f64 stdout ----
	thread 'main' panicked at 'assertion failed: max.is_normal()', src/lib.rs:145

---- test_is_normal stdout ----
	thread 'main' panicked at 'assertion failed: 1f64.is_normal()', src/lib.rs:209

---- test_one stdout ----
	thread 'main' panicked at 'assertion failed: one.is_normal()', src/lib.rs:192


failures:
    next_prev_identity
    ordinary
    prev_float_monotonic
    special_code_paths
    test_classify
    test_f32f64
    test_is_normal
    test_one

test result: FAILED. 0 passed; 8 failed; 0 ignored; 0 measured; 0 filtered out
malbarbo added a commit to malbarbo/rust that referenced this issue Jun 13, 2017
bors added a commit that referenced this issue Jun 16, 2017
Add a travis builder for wasm32-unknown-emscripten

This commits add an entry to travis matrix that will execute wasm32-unknown-emscripten tests suites.

- Emscripten for asmjs was updated to sdk-1.37.13-64bit
- The tests are run with node 8.0.0 (it can execute wasm)
- A wrapper script is used to run each test from the directory where it is (workaround for emscripten-core/emscripten#4542)
- Some tests are ignore, see #42629 and #42630
@Mark-Simulacrum Mark-Simulacrum added A-testsuite Area: The testsuite used to check the correctness of rustc O-wasm Target: WASM (WebAssembly), http://webassembly.org/ labels Jun 23, 2017
@pepyakin
Copy link
Contributor

pepyakin commented Jul 23, 2017

Looks like one culprit is classify.

I managed to minimize example:

use std::num::FpCategory as Fp;

fn repr2() -> Fp {
    const EXP_MASK: u64 = 0x7ff0000000000000;

    let man: u64 = 0;
    let exp: u64 = 0x3FF0000000000000;

    match (man, exp) {
        (0, EXP_MASK) => Fp::Infinite,
        (0, 0) => Fp::Zero,
        _ => Fp::Normal,
    }
}

fn main() {
    let class = repr2();
    assert_eq!(class, Fp::Normal);
}

This passes on asmjs-unknown-emscripten but fails on wasm32-unknown-emscripten.

Outputs: https://gist.github.com/pepyakin/89e31cb565662b216a794875e6258da1

@pepyakin
Copy link
Contributor

pepyakin commented Jul 23, 2017

I finally figured it out.

Let's consider further minified example:

fn main() {
    let x: u64 = 0;
    match x {
        0x7ff0000000000000 => panic!("1"),
        0x7ff0000000000001 => panic!("2"),
        _ => {}
    };
}

asmjs version of switch looks fine:

switch (i64($0)) {
 case i64_const(0,2146435072):  {
  __ZN3std9panicking15begin_panic_new17hd7ebc2b159ba3f26E(5257,1,3232); //@line 47 "main.rs"
  // unreachable; //@line 47 "main.rs"
  break;
 }
 case i64_const(1,2146435072):  {
  __ZN3std9panicking15begin_panic_new17hd7ebc2b159ba3f26E(5256,1,3248); //@line 48 "main.rs"
  // unreachable; //@line 48 "main.rs"
  break;
 }

Then let's look on wasm output produced for this asmjs (only first arm 0x7ff0000000000000):

   (block $switch-case0
     (block $switch-case
      (br_table $switch-case $switch-case0 $switch-default
       (i32.wrap/i64
        (i64.sub
         (get_local $$0)
         (i64.const 9218868437227405312)
        )
       )
      )
     )
     (block
      (call $__ZN3std9panicking15begin_panic_new17hd7ebc2b159ba3f26E
       (i32.const 5257)
       (i32.const 1)
       (i32.const 3232)
      )
      (br $switch)
     )
    )

If I get it right, i32.wrap/i64 takes i64 and truncates it to i32, leaving only lower 32 bits.
So i32.wrap/i64 (i32.const -9218868437227405312) produces 0.

br_table $switch-case $switch-case0 $switch-default (i32.const 0) is same as
br $switch-case, which means first match arm taken and panic!("1") is executed.

@pepyakin
Copy link
Contributor

pepyakin commented Jul 23, 2017

Test ordinary can be minimized to the following example:

fn main() {
    let x64: f64 = 2.2250738585072014e-308;
    let a: String = format!("{:?}", x64);
    let b: String = "0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000022250738585072014".into();
    assert_eq!(a, b);
}

@pepyakin
Copy link
Contributor

pepyakin commented Jul 25, 2017

It seems that test ordinary is fails because of the same issue with match.
Good news: there is fix already here WebAssembly/binaryen#1111 and all tests in this issue passes with it!

@malbarbo
Copy link
Contributor Author

This is great @pepyakin! Thanks for tracking this!

@pepyakin
Copy link
Contributor

WebAssembly/binaryen#1111 is merged.

@alexcrichton
Copy link
Member

Closing due to upstream fix merged, yay!

kennytm added a commit to kennytm/rust that referenced this issue Jan 12, 2018
Re-enable num tests on wasm

Issue rust-lang#42630 was closed but the tests are still ignored, supposedly they should pass now.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-testsuite Area: The testsuite used to check the correctness of rustc C-bug Category: This is a bug. O-wasm Target: WASM (WebAssembly), http://webassembly.org/
Projects
None yet
Development

No branches or pull requests

4 participants