• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #ifndef PANDA_PLUGINS_ETS_RUNTIME_INTRINSICS_HELPERS_H
17 #define PANDA_PLUGINS_ETS_RUNTIME_INTRINSICS_HELPERS_H
18 
19 #include <cmath>
20 #include <cstdint>
21 #include <type_traits>
22 #include "libpandabase/utils/bit_helpers.h"
23 #include "libpandabase/utils/utils.h"
24 #include "intrinsics.h"
25 #include "plugins/ets/runtime/types/ets_string.h"
26 #include "plugins/ets/runtime/ets_exceptions.h"
27 #include "plugins/ets/runtime/ets_panda_file_items.h"
28 
29 namespace panda::ets::intrinsics::helpers {
30 
31 inline constexpr double MIN_RADIX = 2;
32 inline constexpr double MAX_RADIX = 36;
33 inline constexpr double MIN_FRACTION = 0;
34 inline constexpr double MAX_FRACTION = 100;
35 
36 inline constexpr uint32_t MAX_PRECISION = 16;
37 inline constexpr uint8_t BINARY = 2;
38 inline constexpr uint8_t OCTAL = 8;
39 inline constexpr uint8_t DECIMAL = 10;
40 inline constexpr uint8_t HEXADECIMAL = 16;
41 inline constexpr double HALF = 0.5;
42 inline constexpr double EPSILON = std::numeric_limits<double>::epsilon();
43 inline constexpr double MAX_SAFE_INTEGER = 9007199254740991;
44 inline constexpr double MAX_VALUE = std::numeric_limits<double>::max();
45 inline constexpr double MIN_VALUE = std::numeric_limits<double>::min();
46 inline constexpr double POSITIVE_INFINITY = coretypes::TaggedValue::VALUE_INFINITY;
47 inline constexpr double NAN_VALUE = coretypes::TaggedValue::VALUE_NAN;
48 
49 inline constexpr int DOUBLE_MAX_PRECISION = 17;
50 inline constexpr int FLOAT_MAX_PRECISION = 9;
51 
52 // NOLINTNEXTLINE(modernize-avoid-c-arrays)
53 inline constexpr uint16_t SPACE_OR_LINE_TERMINAL[] = {
54     0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x0020, 0x00A0, 0x1680, 0x2000, 0x2001, 0x2002, 0x2003, 0x2004,
55     0x2005, 0x2006, 0x2007, 0x2008, 0x2009, 0x200A, 0x2028, 0x2029, 0x202F, 0x205F, 0x3000, 0xFEFF,
56 };
57 
58 // NOLINTNEXTLINE(modernize-avoid-c-arrays)
59 inline constexpr char CHARS[] = "0123456789abcdefghijklmnopqrstuvwxyz";
60 inline constexpr uint64_t MAX_MANTISSA = 0x1ULL << 52U;
61 inline constexpr size_t BUF_SIZE = 128;
62 inline constexpr size_t TEN = 10;
63 
64 enum class Sign { NONE, NEG, POS };
65 
66 union DoubleValUnion {
67     double fval;
68     uint64_t uval;
69     struct {
70         uint64_t significand : coretypes::DOUBLE_SIGNIFICAND_SIZE;
71         uint16_t exponent : coretypes::DOUBLE_EXPONENT_SIZE;
72         int sign : 1;
73     } bits __attribute__((packed));
74 } __attribute__((may_alias, packed));
75 
76 template <typename T>
MaxSafeInteger()77 constexpr T MaxSafeInteger()
78 {
79     static_assert(std::is_same_v<T, float> || std::is_same_v<T, double>);
80     if (std::is_same_v<T, float>) {
81         return static_cast<float>((UINT32_C(1) << static_cast<uint32_t>(std::numeric_limits<float>::digits)) - 1);
82     }
83     return static_cast<double>((UINT64_C(1) << static_cast<uint32_t>(std::numeric_limits<double>::digits)) - 1);
84 }
85 
SignedZero(Sign sign)86 inline double SignedZero(Sign sign)
87 {
88     return sign == Sign::NEG ? -0.0 : 0.0;
89 }
90 
ToDigit(uint8_t c)91 inline uint8_t ToDigit(uint8_t c)
92 {
93     if (c >= '0' && c <= '9') {
94         return c - '0';
95     }
96     if (c >= 'A' && c <= 'Z') {
97         return c - 'A' + DECIMAL;
98     }
99     if (c >= 'a' && c <= 'z') {
100         return c - 'a' + DECIMAL;
101     }
102     return '$';
103 }
104 
PowHelper(uint64_t number,int16_t exponent,uint8_t radix)105 inline double PowHelper(uint64_t number, int16_t exponent, uint8_t radix)
106 {
107     const double log2Radix {std::log2(radix)};
108 
109     double expRem = log2Radix * exponent;
110     int expI = static_cast<int>(expRem);
111     expRem = expRem - expI;
112 
113     // NOLINTNEXTLINE(readability-magic-numbers)
114     DoubleValUnion u = {static_cast<double>(number) * std::pow(2.0, expRem)};
115 
116     // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access)
117     expI = u.bits.exponent + expI;
118     if (((expI & ~coretypes::DOUBLE_EXPONENT_MASK) != 0) || expI == 0) {  // NOLINT(hicpp-signed-bitwise)
119         if (expI > 0) {
120             return std::numeric_limits<double>::infinity();
121         }
122         if (expI < -static_cast<int>(coretypes::DOUBLE_SIGNIFICAND_SIZE)) {
123             return -std::numeric_limits<double>::infinity();
124         }
125         // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access)
126         u.bits.exponent = 0;
127         // NOLINTNEXTLINE(hicpp-signed-bitwise, cppcoreguidelines-pro-type-union-access)
128         u.bits.significand = (u.bits.significand | coretypes::DOUBLE_HIDDEN_BIT) >> (1 - expI);
129     } else {
130         // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access)
131         u.bits.exponent = expI;
132     }
133 
134     // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access)
135     return u.fval;
136 }
137 
IsNonspace(uint16_t c)138 inline bool IsNonspace(uint16_t c)
139 {
140     int i;
141     int len = sizeof(SPACE_OR_LINE_TERMINAL) / sizeof(SPACE_OR_LINE_TERMINAL[0]);
142     for (i = 0; i < len; i++) {
143         if (c == SPACE_OR_LINE_TERMINAL[i]) {
144             return false;
145         }
146         if (c < SPACE_OR_LINE_TERMINAL[i]) {
147             return true;
148         }
149     }
150     return true;
151 }
152 
GotoNonspace(uint8_t ** ptr,const uint8_t * end)153 inline bool GotoNonspace(uint8_t **ptr, const uint8_t *end)
154 {
155     while (*ptr < end) {
156         uint16_t c = **ptr;
157         size_t size = 1;
158         if (c > INT8_MAX) {
159             size = 0;
160             uint16_t utf8Bit = INT8_MAX + 1;  // equal 0b1000'0000
161             while (utf8Bit > 0 && (c & utf8Bit) == utf8Bit) {
162                 ++size;
163                 utf8Bit >>= 1UL;
164             }
165             if (utf::ConvertRegionUtf8ToUtf16(*ptr, &c, end - *ptr, 1, 0) <= 0) {
166                 return true;
167             }
168         }
169         if (IsNonspace(c)) {
170             return true;
171         }
172         *ptr += size;  // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
173     }
174     return false;
175 }
176 
IsEmptyString(const uint8_t * start,const uint8_t * end)177 inline bool IsEmptyString(const uint8_t *start, const uint8_t *end)
178 {
179     auto p = const_cast<uint8_t *>(start);
180     return !GotoNonspace(&p, end);
181 }
182 
Strtod(const char * str,int exponent,uint8_t radix)183 inline double Strtod(const char *str, int exponent, uint8_t radix)
184 {
185     ASSERT(str != nullptr);
186     ASSERT(radix >= MIN_RADIX && radix <= MAX_RADIX);
187     auto p = const_cast<char *>(str);
188     Sign sign = Sign::NONE;
189     uint64_t number = 0;
190     uint64_t numberMax = (UINT64_MAX - (radix - 1)) / radix;
191     double result = 0.0;
192     if (*p == '-') {
193         sign = Sign::NEG;
194         ++p;
195     }
196     while (*p == '0') {
197         ++p;
198     }
199     while (*p != '\0') {
200         uint8_t digit = ToDigit(static_cast<uint8_t>(*p));
201         if (digit >= radix) {
202             break;
203         }
204         if (number < numberMax) {
205             number = number * radix + digit;
206         } else {
207             ++exponent;
208         }
209         ++p;
210     }
211     if (exponent < 0) {
212         result = number / std::pow(radix, -exponent);
213     } else {
214         result = number * std::pow(radix, exponent);
215     }
216     if (!std::isfinite(result)) {
217         result = PowHelper(number, exponent, radix);
218     }
219     return sign == Sign::NEG ? -result : result;
220 }
221 
Carry(char current,int radix)222 [[maybe_unused]] inline char Carry(char current, int radix)
223 {
224     int digit = static_cast<int>((current > '9') ? (current - 'a' + DECIMAL) : (current - '0'));
225     digit = (digit == (radix - 1)) ? 0 : digit + 1;
226     return CHARS[digit];
227 }
228 
229 template <typename FpType, std::enable_if_t<std::is_floating_point_v<FpType>, bool> = true>
TruncateFp(FpType number)230 FpType TruncateFp(FpType number)
231 {
232     // -0 to +0
233     if (number == 0.0) {
234         return 0;
235     }
236     return (number >= 0) ? std::floor(number) : std::ceil(number);
237 }
238 
239 template <typename FpType, std::enable_if_t<std::is_floating_point_v<FpType>, bool> = true>
DecimalsToString(FpType * numberInteger,FpType fraction,int radix,FpType delta)240 PandaString DecimalsToString(FpType *numberInteger, FpType fraction, int radix, FpType delta)
241 {
242     PandaString result;
243     while (fraction >= delta) {
244         fraction *= radix;
245         delta *= radix;
246         int64_t integer = std::floor(fraction);
247         fraction -= integer;
248         result += CHARS[integer];
249         if (fraction > HALF && fraction + delta > 1) {
250             size_t fractionEnd = result.size() - 1;
251             result[fractionEnd] = Carry(*result.rbegin(), radix);
252             for (; fractionEnd > 0; fractionEnd--) {
253                 if (result[fractionEnd] == '0') {
254                     result[fractionEnd - 1] = Carry(result[fractionEnd - 1], radix);
255                 } else {
256                     break;
257                 }
258             }
259             if (fractionEnd == 0) {
260                 (*numberInteger)++;
261             }
262             break;
263         }
264     }
265     // delete 0 in the end
266     size_t found = result.find_last_not_of('0');
267     if (found != PandaString::npos) {
268         result.erase(found + 1);
269     }
270 
271     return result;
272 }
273 
274 template <typename FpType, std::enable_if_t<std::is_floating_point_v<FpType>, bool> = true>
IntegerToString(FpType number,int radix)275 PandaString IntegerToString(FpType number, int radix)
276 {
277     ASSERT(radix >= MIN_RADIX && radix <= MAX_RADIX);
278     ASSERT(number == std::round(number));
279     PandaString result;
280     while (TruncateFp(number / radix) > static_cast<FpType>(MAX_MANTISSA)) {
281         number /= radix;
282         TruncateFp(number);
283         result = PandaString("0").append(result);
284     }
285     do {
286         auto remainder = static_cast<uint8_t>(std::fmod(number, radix));
287         result = CHARS[remainder] + result;
288         number = TruncateFp((number - remainder) / radix);
289     } while (number > 0);
290     return result;
291 }
292 
293 template <typename FpType, std::enable_if_t<std::is_floating_point_v<FpType>, bool> = true>
StrToFp(char * str,char ** strEnd)294 FpType StrToFp(char *str, char **strEnd)
295 {
296     if constexpr (std::is_same_v<FpType, double>) {
297         return std::strtod(str, strEnd);
298     } else {
299         return std::strtof(str, strEnd);
300     }
301 }
302 
303 template <typename FpType, std::enable_if_t<std::is_floating_point_v<FpType>, bool> = true>
GetBase(FpType d,int digits,int * decpt,char * buf,char * bufTmp,int size)304 void GetBase(FpType d, int digits, int *decpt, char *buf, char *bufTmp, int size)
305 {
306     // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
307     int result = snprintf_s(bufTmp, size, size - 1, "%+.*e", digits - 1, d);
308     if (result == -1) {
309         LOG(FATAL, ETS) << "snprintf_s failed";
310         UNREACHABLE();
311     }
312     // mantissa
313     // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
314     buf[0] = bufTmp[1];
315     if (digits > 1) {
316         // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
317         if (memcpy_s(buf + 1U, digits, bufTmp + 2U, digits) != EOK) {  // 2 means add the point char to buf
318             LOG(FATAL, ETS) << "snprintf_s failed";
319             UNREACHABLE();
320         }
321     }
322     // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
323     buf[digits + 1] = '\0';
324     // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
325     char *endBuf = bufTmp + size;
326     const size_t positive = (digits > 1) ? 1 : 0;
327     // exponent
328     // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
329     *decpt = std::strtol(bufTmp + digits + 2U + positive, &endBuf, TEN) + 1;  // 2 means ignore the integer and point
330 }
331 
332 template <typename FpType, std::enable_if_t<std::is_floating_point_v<FpType>, bool> = true>
GetMinmumDigits(FpType d,int * decpt,char * buf)333 int GetMinmumDigits(FpType d, int *decpt, char *buf)
334 {
335     int digits = 0;
336 
337     // NOLINTNEXTLINE(modernize-avoid-c-arrays)
338     char bufTmp[BUF_SIZE] = {0};
339 
340     // find the minimum amount of digits
341     int minDigits = 1;
342     int maxDigits = std::is_same_v<FpType, double> ? DOUBLE_MAX_PRECISION : FLOAT_MAX_PRECISION;
343 
344     while (minDigits < maxDigits) {
345         digits = (minDigits + maxDigits) / 2_I;
346         GetBase(d, digits, decpt, buf, bufTmp, sizeof(bufTmp));
347 
348         bool same = StrToFp<FpType>(bufTmp, nullptr) == d;
349         if (same) {
350             // no need to keep the trailing zeros
351             // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
352             while (digits >= 2_I && buf[digits] == '0') {  // 2 means ignore the integer and point
353                 digits--;
354             }
355             maxDigits = digits;
356         } else {
357             minDigits = digits + 1;
358         }
359     }
360     digits = maxDigits;
361     GetBase(d, digits, decpt, buf, bufTmp, sizeof(bufTmp));
362 
363     return digits;
364 }
365 
366 double StringToDouble(const uint8_t *start, const uint8_t *end, uint8_t radix, uint32_t flags);
367 double StringToDoubleWithRadix(const uint8_t *start, const uint8_t *end, int radix);
368 EtsString *DoubleToExponential(double number, int digit);
369 EtsString *DoubleToFixed(double number, int digit);
370 EtsString *DoubleToPrecision(double number, int digit);
371 double GetStdDoubleArgument(ObjectHeader *obj);
372 
373 template <typename FpType, std::enable_if_t<std::is_floating_point_v<FpType>, bool> = true>
FpToStringDecimalRadix(FpType number)374 EtsString *FpToStringDecimalRadix(FpType number)
375 {
376     PandaString result;
377     if (number < 0) {
378         result += "-";
379         number = -number;
380     }
381 
382     static constexpr FpType MIN_BOUND = 0.1;
383     using SignedIntType = panda::helpers::TypeHelperT<sizeof(FpType) * CHAR_BIT, true>;
384 
385     if (MIN_BOUND <= number && number < 1) {  // 0.1: 10 ** -1
386         // Fast path. In this case, n==0, just need to calculate k and s.
387         PandaString resultFast = "0.";
388         SignedIntType sFast = 0;
389         int kFast = 1;
390         SignedIntType power = 1;
391         while (kFast <= helpers::FLOAT_MAX_PRECISION) {
392             power *= TEN;                                                      // 10: base 10
393             int digitFast = static_cast<SignedIntType>(number * power) % TEN;  // 10: base 10
394             ASSERT(0 <= digitFast && digitFast <= 9);                          // 9: single digit max
395             sFast = sFast * TEN + digitFast;                                   // 10: base 10
396             resultFast += ToPandaString(digitFast);
397             if (sFast / static_cast<FpType>(power) == number) {  // s * (10 ** -k)
398                 break;
399             }
400             kFast++;
401         }
402         result += resultFast;
403         return EtsString::CreateFromMUtf8(result.c_str());
404     }
405 
406     // NOLINTNEXTLINE(modernize-avoid-c-arrays)
407     char buffer[BUF_SIZE] = {0};
408 
409     int n = 0;
410     int k = GetMinmumDigits(number, &n, buffer);
411     PandaString base = buffer;
412     if (n > 0 && n <= 21_I) {  // NOLINT(readability-magic-numbers)
413         base.erase(1, 1);
414         if (k <= n) {
415             // 6. If k ≤ n ≤ 21, return the String consisting of the code units of the k digits of the decimal
416             // representation of s (in order, with no leading zeroes), followed by n−k occurrences of the code unit
417             // 0x0030 (DIGIT ZERO).
418             base += PandaString(n - k, '0');
419         } else {
420             // 7. If 0 < n ≤ 21, return the String consisting of the code units of the most significant n digits of the
421             // decimal representation of s, followed by the code unit 0x002E (FULL STOP), followed by the code units of
422             // the remaining k−n digits of the decimal representation of s.
423             base.insert(n, 1, '.');
424         }
425     } else if (-6_I < n && n <= 0) {  // NOLINT(readability-magic-numbers)
426         // 8. If −6 < n ≤ 0, return the String consisting of the code unit 0x0030 (DIGIT ZERO), followed by the code
427         // unit 0x002E (FULL STOP), followed by −n occurrences of the code unit 0x0030 (DIGIT ZERO), followed by the
428         // code units of the k digits of the decimal representation of s.
429         base.erase(1, 1);
430         base = PandaString("0.") + PandaString(-n, '0') + base;
431     } else {
432         if (k == 1) {
433             // 9. Otherwise, if k = 1, return the String consisting of the code unit of the single digit of s
434             base.erase(1, 1);
435         }
436         // followed by code unit 0x0065 (LATIN SMALL LETTER E), followed by the code unit 0x002B (PLUS SIGN) or the code
437         // unit 0x002D (HYPHEN-MINUS) according to whether n−1 is positive or negative, followed by the code units of
438         // the decimal representation of the integer abs(n−1) (with no leading zeroes).
439         base += "e" + (n >= 1 ? PandaString("+") : "") + ToPandaString(n - 1);
440     }
441     result += base;
442 
443     return EtsString::CreateFromMUtf8(result.c_str());
444 }
445 
446 template <typename FpType, std::enable_if_t<std::is_floating_point_v<FpType>, bool> = true>
FpToString(FpType number,int radix)447 EtsString *FpToString(FpType number, int radix)
448 {
449     // check radix range
450     if (UNLIKELY(radix > helpers::MAX_RADIX || radix < helpers::MIN_RADIX)) {
451         constexpr size_t MAX_BUF_SIZE = 128;
452         std::array<char, MAX_BUF_SIZE> buf {};
453         // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
454         snprintf_s(buf.data(), buf.size(), buf.size() - 1, "radix must be %.f to %.f", helpers::MIN_RADIX,
455                    helpers::MAX_RADIX);
456         ThrowEtsException(EtsCoroutine::GetCurrent(),
457                           panda_file_items::class_descriptors::ARGUMENT_OUT_OF_RANGE_EXCEPTION, buf.data());
458         return nullptr;
459     }
460 
461     if (std::isnan(number)) {
462         return EtsString::CreateFromMUtf8("NaN");
463     }
464 
465     if (!std::isfinite(number)) {
466         if (number < 0) {
467             return EtsString::CreateFromMUtf8("-Infinity");
468         }
469         return EtsString::CreateFromMUtf8("Infinity");
470     }
471 
472     if (radix == helpers::DECIMAL) {
473         return helpers::FpToStringDecimalRadix(number);
474     }
475 
476     using UnsignedIntType = panda::helpers::TypeHelperT<sizeof(FpType) * CHAR_BIT, false>;
477 
478     bool negative = false;
479     if (number < 0.0) {
480         negative = true;
481         number = -number;
482     }
483 
484     FpType integralPart;
485     FpType numberFraction = std::modf(number, &integralPart);
486 
487     auto value = bit_cast<UnsignedIntType>(number);
488     value += 1;
489     float delta = static_cast<FpType>(HALF) * (bit_cast<FpType>(value) - number);
490     if (delta == 0) {
491         delta = number;
492     }
493 
494     PandaString result;
495     if (numberFraction != 0 && numberFraction >= delta) {
496         result += ".";
497         result += DecimalsToString<FpType>(&integralPart, numberFraction, radix, delta);
498     }
499 
500     result = IntegerToString(integralPart, radix) + result;
501 
502     if (negative) {
503         result = "-" + result;
504     }
505 
506     return EtsString::CreateFromMUtf8(result.c_str());
507 }
508 
509 }  // namespace panda::ets::intrinsics::helpers
510 
511 namespace panda::ets::intrinsics::helpers::flags {
512 
513 inline constexpr uint32_t NO_FLAGS = 0U;
514 inline constexpr uint32_t ALLOW_BINARY = 1U << 0U;
515 inline constexpr uint32_t ALLOW_OCTAL = 1U << 1U;
516 inline constexpr uint32_t ALLOW_HEX = 1U << 2U;
517 inline constexpr uint32_t IGNORE_TRAILING = 1U << 3U;
518 
519 }  // namespace panda::ets::intrinsics::helpers::flags
520 
521 #endif  // PANDA_PLUGINS_ETS_RUNTIME_INTRINSICS_HELPERS_H
522