fix(bench): separate vlibc-under-test TU from host harness TUs

This commit is contained in:
2026-09-03 19:46:20 -04:00
parent 40a392f454
commit 858fc1762c
4 changed files with 86 additions and 9 deletions
+24 -6
View File
@@ -10,18 +10,30 @@ EXTRA_PROGRAMS = bench_vlibc bench_string
bench_vlibc_SOURCES = bench_vlibc.c bench_vlibc_SOURCES = bench_vlibc.c
bench_string_SOURCES = bench_string.c bench_string_SOURCES = bench_string.c
# bench_string is a system-headers-only translation unit (it times whichever # Both harness TUs are system-headers-only translation units (they time
# libc it is linked against), so it must NOT inherit AM_CPPFLAGS: -I include # whichever libc they are linked against), so they must NOT inherit
# would pull vlibc's self-contained <stddef.h>/<string.h> into the same TU as # AM_CPPFLAGS: -I include would pull vlibc's self-contained
# the system <stdio.h>/<time.h> and double-define size_t/NULL/offsetof. # <stdarg.h>/<stddef.h>/<string.h> into the same TU as the system
# bench_vlibc keeps the inherited -I include. # <stdio.h>/<time.h> and double-define size_t/NULL/offsetof (or hide the
# compiler's internal <stdarg.h> 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 = 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 # By default benchmarks link against vlibc itself. --with-libc=glibc links the
# same harness against the system glibc; --with-libc=musl builds it with # same harness against the system glibc; --with-libc=musl builds it with
# musl-gcc for the musl reference. # musl-gcc for the musl reference.
if BENCH_LINK_VLIBC if BENCH_LINK_VLIBC
bench_vlibc_LDADD = ../libvlibc.la bench_vlibc_LDADD = bench_vlibc_under.o ../libvlibc.la
bench_string_LDADD = ../libvlibc.la bench_string_LDADD = ../libvlibc.la
else else
bench_vlibc_LDADD = bench_vlibc_LDADD =
@@ -38,6 +50,12 @@ endif
# binary from the previous configuration and the wrong libc would be measured. # binary from the previous configuration and the wrong libc would be measured.
EXTRA_bench_string_DEPENDENCIES = $(top_builddir)/config.status EXTRA_bench_string_DEPENDENCIES = $(top_builddir)/config.status
EXTRA_bench_vlibc_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 # 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 # --with-libc=glibc/musl it cannot link, so it is built and run only in the
+9
View File
@@ -149,5 +149,14 @@ main(void)
printf("%llu\n", sink); 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; return 0;
} }
+24 -3
View File
@@ -5,16 +5,29 @@
* This stub times vlibc_version() and is the skeleton that per-component * This stub times vlibc_version() and is the skeleton that per-component
* benchmarks build on. Reconfigure with --with-libc=musl or --with-libc=glibc * benchmarks build on. Reconfigure with --with-libc=musl or --with-libc=glibc
* to link the same harness against a reference libc for comparison. * 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
* <stdarg.h>/<stddef.h>/<limits.h>/<float.h> shadow GCC's internal headers
* and mixing them with the system <stdio.h>/<time.h> 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 #ifdef HAVE_CONFIG_H
#include <config.h> #include <config.h>
#endif #endif
#include <vlibc.h>
#include <stdio.h> #include <stdio.h>
#include <time.h> #include <time.h>
/* Adapter entry (bench_vlibc_under.c); declared here rather than including
* <vlibc.h>, 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 #define ITERATIONS 100000000ULL
int int
@@ -34,7 +47,7 @@ main(void)
for (unsigned long long i = 0; i < ITERATIONS; i++) for (unsigned long long i = 0; i < ITERATIONS; i++)
{ {
version = vlibc_version(); version = bench_vlibc_version();
} }
if (clock_gettime(CLOCK_MONOTONIC, &end) != 0) 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, printf("vlibc_version() x %llu: %.3f s (%.2f ns/call), version=%s\n", ITERATIONS, seconds,
seconds * 1000000000.0 / ITERATIONS, (const char *)version); 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; return 0;
} }
+29
View File
@@ -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
* <stdarg.h>/<stddef.h>/<limits.h>/<float.h> shadow GCC's internal headers,
* so any TU that mixes a vlibc header with the host <stdio.h>/<time.h>
* fails to compile (glibc's <stdio.h> needs __gnuc_va_list, which only the
* compiler's internal <stdarg.h> 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 <vlibc.h>
const char *
bench_vlibc_version(void);
const char *
bench_vlibc_version(void)
{
return vlibc_version();
}