diff --git a/test/unittests/CMakeLists.txt b/test/unittests/CMakeLists.txt index 227190b04d..0ae155c990 100644 --- a/test/unittests/CMakeLists.txt +++ b/test/unittests/CMakeLists.txt @@ -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 diff --git a/test/unittests/floating_point_utils_test.cpp b/test/unittests/floating_point_utils_test.cpp new file mode 100644 index 0000000000..4fdf4c4779 --- /dev/null +++ b/test/unittests/floating_point_utils_test.cpp @@ -0,0 +1,106 @@ +// Fizzy: A fast WebAssembly interpreter +// Copyright 2020 The Fizzy Authors. +// SPDX-License-Identifier: Apache-2.0 + +#include +#include + +using namespace fizzy::test; + +TEST(floating_point_utils, double_as_uint64) +{ + using Limits = std::numeric_limits; + + EXPECT_EQ(as_uint64(0.0), 0x0000000000000000); + EXPECT_EQ(as_uint64(-0.0), 0x8000000000000000); + EXPECT_EQ(as_uint64(Limits::infinity()), 0x7FF'0000000000000); + EXPECT_EQ(as_uint64(-Limits::infinity()), 0xFFF'0000000000000); + EXPECT_EQ(as_uint64(Limits::max()), 0x7FE'FFFFFFFFFFFFF); + EXPECT_EQ(as_uint64(-Limits::max()), 0xFFE'FFFFFFFFFFFFF); + EXPECT_EQ(as_uint64(Limits::min()), 0x001'0000000000000); + EXPECT_EQ(as_uint64(-Limits::min()), 0x801'0000000000000); + EXPECT_EQ(as_uint64(Limits::denorm_min()), 0x000'0000000000001); + EXPECT_EQ(as_uint64(-Limits::denorm_min()), 0x800'0000000000001); + EXPECT_EQ(as_uint64(1.0), 0x3FF'0000000000000); + EXPECT_EQ(as_uint64(-1.0), 0xBFF'0000000000000); + EXPECT_EQ(as_uint64(std::nextafter(1.0, 0.0)), 0x3FE'FFFFFFFFFFFFF); + EXPECT_EQ(as_uint64(std::nextafter(-1.0, 0.0)), 0xBFE'FFFFFFFFFFFFF); + EXPECT_EQ(as_uint64(Limits::quiet_NaN()), 0x7FF'8000000000000); + EXPECT_EQ(as_uint64(-Limits::quiet_NaN()), 0xFFF'8000000000000); +} + +TEST(floating_point_utils, float_as_uint64) +{ + using Limits = std::numeric_limits; + + EXPECT_EQ(as_uint64(0.0f), 0x00000000); + EXPECT_EQ(as_uint64(-0.0f), 0x80000000); + EXPECT_EQ(as_uint64(Limits::infinity()), 0x7F800000); + EXPECT_EQ(as_uint64(-Limits::infinity()), 0xFF800000); + EXPECT_EQ(as_uint64(Limits::max()), 0x7F7FFFFF); + EXPECT_EQ(as_uint64(-Limits::max()), 0xFF7FFFFF); + EXPECT_EQ(as_uint64(Limits::min()), 0x00800000); + EXPECT_EQ(as_uint64(-Limits::min()), 0x80800000); + EXPECT_EQ(as_uint64(Limits::denorm_min()), 0x00000001); + EXPECT_EQ(as_uint64(-Limits::denorm_min()), 0x80000001); + EXPECT_EQ(as_uint64(1.0f), 0x3F800000); + EXPECT_EQ(as_uint64(-1.0f), 0xBF800000); + EXPECT_EQ(as_uint64(std::nextafter(1.0f, 0.0f)), 0x3F7FFFFF); + EXPECT_EQ(as_uint64(std::nextafter(-1.0f, 0.0f)), 0xBF7FFFFF); + EXPECT_EQ(as_uint64(Limits::quiet_NaN()), 0x7FC00000); + EXPECT_EQ(as_uint64(-Limits::quiet_NaN()), 0xFFC00000); +} + +TEST(floating_point_utils, double_nan_payload) +{ + using Limits = std::numeric_limits; + constexpr auto qnan = Limits::quiet_NaN(); + + EXPECT_EQ(get_nan_payload(0.0), 0); + EXPECT_EQ(get_nan_payload(Limits::signaling_NaN()), 0x4000000000000); + EXPECT_EQ(get_nan_payload(qnan), 0x8000000000000); // Wasm canonical nan. + EXPECT_EQ(get_nan_payload(qnan + 1.0), 0x8000000000000); + EXPECT_EQ(get_nan_payload(Limits::infinity() - Limits::infinity()), 0x8000000000000); +} + +TEST(floating_point_utils, float_nan_payload) +{ + using Limits = std::numeric_limits; + constexpr auto qnan = Limits::quiet_NaN(); + + EXPECT_EQ(get_nan_payload(0.0f), 0); + EXPECT_EQ(get_nan_payload(Limits::signaling_NaN()), 0x200000); + EXPECT_EQ(get_nan_payload(qnan), 0x400000); // Wasm canonical nan. + EXPECT_EQ(get_nan_payload(qnan + 1.0f), 0x400000); + EXPECT_EQ(get_nan_payload(Limits::infinity() - Limits::infinity()), 0x400000); +} + +TEST(floating_point_utils, double_nan) +{ + EXPECT_TRUE(std::isnan(nan())); + EXPECT_TRUE(std::isnan(nan(1))); + EXPECT_TRUE(std::isnan(nan(0xdeadbeef))); + EXPECT_TRUE(std::isnan(nan(0xdeadbeefbeeef))); + EXPECT_FALSE(std::isnan(nan(0))); + + EXPECT_EQ(get_nan_payload(nan()), FPUtils::CanonicalNaNPayload); + + EXPECT_EQ(as_uint64(nan()), 0x7FF'8000000000000); + EXPECT_EQ(as_uint64(nan(0xdeadbeef)), 0x7FF'00000deadbeef); + EXPECT_EQ(as_uint64(nan(0xdeadbeefbeeef)), 0x7FF'deadbeefbeeef); +} + +TEST(floating_point_utils, float_nan) +{ + EXPECT_TRUE(std::isnan(nan())); + EXPECT_TRUE(std::isnan(nan(1))); + EXPECT_TRUE(std::isnan(nan(0x7fffff))); + EXPECT_TRUE(std::isnan(nan(0x400001))); + EXPECT_FALSE(std::isnan(nan(0))); + + EXPECT_EQ(get_nan_payload(nan()), FPUtils::CanonicalNaNPayload); + + EXPECT_EQ(as_uint64(nan()), 0x7FC00000); + EXPECT_EQ(as_uint64(nan(0x7fffff)), 0x7FFFFFFF); + EXPECT_EQ(as_uint64(nan(0x400001)), 0x7FC00001); +} diff --git a/test/utils/CMakeLists.txt b/test/utils/CMakeLists.txt index f1c166e0c0..f03d76ba61 100644 --- a/test/utils/CMakeLists.txt +++ b/test/utils/CMakeLists.txt @@ -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 diff --git a/test/utils/floating_point_utils.hpp b/test/utils/floating_point_utils.hpp new file mode 100644 index 0000000000..9f4313ec22 --- /dev/null +++ b/test/utils/floating_point_utils.hpp @@ -0,0 +1,68 @@ +// Fizzy: A fast WebAssembly interpreter +// Copyright 2020 The Fizzy Authors. +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include +#include +#include + +namespace fizzy::test +{ +template >> +struct FPUtils +{ + using Limits = std::numeric_limits; + + static constexpr auto NumMantissaBits = Limits::digits - 1; + + static constexpr auto CanonicalNaNPayload = uint64_t{1} << (NumMantissaBits - 1); +}; + +template >> +uint64_t as_uint64(T value) noexcept +{ + constexpr auto num_bytes = sizeof(T); + uint8_t bytes[num_bytes]; + __builtin_memcpy(bytes, &value, num_bytes); + + uint64_t u = 0; + for (size_t i = 0; i < num_bytes; ++i) + u |= uint64_t{bytes[i]} << (i * 8); // little-endian + return u; +} + +template >> +T from_uint64(uint64_t value) noexcept +{ + constexpr auto num_bytes = sizeof(T); + uint8_t bytes[num_bytes]; + + for (size_t i = 0; i < num_bytes; ++i) + bytes[i] = static_cast(value >> (i * 8)); // little-endian + + T result; + __builtin_memcpy(&result, bytes, num_bytes); + return result; +} + +template >> +uint64_t get_nan_payload(T value) noexcept +{ + if (!std::isnan(value)) + return 0; // NaN payload is never 0. + + constexpr auto mask = (uint64_t{1} << FPUtils::NumMantissaBits) - 1; + return as_uint64(value) & mask; +} + +template >> +T nan(uint64_t payload = FPUtils::CanonicalNaNPayload) noexcept +{ + using Utils = FPUtils; + const auto nan_exponent = (uint64_t{Utils::Limits::max_exponent} << 1) - 1; + return from_uint64(((nan_exponent << Utils::NumMantissaBits)) | payload); +} +} // namespace fizzy::test