]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
tools/nolibc/printf: Simplify __nolibc_printf()
authorDavid Laight <david.laight.linux@gmail.com>
Sun, 8 Mar 2026 11:37:31 +0000 (11:37 +0000)
committerThomas Weißschuh <linux@weissschuh.net>
Fri, 20 Mar 2026 16:46:10 +0000 (17:46 +0100)
Move the check for the length modifiers into the format processing
between the field width and conversion specifier.
This lets the loop be simplified and a 'fast scan' for a format start
used.

If an error is detected (eg an invalid conversion specifier) then
copy the invalid format to the output buffer.

Reduces code size by about 10% on x86-64.

Some versions of gcc bloat this version by generating a jump table.
All goes away in the later patches.

Signed-off-by: David Laight <david.laight.linux@gmail.com>
Acked-by: Willy Tarreau <w@1wt.eu>
Link: https://patch.msgid.link/20260308113742.12649-7-david.laight.linux@gmail.com
Signed-off-by: Thomas Weißschuh <linux@weissschuh.net>
tools/include/nolibc/stdio.h

index bdecc703511c2af26e8e4735b348916d75c23436..876bb2b2eb732beea8f34fbb32512a362a8d30e3 100644 (file)
@@ -310,28 +310,52 @@ typedef int (*__nolibc_printf_cb)(void *state, const char *buf, size_t size);
 static __attribute__((unused, format(printf, 3, 0)))
 int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list args)
 {
-       char escape, lpref, ch;
+       char lpref, ch;
        unsigned long long v;
        int written, width, len;
-       size_t ofs;
        char outbuf[21];
        const char *outstr;
 
-       written = ofs = escape = lpref = 0;
+       written = 0;
        while (1) {
-               ch = fmt[ofs++];
+               outstr = fmt;
+               ch = *fmt++;
+               if (!ch)
+                       break;
+
                width = 0;
+               if (ch != '%') {
+                       while (*fmt && *fmt != '%')
+                               fmt++;
+                       /* Output characters from the format string. */
+                       len = fmt - outstr;
+               } else {
+                       /* we're in a format sequence */
 
-               if (escape) {
-                       /* we're in an escape sequence, ofs == 1 */
-                       escape = 0;
+                       ch = *fmt++;
 
                        /* width */
                        while (ch >= '0' && ch <= '9') {
                                width *= 10;
                                width += ch - '0';
 
-                               ch = fmt[ofs++];
+                               ch = *fmt++;
+                       }
+
+                       /* Length modifiers */
+                       if (ch == 'l') {
+                               lpref = 1;
+                               ch = *fmt++;
+                               if (ch == 'l') {
+                                       lpref = 2;
+                                       ch = *fmt++;
+                               }
+                       } else if (ch == 'j') {
+                               /* intmax_t is long long */
+                               lpref = 2;
+                               ch = *fmt++;
+                       } else {
+                               lpref = 0;
                        }
 
                        if (ch == 'c' || ch == 'd' || ch == 'u' || ch == 'x' || ch == 'p') {
@@ -387,56 +411,34 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
 #else
                                outstr = strerror(errno);
 #endif /* NOLIBC_IGNORE_ERRNO */
-                       }
-                       else if (ch == '%') {
-                               /* queue it verbatim */
-                               continue;
-                       }
-                       else {
-                               /* modifiers or final 0 */
-                               if (ch == 'l') {
-                                       /* long format prefix, maintain the escape */
-                                       lpref++;
-                               } else if (ch == 'j') {
-                                       lpref = 2;
+                       } else {
+                               if (ch != '%') {
+                                       /* Invalid format: back up to output the format characters */
+                                       fmt = outstr + 1;
+                                       /* and output a '%' now. */
                                }
-                               escape = 1;
-                               goto do_escape;
+                               /* %% is documented as a 'conversion specifier'.
+                                * Any flags, precision or length modifier are ignored.
+                                */
+                               width = 0;
+                               outstr = "%";
                        }
                        len = strlen(outstr);
-                       goto flush_str;
                }
 
-               /* not an escape sequence */
-               if (ch == 0 || ch == '%') {
-                       /* flush pending data on escape or end */
-                       escape = 1;
-                       lpref = 0;
-                       outstr = fmt;
-                       len = ofs - 1;
-               flush_str:
-                       width -= len;
-                       while (width > 0) {
-                               /* Output pad in 16 byte blocks with the small block first. */
-                               int pad_len = ((width - 1) & 15) + 1;
-                               width -= pad_len;
-                               written += pad_len;
-                               if (cb(state, "                ", pad_len) != 0)
-                                       return -1;
-                       }
-                       if (cb(state, outstr, len) != 0)
-                               return -1;
+               written += len;
 
-                       written += len;
-               do_escape:
-                       if (ch == 0)
-                               break;
-                       fmt += ofs;
-                       ofs = 0;
-                       continue;
+               width -= len;
+               while (width > 0) {
+                       /* Output pad in 16 byte blocks with the small block first. */
+                       int pad_len = ((width - 1) & 15) + 1;
+                       width -= pad_len;
+                       written += pad_len;
+                       if (cb(state, "                ", pad_len) != 0)
+                               return -1;
                }
-
-               /* literal char, just queue it */
+               if (cb(state, outstr, len) != 0)
+                       return -1;
        }
 
        /* Request a final '\0' be added to the snprintf() output.