* [PATCH v5 next 01/17] tools/nolibc: Add _NOLIBC_OPTIMIZER_HIDE_VAR() to compiler.h
2026-03-08 11:37 [PATCH v5 next 00/17] Enhance printf() david.laight.linux
@ 2026-03-08 11:37 ` david.laight.linux
2026-03-08 11:37 ` [PATCH v5 next 02/17] selftests/nolibc: Rename w to written in expect_vfprintf() david.laight.linux
` (17 subsequent siblings)
18 siblings, 0 replies; 26+ messages in thread
From: david.laight.linux @ 2026-03-08 11:37 UTC (permalink / raw)
To: Willy Tarreau, Thomas Weißschuh, linux-kernel, Cheng Li; +Cc: David Laight
From: David Laight <david.laight.linux@gmail.com>
Needed to stop compiler 'optimisations' bloating code.
Equivalent to the definition in include/linux/compiler.h
Acked-by: Willy Tarreau <w@1wt.eu>
Signed-off-by: David Laight <david.laight.linux@gmail.com>
---
Unchanged for v5.
Changes for v4:
- Remove #if guard
tools/include/nolibc/compiler.h | 3 +++
1 file changed, 3 insertions(+)
diff --git a/tools/include/nolibc/compiler.h b/tools/include/nolibc/compiler.h
index a8c7619dcdde..f03f84cfadce 100644
--- a/tools/include/nolibc/compiler.h
+++ b/tools/include/nolibc/compiler.h
@@ -71,4 +71,7 @@
# define __nolibc_static_assert(_t)
#endif
+/* Make the optimizer believe the variable can be manipulated arbitrarily. */
+#define _NOLIBC_OPTIMIZER_HIDE_VAR(var) __asm__ ("" : "+r" (var))
+
#endif /* _NOLIBC_COMPILER_H */
--
2.39.5
^ permalink raw reply related [flat|nested] 26+ messages in thread* [PATCH v5 next 02/17] selftests/nolibc: Rename w to written in expect_vfprintf()
2026-03-08 11:37 [PATCH v5 next 00/17] Enhance printf() david.laight.linux
2026-03-08 11:37 ` [PATCH v5 next 01/17] tools/nolibc: Add _NOLIBC_OPTIMIZER_HIDE_VAR() to compiler.h david.laight.linux
@ 2026-03-08 11:37 ` david.laight.linux
2026-03-08 11:37 ` [PATCH v5 next 03/17] tools/nolibc: Implement strerror() in terms of strerror_r() david.laight.linux
` (16 subsequent siblings)
18 siblings, 0 replies; 26+ messages in thread
From: david.laight.linux @ 2026-03-08 11:37 UTC (permalink / raw)
To: Willy Tarreau, Thomas Weißschuh, linux-kernel, Cheng Li; +Cc: David Laight
From: David Laight <david.laight.linux@gmail.com>
Single character variable names don't make code easy to read.
Rename 'w' (used for the return value from snprintf()) 'written'.
Acked-by: Willy Tarreau <w@1wt.eu>
Signed-off-by: David Laight <david.laight.linux@gmail.com>
---
Unchanged for v5.
V4: Split out from patch 4.
tools/testing/selftests/nolibc/nolibc-test.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/tools/testing/selftests/nolibc/nolibc-test.c b/tools/testing/selftests/nolibc/nolibc-test.c
index 51390b709af1..9ebebe4ff253 100644
--- a/tools/testing/selftests/nolibc/nolibc-test.c
+++ b/tools/testing/selftests/nolibc/nolibc-test.c
@@ -1669,7 +1669,7 @@ static int expect_vfprintf(int llen, const char *expected, const char *fmt, ...)
char buf[VFPRINTF_LEN + 80];
unsigned int cmp_len;
va_list args;
- ssize_t w, expected_len;
+ ssize_t written, expected_len;
/* Fill and terminate buf[] to check for overlong/absent writes */
memset(buf, 0xa5, sizeof(buf) - 1);
@@ -1677,7 +1677,7 @@ static int expect_vfprintf(int llen, const char *expected, const char *fmt, ...)
va_start(args, fmt);
/* Limit buffer length to test truncation */
- w = vsnprintf(buf, VFPRINTF_LEN + 1, fmt, args);
+ written = vsnprintf(buf, VFPRINTF_LEN + 1, fmt, args);
va_end(args);
llen += printf(" \"%s\"", buf);
@@ -1700,8 +1700,8 @@ static int expect_vfprintf(int llen, const char *expected, const char *fmt, ...)
return 1;
}
- if (w != expected_len) {
- llen += printf(" written(%d) != %d", (int)w, (int)expected_len);
+ if (written != expected_len) {
+ llen += printf(" written(%d) != %d", (int)written, (int)expected_len);
result(llen, FAIL);
return 1;
}
--
2.39.5
^ permalink raw reply related [flat|nested] 26+ messages in thread* [PATCH v5 next 03/17] tools/nolibc: Implement strerror() in terms of strerror_r()
2026-03-08 11:37 [PATCH v5 next 00/17] Enhance printf() david.laight.linux
2026-03-08 11:37 ` [PATCH v5 next 01/17] tools/nolibc: Add _NOLIBC_OPTIMIZER_HIDE_VAR() to compiler.h david.laight.linux
2026-03-08 11:37 ` [PATCH v5 next 02/17] selftests/nolibc: Rename w to written in expect_vfprintf() david.laight.linux
@ 2026-03-08 11:37 ` david.laight.linux
2026-03-08 11:37 ` [PATCH v5 next 04/17] tools/nolibc: Rename the 'errnum' parameter to strerror() david.laight.linux
` (15 subsequent siblings)
18 siblings, 0 replies; 26+ messages in thread
From: david.laight.linux @ 2026-03-08 11:37 UTC (permalink / raw)
To: Willy Tarreau, Thomas Weißschuh, linux-kernel, Cheng Li; +Cc: David Laight
From: David Laight <david.laight.linux@gmail.com>
strerror() can be the only part of a program that has a .data section.
This requres 4k in the program file.
Add a simple implementation of strerror_r() and use that in strerror()
so that the "errno=" string is copied at run-time.
Use __builtin_memcpy() because that optimises away the input string
and just writes the required constants to the target buffer.
Code size change largely depends on whether the inlining decision for
strerror() changes.
Change the tests to use the normal EXPECT_VFPRINTF() when testing %m.
Skip the tests when !is_nolibc.
Acked-by: Willy Tarreau <w@1wt.eu>
Signed-off-by: David Laight <david.laight.linux@gmail.com>
---
v5:
- strerror_r() needs to return 0 on success.
- Return ERANGE if the buffer is too short to contain the longest
possible output.
v4:
- Leave the NOLIBC_IGNORE_ERRNO check in __nolibc_printf().
- Don't rename the errno parameter to strerror() in this patch.
tools/include/nolibc/stdio.h | 22 +++++++++++++++++---
tools/testing/selftests/nolibc/nolibc-test.c | 20 ++----------------
2 files changed, 21 insertions(+), 21 deletions(-)
diff --git a/tools/include/nolibc/stdio.h b/tools/include/nolibc/stdio.h
index a4df72d9a2d3..af2d5be47aa3 100644
--- a/tools/include/nolibc/stdio.h
+++ b/tools/include/nolibc/stdio.h
@@ -722,14 +722,30 @@ int setvbuf(FILE *stream __attribute__((unused)),
return 0;
}
+static __attribute__((unused,))
+int strerror_r(int errnum, char *buf, size_t buflen)
+{
+ if (buflen < 18)
+ return ERANGE;
+
+ __builtin_memcpy(buf, "errno=", 6);
+ i64toa_r(errnum, buf + 6);
+ return 0;
+}
+
static __attribute__((unused))
const char *strerror(int errno)
{
- static char buf[18] = "errno=";
+ static char buf[18];
+ char *b = buf;
+
+ /* Force gcc to use 'register offset' to access buf[]. */
+ _NOLIBC_OPTIMIZER_HIDE_VAR(b);
- i64toa_r(errno, &buf[6]);
+ /* Use strerror_r() to avoid having the only .data in small programs. */
+ strerror_r(errno, b, sizeof(buf));
- return buf;
+ return b;
}
#endif /* _NOLIBC_STDIO_H */
diff --git a/tools/testing/selftests/nolibc/nolibc-test.c b/tools/testing/selftests/nolibc/nolibc-test.c
index 9ebebe4ff253..638f18fc5123 100644
--- a/tools/testing/selftests/nolibc/nolibc-test.c
+++ b/tools/testing/selftests/nolibc/nolibc-test.c
@@ -1786,23 +1786,6 @@ static int test_scanf(void)
return 0;
}
-int test_strerror(void)
-{
- char buf[100];
- ssize_t ret;
-
- memset(buf, 'A', sizeof(buf));
-
- errno = EINVAL;
- ret = snprintf(buf, sizeof(buf), "%m");
- if (is_nolibc) {
- if (ret < 6 || memcmp(buf, "errno=", 6))
- return 1;
- }
-
- return 0;
-}
-
static int test_printf_error(void)
{
int fd, ret, saved_errno;
@@ -1852,8 +1835,9 @@ static int run_printf(int min, int max)
CASE_TEST(string_width); EXPECT_VFPRINTF(1, " 1", "%10s", "1"); break;
CASE_TEST(number_width); EXPECT_VFPRINTF(1, " 1", "%10d", 1); break;
CASE_TEST(width_trunc); EXPECT_VFPRINTF(1, " 1", "%25d", 1); break;
+ CASE_TEST(errno); errno = 22; EXPECT_VFPRINTF(is_nolibc, "errno=22", "%m"); break;
+ CASE_TEST(errno-neg); errno = -22; EXPECT_VFPRINTF(is_nolibc, " errno=-22", "%12m"); break;
CASE_TEST(scanf); EXPECT_ZR(1, test_scanf()); break;
- CASE_TEST(strerror); EXPECT_ZR(1, test_strerror()); break;
CASE_TEST(printf_error); EXPECT_ZR(1, test_printf_error()); break;
case __LINE__:
return ret; /* must be last */
--
2.39.5
^ permalink raw reply related [flat|nested] 26+ messages in thread* [PATCH v5 next 04/17] tools/nolibc: Rename the 'errnum' parameter to strerror()
2026-03-08 11:37 [PATCH v5 next 00/17] Enhance printf() david.laight.linux
` (2 preceding siblings ...)
2026-03-08 11:37 ` [PATCH v5 next 03/17] tools/nolibc: Implement strerror() in terms of strerror_r() david.laight.linux
@ 2026-03-08 11:37 ` david.laight.linux
2026-03-08 11:37 ` [PATCH v5 next 05/17] tools/nolibc/printf: Output pad characters in 16 byte chunks david.laight.linux
` (14 subsequent siblings)
18 siblings, 0 replies; 26+ messages in thread
From: david.laight.linux @ 2026-03-08 11:37 UTC (permalink / raw)
To: Willy Tarreau, Thomas Weißschuh, linux-kernel, Cheng Li; +Cc: David Laight
From: David Laight <david.laight.linux@gmail.com>
Change the parameter variable name from 'errno' to 'errnum'.
Matches any documentation and avoids any issues that might happen
if errno is actually a #define (which is not uncommon).
Acked-by: Willy Tarreau <w@1wt.eu>
Signed-off-by: David Laight <david.laight.linux@gmail.com>
---
Unchanged for v5.
v4:
- split from the previous patch.
tools/include/nolibc/stdio.h | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/tools/include/nolibc/stdio.h b/tools/include/nolibc/stdio.h
index af2d5be47aa3..17ca206f77fe 100644
--- a/tools/include/nolibc/stdio.h
+++ b/tools/include/nolibc/stdio.h
@@ -734,7 +734,7 @@ int strerror_r(int errnum, char *buf, size_t buflen)
}
static __attribute__((unused))
-const char *strerror(int errno)
+const char *strerror(int errnum)
{
static char buf[18];
char *b = buf;
@@ -743,7 +743,7 @@ const char *strerror(int errno)
_NOLIBC_OPTIMIZER_HIDE_VAR(b);
/* Use strerror_r() to avoid having the only .data in small programs. */
- strerror_r(errno, b, sizeof(buf));
+ strerror_r(errnum, b, sizeof(buf));
return b;
}
--
2.39.5
^ permalink raw reply related [flat|nested] 26+ messages in thread* [PATCH v5 next 05/17] tools/nolibc/printf: Output pad characters in 16 byte chunks
2026-03-08 11:37 [PATCH v5 next 00/17] Enhance printf() david.laight.linux
` (3 preceding siblings ...)
2026-03-08 11:37 ` [PATCH v5 next 04/17] tools/nolibc: Rename the 'errnum' parameter to strerror() david.laight.linux
@ 2026-03-08 11:37 ` david.laight.linux
2026-03-08 11:37 ` [PATCH v5 next 06/17] tools/nolibc/printf: Simplify __nolibc_printf() david.laight.linux
` (13 subsequent siblings)
18 siblings, 0 replies; 26+ messages in thread
From: david.laight.linux @ 2026-03-08 11:37 UTC (permalink / raw)
To: Willy Tarreau, Thomas Weißschuh, linux-kernel, Cheng Li; +Cc: David Laight
From: David Laight <david.laight.linux@gmail.com>
Simple to do and saves calls to the callback function.
Change variables written, width and len to 'signed int' to get
better code.
Acked-by: Willy Tarreau <w@1wt.eu>
Signed-off-by: David Laight <david.laight.linux@gmail.com>
---
Unchanged for v4 and v5.
For v3:
- Change to signed variables here rather than a later patch.
tools/include/nolibc/stdio.h | 14 +++++++++-----
1 file changed, 9 insertions(+), 5 deletions(-)
diff --git a/tools/include/nolibc/stdio.h b/tools/include/nolibc/stdio.h
index 17ca206f77fe..c6d5d075f012 100644
--- a/tools/include/nolibc/stdio.h
+++ b/tools/include/nolibc/stdio.h
@@ -312,8 +312,8 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
{
char escape, lpref, ch;
unsigned long long v;
- unsigned int written, width;
- size_t len, ofs;
+ int written, width, len;
+ size_t ofs;
char outbuf[21];
const char *outstr;
@@ -415,10 +415,14 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
outstr = fmt;
len = ofs - 1;
flush_str:
- while (width-- > len) {
- if (cb(state, " ", 1) != 0)
+ 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;
- written += 1;
}
if (cb(state, outstr, len) != 0)
return -1;
--
2.39.5
^ permalink raw reply related [flat|nested] 26+ messages in thread* [PATCH v5 next 06/17] tools/nolibc/printf: Simplify __nolibc_printf()
2026-03-08 11:37 [PATCH v5 next 00/17] Enhance printf() david.laight.linux
` (4 preceding siblings ...)
2026-03-08 11:37 ` [PATCH v5 next 05/17] tools/nolibc/printf: Output pad characters in 16 byte chunks david.laight.linux
@ 2026-03-08 11:37 ` david.laight.linux
2026-03-08 11:37 ` [PATCH v5 next 07/17] tools/nolibc/printf: Use goto and reduce indentation david.laight.linux
` (12 subsequent siblings)
18 siblings, 0 replies; 26+ messages in thread
From: david.laight.linux @ 2026-03-08 11:37 UTC (permalink / raw)
To: Willy Tarreau, Thomas Weißschuh, linux-kernel, Cheng Li; +Cc: David Laight
From: David Laight <david.laight.linux@gmail.com>
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.
Acked-By; Willy Tarreau <w@1wt.eu>
Signed-off-by: David Laight <david.laight.linux@gmail.com>
---
No change for v3, v4 or v5.
tools/include/nolibc/stdio.h | 104 ++++++++++++++++++-----------------
1 file changed, 53 insertions(+), 51 deletions(-)
diff --git a/tools/include/nolibc/stdio.h b/tools/include/nolibc/stdio.h
index c6d5d075f012..b3cfed162eb6 100644
--- a/tools/include/nolibc/stdio.h
+++ b/tools/include/nolibc/stdio.h
@@ -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.
--
2.39.5
^ permalink raw reply related [flat|nested] 26+ messages in thread* [PATCH v5 next 07/17] tools/nolibc/printf: Use goto and reduce indentation
2026-03-08 11:37 [PATCH v5 next 00/17] Enhance printf() david.laight.linux
` (5 preceding siblings ...)
2026-03-08 11:37 ` [PATCH v5 next 06/17] tools/nolibc/printf: Simplify __nolibc_printf() david.laight.linux
@ 2026-03-08 11:37 ` david.laight.linux
2026-03-08 11:37 ` [PATCH v5 next 08/17] tools/nolibc/printf: Use bit-masks to hold requested flag, length and conversion chars david.laight.linux
` (11 subsequent siblings)
18 siblings, 0 replies; 26+ messages in thread
From: david.laight.linux @ 2026-03-08 11:37 UTC (permalink / raw)
To: Willy Tarreau, Thomas Weißschuh, linux-kernel, Cheng Li; +Cc: David Laight
From: David Laight <david.laight.linux@gmail.com>
Upcoming changes will need to use goto to jump to the code that
outputs characters.
Use 'goto do_output' to output a known number of characters.
Use 'goto do_strlen_output' to output a '\0' terminated string.
Removes a level of indentation from the format processing code.
The change is best reviewed using 'git diff -b' after applying it.
Acked-by: Willy Tarreau <w@1wt.eu>
Signed-off-by: David Laight <david.laight.linux@gmail.com>
---
Unchanged for v5.
For v4:
- Output a single '%' from the format string.
New patch for v3.
Makes the final code look better and there is less to change if done early.
tools/include/nolibc/stdio.h | 170 +++++++++++++++++++----------------
1 file changed, 92 insertions(+), 78 deletions(-)
diff --git a/tools/include/nolibc/stdio.h b/tools/include/nolibc/stdio.h
index b3cfed162eb6..710a4bce5e81 100644
--- a/tools/include/nolibc/stdio.h
+++ b/tools/include/nolibc/stdio.h
@@ -329,103 +329,117 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
fmt++;
/* Output characters from the format string. */
len = fmt - outstr;
- } else {
- /* we're in a format sequence */
+ goto do_output;
+ }
- ch = *fmt++;
+ /* we're in a format sequence */
- /* width */
- while (ch >= '0' && ch <= '9') {
- width *= 10;
- width += ch - '0';
+ ch = *fmt++;
- ch = *fmt++;
- }
+ /* width */
+ while (ch >= '0' && ch <= '9') {
+ width *= 10;
+ width += ch - '0';
+
+ ch = *fmt++;
+ }
- /* Length modifiers */
+ /* Length modifiers */
+ if (ch == 'l') {
+ lpref = 1;
+ ch = *fmt++;
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;
}
+ } 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') {
- char *out = outbuf;
+ if (ch == 'c' || ch == 'd' || ch == 'u' || ch == 'x' || ch == 'p') {
+ char *out = outbuf;
- if (ch == 'p')
+ if (ch == 'p')
+ 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 if (lpref) {
- if (lpref > 1)
- v = va_arg(args, unsigned long long);
- else
- v = va_arg(args, unsigned long);
- } else
- v = va_arg(args, unsigned int);
-
- if (ch == 'd') {
- /* sign-extend the value */
- if (lpref == 0)
- v = (long long)(int)v;
- else if (lpref == 1)
- v = (long long)(long)v;
- }
+ } else
+ v = va_arg(args, unsigned int);
- switch (ch) {
- case 'c':
- out[0] = v;
- out[1] = 0;
- break;
- case 'd':
- i64toa_r(v, out);
- break;
- case 'u':
- u64toa_r(v, out);
- break;
- case 'p':
- *(out++) = '0';
- *(out++) = 'x';
- __nolibc_fallthrough;
- default: /* 'x' and 'p' above */
- u64toh_r(v, out);
- break;
- }
- outstr = outbuf;
+ if (ch == 'd') {
+ /* sign-extend the value */
+ if (lpref == 0)
+ v = (long long)(int)v;
+ else if (lpref == 1)
+ v = (long long)(long)v;
}
- else if (ch == 's') {
- outstr = va_arg(args, char *);
- if (!outstr)
- outstr="(null)";
+
+ switch (ch) {
+ case 'c':
+ out[0] = v;
+ out[1] = 0;
+ break;
+ case 'd':
+ i64toa_r(v, out);
+ break;
+ case 'u':
+ u64toa_r(v, out);
+ break;
+ case 'p':
+ *(out++) = '0';
+ *(out++) = 'x';
+ __nolibc_fallthrough;
+ default: /* 'x' and 'p' above */
+ u64toh_r(v, out);
+ break;
}
- else if (ch == 'm') {
+ outstr = outbuf;
+ goto do_strlen_output;
+ }
+
+ if (ch == 's') {
+ outstr = va_arg(args, char *);
+ if (!outstr)
+ outstr="(null)";
+ goto do_strlen_output;
+ }
+
+ if (ch == 'm') {
#ifdef NOLIBC_IGNORE_ERRNO
- outstr = "unknown error";
+ outstr = "unknown error";
#else
- outstr = strerror(errno);
+ outstr = strerror(errno);
#endif /* NOLIBC_IGNORE_ERRNO */
- } else {
- if (ch != '%') {
- /* Invalid format: back up to output the format characters */
- fmt = outstr + 1;
- /* and output a '%' now. */
- }
- /* %% is documented as a 'conversion specifier'.
- * Any flags, precision or length modifier are ignored.
- */
- width = 0;
- outstr = "%";
- }
- len = strlen(outstr);
+ goto do_strlen_output;
}
+ if (ch != '%') {
+ /* Invalid format: back up to output the format characters */
+ fmt = outstr + 1;
+ /* and output a '%' now. */
+ }
+ /* %% is documented as a 'conversion specifier'.
+ * Any flags, precision or length modifier are ignored.
+ */
+ len = 1;
+ width = 0;
+ outstr = fmt - 1;
+ goto do_output;
+
+do_strlen_output:
+ /* Open coded strlen() (slightly smaller). */
+ for (len = 0;; len++)
+ if (!outstr[len])
+ break;
+
+do_output:
written += len;
width -= len;
--
2.39.5
^ permalink raw reply related [flat|nested] 26+ messages in thread* [PATCH v5 next 08/17] tools/nolibc/printf: Use bit-masks to hold requested flag, length and conversion chars
2026-03-08 11:37 [PATCH v5 next 00/17] Enhance printf() david.laight.linux
` (6 preceding siblings ...)
2026-03-08 11:37 ` [PATCH v5 next 07/17] tools/nolibc/printf: Use goto and reduce indentation david.laight.linux
@ 2026-03-08 11:37 ` david.laight.linux
2026-03-08 11:37 ` [PATCH v5 next 09/17] tools/nolibc/printf: Add support for length modifiers tzqL and formats iX david.laight.linux
` (10 subsequent siblings)
18 siblings, 0 replies; 26+ messages in thread
From: david.laight.linux @ 2026-03-08 11:37 UTC (permalink / raw)
To: Willy Tarreau, Thomas Weißschuh, linux-kernel, Cheng Li; +Cc: David Laight
From: David Laight <david.laight.linux@gmail.com>
Use flags bits (1u << (ch & 31)) for the flags, length modifiers, and
conversion specifiers.
This makes it easy to test for multiple values at once.
Detect the conversion flags " #+-0" although they are currently all ignored.
Unconditionally generate the signed values (for %d) to remove a second
set of checks for the size.
Separate out the formatting of single characters from numbers.
Output the sign for negative values then negate and treat as unsigned.
Acked-by: Willy Tarreau <w@1wt.eu>
Signed-off-by: David Laight <david.laight.linux@gmail.com>
---
Unchanged for v5.
Changes for v4:
- Move the support for length modifiers t, j, q, L and formats
i and X to the next patch.
- Convert ll to j (not q) since q isn't added until the next patch.
Changes for v3:
- Patch 6 in v2.
- Move all the variable definitions to the top of the function.
The loop body is a bit long to hide definitions at its top.
- Avoid -Wtype-limits validating format characters.
- Include changes to the selftests.
Changes for v2:
- Use #defines to make the code a lot more readable.
- Include the changes from the old patch 10 that used masks for the
conversion specifiers.
- Detect all the valid flag characters even though they are not implemented.
- Support for left justifying field is moved to patch 7.
tools/include/nolibc/stdio.h | 157 ++++++++++++++++++++++++-----------
1 file changed, 108 insertions(+), 49 deletions(-)
diff --git a/tools/include/nolibc/stdio.h b/tools/include/nolibc/stdio.h
index 710a4bce5e81..1c2b2cf9a1f3 100644
--- a/tools/include/nolibc/stdio.h
+++ b/tools/include/nolibc/stdio.h
@@ -291,10 +291,14 @@ int fseek(FILE *stream, long offset, int whence)
}
-/* minimal printf(). It supports the following formats:
- * - %[l*]{d,u,c,x,p}
- * - %s
- * - unknown modifiers are ignored.
+/* printf(). Supports the following integer and string formats.
+ * - %[#-+ 0][width][{l,ll,j}]{c,d,u,x,p,s,m,%}
+ * - %% generates a single %
+ * - %m outputs strerror(errno).
+ * - The modifiers [#-+ 0] are currently ignored.
+ * - No support for precision or variable widths.
+ * - No support for floating point or wide characters.
+ * - Invalid formats are copied to the output buffer.
*
* Called by vfprintf() and snprintf() to do the actual formatting.
* The callers provide a callback function to save the formatted data.
@@ -305,15 +309,43 @@ int fseek(FILE *stream, long offset, int whence)
* - with (NULL, 0) at the end of the __nolibc_printf.
* If the callback returns non-zero __nolibc_printf() immediately returns -1.
*/
+
typedef int (*__nolibc_printf_cb)(void *state, const char *buf, size_t size);
+/* This code uses 'flag' variables that are indexed by the low 6 bits
+ * of characters to optimise checks for multiple characters.
+ *
+ * _NOLIBC_PF_FLAGS_CONTAIN(flags, 'a', 'b'. ...)
+ * returns non-zero if the bit for any of the specified characters is set.
+ *
+ * _NOLIBC_PF_CHAR_IS_ONE_OF(ch, 'a', 'b'. ...)
+ * returns the flag bit for ch if it is one of the specified characters.
+ * All the characters must be in the same 32 character block (non-alphabetic,
+ * upper case, or lower case) of the ASCII character set.
+ */
+#define _NOLIBC_PF_FLAG(ch) (1u << ((ch) & 0x1f))
+#define _NOLIBC_PF_FLAG_NZ(ch) ((ch) ? _NOLIBC_PF_FLAG(ch) : 0)
+#define _NOLIBC_PF_FLAG8(cmp_1, cmp_2, cmp_3, cmp_4, cmp_5, cmp_6, cmp_7, cmp_8, ...) \
+ (_NOLIBC_PF_FLAG_NZ(cmp_1) | _NOLIBC_PF_FLAG_NZ(cmp_2) | \
+ _NOLIBC_PF_FLAG_NZ(cmp_3) | _NOLIBC_PF_FLAG_NZ(cmp_4) | \
+ _NOLIBC_PF_FLAG_NZ(cmp_5) | _NOLIBC_PF_FLAG_NZ(cmp_6) | \
+ _NOLIBC_PF_FLAG_NZ(cmp_7) | _NOLIBC_PF_FLAG_NZ(cmp_8))
+#define _NOLIBC_PF_FLAGS_CONTAIN(flags, ...) \
+ ((flags) & _NOLIBC_PF_FLAG8(__VA_ARGS__, 0, 0, 0, 0, 0, 0, 0))
+#define _NOLIBC_PF_CHAR_IS_ONE_OF(ch, cmp_1, ...) \
+ ((unsigned int)(ch) - (cmp_1 & 0xe0) > 0x1f ? 0 : \
+ _NOLIBC_PF_FLAGS_CONTAIN(_NOLIBC_PF_FLAG(ch), cmp_1, __VA_ARGS__))
+
static __attribute__((unused, format(printf, 3, 0)))
int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list args)
{
- char lpref, ch;
+ char ch;
unsigned long long v;
+ long long signed_v;
int written, width, len;
+ unsigned int flags, ch_flag;
char outbuf[21];
+ char *out;
const char *outstr;
written = 0;
@@ -324,6 +356,7 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
break;
width = 0;
+ flags = 0;
if (ch != '%') {
while (*fmt && *fmt != '%')
fmt++;
@@ -334,7 +367,14 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
/* we're in a format sequence */
- ch = *fmt++;
+ /* Conversion flag characters */
+ while (1) {
+ ch = *fmt++;
+ ch_flag = _NOLIBC_PF_CHAR_IS_ONE_OF(ch, ' ', '#', '+', '-', '0');
+ if (!ch_flag)
+ break;
+ flags |= ch_flag;
+ }
/* width */
while (ch >= '0' && ch <= '9') {
@@ -344,62 +384,78 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
ch = *fmt++;
}
- /* Length modifiers */
- if (ch == 'l') {
- lpref = 1;
- ch = *fmt++;
- if (ch == 'l') {
- lpref = 2;
- ch = *fmt++;
+ /* Length modifier.
+ * They miss the conversion flags characters " #+-0" so can go into flags.
+ * Change ll to j (both always 64bits).
+ */
+ ch_flag = _NOLIBC_PF_CHAR_IS_ONE_OF(ch, 'l', 'j');
+ if (ch_flag != 0) {
+ if (ch == 'l' && fmt[0] == 'l') {
+ fmt++;
+ ch_flag = _NOLIBC_PF_FLAG('j');
}
- } else if (ch == 'j') {
- /* intmax_t is long long */
- lpref = 2;
+ flags |= ch_flag;
ch = *fmt++;
- } else {
- lpref = 0;
}
- if (ch == 'c' || ch == 'd' || ch == 'u' || ch == 'x' || ch == 'p') {
- char *out = outbuf;
+ /* Conversion specifiers. */
- if (ch == 'p')
+ /* Numeric and pointer conversion specifiers.
+ *
+ * Use an explicit bound check (rather than _NOLIBC_PF_CHAR_IS_ONE_OF())
+ * so ch_flag can be used later.
+ */
+ ch_flag = _NOLIBC_PF_FLAG(ch);
+ if ((ch >= 'a' && ch <= 'z') &&
+ _NOLIBC_PF_FLAGS_CONTAIN(ch_flag, 'c', 'd', 'u', 'x', 'p')) {
+ /* 'long' is needed for pointer conversions and ltz lengths.
+ * A single test can be used provided 'p' (the same bit as '0')
+ * is masked from flags.
+ */
+ if (_NOLIBC_PF_FLAGS_CONTAIN(ch_flag | (flags & ~_NOLIBC_PF_FLAG('p')),
+ 'p', 'l')) {
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 (_NOLIBC_PF_FLAGS_CONTAIN(flags, 'j')) {
+ v = va_arg(args, unsigned long long);
+ signed_v = v;
+ } else {
v = va_arg(args, unsigned int);
+ signed_v = (int)v;
+ }
- if (ch == 'd') {
- /* sign-extend the value */
- if (lpref == 0)
- v = (long long)(int)v;
- else if (lpref == 1)
- v = (long long)(long)v;
+ if (ch == 'c') {
+ /* "%c" - single character. */
+ outbuf[0] = v;
+ len = 1;
+ outstr = outbuf;
+ goto do_output;
}
- switch (ch) {
- case 'c':
- out[0] = v;
- out[1] = 0;
- break;
- case 'd':
- i64toa_r(v, out);
- break;
- case 'u':
+ out = outbuf;
+
+ if (_NOLIBC_PF_FLAGS_CONTAIN(ch_flag, 'd')) {
+ /* "%d" and "%i" - signed decimal numbers. */
+ if (signed_v < 0) {
+ *out++ = '-';
+ v = -(signed_v + 1);
+ v++;
+ }
+ }
+
+ /* Convert the number to ascii in the required base. */
+ if (_NOLIBC_PF_FLAGS_CONTAIN(ch_flag, 'd', 'u')) {
+ /* Base 10 */
u64toa_r(v, out);
- break;
- case 'p':
- *(out++) = '0';
- *(out++) = 'x';
- __nolibc_fallthrough;
- default: /* 'x' and 'p' above */
+ } else {
+ /* Base 16 */
+ if (_NOLIBC_PF_FLAGS_CONTAIN(ch_flag, 'p')) {
+ *(out++) = '0';
+ *(out++) = 'x';
+ }
u64toh_r(v, out);
- break;
}
+
outstr = outbuf;
goto do_strlen_output;
}
@@ -442,6 +498,9 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
do_output:
written += len;
+ /* Stop gcc back-merging this code into one of the conditionals above. */
+ _NOLIBC_OPTIMIZER_HIDE_VAR(len);
+
width -= len;
while (width > 0) {
/* Output pad in 16 byte blocks with the small block first. */
--
2.39.5
^ permalink raw reply related [flat|nested] 26+ messages in thread* [PATCH v5 next 09/17] tools/nolibc/printf: Add support for length modifiers tzqL and formats iX
2026-03-08 11:37 [PATCH v5 next 00/17] Enhance printf() david.laight.linux
` (7 preceding siblings ...)
2026-03-08 11:37 ` [PATCH v5 next 08/17] tools/nolibc/printf: Use bit-masks to hold requested flag, length and conversion chars david.laight.linux
@ 2026-03-08 11:37 ` david.laight.linux
2026-03-08 11:37 ` [PATCH v5 next 10/17] tools/nolibc/printf: Handle "%s" with the numeric formats david.laight.linux
` (9 subsequent siblings)
18 siblings, 0 replies; 26+ messages in thread
From: david.laight.linux @ 2026-03-08 11:37 UTC (permalink / raw)
To: Willy Tarreau, Thomas Weißschuh, linux-kernel, Cheng Li; +Cc: David Laight
From: David Laight <david.laight.linux@gmail.com>
Length modifiers t (ptrdiff_t) and z (size_t) are aliases for l (long),
q and L are 64bit the same as j (intmax).
Format i is an alias for d and X similar to x but uppper case.
Supporting them is mostly just adding the relavant bit to the bit
pattern used for maching characters.
Although %X is detected the output will be lower case.
Change/add tests to use conversions i and X, and length modifiers L and ll.
Use the correct minimum value for "%Li".
Acked-by: Willy Tarreau <w@1wt.eu>
Signed-off-by: David Laight <david.laight.linux@gmail.com>
---
Unchanged for v5.
v4: Split from the previous patch.
tools/include/nolibc/stdio.h | 27 ++++++++++++--------
tools/testing/selftests/nolibc/nolibc-test.c | 14 +++++++---
2 files changed, 27 insertions(+), 14 deletions(-)
diff --git a/tools/include/nolibc/stdio.h b/tools/include/nolibc/stdio.h
index 1c2b2cf9a1f3..09ea2e0c392f 100644
--- a/tools/include/nolibc/stdio.h
+++ b/tools/include/nolibc/stdio.h
@@ -291,10 +291,11 @@ int fseek(FILE *stream, long offset, int whence)
}
-/* printf(). Supports the following integer and string formats.
- * - %[#-+ 0][width][{l,ll,j}]{c,d,u,x,p,s,m,%}
+/* printf(). Supports most of the normal integer and string formats.
+ * - %[#-+ 0][width][{l,t,z,ll,L,j,q}]{c,d,i,u,x,X,p,s,m,%}
* - %% generates a single %
* - %m outputs strerror(errno).
+ * - %X outputs a..f the same as %x.
* - The modifiers [#-+ 0] are currently ignored.
* - No support for precision or variable widths.
* - No support for floating point or wide characters.
@@ -386,9 +387,11 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
/* Length modifier.
* They miss the conversion flags characters " #+-0" so can go into flags.
- * Change ll to j (both always 64bits).
+ * Change both L and ll to j (all alwys 64bit).
*/
- ch_flag = _NOLIBC_PF_CHAR_IS_ONE_OF(ch, 'l', 'j');
+ if (ch == 'L')
+ ch = 'j';
+ ch_flag = _NOLIBC_PF_CHAR_IS_ONE_OF(ch, 'l', 't', 'z', 'j', 'q');
if (ch_flag != 0) {
if (ch == 'l' && fmt[0] == 'l') {
fmt++;
@@ -403,20 +406,22 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
/* Numeric and pointer conversion specifiers.
*
* Use an explicit bound check (rather than _NOLIBC_PF_CHAR_IS_ONE_OF())
- * so ch_flag can be used later.
+ * so that 'X' can be allowed through.
+ * 'X' gets treated and 'x' because _NOLIBC_PF_FLAG() returns the same
+ * value for both.
*/
ch_flag = _NOLIBC_PF_FLAG(ch);
- if ((ch >= 'a' && ch <= 'z') &&
- _NOLIBC_PF_FLAGS_CONTAIN(ch_flag, 'c', 'd', 'u', 'x', 'p')) {
+ if (((ch >= 'a' && ch <= 'z') || ch == 'X') &&
+ _NOLIBC_PF_FLAGS_CONTAIN(ch_flag, 'c', 'd', 'i', 'u', 'x', 'p')) {
/* 'long' is needed for pointer conversions and ltz lengths.
* A single test can be used provided 'p' (the same bit as '0')
* is masked from flags.
*/
if (_NOLIBC_PF_FLAGS_CONTAIN(ch_flag | (flags & ~_NOLIBC_PF_FLAG('p')),
- 'p', 'l')) {
+ 'p', 'l', 't', 'z')) {
v = va_arg(args, unsigned long);
signed_v = (long)v;
- } else if (_NOLIBC_PF_FLAGS_CONTAIN(flags, 'j')) {
+ } else if (_NOLIBC_PF_FLAGS_CONTAIN(flags, 'j', 'q')) {
v = va_arg(args, unsigned long long);
signed_v = v;
} else {
@@ -434,7 +439,7 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
out = outbuf;
- if (_NOLIBC_PF_FLAGS_CONTAIN(ch_flag, 'd')) {
+ if (_NOLIBC_PF_FLAGS_CONTAIN(ch_flag, 'd', 'i')) {
/* "%d" and "%i" - signed decimal numbers. */
if (signed_v < 0) {
*out++ = '-';
@@ -444,7 +449,7 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
}
/* Convert the number to ascii in the required base. */
- if (_NOLIBC_PF_FLAGS_CONTAIN(ch_flag, 'd', 'u')) {
+ if (_NOLIBC_PF_FLAGS_CONTAIN(ch_flag, 'd', 'i', 'u')) {
/* Base 10 */
u64toa_r(v, out);
} else {
diff --git a/tools/testing/selftests/nolibc/nolibc-test.c b/tools/testing/selftests/nolibc/nolibc-test.c
index 638f18fc5123..a140c54e4d72 100644
--- a/tools/testing/selftests/nolibc/nolibc-test.c
+++ b/tools/testing/selftests/nolibc/nolibc-test.c
@@ -1826,11 +1826,19 @@ static int run_printf(int min, int max)
CASE_TEST(number); EXPECT_VFPRINTF(1, "1234", "%d", 1234); break;
CASE_TEST(negnumber); EXPECT_VFPRINTF(1, "-1234", "%d", -1234); break;
CASE_TEST(unsigned); EXPECT_VFPRINTF(1, "12345", "%u", 12345); break;
+ CASE_TEST(signed_max); EXPECT_VFPRINTF(1, "2147483647", "%i", ~0u >> 1); break;
+ CASE_TEST(signed_min); EXPECT_VFPRINTF(1, "-2147483648", "%i", (~0u >> 1) + 1); break;
+ CASE_TEST(unsigned_max); EXPECT_VFPRINTF(1, "4294967295", "%u", ~0u); break;
CASE_TEST(char); EXPECT_VFPRINTF(1, "c", "%c", 'c'); break;
- CASE_TEST(hex); EXPECT_VFPRINTF(1, "f", "%x", 0xf); break;
+ CASE_TEST(hex_nolibc); EXPECT_VFPRINTF(is_nolibc, "|f|d|", "|%x|%X|", 0xf, 0xd); break;
+ CASE_TEST(hex_libc); EXPECT_VFPRINTF(!is_nolibc, "|f|D|", "|%x|%X|", 0xf, 0xd); break;
CASE_TEST(pointer); EXPECT_VFPRINTF(1, "0x1", "%p", (void *) 0x1); break;
- CASE_TEST(uintmax_t); EXPECT_VFPRINTF(1, "18446744073709551615", "%ju", 0xffffffffffffffffULL); break;
- CASE_TEST(intmax_t); EXPECT_VFPRINTF(1, "-9223372036854775807", "%jd", 0x8000000000000001LL); break;
+ CASE_TEST(percent); EXPECT_VFPRINTF(1, "a%d42%69%", "a%%d%d%%%d%%", 42, 69); break;
+ CASE_TEST(perc_qual); EXPECT_VFPRINTF(1, "a%d2", "a%-14l%d%d", 2); break;
+ CASE_TEST(invalid); EXPECT_VFPRINTF(1, "a%12yx3%y42%P", "a%12yx%d%y%d%P", 3, 42); break;
+ CASE_TEST(intmax_max); EXPECT_VFPRINTF(1, "9223372036854775807", "%lld", ~0ULL >> 1); break;
+ CASE_TEST(intmax_min); EXPECT_VFPRINTF(1, "-9223372036854775808", "%Li", (~0ULL >> 1) + 1); break;
+ CASE_TEST(uintmax_max); EXPECT_VFPRINTF(1, "18446744073709551615", "%ju", ~0ULL); break;
CASE_TEST(truncation); EXPECT_VFPRINTF(1, "0123456789012345678901234", "%s", "0123456789012345678901234"); break;
CASE_TEST(string_width); EXPECT_VFPRINTF(1, " 1", "%10s", "1"); break;
CASE_TEST(number_width); EXPECT_VFPRINTF(1, " 1", "%10d", 1); break;
--
2.39.5
^ permalink raw reply related [flat|nested] 26+ messages in thread* [PATCH v5 next 10/17] tools/nolibc/printf: Handle "%s" with the numeric formats
2026-03-08 11:37 [PATCH v5 next 00/17] Enhance printf() david.laight.linux
` (8 preceding siblings ...)
2026-03-08 11:37 ` [PATCH v5 next 09/17] tools/nolibc/printf: Add support for length modifiers tzqL and formats iX david.laight.linux
@ 2026-03-08 11:37 ` david.laight.linux
2026-03-08 11:37 ` [PATCH v5 next 11/17] tools/nolibc/printf: Prepend sign to converted number david.laight.linux
` (8 subsequent siblings)
18 siblings, 0 replies; 26+ messages in thread
From: david.laight.linux @ 2026-03-08 11:37 UTC (permalink / raw)
To: Willy Tarreau, Thomas Weißschuh, linux-kernel, Cheng Li; +Cc: David Laight
From: David Laight <david.laight.linux@gmail.com>
Avoids the extra va_arg() call with is non-trivial on a lot of
modern ABI.
Acked-by: Willy Tarreau <w@1wt.eu>
Signed-off-by: David Laight <david.laight.linux@gmail.com>
---
Unchanched for v4 and v5.
Changes for v3:
- Moved to its own patch (part of patch 7 in v2)..
- Fix 32bit compile.
tools/include/nolibc/stdio.h | 22 ++++++++++++----------
1 file changed, 12 insertions(+), 10 deletions(-)
diff --git a/tools/include/nolibc/stdio.h b/tools/include/nolibc/stdio.h
index 09ea2e0c392f..40ceb7a0b25a 100644
--- a/tools/include/nolibc/stdio.h
+++ b/tools/include/nolibc/stdio.h
@@ -412,13 +412,13 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
*/
ch_flag = _NOLIBC_PF_FLAG(ch);
if (((ch >= 'a' && ch <= 'z') || ch == 'X') &&
- _NOLIBC_PF_FLAGS_CONTAIN(ch_flag, 'c', 'd', 'i', 'u', 'x', 'p')) {
- /* 'long' is needed for pointer conversions and ltz lengths.
+ _NOLIBC_PF_FLAGS_CONTAIN(ch_flag, 'c', 'd', 'i', 'u', 'x', 'p', 's')) {
+ /* 'long' is needed for pointer/string conversions and ltz lengths.
* A single test can be used provided 'p' (the same bit as '0')
* is masked from flags.
*/
if (_NOLIBC_PF_FLAGS_CONTAIN(ch_flag | (flags & ~_NOLIBC_PF_FLAG('p')),
- 'p', 'l', 't', 'z')) {
+ 'p', 's', 'l', 't', 'z')) {
v = va_arg(args, unsigned long);
signed_v = (long)v;
} else if (_NOLIBC_PF_FLAGS_CONTAIN(flags, 'j', 'q')) {
@@ -437,6 +437,15 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
goto do_output;
}
+ if (ch == 's') {
+ /* "%s" - character string. */
+ outstr = (const char *)(uintptr_t)v;
+ if (!outstr) {
+ outstr = "(null)";
+ }
+ goto do_strlen_output;
+ }
+
out = outbuf;
if (_NOLIBC_PF_FLAGS_CONTAIN(ch_flag, 'd', 'i')) {
@@ -465,13 +474,6 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
goto do_strlen_output;
}
- if (ch == 's') {
- outstr = va_arg(args, char *);
- if (!outstr)
- outstr="(null)";
- goto do_strlen_output;
- }
-
if (ch == 'm') {
#ifdef NOLIBC_IGNORE_ERRNO
outstr = "unknown error";
--
2.39.5
^ permalink raw reply related [flat|nested] 26+ messages in thread* [PATCH v5 next 11/17] tools/nolibc/printf: Prepend sign to converted number
2026-03-08 11:37 [PATCH v5 next 00/17] Enhance printf() david.laight.linux
` (9 preceding siblings ...)
2026-03-08 11:37 ` [PATCH v5 next 10/17] tools/nolibc/printf: Handle "%s" with the numeric formats david.laight.linux
@ 2026-03-08 11:37 ` david.laight.linux
2026-03-08 11:37 ` [PATCH v5 next 12/17] tools/nolibc/printf: Add support for conversion flags space and plus david.laight.linux
` (7 subsequent siblings)
18 siblings, 0 replies; 26+ messages in thread
From: david.laight.linux @ 2026-03-08 11:37 UTC (permalink / raw)
To: Willy Tarreau, Thomas Weißschuh, linux-kernel, Cheng Li; +Cc: David Laight
From: David Laight <david.laight.linux@gmail.com>
Instead of appending the converted number to the sign, convert first
and then prepend the sign (or "0x").
Use the length returned by u64toh_r() instead of calling strlen().
Needed so that zero padding can be inserted between the sign and digits
in an upcoming patch.
Acked-by: Willy Tarreau <w@1wt.eu>
Signed-off-by: David Laight <david.laight.linux@gmail.com>
---
v5:
- Updated comments w.r.t sign_prefix
Changes for v4:
- Split from the patch that supported modifiers " +#".
tools/include/nolibc/stdio.h | 36 +++++++++++++++++++++++++++---------
1 file changed, 27 insertions(+), 9 deletions(-)
diff --git a/tools/include/nolibc/stdio.h b/tools/include/nolibc/stdio.h
index 40ceb7a0b25a..93fc24238661 100644
--- a/tools/include/nolibc/stdio.h
+++ b/tools/include/nolibc/stdio.h
@@ -345,9 +345,10 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
long long signed_v;
int written, width, len;
unsigned int flags, ch_flag;
- char outbuf[21];
+ char outbuf[2 + 22 + 1];
char *out;
const char *outstr;
+ unsigned int sign_prefix;
written = 0;
while (1) {
@@ -446,32 +447,49 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
goto do_strlen_output;
}
- out = outbuf;
+ /* The 'sign_prefix' can be zero, one or two ("0x") characters.
+ * Prepended least significant byte first stopping on a zero byte.
+ */
+ sign_prefix = 0;
if (_NOLIBC_PF_FLAGS_CONTAIN(ch_flag, 'd', 'i')) {
/* "%d" and "%i" - signed decimal numbers. */
if (signed_v < 0) {
- *out++ = '-';
+ sign_prefix = '-';
v = -(signed_v + 1);
v++;
}
}
+ /* The value is converted offset into the buffer so that
+ * the sign/prefix can be added in front.
+ * The longest digit string is 22 + 1 for octal conversions, the
+ * space is reserved even though octal isn't currently supported.
+ */
+ out = outbuf + 2;
+
/* Convert the number to ascii in the required base. */
if (_NOLIBC_PF_FLAGS_CONTAIN(ch_flag, 'd', 'i', 'u')) {
/* Base 10 */
- u64toa_r(v, out);
+ len = u64toa_r(v, out);
} else {
/* Base 16 */
if (_NOLIBC_PF_FLAGS_CONTAIN(ch_flag, 'p')) {
- *(out++) = '0';
- *(out++) = 'x';
+ /* "%p" needs "0x" prepending. */
+ sign_prefix = '0' << 8 | 'x';
}
- u64toh_r(v, out);
+ len = u64toh_r(v, out);
}
- outstr = outbuf;
- goto do_strlen_output;
+ /* Add the 0, 1 or 2 ("0x") sign/prefix characters at the front. */
+ for (; sign_prefix; sign_prefix >>= 8) {
+ /* Force gcc to increment len inside the loop. */
+ _NOLIBC_OPTIMIZER_HIDE_VAR(len);
+ len++;
+ *--out = sign_prefix;
+ }
+ outstr = out;
+ goto do_output;
}
if (ch == 'm') {
--
2.39.5
^ permalink raw reply related [flat|nested] 26+ messages in thread* [PATCH v5 next 12/17] tools/nolibc/printf: Add support for conversion flags space and plus
2026-03-08 11:37 [PATCH v5 next 00/17] Enhance printf() david.laight.linux
` (10 preceding siblings ...)
2026-03-08 11:37 ` [PATCH v5 next 11/17] tools/nolibc/printf: Prepend sign to converted number david.laight.linux
@ 2026-03-08 11:37 ` david.laight.linux
2026-03-08 11:37 ` [PATCH v5 next 13/17] tools/nolibc/printf: Special case 0 and add support for %#x david.laight.linux
` (6 subsequent siblings)
18 siblings, 0 replies; 26+ messages in thread
From: david.laight.linux @ 2026-03-08 11:37 UTC (permalink / raw)
To: Willy Tarreau, Thomas Weißschuh, linux-kernel, Cheng Li; +Cc: David Laight
From: David Laight <david.laight.linux@gmail.com>
Flags ' ' and '+' are sign characters for positive numbers.
Acked-by: Willy Tarreau <w@1wt.eu>
Signed-off-by: David Laight <david.laight.linux@gmail.com>
---
Unchanged for v5.
v4: Split from the previous patch and support for the # flag.
tools/include/nolibc/stdio.h | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/tools/include/nolibc/stdio.h b/tools/include/nolibc/stdio.h
index 93fc24238661..8b003a9bd268 100644
--- a/tools/include/nolibc/stdio.h
+++ b/tools/include/nolibc/stdio.h
@@ -296,7 +296,7 @@ int fseek(FILE *stream, long offset, int whence)
* - %% generates a single %
* - %m outputs strerror(errno).
* - %X outputs a..f the same as %x.
- * - The modifiers [#-+ 0] are currently ignored.
+ * - The modifiers [#-0] are currently ignored.
* - No support for precision or variable widths.
* - No support for floating point or wide characters.
* - Invalid formats are copied to the output buffer.
@@ -458,6 +458,10 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
sign_prefix = '-';
v = -(signed_v + 1);
v++;
+ } else if (_NOLIBC_PF_FLAGS_CONTAIN(flags, '+')) {
+ sign_prefix = '+';
+ } else if (_NOLIBC_PF_FLAGS_CONTAIN(flags, ' ')) {
+ sign_prefix = ' ';
}
}
--
2.39.5
^ permalink raw reply related [flat|nested] 26+ messages in thread* [PATCH v5 next 13/17] tools/nolibc/printf: Special case 0 and add support for %#x
2026-03-08 11:37 [PATCH v5 next 00/17] Enhance printf() david.laight.linux
` (11 preceding siblings ...)
2026-03-08 11:37 ` [PATCH v5 next 12/17] tools/nolibc/printf: Add support for conversion flags space and plus david.laight.linux
@ 2026-03-08 11:37 ` david.laight.linux
2026-03-08 11:37 ` [PATCH v5 next 14/17] tools/nolibc/printf: Add support for left aligning fields david.laight.linux
` (5 subsequent siblings)
18 siblings, 0 replies; 26+ messages in thread
From: david.laight.linux @ 2026-03-08 11:37 UTC (permalink / raw)
To: Willy Tarreau, Thomas Weißschuh, linux-kernel, Cheng Li; +Cc: David Laight
From: David Laight <david.laight.linux@gmail.com>
The output for %#x is almost the same as that for %p, both output in
hexadecimal with a leading "0x".
However for zero %#x should just output "0" (the same as decimal and ocal).
For %p match glibc and output "(nil)" rather than "0x0" or "0".
Add tests for "%#x", "% d", "%+d" and passing NULL to "%p".
Acked-by: Willy Tarreau <w@1wt.eu>
Signed-off-by: David Laight <david.laight.linux@gmail.com>
---
Unchanged for v5.
v4: Split into its' own patch.
tools/include/nolibc/stdio.h | 38 ++++++++++++++------
tools/testing/selftests/nolibc/nolibc-test.c | 3 ++
2 files changed, 30 insertions(+), 11 deletions(-)
diff --git a/tools/include/nolibc/stdio.h b/tools/include/nolibc/stdio.h
index 8b003a9bd268..4bb4d08d834e 100644
--- a/tools/include/nolibc/stdio.h
+++ b/tools/include/nolibc/stdio.h
@@ -296,7 +296,7 @@ int fseek(FILE *stream, long offset, int whence)
* - %% generates a single %
* - %m outputs strerror(errno).
* - %X outputs a..f the same as %x.
- * - The modifiers [#-0] are currently ignored.
+ * - The modifiers [-0] are currently ignored.
* - No support for precision or variable widths.
* - No support for floating point or wide characters.
* - Invalid formats are copied to the output buffer.
@@ -410,8 +410,11 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
* so that 'X' can be allowed through.
* 'X' gets treated and 'x' because _NOLIBC_PF_FLAG() returns the same
* value for both.
+ *
+ * We need to check for "%p" or "%#x" later, merging here gives better code.
+ * But '#' collides with 'c' so shift right.
*/
- ch_flag = _NOLIBC_PF_FLAG(ch);
+ ch_flag = _NOLIBC_PF_FLAG(ch) | (flags & _NOLIBC_PF_FLAG('#')) >> 1;
if (((ch >= 'a' && ch <= 'z') || ch == 'X') &&
_NOLIBC_PF_FLAGS_CONTAIN(ch_flag, 'c', 'd', 'i', 'u', 'x', 'p', 's')) {
/* 'long' is needed for pointer/string conversions and ltz lengths.
@@ -472,17 +475,30 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
*/
out = outbuf + 2;
- /* Convert the number to ascii in the required base. */
- if (_NOLIBC_PF_FLAGS_CONTAIN(ch_flag, 'd', 'i', 'u')) {
- /* Base 10 */
- len = u64toa_r(v, out);
- } else {
- /* Base 16 */
+ if (v == 0) {
+ /* There are special rules for zero. */
if (_NOLIBC_PF_FLAGS_CONTAIN(ch_flag, 'p')) {
- /* "%p" needs "0x" prepending. */
- sign_prefix = '0' << 8 | 'x';
+ /* "%p" match glibc, precision is ignored */
+ outstr = "(nil)";
+ len = 5;
+ goto do_output;
+ }
+ /* All other formats (including "%#x") just output "0". */
+ out[0] = '0';
+ len = 1;
+ } else {
+ /* Convert the number to ascii in the required base. */
+ if (_NOLIBC_PF_FLAGS_CONTAIN(ch_flag, 'd', 'i', 'u')) {
+ /* Base 10 */
+ len = u64toa_r(v, out);
+ } else {
+ /* Base 16 */
+ if (_NOLIBC_PF_FLAGS_CONTAIN(ch_flag, 'p', '#' - 1)) {
+ /* "%p" and "%#x" need "0x" prepending. */
+ sign_prefix = '0' << 8 | 'x';
+ }
+ len = u64toh_r(v, out);
}
- len = u64toh_r(v, out);
}
/* Add the 0, 1 or 2 ("0x") sign/prefix characters at the front. */
diff --git a/tools/testing/selftests/nolibc/nolibc-test.c b/tools/testing/selftests/nolibc/nolibc-test.c
index a140c54e4d72..4267c69ec37c 100644
--- a/tools/testing/selftests/nolibc/nolibc-test.c
+++ b/tools/testing/selftests/nolibc/nolibc-test.c
@@ -1825,6 +1825,7 @@ static int run_printf(int min, int max)
CASE_TEST(string); EXPECT_VFPRINTF(1, "foo", "%s", "foo"); break;
CASE_TEST(number); EXPECT_VFPRINTF(1, "1234", "%d", 1234); break;
CASE_TEST(negnumber); EXPECT_VFPRINTF(1, "-1234", "%d", -1234); break;
+ CASE_TEST(num_sign); EXPECT_VFPRINTF(1, "| 1|+2|+3|+4|5|", "|% d|%+d|% +d|%+ d|%#d|", 1, 2, 3, 4, 5); break;
CASE_TEST(unsigned); EXPECT_VFPRINTF(1, "12345", "%u", 12345); break;
CASE_TEST(signed_max); EXPECT_VFPRINTF(1, "2147483647", "%i", ~0u >> 1); break;
CASE_TEST(signed_min); EXPECT_VFPRINTF(1, "-2147483648", "%i", (~0u >> 1) + 1); break;
@@ -1832,7 +1833,9 @@ static int run_printf(int min, int max)
CASE_TEST(char); EXPECT_VFPRINTF(1, "c", "%c", 'c'); break;
CASE_TEST(hex_nolibc); EXPECT_VFPRINTF(is_nolibc, "|f|d|", "|%x|%X|", 0xf, 0xd); break;
CASE_TEST(hex_libc); EXPECT_VFPRINTF(!is_nolibc, "|f|D|", "|%x|%X|", 0xf, 0xd); break;
+ CASE_TEST(hex_alt); EXPECT_VFPRINTF(1, "|0x1| 0x2| 0|", "|%#x|%#5x|%#5x|", 1, 2, 0); break;
CASE_TEST(pointer); EXPECT_VFPRINTF(1, "0x1", "%p", (void *) 0x1); break;
+ CASE_TEST(pointer_NULL); EXPECT_VFPRINTF(1, "(nil)", "%p", (void *)0); break;
CASE_TEST(percent); EXPECT_VFPRINTF(1, "a%d42%69%", "a%%d%d%%%d%%", 42, 69); break;
CASE_TEST(perc_qual); EXPECT_VFPRINTF(1, "a%d2", "a%-14l%d%d", 2); break;
CASE_TEST(invalid); EXPECT_VFPRINTF(1, "a%12yx3%y42%P", "a%12yx%d%y%d%P", 3, 42); break;
--
2.39.5
^ permalink raw reply related [flat|nested] 26+ messages in thread* [PATCH v5 next 14/17] tools/nolibc/printf: Add support for left aligning fields
2026-03-08 11:37 [PATCH v5 next 00/17] Enhance printf() david.laight.linux
` (12 preceding siblings ...)
2026-03-08 11:37 ` [PATCH v5 next 13/17] tools/nolibc/printf: Special case 0 and add support for %#x david.laight.linux
@ 2026-03-08 11:37 ` david.laight.linux
2026-03-08 11:37 ` [PATCH v5 next 15/17] tools/nolibc/printf: Add support for zero padding and field precision david.laight.linux
` (4 subsequent siblings)
18 siblings, 0 replies; 26+ messages in thread
From: david.laight.linux @ 2026-03-08 11:37 UTC (permalink / raw)
To: Willy Tarreau, Thomas Weißschuh, linux-kernel, Cheng Li; +Cc: David Laight
From: David Laight <david.laight.linux@gmail.com>
Output the characters before or after the pad - writing the pad takes more code.
Include additional/changed tests
Acked-by: Willy Tarreau <w@1wt.eu>
Signed-off-by: David Laight <david.laight.linux@gmail.com>
---
Unchanged for v4 and v5.
Changes for v3:
- Formally part of patch 7.
tools/include/nolibc/stdio.h | 6 +++++-
tools/testing/selftests/nolibc/nolibc-test.c | 4 +++-
2 files changed, 8 insertions(+), 2 deletions(-)
diff --git a/tools/include/nolibc/stdio.h b/tools/include/nolibc/stdio.h
index 4bb4d08d834e..fccc8a2d9b8a 100644
--- a/tools/include/nolibc/stdio.h
+++ b/tools/include/nolibc/stdio.h
@@ -546,7 +546,11 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
/* Stop gcc back-merging this code into one of the conditionals above. */
_NOLIBC_OPTIMIZER_HIDE_VAR(len);
+ /* Output the characters on the required side of any padding. */
width -= len;
+ flags = _NOLIBC_PF_FLAGS_CONTAIN(flags, '-');
+ if (flags && cb(state, outstr, len) != 0)
+ return -1;
while (width > 0) {
/* Output pad in 16 byte blocks with the small block first. */
int pad_len = ((width - 1) & 15) + 1;
@@ -555,7 +559,7 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
if (cb(state, " ", pad_len) != 0)
return -1;
}
- if (cb(state, outstr, len) != 0)
+ if (!flags && cb(state, outstr, len) != 0)
return -1;
}
diff --git a/tools/testing/selftests/nolibc/nolibc-test.c b/tools/testing/selftests/nolibc/nolibc-test.c
index 4267c69ec37c..89ded1dd7318 100644
--- a/tools/testing/selftests/nolibc/nolibc-test.c
+++ b/tools/testing/selftests/nolibc/nolibc-test.c
@@ -1845,9 +1845,11 @@ static int run_printf(int min, int max)
CASE_TEST(truncation); EXPECT_VFPRINTF(1, "0123456789012345678901234", "%s", "0123456789012345678901234"); break;
CASE_TEST(string_width); EXPECT_VFPRINTF(1, " 1", "%10s", "1"); break;
CASE_TEST(number_width); EXPECT_VFPRINTF(1, " 1", "%10d", 1); break;
+ CASE_TEST(number_left); EXPECT_VFPRINTF(1, "|-5 |", "|%-8d|", -5); break;
+ CASE_TEST(string_align); EXPECT_VFPRINTF(1, "|foo |", "|%-8s|", "foo"); break;
CASE_TEST(width_trunc); EXPECT_VFPRINTF(1, " 1", "%25d", 1); break;
CASE_TEST(errno); errno = 22; EXPECT_VFPRINTF(is_nolibc, "errno=22", "%m"); break;
- CASE_TEST(errno-neg); errno = -22; EXPECT_VFPRINTF(is_nolibc, " errno=-22", "%12m"); break;
+ CASE_TEST(errno-neg); errno = -22; EXPECT_VFPRINTF(is_nolibc, "errno=-22 ", "%-12m"); break;
CASE_TEST(scanf); EXPECT_ZR(1, test_scanf()); break;
CASE_TEST(printf_error); EXPECT_ZR(1, test_printf_error()); break;
case __LINE__:
--
2.39.5
^ permalink raw reply related [flat|nested] 26+ messages in thread* [PATCH v5 next 15/17] tools/nolibc/printf: Add support for zero padding and field precision
2026-03-08 11:37 [PATCH v5 next 00/17] Enhance printf() david.laight.linux
` (13 preceding siblings ...)
2026-03-08 11:37 ` [PATCH v5 next 14/17] tools/nolibc/printf: Add support for left aligning fields david.laight.linux
@ 2026-03-08 11:37 ` david.laight.linux
2026-03-08 11:37 ` [PATCH v5 next 16/17] tools/nolibc/printf: Add support for octal output david.laight.linux
` (3 subsequent siblings)
18 siblings, 0 replies; 26+ messages in thread
From: david.laight.linux @ 2026-03-08 11:37 UTC (permalink / raw)
To: Willy Tarreau, Thomas Weißschuh, linux-kernel, Cheng Li; +Cc: David Laight
From: David Laight <david.laight.linux@gmail.com>
Includes support for variable field widths (eg "%*.*d").
Zero padding is limited to 31 zero characters.
This is wider than the largest numeric field so shouldn't be a problem.
All the standard printf formats are now supported except octal
and floating point.
Add tests for new features
Acked-by: Willy Tarreau <w@1wt.eu>
Signed-off-by: David Laight <david.laight.linux@gmail.com>
---
Unchanged for v4 and v5.
Changes for v3:
- Formerly patch 8
- Extra comments.
- Adjust offsets in outbuf[], support adding 31 '0' digits while
still allowing enough room for octal support to be added and keeping
outbuf[] at a nice round 56 bytes.
Changes for v2:
- These changes were previously in patch 9.
However you need to apply the old patch 10 to get anything like
the same source. The files then more of less match apart from 'c'
being renamed 'ch' and the 'magic' #defines.
---
tools/include/nolibc/stdio.h | 77 ++++++++++++++++----
tools/testing/selftests/nolibc/nolibc-test.c | 19 ++++-
2 files changed, 79 insertions(+), 17 deletions(-)
diff --git a/tools/include/nolibc/stdio.h b/tools/include/nolibc/stdio.h
index fccc8a2d9b8a..3b2a5613f603 100644
--- a/tools/include/nolibc/stdio.h
+++ b/tools/include/nolibc/stdio.h
@@ -292,12 +292,10 @@ int fseek(FILE *stream, long offset, int whence)
/* printf(). Supports most of the normal integer and string formats.
- * - %[#-+ 0][width][{l,t,z,ll,L,j,q}]{c,d,i,u,x,X,p,s,m,%}
+ * - %[#0-+ ][width|*[.precision|*}][{l,t,z,ll,L,j,q}]{c,d,i,u,x,X,p,s,m,%}
* - %% generates a single %
* - %m outputs strerror(errno).
* - %X outputs a..f the same as %x.
- * - The modifiers [-0] are currently ignored.
- * - No support for precision or variable widths.
* - No support for floating point or wide characters.
* - Invalid formats are copied to the output buffer.
*
@@ -343,9 +341,9 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
char ch;
unsigned long long v;
long long signed_v;
- int written, width, len;
+ int written, width, precision, len;
unsigned int flags, ch_flag;
- char outbuf[2 + 22 + 1];
+ char outbuf[2 + 31 + 22 + 1];
char *out;
const char *outstr;
unsigned int sign_prefix;
@@ -378,12 +376,24 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
flags |= ch_flag;
}
- /* width */
- while (ch >= '0' && ch <= '9') {
- width *= 10;
- width += ch - '0';
-
- ch = *fmt++;
+ /* Width and precision */
+ for (;; ch = *fmt++) {
+ if (ch == '*') {
+ precision = va_arg(args, unsigned int);
+ ch = *fmt++;
+ } else {
+ for (precision = 0; ch >= '0' && ch <= '9'; ch = *fmt++)
+ precision = precision * 10 + (ch - '0');
+ }
+ if (_NOLIBC_PF_FLAGS_CONTAIN(flags, '.'))
+ break;
+ width = precision;
+ if (ch != '.') {
+ /* Default precision for strings */
+ precision = INT_MAX;
+ break;
+ }
+ flags |= _NOLIBC_PF_FLAG('.');
}
/* Length modifier.
@@ -446,6 +456,9 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
outstr = (const char *)(uintptr_t)v;
if (!outstr) {
outstr = "(null)";
+ /* Match glibc, nothing output if precision too small */
+ len = precision >= 6 ? 6 : 0;
+ goto do_output;
}
goto do_strlen_output;
}
@@ -469,11 +482,11 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
}
/* The value is converted offset into the buffer so that
- * the sign/prefix can be added in front.
+ * 31 zero pad characters and the sign/prefix can be added in front.
* The longest digit string is 22 + 1 for octal conversions, the
* space is reserved even though octal isn't currently supported.
*/
- out = outbuf + 2;
+ out = outbuf + 2 + 31;
if (v == 0) {
/* There are special rules for zero. */
@@ -483,6 +496,11 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
len = 5;
goto do_output;
}
+ if (!precision) {
+ /* Explicit %nn.0d, no digits output */
+ len = 0;
+ goto prepend_sign;
+ }
/* All other formats (including "%#x") just output "0". */
out[0] = '0';
len = 1;
@@ -501,6 +519,35 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
}
}
+ /* Add zero padding */
+ if (_NOLIBC_PF_FLAGS_CONTAIN(flags, '0', '.')) {
+ if (!_NOLIBC_PF_FLAGS_CONTAIN(flags, '.')) {
+ if (_NOLIBC_PF_FLAGS_CONTAIN(flags, '-'))
+ /* Left justify overrides zero pad */
+ goto prepend_sign;
+ /* eg "%05d", Zero pad to field width less sign.
+ * Note that precision can end up negative so all
+ * the variables have to be 'signed int'.
+ */
+ precision = width;
+ if (sign_prefix) {
+ precision--;
+ if (sign_prefix >= 256)
+ precision--;
+ }
+ }
+ if (precision > 31)
+ /* Don't run off the start of outbuf[], arbitrary limit
+ * longer than the longest number field. */
+ precision = 31;
+ for (; len < precision; len++) {
+ /* Stop gcc generating horrid code and memset(). */
+ _NOLIBC_OPTIMIZER_HIDE_VAR(len);
+ *--out = '0';
+ }
+ }
+
+prepend_sign:
/* Add the 0, 1 or 2 ("0x") sign/prefix characters at the front. */
for (; sign_prefix; sign_prefix >>= 8) {
/* Force gcc to increment len inside the loop. */
@@ -535,8 +582,8 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
goto do_output;
do_strlen_output:
- /* Open coded strlen() (slightly smaller). */
- for (len = 0;; len++)
+ /* Open coded strnlen() (slightly smaller). */
+ for (len = 0; len < precision; len++)
if (!outstr[len])
break;
diff --git a/tools/testing/selftests/nolibc/nolibc-test.c b/tools/testing/selftests/nolibc/nolibc-test.c
index 89ded1dd7318..7398827fa08b 100644
--- a/tools/testing/selftests/nolibc/nolibc-test.c
+++ b/tools/testing/selftests/nolibc/nolibc-test.c
@@ -1830,12 +1830,15 @@ static int run_printf(int min, int max)
CASE_TEST(signed_max); EXPECT_VFPRINTF(1, "2147483647", "%i", ~0u >> 1); break;
CASE_TEST(signed_min); EXPECT_VFPRINTF(1, "-2147483648", "%i", (~0u >> 1) + 1); break;
CASE_TEST(unsigned_max); EXPECT_VFPRINTF(1, "4294967295", "%u", ~0u); break;
- CASE_TEST(char); EXPECT_VFPRINTF(1, "c", "%c", 'c'); break;
+ CASE_TEST(char); EXPECT_VFPRINTF(1, "|c|d| e|", "|%c|%.0c|%4c|", 'c', 'd', 'e'); break;
CASE_TEST(hex_nolibc); EXPECT_VFPRINTF(is_nolibc, "|f|d|", "|%x|%X|", 0xf, 0xd); break;
CASE_TEST(hex_libc); EXPECT_VFPRINTF(!is_nolibc, "|f|D|", "|%x|%X|", 0xf, 0xd); break;
CASE_TEST(hex_alt); EXPECT_VFPRINTF(1, "|0x1| 0x2| 0|", "|%#x|%#5x|%#5x|", 1, 2, 0); break;
+ CASE_TEST(hex_alt_prec); EXPECT_VFPRINTF(1, "| 0x02|0x03| 0x123|", "|%#5.2x|%#04x|%#6.2x|", 2, 3, 0x123); break;
+ CASE_TEST(hex_0_alt); EXPECT_VFPRINTF(1, "|0|0000| 00|", "|%#x|%#04x|%#5.2x|", 0, 0, 0); break;
CASE_TEST(pointer); EXPECT_VFPRINTF(1, "0x1", "%p", (void *) 0x1); break;
- CASE_TEST(pointer_NULL); EXPECT_VFPRINTF(1, "(nil)", "%p", (void *)0); break;
+ CASE_TEST(pointer_NULL); EXPECT_VFPRINTF(1, "|(nil)|(nil)|", "|%p|%.4p|", (void *)0, (void *)0); break;
+ CASE_TEST(string_NULL); EXPECT_VFPRINTF(1, "|(null)||(null)|", "|%s|%.5s|%.6s|", (void *)0, (void *)0, (void *)0); break;
CASE_TEST(percent); EXPECT_VFPRINTF(1, "a%d42%69%", "a%%d%d%%%d%%", 42, 69); break;
CASE_TEST(perc_qual); EXPECT_VFPRINTF(1, "a%d2", "a%-14l%d%d", 2); break;
CASE_TEST(invalid); EXPECT_VFPRINTF(1, "a%12yx3%y42%P", "a%12yx%d%y%d%P", 3, 42); break;
@@ -1844,10 +1847,22 @@ static int run_printf(int min, int max)
CASE_TEST(uintmax_max); EXPECT_VFPRINTF(1, "18446744073709551615", "%ju", ~0ULL); break;
CASE_TEST(truncation); EXPECT_VFPRINTF(1, "0123456789012345678901234", "%s", "0123456789012345678901234"); break;
CASE_TEST(string_width); EXPECT_VFPRINTF(1, " 1", "%10s", "1"); break;
+ CASE_TEST(string_trunc); EXPECT_VFPRINTF(1, " 12345", "%10.5s", "1234567890"); break;
CASE_TEST(number_width); EXPECT_VFPRINTF(1, " 1", "%10d", 1); break;
CASE_TEST(number_left); EXPECT_VFPRINTF(1, "|-5 |", "|%-8d|", -5); break;
CASE_TEST(string_align); EXPECT_VFPRINTF(1, "|foo |", "|%-8s|", "foo"); break;
CASE_TEST(width_trunc); EXPECT_VFPRINTF(1, " 1", "%25d", 1); break;
+ CASE_TEST(width_tr_lft); EXPECT_VFPRINTF(1, "1 ", "%-30d", 1); break;
+ CASE_TEST(number_pad); EXPECT_VFPRINTF(1, "0000000005", "%010d", 5); break;
+ CASE_TEST(number_pad); EXPECT_VFPRINTF(1, "|0000000005|0x1234|", "|%010d|%#01x|", 5, 0x1234); break;
+ CASE_TEST(num_pad_neg); EXPECT_VFPRINTF(1, "-000000005", "%010d", -5); break;
+ CASE_TEST(num_pad_hex); EXPECT_VFPRINTF(1, "00fffffffb", "%010x", -5); break;
+ CASE_TEST(num_pad_trunc);EXPECT_VFPRINTF(is_nolibc, " 0000000000000000000000000000005", "%035d", 5); break; /* max 31 '0' can be added */
+ CASE_TEST(num_p_tr_libc);EXPECT_VFPRINTF(!is_nolibc, "00000000000000000000000000000000005", "%035d", 5); break;
+ CASE_TEST(number_prec); EXPECT_VFPRINTF(1, " 00005", "%10.5d", 5); break;
+ CASE_TEST(num_prec_neg); EXPECT_VFPRINTF(1, " -00005", "%10.5d", -5); break;
+ CASE_TEST(num_prec_var); EXPECT_VFPRINTF(1, " -00005", "%*.*d", 10, 5, -5); break;
+ CASE_TEST(num_0_prec_0); EXPECT_VFPRINTF(1, "|| |+||||", "|%.0d|% .0d|%+.0d|%.0u|%.0x|%#.0x|", 0, 0, 0, 0, 0, 0); break;
CASE_TEST(errno); errno = 22; EXPECT_VFPRINTF(is_nolibc, "errno=22", "%m"); break;
CASE_TEST(errno-neg); errno = -22; EXPECT_VFPRINTF(is_nolibc, "errno=-22 ", "%-12m"); break;
CASE_TEST(scanf); EXPECT_ZR(1, test_scanf()); break;
--
2.39.5
^ permalink raw reply related [flat|nested] 26+ messages in thread* [PATCH v5 next 16/17] tools/nolibc/printf: Add support for octal output
2026-03-08 11:37 [PATCH v5 next 00/17] Enhance printf() david.laight.linux
` (14 preceding siblings ...)
2026-03-08 11:37 ` [PATCH v5 next 15/17] tools/nolibc/printf: Add support for zero padding and field precision david.laight.linux
@ 2026-03-08 11:37 ` david.laight.linux
2026-03-08 11:37 ` [PATCH v5 next 17/17] selftests/nolibc: Use printf variable field widths and precisions david.laight.linux
` (2 subsequent siblings)
18 siblings, 0 replies; 26+ messages in thread
From: david.laight.linux @ 2026-03-08 11:37 UTC (permalink / raw)
To: Willy Tarreau, Thomas Weißschuh, linux-kernel, Cheng Li; +Cc: David Laight
From: David Laight <david.laight.linux@gmail.com>
Octal output isn't often used, but adding it costs very little.
Supporting "%#o" is mildly annoying, it has to add a leading '0' if
there isn't one present. In simple cases this is the same as adding a sign
of '0' - but that adds an extra '0' in a few places.
So you need 3 tests, %o, # and no leading '0' (which can only be checked
after the zero pad for precision).
If all the test are deferred until after zero padding then too many values
are 'live' across the call to _nolibc_u64toa_base() and get spilled to stack.
Hence the check that ignores the 'sign' if it is the same as the first
character of the output string.
Add tests for octal output.
Acked-by: Willy Tarreau <w@1wt.eu>
Signed-off-by: David Laight <david.laight.linux@gmail.com>
---
Unchanged for v5.
v4:
- Increase VFPRINTF_LEN to 25 to that 64bit octal output isn't truncated.
Change truncation tests to match.
(Was done earlier in v3.)
New patch for v3.
Relies on the changes to u64toa_r() etc in stdlib.h (patch 2).
tools/include/nolibc/stdio.h | 50 ++++++++++++++------
tools/testing/selftests/nolibc/nolibc-test.c | 9 ++--
2 files changed, 41 insertions(+), 18 deletions(-)
diff --git a/tools/include/nolibc/stdio.h b/tools/include/nolibc/stdio.h
index 3b2a5613f603..10c48b88fcbe 100644
--- a/tools/include/nolibc/stdio.h
+++ b/tools/include/nolibc/stdio.h
@@ -292,7 +292,7 @@ int fseek(FILE *stream, long offset, int whence)
/* printf(). Supports most of the normal integer and string formats.
- * - %[#0-+ ][width|*[.precision|*}][{l,t,z,ll,L,j,q}]{c,d,i,u,x,X,p,s,m,%}
+ * - %[#0-+ ][width|*[.precision|*}][{l,t,z,ll,L,j,q}]{c,d,i,u,o,x,X,p,s,m,%}
* - %% generates a single %
* - %m outputs strerror(errno).
* - %X outputs a..f the same as %x.
@@ -426,7 +426,7 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
*/
ch_flag = _NOLIBC_PF_FLAG(ch) | (flags & _NOLIBC_PF_FLAG('#')) >> 1;
if (((ch >= 'a' && ch <= 'z') || ch == 'X') &&
- _NOLIBC_PF_FLAGS_CONTAIN(ch_flag, 'c', 'd', 'i', 'u', 'x', 'p', 's')) {
+ _NOLIBC_PF_FLAGS_CONTAIN(ch_flag, 'c', 'd', 'i', 'u', 'o', 'x', 'p', 's')) {
/* 'long' is needed for pointer/string conversions and ltz lengths.
* A single test can be used provided 'p' (the same bit as '0')
* is masked from flags.
@@ -479,12 +479,19 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
} else if (_NOLIBC_PF_FLAGS_CONTAIN(flags, ' ')) {
sign_prefix = ' ';
}
+ } else {
+ /* "#o" requires that the output always starts with a '0'.
+ * This needs another check after any zero padding to avoid
+ * adding an extra leading '0'.
+ */
+ if (_NOLIBC_PF_FLAGS_CONTAIN(ch_flag, 'o') &&
+ _NOLIBC_PF_FLAGS_CONTAIN(ch_flag, '#' - 1))
+ sign_prefix = '0';
}
/* The value is converted offset into the buffer so that
* 31 zero pad characters and the sign/prefix can be added in front.
- * The longest digit string is 22 + 1 for octal conversions, the
- * space is reserved even though octal isn't currently supported.
+ * The longest digit string is 22 + 1 for octal conversions.
*/
out = outbuf + 2 + 31;
@@ -497,7 +504,7 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
goto do_output;
}
if (!precision) {
- /* Explicit %nn.0d, no digits output */
+ /* Explicit %nn.0d, no digits output (except for %#.0o) */
len = 0;
goto prepend_sign;
}
@@ -506,17 +513,23 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
len = 1;
} else {
/* Convert the number to ascii in the required base. */
+ unsigned long long recip;
+ unsigned int base;
if (_NOLIBC_PF_FLAGS_CONTAIN(ch_flag, 'd', 'i', 'u')) {
- /* Base 10 */
- len = u64toa_r(v, out);
+ base = 10;
+ recip = _NOLIBC_U64TOA_RECIP(10);
+ } else if (_NOLIBC_PF_FLAGS_CONTAIN(ch_flag, 'o')) {
+ base = 8;
+ recip = _NOLIBC_U64TOA_RECIP(8);
} else {
- /* Base 16 */
+ base = 16;
+ recip = _NOLIBC_U64TOA_RECIP(16);
if (_NOLIBC_PF_FLAGS_CONTAIN(ch_flag, 'p', '#' - 1)) {
/* "%p" and "%#x" need "0x" prepending. */
sign_prefix = '0' << 8 | 'x';
}
- len = u64toh_r(v, out);
}
+ len = _nolibc_u64toa_base(v, out, base, recip);
}
/* Add zero padding */
@@ -547,13 +560,20 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
}
}
+ /* %#o has set sign_prefix to '0', but we don't want so add an extra
+ * leading zero here.
+ * Since the only other byte values of sign_prefix are ' ', '+' and '-'
+ * it is enough to check that out[] doesn't already start with sign_prefix.
+ */
+ if (sign_prefix != *out) {
prepend_sign:
- /* Add the 0, 1 or 2 ("0x") sign/prefix characters at the front. */
- for (; sign_prefix; sign_prefix >>= 8) {
- /* Force gcc to increment len inside the loop. */
- _NOLIBC_OPTIMIZER_HIDE_VAR(len);
- len++;
- *--out = sign_prefix;
+ /* Add the 0, 1 or 2 ("0x") sign/prefix characters at the front. */
+ for (; sign_prefix; sign_prefix >>= 8) {
+ /* Force gcc to increment len inside the loop. */
+ _NOLIBC_OPTIMIZER_HIDE_VAR(len);
+ len++;
+ *--out = sign_prefix;
+ }
}
outstr = out;
goto do_output;
diff --git a/tools/testing/selftests/nolibc/nolibc-test.c b/tools/testing/selftests/nolibc/nolibc-test.c
index 7398827fa08b..ebeb7245005c 100644
--- a/tools/testing/selftests/nolibc/nolibc-test.c
+++ b/tools/testing/selftests/nolibc/nolibc-test.c
@@ -1663,7 +1663,7 @@ int run_stdlib(int min, int max)
#define EXPECT_VFPRINTF(cond, expected, fmt, ...) \
do { if (!(cond)) result(llen, SKIPPED); else ret += expect_vfprintf(llen, expected, fmt, ##__VA_ARGS__); } while (0)
-#define VFPRINTF_LEN 20
+#define VFPRINTF_LEN 25
static int expect_vfprintf(int llen, const char *expected, const char *fmt, ...)
{
char buf[VFPRINTF_LEN + 80];
@@ -1831,6 +1831,9 @@ static int run_printf(int min, int max)
CASE_TEST(signed_min); EXPECT_VFPRINTF(1, "-2147483648", "%i", (~0u >> 1) + 1); break;
CASE_TEST(unsigned_max); EXPECT_VFPRINTF(1, "4294967295", "%u", ~0u); break;
CASE_TEST(char); EXPECT_VFPRINTF(1, "|c|d| e|", "|%c|%.0c|%4c|", 'c', 'd', 'e'); break;
+ CASE_TEST(octal); EXPECT_VFPRINTF(1, "|17| 0033||", "|%o|%6.4o|%.0o|", 017, 033, 0); break;
+ CASE_TEST(octal_max); EXPECT_VFPRINTF(1, "1777777777777777777777", "%llo", ~0ULL); break;
+ CASE_TEST(octal_alt); EXPECT_VFPRINTF(1, "|0|01|02|034|0|", "|%#o|%#o|%#02o|%#02o|%#.0o|", 0, 1, 2, 034, 0); break;
CASE_TEST(hex_nolibc); EXPECT_VFPRINTF(is_nolibc, "|f|d|", "|%x|%X|", 0xf, 0xd); break;
CASE_TEST(hex_libc); EXPECT_VFPRINTF(!is_nolibc, "|f|D|", "|%x|%X|", 0xf, 0xd); break;
CASE_TEST(hex_alt); EXPECT_VFPRINTF(1, "|0x1| 0x2| 0|", "|%#x|%#5x|%#5x|", 1, 2, 0); break;
@@ -1845,13 +1848,13 @@ static int run_printf(int min, int max)
CASE_TEST(intmax_max); EXPECT_VFPRINTF(1, "9223372036854775807", "%lld", ~0ULL >> 1); break;
CASE_TEST(intmax_min); EXPECT_VFPRINTF(1, "-9223372036854775808", "%Li", (~0ULL >> 1) + 1); break;
CASE_TEST(uintmax_max); EXPECT_VFPRINTF(1, "18446744073709551615", "%ju", ~0ULL); break;
- CASE_TEST(truncation); EXPECT_VFPRINTF(1, "0123456789012345678901234", "%s", "0123456789012345678901234"); break;
+ CASE_TEST(truncation); EXPECT_VFPRINTF(1, "012345678901234567890123456789", "%s", "012345678901234567890123456789"); break;
CASE_TEST(string_width); EXPECT_VFPRINTF(1, " 1", "%10s", "1"); break;
CASE_TEST(string_trunc); EXPECT_VFPRINTF(1, " 12345", "%10.5s", "1234567890"); break;
CASE_TEST(number_width); EXPECT_VFPRINTF(1, " 1", "%10d", 1); break;
CASE_TEST(number_left); EXPECT_VFPRINTF(1, "|-5 |", "|%-8d|", -5); break;
CASE_TEST(string_align); EXPECT_VFPRINTF(1, "|foo |", "|%-8s|", "foo"); break;
- CASE_TEST(width_trunc); EXPECT_VFPRINTF(1, " 1", "%25d", 1); break;
+ CASE_TEST(width_trunc); EXPECT_VFPRINTF(1, " 1", "%30d", 1); break;
CASE_TEST(width_tr_lft); EXPECT_VFPRINTF(1, "1 ", "%-30d", 1); break;
CASE_TEST(number_pad); EXPECT_VFPRINTF(1, "0000000005", "%010d", 5); break;
CASE_TEST(number_pad); EXPECT_VFPRINTF(1, "|0000000005|0x1234|", "|%010d|%#01x|", 5, 0x1234); break;
--
2.39.5
^ permalink raw reply related [flat|nested] 26+ messages in thread* [PATCH v5 next 17/17] selftests/nolibc: Use printf variable field widths and precisions
2026-03-08 11:37 [PATCH v5 next 00/17] Enhance printf() david.laight.linux
` (15 preceding siblings ...)
2026-03-08 11:37 ` [PATCH v5 next 16/17] tools/nolibc/printf: Add support for octal output david.laight.linux
@ 2026-03-08 11:37 ` david.laight.linux
2026-03-08 11:58 ` [PATCH v5 next 00/17] Enhance printf() Willy Tarreau
2026-03-08 21:01 ` Thomas Weißschuh
18 siblings, 0 replies; 26+ messages in thread
From: david.laight.linux @ 2026-03-08 11:37 UTC (permalink / raw)
To: Willy Tarreau, Thomas Weißschuh, linux-kernel, Cheng Li; +Cc: David Laight
From: David Laight <david.laight.linux@gmail.com>
Now that printf supports '*' for field widths and precisions
then can be used to simplify the test output.
- aligning the "[OK]" strings.
- reporting the expected sprintf() output when there is a mismatch.
Acked-by: Willy Tarreau <w@1wt.eu>
Signed-off-by: David Laight <david.laight.linux@gmail.com>
---
Unchanged for v4 and v5.
tools/testing/selftests/nolibc/nolibc-test.c | 26 ++++----------------
1 file changed, 5 insertions(+), 21 deletions(-)
diff --git a/tools/testing/selftests/nolibc/nolibc-test.c b/tools/testing/selftests/nolibc/nolibc-test.c
index ebeb7245005c..180611aabbfb 100644
--- a/tools/testing/selftests/nolibc/nolibc-test.c
+++ b/tools/testing/selftests/nolibc/nolibc-test.c
@@ -156,21 +156,6 @@ static const char *errorname(int err)
}
}
-static void align_result(size_t llen)
-{
- const size_t align = 64;
- char buf[align];
- size_t n;
-
- if (llen >= align)
- return;
-
- n = align - llen;
- memset(buf, ' ', n);
- buf[n] = '\0';
- fputs(buf, stdout);
-}
-
enum RESULT {
OK,
FAIL,
@@ -188,8 +173,10 @@ static void result(int llen, enum RESULT r)
else
msg = " [FAIL]";
- align_result(llen);
- puts(msg);
+ llen = 64 - llen;
+ if (llen < 0)
+ llen = 0;
+ printf("%*s%s\n", llen, "", msg);
}
/* The tests below are intended to be used by the macroes, which evaluate
@@ -1692,10 +1679,7 @@ static int expect_vfprintf(int llen, const char *expected, const char *fmt, ...)
}
if (memcmp(expected, buf, cmp_len) || buf[cmp_len]) {
- /* Copy and truncate until "%.*s" supported */
- memcpy(buf, expected, cmp_len);
- buf[cmp_len] = 0;
- llen += printf(" should be \"%s\"", buf);
+ llen += printf(" should be \"%.*s\"", VFPRINTF_LEN, expected);
result(llen, FAIL);
return 1;
}
--
2.39.5
^ permalink raw reply related [flat|nested] 26+ messages in thread* Re: [PATCH v5 next 00/17] Enhance printf()
2026-03-08 11:37 [PATCH v5 next 00/17] Enhance printf() david.laight.linux
` (16 preceding siblings ...)
2026-03-08 11:37 ` [PATCH v5 next 17/17] selftests/nolibc: Use printf variable field widths and precisions david.laight.linux
@ 2026-03-08 11:58 ` Willy Tarreau
2026-03-08 21:01 ` Thomas Weißschuh
18 siblings, 0 replies; 26+ messages in thread
From: Willy Tarreau @ 2026-03-08 11:58 UTC (permalink / raw)
To: david.laight.linux; +Cc: Thomas Weißschuh, linux-kernel, Cheng Li
On Sun, Mar 08, 2026 at 11:37:25AM +0000, david.laight.linux@gmail.com wrote:
> From: David Laight <david.laight.linux@gmail.com>
>
> Update printf() so that it handles almost all the non-fp formats.
> In particular:
> - Left alignment.
> - Zero padding.
> - Alternate form "%#x" and "%#o".
> - Field precision.
> - Variable field width and precision.
> - Width modifiers q, L, t and z.
> - Conversion specifiers i, o and X (X generates lower case).
> About the only things that are missing are wide chanacters and floating point.
>
> The tests are updated to match.
>
> Bloat/savings (in nolibc-test, but excluding the program) to patch 11:
> (Measured for v3)
> Function old new delta
> _nolibc_u64toa_base.isra - 143 +143
> strerror - 78 +78
> __nolibc_sprintf_cb 58 91 +33
> itoa_r.isra 60 75 +15
> utoa_r.isra 144 - -144
> __nolibc_printf 1081 729 -352
> (All these functions include ~40 bytes for the stack protector code.)
> utoa_r.isra and _nolibc_u64toa_base.isra pretty much cancel each other out.
> itoa_r.isra grows slightly since it calls _nolibc_u64toa_base().
> strerror() used to be inlined, but over half of it is the stack check.
> While some of the code added to __nolibc_sprintf_cb() has come out of
> __nolibc_printf() 16-20 bytes is removed from the caller.
> So there is a net saving of about 280 bytes (including losing a copy of
> the number to ascii code).
>
> The later patches add code back in:
> patch 13 - conversion flags " +#" +80 bytes
> patch 14 - left aligning fields +38 bytes
> patch 15 - zero padding and field precision +260 bytes
> patch 16 - octal output +34 bytes
> So probably about +130 bytes, but it will depend on what the application
> actually calls and inlining decisions made by the compiler.
> (All x86-64, other architectures will vary.)
>
> The biggest size change is probably removing the .data from strerror().
> This reduced the program binary file by 4k if it is the only initialised
> data in a small program.
>
> Changes for v5:
> - Old patches 2 to 7 have been applied to nolibc-next and removed.
> - Patch 3 changed to return the correct (success/fail not length)
> value from strerror_r() and to return ERANGE if the buffer is short.
> - Patch 11 (prepend sign) has an updated comment.
Thank you David. For me this is all OK. Great work!
Willy
^ permalink raw reply [flat|nested] 26+ messages in thread* Re: [PATCH v5 next 00/17] Enhance printf()
2026-03-08 11:37 [PATCH v5 next 00/17] Enhance printf() david.laight.linux
` (17 preceding siblings ...)
2026-03-08 11:58 ` [PATCH v5 next 00/17] Enhance printf() Willy Tarreau
@ 2026-03-08 21:01 ` Thomas Weißschuh
2026-03-08 22:41 ` David Laight
18 siblings, 1 reply; 26+ messages in thread
From: Thomas Weißschuh @ 2026-03-08 21:01 UTC (permalink / raw)
To: david.laight.linux; +Cc: Willy Tarreau, linux-kernel, Cheng Li
Hi David,
thanks again for your patches!
On 2026-03-08 11:37:25+0000, david.laight.linux@gmail.com wrote:
> From: David Laight <david.laight.linux@gmail.com>
> David Laight (17):
> tools/nolibc: Add _NOLIBC_OPTIMIZER_HIDE_VAR() to compiler.h
> selftests/nolibc: Rename w to written in expect_vfprintf()
> tools/nolibc: Implement strerror() in terms of strerror_r()
> tools/nolibc: Rename the 'errnum' parameter to strerror()
> tools/nolibc/printf: Output pad characters in 16 byte chunks
> tools/nolibc/printf: Simplify __nolibc_printf()
> tools/nolibc/printf: Use goto and reduce indentation
> tools/nolibc/printf: Use bit-masks to hold requested flag, length and
> conversion chars
> tools/nolibc/printf: Add support for length modifiers tzqL and formats
> iX
> tools/nolibc/printf: Handle "%s" with the numeric formats
> tools/nolibc/printf: Prepend sign to converted number
> tools/nolibc/printf: Add support for conversion flags space and plus
> tools/nolibc/printf: Special case 0 and add support for %#x
> tools/nolibc/printf: Add support for left aligning fields
> tools/nolibc/printf: Add support for zero padding and field precision
> tools/nolibc/printf: Add support for octal output
Beginning from here we have another sign-compare warning:
/home/t-8ch/.cache/crosstools/gcc-13.2.0-nolibc/i386-linux/bin/i386-linux-gcc -Os -fno-ident -fno-asynchronous-unwind-tables -std=c89 -W -Wall -Wextra -fno-stack-protector -Wmissing-prototypes -fstack-protector-all -mstack-protector-guard=global -fsanitize=undefined -fsanitize-trap=all -m32 -Werror -Wl,--fatal-warnings -o nolibc-test \
-nostdlib -nostdinc -static -Isysroot/i386/include nolibc-test.c nolibc-test-linkage.c -lgcc
In file included from sysroot/i386/include/nolibc.h:123,
from sysroot/i386/include/stdio.h:8,
from nolibc-test.c:12:
sysroot/i386/include/stdio.h: In function '__nolibc_printf':
sysroot/i386/include/stdio.h:569:41: error: comparison of integer expressions of different signedness: 'unsigned int' and 'char' [-Werror=sign-compare]
569 | if (sign_prefix != *out) {
| ^~
cc1: all warnings being treated as errors
I have applied all of the patches *before*
"tools/nolibc/printf: Prepend sign to converted number", which
introduced the sign_prefix variable.
Could you fix this up and repost the remaining patches?
Thomas
> selftests/nolibc: Use printf variable field widths and precisions
>
> tools/include/nolibc/compiler.h | 3 +
> tools/include/nolibc/stdio.h | 427 ++++++++++++++-----
> tools/testing/selftests/nolibc/nolibc-test.c | 99 +++--
> 3 files changed, 371 insertions(+), 158 deletions(-)
^ permalink raw reply [flat|nested] 26+ messages in thread* Re: [PATCH v5 next 00/17] Enhance printf()
2026-03-08 21:01 ` Thomas Weißschuh
@ 2026-03-08 22:41 ` David Laight
2026-03-09 6:55 ` Willy Tarreau
2026-03-13 20:07 ` Thomas Weißschuh
0 siblings, 2 replies; 26+ messages in thread
From: David Laight @ 2026-03-08 22:41 UTC (permalink / raw)
To: Thomas Weißschuh; +Cc: Willy Tarreau, linux-kernel, Cheng Li
On Sun, 8 Mar 2026 22:01:19 +0100
Thomas Weißschuh <linux@weissschuh.net> wrote:
> Hi David,
>
> thanks again for your patches!
>
> On 2026-03-08 11:37:25+0000, david.laight.linux@gmail.com wrote:
> > From: David Laight <david.laight.linux@gmail.com>
> > David Laight (17):
> > tools/nolibc: Add _NOLIBC_OPTIMIZER_HIDE_VAR() to compiler.h
> > selftests/nolibc: Rename w to written in expect_vfprintf()
> > tools/nolibc: Implement strerror() in terms of strerror_r()
> > tools/nolibc: Rename the 'errnum' parameter to strerror()
> > tools/nolibc/printf: Output pad characters in 16 byte chunks
> > tools/nolibc/printf: Simplify __nolibc_printf()
> > tools/nolibc/printf: Use goto and reduce indentation
> > tools/nolibc/printf: Use bit-masks to hold requested flag, length and
> > conversion chars
> > tools/nolibc/printf: Add support for length modifiers tzqL and formats
> > iX
> > tools/nolibc/printf: Handle "%s" with the numeric formats
> > tools/nolibc/printf: Prepend sign to converted number
> > tools/nolibc/printf: Add support for conversion flags space and plus
> > tools/nolibc/printf: Special case 0 and add support for %#x
> > tools/nolibc/printf: Add support for left aligning fields
> > tools/nolibc/printf: Add support for zero padding and field precision
> > tools/nolibc/printf: Add support for octal output
>
> Beginning from here we have another sign-compare warning:
>
> /home/t-8ch/.cache/crosstools/gcc-13.2.0-nolibc/i386-linux/bin/i386-linux-gcc -Os -fno-ident -fno-asynchronous-unwind-tables -std=c89 -W -Wall -Wextra -fno-stack-protector -Wmissing-prototypes -fstack-protector-all -mstack-protector-guard=global -fsanitize=undefined -fsanitize-trap=all -m32 -Werror -Wl,--fatal-warnings -o nolibc-test \
> -nostdlib -nostdinc -static -Isysroot/i386/include nolibc-test.c nolibc-test-linkage.c -lgcc
> In file included from sysroot/i386/include/nolibc.h:123,
> from sysroot/i386/include/stdio.h:8,
> from nolibc-test.c:12:
> sysroot/i386/include/stdio.h: In function '__nolibc_printf':
> sysroot/i386/include/stdio.h:569:41: error: comparison of integer expressions of different signedness: 'unsigned int' and 'char' [-Werror=sign-compare]
> 569 | if (sign_prefix != *out) {
> | ^~
> cc1: all warnings being treated as errors
>
> I have applied all of the patches *before*
> "tools/nolibc/printf: Prepend sign to converted number", which
> introduced the sign_prefix variable.
>
> Could you fix this up and repost the remaining patches?
I hate 'sign compare' ...
I dislike using casts to 'fix' it - random 'integer' casts have caused me
grief in the past.
I don't want to make 'sign_prefix' signed - stops you adding 4 characters
(should you so desire); not to mention >> being either UB or implementation
defined on signed values (or maybe just negative ones).
The two obvious fixes are:
if (sign_prefix - *out)
or:
if (sign_prefix != *out + 0u)
Your pick :-)
At least it is only the last couple of patches.
David
>
>
> Thomas
>
> > selftests/nolibc: Use printf variable field widths and precisions
> >
> > tools/include/nolibc/compiler.h | 3 +
> > tools/include/nolibc/stdio.h | 427 ++++++++++++++-----
> > tools/testing/selftests/nolibc/nolibc-test.c | 99 +++--
> > 3 files changed, 371 insertions(+), 158 deletions(-)
^ permalink raw reply [flat|nested] 26+ messages in thread* Re: [PATCH v5 next 00/17] Enhance printf()
2026-03-08 22:41 ` David Laight
@ 2026-03-09 6:55 ` Willy Tarreau
2026-03-09 9:20 ` David Laight
2026-03-13 20:07 ` Thomas Weißschuh
1 sibling, 1 reply; 26+ messages in thread
From: Willy Tarreau @ 2026-03-09 6:55 UTC (permalink / raw)
To: David Laight; +Cc: Thomas Weißschuh, linux-kernel, Cheng Li
Hi David,
On Sun, Mar 08, 2026 at 10:41:21PM +0000, David Laight wrote:
> On Sun, 8 Mar 2026 22:01:19 +0100
> Thomas Weißschuh <linux@weissschuh.net> wrote:
>
> > Hi David,
> >
> > thanks again for your patches!
> >
> > On 2026-03-08 11:37:25+0000, david.laight.linux@gmail.com wrote:
> > > From: David Laight <david.laight.linux@gmail.com>
> > > David Laight (17):
> > > tools/nolibc: Add _NOLIBC_OPTIMIZER_HIDE_VAR() to compiler.h
> > > selftests/nolibc: Rename w to written in expect_vfprintf()
> > > tools/nolibc: Implement strerror() in terms of strerror_r()
> > > tools/nolibc: Rename the 'errnum' parameter to strerror()
> > > tools/nolibc/printf: Output pad characters in 16 byte chunks
> > > tools/nolibc/printf: Simplify __nolibc_printf()
> > > tools/nolibc/printf: Use goto and reduce indentation
> > > tools/nolibc/printf: Use bit-masks to hold requested flag, length and
> > > conversion chars
> > > tools/nolibc/printf: Add support for length modifiers tzqL and formats
> > > iX
> > > tools/nolibc/printf: Handle "%s" with the numeric formats
> > > tools/nolibc/printf: Prepend sign to converted number
> > > tools/nolibc/printf: Add support for conversion flags space and plus
> > > tools/nolibc/printf: Special case 0 and add support for %#x
> > > tools/nolibc/printf: Add support for left aligning fields
> > > tools/nolibc/printf: Add support for zero padding and field precision
> > > tools/nolibc/printf: Add support for octal output
> >
> > Beginning from here we have another sign-compare warning:
> >
> > /home/t-8ch/.cache/crosstools/gcc-13.2.0-nolibc/i386-linux/bin/i386-linux-gcc -Os -fno-ident -fno-asynchronous-unwind-tables -std=c89 -W -Wall -Wextra -fno-stack-protector -Wmissing-prototypes -fstack-protector-all -mstack-protector-guard=global -fsanitize=undefined -fsanitize-trap=all -m32 -Werror -Wl,--fatal-warnings -o nolibc-test \
> > -nostdlib -nostdinc -static -Isysroot/i386/include nolibc-test.c nolibc-test-linkage.c -lgcc
> > In file included from sysroot/i386/include/nolibc.h:123,
> > from sysroot/i386/include/stdio.h:8,
> > from nolibc-test.c:12:
> > sysroot/i386/include/stdio.h: In function '__nolibc_printf':
> > sysroot/i386/include/stdio.h:569:41: error: comparison of integer expressions of different signedness: 'unsigned int' and 'char' [-Werror=sign-compare]
> > 569 | if (sign_prefix != *out) {
> > | ^~
> > cc1: all warnings being treated as errors
> >
> > I have applied all of the patches *before*
> > "tools/nolibc/printf: Prepend sign to converted number", which
> > introduced the sign_prefix variable.
> >
> > Could you fix this up and repost the remaining patches?
>
> I hate 'sign compare' ...
> I dislike using casts to 'fix' it - random 'integer' casts have caused me
> grief in the past.
The cast here is unavoidable. One is a signed char, the other is an unsigned
int, so the comparison can be done in 3 methods depending on the developer's
original intent:
- 8 bits of the char against 8 lower bits of the unsigned int
- signed comparison where char is sign-extended and both are compared
as a 32-bit signed int
- unsigned comparison where char is zero-extended and both are compared
as a 32-bit unsigned int
> I don't want to make 'sign_prefix' signed - stops you adding 4 characters
> (should you so desire); not to mention >> being either UB or implementation
> defined on signed values (or maybe just negative ones).
>
> The two obvious fixes are:
> if (sign_prefix - *out)
> or:
> if (sign_prefix != *out + 0u)
>
> Your pick :-)
>
> At least it is only the last couple of patches.
It will not change much or will just even more hide the problem. My
understanding of that code is that it's neither of these cases. Based
on the comment you apparently want in this test to only check for the
first byte of sign_prefix against *out, right ? So that should be:
if ((char)sign_prefix != *out)
Did I get it right ?
Willy
^ permalink raw reply [flat|nested] 26+ messages in thread* Re: [PATCH v5 next 00/17] Enhance printf()
2026-03-09 6:55 ` Willy Tarreau
@ 2026-03-09 9:20 ` David Laight
0 siblings, 0 replies; 26+ messages in thread
From: David Laight @ 2026-03-09 9:20 UTC (permalink / raw)
To: Willy Tarreau; +Cc: Thomas Weißschuh, linux-kernel, Cheng Li
On Mon, 9 Mar 2026 07:55:30 +0100
Willy Tarreau <w@1wt.eu> wrote:
> Hi David,
>
> On Sun, Mar 08, 2026 at 10:41:21PM +0000, David Laight wrote:
> > On Sun, 8 Mar 2026 22:01:19 +0100
> > Thomas Weißschuh <linux@weissschuh.net> wrote:
> >
> > > Hi David,
> > >
> > > thanks again for your patches!
> > >
> > > On 2026-03-08 11:37:25+0000, david.laight.linux@gmail.com wrote:
> > > > From: David Laight <david.laight.linux@gmail.com>
> > > > David Laight (17):
> > > > tools/nolibc: Add _NOLIBC_OPTIMIZER_HIDE_VAR() to compiler.h
> > > > selftests/nolibc: Rename w to written in expect_vfprintf()
> > > > tools/nolibc: Implement strerror() in terms of strerror_r()
> > > > tools/nolibc: Rename the 'errnum' parameter to strerror()
> > > > tools/nolibc/printf: Output pad characters in 16 byte chunks
> > > > tools/nolibc/printf: Simplify __nolibc_printf()
> > > > tools/nolibc/printf: Use goto and reduce indentation
> > > > tools/nolibc/printf: Use bit-masks to hold requested flag, length and
> > > > conversion chars
> > > > tools/nolibc/printf: Add support for length modifiers tzqL and formats
> > > > iX
> > > > tools/nolibc/printf: Handle "%s" with the numeric formats
> > > > tools/nolibc/printf: Prepend sign to converted number
> > > > tools/nolibc/printf: Add support for conversion flags space and plus
> > > > tools/nolibc/printf: Special case 0 and add support for %#x
> > > > tools/nolibc/printf: Add support for left aligning fields
> > > > tools/nolibc/printf: Add support for zero padding and field precision
> > > > tools/nolibc/printf: Add support for octal output
> > >
> > > Beginning from here we have another sign-compare warning:
> > >
> > > /home/t-8ch/.cache/crosstools/gcc-13.2.0-nolibc/i386-linux/bin/i386-linux-gcc -Os -fno-ident -fno-asynchronous-unwind-tables -std=c89 -W -Wall -Wextra -fno-stack-protector -Wmissing-prototypes -fstack-protector-all -mstack-protector-guard=global -fsanitize=undefined -fsanitize-trap=all -m32 -Werror -Wl,--fatal-warnings -o nolibc-test \
> > > -nostdlib -nostdinc -static -Isysroot/i386/include nolibc-test.c nolibc-test-linkage.c -lgcc
> > > In file included from sysroot/i386/include/nolibc.h:123,
> > > from sysroot/i386/include/stdio.h:8,
> > > from nolibc-test.c:12:
> > > sysroot/i386/include/stdio.h: In function '__nolibc_printf':
> > > sysroot/i386/include/stdio.h:569:41: error: comparison of integer expressions of different signedness: 'unsigned int' and 'char' [-Werror=sign-compare]
> > > 569 | if (sign_prefix != *out) {
> > > | ^~
> > > cc1: all warnings being treated as errors
> > >
> > > I have applied all of the patches *before*
> > > "tools/nolibc/printf: Prepend sign to converted number", which
> > > introduced the sign_prefix variable.
> > >
> > > Could you fix this up and repost the remaining patches?
> >
> > I hate 'sign compare' ...
> > I dislike using casts to 'fix' it - random 'integer' casts have caused me
> > grief in the past.
>
> The cast here is unavoidable. One is a signed char, the other is an unsigned
> int, so the comparison can be done in 3 methods depending on the developer's
> original intent:
> - 8 bits of the char against 8 lower bits of the unsigned int
> - signed comparison where char is sign-extended and both are compared
> as a 32-bit signed int
> - unsigned comparison where char is zero-extended and both are compared
> as a 32-bit unsigned int
>
> > I don't want to make 'sign_prefix' signed - stops you adding 4 characters
> > (should you so desire); not to mention >> being either UB or implementation
> > defined on signed values (or maybe just negative ones).
> >
> > The two obvious fixes are:
> > if (sign_prefix - *out)
> > or:
> > if (sign_prefix != *out + 0u)
> >
> > Your pick :-)
> >
> > At least it is only the last couple of patches.
>
> It will not change much or will just even more hide the problem.
There is nothing to change, the 'problem' just need hiding so the
compiler doesn't complain.
> My understanding of that code is that it's neither of these cases. Based
> on the comment you apparently want in this test to only check for the
> first byte of sign_prefix against *out, right ? So that should be:
>
> if ((char)sign_prefix != *out)
>
> Did I get it right ?
Not really, the check can only succeed when both values are '0'
(which is the real condition being tested - as in the comment).
If you add that (char) cast you'll get an unnecessary '& 0xff' on
everything except x86 (where you might get a byte compare to memory
instead of a sign/zero extending memory read and register compare).
An alternative way of stopping the compiler complaining would be:
if (sign_prefix != *(unsigned char *)out)
I'm waiting for the 'security' people to stop worrying about string
truncation and turn their attention to casts of integers :-)
David
>
> Willy
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v5 next 00/17] Enhance printf()
2026-03-08 22:41 ` David Laight
2026-03-09 6:55 ` Willy Tarreau
@ 2026-03-13 20:07 ` Thomas Weißschuh
2026-03-13 22:40 ` David Laight
1 sibling, 1 reply; 26+ messages in thread
From: Thomas Weißschuh @ 2026-03-13 20:07 UTC (permalink / raw)
To: David Laight; +Cc: Willy Tarreau, linux-kernel, Cheng Li
Hi David,
On 2026-03-08 22:41:21+0000, David Laight wrote:
> On Sun, 8 Mar 2026 22:01:19 +0100
> Thomas Weißschuh <linux@weissschuh.net> wrote:
> > On 2026-03-08 11:37:25+0000, david.laight.linux@gmail.com wrote:
> > > From: David Laight <david.laight.linux@gmail.com>
> > > David Laight (17):
> > > tools/nolibc: Add _NOLIBC_OPTIMIZER_HIDE_VAR() to compiler.h
> > > selftests/nolibc: Rename w to written in expect_vfprintf()
> > > tools/nolibc: Implement strerror() in terms of strerror_r()
> > > tools/nolibc: Rename the 'errnum' parameter to strerror()
> > > tools/nolibc/printf: Output pad characters in 16 byte chunks
> > > tools/nolibc/printf: Simplify __nolibc_printf()
> > > tools/nolibc/printf: Use goto and reduce indentation
> > > tools/nolibc/printf: Use bit-masks to hold requested flag, length and
> > > conversion chars
> > > tools/nolibc/printf: Add support for length modifiers tzqL and formats
> > > iX
> > > tools/nolibc/printf: Handle "%s" with the numeric formats
> > > tools/nolibc/printf: Prepend sign to converted number
> > > tools/nolibc/printf: Add support for conversion flags space and plus
> > > tools/nolibc/printf: Special case 0 and add support for %#x
> > > tools/nolibc/printf: Add support for left aligning fields
> > > tools/nolibc/printf: Add support for zero padding and field precision
> > > tools/nolibc/printf: Add support for octal output
> >
> > Beginning from here we have another sign-compare warning:
> >
> > /home/t-8ch/.cache/crosstools/gcc-13.2.0-nolibc/i386-linux/bin/i386-linux-gcc -Os -fno-ident -fno-asynchronous-unwind-tables -std=c89 -W -Wall -Wextra -fno-stack-protector -Wmissing-prototypes -fstack-protector-all -mstack-protector-guard=global -fsanitize=undefined -fsanitize-trap=all -m32 -Werror -Wl,--fatal-warnings -o nolibc-test \
> > -nostdlib -nostdinc -static -Isysroot/i386/include nolibc-test.c nolibc-test-linkage.c -lgcc
> > In file included from sysroot/i386/include/nolibc.h:123,
> > from sysroot/i386/include/stdio.h:8,
> > from nolibc-test.c:12:
> > sysroot/i386/include/stdio.h: In function '__nolibc_printf':
> > sysroot/i386/include/stdio.h:569:41: error: comparison of integer expressions of different signedness: 'unsigned int' and 'char' [-Werror=sign-compare]
> > 569 | if (sign_prefix != *out) {
> > | ^~
> > cc1: all warnings being treated as errors
> >
> > I have applied all of the patches *before*
> > "tools/nolibc/printf: Prepend sign to converted number", which
> > introduced the sign_prefix variable.
> >
> > Could you fix this up and repost the remaining patches?
>
> I hate 'sign compare' ...
> I dislike using casts to 'fix' it - random 'integer' casts have caused me
> grief in the past.
> I don't want to make 'sign_prefix' signed - stops you adding 4 characters
> (should you so desire); not to mention >> being either UB or implementation
> defined on signed values (or maybe just negative ones).
Sorry for the long delay, I missed these mails.
>
> The two obvious fixes are:
> if (sign_prefix - *out)
I went with this and applied the rest of the series.
Thanks for all your patches!
> or:
> if (sign_prefix != *out + 0u)
>
> Your pick :-)
>
> At least it is only the last couple of patches.
Thomas
^ permalink raw reply [flat|nested] 26+ messages in thread* Re: [PATCH v5 next 00/17] Enhance printf()
2026-03-13 20:07 ` Thomas Weißschuh
@ 2026-03-13 22:40 ` David Laight
2026-03-14 4:48 ` Willy Tarreau
0 siblings, 1 reply; 26+ messages in thread
From: David Laight @ 2026-03-13 22:40 UTC (permalink / raw)
To: Thomas Weißschuh; +Cc: Willy Tarreau, linux-kernel, Cheng Li
On Fri, 13 Mar 2026 21:07:28 +0100
Thomas Weißschuh <linux@weissschuh.net> wrote:
> Hi David,
>
> On 2026-03-08 22:41:21+0000, David Laight wrote:
> > On Sun, 8 Mar 2026 22:01:19 +0100
> > Thomas Weißschuh <linux@weissschuh.net> wrote:
> > > On 2026-03-08 11:37:25+0000, david.laight.linux@gmail.com wrote:
> > > > From: David Laight <david.laight.linux@gmail.com>
> > > > David Laight (17):
> > > > tools/nolibc: Add _NOLIBC_OPTIMIZER_HIDE_VAR() to compiler.h
> > > > selftests/nolibc: Rename w to written in expect_vfprintf()
> > > > tools/nolibc: Implement strerror() in terms of strerror_r()
> > > > tools/nolibc: Rename the 'errnum' parameter to strerror()
> > > > tools/nolibc/printf: Output pad characters in 16 byte chunks
> > > > tools/nolibc/printf: Simplify __nolibc_printf()
> > > > tools/nolibc/printf: Use goto and reduce indentation
> > > > tools/nolibc/printf: Use bit-masks to hold requested flag, length and
> > > > conversion chars
> > > > tools/nolibc/printf: Add support for length modifiers tzqL and formats
> > > > iX
> > > > tools/nolibc/printf: Handle "%s" with the numeric formats
> > > > tools/nolibc/printf: Prepend sign to converted number
> > > > tools/nolibc/printf: Add support for conversion flags space and plus
> > > > tools/nolibc/printf: Special case 0 and add support for %#x
> > > > tools/nolibc/printf: Add support for left aligning fields
> > > > tools/nolibc/printf: Add support for zero padding and field precision
> > > > tools/nolibc/printf: Add support for octal output
> > >
> > > Beginning from here we have another sign-compare warning:
> > >
> > > /home/t-8ch/.cache/crosstools/gcc-13.2.0-nolibc/i386-linux/bin/i386-linux-gcc -Os -fno-ident -fno-asynchronous-unwind-tables -std=c89 -W -Wall -Wextra -fno-stack-protector -Wmissing-prototypes -fstack-protector-all -mstack-protector-guard=global -fsanitize=undefined -fsanitize-trap=all -m32 -Werror -Wl,--fatal-warnings -o nolibc-test \
> > > -nostdlib -nostdinc -static -Isysroot/i386/include nolibc-test.c nolibc-test-linkage.c -lgcc
> > > In file included from sysroot/i386/include/nolibc.h:123,
> > > from sysroot/i386/include/stdio.h:8,
> > > from nolibc-test.c:12:
> > > sysroot/i386/include/stdio.h: In function '__nolibc_printf':
> > > sysroot/i386/include/stdio.h:569:41: error: comparison of integer expressions of different signedness: 'unsigned int' and 'char' [-Werror=sign-compare]
> > > 569 | if (sign_prefix != *out) {
> > > | ^~
> > > cc1: all warnings being treated as errors
> > >
> > > I have applied all of the patches *before*
> > > "tools/nolibc/printf: Prepend sign to converted number", which
> > > introduced the sign_prefix variable.
> > >
> > > Could you fix this up and repost the remaining patches?
> >
> > I hate 'sign compare' ...
> > I dislike using casts to 'fix' it - random 'integer' casts have caused me
> > grief in the past.
> > I don't want to make 'sign_prefix' signed - stops you adding 4 characters
> > (should you so desire); not to mention >> being either UB or implementation
> > defined on signed values (or maybe just negative ones).
>
> Sorry for the long delay, I missed these mails.
>
> >
> > The two obvious fixes are:
> > if (sign_prefix - *out)
>
> I went with this and applied the rest of the series.
> Thanks for all your patches!
Thanks - I hadn't got around to resending them.
David
>
> > or:
> > if (sign_prefix != *out + 0u)
> >
> > Your pick :-)
> >
> > At least it is only the last couple of patches.
>
>
> Thomas
^ permalink raw reply [flat|nested] 26+ messages in thread* Re: [PATCH v5 next 00/17] Enhance printf()
2026-03-13 22:40 ` David Laight
@ 2026-03-14 4:48 ` Willy Tarreau
0 siblings, 0 replies; 26+ messages in thread
From: Willy Tarreau @ 2026-03-14 4:48 UTC (permalink / raw)
To: David Laight; +Cc: Thomas Weißschuh, linux-kernel, Cheng Li
On Fri, Mar 13, 2026 at 10:40:48PM +0000, David Laight wrote:
> On Fri, 13 Mar 2026 21:07:28 +0100
> Thomas Weißschuh <linux@weissschuh.net> wrote:
>
> > Hi David,
> >
> > On 2026-03-08 22:41:21+0000, David Laight wrote:
> > > On Sun, 8 Mar 2026 22:01:19 +0100
> > > Thomas Weißschuh <linux@weissschuh.net> wrote:
> > > > On 2026-03-08 11:37:25+0000, david.laight.linux@gmail.com wrote:
> > > > > From: David Laight <david.laight.linux@gmail.com>
> > > > > David Laight (17):
> > > > > tools/nolibc: Add _NOLIBC_OPTIMIZER_HIDE_VAR() to compiler.h
> > > > > selftests/nolibc: Rename w to written in expect_vfprintf()
> > > > > tools/nolibc: Implement strerror() in terms of strerror_r()
> > > > > tools/nolibc: Rename the 'errnum' parameter to strerror()
> > > > > tools/nolibc/printf: Output pad characters in 16 byte chunks
> > > > > tools/nolibc/printf: Simplify __nolibc_printf()
> > > > > tools/nolibc/printf: Use goto and reduce indentation
> > > > > tools/nolibc/printf: Use bit-masks to hold requested flag, length and
> > > > > conversion chars
> > > > > tools/nolibc/printf: Add support for length modifiers tzqL and formats
> > > > > iX
> > > > > tools/nolibc/printf: Handle "%s" with the numeric formats
> > > > > tools/nolibc/printf: Prepend sign to converted number
> > > > > tools/nolibc/printf: Add support for conversion flags space and plus
> > > > > tools/nolibc/printf: Special case 0 and add support for %#x
> > > > > tools/nolibc/printf: Add support for left aligning fields
> > > > > tools/nolibc/printf: Add support for zero padding and field precision
> > > > > tools/nolibc/printf: Add support for octal output
> > > >
> > > > Beginning from here we have another sign-compare warning:
> > > >
> > > > /home/t-8ch/.cache/crosstools/gcc-13.2.0-nolibc/i386-linux/bin/i386-linux-gcc -Os -fno-ident -fno-asynchronous-unwind-tables -std=c89 -W -Wall -Wextra -fno-stack-protector -Wmissing-prototypes -fstack-protector-all -mstack-protector-guard=global -fsanitize=undefined -fsanitize-trap=all -m32 -Werror -Wl,--fatal-warnings -o nolibc-test \
> > > > -nostdlib -nostdinc -static -Isysroot/i386/include nolibc-test.c nolibc-test-linkage.c -lgcc
> > > > In file included from sysroot/i386/include/nolibc.h:123,
> > > > from sysroot/i386/include/stdio.h:8,
> > > > from nolibc-test.c:12:
> > > > sysroot/i386/include/stdio.h: In function '__nolibc_printf':
> > > > sysroot/i386/include/stdio.h:569:41: error: comparison of integer expressions of different signedness: 'unsigned int' and 'char' [-Werror=sign-compare]
> > > > 569 | if (sign_prefix != *out) {
> > > > | ^~
> > > > cc1: all warnings being treated as errors
> > > >
> > > > I have applied all of the patches *before*
> > > > "tools/nolibc/printf: Prepend sign to converted number", which
> > > > introduced the sign_prefix variable.
> > > >
> > > > Could you fix this up and repost the remaining patches?
> > >
> > > I hate 'sign compare' ...
> > > I dislike using casts to 'fix' it - random 'integer' casts have caused me
> > > grief in the past.
> > > I don't want to make 'sign_prefix' signed - stops you adding 4 characters
> > > (should you so desire); not to mention >> being either UB or implementation
> > > defined on signed values (or maybe just negative ones).
> >
> > Sorry for the long delay, I missed these mails.
> >
> > >
> > > The two obvious fixes are:
> > > if (sign_prefix - *out)
> >
> > I went with this and applied the rest of the series.
> > Thanks for all your patches!
>
> Thanks - I hadn't got around to resending them.
Thanks to both of you, I think it's a really nice improvement, and the
final quality was worth the number of round trips.
Willy
^ permalink raw reply [flat|nested] 26+ messages in thread