From c495d1fbe20d034a61d31898a138e0cf808b8122 Mon Sep 17 00:00:00 2001 From: Thomas Gazagnaire Date: Mon, 29 Jun 2026 14:31:02 -0700 Subject: [PATCH] nolibc: format printf floats as double, not long double On targets where long double is IEEE quad (aarch64), vfprintf's `union arg` held the float value as long double, so `%f` (and the fmt_fp call) round-tripped double -> long double -> double through __extenddftf2/__trunctfdf2. That pulls libgcc (or compiler-rt) soft-float into every freestanding link that uses printf, even for integer-only formats. fmt_fp already takes a double, so the long double was pure overhead. Use double for the field; %L float conversions are treated as double (never used by OCaml unikernels). aarch64 cross unikernels now link with no libgcc at all (clang already inlines the atomics). --- nolibc/vfprintf.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/nolibc/vfprintf.c b/nolibc/vfprintf.c index 9e886a0b..f852e9b8 100644 --- a/nolibc/vfprintf.c +++ b/nolibc/vfprintf.c @@ -121,7 +121,10 @@ static const unsigned char states[]['z'-'A'+1] = { union arg { uintmax_t i; - long double f; + /* double, not long double: avoids pulling libgcc soft-float (__extenddftf2, + __trunctfdf2) into freestanding links; %L is treated as double (fmt_fp + formats at double). */ + double f; void *p; }; @@ -151,7 +154,7 @@ static void pop_arg(union arg *arg, int type, va_list *ap) break; case UIPTR: arg->i = (uintptr_t)va_arg(*ap, void *); #endif break; case DBL: arg->f = va_arg(*ap, double); - break; case LDBL: arg->f = va_arg(*ap, long double); + break; case LDBL: arg->f = va_arg(*ap, double); } }