Skip to content

Commit 6b956f3

Browse files
authored
assembler: Handle leading + in hex float literals (KhronosGroup#6565)
Make it symmetric with a leading - Test underflow to zero. Bug: crbug.com/488728576
1 parent 4972c69 commit 6b956f3

3 files changed

Lines changed: 257 additions & 122 deletions

File tree

docs/syntax.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ decimal and hexadecimal form.
101101

102102
The syntax for a floating point literal is the same as floating point
103103
constants in the C programming language, except:
104-
* An optional leading minus (`-`) is part of the literal.
104+
* An optional leading minus (`-`) or leading plus (`+`) is part of the literal.
105105
* An optional type specifier suffix is not allowed.
106106
Infinity and NaN values are expressed in hexadecimal float literals
107107
by using the maximum representable exponent for the bit width.
@@ -125,6 +125,9 @@ The disassembler prints infinite, NaN, and subnormal values in hexadecimal form.
125125
Zero and normal values are printed in decimal form with enough digits
126126
to preserve all significand bits.
127127

128+
Hex float values that underflow are rounded to zero. If there is a leading
129+
minus sign, underflow goes to negative zero.
130+
128131
## Arbitrary Integers
129132
<a name="immediate"></a>
130133

source/util/hex_float.h

Lines changed: 44 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1011,13 +1011,17 @@ std::ostream& operator<<(std::ostream& os, const HexFloat<T, Traits>& value) {
10111011
return os;
10121012
}
10131013

1014-
// Returns true if negate_value is true and the next character on the
1015-
// input stream is a plus or minus sign. In that case we also set the fail bit
1016-
// on the stream and set the value to the zero value for its type.
1014+
// Encodes whether a leading sign has been seen, and if so which one.
1015+
enum class LeadingSign { None, Plus, Minus };
1016+
1017+
// Returns true if leading_sign is either Plus or Minus, and the next character
1018+
// on the input stream is a plus or minus sign. In that case we also set the
1019+
// fail bit on the stream and set the value to the zero value for its type.
10171020
template <typename T, typename Traits>
1018-
inline bool RejectParseDueToLeadingSign(std::istream& is, bool negate_value,
1021+
inline bool RejectParseDueToLeadingSign(std::istream& is,
1022+
LeadingSign leading_sign,
10191023
HexFloat<T, Traits>& value) {
1020-
if (negate_value) {
1024+
if (leading_sign != LeadingSign::None) {
10211025
auto next_char = is.peek();
10221026
if (next_char == '-' || next_char == '+') {
10231027
// Fail the parse. Emulate standard behaviour by setting the value to
@@ -1032,22 +1036,24 @@ inline bool RejectParseDueToLeadingSign(std::istream& is, bool negate_value,
10321036

10331037
// Parses a floating point number from the given stream and stores it into the
10341038
// value parameter.
1035-
// If negate_value is true then the number may not have a leading minus or
1036-
// plus, and if it successfully parses, then the number is negated before
1037-
// being stored into the value parameter.
1039+
// If leading_sign is Plus or Minus, then the number may not have a leading
1040+
// minus or plus. If it successfully parses, and the leading sign was Minus,
1041+
// then the number is negated before being stored into the value parameter.
10381042
// If the value cannot be correctly parsed or overflows the target floating
10391043
// point type, then set the fail bit on the stream.
10401044
// TODO(dneto): Promise C++11 standard behavior in how the value is set in
10411045
// the error case, but only after all target platforms implement it correctly.
10421046
// In particular, the Microsoft C++ runtime appears to be out of spec.
10431047
template <typename T, typename Traits>
1044-
inline std::istream& ParseNormalFloat(std::istream& is, bool negate_value,
1048+
inline std::istream& ParseNormalFloat(std::istream& is,
1049+
LeadingSign leading_sign,
10451050
HexFloat<T, Traits>& value) {
1046-
if (RejectParseDueToLeadingSign(is, negate_value, value)) {
1051+
if (RejectParseDueToLeadingSign(is, leading_sign, value)) {
10471052
return is;
10481053
}
10491054
T val;
10501055
is >> val;
1056+
const bool negate_value = leading_sign == LeadingSign::Minus;
10511057
if (negate_value) {
10521058
val = -val;
10531059
}
@@ -1070,8 +1076,9 @@ inline std::istream& ParseNormalFloat(std::istream& is, bool negate_value,
10701076
// This will parse the float as it were a 32-bit floating point number,
10711077
// and then round it down to fit into a Float16 value.
10721078
// The number is rounded towards zero.
1073-
// If negate_value is true then the number may not have a leading minus or
1074-
// plus, and if it successfully parses, then the number is negated before
1079+
// If leading_sign is Plus or Minus, then the number may not have a leading
1080+
// minus or plus. If it successfully parses, and the leading sign was Minus,
1081+
// then the number is negated before being stored into the value parameter.
10751082
// being stored into the value parameter.
10761083
// If the value cannot be correctly parsed or overflows the target floating
10771084
// point type, then set the fail bit on the stream.
@@ -1081,11 +1088,11 @@ inline std::istream& ParseNormalFloat(std::istream& is, bool negate_value,
10811088
template <>
10821089
inline std::istream&
10831090
ParseNormalFloat<FloatProxy<Float16>, HexFloatTraits<FloatProxy<Float16>>>(
1084-
std::istream& is, bool negate_value,
1091+
std::istream& is, LeadingSign leading_sign,
10851092
HexFloat<FloatProxy<Float16>, HexFloatTraits<FloatProxy<Float16>>>& value) {
10861093
// First parse as a 32-bit float.
10871094
HexFloat<FloatProxy<float>> float_val(0.0f);
1088-
ParseNormalFloat(is, negate_value, float_val);
1095+
ParseNormalFloat(is, leading_sign, float_val);
10891096

10901097
// Then convert to 16-bit float, saturating at infinities, and
10911098
// rounding toward zero.
@@ -1106,11 +1113,11 @@ ParseNormalFloat<FloatProxy<Float16>, HexFloatTraits<FloatProxy<Float16>>>(
11061113
template <>
11071114
inline std::istream&
11081115
ParseNormalFloat<FloatProxy<BFloat16>, HexFloatTraits<FloatProxy<BFloat16>>>(
1109-
std::istream& is, bool negate_value,
1116+
std::istream& is, LeadingSign leading_sign,
11101117
HexFloat<FloatProxy<BFloat16>, HexFloatTraits<FloatProxy<BFloat16>>>&
11111118
value) {
11121119
HexFloat<FloatProxy<float>> float_val(0.0f);
1113-
ParseNormalFloat(is, negate_value, float_val);
1120+
ParseNormalFloat(is, leading_sign, float_val);
11141121

11151122
float_val.castTo(value, round_direction::kToZero);
11161123

@@ -1125,8 +1132,8 @@ ParseNormalFloat<FloatProxy<BFloat16>, HexFloatTraits<FloatProxy<BFloat16>>>(
11251132
// This will parse the float as it were a 32-bit floating point number,
11261133
// and then round it down to fit into a Float8_E4M3 value.
11271134
// The number is rounded towards zero.
1128-
// If negate_value is true then the number may not have a leading minus or
1129-
// plus, and if it successfully parses, then the number is negated before
1135+
// If leading_sign is Plus or Minus, then the number may not have a leading
1136+
// minus or plus. If it successfully parses, and the leading sign was Minus,
11301137
// being stored into the value parameter.
11311138
// If the value cannot be correctly parsed or overflows the target floating
11321139
// point type, then set the fail bit on the stream.
@@ -1136,12 +1143,12 @@ ParseNormalFloat<FloatProxy<BFloat16>, HexFloatTraits<FloatProxy<BFloat16>>>(
11361143
template <>
11371144
inline std::istream& ParseNormalFloat<FloatProxy<Float8_E4M3>,
11381145
HexFloatTraits<FloatProxy<Float8_E4M3>>>(
1139-
std::istream& is, bool negate_value,
1146+
std::istream& is, LeadingSign leading_sign,
11401147
HexFloat<FloatProxy<Float8_E4M3>, HexFloatTraits<FloatProxy<Float8_E4M3>>>&
11411148
value) {
11421149
// First parse as a 32-bit float.
11431150
HexFloat<FloatProxy<float>> float_val(0.0f);
1144-
ParseNormalFloat(is, negate_value, float_val);
1151+
ParseNormalFloat(is, leading_sign, float_val);
11451152

11461153
if (float_val.value().getAsFloat() > 448.0f) {
11471154
is.setstate(std::ios_base::failbit);
@@ -1162,8 +1169,8 @@ inline std::istream& ParseNormalFloat<FloatProxy<Float8_E4M3>,
11621169
// This will parse the float as it were a Float8_E5M2 floating point number,
11631170
// and then round it down to fit into a Float16 value.
11641171
// The number is rounded towards zero.
1165-
// If negate_value is true then the number may not have a leading minus or
1166-
// plus, and if it successfully parses, then the number is negated before
1172+
// If leading_sign is Plus or Minus, then the number may not have a leading
1173+
// minus or plus. If it successfully parses, and the leading sign was Minus,
11671174
// being stored into the value parameter.
11681175
// If the value cannot be correctly parsed or overflows the target floating
11691176
// point type, then set the fail bit on the stream.
@@ -1173,12 +1180,12 @@ inline std::istream& ParseNormalFloat<FloatProxy<Float8_E4M3>,
11731180
template <>
11741181
inline std::istream& ParseNormalFloat<FloatProxy<Float8_E5M2>,
11751182
HexFloatTraits<FloatProxy<Float8_E5M2>>>(
1176-
std::istream& is, bool negate_value,
1183+
std::istream& is, LeadingSign leading_sign,
11771184
HexFloat<FloatProxy<Float8_E5M2>, HexFloatTraits<FloatProxy<Float8_E5M2>>>&
11781185
value) {
11791186
// First parse as a 32-bit float.
11801187
HexFloat<FloatProxy<float>> float_val(0.0f);
1181-
ParseNormalFloat(is, negate_value, float_val);
1188+
ParseNormalFloat(is, leading_sign, float_val);
11821189

11831190
// Then convert to Float8_E5M2 float, saturating at infinities, and
11841191
// rounding toward zero.
@@ -1270,14 +1277,19 @@ std::istream& operator>>(std::istream& is, HexFloat<T, Traits>& value) {
12701277
}
12711278

12721279
auto next_char = is.peek();
1273-
bool negate_value = false;
12741280

1275-
if (next_char != '-' && next_char != '0') {
1276-
return ParseNormalFloat(is, negate_value, value);
1281+
auto leading_sign = LeadingSign::None;
1282+
1283+
if (next_char != '-' && next_char != '0' && next_char != '+') {
1284+
return ParseNormalFloat(is, LeadingSign::None, value);
12771285
}
12781286

12791287
if (next_char == '-') {
1280-
negate_value = true;
1288+
leading_sign = LeadingSign::Minus;
1289+
is.get();
1290+
next_char = is.peek();
1291+
} else if (next_char == '+') {
1292+
leading_sign = LeadingSign::Plus;
12811293
is.get();
12821294
next_char = is.peek();
12831295
}
@@ -1287,12 +1299,12 @@ std::istream& operator>>(std::istream& is, HexFloat<T, Traits>& value) {
12871299
auto maybe_hex_start = is.peek();
12881300
if (maybe_hex_start != 'x' && maybe_hex_start != 'X') {
12891301
is.unget();
1290-
return ParseNormalFloat(is, negate_value, value);
1302+
return ParseNormalFloat(is, leading_sign, value);
12911303
} else {
12921304
is.get(); // Throw away the 'x';
12931305
}
12941306
} else {
1295-
return ParseNormalFloat(is, negate_value, value);
1307+
return ParseNormalFloat(is, leading_sign, value);
12961308
}
12971309

12981310
// This "looks" like a hex-float so treat it as one.
@@ -1508,7 +1520,8 @@ std::istream& operator>>(std::istream& is, HexFloat<T, Traits>& value) {
15081520
}
15091521

15101522
uint_type output_bits = static_cast<uint_type>(
1511-
static_cast<uint_type>(negate_value ? 1 : 0) << HF::top_bit_left_shift);
1523+
static_cast<uint_type>(leading_sign == LeadingSign::Minus ? 1 : 0)
1524+
<< HF::top_bit_left_shift);
15121525
output_bits |= fraction;
15131526

15141527
uint_type shifted_exponent = static_cast<uint_type>(

0 commit comments

Comments
 (0)