From 6c26a86304c631bf54d4048acb7ce31924ed3be8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Tue, 4 Aug 2020 19:02:59 +0200 Subject: [PATCH 1/3] Implement f32.demote_f64 --- lib/fizzy/execute.cpp | 15 ++++++++++++++- test/unittests/execute_floating_point_test.cpp | 2 +- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/lib/fizzy/execute.cpp b/lib/fizzy/execute.cpp index 32b7496af..a022eae9e 100644 --- a/lib/fizzy/execute.cpp +++ b/lib/fizzy/execute.cpp @@ -541,6 +541,15 @@ inline constexpr T fmax(T a, T b) noexcept return a < b ? b : a; } +__attribute__((no_sanitize("float-cast-overflow"))) inline constexpr float demote( + double value) noexcept +{ + // The float-cast-overflow UBSan check disabled for this conversion. In older clang versions + // (up to 8.0) it reports a failure when non-infinity f64 value is converted to f32 infinity. + // Such behavior is expected. + return static_cast(value); +} + std::optional find_export(const Module& module, ExternalKind kind, std::string_view name) { const auto it = std::find_if(module.exportsec.begin(), module.exportsec.end(), @@ -1759,6 +1768,11 @@ ExecutionResult execute(Instance& instance, FuncIdx func_idx, span convert(stack); break; } + case Instr::f32_demote_f64: + { + stack.top() = demote(stack.top().f64); + break; + } case Instr::f64_convert_i32_s: { convert(stack); @@ -1795,7 +1809,6 @@ ExecutionResult execute(Instance& instance, FuncIdx func_idx, span case Instr::f64_trunc: case Instr::f64_nearest: case Instr::f64_sub: - case Instr::f32_demote_f64: case Instr::i32_reinterpret_f32: case Instr::i64_reinterpret_f64: case Instr::f32_reinterpret_i32: diff --git a/test/unittests/execute_floating_point_test.cpp b/test/unittests/execute_floating_point_test.cpp index e6be094c3..0fd1fb4fd 100644 --- a/test/unittests/execute_floating_point_test.cpp +++ b/test/unittests/execute_floating_point_test.cpp @@ -800,7 +800,7 @@ TEST(execute_floating_point, f64_promote_f32) f64.promote_f32 ) */ - auto wasm = from_hex("0061736d0100000001060160017d017c030201000a070105002000bb0b"); + const auto wasm = from_hex("0061736d0100000001060160017d017c030201000a070105002000bb0b"); auto instance = instantiate(parse(wasm)); const std::pair test_cases[] = { From 821eb740a02e4c4f6078c382e176e61be1596d3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Tue, 4 Aug 2020 19:02:59 +0200 Subject: [PATCH 2/3] test: Add tests for f32.demote_f64 --- .../unittests/execute_floating_point_test.cpp | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/test/unittests/execute_floating_point_test.cpp b/test/unittests/execute_floating_point_test.cpp index 0fd1fb4fd..2ec49d593 100644 --- a/test/unittests/execute_floating_point_test.cpp +++ b/test/unittests/execute_floating_point_test.cpp @@ -850,6 +850,106 @@ TEST(execute_floating_point, f64_promote_f32) EXPECT_GT(FP{res4.value.f64}.nan_payload(), FP64::canon); } +TEST(execute_floating_point, f32_demote_f64) +{ + /* wat2wasm + (func (param f64) (result f32) + local.get 0 + f32.demote_f64 + ) + */ + const auto wasm = from_hex("0061736d0100000001060160017c017d030201000a070105002000b60b"); + auto instance = instantiate(parse(wasm)); + + constexpr double f32_max = FP32::Limits::max(); + ASSERT_EQ(f32_max, 0x1.fffffep127); + + // The "artificial" f32 range limit: the next f32 number that could be represented + // if the exponent had a larger range. + // Wasm spec Rounding section denotes this as the limit_N in the float_N function (for N=32). + // https://webassembly.github.io/spec/core/exec/numerics.html#rounding + constexpr double f32_limit = 0x1p128; // 2**128. + + // The lower boundary input value that results in the infinity. The number is midway between + // f32_max and f32_limit. For this value rounding prefers infinity, because f32_limit is even. + constexpr double lowest_to_inf = (f32_max + f32_limit) / 2; + ASSERT_EQ(lowest_to_inf, 0x1.ffffffp127); + + const std::pair test_cases[] = { + // demote(+-0) = +-0 + {0.0, 0.0f}, + {-0.0, -0.0f}, + + {1.0, 1.0f}, + {-1.0, -1.0f}, + {double{FP32::Limits::lowest()}, FP32::Limits::lowest()}, + {double{FP32::Limits::max()}, FP32::Limits::max()}, + {double{FP32::Limits::min()}, FP32::Limits::min()}, + {double{-FP32::Limits::min()}, -FP32::Limits::min()}, + {double{FP32::Limits::denorm_min()}, FP32::Limits::denorm_min()}, + {double{-FP32::Limits::denorm_min()}, -FP32::Limits::denorm_min()}, + + // Some special f64 values. + {FP64::Limits::lowest(), -FP32::Limits::infinity()}, + {FP64::Limits::max(), FP32::Limits::infinity()}, + {FP64::Limits::min(), 0.0f}, + {-FP64::Limits::min(), -0.0f}, + {FP64::Limits::denorm_min(), 0.0f}, + {-FP64::Limits::denorm_min(), -0.0f}, + + // Out of range values rounded to max/lowest. + {std::nextafter(f32_max, FP64::Limits::infinity()), FP32::Limits::max()}, + {std::nextafter(double{FP32::Limits::lowest()}, -FP64::Limits::infinity()), + FP32::Limits::lowest()}, + + {std::nextafter(lowest_to_inf, 0.0), FP32::Limits::max()}, + {std::nextafter(-lowest_to_inf, 0.0), FP32::Limits::lowest()}, + + // The smallest of range values rounded to infinity. + {lowest_to_inf, FP32::Limits::infinity()}, + {-lowest_to_inf, -FP32::Limits::infinity()}, + + {std::nextafter(lowest_to_inf, FP64::Limits::infinity()), FP32::Limits::infinity()}, + {std::nextafter(-lowest_to_inf, -FP64::Limits::infinity()), -FP32::Limits::infinity()}, + + // float_32(r) = +inf (if r >= +limit_32) + {f32_limit, FP32::Limits::infinity()}, + + // float_32(r) = -inf (if r <= -limit_32) + {-f32_limit, -FP32::Limits::infinity()}, + + // demote(+-inf) = +-inf + {FP64::Limits::infinity(), FP32::Limits::infinity()}, + {-FP64::Limits::infinity(), -FP32::Limits::infinity()}, + + // Rounding. + {0x1.fffffefffffffp0, 0x1.fffffep0f}, // round down + {0x1.fffffe0000000p0, 0x1.fffffep0f}, // exact (odd) + {0x1.fffffd0000001p0, 0x1.fffffep0f}, // round up + + {0x1.fffff8p0, 0x1.fffff8p0f}, // exact (even) + {(0x1.fffff8p0 + 0x1.fffffap0) / 2, 0x1.fffff8p0f}, // tie-to-even down + {0x1.fffffap0, 0x1.fffffap0f}, // exact (odd) + {(0x1.fffffap0 + 0x1.fffffcp0) / 2, 0x1.fffffcp0f}, // tie-to-even up + {0x1.fffffcp0, 0x1.fffffcp0f}, // exact (even) + + // The canonical NaN must result in canonical NaN (only the top bit of payload set). + {FP32::nan(FP32::canon), FP64::nan(FP64::canon)}, + {-FP32::nan(FP32::canon), -FP64::nan(FP64::canon)}, + }; + + for (const auto& [arg, expected] : test_cases) + { + EXPECT_THAT(execute(*instance, 0, {arg}), Result(expected)) << arg << " -> " << expected; + } + + // Any input NaN other than canonical must result in an arithmetic NaN. + EXPECT_THAT(execute(*instance, 0, {FP64::nan(FP64::canon + 1)}), ArithmeticNaN(float{})); + EXPECT_THAT(execute(*instance, 0, {-FP64::nan(FP64::canon + 1)}), ArithmeticNaN(float{})); + EXPECT_THAT(execute(*instance, 0, {FP64::nan(1)}), ArithmeticNaN(float{})); + EXPECT_THAT(execute(*instance, 0, {-FP64::nan(1)}), ArithmeticNaN(float{})); +} + template struct ConversionPairWasmTraits; From b0b31f4e083ed355c2ef1f3ba89c03926d2c69be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Fri, 14 Aug 2020 13:58:00 +0200 Subject: [PATCH 3/3] test: Extend NaN propagation tests for promote and demote --- test/unittests/execute_floating_point_test.cpp | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/test/unittests/execute_floating_point_test.cpp b/test/unittests/execute_floating_point_test.cpp index 2ec49d593..b26177888 100644 --- a/test/unittests/execute_floating_point_test.cpp +++ b/test/unittests/execute_floating_point_test.cpp @@ -92,7 +92,7 @@ struct WasmTypeName template class execute_floating_point_types : public testing::Test { -protected: +public: using L = typename FP::Limits; // The list of positive floating-point values without zeros, infinities and NaNs. @@ -848,6 +848,13 @@ TEST(execute_floating_point, f64_promote_f32) ASSERT_TRUE(!res4.trapped && res4.has_value); EXPECT_EQ(std::signbit(res4.value.f64), 1); EXPECT_GT(FP{res4.value.f64}.nan_payload(), FP64::canon); + + // Any input NaN other than canonical must result in an arithmetic NaN. + for (const auto nan : execute_floating_point_types::positive_noncanonical_nans) + { + EXPECT_THAT(execute(*instance, 0, {nan}), ArithmeticNaN(double{})); + EXPECT_THAT(execute(*instance, 0, {-nan}), ArithmeticNaN(double{})); + } } TEST(execute_floating_point, f32_demote_f64) @@ -944,10 +951,11 @@ TEST(execute_floating_point, f32_demote_f64) } // Any input NaN other than canonical must result in an arithmetic NaN. - EXPECT_THAT(execute(*instance, 0, {FP64::nan(FP64::canon + 1)}), ArithmeticNaN(float{})); - EXPECT_THAT(execute(*instance, 0, {-FP64::nan(FP64::canon + 1)}), ArithmeticNaN(float{})); - EXPECT_THAT(execute(*instance, 0, {FP64::nan(1)}), ArithmeticNaN(float{})); - EXPECT_THAT(execute(*instance, 0, {-FP64::nan(1)}), ArithmeticNaN(float{})); + for (const auto nan : execute_floating_point_types::positive_noncanonical_nans) + { + EXPECT_THAT(execute(*instance, 0, {nan}), ArithmeticNaN(float{})); + EXPECT_THAT(execute(*instance, 0, {-nan}), ArithmeticNaN(float{})); + } }