Browse Source

Clean up and optimize minimal printf()

Simplify negative sign handling, shave off about 60 bytes on x86.
Total footprint on x86 -Os is about 740-750 bytes.
pull/803/head
Sami Vaarala 9 years ago
parent
commit
e095ca011b
  1. 81
      extras/minimal-printf/duk_minimal_printf.c

81
extras/minimal-printf/duk_minimal_printf.c

@ -1,11 +1,16 @@
/* /*
* Minimal sprintf() for Duktape. * Minimal vsnprintf(), snprintf(), sprintf(), and sscanf() for Duktape.
* The supported conversion formats narrowly match what Duktape needs.
*/ */
#include <stdarg.h> /* va_list etc */ #include <stdarg.h> /* va_list etc */
#include <stddef.h> /* size_t */ #include <stddef.h> /* size_t */
#include <stdint.h> /* SIZE_MAX */ #include <stdint.h> /* SIZE_MAX */
/* Write character with bound checking. Offset 'off' is updated regardless
* of whether an actual write is made. This is necessary to satisfy snprintf()
* return value semantics.
*/
#define DUK__WRITE_CHAR(c) do { \ #define DUK__WRITE_CHAR(c) do { \
if (off < size) { \ if (off < size) { \
str[off] = (char) c; \ str[off] = (char) c; \
@ -13,11 +18,22 @@
off++; \ off++; \
} while (0) } while (0)
static const char duk__fmt_nybbles[16] = { /* Digits up to radix 16. */
static const char duk__format_digits[16] = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
}; };
static size_t duk__format_long(char *str, size_t size, size_t off, int fixed_length, char pad, int radix, int neg_sign, unsigned long v) { /* Format an unsigned long with various options. An unsigned long is large
* enough for formatting all supported types.
*/
static size_t duk__format_long(char *str,
size_t size,
size_t off,
int fixed_length,
char pad,
int radix,
int neg_sign,
unsigned long v) {
char buf[24]; /* 2^64 = 18446744073709552000, length 20 */ char buf[24]; /* 2^64 = 18446744073709552000, length 20 */
char *required; char *required;
char *p; char *p;
@ -37,19 +53,24 @@ static size_t duk__format_long(char *str, size_t size, size_t off, int fixed_len
p = buf; p = buf;
do { do {
*p++ = duk__fmt_nybbles[v % radix]; *p++ = duk__format_digits[v % radix];
v /= radix; v /= radix;
} while (v != 0); } while (v != 0);
required = buf + fixed_length; required = buf + fixed_length;
if (pad == (char) '0' && fixed_length > 0 /* handle "%0d" correctly, though insane */) { if (p < required && pad == (char) '0') {
/* Leave space for negative sign. */ /* Zero padding and we didn't reach maximum length: place
if (p < required - neg_sign) { * negative sign at the last position. We can't get here
p = required - neg_sign; * with fixed_length == 0 so that required[-1] is safe.
} *
* Technically we should only do this for 'neg_sign == 1',
* but it's OK to advance the pointer even when that's not
* the case.
*/
p = required - 1;
} }
if (neg_sign) { if (neg_sign) {
*p++ = '-'; *p++ = (char) '-';
} }
if (p < required) { if (p < required) {
p = required; p = required;
@ -65,11 +86,13 @@ static size_t duk__format_long(char *str, size_t size, size_t off, int fixed_len
return off; return off;
} }
/* Parse a pointer. Must parse whatever is produced by '%p' in sprintf(). */
static int duk__parse_pointer(const char *str, void **out) { static int duk__parse_pointer(const char *str, void **out) {
const unsigned char *p; const unsigned char *p;
long val; /* assume void * fits into long */
int count;
unsigned char ch; unsigned char ch;
int count;
int limit;
long val; /* assume void * fits into long */
/* We only need to parse what our minimal printf() produces, so that /* We only need to parse what our minimal printf() produces, so that
* we can check for a '0x' prefix, and assume all hex digits are * we can check for a '0x' prefix, and assume all hex digits are
@ -77,14 +100,12 @@ static int duk__parse_pointer(const char *str, void **out) {
*/ */
p = (const unsigned char *) str; p = (const unsigned char *) str;
if (*p++ != (unsigned char) '0') { if (p[0] != (unsigned char) '0' || p[1] != (unsigned char) 'x') {
return 0;
}
if (*p++ != (unsigned char) 'x') {
return 0; return 0;
} }
p += 2;
for (val = 0, count = 0; count < (int) (sizeof(void *) * 2); count++) { for (val = 0, count = 0, limit = sizeof(void *) * 2; count < limit; count++) {
ch = *p++; ch = *p++;
val <<= 4; val <<= 4;
@ -106,11 +127,14 @@ static int duk__parse_pointer(const char *str, void **out) {
return 1; return 1;
} }
/* Minimal vsnprintf() entry point. */
int duk_minimal_vsnprintf(char *str, size_t size, const char *format, va_list ap) { int duk_minimal_vsnprintf(char *str, size_t size, const char *format, va_list ap) {
size_t off = 0; size_t off = 0;
const char *p; const char *p;
#if 0
const char *p_tmp; const char *p_tmp;
const char *p_fmt_start; const char *p_fmt_start;
#endif
char c; char c;
char pad; char pad;
int fixed_length; int fixed_length;
@ -133,7 +157,9 @@ int duk_minimal_vsnprintf(char *str, size_t size, const char *format, va_list ap
/* Start format sequence. Scan flags and format specifier. */ /* Start format sequence. Scan flags and format specifier. */
#if 0
p_fmt_start = p - 1; p_fmt_start = p - 1;
#endif
is_long = 0; is_long = 0;
pad = ' '; pad = ' ';
fixed_length = 0; fixed_length = 0;
@ -181,7 +207,7 @@ int duk_minimal_vsnprintf(char *str, size_t size, const char *format, va_list ap
break; break;
} else if (c == (char) 'c') { } else if (c == (char) 'c') {
char v; char v;
v = va_arg(ap, int); /* intentionally not 'char' */ v = (char) va_arg(ap, int); /* intentionally not 'char' */
DUK__WRITE_CHAR(v); DUK__WRITE_CHAR(v);
break; break;
} else if (c == (char) 's') { } else if (c == (char) 's') {
@ -210,18 +236,25 @@ int duk_minimal_vsnprintf(char *str, size_t size, const char *format, va_list ap
off = duk__format_long(str, size, off, sizeof(void *) * 2, '0', 16, 0, (unsigned long) v); off = duk__format_long(str, size, off, sizeof(void *) * 2, '0', 16, 0, (unsigned long) v);
break; break;
} else { } else {
/* Unrecognized, just copy verbatim. */ /* Unrecognized, bail out early. We could also emit the format
* specifier verbatim, but it'd be a waste of footprint because
* this case should never happen in practice.
*/
#if 0 #if 0
DUK__WRITE_CHAR('!'); DUK__WRITE_CHAR('!');
#endif #endif
#if 0
for (p_tmp = p_fmt_start; p_tmp != p; p_tmp++) { for (p_tmp = p_fmt_start; p_tmp != p; p_tmp++) {
DUK__WRITE_CHAR(*p_tmp); DUK__WRITE_CHAR(*p_tmp);
} }
break; break;
#endif
goto finish;
} }
} }
} }
finish:
if (off < size) { if (off < size) {
str[off] = (char) 0; /* No increment for 'off', not counted in return value. */ str[off] = (char) 0; /* No increment for 'off', not counted in return value. */
} else if (size > 0) { } else if (size > 0) {
@ -232,31 +265,39 @@ int duk_minimal_vsnprintf(char *str, size_t size, const char *format, va_list ap
return (int) off; return (int) off;
} }
/* Minimal snprintf() entry point. */
int duk_minimal_snprintf(char *str, size_t size, const char *format, ...) { int duk_minimal_snprintf(char *str, size_t size, const char *format, ...) {
va_list ap; va_list ap;
int ret; int ret;
va_start(ap, format); va_start(ap, format);
ret = duk_minimal_vsnprintf(str, size, format, ap); ret = duk_minimal_vsnprintf(str, size, format, ap);
va_end(ap); va_end(ap);
return ret; return ret;
} }
/* Minimal sprintf() entry point. */
int duk_minimal_sprintf(char *str, const char *format, ...) { int duk_minimal_sprintf(char *str, const char *format, ...) {
va_list ap; va_list ap;
int ret; int ret;
va_start(ap, format); va_start(ap, format);
ret = duk_minimal_vsnprintf(str, SIZE_MAX, format, ap); ret = duk_minimal_vsnprintf(str, SIZE_MAX, format, ap);
va_end(ap); va_end(ap);
return ret; return ret;
} }
/* Minimal sscanf() entry point. */
int duk_minimal_sscanf(const char *str, const char *format, ...) { int duk_minimal_sscanf(const char *str, const char *format, ...) {
va_list ap; va_list ap;
int ret; int ret;
void **out; void **out;
/* Only the exact "%p" format is supported. */ /* Only the exact "%p" format is supported. */
if (format[0] != (char) '%' || format[1] != (char) 'p' || if (format[0] != (char) '%' ||
format[1] != (char) 'p' ||
format[2] != (char) 0) { format[2] != (char) 0) {
} }

Loading…
Cancel
Save