blob: bd9bd10ca0be7355c47008814a0f1dda3707276e [file]
// 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); }