summaryrefslogtreecommitdiff
path: root/firmware/common/vuprintf.c
diff options
context:
space:
mode:
Diffstat (limited to 'firmware/common/vuprintf.c')
-rw-r--r--firmware/common/vuprintf.c475
1 files changed, 465 insertions, 10 deletions
diff --git a/firmware/common/vuprintf.c b/firmware/common/vuprintf.c
index 7dd9449e56..8bb9451662 100644
--- a/firmware/common/vuprintf.c
+++ b/firmware/common/vuprintf.c
@@ -23,8 +23,10 @@
23#include <limits.h> 23#include <limits.h>
24#include <string.h> 24#include <string.h>
25#include <stddef.h> 25#include <stddef.h>
26#include <stdlib.h>
26#include "system.h" 27#include "system.h"
27#include "vuprintf.h" 28#include "vuprintf.h"
29#include "ap_int.h"
28 30
29#ifndef BOOTLOADER 31#ifndef BOOTLOADER
30/* turn everything on */ 32/* turn everything on */
@@ -58,7 +60,11 @@
58#define FMT_LENMOD_ll 0x010 /* signed/unsigned long long (%ll<radix>) */ 60#define FMT_LENMOD_ll 0x010 /* signed/unsigned long long (%ll<radix>) */
59#define FMT_LENMOD_t 0x020 /* signed/unsigned ptrdiff_t (%t<radix>) */ 61#define FMT_LENMOD_t 0x020 /* signed/unsigned ptrdiff_t (%t<radix>) */
60#define FMT_LENMOD_z 0x040 /* size_t/ssize_t (%z<radix>) */ 62#define FMT_LENMOD_z 0x040 /* size_t/ssize_t (%z<radix>) */
63#if 0
61#define FMT_LENMOD_L 0x080 /* long double (instead of double) */ 64#define FMT_LENMOD_L 0x080 /* long double (instead of double) */
65#else
66#define FMT_LENMOD_L 0x000
67#endif
62 68
63/* compulsory radixes: c, d, i, u, s */ 69/* compulsory radixes: c, d, i, u, s */
64#define FMT_RADIX_c 0x001 /* single character (%c) */ 70#define FMT_RADIX_c 0x001 /* single character (%c) */
@@ -75,6 +81,18 @@
75#define FMT_RADIX_g 0x800 /* floating point exponent or decimal depending 81#define FMT_RADIX_g 0x800 /* floating point exponent or decimal depending
76 upon value and precision */ 82 upon value and precision */
77 83
84/* TODO: 'a' 'A' */
85#define FMT_RADIX_floats (FMT_RADIX_e|FMT_RADIX_f|FMT_RADIX_g)
86
87#if (FMT_RADIX & FMT_RADIX_floats)
88/* Assumes IEEE 754 double-precision, native-endian; replace to parse and init
89 for some other format */
90#define parse_double parse_ieee754_double
91#define init_double_chunks init_ieee754_double_chunks
92#define format_double_int10 format_ap_int10
93#define format_double_frac10 format_ap_frac10
94#endif
95
78/* avoid defining redundant functions if two or more types can use the same 96/* avoid defining redundant functions if two or more types can use the same
79 * something not getting a macro means it gets assigned its own value and 97 * something not getting a macro means it gets assigned its own value and
80 * formatter */ 98 * formatter */
@@ -374,8 +392,24 @@ struct fmt_buf {
374 or prefix (numeric) */ 392 or prefix (numeric) */
375 char buf[24]; /* work buffer */ 393 char buf[24]; /* work buffer */
376 char bufend[1]; /* buffer end marker and guard '0' */ 394 char bufend[1]; /* buffer end marker and guard '0' */
395#if (FMT_RADIX & FMT_RADIX_floats)
396 int lenmod;
397 int radixchar;
398 int signchar;
399 int alignchar;
400 int width;
401 int precision;
402 char *p;
403#endif
377}; 404};
378 405
406#define PUSHCHAR(ch) \
407 ({ int __rc = push(userp, (ch)); \
408 count += __rc >= 0; \
409 if (__rc <= 0) { \
410 goto done; \
411 } })
412
379/* %d %i */ 413/* %d %i */
380static inline const char * format_d(int val, 414static inline const char * format_d(int val,
381 struct fmt_buf *fmt_buf, 415 struct fmt_buf *fmt_buf,
@@ -530,6 +564,400 @@ static inline const char * format_p(const void *p,
530} 564}
531#endif /* FMT_RADIX_p */ 565#endif /* FMT_RADIX_p */
532 566
567#if (FMT_RADIX & FMT_RADIX_floats)
568/* find out how many uint32_t chunks need to be allocated, if any
569 * if none are needed, finish the init for the number here */
570static long parse_ieee754_double(double f,
571 struct ap_int *ia,
572 struct ap_int *fa,
573 struct fmt_buf *fmt_buf)
574{
575 long rc = 0;
576
577 union {
578 double f;
579 uint64_t f64;
580 } u = { .f = f };
581
582 int e = ((int)(u.f64 >> 52) & 0x7ff) - 1023; /* -1023..1024 */
583 uint64_t mantissa = u.f64 & 0x000fffffffffffffull;
584
585 if (u.f64 >> 63) {
586 fmt_buf->signchar = '-';
587 }
588
589 if (LIKELY(e >= -8 && e <= 63)) { /* -8 to +63 */
590 /* integer, fraction and manipulations fit in uint64_t */
591 mantissa |= 0x0010000000000000ull;
592 ia->numchunks = 0;
593 ia->shift = 0;
594 fa->numchunks = 0;
595
596 if (e < 0) { /* -8 to -1 - fraction */
597 long fracbits = 52 - e;
598 /* int - none */
599 ia->len = 0;
600 ia->val = 0;
601 /* frac */
602 fa->len = fracbits - __builtin_ctzll(mantissa);
603 fa->shift = fracbits;
604 fa->val = mantissa;
605 }
606 else if (e <= 51) { /* 0 to +51 - integer|fraction */
607 long fracbits = 52 - e;
608 /* int */
609 ia->len = base10exp(e) + 2; /* go up + possibly 1 longer */
610 ia->val = mantissa >> fracbits;
611 /* frac */
612 fa->shift = fracbits;
613 fa->val = mantissa ^ (ia->val << fracbits);
614 fa->len = fa->val ? fracbits - __builtin_ctzll(mantissa) : 0;
615 }
616 else { /* +52 to +63 - integer */
617 /* int */
618 ia->len = base10exp(e) + 2;
619 ia->val = mantissa << (e - 52);
620 /* frac - none */
621 fa->len = 0;
622 fa->shift = 0;
623 fa->val = 0;
624 }
625 }
626 else if (e < 0) { /* -1023 to -9 - fraction */
627 /* int - none */
628 ia->numchunks = 0;
629 ia->len = 0;
630 ia->shift = 0;
631 ia->val = 0;
632 /* frac - left-justify on bit 31 of the chunk of the MSb */
633 if (e >= -1022) { /* normal */
634 mantissa |= 0x0010000000000000ull;
635 }
636 else { /* subnormal (including zero) */
637 e = -1022;
638 }
639
640 if (mantissa) {
641 long fracbits = 52 - e;
642 fa->len = fracbits - __builtin_ctzll(mantissa);
643 fa->shift = 31 - ((51 - e) % 32);
644 fa->val = mantissa;
645 fa->basechunk = (fa->shift + 52) / 32;
646 fa->numchunks = (51 - e + fa->shift) / 32 + 1;
647 rc = fa->numchunks;
648 }
649 else { /* zero */
650 fa->numchunks = 0;
651 fa->len = 0;
652 fa->shift = 0;
653 fa->val = 0;
654 }
655 }
656 else if (e <= 1023) { /* +64 to +1023 - integer */
657 /* int - right-justify on bit 0 of the first chunk */
658 ia->val = mantissa | 0x0010000000000000ull;
659 ia->len = base10exp(e) + 2;
660 ia->shift = (e - 52) % 32;
661 ia->basechunk = e / 32;
662 ia->numchunks = ia->basechunk + 1;
663 rc = ia->numchunks;
664 /* frac - none */
665 fa->numchunks = 0;
666 fa->len = 0;
667 fa->shift = 0;
668 fa->val = 0;
669 }
670 else { /* +1024: INF, NAN */
671 rc = -1 - !!mantissa;
672 }
673
674 return rc;
675}
676
677/* construct the arbitrary-precision value in the provided allocation */
678static void init_ieee754_double_chunks(struct ap_int *a,
679 uint32_t *a_chunks)
680{
681 long basechunk = a->basechunk;
682 long shift = a->shift;
683 uint64_t val = a->val;
684
685 a->chunks = a_chunks;
686
687 memset(a_chunks, 0, a->numchunks*sizeof (uint32_t));
688
689 if (shift < 12) {
690 a_chunks[basechunk - 1] = val << shift;
691 a_chunks[basechunk - 0] = val >> (32 - shift);
692 }
693 else {
694 a_chunks[basechunk - 2] = val << shift;
695 a_chunks[basechunk - 1] = val >> (32 - shift);
696 a_chunks[basechunk - 0] = val >> (64 - shift);
697 }
698}
699
700/* format inf, nan strings */
701static void format_inf_nan(struct fmt_buf *fmt_buf, long type)
702{
703 /* certain special values */
704 static const char text[2][2][3] =
705 {
706 { { 'I', 'N', 'F' }, { 'i', 'n', 'f' } },
707 { { 'N', 'A', 'N' }, { 'n', 'a', 'n' } },
708 };
709
710 char *p = fmt_buf->buf;
711 fmt_buf->p = p;
712 fmt_buf->length = 3;
713
714 /* they also have a sign */
715 if (fmt_buf->signchar) {
716 *p++ = fmt_buf->signchar;
717 fmt_buf->length++;
718 }
719
720 memcpy(p, &text[type][(fmt_buf->radixchar >> 5) & 0x1], 3);
721}
722
723/* %e %E %f %F %g %G */
724static int format_double_radix(double f,
725 struct fmt_buf *fmt_buf,
726 vuprintf_push_cb push,
727 void *userp)
728{
729 struct ap_int ia, fa;
730 long rc = parse_double(f, &ia, &fa, fmt_buf);
731
732 if (UNLIKELY(rc < 0)) {
733 format_inf_nan(fmt_buf, -rc - 1);
734 return 0;
735 }
736
737 int count = 0;
738
739 /* default precision is 6 for all formats */
740 int prec_rem = fmt_buf->precision < 0 ? 6 : fmt_buf->precision;
741
742 int exp = exp;
743 int explen = 0;
744
745 switch (fmt_buf->radixchar & 0x3)
746 {
747 case 3: /* %g, %G */
748 fmt_buf->precision = prec_rem;
749 if (prec_rem) {
750 prec_rem--;
751 }
752 case 1: /* %e, %E */
753 explen = 2;
754 break;
755 default:
756 break;
757 }
758
759 if (rc > 0 && ia.numchunks > 0) {
760 /* large integer required */
761 init_double_chunks(&ia, alloca(rc*sizeof(*ia.chunks)));
762 rc = 0;
763 }
764
765 const int bufoffs = 6; /* log rollover + round rollover + leading zeros (%g) */
766 long f_prec = MIN(fa.len, prec_rem + 1);
767 char buf[bufoffs + ia.len + f_prec + 1];
768 char *p_last = &buf[bufoffs + ia.len];
769 char *p_dec = p_last;
770 char *p_first = format_double_int10(&ia, p_last);
771
772 if (explen) {
773 if (!ia.val && fa.len) {
774 p_first = p_last = &buf[bufoffs];
775 f_prec = -f_prec - 1; /* no lead zeros */
776 }
777 else { /* handles 0e+0 too */
778 exp = ia.len - 1;
779
780 if (exp) {
781 prec_rem -= exp;
782
783 if (prec_rem < 0) {
784 p_last += prec_rem + 1;
785 f_prec = 0;
786 }
787 else {
788 f_prec = MIN(fa.len, prec_rem + 1);
789 }
790 }
791 }
792
793 p_dec = p_first + 1;
794 }
795
796 if (f_prec) {
797 if (rc > 0) {
798 /* large integer required */
799 init_double_chunks(&fa, alloca(rc*sizeof(*fa.chunks)));
800 }
801
802 p_last = format_double_frac10(&fa, p_last, f_prec);
803
804 if (f_prec < 0) {
805 f_prec = -f_prec - 1;
806 exp = f_prec - fa.len;
807 }
808
809 prec_rem -= f_prec;
810 }
811
812 if (prec_rem < 0) {
813 prec_rem = 0;
814 p_last--;
815
816 if (round_number_string10(p_last, p_last - p_first)) {
817 /* carried left */
818 p_first--;
819
820 if (explen) {
821 /* slide everything left by 1 */
822 exp++;
823 p_dec--;
824 p_last--;
825 }
826 }
827 }
828
829 if (explen) {
830 if ((fmt_buf->radixchar & 0x3) == 0x3) { /* g, G */
831 /* 'g' is some weird crap */
832 /* now that the final exponent is known and everything rounded,
833 it is possible to decide whether to format similarly to
834 'e' or 'f' */
835 if (fmt_buf->precision > exp && exp >= -4) { /* P > X >= -4 */
836 if (exp >= 0) {
837 /* integer digits will be in the buffer */
838 p_dec = p_first + exp + 1;
839 }
840 else {
841 /* we didn't keep leading zeros and need to regenerate
842 them; space was reserved just in case */
843 p_first = memset(p_dec + exp - 1, '0', -exp);
844 p_dec = p_first + 1;
845 }
846
847 /* suppress exponent */
848 explen = 0;
849 }
850
851 if (!fmt_buf->length) {
852 /* strip any trailing zeros from the fraction */
853 while (p_last > p_dec && p_last[-1] == '0') {
854 p_last--;
855 }
856
857 /* suppress trailing precision fill */
858 prec_rem = 0;
859 }
860 }
861
862 if (explen) {
863 /* build exponent string: 'eħdd' */
864 char *p = fmt_buf->bufend;
865 int signchar = '+';
866
867 if (exp < 0) {
868 signchar = '-';
869 exp = -exp;
870 }
871
872 while (exp || explen < 4) {
873 *--p = exp % 10 + '0';
874 exp /= 10;
875 explen++;
876 }
877
878 *--p = signchar;
879 *--p = fmt_buf->radixchar & ~0x2;
880 }
881 }
882
883 int width = fmt_buf->width;
884 int point = p_last > p_dec || prec_rem || fmt_buf->length;
885 int length = p_last - p_first + !!fmt_buf->signchar + point + explen;
886
887 if (width) {
888 if (width - length <= prec_rem) {
889 width = 0;
890 }
891 else {
892 width -= length + prec_rem;
893 }
894 }
895
896 rc = -1;
897
898 /* left padding */
899 if (fmt_buf->alignchar > '0') {
900 /* space-padded width -- before sign */
901 while (width > 0) {
902 PUSHCHAR(' ');
903 width--;
904 }
905 }
906
907 if (fmt_buf->signchar) {
908 PUSHCHAR(fmt_buf->signchar);
909 }
910
911 if (fmt_buf->alignchar == '0') {
912 /* zero-padded width -- after sign */
913 while (width > 0) {
914 PUSHCHAR('0');
915 width--;
916 }
917 }
918
919 /* integer part */
920 while (p_first < p_dec) {
921 PUSHCHAR(*p_first++);
922 }
923
924 /* decimal point */
925 if (point) {
926 PUSHCHAR('.');
927 }
928
929 /* fractional part */
930 while (p_first < p_last) {
931 PUSHCHAR(*p_first++);
932 }
933
934 /* precision 0-padding */
935 while (prec_rem > 0) {
936 PUSHCHAR('0');
937 prec_rem--;
938 }
939
940 /* exponent */
941 if (explen > 0) {
942 char *p = fmt_buf->bufend;
943 while (explen > 0) {
944 PUSHCHAR(p[-explen--]);
945 }
946 }
947
948 /* right padding */
949 while (width > 0) {
950 PUSHCHAR(' ');
951 width--;
952 }
953
954 rc = 1;
955done:
956 fmt_buf->length = count;
957 return rc;
958}
959#endif /* FMT_RADIX_floats */
960
533/* parse fixed width or precision field */ 961/* parse fixed width or precision field */
534static const char * parse_number_spec(const char *fmt, 962static const char * parse_number_spec(const char *fmt,
535 int ch, 963 int ch,
@@ -558,16 +986,6 @@ int vuprintf(vuprintf_push_cb push, /* call 'push()' for each output letter */
558 const char *fmt, 986 const char *fmt,
559 va_list ap) 987 va_list ap)
560{ 988{
561 #define PUSHCHAR(ch) \
562 do { \
563 int __ch = (ch); \
564 int __rc = push(userp, __ch); \
565 count += __rc >= 0; \
566 if (__rc <= 0) { \
567 goto done; \
568 } \
569 } while (0)
570
571 int count = 0; 989 int count = 0;
572 int ch; 990 int ch;
573 991
@@ -706,6 +1124,9 @@ int vuprintf(vuprintf_push_cb push, /* call 'push()' for each output letter */
706 #if (FMT_LENMOD & FMT_LENMOD_z) 1124 #if (FMT_LENMOD & FMT_LENMOD_z)
707 case 'z': 1125 case 'z':
708 #endif 1126 #endif
1127 #if (FMT_LENMOD & FMT_LENMOD_L)
1128 case 'L':
1129 #endif
709 lenmod = ch; 1130 lenmod = ch;
710 ch = *fmt++; 1131 ch = *fmt++;
711 #if (FMT_LENMOD & (FMT_LENMOD_hh | FMT_LENMOD_ll)) 1132 #if (FMT_LENMOD & (FMT_LENMOD_hh | FMT_LENMOD_ll))
@@ -747,6 +1168,40 @@ int vuprintf(vuprintf_push_cb push, /* call 'push()' for each output letter */
747 break; 1168 break;
748 #endif 1169 #endif
749 1170
1171 #if (FMT_RADIX & FMT_RADIX_floats)
1172 /* any floats gets all of them (except with 'L' and %a, %A for now) */
1173 case 'e':
1174 case 'E':
1175 case 'f':
1176 case 'F':
1177 case 'g':
1178 case 'G':
1179 /* LENMOD_L isn't supported for now and will be rejected automatically */
1180
1181 /* floating point has very different spec interpretations to other
1182 formats and requires special handling */
1183 fmt_buf.length = pfxlen;
1184 fmt_buf.lenmod = lenmod;
1185 fmt_buf.radixchar = ch;
1186 fmt_buf.signchar = signchar;
1187 fmt_buf.alignchar = alignchar;
1188 fmt_buf.width = width;
1189 fmt_buf.precision = precision;
1190
1191 ch = format_double_radix(va_arg(ap, double), &fmt_buf, push, userp);
1192 if (ch) {
1193 count += fmt_buf.length;
1194 if (ch > 0) {
1195 continue;
1196 }
1197
1198 goto done;
1199 }
1200
1201 buf = fmt_buf.p;
1202 break;
1203 #endif
1204
750 /** signed integer **/ 1205 /** signed integer **/
751 case 'd': 1206 case 'd':
752 case 'i': 1207 case 'i':