diff options
-rw-r--r-- | src/literal.cc | 131 | ||||
-rw-r--r-- | test/dump/hexfloat_f32.txt | 6 | ||||
-rw-r--r-- | test/dump/hexfloat_f64.txt | 6 | ||||
-rw-r--r-- | test/hexfloat.cc | 94 | ||||
-rw-r--r-- | test/spec/const.txt | 66 | ||||
-rw-r--r-- | test/spec/type.txt | 8 | ||||
m--------- | third_party/testsuite | 0 |
7 files changed, 235 insertions, 76 deletions
diff --git a/src/literal.cc b/src/literal.cc index e57f513e..0844ff60 100644 --- a/src/literal.cc +++ b/src/literal.cc @@ -27,11 +27,11 @@ namespace wabt { namespace { int Clz(uint32_t value) { - return wabt_clz_u32(value); + return value == 0 ? 32 : wabt_clz_u32(value); } int Clz(uint64_t value) { - return wabt_clz_u64(value); + return value == 0 ? 64 : wabt_clz_u64(value); } template <typename T> @@ -47,6 +47,8 @@ struct FloatTraitsBase<float> { static constexpr int kSigBits = 23; static constexpr float kHugeVal = HUGE_VALF; static constexpr int kMaxHexBufferSize = WABT_MAX_FLOAT_HEX; + + static float Strto(const char* s, char** endptr) { return strtof(s, endptr); } }; template <> @@ -56,6 +58,10 @@ struct FloatTraitsBase<double> { static constexpr int kSigBits = 52; static constexpr float kHugeVal = HUGE_VAL; static constexpr int kMaxHexBufferSize = WABT_MAX_DOUBLE_HEX; + + static double Strto(const char* s, char** endptr) { + return strtod(s, endptr); + } }; template <typename T> @@ -92,12 +98,12 @@ class FloatParser { static bool StringStartsWith(const char* start, const char* end, const char* prefix); - static Float Strto(const char* s, char** endptr); static Uint Make(bool sign, int exp, Uint sig); static Uint ShiftAndRoundToNearest(Uint significand, int shift); + static Result ParseFloat(const char* s, const char* end, Uint* out_bits); static Result ParseNan(const char* s, const char* end, Uint* out_bits); - static void ParseHex(const char* s, const char* end, Uint* out_bits); + static Result ParseHex(const char* s, const char* end, Uint* out_bits); static void ParseInfinity(const char* s, const char* end, Uint* out_bits); }; @@ -127,18 +133,40 @@ bool FloatParser<T>::StringStartsWith(const char* start, } // static -template <> -float FloatParser<float>::Strto(const char* s, char** endptr) { - return strtof(s, endptr); -} +template <typename T> +Result FloatParser<T>::ParseFloat(const char* s, + const char* end, + Uint* out_bits) { + // Here is the normal behavior for strtof/strtod: + // + // input | errno | output | + // --------------------------------- + // overflow | ERANGE | +-HUGE_VAL | + // underflow | ERANGE | 0.0 | + // otherwise | 0 | value | + // + // So normally we need to clear errno before calling strto{f,d}, and check + // afterward whether it was set to ERANGE. + // + // glibc seems to have a bug where + // strtof("340282356779733661637539395458142568448") will return HUGE_VAL, + // but will not set errno to ERANGE. Since this function is only called when + // we know that we have parsed a "normal" number (i.e. not "inf"), we know + // that if we ever get HUGE_VAL, it must be overflow. + // + // The WebAssembly spec also ignores underflow, so we don't need to check for + // ERANGE at all. + char* endptr; + Float value = Traits::Strto(s, &endptr); + if (endptr != end || + (value == Traits::kHugeVal || value == -Traits::kHugeVal)) { + return Result::Error; + } -// static -template <> -double FloatParser<double>::Strto(const char* s, char** endptr) { - return strtod(s, endptr); + memcpy(out_bits, &value, sizeof(value)); + return Result::Ok; } - // static template <typename T> typename FloatParser<T>::Uint FloatParser<T>::Make(bool sign, @@ -207,9 +235,9 @@ Result FloatParser<T>::ParseNan(const char* s, // static template <typename T> -void FloatParser<T>::ParseHex(const char* s, const char* end, Uint* out_bits) { - static constexpr int kHexDigitBits = 4; - +Result FloatParser<T>::ParseHex(const char* s, + const char* end, + Uint* out_bits) { bool is_neg = false; if (*s == '-') { is_neg = true; @@ -229,42 +257,28 @@ void FloatParser<T>::ParseHex(const char* s, const char* end, Uint* out_bits) { // 0x0.000001p0 => significand = 1, significand_exponent = -24 bool seen_dot = false; Uint significand = 0; - // How much to shift |significand| if a non-zero value is appended. - int significand_shift = 0; - int significand_bits = 0; // Bits of |significand|. int significand_exponent = 0; // Exponent adjustment due to dot placement. for (; s < end; ++s) { uint32_t digit; if (*s == '.') { - if (significand != 0) - significand_exponent += significand_shift; - significand_shift = 0; seen_dot = true; - continue; - } else if (WABT_FAILED(parse_hexdigit(*s, &digit))) { + } else if (WABT_SUCCEEDED(parse_hexdigit(*s, &digit))) { + if (Traits::kBits - Clz(significand) <= Traits::kSigPlusOneBits) { + significand = (significand << 4) + digit; + if (seen_dot) + significand_exponent -= 4; + } else if (!seen_dot) { + significand_exponent += 4; + } + } else { break; } - significand_shift += kHexDigitBits; - if (digit != 0 && - (significand == 0 || significand_bits + significand_shift <= - Traits::kSigBits + 1 + kHexDigitBits)) { - if (significand != 0) - significand <<= significand_shift; - if (seen_dot) - significand_exponent -= significand_shift; - significand += digit; - significand_shift = 0; - significand_bits += kHexDigitBits; - } } - if (!seen_dot) - significand_exponent += significand_shift; - if (significand == 0) { // 0 or -0. *out_bits = Make(is_neg, Traits::kMinExp, 0); - return; + return Result::Ok; } int exponent = 0; @@ -297,14 +311,11 @@ void FloatParser<T>::ParseHex(const char* s, const char* end, Uint* out_bits) { if (exponent_is_neg) exponent = -exponent; - significand_bits = Traits::kBits - Clz(significand); + int significand_bits = Traits::kBits - Clz(significand); // -1 for the implicit 1 bit of the significand. exponent += significand_exponent + significand_bits - 1; - if (exponent >= Traits::kMaxExp) { - // inf or -inf. - *out_bits = Make(is_neg, Traits::kMaxExp, 0); - } else if (exponent <= Traits::kMinExp) { + if (exponent <= Traits::kMinExp) { // Maybe subnormal. if (significand_bits > Traits::kSigBits) { significand = ShiftAndRoundToNearest(significand, @@ -323,14 +334,14 @@ void FloatParser<T>::ParseHex(const char* s, const char* end, Uint* out_bits) { if (significand != 0) { *out_bits = Make(is_neg, exponent, significand); - return; + return Result::Ok; } } // Not subnormal, too small; return 0 or -0. *out_bits = Make(is_neg, Traits::kMinExp, 0); } else { - // Normal value. + // Maybe Normal value. if (significand_bits > Traits::kSigPlusOneBits) { significand = ShiftAndRoundToNearest( significand, significand_bits - Traits::kSigPlusOneBits); @@ -340,8 +351,16 @@ void FloatParser<T>::ParseHex(const char* s, const char* end, Uint* out_bits) { significand <<= (Traits::kSigPlusOneBits - significand_bits); } + if (exponent >= Traits::kMaxExp) { + // Would be inf or -inf, but the spec doesn't allow rounding hex-floats to + // infinity. + return Result::Error; + } + *out_bits = Make(is_neg, exponent, significand & Traits::kSigMask); } + + return Result::Ok; } // static @@ -374,23 +393,11 @@ Result FloatParser<T>::Parse(LiteralType literal_type, #endif switch (literal_type) { case LiteralType::Int: - case LiteralType::Float: { - errno = 0; - char* endptr; - Float value; - value = Strto(s, &endptr); - if (endptr != end || ((value == 0 || value == Traits::kHugeVal || - value == -Traits::kHugeVal) && - errno != 0)) - return Result::Error; - - memcpy(out_bits, &value, sizeof(value)); - return Result::Ok; - } + case LiteralType::Float: + return ParseFloat(s, end, out_bits); case LiteralType::Hexfloat: - ParseHex(s, end, out_bits); - return Result::Ok; + return ParseHex(s, end, out_bits); case LiteralType::Infinity: ParseInfinity(s, end, out_bits); diff --git a/test/dump/hexfloat_f32.txt b/test/dump/hexfloat_f32.txt index 2be9bab8..d65ccb24 100644 --- a/test/dump/hexfloat_f32.txt +++ b/test/dump/hexfloat_f32.txt @@ -18,7 +18,7 @@ drop f32.const 0x0.7fffffp127 drop - f32.const 0x0.ffffffffp128 + f32.const 0x0.ffffffffp127 drop f32.const 0x1.ffff88p127 drop @@ -88,7 +88,7 @@ 0000042: feff 7f7e ; f32 literal 0000046: 1a ; drop 0000047: 43 ; f32.const -0000048: 0000 807f ; f32 literal +0000048: 0000 007f ; f32 literal 000004c: 1a ; drop 000004d: 43 ; f32.const 000004e: c4ff 7f7f ; f32 literal @@ -139,7 +139,7 @@ Code Disassembly: 000040: 1a | drop 000041: 43 fe ff 7f 7e | f32.const 0x1.fffffcp+125 000046: 1a | drop - 000047: 43 00 00 80 7f | f32.const inf + 000047: 43 00 00 00 7f | f32.const 0x1p+127 00004c: 1a | drop 00004d: 43 c4 ff 7f 7f | f32.const 0x1.ffff88p+127 000052: 1a | drop diff --git a/test/dump/hexfloat_f64.txt b/test/dump/hexfloat_f64.txt index 7f78e752..c81e4a28 100644 --- a/test/dump/hexfloat_f64.txt +++ b/test/dump/hexfloat_f64.txt @@ -18,7 +18,7 @@ drop f64.const 0x0.7fffffffffffp1023 drop - f64.const 0x0.ffffffffffffffffp1024 + f64.const 0x0.ffffffffffffffffp1023 drop f64.const 0x1.ffffffffffffcp1023 drop @@ -88,7 +88,7 @@ 000005e: c0ff ffff ffff cf7f ; f64 literal 0000066: 1a ; drop 0000067: 44 ; f64.const -0000068: 0000 0000 0000 f07f ; f64 literal +0000068: 0000 0000 0000 e07f ; f64 literal 0000070: 1a ; drop 0000071: 44 ; f64.const 0000072: fcff ffff ffff ef7f ; f64 literal @@ -141,7 +141,7 @@ Code Disassembly: 00005e: 1a | drop 00005f: 44 c0 ff ff ff ff ff cf 7f | f64.const 0x1.fffffffffffcp+1021 000068: 1a | drop - 000069: 44 00 00 00 00 00 00 f0 7f | f64.const inf + 000069: 44 00 00 00 00 00 00 e0 7f | f64.const 0x1p+1023 000072: 1a | drop 000073: 44 fc ff ff ff ff ff ef 7f | f64.const 0x1.ffffffffffffcp+1023 00007c: 1a | drop diff --git a/test/hexfloat.cc b/test/hexfloat.cc index 70e5c4c4..2b4576b5 100644 --- a/test/hexfloat.cc +++ b/test/hexfloat.cc @@ -114,7 +114,8 @@ class AllFloatsParseTest : public ThreadedTest { int len = snprintf(buffer, sizeof(buffer), "%a", value); uint32_t me; - parse_float(LiteralType::Hexfloat, buffer, buffer + len, &me); + ASSERT_EQ(Result::Ok, + parse_float(LiteralType::Hexfloat, buffer, buffer + len, &me)); ASSERT_EQ(me, bits); } LOG_DONE(); @@ -171,7 +172,8 @@ class AllFloatsRoundtripTest : public ThreadedTest { int len = strlen(buffer); uint32_t new_bits; - parse_float(ClassifyFloat(bits), buffer, buffer + len, &new_bits); + ASSERT_EQ(Result::Ok, parse_float(ClassifyFloat(bits), buffer, + buffer + len, &new_bits)); ASSERT_EQ(new_bits, bits); } LOG_DONE(); @@ -197,7 +199,8 @@ class ManyDoublesParseTest : public ThreadedTest { int len = snprintf(buffer, sizeof(buffer), "%a", value); uint64_t me; - parse_double(LiteralType::Hexfloat, buffer, buffer + len, &me); + ASSERT_EQ(Result::Ok, + parse_double(LiteralType::Hexfloat, buffer, buffer + len, &me)); ASSERT_EQ(me, bits); } LOG_DONE(); @@ -256,7 +259,8 @@ class ManyDoublesRoundtripTest : public ThreadedTest { int len = strlen(buffer); uint64_t new_bits; - parse_double(ClassifyDouble(bits), buffer, buffer + len, &new_bits); + ASSERT_EQ(Result::Ok, parse_double(ClassifyDouble(bits), buffer, + buffer + len, &new_bits)); ASSERT_EQ(new_bits, bits); } LOG_DONE(); @@ -266,3 +270,85 @@ class ManyDoublesRoundtripTest : public ThreadedTest { TEST_F(ManyDoublesRoundtripTest, Run) { RunThreads(); } + +static void AssertHexFloatEquals(uint32_t expected_bits, const char* s) { + uint32_t actual_bits; + ASSERT_EQ(Result::Ok, + parse_float(LiteralType::Hexfloat, s, s + strlen(s), &actual_bits)); + ASSERT_EQ(expected_bits, actual_bits); +} + +static void AssertHexFloatFails(const char* s) { + uint32_t actual_bits; + ASSERT_EQ(Result::Error, + parse_float(LiteralType::Hexfloat, s, s + strlen(s), &actual_bits)); +} + +static void AssertHexDoubleEquals(uint64_t expected_bits, const char* s) { + uint64_t actual_bits; + ASSERT_EQ(Result::Ok, parse_double(LiteralType::Hexfloat, s, s + strlen(s), + &actual_bits)); + ASSERT_EQ(expected_bits, actual_bits); +} + +static void AssertHexDoubleFails(const char* s) { + uint64_t actual_bits; + ASSERT_EQ(Result::Error, parse_double(LiteralType::Hexfloat, s, s + strlen(s), + &actual_bits)); +} + +TEST(ParseFloat, NonCanonical) { + AssertHexFloatEquals(0x3f800000, "0x00000000000000000000001.0p0"); + AssertHexFloatEquals(0x3f800000, "0x1.00000000000000000000000p0"); + AssertHexFloatEquals(0x3f800000, "0x0.0000000000000000000001p88"); +} + +TEST(ParseFloat, Rounding) { + // |------- 23 bits -----| V-- extra bit + // + // 11111111111111111111101 0 ==> no rounding + AssertHexFloatEquals(0x7f7ffffd, "0x1.fffffap127"); + // 11111111111111111111101 1 ==> round up + AssertHexFloatEquals(0x7f7ffffe, "0x1.fffffbp127"); + // 11111111111111111111110 0 ==> no rounding + AssertHexFloatEquals(0x7f7ffffe, "0x1.fffffcp127"); + // 11111111111111111111110 1 ==> round down + AssertHexFloatEquals(0x7f7ffffe, "0x1.fffffdp127"); + // 11111111111111111111111 0 ==> no rounding + AssertHexFloatEquals(0x7f7fffff, "0x1.fffffep127"); +} + +TEST(ParseFloat, OutOfRange) { + AssertHexFloatFails("0x1p128"); + AssertHexFloatFails("-0x1p128"); + AssertHexFloatFails("0x1.ffffffp127"); + AssertHexFloatFails("-0x1.ffffffp127"); +} + +TEST(ParseDouble, NonCanonical) { + AssertHexDoubleEquals(0x3ff0000000000000, "0x00000000000000000000001.0p0"); + AssertHexDoubleEquals(0x3ff0000000000000, "0x1.00000000000000000000000p0"); + AssertHexDoubleEquals(0x3ff0000000000000, "0x0.0000000000000000000001p88"); +} + +TEST(ParseDouble, Rounding) { + // |-------------------- 52 bits ---------------------| V-- extra bit + // + // 1111111111111111111111111111111111111111111111111101 0 ==> no rounding + AssertHexDoubleEquals(0x7feffffffffffffd, "0x1.ffffffffffffd0p1023"); + // 1111111111111111111111111111111111111111111111111101 1 ==> round up + AssertHexDoubleEquals(0x7feffffffffffffe, "0x1.ffffffffffffd8p1023"); + // 1111111111111111111111111111111111111111111111111110 0 ==> no rounding + AssertHexDoubleEquals(0x7feffffffffffffe, "0x1.ffffffffffffe0p1023"); + // 1111111111111111111111111111111111111111111111111110 1 ==> round down + AssertHexDoubleEquals(0x7feffffffffffffe, "0x1.ffffffffffffe8p1023"); + // 1111111111111111111111111111111111111111111111111111 0 ==> no rounding + AssertHexDoubleEquals(0x7fefffffffffffff, "0x1.fffffffffffff0p1023"); +} + +TEST(ParseDouble, OutOfRange) { + AssertHexDoubleFails("0x1p1024"); + AssertHexDoubleFails("-0x1p1024"); + AssertHexDoubleFails("0x1.fffffffffffff8p1023"); + AssertHexDoubleFails("-0x1.fffffffffffff8p1023"); +} diff --git a/test/spec/const.txt b/test/spec/const.txt index 67592979..4b61a8df 100644 --- a/test/spec/const.txt +++ b/test/spec/const.txt @@ -33,5 +33,69 @@ out/third_party/testsuite/const.wast:45: assert_malformed passed: out/third_party/testsuite/const/const.15.wast:1:18: invalid literal "-9223372036854775809" (func (i64.const -9223372036854775809) drop) ^^^^^^^^^^^^^^^^^^^^ -8/8 tests passed. +out/third_party/testsuite/const.wast:56: assert_malformed passed: + out/third_party/testsuite/const/const.22.wast:1:18: invalid literal "0x1p128" + (func (f32.const 0x1p128) drop) + ^^^^^^^ +out/third_party/testsuite/const.wast:60: assert_malformed passed: + out/third_party/testsuite/const/const.23.wast:1:18: invalid literal "-0x1p128" + (func (f32.const -0x1p128) drop) + ^^^^^^^^ +out/third_party/testsuite/const.wast:64: assert_malformed passed: + out/third_party/testsuite/const/const.24.wast:1:18: invalid literal "0x1.ffffffp127" + (func (f32.const 0x1.ffffffp127) drop) + ^^^^^^^^^^^^^^ +out/third_party/testsuite/const.wast:68: assert_malformed passed: + out/third_party/testsuite/const/const.25.wast:1:18: invalid literal "-0x1.ffffffp127" + (func (f32.const -0x1.ffffffp127) drop) + ^^^^^^^^^^^^^^^ +out/third_party/testsuite/const.wast:75: assert_malformed passed: + out/third_party/testsuite/const/const.28.wast:1:18: invalid literal "1e39" + (func (f32.const 1e39) drop) + ^^^^ +out/third_party/testsuite/const.wast:79: assert_malformed passed: + out/third_party/testsuite/const/const.29.wast:1:18: invalid literal "-1e39" + (func (f32.const -1e39) drop) + ^^^^^ +out/third_party/testsuite/const.wast:86: assert_malformed passed: + out/third_party/testsuite/const/const.32.wast:1:18: invalid literal "340282356779733661637539395458142568448" + (func (f32.const 340282356779733661637539395458142568448) drop) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +out/third_party/testsuite/const.wast:90: assert_malformed passed: + out/third_party/testsuite/const/const.33.wast:1:18: invalid literal "-340282356779733661637539395458142568448" + (func (f32.const -340282356779733661637539395458142568448) drop) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +out/third_party/testsuite/const.wast:101: assert_malformed passed: + out/third_party/testsuite/const/const.40.wast:1:18: invalid literal "0x1p1024" + (func (f64.const 0x1p1024) drop) + ^^^^^^^^ +out/third_party/testsuite/const.wast:105: assert_malformed passed: + out/third_party/testsuite/const/const.41.wast:1:18: invalid literal "-0x1p1024" + (func (f64.const -0x1p1024) drop) + ^^^^^^^^^ +out/third_party/testsuite/const.wast:109: assert_malformed passed: + out/third_party/testsuite/const/const.42.wast:1:18: invalid literal "0x1.fffffffffffff8p1023" + (func (f64.const 0x1.fffffffffffff8p1023) drop) + ^^^^^^^^^^^^^^^^^^^^^^^ +out/third_party/testsuite/const.wast:113: assert_malformed passed: + out/third_party/testsuite/const/const.43.wast:1:18: invalid literal "-0x1.fffffffffffff8p1023" + (func (f64.const -0x1.fffffffffffff8p1023) drop) + ^^^^^^^^^^^^^^^^^^^^^^^^ +out/third_party/testsuite/const.wast:120: assert_malformed passed: + out/third_party/testsuite/const/const.46.wast:1:18: invalid literal "1e309" + (func (f64.const 1e309) drop) + ^^^^^ +out/third_party/testsuite/const.wast:124: assert_malformed passed: + out/third_party/testsuite/const/const.47.wast:1:18: invalid literal "-1e309" + (func (f64.const -1e309) drop) + ^^^^^^ +out/third_party/testsuite/const.wast:131: assert_malformed passed: + out/third_party/testsuite/const/const.50.wast:1:18: invalid literal "269653970229347356221791135597556535197105851288767494898376215204735891170042808140884337949150317257310688430271573696351481990334196274152701320055306275479074865864826923114368235135583993416113802762682700913456874855354834422248712838998185022412196739306217084753107265771378949821875606039276187287552" + (func (f64.const 269653970229347356221791135597556535197105851288767494898376... + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +out/third_party/testsuite/const.wast:135: assert_malformed passed: + out/third_party/testsuite/const/const.51.wast:1:18: invalid literal "-269653970229347356221791135597556535197105851288767494898376215204735891170042808140884337949150317257310688430271573696351481990334196274152701320055306275479074865864826923114368235135583993416113802762682700913456874855354834422248712838998185022412196739306217084753107265771378949821875606039276187287552" + (func (f64.const -26965397022934735622179113559755653519710585128876749489837... + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +24/24 tests passed. ;;; STDOUT ;;) diff --git a/test/spec/type.txt b/test/spec/type.txt index 952304eb..1fc10a57 100644 --- a/test/spec/type.txt +++ b/test/spec/type.txt @@ -10,7 +10,9 @@ out/third_party/testsuite/type.wast:48: assert_malformed passed: out/third_party/testsuite/type/type.2.wast:1:21: syntax error, unexpected VAR, expecting ) or VALUE_TYPE (type (func (result $x i32))) ^^ -out/third_party/testsuite/type.wast:52: expected module to be invalid: "out/third_party/testsuite/type/type.3.wast" -out/third_party/testsuite/type.wast:56: expected module to be invalid: "out/third_party/testsuite/type/type.4.wast" -2/4 tests passed. +out/third_party/testsuite/type.wast:53: assert_invalid passed: + error: @0x0000000e: result count must be 0 or 1 +out/third_party/testsuite/type.wast:57: assert_invalid passed: + error: @0x0000000e: result count must be 0 or 1 +4/4 tests passed. ;;; STDOUT ;;) diff --git a/third_party/testsuite b/third_party/testsuite -Subproject aabd5c2c6fe4d6576ab09bece39b32954d18c44 +Subproject ae1dcc2256cd319975e98f3b25526f55f6be07d |