// 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); }