Commit 3a078045 authored by Alessandro Rubini's avatar Alessandro Rubini

pp_printf: use new upstream master e17d874

This picks the following commits (most recent on top):

   e17d874 README: update for the new code base
   d391e62 added 64-bit example/test
   670b5e8 Makefile: support 64-bit option; add div64.c
   c2384cd vsprintf-full: offer 64-bit printing as an option
   220b19e vsprintf-full: simplify a little
   526daa6 vsprintf-full: remove unsed code for %p printing
   1a28825 vsprintf-full: prevent a warning when a pointer is 16 bits

We are using 64-bit prints currently, but wrpc-sw does.
Signed-off-by: Alessandro Rubini's avatarAlessandro Rubini <rubini@gnudd.com>
parent 3ef53ace
*.o
example-printf
\ No newline at end of file
example-printf
example-printf64
......@@ -20,12 +20,18 @@ obj-$(CONFIG_PRINTF_XINT) += vsprintf-xint.o
# set full as a default if nothing is selected
obj-y ?= vsprintf-full.o
# If you want to pick the local div64.c, define this as "y"
obj-$(CONFIG_PRINTF_LOCAL_DIV64) += div64.o
obj-y += printf.o
# There is a static variable in pp-printf.c to accumulate stuff
CONFIG_PRINT_BUFSIZE ?= 256
CFLAGS += -DCONFIG_PRINT_BUFSIZE=$(CONFIG_PRINT_BUFSIZE)
ifdef CONFIG_PRINTF_64BIT
CFLAGS += -DCONFIG_PRINTF_64BIT
endif
# Targets. You may want to make them different in your package
......@@ -37,6 +43,9 @@ pp-printf.o: $(obj-y)
example-printf: example-printf.c pp-printf.o
$(CC) $(CFLAGS) $^ -o $@
# build a special example/test for 64-bit prints (not built by default)
example-printf64: example-printf64.o pp-printf.o div64.o
.c.o:
$(CC) -c $(CFLAGS) $< -o $@
......
......@@ -16,6 +16,7 @@ The printf engine, vsprintf(), comes in four flavours:
In summary:
- the "full" version is a normal printf (GPL2, from older Linux kernel)
(as a build-time option, it supports int64_t items, as "ll" or "L")
- the "xint" version accepts all formats and prints hex and int only
......@@ -42,6 +43,10 @@ used in the Linux kernel. It is licensed according to the GNU GPL
version 2. It includes all formats and prefixes and so on. It is
clearly bugless because everybody is using it.
In July 2014 I made some changes, which includes adding 64-bit items,
but the feature is disabled by default. There is a whole section later
in this file about this.
It is selected at compile time by setting the make variable
"CONFIG_PRINTF_FULL" to "y". You can do that in the environment,
or use Kconfig in your application.
......@@ -72,6 +77,10 @@ Result (as you see, format modifiers are respected):
Footprint: 1400-3200 bytes, plus 100-400 bytes for the frontend.
(With the 2014 changes the footprint is a few hundred bytes less, but
I lacked the time to make comprehensive builds; if you enable 64-bit
support, you'll get a size impact of a few hunderd bytes: for ARM,
around 200 bytes in vsprintf-full plus 200 for div64.o).
The xint implementation in detail
================================
......@@ -164,10 +173,83 @@ the constant strings in .rodata. I don't support this in the package,
though, and I discourage from doing it, for the usual
preprocessor-related reasons.
int64_t support
===============
vsprintf-full.c now supports printing 64-bit items. It works in all
platforms, including the 16-bit AVR CPU. The feature is disabled by
default because it has run-time impact (all integer conversions are
performed using 64-bit division.
Moreover, 64-bit supports requires 64-bit division. As customary in
the kernel/bootloader world we avoid picking the libgcc
implementation, and rely on the smaller __div64_32() function,
instead, which only supports a 32-bit divisor and thus a 32-bit
remainder; but this is all we need. The function is available in
"lib64.c", by Bernardo Innocenti, which is used in the kernel and many
other projects.
So, to enable 64-bit support in pp-printf, please use
CONFIG_PRINTF_64BIT=y
in your environment or command-line of make. If your project uses
Kconfig, you'll rely on it instead (I'm running pp-printf in 3
Kconfig-based projects and it works great).
If you already have __div64_32() in your project, you are done:
vsprintf-full.o will have an undefined symbol, that your final link
will resolve. If you miss the function, you can use the local
version. To do that, set
CONFIG_PRINTF_LOCAL_DIV64=y
in your environment or the command line of make.
The test program for 64-bit formats is not built by default. To build
it, you should
make CONFIG_PRINTF_64BIT=y example-printf64
This automatically picks div64.o: you'll get a link error if you also
set CONFIG_PRINTF_LOCAL_DIV64=y. This is not a problem as the test
is meant to be run locally, only once or so.
The test prints one thousand, one million and so on using the various
formats, ending with the maximum positive and maximum negative::
positive: 1000
negative: -1000
neg-unsigned: 18446744073709550616
pos-hex: 0x00000000000003e8
pos-HEX: 0x00000000000003E8
neg-hex: 0xfffffffffffffc18
[...]
positive: 1000000000000000000
negative: -1000000000000000000
neg-unsigned: 17446744073709551616
pos-hex: 0x0de0b6b3a7640000
pos-HEX: 0x0DE0B6B3A7640000
neg-hex: 0xf21f494c589c0000
max positive: 9223372036854775807
max negative: -9223372036854775808
You may want to verify with your own target platform, before using the
code in production (you can avoid that if you trust me, but I wouldn't
blindly trust programmers, in general).
Footprint of the various implementations
========================================
NOTE: these figures refer to the 2012 master. The "full"
implementation is now a few hundred bytes smaller.
Also, I made no measures for the 64-bit version, which adds a few
hundred bytes (and run-time overhead of every integer print).
This table excludes the static buffer (256 in .bss by default) and
only lists the code size (command "size", column "text"), compiled
with -Os as for this Makefile.
......
/* This file in ppsi is a subset of lib/div64.c in Linux source code */
/*
* Copyright (C) 2003 Bernardo Innocenti <bernie@develer.com>
*
* Based on former do_div() implementation from asm-parisc/div64.h:
* Copyright (C) 1999 Hewlett-Packard Co
* Copyright (C) 1999 David Mosberger-Tang <davidm@hpl.hp.com>
*
*
* Generic C version of 64bit/32bit division and modulo, with
* 64bit result and 32bit remainder.
*
* The fast case for (n>>32 == 0) is handled inline by do_div().
*
* Code generated for this function might be very inefficient
* for some CPUs. __div64_32() can be overridden by linking arch-specific
* assembly versions such as arch/ppc/lib/div64.S and arch/sh/lib/div64.S.
*/
#include <stdint.h>
uint32_t __div64_32(uint64_t *n, uint32_t base)
{
uint64_t rem = *n;
uint64_t b = base;
uint64_t res, d = 1;
uint32_t high = rem >> 32;
/* Reduce the thing a bit first */
res = 0;
if (high >= base) {
high /= base;
res = (uint64_t) high << 32;
rem -= (uint64_t) (high*base) << 32;
}
while ((int64_t)b > 0 && b < rem) {
b = b+b;
d = d+d;
}
do {
if (rem >= b) {
rem -= b;
res += d;
}
b >>= 1;
d >>= 1;
} while (d);
*n = res;
return rem;
}
#include <stdint.h>
#include <pp-printf.h>
int main(int argc, char **argv)
{
long long ll; /* I'd use "int64_t" but gcc wars about formats */
for (ll = 1000; ll < (1LL << 61); ll *= 1000) {
pp_printf("positive: %20lli\n", ll);
pp_printf("negative: %20lli\n", -ll);
pp_printf("neg-unsigned: %20llu\n", -ll);
pp_printf("pos-hex: 0x%016llx\n", ll);
pp_printf("pos-HEX: 0x%016llX\n", ll);
pp_printf("neg-hex: 0x%016Lx\n", -ll);
pp_printf("\n");
}
ll = (1LL <<63) - 1;
pp_printf("max positive: %20lli\n", ll);
ll = (1LL << 63);
pp_printf("max negative: %20lli\n", ll);
return 0;
}
......@@ -18,38 +18,55 @@
/* BEGIN OF HACKS */
#include <pp-printf.h>
/* <ctype.h> */
static inline int isdigit(int c)
{
return c >= '0' && c <= '9';
}
static inline int islower(int c)
{
return c >= 'a' && c <= 'z';
}
static inline int isupper(int c)
{
return c >= 'A' && c <= 'Z';
}
static inline int isalpha(int c)
{
return islower(c) || isupper(c);
}
static inline int isalnum(int c)
{
return isalpha(c) || isdigit(c);
}
/* <linux/types.h> -- but if we typedef we get redefined type when hosted */
#define u8 uint8_t
#define size_t unsigned long
#define ptrdiff_t unsigned long
#define noinline __attribute__((noinline))
/*
* We now have optional 64-bit support. It depends on __div64_32.
* The suggested implementaion is the one by Bernardo Innocenti, found
* in asm-generic in the kernel -- ARub
*/
#ifdef CONFIG_PRINTF_64BIT
#define NUMBER_TYPE uint64_t
#define SIGNED_NUMBER_TYPE int64_t
extern uint32_t __div64_32(uint64_t *n, uint32_t base);
/* The unnecessary pointer compare is there
* to check for type safety (n must be 64bit)
*/
#define do_div(n,base) ({ \
uint32_t __base = (base); \
uint32_t __rem; \
(void)(((typeof((n)) *)0) == ((uint64_t *)0)); \
if (((n) >> 32) == 0) { \
__rem = (uint32_t)(n) % __base; \
(n) = (uint32_t)(n) / __base; \
} else \
__rem = __div64_32(&(n), __base); \
__rem; \
})
#else /* 32 bits (or native 64 bits): a reduced version of above */
#define NUMBER_TYPE unsigned long
#define SIGNED_NUMBER_TYPE signed long
#define do_div(n,base) ({ \
uint32_t __base = (base); \
uint32_t __rem; \
(void)(((typeof((n)) *)0) == ((unsigned long *)0)); \
__rem = (n) % __base; \
(n) = (n) / __base; \
__rem; \
})
#endif /* CONFIG_PRINTF_64BIT */
/* END OF HACKS */
const char hex_asc[] = "0123456789abcdef";
......@@ -76,106 +93,6 @@ static int skip_atoi(const char **s)
return i;
}
/* Decimal conversion is by far the most typical, and is used
* for /proc and /sys data. This directly impacts e.g. top performance
* with many processes running. We optimize it for speed
* using code from
* http://www.cs.uiowa.edu/~jones/bcd/decimal.html
* (with permission from the author, Douglas W. Jones). */
/* Formats correctly any integer in [0,99999].
* Outputs from one to five digits depending on input.
* On i386 gcc 4.1.2 -O2: ~250 bytes of code. */
static char* put_dec_trunc(char *buf, unsigned q)
{
unsigned d3, d2, d1, d0;
d1 = (q>>4) & 0xf;
d2 = (q>>8) & 0xf;
d3 = (q>>12);
d0 = 6*(d3 + d2 + d1) + (q & 0xf);
q = (d0 * 0xcd) >> 11;
d0 = d0 - 10*q;
*buf++ = d0 + '0'; /* least significant digit */
d1 = q + 9*d3 + 5*d2 + d1;
if (d1 != 0) {
q = (d1 * 0xcd) >> 11;
d1 = d1 - 10*q;
*buf++ = d1 + '0'; /* next digit */
d2 = q + 2*d2;
if ((d2 != 0) || (d3 != 0)) {
q = (d2 * 0xd) >> 7;
d2 = d2 - 10*q;
*buf++ = d2 + '0'; /* next digit */
d3 = q + 4*d3;
if (d3 != 0) {
q = (d3 * 0xcd) >> 11;
d3 = d3 - 10*q;
*buf++ = d3 + '0'; /* next digit */
if (q != 0)
*buf++ = q + '0'; /* most sign. digit */
}
}
}
return buf;
}
/* Same with if's removed. Always emits five digits */
static char* put_dec_full(char *buf, unsigned q)
{
/* BTW, if q is in [0,9999], 8-bit ints will be enough, */
/* but anyway, gcc produces better code with full-sized ints */
unsigned d3, d2, d1, d0;
d1 = (q>>4) & 0xf;
d2 = (q>>8) & 0xf;
d3 = (q>>12);
/*
* Possible ways to approx. divide by 10
* gcc -O2 replaces multiply with shifts and adds
* (x * 0xcd) >> 11: 11001101 - shorter code than * 0x67 (on i386)
* (x * 0x67) >> 10: 1100111
* (x * 0x34) >> 9: 110100 - same
* (x * 0x1a) >> 8: 11010 - same
* (x * 0x0d) >> 7: 1101 - same, shortest code (on i386)
*/
d0 = 6*(d3 + d2 + d1) + (q & 0xf);
q = (d0 * 0xcd) >> 11;
d0 = d0 - 10*q;
*buf++ = d0 + '0';
d1 = q + 9*d3 + 5*d2 + d1;
q = (d1 * 0xcd) >> 11;
d1 = d1 - 10*q;
*buf++ = d1 + '0';
d2 = q + 2*d2;
q = (d2 * 0xd) >> 7;
d2 = d2 - 10*q;
*buf++ = d2 + '0';
d3 = q + 4*d3;
q = (d3 * 0xcd) >> 11; /* - shorter code */
/* q = (d3 * 0x67) >> 10; - would also work */
d3 = d3 - 10*q;
*buf++ = d3 + '0';
*buf++ = q + '0';
return buf;
}
/* No inlining helps gcc to use registers better */
static noinline char* put_dec(char *buf, unsigned long num)
{
while (1) {
unsigned rem;
if (num < 100000)
return put_dec_trunc(buf, num);
rem = num % 100000;
num /= 100000;
buf = put_dec_full(buf, rem);
}
}
#define ZEROPAD 1 /* pad with zero */
#define SIGN 2 /* unsigned/signed long */
#define PLUS 4 /* show plus */
......@@ -184,7 +101,7 @@ static noinline char* put_dec(char *buf, unsigned long num)
#define SMALL 32 /* Must be 32 == 0x20 */
#define SPECIAL 64 /* 0x */
static char *number(char *buf, unsigned long num, int base, int size, int precision, int type)
static char *number(char *buf, NUMBER_TYPE num, int base, int size, int precision, int type)
{
/* we are called with base 8, 10 or 16, only, thus don't need "G..." */
static const char digits[16] = "0123456789ABCDEF"; /* "GHIJKLMNOPQRSTUVWXYZ"; */
......@@ -202,9 +119,9 @@ static char *number(char *buf, unsigned long num, int base, int size, int precis
type &= ~ZEROPAD;
sign = 0;
if (type & SIGN) {
if ((signed long) num < 0) {
if ((SIGNED_NUMBER_TYPE) num < 0) {
sign = '-';
num = - (signed long) num;
num = - (SIGNED_NUMBER_TYPE) num;
size--;
} else if (type & PLUS) {
sign = '+';
......@@ -224,22 +141,10 @@ static char *number(char *buf, unsigned long num, int base, int size, int precis
i = 0;
if (num == 0)
tmp[i++] = '0';
/* Generic code, for any base:
/* Generic code, for any base: */
else do {
tmp[i++] = (digits[do_div(num,base)] | locase);
} while (num != 0);
*/
else if (base != 10) { /* 8 or 16 */
int mask = base - 1;
int shift = 3;
if (base == 16) shift = 4;
do {
tmp[i++] = (digits[((unsigned char)num) & mask] | locase);
num >>= shift;
} while (num);
} else { /* base 10 */
i = put_dec(tmp, num) - tmp;
}
/* printing 100 using %2d gives "100", not "00" */
if (i > precision)
......@@ -295,112 +200,28 @@ static char *string(char *buf, char *s, int field_width, int precision, int flag
return buf;
}
#ifdef CONFIG_CMD_NET
static char *mac_address_string(char *buf, u8 *addr, int field_width,
int precision, int flags)
{
char mac_addr[6 * 3]; /* (6 * 2 hex digits), 5 colons and trailing zero */
char *p = mac_addr;
int i;
for (i = 0; i < 6; i++) {
p = pack_hex_byte(p, addr[i]);
if (!(flags & SPECIAL) && i != 5)
*p++ = ':';
}
*p = '\0';
return string(buf, mac_addr, field_width, precision, flags & ~SPECIAL);
}
static char *ip6_addr_string(char *buf, u8 *addr, int field_width,
int precision, int flags)
{
char ip6_addr[8 * 5]; /* (8 * 4 hex digits), 7 colons and trailing zero */
char *p = ip6_addr;
int i;
for (i = 0; i < 8; i++) {
p = pack_hex_byte(p, addr[2 * i]);
p = pack_hex_byte(p, addr[2 * i + 1]);
if (!(flags & SPECIAL) && i != 7)
*p++ = ':';
}
*p = '\0';
return string(buf, ip6_addr, field_width, precision, flags & ~SPECIAL);
}
static char *ip4_addr_string(char *buf, u8 *addr, int field_width,
int precision, int flags)
{
char ip4_addr[4 * 4]; /* (4 * 3 decimal digits), 3 dots and trailing zero */
char temp[3]; /* hold each IP quad in reverse order */
char *p = ip4_addr;
int i, digits;
for (i = 0; i < 4; i++) {
digits = put_dec_trunc(temp, addr[i]) - temp;
/* reverse the digits in the quad */
while (digits--)
*p++ = temp[digits];
if (i != 3)
*p++ = '.';
}
*p = '\0';
return string(buf, ip4_addr, field_width, precision, flags & ~SPECIAL);
}
#endif
/*
* Show a '%p' thing. A kernel extension is that the '%p' is followed
* by an extra set of alphanumeric characters that are extended format
* specifiers.
*
* Right now we handle:
*
* - 'M' For a 6-byte MAC address, it prints the address in the
* usual colon-separated hex notation
* - 'I' [46] for IPv4/IPv6 addresses printed in the usual way (dot-separated
* decimal for v4 and colon separated network-order 16 bit hex for v6)
* - 'i' [46] for 'raw' IPv4/IPv6 addresses, IPv6 omits the colons, IPv4 is
* currently the same
*
* Note: The difference between 'S' and 'F' is that on ia64 and ppc64
* function pointers are really function descriptors, which contain a
* pointer to the real address.
* -- Such extension is removed in pp_printf
*/
static char *pointer(const char *fmt, char *buf, void *ptr, int field_width, int precision, int flags)
{
unsigned long plong;
if (!ptr)
return string(buf, "(null)", field_width, precision, flags);
#ifdef CONFIG_CMD_NET
switch (*fmt) {
case 'm':
flags |= SPECIAL;
/* Fallthrough */
case 'M':
return mac_address_string(buf, ptr, field_width, precision, flags);
case 'i':
flags |= SPECIAL;
/* Fallthrough */
case 'I':
if (fmt[1] == '6')
return ip6_addr_string(buf, ptr, field_width, precision, flags);
if (fmt[1] == '4')
return ip4_addr_string(buf, ptr, field_width, precision, flags);
flags &= ~SPECIAL;
break;
}
#endif
flags |= SMALL;
if (field_width == -1) {
field_width = 2*sizeof(void *);
flags |= ZEROPAD;
}
return number(buf, (unsigned long) ptr, 16, field_width, precision, flags);
plong = (intptr_t)ptr;
return number(buf, plong, 16, field_width, precision, flags);
}
/**
......@@ -422,7 +243,7 @@ static char *pointer(const char *fmt, char *buf, void *ptr, int field_width, int
*/
int pp_vsprintf(char *buf, const char *fmt, va_list args)
{
unsigned long num;
NUMBER_TYPE num;
int base;
char *str;
......@@ -518,9 +339,6 @@ int pp_vsprintf(char *buf, const char *fmt, va_list args)
str = pointer(fmt+1, str,
va_arg(args, void *),
field_width, precision, flags);
/* Skip all alphanumeric pointer suffixes */
while (isalnum(fmt[1]))
fmt++;
continue;
case 'n':
......@@ -562,8 +380,8 @@ int pp_vsprintf(char *buf, const char *fmt, va_list args)
--fmt;
continue;
}
#ifdef CONFIG_SYS_64BIT_VSPRINTF
if (qualifier == 'L') /* "quad" for 64 bit variables */
#ifdef CONFIG_PRINTF_64BIT
if (qualifier == 'L')
num = va_arg(args, unsigned long long);
else
#endif
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment