refactor: libc - share printf, split the float path, fix relink deps

This commit is contained in:
2026-08-04 17:29:24 +02:00
parent edc3452d61
commit 75d92d560c
39 changed files with 388 additions and 586 deletions
+26 -4
View File
@@ -10,9 +10,11 @@ TOOLCHAIN_PREFIX := $(shell cd ../../.. && pwd)/toolchain/local/bin/x86_64-monta
ifneq ($(wildcard $(TOOLCHAIN_PREFIX)gcc),)
CC := $(TOOLCHAIN_PREFIX)gcc
AR := $(TOOLCHAIN_PREFIX)ar
LD := $(TOOLCHAIN_PREFIX)ld
else
CC := gcc
AR := ar
LD := ld
endif
# ---- Paths ----
@@ -63,16 +65,25 @@ CRT_CFLAGS := \
# ---- Target ----
TARGET := liblibc.a
SDK_TARGET := liblibc-full.a
CRT_OBJS := crt1.o crti.o crtn.o
.PHONY: all clean
all: $(TARGET) $(CRT_OBJS)
all: $(TARGET) $(SDK_TARGET) $(CRT_OBJS)
$(TARGET): $(OBJDIR)/libc.o $(ASM_OBJS)
# printf_float.o is deliberately a SEPARATE archive member: a weak undefined
# reference does not extract an archive member, so programs that never format
# a float leave the decimal-conversion and pow/exp/log code behind. Programs
# that do need %e/%f/%g must link with -Wl,-u,_pf_putfloat to force it in.
$(TARGET): $(OBJDIR)/libc.o $(OBJDIR)/printf_float.o $(ASM_OBJS)
$(AR) rcs $@ $^
$(OBJDIR)/libc.o: libc.c Makefile
$(OBJDIR)/libc.o: libc.c printf_internal.h Makefile
@mkdir -p $(OBJDIR)
$(CC) $(CFLAGS) -c $< -o $@
$(OBJDIR)/printf_float.o: printf_float.c printf_internal.h Makefile
@mkdir -p $(OBJDIR)
$(CC) $(CFLAGS) -c $< -o $@
@@ -80,6 +91,17 @@ $(OBJDIR)/setjmp.o: setjmp.S Makefile
@mkdir -p $(OBJDIR)
$(CC) $(CFLAGS) -c $< -o $@
# Variant shipped in the on-OS sysroots (0:/sdk/lib/libc.a and tcc's), where
# nobody can pass -Wl,-u,_pf_putfloat by hand. `ld -r` binds vsnprintf's weak
# reference to the float module at merge time, so %e/%f/%g just work for code
# compiled ON MontaukOS. Function-level --gc-sections still applies to the
# rest; only the float path is pinned. Image size is not a concern here.
$(SDK_TARGET): $(OBJDIR)/libc-merged.o $(ASM_OBJS)
$(AR) rcs $@ $^
$(OBJDIR)/libc-merged.o: $(OBJDIR)/libc.o $(OBJDIR)/printf_float.o
$(LD) -r -o $@ $(OBJDIR)/libc.o $(OBJDIR)/printf_float.o
crt1.o: $(CRTDIR)/crt1.c Makefile
$(CC) $(CRT_CFLAGS) -c $< -o $@
@@ -90,4 +112,4 @@ crtn.o: $(CRTDIR)/crtn.c Makefile
$(CC) $(CRT_CFLAGS) -c $< -o $@
clean:
rm -rf $(OBJDIR) $(TARGET) $(CRT_OBJS)
rm -rf $(OBJDIR) $(TARGET) $(SDK_TARGET) $(CRT_OBJS)
+21 -228
View File
@@ -1150,13 +1150,17 @@ int system(const char *command) {
printf family — vsnprintf core
======================================================================== */
struct _pf_state {
char *buf;
size_t pos;
size_t max;
};
#include "printf_internal.h"
static void _pf_putc(struct _pf_state *st, char c) {
/* Weak: resolves to 0 unless the program pulls in printf_float.o with
-Wl,-u,_pf_putfloat. See the %e/%f/%g case in vsnprintf below. */
extern __attribute__((weak)) void _pf_putfloat(struct _pf_state *st, double v,
char conv, int precision,
int width, char pad,
int left_align, int plus,
int space, int alt);
void _pf_putc(struct _pf_state *st, char c) {
if (st->pos < st->max)
st->buf[st->pos] = c;
st->pos++;
@@ -1208,226 +1212,6 @@ static void _pf_putnum(struct _pf_state *st, unsigned long val, int base, int up
_pf_putc(st, tmp[--i]);
}
/* Union for double bit manipulation (used by float formatting and math). */
typedef union { double d; uint64_t u; } _dbl_bits;
/* Base-10 exponent of the leading digit of a positive finite value, i.e. the
E such that value = m * 10^E with m in [1, 10). No rounding is applied. */
static int _pf_exp10(double v) {
int E = (int)floor(log10(v));
double s = v / pow(10.0, (double)E);
if (s >= 10.0) E++;
else if (s < 1.0) E--;
return E;
}
/* Generate `nsig` significant decimal digits of a positive finite value into
`out` (ASCII, no terminator). Returns the base-10 exponent of the leading
digit, i.e. value ~= (out[0].out[1..]) * 10^E. Rounding is half-up and may
carry into a new leading digit, bumping the returned exponent. */
static int _pf_gendigits(double v, int nsig, char *out) {
int E = (int)floor(log10(v));
double scale = v / pow(10.0, (double)E);
/* Correct estimate errors so scale lands in [1, 10). */
if (scale >= 10.0) { scale /= 10.0; E++; }
else if (scale < 1.0) { scale *= 10.0; E--; }
for (int i = 0; i < nsig; i++) {
int d = (int)scale;
if (d < 0) d = 0; else if (d > 9) d = 9;
out[i] = (char)('0' + d);
scale = (scale - d) * 10.0;
}
if (scale >= 5.0) { /* round up based on the next digit */
int i = nsig - 1;
while (i >= 0) {
if (out[i] == '9') { out[i] = '0'; i--; }
else { out[i]++; break; }
}
if (i < 0) { out[0] = '1'; E++; } /* all nines carried out */
}
return E;
}
/* Append the two-or-more-digit exponent field of %e/%g ("e+NN"). */
static void _pf_put_exp(char *mbuf, int *mlen, int E, int upper) {
int m = *mlen;
mbuf[m++] = upper ? 'E' : 'e';
int ex = E; char es = '+';
if (ex < 0) { es = '-'; ex = -ex; }
mbuf[m++] = es;
char eb[8]; int el = 0;
if (ex == 0) eb[el++] = '0';
while (ex > 0) { eb[el++] = (char)('0' + ex % 10); ex /= 10; }
while (el < 2) eb[el++] = '0';
while (el > 0) mbuf[m++] = eb[--el];
*mlen = m;
}
/* Format a floating-point value for the %e/%f/%g conversions (and uppercase
variants), honoring precision, width, padding and sign flags. */
static void _pf_putfloat(struct _pf_state *st, double v, char conv,
int precision, int width, char pad,
int left_align, int plus, int space, int alt) {
int upper = (conv >= 'A' && conv <= 'Z');
char lc = upper ? (char)(conv - 'A' + 'a') : conv;
_dbl_bits bits; bits.d = v;
int neg = (int)(bits.u >> 63);
uint64_t expo = (bits.u >> 52) & 0x7FF;
uint64_t mant = bits.u & 0x000FFFFFFFFFFFFFULL;
char sign = 0;
if (neg) sign = '-';
else if (plus) sign = '+';
else if (space) sign = ' ';
/* Infinity / NaN: never zero-padded. */
if (expo == 0x7FF) {
const char *word = mant ? (upper ? "NAN" : "nan") : (upper ? "INF" : "inf");
if (mant) sign = 0;
char tmp[8]; int tl = 0;
if (sign) tmp[tl++] = sign;
for (const char *p = word; *p; p++) tmp[tl++] = *p;
if (!left_align) for (int w = tl; w < width; w++) _pf_putc(st, ' ');
for (int i = 0; i < tl; i++) _pf_putc(st, tmp[i]);
if (left_align) for (int w = tl; w < width; w++) _pf_putc(st, ' ');
return;
}
if (precision < 0) precision = 6;
if (precision > 340) precision = 340;
double av = neg ? -v : v;
char mbuf[800];
int mlen = 0;
if (lc == 'e') {
int nsig = precision + 1;
char dig[400];
int E;
if (av == 0.0) { for (int i = 0; i < nsig; i++) dig[i] = '0'; E = 0; }
else E = _pf_gendigits(av, nsig, dig);
mbuf[mlen++] = dig[0];
if (precision > 0 || alt) {
mbuf[mlen++] = '.';
for (int i = 1; i <= precision; i++) mbuf[mlen++] = dig[i];
}
_pf_put_exp(mbuf, &mlen, E, upper);
}
else if (lc == 'f') {
if (av == 0.0) {
mbuf[mlen++] = '0';
if (precision > 0 || alt) {
mbuf[mlen++] = '.';
for (int i = 0; i < precision; i++) mbuf[mlen++] = '0';
}
} else {
int Ef = _pf_exp10(av);
int keep = Ef + 1 + precision;
if (keep <= 0) {
char d1[4];
_pf_gendigits(av, 1, d1);
int roundUp = (keep == 0 && d1[0] >= '5');
mbuf[mlen++] = '0';
if (precision > 0 || alt) {
mbuf[mlen++] = '.';
for (int i = 0; i < precision; i++) mbuf[mlen++] = '0';
}
if (roundUp) mbuf[mlen - 1] = '1';
} else {
if (keep > 380) keep = 380;
char dig[400];
int E = _pf_gendigits(av, keep, dig);
int len = keep;
int intlen = (E >= 0) ? (E + 1) : 0;
if (intlen == 0) mbuf[mlen++] = '0';
else for (int i = 0; i < intlen; i++) mbuf[mlen++] = (i < len ? dig[i] : '0');
if (precision > 0 || alt) {
mbuf[mlen++] = '.';
if (E < 0) {
int lead = -E - 1;
for (int k = 0; k < precision; k++) {
if (k < lead) mbuf[mlen++] = '0';
else { int idx = k - lead; mbuf[mlen++] = (idx < len ? dig[idx] : '0'); }
}
} else {
for (int k = 0; k < precision; k++) {
int idx = intlen + k;
mbuf[mlen++] = (idx < len ? dig[idx] : '0');
}
}
}
}
}
}
else { /* 'g' / 'G' */
int P = precision; if (P == 0) P = 1;
char dig[400];
int E;
if (av == 0.0) { E = 0; for (int i = 0; i < P; i++) dig[i] = '0'; }
else E = _pf_gendigits(av, P, dig);
int len = P;
int useExp = (E < -4 || E >= P);
int hasDot = 0;
if (useExp) {
int fp = P - 1;
mbuf[mlen++] = dig[0];
if (fp > 0 || alt) {
mbuf[mlen++] = '.'; hasDot = 1;
for (int i = 1; i <= fp; i++) mbuf[mlen++] = dig[i];
}
if (!alt && hasDot) {
while (mbuf[mlen - 1] == '0') mlen--;
if (mbuf[mlen - 1] == '.') mlen--;
}
_pf_put_exp(mbuf, &mlen, E, upper);
} else {
int fp = P - 1 - E;
if (fp < 0) fp = 0;
int intlen = (E >= 0) ? (E + 1) : 0;
if (intlen == 0) mbuf[mlen++] = '0';
else for (int i = 0; i < intlen; i++) mbuf[mlen++] = (i < len ? dig[i] : '0');
if (fp > 0 || alt) {
mbuf[mlen++] = '.'; hasDot = 1;
if (E < 0) {
int lead = -E - 1;
for (int k = 0; k < fp; k++) {
if (k < lead) mbuf[mlen++] = '0';
else { int idx = k - lead; mbuf[mlen++] = (idx < len ? dig[idx] : '0'); }
}
} else {
for (int k = 0; k < fp; k++) {
int idx = intlen + k;
mbuf[mlen++] = (idx < len ? dig[idx] : '0');
}
}
}
if (!alt && hasDot) {
while (mbuf[mlen - 1] == '0') mlen--;
if (mbuf[mlen - 1] == '.') mlen--;
}
}
}
int total = (sign ? 1 : 0) + mlen;
if (!left_align && pad == '0') {
if (sign) _pf_putc(st, sign);
for (int w = total; w < width; w++) _pf_putc(st, '0');
for (int i = 0; i < mlen; i++) _pf_putc(st, mbuf[i]);
} else if (!left_align) {
for (int w = total; w < width; w++) _pf_putc(st, ' ');
if (sign) _pf_putc(st, sign);
for (int i = 0; i < mlen; i++) _pf_putc(st, mbuf[i]);
} else {
if (sign) _pf_putc(st, sign);
for (int i = 0; i < mlen; i++) _pf_putc(st, mbuf[i]);
for (int w = total; w < width; w++) _pf_putc(st, ' ');
}
}
int vsnprintf(char *buf, size_t size, const char *fmt, va_list ap) {
struct _pf_state st;
st.buf = buf;
@@ -1579,9 +1363,18 @@ int vsnprintf(char *buf, size_t size, const char *fmt, va_list ap) {
case 'f': case 'F':
case 'e': case 'E':
case 'g': case 'G': {
/* Consume the argument either way: skipping it would desync every
later conversion in the same format string. */
double dv = va_arg(ap, double);
_pf_putfloat(&st, dv, *fmt, precision, width, pad,
left_align, plus, space, alt);
if (_pf_putfloat) {
_pf_putfloat(&st, dv, *fmt, precision, width, pad,
left_align, plus, space, alt);
} else {
/* printf_float.o was not linked in; echo the spec so the gap
is visible rather than silently printing a wrong number. */
_pf_putc(&st, '%');
_pf_putc(&st, *fmt);
}
break;
}
case '%':
Binary file not shown.
Binary file not shown.
+233
View File
@@ -0,0 +1,233 @@
/*
* printf_float.c
* Floating-point conversions (%e/%f/%g and uppercase variants) for the
* printf family. Kept in its own translation unit -- and therefore its own
* archive member -- so that --gc-sections can drop it, along with the
* pow/exp/log it drags in, from the many programs that only ever format
* integers and strings. vsnprintf reaches this through a weak reference;
* see printf_internal.h and lib/libc/Makefile.
* Copyright (c) 2026 Daniel Hammer
*/
#include <stddef.h>
#include <stdint.h>
#include <math.h>
#include "printf_internal.h"
/* Base-10 exponent of the leading digit of a positive finite value, i.e. the
E such that value = m * 10^E with m in [1, 10). No rounding is applied. */
static int _pf_exp10(double v) {
int E = (int)floor(log10(v));
double s = v / pow(10.0, (double)E);
if (s >= 10.0) E++;
else if (s < 1.0) E--;
return E;
}
/* Generate `nsig` significant decimal digits of a positive finite value into
`out` (ASCII, no terminator). Returns the base-10 exponent of the leading
digit, i.e. value ~= (out[0].out[1..]) * 10^E. Rounding is half-up and may
carry into a new leading digit, bumping the returned exponent. */
static int _pf_gendigits(double v, int nsig, char *out) {
int E = (int)floor(log10(v));
double scale = v / pow(10.0, (double)E);
/* Correct estimate errors so scale lands in [1, 10). */
if (scale >= 10.0) { scale /= 10.0; E++; }
else if (scale < 1.0) { scale *= 10.0; E--; }
for (int i = 0; i < nsig; i++) {
int d = (int)scale;
if (d < 0) d = 0; else if (d > 9) d = 9;
out[i] = (char)('0' + d);
scale = (scale - d) * 10.0;
}
if (scale >= 5.0) { /* round up based on the next digit */
int i = nsig - 1;
while (i >= 0) {
if (out[i] == '9') { out[i] = '0'; i--; }
else { out[i]++; break; }
}
if (i < 0) { out[0] = '1'; E++; } /* all nines carried out */
}
return E;
}
/* Append the two-or-more-digit exponent field of %e/%g ("e+NN"). */
static void _pf_put_exp(char *mbuf, int *mlen, int E, int upper) {
int m = *mlen;
mbuf[m++] = upper ? 'E' : 'e';
int ex = E; char es = '+';
if (ex < 0) { es = '-'; ex = -ex; }
mbuf[m++] = es;
char eb[8]; int el = 0;
if (ex == 0) eb[el++] = '0';
while (ex > 0) { eb[el++] = (char)('0' + ex % 10); ex /= 10; }
while (el < 2) eb[el++] = '0';
while (el > 0) mbuf[m++] = eb[--el];
*mlen = m;
}
/* Format a floating-point value for the %e/%f/%g conversions (and uppercase
variants), honoring precision, width, padding and sign flags. */
void _pf_putfloat(struct _pf_state *st, double v, char conv,
int precision, int width, char pad,
int left_align, int plus, int space, int alt) {
int upper = (conv >= 'A' && conv <= 'Z');
char lc = upper ? (char)(conv - 'A' + 'a') : conv;
_dbl_bits bits; bits.d = v;
int neg = (int)(bits.u >> 63);
uint64_t expo = (bits.u >> 52) & 0x7FF;
uint64_t mant = bits.u & 0x000FFFFFFFFFFFFFULL;
char sign = 0;
if (neg) sign = '-';
else if (plus) sign = '+';
else if (space) sign = ' ';
/* Infinity / NaN: never zero-padded. */
if (expo == 0x7FF) {
const char *word = mant ? (upper ? "NAN" : "nan") : (upper ? "INF" : "inf");
if (mant) sign = 0;
char tmp[8]; int tl = 0;
if (sign) tmp[tl++] = sign;
for (const char *p = word; *p; p++) tmp[tl++] = *p;
if (!left_align) for (int w = tl; w < width; w++) _pf_putc(st, ' ');
for (int i = 0; i < tl; i++) _pf_putc(st, tmp[i]);
if (left_align) for (int w = tl; w < width; w++) _pf_putc(st, ' ');
return;
}
if (precision < 0) precision = 6;
if (precision > 340) precision = 340;
double av = neg ? -v : v;
char mbuf[800];
int mlen = 0;
if (lc == 'e') {
int nsig = precision + 1;
char dig[400];
int E;
if (av == 0.0) { for (int i = 0; i < nsig; i++) dig[i] = '0'; E = 0; }
else E = _pf_gendigits(av, nsig, dig);
mbuf[mlen++] = dig[0];
if (precision > 0 || alt) {
mbuf[mlen++] = '.';
for (int i = 1; i <= precision; i++) mbuf[mlen++] = dig[i];
}
_pf_put_exp(mbuf, &mlen, E, upper);
}
else if (lc == 'f') {
if (av == 0.0) {
mbuf[mlen++] = '0';
if (precision > 0 || alt) {
mbuf[mlen++] = '.';
for (int i = 0; i < precision; i++) mbuf[mlen++] = '0';
}
} else {
int Ef = _pf_exp10(av);
int keep = Ef + 1 + precision;
if (keep <= 0) {
char d1[4];
_pf_gendigits(av, 1, d1);
int roundUp = (keep == 0 && d1[0] >= '5');
mbuf[mlen++] = '0';
if (precision > 0 || alt) {
mbuf[mlen++] = '.';
for (int i = 0; i < precision; i++) mbuf[mlen++] = '0';
}
if (roundUp) mbuf[mlen - 1] = '1';
} else {
if (keep > 380) keep = 380;
char dig[400];
int E = _pf_gendigits(av, keep, dig);
int len = keep;
int intlen = (E >= 0) ? (E + 1) : 0;
if (intlen == 0) mbuf[mlen++] = '0';
else for (int i = 0; i < intlen; i++) mbuf[mlen++] = (i < len ? dig[i] : '0');
if (precision > 0 || alt) {
mbuf[mlen++] = '.';
if (E < 0) {
int lead = -E - 1;
for (int k = 0; k < precision; k++) {
if (k < lead) mbuf[mlen++] = '0';
else { int idx = k - lead; mbuf[mlen++] = (idx < len ? dig[idx] : '0'); }
}
} else {
for (int k = 0; k < precision; k++) {
int idx = intlen + k;
mbuf[mlen++] = (idx < len ? dig[idx] : '0');
}
}
}
}
}
}
else { /* 'g' / 'G' */
int P = precision; if (P == 0) P = 1;
char dig[400];
int E;
if (av == 0.0) { E = 0; for (int i = 0; i < P; i++) dig[i] = '0'; }
else E = _pf_gendigits(av, P, dig);
int len = P;
int useExp = (E < -4 || E >= P);
int hasDot = 0;
if (useExp) {
int fp = P - 1;
mbuf[mlen++] = dig[0];
if (fp > 0 || alt) {
mbuf[mlen++] = '.'; hasDot = 1;
for (int i = 1; i <= fp; i++) mbuf[mlen++] = dig[i];
}
if (!alt && hasDot) {
while (mbuf[mlen - 1] == '0') mlen--;
if (mbuf[mlen - 1] == '.') mlen--;
}
_pf_put_exp(mbuf, &mlen, E, upper);
} else {
int fp = P - 1 - E;
if (fp < 0) fp = 0;
int intlen = (E >= 0) ? (E + 1) : 0;
if (intlen == 0) mbuf[mlen++] = '0';
else for (int i = 0; i < intlen; i++) mbuf[mlen++] = (i < len ? dig[i] : '0');
if (fp > 0 || alt) {
mbuf[mlen++] = '.'; hasDot = 1;
if (E < 0) {
int lead = -E - 1;
for (int k = 0; k < fp; k++) {
if (k < lead) mbuf[mlen++] = '0';
else { int idx = k - lead; mbuf[mlen++] = (idx < len ? dig[idx] : '0'); }
}
} else {
for (int k = 0; k < fp; k++) {
int idx = intlen + k;
mbuf[mlen++] = (idx < len ? dig[idx] : '0');
}
}
}
if (!alt && hasDot) {
while (mbuf[mlen - 1] == '0') mlen--;
if (mbuf[mlen - 1] == '.') mlen--;
}
}
}
int total = (sign ? 1 : 0) + mlen;
if (!left_align && pad == '0') {
if (sign) _pf_putc(st, sign);
for (int w = total; w < width; w++) _pf_putc(st, '0');
for (int i = 0; i < mlen; i++) _pf_putc(st, mbuf[i]);
} else if (!left_align) {
for (int w = total; w < width; w++) _pf_putc(st, ' ');
if (sign) _pf_putc(st, sign);
for (int i = 0; i < mlen; i++) _pf_putc(st, mbuf[i]);
} else {
if (sign) _pf_putc(st, sign);
for (int i = 0; i < mlen; i++) _pf_putc(st, mbuf[i]);
for (int w = total; w < width; w++) _pf_putc(st, ' ');
}
}
+39
View File
@@ -0,0 +1,39 @@
/*
* printf_internal.h
* Shared state between the integer printf core (libc.c) and the optional
* floating-point conversion module (printf_float.c).
* Copyright (c) 2026 Daniel Hammer
*/
#ifndef _LIBC_PRINTF_INTERNAL_H
#define _LIBC_PRINTF_INTERNAL_H
#include <stddef.h>
#include <stdint.h>
/* Output cursor shared by every conversion. `pos` counts the characters a
conversion WOULD have written, so it may run past `max`; only writes below
`max` land in `buf`. That is what gives snprintf its C99 return value. */
struct _pf_state {
char *buf;
size_t pos;
size_t max;
};
/* Defined in libc.c. External rather than static so the float module can
reach it; it is the only core helper the float conversions need. */
void _pf_putc(struct _pf_state *st, char c);
/* Union for double bit manipulation (used by float formatting and math). */
typedef union { double d; uint64_t u; } _dbl_bits;
/* Defined in printf_float.c, which is a SEPARATE archive member so that
--gc-sections can drop the decimal-conversion and pow/exp/log code from
programs that never format a float. vsnprintf calls this through a WEAK
reference: link with -Wl,-u,_pf_putfloat to pull the module in. Without
that flag %e/%f/%g echo their conversion spec instead of a number. */
void _pf_putfloat(struct _pf_state *st, double v, char conv,
int precision, int width, char pad,
int left_align, int plus, int space, int alt);
#endif /* _LIBC_PRINTF_INTERNAL_H */
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.