Skip to content

float tests fails on wasm32-unknown-emscripten #42630

Closed

Description

@malbarbo

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

Activity

  1. added
    A-testsuiteArea: The testsuite used to check the correctness of rustc
    O-wasmTarget: WASM (WebAssembly), http://webassembly.org/
    on Jun 23, 2017
  2. pepyakin commented on Jul 23, 2017

    @pepyakin
    Contributor

    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

  3. pepyakin commented on Jul 23, 2017

    @pepyakin
    Contributor

    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.

  4. pepyakin commented on Jul 23, 2017

    @pepyakin
    Contributor

    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);
    }
  5. pepyakin commented on Jul 25, 2017

    @pepyakin
    Contributor

    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!

  6. malbarbo commented on Jul 25, 2017

    @malbarbo
    ContributorAuthor

    This is great @pepyakin! Thanks for tracking this!

  7. pepyakin commented on Jul 26, 2017

    @pepyakin
    Contributor
  8. alexcrichton commented on Aug 27, 2017

    @alexcrichton
    Member

    Closing due to upstream fix merged, yay!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-testsuiteArea: The testsuite used to check the correctness of rustcC-bugCategory: This is a bug.O-wasmTarget: WASM (WebAssembly), http://webassembly.org/

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions