From 426e402c54a710ba7ec2242e8a6fb4e29fa5b573 Mon Sep 17 00:00:00 2001 From: Timo Kreuzer Date: Tue, 31 Oct 2023 22:14:28 +0200 Subject: [PATCH] [CRT] Rewrite floating point handling for streamout Fixes some crashes and tests. --- sdk/lib/crt/printf/streamout.c | 671 ++++++++++++++++++++++++--------- 1 file changed, 498 insertions(+), 173 deletions(-) diff --git a/sdk/lib/crt/printf/streamout.c b/sdk/lib/crt/printf/streamout.c index 36bc7a31182..128d95a049d 100644 --- a/sdk/lib/crt/printf/streamout.c +++ b/sdk/lib/crt/printf/streamout.c @@ -13,6 +13,14 @@ #include #include #include +#include +#include + +#if DBG && defined(_MSC_VER) +#define assert(x) if (!(x)) __int2c() +#else +#define assert(x) +#endif #ifdef _UNICODE # define streamout wstreamout @@ -64,172 +72,14 @@ enum (flags & FLAG_SHORT) ? (unsigned short)va_arg(argptr, int) : \ va_arg(argptr, unsigned int) -#define va_arg_ffp(argptr, flags) \ - (flags & FLAG_LONGDOUBLE) ? va_arg(argptr, long double) : \ - va_arg(argptr, double) - -#define get_exp(f) (int)floor(f == 0 ? 0 : (f >= 0 ? log10(f) : log10(-f))) #define round(x) floor((x) + 0.5) -#ifndef _USER32_WSPRINTF - -void -#ifdef _LIBCNT_ -/* Due to restrictions in kernel mode regarding the use of floating point, - we prevent it from being inlined */ -__declspec(noinline) -#endif -format_float( - TCHAR chr, - unsigned int flags, - int precision, - TCHAR **string, - const TCHAR **prefix, - va_list *argptr) -{ - static const TCHAR digits_l[] = _T("0123456789abcdef0x"); - static const TCHAR digits_u[] = _T("0123456789ABCDEF0X"); - static const TCHAR _nan[] = _T("#QNAN"); - static const TCHAR _infinity[] = _T("#INF"); - const TCHAR *digits = digits_l; - int exponent = 0, sign; - long double fpval, fpval2; - int padding = 0, num_digits, val32, base = 10; - - /* Normalize the precision */ - if (precision < 0) precision = 6; - else if (precision > 17) - { - padding = precision - 17; - precision = 17; - } - - /* Get the float value and calculate the exponent */ - fpval = va_arg_ffp(*argptr, flags); - exponent = get_exp(fpval); - sign = fpval < 0 ? -1 : 1; - - switch (chr) - { - case _T('G'): - digits = digits_u; - case _T('g'): - if (precision > 0) precision--; - if (exponent < -4 || exponent >= precision) goto case_e; - - /* Shift the decimal point and round */ - fpval2 = round(sign * fpval * pow(10., precision)); - - /* Skip trailing zeroes */ - while (precision && (unsigned __int64)fpval2 % 10 == 0) - { - precision--; - fpval2 /= 10; - } - break; - - case _T('E'): - digits = digits_u; - case _T('e'): - case_e: - /* Shift the decimal point and round */ - fpval2 = round(sign * fpval * pow(10., precision - exponent)); - - /* Compensate for changed exponent through rounding */ - if (fpval2 >= (unsigned __int64)pow(10., precision + 1)) - { - exponent++; - fpval2 = round(sign * fpval * pow(10., precision - exponent)); - } - - val32 = exponent >= 0 ? exponent : -exponent; - - // FIXME: handle length of exponent field: - // http://msdn.microsoft.com/de-de/library/0fatw238%28VS.80%29.aspx - num_digits = 3; - while (num_digits--) - { - *--(*string) = digits[val32 % 10]; - val32 /= 10; - } - - /* Sign for the exponent */ - *--(*string) = exponent >= 0 ? _T('+') : _T('-'); - - /* Add 'e' or 'E' separator */ - *--(*string) = digits[0xe]; - break; - - case _T('A'): - digits = digits_u; - case _T('a'): -// base = 16; - // FIXME: TODO - - case _T('f'): - default: - /* Shift the decimal point and round */ - fpval2 = round(sign * fpval * pow(10., precision)); - break; - } - - /* Handle sign */ - if (fpval < 0) - { - *prefix = _T("-"); - } - else if (flags & FLAG_FORCE_SIGN) - *prefix = _T("+"); - else if (flags & FLAG_FORCE_SIGNSP) - *prefix = _T(" "); - - /* Handle special cases first */ - if (_isnan(fpval)) - { - (*string) -= sizeof(_nan) / sizeof(TCHAR) - 1; - _tcscpy((*string), _nan); - fpval2 = 1; - } - else if (!_finite(fpval)) - { - (*string) -= sizeof(_infinity) / sizeof(TCHAR) - 1; - _tcscpy((*string), _infinity); - fpval2 = 1; - } - else - { - /* Zero padding */ - while (padding-- > 0) *--(*string) = _T('0'); - - /* Digits after the decimal point */ - num_digits = precision; - while (num_digits-- > 0) - { - *--(*string) = digits[(unsigned __int64)fpval2 % 10]; - fpval2 /= base; - } - } - - if (precision > 0 || flags & FLAG_SPECIAL) - *--(*string) = _T('.'); - - /* Digits before the decimal point */ - do - { - *--(*string) = digits[(unsigned __int64)fpval2 % base]; - fpval2 /= base; - } - while ((unsigned __int64)fpval2); - -} -#endif - static int streamout_char(FILE *stream, int chr) { #if !defined(_USER32_WSPRINTF) - if ((stream->_flag & _IOSTRG) && (stream->_base == NULL)) + if ((stream->_flag & _IOSTRG) && (stream->_base == NULL)) return 1; #endif #if defined(_USER32_WSPRINTF) || defined(_LIBCNT_) @@ -255,8 +105,8 @@ streamout_astring(FILE *stream, const char *string, size_t count) int written = 0; #if !defined(_USER32_WSPRINTF) - if ((stream->_flag & _IOSTRG) && (stream->_base == NULL)) - return count; + if ((stream->_flag & _IOSTRG) && (stream->_base == NULL)) + return (int)count; #endif while (count--) @@ -283,8 +133,8 @@ streamout_wstring(FILE *stream, const wchar_t *string, size_t count) int written = 0; #if defined(_UNICODE) && !defined(_USER32_WSPRINTF) - if ((stream->_flag & _IOSTRG) && (stream->_base == NULL)) - return count; + if ((stream->_flag & _IOSTRG) && (stream->_base == NULL)) + return (int)count; #endif while (count--) @@ -315,6 +165,484 @@ streamout_wstring(FILE *stream, const wchar_t *string, size_t count) #define streamout_string streamout_astring #endif +#ifndef _USER32_WSPRINTF + +// Base 2 exponent divided by 8 is base 16 exponent +#define DBL_MAX_16_EXP (DBL_MAX_EXP / 8) + +// A double has 52 fraction bits, which is 14 hex digits +#define DBL_DIG_HEX 14 + +#define DBL_MAX_DIGITS_10 17 +#define DBL_MAX_DIGITS_16 14 + +static +int +get_exponent(double fpval, int base) +{ + int exponent; + + if (fpval == 0.) + { + exponent = 0; + } + else if (base == 10) + { + exponent = (int)floor(log10(fpval)); + assert(exponent <= DBL_MAX_10_EXP); + } + else + { + unsigned __int64 fp_bits = *(unsigned __int64*)&fpval; + int exponent2 = (int)((fp_bits >> 52) & 0x7ff); + exponent2 -= 1023; + exponent = (int)(exponent2 / 8); + assert(exponent <= DBL_MAX_16_EXP); + } + + return exponent; +} + +static +int +get_dbl_digits( + unsigned char digit_buffer[DBL_MAX_DIGITS_10], + double fpval, + int base, + int exponent, + int *pnum_digits) +{ + int num_digits = *pnum_digits; + + /* Only base 10 (dec) and 16 (hex) are valid */ + assert((base == 10) || (base == 16)); + + /* fpval must be positive! */ + assert(fpval >= 0.); + + /* Must fit into the buffer */ + assert(num_digits <= DBL_MAX_DIGITS_10); + + /* Calculate the maximum divisor */ + double divisor = pow(10.0, exponent); + + /* Calculate the digits */ + double remainder = fpval; + for (int i = 0; i < num_digits; i++) + { + int digit = (int)(remainder / divisor); + digit_buffer[i] = digit; + remainder -= digit * divisor; + divisor /= 10.0; + } + + /* Round up */ + int trailing_digit = (int)(remainder / divisor); + if (trailing_digit >= 5) + { + int carry = 1; + for (int i = num_digits - 1; i >= 0; i--) + { + digit_buffer[i] += carry; + if (digit_buffer[i] == 10) + { + digit_buffer[i] = 0; + carry = 1; + } + else + { + carry = 0; + } + } + + /* If we carried over the first digit, we need to shift the digits */ + if (carry) + { + assert(num_digits < DBL_MAX_DIGITS_10); + for (int i = num_digits; i > 0; i--) + { + digit_buffer[i] = digit_buffer[i - 1]; + } + digit_buffer[0] = 1; + exponent++; + num_digits++; + } + } + + /* Strip trailing zeroes */ + while ((num_digits > 0) && (digit_buffer[num_digits - 1] == 0)) + { + num_digits--; + } + + *pnum_digits = num_digits; + + return exponent; +} + +static +int +stramout_dbl_digits( + FILE* stream, + const TCHAR* dig_chars, + const char * inv_str, + unsigned int flags, + const unsigned char dig_buffer[DBL_MAX_DIGITS_10], + int num_real_digits, + int num_int_digits, + int num_frac_digits, + int first_real_digit, + char use_frac_padding) +{ + int current_digit = 0; + + /* Check if we need to insert a virtual 0 */ + if (first_real_digit > 0) + { + /* This can only happen when exponent < 0 */ + assert(num_int_digits == 1); + + /* Output virtual 0 integer digit */ + streamout_char(stream, dig_chars[0]); + current_digit++; + } + else + { + const int num_real_int_digits = min(num_int_digits, num_real_digits); + current_digit += num_real_int_digits; + for (int i = 0; i < num_real_int_digits; i++) + { + streamout_char(stream, dig_chars[dig_buffer[i]]); + } + + /* Output optional right 0 padding */ + const int right_padding = num_int_digits - num_real_int_digits; + current_digit += max(right_padding, 0); + for (int i = 0; i < right_padding; i++) + { + streamout_char(stream, '0'); + } + } + + /* We print the dot when there are fraction digits or the # flag was used */ + if ((num_frac_digits > 0) || ((flags & FLAG_SPECIAL) != 0)) + { + streamout_char(stream, '.'); + } + + /* Only output fraction digits, if precision is > 0 */ + if (num_frac_digits > 0) + { + int num_real_frac_digits = num_real_digits - num_int_digits; + + /* Check for invalid numbers */ + if (inv_str != 0) + { + const int inv_str_len = (int)strlen(inv_str); + num_real_frac_digits = min(inv_str_len, num_frac_digits); + for (int i = 0; i < num_real_frac_digits; i++) + { + streamout_char(stream, inv_str[i]); + current_digit++; + } + } + else + { + /* Output optional 0 chars before real digits begin */ + const int left_padding = first_real_digit - current_digit; + current_digit += max(left_padding, 0); + for (int i = 0; i < left_padding; i++) + { + streamout_char(stream, '0'); + } + + /* Output remaining real digits */ + const int start_digit = current_digit - first_real_digit; + for (int i = start_digit; i < num_real_digits; i++) + { + streamout_char(stream, dig_chars[dig_buffer[i]]); + current_digit++; + } + } + + /* Pad right with '0' for additional precision */ + if (use_frac_padding) + { + const int right_padding = num_frac_digits + num_int_digits - current_digit; + for (int i = 0; i < right_padding; i++) + { + streamout_char(stream, '0'); + current_digit++; + } + } + } + + /* Return the number of written characters */ + return current_digit + (num_frac_digits > 0); +} + +static +int +#ifdef _LIBCNT_ +/* Due to restrictions in kernel mode regarding the use of floating point, + we prevent it from being inlined */ +__declspec(noinline) +#endif +streamout_double( + FILE* stream, + char format, + unsigned __int64 fp_bits, + unsigned int flags, + int width, + int precision) +{ + const char use_frac_padding = (format != 'g');// || use_exp; + static const char _qnan[] = "#QNAN"; + static const char _snan[] = "#SNAN"; + static const char _ind[] = "#IND"; + static const char _infinity[] = "#INF"; + const char* inv_str = 0; + static const TCHAR dig_chars_l[] = _T("0123456789abcdefp0x"); + static const TCHAR dig_chars_u[] = _T("0123456789ABCDEFP0X"); + const TCHAR* dig_chars = dig_chars_l; + unsigned char dig_buffer[DBL_MAX_DIGITS_10]; + int base = 10; + int use_exp_format = 0; + char sign_char = 0; + int exponent; + int rounded_exponent; + int width_exp; + int num_int_digits; + int num_frac_digits; + int first_real_digit; + int num_digits; + + /* Convert to an actual double */ + double fpval = *(double*)&fp_bits; + + /* Check for upper case digits to use */ + if ((format == 'E') || (format == 'F') || (format == 'G') || (format == 'A')) + { + dig_chars = dig_chars_u; + format = format - 'A' + 'a'; + } + + /* Check for base 16 (hex) */ + if (format == 'a') + { + base = 16; + } + + /* Check for default precision */ + if (precision < 0) precision = 6; + + /* Get sign and normalize fpval to absolute */ + if (fp_bits & 0x8000000000000000ULL) + { + sign_char = '-'; + fpval = -fpval; + } + else if (flags & FLAG_FORCE_SIGN) + { + sign_char = '+'; + } + else if (flags & FLAG_FORCE_SIGNSP) + { + sign_char = ' '; + } + + const int width_sign = (sign_char != 0) ? 1 : 0; + + /* Handle NAN / INF */ + if (_isnan(fpval)) + { + if (fp_bits == 0xFFF8000000000000ULL) + { + inv_str = _ind; + } + else if (fp_bits & 0x0008000000000000ULL) + { + inv_str = _qnan; + } + else + { + inv_str = _snan; + } + fpval = 1.; + } + else if (!_finite(fpval)) + { + inv_str = _infinity; + fpval = 1.; + } + + /* Calculate the exponent (i.e. digits before decimal point) */ + exponent = get_exponent(fpval, base); + +retry: + + /* Check whether to ise the exponent format */ + if (format == 'g') + { + use_exp_format = ((exponent < -4) || (exponent >= precision)); + } + else if (format == 'f') + { + use_exp_format = 0; + } + else + { + use_exp_format = 1; + } + + /* Check for explicit exponent format */ + if (use_exp_format) + { + width_exp = 5; + + /* One digit before the decimal point */ + num_int_digits = 1; + + /* Precision includes the integer digit */ + first_real_digit = 0; + } + else + { + width_exp = 0; + + /* Integer digits based on exponent, at least 1 */ + num_int_digits = max(exponent + 1, 1); + + /* First real digit based on exponent */ + first_real_digit = max(-exponent, 0); + } + + if (format == 'g') + { + num_frac_digits = max(precision - num_int_digits, 0); + } + else + { + /* Precision is the number of fractional digits */ + num_frac_digits = precision; + } + + num_digits = num_int_digits + num_frac_digits; + + /* Get max number of actual digits */ + const int max_real_digits = (base == 16) ? DBL_MAX_DIGITS_16 : DBL_MAX_DIGITS_10; + + /* Calculate the number of real digits to return */ + int num_real_digits = min(num_digits - first_real_digit, max_real_digits); + + /* Get the digits (0 based) */ + rounded_exponent = get_dbl_digits(dig_buffer, fpval, base, exponent, &num_real_digits); + + /* If the expoent changed due to rounding, we need to try again */ + if (rounded_exponent != exponent) + { + assert(rounded_exponent > exponent); + exponent = rounded_exponent; + goto retry; + } + + /* Special handling for special numbers */ + if (inv_str != NULL) + { + num_real_digits = (int)strlen(inv_str) + 1; + } + + /* In the g format we need to handle stripped trailing zeroes */ + if ((format == 'g')) + { + int max_digits = first_real_digit + num_real_digits; + num_frac_digits = max(max_digits - num_int_digits, 0); + num_digits = num_int_digits + num_frac_digits; + } + + /* Calculate widths */ + int width_dot = ((num_frac_digits > 0) || (flags & FLAG_SPECIAL)) ? 1 : 0; + int width_of_number = width_sign + num_digits + width_dot + width_exp; + + /* Output left space padding */ + if (((flags & FLAG_ALIGN_LEFT) == 0) && + ((flags & FLAG_PAD_ZERO) == 0) && + (width > width_of_number)) + { + const int padding = width - width_of_number; + for (int i = 0; i < padding; i++) + { + streamout_char(stream, ' '); + } + } + + /* Output sign */ + if (sign_char != 0) + { + streamout_char(stream, sign_char); + } + + /* Output hex prefix */ + if (format == 'a') + { + streamout_string(stream, &dig_chars[0x11], 2); + } + + /* Output left 0 padding */ + if (((flags & FLAG_ALIGN_LEFT) == 0) && + ((flags & FLAG_PAD_ZERO) != 0) && + (width > width_of_number)) + { + const int padding = width - width_of_number; + for (int i = 0; i < padding; i++) + { + streamout_char(stream, '0'); + } + } + + /* Output the digits */ + stramout_dbl_digits( + stream, + dig_chars, + inv_str, + flags, + dig_buffer, + num_real_digits, + num_int_digits, + num_frac_digits, + first_real_digit, + use_frac_padding); + + /* Output the exponent */ + if (use_exp_format) + { + streamout_char(stream, format == 'a' ? dig_chars[0x10] : dig_chars[0xe]); + streamout_char(stream, (exponent >= 0) ? '+' : '-'); + exponent = (exponent < 0) ? -exponent : exponent; + assert(exponent < 1000); + streamout_char(stream, dig_chars[exponent / 100]); + exponent %= 100; + streamout_char(stream, dig_chars[exponent / 10]); + exponent %= 10; + streamout_char(stream, dig_chars[exponent]); + } + + /* Output right padding */ + if (((flags & FLAG_ALIGN_LEFT) != 0) && + (width > width_of_number)) + { + const int padding = width - width_of_number; + for (int i = 0; i < padding; i++) + { + streamout_char(stream, ' '); + } + } + + return max(width, width_of_number); +} + +#endif // _USER32_WSPRINTF + #ifdef _USER32_WSPRINTF # define USE_MULTISIZE 0 #else @@ -332,7 +660,7 @@ streamout(FILE *stream, const TCHAR *format, va_list argptr) TCHAR chr, *string; STRING *nt_string; const TCHAR *digits, *prefix; - int base, fieldwidth, precision, padding; + int base, fieldwidth, precision, padding, rpadding = 0; size_t prefixlen, len; int written = 1, written_all = 0; unsigned int flags; @@ -558,11 +886,11 @@ streamout(FILE *stream, const TCHAR *format, va_list argptr) #else flags &= ~FLAG_WIDECHAR; #endif - /* Use external function, one for kernel one for user mode */ - format_float(chr, flags, precision, &string, &prefix, &argptr); - len = _tcslen(string); - precision = 0; - break; + /* Capture the double as 64bit int to avoid changing it with the FPU */ + unsigned __int64 fpval = va_arg(argptr, unsigned __int64); + written = streamout_double(stream, chr, fpval, flags, fieldwidth, precision); + written_all += written; + continue; #endif case _T('d'): @@ -679,15 +1007,13 @@ streamout(FILE *stream, const TCHAR *format, va_list argptr) if (written == -1) return -1; written_all += written; -#if 0 && SUPPORT_FLOAT /* Optional right '0' padding */ - while (precision-- > 0) + while (rpadding-- > 0) { if ((written = streamout_char(stream, _T('0'))) == 0) return -1; written_all += written; len++; } -#endif /* Optional right padding */ if (flags & FLAG_ALIGN_LEFT) @@ -698,7 +1024,6 @@ streamout(FILE *stream, const TCHAR *format, va_list argptr) written_all += written; } } - } if (written == -1) return -1;