[PATCH v5 next 11/17] tools/nolibc/printf: Prepend sign to converted number

david.laight.linux@gmail.com posted 17 patches 1 month ago
[PATCH v5 next 11/17] tools/nolibc/printf: Prepend sign to converted number
Posted by david.laight.linux@gmail.com 1 month ago
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