[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <aYLHuzt5AGO6h2yW@1wt.eu>
Date: Wed, 4 Feb 2026 05:14:51 +0100
From: Willy Tarreau <w@....eu>
To: david.laight.linux@...il.com
Cc: Thomas Weißschuh <linux@...ssschuh.net>,
linux-kernel@...r.kernel.org, Cheng Li <lechain@...il.com>
Subject: Re: [PATCH next 06/12] tools/nolibc/printf: Add support for left
alignment and %[tzLq]d"
Hi David,
On Tue, Feb 03, 2026 at 10:29:54AM +0000, david.laight.linux@...il.com wrote:
> From: David Laight <david.laight.linux@...il.com>
>
> Use a single 'flags' variable to hold both format flags and length modifiers.
> Use (1u << (c & 31)) for the flag bits to reduce code complexity.
>
> Add support for left justifying fields.
>
> Add support for length modifiers 't' and 'z' (both long) and 'q' and 'L'
> (both long long).
>
> Unconditionall generate the signed values (for %d) to remove a second
> set of checks for the size.
>
> Use 'signed int' for the lengths to make the pad test simpler.
>
> Signed-off-by: David Laight <david.laight.linux@...il.com>
> ---
> tools/include/nolibc/stdio.h | 88 ++++++++++++++++++++----------------
> 1 file changed, 50 insertions(+), 38 deletions(-)
>
> diff --git a/tools/include/nolibc/stdio.h b/tools/include/nolibc/stdio.h
> index 164d2384978e..1ce4d357a802 100644
> --- a/tools/include/nolibc/stdio.h
> +++ b/tools/include/nolibc/stdio.h
> @@ -240,20 +240,20 @@ char *fgets(char *s, int size, FILE *stream)
> }
>
>
> -/* minimal printf(). It supports the following formats:
> - * - %[l*]{d,u,c,x,p}
> - * - %s
> - * - unknown modifiers are ignored.
> +/* simple printf(). It supports the following formats:
> + * - %[-][width][{l,t,z,ll,L,j,q}]{d,u,c,x,p,s,m}
> + * - %%
> + * - invalid formats are copied to the output buffer
> */
> typedef int (*__nolibc_printf_cb)(void *state, const char *buf, size_t size);
>
> +#define __PF_FLAG(c) (1u << ((c) & 0x1f))
This flag will be exposed to user code, you'll have to previx it with
_NOLIBC_.
> static __attribute__((unused, format(printf, 3, 0)))
> int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list args)
> {
> - char lpref, c;
> - unsigned long long v;
> - unsigned int written, width;
> - size_t len;
> + char c;
> + int len, written, width;
> + unsigned int flags;
> char tmpbuf[21];
> const char *outstr;
>
> @@ -265,6 +265,7 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
> break;
>
> width = 0;
> + flags = 0;
> if (c != '%') {
> while (*fmt && *fmt != '%')
> fmt++;
> @@ -274,6 +275,13 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
>
> c = *fmt++;
>
> + /* Flag characters */
> + for (; c >= 0x20 && c <= 0x3f; c = *fmt++) {
> + if ((__PF_FLAG(c) & (__PF_FLAG('-'))) == 0)
> + break;
> + flags |= __PF_FLAG(c);
> + }
Honestly I don't find that it improves readability here and makes one
keep doubts in background as "what if c == 'm' which will match '-'?".
I think that one would better be written as the usual:
for (; c >= 0x20 && c <= 0x3f; c = *fmt++) {
if (c == '-')
break;
flags |= __PF_FLAG(c);
}
Or even simpler since there's already a condition in the for() loop:
for (; c >= 0x20 && c != '-' && c <= 0x3f; c = *fmt++)
flags |= __PF_FLAG(c);
> /* width */
> while (c >= '0' && c <= '9') {
> width *= 10;
> @@ -282,41 +290,34 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
> c = *fmt++;
> }
>
> - /* Length modifiers */
> - if (c == 'l') {
> - lpref = 1;
> - c = *fmt++;
> - if (c == 'l') {
> - lpref = 2;
> + /* Length modifiers are lower case except 'L' which is the same a 'q' */
> + if ((c >= 'a' && c <= 'z') || (c == 'L' && (c = 'q'))) {
Then please say "... which we replace by 'q'" so that we don't first read
that (c = 'q') as a possible typo. Also maybe add a mention to the fact
that "flags" will exclusively represent lowercase modifiers from now on?
> + if (__PF_FLAG(c) & (__PF_FLAG('l') | __PF_FLAG('t') | __PF_FLAG('z') |
> + __PF_FLAG('j') | __PF_FLAG('q'))) {
Even though I understand the value in checking bit positions (I use that
all the time as well), above this is just unreadable. Maybe you need a
different macro, maybe define another macro _NOLIBC_PF_LEN_MOD made of
the addition of all the flags to test against, I don't know, but the
construct, the line break in the middle of the expression and the
parenthesis needed for the macro just requires a lot of effort to
understand what's being tested. Alternately another possibility would
be to have another macro taking 4-5 char args and composing the flags
in one call, passing 0 or -1 for unused ones. This would also make
several parenthesis disappear which would help.
> + if (c == 'l' && fmt[0] == 'l') {
> + fmt++;
> + c = 'q';
> + }
> + /* These all miss "# -0+" */
> + flags |= __PF_FLAG(c);
> c = *fmt++;
> }
> - } else if (c == 'j') {
> - /* intmax_t is long long */
> - lpref = 2;
> - c = *fmt++;
> - } else {
> - lpref = 0;
> }
>
> if (c == 'c' || c == 'd' || c == 'u' || c == 'x' || c == 'p') {
> + unsigned long long v;
> + long long signed_v;
> char *out = tmpbuf;
>
> - if (c == 'p')
> + if ((c == 'p') || (flags & (__PF_FLAG('l') | __PF_FLAG('t') | __PF_FLAG('z')))) {
> v = va_arg(args, unsigned long);
> - else if (lpref) {
> - if (lpref > 1)
> - v = va_arg(args, unsigned long long);
> - else
> - v = va_arg(args, unsigned long);
> - } else
> + signed_v = (long)v;
> + } else if (flags & (__PF_FLAG('j') | __PF_FLAG('q'))) {
> + v = va_arg(args, unsigned long long);
> + signed_v = v;
> + } else {
> v = va_arg(args, unsigned int);
> -
> - if (c == 'd') {
> - /* sign-extend the value */
> - if (lpref == 0)
> - v = (long long)(int)v;
> - else if (lpref == 1)
> - v = (long long)(long)v;
> + signed_v = (int)v;
> }
>
> switch (c) {
> @@ -325,7 +326,7 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
> out[1] = 0;
> break;
> case 'd':
> - i64toa_r(v, out);
> + i64toa_r(signed_v, out);
> break;
> case 'u':
> u64toa_r(v, out);
> @@ -366,14 +367,24 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
>
> written += len;
>
> - while (width > len) {
> - unsigned int pad_len = ((width - len - 1) & 15) + 1;
> + /* An OPTIMIZER_HIDE_VAR() seems to stop gcc back-merging this
> + * code into one of the conditionals above.
Be careful, space indentation above.
> + */
> + __asm__ volatile("" : "=r"(len) : "0"(len));
and here.
> +
> + /* Output 'left pad', 'value' then 'right pad'. */
> + flags &= __PF_FLAG('-');
> + width -= len;
> + if (flags && cb(state, outstr, len) != 0)
> + return -1;
> + while (width > 0) {
> + 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)
> + if (!flags && cb(state, outstr, len) != 0)
> return -1;
> }
>
> @@ -382,6 +393,7 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
>
> return written;
> }
> +#undef _PF_FLAG
This one can be dropped once named as _NOLIBC_xxx
Willy
Powered by blists - more mailing lists