diff --git a/lib/fizzy/execute.cpp b/lib/fizzy/execute.cpp index e8e403c16b..6c669fc188 100644 --- a/lib/fizzy/execute.cpp +++ b/lib/fizzy/execute.cpp @@ -510,6 +510,12 @@ inline uint64_t popcnt64(uint64_t value) noexcept return static_cast(__builtin_popcountll(value)); } +__attribute__((no_sanitize("float-cast-overflow"))) inline constexpr float demote( + double value) noexcept +{ + 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(), @@ -1655,6 +1661,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); @@ -1705,7 +1716,6 @@ ExecutionResult execute(Instance& instance, FuncIdx func_idx, span case Instr::f64_min: case Instr::f64_max: case Instr::f64_copysign: - 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 20982df597..5bdb83f1d8 100644 --- a/test/unittests/execute_floating_point_test.cpp +++ b/test/unittests/execute_floating_point_test.cpp @@ -415,7 +415,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[] = { @@ -465,6 +465,62 @@ 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)); + + 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::denorm_min()}, FP32::Limits::denorm_min()}, + + // Out of range values rounded to max/lowest. + {std::nextafter(double{FP32::Limits::max()}, FP64::Limits::infinity()), + FP32::Limits::max()}, + {std::nextafter(double{FP32::Limits::lowest()}, -FP64::Limits::infinity()), + FP32::Limits::lowest()}, + {double{FP32::Limits::max()} * 0x1.0000008000007p0, FP32::Limits::max()}, + {double{FP32::Limits::lowest()} * 0x1.0000008000007p0, FP32::Limits::lowest()}, + + // The smallest of range values rounded to infinity. + {double{FP32::Limits::max()} * 0x1.0000008000008p0, FP32::Limits::infinity()}, + {double{FP32::Limits::lowest()} * 0x1.0000008000008p0, -FP32::Limits::infinity()}, + + // demote(+-inf) = +-inf + {FP64::Limits::infinity(), FP32::Limits::infinity()}, + {-FP64::Limits::infinity(), -FP32::Limits::infinity()}, + + // The canonical NaN must result in canonical NaN (only the top bit 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;