Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement f32.demote_f64 instruction #458

Merged
merged 3 commits into from
Aug 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion lib/fizzy/execute.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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(
chfast marked this conversation as resolved.
Show resolved Hide resolved
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<float>(value);
}

std::optional<uint32_t> find_export(const Module& module, ExternalKind kind, std::string_view name)
{
const auto it = std::find_if(module.exportsec.begin(), module.exportsec.end(),
Expand Down Expand Up @@ -1759,6 +1768,11 @@ ExecutionResult execute(Instance& instance, FuncIdx func_idx, span<const Value>
convert<uint64_t, float>(stack);
break;
}
case Instr::f32_demote_f64:
{
stack.top() = demote(stack.top().f64);
break;
}
case Instr::f64_convert_i32_s:
{
convert<int32_t, double>(stack);
Expand Down Expand Up @@ -1795,7 +1809,6 @@ ExecutionResult execute(Instance& instance, FuncIdx func_idx, span<const Value>
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:
Expand Down
112 changes: 110 additions & 2 deletions test/unittests/execute_floating_point_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ struct WasmTypeName
template <typename T>
class execute_floating_point_types : public testing::Test
{
protected:
public:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to access the list of NaNs from other test suite.

using L = typename FP<T>::Limits;

// The list of positive floating-point values without zeros, infinities and NaNs.
Expand Down Expand Up @@ -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<float, double> test_cases[] = {
Expand Down Expand Up @@ -848,6 +848,114 @@ 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<float>::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)
{
/* 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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this assert here and not in limits? If here, can't this be made into a static_assert?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be. I just want to see what the value is.


// 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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't quite understand the last sentence. f32_limit would indeed be even if casted to int, but for floats there's no even/odd ?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I guess it's about evenness of the mantissa part

image

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of course there is (at least in Wasm spec).
image
In this case f32_limit is even by definition.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But still confused, mantissa of f32_max is even, manitssa of f32_limit is odd

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think in both cases you are confused by the size of the f32 mantisa which is 23 bits. When written as 6 hex digits the last bit should always be 0. The mantissa full of ones is 1.fffffe (leading 1 is implicit). Then m = 0x7fffff and is odd.

The f32_limit is even by definition: even_N(+-limit_N) <=> true. But also mantissa of 1p128 is all zero therefore even.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, yes, I was confused by hex notation.

constexpr double lowest_to_inf = (f32_max + f32_limit) / 2;
ASSERT_EQ(lowest_to_inf, 0x1.ffffffp127);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't really matter, but is there a reason this should assert_eq and not static_assert?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I started with static_assert, then decided that maybe it should build anyway even if not true. I guess static_assert may be less confusing for readers.


const std::pair<double, float> 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()},
axic marked this conversation as resolved.
Show resolved Hide resolved
{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)},
};
axic marked this conversation as resolved.
Show resolved Hide resolved

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.
for (const auto nan : execute_floating_point_types<double>::positive_noncanonical_nans)
{
EXPECT_THAT(execute(*instance, 0, {nan}), ArithmeticNaN(float{}));
EXPECT_THAT(execute(*instance, 0, {-nan}), ArithmeticNaN(float{}));
}
}


Expand Down