/src/hermes/include/hermes/Support/Conversions.h
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (c) Meta Platforms, Inc. and affiliates. |
3 | | * |
4 | | * This source code is licensed under the MIT license found in the |
5 | | * LICENSE file in the root directory of this source tree. |
6 | | */ |
7 | | |
8 | | #ifndef HERMES_SUPPORT_CONVERSIONS_H |
9 | | #define HERMES_SUPPORT_CONVERSIONS_H |
10 | | |
11 | | #include "hermes/Support/OptValue.h" |
12 | | |
13 | | #include "llvh/ADT/StringRef.h" |
14 | | #include "llvh/Support/MathExtras.h" |
15 | | |
16 | | #include <cstdint> |
17 | | |
18 | | namespace hermes { |
19 | | |
20 | | /// Convert a double to a 32-bit integer according to ES5.1 section 9.5. |
21 | | /// It can also be used for converting to an unsigned integer, which has the |
22 | | /// same bit pattern. |
23 | | /// NaN and Infinity are always converted to 0. The rest of the numbers are |
24 | | /// converted to a (conceptually) infinite-width integer and the low 32 bits of |
25 | | /// the integer are then returned. |
26 | | /// This is the out-of-line "slow path" which performs a relatively slow |
27 | | /// conversion by manipulating bits in the representation of the double value. |
28 | | int32_t truncateToInt32SlowPath(double d); |
29 | | |
30 | | /// Convert a double to a 32-bit integer according to ES5.1 section 9.5. |
31 | | /// It can also be used for converting to an unsigned integer, which has the |
32 | | /// same bit pattern. |
33 | | /// NaN and Infinity are always converted to 0. The rest of the numbers are |
34 | | /// converted to a (conceptually) infinite-width integer and the low 32 bits of |
35 | | /// the integer are then returned. |
36 | | int32_t truncateToInt32(double d) LLVM_NO_SANITIZE("float-cast-overflow"); |
37 | 83 | inline int32_t truncateToInt32(double d) { |
38 | | /// ARM64 has different behavior when the double value can't fit into |
39 | | /// int64_t (results in 2^63-1 instead of -2^63 on x86-64), and 2^63-1 can't |
40 | | /// be represented precisely in double, so it's converted to 2^63. The result |
41 | | /// is that a double value 2^63 still goes through the fast path and |
42 | | /// eventually is casted to int32_t and -1 is returned, which is wrong. The |
43 | | /// solution is to use smaller width integer (so every value can be |
44 | | /// represented in double). In constant path, we check the range of |
45 | | /// [-2^53, 2^53], where 53 is the number of precision bits for double. In |
46 | | /// non-constant fast path, we do a left shift followed by right shift of 1 |
47 | | /// bit to avoid imprecise conversion between double and int64_t on 2^63 ( |
48 | | /// the top 2 bits "10" becomes "00" after the shifting). |
49 | | /// On 32bit platform, this non-constant path produces less efficient code, |
50 | | /// so instead, we use conversion to int32_t directly. |
51 | | /// In addition, use __builtin_constant_p() to avoid UB caused by constant |
52 | | /// propagation. |
53 | | |
54 | 83 | if constexpr (sizeof(void *) == 8) { |
55 | | // Use this builtin to avoid undefined behavior caused by constant |
56 | | // propagation when \p d can't fit into int64_t. |
57 | 83 | #if defined(__GNUC__) || defined(__clang__) |
58 | 83 | if (__builtin_constant_p(d)) { |
59 | 0 | #endif |
60 | | // Be aggressive on constant path, use the maximum precision bits |
61 | | // of double type for range check. |
62 | 0 | if (d >= (int64_t)(-1ULL << 53) && d <= (1LL << 53)) { |
63 | 0 | return (int32_t)(int64_t)d; |
64 | 0 | } |
65 | 0 | #if defined(__GNUC__) || defined(__clang__) |
66 | 83 | } else { |
67 | 83 | int64_t fast = (int64_t)((uint64_t)d << 1) >> 1; |
68 | 83 | if (LLVM_LIKELY(fast == d)) |
69 | 81 | return (int32_t)fast; |
70 | 83 | } |
71 | 83 | #endif |
72 | 83 | } else { |
73 | 0 | #if defined(__GNUC__) || defined(__clang__) |
74 | 0 | if (__builtin_constant_p(d)) { |
75 | 0 | #endif |
76 | | // Converted to int32_t directly on 32bit arch for efficiency. |
77 | | // Many uint32_t values may fall to slow path though. |
78 | 0 | if (d >= (int64_t)(-1ULL << 53) && d <= (1LL << 53)) { |
79 | 0 | return (int32_t)(int64_t)d; |
80 | 0 | } |
81 | 0 | #if defined(__GNUC__) || defined(__clang__) |
82 | 0 | } else { |
83 | 0 | int32_t fast = (int32_t)d; |
84 | 0 | if (LLVM_LIKELY(fast == d)) |
85 | 0 | return fast; |
86 | 0 | } |
87 | 0 | #endif |
88 | 0 | } |
89 | 2 | return truncateToInt32SlowPath(d); |
90 | 83 | } |
91 | | |
92 | 0 | inline uint32_t truncateToUInt32(double d) { |
93 | 0 | return (uint32_t)truncateToInt32(d); |
94 | 0 | } |
95 | | |
96 | | /// Convert a string in the range defined by [first, last) to an array index |
97 | | /// following ES5.1 15.4: |
98 | | /// "A property name P (in the form of a String value) is an array index if and |
99 | | /// only if ToString(ToUint32(P)) is equal to P and ToUint32(P) is not equal to |
100 | | /// 2**32−1." |
101 | | /// IT must meet the requirements of InputIterator and its 'value_type' must be |
102 | | /// an integral type holding a superset of ASCII, typically char, char16_t, |
103 | | /// char32_t or wchar_t. |
104 | | /// \return an "empty" OptValue if the string isn't a valid index, or the |
105 | | /// converted uint32_t value otherwise. |
106 | | template <typename IT> |
107 | 414k | OptValue<uint32_t> toArrayIndex(IT first, IT last) { |
108 | | /// Empty string is invalid. |
109 | 414k | if (first == last) |
110 | 18 | return llvh::None; |
111 | | |
112 | | // Leading 0 is special. |
113 | 414k | if (*first == '0') { |
114 | 11 | ++first; |
115 | | // Just "0"? |
116 | 11 | if (first == last) |
117 | 11 | return 0; |
118 | | // Leading 0 is invalid otherwise. |
119 | 0 | return llvh::None; |
120 | 11 | } |
121 | | |
122 | 414k | uint32_t res = 0; |
123 | 719k | do { |
124 | 719k | auto ch = *first; |
125 | 719k | if (ch < '0' || ch > '9') |
126 | 302k | return llvh::None; |
127 | 417k | uint64_t tmp = (uint64_t)res * 10 + (ch - '0'); |
128 | | // Check for overflow. |
129 | 417k | if (tmp & ((uint64_t)0xFFFFFFFFu << 32)) |
130 | 0 | return llvh::None; |
131 | 417k | res = (uint32_t)tmp; |
132 | 417k | } while (++first != last); |
133 | | |
134 | | // 0xFFFFFFFF is not a valid array index. |
135 | 111k | if (res == 0xFFFFFFFFu) |
136 | 0 | return llvh::None; |
137 | | |
138 | 111k | return res; |
139 | 111k | } hermes::OptValue<unsigned int> hermes::toArrayIndex<char const*>(char const*, char const*) Line | Count | Source | 107 | 414k | OptValue<uint32_t> toArrayIndex(IT first, IT last) { | 108 | | /// Empty string is invalid. | 109 | 414k | if (first == last) | 110 | 18 | return llvh::None; | 111 | | | 112 | | // Leading 0 is special. | 113 | 414k | if (*first == '0') { | 114 | 11 | ++first; | 115 | | // Just "0"? | 116 | 11 | if (first == last) | 117 | 11 | return 0; | 118 | | // Leading 0 is invalid otherwise. | 119 | 0 | return llvh::None; | 120 | 11 | } | 121 | | | 122 | 414k | uint32_t res = 0; | 123 | 719k | do { | 124 | 719k | auto ch = *first; | 125 | 719k | if (ch < '0' || ch > '9') | 126 | 302k | return llvh::None; | 127 | 417k | uint64_t tmp = (uint64_t)res * 10 + (ch - '0'); | 128 | | // Check for overflow. | 129 | 417k | if (tmp & ((uint64_t)0xFFFFFFFFu << 32)) | 130 | 0 | return llvh::None; | 131 | 417k | res = (uint32_t)tmp; | 132 | 417k | } while (++first != last); | 133 | | | 134 | | // 0xFFFFFFFF is not a valid array index. | 135 | 111k | if (res == 0xFFFFFFFFu) | 136 | 0 | return llvh::None; | 137 | | | 138 | 111k | return res; | 139 | 111k | } |
Unexecuted instantiation: hermes::OptValue<unsigned int> hermes::toArrayIndex<char16_t const*>(char16_t const*, char16_t const*) |
140 | | |
141 | | /// A convenient wrapper around 'toArrayIndex(first,last)'. |
142 | 183k | inline OptValue<uint32_t> toArrayIndex(llvh::StringRef str) { |
143 | 183k | return toArrayIndex(str.begin(), str.end()); |
144 | 183k | } |
145 | | |
146 | | /// Attempt to convert a double to a valid JavaScript array number. |
147 | | OptValue<uint32_t> doubleToArrayIndex(double d) |
148 | | LLVM_NO_SANITIZE("float-cast-overflow"); |
149 | 903k | inline OptValue<uint32_t> doubleToArrayIndex(double d) { |
150 | 903k | uint32_t index = (uint32_t)d; |
151 | 903k | if (index == d && index != 0xFFFFFFFFu) |
152 | 903k | return index; |
153 | 0 | return llvh::None; |
154 | 903k | } |
155 | | |
156 | | /// Size of buffer that must be passed to numberToString. |
157 | | const size_t NUMBER_TO_STRING_BUF_SIZE = 32; |
158 | | |
159 | | /// Convert a double number to string, following ES5.1 9.8.1. |
160 | | /// \param m the number to convert |
161 | | /// \param dest output buffer |
162 | | /// \param destSize size of dest, at least NUMBER_TO_STRING_BUF_SIZE |
163 | | /// \return the length of the generated string (excluding the terminating zero). |
164 | | size_t numberToString(double m, char *dest, size_t destSize); |
165 | | |
166 | | /// Takes a letter (a-z or A-Z) and makes it lowercase. |
167 | | template <typename T> |
168 | 4.88M | inline T charLetterToLower(T ch) { |
169 | 4.88M | return ch | 32; |
170 | 4.88M | } Unexecuted instantiation: char16_t hermes::charLetterToLower<char16_t>(char16_t) char hermes::charLetterToLower<char>(char) Line | Count | Source | 168 | 4.88M | inline T charLetterToLower(T ch) { | 169 | 4.88M | return ch | 32; | 170 | 4.88M | } |
|
171 | | |
172 | | /// Takes a non-empty string (without the leading "0x" if hex) and parses it |
173 | | /// as radix \p radix, calling \p digitCallback with the value of each digit, |
174 | | /// going from left to right. |
175 | | /// \param AllowNumericSeparator when true, allow '_' as a separator and ignore |
176 | | /// it when parsing. |
177 | | /// \returns true if the string was successfully parsed, false otherwise. |
178 | | template <bool AllowNumericSeparator, class Iterable, typename Callback> |
179 | 1.31M | bool parseIntWithRadixDigits(Iterable str, int radix, Callback digitCallback) { |
180 | 1.31M | assert( |
181 | 1.31M | radix >= 2 && radix <= 36 && "Invalid radix passed to parseIntWithRadix"); |
182 | 0 | assert(str.begin() != str.end() && "Empty string"); |
183 | 5.38M | for (auto it = str.begin(); it != str.end(); ++it) { |
184 | 4.07M | auto c = *it; |
185 | 4.07M | auto cLow = charLetterToLower(c); |
186 | 4.07M | if ('0' <= c && c <= '9' && c < '0' + radix) { |
187 | 4.07M | digitCallback(c - '0'); |
188 | 4.07M | } else if ('a' <= cLow && cLow < 'a' + radix - 10) { |
189 | 0 | digitCallback(cLow - 'a' + 0xa); |
190 | 0 | } else if (AllowNumericSeparator && LLVM_UNLIKELY(c == '_')) { |
191 | | // Ensure the '_' is in a valid location. |
192 | | // It can only be between two existing digits. |
193 | 0 | if (it == str.begin() || it == str.end() - 1) { |
194 | 0 | return false; |
195 | 0 | } |
196 | | // Note that the previous character must not be '_' if the current |
197 | | // character is '_', because we would have returned None. |
198 | | // So just check if the next character is '_'. |
199 | 0 | char next = *(it + 1); |
200 | 0 | if (next == '_') { |
201 | 0 | return false; |
202 | 0 | } |
203 | 0 | } else { |
204 | 0 | return false; |
205 | 0 | } |
206 | 4.07M | } |
207 | 1.31M | return true; |
208 | 1.31M | } Unexecuted instantiation: bool hermes::parseIntWithRadixDigits<false, hermes::vm::StringView, hermes::parseIntWithRadix<false, hermes::vm::StringView>(hermes::vm::StringView, int)::{lambda(unsigned char)#1}>(hermes::vm::StringView, int, hermes::parseIntWithRadix<false, hermes::vm::StringView>(hermes::vm::StringView, int)::{lambda(unsigned char)#1}) JSLexer.cpp:bool hermes::parseIntWithRadixDigits<true, llvh::ArrayRef<char>, hermes::parser::JSLexer::scanNumber(hermes::parser::JSLexer::GrammarContext)::$_2>(llvh::ArrayRef<char>, int, hermes::parser::JSLexer::scanNumber(hermes::parser::JSLexer::GrammarContext)::$_2) Line | Count | Source | 179 | 8 | bool parseIntWithRadixDigits(Iterable str, int radix, Callback digitCallback) { | 180 | 8 | assert( | 181 | 8 | radix >= 2 && radix <= 36 && "Invalid radix passed to parseIntWithRadix"); | 182 | 0 | assert(str.begin() != str.end() && "Empty string"); | 183 | 87 | for (auto it = str.begin(); it != str.end(); ++it) { | 184 | 79 | auto c = *it; | 185 | 79 | auto cLow = charLetterToLower(c); | 186 | 79 | if ('0' <= c && c <= '9' && c < '0' + radix) { | 187 | 79 | digitCallback(c - '0'); | 188 | 79 | } else if ('a' <= cLow && cLow < 'a' + radix - 10) { | 189 | 0 | digitCallback(cLow - 'a' + 0xa); | 190 | 0 | } else if (AllowNumericSeparator && LLVM_UNLIKELY(c == '_')) { | 191 | | // Ensure the '_' is in a valid location. | 192 | | // It can only be between two existing digits. | 193 | 0 | if (it == str.begin() || it == str.end() - 1) { | 194 | 0 | return false; | 195 | 0 | } | 196 | | // Note that the previous character must not be '_' if the current | 197 | | // character is '_', because we would have returned None. | 198 | | // So just check if the next character is '_'. | 199 | 0 | char next = *(it + 1); | 200 | 0 | if (next == '_') { | 201 | 0 | return false; | 202 | 0 | } | 203 | 0 | } else { | 204 | 0 | return false; | 205 | 0 | } | 206 | 79 | } | 207 | 8 | return true; | 208 | 8 | } |
bool hermes::parseIntWithRadixDigits<true, llvh::ArrayRef<char>, hermes::parseIntWithRadix<true, llvh::ArrayRef<char> >(llvh::ArrayRef<char>, int)::{lambda(unsigned char)#1}>(llvh::ArrayRef<char>, int, hermes::parseIntWithRadix<true, llvh::ArrayRef<char> >(llvh::ArrayRef<char>, int)::{lambda(unsigned char)#1}) Line | Count | Source | 179 | 1.31M | bool parseIntWithRadixDigits(Iterable str, int radix, Callback digitCallback) { | 180 | 1.31M | assert( | 181 | 1.31M | radix >= 2 && radix <= 36 && "Invalid radix passed to parseIntWithRadix"); | 182 | 0 | assert(str.begin() != str.end() && "Empty string"); | 183 | 5.38M | for (auto it = str.begin(); it != str.end(); ++it) { | 184 | 4.07M | auto c = *it; | 185 | 4.07M | auto cLow = charLetterToLower(c); | 186 | 4.07M | if ('0' <= c && c <= '9' && c < '0' + radix) { | 187 | 4.07M | digitCallback(c - '0'); | 188 | 4.07M | } else if ('a' <= cLow && cLow < 'a' + radix - 10) { | 189 | 0 | digitCallback(cLow - 'a' + 0xa); | 190 | 0 | } else if (AllowNumericSeparator && LLVM_UNLIKELY(c == '_')) { | 191 | | // Ensure the '_' is in a valid location. | 192 | | // It can only be between two existing digits. | 193 | 0 | if (it == str.begin() || it == str.end() - 1) { | 194 | 0 | return false; | 195 | 0 | } | 196 | | // Note that the previous character must not be '_' if the current | 197 | | // character is '_', because we would have returned None. | 198 | | // So just check if the next character is '_'. | 199 | 0 | char next = *(it + 1); | 200 | 0 | if (next == '_') { | 201 | 0 | return false; | 202 | 0 | } | 203 | 0 | } else { | 204 | 0 | return false; | 205 | 0 | } | 206 | 4.07M | } | 207 | 1.31M | return true; | 208 | 1.31M | } |
|
209 | | |
210 | | /// Takes a non-empty string (without the leading "0x" if hex) and parses it |
211 | | /// as radix \p radix. |
212 | | /// \param AllowNumericSeparator when true, allow '_' as a separator and ignore |
213 | | /// it when parsing. |
214 | | /// \returns the double that results on success, empty on error. |
215 | | template <bool AllowNumericSeparator, class Iterable> |
216 | 1.31M | OptValue<double> parseIntWithRadix(Iterable str, int radix) { |
217 | 1.31M | double result = 0; |
218 | 1.31M | bool success = parseIntWithRadixDigits<AllowNumericSeparator>( |
219 | 4.07M | str, radix, [&result, radix](uint8_t d) { |
220 | 4.07M | result *= radix; |
221 | 4.07M | result += d; |
222 | 4.07M | }); Unexecuted instantiation: hermes::parseIntWithRadix<false, hermes::vm::StringView>(hermes::vm::StringView, int)::{lambda(unsigned char)#1}::operator()(unsigned char) const hermes::parseIntWithRadix<true, llvh::ArrayRef<char> >(llvh::ArrayRef<char>, int)::{lambda(unsigned char)#1}::operator()(unsigned char) const Line | Count | Source | 219 | 4.07M | str, radix, [&result, radix](uint8_t d) { | 220 | 4.07M | result *= radix; | 221 | 4.07M | result += d; | 222 | 4.07M | }); |
|
223 | 1.31M | if (!success) |
224 | 0 | return llvh::None; |
225 | | |
226 | | // The largest value that fits in the 53-bit mantissa (2**53). |
227 | 1.31M | const double MAX_MANTISSA = 9007199254740992.0; |
228 | 1.31M | if (result >= MAX_MANTISSA && llvh::isPowerOf2_32(radix)) { |
229 | | // If the result is too high, manually reconstruct the double if |
230 | | // the radix is 2, 4, 8, 16, 32. |
231 | | // Go through the digits bit by bit, and manually round when necessary. |
232 | 14 | result = 0; |
233 | | |
234 | | // Keep track of how far along parsing is using this enum. |
235 | 14 | enum Mode { |
236 | 14 | LEADING_ZERO, // Haven't seen a set bit yet. |
237 | 14 | MANTISSA, // Lower bits that allow exact representation. |
238 | 14 | EXP_LOW_BIT, // Lowest bit of the exponent (determine rounding). |
239 | 14 | EXP_LEADING_ZERO, // Zeros in the exponent. |
240 | 14 | EXPONENT, // Seen a set bit in the exponent. |
241 | 14 | }; |
242 | | |
243 | 14 | size_t remainingMantissa = 53; |
244 | 14 | double expFactor = 0.0; |
245 | 14 | size_t curDigit = 0; |
246 | | |
247 | 14 | bool lastMantissaBit = false; |
248 | 14 | bool lowestExponentBit = false; |
249 | | |
250 | 14 | Mode curMode = Mode::LEADING_ZERO; |
251 | 14 | auto itr = str.begin(); |
252 | 14 | auto e = str.end(); |
253 | 2.41M | for (size_t bitMask = 0;;) { |
254 | 2.41M | if (bitMask == 0) { |
255 | | // Only need to do this check every log2(radix) iterations. |
256 | 803k | if (itr == e) { |
257 | 14 | break; |
258 | 14 | } |
259 | | // We know it fits in 7 bits after the first pass. |
260 | 803k | char c = (char)*itr; |
261 | 803k | if (AllowNumericSeparator && LLVM_UNLIKELY(c == '_')) { |
262 | 0 | ++itr; |
263 | 0 | continue; |
264 | 0 | } |
265 | 803k | auto cLow = charLetterToLower(c); |
266 | 803k | if ('0' <= c && c <= '9') { |
267 | 803k | curDigit = c - '0'; |
268 | 803k | } else { |
269 | | // Must be valid, else we would have returned NaN on first pass. |
270 | 0 | assert('a' <= cLow && cLow < 'a' + radix - 10); |
271 | 0 | curDigit = cLow - 'a' + 0xa; |
272 | 0 | } |
273 | 0 | ++itr; |
274 | | // Reset bitmask to look at the first bit. |
275 | 803k | bitMask = radix >> 1; |
276 | 803k | } |
277 | 2.41M | bool curBit = (curDigit & bitMask) != 0; |
278 | 2.41M | bitMask >>= 1; |
279 | | |
280 | 2.41M | switch (curMode) { |
281 | 164k | case Mode::LEADING_ZERO: |
282 | | // Go through the string until we hit a nonzero bit. |
283 | 164k | if (curBit) { |
284 | 14 | --remainingMantissa; |
285 | 14 | result = 1; |
286 | | // No more leading zeros. |
287 | 14 | curMode = Mode::MANTISSA; |
288 | 14 | } |
289 | 164k | break; |
290 | 728 | case Mode::MANTISSA: |
291 | | // Read into the lower bits of the mantissa (plain binary). |
292 | 728 | result *= 2; |
293 | 728 | result += curBit; |
294 | 728 | --remainingMantissa; |
295 | 728 | if (remainingMantissa == 0) { |
296 | | // Out of bits, set the last bit and go to the next curMode. |
297 | 14 | lastMantissaBit = curBit; |
298 | 14 | curMode = Mode::EXP_LOW_BIT; |
299 | 14 | } |
300 | 728 | break; |
301 | 14 | case Mode::EXP_LOW_BIT: |
302 | 14 | lowestExponentBit = curBit; |
303 | 14 | expFactor = 2.0; |
304 | 14 | curMode = Mode::EXP_LEADING_ZERO; |
305 | 14 | break; |
306 | 2.24M | case Mode::EXP_LEADING_ZERO: |
307 | 2.24M | if (curBit) { |
308 | 0 | curMode = Mode::EXPONENT; |
309 | 0 | } |
310 | 2.24M | expFactor *= 2.0; |
311 | 2.24M | break; |
312 | 0 | case Mode::EXPONENT: |
313 | 0 | expFactor *= 2.0; |
314 | 0 | break; |
315 | 2.41M | } |
316 | 2.41M | } |
317 | 14 | switch (curMode) { |
318 | 0 | case Mode::LEADING_ZERO: |
319 | 0 | case Mode::MANTISSA: |
320 | 0 | case Mode::EXP_LOW_BIT: |
321 | | // Nothing to do here, already read those in. |
322 | 0 | break; |
323 | 14 | case Mode::EXP_LEADING_ZERO: |
324 | | // Rounding up. |
325 | 14 | result += lowestExponentBit && lastMantissaBit; |
326 | 14 | result *= expFactor; |
327 | 14 | break; |
328 | 0 | case Mode::EXPONENT: |
329 | | // Rounding up. |
330 | 0 | result += lowestExponentBit; |
331 | 0 | result *= expFactor; |
332 | 0 | break; |
333 | 14 | } |
334 | 14 | } |
335 | 1.31M | return result; |
336 | 1.31M | } Unexecuted instantiation: hermes::OptValue<double> hermes::parseIntWithRadix<false, hermes::vm::StringView>(hermes::vm::StringView, int) hermes::OptValue<double> hermes::parseIntWithRadix<true, llvh::ArrayRef<char> >(llvh::ArrayRef<char>, int) Line | Count | Source | 216 | 1.31M | OptValue<double> parseIntWithRadix(Iterable str, int radix) { | 217 | 1.31M | double result = 0; | 218 | 1.31M | bool success = parseIntWithRadixDigits<AllowNumericSeparator>( | 219 | 1.31M | str, radix, [&result, radix](uint8_t d) { | 220 | 1.31M | result *= radix; | 221 | 1.31M | result += d; | 222 | 1.31M | }); | 223 | 1.31M | if (!success) | 224 | 0 | return llvh::None; | 225 | | | 226 | | // The largest value that fits in the 53-bit mantissa (2**53). | 227 | 1.31M | const double MAX_MANTISSA = 9007199254740992.0; | 228 | 1.31M | if (result >= MAX_MANTISSA && llvh::isPowerOf2_32(radix)) { | 229 | | // If the result is too high, manually reconstruct the double if | 230 | | // the radix is 2, 4, 8, 16, 32. | 231 | | // Go through the digits bit by bit, and manually round when necessary. | 232 | 14 | result = 0; | 233 | | | 234 | | // Keep track of how far along parsing is using this enum. | 235 | 14 | enum Mode { | 236 | 14 | LEADING_ZERO, // Haven't seen a set bit yet. | 237 | 14 | MANTISSA, // Lower bits that allow exact representation. | 238 | 14 | EXP_LOW_BIT, // Lowest bit of the exponent (determine rounding). | 239 | 14 | EXP_LEADING_ZERO, // Zeros in the exponent. | 240 | 14 | EXPONENT, // Seen a set bit in the exponent. | 241 | 14 | }; | 242 | | | 243 | 14 | size_t remainingMantissa = 53; | 244 | 14 | double expFactor = 0.0; | 245 | 14 | size_t curDigit = 0; | 246 | | | 247 | 14 | bool lastMantissaBit = false; | 248 | 14 | bool lowestExponentBit = false; | 249 | | | 250 | 14 | Mode curMode = Mode::LEADING_ZERO; | 251 | 14 | auto itr = str.begin(); | 252 | 14 | auto e = str.end(); | 253 | 2.41M | for (size_t bitMask = 0;;) { | 254 | 2.41M | if (bitMask == 0) { | 255 | | // Only need to do this check every log2(radix) iterations. | 256 | 803k | if (itr == e) { | 257 | 14 | break; | 258 | 14 | } | 259 | | // We know it fits in 7 bits after the first pass. | 260 | 803k | char c = (char)*itr; | 261 | 803k | if (AllowNumericSeparator && LLVM_UNLIKELY(c == '_')) { | 262 | 0 | ++itr; | 263 | 0 | continue; | 264 | 0 | } | 265 | 803k | auto cLow = charLetterToLower(c); | 266 | 803k | if ('0' <= c && c <= '9') { | 267 | 803k | curDigit = c - '0'; | 268 | 803k | } else { | 269 | | // Must be valid, else we would have returned NaN on first pass. | 270 | 0 | assert('a' <= cLow && cLow < 'a' + radix - 10); | 271 | 0 | curDigit = cLow - 'a' + 0xa; | 272 | 0 | } | 273 | 0 | ++itr; | 274 | | // Reset bitmask to look at the first bit. | 275 | 803k | bitMask = radix >> 1; | 276 | 803k | } | 277 | 2.41M | bool curBit = (curDigit & bitMask) != 0; | 278 | 2.41M | bitMask >>= 1; | 279 | | | 280 | 2.41M | switch (curMode) { | 281 | 164k | case Mode::LEADING_ZERO: | 282 | | // Go through the string until we hit a nonzero bit. | 283 | 164k | if (curBit) { | 284 | 14 | --remainingMantissa; | 285 | 14 | result = 1; | 286 | | // No more leading zeros. | 287 | 14 | curMode = Mode::MANTISSA; | 288 | 14 | } | 289 | 164k | break; | 290 | 728 | case Mode::MANTISSA: | 291 | | // Read into the lower bits of the mantissa (plain binary). | 292 | 728 | result *= 2; | 293 | 728 | result += curBit; | 294 | 728 | --remainingMantissa; | 295 | 728 | if (remainingMantissa == 0) { | 296 | | // Out of bits, set the last bit and go to the next curMode. | 297 | 14 | lastMantissaBit = curBit; | 298 | 14 | curMode = Mode::EXP_LOW_BIT; | 299 | 14 | } | 300 | 728 | break; | 301 | 14 | case Mode::EXP_LOW_BIT: | 302 | 14 | lowestExponentBit = curBit; | 303 | 14 | expFactor = 2.0; | 304 | 14 | curMode = Mode::EXP_LEADING_ZERO; | 305 | 14 | break; | 306 | 2.24M | case Mode::EXP_LEADING_ZERO: | 307 | 2.24M | if (curBit) { | 308 | 0 | curMode = Mode::EXPONENT; | 309 | 0 | } | 310 | 2.24M | expFactor *= 2.0; | 311 | 2.24M | break; | 312 | 0 | case Mode::EXPONENT: | 313 | 0 | expFactor *= 2.0; | 314 | 0 | break; | 315 | 2.41M | } | 316 | 2.41M | } | 317 | 14 | switch (curMode) { | 318 | 0 | case Mode::LEADING_ZERO: | 319 | 0 | case Mode::MANTISSA: | 320 | 0 | case Mode::EXP_LOW_BIT: | 321 | | // Nothing to do here, already read those in. | 322 | 0 | break; | 323 | 14 | case Mode::EXP_LEADING_ZERO: | 324 | | // Rounding up. | 325 | 14 | result += lowestExponentBit && lastMantissaBit; | 326 | 14 | result *= expFactor; | 327 | 14 | break; | 328 | 0 | case Mode::EXPONENT: | 329 | | // Rounding up. | 330 | 0 | result += lowestExponentBit; | 331 | 0 | result *= expFactor; | 332 | 0 | break; | 333 | 14 | } | 334 | 14 | } | 335 | 1.31M | return result; | 336 | 1.31M | } |
|
337 | | |
338 | | } // namespace hermes |
339 | | |
340 | | extern "C" { |
341 | | size_t hermes_numberToString(double m, char *dest, size_t destSize); |
342 | | } |
343 | | |
344 | | #endif // HERMES_SUPPORT_CONVERSIONS_H |