Skip to content

Commit

Permalink
Merge pull request #454 from wasmx/fp_utils
Browse files Browse the repository at this point in the history
test: Add floating-point utils
  • Loading branch information
chfast authored Aug 7, 2020
2 parents c04cfeb + 167c41b commit 55801bc
Show file tree
Hide file tree
Showing 5 changed files with 292 additions and 4 deletions.
1 change: 1 addition & 0 deletions test/unittests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ target_sources(
execute_floating_point_test.cpp
execute_numeric_test.cpp
execute_test.cpp
floating_point_utils_test.cpp
instantiate_test.cpp
leb128_test.cpp
module_test.cpp
Expand Down
192 changes: 192 additions & 0 deletions test/unittests/floating_point_utils_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
// Fizzy: A fast WebAssembly interpreter
// Copyright 2020 The Fizzy Authors.
// SPDX-License-Identifier: Apache-2.0

#include <gtest/gtest.h>
#include <test/utils/floating_point_utils.hpp>

using namespace fizzy::test;

TEST(floating_point_utils, double_as_uint)
{
EXPECT_EQ(FP(0.0).as_uint(), 0x0000000000000000);
EXPECT_EQ(FP(-0.0).as_uint(), 0x8000000000000000);
EXPECT_EQ(FP(FP64::Limits::infinity()).as_uint(), 0x7FF'0000000000000);
EXPECT_EQ(FP(-FP64::Limits::infinity()).as_uint(), 0xFFF'0000000000000);
EXPECT_EQ(FP(FP64::Limits::max()).as_uint(), 0x7FE'FFFFFFFFFFFFF);
EXPECT_EQ(FP(-FP64::Limits::max()).as_uint(), 0xFFE'FFFFFFFFFFFFF);
EXPECT_EQ(FP(FP64::Limits::min()).as_uint(), 0x001'0000000000000);
EXPECT_EQ(FP(-FP64::Limits::min()).as_uint(), 0x801'0000000000000);
EXPECT_EQ(FP(FP64::Limits::denorm_min()).as_uint(), 0x000'0000000000001);
EXPECT_EQ(FP(-FP64::Limits::denorm_min()).as_uint(), 0x800'0000000000001);
EXPECT_EQ(FP(1.0).as_uint(), 0x3FF'0000000000000);
EXPECT_EQ(FP(-1.0).as_uint(), 0xBFF'0000000000000);
EXPECT_EQ(FP(std::nextafter(1.0, 0.0)).as_uint(), 0x3FE'FFFFFFFFFFFFF);
EXPECT_EQ(FP(std::nextafter(-1.0, 0.0)).as_uint(), 0xBFE'FFFFFFFFFFFFF);
EXPECT_EQ(FP(FP64::nan(FP64::canon)).as_uint(), 0x7FF'8000000000000);
EXPECT_EQ(FP(-FP64::nan(FP64::canon)).as_uint(), 0xFFF'8000000000000);
}

TEST(floating_point_utils, binary_representation_implementation_defined)
{
EXPECT_EQ(FP(FP64::Limits::quiet_NaN()).as_uint(), 0x7FF'8000000000000);
EXPECT_EQ(FP(FP64::Limits::quiet_NaN()).nan_payload(), 0x8000000000000);
EXPECT_EQ(FP(FP64::Limits::signaling_NaN()).nan_payload(), 0x4000000000000);

EXPECT_EQ(FP(FP32::Limits::quiet_NaN()).as_uint(), 0x7FC00000);
EXPECT_EQ(FP(FP32::Limits::quiet_NaN()).nan_payload(), 0x400000);
EXPECT_EQ(FP(FP32::Limits::signaling_NaN()).nan_payload(), 0x200000);
}

TEST(floating_point_utils, float_as_uint)
{
EXPECT_EQ(FP(0.0f).as_uint(), 0x00000000);
EXPECT_EQ(FP(-0.0f).as_uint(), 0x80000000);
EXPECT_EQ(FP(FP32::Limits::infinity()).as_uint(), 0x7F800000);
EXPECT_EQ(FP(-FP32::Limits::infinity()).as_uint(), 0xFF800000);
EXPECT_EQ(FP(FP32::Limits::max()).as_uint(), 0x7F7FFFFF);
EXPECT_EQ(FP(-FP32::Limits::max()).as_uint(), 0xFF7FFFFF);
EXPECT_EQ(FP(FP32::Limits::min()).as_uint(), 0x00800000);
EXPECT_EQ(FP(-FP32::Limits::min()).as_uint(), 0x80800000);
EXPECT_EQ(FP(FP32::Limits::denorm_min()).as_uint(), 0x00000001);
EXPECT_EQ(FP(-FP32::Limits::denorm_min()).as_uint(), 0x80000001);
EXPECT_EQ(FP(1.0f).as_uint(), 0x3F800000);
EXPECT_EQ(FP(-1.0f).as_uint(), 0xBF800000);
EXPECT_EQ(FP(std::nextafter(1.0f, 0.0f)).as_uint(), 0x3F7FFFFF);
EXPECT_EQ(FP(std::nextafter(-1.0f, 0.0f)).as_uint(), 0xBF7FFFFF);
EXPECT_EQ(FP(FP32::nan(FP32::canon)).as_uint(), 0x7FC00000);
EXPECT_EQ(FP(-FP32::nan(FP32::canon)).as_uint(), 0xFFC00000);
}

TEST(floating_point_utils, double_from_uint)
{
EXPECT_EQ(FP(uint64_t{0x0000000000000000}).value, 0.0);
EXPECT_EQ(FP(uint64_t{0x8000000000000000}).value, -0.0);
EXPECT_EQ(FP(uint64_t{0x3FF'000000000DEAD}).value, 0x1.000000000DEADp0);
EXPECT_EQ(FP(uint64_t{0xBFF'000000000DEAD}).value, -0x1.000000000DEADp0);
EXPECT_EQ(FP(uint64_t{0x7FF'0000000000000}).value, FP64::Limits::infinity());
EXPECT_EQ(FP(uint64_t{0xFFF'0000000000000}).value, -FP64::Limits::infinity());
}

TEST(floating_point_utils, float_from_uint)
{
EXPECT_EQ(FP(uint32_t{0x00000000}).value, 0.0f);
EXPECT_EQ(FP(uint32_t{0x80000000}).value, -0.0f);
EXPECT_EQ(FP(uint32_t{0x3FEF5680}).value, 0x1.DEADp0f);
EXPECT_EQ(FP(uint32_t{0xBFEF5680}).value, -0x1.DEADp0f);
EXPECT_EQ(FP(uint32_t{0x7F800000}).value, FP32::Limits::infinity());
EXPECT_EQ(FP(uint32_t{0xFF800000}).value, -FP32::Limits::infinity());
}

TEST(floating_point_utils, double_nan_payload)
{
constexpr auto inf = FP64::Limits::infinity();
const auto qnan = FP64::nan(FP64::canon);

EXPECT_EQ(FP(0.0).nan_payload(), 0);
EXPECT_EQ(FP(FP64::nan(1)).nan_payload(), 1);
EXPECT_EQ(FP(FP64::nan(FP64::canon + 1)).nan_payload(), FP64::canon + 1);
EXPECT_EQ(FP(qnan).nan_payload(), FP64::canon);
EXPECT_EQ(FP(qnan + 1.0).nan_payload(), FP64::canon);
EXPECT_EQ(FP(inf - inf).nan_payload(), FP64::canon);
EXPECT_EQ(FP(inf * 0.0).nan_payload(), FP64::canon);
}

TEST(floating_point_utils, float_nan_payload)
{
constexpr auto inf = FP32::Limits::infinity();
const auto qnan = FP32::nan(FP32::canon);

EXPECT_EQ(FP(0.0f).nan_payload(), 0);
EXPECT_EQ(FP(FP32::nan(1)).nan_payload(), 1);
EXPECT_EQ(FP(FP32::nan(FP32::canon + 1)).nan_payload(), FP32::canon + 1);
EXPECT_EQ(FP(qnan).nan_payload(), FP32::canon);
EXPECT_EQ(FP(qnan + 1.0f).nan_payload(), FP32::canon);
EXPECT_EQ(FP(inf - inf).nan_payload(), FP32::canon);
EXPECT_EQ(FP(inf * 0.0f).nan_payload(), FP32::canon);
}

TEST(floating_point_utils, double_nan)
{
EXPECT_TRUE(std::isnan(FP64::nan(FP64::canon)));
EXPECT_TRUE(std::isnan(FP64::nan(1)));
EXPECT_TRUE(std::isnan(FP64::nan(0xDEADBEEF)));
EXPECT_TRUE(std::isnan(FP64::nan(0xDEADBEEFBEEEF)));
EXPECT_FALSE(std::isnan(FP64::nan(0)));

EXPECT_EQ(FP{FP64::nan(FP64::canon)}.nan_payload(), FP64::canon);

EXPECT_EQ(FP{FP64::nan(FP64::canon)}.as_uint(), 0x7FF'8000000000000);
EXPECT_EQ(FP{FP64::nan(0xDEADBEEF)}.as_uint(), 0x7FF'00000DEADBEEF);
EXPECT_EQ(FP{FP64::nan(0xDEADBEEFBEEEF)}.as_uint(), 0x7FF'DEADBEEFBEEEF);
}

TEST(floating_point_utils, float_nan)
{
EXPECT_TRUE(std::isnan(FP32::nan(FP32::canon)));
EXPECT_TRUE(std::isnan(FP32::nan(1)));
EXPECT_TRUE(std::isnan(FP32::nan(0x7fffff)));
EXPECT_TRUE(std::isnan(FP32::nan(0x400001)));
EXPECT_FALSE(std::isnan(FP32::nan(0)));

EXPECT_EQ(FP{FP32::nan(FP32::canon)}.nan_payload(), FP32::canon);

EXPECT_EQ(FP{FP32::nan(FP32::canon)}.as_uint(), 0x7FC00000);
EXPECT_EQ(FP{FP32::nan(0x7FFFFF)}.as_uint(), 0x7FFFFFFF);
EXPECT_EQ(FP{FP32::nan(0x400001)}.as_uint(), 0x7FC00001);
}

TEST(floating_point_utils, std_nan)
{
EXPECT_EQ(FP(std::nan("")).nan_payload(), FP64::canon);
EXPECT_EQ(FP(std::nan("1")).nan_payload(), FP64::canon + 1);
EXPECT_EQ(FP(std::nan("0xDEAD")).nan_payload(), FP64::canon + 0xDEAD);
}

TEST(floating_point_utils, compare_double)
{
const auto one = 1.0;
const auto inf = FP64::Limits::infinity();
const auto cnan = FP64::nan(FP64::canon);
const auto snan = FP64::nan(1);

EXPECT_EQ(FP{one}, FP{one});
EXPECT_EQ(FP{one}, one);
EXPECT_EQ(one, FP{one});

EXPECT_EQ(FP{inf}, FP{inf});
EXPECT_EQ(FP{inf}, inf);
EXPECT_EQ(inf, FP{inf});

EXPECT_EQ(FP{cnan}, FP{cnan});
EXPECT_EQ(FP{cnan}, cnan);
EXPECT_EQ(cnan, FP{cnan});

EXPECT_EQ(FP{snan}, FP{snan});
EXPECT_EQ(FP{snan}, snan);
EXPECT_EQ(snan, FP{snan});

EXPECT_NE(FP{one}, FP{inf});
EXPECT_NE(FP{one}, inf);
EXPECT_NE(one, FP{inf});

EXPECT_NE(FP{one}, FP{cnan});
EXPECT_NE(FP{one}, cnan);
EXPECT_NE(one, FP{cnan});

EXPECT_NE(FP{one}, FP{snan});
EXPECT_NE(FP{one}, snan);
EXPECT_NE(one, FP{snan});

EXPECT_NE(FP{inf}, FP{cnan});
EXPECT_NE(FP{inf}, cnan);
EXPECT_NE(inf, FP{cnan});

EXPECT_NE(FP{inf}, FP{snan});
EXPECT_NE(FP{inf}, snan);
EXPECT_NE(inf, FP{snan});

EXPECT_NE(FP{cnan}, FP{snan});
EXPECT_NE(FP{cnan}, snan);
EXPECT_NE(cnan, FP{snan});
}
1 change: 1 addition & 0 deletions test/utils/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ target_sources(
asserts.hpp
execute_helpers.hpp
fizzy_engine.cpp
floating_point_utils.hpp
hex.cpp
hex.hpp
leb128_encode.cpp
Expand Down
7 changes: 3 additions & 4 deletions test/utils/asserts.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include "execute.hpp"
#include <gmock/gmock.h>
#include <test/utils/floating_point_utils.hpp>
#include <iosfwd>

MATCHER(Traps, "") // NOLINT(readability-redundant-string-init)
Expand All @@ -23,10 +24,8 @@ MATCHER_P(Result, value, "") // NOLINT(readability-redundant-string-init)
if (arg.trapped || !arg.has_value)
return false;

if constexpr (std::is_same_v<value_type, float>)
return arg.value.f32 == value;
else if constexpr (std::is_same_v<value_type, double>)
return arg.value.f64 == value;
if constexpr (std::is_floating_point_v<value_type>)
return arg.value.template as<value_type>() == fizzy::test::FP{value};
else // always check 64 bit of result for all integers, including 32-bit results
return arg.value.i64 == static_cast<std::make_unsigned_t<value_type>>(value);
}
Expand Down
95 changes: 95 additions & 0 deletions test/utils/floating_point_utils.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Fizzy: A fast WebAssembly interpreter
// Copyright 2020 The Fizzy Authors.
// SPDX-License-Identifier: Apache-2.0

#pragma once

#include <cmath>
#include <cstdint>
#include <limits>
#include <type_traits>

namespace fizzy::test
{
/// Simple implementation of C++20's std::bit_cast.
template <typename DstT, typename SrcT>
DstT bit_cast(SrcT x) noexcept
{
DstT z;
static_assert(sizeof(x) == sizeof(z));
__builtin_memcpy(&z, &x, sizeof(x));
return z;
}

/// A wrapper for floating-point types with inspection/construction/comparison utilities.
template <typename T>
struct FP
{
static_assert(std::is_same_v<T, float> || std::is_same_v<T, double>);

/// Shortcut to numeric_limits.
using Limits = std::numeric_limits<T>;
static_assert(Limits::is_iec559);

/// The unsigned integer type matching the size of this floating-point type.
using UintType = std::conditional_t<std::is_same_v<T, float>, uint32_t, uint64_t>;
static_assert(sizeof(T) == sizeof(UintType));

/// The number of mantissa bits in the binary representation.
static constexpr auto num_mantissa_bits = Limits::digits - 1;

/// The binary mask of the mantissa part of the binary representation.
static constexpr auto mantissa_mask = (UintType{1} << num_mantissa_bits) - 1;

/// The number of exponent bits in the binary representation.
static constexpr auto num_exponent_bits = int{sizeof(T) * 8} - num_mantissa_bits - 1;

/// The exponent value (all exponent bits set) for NaNs.
static constexpr auto nan_exponent = (UintType{1} << num_exponent_bits) - 1;

/// The payload of the canonical NaN (only the top bit set).
/// See: https://webassembly.github.io/spec/core/syntax/values.html#canonical-nan.
static constexpr auto canon = UintType{1} << (num_mantissa_bits - 1);

T value{};

explicit FP(T v) noexcept : value{v} {};

explicit FP(UintType u) noexcept : value{bit_cast<T>(u)} {};

/// Return unsigned integer with the binary representation of the value.
UintType as_uint() const noexcept { return bit_cast<UintType>(value); }

/// Returns NaN payload if the value is a NaN, otherwise 0 (NaN payload is never 0).
UintType nan_payload() const noexcept
{
return std::isnan(value) ? (as_uint() & mantissa_mask) : 0;
}

/// Build the NaN value with the given payload.
///
/// The NaN values have any sign, all exponent bits set, and non-zero mantissa (otherwise they
/// would be infinities).
/// The IEEE 754 defines quiet NaN as having the top bit of the mantissa set to 1. Wasm calls
/// this NaN _arithmetic_. The arithmetic NaN with the lowest mantissa (the top bit set, all
/// other zeros) is the _canonical_ NaN.
static T nan(UintType payload) noexcept
{
return FP{(nan_exponent << num_mantissa_bits) | (payload & mantissa_mask)}.value;
}

friend bool operator==(FP a, FP b) noexcept { return a.as_uint() == b.as_uint(); }
friend bool operator==(FP a, T b) noexcept { return a == FP{b}; }
friend bool operator==(T a, FP b) noexcept { return FP{a} == b; }

friend bool operator!=(FP a, FP b) noexcept { return !(a == b); }
friend bool operator!=(FP a, T b) noexcept { return a != FP{b}; }
friend bool operator!=(T a, FP b) noexcept { return FP{a} != b; }
};

FP(uint32_t)->FP<float>;
FP(uint64_t)->FP<double>;

using FP32 = FP<float>;
using FP64 = FP<double>;
} // namespace fizzy::test

0 comments on commit 55801bc

Please sign in to comment.