• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (c) 2021-2024 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_
17 #define PANDA_PLUGINS_ETS_RUNTIME_INTRINSICS_HELPERS_
18 
19 #include <charconv>
20 #include <cmath>
21 #include <cstdint>
22 #include <type_traits>
23 #include "libpandabase/utils/bit_helpers.h"
24 #include "libpandabase/utils/utils.h"
25 #include "intrinsics.h"
26 #include "plugins/ets/runtime/types/ets_string.h"
27 #include "plugins/ets/runtime/ets_exceptions.h"
28 #include "plugins/ets/runtime/ets_panda_file_items.h"
29 
30 namespace ark::ets::intrinsics::helpers {
31 
32 inline constexpr double MIN_RADIX = 2;
33 inline constexpr double MAX_RADIX = 36;
34 inline constexpr double MIN_FRACTION = 0;
35 inline constexpr double MAX_FRACTION = 100;
36 
37 inline constexpr uint32_t MAX_PRECISION = 16;
38 inline constexpr uint8_t BINARY = 2;
39 inline constexpr uint8_t OCTAL = 8;
40 inline constexpr uint8_t DECIMAL = 10;
41 inline constexpr uint8_t HEXADECIMAL = 16;
42 inline constexpr double HALF = 0.5;
43 inline constexpr double EPSILON = std::numeric_limits<double>::epsilon();
44 inline constexpr double MAX_SAFE_INTEGER = 9007199254740991;
45 inline constexpr double MAX_VALUE = std::numeric_limits<double>::max();
46 inline constexpr double MIN_VALUE = std::numeric_limits<double>::min();
47 inline constexpr double POSITIVE_INFINITY = coretypes::TaggedValue::VALUE_INFINITY;
48 inline constexpr double NAN_VALUE = coretypes::TaggedValue::VALUE_NAN;
49 
50 inline constexpr int DOUBLE_MAX_PRECISION = 17;
51 inline constexpr int FLOAT_MAX_PRECISION = 9;
52 
53 // NOLINTNEXTLINE(modernize-avoid-c-arrays)
54 inline constexpr uint16_t SPACE_OR_LINE_TERMINAL[] = {
55     0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x0020, 0x00A0, 0x1680, 0x2000, 0x2001, 0x2002, 0x2003, 0x2004,
56     0x2005, 0x2006, 0x2007, 0x2008, 0x2009, 0x200A, 0x2028, 0x2029, 0x202F, 0x205F, 0x3000, 0xFEFF,
57 };
58 
59 // NOLINTNEXTLINE(modernize-avoid-c-arrays)
60 inline constexpr char CHARS[] = "0123456789abcdefghijklmnopqrstuvwxyz";
61 inline constexpr uint64_t MAX_MANTISSA = 0x1ULL << 52U;
62 inline constexpr size_t BUF_SIZE = 128;
63 inline constexpr size_t TEN = 10;
64 
65 enum class Sign { NONE, NEG, POS };
66 
67 union DoubleValUnion {
68     double fval;
69     uint64_t uval;
70     struct {
71         uint64_t significand : coretypes::DOUBLE_SIGNIFICAND_SIZE;
72         uint16_t exponent : coretypes::DOUBLE_EXPONENT_SIZE;
73         int sign : 1;
74     } bits __attribute__((packed));
75 } __attribute__((may_alias, packed));
76 
77 template <typename T>
MaxSafeInteger()78 constexpr T MaxSafeInteger()
79 {
80     static_assert(std::is_same_v<T, float> || std::is_same_v<T, double>);
81     if (std::is_same_v<T, float>) {
82         return static_cast<float>((UINT32_C(1) << static_cast<uint32_t>(std::numeric_limits<float>::digits)) - 1);
83     }
84     return static_cast<double>((UINT64_C(1) << static_cast<uint32_t>(std::numeric_limits<double>::digits)) - 1);
85 }
86 
SignedZero(Sign sign)87 inline double SignedZero(Sign sign)
88 {
89     return sign == Sign::NEG ? -0.0 : 0.0;
90 }
91 
ToDigit(uint8_t c)92 inline uint8_t ToDigit(uint8_t c)
93 {
94     if (c >= '0' && c <= '9') {
95         return c - '0';
96     }
97     if (c >= 'A' && c <= 'Z') {
98         return c - 'A' + DECIMAL;
99     }
100     if (c >= 'a' && c <= 'z') {
101         return c - 'a' + DECIMAL;
102     }
103     return '$';
104 }
105 
106 // CC-OFFNXT(G.FUD.06) perf critical
PowHelper(uint64_t number,int16_t exponent,uint8_t radix)107 inline double PowHelper(uint64_t number, int16_t exponent, uint8_t radix)
108 {
109     const double log2Radix {std::log2(radix)};
110 
111     double expRem = log2Radix * exponent;
112     int expI = static_cast<int>(expRem);
113     expRem = expRem - expI;
114 
115     // NOLINTNEXTLINE(readability-magic-numbers)
116     DoubleValUnion u = {static_cast<double>(number) * std::pow(2.0, expRem)};
117 
118     // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access)
119     expI = u.bits.exponent + expI;
120     if (((expI & ~coretypes::DOUBLE_EXPONENT_MAX) != 0) || expI == 0) {  // NOLINT(hicpp-signed-bitwise)
121         if (expI > 0) {
122             return std::numeric_limits<double>::infinity();
123         }
124         if (expI <= -static_cast<int>(coretypes::DOUBLE_SIGNIFICAND_SIZE)) {
125             return 0;
126         }
127         // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access)
128         u.bits.exponent = 0;
129         // NOLINTNEXTLINE(hicpp-signed-bitwise, cppcoreguidelines-pro-type-union-access)
130         u.bits.significand = (u.bits.significand | coretypes::DOUBLE_HIDDEN_BIT) >> (1 - expI);
131     } else {
132         // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access)
133         u.bits.exponent = expI;
134     }
135 
136     // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access)
137     return u.fval;
138 }
139 
140 // CC-OFFNXT(G.FUD.06) perf critical
IsNonspace(uint16_t c)141 inline bool IsNonspace(uint16_t c)
142 {
143     int i;
144     int len = sizeof(SPACE_OR_LINE_TERMINAL) / sizeof(SPACE_OR_LINE_TERMINAL[0]);
145     for (i = 0; i < len; i++) {
146         if (c == SPACE_OR_LINE_TERMINAL[i]) {
147             return false;
148         }
149         if (c < SPACE_OR_LINE_TERMINAL[i]) {
150             return true;
151         }
152     }
153     return true;
154 }
155 
156 // CC-OFFNXT(G.FUD.06) perf critical
GotoNonspace(uint8_t ** ptr,const uint8_t * end)157 inline bool GotoNonspace(uint8_t **ptr, const uint8_t *end)
158 {
159     while (*ptr < end) {
160         uint16_t c = **ptr;
161         size_t size = 1;
162         if (c > INT8_MAX) {
163             size = 0;
164             uint16_t utf8Bit = INT8_MAX + 1;  // equal 0b1000'0000
165             while (utf8Bit > 0 && (c & utf8Bit) == utf8Bit) {
166                 ++size;
167                 utf8Bit >>= 1UL;
168             }
169             if (utf::ConvertRegionUtf8ToUtf16(*ptr, &c, end - *ptr, 1, 0) <= 0) {
170                 return true;
171             }
172         }
173         if (IsNonspace(c)) {
174             return true;
175         }
176         *ptr += size;  // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
177     }
178     return false;
179 }
180 
IsEmptyString(const uint8_t * start,const uint8_t * end)181 inline bool IsEmptyString(const uint8_t *start, const uint8_t *end)
182 {
183     auto p = const_cast<uint8_t *>(start);
184     return !GotoNonspace(&p, end);
185 }
186 
187 // CC-OFFNXT(G.FUD.06) perf critical
Strtod(const char * str,int exponent,uint8_t radix)188 inline double Strtod(const char *str, int exponent, uint8_t radix)
189 {
190     ASSERT(str != nullptr);
191     ASSERT(radix >= MIN_RADIX && radix <= MAX_RADIX);
192     auto p = const_cast<char *>(str);
193     Sign sign = Sign::NONE;
194     uint64_t number = 0;
195     uint64_t numberMax = (UINT64_MAX - (radix - 1)) / radix;
196     double result = 0.0;
197     if (*p == '-') {
198         sign = Sign::NEG;
199         ++p;
200     }
201     while (*p == '0') {
202         ++p;
203     }
204     while (*p != '\0') {
205         uint8_t digit = ToDigit(static_cast<uint8_t>(*p));
206         if (digit >= radix) {
207             break;
208         }
209         if (number < numberMax) {
210             number = number * radix + digit;
211         } else {
212             ++exponent;
213         }
214         ++p;
215     }
216     if (exponent < 0) {
217         result = number / std::pow(radix, -exponent);
218     } else {
219         result = number * std::pow(radix, exponent);
220     }
221     if (!std::isfinite(result) || (result == 0 && number != 0)) {
222         result = PowHelper(number, exponent, radix);
223     }
224     return sign == Sign::NEG ? -result : result;
225 }
226 
Carry(char current,int radix)227 [[maybe_unused]] inline char Carry(char current, int radix)
228 {
229     int digit = static_cast<int>((current > '9') ? (current - 'a' + DECIMAL) : (current - '0'));
230     digit = (digit == (radix - 1)) ? 0 : digit + 1;
231     return CHARS[digit];
232 }
233 
234 template <typename FpType, std::enable_if_t<std::is_floating_point_v<FpType>, bool> = true>
TruncateFp(FpType number)235 FpType TruncateFp(FpType number)
236 {
237     // -0 to +0
238     if (number == 0.0) {
239         return 0;
240     }
241     return (number >= 0) ? std::floor(number) : std::ceil(number);
242 }
243 
244 template <typename FpType, std::enable_if_t<std::is_floating_point_v<FpType>, bool> = true>
DecimalsToString(FpType * numberInteger,FpType fraction,int radix,FpType delta)245 PandaString DecimalsToString(FpType *numberInteger, FpType fraction, int radix, FpType delta)
246 {
247     PandaString result;
248     while (fraction >= delta) {
249         fraction *= radix;
250         delta *= radix;
251         int64_t integer = std::floor(fraction);
252         fraction -= integer;
253         result += CHARS[integer];
254         if (fraction > HALF && fraction + delta > 1) {
255             size_t fractionEnd = result.size() - 1;
256             result[fractionEnd] = Carry(*result.rbegin(), radix);
257             for (; fractionEnd > 0 && result[fractionEnd] == '0'; fractionEnd--) {
258                 result[fractionEnd - 1] = Carry(result[fractionEnd - 1], radix);
259             }
260             if (fractionEnd == 0) {
261                 (*numberInteger)++;
262             }
263             break;
264         }
265     }
266     // delete 0 in the end
267     size_t found = result.find_last_not_of('0');
268     if (found != PandaString::npos) {
269         result.erase(found + 1);
270     }
271 
272     return result;
273 }
274 
275 template <typename FpType, std::enable_if_t<std::is_floating_point_v<FpType>, bool> = true>
IntegerToString(FpType number,int radix)276 PandaString IntegerToString(FpType number, int radix)
277 {
278     ASSERT(radix >= MIN_RADIX && radix <= MAX_RADIX);
279     ASSERT(number == std::round(number));
280     PandaString result;
281     while (TruncateFp(number / radix) > static_cast<FpType>(MAX_MANTISSA)) {
282         number /= radix;
283         TruncateFp(number);
284         result = PandaString("0").append(result);
285     }
286     do {
287         auto remainder = static_cast<uint8_t>(std::fmod(number, radix));
288         result = CHARS[remainder] + result;
289         number = TruncateFp((number - remainder) / radix);
290     } while (number > 0);
291     return result;
292 }
293 
294 template <typename FpType, std::enable_if_t<std::is_floating_point_v<FpType>, bool> = true>
StrToFp(char * str,char ** strEnd)295 FpType StrToFp(char *str, char **strEnd)
296 {
297     if constexpr (std::is_same_v<FpType, double>) {
298         return std::strtod(str, strEnd);
299     } else {
300         return std::strtof(str, strEnd);
301     }
302 }
303 
304 double StringToDouble(const uint8_t *start, const uint8_t *end, uint8_t radix, uint32_t flags);
305 double StringToDoubleWithRadix(const uint8_t *start, const uint8_t *end, int radix);
306 EtsString *DoubleToExponential(double number, int digit);
307 EtsString *DoubleToFixed(double number, int digit);
308 EtsString *DoubleToPrecision(double number, int digit);
309 double GetStdDoubleArgument(ObjectHeader *obj);
310 
311 template <typename FpType, std::enable_if_t<std::is_floating_point_v<FpType>, bool> = true>
FpNonFiniteToString(FpType number)312 inline const char *FpNonFiniteToString(FpType number)
313 {
314     ASSERT(std::isnan(number) || !std::isfinite(number));
315     if (std::isnan(number)) {
316         return "NaN";
317     }
318     return std::signbit(number) ? "-Infinity" : "Infinity";
319 }
320 
321 template <typename FpType>
322 char *SmallFpToString(FpType number, bool negative, char *buffer);
323 
324 template <typename FpType>
325 Span<char> FpToStringDecimalRadixMainCase(FpType number, bool negative, Span<char> buffer);
326 
327 // NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic)
328 template <typename FpType, std::enable_if_t<std::is_floating_point_v<FpType>, bool> = true, typename Cb>
FpToStringDecimalRadix(FpType number,Cb cb)329 auto FpToStringDecimalRadix(FpType number, Cb cb)
330 {
331     static constexpr FpType MIN_BOUND = 0.1;
332 
333     // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init)
334     std::array<char, BUF_SIZE + 2U> buffer;
335 
336     if (INT_MIN < number && number < static_cast<FpType>(INT_MAX)) {
337         if (auto intVal = static_cast<int32_t>(number); number == static_cast<double>(intVal)) {
338             auto bufferEnd = std::to_chars(buffer.begin(), buffer.end(), intVal).ptr;
339             return cb({buffer.begin(), static_cast<size_t>(bufferEnd - buffer.begin())});
340         }
341     }
342 
343     // isfinite checks if the number is normal, subnormal or zero, but not infinite or NaN.
344     if (!std::isfinite(number)) {
345         auto *str = FpNonFiniteToString(number);
346         return cb(str);
347     }
348 
349     bool negative = false;
350     if (number < 0) {
351         negative = true;
352         number = -number;
353     }
354     if (!std::is_same_v<FpType, double> && MIN_BOUND <= number && number < 1) {
355         // Fast path. In this case, n==0, just need to calculate k and s.
356         auto bufferEnd = SmallFpToString(number, negative, buffer.begin());
357         return cb({buffer.begin(), static_cast<size_t>(bufferEnd - buffer.begin())});
358     }
359 
360     auto newBuffer = FpToStringDecimalRadixMainCase(number, negative, Span(buffer));
361     return cb({newBuffer.begin(), newBuffer.size()});
362 }
363 // NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic)
364 
365 template <typename FpType, std::enable_if_t<std::is_floating_point_v<FpType>, bool> = true>
FpDelta(FpType number)366 inline float FpDelta(FpType number)
367 {
368     using UnsignedIntType = ark::helpers::TypeHelperT<sizeof(FpType) * CHAR_BIT, false>;
369     auto value = bit_cast<FpType>(bit_cast<UnsignedIntType>(number) + 1U);
370     float delta = static_cast<FpType>(HALF) * (bit_cast<FpType>(value) - number);
371     return delta == 0 ? number : delta;
372 }
373 
374 template <typename FpType, std::enable_if_t<std::is_floating_point_v<FpType>, bool> = true>
FpToString(FpType number,int radix)375 EtsString *FpToString(FpType number, int radix)
376 {
377     // check radix range
378     if (UNLIKELY(radix > helpers::MAX_RADIX || radix < helpers::MIN_RADIX)) {
379         constexpr size_t MAX_BUF_SIZE = 128;
380         std::array<char, MAX_BUF_SIZE> buf {};
381         // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
382         snprintf_s(buf.data(), buf.size(), buf.size() - 1, "radix must be %.f to %.f", helpers::MIN_RADIX,
383                    helpers::MAX_RADIX);
384         ThrowEtsException(EtsCoroutine::GetCurrent(),
385                           panda_file_items::class_descriptors::ARGUMENT_OUT_OF_RANGE_EXCEPTION, buf.data());
386         return nullptr;
387     }
388 
389     if (radix == helpers::DECIMAL) {
390         return helpers::FpToStringDecimalRadix(
391             number, [](std::string_view str) { return EtsString::CreateFromAscii(str.data(), str.length()); });
392     }
393 
394     // isfinite checks if the number is normal, subnormal or zero, but not infinite or NaN.
395     if (!std::isfinite(number)) {
396         return EtsString::CreateFromMUtf8(FpNonFiniteToString(number));
397     }
398 
399     PandaString result;
400     if (number < 0.0) {
401         result += "-";
402         number = -number;
403     }
404 
405     float delta = FpDelta(number);
406     FpType integral;
407     FpType fractional = std::modf(number, &integral);
408     if (fractional != 0 && fractional >= delta) {
409         PandaString fraction(DecimalsToString<FpType>(&integral, fractional, radix, delta));
410         result += IntegerToString(integral, radix) + "." + fraction;
411     } else {
412         result += IntegerToString(integral, radix);
413     }
414 
415     return EtsString::CreateFromMUtf8(result.c_str());
416 }
417 
418 }  // namespace ark::ets::intrinsics::helpers
419 
420 namespace ark::ets::intrinsics::helpers::flags {
421 
422 inline constexpr uint32_t NO_FLAGS = 0U;
423 inline constexpr uint32_t ALLOW_BINARY = 1U << 0U;
424 inline constexpr uint32_t ALLOW_OCTAL = 1U << 1U;
425 inline constexpr uint32_t ALLOW_HEX = 1U << 2U;
426 inline constexpr uint32_t IGNORE_TRAILING = 1U << 3U;
427 inline constexpr uint32_t EMPTY_IS_ZERO = 1U << 4U;
428 inline constexpr uint32_t ERROR_IN_EXPONENT_IS_NAN = 1U << 5U;
429 
430 }  // namespace ark::ets::intrinsics::helpers::flags
431 
432 #endif  // PANDA_PLUGINS_ETS_RUNTIME_INTRINSICS_HELPERS_
433