From 858fc1762c58d0bb88457308fcce24d5fc0ebb74 Mon Sep 17 00:00:00 2001 From: huntedbytheirs Date: Thu, 3 Sep 2026 19:46:20 -0400 Subject: [PATCH] fix(bench): separate vlibc-under-test TU from host harness TUs --- benchmarks/Makefile.am | 30 ++++++++++++++++++++++++------ benchmarks/bench_string.c | 9 +++++++++ benchmarks/bench_vlibc.c | 27 ++++++++++++++++++++++++--- benchmarks/bench_vlibc_under.c | 29 +++++++++++++++++++++++++++++ 4 files changed, 86 insertions(+), 9 deletions(-) create mode 100644 benchmarks/bench_vlibc_under.c diff --git a/benchmarks/Makefile.am b/benchmarks/Makefile.am index 68fae2a..cba3096 100644 --- a/benchmarks/Makefile.am +++ b/benchmarks/Makefile.am @@ -10,18 +10,30 @@ EXTRA_PROGRAMS = bench_vlibc bench_string bench_vlibc_SOURCES = bench_vlibc.c bench_string_SOURCES = bench_string.c -# bench_string is a system-headers-only translation unit (it times whichever -# libc it is linked against), so it must NOT inherit AM_CPPFLAGS: -I include -# would pull vlibc's self-contained / into the same TU as -# the system / and double-define size_t/NULL/offsetof. -# bench_vlibc keeps the inherited -I include. +# Both harness TUs are system-headers-only translation units (they time +# whichever libc they are linked against), so they must NOT inherit +# AM_CPPFLAGS: -I include would pull vlibc's self-contained +# // into the same TU as the system +# / and double-define size_t/NULL/offsetof (or hide the +# compiler's internal behind vlibc's). The vlibc function under +# test is reached through the adapter TU bench_vlibc_under.c, which is +# compiled with vlibc's headers and exposes the call to the harness through +# its own declaration (see that file). +bench_vlibc_CPPFLAGS = bench_string_CPPFLAGS = +# Adapter TU for bench_vlibc: vlibc's headers only, so it must NOT inherit +# the (empty) per-target CPPFLAGS above; it gets the include path back +# explicitly. +bench_vlibc_under.o: bench_vlibc_under.c + $(AM_V_CC)$(CC) -I$(top_srcdir)/include $(CPPFLAGS) $(AM_CFLAGS) \ + $(CFLAGS) -c -o $@ $< + # By default benchmarks link against vlibc itself. --with-libc=glibc links the # same harness against the system glibc; --with-libc=musl builds it with # musl-gcc for the musl reference. if BENCH_LINK_VLIBC -bench_vlibc_LDADD = ../libvlibc.la +bench_vlibc_LDADD = bench_vlibc_under.o ../libvlibc.la bench_string_LDADD = ../libvlibc.la else bench_vlibc_LDADD = @@ -38,6 +50,12 @@ endif # binary from the previous configuration and the wrong libc would be measured. EXTRA_bench_string_DEPENDENCIES = $(top_builddir)/config.status EXTRA_bench_vlibc_DEPENDENCIES = $(top_builddir)/config.status +bench_vlibc_DEPENDENCIES = bench_vlibc_under.o + +# The adapter TU is built by a hand-rolled rule (per-TU include paths), so it +# is not picked up by automake's automatic distribution/cleaning. +EXTRA_DIST = bench_vlibc_under.c +CLEANFILES = bench_vlibc_under.o # bench_vlibc.c calls vlibc_version(), which exists only in vlibc — under # --with-libc=glibc/musl it cannot link, so it is built and run only in the diff --git a/benchmarks/bench_string.c b/benchmarks/bench_string.c index 9cae993..2f9f1fb 100644 --- a/benchmarks/bench_string.c +++ b/benchmarks/bench_string.c @@ -149,5 +149,14 @@ main(void) printf("%llu\n", sink); } + /* + * Explicit flush: this harness is a host program, but under + * --with-libc=vlibc its DT_NEEDED order puts libvlibc.so before + * libc.so.6, so the exit() that runs at process end is vlibc's — which + * does not flush stdio yet (that hook lands with the stdio todo). + * Without the flush the timed results above are lost. + */ + fflush(stdout); + return 0; } diff --git a/benchmarks/bench_vlibc.c b/benchmarks/bench_vlibc.c index 45356b9..7cac683 100644 --- a/benchmarks/bench_vlibc.c +++ b/benchmarks/bench_vlibc.c @@ -5,16 +5,29 @@ * This stub times vlibc_version() and is the skeleton that per-component * benchmarks build on. Reconfigure with --with-libc=musl or --with-libc=glibc * to link the same harness against a reference libc for comparison. + * + * This TU is deliberately a HOST-headers-only translation unit: it includes + * no vlibc header, because vlibc's self-contained + * /// shadow GCC's internal headers + * and mixing them with the system / hard-errors. The + * function under test is reached through the adapter TU + * bench_vlibc_under.c, which is compiled with vlibc's headers and exposes + * the call through its own declaration here. */ #ifdef HAVE_CONFIG_H #include #endif -#include - #include #include +/* Adapter entry (bench_vlibc_under.c); declared here rather than including + * , which this host-header TU must not do. The adapter also drops + * vlibc_version()'s __attribute__((const)), so the timed loop really + * executes the call. */ +const char * +bench_vlibc_version(void); + #define ITERATIONS 100000000ULL int @@ -34,7 +47,7 @@ main(void) for (unsigned long long i = 0; i < ITERATIONS; i++) { - version = vlibc_version(); + version = bench_vlibc_version(); } if (clock_gettime(CLOCK_MONOTONIC, &end) != 0) @@ -49,5 +62,13 @@ main(void) printf("vlibc_version() x %llu: %.3f s (%.2f ns/call), version=%s\n", ITERATIONS, seconds, seconds * 1000000000.0 / ITERATIONS, (const char *)version); + /* + * Explicit flush: this harness is a host program, but its DT_NEEDED order + * puts libvlibc.so before libc.so.6, so the exit() that runs at process + * end is vlibc's — which does not flush stdio yet (that hook lands with + * the stdio todo). Without the flush the buffered result above is lost. + */ + fflush(stdout); + return 0; } diff --git a/benchmarks/bench_vlibc_under.c b/benchmarks/bench_vlibc_under.c new file mode 100644 index 0000000..12b4b33 --- /dev/null +++ b/benchmarks/bench_vlibc_under.c @@ -0,0 +1,29 @@ +/* + * vlibc-under-test adapter for the bench_vlibc harness (todo 6 bench fix). + * + * This translation unit is compiled with vlibc's OWN headers (-I ../include) + * and is the ONLY TU in the benchmark that may include a vlibc header. The + * harness TU (bench_vlibc.c) must never include one: vlibc's self-contained + * /// shadow GCC's internal headers, + * so any TU that mixes a vlibc header with the host / + * fails to compile (glibc's needs __gnuc_va_list, which only the + * compiler's internal defines). The adapter isolates the + * vlibc-facing call here and exposes it to the host-header harness through + * its own declaration. + * + * Passing through the adapter also drops vlibc_version()'s + * __attribute__((const)) at the harness call site: the harness sees a plain + * external function, so the timed loop genuinely executes the call instead + * of being hoisted out by the optimizer. + */ + +#include + +const char * +bench_vlibc_version(void); + +const char * +bench_vlibc_version(void) +{ + return vlibc_version(); +}