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:
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;
}