Add more tests for strlcpy()

I asked ChatGPT o1 for an optimized version of strlcpy() but it couldn't
produce an implementation that didn't crash. Eventually it just gave up.
This commit is contained in:
Justine Tunney 2024-09-13 01:10:14 -07:00
parent e142124730
commit 1260f9d0ed
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
2 changed files with 105 additions and 32 deletions

View file

@ -7,24 +7,22 @@ COSMOPOLITAN_C_START_
#define X(x) __expropriate(x) #define X(x) __expropriate(x)
#define V(x) __veil("r", x) #define V(x) __veil("r", x)
#define BENCHMARK(ITERATIONS, WORK_PER_RUN, CODE) \ #define BENCHMARK(ITERATIONS, WORK_PER_RUN, CODE) \
do { \ do { \
struct timespec start = timespec_mono(); \ struct timespec start = timespec_real(); \
for (int __i = 0; __i < ITERATIONS; ++__i) { \ for (int __i = 0; __i < ITERATIONS; ++__i) { \
asm volatile("" ::: "memory"); \ asm volatile("" ::: "memory"); \
CODE; \ CODE; \
} \ } \
double total_nanos = \ long ns = timespec_tonanos(timespec_sub(timespec_real(), start)); \
(double)timespec_tonanos(timespec_sub(timespec_mono(), start)); \ _print_benchmark_result(ns, WORK_PER_RUN, ITERATIONS, #CODE); \
double total_work = \
(double)((WORK_PER_RUN) ? (WORK_PER_RUN) : 1) * (ITERATIONS); \
_print_benchmark_result(total_nanos, total_work, ITERATIONS, #CODE); \
} while (0) } while (0)
static void _print_benchmark_result(double total_nanos, double total_work, static void _print_benchmark_result(double total_nanos, double work_per_run,
int iterations, const char* code) { int iterations, const char* code) {
double time_per_op = total_nanos / iterations; double time_per_op = total_nanos / (work_per_run * iterations);
double throughput = total_work / (total_nanos * 1e-9); double throughput = work_per_run / (total_nanos / iterations * 1e-9);
const char* throughput_unit; const char* throughput_unit;
const char* time_unit; const char* time_unit;
double time_value; double time_value;
@ -50,29 +48,32 @@ static void _print_benchmark_result(double total_nanos, double total_work,
} else if (time_per_op >= 1e3) { } else if (time_per_op >= 1e3) {
time_value = time_per_op / 1e3; time_value = time_per_op / 1e3;
time_unit = "µs"; time_unit = "µs";
} else { } else if (time_per_op >= .01) {
time_value = time_per_op; time_value = time_per_op;
time_unit = "ns"; time_unit = "ns";
} else {
time_value = time_per_op * 1e3;
time_unit = "ps";
} }
// Determine work unit // Determine work unit
const char* work_unit; const char* work_unit;
double work_value = total_work / iterations; if (work_per_run >= 1e9) {
if (work_value >= 1e9) { work_per_run /= 1e9;
work_value /= 1e9;
work_unit = "G"; work_unit = "G";
} else if (work_value >= 1e6) { } else if (work_per_run >= 1e6) {
work_value /= 1e6; work_per_run /= 1e6;
work_unit = "M"; work_unit = "M";
} else if (work_value >= 1e3) { } else if (work_per_run >= 1e3) {
work_value /= 1e3; work_per_run /= 1e3;
work_unit = "K"; work_unit = "K";
} else { } else {
work_unit = " "; work_unit = " ";
} }
printf("%8.2f %-2s %6.2f %s/s %6.2f %s %2dx %s\n", time_value, time_unit, printf("%8.2f %-2s %8.2f %s/s %6.2f %s %2dx %s\n", time_value, time_unit,
throughput, throughput_unit, work_value, work_unit, iterations, code); throughput, throughput_unit, work_per_run, work_unit, iterations,
code);
} }
COSMOPOLITAN_C_END_ COSMOPOLITAN_C_END_

View file

@ -16,12 +16,71 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE. PERFORMANCE OF THIS SOFTWARE.
*/ */
#include "libc/assert.h"
#include "libc/calls/calls.h"
#include "libc/intrin/kprintf.h"
#include "libc/intrin/safemacros.h"
#include "libc/mem/gc.h" #include "libc/mem/gc.h"
#include "libc/mem/mem.h" #include "libc/mem/mem.h"
#include "libc/runtime/runtime.h"
#include "libc/runtime/sysconf.h"
#include "libc/stdio/rand.h"
#include "libc/str/str.h" #include "libc/str/str.h"
#include "libc/sysv/consts/map.h"
#include "libc/sysv/consts/prot.h"
#include "libc/testlib/benchmark.h"
#include "libc/testlib/ezbench.h" #include "libc/testlib/ezbench.h"
#include "libc/testlib/testlib.h" #include "libc/testlib/testlib.h"
size_t todd(char *dst, const char *src, size_t dsize) {
const char *osrc = src;
size_t nleft = dsize;
if (nleft != 0)
while (--nleft != 0)
if ((*dst++ = *src++) == '\0')
break;
if (nleft == 0) {
if (dsize != 0)
*dst = '\0';
while (*src++)
;
}
return src - osrc - 1;
}
TEST(strlcpy, fuzz) {
int pagesz = sysconf(_SC_PAGESIZE);
char *map1 = (char *)mmap(0, pagesz * 2, PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
npassert(map1 != MAP_FAILED);
npassert(!mprotect(map1 + pagesz, pagesz, PROT_NONE));
char *map2 = (char *)mmap(0, pagesz * 2, PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
npassert(map2 != MAP_FAILED);
npassert(!mprotect(map2 + pagesz, pagesz, PROT_NONE));
char *map3 = (char *)mmap(0, pagesz * 2, PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
npassert(map3 != MAP_FAILED);
npassert(!mprotect(map3 + pagesz, pagesz, PROT_NONE));
for (int dsize = 1; dsize < 128; ++dsize) {
char *volatile dst1 = map1 + pagesz - dsize;
char *volatile dst2 = map1 + pagesz - dsize;
for (int i = 0; i < dsize; ++i)
dst1[i] = dst2[i] = max(rand() & 255, 1);
for (int ssize = 1; ssize < dsize * 2; ++ssize) {
char *volatile src = map3 + pagesz - (ssize + 1);
for (int i = 0; i < ssize; ++i)
src[i] = max(rand() & 255, 1);
src[ssize] = 0;
ASSERT_EQ(todd(dst1, src, dsize), strlcpy(dst2, src, dsize));
ASSERT_EQ(0, memcmp(dst1, dst2, dsize));
}
}
npassert(!munmap(map3, pagesz * 2));
npassert(!munmap(map2, pagesz * 2));
npassert(!munmap(map1, pagesz * 2));
}
TEST(strlcpy, testEmptyBuffer_doesNothing) { TEST(strlcpy, testEmptyBuffer_doesNothing) {
EXPECT_EQ(5, strlcpy(NULL, "hello", 0)); EXPECT_EQ(5, strlcpy(NULL, "hello", 0));
} }
@ -38,12 +97,25 @@ TEST(strlcpy, testShortBuffer_copies) {
EXPECT_STREQ("h", buf); EXPECT_STREQ("h", buf);
} }
#define N 4096
BENCH(strlcpy, bench) { BENCH(strlcpy, bench) {
char buf[256]; char dst[N];
EZBENCH2( char src[N + 1];
"strlcpy", donothing,
__expropriate(strlcpy(__veil("r", buf), "hello there", sizeof(buf)))); printf("\n");
EZBENCH2( for (int n = 1; n <= N; n *= 2) {
"strncpy", donothing, for (int i = 0; i < n; ++i)
__expropriate(strncpy(__veil("r", buf), "hello there", sizeof(buf)))); src[i] = max(rand() & 255, 1);
src[n] = 0;
BENCHMARK(100, n, X(strlcpy(dst, src, V(N))));
}
printf("\n");
for (int n = 1; n <= N; n *= 2) {
for (int i = 0; i < n; ++i)
src[i] = max(rand() & 255, 1);
src[n] = 0;
BENCHMARK(100, n, X(todd(dst, src, V(N))));
}
} }