diff --git a/app/accelerometer/accelerometer.c b/app/accelerometer/accelerometer.c index d4ff227b..23a17e00 100644 --- a/app/accelerometer/accelerometer.c +++ b/app/accelerometer/accelerometer.c @@ -21,10 +21,9 @@ STATIC_COMMAND_END(accelerometer); void read_xyz(void) { position_vector_t pos_vector; acc_read_xyz(&pos_vector); - printf("X value = %f\n",pos_vector.x); - printf("Y value = %f\n",pos_vector.y); - printf("Z value = %f\n",pos_vector.z); - + printf_float("X value = %f\n",pos_vector.x); + printf_float("Y value = %f\n",pos_vector.y); + printf_float("Z value = %f\n",pos_vector.z); } APP_START(accelerometer) diff --git a/app/tests/benchmarks.c b/app/tests/benchmarks.c index 1c4f8618..1a8b7384 100644 --- a/app/tests/benchmarks.c +++ b/app/tests/benchmarks.c @@ -54,7 +54,7 @@ __NO_INLINE static void bench_memset(void) { size_t total_bytes = BUFSIZE * ITER; double bytes_cycle = total_bytes / (double)count; - printf("took %lu cycles to memset a buffer of size %zu %d times (%zu bytes), %f bytes/cycle\n", + printf_float("took %lu cycles to memset a buffer of size %zu %d times (%zu bytes), %f bytes/cycle\n", count, BUFSIZE, ITER, total_bytes, bytes_cycle); free(buf); @@ -79,7 +79,7 @@ __NO_INLINE static void bench_cset_##type(void) \ \ size_t total_bytes = BUFSIZE * ITER; \ double bytes_cycle = total_bytes / (double)count; \ - printf("took %lu cycles to manually clear a buffer using wordsize %zu of size %zu %u times (%zu bytes), %f bytes/cycle\n", \ + printf_float("took %lu cycles to manually clear a buffer using wordsize %zu of size %zu %u times (%zu bytes), %f bytes/cycle\n", \ count, sizeof(*buf), BUFSIZE, ITER, total_bytes, bytes_cycle); \ \ free(buf); \ @@ -114,7 +114,7 @@ __NO_INLINE static void bench_cset_wide(void) { size_t total_bytes = BUFSIZE * ITER; double bytes_cycle = total_bytes / (double)count; - printf("took %lu cycles to manually clear a buffer of size %zu %d times 8 words at a time (%zu bytes), %f bytes/cycle\n", + printf_float("took %lu cycles to manually clear a buffer of size %zu %d times 8 words at a time (%zu bytes), %f bytes/cycle\n", count, BUFSIZE, ITER, total_bytes, bytes_cycle); free(buf); @@ -135,7 +135,7 @@ __NO_INLINE static void bench_memcpy(void) { size_t total_bytes = (BUFSIZE / 2) * ITER; double bytes_cycle = total_bytes / (double)count; - printf("took %lu cycles to memcpy a buffer of size %zu %d times (%zu source bytes), %f source bytes/cycle\n", + printf_float("took %lu cycles to memcpy a buffer of size %zu %d times (%zu source bytes), %f source bytes/cycle\n", count, BUFSIZE / 2, ITER, total_bytes, bytes_cycle); free(buf); @@ -162,7 +162,7 @@ __NO_INLINE static void arm_bench_cset_stm(void) { size_t total_bytes = BUFSIZE * ITER; double bytes_cycle = total_bytes / (float)count; - printf("took %lu cycles to manually clear a buffer of size %zu %d times 8 words at a time using stm (%zu bytes), %f bytes/cycle\n", + printf_float("took %lu cycles to manually clear a buffer of size %zu %d times 8 words at a time using stm (%zu bytes), %f bytes/cycle\n", count, BUFSIZE, ITER, total_bytes, bytes_cycle); free(buf); @@ -189,7 +189,7 @@ __NO_INLINE static void arm_bench_multi_issue(void) { cycles = arch_cycle_count() - cycles; double cycles_iter = (float)cycles / ITER; - printf("took %lu cycles to issue 8 integer ops (%f cycles/iteration)\n", cycles, cycles_iter); + printf_float("took %lu cycles to issue 8 integer ops (%f cycles/iteration)\n", cycles, cycles_iter); #undef ITER } #endif // __CORTEX_M diff --git a/app/tests/float.c b/app/tests/float.c index 88fc6f9a..f693776a 100644 --- a/app/tests/float.c +++ b/app/tests/float.c @@ -114,7 +114,7 @@ static void float_test(void) { float result = val[i]; uint32_t result_u32; memcpy(&result_u32, &result, sizeof(result_u32)); - printf("float thread %u returns %d, hex val %a, uint32 %#" PRIx32, i, res, (double)result, result_u32); + printf_float("float thread %u returns %d, hex val %a, uint32 %#" PRIx32, i, res, (double)result, result_u32); if (result_u32 != test_results_32[i]) { printf("\nfloat thread %u failed, expected %#" PRIx32 "\n", i, test_results_32[i]); } else { @@ -124,7 +124,7 @@ static void float_test(void) { double result = val[i]; uint64_t result_u64; memcpy(&result_u64, &result, sizeof(result_u64)); - printf("float thread %u returns %d, hex val %a, uint64 %#" PRIx64, i, res, result, result_u64); + printf_float("float thread %u returns %d, hex val %a, uint64 %#" PRIx64, i, res, result, result_u64); if (result_u64 != test_results_64[i]) { printf("\nfloat thread %u failed, expected %#" PRIx64 "\n", i, test_results_64[i]); } else { diff --git a/lib/libc/include/printf.h b/lib/libc/include/printf.h index 10ca946a..9bb4221f 100644 --- a/lib/libc/include/printf.h +++ b/lib/libc/include/printf.h @@ -22,6 +22,9 @@ __BEGIN_CDECLS typedef int (*_printf_engine_output_func)(const char *str, size_t len, void *state); int _printf_engine(_printf_engine_output_func out, void *state, const char *fmt, va_list ap); +#if !WITH_NO_FP +int _printf_engine_float(_printf_engine_output_func out, void *state, const char *fmt, va_list ap); +#endif __END_CDECLS diff --git a/lib/libc/include/stdio.h b/lib/libc/include/stdio.h index 3d3c44ce..2ffea251 100644 --- a/lib/libc/include/stdio.h +++ b/lib/libc/include/stdio.h @@ -81,13 +81,45 @@ static inline int vprintf(const char *fmt, va_list ap) { return 0; } int fprintf(FILE *fp, const char *fmt, ...) __PRINTFLIKE(2, 3); int vfprintf(FILE *fp, const char *fmt, va_list ap); -int _fprintf_output_func(const char *str, size_t len, void *state); int sprintf(char *str, const char *fmt, ...) __PRINTFLIKE(2, 3); int snprintf(char *str, size_t len, const char *fmt, ...) __PRINTFLIKE(3, 4); int vsprintf(char *str, const char *fmt, va_list ap); int vsnprintf(char *str, size_t len, const char *fmt, va_list ap); +// Special variants of printf class functions that implement floating point printing +// if enabled. This avoids having floating point support in the default printf. +// If floating point is not enabled, these functions will simply alias to the non-floating +// point versions above. +#if !WITH_NO_FP +#if !DISABLE_DEBUG_OUTPUT +int printf_float(const char *fmt, ...) __PRINTFLIKE(1, 2); +int vprintf_float(const char *fmt, va_list ap); +#else +static inline int __PRINTFLIKE(1, 2) printf_float(const char *fmt, ...) { return 0; } +static inline int vprintf_float(const char *fmt, va_list ap) { return 0; } +#endif + +int fprintf_float(FILE *fp, const char *fmt, ...) __PRINTFLIKE(2, 3); +int vfprintf_float(FILE *fp, const char *fmt, va_list ap); + +int sprintf_float(char *str, const char *fmt, ...) __PRINTFLIKE(2, 3); +int snprintf_float(char *str, size_t len, const char *fmt, ...) __PRINTFLIKE(3, 4); +int vsprintf_float(char *str, const char *fmt, va_list ap); +int vsnprintf_float(char *str, size_t len, const char *fmt, va_list ap); +#else +#define printf_float printf +#define vprintf_float vprintf +#define fprintf_float fprintf +#define vfprintf_float vfprintf + +#define sprintf_float sprintf +#define snprintf_float snprintf +#define vsprintf_float vsprintf +#define vsnprintf_float vsnprintf +#endif + +int _fprintf_output_func(const char *str, size_t len, void *state); __END_CDECLS diff --git a/lib/libc/printf.c b/lib/libc/printf.c index a4f18c27..b706869e 100644 --- a/lib/libc/printf.c +++ b/lib/libc/printf.c @@ -1,666 +1,10 @@ /* - * Copyright (c) 2008-2014 Travis Geiselbrecht + * Copyright (c) 2025 Travis Geiselbrecht * * Use of this source code is governed by a MIT-style * license that can be found in the LICENSE file or at * https://opensource.org/licenses/MIT */ -#include -#include -#include -#include -#include -#include -#include -#include -// The main printf engine and all of the printf wrapper routines. -// It's important these are all in the same file, or at least all -// compiled with the same flags concerning floating point support. - -#if WITH_NO_FP -#define FLOAT_PRINTF 0 -#else -#define FLOAT_PRINTF 1 -#endif - -int sprintf(char *str, const char *fmt, ...) { - int err; - - va_list ap; - va_start(ap, fmt); - err = vsprintf(str, fmt, ap); - va_end(ap); - - return err; -} - -int snprintf(char *str, size_t len, const char *fmt, ...) { - int err; - - va_list ap; - va_start(ap, fmt); - err = vsnprintf(str, len, fmt, ap); - va_end(ap); - - return err; -} - -int vsprintf(char *str, const char *fmt, va_list ap) { - return vsnprintf(str, INT_MAX, fmt, ap); -} - -struct _output_args { - char *outstr; - size_t len; - size_t pos; -}; - -static int _vsnprintf_output(const char *str, size_t len, void *state) { - struct _output_args *args = state; - - size_t count = 0; - while (count < len) { - if (args->pos < args->len) { - args->outstr[args->pos++] = *str; - } - - str++; - count++; - } - - // Return the count of the number of bytes that would be written even if the buffer - // wasn't large enough. - return (int)count; -} - -int vsnprintf(char *str, size_t len, const char *fmt, va_list ap) { - struct _output_args args; - int wlen; - - args.outstr = str; - args.len = len; - args.pos = 0; - - wlen = _printf_engine(&_vsnprintf_output, (void *)&args, fmt, ap); - if (len == 0) { - // do nothing, we can't null terminate the output - } else if (args.pos >= len) { - str[len-1] = '\0'; - } else { - str[wlen] = '\0'; - } - return wlen; -} - -int vfprintf(FILE *fp, const char *fmt, va_list ap) { - return _printf_engine(&_fprintf_output_func, (void *)fp, fmt, ap); -} - -int fprintf(FILE *fp, const char *fmt, ...) { - va_list ap; - int err; - - va_start(ap, fmt); - err = vfprintf(fp, fmt, ap); - va_end(ap); - return err; -} - -#if !DISABLE_DEBUG_OUTPUT -int printf(const char *fmt, ...) { - va_list ap; - int err; - - va_start(ap, fmt); - err = vfprintf(stdout, fmt, ap); - va_end(ap); - - return err; -} - -int vprintf(const char *fmt, va_list ap) { - return vfprintf(stdout, fmt, ap); -} -#endif // !DISABLE_DEBUG_OUTPUT - -#define LONGFLAG 0x00000001 -#define LONGLONGFLAG 0x00000002 -#define HALFFLAG 0x00000004 -#define HALFHALFFLAG 0x00000008 -#define SIZETFLAG 0x00000010 -#define INTMAXFLAG 0x00000020 -#define PTRDIFFFLAG 0x00000040 -#define ALTFLAG 0x00000080 -#define CAPSFLAG 0x00000100 -#define SHOWSIGNFLAG 0x00000200 -#define SIGNEDFLAG 0x00000400 -#define LEFTFORMATFLAG 0x00000800 -#define LEADZEROFLAG 0x00001000 -#define BLANKPOSFLAG 0x00002000 - -__NO_INLINE static char *longlong_to_string(char *buf, unsigned long long n, size_t len, uint flag, char *signchar) { - size_t pos = len; - bool negative = false; - - if ((flag & SIGNEDFLAG) && (long long)n < 0) { - negative = true; - n = -n; - } - - buf[--pos] = 0; - - /* only do the math if the number is >= 10 */ - while (n >= 10) { - unsigned int digit = n % 10; - - n /= 10; - - buf[--pos] = (char)digit + '0'; - } - buf[--pos] = (char)n + '0'; - - if (negative) { - *signchar = '-'; - } else if ((flag & SHOWSIGNFLAG)) { - *signchar = '+'; - } else if ((flag & BLANKPOSFLAG)) { - *signchar = ' '; - } else { - *signchar = '\0'; - } - - return &buf[pos]; -} - -static const char hextable[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; -static const char hextable_caps[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; - -__NO_INLINE static char *longlong_to_hexstring(char *buf, unsigned long long u, size_t len, uint flag) { - size_t pos = len; - const char *table = (flag & CAPSFLAG) ? hextable_caps : hextable; - - buf[--pos] = 0; - do { - unsigned int digit = u % 16; - u /= 16; - - buf[--pos] = table[digit]; - } while (u != 0); - - return &buf[pos]; -} - -#if FLOAT_PRINTF -union double_int { - double d; - uint64_t i; -}; - -#define OUT(c) buf[pos++] = (c) -#define OUTSTR(str) do { for (size_t i = 0; (str)[i] != 0; i++) OUT((str)[i]); } while (0) - -/* print up to a 4 digit exponent as string, with sign */ -__NO_INLINE static size_t exponent_to_string(char *buf, int32_t exponent) { - size_t pos = 0; - - /* handle sign */ - if (exponent < 0) { - OUT('-'); - exponent = -exponent; - } else { - OUT('+'); - } - - /* see how far we need to bump into the string to print from the right */ - if (exponent >= 1000) pos += 4; - else if (exponent >= 100) pos += 3; - else if (exponent >= 10) pos += 2; - else pos++; - - /* print decimal string, from the right */ - uint i = pos; - do { - uint digit = (uint32_t)exponent % 10; - - buf[--i] = (char)digit + '0'; - - exponent /= 10; - } while (exponent != 0); - - /* return number of characters printed */ - return pos; -} - -__NO_INLINE static char *double_to_string(char *buf, size_t len, double d, uint flag) { - size_t pos = 0; - union double_int du = { d }; - - uint32_t exponent = (du.i >> 52) & 0x7ff; - uint64_t fraction = (du.i & ((1ULL << 52) - 1)); - bool neg = !!(du.i & (1ULL << 63)); - - /* start constructing the string */ - if (neg) { - OUT('-'); - d = -d; - } - - /* longest: - * 179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.000000o - */ - - /* look for special cases */ - if (exponent == 0x7ff) { - if (fraction == 0) { - /* infinity */ - if (flag & CAPSFLAG) OUTSTR("INF"); - else OUTSTR("inf"); - } else { - /* NaN */ - if (flag & CAPSFLAG) OUTSTR("NAN"); - else OUTSTR("nan"); - } - } else if (exponent == 0) { - if (fraction == 0) { - /* zero */ - OUTSTR("0.000000"); - } else { - /* denormalized */ - /* XXX does not handle */ - if (flag & CAPSFLAG) OUTSTR("DEN"); - else OUTSTR("den"); - } - } else { - /* see if it's in the range of floats we can easily print */ - int exponent_signed = exponent - 1023; - if (exponent_signed < -52 || exponent_signed > 52) { - OUTSTR(""); - } else { - /* start by walking backwards through the string */ -#define OUTREV(c) do { if (&buf[pos] == buf) goto done; else buf[--pos] = (c); } while (0) - pos = len; - OUTREV(0); - - /* reserve space for the fractional component first */ - for (int i = 0; i <= 6; i++) - OUTREV('0'); - size_t decimal_spot = pos; - - /* print the integer portion */ - uint64_t u; - if (exponent_signed >= 0) { - u = fraction; - u |= (1ULL<<52); - u >>= (52 - exponent_signed); - - char *s = longlong_to_string(buf, u, pos + 1, flag, &(char) {0}); - - pos = s - buf; - } else { - /* exponent is negative */ - u = 0; - OUTREV('0'); - } - - buf[decimal_spot] = '.'; - - /* handle the fractional part */ - uint32_t frac = ((d - u) * 1000000) + .5; - - uint i = decimal_spot + 6 + 1; - while (frac != 0) { - uint digit = frac % 10; - - buf[--i] = digit + '0'; - - frac /= 10; - } - - if (neg) - OUTREV('-'); - -done: - /* separate return path, since we've been walking backwards through the string */ - return &buf[pos]; - } -#undef OUTREV - } - - buf[pos] = 0; - return buf; -} - -__NO_INLINE static char *double_to_hexstring(char *buf, size_t len, double d, uint flag) { - size_t pos = 0; - union double_int u = { d }; - - uint32_t exponent = (u.i >> 52) & 0x7ff; - uint64_t fraction = (u.i & ((1ULL << 52) - 1)); - bool neg = !!(u.i & (1ULL << 63)); - - /* start constructing the string */ - if (neg) { - OUT('-'); - } - - /* look for special cases */ - if (exponent == 0x7ff) { - if (fraction == 0) { - /* infinity */ - if (flag & CAPSFLAG) OUTSTR("INF"); - else OUTSTR("inf"); - } else { - /* NaN */ - if (flag & CAPSFLAG) OUTSTR("NAN"); - else OUTSTR("nan"); - } - } else if (exponent == 0) { - if (fraction == 0) { - /* zero */ - if (flag & CAPSFLAG) OUTSTR("0X0P+0"); - else OUTSTR("0x0p+0"); - } else { - /* denormalized */ - /* XXX does not handle */ - if (flag & CAPSFLAG) OUTSTR("DEN"); - else OUTSTR("den"); - } - } else { - /* regular normalized numbers: - * 0x1p+1 - * 0x1.0000000000001p+1 - * 0X1.FFFFFFFFFFFFFP+1023 - * 0x1.FFFFFFFFFFFFFP+1023 - */ - int32_t exponent_signed = exponent - 1023; - - /* implicit 1. */ - if (flag & CAPSFLAG) OUTSTR("0X1"); - else OUTSTR("0x1"); - - /* select the appropriate hex case table */ - const char *table = (flag & CAPSFLAG) ? hextable_caps : hextable; - - int zero_count = 0; - bool output_dot = false; - for (int i = 52 - 4; i >= 0; i -= 4) { - uint digit = (fraction >> i) & 0xf; - - if (digit == 0) { - zero_count++; - } else { - /* output a . the first time we output a char */ - if (!output_dot) { - OUT('.'); - output_dot = true; - } - /* if we have a non zero digit, see if we need to output a string of zeros */ - while (zero_count > 0) { - OUT('0'); - zero_count--; - } - buf[pos++] = table[digit]; - } - } - - /* handle the exponent */ - OUT((flag & CAPSFLAG) ? 'P' : 'p'); - pos += exponent_to_string(&buf[pos], exponent_signed); - } - - buf[pos] = 0; - return buf; -} - -#undef OUT -#undef OUTSTR - -#endif // FLOAT_PRINTF - -int _printf_engine(_printf_engine_output_func out, void *state, const char *fmt, va_list ap) { - int err = 0; - char c; - unsigned char uc; - const char *s; - size_t string_len; - unsigned long long n; - void *ptr; - int flags; - unsigned int format_num; - char signchar; - size_t chars_written = 0; - char num_buffer[32]; - -#define OUTPUT_STRING(str, len) do { err = out(str, len, state); if (err < 0) { goto exit; } else { chars_written += err; } } while(0) -#define OUTPUT_CHAR(c) do { char __temp[1] = { c }; OUTPUT_STRING(__temp, 1); } while (0) - - for (;;) { - /* reset the format state */ - flags = 0; - format_num = 0; - signchar = '\0'; - - /* handle regular chars that aren't format related */ - s = fmt; - string_len = 0; - while ((c = *fmt++) != 0) { - if (c == '%') - break; /* we saw a '%', break and start parsing format */ - string_len++; - } - - /* output the string we've accumulated */ - OUTPUT_STRING(s, string_len); - - /* make sure we haven't just hit the end of the string */ - if (c == 0) - break; - -next_format: - /* grab the next format character */ - c = *fmt++; - if (c == 0) - break; - - switch (c) { - case '0'...'9': - if (c == '0' && format_num == 0) - flags |= LEADZEROFLAG; - format_num *= 10; - format_num += c - '0'; - goto next_format; - case '.': - /* XXX for now eat numeric formatting */ - goto next_format; - case '%': - OUTPUT_CHAR('%'); - break; - case 'c': - uc = va_arg(ap, unsigned int); - OUTPUT_CHAR(uc); - break; - case 's': - s = va_arg(ap, const char *); - if (s == 0) - s = ""; - flags &= ~LEADZEROFLAG; /* doesn't make sense for strings */ - goto _output_string; - case '-': - flags |= LEFTFORMATFLAG; - goto next_format; - case '+': - flags |= SHOWSIGNFLAG; - goto next_format; - case ' ': - flags |= BLANKPOSFLAG; - goto next_format; - case '#': - flags |= ALTFLAG; - goto next_format; - case 'l': - if (flags & LONGFLAG) - flags |= LONGLONGFLAG; - flags |= LONGFLAG; - goto next_format; - case 'h': - if (flags & HALFFLAG) - flags |= HALFHALFFLAG; - flags |= HALFFLAG; - goto next_format; - case 'z': - flags |= SIZETFLAG; - goto next_format; - case 'j': - flags |= INTMAXFLAG; - goto next_format; - case 't': - flags |= PTRDIFFFLAG; - goto next_format; - case 'i': - case 'd': - n = (flags & LONGLONGFLAG) ? va_arg(ap, long long) : - (flags & LONGFLAG) ? va_arg(ap, long) : - (flags & HALFHALFFLAG) ? (signed char)va_arg(ap, int) : - (flags & HALFFLAG) ? (short)va_arg(ap, int) : - (flags & SIZETFLAG) ? va_arg(ap, ssize_t) : - (flags & INTMAXFLAG) ? va_arg(ap, intmax_t) : - (flags & PTRDIFFFLAG) ? va_arg(ap, ptrdiff_t) : - va_arg(ap, int); - flags |= SIGNEDFLAG; - s = longlong_to_string(num_buffer, n, sizeof(num_buffer), flags, &signchar); - goto _output_string; - case 'u': - n = (flags & LONGLONGFLAG) ? va_arg(ap, unsigned long long) : - (flags & LONGFLAG) ? va_arg(ap, unsigned long) : - (flags & HALFHALFFLAG) ? (unsigned char)va_arg(ap, unsigned int) : - (flags & HALFFLAG) ? (unsigned short)va_arg(ap, unsigned int) : - (flags & SIZETFLAG) ? va_arg(ap, size_t) : - (flags & INTMAXFLAG) ? va_arg(ap, uintmax_t) : - (flags & PTRDIFFFLAG) ? (uintptr_t)va_arg(ap, ptrdiff_t) : - va_arg(ap, unsigned int); - s = longlong_to_string(num_buffer, n, sizeof(num_buffer), flags, &signchar); - goto _output_string; - case 'p': - flags |= SIZETFLAG | ALTFLAG; - goto hex; - case 'X': - flags |= CAPSFLAG; - /* fallthrough */ -hex: - case 'x': - n = (flags & LONGLONGFLAG) ? va_arg(ap, unsigned long long) : - (flags & LONGFLAG) ? va_arg(ap, unsigned long) : - (flags & HALFHALFFLAG) ? (unsigned char)va_arg(ap, unsigned int) : - (flags & HALFFLAG) ? (unsigned short)va_arg(ap, unsigned int) : - (flags & SIZETFLAG) ? va_arg(ap, size_t) : - (flags & INTMAXFLAG) ? va_arg(ap, uintmax_t) : - (flags & PTRDIFFFLAG) ? (uintptr_t)va_arg(ap, ptrdiff_t) : - va_arg(ap, unsigned int); - s = longlong_to_hexstring(num_buffer, n, sizeof(num_buffer), flags); - - /* Normalize c, since code in _output_string needs to know that this is printing hex */ - c = 'x'; - - /* Altflag processing should be bypassed when n == 0 so that 0x is not prepended to it */ - if (n == 0) { - flags &= ~ALTFLAG; - } - - goto _output_string; - case 'n': - ptr = va_arg(ap, void *); - if (flags & LONGLONGFLAG) - *(long long *)ptr = chars_written; - else if (flags & LONGFLAG) - *(long *)ptr = chars_written; - else if (flags & HALFHALFFLAG) - *(signed char *)ptr = chars_written; - else if (flags & HALFFLAG) - *(short *)ptr = chars_written; - else if (flags & SIZETFLAG) - *(size_t *)ptr = chars_written; - else - *(int *)ptr = chars_written; - break; -#if FLOAT_PRINTF - case 'F': - flags |= CAPSFLAG; - /* fallthrough */ - case 'f': { - double d = va_arg(ap, double); - s = double_to_string(num_buffer, sizeof(num_buffer), d, flags); - goto _output_string; - } - case 'A': - flags |= CAPSFLAG; - /* fallthrough */ - case 'a': { - double d = va_arg(ap, double); - s = double_to_hexstring(num_buffer, sizeof(num_buffer), d, flags); - goto _output_string; - } -#endif - default: - OUTPUT_CHAR('%'); - OUTPUT_CHAR(c); - break; - } - - /* move on to the next field */ - continue; - - /* shared output code */ -_output_string: - string_len = strlen(s); - - if (flags & LEFTFORMATFLAG) { - /* left justify the text */ - OUTPUT_STRING(s, string_len); - uint written = err; - - /* pad to the right (if necessary) */ - for (; format_num > written; format_num--) - OUTPUT_CHAR(' '); - } else { - /* right justify the text (digits) */ - - /* if we're going to print a sign digit, - it'll chew up one byte of the format size */ - if (signchar != '\0' && format_num > 0) - format_num--; - - /* output the sign char before the leading zeros */ - if (flags & LEADZEROFLAG && signchar != '\0') - OUTPUT_CHAR(signchar); - - /* Handle (altflag) printing 0x before the number */ - /* Note that this needs to be done before padding the number */ - if (c == 'x' && (flags & ALTFLAG)) { - OUTPUT_CHAR('0'); - OUTPUT_CHAR(flags & CAPSFLAG ? 'X' : 'x'); - /* Width is adjusted so i.e printf("%#04x", 0x02) -> 0x02 instead of 0x0002 */ - if (format_num >= 2) { - format_num -= 2; - } - } - - /* pad according to the format string */ - for (; format_num > string_len; format_num--) - OUTPUT_CHAR(flags & LEADZEROFLAG ? '0' : ' '); - - /* if not leading zeros, output the sign char just before the number */ - if (!(flags & LEADZEROFLAG) && signchar != '\0') - OUTPUT_CHAR(signchar); - - /* output the string */ - OUTPUT_STRING(s, string_len); - } - } - -#undef OUTPUT_STRING -#undef OUTPUT_CHAR - -exit: - return (err < 0) ? err : (int)chars_written; -} +// Include the printf implementation without floating point. +#include "printf.c.inc" diff --git a/lib/libc/printf.c.inc b/lib/libc/printf.c.inc new file mode 100644 index 00000000..89922e87 --- /dev/null +++ b/lib/libc/printf.c.inc @@ -0,0 +1,671 @@ +/* + * Copyright (c) 2008-2014 Travis Geiselbrecht + * + * Use of this source code is governed by a MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT + */ +#include +#include +#include +#include +#include +#include +#include +#include + +// The main printf engine and all of the printf wrapper routines. +// It's important these are all in the same file, or at least all +// compiled with the same flags concerning floating point support. + +// Included by two wrapper files (printf.c and printf_float.c) with +// different compile settings. + +// If we're compiling with FLOAT_PRINTF set, emit all the functions +// with the _float suffix. +#if FLOAT_PRINTF +#define FUNC(x) x##_float +#else +#define FUNC(x) x +#endif + +int FUNC(sprintf)(char *str, const char *fmt, ...) { + int err; + + va_list ap; + va_start(ap, fmt); + err = FUNC(vsprintf)(str, fmt, ap); + va_end(ap); + + return err; +} + +int FUNC(snprintf)(char *str, size_t len, const char *fmt, ...) { + int err; + + va_list ap; + va_start(ap, fmt); + err = FUNC(vsnprintf)(str, len, fmt, ap); + va_end(ap); + + return err; +} + +int FUNC(vsprintf)(char *str, const char *fmt, va_list ap) { + return FUNC(vsnprintf)(str, INT_MAX, fmt, ap); +} + +struct _output_args { + char *outstr; + size_t len; + size_t pos; +}; + +static int _vsnprintf_output(const char *str, size_t len, void *state) { + struct _output_args *args = state; + + size_t count = 0; + while (count < len) { + if (args->pos < args->len) { + args->outstr[args->pos++] = *str; + } + + str++; + count++; + } + + // Return the count of the number of bytes that would be written even if the buffer + // wasn't large enough. + return (int)count; +} + +int FUNC(vsnprintf)(char *str, size_t len, const char *fmt, va_list ap) { + struct _output_args args; + int wlen; + + args.outstr = str; + args.len = len; + args.pos = 0; + + wlen = FUNC(_printf_engine)(&_vsnprintf_output, (void *)&args, fmt, ap); + if (len == 0) { + // do nothing, we can't null terminate the output + } else if (args.pos >= len) { + str[len-1] = '\0'; + } else { + str[wlen] = '\0'; + } + return wlen; +} + +int FUNC(vfprintf)(FILE *fp, const char *fmt, va_list ap) { + return FUNC(_printf_engine)(&_fprintf_output_func, (void *)fp, fmt, ap); +} + +int FUNC(fprintf)(FILE *fp, const char *fmt, ...) { + va_list ap; + int err; + + va_start(ap, fmt); + err = FUNC(vfprintf)(fp, fmt, ap); + va_end(ap); + return err; +} + +#if !DISABLE_DEBUG_OUTPUT +int FUNC(printf)(const char *fmt, ...) { + va_list ap; + int err; + + va_start(ap, fmt); + err = FUNC(vfprintf)(stdout, fmt, ap); + va_end(ap); + + return err; +} + +int FUNC(vprintf)(const char *fmt, va_list ap) { + return FUNC(vfprintf)(stdout, fmt, ap); +} +#endif // !DISABLE_DEBUG_OUTPUT + +#define LONGFLAG 0x00000001 +#define LONGLONGFLAG 0x00000002 +#define HALFFLAG 0x00000004 +#define HALFHALFFLAG 0x00000008 +#define SIZETFLAG 0x00000010 +#define INTMAXFLAG 0x00000020 +#define PTRDIFFFLAG 0x00000040 +#define ALTFLAG 0x00000080 +#define CAPSFLAG 0x00000100 +#define SHOWSIGNFLAG 0x00000200 +#define SIGNEDFLAG 0x00000400 +#define LEFTFORMATFLAG 0x00000800 +#define LEADZEROFLAG 0x00001000 +#define BLANKPOSFLAG 0x00002000 + +static char *longlong_to_string(char *buf, unsigned long long n, size_t len, uint flag, char *signchar) { + size_t pos = len; + bool negative = false; + + if ((flag & SIGNEDFLAG) && (long long)n < 0) { + negative = true; + n = -n; + } + + buf[--pos] = 0; + + /* only do the math if the number is >= 10 */ + while (n >= 10) { + unsigned int digit = n % 10; + + n /= 10; + + buf[--pos] = (char)digit + '0'; + } + buf[--pos] = (char)n + '0'; + + if (negative) { + *signchar = '-'; + } else if ((flag & SHOWSIGNFLAG)) { + *signchar = '+'; + } else if ((flag & BLANKPOSFLAG)) { + *signchar = ' '; + } else { + *signchar = '\0'; + } + + return &buf[pos]; +} + +static const char hextable[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; +static const char hextable_caps[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + +static char *longlong_to_hexstring(char *buf, unsigned long long u, size_t len, uint flag) { + size_t pos = len; + const char *table = (flag & CAPSFLAG) ? hextable_caps : hextable; + + buf[--pos] = 0; + do { + unsigned int digit = u % 16; + u /= 16; + + buf[--pos] = table[digit]; + } while (u != 0); + + return &buf[pos]; +} + +#if FLOAT_PRINTF +union double_int { + double d; + uint64_t i; +}; + +#define OUT(c) buf[pos++] = (c) +#define OUTSTR(str) do { for (size_t i = 0; (str)[i] != 0; i++) OUT((str)[i]); } while (0) + +/* print up to a 4 digit exponent as string, with sign */ +static size_t exponent_to_string(char *buf, int32_t exponent) { + size_t pos = 0; + + /* handle sign */ + if (exponent < 0) { + OUT('-'); + exponent = -exponent; + } else { + OUT('+'); + } + + /* see how far we need to bump into the string to print from the right */ + if (exponent >= 1000) pos += 4; + else if (exponent >= 100) pos += 3; + else if (exponent >= 10) pos += 2; + else pos++; + + /* print decimal string, from the right */ + uint i = pos; + do { + uint digit = (uint32_t)exponent % 10; + + buf[--i] = (char)digit + '0'; + + exponent /= 10; + } while (exponent != 0); + + /* return number of characters printed */ + return pos; +} + +static char *double_to_string(char *buf, size_t len, double d, uint flag) { + size_t pos = 0; + union double_int du = { d }; + + uint32_t exponent = (du.i >> 52) & 0x7ff; + uint64_t fraction = (du.i & ((1ULL << 52) - 1)); + bool neg = !!(du.i & (1ULL << 63)); + + /* start constructing the string */ + if (neg) { + OUT('-'); + d = -d; + } + + /* longest: + * 179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.000000o + */ + + /* look for special cases */ + if (exponent == 0x7ff) { + if (fraction == 0) { + /* infinity */ + if (flag & CAPSFLAG) OUTSTR("INF"); + else OUTSTR("inf"); + } else { + /* NaN */ + if (flag & CAPSFLAG) OUTSTR("NAN"); + else OUTSTR("nan"); + } + } else if (exponent == 0) { + if (fraction == 0) { + /* zero */ + OUTSTR("0.000000"); + } else { + /* denormalized */ + /* XXX does not handle */ + if (flag & CAPSFLAG) OUTSTR("DEN"); + else OUTSTR("den"); + } + } else { + /* see if it's in the range of floats we can easily print */ + int exponent_signed = exponent - 1023; + if (exponent_signed < -52 || exponent_signed > 52) { + OUTSTR(""); + } else { + /* start by walking backwards through the string */ +#define OUTREV(c) do { if (&buf[pos] == buf) goto done; else buf[--pos] = (c); } while (0) + pos = len; + OUTREV(0); + + /* reserve space for the fractional component first */ + for (int i = 0; i <= 6; i++) + OUTREV('0'); + size_t decimal_spot = pos; + + /* print the integer portion */ + uint64_t u; + if (exponent_signed >= 0) { + u = fraction; + u |= (1ULL<<52); + u >>= (52 - exponent_signed); + + char *s = longlong_to_string(buf, u, pos + 1, flag, &(char) {0}); + + pos = s - buf; + } else { + /* exponent is negative */ + u = 0; + OUTREV('0'); + } + + buf[decimal_spot] = '.'; + + /* handle the fractional part */ + uint32_t frac = ((d - u) * 1000000) + .5; + + uint i = decimal_spot + 6 + 1; + while (frac != 0) { + uint digit = frac % 10; + + buf[--i] = digit + '0'; + + frac /= 10; + } + + if (neg) + OUTREV('-'); + +done: + /* separate return path, since we've been walking backwards through the string */ + return &buf[pos]; + } +#undef OUTREV + } + + buf[pos] = 0; + return buf; +} + +static char *double_to_hexstring(char *buf, size_t len, double d, uint flag) { + size_t pos = 0; + union double_int u = { d }; + + uint32_t exponent = (u.i >> 52) & 0x7ff; + uint64_t fraction = (u.i & ((1ULL << 52) - 1)); + bool neg = !!(u.i & (1ULL << 63)); + + /* start constructing the string */ + if (neg) { + OUT('-'); + } + + /* look for special cases */ + if (exponent == 0x7ff) { + if (fraction == 0) { + /* infinity */ + if (flag & CAPSFLAG) OUTSTR("INF"); + else OUTSTR("inf"); + } else { + /* NaN */ + if (flag & CAPSFLAG) OUTSTR("NAN"); + else OUTSTR("nan"); + } + } else if (exponent == 0) { + if (fraction == 0) { + /* zero */ + if (flag & CAPSFLAG) OUTSTR("0X0P+0"); + else OUTSTR("0x0p+0"); + } else { + /* denormalized */ + /* XXX does not handle */ + if (flag & CAPSFLAG) OUTSTR("DEN"); + else OUTSTR("den"); + } + } else { + /* regular normalized numbers: + * 0x1p+1 + * 0x1.0000000000001p+1 + * 0X1.FFFFFFFFFFFFFP+1023 + * 0x1.FFFFFFFFFFFFFP+1023 + */ + int32_t exponent_signed = exponent - 1023; + + /* implicit 1. */ + if (flag & CAPSFLAG) OUTSTR("0X1"); + else OUTSTR("0x1"); + + /* select the appropriate hex case table */ + const char *table = (flag & CAPSFLAG) ? hextable_caps : hextable; + + int zero_count = 0; + bool output_dot = false; + for (int i = 52 - 4; i >= 0; i -= 4) { + uint digit = (fraction >> i) & 0xf; + + if (digit == 0) { + zero_count++; + } else { + /* output a . the first time we output a char */ + if (!output_dot) { + OUT('.'); + output_dot = true; + } + /* if we have a non zero digit, see if we need to output a string of zeros */ + while (zero_count > 0) { + OUT('0'); + zero_count--; + } + buf[pos++] = table[digit]; + } + } + + /* handle the exponent */ + OUT((flag & CAPSFLAG) ? 'P' : 'p'); + pos += exponent_to_string(&buf[pos], exponent_signed); + } + + buf[pos] = 0; + return buf; +} + +#undef OUT +#undef OUTSTR + +#endif // FLOAT_PRINTF + +int FUNC(_printf_engine)(_printf_engine_output_func out, void *state, const char *fmt, va_list ap) { + int err = 0; + char c; + unsigned char uc; + const char *s; + size_t string_len; + unsigned long long n; + void *ptr; + int flags; + unsigned int format_num; + char signchar; + size_t chars_written = 0; + char num_buffer[32]; + +#define OUTPUT_STRING(str, len) do { err = out(str, len, state); if (err < 0) { goto exit; } else { chars_written += err; } } while(0) +#define OUTPUT_CHAR(c) do { char __temp[1] = { c }; OUTPUT_STRING(__temp, 1); } while (0) + + for (;;) { + /* reset the format state */ + flags = 0; + format_num = 0; + signchar = '\0'; + + /* handle regular chars that aren't format related */ + s = fmt; + string_len = 0; + while ((c = *fmt++) != 0) { + if (c == '%') + break; /* we saw a '%', break and start parsing format */ + string_len++; + } + + /* output the string we've accumulated */ + OUTPUT_STRING(s, string_len); + + /* make sure we haven't just hit the end of the string */ + if (c == 0) + break; + +next_format: + /* grab the next format character */ + c = *fmt++; + if (c == 0) + break; + + switch (c) { + case '0'...'9': + if (c == '0' && format_num == 0) + flags |= LEADZEROFLAG; + format_num *= 10; + format_num += c - '0'; + goto next_format; + case '.': + /* XXX for now eat numeric formatting */ + goto next_format; + case '%': + OUTPUT_CHAR('%'); + break; + case 'c': + uc = va_arg(ap, unsigned int); + OUTPUT_CHAR(uc); + break; + case 's': + s = va_arg(ap, const char *); + if (s == 0) + s = ""; + flags &= ~LEADZEROFLAG; /* doesn't make sense for strings */ + goto _output_string; + case '-': + flags |= LEFTFORMATFLAG; + goto next_format; + case '+': + flags |= SHOWSIGNFLAG; + goto next_format; + case ' ': + flags |= BLANKPOSFLAG; + goto next_format; + case '#': + flags |= ALTFLAG; + goto next_format; + case 'l': + if (flags & LONGFLAG) + flags |= LONGLONGFLAG; + flags |= LONGFLAG; + goto next_format; + case 'h': + if (flags & HALFFLAG) + flags |= HALFHALFFLAG; + flags |= HALFFLAG; + goto next_format; + case 'z': + flags |= SIZETFLAG; + goto next_format; + case 'j': + flags |= INTMAXFLAG; + goto next_format; + case 't': + flags |= PTRDIFFFLAG; + goto next_format; + case 'i': + case 'd': + n = (flags & LONGLONGFLAG) ? va_arg(ap, long long) : + (flags & LONGFLAG) ? va_arg(ap, long) : + (flags & HALFHALFFLAG) ? (signed char)va_arg(ap, int) : + (flags & HALFFLAG) ? (short)va_arg(ap, int) : + (flags & SIZETFLAG) ? va_arg(ap, ssize_t) : + (flags & INTMAXFLAG) ? va_arg(ap, intmax_t) : + (flags & PTRDIFFFLAG) ? va_arg(ap, ptrdiff_t) : + va_arg(ap, int); + flags |= SIGNEDFLAG; + s = longlong_to_string(num_buffer, n, sizeof(num_buffer), flags, &signchar); + goto _output_string; + case 'u': + n = (flags & LONGLONGFLAG) ? va_arg(ap, unsigned long long) : + (flags & LONGFLAG) ? va_arg(ap, unsigned long) : + (flags & HALFHALFFLAG) ? (unsigned char)va_arg(ap, unsigned int) : + (flags & HALFFLAG) ? (unsigned short)va_arg(ap, unsigned int) : + (flags & SIZETFLAG) ? va_arg(ap, size_t) : + (flags & INTMAXFLAG) ? va_arg(ap, uintmax_t) : + (flags & PTRDIFFFLAG) ? (uintptr_t)va_arg(ap, ptrdiff_t) : + va_arg(ap, unsigned int); + s = longlong_to_string(num_buffer, n, sizeof(num_buffer), flags, &signchar); + goto _output_string; + case 'p': + flags |= SIZETFLAG | ALTFLAG; + goto hex; + case 'X': + flags |= CAPSFLAG; + /* fallthrough */ +hex: + case 'x': + n = (flags & LONGLONGFLAG) ? va_arg(ap, unsigned long long) : + (flags & LONGFLAG) ? va_arg(ap, unsigned long) : + (flags & HALFHALFFLAG) ? (unsigned char)va_arg(ap, unsigned int) : + (flags & HALFFLAG) ? (unsigned short)va_arg(ap, unsigned int) : + (flags & SIZETFLAG) ? va_arg(ap, size_t) : + (flags & INTMAXFLAG) ? va_arg(ap, uintmax_t) : + (flags & PTRDIFFFLAG) ? (uintptr_t)va_arg(ap, ptrdiff_t) : + va_arg(ap, unsigned int); + s = longlong_to_hexstring(num_buffer, n, sizeof(num_buffer), flags); + + /* Normalize c, since code in _output_string needs to know that this is printing hex */ + c = 'x'; + + /* Altflag processing should be bypassed when n == 0 so that 0x is not prepended to it */ + if (n == 0) { + flags &= ~ALTFLAG; + } + + goto _output_string; + case 'n': + ptr = va_arg(ap, void *); + if (flags & LONGLONGFLAG) + *(long long *)ptr = chars_written; + else if (flags & LONGFLAG) + *(long *)ptr = chars_written; + else if (flags & HALFHALFFLAG) + *(signed char *)ptr = chars_written; + else if (flags & HALFFLAG) + *(short *)ptr = chars_written; + else if (flags & SIZETFLAG) + *(size_t *)ptr = chars_written; + else + *(int *)ptr = chars_written; + break; +#if FLOAT_PRINTF + case 'F': + flags |= CAPSFLAG; + /* fallthrough */ + case 'f': { + double d = va_arg(ap, double); + s = double_to_string(num_buffer, sizeof(num_buffer), d, flags); + goto _output_string; + } + case 'A': + flags |= CAPSFLAG; + /* fallthrough */ + case 'a': { + double d = va_arg(ap, double); + s = double_to_hexstring(num_buffer, sizeof(num_buffer), d, flags); + goto _output_string; + } +#endif + default: + OUTPUT_CHAR('%'); + OUTPUT_CHAR(c); + break; + } + + /* move on to the next field */ + continue; + + /* shared output code */ +_output_string: + string_len = strlen(s); + + if (flags & LEFTFORMATFLAG) { + /* left justify the text */ + OUTPUT_STRING(s, string_len); + uint written = err; + + /* pad to the right (if necessary) */ + for (; format_num > written; format_num--) + OUTPUT_CHAR(' '); + } else { + /* right justify the text (digits) */ + + /* if we're going to print a sign digit, + it'll chew up one byte of the format size */ + if (signchar != '\0' && format_num > 0) + format_num--; + + /* output the sign char before the leading zeros */ + if (flags & LEADZEROFLAG && signchar != '\0') + OUTPUT_CHAR(signchar); + + /* Handle (altflag) printing 0x before the number */ + /* Note that this needs to be done before padding the number */ + if (c == 'x' && (flags & ALTFLAG)) { + OUTPUT_CHAR('0'); + OUTPUT_CHAR(flags & CAPSFLAG ? 'X' : 'x'); + /* Width is adjusted so i.e printf("%#04x", 0x02) -> 0x02 instead of 0x0002 */ + if (format_num >= 2) { + format_num -= 2; + } + } + + /* pad according to the format string */ + for (; format_num > string_len; format_num--) + OUTPUT_CHAR(flags & LEADZEROFLAG ? '0' : ' '); + + /* if not leading zeros, output the sign char just before the number */ + if (!(flags & LEADZEROFLAG) && signchar != '\0') + OUTPUT_CHAR(signchar); + + /* output the string */ + OUTPUT_STRING(s, string_len); + } + } + +#undef OUTPUT_STRING +#undef OUTPUT_CHAR + +exit: + return (err < 0) ? err : (int)chars_written; +} diff --git a/lib/libc/printf_float.c b/lib/libc/printf_float.c new file mode 100644 index 00000000..dc7955b1 --- /dev/null +++ b/lib/libc/printf_float.c @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2025 Travis Geiselbrecht + * + * Use of this source code is governed by a MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT + */ + +// Include the printf implementation with floating point. + +#if !WITH_NO_FP + +#define FLOAT_PRINTF 1 + +#include "printf.c.inc" +#endif diff --git a/lib/libc/rules.mk b/lib/libc/rules.mk index cb647b66..651fd8d7 100644 --- a/lib/libc/rules.mk +++ b/lib/libc/rules.mk @@ -14,6 +14,7 @@ MODULE_SRCS += \ $(LOCAL_DIR)/ctype.c \ $(LOCAL_DIR)/eabi.c \ $(LOCAL_DIR)/errno.c \ + $(LOCAL_DIR)/printf.c \ $(LOCAL_DIR)/qsort.c \ $(LOCAL_DIR)/rand.c \ $(LOCAL_DIR)/stdio.c \ @@ -21,7 +22,7 @@ MODULE_SRCS += \ $(LOCAL_DIR)/strtoll.c \ MODULE_FLOAT_SRCS += \ - $(LOCAL_DIR)/printf.c \ + $(LOCAL_DIR)/printf_float.c \ $(LOCAL_DIR)/atof.c \ MODULE_COMPILEFLAGS += -fno-builtin diff --git a/lib/libc/test/printf_tests_float.cpp b/lib/libc/test/printf_tests_float.cpp index 344fabba..a5165484 100644 --- a/lib/libc/test/printf_tests_float.cpp +++ b/lib/libc/test/printf_tests_float.cpp @@ -18,7 +18,7 @@ bool test_printf(const char* expected, const char* format, ...) { char buf[100]; va_list args; va_start(args, format); - int length = vsnprintf(buf, sizeof(buf), format, args); + int length = vsnprintf_float(buf, sizeof(buf), format, args); va_end(args); if (length < 0 || length >= static_cast(sizeof(buf))) {