From: Cheng Li <im.lechain@gmail.com>
Currently, __nolibc_printf() in nolibc parses the width field but always
pads with spaces on the left. It ignores the '-' flag (left alignment)
and treats leading zeros in the width as part of the number parsing
(resulting in space padding instead of zero padding).
This patch implements support for:
1. The '-' flag: forces left alignment by padding spaces on the right.
2. The '0' flag: forces zero padding on the left instead of spaces.
The implementation reuses the padding character logic to handle both
cases.
Logic behavior:
- "%5d" -> " 12" (unchanged)
- "%-5d" -> "12 " (new: left align)
- "%05d" -> "00012" (new: zero pad)
- "%-05d"-> "12 " (new: left align overrides zero pad)
The code is optimized to keep the binary size impact minimal.
Measuring the nolibc-test binary on x86_64:
text data bss dec hex filename
43552 248 112 43912 ab88 nolibc-test (before)
43677 248 112 44037 ac05 nolibc-test (after)
The net increase is 125 bytes.
Suggested-by: Willy Tarreau <w@1wt.eu>
Signed-off-by: Cheng Li <im.lechain@gmail.com>
---
v2 changes:
- Adopted optimization suggestions from Willy Tarreau:
- Incremented 'written' counter at the start of loops.
- Reordered loop checks to optimize compiler register usage.
- Updated commit message to explicitly mention zero-padding ('0') support.
---
tools/include/nolibc/stdio.h | 20 ++++++++++++++++----
1 file changed, 16 insertions(+), 4 deletions(-)
diff --git a/tools/include/nolibc/stdio.h b/tools/include/nolibc/stdio.h
index 1f16dab2ac88..df8a96e39986 100644
--- a/tools/include/nolibc/stdio.h
+++ b/tools/include/nolibc/stdio.h
@@ -250,7 +250,7 @@ typedef int (*__nolibc_printf_cb)(intptr_t state, const char *buf, size_t size);
static __attribute__((unused, format(printf, 4, 0)))
int __nolibc_printf(__nolibc_printf_cb cb, intptr_t state, size_t n, const char *fmt, va_list args)
{
- char escape, lpref, c;
+ char escape, lpref, padc, c;
unsigned long long v;
unsigned int written, width;
size_t len, ofs, w;
@@ -261,11 +261,17 @@ int __nolibc_printf(__nolibc_printf_cb cb, intptr_t state, size_t n, const char
while (1) {
c = fmt[ofs++];
width = 0;
+ padc = ' ';
if (escape) {
/* we're in an escape sequence, ofs == 1 */
escape = 0;
+ if (c == '-' || c == '0') {
+ padc = c;
+ c = fmt[ofs++];
+ }
+
/* width */
while (c >= '0' && c <= '9') {
width *= 10;
@@ -358,13 +364,19 @@ int __nolibc_printf(__nolibc_printf_cb cb, intptr_t state, size_t n, const char
if (n) {
w = len < n ? len : n;
n -= w;
- while (width-- > w) {
- if (cb(state, " ", 1) != 0)
- return -1;
+ while (width > w && padc != '-') {
written += 1;
+ if (cb(state, &padc, 1) != 0)
+ return -1;
+ width--;
}
if (cb(state, outstr, w) != 0)
return -1;
+ while (width-- > w) {
+ written += 1;
+ if (cb(state, " ", 1) != 0)
+ return -1;
+ }
}
written += len;
--
2.52.0
On Wed, 28 Jan 2026 17:42:23 +0800
"licheng.li" <im.lechain@gmail.com> wrote:
> From: Cheng Li <im.lechain@gmail.com>
>
> Currently, __nolibc_printf() in nolibc parses the width field but always
> pads with spaces on the left. It ignores the '-' flag (left alignment)
> and treats leading zeros in the width as part of the number parsing
> (resulting in space padding instead of zero padding).
>
> This patch implements support for:
> 1. The '-' flag: forces left alignment by padding spaces on the right.
> 2. The '0' flag: forces zero padding on the left instead of spaces.
Does that work properly?
I think it is padding zeros the wrong side of the sign (or "0x").
Try testing with a negative number or a pointer.
You might need to limit the width for numeric fields to (say) 32,
make the on-stack buffer 64 bytes and convert into the middle.
Then add pad zeros to the front before adding any "-" or "0x".
It then gets very close to supporting precisions as well as widths.
It might be worth changing the 'pad' loop to:
while (width > w) {
unsigned int pad_len = ((width - w - 1) & 0xf) + 1;
width -= pad_len;
written += pad_len;
if (cb(state, " ", pad_len) != 0)
return -1;
}
while the code is marginally larger writing the pad in blocks of 16
will be much faster.
The linker should merge all the strings of spaces.
I looked at the (existing) version last night.
If the 'l' flag code is moved to after the width is calculated it all
becomes a lot simpler.
Set 'outstr = fmt' at the top of the loop and the code can use *fmt++
and do a 'fast scan' of the format string for a '%' or '\0' then
output the chars from the format string at the bottom.
I didn't size the change, but getting rid of ofs and escape must help.
Might post it later.
David
>
> The implementation reuses the padding character logic to handle both
> cases.
>
> Logic behavior:
> - "%5d" -> " 12" (unchanged)
> - "%-5d" -> "12 " (new: left align)
> - "%05d" -> "00012" (new: zero pad)
> - "%-05d"-> "12 " (new: left align overrides zero pad)
>
> The code is optimized to keep the binary size impact minimal.
> Measuring the nolibc-test binary on x86_64:
>
> text data bss dec hex filename
> 43552 248 112 43912 ab88 nolibc-test (before)
> 43677 248 112 44037 ac05 nolibc-test (after)
>
> The net increase is 125 bytes.
>
> Suggested-by: Willy Tarreau <w@1wt.eu>
> Signed-off-by: Cheng Li <im.lechain@gmail.com>
> ---
> v2 changes:
> - Adopted optimization suggestions from Willy Tarreau:
> - Incremented 'written' counter at the start of loops.
> - Reordered loop checks to optimize compiler register usage.
> - Updated commit message to explicitly mention zero-padding ('0') support.
> ---
> tools/include/nolibc/stdio.h | 20 ++++++++++++++++----
> 1 file changed, 16 insertions(+), 4 deletions(-)
>
> diff --git a/tools/include/nolibc/stdio.h b/tools/include/nolibc/stdio.h
> index 1f16dab2ac88..df8a96e39986 100644
> --- a/tools/include/nolibc/stdio.h
> +++ b/tools/include/nolibc/stdio.h
> @@ -250,7 +250,7 @@ typedef int (*__nolibc_printf_cb)(intptr_t state, const char *buf, size_t size);
> static __attribute__((unused, format(printf, 4, 0)))
> int __nolibc_printf(__nolibc_printf_cb cb, intptr_t state, size_t n, const char *fmt, va_list args)
> {
> - char escape, lpref, c;
> + char escape, lpref, padc, c;
> unsigned long long v;
> unsigned int written, width;
> size_t len, ofs, w;
> @@ -261,11 +261,17 @@ int __nolibc_printf(__nolibc_printf_cb cb, intptr_t state, size_t n, const char
> while (1) {
> c = fmt[ofs++];
> width = 0;
> + padc = ' ';
>
> if (escape) {
> /* we're in an escape sequence, ofs == 1 */
> escape = 0;
>
> + if (c == '-' || c == '0') {
> + padc = c;
> + c = fmt[ofs++];
> + }
> +
> /* width */
> while (c >= '0' && c <= '9') {
> width *= 10;
> @@ -358,13 +364,19 @@ int __nolibc_printf(__nolibc_printf_cb cb, intptr_t state, size_t n, const char
> if (n) {
> w = len < n ? len : n;
> n -= w;
> - while (width-- > w) {
> - if (cb(state, " ", 1) != 0)
> - return -1;
> + while (width > w && padc != '-') {
> written += 1;
> + if (cb(state, &padc, 1) != 0)
> + return -1;
> + width--;
> }
> if (cb(state, outstr, w) != 0)
> return -1;
> + while (width-- > w) {
> + written += 1;
> + if (cb(state, " ", 1) != 0)
> + return -1;
> + }
> }
>
> written += len;
David Laight <david.laight.linux@gmail.com> 于2026年1月29日周四 18:30写道:
>
> On Wed, 28 Jan 2026 17:42:23 +0800
> "licheng.li" <im.lechain@gmail.com> wrote:
>
> > From: Cheng Li <im.lechain@gmail.com>
> >
> > Currently, __nolibc_printf() in nolibc parses the width field but always
> > pads with spaces on the left. It ignores the '-' flag (left alignment)
> > and treats leading zeros in the width as part of the number parsing
> > (resulting in space padding instead of zero padding).
> >
> > This patch implements support for:
> > 1. The '-' flag: forces left alignment by padding spaces on the right.
> > 2. The '0' flag: forces zero padding on the left instead of spaces.
>
> Does that work properly?
> I think it is padding zeros the wrong side of the sign (or "0x").
> Try testing with a negative number or a pointer.
>
> You might need to limit the width for numeric fields to (say) 32,
> make the on-stack buffer 64 bytes and convert into the middle.
> Then add pad zeros to the front before adding any "-" or "0x".
You are absolutely right. I just tested printf("%05d", -5) and it
resulted in 000-5,
which is incorrect. Standard behavior should be -0005.
Since nolibc constructs the integer string (including the sign) before
this padding logic,
implementing correct zero-padding for negative numbers would require
parsing the buffer
or refactoring the conversion logic. This would add significant
complexity and size,
which goes against the goal of this small patch.
Proposed solution:
I will drop the implicit support for the '0' flag from this patch and
revert to my original goal: strictly supporting left alignment ('-').
Left alignment pads spaces on the right (-5 ), so it does not suffer
from the sign issue.
> It then gets very close to supporting precisions as well as widths.
> It might be worth changing the 'pad' loop to:
> while (width > w) {
> unsigned int pad_len = ((width - w - 1) & 0xf) + 1;
> width -= pad_len;
> written += pad_len;
> if (cb(state, " ", pad_len) != 0)
> return -1;
> }
> while the code is marginally larger writing the pad in blocks of 16
> will be much faster.
> The linker should merge all the strings of spaces.
>
> I looked at the (existing) version last night.
> If the 'l' flag code is moved to after the width is calculated it all
> becomes a lot simpler.
> Set 'outstr = fmt' at the top of the loop and the code can use *fmt++
> and do a 'fast scan' of the format string for a '%' or '\0' then
> output the chars from the format string at the bottom.
> I didn't size the change, but getting rid of ofs and escape must help.
These are great suggestions for a larger refactoring of printf to
improve performance and standard compliance.
However, to keep this current change minimal and safe, I prefer to
focus on fixing the alignment feature first.
@Willy, I will send a v3 that removes the '0' flag handling logic to
avoid the bug pointed out above.
I will rethink how to implement leading zero pad support.
Best regards,
Cheng
> Might post it later.
>
> David
>
>
> >
> > The implementation reuses the padding character logic to handle both
> > cases.
> >
> > Logic behavior:
> > - "%5d" -> " 12" (unchanged)
> > - "%-5d" -> "12 " (new: left align)
> > - "%05d" -> "00012" (new: zero pad)
> > - "%-05d"-> "12 " (new: left align overrides zero pad)
> >
> > The code is optimized to keep the binary size impact minimal.
> > Measuring the nolibc-test binary on x86_64:
> >
> > text data bss dec hex filename
> > 43552 248 112 43912 ab88 nolibc-test (before)
> > 43677 248 112 44037 ac05 nolibc-test (after)
> >
> > The net increase is 125 bytes.
> >
> > Suggested-by: Willy Tarreau <w@1wt.eu>
> > Signed-off-by: Cheng Li <im.lechain@gmail.com>
> > ---
> > v2 changes:
> > - Adopted optimization suggestions from Willy Tarreau:
> > - Incremented 'written' counter at the start of loops.
> > - Reordered loop checks to optimize compiler register usage.
> > - Updated commit message to explicitly mention zero-padding ('0') support.
> > ---
> > tools/include/nolibc/stdio.h | 20 ++++++++++++++++----
> > 1 file changed, 16 insertions(+), 4 deletions(-)
> >
> > diff --git a/tools/include/nolibc/stdio.h b/tools/include/nolibc/stdio.h
> > index 1f16dab2ac88..df8a96e39986 100644
> > --- a/tools/include/nolibc/stdio.h
> > +++ b/tools/include/nolibc/stdio.h
> > @@ -250,7 +250,7 @@ typedef int (*__nolibc_printf_cb)(intptr_t state, const char *buf, size_t size);
> > static __attribute__((unused, format(printf, 4, 0)))
> > int __nolibc_printf(__nolibc_printf_cb cb, intptr_t state, size_t n, const char *fmt, va_list args)
> > {
> > - char escape, lpref, c;
> > + char escape, lpref, padc, c;
> > unsigned long long v;
> > unsigned int written, width;
> > size_t len, ofs, w;
> > @@ -261,11 +261,17 @@ int __nolibc_printf(__nolibc_printf_cb cb, intptr_t state, size_t n, const char
> > while (1) {
> > c = fmt[ofs++];
> > width = 0;
> > + padc = ' ';
> >
> > if (escape) {
> > /* we're in an escape sequence, ofs == 1 */
> > escape = 0;
> >
> > + if (c == '-' || c == '0') {
> > + padc = c;
> > + c = fmt[ofs++];
> > + }
> > +
> > /* width */
> > while (c >= '0' && c <= '9') {
> > width *= 10;
> > @@ -358,13 +364,19 @@ int __nolibc_printf(__nolibc_printf_cb cb, intptr_t state, size_t n, const char
> > if (n) {
> > w = len < n ? len : n;
> > n -= w;
> > - while (width-- > w) {
> > - if (cb(state, " ", 1) != 0)
> > - return -1;
> > + while (width > w && padc != '-') {
> > written += 1;
> > + if (cb(state, &padc, 1) != 0)
> > + return -1;
> > + width--;
> > }
> > if (cb(state, outstr, w) != 0)
> > return -1;
> > + while (width-- > w) {
> > + written += 1;
> > + if (cb(state, " ", 1) != 0)
> > + return -1;
> > + }
> > }
> >
> > written += len;
>
On Fri, 30 Jan 2026 09:05:39 +0800 Cheng Li <im.lechain@gmail.com> wrote: > David Laight <david.laight.linux@gmail.com> 于2026年1月29日周四 18:30写道: ... > These are great suggestions for a larger refactoring of printf to > improve performance and standard compliance. > However, to keep this current change minimal and safe, I prefer to > focus on fixing the alignment feature first. > > @Willy, I will send a v3 that removes the '0' flag handling logic to > avoid the bug pointed out above. > > I will rethink how to implement leading zero pad support. You made me look at the code. A simple refactor saved 98 bytes (about 10%). I will look at adding support for field precision - which is what is needed for leading zeros. Will use up some of the saved space, but probably not all of it. I'll base any patch on top of your change. David
David Laight <david.laight.linux@gmail.com> 于2026年1月30日周五 16:01写道: > > On Fri, 30 Jan 2026 09:05:39 +0800 > Cheng Li <im.lechain@gmail.com> wrote: > > > David Laight <david.laight.linux@gmail.com> 于2026年1月29日周四 18:30写道: > ... > > These are great suggestions for a larger refactoring of printf to > > improve performance and standard compliance. > > However, to keep this current change minimal and safe, I prefer to > > focus on fixing the alignment feature first. > > > > @Willy, I will send a v3 that removes the '0' flag handling logic to > > avoid the bug pointed out above. > > > > I will rethink how to implement leading zero pad support. > > You made me look at the code. > A simple refactor saved 98 bytes (about 10%). > > I will look at adding support for field precision - which is what > is needed for leading zeros. > Will use up some of the saved space, but probably not all of it. > > I'll base any patch on top of your change. Hi David, Just a heads-up: I actually sent out a patch set implementing zero padding (with explicit sign handling) right before reading your message about adding precision support. However, I agree that implementing full field precision is the cleaner and more standard way to handle leading zeros. My patch handles it by parsing the output buffer, which is less elegant. Also, looking at my just-sent zero-padding patch again, I suspect I might have missed adjusting the remaining length 'w' after skipping the sign, which would be a bug. So, let's stick to the plan: I will discard my zero-padding attempt and focus on sending a **v4 of the Left Alignment patch**. I will implement your suggested optimization (swapping the logic to share the padding loop) to make it a solid base for your future precision work. Thanks, Cheng > David
Hi Cheng, On Wed, Jan 28, 2026 at 05:42:23PM +0800, licheng.li wrote: > From: Cheng Li <im.lechain@gmail.com> > > Currently, __nolibc_printf() in nolibc parses the width field but always > pads with spaces on the left. It ignores the '-' flag (left alignment) > and treats leading zeros in the width as part of the number parsing > (resulting in space padding instead of zero padding). > > This patch implements support for: > 1. The '-' flag: forces left alignment by padding spaces on the right. > 2. The '0' flag: forces zero padding on the left instead of spaces. > > The implementation reuses the padding character logic to handle both > cases. > > Logic behavior: > - "%5d" -> " 12" (unchanged) > - "%-5d" -> "12 " (new: left align) > - "%05d" -> "00012" (new: zero pad) > - "%-05d"-> "12 " (new: left align overrides zero pad) > > The code is optimized to keep the binary size impact minimal. > Measuring the nolibc-test binary on x86_64: > > text data bss dec hex filename > 43552 248 112 43912 ab88 nolibc-test (before) > 43677 248 112 44037 ac05 nolibc-test (after) > > The net increase is 125 bytes. Is it normal this didn't change since previous patch, or did you just forget to update that part of the commit message? In my case I'm seeing a difference: text data bss dec hex filename 35964 112 120 36196 8d64 nolibc-test-before 36070 112 120 36302 8dce nolibc-test-after1 <- first patchset: +106 36044 112 120 36276 8db4 nolibc-test-after2 <- this patchset: +80 If it's just an omission, I can edit the commit message if you paste the new before/after in response, and save you from a respin just for this! If it didn't change anything, that's possible as well, but surprising, which is why I preferred to ask. Thanks, Willy
Hi Willy, Willy Tarreau <w@1wt.eu> 于2026年1月29日周四 14:41写道: > > Hi Cheng, > > On Wed, Jan 28, 2026 at 05:42:23PM +0800, licheng.li wrote: > > From: Cheng Li <im.lechain@gmail.com> > > > > Currently, __nolibc_printf() in nolibc parses the width field but always > > pads with spaces on the left. It ignores the '-' flag (left alignment) > > and treats leading zeros in the width as part of the number parsing > > (resulting in space padding instead of zero padding). > > > > This patch implements support for: > > 1. The '-' flag: forces left alignment by padding spaces on the right. > > 2. The '0' flag: forces zero padding on the left instead of spaces. > > > > The implementation reuses the padding character logic to handle both > > cases. > > > > Logic behavior: > > - "%5d" -> " 12" (unchanged) > > - "%-5d" -> "12 " (new: left align) > > - "%05d" -> "00012" (new: zero pad) > > - "%-05d"-> "12 " (new: left align overrides zero pad) > > > > The code is optimized to keep the binary size impact minimal. > > Measuring the nolibc-test binary on x86_64: > > > > text data bss dec hex filename > > 43552 248 112 43912 ab88 nolibc-test (before) > > 43677 248 112 44037 ac05 nolibc-test (after) > > > > The net increase is 125 bytes. > > Is it normal this didn't change since previous patch, or did you just > forget to update that part of the commit message? In my case I'm seeing > a difference: > > text data bss dec hex filename > 35964 112 120 36196 8d64 nolibc-test-before > 36070 112 120 36302 8dce nolibc-test-after1 <- first patchset: +106 > 36044 112 120 36276 8db4 nolibc-test-after2 <- this patchset: +80 > > If it's just an omission, I can edit the commit message if you paste > the new before/after in response, and save you from a respin just for > this! If it didn't change anything, that's possible as well, but > surprising, which is why I preferred to ask. Thanks for catching this! You are right, I missed updating the binary size data in the v2 commit message. I performed the previous measurement using the simplest command: `make -C tools/testing/selftests/nolibc' The discrepancy in absolute numbers is likely caused by the different compiler versions and optimization levels between our environments. Since your environment shows a more accurate delta for the current upstream state, please feel free to update the commit message directly to save a respin. Thank you for your help and for the detailed review! Best regards, Cheng Li > Thanks, > Willy
On Thu, Jan 29, 2026 at 02:57:26PM +0800, licheng wrote: > Hi Willy, > > Willy Tarreau <w@1wt.eu> ?2026?1?29??? 14:41??: > > > > Hi Cheng, > > > > On Wed, Jan 28, 2026 at 05:42:23PM +0800, licheng.li wrote: > > > From: Cheng Li <im.lechain@gmail.com> > > > > > > Currently, __nolibc_printf() in nolibc parses the width field but always > > > pads with spaces on the left. It ignores the '-' flag (left alignment) > > > and treats leading zeros in the width as part of the number parsing > > > (resulting in space padding instead of zero padding). > > > > > > This patch implements support for: > > > 1. The '-' flag: forces left alignment by padding spaces on the right. > > > 2. The '0' flag: forces zero padding on the left instead of spaces. > > > > > > The implementation reuses the padding character logic to handle both > > > cases. > > > > > > Logic behavior: > > > - "%5d" -> " 12" (unchanged) > > > - "%-5d" -> "12 " (new: left align) > > > - "%05d" -> "00012" (new: zero pad) > > > - "%-05d"-> "12 " (new: left align overrides zero pad) > > > > > > The code is optimized to keep the binary size impact minimal. > > > Measuring the nolibc-test binary on x86_64: > > > > > > text data bss dec hex filename > > > 43552 248 112 43912 ab88 nolibc-test (before) > > > 43677 248 112 44037 ac05 nolibc-test (after) > > > > > > The net increase is 125 bytes. > > > > Is it normal this didn't change since previous patch, or did you just > > forget to update that part of the commit message? In my case I'm seeing > > a difference: > > > > text data bss dec hex filename > > 35964 112 120 36196 8d64 nolibc-test-before > > 36070 112 120 36302 8dce nolibc-test-after1 <- first patchset: +106 > > 36044 112 120 36276 8db4 nolibc-test-after2 <- this patchset: +80 > > > > If it's just an omission, I can edit the commit message if you paste > > the new before/after in response, and save you from a respin just for > > this! If it didn't change anything, that's possible as well, but > > surprising, which is why I preferred to ask. > > Thanks for catching this! You are right, I missed updating the binary > size data in the v2 commit message. I performed the previous measurement > using the simplest command: `make -C tools/testing/selftests/nolibc' OK! > The discrepancy in absolute numbers is likely caused by the different compiler > versions and optimization levels between our environments. Since your > environment > shows a more accurate delta for the current upstream state, please feel free to > update the commit message directly to save a respin. OK will do. I'll handle this this week-end unless Thomas has other comments or beats me at it. In case I forget, do not hesitate to ping me early next week ;-) thanks, Willy
© 2016 - 2026 Red Hat, Inc.