From 4255355bfaccb6b7fc81acc360e854a2a23c720c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Fri, 31 Jul 2020 09:30:18 +0200 Subject: [PATCH] Implement float->int conversion instructions --- lib/fizzy/execute.cpp | 54 ++++- .../unittests/execute_floating_point_test.cpp | 185 +++++++++++++++++- 2 files changed, 230 insertions(+), 9 deletions(-) diff --git a/lib/fizzy/execute.cpp b/lib/fizzy/execute.cpp index b3c97eeec9..e8bdc88b85 100644 --- a/lib/fizzy/execute.cpp +++ b/lib/fizzy/execute.cpp @@ -324,6 +324,12 @@ inline DstT extend(SrcT in) noexcept return DstT{in}; } +template +inline void trunc(OperandStack& stack) noexcept +{ + stack.top() = static_cast(stack.top().as()); +} + template inline bool load_from_memory( bytes_view memory, OperandStack& stack, const uint8_t*& immediates) noexcept @@ -1375,6 +1381,26 @@ ExecutionResult execute(Instance& instance, FuncIdx func_idx, span stack.push(static_cast(stack.pop())); break; } + case Instr::i32_trunc_f32_s: + { + trunc(stack); + break; + } + case Instr::i32_trunc_f32_u: + { + trunc(stack); + break; + } + case Instr::i32_trunc_f64_s: + { + trunc(stack); + break; + } + case Instr::i32_trunc_f64_u: + { + trunc(stack); + break; + } case Instr::i64_extend_i32_s: { const auto value = static_cast(stack.pop()); @@ -1386,6 +1412,26 @@ ExecutionResult execute(Instance& instance, FuncIdx func_idx, span // effectively no-op break; } + case Instr::i64_trunc_f32_s: + { + trunc(stack); + break; + } + case Instr::i64_trunc_f32_u: + { + trunc(stack); + break; + } + case Instr::i64_trunc_f64_s: + { + trunc(stack); + break; + } + case Instr::i64_trunc_f64_u: + { + trunc(stack); + break; + } case Instr::f32_add: { binary_op(stack, std::plus{}); @@ -1439,14 +1485,6 @@ ExecutionResult execute(Instance& instance, FuncIdx func_idx, span case Instr::f64_min: case Instr::f64_max: case Instr::f64_copysign: - case Instr::i32_trunc_f32_s: - case Instr::i32_trunc_f32_u: - case Instr::i32_trunc_f64_s: - case Instr::i32_trunc_f64_u: - case Instr::i64_trunc_f32_s: - case Instr::i64_trunc_f32_u: - case Instr::i64_trunc_f64_s: - case Instr::i64_trunc_f64_u: case Instr::f32_convert_i32_s: case Instr::f32_convert_i32_u: case Instr::f32_convert_i64_s: diff --git a/test/unittests/execute_floating_point_test.cpp b/test/unittests/execute_floating_point_test.cpp index 1a087982b8..613237d21a 100644 --- a/test/unittests/execute_floating_point_test.cpp +++ b/test/unittests/execute_floating_point_test.cpp @@ -6,6 +6,7 @@ #include "parser.hpp" #include #include +#include using namespace fizzy; using namespace fizzy::test; @@ -69,4 +70,186 @@ TEST(execute_floating_point, f64_add) auto instance = instantiate(parse(wasm)); const auto result = execute(*instance, 0, {1.0011, 6.0066}); EXPECT_EQ(result.value.f64, 7.0077); -} \ No newline at end of file +} + + +template +struct ConversionPairWasmTraits; + +template <> +struct ConversionPairWasmTraits +{ + static constexpr auto opcode_name = "i32_trunc_f32_s"; + static constexpr auto opcode = Instr::i32_trunc_f32_s; + static constexpr auto src_valtype = ValType::f32; + static constexpr auto dst_valtype = ValType::i32; +}; +template <> +struct ConversionPairWasmTraits +{ + static constexpr auto opcode_name = "i32_trunc_f32_u"; + static constexpr auto opcode = Instr::i32_trunc_f32_u; + static constexpr auto src_valtype = ValType::f32; + static constexpr auto dst_valtype = ValType::i32; +}; +template <> +struct ConversionPairWasmTraits +{ + static constexpr auto opcode_name = "i32_trunc_f64_s"; + static constexpr auto opcode = Instr::i32_trunc_f64_s; + static constexpr auto src_valtype = ValType::f64; + static constexpr auto dst_valtype = ValType::i32; +}; +template <> +struct ConversionPairWasmTraits +{ + static constexpr auto opcode_name = "i32_trunc_f64_u"; + static constexpr auto opcode = Instr::i32_trunc_f64_u; + static constexpr auto src_valtype = ValType::f64; + static constexpr auto dst_valtype = ValType::i32; +}; +template <> +struct ConversionPairWasmTraits +{ + static constexpr auto opcode_name = "i64_trunc_f32_s"; + static constexpr auto opcode = Instr::i64_trunc_f32_s; + static constexpr auto src_valtype = ValType::f32; + static constexpr auto dst_valtype = ValType::i64; +}; +template <> +struct ConversionPairWasmTraits +{ + static constexpr auto opcode_name = "i64_trunc_f32_u"; + static constexpr auto opcode = Instr::i64_trunc_f32_u; + static constexpr auto src_valtype = ValType::f32; + static constexpr auto dst_valtype = ValType::i64; +}; +template <> +struct ConversionPairWasmTraits +{ + static constexpr auto opcode_name = "i64_trunc_f64_s"; + static constexpr auto opcode = Instr::i64_trunc_f64_s; + static constexpr auto src_valtype = ValType::f64; + static constexpr auto dst_valtype = ValType::i64; +}; +template <> +struct ConversionPairWasmTraits +{ + static constexpr auto opcode_name = "i64_trunc_f64_u"; + static constexpr auto opcode = Instr::i64_trunc_f64_u; + static constexpr auto src_valtype = ValType::f64; + static constexpr auto dst_valtype = ValType::i64; +}; + +template +struct ConversionPair : ConversionPairWasmTraits +{ + using src_type = SrcT; + using dst_type = DstT; + + using src_limits = std::numeric_limits; + using dst_limits = std::numeric_limits; + + static src_type max_defined() noexcept + { + const auto p = std::pow(src_type{2}, src_type{dst_limits::digits}); + if (dst_limits::digits < src_limits::digits) + return p - 1; // Lossless conversion. + return std::nextafter(p, src_type{0}); + } + + static src_type min_defined() noexcept + { + return dst_limits::is_signed ? -std::pow(src_type{2}, src_type{dst_limits::digits}) : 0; + } +}; + +template +class execute_floating_point_conversions : public testing::Test +{ +}; + +struct ConversionName +{ + template + static std::string GetName(int /*unused*/) + { + return T::opcode_name; + } +}; + +using pairs = testing::Types, ConversionPair, + ConversionPair, ConversionPair, + ConversionPair, ConversionPair, + ConversionPair, ConversionPair>; +TYPED_TEST_SUITE(execute_floating_point_conversions, pairs, ConversionName); + +TYPED_TEST(execute_floating_point_conversions, trunc) +{ + using src_type = typename TypeParam::src_type; + using dst_type = typename TypeParam::dst_type; + + /* wat2wasm + (func (param f32) (result i32) + local.get 0 + i32.trunc_f32_s + ) + */ + auto wasm = from_hex("0061736d0100000001060160017d017f030201000a070105002000a80b"); + + // Find and replace changeable values: types and the conversion instruction. + auto& param_type = wasm[wasm.find(static_cast(ValType::f32))]; + auto& result_type = wasm[wasm.find(static_cast(ValType::i32))]; + auto& opcode = wasm[wasm.find(static_cast(Instr::i32_trunc_f32_s))]; + param_type = static_cast(TypeParam::src_valtype); + result_type = static_cast(TypeParam::dst_valtype); + opcode = static_cast(TypeParam::opcode); + + auto instance = instantiate(parse(wasm)); + + { // Max defined. + const auto arg = TypeParam::max_defined(); + const auto expected = static_cast(arg); + const auto result = execute(*instance, 0, {arg}); + EXPECT_EQ(result.value.template as(), expected); + } + + { // Min defined. + const auto arg = TypeParam::min_defined(); + const auto expected = static_cast(arg); + const auto result = execute(*instance, 0, {arg}); + EXPECT_EQ(result.value.template as(), expected); + } + + { // Something slightly bigger than 0.0. + const auto arg = TypeParam::src_limits::denorm_min(); + const auto result = execute(*instance, 0, {arg}); + EXPECT_EQ(result.value.template as(), 0); + } + + { // Something slightly smaller than 0.0. + const auto arg = -TypeParam::src_limits::denorm_min(); + const auto result = execute(*instance, 0, {arg}); + EXPECT_EQ(result.value.template as(), 0); + } + + { // Something smaller than 2.0. + const auto arg = std::nextafter(src_type{2}, src_type{0}); + const auto result = execute(*instance, 0, {arg}); + EXPECT_EQ(result.value.template as(), src_type{1}); + } + + { // Something bigger than -1.0. + const auto arg = std::nextafter(src_type{-1}, src_type{0}); + const auto result = execute(*instance, 0, {arg}); + EXPECT_EQ(result.value.template as(), 0); + } + + if (TypeParam::dst_limits::is_signed) + { + // Something bigger than -2.0. + const auto arg = std::nextafter(src_type{-2}, src_type{0}); + const auto result = execute(*instance, 0, {arg}); + EXPECT_EQ(result.value.template as(), src_type{-1}); + } +}