Skip to content

Commit 973487f

Browse files
Andy Rossandrewboie
authored andcommitted
lib/os: Rework/shrink printk conversions, add 64 bit support
Add support for 64 bit conversions in a uniformly expressable way by printing values backwards into a buffer on the stack first. This allows all operations to work on the low bits of the value and so the code doesn't need to care (beyond the size of that buffer) about the word size. This trick also doesn't care about the specifics of the base value, so in the process this unifies the decimal and hex printk conversion code to a single function. This comes at a mild cost in CPU cycles to the decimal converter and somewhat higher cost to hex (because it's now doing a full div/mod operation instead of shifting and masking). And stack usage has grown by a few words to hold the temporary. But the benefits in code size are substantial (e.g. ~250 bytes of .text on arm32). Note that this also contains a change to tests/kernel/common to address what appears to have been a bug in the original converters. The printk test uses a format string that looks like "%-4x%-2p" and feeds it the literal arguments "0xABCDEF" and "(char *)42". Now... clearly both those results are going to overflow the 4 and 2-byte field sizes, so there shouldn't be any whitespace between these fields. But the test was written to expect two spaces, inexplicably (yes, I checked: POSIX-compatible printf implementations don't have those spaces either). The new code is definitely doing the right thing, so fix the test instead. Signed-off-by: Andy Ross <[email protected]>
1 parent da2db1c commit 973487f

File tree

3 files changed

+101
-150
lines changed

3 files changed

+101
-150
lines changed

lib/os/Kconfig

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,21 @@ config SYS_HEAP_ALIGNED_ALLOC
5656
increase heap memory overhead on 32 bit platforms when using
5757
small (<256kb) heaps.
5858

59+
config PRINTK64
60+
bool
61+
prompt "Enable 64 bit printk conversions" if !64BIT
62+
default y
63+
help
64+
When true, 64 bit values will be printable on 32 bit systems
65+
using the "ll" flag to the %d, %i, %u, %x and %X specifiers.
66+
When false, these values will be correctly parsed from the
67+
function arguments, but will print "ERR" if their value is
68+
unrepresentable in 32 bits. This setting is always true on
69+
64 bit systems. Note that setting this =n does not produce
70+
significant code savings within the printk library code
71+
itself. Instead, it suppresses the use of the
72+
toolchain-provided 64 bit division implementation, which may
73+
reduce code size if it is unused elsewhere in the
74+
application. Most apps should leave this set to y.
75+
5976
endmenu

lib/os/printk.c

Lines changed: 81 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,17 @@ enum pad_type {
3030
PAD_SPACE_AFTER,
3131
};
3232

33-
static void _printk_dec_ulong(out_func_t out, void *ctx,
34-
const unsigned long num, enum pad_type padding,
35-
int min_width);
36-
static void _printk_hex_ulong(out_func_t out, void *ctx,
37-
const unsigned long long num, enum pad_type padding,
38-
int min_width);
33+
#ifdef CONFIG_PRINTK64
34+
typedef uint64_t printk_val_t;
35+
#else
36+
typedef uint32_t printk_val_t;
37+
#endif
38+
39+
/* Maximum number of digits in a printed decimal value (hex is always
40+
* less, obviously). Funny formula produces 10 max digits for 32 bit,
41+
* 21 for 64.
42+
*/
43+
#define DIGITS_BUFLEN (11 * (sizeof(printk_val_t) / 4) - 1)
3944

4045
#ifdef CONFIG_PRINTK
4146
/**
@@ -87,11 +92,67 @@ void *__printk_get_hook(void)
8792
}
8893
#endif /* CONFIG_PRINTK */
8994

90-
static void print_err(out_func_t out, void *ctx)
95+
static void print_digits(out_func_t out, void *ctx, printk_val_t num, int base,
96+
bool pad_before, char pad_char, int min_width)
97+
{
98+
char buf[DIGITS_BUFLEN];
99+
int i;
100+
101+
/* Print it backwards into the end of the buffer, low digits first */
102+
for (i = DIGITS_BUFLEN - 1; num != 0; i--) {
103+
buf[i] = "0123456789abcdef"[num % base];
104+
num /= base;
105+
}
106+
107+
if (i == DIGITS_BUFLEN - 1) {
108+
buf[i] = '0';
109+
} else {
110+
i++;
111+
}
112+
113+
int pad = MAX(min_width - (DIGITS_BUFLEN - i), 0);
114+
115+
for (/**/; pad > 0 && pad_before; pad--) {
116+
out(pad_char, ctx);
117+
}
118+
for (/**/; i < DIGITS_BUFLEN; i++) {
119+
out(buf[i], ctx);
120+
}
121+
for (/**/; pad > 0; pad--) {
122+
out(pad_char, ctx);
123+
}
124+
}
125+
126+
static void print_hex(out_func_t out, void *ctx, printk_val_t num,
127+
enum pad_type padding, int min_width)
128+
{
129+
print_digits(out, ctx, num, 16, padding != PAD_SPACE_AFTER,
130+
padding == PAD_ZERO_BEFORE ? '0' : ' ', min_width);
131+
}
132+
133+
static void print_dec(out_func_t out, void *ctx, printk_val_t num,
134+
enum pad_type padding, int min_width)
91135
{
92-
out('E', ctx);
93-
out('R', ctx);
94-
out('R', ctx);
136+
print_digits(out, ctx, num, 10, padding != PAD_SPACE_AFTER,
137+
padding == PAD_ZERO_BEFORE ? '0' : ' ', min_width);
138+
}
139+
140+
static bool ok64(out_func_t out, void *ctx, long long val)
141+
{
142+
if (sizeof(printk_val_t) < 8 && val != (long) val) {
143+
out('E', ctx);
144+
out('R', ctx);
145+
out('R', ctx);
146+
return false;
147+
}
148+
return true;
149+
}
150+
151+
static bool negative(printk_val_t val)
152+
{
153+
const printk_val_t hibit = ~(((printk_val_t) ~1) >> 1);
154+
155+
return (val & hibit) != 0;
95156
}
96157

97158
/**
@@ -169,71 +230,46 @@ void z_vprintk(out_func_t out, void *ctx, const char *fmt, va_list ap)
169230
}
170231
goto still_might_format;
171232
case 'd':
172-
case 'i': {
173-
long d;
233+
case 'i':
234+
case 'u': {
235+
printk_val_t d;
174236

175237
if (length_mod == 'z') {
176238
d = va_arg(ap, ssize_t);
177239
} else if (length_mod == 'l') {
178240
d = va_arg(ap, long);
179241
} else if (length_mod == 'L') {
180242
long long lld = va_arg(ap, long long);
181-
if (lld > __LONG_MAX__ ||
182-
lld < ~__LONG_MAX__) {
183-
print_err(out, ctx);
243+
if (!ok64(out, ctx, lld)) {
184244
break;
185245
}
186-
d = lld;
246+
d = (printk_val_t) lld;
187247
} else {
188248
d = va_arg(ap, int);
189249
}
190250

191-
if (d < 0) {
251+
if (*fmt != 'u' && negative(d)) {
192252
out((int)'-', ctx);
193253
d = -d;
194254
min_width--;
195255
}
196-
_printk_dec_ulong(out, ctx, d, padding,
197-
min_width);
198-
break;
199-
}
200-
case 'u': {
201-
unsigned long u;
202-
203-
if (length_mod == 'z') {
204-
u = va_arg(ap, size_t);
205-
} else if (length_mod == 'l') {
206-
u = va_arg(ap, unsigned long);
207-
} else if (length_mod == 'L') {
208-
unsigned long long llu =
209-
va_arg(ap, unsigned long long);
210-
if (llu > ~0UL) {
211-
print_err(out, ctx);
212-
break;
213-
}
214-
u = llu;
215-
} else {
216-
u = va_arg(ap, unsigned int);
217-
}
218-
219-
_printk_dec_ulong(out, ctx, u, padding,
220-
min_width);
256+
print_dec(out, ctx, d, padding, min_width);
221257
break;
222258
}
223259
case 'p':
224260
out('0', ctx);
225261
out('x', ctx);
226262
/* left-pad pointers with zeros */
227263
padding = PAD_ZERO_BEFORE;
228-
if (IS_ENABLED(CONFIG_64BIT)) {
264+
if (sizeof(printk_val_t) > 4) {
229265
min_width = 16;
230266
} else {
231267
min_width = 8;
232268
}
233269
/* Fall through */
234270
case 'x':
235271
case 'X': {
236-
unsigned long long x;
272+
printk_val_t x;
237273

238274
if (*fmt == 'p') {
239275
x = (uintptr_t)va_arg(ap, void *);
@@ -245,8 +281,7 @@ void z_vprintk(out_func_t out, void *ctx, const char *fmt, va_list ap)
245281
x = va_arg(ap, unsigned int);
246282
}
247283

248-
_printk_hex_ulong(out, ctx, x, padding,
249-
min_width);
284+
print_hex(out, ctx, x, padding, min_width);
250285
break;
251286
}
252287
case 's': {
@@ -408,107 +443,6 @@ void printk(const char *fmt, ...)
408443
}
409444
#endif /* CONFIG_PRINTK */
410445

411-
/**
412-
* @brief Output an unsigned long long in hex format
413-
*
414-
* Output an unsigned long long on output installed by platform at init time.
415-
* Able to print full 64-bit values.
416-
* @param num Number to output
417-
*
418-
* @return N/A
419-
*/
420-
static void _printk_hex_ulong(out_func_t out, void *ctx,
421-
const unsigned long long num,
422-
enum pad_type padding,
423-
int min_width)
424-
{
425-
int shift = sizeof(num) * 8;
426-
int found_largest_digit = 0;
427-
int remaining = 16; /* 16 digits max */
428-
int digits = 0;
429-
char nibble;
430-
431-
while (shift >= 4) {
432-
shift -= 4;
433-
nibble = (num >> shift) & 0xf;
434-
435-
if (nibble != 0 || found_largest_digit != 0 || shift == 0) {
436-
found_largest_digit = 1;
437-
nibble += nibble > 9 ? 87 : 48;
438-
out((int)nibble, ctx);
439-
digits++;
440-
continue;
441-
}
442-
443-
if (remaining-- <= min_width) {
444-
if (padding == PAD_ZERO_BEFORE) {
445-
out('0', ctx);
446-
} else if (padding == PAD_SPACE_BEFORE) {
447-
out(' ', ctx);
448-
}
449-
}
450-
}
451-
452-
if (padding == PAD_SPACE_AFTER) {
453-
remaining = min_width * 2 - digits;
454-
while (remaining-- > 0) {
455-
out(' ', ctx);
456-
}
457-
}
458-
}
459-
460-
/**
461-
* @brief Output an unsigned long in decimal format
462-
*
463-
* Output an unsigned long on output installed by platform at init time.
464-
*
465-
* @param num Number to output
466-
*
467-
* @return N/A
468-
*/
469-
static void _printk_dec_ulong(out_func_t out, void *ctx,
470-
const unsigned long num, enum pad_type padding,
471-
int min_width)
472-
{
473-
unsigned long pos = 1000000000;
474-
unsigned long remainder = num;
475-
int found_largest_digit = 0;
476-
int remaining = sizeof(long) * 5 / 2;
477-
int digits = 1;
478-
479-
if (sizeof(long) == 8) {
480-
pos *= 10000000000;
481-
}
482-
483-
/* make sure we don't skip if value is zero */
484-
if (min_width <= 0) {
485-
min_width = 1;
486-
}
487-
488-
while (pos >= 10) {
489-
if (found_largest_digit != 0 || remainder >= pos) {
490-
found_largest_digit = 1;
491-
out((int)(remainder / pos + 48), ctx);
492-
digits++;
493-
} else if (remaining <= min_width
494-
&& padding < PAD_SPACE_AFTER) {
495-
out((int)(padding == PAD_ZERO_BEFORE ? '0' : ' '), ctx);
496-
digits++;
497-
}
498-
remaining--;
499-
remainder %= pos;
500-
pos /= 10;
501-
}
502-
out((int)(remainder + 48), ctx);
503-
504-
if (padding == PAD_SPACE_AFTER) {
505-
remaining = min_width - digits;
506-
while (remaining-- > 0) {
507-
out(' ', ctx);
508-
}
509-
}
510-
}
511-
512446
struct str_context {
513447
char *str;
514448
int max;

tests/kernel/common/src/printk.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ void __printk_hook_install(int (*fn)(int));
1515
void *__printk_get_hook(void);
1616
int (*_old_char_out)(int);
1717

18-
#ifndef CONFIG_64BIT
18+
#ifndef CONFIG_PRINTK64
1919

2020
char *expected = "22 113 10000 32768 40000 22\n"
2121
"p 112 -10000 -32768 -40000 -22\n"
@@ -101,7 +101,7 @@ void test_printk(void)
101101
printk("%d %02d %04d %08d\n", -42, -42, -42, -42);
102102
printk("%u %2u %4u %8u\n", 42, 42, 42, 42);
103103
printk("%u %02u %04u %08u\n", 42, 42, 42, 42);
104-
printk("%-8u%-6d%-4x%-2p%8d\n", 0xFF, 42, 0xABCDEF, (char *)42, 42);
104+
printk("%-8u%-6d%-4x %-2p%8d\n", 0xFF, 42, 0xABCDEF, (char *)42, 42);
105105
printk("%lld %lld %llu %llx\n", 0xFFFFFFFFFULL, -1LL, -1ULL, -1ULL);
106106

107107
pk_console[pos] = '\0';
@@ -130,7 +130,7 @@ void test_printk(void)
130130
count += snprintk(pk_console + count, sizeof(pk_console) - count,
131131
"%u %02u %04u %08u\n", 42, 42, 42, 42);
132132
count += snprintk(pk_console + count, sizeof(pk_console) - count,
133-
"%-8u%-6d%-4x%-2p%8d\n",
133+
"%-8u%-6d%-4x %-2p%8d\n",
134134
0xFF, 42, 0xABCDEF, (char *)42, 42);
135135
count += snprintk(pk_console + count, sizeof(pk_console) - count,
136136
"%lld %lld %llu %llx\n",

0 commit comments

Comments
 (0)