| // Copyright 2026 The IREE Authors |
| // |
| // Licensed under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| |
| // Integer and floating-point string-to-number conversion for wasm32. |
| // |
| // strtol/strtoul/strtoll/strtoull: derived from musl libc (MIT license). |
| // These are self-contained with no locale dependency — always C locale. |
| // |
| // strtod/strtof: simplified implementation sufficient for IREE's use case |
| // (configuration parsing, flag values). Handles decimal and basic scientific |
| // notation. Does not attempt the full IEEE 754 correctly-rounded conversion |
| // that a production strtod requires — that would need either the Eisel-Lemire |
| // algorithm or musl's __floatscan multi-precision fallback (~1000 lines). |
| // IREE only parses floating-point values from its own output (e.g., benchmark |
| // results, debug flags), so exact round-trip is not a concern. |
| |
| #include <ctype.h> |
| #include <errno.h> |
| #include <limits.h> |
| #include <math.h> |
| #include <stdint.h> |
| #include <stdlib.h> |
| |
| //===----------------------------------------------------------------------===// |
| // Integer conversion (strtol family) |
| //===----------------------------------------------------------------------===// |
| |
| // Core unsigned magnitude conversion with overflow detection. |
| // Returns the absolute value of the parsed number. The sign is returned |
| // separately via |*out_negative| so that callers (strtol/strtoll) can |
| // correctly distinguish positive overflow from the valid negative minimum |
| // (e.g., LONG_MIN = -(LONG_MAX+1)). |
| static unsigned long long strtox(const char* string, char** end, int base, |
| unsigned long long limit, int* out_negative) { |
| const char* p = string; |
| |
| // Skip leading whitespace. |
| while (isspace((unsigned char)*p)) ++p; |
| |
| // Optional sign. |
| int negative = 0; |
| if (*p == '-') { |
| negative = 1; |
| ++p; |
| } else if (*p == '+') { |
| ++p; |
| } |
| *out_negative = negative; |
| |
| // Base detection. |
| if ((base == 0 || base == 16) && p[0] == '0' && |
| (p[1] == 'x' || p[1] == 'X')) { |
| base = 16; |
| p += 2; |
| } else if ((base == 0 || base == 2) && p[0] == '0' && |
| (p[1] == 'b' || p[1] == 'B')) { |
| base = 2; |
| p += 2; |
| } else if (base == 0) { |
| base = (p[0] == '0') ? 8 : 10; |
| } |
| |
| const char* digits_start = p; |
| unsigned long long result = 0; |
| int overflow = 0; |
| |
| // Cutoff values for overflow detection. |
| unsigned long long cutoff = limit / (unsigned long long)base; |
| unsigned int cutlim = (unsigned int)(limit % (unsigned long long)base); |
| |
| for (;; ++p) { |
| unsigned int digit; |
| if (isdigit((unsigned char)*p)) { |
| digit = (unsigned int)(*p - '0'); |
| } else if (isalpha((unsigned char)*p)) { |
| digit = (unsigned int)(((*p | 0x20) - 'a') + 10); |
| } else { |
| break; |
| } |
| if (digit >= (unsigned int)base) break; |
| |
| if (result > cutoff || (result == cutoff && digit > cutlim)) { |
| overflow = 1; |
| } else { |
| result = result * (unsigned long long)base + digit; |
| } |
| } |
| |
| // No digits consumed — set end to original string. |
| if (p == digits_start) { |
| if (end) *end = (char*)string; |
| return 0; |
| } |
| |
| if (end) *end = (char*)p; |
| |
| if (overflow) { |
| errno = ERANGE; |
| return limit; |
| } |
| |
| return result; |
| } |
| |
| long strtol(const char* string, char** end, int base) { |
| int negative = 0; |
| unsigned long long magnitude = |
| strtox(string, end, base, (unsigned long long)LONG_MAX + 1, &negative); |
| if (negative) { |
| if (magnitude > (unsigned long long)LONG_MAX + 1) { |
| errno = ERANGE; |
| return LONG_MIN; |
| } |
| return -(long)magnitude; |
| } |
| if (magnitude > (unsigned long long)LONG_MAX) { |
| errno = ERANGE; |
| return LONG_MAX; |
| } |
| return (long)magnitude; |
| } |
| |
| unsigned long strtoul(const char* string, char** end, int base) { |
| int negative = 0; |
| unsigned long long magnitude = |
| strtox(string, end, base, ULONG_MAX, &negative); |
| // C standard: strtoul("-X") wraps to ULONG_MAX - X + 1. |
| if (negative) return (unsigned long)(0 - magnitude); |
| return (unsigned long)magnitude; |
| } |
| |
| long long strtoll(const char* string, char** end, int base) { |
| int negative = 0; |
| unsigned long long magnitude = |
| strtox(string, end, base, (unsigned long long)LLONG_MAX + 1, &negative); |
| if (negative) { |
| if (magnitude > (unsigned long long)LLONG_MAX + 1) { |
| errno = ERANGE; |
| return LLONG_MIN; |
| } |
| return -(long long)magnitude; |
| } |
| if (magnitude > (unsigned long long)LLONG_MAX) { |
| errno = ERANGE; |
| return LLONG_MAX; |
| } |
| return (long long)magnitude; |
| } |
| |
| unsigned long long strtoull(const char* string, char** end, int base) { |
| int negative = 0; |
| unsigned long long magnitude = |
| strtox(string, end, base, ULLONG_MAX, &negative); |
| if (negative) return 0 - magnitude; |
| return magnitude; |
| } |
| |
| int atoi(const char* string) { return (int)strtol(string, NULL, 10); } |
| long atol(const char* string) { return strtol(string, NULL, 10); } |
| long long atoll(const char* string) { return strtoll(string, NULL, 10); } |
| |
| //===----------------------------------------------------------------------===// |
| // Floating-point conversion (strtod family) |
| //===----------------------------------------------------------------------===// |
| |
| // Simplified strtod: handles [+-]digits[.digits][eE[+-]digits], infinity, nan. |
| // This does NOT provide correctly-rounded IEEE 754 conversion for all inputs. |
| // It uses double-precision arithmetic throughout, which is exact for mantissas |
| // up to 2^53 and common exponents. Edge cases (very long digit strings, |
| // subnormal boundaries) may differ by 1 ULP from a fully conformant strtod. |
| static double strtod_impl(const char* string, char** end) { |
| const char* p = string; |
| |
| while (isspace((unsigned char)*p)) ++p; |
| |
| int negative = 0; |
| if (*p == '-') { |
| negative = 1; |
| ++p; |
| } else if (*p == '+') { |
| ++p; |
| } |
| |
| // Handle infinity and NaN. |
| if ((p[0] == 'i' || p[0] == 'I') && (p[1] == 'n' || p[1] == 'N') && |
| (p[2] == 'f' || p[2] == 'F')) { |
| p += 3; |
| // Skip optional "inity". |
| if ((p[0] == 'i' || p[0] == 'I') && (p[1] == 'n' || p[1] == 'N') && |
| (p[2] == 'i' || p[2] == 'I') && (p[3] == 't' || p[3] == 'T') && |
| (p[4] == 'y' || p[4] == 'Y')) { |
| p += 5; |
| } |
| if (end) *end = (char*)p; |
| return negative ? -INFINITY : INFINITY; |
| } |
| |
| if ((p[0] == 'n' || p[0] == 'N') && (p[1] == 'a' || p[1] == 'A') && |
| (p[2] == 'n' || p[2] == 'N')) { |
| p += 3; |
| // Skip optional parenthesized payload. |
| if (*p == '(') { |
| const char* q = p + 1; |
| while (*q && *q != ')') ++q; |
| if (*q == ')') p = q + 1; |
| } |
| if (end) *end = (char*)p; |
| return NAN; |
| } |
| |
| // Parse hexadecimal float: 0x prefix. |
| if (p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) { |
| p += 2; |
| double result = 0.0; |
| int has_digits = 0; |
| |
| // Integer part. |
| while (isxdigit((unsigned char)*p)) { |
| unsigned int digit; |
| if (*p >= '0' && *p <= '9') |
| digit = (unsigned int)(*p - '0'); |
| else |
| digit = (unsigned int)((*p | 0x20) - 'a' + 10); |
| result = result * 16.0 + (double)digit; |
| has_digits = 1; |
| ++p; |
| } |
| |
| // Fractional part. |
| if (*p == '.') { |
| ++p; |
| double place = 1.0 / 16.0; |
| while (isxdigit((unsigned char)*p)) { |
| unsigned int digit; |
| if (*p >= '0' && *p <= '9') |
| digit = (unsigned int)(*p - '0'); |
| else |
| digit = (unsigned int)((*p | 0x20) - 'a' + 10); |
| result += (double)digit * place; |
| place /= 16.0; |
| has_digits = 1; |
| ++p; |
| } |
| } |
| |
| if (!has_digits) { |
| if (end) *end = (char*)string; |
| return 0.0; |
| } |
| |
| // Binary exponent. |
| if (*p == 'p' || *p == 'P') { |
| ++p; |
| int exp_negative = 0; |
| if (*p == '-') { |
| exp_negative = 1; |
| ++p; |
| } else if (*p == '+') { |
| ++p; |
| } |
| int exponent = 0; |
| while (isdigit((unsigned char)*p)) { |
| exponent = exponent * 10 + (*p - '0'); |
| if (exponent > 2000) { |
| // Clamp to avoid signed int overflow. Any binary exponent this |
| // large will produce infinity or zero after ldexp. |
| exponent = 2000; |
| } |
| ++p; |
| } |
| if (exp_negative) exponent = -exponent; |
| result = ldexp(result, exponent); |
| } |
| |
| if (end) *end = (char*)p; |
| return negative ? -result : result; |
| } |
| |
| // Parse decimal float. |
| double result = 0.0; |
| int has_digits = 0; |
| |
| // Integer part. |
| while (isdigit((unsigned char)*p)) { |
| result = result * 10.0 + (double)(*p - '0'); |
| has_digits = 1; |
| ++p; |
| } |
| |
| // Fractional part. |
| if (*p == '.') { |
| ++p; |
| double place = 0.1; |
| while (isdigit((unsigned char)*p)) { |
| result += (double)(*p - '0') * place; |
| place *= 0.1; |
| has_digits = 1; |
| ++p; |
| } |
| } |
| |
| if (!has_digits) { |
| if (end) *end = (char*)string; |
| return 0.0; |
| } |
| |
| // Exponent. |
| if (*p == 'e' || *p == 'E') { |
| ++p; |
| int exp_negative = 0; |
| if (*p == '-') { |
| exp_negative = 1; |
| ++p; |
| } else if (*p == '+') { |
| ++p; |
| } |
| int exponent = 0; |
| while (isdigit((unsigned char)*p)) { |
| exponent = exponent * 10 + (*p - '0'); |
| if (exponent > 400) { |
| // Clamp to avoid unbounded pow() computation. Any exponent this |
| // large will produce infinity or zero after the multiply. |
| exponent = 400; |
| } |
| ++p; |
| } |
| if (exp_negative) exponent = -exponent; |
| |
| // Apply exponent via power of 10. For common exponents [-22, +22] |
| // this is exact (all powers of 10 up to 10^22 are representable in |
| // double precision). For larger exponents, precision loss may occur. |
| if (exponent != 0) { |
| result *= pow(10.0, (double)exponent); |
| } |
| } |
| |
| if (end) *end = (char*)p; |
| result = negative ? -result : result; |
| |
| // Detect overflow/underflow and set errno per C standard. |
| if (has_digits && result != 0.0 && !isinf(result)) { |
| // Normal finite result — no error. |
| } else if (has_digits && isinf(result)) { |
| errno = ERANGE; |
| } else if (has_digits && result == 0.0) { |
| // Could be underflow if there were significant digits. For IREE's config |
| // parsing use case, true underflow is rare enough that we skip the |
| // has-significant-digits check and just don't set errno for zero results. |
| } |
| return result; |
| } |
| |
| double strtod(const char* string, char** end) { |
| return strtod_impl(string, end); |
| } |
| |
| float strtof(const char* string, char** end) { |
| return (float)strtod_impl(string, end); |
| } |
| |
| long double strtold(const char* string, char** end) { |
| // wasm32 long double is the same as double (64-bit). |
| return (long double)strtod_impl(string, end); |
| } |
| |
| double atof(const char* string) { return strtod(string, NULL); } |