65816-llvm-mos/runtime/src/snprintf.c
Scott Duensing 09f7405362 Updates
2026-06-03 16:08:42 -05:00

792 lines
28 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:
// conversions %d %i %u %x %X %o %c %s %p %f %F %e %E %g %G %n %%
// flags - + (space) # 0
// width decimal or `*` (from va_arg int)
// precision .N or .*
// length hh, h, l, ll, j, z, t
//
// Floats are soft-double (double + float promote-to-double via va_arg);
// precision capped at 9 fractional digits. Hex-float (%a / %A) is
// fully supported: IEEE-754 double bits decoded into 4 u16 words (no
// i64 shift libcalls), emitted as `0x1.{13-hex}p{signed-decimal}` with
// glibc-style trailing-zero stripping when precision is unspecified.
// Subnormals canonicalize as `0x0.{mantissa}p-1022`. Inf/NaN parity
// across %f / %F / %g / %G / %e / %E / %a / %A. Multibyte / wide-char
// specifiers (%lc, %ls) fall through and emit `%lc` literally.
//
// 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 (gCur/gEnd/gTotal) rather
// than a per-call context. Single-threaded use only, but that matches
// the rest of this runtime.
typedef unsigned long 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)
// Unbounded sink sentinel used by sprintf/vsprintf. Setting gEnd to
// `buf + 0xFFFE` looks innocuous but clang lowers the +0xFFFE to a
// `dec a; dec a` peephole (0xFFFE is -2 in 16-bit), giving gEnd =
// buf - 2 -- the `cur < end` bounds test then always fails. Use the
// absolute top-of-bank sentinel instead.
#define SPRINTF_END_SENTINEL ((char *)0xFFFF)
static char *gCur;
static char *gEnd;
static size_t gTotal;
static void emit(char c) {
if (gCur < gEnd) {
*gCur++ = c;
}
gTotal++;
}
static void emitStr(const char *p) {
if (!p) {
p = "(null)";
}
while (*p) {
emit(*p++);
}
}
// ---- Number-to-buffer helpers (reverse order, returns digit count) -----
// uint64 -> decimal digits in reverse order. buf must be >= 20 bytes
// (UINT64_MAX = 18446744073709551615 = 20 digits).
static int u64ToDec(unsigned long long n, char *buf) {
int i = 0;
if (n == 0) {
buf[i++] = '0';
return i;
}
while (n > 0) {
buf[i++] = (char)('0' + (n % 10ull));
n /= 10ull;
}
return i;
}
// uint64 -> hex digits in reverse order. Returns digit count. Buf
// must be >= 16 bytes (UINT64_MAX = 16 hex digits).
static int u64ToHex(unsigned long long n, int upper, char *buf) {
const char *digits = upper ? "0123456789ABCDEF" : "0123456789abcdef";
int i = 0;
if (n == 0) {
buf[i++] = '0';
return i;
}
while (n > 0) {
buf[i++] = digits[n & 0xFull];
n >>= 4;
}
return i;
}
// uint64 -> octal digits in reverse order. Returns digit count.
static int u64ToOct(unsigned long long n, char *buf) {
int i = 0;
if (n == 0) {
buf[i++] = '0';
return i;
}
while (n > 0) {
buf[i++] = (char)('0' + (n & 7ull));
n >>= 3;
}
return i;
}
// Emit n copies of c (used for width / precision padding).
static void emitPad(int n, char c) {
while (n-- > 0) emit(c);
}
// Emit a reversed buffer in forward order.
static void emitRev(const char *buf, int n) {
while (n-- > 0) emit(buf[n]);
}
// ---- Integer formatting with full flag/width/prec/length surface ----
typedef struct {
int leftAlign; // '-'
int signPlus; // '+'
int signSpace; // ' '
int altForm; // '#'
int zeroPad; // '0'
int width;
int prec; // -1 if unset
} Spec;
// Emit an integer with all the conversion flags. `value` is the
// magnitude (always non-negative); `isNeg` says whether to prefix '-'.
// `base` is 8 / 10 / 16. `upper` selects A-F vs a-f for base 16.
static void emitNumber(unsigned long long value,
int isSigned, int isNeg,
int base, int upper,
const Spec *s) {
char buf[20];
int len;
if (base == 16) {
len = u64ToHex(value, upper, buf);
} else if (base == 8) {
len = u64ToOct(value, buf);
} else {
len = u64ToDec(value, buf);
}
// Sign / alt-form prefix.
char prefix1 = 0; // '-', '+', or ' '
char prefix2 = 0; // 'x' / 'X' / '0' for # alt-form
char prefix0 = 0; // '0' before the 'x' for hex alt
if (isSigned) {
if (isNeg) prefix1 = '-';
else if (s->signPlus) prefix1 = '+';
else if (s->signSpace) prefix1 = ' ';
}
if (s->altForm && base == 16 && value != 0ull) {
prefix0 = '0';
prefix2 = upper ? 'X' : 'x';
} else if (s->altForm && base == 8 && (s->prec < 0 || s->prec < len + 1)) {
// Octal alt-form: ensure leading 0. Easiest: bump precision.
if (buf[len - 1] != '0') {
buf[len++] = '0';
}
}
// Precision (min number of digits — left-pad with '0').
int digitPad = 0;
if (s->prec >= 0 && len < s->prec) {
digitPad = s->prec - len;
}
int prefixLen = (prefix1 ? 1 : 0) + (prefix0 ? 1 : 0) + (prefix2 ? 1 : 0);
int contentLen = prefixLen + digitPad + len;
int fieldPad = s->width > contentLen ? s->width - contentLen : 0;
// When zero-padding is requested AND no precision, the zero pad
// counts toward digitPad (so '+' / sign goes first, then zeros,
// then digits). Precision specified disables zero pad per C99.
if (s->zeroPad && !s->leftAlign && s->prec < 0) {
digitPad += fieldPad;
fieldPad = 0;
}
if (!s->leftAlign) {
emitPad(fieldPad, ' ');
}
if (prefix1) emit(prefix1);
if (prefix0) emit(prefix0);
if (prefix2) emit(prefix2);
emitPad(digitPad, '0');
emitRev(buf, len);
if (s->leftAlign) {
emitPad(fieldPad, ' ');
}
}
// Emit a string with width + precision honored (precision = max chars).
static void emitStrField(const char *p, const Spec *s) {
if (!p) p = "(null)";
int len = 0;
while (p[len] && (s->prec < 0 || len < s->prec)) len++;
int fieldPad = s->width > len ? s->width - len : 0;
if (!s->leftAlign) emitPad(fieldPad, ' ');
for (int i = 0; i < len; i++) emit(p[i]);
if (s->leftAlign) emitPad(fieldPad, ' ');
}
// IEEE-754 double decoded into a sign bit + 11-bit exponent + four
// 16-bit mantissa words. Mantissa is laid out LSB-first: m[0] is
// bits[15:0], m[1] bits[31:16], m[2] bits[47:32], m[3] bits[51:48]
// (only the low 4 bits of m[3] are used). Reading the bits as 4 u16
// words avoids the >>52 / 12-bit-mask paths that drag i64 libcalls in.
#ifndef LLVM816_NO_FLOAT_PRINTF
typedef struct {
unsigned short m[4]; // mantissa: low-to-high, m[3] only 4 LSBs
unsigned short exp; // 11-bit biased exponent (0..0x7FF)
unsigned char sign; // 0 / 1
} DblBits;
static void decodeDouble(double v, DblBits *d) {
unsigned short w[4];
__builtin_memcpy(w, &v, 8);
// Little-endian byte order: w[0] = bytes 0-1 (mantissa LSB),
// w[3] = bytes 6-7 (sign + exp + mantissa MSB-nibble).
d->m[0] = w[0];
d->m[1] = w[1];
d->m[2] = w[2];
d->m[3] = (unsigned short)(w[3] & 0x000F);
d->exp = (unsigned short)((w[3] >> 4) & 0x07FF);
d->sign = (unsigned char)((w[3] >> 15) & 1);
}
// If v is +/-Inf or NaN, emit the canonical glibc-style spelling and
// return 1. Otherwise return 0 (caller continues with finite path).
// `upper` selects "INF"/"NAN" vs "inf"/"nan". Width/left-align/space/
// '+' flags are honored exactly like glibc.
static int emitInfNan(const DblBits *d, int upper, const Spec *s) {
if (d->exp != 0x7FF) {
return 0;
}
int isNan = (d->m[0] | d->m[1] | d->m[2] | d->m[3]) != 0;
const char *body = isNan ? (upper ? "NAN" : "nan")
: (upper ? "INF" : "inf");
char prefix = 0;
if (!isNan) {
if (d->sign) prefix = '-';
else if (s->signPlus) prefix = '+';
else if (s->signSpace)prefix = ' ';
}
int bodyLen = 3;
int total = bodyLen + (prefix ? 1 : 0);
int fieldPad = s->width > total ? s->width - total : 0;
// C99: zero-padding is undefined / ignored for Inf/NaN; glibc uses
// spaces. We follow glibc.
if (!s->leftAlign) {
emitPad(fieldPad, ' ');
}
if (prefix) {
emit(prefix);
}
emitStr(body);
if (s->leftAlign) {
emitPad(fieldPad, ' ');
}
return 1;
}
// Emit %a / %A hex-float. Local width/leftAlign/zeroPad handling --
// emitNumber's monolithic numeric body can only honor one prefix at a
// time, and hex-float needs prefix = sign + "0x" + content. We do use
// emitNumber for the exponent tail (sign + decimal digits, no prefix).
//
// Format: [-]0x{H}.{F}p{SE} where H is 0 or 1, F is up to 13 hex digits
// (52 mantissa bits / 4), SE is signed decimal exponent. Subnormals
// canonicalize as 0x0.{F}p-1022 (matching glibc). Trailing-zero
// stripping for the fractional part fires when precision is unspecified.
static void emitHexFloat(double v, char spec, const Spec *s) {
DblBits d;
decodeDouble(v, &d);
int upper = (spec == 'A');
if (emitInfNan(&d, upper, s)) {
return;
}
// Pull the 13 fractional hex nibbles of the mantissa (high-to-low).
// The 52-bit mantissa = 13 hex digits. All of n[0..12] are
// FRACTIONAL nibbles; the integral digit (0 or 1) is implicit
// (set by the exp == 0 subnormal-vs-zero split below).
// n[0] is the most significant nibble (m[3] LSBs); n[12] is the
// least significant nibble (m[0] LSBs).
unsigned char n[13];
n[0] = (unsigned char)(d.m[3] & 0x0F);
n[1] = (unsigned char)((d.m[2] >> 12) & 0x0F);
n[2] = (unsigned char)((d.m[2] >> 8) & 0x0F);
n[3] = (unsigned char)((d.m[2] >> 4) & 0x0F);
n[4] = (unsigned char)( d.m[2] & 0x0F);
n[5] = (unsigned char)((d.m[1] >> 12) & 0x0F);
n[6] = (unsigned char)((d.m[1] >> 8) & 0x0F);
n[7] = (unsigned char)((d.m[1] >> 4) & 0x0F);
n[8] = (unsigned char)( d.m[1] & 0x0F);
n[9] = (unsigned char)((d.m[0] >> 12) & 0x0F);
n[10] = (unsigned char)((d.m[0] >> 8) & 0x0F);
n[11] = (unsigned char)((d.m[0] >> 4) & 0x0F);
n[12] = (unsigned char)( d.m[0] & 0x0F);
// Determine integral hex digit + biased-to-unbiased exponent.
// C99 canonical: normal -> 1.fp{e-1023}, subnormal -> 0.fp-1022,
// zero -> 0x0p+0 (glibc prints with prec digits if requested).
char integral; // '0' or '1'
int expVal; // exponent of 2 (already accounting for the
// implicit-1 / subnormal split)
int zero = (d.exp == 0)
&& (d.m[0] | d.m[1] | d.m[2] | d.m[3]) == 0;
if (d.exp == 0) {
integral = '0';
expVal = zero ? 0 : -1022; // subnormals all share -1022
} else {
integral = '1';
expVal = (int)d.exp - 1023;
}
// Decide how many fractional hex digits to emit. fracLen is the
// count of nibbles to emit from n[0..fracLen-1]. When prec is
// unspecified (s->prec < 0): emit exact representation, strip
// trailing zeros (glibc style). Otherwise: emit `prec` digits
// (zero-pad or round if needed).
int fracLen;
if (s->prec < 0) {
// Trailing-zero strip: find the largest index < 13 with a
// non-zero nibble; fracLen = (idx + 1). If all zero,
// fracLen = 0.
fracLen = 13;
while (fracLen > 0 && n[fracLen - 1] == 0) {
fracLen--;
}
} else if (s->prec > 13) {
fracLen = 13; // We have at most 13 nibbles of real data;
// pad below with '0' up to s->prec.
} else {
fracLen = s->prec;
// Round-half-even at fracLen. When fracLen < 13, the first
// discarded nibble is n[fracLen]. Half = 8. Round up if >8;
// round to even on exactly 8 with no remainder; round down if <8.
if (fracLen < 13) {
int round = 0;
unsigned char first = n[fracLen];
if (first > 8) {
round = 1;
} else if (first == 8) {
// Any remaining non-zero nibble after first -> round up.
int sticky = 0;
for (int i = fracLen + 1; i < 13; i++) {
if (n[i] != 0) { sticky = 1; break; }
}
if (sticky) {
round = 1;
} else {
// Half: round to even (last kept nibble even -> down).
unsigned char last = (fracLen > 0) ? n[fracLen - 1]
: (unsigned char)(integral - '0');
round = (last & 1);
}
}
if (round) {
int i = fracLen - 1;
while (i >= 0) {
n[i] = (unsigned char)((n[i] + 1) & 0x0F);
if (n[i] != 0) break;
i--;
}
if (i < 0) {
// Carry propagated into the integral digit. glibc
// does NOT re-normalize on overflow here: `%.0a` of
// 1.5 (0x1.8p+0) emits `0x2p+0`, not `0x1p+1`. We
// match that. Subnormal rounding up to 0x1 keeps
// the -1022 exponent (subnormal-to-smallest-normal).
unsigned char ih = (unsigned char)(integral - '0');
ih = (unsigned char)(ih + 1);
integral = (char)('0' + ih);
}
}
}
}
// Build the body in a local buffer so we can apply width padding
// without reusing emitNumber's prefix logic. Body layout:
// [sign] 0x H . F p SE
// Worst case: sign(1) + "0x"(2) + integral(1) + "."(1) +
// 13 hex digits + "p"(1) + sign(1) + 5 decimal = 25.
// We allow up to 32 to give the prec>13 padding case headroom.
char body[40];
int bi = 0;
if (d.sign) body[bi++] = '-';
else if (s->signPlus) body[bi++] = '+';
else if (s->signSpace) body[bi++] = ' ';
body[bi++] = '0';
body[bi++] = upper ? 'X' : 'x';
body[bi++] = integral;
// The '.' is emitted IFF we will emit at least one fractional digit
// OR alt-form is set (# forces the radix point).
int emitDot = (fracLen > 0) || (s->prec > 0) || s->altForm;
if (emitDot) {
body[bi++] = '.';
}
{
const char *digits = upper ? "0123456789ABCDEF"
: "0123456789abcdef";
int written = 0;
for (int i = 0; i < fracLen && i < 13; i++) {
body[bi++] = digits[n[i]];
written++;
}
// Zero-pad up to s->prec when prec exceeds available nibbles.
if (s->prec > written) {
int pad = s->prec - written;
while (pad-- > 0) {
body[bi++] = '0';
}
}
}
body[bi++] = upper ? 'P' : 'p';
// Exponent: ALWAYS prints a sign ('+' or '-') and at least one digit.
int eAbs = expVal < 0 ? -expVal : expVal;
char ebuf[8]; // up to 4-5 digits
int elen = u64ToDec((unsigned long long)eAbs, ebuf);
body[bi++] = (expVal < 0) ? '-' : '+';
while (elen-- > 0) {
body[bi++] = ebuf[elen];
}
// Field-width + zero-pad logic (local, NOT via emitNumber).
int contentLen = bi;
int fieldPad = s->width > contentLen ? s->width - contentLen : 0;
if (s->zeroPad && !s->leftAlign) {
// Zero pad goes BETWEEN the "0x" prefix (incl. any sign) and
// the integral digit, matching glibc / C99 for %a.
int prefixEnd = 0;
if (body[0] == '-' || body[0] == '+' || body[0] == ' ') {
prefixEnd = 3; // sign + 0x
} else {
prefixEnd = 2; // 0x
}
// Emit the leading prefix, then the zeros, then the rest.
for (int i = 0; i < prefixEnd; i++) emit(body[i]);
emitPad(fieldPad, '0');
for (int i = prefixEnd; i < bi; i++) emit(body[i]);
return;
}
if (!s->leftAlign) {
emitPad(fieldPad, ' ');
}
for (int i = 0; i < bi; i++) emit(body[i]);
if (s->leftAlign) {
emitPad(fieldPad, ' ');
}
}
static void emitDouble(double v, int prec, char spec, const Spec *s) {
// For %g / %G, "precision" is total significant digits. Real glibc
// would compute exponent and choose between %e and %f styles, but
// we keep things simple and just emit `X.YYY` with trailing zeros
// stripped at the end. For %f / %e, prec is decimal places.
int isG = (spec == 'g' || spec == 'G');
// Inf/NaN parity with %a (must precede prec clamp and sign strip
// since those don't make sense on non-finite values). `upper` for
// %F/%E/%G follows the same caps convention as %A.
{
DblBits d;
decodeDouble(v, &d);
int upper = (spec == 'F' || spec == 'E' || spec == 'G');
if (emitInfNan(&d, upper, s)) {
return;
}
}
if (prec < 0) {
prec = 6;
}
if (prec > 9) {
prec = 9;
}
// Avoid `if (v < 0)` (which calls __ltdf2) — the W65816 codegen
// for that comparison passes its double arg with a missing word,
// and the test silently returns false for negatives. Read the
// IEEE-754 sign bit and clear it inline instead.
unsigned long long bits;
__builtin_memcpy(&bits, &v, 8);
if (bits & ((unsigned long long)1 << 63)) {
emit('-');
bits &= ~((unsigned long long)1 << 63);
__builtin_memcpy(&v, &bits, 8);
}
// Split int part first, then scale only the fractional part. The
// earlier "multiply v by 10^prec then split via integer divide"
// approach silently overflowed long for v*10^prec > 2^31 (e.g. any
// value ≥ 2.15 with prec=9 in `%.12g`). We've since reworked the
// libcall ABI, so the previously-buggy `v - (double)ipart` chain
// works now — smoke catches a regression of either bug.
unsigned long intPart = (unsigned long)(long)v;
double frac = v - (double)intPart;
unsigned long mul = 1;
for (int i = 0; i < prec; i++) {
frac = frac * 10.0;
mul *= 10;
}
// Round-half-up before truncation: 0.314 * 100 = 31.3999... in
// soft-double, but `%.2f` of 3.14 should print "3.14". Adding 0.5
// then truncating is round-half-up for the non-negative frac here.
frac = frac + 0.5;
unsigned long frcPart = (unsigned long)(long)frac;
// Carry-up if rounding pushed frac to a full integer (e.g. 0.9995
// → 0.9995*1000+0.5 = 1000 = mul; the "0.9995" wanted to become
// "1.000", not "0.1000").
if (frcPart >= mul) {
intPart += 1;
frcPart = 0;
}
{
char ibuf[20];
int ilen = u64ToDec(intPart, ibuf);
emitRev(ibuf, ilen);
}
if (prec == 0) {
return;
}
// Build fractional digits into a local buffer (reverse order to
// forward) so we can trim trailing zeros for %g before emitting.
char buf[10];
for (int i = prec - 1; i >= 0; i--) {
buf[i] = (char)('0' + (frcPart % 10));
frcPart /= 10;
}
int emitCount = prec;
if (isG) {
// Strip trailing zeros. If the whole fractional part is
// zeros, skip the '.' too.
while (emitCount > 0 && buf[emitCount - 1] == '0') {
emitCount -= 1;
}
}
if (emitCount == 0) {
return; // No fractional digits to emit → no '.' either.
}
emit('.');
for (int i = 0; i < emitCount; i++) {
emit(buf[i]);
}
}
#endif // LLVM816_NO_FLOAT_PRINTF
// Length modifiers — encoded as small ints to keep the dispatch flat.
enum {
LEN_NONE = 0,
LEN_HH, // hh: char-promoted-to-int
LEN_H, // h: short-promoted-to-int
LEN_L, // l: long
LEN_LL, // ll: long long
LEN_J, // j: intmax_t (= long long)
LEN_Z, // z: size_t (= unsigned long)
LEN_T // t: ptrdiff_t (= int)
};
// 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;
}
Spec s;
s.leftAlign = 0;
s.signPlus = 0;
s.signSpace = 0;
s.altForm = 0;
s.zeroPad = 0;
s.width = 0;
s.prec = -1;
// Flags (any subset, any order).
for (;;) {
char f = *fmt;
if (f == '-') s.leftAlign = 1;
else if (f == '+') s.signPlus = 1;
else if (f == ' ') s.signSpace = 1;
else if (f == '#') s.altForm = 1;
else if (f == '0') s.zeroPad = 1;
else break;
fmt++;
}
// Width: decimal or `*`.
if (*fmt == '*') {
int w = va_arg(ap, int);
if (w < 0) { s.leftAlign = 1; w = -w; }
s.width = w;
fmt++;
} else {
while (*fmt >= '0' && *fmt <= '9') {
s.width = s.width * 10 + (*fmt - '0');
fmt++;
}
}
// Precision: `.N` or `.*` (presence enables, default 0 if no digits).
if (*fmt == '.') {
fmt++;
if (*fmt == '*') {
s.prec = va_arg(ap, int);
fmt++;
} else {
s.prec = 0;
while (*fmt >= '0' && *fmt <= '9') {
s.prec = s.prec * 10 + (*fmt - '0');
fmt++;
}
}
}
// Length modifier (one of hh / h / l / ll / j / z / t).
int len = LEN_NONE;
if (*fmt == 'h') {
fmt++;
if (*fmt == 'h') { fmt++; len = LEN_HH; }
else len = LEN_H;
} else if (*fmt == 'l') {
fmt++;
if (*fmt == 'l') { fmt++; len = LEN_LL; }
else len = LEN_L;
} else if (*fmt == 'j') { fmt++; len = LEN_J; }
else if (*fmt == 'z') { fmt++; len = LEN_Z; }
else if (*fmt == 't') { fmt++; len = LEN_T; }
char spec = *fmt++;
// Signed integers.
if (spec == 'd' || spec == 'i') {
long long v;
switch (len) {
case LEN_HH: v = (signed char)va_arg(ap, int); break;
case LEN_H: v = (short)va_arg(ap, int); break;
case LEN_L: v = (long)va_arg(ap, long); break;
case LEN_LL:
case LEN_J: v = va_arg(ap, long long); break;
case LEN_Z: v = (long)va_arg(ap, unsigned long); break;
case LEN_T: v = va_arg(ap, int); break;
default: v = va_arg(ap, int); break;
}
int isNeg = v < 0;
unsigned long long mag = isNeg ? (0ull - (unsigned long long)v)
: (unsigned long long)v;
emitNumber(mag, /*isSigned=*/1, isNeg, 10, 0, &s);
}
// Unsigned bases: %u %x %X %o.
else if (spec == 'u' || spec == 'x' || spec == 'X' || spec == 'o') {
unsigned long long v;
switch (len) {
case LEN_HH: v = (unsigned char)va_arg(ap, unsigned int); break;
case LEN_H: v = (unsigned short)va_arg(ap, unsigned int); break;
case LEN_L: v = va_arg(ap, unsigned long); break;
case LEN_LL:
case LEN_J: v = va_arg(ap, unsigned long long); break;
case LEN_Z: v = va_arg(ap, unsigned long); break;
case LEN_T: v = (unsigned int)va_arg(ap, int); break;
default: v = va_arg(ap, unsigned int); break;
}
int base = (spec == 'u') ? 10 : (spec == 'o') ? 8 : 16;
int upper = (spec == 'X');
emitNumber(v, /*isSigned=*/0, 0, base, upper, &s);
}
else if (spec == 'c') {
char ch = (char)va_arg(ap, int);
int fieldPad = s.width > 1 ? s.width - 1 : 0;
if (!s.leftAlign) emitPad(fieldPad, ' ');
emit(ch);
if (s.leftAlign) emitPad(fieldPad, ' ');
}
else if (spec == 's') {
emitStrField(va_arg(ap, const char *), &s);
}
#ifndef LLVM816_NO_FLOAT_PRINTF
else if (spec == 'f' || spec == 'F' ||
spec == 'g' || spec == 'G' ||
spec == 'e' || spec == 'E') {
emitDouble(va_arg(ap, double), s.prec, spec, &s);
}
else if (spec == 'a' || spec == 'A') {
emitHexFloat(va_arg(ap, double), spec, &s);
}
#endif
else if (spec == 'p') {
// ptr32 — print as "0xBBBBOOOO" (8 hex digits, bank + offset).
unsigned long pp = (unsigned long)(unsigned long)va_arg(ap, void *);
emit('0');
emit('x');
Spec p = s;
p.prec = 8;
p.width = 0;
emitNumber(pp, 0, 0, 16, 0, &p);
}
else if (spec == 'n') {
// Store the number of chars emitted so far through the
// pointer arg. Length modifier picks the integer width.
int count = (int)gTotal;
switch (len) {
case LEN_HH: *va_arg(ap, signed char *) = (signed char)count; break;
case LEN_H: *va_arg(ap, short *) = (short)count; break;
case LEN_L: *va_arg(ap, long *) = (long)count; break;
case LEN_LL:
case LEN_J: *va_arg(ap, long long *) = (long long)count; break;
case LEN_Z: *va_arg(ap, unsigned long *) = (unsigned long)count; break;
case LEN_T: *va_arg(ap, int *) = count; break;
default: *va_arg(ap, int *) = count; break;
}
}
else if (spec == '%') {
emit('%');
}
else {
// Unknown conversion — echo `%spec` literally.
emit('%');
emit(spec);
}
}
if (gCur < gEnd) {
*gCur = '\0';
} else if (gEnd > (char *)0) {
// Truncated, but n > 0: overwrite the last byte with NUL so
// the result is a valid C string. snprintf with n=0 sets
// gEnd = NULL up front so this branch correctly skips —
// previously it wrote `gEnd[-1]` to `buf[-1]`, clobbering
// memory before the buffer.
gEnd[-1] = '\0';
}
return (int)gTotal;
}
int snprintf(char *buf, size_t n, const char *fmt, ...) {
gCur = buf;
// n == 0 must NOT touch the buffer (C99 7.19.6.5). Setting
// gEnd = NULL here makes both `gCur < gEnd` and `gEnd > 0`
// false, so no NUL terminator gets written.
gEnd = n ? buf + n : (char *)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; see SPRINTF_END_SENTINEL above for the
// reason we don't use buf + 0xFFFE.
gEnd = SPRINTF_END_SENTINEL;
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 = n ? buf + n : (char *)0;
gTotal = 0;
return format(fmt, ap);
}
int vsprintf(char *buf, const char *fmt, va_list ap) {
gCur = buf;
gEnd = SPRINTF_END_SENTINEL;
gTotal = 0;
return format(fmt, ap);
}