kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

commit cc083177e030aaca9974f214dbd650968cdc3952
parent ab2d4c8ad60e56bba34ca8d3b407980ba8d93004
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Sun, 24 May 2026 06:05:50 -0700

rt: implement printf '*' width/precision; fix stale rt archive members

The freestanding printf engine parsed width and precision as digits only,
so `%*d` / `%.*s` fell through to the conversion switch's default and were
emitted literally (e.g. `%.*s-%.*s` -> `*s-*s`). Since the driver formats
slices via CFREE_SLICE_ARG -> `%.*s` everywhere, a fully self-hosted cfree
(which links its own libcfree_rt printf, unlike the clang-built host binary
that uses libSystem) mangled most slice output, e.g. `cc -dumpmachine`.

Add '*' handling for both width and precision, reading the int argument in
format order. A negative width is taken as a '-' flag plus a positive width;
a negative precision is taken as omitted (C11 7.21.6.1p5).

Also add `rm -f $@` before `ar rcs` in the rt archive rule, mirroring the
main libcfree.a rule. Without it the archive only accumulates members, so a
renamed object (int64.c -> int64.c.o) left both behind and double-defined
__clzti2, breaking the bootstrap relink.

Regression coverage in the freestanding_lib rt-runtime case.

Diffstat:
Mrt/Makefile | 1+
Mrt/lib/stdio/printf.c | 29+++++++++++++++++++++++++++--
Mtest/rt/cases/freestanding_lib.c | 13+++++++++++++
3 files changed, 41 insertions(+), 2 deletions(-)

diff --git a/rt/Makefile b/rt/Makefile @@ -220,6 +220,7 @@ rt-$(1): $$(RT_BUILD_DIR)/$(1)/libcfree_rt.a $$(RT_BUILD_DIR)/$(1)/libcfree_rt.a: $$(RT_OBJS_$(1)) | $$(BIN) @mkdir -p $$(dir $$@) + @rm -f $$@ $$(RT_AR) rcs $$@ $$^ $$(RT_BUILD_DIR)/$(1)/%.s.o: rt/lib/%.s | $$(BIN) diff --git a/rt/lib/stdio/printf.c b/rt/lib/stdio/printf.c @@ -357,11 +357,36 @@ static int cfree_vprintf_impl(CfreePrintfOut* out, const char* fmt, break; } } - if (cfree_is_digit(*fmt)) width = cfree_parse_uint(&fmt); + if (*fmt == '*') { + /* Width from an int arg; a negative value means left-justify (C11 + * 7.21.6.1p5: taken as a '-' flag plus a positive width). */ + int w = va_arg(ap, int); + fmt++; + if (w < 0) { + left = 1; + width = (unsigned)(-w); + } else { + width = (unsigned)w; + } + } else if (cfree_is_digit(*fmt)) { + width = cfree_parse_uint(&fmt); + } if (*fmt == '.') { fmt++; has_prec = 1; - prec = cfree_parse_uint(&fmt); + if (*fmt == '*') { + /* Precision from an int arg; a negative value is taken as if the + * precision were omitted (C11 7.21.6.1p5). */ + int p = va_arg(ap, int); + fmt++; + if (p < 0) { + has_prec = 0; + } else { + prec = (unsigned)p; + } + } else { + prec = cfree_parse_uint(&fmt); + } } if (*fmt == 'l') { fmt++; diff --git a/test/rt/cases/freestanding_lib.c b/test/rt/cases/freestanding_lib.c @@ -141,6 +141,19 @@ static int stdio_ok(void) { out.buf[out.len] = '\0'; if (rc != 7 || strcmp(out.buf, "cb:0009") != 0) return 39; } + + /* '*' width/precision taken from an int argument (codes skip 42, the + * run.sh pass sentinel). */ + rc = snprintf(buf, sizeof(buf), "%.*s-%.*s", 7, "riscv64xx", 5, "linuxyy"); + if (rc != 13 || strcmp(buf, "riscv64-linux") != 0) return 40; + + rc = snprintf(buf, sizeof(buf), "%*d", 5, 42); + if (rc != 5 || strcmp(buf, " 42") != 0) return 41; + + /* Negative '*' width is read as a '-' flag plus a positive width. */ + rc = snprintf(buf, sizeof(buf), "%*d|", -5, 42); + if (rc != 6 || strcmp(buf, "42 |") != 0) return 43; + return 0; }