diff --git a/circle.yml b/circle.yml index 3b55cfe93..d49a00f1a 100644 --- a/circle.yml +++ b/circle.yml @@ -193,6 +193,7 @@ jobs: ./wat2wasm4cpp.py test/unittests/end_to_end_test.cpp ./wat2wasm4cpp.py test/unittests/execute_call_test.cpp ./wat2wasm4cpp.py test/unittests/execute_control_test.cpp + ./wat2wasm4cpp.py test/unittests/execute_floating_point_test.cpp ./wat2wasm4cpp.py test/unittests/execute_numeric_test.cpp ./wat2wasm4cpp.py test/unittests/execute_test.cpp ./wat2wasm4cpp.py test/unittests/instantiate_test.cpp @@ -270,9 +271,9 @@ jobs: - benchmark: min_time: "0.01" - spectest: - expected_passed: 5423 + expected_passed: 5468 expected_failed: 9 - expected_skipped: 6381 + expected_skipped: 6336 sanitizers-macos: executor: macos @@ -288,9 +289,9 @@ jobs: - benchmark: min_time: "0.01" - spectest: - expected_passed: 5423 + expected_passed: 5468 expected_failed: 9 - expected_skipped: 6381 + expected_skipped: 6336 benchmark: machine: @@ -396,13 +397,13 @@ jobs: at: ~/build - spectest: skip_validation: true - expected_passed: 4481 + expected_passed: 4526 expected_failed: 9 - expected_skipped: 7323 + expected_skipped: 7278 - spectest: - expected_passed: 5423 + expected_passed: 5468 expected_failed: 9 - expected_skipped: 6381 + expected_skipped: 6336 - collect_coverage_data workflows: diff --git a/lib/fizzy/execute.cpp b/lib/fizzy/execute.cpp index 774b8652e..b3c97eeec 100644 --- a/lib/fizzy/execute.cpp +++ b/lib/fizzy/execute.cpp @@ -1002,12 +1002,14 @@ ExecutionResult execute(Instance& instance, FuncIdx func_idx, span break; } case Instr::i32_const: + case Instr::f32_const: { const auto value = read(immediates); stack.push(value); break; } case Instr::i64_const: + case Instr::f64_const: { const auto value = read(immediates); stack.push(value); @@ -1384,12 +1386,21 @@ ExecutionResult execute(Instance& instance, FuncIdx func_idx, span // effectively no-op break; } + case Instr::f32_add: + { + binary_op(stack, std::plus{}); + break; + } + case Instr::f64_add: + { + binary_op(stack, std::plus{}); + break; + } + case Instr::f32_load: case Instr::f64_load: case Instr::f32_store: case Instr::f64_store: - case Instr::f32_const: - case Instr::f64_const: case Instr::f32_eq: case Instr::f32_ne: case Instr::f32_lt: @@ -1409,7 +1420,6 @@ ExecutionResult execute(Instance& instance, FuncIdx func_idx, span case Instr::f32_trunc: case Instr::f32_nearest: case Instr::f32_sqrt: - case Instr::f32_add: case Instr::f32_sub: case Instr::f32_mul: case Instr::f32_div: @@ -1423,7 +1433,6 @@ ExecutionResult execute(Instance& instance, FuncIdx func_idx, span case Instr::f64_trunc: case Instr::f64_nearest: case Instr::f64_sqrt: - case Instr::f64_add: case Instr::f64_sub: case Instr::f64_mul: case Instr::f64_div: diff --git a/lib/fizzy/parser_expr.cpp b/lib/fizzy/parser_expr.cpp index b3df735f0..935e3eac0 100644 --- a/lib/fizzy/parser_expr.cpp +++ b/lib/fizzy/parser_expr.cpp @@ -27,6 +27,18 @@ inline void push(bytes& b, T value) b.append(storage, sizeof(storage)); } +template +inline parser_result read_const(const uint8_t* pos, const uint8_t* end) +{ + constexpr auto size = sizeof(T); + if (pos + size > end) + throw parser_error{"unexpected EOF"}; + + T value; + __builtin_memcpy(&value, pos, size); + return {value, pos + size}; +} + /// The control frame to keep information about labels and blocks as defined in /// Wasm Validation Algorithm https://webassembly.github.io/spec/core/appendix/algorithm.html. struct ControlFrame @@ -305,12 +317,6 @@ parser_result parse_expr(const uint8_t* pos, const uint8_t* end, FuncIdx f throw parser_error{"invalid instruction " + std::to_string(*(pos - 1))}; // Floating point instructions are unsupported - case Instr::f32_const: - pos = skip(4, pos, end); - break; - case Instr::f64_const: - pos = skip(8, pos, end); - break; case Instr::f32_eq: case Instr::f32_ne: case Instr::f32_lt: @@ -799,7 +805,6 @@ parser_result parse_expr(const uint8_t* pos, const uint8_t* end, FuncIdx f push(code.immediates, static_cast(value)); break; } - case Instr::i64_const: { int64_t value; @@ -807,6 +812,20 @@ parser_result parse_expr(const uint8_t* pos, const uint8_t* end, FuncIdx f push(code.immediates, static_cast(value)); break; } + case Instr::f32_const: + { + uint32_t value; + std::tie(value, pos) = read_const(pos, end); + push(code.immediates, value); + break; + } + case Instr::f64_const: + { + uint64_t value; + std::tie(value, pos) = read_const(pos, end); + push(code.immediates, value); + break; + } case Instr::i32_load: case Instr::i64_load: diff --git a/lib/fizzy/value.hpp b/lib/fizzy/value.hpp index b034fd44c..bfd064c29 100644 --- a/lib/fizzy/value.hpp +++ b/lib/fizzy/value.hpp @@ -11,6 +11,8 @@ namespace fizzy union Value { uint64_t i64; + float f32; + double f64; Value() = default; @@ -25,6 +27,9 @@ union Value constexpr Value(int64_t v) noexcept : i64{static_cast(v)} {} constexpr Value(int32_t v) noexcept : i64{static_cast(v)} {} + constexpr Value(float v) noexcept : f32{v} {} + constexpr Value(double v) noexcept : f64{v} {} + /// Converting constructor from any other type (including smaller integer types) is deleted. template constexpr Value(T) = delete; @@ -60,4 +65,16 @@ constexpr int32_t Value::as() const noexcept { return static_cast(i64); } + +template <> +constexpr float Value::as() const noexcept +{ + return f32; +} + +template <> +constexpr double Value::as() const noexcept +{ + return f64; +} } // namespace fizzy diff --git a/test/unittests/CMakeLists.txt b/test/unittests/CMakeLists.txt index 6e3f594fe..227190b04 100644 --- a/test/unittests/CMakeLists.txt +++ b/test/unittests/CMakeLists.txt @@ -14,6 +14,7 @@ target_sources( end_to_end_test.cpp execute_call_test.cpp execute_control_test.cpp + execute_floating_point_test.cpp execute_numeric_test.cpp execute_test.cpp instantiate_test.cpp diff --git a/test/unittests/execute_floating_point_test.cpp b/test/unittests/execute_floating_point_test.cpp new file mode 100644 index 000000000..1a087982b --- /dev/null +++ b/test/unittests/execute_floating_point_test.cpp @@ -0,0 +1,72 @@ +// Fizzy: A fast WebAssembly interpreter +// Copyright 2020 The Fizzy Authors. +// SPDX-License-Identifier: Apache-2.0 + +#include "execute.hpp" +#include "parser.hpp" +#include +#include + +using namespace fizzy; +using namespace fizzy::test; + +TEST(execute_floating_point, f32_const) +{ + /* wat2wasm + (func (result f32) + f32.const 4194304.1 + ) + */ + const auto wasm = from_hex("0061736d010000000105016000017d030201000a09010700430000804a0b"); + + auto instance = instantiate(parse(wasm)); + const auto result = execute(*instance, 0, {}); + EXPECT_EQ(result.value.f32, 4194304.1f); +} + +TEST(execute_floating_point, f64_const) +{ + /* wat2wasm + (func (result f64) + f64.const 8589934592.1 + ) + */ + const auto wasm = + from_hex("0061736d010000000105016000017c030201000a0d010b0044cdcc0000000000420b"); + + auto instance = instantiate(parse(wasm)); + const auto result = execute(*instance, 0, {}); + EXPECT_EQ(result.value.f64, 8589934592.1); +} + +TEST(execute_floating_point, f32_add) +{ + /* wat2wasm + (func (param f32 f32) (result f32) + local.get 0 + local.get 1 + f32.add + ) + */ + const auto wasm = from_hex("0061736d0100000001070160027d7d017d030201000a0901070020002001920b"); + + auto instance = instantiate(parse(wasm)); + const auto result = execute(*instance, 0, {1.001f, 6.006f}); + EXPECT_EQ(result.value.f32, 7.007f); +} + +TEST(execute_floating_point, f64_add) +{ + /* wat2wasm + (func (param f64 f64) (result f64) + local.get 0 + local.get 1 + f64.add + ) + */ + const auto wasm = from_hex("0061736d0100000001070160027c7c017c030201000a0901070020002001a00b"); + + 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 diff --git a/test/unittests/parser_expr_test.cpp b/test/unittests/parser_expr_test.cpp index 4462e66ff..11617ffd0 100644 --- a/test/unittests/parser_expr_test.cpp +++ b/test/unittests/parser_expr_test.cpp @@ -42,14 +42,14 @@ TEST(parser_expr, instr_loop) const auto [code3, pos3] = parse_expr(loop_f32); EXPECT_EQ(code3.instructions, (std::vector{Instr::loop, Instr::f32_const, Instr::end, Instr::drop, Instr::end})); - EXPECT_EQ(code3.immediates.size(), 0); + EXPECT_EQ(code3.immediates.size(), 4); EXPECT_EQ(code3.max_stack_height, 1); const auto loop_f64 = "037c4400000000000000000b1a0b"_bytes; const auto [code4, pos4] = parse_expr(loop_f64); EXPECT_EQ(code4.instructions, (std::vector{Instr::loop, Instr::f64_const, Instr::end, Instr::drop, Instr::end})); - EXPECT_EQ(code4.immediates.size(), 0); + EXPECT_EQ(code4.immediates.size(), 8); EXPECT_EQ(code4.max_stack_height, 1); } @@ -81,7 +81,7 @@ TEST(parser_expr, instr_block) const auto [code3, pos3] = parse_expr(block_f64); EXPECT_EQ(code3.instructions, (std::vector{Instr::block, Instr::f64_const, Instr::end, Instr::drop, Instr::end})); - EXPECT_TRUE(code3.immediates.empty()); + EXPECT_EQ(code2.immediates, "0000000000000000"_bytes); } TEST(parser_expr, instr_block_input_buffer_overflow) diff --git a/test/unittests/value_test.cpp b/test/unittests/value_test.cpp index 6482812cd..269aa86f6 100644 --- a/test/unittests/value_test.cpp +++ b/test/unittests/value_test.cpp @@ -41,6 +41,12 @@ TEST(value, constructor_from_signed_ints) EXPECT_EQ(Value{int64_t{-3}}.i64, 0xfffffffffffffffd); } +TEST(value, constructor_from_floating_points) +{ + EXPECT_EQ(Value{123.456f}.f32, 123.456f); + EXPECT_EQ(Value{123.456789001}.f64, 123.456789001); +} + TEST(value, as_integer) { const Value v{0xfffffffffffffffe}; @@ -50,6 +56,46 @@ TEST(value, as_integer) EXPECT_EQ(v.as(), -2); } +TEST(value, as_floating_point) +{ + EXPECT_EQ(Value{123.456f}.as(), 123.456f); + EXPECT_EQ(Value{123.456789001}.as(), 123.456789001); + + float f; + f = std::numeric_limits::infinity(); + EXPECT_EQ(Value{f}.f32, f); + EXPECT_EQ(Value{f}.as(), f); + f = std::numeric_limits::min(); + EXPECT_EQ(Value{f}.f32, f); + EXPECT_EQ(Value{f}.as(), f); + f = std::numeric_limits::max(); + EXPECT_EQ(Value{f}.f32, f); + EXPECT_EQ(Value{f}.as(), f); + f = std::numeric_limits::denorm_min(); + EXPECT_EQ(Value{f}.f32, f); + EXPECT_EQ(Value{f}.as(), f); + f = std::numeric_limits::lowest(); + EXPECT_EQ(Value{f}.f32, f); + EXPECT_EQ(Value{f}.as(), f); + + double d; + d = std::numeric_limits::infinity(); + EXPECT_EQ(Value{d}.f64, d); + EXPECT_EQ(Value{d}.as(), d); + d = std::numeric_limits::min(); + EXPECT_EQ(Value{d}.f64, d); + EXPECT_EQ(Value{d}.as(), d); + d = std::numeric_limits::max(); + EXPECT_EQ(Value{d}.f64, d); + EXPECT_EQ(Value{d}.as(), d); + d = std::numeric_limits::denorm_min(); + EXPECT_EQ(Value{d}.f64, d); + EXPECT_EQ(Value{d}.as(), d); + d = std::numeric_limits::lowest(); + EXPECT_EQ(Value{d}.f64, d); + EXPECT_EQ(Value{d}.as(), d); +} + TEST(value, implicit_conversion_to_i64) { const Value v{1};