Support float values where the zeros change the exponent

When underflow is detected in float parsing, WGSL returns `0.0`.
Currently, if the number of `0`s following a `.` shifts the exponent
from a positive to a negative we were not correctly identify the
underflow and returning 0. This CL updates the float parsing to detect
undeflow in this case and return `0.0` as expected.

Bug: tint:1863
Change-Id: I164063cebf70f825fdf2753dff8a4f016939c38e
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/124341
Commit-Queue: David Neto <dneto@google.com>
Reviewed-by: David Neto <dneto@google.com>
Auto-Submit: Dan Sinclair <dsinclair@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
This commit is contained in:
dan sinclair 2023-03-17 15:08:06 +00:00 committed by Dawn LUCI CQ
parent 7a9ea24fdf
commit 6c34e11e43
2 changed files with 99 additions and 11 deletions

View File

@ -331,18 +331,32 @@ Token Lexer::try_float() {
auto source = begin_source(); auto source = begin_source();
bool has_mantissa_digits = false; bool has_mantissa_digits = false;
std::optional<size_t> first_significant_digit_position;
while (end < length() && is_digit(at(end))) { while (end < length() && is_digit(at(end))) {
if (!first_significant_digit_position.has_value() && (at(end) != '0')) {
first_significant_digit_position = end;
}
has_mantissa_digits = true; has_mantissa_digits = true;
end++; end++;
} }
bool has_point = false; std::optional<size_t> dot_position;
if (end < length() && matches(end, '.')) { if (end < length() && matches(end, '.')) {
has_point = true; dot_position = end;
end++; end++;
} }
size_t zeros_before_digit = 0;
while (end < length() && is_digit(at(end))) { while (end < length() && is_digit(at(end))) {
if (!first_significant_digit_position.has_value()) {
if (at(end) == '0') {
zeros_before_digit += 1;
} else {
first_significant_digit_position = end;
}
}
has_mantissa_digits = true; has_mantissa_digits = true;
end++; end++;
} }
@ -352,7 +366,7 @@ Token Lexer::try_float() {
} }
// Parse the exponent if one exists // Parse the exponent if one exists
bool has_exponent = false; std::optional<size_t> exponent_value_position;
bool negative_exponent = false; bool negative_exponent = false;
if (end < length() && (matches(end, 'e') || matches(end, 'E'))) { if (end < length() && (matches(end, 'e') || matches(end, 'E'))) {
end++; end++;
@ -360,14 +374,16 @@ Token Lexer::try_float() {
negative_exponent = matches(end, '-'); negative_exponent = matches(end, '-');
end++; end++;
} }
exponent_value_position = end;
bool has_digits = false;
while (end < length() && isdigit(at(end))) { while (end < length() && isdigit(at(end))) {
has_exponent = true; has_digits = true;
end++; end++;
} }
// If an 'e' or 'E' was present, then the number part must also be present. // If an 'e' or 'E' was present, then the number part must also be present.
if (!has_exponent) { if (!has_digits) {
const auto str = std::string{substr(start, end - start)}; const auto str = std::string{substr(start, end - start)};
return {Token::Type::kError, source, return {Token::Type::kError, source,
"incomplete exponent for floating point literal: " + str}; "incomplete exponent for floating point literal: " + str};
@ -382,7 +398,8 @@ Token Lexer::try_float() {
has_h_suffix = true; has_h_suffix = true;
} }
if (!has_point && !has_exponent && !has_f_suffix && !has_h_suffix) { if (!dot_position.has_value() && !exponent_value_position.has_value() && !has_f_suffix &&
!has_h_suffix) {
// If it only has digits then it's an integer. // If it only has digits then it's an integer.
return {}; return {};
} }
@ -399,10 +416,32 @@ Token Lexer::try_float() {
auto ret = absl::from_chars(&at(start), end_ptr, value); auto ret = absl::from_chars(&at(start), end_ptr, value);
bool overflow = ret.ec != std::errc(); bool overflow = ret.ec != std::errc();
// The provided value did not fit in a double and has a negative exponent, so treat it as a 0. // Value didn't fit in a double, check for underflow as that is 0.0 in WGSL and not an error.
if (negative_exponent && ret.ec == std::errc::result_out_of_range) { if (ret.ec == std::errc::result_out_of_range) {
// The exponent is negative, so treat as underflow
if (negative_exponent) {
overflow = false; overflow = false;
value = 0.0; value = 0.0;
} else if (dot_position.has_value() && first_significant_digit_position.has_value() &&
first_significant_digit_position.value() > dot_position.value()) {
// Parse the exponent from the float if provided
size_t exp_value = 0;
bool exp_conversion_succeeded = true;
if (exponent_value_position.has_value()) {
auto exp_end_ptr = end_ptr - (has_f_suffix || has_h_suffix ? 1 : 0);
auto exp_ret = std::from_chars(&at(exponent_value_position.value()), exp_end_ptr,
exp_value, 10);
if (exp_ret.ec != std::errc{}) {
exp_conversion_succeeded = false;
}
}
// If the exponent has gone negative, then this is an underflow case
if (exp_conversion_succeeded && exp_value < zeros_before_digit) {
overflow = false;
value = 0.0;
}
}
} }
TINT_ASSERT(Reader, end_ptr == ret.ptr); TINT_ASSERT(Reader, end_ptr == ret.ptr);

View File

@ -492,7 +492,56 @@ INSTANTIATE_TEST_SUITE_P(LexerTest,
// Quantization // Quantization
FloatData{"3.141592653589793", 3.141592653589793}, // no quantization FloatData{"3.141592653589793", 3.141592653589793}, // no quantization
FloatData{"3.141592653589793f", 3.1415927410125732}, // f32 quantized FloatData{"3.141592653589793f", 3.1415927410125732}, // f32 quantized
FloatData{"3.141592653589793h", 3.140625} // f16 quantized FloatData{"3.141592653589793h", 3.140625}, // f16 quantized
// https://bugs.chromium.org/p/tint/issues/detail?id=1863
FloatData{"0."
"00000000000000000000000000000000000000000000000000" // 50
"00000000000000000000000000000000000000000000000000" // 100
"00000000000000000000000000000000000000000000000000" // 150
"00000000000000000000000000000000000000000000000000" // 200
"00000000000000000000000000000000000000000000000000" // 250
"00000000000000000000000000000000000000000000000000" // 300
"00000000000000000000000000000000000000000000000000" // 350
"1e+0",
0.0},
FloatData{"0."
"00000000000000000000000000000000000000000000000000" // 50
"00000000000000000000000000000000000000000000000000" // 100
"00000000000000000000000000000000000000000000000000" // 150
"00000000000000000000000000000000000000000000000000" // 200
"00000000000000000000000000000000000000000000000000" // 250
"00000000000000000000000000000000000000000000000000" // 300
"00000000000000000000000000000000000000000000000000" // 350
"1e+10",
0.0},
FloatData{"0."
"00000000000000000000000000000000000000000000000000" // 50
"00000000000000000000000000000000000000000000000000" // 100
"00000000000000000000000000000000000000000000000000" // 150
"00000000000000000000000000000000000000000000000000" // 200
"00000000000000000000000000000000000000000000000000" // 250
"00000000000000000000000000000000000000000000000000" // 300
"00000000000000000000000000000000000000000000000000" // 350
"1e+300",
1e-51},
FloatData{"0."
"00000000000000000000000000000000000000000000000000" // 50
"00000000000000000000000000000000000000000000000000" // 100
"00000000000000000000000000000000000000000000000000" // 150
"00000000000000000000000000000000000000000000000000" // 200
"00000000000000000000000000000000000000000000000000" // 250
"00000000000000000000000000000000000000000000000000" // 300
"00000000000000000000000000000000000000000000000000" // 350
"1",
0.0},
FloatData{"1"
"00000000000000000000000000000000000000000000000000" // 50
"00000000000000000000000000000000000000000000000000" // 100
".0e-350",
1e-250}
)); ));
using FloatTest_Invalid = testing::TestWithParam<const char*>; using FloatTest_Invalid = testing::TestWithParam<const char*>;