From 87ab610a9b52caa234d73f016949913307c30aaa Mon Sep 17 00:00:00 2001 From: huntedbytheirs Date: Sat, 5 Sep 2026 16:27:09 -0400 Subject: [PATCH] feat(stdio): printf family --- include/stdio.h | 33 + src/stdio/vfprintf.c | 2151 ++++++++++++++++++++++++++++++++++++ src/stdio/vfprintf_float.c | 842 ++++++++++++++ tests/test_printf.c | 734 ++++++++++++ 4 files changed, 3760 insertions(+) create mode 100644 src/stdio/vfprintf.c create mode 100644 src/stdio/vfprintf_float.c create mode 100644 tests/test_printf.c diff --git a/include/stdio.h b/include/stdio.h index ec6b1f0..e251cf6 100644 --- a/include/stdio.h +++ b/include/stdio.h @@ -25,6 +25,7 @@ #include +#include #include #include @@ -299,4 +300,36 @@ fopen64(const char *restrict path, const char *restrict mode); } #endif +/* formatted output (todo 16) */ + +int +printf(const char *restrict format, ...); +int +fprintf(FILE *restrict stream, const char *restrict format, ...); +int +sprintf(char *restrict s, const char *restrict format, ...); +int +snprintf(char *restrict s, size_t n, const char *restrict format, ...); +int +vprintf(const char *restrict format, va_list ap); +int +vfprintf(FILE *restrict stream, const char *restrict format, va_list ap); +int +vsprintf(char *restrict s, const char *restrict format, va_list ap); +int +vsnprintf(char *restrict s, size_t n, const char *restrict format, va_list ap); +int +dprintf(int fd, const char *restrict format, ...); +int +vdprintf(int fd, const char *restrict format, va_list ap); +void +perror(const char *s); + +#if VLIBC_LEVEL_GE(2) +int +asprintf(char **restrict strp, const char *restrict format, ...); +int +vasprintf(char **restrict strp, const char *restrict format, va_list ap); +#endif + #endif /* VLIBC_STDIO_H */ diff --git a/src/stdio/vfprintf.c b/src/stdio/vfprintf.c new file mode 100644 index 0000000..69aeefa --- /dev/null +++ b/src/stdio/vfprintf.c @@ -0,0 +1,2151 @@ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include + +#include "../internal/malloc.h" +#include "../internal/syscall.h" +#include "stdio_impl.h" + +/* The u32-limb bignum core used by the float conversions (todo 16). It is + * written as a header so its static helpers fold into this translation unit; + * nothing it defines is externally visible. */ +#include "vfprintf_float.c" + +/* + * vlibc — formatted output (todo 16). Integer conversions and the f/F/e/E, + * g/G, a/A and L (long double) float conversions are implemented. The + * float conversion core works on a decomposed value (struct vfpn_x), so the + * double and long double lengths share every formatter. + * + * Output plumbing is a small sink abstraction. Every character produced by a + * conversion flows through one of three put implementations: + * + * - a FILE sink that mirrors the mode switching and buffering of fputc + * (see src/stdio/stdio.c), batching each emit into the stream buffer; + * - a memory sink that counts always and writes at most cap - 1 bytes + * (cap counts the terminating NUL slot), so snprintf truncation and the + * asprintf two-pass count-then-fill pattern share one code path; + * - a descriptor sink that loops over raw SYS_write for dprintf/vdprintf. + * + * printf_emit is the single choke point that maintains the running count and + * the INT_MAX overflow rule: once the count would exceed INT_MAX the stream + * is marked failed with errno EOVERFLOW and every later emit short-circuits. + */ + +/* Sink dispatch table. */ +struct printf_sink +{ + int (*put)(struct printf_sink *sk, const unsigned char *s, size_t n); + void *ctx; + size_t count; /* characters accepted so far, <= INT_MAX */ + int fail; +}; + +/* Memory-sink context: cap includes the terminating NUL slot. */ +struct printf_mem +{ + char *dst; /* NULL allowed while counting only */ + size_t cap; /* 0 = count-only pass */ + size_t len; /* required length so far (grows even when truncating) */ +}; + +/* Descriptor-sink context. */ +struct printf_fd +{ + int fd; +}; + +/* Conversion flags. */ +#define PF_MINUS 0x01 /* '-' */ +#define PF_PLUS 0x02 /* '+' */ +#define PF_SPACE 0x04 /* ' ' */ +#define PF_HASH 0x08 /* '#' */ +#define PF_ZERO 0x10 /* '0' */ + +/* Length modifiers. */ +#define LN_NONE 0 +#define LN_HH 1 +#define LN_H 2 +#define LN_L 3 +#define LN_LL 4 +#define LN_J 5 +#define LN_Z 6 +#define LN_T 7 +#define LN_CAPL 8 /* 'L': long double, a float-only modifier (todo B) */ + +/* + * Emit n bytes, updating the count. Fails (returns -1 and latches the sink) + * when the count would exceed INT_MAX or the underlying put reports an error. + */ +static int +printf_emit(struct printf_sink *sk, const unsigned char *s, size_t n) +{ + if (sk->fail) + { + return -1; + } + if ((size_t)INT_MAX - sk->count < n) + { + sk->fail = 1; + errno = EOVERFLOW; + return -1; + } + if (sk->put(sk, s, n) < 0) + { + sk->fail = 1; + return -1; + } + sk->count += n; + return 0; +} + +/* Emit n copies of the single byte ch in 64-byte chunks. */ +static int +printf_emit_pad(struct printf_sink *sk, int ch, size_t n) +{ + unsigned char buf[64]; + size_t i; + + for (i = 0; i < sizeof buf; i++) + { + buf[i] = (unsigned char)ch; + } + while (n > 0) + { + size_t take = n < sizeof buf ? n : sizeof buf; + + if (printf_emit(sk, buf, take) < 0) + { + return -1; + } + n -= take; + } + return 0; +} + +/* (a) FILE sink: buffered, mirroring the fputc mode switch in stdio.c. */ +static int +printf_put_file(struct printf_sink *sk, const unsigned char *s, size_t n) +{ + FILE *f = (FILE *)sk->ctx; + size_t off = 0; + + stdio_init_if_needed(f); + if (!(f->flags & F_WRITE) && !(f->flags & F_RDWR)) + { + /* The open mode does not allow writes. */ + f->flags |= F_ERR; + return -1; + } + if (f->rpos < f->rstop && stdio_discard_read(f) < 0) + { + return -1; /* F_ERR set by the discard */ + } + f->flags |= F_WRITE; + if (f->flags & F_UNBUF) + { + while (off < n) + { + long r = __syscall3(SYS_write, f->fd, (long)(s + off), (long)(n - off)); + + if (r <= 0) + { + if (r < 0) + { + (void)syscall_ret(r); /* sets errno */ + } + f->flags |= F_ERR; + return -1; + } + f->pos += (off_t)r; + off += (size_t)r; + } + return 0; + } + if (f->buf == NULL) + { + /* Lazy buffer allocation failed; the stream is unusable. */ + return -1; + } + while (off < n) + { + size_t space = (size_t)(f->wstop - f->wpos); + size_t take; + int has_nl = 0; + size_t k; + + if (space == 0) + { + if (stdio_flush(f) < 0) + { + return -1; + } + space = (size_t)(f->wstop - f->wpos); + } + take = n - off < space ? n - off : space; + if (f->flags & F_LINEBUF) + { + for (k = 0; k < take; k++) + { + if (s[off + k] == '\n') + { + has_nl = 1; + break; + } + } + } + __builtin_memcpy(f->wpos, s + off, take); + f->wpos += take; + off += take; + if (has_nl || f->wpos >= f->wstop) + { + if (stdio_flush(f) < 0) + { + return -1; + } + } + } + return 0; +} + +/* (b) Memory sink: len is the required length and always grows by n. */ +static int +printf_put_mem(struct printf_sink *sk, const unsigned char *s, size_t n) +{ + struct printf_mem *m = (struct printf_mem *)sk->ctx; + size_t off = m->len; + + m->len += n; + if (m->cap == 0 || m->dst == NULL) + { + return 0; /* count-only pass, or an unbounded write already failed */ + } + if (off < m->cap - 1) + { + size_t room = m->cap - 1 - off; + + if (room > n) + { + room = n; + } + __builtin_memcpy(m->dst + off, s, room); + } + return 0; +} + +/* (c) Descriptor sink: raw SYS_write with a short-write loop. */ +static int +printf_put_fd(struct printf_sink *sk, const unsigned char *s, size_t n) +{ + struct printf_fd *fd = (struct printf_fd *)sk->ctx; + size_t off = 0; + + while (off < n) + { + long r = __syscall3(SYS_write, fd->fd, (long)(s + off), (long)(n - off)); + + if (r <= 0) + { + if (r < 0) + { + (void)syscall_ret(r); /* sets errno */ + } + return -1; + } + off += (size_t)r; + } + return 0; +} + +/* + * Render uval in base into digs (most significant first). Returns the digit + * count. A zero value with precision 0 renders no digits; otherwise a zero + * value always renders the single digit "0". digs must hold at least 66 + * bytes (binary of a 64-bit value needs 64). + */ +static size_t +printf_render_uint(uintmax_t uval, int base, int upper, int prec, unsigned char *digs) +{ + static const char *const lower = "0123456789abcdefghijklmnopqrstuvwxyz"; + static const char *const upperd = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + const char *alpha = upper ? upperd : lower; + unsigned char rev[66]; + size_t n = 0; + size_t i; + + if (uval == 0 && prec != 0) + { + digs[0] = '0'; + return 1; + } + while (uval != 0) + { + rev[n++] = (unsigned char)alpha[uval % (uintmax_t)base]; + uval /= (uintmax_t)base; + } + for (i = 0; i < n; i++) + { + digs[i] = rev[n - 1 - i]; + } + return n; +} + +/* + * Emit one integer conversion. p_style selects the %p layout: "0x" prefix + * always shown and a zero value keeps at least one hex digit. The emit order + * is [space pad][sign][prefix][zero fill][digits][space pad]; the zero fill + * is the run of '0' flag zeros followed by the precision zeros and any '#' + * forced octal leading zero. + */ +static int +printf_fmt_int(struct printf_sink *sk, uintmax_t uval, int is_signed, int neg, int flags, int width, + int prec, int base, int upper, int p_style) +{ + unsigned char digs[66]; + unsigned char pfx[2]; + size_t pfx_len = 0; + size_t ndig; + size_t pre0 = 0; /* '#'-forced octal leading zero (no precision given) */ + size_t precz = 0; /* precision zeros before the digits */ + size_t zeros = 0; /* '0'-flag zeros between sign/prefix and digits */ + size_t pad = 0; + size_t body; + char sign = '\0'; + + if (p_style) + { + pfx[0] = '0'; + pfx[1] = 'x'; + pfx_len = 2; + if (uval == 0 && prec == 0) + { + prec = 1; /* %p of NULL is "0x0", never empty */ + } + } + ndig = printf_render_uint(uval, base, upper, prec, digs); + if (base == 16 && (flags & PF_HASH) && uval != 0) + { + pfx[0] = '0'; + pfx[1] = upper ? 'X' : 'x'; + pfx_len = 2; + } + if (base == 8 && (flags & PF_HASH)) + { + if (uval == 0 && ndig == 0) + { + /* %#.0o of 0 prints a single "0". */ + digs[0] = '0'; + ndig = 1; + } + else if (uval != 0 && prec < 0) + { + pre0 = 1; /* a plain leading zero forced by '#' */ + } + else if (uval != 0 && prec <= (int)ndig) + { + prec = (int)ndig + 1; /* let the precision carry the zero */ + } + } + if (prec > (int)ndig) + { + precz = (size_t)prec - ndig; + } + if (is_signed) + { + if (neg) + { + sign = '-'; + } + else if (flags & PF_PLUS) + { + sign = '+'; + } + else if (flags & PF_SPACE) + { + sign = ' '; + } + } + body = (sign != '\0' ? 1 : 0) + pfx_len + pre0 + precz + ndig; + if ((flags & PF_ZERO) && !(flags & PF_MINUS) && prec < 0 && (size_t)width > body) + { + zeros = (size_t)width - body; + } + else if ((size_t)width > body) + { + pad = (size_t)width - body; + } + if (pad > 0 && !(flags & PF_MINUS) && printf_emit_pad(sk, ' ', pad) < 0) + { + return -1; + } + if (sign != '\0' && printf_emit(sk, (const unsigned char *)&sign, 1) < 0) + { + return -1; + } + if (pfx_len > 0 && printf_emit(sk, pfx, pfx_len) < 0) + { + return -1; + } + if (zeros + pre0 + precz > 0 && printf_emit_pad(sk, '0', zeros + pre0 + precz) < 0) + { + return -1; + } + if (printf_emit(sk, digs, ndig) < 0) + { + return -1; + } + if (pad > 0 && (flags & PF_MINUS) && printf_emit_pad(sk, ' ', pad) < 0) + { + return -1; + } + return 0; +} + +/* + * Fetch the argument for a d/i/u/o/x/X conversion according to the length + * modifier and emit it. Signed values are negated in the unsigned domain so + * INTMAX_MIN / LLONG_MIN never overflow. + */ +static int +printf_emit_int(struct printf_sink *sk, va_list *ap, int conv, int len, int flags, int width, + int prec) +{ + int is_signed; + int base; + int upper; + int neg = 0; + uintmax_t mag; + + if (conv == 'd' || conv == 'i') + { + intmax_t sv = 0; + + is_signed = 1; + base = 10; + upper = 0; + switch (len) + { + case LN_HH: + sv = (intmax_t)(signed char)va_arg(*ap, int); + break; + case LN_H: + sv = (intmax_t)(short)va_arg(*ap, int); + break; + case LN_L: + sv = (intmax_t)va_arg(*ap, long); + break; + case LN_LL: + sv = (intmax_t)va_arg(*ap, long long); + break; + case LN_J: + sv = va_arg(*ap, intmax_t); + break; + case LN_Z: + sv = (intmax_t)va_arg(*ap, ssize_t); + break; + case LN_T: + sv = (intmax_t)va_arg(*ap, ptrdiff_t); + break; + default: + sv = (intmax_t)va_arg(*ap, int); + break; + } + if (sv < 0) + { + neg = 1; + mag = (uintmax_t)0 - (uintmax_t)sv; + } + else + { + mag = (uintmax_t)sv; + } + } + else + { + uintmax_t uv = 0; + + is_signed = 0; + upper = (conv == 'X'); + base = (conv == 'o') ? 8 : (conv == 'x' || conv == 'X') ? 16 : 10; + switch (len) + { + case LN_HH: + uv = (uintmax_t)(unsigned char)va_arg(*ap, int); + break; + case LN_H: + uv = (uintmax_t)(unsigned short)va_arg(*ap, int); + break; + case LN_L: + uv = (uintmax_t)va_arg(*ap, unsigned long); + break; + case LN_LL: + uv = (uintmax_t)va_arg(*ap, unsigned long long); + break; + case LN_J: + uv = va_arg(*ap, uintmax_t); + break; + case LN_Z: + uv = (uintmax_t)va_arg(*ap, size_t); + break; + case LN_T: + uv = (uintmax_t)va_arg(*ap, ptrdiff_t); + break; + default: + uv = (uintmax_t)va_arg(*ap, unsigned int); + break; + } + mag = uv; + } + return printf_fmt_int(sk, mag, is_signed, neg, flags, width, prec, base, upper, 0); +} + +/* %c */ +static int +printf_emit_char(struct printf_sink *sk, int flags, int width, unsigned char ch) +{ + size_t pad = (size_t)width > 1 ? (size_t)width - 1 : 0; + + if (pad > 0 && !(flags & PF_MINUS) && printf_emit_pad(sk, ' ', pad) < 0) + { + return -1; + } + if (printf_emit(sk, &ch, 1) < 0) + { + return -1; + } + if (pad > 0 && (flags & PF_MINUS) && printf_emit_pad(sk, ' ', pad) < 0) + { + return -1; + } + return 0; +} + +/* %s: NULL prints "(null)", precision truncates, width pads with spaces. */ +static int +printf_emit_string(struct printf_sink *sk, int flags, int width, int prec, const char *s) +{ + size_t n; + size_t pad; + + if (s == NULL) + { + s = "(null)"; + } + n = strlen(s); + if (prec >= 0 && (size_t)prec < n) + { + n = (size_t)prec; + } + pad = (size_t)width > n ? (size_t)width - n : 0; + if (pad > 0 && !(flags & PF_MINUS) && printf_emit_pad(sk, ' ', pad) < 0) + { + return -1; + } + if (printf_emit(sk, (const unsigned char *)s, n) < 0) + { + return -1; + } + if (pad > 0 && (flags & PF_MINUS) && printf_emit_pad(sk, ' ', pad) < 0) + { + return -1; + } + return 0; +} + +/* %p: "0x" followed by lowercase hex; NULL prints "0x0". */ +static int +printf_emit_pointer(struct printf_sink *sk, int flags, int width, int prec, void *p) +{ + return printf_fmt_int(sk, (uintmax_t)(uintptr_t)p, 0, 0, flags, width, prec, 16, 0, 1); +} + +/* --- float conversions (todo 16: %f/%F/%e/%E/%g/%G/%a/%A) ------------- */ + +/* + * A decomposed binary float, shared by every conversion and both lengths: + * sign, class (0 finite, 1 inf, 2 nan), the significand W and its binary + * exponent. sub marks a subnormal value (its exponent is the true one and W + * carries no hidden bit); ld marks an 80-bit long double source. Classed + * values carry mant = 0 and exp2 = 0. + */ +struct vfpn_x +{ + int neg; + int cls; + unsigned long long mant; + int exp2; + int sub; + int ld; +}; + +/* Split an IEEE-754 binary64 double into a struct vfpn_x. */ +static void +vfpn_decomp_double(struct vfpn_x *x, double v) +{ + uint64_t u; + uint64_t frac; + unsigned e11; + + __builtin_memcpy(&u, &v, sizeof u); + x->neg = (int)(u >> 63); + e11 = (unsigned)((u >> 52) & 0x7FFu); + frac = u & ((1ULL << 52) - 1u); + x->ld = 0; + if (e11 == 0x7FFu) + { + x->cls = (frac != 0) ? 2 : 1; + x->mant = 0; + x->exp2 = 0; + x->sub = 0; + } + else if (e11 == 0) + { + x->cls = 0; + x->mant = frac; + x->exp2 = -1074; + x->sub = 1; + } + else + { + x->cls = 0; + x->mant = frac | (1ULL << 52); + x->exp2 = (int)e11 - 1075; + x->sub = 0; + } +} + +/* Split an x87 80-bit long double into a struct vfpn_x. The in-memory layout + * is little-endian: 8 significand bytes, then a 16-bit sign/exponent word. */ +static void +vfpn_decomp_ldbl(struct vfpn_x *x, long double v) +{ + unsigned char raw[16]; + uint64_t sig; + uint16_t se; + unsigned e15; + + __builtin_memcpy(raw, &v, sizeof raw); + __builtin_memcpy(&sig, raw, sizeof sig); + __builtin_memcpy(&se, raw + 8, sizeof se); + x->neg = (int)(se >> 15); + e15 = se & 0x7FFFu; + x->ld = 1; + if (e15 == 0x7FFFu) + { + x->cls = (sig != 0x8000000000000000ULL) ? 2 : 1; + x->mant = 0; + x->exp2 = 0; + x->sub = 0; + } + else if (e15 == 0) + { + x->cls = 0; + x->mant = sig; + x->exp2 = -16445; + x->sub = 1; + } + else + { + x->cls = 0; + x->mant = sig; + x->exp2 = (int)e15 - 16446; + x->sub = 0; + } +} + +/* Exact comparison of mant*2^exp2 against 10^k. -1/0/1, or -2 when a bignum + * allocation fails (errno then carries __libc_malloc's error). */ +static int +vfpn_cmp_pow10(uint64_t mant, int exp2, int k) +{ + struct vfpn_bn A; + struct vfpn_bn B; + int r; + int c; + + vfpn_bn_init(&A); + vfpn_bn_init(&B); + if (k >= 0) + { + r = vfpn_bn_set_u64(&A, mant); + if (r == 0) + { + r = vfpn_bn_pow5(&B, (size_t)k); + } + if (r == 0) + { + if (exp2 >= k) + { + r = vfpn_bn_shl(&A, (size_t)(exp2 - k)); + } + else + { + r = vfpn_bn_shl(&B, (size_t)(k - exp2)); + } + } + } + else + { + /* value vs 1/(2^(-k)*5^(-k)): fold 5^(-k) into the mantissa side + * (mant*5^(-k)*2^(exp2-k) vs 1) so both sides stay integers. */ + int m5 = -k; + + r = vfpn_bn_set_u64(&A, mant); + for (int i = 0; r == 0 && i < m5; i++) + { + r = vfpn_bn_mul_small(&A, 5); + } + if (r == 0) + { + r = vfpn_bn_set_u64(&B, 1); + } + if (r == 0) + { + if (exp2 >= k) + { + r = vfpn_bn_shl(&A, (size_t)(exp2 - k)); + } + else + { + r = vfpn_bn_shl(&B, (size_t)(k - exp2)); + } + } + } + if (r != 0) + { + c = -2; + } + else + { + vfpn_bn_norm(&A); + vfpn_bn_norm(&B); + c = vfpn_bn_cmp(&A, &B); + } + vfpn_bn_free(&A); + vfpn_bn_free(&B); + return c; +} + +/* floor(log10(mant*2^exp2)) for mant != 0, or INT_MIN on allocation failure. + * The estimate is within one of the true value; the two comparison loops + * (bounded, the estimate error is at most one step) correct it exactly. */ +static int +vfpn_floor_log10(uint64_t mant, int exp2) +{ + int e2 = (int)(64 - __builtin_clzll(mant)) - 1 + exp2; + double est = (double)e2 * 0.30102999566398119521373889472449; + int d = (int)est; + + if (est < 0.0 && est != (double)d) + { + d--; + } + for (int i = 0; i < 4; i++) + { + int c = vfpn_cmp_pow10(mant, exp2, d + 1); + + if (c == -2) + { + return INT_MIN; + } + if (c < 0) + { + break; + } + d++; + } + for (int i = 0; i < 4; i++) + { + int c = vfpn_cmp_pow10(mant, exp2, d); + + if (c == -2) + { + return INT_MIN; + } + if (c >= 0) + { + break; + } + d--; + } + return d; +} + +/* Write the %e exponent suffix "e+04"/"E+100"/"e-05" (at least two digits) + * into out and return its length. */ +static size_t +printf_float_exp(char *out, int upper, long E) +{ + char rev[12]; + unsigned long ae; + size_t n = 0; + size_t m = 0; + size_t i; + + out[n++] = (char)(upper ? 'E' : 'e'); + if (E < 0) + { + out[n++] = '-'; + ae = (unsigned long)(-(E + 1)) + 1ul; + } + else + { + out[n++] = '+'; + ae = (unsigned long)E; + } + do + { + rev[m++] = (char)('0' + (int)(ae % 10ul)); + ae /= 10ul; + } while (ae != 0); + while (m < 2) + { + rev[m++] = '0'; + } + for (i = m; i-- > 0;) + { + out[n++] = rev[i]; + } + return n; +} + +/* Emit a fully composed float body with sign and width handling. special + * selects the inf/nan rules: the '0' flag is ignored and only spaces pad. */ +static int +printf_emit_float_body(struct printf_sink *sk, int flags, int width, char sign, const char *body, + size_t blen, int special) +{ + size_t extra = (sign != '\0') ? 1u : 0u; + size_t pad; + + if ((size_t)width > extra + blen) + { + pad = (size_t)width - extra - blen; + } + else + { + pad = 0; + } + if (pad > 0 && !(flags & PF_MINUS) && !((flags & PF_ZERO) && !special)) + { + if (printf_emit_pad(sk, ' ', pad) < 0) + { + return -1; + } + pad = 0; + } + if (sign != '\0' && printf_emit(sk, (const unsigned char *)&sign, 1) < 0) + { + return -1; + } + if (pad > 0 && !(flags & PF_MINUS) && printf_emit_pad(sk, '0', pad) < 0) + { + return -1; + } + if (printf_emit(sk, (const unsigned char *)body, blen) < 0) + { + return -1; + } + if ((flags & PF_MINUS) && (size_t)width > extra + blen && + printf_emit_pad(sk, ' ', (size_t)width - extra - blen) < 0) + { + return -1; + } + return 0; +} + +/* %f/%F/%e/%E of a decomposed value. Returns 0 on success, -1 on sink + * failure or on an internal allocation failure (errno set by __libc_malloc). */ +static int +printf_fmt_float(struct printf_sink *sk, int flags, int width, int prec, char conv, + const struct vfpn_x *x) +{ + uint64_t mant; + int exp2; + int upper; + int e_style; + int p; + char sign = '\0'; + + upper = (conv == 'F' || conv == 'E'); + e_style = (conv == 'e' || conv == 'E'); + p = (prec < 0) ? 6 : prec; + + if (x->neg) + { + sign = '-'; + } + else if (flags & PF_PLUS) + { + sign = '+'; + } + else if (flags & PF_SPACE) + { + sign = ' '; + } + + if (x->cls != 0) + { + const char *word = (x->cls == 2) ? (upper ? "NAN" : "nan") : (upper ? "INF" : "inf"); + + return printf_emit_float_body(sk, flags, width, sign, word, 3, 1); + } + mant = x->mant; + exp2 = x->exp2; + + { + struct vfpn_bn bn; + char local[576]; + char *buf = local; + /* An 80-bit long double carries up to 4933 integer digits; the +420 + * double-era margin only covers ~310. */ + size_t cap = (size_t)p + (x->ld ? 5100u : 420u); + char ex[16]; + size_t explen; + size_t blen; + long E = 0; + int D; + int rc; + + if (cap > sizeof local) + { + buf = (char *)__libc_malloc(cap); + if (buf == NULL) + { + return -1; + } + } + + if (mant == 0) + { + if (e_style) + { + size_t n = 1; + + buf[0] = '0'; + if (p > 0 || (flags & PF_HASH)) + { + buf[n++] = '.'; + for (int i = 0; i < p; i++) + { + buf[n++] = '0'; + } + } + explen = printf_float_exp(ex, upper, 0); + __builtin_memcpy(buf + n, ex, explen); + blen = n + explen; + } + else + { + size_t n = 1; + + buf[0] = '0'; + if (p > 0) + { + buf[n++] = '.'; + for (int i = 0; i < p; i++) + { + buf[n++] = '0'; + } + } + else if (flags & PF_HASH) + { + buf[n++] = '.'; + } + blen = n; + } + } + else + { + vfpn_bn_init(&bn); + if (e_style) + { + int d = vfpn_floor_log10(mant, exp2); + + if (d == INT_MIN) + { + rc = -1; + goto out; + } + rc = vfpn_round_scale10(&bn, mant, exp2, (long)p - (long)d); + if (rc != 0) + { + goto out; + } + D = vfpn_bn_to_dec(&bn, buf, cap); + if (D < 0) + { + rc = -1; + goto out; + } + E = (long)d + (long)D - (long)(p + 1); + if (p > 0) + { + __builtin_memmove(buf + 2, buf + 1, (size_t)p); + buf[1] = '.'; + } + else if (flags & PF_HASH) + { + buf[1] = '.'; + } + explen = printf_float_exp(ex, upper, E); + { + size_t pos = + (p > 0) ? (size_t)(2 + p) : (size_t)(1 + ((flags & PF_HASH) ? 1 : 0)); + + __builtin_memcpy(buf + pos, ex, explen); + blen = pos + explen; + } + } + else + { + rc = vfpn_round_scale10(&bn, mant, exp2, (long)p); + if (rc != 0) + { + goto out; + } + D = vfpn_bn_to_dec(&bn, buf, cap); + if (D < 0) + { + rc = -1; + goto out; + } + if (p == 0) + { + blen = (size_t)D; + if (flags & PF_HASH) + { + buf[blen++] = '.'; + } + } + else if (D > p) + { + __builtin_memmove(buf + (size_t)(D - p) + 1, buf + (size_t)(D - p), (size_t)p); + buf[D - p] = '.'; + blen = (size_t)D + 1; + } + else + { + __builtin_memmove(buf + 2 + (size_t)(p - D), buf, (size_t)D); + buf[0] = '0'; + buf[1] = '.'; + if (p - D > 0) + { + __builtin_memset(buf + 2, '0', (size_t)(p - D)); + } + blen = (size_t)p + 2; + } + } + rc = 0; + out: + vfpn_bn_free(&bn); + if (rc != 0) + { + if (buf != local) + { + __libc_free(buf); + } + return -1; + } + } + rc = printf_emit_float_body(sk, flags, width, sign, buf, blen, 0); + if (buf != local) + { + __libc_free(buf); + } + return rc; + } +} + +/* %g/%G of a decomposed value. P = prec<0 ? 6 : prec==0 ? 1 : prec + * significant digits are kept; the value is rounded to them first + * (vfpn_round_scale10, exactly as %e rounds to p+1), then %e-style is used + * when the rounded exponent E is < -4 or >= P and %f-style otherwise. + * Trailing zeros of the fraction (and a trailing point) are dropped unless + * '#'; %G only differs by the uppercase exponent letter and INF/NAN words. + * Returns 0 on success, -1 on sink failure or on an internal allocation + * failure (errno set by __libc_malloc). */ +static int +printf_fmt_g(struct printf_sink *sk, int flags, int width, int prec, char conv, + const struct vfpn_x *x) +{ + uint64_t mant; + int exp2; + int upper; + int P; + char sign = '\0'; + + upper = (conv == 'G'); + P = (prec < 0) ? 6 : prec; + if (P == 0) + { + P = 1; + } + + if (x->neg) + { + sign = '-'; + } + else if (flags & PF_PLUS) + { + sign = '+'; + } + else if (flags & PF_SPACE) + { + sign = ' '; + } + + if (x->cls != 0) + { + const char *word = (x->cls == 2) ? (upper ? "NAN" : "nan") : (upper ? "INF" : "inf"); + + return printf_emit_float_body(sk, flags, width, sign, word, 3, 1); + } + mant = x->mant; + exp2 = x->exp2; + + { + struct vfpn_bn bn; + char local[576]; + char *buf = local; + size_t cap = (size_t)P + (x->ld ? 5100u : 420u); + char ex[16]; + size_t explen; + size_t blen; + long E = 0; + int rc = 0; + + if (cap > sizeof local) + { + buf = (char *)__libc_malloc(cap); + if (buf == NULL) + { + return -1; + } + } + + if (mant == 0) + { + /* Zero is always %f-style with a "0" integer part: "0", or (with + * '#') "0." plus P-1 zeros when P > 1. */ + size_t n = 1; + + buf[0] = '0'; + if (P > 1 || (flags & PF_HASH)) + { + buf[n++] = '.'; + for (int i = 1; i < P; i++) + { + buf[n++] = '0'; + } + if (!(flags & PF_HASH)) + { + n = 1; /* "0." followed by zeros trims to "0" */ + } + } + blen = n; + } + else + { + int d; + int Dn; + + vfpn_bn_init(&bn); + d = vfpn_floor_log10(mant, exp2); + if (d == INT_MIN) + { + rc = -1; + goto out; + } + rc = vfpn_round_scale10(&bn, mant, exp2, (long)(P - 1) - (long)d); + if (rc != 0) + { + goto out; + } + Dn = vfpn_bn_to_dec(&bn, buf, cap); + if (Dn < 0) + { + rc = -1; + goto out; + } + /* Rounded exponent: a carry past P digits bumps it by one. */ + E = (long)d + (long)Dn - (long)P; + if (Dn == P + 1 && d == P - 1) + { + /* glibc: the value's layout was %f-style with exactly P + * integer digits (d == P-1) and rounding carried it over to + * 10^P; converted to %e-style on the fly, its mantissa + * collapses to a single '1'. */ + size_t pos = 1; + + buf[0] = '1'; + if (flags & PF_HASH) + { + buf[pos++] = '.'; + } + explen = printf_float_exp(ex, upper, E); + __builtin_memcpy(buf + pos, ex, explen); + blen = pos + explen; + } + else if (E < -4 || E >= P) + { + /* %e-style: d[.ddd]e±XX. The digit string holds Dn digits + * (P, or P+1 on a rounding carry to exactly 10^P); only the + * first P are significant. */ + size_t pos; + + if (P > 1) + { + __builtin_memmove(buf + 2, buf + 1, (size_t)(P - 1)); + buf[1] = '.'; + pos = (size_t)P + 1; /* one past the last fraction digit */ + if (!(flags & PF_HASH)) + { + while (pos > 2 && buf[pos - 1] == '0') + { + pos--; + } + } + if (pos == 2 && !(flags & PF_HASH)) + { + pos = 1; /* fraction trimmed away: drop the point */ + } + } + else + { + pos = 1; + if (flags & PF_HASH) + { + buf[pos++] = '.'; + } + } + explen = printf_float_exp(ex, upper, E); + __builtin_memcpy(buf + pos, ex, explen); + blen = pos + explen; + } + else + { + /* %f-style: point after E+1 significant digits; with E < 0 + * the integer part is "0" and -(E+1) zeros lead the fraction. + * Fraction trailing zeros are trimmed unless '#'. */ + size_t pos; + + if (E >= 0) + { + size_t ip = (size_t)E + 1; /* integer digits, 1..P */ + size_t fdig = (size_t)P - ip; + + if (fdig > 0) + { + __builtin_memmove(buf + ip + 1, buf + ip, fdig); + buf[ip] = '.'; + pos = ip + 1 + fdig; + if (!(flags & PF_HASH)) + { + while (pos > ip + 1 && buf[pos - 1] == '0') + { + pos--; + } + } + if (pos == ip + 1 && !(flags & PF_HASH)) + { + pos = ip; /* fraction trimmed away: drop the point */ + } + } + else + { + pos = ip; + if (flags & PF_HASH) + { + buf[pos++] = '.'; + } + } + } + else + { + size_t z = (size_t)(-E - 1); /* leading fraction zeros */ + + __builtin_memmove(buf + 2 + z, buf, (size_t)P); + buf[0] = '0'; + buf[1] = '.'; + if (z > 0) + { + __builtin_memset(buf + 2, '0', z); + } + pos = 2 + z + (size_t)P; + if (!(flags & PF_HASH)) + { + while (pos > 2 + z && buf[pos - 1] == '0') + { + pos--; + } + } + } + blen = pos; + } + rc = 0; + out: + vfpn_bn_free(&bn); + if (rc != 0) + { + if (buf != local) + { + __libc_free(buf); + } + return -1; + } + } + rc = printf_emit_float_body(sk, flags, width, sign, buf, blen, 0); + if (buf != local) + { + __libc_free(buf); + } + return rc; + } +} + +/* Write the %a exponent suffix "p+0"/"P-16384"/"p-1022" into out (the + * magnitude is never zero-padded) and return its length. */ +static size_t +printf_fmt_a_exp(char *out, int upper, long E) +{ + char rev[8]; + unsigned long ae; + size_t n = 0; + size_t m = 0; + size_t i; + + out[n++] = (char)(upper ? 'P' : 'p'); + if (E < 0) + { + out[n++] = '-'; + ae = (unsigned long)(-(E + 1)) + 1ul; + } + else + { + out[n++] = '+'; + ae = (unsigned long)E; + } + do + { + rev[m++] = (char)('0' + (int)(ae % 10ul)); + ae /= 10ul; + } while (ae != 0); + for (i = m; i-- > 0;) + { + out[n++] = rev[i]; + } + return n; +} + +/* %a/%A of a decomposed value, byte-matching glibc. The mantissa W is the + * full significand whose hex, padded to F+1 digits (F = 13 for double, 15 + * for long double), places the radix point after its first digit; E = + * exp2 + 4*F is the corresponding binary exponent. Returns 0 on success or + * -1 on an allocation failure (errno set by __libc_malloc). */ +static int +printf_fmt_a(struct printf_sink *sk, int flags, int width, int prec, char conv, + const struct vfpn_x *x) +{ + const char *digits = (conv == 'A') ? "0123456789ABCDEF" : "0123456789abcdef"; + const char *word; + char local[128]; + char *buf = local; + size_t cap = (size_t)(prec > 0 ? prec : 0) + 32; + size_t blen = 0; + size_t fstart; + size_t fend; + size_t extra; + size_t pad = 0; + size_t zeros = 0; + size_t i; + char ex[8]; + size_t explen; + int F = x->ld ? 15 : 13; + long E = x->exp2 + 4 * F; + char sign = '\0'; + + if (x->neg) + { + sign = '-'; + } + else if (flags & PF_PLUS) + { + sign = '+'; + } + else if (flags & PF_SPACE) + { + sign = ' '; + } + if (x->cls != 0) + { + word = (x->cls == 2) ? (conv == 'A' ? "NAN" : "nan") : (conv == 'A' ? "INF" : "inf"); + + return printf_emit_float_body(sk, flags, width, sign, word, 3, 1); + } + + if (cap > sizeof local) + { + buf = (char *)__libc_malloc(cap); + if (buf == NULL) + { + return -1; + } + } + + if (x->mant == 0) + { + /* Zero is "0x0"; the point and its zeros follow the precision. */ + E = 0; + buf[blen++] = '0'; + if (prec > 0) + { + buf[blen++] = '.'; + for (i = 0; i < (size_t)prec; i++) + { + buf[blen++] = '0'; + } + } + else if (flags & PF_HASH) + { + buf[blen++] = '.'; + } + } + else if (prec < 0) + { + /* Default precision: the F+1-digit window, trailing fraction zeros + * dropped ('#' keeps the bare point at most, never the zeros). */ + buf[blen++] = digits[(int)((x->mant >> (4 * F)) & 0xF)]; + fstart = blen; + for (i = 0; i < (size_t)F; i++) + { + buf[blen++] = digits[(int)((x->mant >> (4 * ((int)F - 1 - (int)i))) & 0xF)]; + } + fend = blen; + while (fend > fstart && buf[fend - 1] == '0') + { + fend--; + } + if (fend > fstart) + { + __builtin_memmove(buf + fstart + 1, buf + fstart, fend - fstart); + buf[fstart] = '.'; + blen = fend + 1; + } + else + { + blen = fstart; + if (flags & PF_HASH) + { + buf[blen++] = '.'; + } + } + } + else if (prec >= F) + { + /* The full window with the requested trailing zeros, never rounded. */ + buf[blen++] = digits[(int)((x->mant >> (4 * F)) & 0xF)]; + buf[blen++] = '.'; + for (i = 0; i < (size_t)F; i++) + { + buf[blen++] = digits[(int)((x->mant >> (4 * ((int)F - 1 - (int)i))) & 0xF)]; + } + for (i = 0; i < (size_t)prec - (size_t)F; i++) + { + buf[blen++] = '0'; + } + } + else + { + /* Round half to even on the 4*(F-p) discarded bits. The rounded + * window R fits p+1 hex digits, except a full-16-digit long double + * window that rounds up to exactly 16^(p+1): the '1' carries into + * the exponent. */ + unsigned q = 4u * (unsigned)(F - prec); + uint64_t R = x->mant >> q; + uint64_t D = x->mant - (R << q); + uint64_t half = 1ull << (q - 1); + + if (D > half || (D == half && (R & 1ull) != 0)) + { + R++; + } + if (R == 1ull << (4u * (unsigned)(prec + 1))) + { + E += 4; + buf[blen++] = '1'; + if (prec > 0) + { + buf[blen++] = '.'; + for (i = 0; i < (size_t)prec; i++) + { + buf[blen++] = '0'; + } + } + else if (flags & PF_HASH) + { + buf[blen++] = '.'; + } + } + else + { + size_t start = blen; + + for (i = 0; i <= (size_t)prec; i++) + { + buf[blen++] = digits[(int)((R >> (4 * ((int)prec - (int)i))) & 0xF)]; + } + if (prec > 0) + { + __builtin_memmove(buf + start + 2, buf + start + 1, (size_t)prec); + buf[start + 1] = '.'; + blen++; + } + else if (flags & PF_HASH) + { + buf[blen++] = '.'; + } + } + } + + explen = printf_fmt_a_exp(ex, conv == 'A', E); + __builtin_memcpy(buf + blen, ex, explen); + blen += explen; + + extra = (sign != '\0') ? 1u : 0u; + if ((flags & PF_ZERO) && !(flags & PF_MINUS) && (size_t)width > extra + 2 + blen) + { + zeros = (size_t)width - extra - 2 - blen; + } + else if ((size_t)width > extra + 2 + blen) + { + pad = (size_t)width - extra - 2 - blen; + } + if (pad > 0 && !(flags & PF_MINUS) && printf_emit_pad(sk, ' ', pad) < 0) + { + goto fail; + } + if (sign != '\0' && printf_emit(sk, (const unsigned char *)&sign, 1) < 0) + { + goto fail; + } + if (printf_emit(sk, (const unsigned char *)((conv == 'A') ? "0X" : "0x"), 2) < 0) + { + goto fail; + } + if (zeros > 0 && printf_emit_pad(sk, '0', zeros) < 0) + { + goto fail; + } + if (printf_emit(sk, (const unsigned char *)buf, blen) < 0) + { + goto fail; + } + if (pad > 0 && (flags & PF_MINUS) && printf_emit_pad(sk, ' ', pad) < 0) + { + goto fail; + } + if (buf != local) + { + __libc_free(buf); + } + return 0; +fail: + if (buf != local) + { + __libc_free(buf); + } + return -1; +} + +/* Fetch a float argument for a f/e/g/a conversion: 'L' reads a long double, + * any other (or no) length reads a double. */ +static void +printf_decomp_arg(struct vfpn_x *x, va_list *ap, int len) +{ + if (len == LN_CAPL) + { + vfpn_decomp_ldbl(x, va_arg(*ap, long double)); + } + else + { + vfpn_decomp_double(x, va_arg(*ap, double)); + } +} + +/* + * Scan format and drive every conversion. Returns 0 on success (all output + * accepted) or -1 once the sink has failed. + */ +static int +printf_core(struct printf_sink *sk, const char *fmt, va_list *ap) +{ + for (;;) + { + const char *lit = fmt; + int flags; + int width; + int prec; + int len; + char conv; + + while (*fmt != '\0' && *fmt != '%') + { + fmt++; + } + if (printf_emit(sk, (const unsigned char *)lit, (size_t)(fmt - lit)) < 0) + { + return -1; + } + if (*fmt == '\0') + { + return 0; + } + fmt++; /* skip the '%' */ + if (*fmt == '%') + { + fmt++; + if (printf_emit(sk, (const unsigned char *)"%", 1) < 0) + { + return -1; + } + continue; + } + if (*fmt == '\0') + { + /* A bare trailing '%': print it verbatim. */ + return printf_emit(sk, (const unsigned char *)"%", 1); + } + + flags = 0; + for (;;) + { + if (*fmt == '-') + { + flags |= PF_MINUS; + fmt++; + } + else if (*fmt == '+') + { + flags |= PF_PLUS; + fmt++; + } + else if (*fmt == ' ') + { + flags |= PF_SPACE; + fmt++; + } + else if (*fmt == '#') + { + flags |= PF_HASH; + fmt++; + } + else if (*fmt == '0') + { + flags |= PF_ZERO; + fmt++; + } + else + { + break; + } + } + + width = 0; + if (*fmt == '*') + { + int w = va_arg(*ap, int); + + fmt++; + if (w < 0) + { + flags |= PF_MINUS; + width = (w == INT_MIN) ? INT_MAX : -w; + } + else + { + width = w; + } + } + else + { + long wl = 0; + + while (*fmt >= '0' && *fmt <= '9') + { + wl = wl * 10 + (*fmt - '0'); + if (wl > INT_MAX) + { + wl = INT_MAX; + } + fmt++; + } + width = (int)wl; + } + + prec = -1; + if (*fmt == '.') + { + fmt++; + prec = 0; + if (*fmt == '*') + { + int p = va_arg(*ap, int); + + fmt++; + if (p >= 0) + { + prec = p; + } + /* A negative * precision is treated as if omitted. */ + } + else + { + long pl = 0; + + while (*fmt >= '0' && *fmt <= '9') + { + pl = pl * 10 + (*fmt - '0'); + if (pl > INT_MAX) + { + pl = INT_MAX; + } + fmt++; + } + prec = (int)pl; + } + } + + len = LN_NONE; + if (*fmt == 'h' && fmt[1] == 'h') + { + len = LN_HH; + fmt += 2; + } + else if (*fmt == 'h') + { + len = LN_H; + fmt++; + } + else if (*fmt == 'l' && fmt[1] == 'l') + { + len = LN_LL; + fmt += 2; + } + else if (*fmt == 'l') + { + len = LN_L; + fmt++; + } + else if (*fmt == 'j') + { + len = LN_J; + fmt++; + } + else if (*fmt == 'z') + { + len = LN_Z; + fmt++; + } + else if (*fmt == 't') + { + len = LN_T; + fmt++; + } + else if (*fmt == 'L') + { + len = LN_CAPL; + fmt++; + } + + conv = *fmt; + if (conv == '\0') + { + /* The format ends right after a '%' with no conversion. */ + return printf_emit(sk, (const unsigned char *)"%", 1); + } + fmt++; + + if (conv == '%') + { + if (printf_emit(sk, (const unsigned char *)"%", 1) < 0) + { + return -1; + } + continue; + } + switch (conv) + { + case 'd': + case 'i': + case 'u': + case 'o': + case 'x': + case 'X': + if (printf_emit_int(sk, ap, conv, len, flags, width, prec) < 0) + { + return -1; + } + break; + case 'c': + if (printf_emit_char(sk, flags, width, (unsigned char)va_arg(*ap, int)) < 0) + { + return -1; + } + break; + case 's': + if (printf_emit_string(sk, flags, width, prec, va_arg(*ap, const char *)) < 0) + { + return -1; + } + break; + case 'p': + if (printf_emit_pointer(sk, flags, width, prec, va_arg(*ap, void *)) < 0) + { + return -1; + } + break; + case 'n': + { + void *np = va_arg(*ap, void *); + + switch (len) + { + case LN_HH: + *(signed char *)np = (signed char)sk->count; + break; + case LN_H: + *(short *)np = (short)sk->count; + break; + case LN_L: + *(long *)np = (long)sk->count; + break; + case LN_LL: + *(long long *)np = (long long)sk->count; + break; + case LN_J: + *(intmax_t *)np = (intmax_t)sk->count; + break; + case LN_Z: + *(ssize_t *)np = (ssize_t)sk->count; + break; + case LN_T: + *(ptrdiff_t *)np = (ptrdiff_t)sk->count; + break; + default: + *(int *)np = (int)sk->count; + break; + } + break; + } + case 'f': + case 'F': + case 'e': + case 'E': + case 'g': + case 'G': + { + struct vfpn_x x; + + printf_decomp_arg(&x, ap, len); + if (conv == 'g' || conv == 'G') + { + if (printf_fmt_g(sk, flags, width, prec, conv, &x) < 0) + { + return -1; + } + } + else if (printf_fmt_float(sk, flags, width, prec, conv, &x) < 0) + { + return -1; + } + break; + } + case 'a': + case 'A': + { + struct vfpn_x x; + + printf_decomp_arg(&x, ap, len); + if (printf_fmt_a(sk, flags, width, prec, conv, &x) < 0) + { + return -1; + } + break; + } + default: + { + /* An unrecognized conversion is emitted verbatim. */ + unsigned char ch = (unsigned char)conv; + + if (printf_emit(sk, &ch, 1) < 0) + { + return -1; + } + break; + } + } + } +} + +/* Wire a FILE stream (or stderr, for perror) as the sink and run the core. */ +int +vfprintf(FILE *restrict stream, const char *restrict format, va_list ap) +{ + struct printf_sink sk; + va_list apc; + + sk.put = printf_put_file; + sk.ctx = (void *)stream; + sk.count = 0; + sk.fail = 0; + va_copy(apc, ap); + if (printf_core(&sk, format, &apc) < 0) + { + va_end(apc); + return -1; + } + va_end(apc); + return (int)sk.count; +} + +int +vprintf(const char *restrict format, va_list ap) +{ + return vfprintf(stdout, format, ap); +} + +int +printf(const char *restrict format, ...) +{ + va_list ap; + int rc; + + va_start(ap, format); + rc = vfprintf(stdout, format, ap); + va_end(ap); + return rc; +} + +int +fprintf(FILE *restrict stream, const char *restrict format, ...) +{ + va_list ap; + int rc; + + va_start(ap, format); + rc = vfprintf(stream, format, ap); + va_end(ap); + return rc; +} + +/* + * Wire a memory sink and run the core. cap is the total allocation including + * the terminating NUL (SIZE_MAX for sprintf). Returns -1 on failure, else + * the number of characters that would have been written even when truncated. + */ +static int +printf_vmem(char *restrict s, size_t cap, const char *restrict format, va_list ap) +{ + struct printf_mem mem; + struct printf_sink sk; + va_list apc; + int rc; + + mem.dst = s; + mem.cap = cap; + mem.len = 0; + sk.put = printf_put_mem; + sk.ctx = &mem; + sk.count = 0; + sk.fail = 0; + va_copy(apc, ap); + rc = printf_core(&sk, format, &apc); + va_end(apc); + if (rc < 0) + { + return -1; + } + if (cap > 0 && s != NULL) + { + size_t nul = sk.count < cap - 1 ? sk.count : cap - 1; + + s[nul] = '\0'; + } + return (int)sk.count; +} + +int +vsprintf(char *restrict s, const char *restrict format, va_list ap) +{ + return printf_vmem(s, (size_t)-1, format, ap); +} + +int +sprintf(char *restrict s, const char *restrict format, ...) +{ + va_list ap; + int rc; + + va_start(ap, format); + rc = vsprintf(s, format, ap); + va_end(ap); + return rc; +} + +int +vsnprintf(char *restrict s, size_t n, const char *restrict format, va_list ap) +{ + return printf_vmem(s, n, format, ap); +} + +int +snprintf(char *restrict s, size_t n, const char *restrict format, ...) +{ + va_list ap; + int rc; + + va_start(ap, format); + rc = vsnprintf(s, n, format, ap); + va_end(ap); + return rc; +} + +int +vdprintf(int fd, const char *restrict format, va_list ap) +{ + struct printf_fd fdctx; + struct printf_sink sk; + va_list apc; + + if (fd < 0) + { + errno = EBADF; + return -1; + } + fdctx.fd = fd; + sk.put = printf_put_fd; + sk.ctx = &fdctx; + sk.count = 0; + sk.fail = 0; + va_copy(apc, ap); + if (printf_core(&sk, format, &apc) < 0) + { + va_end(apc); + return -1; + } + va_end(apc); + return (int)sk.count; +} + +int +dprintf(int fd, const char *restrict format, ...) +{ + va_list ap; + int rc; + + va_start(ap, format); + rc = vdprintf(fd, format, ap); + va_end(ap); + return rc; +} + +#if VLIBC_LEVEL_GE(2) + +int +vasprintf(char **restrict strp, const char *restrict format, va_list ap) +{ + struct printf_mem mem; + struct printf_sink sk; + va_list ap2; + int rc; + + if (strp == NULL) + { + errno = EINVAL; + return -1; + } + /* Count-only pass to size the allocation (dst NULL, cap 0). */ + mem.dst = NULL; + mem.cap = 0; + mem.len = 0; + sk.put = printf_put_mem; + sk.ctx = &mem; + sk.count = 0; + sk.fail = 0; + va_copy(ap2, ap); + rc = printf_core(&sk, format, &ap2); + va_end(ap2); + if (rc < 0) + { + *strp = NULL; + return -1; + } + *strp = (char *)__libc_malloc(mem.len + 1); + if (*strp == NULL) + { + return -1; + } + /* Fill pass into the freshly sized buffer. */ + mem.dst = *strp; + mem.cap = mem.len + 1; + mem.len = 0; + sk.count = 0; + sk.fail = 0; + va_copy(ap2, ap); + rc = printf_core(&sk, format, &ap2); + va_end(ap2); + if (rc < 0) + { + __libc_free(*strp); + *strp = NULL; + return -1; + } + (*strp)[sk.count] = '\0'; + return (int)sk.count; +} + +int +asprintf(char **restrict strp, const char *restrict format, ...) +{ + va_list ap; + int rc; + + va_start(ap, format); + rc = vasprintf(strp, format, ap); + va_end(ap); + return rc; +} + +#endif /* VLIBC_LEVEL_GE(2) */ + +/* + * Print ": \n" to stderr (the "s: " part is skipped when + * s is NULL or empty). The message text is emitted raw — never as a format — + * because it may contain arbitrary bytes such as '%' from a file name. + */ +void +perror(const char *s) +{ + struct printf_sink sk; + const char *msg; + + sk.put = printf_put_file; + sk.ctx = (void *)stderr; + sk.count = 0; + sk.fail = 0; + if (s != NULL && s[0] != '\0') + { + (void)printf_emit(&sk, (const unsigned char *)s, strlen(s)); + (void)printf_emit(&sk, (const unsigned char *)": ", 2); + } + msg = strerror(errno); + (void)printf_emit(&sk, (const unsigned char *)msg, strlen(msg)); + (void)printf_emit(&sk, (const unsigned char *)"\n", 1); +} diff --git a/src/stdio/vfprintf_float.c b/src/stdio/vfprintf_float.c new file mode 100644 index 0000000..691f173 --- /dev/null +++ b/src/stdio/vfprintf_float.c @@ -0,0 +1,842 @@ +/* internal unsigned 2^32-limb bignum core for printf float formatting (todo 16) + * clean-room implementation; nothing here is exported */ +/* + * Invariants: limbs are little-endian (value = sum d[i] * 2^(32*i)); n == 0 + * means value 0; no trailing-zero-limb invariant is kept -- callers normalize + * via vfpn_bn_norm. Shifts and rounding operate in place; allocation failure + * is reported as -1 and leaves errno to __libc_malloc. + */ +#include "../internal/malloc.h" +#include +#include + +struct vfpn_bn +{ + uint32_t fixed[640]; + uint32_t *d; + size_t n; + size_t cap; + int heap; +}; + +static __attribute__((unused)) void +vfpn_bn_init(struct vfpn_bn *b) +{ + b->d = b->fixed; + b->n = 0; + b->cap = 640; + b->heap = 0; +} + +static __attribute__((unused)) void +vfpn_bn_free(struct vfpn_bn *b) +{ + if (b->heap) + { + __libc_free(b->d); + } +} + +static __attribute__((unused)) int +vfpn_bn_reserve(struct vfpn_bn *b, size_t need) +{ + if (need <= b->cap) + { + return 0; + } + + uint32_t *new = __libc_malloc(need * sizeof(uint32_t)); + if (!new) + { + return -1; + } + + size_t copy = (b->cap < need) ? b->cap : need; + __builtin_memcpy(new, b->d, copy * sizeof(uint32_t)); + if (b->heap) + { + __libc_free(b->d); + } + + b->d = new; + b->cap = need; + b->heap = 1; + return 0; +} + +static __attribute__((unused)) void +vfpn_bn_norm(struct vfpn_bn *b) +{ + while (b->n > 0 && b->d[b->n - 1] == 0) + { + b->n--; + } +} + +static __attribute__((unused)) int +vfpn_bn_set_u64(struct vfpn_bn *b, uint64_t v) +{ + size_t need = 0; + for (uint64_t t = v; t != 0; t >>= 32) + { + need++; + } + + if (vfpn_bn_reserve(b, need) != 0) + { + return -1; + } + + b->n = 0; + while (v != 0) + { + b->d[b->n++] = (uint32_t)v; + v >>= 32; + } + + return 0; +} + +static __attribute__((unused)) int +vfpn_bn_mul_small(struct vfpn_bn *b, uint32_t m) +{ + if (m == 0) + { + b->n = 0; + return 0; + } + + if (vfpn_bn_reserve(b, b->n + 1) != 0) + { + return -1; + } + + uint64_t carry = 0; + for (size_t i = 0; i < b->n; i++) + { + uint64_t cur = (uint64_t)b->d[i] * m + carry; + b->d[i] = (uint32_t)cur; + carry = cur >> 32; + } + + if (carry != 0) + { + b->d[b->n++] = (uint32_t)carry; + } + + return 0; +} + +static __attribute__((unused)) int +vfpn_bn_shl(struct vfpn_bn *b, size_t bits) +{ + if (bits == 0) + { + return 0; + } + + size_t wl = bits / 32; + size_t s = bits % 32; + size_t newn = b->n + wl + ((s != 0) ? 1 : 0); + if (vfpn_bn_reserve(b, newn) != 0) + { + return -1; + } + + if (s == 0) + { + for (size_t i = b->n; i-- > 0;) + { + b->d[i + wl] = b->d[i]; + } + + for (size_t i = 0; i < wl; i++) + { + b->d[i] = 0; + } + } + else + { + for (size_t j = newn; j-- > 0;) + { + uint64_t cur = 0; + if (j >= wl && j - wl < b->n) + { + cur |= (uint64_t)b->d[j - wl] << s; + } + + if (j > wl && j - wl - 1 < b->n) + { + cur |= (uint64_t)b->d[j - wl - 1] >> (32 - s); + } + + b->d[j] = (uint32_t)cur; + } + } + + b->n = newn; + return 0; +} + +static __attribute__((unused)) int +vfpn_bn_add_one(struct vfpn_bn *b) +{ + size_t i = 0; + while (i < b->n) + { + b->d[i]++; + if (b->d[i] != 0) + { + return 0; + } + + i++; + } + + if (vfpn_bn_reserve(b, b->n + 1) != 0) + { + return -1; + } + + b->d[b->n] = 1; + b->n++; + return 0; +} + +static __attribute__((unused)) int +vfpn_bn_is_zero(const struct vfpn_bn *b) +{ + return b->n == 0; +} + +static __attribute__((unused)) int +vfpn_bn_cmp(const struct vfpn_bn *a, const struct vfpn_bn *b) +{ + if (a->n < b->n) + { + return -1; + } + + if (a->n > b->n) + { + return 1; + } + + for (size_t i = a->n; i-- > 0;) + { + if (a->d[i] < b->d[i]) + { + return -1; + } + + if (a->d[i] > b->d[i]) + { + return 1; + } + } + + return 0; +} + +static __attribute__((unused)) int +vfpn_bn_shr_round(struct vfpn_bn *b, size_t bits) +{ + if (bits == 0) + { + return 0; + } + + size_t w = bits / 32; + size_t s = bits % 32; + + if (w >= b->n) + { + /* Whole value discarded: result is 0, rounded up iff the original + * value exceeds 2^(bits-1). That can only happen when bits == 32*n + * (s == 0 and w == n), so compare the top limb against half. */ + int round = 0; + if (s == 0 && w == b->n && b->n > 0) + { + uint32_t top = b->d[b->n - 1]; + if (top > 0x80000000u) + { + round = 1; + } + else if (top == 0x80000000u) + { + for (size_t i = b->n - 1; i-- > 0;) + { + if (b->d[i] != 0) + { + round = 1; + break; + } + } + } + } + + b->n = 0; + if (round) + { + return vfpn_bn_add_one(b); + } + + return 0; + } + + /* Round bit and sticky are captured from the discarded low `bits` bits + * of the original value before the in-place shift. */ + int round_bit; + int sticky; + if (s > 0) + { + round_bit = (int)((b->d[w] >> (s - 1)) & 1u); + sticky = (b->d[w] & ((1u << (s - 1)) - 1u)) != 0; + for (size_t i = 0; i < w; i++) + { + if (b->d[i] != 0) + { + sticky = 1; + break; + } + } + } + else + { + round_bit = (int)((b->d[w - 1] >> 31) & 1u); + sticky = (b->d[w - 1] & 0x7FFFFFFFu) != 0; + for (size_t i = 0; i + 1 < w; i++) + { + if (b->d[i] != 0) + { + sticky = 1; + break; + } + } + } + + /* In-place right shift by `bits`; ascending so every source limb is + * still readable when its destination is written. */ + size_t old_n = b->n; + if (s == 0) + { + for (size_t j = 0; j + w < old_n; j++) + { + b->d[j] = b->d[j + w]; + } + } + else + { + for (size_t j = 0; j + w < old_n; j++) + { + uint32_t lo = b->d[j + w] >> s; + uint32_t hi = (j + w + 1 < old_n) ? (b->d[j + w + 1] << (32 - s)) : 0; + b->d[j] = lo | hi; + } + } + + b->n = old_n - w; + vfpn_bn_norm(b); + + int odd = (b->n > 0 && (b->d[0] & 1u)) ? 1 : 0; + if (round_bit && (sticky || odd)) + { + return vfpn_bn_add_one(b); + } + + return 0; +} + +static __attribute__((unused)) uint32_t +vfpn_bn_divmod_small_1e9(struct vfpn_bn *b) +{ + uint64_t r = 0; + for (size_t i = b->n; i-- > 0;) + { + uint64_t cur = (r << 32) | b->d[i]; + b->d[i] = (uint32_t)(cur / 1000000000ULL); + r = cur % 1000000000ULL; + } + + vfpn_bn_norm(b); + return (uint32_t)r; +} + +static __attribute__((unused)) int +vfpn_bn_pow5(struct vfpn_bn *b, size_t k) +{ + int r = vfpn_bn_set_u64(b, 1); + if (r != 0) + { + return r; + } + + for (size_t i = 0; i < k; i++) + { + r = vfpn_bn_mul_small(b, 5); + if (r != 0) + { + return r; + } + } + + return 0; +} + +/* --- exact decimal rounding layer ------------------------------------ */ + +/* + * Number of bits needed to hold |b| (index of the highest set bit plus one); + * 0 for the value zero. The caller may leave a trailing zero limb in place, + * so the scan starts from the recorded length and skips down. + */ +static __attribute__((unused)) size_t +vfpn_bn_bitlen(const struct vfpn_bn *b) +{ + if (b->n == 0) + { + return 0; + } + + size_t i = b->n - 1; + while (i > 0 && b->d[i] == 0) + { + i--; + } + + if (b->d[i] == 0) + { + return 0; + } + + return i * 32 + (size_t)(32 - __builtin_clz(b->d[i])); +} + +static __attribute__((unused)) int +vfpn_bn_get_bit(const struct vfpn_bn *b, size_t i) +{ + return (int)((b->d[i / 32] >> (i % 32)) & 1u); +} + +/* + * Set bit i of b. Zero-extends the limb array when the bit lies beyond the + * current length; ORing never clears, so the value only grows. + */ +static __attribute__((unused)) int +vfpn_bn_set_bit(struct vfpn_bn *b, size_t i, int bit) +{ + if (!bit) + { + return 0; + } + + if (b->n <= i / 32) + { + size_t need = i / 32 + 1; + if (vfpn_bn_reserve(b, need) != 0) + { + return -1; + } + + for (size_t j = b->n; j < need; j++) + { + b->d[j] = 0; + } + + b->n = need; + } + + b->d[i / 32] |= (1u << (i % 32)); + return 0; +} + +/* + * Shift |b| left by one bit in place (limb ripple). The caller's cap must + * already make room for the possible extra top limb; the reserve here is + * only a safety net and normally returns immediately. + */ +static __attribute__((unused)) int +vfpn_bn_shl1(struct vfpn_bn *b) +{ + if (b->n == 0) + { + return 0; + } + + if (vfpn_bn_reserve(b, b->n + 1) != 0) + { + return -1; + } + + uint32_t carry = 0; + for (size_t i = 0; i < b->n; i++) + { + uint32_t cur = b->d[i]; + b->d[i] = (cur << 1) | carry; + carry = cur >> 31; + } + + if (carry != 0) + { + b->d[b->n++] = carry; + } + + return 0; +} + +/* + * In-place limb-wise subtraction a -= b. The caller guarantees a >= b, so + * the borrow always clears before the top of a; a is renormalized. + */ +static __attribute__((unused)) void +vfpn_bn_sub_inplace(struct vfpn_bn *a, const struct vfpn_bn *b) +{ + uint32_t borrow = 0; + size_t i = 0; + for (; i < b->n; i++) + { + uint64_t diff = (uint64_t)a->d[i] - (uint64_t)b->d[i] - borrow; + a->d[i] = (uint32_t)diff; + borrow = (uint32_t)((diff >> 32) & 1u); + } + + while (i < a->n && borrow != 0) + { + a->d[i]--; + borrow = (a->d[i] == 0xFFFFFFFFu) ? 1u : 0u; + i++; + } + + vfpn_bn_norm(a); +} + +/* + * Q = N / D, R = N mod D by restoring binary long division. N may be zero; + * D must be nonzero. Q and R are written over whatever they held. The + * quotients bits are produced most-significant first; the loop runs over the + * dividend's bit positions from bitlen(N)-1 down to 0, pulling one new + * dividend bit into R and subtracting D whenever R >= D. + */ +static __attribute__((unused)) int +vfpn_divmod_binary(struct vfpn_bn *Q, struct vfpn_bn *R, const struct vfpn_bn *N, + const struct vfpn_bn *D) +{ + if (vfpn_bn_is_zero(D)) + { + return -1; + } + + if (vfpn_bn_reserve(Q, N->n + 2) != 0) + { + return -1; + } + + if (vfpn_bn_reserve(R, N->n + 2) != 0) + { + return -1; + } + + Q->n = 0; + R->n = 0; + + size_t nbits = vfpn_bn_bitlen(N); + for (size_t i = nbits; i-- > 0;) + { + if (vfpn_bn_shl1(R) != 0) + { + return -1; + } + + if (vfpn_bn_set_bit(R, 0, vfpn_bn_get_bit(N, i)) != 0) + { + return -1; + } + + if (vfpn_bn_cmp(R, D) >= 0) + { + vfpn_bn_sub_inplace(R, D); + if (vfpn_bn_set_bit(Q, i, 1) != 0) + { + return -1; + } + } + } + + vfpn_bn_norm(Q); + vfpn_bn_norm(R); + return 0; +} + +/* + * Compare 2*a against b (both canonical base-2^32 digit strings) without + * building the doubled value: doubling a limb's carry is the next limb's + * (d[i] >> 31), which the loop below folds in, and an extra top digit + * appears when the original top limb had its high bit set. All digits of 2*a + * stay below 2^32, so a plain digitwise comparison is exact. + */ +static __attribute__((unused)) int +vfpn_bn_cmp_doubled(const struct vfpn_bn *a, const struct vfpn_bn *b) +{ + if (a->n == 0) + { + return vfpn_bn_is_zero(b) ? 0 : -1; + } + + size_t extra = (a->d[a->n - 1] >> 31) ? 1u : 0u; + size_t la = a->n + extra; + if (la != b->n) + { + return (la > b->n) ? 1 : -1; + } + + for (size_t i = la; i-- > 0;) + { + uint32_t da; + if (extra != 0 && i == a->n) + { + da = 1; /* carry out of the doubled top limb */ + } + else + { + da = (uint32_t)(a->d[i] << 1); + if (i > 0) + { + da += (a->d[i - 1] >> 31); + } + } + + if (da != b->d[i]) + { + return (da < b->d[i]) ? -1 : 1; + } + } + + return 0; +} + +/* + * R = round_half_even(mant * 2^exp2 * 10^s), computed exactly as an integer + * bignum (mant > 0; s may be negative). s >= 0 scales R by 5^s and by a + * single power of two, rounding once at the end (nearest-even). s < 0 builds + * D = 5^(-s) and shifts either N or D so that value = N / D, divides exactly, + * and rounds the quotient half-even off the remainder: 2*rem > D rounds up, + * 2*rem == D rounds up only when the quotient is odd. Returns 0 or -1 on + * allocation failure (R is then unspecified). + */ +static __attribute__((unused)) int +vfpn_round_scale10(struct vfpn_bn *R, uint64_t mant, int exp2, long s) +{ + struct vfpn_bn N; + struct vfpn_bn D; + struct vfpn_bn Q; + struct vfpn_bn rem; + vfpn_bn_init(&N); + vfpn_bn_init(&D); + vfpn_bn_init(&Q); + vfpn_bn_init(&rem); + + if (s >= 0) + { + if (vfpn_bn_set_u64(R, mant) != 0) + { + goto fail; + } + + for (long i = 0; i < s; i++) + { + if (vfpn_bn_mul_small(R, 5) != 0) + { + goto fail; + } + } + + long long sh = (long long)exp2 + (long long)s; + if (sh >= 0) + { + if (vfpn_bn_shl(R, (size_t)sh) != 0) + { + goto fail; + } + } + else + { + if (vfpn_bn_shr_round(R, (size_t)(-sh)) != 0) + { + goto fail; + } + } + + vfpn_bn_free(&N); + vfpn_bn_free(&D); + vfpn_bn_free(&Q); + vfpn_bn_free(&rem); + return 0; + } + + /* value = mant * 2^exp2 / (2^t * 5^t) with t = -s: make it N / D. */ + { + long long t = -(long long)s; + long long sh = (long long)exp2 - t; + + if (vfpn_bn_pow5(&D, (size_t)t) != 0) + { + goto fail; + } + + if (vfpn_bn_set_u64(&N, mant) != 0) + { + goto fail; + } + + if (sh >= 0) + { + if (vfpn_bn_shl(&N, (size_t)sh) != 0) + { + goto fail; + } + + vfpn_bn_norm(&N); + } + else + { + if (vfpn_bn_shl(&D, (size_t)(-sh)) != 0) + { + goto fail; + } + + vfpn_bn_norm(&D); + } + + if (vfpn_divmod_binary(&Q, &rem, &N, &D) != 0) + { + goto fail; + } + + int c = vfpn_bn_cmp_doubled(&rem, &D); + if (c > 0 || (c == 0 && Q.n > 0 && (Q.d[0] & 1u) != 0)) + { + if (vfpn_bn_add_one(&Q) != 0) + { + goto fail; + } + } + + if (vfpn_bn_reserve(R, Q.n) != 0) + { + goto fail; + } + + if (Q.n > 0) + { + __builtin_memcpy(R->d, Q.d, Q.n * sizeof(uint32_t)); + } + + R->n = Q.n; + } + + vfpn_bn_free(&N); + vfpn_bn_free(&D); + vfpn_bn_free(&Q); + vfpn_bn_free(&rem); + return 0; + +fail: + vfpn_bn_free(&N); + vfpn_bn_free(&D); + vfpn_bn_free(&Q); + vfpn_bn_free(&rem); + return -1; +} + +/* + * Decimal digits of |v| into out (most significant first, no terminating + * NUL); returns the digit count or -1 when outcap is too small. The value + * zero yields a single '0'. Groups of nine decimal digits are stripped from + * the low end into a fixed stack (a 640-limb value caps at ~6165 digits, far + * inside 700 groups), then re-emitted top group unpadded, lower groups + * zero-padded to nine. + */ +static __attribute__((unused)) int +vfpn_bn_to_dec(const struct vfpn_bn *v, char *out, size_t outcap) +{ + if (vfpn_bn_is_zero(v)) + { + if (outcap < 1) + { + return -1; + } + + out[0] = '0'; + return 1; + } + + struct vfpn_bn w; + vfpn_bn_init(&w); + if (vfpn_bn_reserve(&w, v->n) != 0) + { + return -1; + } + + w.n = v->n; + if (v->n > 0) + { + __builtin_memcpy(w.d, v->d, v->n * sizeof(uint32_t)); + } + + uint32_t groups[700]; + size_t ngroups = 0; + while (!vfpn_bn_is_zero(&w)) + { + if (ngroups >= 700) + { + vfpn_bn_free(&w); + return -1; + } + + groups[ngroups++] = vfpn_bn_divmod_small_1e9(&w); + } + + vfpn_bn_free(&w); + + size_t top = ngroups - 1; + uint32_t t = groups[top]; + size_t tdig = 1; + for (uint32_t x = t / 10; x != 0; x /= 10) + { + tdig++; + } + + size_t ndigits = tdig + (ngroups - 1) * 9; + if (outcap < ndigits) + { + return -1; + } + + size_t pos = 0; + char tmp[10]; + size_t ntmp = 0; + do + { + tmp[ntmp++] = (char)('0' + t % 10); + t /= 10; + } while (t != 0); + + while (ntmp > 0) + { + out[pos++] = tmp[--ntmp]; + } + + for (size_t gi = ngroups - 1; gi-- > 0;) + { + uint32_t val = groups[gi]; + for (int k = 8; k >= 0; k--) + { + out[pos + (size_t)k] = (char)('0' + val % 10); + val /= 10; + } + + pos += 9; + } + + return (int)ndigits; +} diff --git a/tests/test_printf.c b/tests/test_printf.c new file mode 100644 index 0000000..c7de329 --- /dev/null +++ b/tests/test_printf.c @@ -0,0 +1,734 @@ +/* + * vlibc — formatted output test, integer conversions (todo 16, task A). + * + * Compares the snprintf/sprintf/dprintf/FILE-output of vfprintf.c against + * golden string literals, all of which were verified against the host glibc + * (a throwaway /tmp/probe.c) EXCEPT the spec'd divergence that %p of NULL is + * "0x0" (glibc prints "(nil)"). + * + * Coverage: %d %i %u %o %x %X over 0, +/-1, INT_MIN/MAX, UINT_MAX, + * LONG_MIN/MAX (as %ld), LLONG_MIN/MAX (%lld), ULONG_MAX; %hhd/%hd + * truncation; %zu %zd %tu %td %ju %jd; the '#' prefix rules (including + * %#.0o of 0, %#x of 0, %#o of 0); the - + space 0 flags and their + * interactions with width and precision; %c %s with precision/width and the + * NULL "(null)" spelling; %p of a local (shape check) and of NULL ("0x0"); + * %n; width and precision supplied via '*'; %% escaping; snprintf truncation + * (including n == 0 with a NULL buffer); sprintf/vsprintf; fprintf/vfprintf + * to a file; dprintf/vdprintf to a raw descriptor; printf/vprintf through a + * redirected stdout; and (level 2) asprintf/vasprintf. + * + * All diagnostics go through raw SYS_write; errno is never read and never + * deliberately written in the success paths exercised here, so no host TCB + * preservation is needed. Only vlibc headers are included (-Iinclude wins + * over the host's). + */ + +#include +#include +#include +#include +#include +#include + +#include "../src/internal/syscall.h" + +static int failures; + +/* Write a NUL-terminated string to fd via the raw syscall layer. */ +static void +say(int fd, const char *s) +{ + long n = 0; + + while (s[n] != '\0') + { + n++; + } + __syscall3(SYS_write, fd, (long)s, n); +} + +/* Write v in decimal to fd. */ +static void +say_dec(int fd, unsigned long v) // NOLINT(bugprone-easily-swappable-parameters) +{ + char buf[24]; + int i = (int)sizeof(buf); + + buf[--i] = '\0'; + do + { + buf[--i] = (char)('0' + (v % 10)); + v /= 10; + } while (v != 0); + __syscall3(SYS_write, fd, (long)(buf + i), (long)(sizeof(buf) - 1 - i)); +} + +static void +check(int cond, const char *what) +{ + if (cond) + { + say(1, "PASS: "); + say(1, what); + say(1, "\n"); + } + else + { + say(2, "FAIL: "); + say(2, what); + say(2, "\n"); + failures++; + } +} + +/* Byte-compare two buffers (the produced content need not be NUL-ended). */ +static int +mem_eq(const unsigned char *a, const unsigned char *b, unsigned long n) +{ + unsigned long i; + + for (i = 0; i < n; i++) + { + if (a[i] != b[i]) + { + return 0; + } + } + return 1; +} + +/* Read a whole small file through raw syscalls into buf; returns size. */ +static long +raw_read_all(const char *path, char *buf, unsigned long cap) +{ + long fd = __syscall4(SYS_openat, -100, (long)path, 0x0, 0); + long got = -1; + long total = 0; + + if (fd < 0) + { + return -1; + } + while (total < (long)cap) + { + got = __syscall3(SYS_read, fd, (long)(buf + total), (long)(cap - (unsigned long)total)); + if (got <= 0) + { + break; + } + total += got; + } + __syscall1(SYS_close, fd); + return got < 0 ? -1 : total; +} + +/* Forwarding wrappers so the v* functions get exercised with real va_lists. */ +static int +run_vprintf(const char *format, ...) // NOLINT(bugprone-easily-swappable-parameters) +{ + va_list ap; + int rc; + + va_start(ap, format); + rc = vprintf(format, ap); + va_end(ap); + return rc; +} + +static int +run_vfprintf(FILE *f, const char *format, ...) // NOLINT(bugprone-easily-swappable-parameters) +{ + va_list ap; + int rc; + + va_start(ap, format); + rc = vfprintf(f, format, ap); + va_end(ap); + return rc; +} + +static int +run_vsprintf(char *s, const char *format, ...) // NOLINT(bugprone-easily-swappable-parameters) +{ + va_list ap; + int rc; + + va_start(ap, format); + rc = vsprintf(s, format, ap); + va_end(ap); + return rc; +} + +static int +run_vdprintf(int fd, const char *format, ...) // NOLINT(bugprone-easily-swappable-parameters) +{ + va_list ap; + int rc; + + va_start(ap, format); + rc = vdprintf(fd, format, ap); + va_end(ap); + return rc; +} + +#if VLIBC_LEVEL_GE(2) +static int +run_vasprintf(char **p, const char *format, ...) // NOLINT(bugprone-easily-swappable-parameters) +{ + va_list ap; + int rc; + + va_start(ap, format); + rc = vasprintf(p, format, ap); + va_end(ap); + return rc; +} +#endif + +/* A single snprintf result checked against a golden literal. */ +static void +golden(const char *want, const char *format, ...) // NOLINT(bugprone-easily-swappable-parameters) +{ + char out[64]; + va_list ap; + int rc; + + va_start(ap, format); + rc = vsnprintf(out, sizeof out, format, ap); + va_end(ap); + check(rc >= 0 && strcmp(out, want) == 0, want); +} + +/* 1. Plain integers: d/i/u/o/x/X over the extremes and the sign cases. */ +static void +int_scenario(void) +{ + char out[64]; + int rc; + + golden("0", "%d", 0); + golden("1", "%d", 1); + golden("-1", "%d", -1); + golden("-2147483648", "%d", INT_MIN); + golden("2147483647", "%d", INT_MAX); + golden("-7", "%i", -7); + golden("4294967295", "%u", UINT_MAX); + golden("0", "%u", 0); + golden("10", "%o", 8); + golden("ff", "%x", 255); + golden("FF", "%X", 255); + golden("-9223372036854775808", "%ld", LONG_MIN); + golden("9223372036854775807", "%ld", LONG_MAX); + golden("-9223372036854775808", "%lld", LLONG_MIN); + golden("9223372036854775807", "%lld", LLONG_MAX); + golden("18446744073709551615", "%lu", ULONG_MAX); + rc = snprintf(out, sizeof out, "%d%d", 1, 2); + check(rc == 2 && strcmp(out, "12") == 0, "adjacent conversions concatenate"); + rc = snprintf(out, sizeof out, "%s%d", "v", -7); + check(rc == 3 && strcmp(out, "v-7") == 0, "literal text between conversions"); +} + +/* 2. Length modifiers: hh/h truncation and the j/z/t widths. */ +static void +length_scenario(void) +{ + golden("44", "%hhd", 300); + golden("4464", "%hd", 70000); + golden("44", "%hhu", 300); + golden("-44", "%hhd", -300); + golden("4000000000", "%zu", (size_t)4000000000UL); + golden("-4000000000", "%zd", (ssize_t)-4000000000L); + golden("123456789", "%tu", (ptrdiff_t)123456789L); + golden("-987654321", "%td", (ptrdiff_t)-987654321L); + golden("18446744073709551615", "%ju", (uintmax_t)UINTMAX_MAX); + golden("-9223372036854775807", "%jd", (intmax_t)-9223372036854775807LL); + golden("ffffffffffffffff", "%lx", (unsigned long)ULONG_MAX); +} + +/* 3. The '#' prefixes (octal, hex) and the zero value corner cases. */ +static void +alt_scenario(void) +{ + golden("0x1abc", "%#x", 0x1abc); + golden("0X1ABC", "%#X", 0x1abc); + golden("010", "%#o", 8); + golden("0", "%#.0o", 0); + golden("0", "%#x", 0); + golden("0", "%#o", 0); + golden("00000010", "%#08o", 8); + golden("010", "%#.3o", 8); + golden("", "%#.0x", 0); + golden(" 0", "%#5.0o", 0); +} + +/* 4. Flags and their interactions with width and precision. */ +static void +flag_scenario(void) +{ + golden("42 ", "%-8d", 42); + golden("+42", "%+d", 42); + golden("-42", "%+d", -42); + golden(" 42", "% d", 42); + golden("-42", "% d", -42); + golden("00000042", "%08d", 42); + golden("42 ", "%-08d", 42); + golden("+0000042", "%+08d", 42); + golden(" 042", "%5.3d", 42); + golden("042 ", "%-5.3d", 42); + golden(" ", "%05.0d", 0); + golden(" 42", "%05.0d", 42); + golden(" 000", "%5.3d", 0); + golden(" 42", "%5d", 42); + golden("42 ", "%-5d", 42); + golden("+0042", "%+05d", 42); + golden(" 42", "%*d", 5, 42); +} + +/* 5. Characters, strings, pointers, %n, and the literal percent. */ +static void +charstr_scenario(void) +{ + char out[64]; + int marker; + int n; + int rc; + + golden("A", "%c", 'A'); + golden("hello", "%s", "hello"); + golden("hel", "%.3s", "hello"); + golden(" hel", "%5.3s", "hello"); + golden("hel ", "%-5.3s", "hello"); + golden("(null)", "%s", (char *)0); + golden("%", "%%"); + golden("0x0", "%p", (void *)0); + + n = -1; + rc = snprintf(out, sizeof out, "abc%n", &n); + check(rc == 3 && n == 3 && strcmp(out, "abc") == 0, "%n stores the count so far"); + + rc = snprintf(out, sizeof out, "%p", (void *)&marker); + if (rc > 2 && out[0] == '0' && out[1] == 'x') + { + int i; + int hex = 1; + + for (i = 2; out[i] != '\0'; i++) + { + if (!((out[i] >= '0' && out[i] <= '9') || (out[i] >= 'a' && out[i] <= 'f'))) + { + hex = 0; + } + } + check(hex && i > 2, "%p of a local prints 0x + lowercase hex"); + } + else + { + check(0, "%p of a local prints 0x + lowercase hex"); + } +} + +/* 6. Width and precision supplied through '*'. */ +static void +star_scenario(void) +{ + golden("00042", "%.*d", 5, 42); + golden("042", "%.*d", 3, 42); + golden("42", "%.*d", -1, 42); + golden("hel", "%.*s", 3, "hello"); +} + +/* 7. snprintf truncation semantics. */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-truncation" + +static void +trunc_scenario(void) +{ + char b[8]; + int rc; + + rc = snprintf(b, 5, "%s", "hello world"); + check(rc == 11 && strcmp(b, "hell") == 0, + "snprintf truncates at n-1 and returns the full length"); + rc = snprintf((char *)0, 0, "%s", "hello world"); + check(rc == 11, "snprintf with n == 0 writes nothing and returns the length"); + rc = snprintf(b, 1, "%s", "hello world"); + check(rc == 11 && b[0] == '\0', "snprintf with n == 1 writes only the NUL"); +} + +#pragma GCC diagnostic pop + +/* 8. sprintf/vsprintf (no bound). */ +static void +sprintf_scenario(void) +{ + char s[32]; + int rc; + + rc = sprintf(s, "%d-%s", 7, "x"); + check(rc == 3 && strcmp(s, "7-x") == 0, "sprintf writes past the argument bound"); + rc = run_vsprintf(s, "%d-%s", 7, "x"); + check(rc == 3 && strcmp(s, "7-x") == 0, "vsprintf writes past the argument bound"); +} + +/* 9. dprintf/vdprintf to a raw descriptor. */ +static void +dprintf_scenario(void) +{ + const char path[] = "/tmp/vlibc-test-printf-dprintf.txt"; + char buf[64]; + long fd; + long got; + int r1; + int r2; + + fd = __syscall4(SYS_openat, -100, (long)path, (long)(0x1 | 0x40 | 0x200), 0666); + check(fd >= 0, "dprintf openat succeeds"); + if (fd < 0) + { + return; + } + r1 = (dprintf((int)fd, "%d %s", 9, "nine") == 6); + r2 = (run_vdprintf((int)fd, " %#x", 0xbeef) == 7); + __syscall1(SYS_close, fd); + check(r1, "dprintf returns the byte count"); + check(r2, "vdprintf returns the byte count"); + got = raw_read_all(path, buf, sizeof buf); + check(got == 13 && + mem_eq((const unsigned char *)buf, (const unsigned char *)"9 nine 0xbeef", 13), + "dprintf content reached the file unbuffered"); + check(remove(path) == 0, "remove deletes the dprintf file"); +} + +/* 10. fprintf/vfprintf through a buffered FILE, read back raw. */ +static void +file_scenario(void) +{ + const char path[] = "/tmp/vlibc-test-printf-file.txt"; + char buf[64]; + FILE *f; + long got; + int r1; + int r2; + + f = fopen(path, "w"); + check(f != NULL, "fopen for fprintf succeeds"); + if (f == NULL) + { + return; + } + r1 = (fprintf(f, "%s %d", "abc", 123) == 7); + r2 = (run_vfprintf(f, " %x", 255) == 3); + check(r1, "fprintf returns the byte count"); + check(r2, "vfprintf returns the byte count"); + check(fclose(f) == 0, "fclose flushes the fprintf file"); + got = raw_read_all(path, buf, sizeof buf); + check(got == 10 && mem_eq((const unsigned char *)buf, (const unsigned char *)"abc 123 ff", 10), + "fprintf/vfprintf content reached the file"); + check(remove(path) == 0, "remove deletes the fprintf file"); +} + +/* 11. printf/vprintf through a redirected stdout (the flush is explicit). */ +static void +stdout_scenario(void) +{ + const char path[] = "/tmp/vlibc-test-printf-stdout.txt"; + char buf[64]; + long saved; + long fd; + long got; + int r1; + int r2; + int r3; + + saved = __syscall1(SYS_dup, 1); + check(saved >= 0, "dup(1) for the printf scenario"); + if (saved < 0) + { + return; + } + fd = __syscall4(SYS_openat, -100, (long)path, (long)(0x1 | 0x40 | 0x200), 0666); + if (fd < 0) + { + __syscall2(SYS_dup2, saved, 1); + __syscall1(SYS_close, saved); + check(0, "openat for the printf scenario"); + return; + } + __syscall2(SYS_dup2, fd, 1); + __syscall1(SYS_close, fd); + /* No check() output while fd 1 names the file. */ + r1 = (printf("%d-%s", 1, "x") == 3); + r2 = (run_vprintf("%c%d", 'q', 5) == 2); + r3 = (fflush(stdout) == 0); + __syscall2(SYS_dup2, saved, 1); + __syscall1(SYS_close, saved); + check(r1, "printf to stdout returns its byte count"); + check(r2, "vprintf to stdout returns its byte count"); + check(r3, "fflush(stdout) after the redirect"); + got = raw_read_all(path, buf, sizeof buf); + check(got == 5 && mem_eq((const unsigned char *)buf, (const unsigned char *)"1-xq5", 5), + "printf/vprintf content reached the redirected stdout"); + check(remove(path) == 0, "remove deletes the stdout file"); +} + +#if VLIBC_LEVEL_GE(2) + +/* 12. Level 2: asprintf/vasprintf. */ +static void +asprintf_scenario(void) +{ + char *p = NULL; + int rc; + + rc = asprintf(&p, "%d-%s", 5, "x"); + check(rc == 3 && p != NULL && strcmp(p, "5-x") == 0, "asprintf allocates and fills"); + free(p); + p = NULL; + rc = run_vasprintf(&p, "%u", 42U); + check(rc == 2 && p != NULL && strcmp(p, "42") == 0, "vasprintf allocates and fills"); + free(p); +} + +#endif /* VLIBC_LEVEL_GE(2) */ + +/* Bit-pattern constructors for the float scenarios (host arithmetic cannot + * name every subnormal or sign-of-NaN case portably). */ +static double +mkdbl(unsigned long long bits) +{ + double v; + + memcpy(&v, &bits, sizeof v); + return v; +} + +static long double +mkldbl(unsigned e15, unsigned neg, unsigned long long sig) +{ + unsigned char raw[16]; + long double v; + + memset(raw, 0, sizeof raw); + memcpy(raw, &sig, 8); + raw[8] = (unsigned char)(e15 & 0xFF); + raw[9] = (unsigned char)((e15 >> 8) | (neg ? 0x80 : 0)); + memcpy(&v, raw, sizeof raw); + return v; +} + +/* 13. %a/%A hex floats, the long double L length, and the round-half-even + * hex-digit rounding (all goldens verified against host glibc). */ +static void +hexfloat_scenario(void) +{ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Woverlength-strings" + static const char *const ldmax_dec = + "11897314953572317650212638530309702051690633222946242004403237338917370055229707" + "22616410290336528882853545697807495577314427443153670288434198125573853743678673" + "59320070697326320191591828296152436552951064679108661431179063216977883889613478" + "65606003991487534332114549111600886798451548665128523401497730376000091254793939" + "66223151383622417838542743917838138717805889487540575168226347659235576974805113" + "72564902088485522249479139937758502601177354918009979622602685950855888360815984" + "69002356451323465944763849398592764562845796617729304078066092291027150460853880" + "87959327781622986827547830768080040150694942303411728957777100335714010559775242" + "12405734700738625166011082837911962300846927720096515350020847447079244384854591" + "28867230006190851264721119513614675276335195629275979572502780029807959041931396" + "03021470997035276467445530922022679656280991498232083329641241038509239184734786" + "12192169721054348428704835340811304257300221642134891734717423480071488075100206" + "43905172342476560047217680964861079949434157034763206435586242074435044243805661" + "36017608837478165389027809576975977286860071487028287955567141404632615832623602" + "76289631617397848425448686060994827086796804807870251185893083854658422304090880" + "59962945945862019037660484467909260022254105307759010657606713472001258464069570" + "30257138960983757998926954553052368560758683179223113639519468850880771872104705" + "20395758748001314313144425494391994017575316933939236688185618912993172910425292" + "12368351599223220509980016771027840353601408292963981151228777681357060457893435" + "35451696539561254048846447169786893211671087229088082778350518228857646062218739" + "70285165508372099234948333443522898475123275372663606621390228126470623407535207" + "17240586650795182173034637826313533937067749019501978416904418247380631628285868" + "57741432581165364040218402724913393320949219498422442730427019873044536620350262" + "38695780468200360144729199712309553005720614186697485284685618651483271597448120" + "31219467516863793430961896151073300655524214851952017628585950910518394725028638" + "71632494167613804996319791441870254302706758495192008837915169401581740046711477" + "87720145964446117520405945350476472180797576111172084627363927960033967047003761" + "33745095531841500737964126050479232516613548412918842113408230154733047540670728" + "18763503617332908005951896325207071673904547777129682265206225651439919376804400" + "29238090311243791261477625596469422198137514696707944687035800439250765945161837" + "98118593920495440361149153107822510726914869798092409467721427270124043771874092" + "16756613634938900451232351668146089322400697993176017805338191849981933008410985" + "99393876029260139091141452600372028487213241195542428210183120421610446740462163" + "53369005836646065911562987647455250681450039329414041314954006776029510059622530" + "22823003631473824681059648442441324864573137437595096416168048024129351876204668" + "13563687753281467553879887177183651289394719533506188500326760735438867336800207" + "43878496570145760903498575712430451020387304948542567024793393228091105260415385" + "28994849203991091946129912491633289917998094380337879522093131466946149705939664" + "15237594928589096048991612194498998638483702248667224914892467841020618336462741" + "69695763076324802355879752452537370354338829608627534277400163334340550835370485" + "07374544819754722228975281083020898682633020285259923084168054539687911418297629" + "98896457648276528750456285492426516521775079951625966922911497778896235667095662" + "71384820181913483216879958636526376209782850700993372943967846398790249145142227" + "42527006363942327998483976739987154418554201562244154926653014515504685489258620" + "27608576183712976335876121538256512963353814166394951655600026415918655485005705" + "26114319529199188079545223946496276356301785808966922264062353828985358675959906" + "47008385687123810329591926494846250768992258419305480763620215089022149220528069" + "84201835084058693849381549890944546197789302911357651677540623227829831403347327" + "66039522316034228247175281818188443048809213219335508698733958612760736708666523" + "75555675803171490108477320096424318780070008797346032906278943553743564448851907" + "19161645514115576193939969076741515640282654366402676009508752394550734155613586" + "79330660317447209244465135323666476497354008519670407711036405381500734868917983" + "64049570606189535005089840913826869535090066783324472578712196604415284924840041" + "85093281190896363417573989716659600075948780061916409485433875852065711654107226" + "09962881501231443779440087493019447443307843889957018427100048083050121771235606" + "22895076269042856800047718893158089358515593863176652948089031267747029662545110" + "86154895839508779675546413794489596052797520987481383976257859210575628440175934" + "93241621483395653501891968113890918437957347032694063428900878058469403524534793" + "98080674273236297887100867175802531561302356064878709259865288416350972529537091" + "11431720488774740553905400942537542411931794417513706468964386151771884986701034" + "15325423859110896247108853858086888377772586485641459342621210866475884892600317" + "62345960769508849149662444156604419552086811989770240"; +#pragma GCC diagnostic pop + double pos_inf = mkdbl(0x7FF0000000000000ULL); + double neg_inf = mkdbl(0xFFF0000000000000ULL); + double pos_nan = mkdbl(0x7FF8000000000000ULL); + double neg_nan = mkdbl(0xFFF8000000000000ULL); + double maxsub = mkdbl(0x000FFFFFFFFFFFFFULL); + char out[6000]; + int rc; + + golden("0x0p+0", "%a", 0.0); + golden("0x1p+0", "%a", 1.0); + golden("0x1.8p+0", "%a", 1.5); + golden("0x1p-1", "%a", 0.5); + golden("0x1.91eb851eb851fp+1", "%a", 3.14); + golden("0x1.fffffffffffffp+1023", "%a", DBL_MAX); + golden("0x2p+1023", "%.0a", DBL_MAX); + golden("0x2.0p+1023", "%.1a", DBL_MAX); + golden("0x0.0000000000001p-1022", "%a", DBL_TRUE_MIN); + golden("0x0.0p-1022", "%.1a", DBL_TRUE_MIN); + golden("0x0p-1022", "%.0a", DBL_TRUE_MIN); + golden("0x0.fffffffffffffp-1022", "%a", maxsub); + golden("0x1.0p-1022", "%.1a", maxsub); + golden("0x1p-1022", "%.0a", maxsub); + golden("0x0.0000p-1022", "%.4a", DBL_TRUE_MIN); + golden("0x1.81cd6e631f8a1p+13", "%a", 12345.6789); + golden("0x1.8000000000000p+0", "%.13a", 1.5); + golden("0x1.8000000000000000p+0", "%.16a", 1.5); + golden("0x2.0p+0", "%.1a", 0x1.fffffffffffffp+0); + golden("0x2p+0", "%.0a", 0x1.8p+0); + golden("0x1p+0", "%.0a", 0x1.4p+0); + golden("0x2p-1", "%.0a", 0x1.fffffffffffffp-1); + golden("0x1.000p+0", "%.3a", 1.0); + golden("0x1.p+0", "%#a", 1.0); + golden("0x2.p+0", "%#.0a", 1.5); + golden("0x0.p+0", "%#.0a", 0.0); + golden("0x0.p+0", "%#a", 0.0); + golden("0x0.000p+0", "%.3a", 0.0); + golden("0x0000000000001.8p+0", "%020a", 1.5); + golden("+0x000000000001.8p+0", "%+020a", 1.5); + golden("+0x1.8p+0", "%+a", 1.5); + golden(" 0x1.8p+0", "% a", 1.5); + golden("+0x1.8p+0 ", "%-+20a", 1.5); + golden("0x1.8p+0 ", "%-20a", 1.5); + golden("0x1.800p+0", "%.3a", 1.5); + golden("+0x1.800p+0", "%+08.3a", 1.5); + golden("0X1.8P+0", "%A", 1.5); + golden("0X1.91EB851EB851FP+1", "%A", 3.14); + golden("0X1.FFFFFFFFFFFFFP+1023", "%A", DBL_MAX); + golden("0X2P+0", "%.0A", 1.5); + golden("inf", "%a", pos_inf); + golden("-inf", "%a", neg_inf); + golden("nan", "%a", pos_nan); + golden("-nan", "%a", neg_nan); + golden("INF", "%A", pos_inf); + golden("NAN", "%A", pos_nan); + golden("inf", "%La", mkldbl(0x7FFF, 0, 0x8000000000000000ULL)); + golden("-inf", "%La", mkldbl(0x7FFF, 1, 0x8000000000000000ULL)); + golden("-nan", "%La", mkldbl(0x7FFF, 1, 0x0000000000000001ULL)); + + golden("0x8p-3", "%La", 1.0L); + golden("0xcp-3", "%La", 1.5L); + golden("0xcp-3", "%.0La", 0xcp-3L); + golden("0xc.0p-3", "%.1La", 0xcp-3L); + golden("0x8p-4", "%La", 0.5L); + golden("0xc.8f5c28f5c28f5c3p-2", "%La", 3.14L); + golden("0xf.fffffffffffffffp+16380", "%La", LDBL_MAX); + golden("0x1p+16384", "%.0La", LDBL_MAX); + golden("0x1.0p+16384", "%.1La", LDBL_MAX); + golden("0x8p-16385", "%La", LDBL_MIN); + golden("0x8.0p-16385", "%.1La", LDBL_MIN); + golden("0x0.000000000000001p-16385", "%La", LDBL_TRUE_MIN); + golden("0x0.0p-16385", "%.1La", LDBL_TRUE_MIN); + golden("0x0p-16385", "%.0La", LDBL_TRUE_MIN); + golden("0xc.p-3", "%#.0La", 1.5L); + golden("0x0.p+0", "%#.0La", 0.0L); + golden("-0xcp-3", "%La", -1.5L); + golden("-0x0p+0", "%.0La", -0.0L); + golden("0x0.0p+0", "%.1La", 0.0L); + golden("0xc.000p-3", "%.3La", 1.5L); + golden("0xc.000000000000000p-3", "%.15La", 1.5L); + golden("0x00000000000000cp-3", "%020La", 1.5L); + golden("+0x1p+16384", "%+.0La", LDBL_MAX); + golden("0x1p+4", "%.0La", 0xf.fffffffffffffffp+0L); + golden("0x1.0p+4", "%.1La", 0xf.fffffffffffffffp+0L); + golden("0XCP-3", "%LA", 1.5L); + golden("0XC.8F5C28F5C28F5C3P-2", "%LA", 3.14L); + golden("0xc.0e6b7318fc50481p+10", "%La", 12345.6789L); + golden("0xc.8p-3", "%.1La", 0xc.8p-3L); + golden("0xcp-3", "%.0La", 0xc.8p-3L); + golden("0xc.000000000000001p-3", "%.15La", 0xc.000000000000001p-3L); + + golden("1.500000e+00", "%Le", 1.5L); + golden("1.500000", "%Lf", 1.5L); + golden("1.5", "%Lg", 1.5L); + golden("-0.000000e+00", "%Le", -0.0L); + golden("-0.000000", "%Lf", -0.0L); + golden("3.14159", "%Lg", 3.14159L); + golden("3.141590", "%Lf", 3.14159L); + golden("3.141590e+00", "%Le", 3.14159L); + golden("0.500000", "%Lf", 0.5L); + golden("1.000000", "%Lf", 1.0L); + golden("0.0001", "%Lg", 0.0001L); + golden("1.189731e+4932", "%Le", LDBL_MAX); + golden("0.000000", "%Lf", 0.0L); + golden("3.14159", "%.6Lg", 3.14159L); + golden("1.5", "%Lg", 1.5000001L); + + rc = snprintf(out, sizeof out, "%.0Lf", LDBL_MAX); + check(rc == 4933 && strcmp(out, ldmax_dec) == 0, + "%.0Lf of LDBL_MAX prints all 4933 digits (no truncation)"); + rc = snprintf(out, sizeof out, "%Lf", LDBL_MAX); + check(rc == 4940 && out[0] == '1' && strncmp(out, "11897314953572317650", 20) == 0 && + out[4933] == '.' && out[4939] == '0', + "%Lf of LDBL_MAX fills the 4933-digit integer part"); +} + +int +main(void) +{ + int_scenario(); + length_scenario(); + alt_scenario(); + flag_scenario(); + charstr_scenario(); + star_scenario(); + trunc_scenario(); + sprintf_scenario(); + dprintf_scenario(); + file_scenario(); + stdout_scenario(); +#if VLIBC_LEVEL_GE(2) + asprintf_scenario(); +#endif + + hexfloat_scenario(); + if (failures > 0) + { + say(2, "FAILED ("); + say_dec(2, (unsigned long)failures); + say(2, " check(s))\n"); + return 1; + } + say(1, "all printf tests passed\n"); + return 0; +}