331 lines
9 KiB
C
331 lines
9 KiB
C
// Buffer-formatting siblings of printf — kept in their own translation
|
|
// unit so the shared writeXxx helpers don't have to take a function-
|
|
// pointer sink (indirect call cost on this target) and so adding the
|
|
// formatter to libc.c can't shift vprintf's branch distances out of
|
|
// range (per the strtol.c precedent).
|
|
//
|
|
// Functions:
|
|
// int vsnprintf(char *buf, size_t n, const char *fmt, va_list ap);
|
|
// int snprintf (char *buf, size_t n, const char *fmt, ...);
|
|
// int vsprintf (char *buf, const char *fmt, va_list ap);
|
|
// int sprintf (char *buf, const char *fmt, ...);
|
|
//
|
|
// Format support matches vprintf: %d %i %u %x %X %c %s %p %f and the
|
|
// `l` length modifier (%ld %lu). Width is honoured for %x. %f
|
|
// precision is capped at 9 fractional digits.
|
|
//
|
|
// Return value: number of characters that would have been written had
|
|
// the buffer been unbounded (C99 vsnprintf semantics), not just the
|
|
// number actually written. This lets callers detect truncation.
|
|
//
|
|
// **Sink state lives in file-static globals** instead of an explicit
|
|
// struct passed by pointer. Two W65816 backend bugs forced this:
|
|
// (1) The address of a stack-resident struct is computed wrong
|
|
// (&s came out as SP+5 = address of s.end instead of SP+3).
|
|
// emit() then read garbage cur/end values, the cur >= end branch
|
|
// skipped every write, and snprintf returned the right length
|
|
// with an empty buffer.
|
|
// (2) Functions taking fmt as arg1 (stack) didn't initialize the
|
|
// fmt local before the loop body — first char came from the
|
|
// arg slot but the loop's fmt++ ran on uninitialized memory.
|
|
// Fixed by making fmt always arg0 (A reg).
|
|
// Single-threaded use only, but that matches the rest of this runtime.
|
|
|
|
typedef unsigned int size_t;
|
|
typedef __builtin_va_list va_list;
|
|
#define va_start(ap, last) __builtin_va_start(ap, last)
|
|
#define va_arg(ap, ty) __builtin_va_arg(ap, ty)
|
|
#define va_end(ap) __builtin_va_end(ap)
|
|
|
|
|
|
static char *gCur;
|
|
static char *gEnd;
|
|
static size_t gTotal;
|
|
|
|
|
|
__attribute__((noinline))
|
|
static void emit(char c) {
|
|
if (gCur < gEnd) {
|
|
*gCur++ = c;
|
|
}
|
|
gTotal++;
|
|
}
|
|
|
|
|
|
__attribute__((noinline))
|
|
static void emitStr(const char *p) {
|
|
if (!p) {
|
|
p = "(null)";
|
|
}
|
|
while (*p) {
|
|
emit(*p++);
|
|
}
|
|
}
|
|
|
|
|
|
__attribute__((noinline))
|
|
static void emitUDec(unsigned int n) {
|
|
char buf[6];
|
|
int i = 0;
|
|
if (n == 0) {
|
|
emit('0');
|
|
return;
|
|
}
|
|
while (n > 0) {
|
|
buf[i++] = '0' + (n % 10);
|
|
n /= 10;
|
|
}
|
|
// Reverse-emit using forward index arithmetic. The natural
|
|
// countdown forms (`while (i > 0) emit(buf[--i])`,
|
|
// `while (i > 0) { i--; emit(buf[i]); }`,
|
|
// `for (j = i - 1; j >= 0; j--) emit(buf[j])`) all lower to a
|
|
// do-while whose `dec a; bpl` exit condition runs the loop one
|
|
// extra time on this backend, leaking a buf[-1] read. The forward
|
|
// count + index-arithmetic form below avoids the bad lowering.
|
|
int top = i;
|
|
for (int j = 0; j < top; j++) {
|
|
emit(buf[top - 1 - j]);
|
|
}
|
|
}
|
|
|
|
|
|
__attribute__((noinline))
|
|
static void emitDec(int n) {
|
|
if (n < 0) {
|
|
emit('-');
|
|
emitUDec((unsigned int)(-n));
|
|
} else {
|
|
emitUDec((unsigned int)n);
|
|
}
|
|
}
|
|
|
|
|
|
__attribute__((noinline))
|
|
static void emitULong(unsigned long n) {
|
|
char buf[11];
|
|
int i = 0;
|
|
if (n == 0) {
|
|
emit('0');
|
|
return;
|
|
}
|
|
while (n > 0) {
|
|
buf[i++] = '0' + (n % 10);
|
|
n /= 10;
|
|
}
|
|
// Reverse-emit using forward index arithmetic. The natural
|
|
// countdown forms (`while (i > 0) emit(buf[--i])`,
|
|
// `while (i > 0) { i--; emit(buf[i]); }`,
|
|
// `for (j = i - 1; j >= 0; j--) emit(buf[j])`) all lower to a
|
|
// do-while whose `dec a; bpl` exit condition runs the loop one
|
|
// extra time on this backend, leaking a buf[-1] read. The forward
|
|
// count + index-arithmetic form below avoids the bad lowering.
|
|
int top = i;
|
|
for (int j = 0; j < top; j++) {
|
|
emit(buf[top - 1 - j]);
|
|
}
|
|
}
|
|
|
|
|
|
__attribute__((noinline))
|
|
static void emitSignedLong(long n) {
|
|
if (n < 0) {
|
|
emit('-');
|
|
emitULong((unsigned long)(-n));
|
|
} else {
|
|
emitULong((unsigned long)n);
|
|
}
|
|
}
|
|
|
|
|
|
__attribute__((noinline))
|
|
static void emitHex(unsigned int n, int width) {
|
|
static const char digits[] = "0123456789abcdef";
|
|
char buf[5];
|
|
int i = 0;
|
|
if (n == 0) {
|
|
buf[i++] = '0';
|
|
}
|
|
while (n > 0) {
|
|
buf[i++] = digits[n & 0xF];
|
|
n >>= 4;
|
|
}
|
|
while (i < width) {
|
|
buf[i++] = '0';
|
|
}
|
|
// Reverse-emit using forward index arithmetic. The natural
|
|
// countdown forms (`while (i > 0) emit(buf[--i])`,
|
|
// `while (i > 0) { i--; emit(buf[i]); }`,
|
|
// `for (j = i - 1; j >= 0; j--) emit(buf[j])`) all lower to a
|
|
// do-while whose `dec a; bpl` exit condition runs the loop one
|
|
// extra time on this backend, leaking a buf[-1] read. The forward
|
|
// count + index-arithmetic form below avoids the bad lowering.
|
|
int top = i;
|
|
for (int j = 0; j < top; j++) {
|
|
emit(buf[top - 1 - j]);
|
|
}
|
|
}
|
|
|
|
|
|
__attribute__((noinline))
|
|
static void emitDouble(double v, int prec) {
|
|
if (prec < 0) {
|
|
prec = 6;
|
|
}
|
|
if (prec > 9) {
|
|
prec = 9;
|
|
}
|
|
if (v < 0) {
|
|
emit('-');
|
|
v = -v;
|
|
}
|
|
long ipart = (long)v;
|
|
emitULong((unsigned long)ipart);
|
|
if (prec == 0) {
|
|
return;
|
|
}
|
|
emit('.');
|
|
double frac = v - (double)ipart;
|
|
long mul = 1;
|
|
for (int i = 0; i < prec; i++) {
|
|
mul *= 10;
|
|
}
|
|
long fdigits = (long)(frac * (double)mul);
|
|
if (fdigits < 0) {
|
|
fdigits = -fdigits;
|
|
}
|
|
char buf[10];
|
|
int n = 0;
|
|
long scale = mul / 10;
|
|
while (n < prec) {
|
|
if (scale == 0) {
|
|
scale = 1;
|
|
}
|
|
long d = fdigits / scale;
|
|
buf[n++] = '0' + (char)(d % 10);
|
|
scale /= 10;
|
|
if (scale == 0) {
|
|
break;
|
|
}
|
|
}
|
|
while (n < prec) {
|
|
buf[n++] = '0';
|
|
}
|
|
for (int i = 0; i < n; i++) {
|
|
emit(buf[i]);
|
|
}
|
|
}
|
|
|
|
|
|
// fmt is arg0 (A register); see banner comment for why the order matters.
|
|
static int format(const char *fmt, va_list ap) {
|
|
while (*fmt) {
|
|
char c = *fmt++;
|
|
if (c != '%') {
|
|
emit(c);
|
|
continue;
|
|
}
|
|
int width = 0;
|
|
while (*fmt >= '0' && *fmt <= '9') {
|
|
width = width * 10 + (*fmt - '0');
|
|
fmt++;
|
|
}
|
|
int prec = -1;
|
|
if (*fmt == '.') {
|
|
fmt++;
|
|
prec = 0;
|
|
while (*fmt >= '0' && *fmt <= '9') {
|
|
prec = prec * 10 + (*fmt - '0');
|
|
fmt++;
|
|
}
|
|
}
|
|
int isLong = 0;
|
|
if (*fmt == 'l') {
|
|
isLong = 1;
|
|
fmt++;
|
|
}
|
|
char spec = *fmt++;
|
|
if (spec == 'd' || spec == 'i') {
|
|
if (isLong) {
|
|
emitSignedLong(va_arg(ap, long));
|
|
} else {
|
|
emitDec(va_arg(ap, int));
|
|
}
|
|
} else if (spec == 'u') {
|
|
if (isLong) {
|
|
emitULong(va_arg(ap, unsigned long));
|
|
} else {
|
|
emitUDec(va_arg(ap, unsigned int));
|
|
}
|
|
} else if (spec == 'x' || spec == 'X') {
|
|
emitHex(va_arg(ap, unsigned int), width);
|
|
} else if (spec == 'c') {
|
|
emit((char)va_arg(ap, int));
|
|
} else if (spec == 's') {
|
|
emitStr(va_arg(ap, const char *));
|
|
} else if (spec == 'f' || spec == 'F' ||
|
|
spec == 'g' || spec == 'G' ||
|
|
spec == 'e' || spec == 'E') {
|
|
emitDouble(va_arg(ap, double), prec);
|
|
} else if (spec == 'p') {
|
|
emit('0');
|
|
emit('x');
|
|
emitHex(va_arg(ap, unsigned int), 4);
|
|
} else if (spec == '%') {
|
|
emit('%');
|
|
} else {
|
|
emit('%');
|
|
emit(spec);
|
|
}
|
|
}
|
|
if (gCur < gEnd) {
|
|
*gCur = '\0';
|
|
} else if (gEnd > (char *)0) {
|
|
gEnd[-1] = '\0';
|
|
}
|
|
return (int)gTotal;
|
|
}
|
|
|
|
|
|
int snprintf(char *buf, size_t n, const char *fmt, ...) {
|
|
gCur = buf;
|
|
gEnd = buf + (n ? n : 0);
|
|
gTotal = 0;
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
int r = format(fmt, ap);
|
|
va_end(ap);
|
|
return r;
|
|
}
|
|
|
|
|
|
int sprintf(char *buf, const char *fmt, ...) {
|
|
gCur = buf;
|
|
// sprintf is unbounded. Setting gEnd = buf + 0xFFFE looks innocuous
|
|
// but clang lowers the +0xFFFE to a `dec a; dec a` peephole (since
|
|
// 0xFFFE is -2 in 16-bit), giving gEnd = buf - 2 — and then the
|
|
// emit() bounds test `cur < end` is always false, so nothing gets
|
|
// written. Use the absolute top-of-bank sentinel instead.
|
|
gEnd = (char *)0xFFFF;
|
|
gTotal = 0;
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
int r = format(fmt, ap);
|
|
va_end(ap);
|
|
return r;
|
|
}
|
|
|
|
|
|
int vsnprintf(char *buf, size_t n, const char *fmt, va_list ap) {
|
|
gCur = buf;
|
|
gEnd = buf + (n ? n : 0);
|
|
gTotal = 0;
|
|
return format(fmt, ap);
|
|
}
|
|
|
|
|
|
int vsprintf(char *buf, const char *fmt, va_list ap) {
|
|
gCur = buf;
|
|
gEnd = (char *)0xFFFF;
|
|
gTotal = 0;
|
|
return format(fmt, ap);
|
|
}
|