Skip to content

Commit 8d4826c

Browse files
committed
vsnprintf: collapse the number format state into one single state
We'll squirrel away the size of the number in 'struct fmt' instead. We have two fairly separate state structures: the 'decode state' is in 'struct fmt', while the 'printout format' is in 'printf_spec'. Both structures are small enough to pass around in registers even across function boundaries (ie two words), even on 32-bit machines. The goal here is to avoid the case statements on the format states, which generate either deep conditionals or jump tables, while also keeping the state size manageable. Signed-off-by: Linus Torvalds <[email protected]>
1 parent 2b76e39 commit 8d4826c

File tree

1 file changed

+66
-71
lines changed

1 file changed

+66
-71
lines changed

lib/vsprintf.c

Lines changed: 66 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -419,10 +419,7 @@ static_assert(SMALL == ('a' ^ 'A'));
419419

420420
enum format_state {
421421
FORMAT_STATE_NONE, /* Just a string part */
422-
FORMAT_STATE_1BYTE = 1, /* char/short/int are their own sizes */
423-
FORMAT_STATE_2BYTE = 2,
424-
FORMAT_STATE_8BYTE = 3,
425-
FORMAT_STATE_4BYTE = 4,
422+
FORMAT_STATE_NUM,
426423
FORMAT_STATE_WIDTH,
427424
FORMAT_STATE_PRECISION,
428425
FORMAT_STATE_CHAR,
@@ -432,8 +429,6 @@ enum format_state {
432429
FORMAT_STATE_INVALID,
433430
};
434431

435-
#define FORMAT_STATE_SIZE(type) (sizeof(type) <= 4 ? sizeof(type) : FORMAT_STATE_8BYTE)
436-
437432
struct printf_spec {
438433
unsigned char flags; /* flags to number() */
439434
unsigned char base; /* number base, 8, 10 or 16 only */
@@ -2523,7 +2518,8 @@ char *pointer(const char *fmt, char *buf, char *end, void *ptr,
25232518

25242519
struct fmt {
25252520
const char *str;
2526-
enum format_state state;
2521+
unsigned char state; // enum format_state
2522+
unsigned char size; // size of numbers
25272523
};
25282524

25292525
#define SPEC_CHAR(x, flag) [(x)-32] = flag
@@ -2638,20 +2634,21 @@ struct fmt format_decode(struct fmt fmt, struct printf_spec *spec)
26382634
qualifier:
26392635
/* Set up default numeric format */
26402636
spec->base = 10;
2641-
fmt.state = FORMAT_STATE_SIZE(int);
2637+
fmt.state = FORMAT_STATE_NUM;
2638+
fmt.size = sizeof(int);
26422639
static const struct format_state {
26432640
unsigned char state;
2644-
unsigned char flags_or_double_state;
2645-
unsigned char modifier;
2641+
unsigned char size;
2642+
unsigned char flags_or_double_size;
26462643
unsigned char base;
26472644
} lookup_state[256] = {
2648-
// Qualifiers
2649-
['l'] = { FORMAT_STATE_SIZE(long), FORMAT_STATE_SIZE(long long), 1 },
2650-
['L'] = { FORMAT_STATE_SIZE(long long), 0, 1 },
2651-
['h'] = { FORMAT_STATE_SIZE(short), FORMAT_STATE_SIZE(char), 1 },
2652-
['H'] = { FORMAT_STATE_SIZE(char), 0, 1 }, // Questionable, historic
2653-
['z'] = { FORMAT_STATE_SIZE(size_t), 0, 1 },
2654-
['t'] = { FORMAT_STATE_SIZE(ptrdiff_t), 0, 1 },
2645+
// Length
2646+
['l'] = { 0, sizeof(long), sizeof(long long) },
2647+
['L'] = { 0, sizeof(long long) },
2648+
['h'] = { 0, sizeof(short), sizeof(char) },
2649+
['H'] = { 0, sizeof(char) }, // Questionable historical
2650+
['z'] = { 0, sizeof(size_t) },
2651+
['t'] = { 0, sizeof(ptrdiff_t) },
26552652

26562653
// Non-numeric formats
26572654
['c'] = { FORMAT_STATE_CHAR },
@@ -2660,12 +2657,12 @@ struct fmt format_decode(struct fmt fmt, struct printf_spec *spec)
26602657
['%'] = { FORMAT_STATE_PERCENT_CHAR },
26612658

26622659
// Numerics
2663-
['o'] = { 0, 0, 0, 8 },
2664-
['x'] = { 0, SMALL, 0, 16 },
2665-
['X'] = { 0, 0, 0, 16 },
2666-
['d'] = { 0, SIGN, 0, 10 },
2667-
['i'] = { 0, SIGN, 0, 10 },
2668-
['u'] = { 0, 0, 0, 10, },
2660+
['o'] = { FORMAT_STATE_NUM, 0, 0, 8 },
2661+
['x'] = { FORMAT_STATE_NUM, 0, SMALL, 16 },
2662+
['X'] = { FORMAT_STATE_NUM, 0, 0, 16 },
2663+
['d'] = { FORMAT_STATE_NUM, 0, SIGN, 10 },
2664+
['i'] = { FORMAT_STATE_NUM, 0, SIGN, 10 },
2665+
['u'] = { FORMAT_STATE_NUM, 0, 0, 10, },
26692666

26702667
/*
26712668
* Since %n poses a greater security risk than
@@ -2675,30 +2672,23 @@ struct fmt format_decode(struct fmt fmt, struct printf_spec *spec)
26752672
};
26762673

26772674
const struct format_state *p = lookup_state + (u8)*fmt.str;
2678-
if (p->modifier) {
2679-
fmt.state = p->state;
2680-
if (p->flags_or_double_state && fmt.str[0] == fmt.str[1]) {
2681-
fmt.state = p->flags_or_double_state;
2675+
if (p->size) {
2676+
fmt.size = p->size;
2677+
if (p->flags_or_double_size && fmt.str[0] == fmt.str[1]) {
2678+
fmt.size = p->flags_or_double_size;
26822679
fmt.str++;
26832680
}
26842681
fmt.str++;
26852682
p = lookup_state + *fmt.str;
2686-
if (unlikely(p->modifier))
2687-
goto invalid;
2688-
}
2689-
if (p->base) {
2690-
spec->base = p->base;
2691-
spec->flags |= p->flags_or_double_state;
2692-
fmt.str++;
2693-
return fmt;
26942683
}
26952684
if (p->state) {
2685+
spec->base = p->base;
2686+
spec->flags |= p->flags_or_double_size;
26962687
fmt.state = p->state;
26972688
fmt.str++;
26982689
return fmt;
26992690
}
27002691

2701-
invalid:
27022692
WARN_ONCE(1, "Please remove unsupported %%%c in format string\n", *fmt.str);
27032693
fmt.state = FORMAT_STATE_INVALID;
27042694
return fmt;
@@ -2768,7 +2758,6 @@ static unsigned long long convert_num_spec(unsigned int val, int size, struct pr
27682758
*/
27692759
int vsnprintf(char *buf, size_t size, const char *fmt_str, va_list args)
27702760
{
2771-
unsigned long long num;
27722761
char *str, *end;
27732762
struct printf_spec spec = {0};
27742763
struct fmt fmt = {
@@ -2808,6 +2797,16 @@ int vsnprintf(char *buf, size_t size, const char *fmt_str, va_list args)
28082797
continue;
28092798
}
28102799

2800+
case FORMAT_STATE_NUM: {
2801+
unsigned long long num;
2802+
if (fmt.size <= sizeof(int))
2803+
num = convert_num_spec(va_arg(args, int), fmt.size, spec);
2804+
else
2805+
num = va_arg(args, long long);
2806+
str = number(str, end, num, spec);
2807+
continue;
2808+
}
2809+
28112810
case FORMAT_STATE_WIDTH:
28122811
set_field_width(&spec, va_arg(args, int));
28132812
continue;
@@ -2856,7 +2855,7 @@ int vsnprintf(char *buf, size_t size, const char *fmt_str, va_list args)
28562855
++str;
28572856
continue;
28582857

2859-
case FORMAT_STATE_INVALID:
2858+
default:
28602859
/*
28612860
* Presumably the arguments passed gcc's type
28622861
* checking, but there is no safe or sane way
@@ -2866,17 +2865,7 @@ int vsnprintf(char *buf, size_t size, const char *fmt_str, va_list args)
28662865
* sync.
28672866
*/
28682867
goto out;
2869-
2870-
case FORMAT_STATE_8BYTE:
2871-
num = va_arg(args, long long);
2872-
break;
2873-
2874-
default:
2875-
num = convert_num_spec(va_arg(args, int), fmt.state, spec);
2876-
break;
28772868
}
2878-
2879-
str = number(str, end, num, spec);
28802869
}
28812870

28822871
out:
@@ -3147,17 +3136,20 @@ int vbin_printf(u32 *bin_buf, size_t size, const char *fmt_str, va_list args)
31473136
fmt.str++;
31483137
break;
31493138

3150-
case FORMAT_STATE_8BYTE:
3151-
save_arg(long long);
3152-
break;
3153-
case FORMAT_STATE_1BYTE:
3154-
save_arg(char);
3155-
break;
3156-
case FORMAT_STATE_2BYTE:
3157-
save_arg(short);
3158-
break;
3159-
default:
3160-
save_arg(int);
3139+
case FORMAT_STATE_NUM:
3140+
switch (fmt.size) {
3141+
case 8:
3142+
save_arg(long long);
3143+
break;
3144+
case 1:
3145+
save_arg(char);
3146+
break;
3147+
case 2:
3148+
save_arg(short);
3149+
break;
3150+
default:
3151+
save_arg(int);
3152+
}
31613153
}
31623154
}
31633155

@@ -3325,18 +3317,21 @@ int bstr_printf(char *buf, size_t size, const char *fmt_str, const u32 *bin_buf)
33253317
case FORMAT_STATE_INVALID:
33263318
goto out;
33273319

3328-
case FORMAT_STATE_8BYTE:
3329-
num = get_arg(long long);
3330-
break;
3331-
case FORMAT_STATE_2BYTE:
3332-
num = convert_num_spec(get_arg(short), fmt.state, spec);
3333-
break;
3334-
case FORMAT_STATE_1BYTE:
3335-
num = convert_num_spec(get_arg(char), fmt.state, spec);
3336-
break;
3337-
default:
3338-
num = convert_num_spec(get_arg(int), fmt.state, spec);
3339-
break;
3320+
case FORMAT_STATE_NUM:
3321+
switch (fmt.size) {
3322+
case 8:
3323+
num = get_arg(long long);
3324+
break;
3325+
case 1:
3326+
num = convert_num_spec(get_arg(char), fmt.size, spec);
3327+
break;
3328+
case 2:
3329+
num = convert_num_spec(get_arg(short), fmt.size, spec);
3330+
break;
3331+
default:
3332+
num = convert_num_spec(get_arg(int), fmt.size, spec);
3333+
break;
3334+
}
33403335
}
33413336

33423337
str = number(str, end, num, spec);

0 commit comments

Comments
 (0)