From 370e64c3a6ec2e0d5f678def879a69e3770a3497 Mon Sep 17 00:00:00 2001 From: huntedbytheirs Date: Sun, 30 Aug 2026 04:08:01 -0400 Subject: [PATCH] Add smoke test and benchmarks smoke links crt0.o + libc.a with -nostdlib -static and proves the whole chain runs without the dynamic linker; bench_strlen and bench_syscall measure the string core and raw syscall round-trip cost via make bench. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- benchmarks/bench.h | 44 ++++++++++++++++++++++++++++++++++++++ benchmarks/bench_strlen.c | 28 ++++++++++++++++++++++++ benchmarks/bench_syscall.c | 29 +++++++++++++++++++++++++ tests/smoke.c | 30 ++++++++++++++++++++++++++ 4 files changed, 131 insertions(+) create mode 100644 benchmarks/bench.h create mode 100644 benchmarks/bench_strlen.c create mode 100644 benchmarks/bench_syscall.c create mode 100644 tests/smoke.c diff --git a/benchmarks/bench.h b/benchmarks/bench.h new file mode 100644 index 0000000..e39aadf --- /dev/null +++ b/benchmarks/bench.h @@ -0,0 +1,44 @@ +/* + * bench.h — tiny shared helpers for nulsl-libc benchmarks. + * + * Deliberately dependency-free beyond the libc itself: timing goes + * through a raw clock_gettime syscall, and numbers are printed with + * putchar/write because printf() is still a stub. If you write a new + * benchmark, include this and stay this lean. + */ + +#ifndef _NULSL_BENCH_H +#define _NULSL_BENCH_H + +#include +#include + +#define BENCH_CLOCK_MONOTONIC 1 + +/* Monotonic time in nanoseconds, via raw clock_gettime(2). */ +static inline long long bench_now_ns(void) +{ + struct { + long tv_sec; + long tv_nsec; + } t; + + syscall(SYS_clock_gettime, BENCH_CLOCK_MONOTONIC, &t); + return t.tv_sec * 1000000000LL + t.tv_nsec; +} + +/* Print an unsigned integer plus a newline, without printf(). */ +static inline void bench_print_u64(unsigned long long v) +{ + char buf[24]; + int i = sizeof buf; + + buf[--i] = '\n'; + do { + buf[--i] = (char)('0' + v % 10); + v /= 10; + } while (v); + write(STDOUT_FILENO, buf + i, sizeof buf - i); +} + +#endif /* _NULSL_BENCH_H */ diff --git a/benchmarks/bench_strlen.c b/benchmarks/bench_strlen.c new file mode 100644 index 0000000..1bd7115 --- /dev/null +++ b/benchmarks/bench_strlen.c @@ -0,0 +1,28 @@ +/* + * bench_strlen.c — how fast is our string core? + * + * Measures strlen() over a typical short string. 10M iterations keeps + * the loop overhead negligible; results are printed as ns per call. + */ + +#include + +#include "bench.h" + +#define ITERS 10000000UL + +int main(void) +{ + static const char s[] = "the quick brown fox jumps over the lazy dog"; + volatile size_t sink = 0; + long long t0, t1; + + t0 = bench_now_ns(); + for (unsigned long i = 0; i < ITERS; i++) + sink += strlen(s); + t1 = bench_now_ns(); + + (void)sink; /* keep the loop observable */ + bench_print_u64((unsigned long long)((t1 - t0) / ITERS)); + return 0; +} diff --git a/benchmarks/bench_syscall.c b/benchmarks/bench_syscall.c new file mode 100644 index 0000000..b271d08 --- /dev/null +++ b/benchmarks/bench_syscall.c @@ -0,0 +1,29 @@ +/* + * bench_syscall.c — raw syscall round-trip cost. + * + * This is the number that justifies the whole project: for a kernel-first + * libc (project guideline #6), every wrapper is one `syscall` instruction + * away from the kernel. getpid() is the cheapest syscall there is, so + * this measures the floor. 1M iterations; results in ns per call. + */ + +#include + +#include "bench.h" + +#define ITERS 1000000UL + +int main(void) +{ + volatile pid_t sink = 0; + long long t0, t1; + + t0 = bench_now_ns(); + for (unsigned long i = 0; i < ITERS; i++) + sink += getpid(); + t1 = bench_now_ns(); + + (void)sink; + bench_print_u64((unsigned long long)((t1 - t0) / ITERS)); + return 0; +} diff --git a/tests/smoke.c b/tests/smoke.c new file mode 100644 index 0000000..5c4bf31 --- /dev/null +++ b/tests/smoke.c @@ -0,0 +1,30 @@ +/* + * smoke.c — end-to-end smoke test. + * + * Linked with crt0.o + libc.a, -nostdlib -static: if this binary runs at + * all, the whole chain works — entry point, main(), our string code, and + * raw syscalls — with no dynamic linker and no glibc in the process. + */ + +#include +#include + +int main(void) +{ + static const char msg[] = "nulsl-libc smoke test: ok\n"; + + if (strcmp("abc", "abc") != 0) + return 1; + if (strcmp("abc", "abd") == 0) + return 2; + if (strncmp("abcdef", "abcxyz", 3) != 0) + return 3; + if (strlen("nulsl") != 5) + return 4; + if (memcmp("abcd", "abce", 3) != 0) + return 5; + if (write(STDOUT_FILENO, msg, sizeof msg - 1) < 0) + return 6; + + return 0; +}