alright, 5hrs of refactoring and remaking it because I was tired of the AI code breaking
This commit is contained in:
+1037
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,6 @@
|
||||
#ifndef FOSSBENCH_BENCHMARK_H
|
||||
#define FOSSBENCH_BENCHMARK_H
|
||||
|
||||
int fossbench_run(int verbose, int upload_mode, int system_check);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,316 @@
|
||||
/* Upload results if the user wants to. */
|
||||
|
||||
static void json_escape(const char *src, char *dst, size_t cap)
|
||||
{
|
||||
size_t used = 0;
|
||||
while (*src && used + 1 < cap) {
|
||||
unsigned char c = (unsigned char)*src++;
|
||||
const char *esc = NULL;
|
||||
if (c == '\"') esc = "\\\"";
|
||||
else if (c == '\\') esc = "\\\\";
|
||||
else if (c == '\n') esc = "\\n";
|
||||
else if (c == '\r') esc = "\\r";
|
||||
else if (c == '\t') esc = "\\t";
|
||||
if (esc) {
|
||||
size_t n = strlen(esc); if (used + n >= cap) break;
|
||||
memcpy(dst + used, esc, n); used += n;
|
||||
} else if (c >= 0x20) dst[used++] = (char)c;
|
||||
}
|
||||
dst[used] = '\0';
|
||||
}
|
||||
|
||||
#if !defined(_WIN32)
|
||||
/* Load the certificates included with the program. */
|
||||
static int load_embedded_ca_bundle(SSL_CTX *ctx)
|
||||
{
|
||||
X509_STORE *store = SSL_CTX_get_cert_store(ctx);
|
||||
BIO *bio = BIO_new_mem_buf(fb_ca_bundle_pem, -1);
|
||||
X509 *cert;
|
||||
int loaded = 0;
|
||||
|
||||
if (!bio) return 0;
|
||||
while ((cert = PEM_read_bio_X509(bio, NULL, NULL, NULL)) != NULL) {
|
||||
if (X509_STORE_add_cert(store, cert)) loaded++;
|
||||
X509_free(cert);
|
||||
}
|
||||
BIO_free(bio);
|
||||
ERR_clear_error(); /* Reaching the end sets an error, which is fine. */
|
||||
return loaded > 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32)
|
||||
/* Check if WinHTTP found a certificate problem. */
|
||||
static int is_winhttp_secure_error(DWORD err)
|
||||
{
|
||||
switch (err) {
|
||||
case ERROR_WINHTTP_SECURE_CERT_DATE_INVALID:
|
||||
case ERROR_WINHTTP_SECURE_CERT_CN_INVALID:
|
||||
case ERROR_WINHTTP_SECURE_INVALID_CA:
|
||||
case ERROR_WINHTTP_SECURE_CERT_REV_FAILED:
|
||||
case ERROR_WINHTTP_SECURE_CHANNEL_ERROR:
|
||||
case ERROR_WINHTTP_SECURE_INVALID_CERT:
|
||||
case ERROR_WINHTTP_SECURE_CERT_REVOKED:
|
||||
case ERROR_WINHTTP_SECURE_FAILURE:
|
||||
case ERROR_WINHTTP_SECURE_CERT_WRONG_USAGE:
|
||||
case ERROR_WINHTTP_SECURE_FAILURE_PROXY:
|
||||
return 1;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Send the request with Windows networking. */
|
||||
static int winhttp_post(const char *host, const char *port, const char *path,
|
||||
int use_tls, const char *payload, int payload_len,
|
||||
const char *auth_header, int *out_status)
|
||||
{
|
||||
wchar_t whost[256], wpath[512], wheaders[700];
|
||||
char header_buf[700];
|
||||
HINTERNET hsession = NULL, hconnect = NULL, hrequest = NULL;
|
||||
INTERNET_PORT wport = (INTERNET_PORT)atoi(port);
|
||||
DWORD status = 0, status_size = sizeof(status);
|
||||
int ok = 0;
|
||||
|
||||
if (MultiByteToWideChar(CP_UTF8, 0, host, -1, whost, sizeof whost / sizeof whost[0]) == 0 ||
|
||||
MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, sizeof wpath / sizeof wpath[0]) == 0)
|
||||
return 0;
|
||||
snprintf(header_buf, sizeof(header_buf), "Content-Type: application/json\r\n%s", auth_header);
|
||||
if (MultiByteToWideChar(CP_UTF8, 0, header_buf, -1, wheaders, sizeof wheaders / sizeof wheaders[0]) == 0)
|
||||
return 0;
|
||||
|
||||
hsession = WinHttpOpen(L"fossbench", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
|
||||
WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
|
||||
if (!hsession) {
|
||||
fprintf(stderr, " upload error: cannot initialize WinHTTP\n");
|
||||
return 0;
|
||||
}
|
||||
hconnect = WinHttpConnect(hsession, whost, wport, 0);
|
||||
if (!hconnect) {
|
||||
fprintf(stderr, " upload error: cannot connect to %s:%s\n", host, port);
|
||||
goto done;
|
||||
}
|
||||
hrequest = WinHttpOpenRequest(hconnect, L"POST", wpath, NULL, WINHTTP_NO_REFERER,
|
||||
WINHTTP_DEFAULT_ACCEPT_TYPES,
|
||||
use_tls ? WINHTTP_FLAG_SECURE : 0);
|
||||
if (!hrequest) {
|
||||
fprintf(stderr, " upload error: cannot create HTTP request\n");
|
||||
goto done;
|
||||
}
|
||||
if (!WinHttpSendRequest(hrequest, wheaders, (DWORD)-1L, (LPVOID)payload,
|
||||
(DWORD)payload_len, (DWORD)payload_len, 0)) {
|
||||
if (is_winhttp_secure_error(GetLastError()))
|
||||
fprintf(stderr, " upload error: TLS connection or certificate verification failed\n");
|
||||
else
|
||||
fprintf(stderr, " upload error: send failed\n");
|
||||
goto done;
|
||||
}
|
||||
if (!WinHttpReceiveResponse(hrequest, NULL)) {
|
||||
if (is_winhttp_secure_error(GetLastError()))
|
||||
fprintf(stderr, " upload error: TLS connection or certificate verification failed\n");
|
||||
else
|
||||
fprintf(stderr, " upload error: no server response\n");
|
||||
goto done;
|
||||
}
|
||||
if (!WinHttpQueryHeaders(hrequest, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
|
||||
WINHTTP_HEADER_NAME_BY_INDEX, &status, &status_size,
|
||||
WINHTTP_NO_HEADER_INDEX)) {
|
||||
fprintf(stderr, " upload error: no server response\n");
|
||||
goto done;
|
||||
}
|
||||
*out_status = (int)status;
|
||||
ok = 1;
|
||||
done:
|
||||
if (hrequest) WinHttpCloseHandle(hrequest);
|
||||
if (hconnect) WinHttpCloseHandle(hconnect);
|
||||
WinHttpCloseHandle(hsession);
|
||||
return ok;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int upload_results(const struct system_info *info, double score,
|
||||
double singlecore_score, const struct result *multi,
|
||||
const struct result *single, uint64_t duration_ms,
|
||||
const struct background_metrics *background, const char *token)
|
||||
{
|
||||
char host[256], port[16], path[512], payload[16384];
|
||||
char auth_header[600];
|
||||
char cpu[512], model[512], os[512], compiler[256], kernel[256];
|
||||
const char *base = FB_API_BASE_URL, *p, *slash, *colon;
|
||||
int use_tls, status = 0, payload_len;
|
||||
#if !defined(_WIN32)
|
||||
char request[20000], response[512];
|
||||
struct addrinfo hints, *addresses = NULL, *a;
|
||||
SSL_CTX *tls_ctx = NULL;
|
||||
SSL *tls = NULL;
|
||||
int fd = -1, request_len;
|
||||
#endif
|
||||
|
||||
if (!strncmp(base, "https://", 8)) {
|
||||
use_tls = 1; p = base + 8; strcpy(port, "443");
|
||||
} else if (!strncmp(base, "http://", 7)) {
|
||||
use_tls = 0; p = base + 7; strcpy(port, "80");
|
||||
} else {
|
||||
fprintf(stderr, " upload error: unsupported URL scheme\n");
|
||||
return 0;
|
||||
}
|
||||
slash = strchr(p, '/');
|
||||
if (!slash) slash = p + strlen(p);
|
||||
colon = memchr(p, ':', (size_t)(slash - p));
|
||||
if (colon) {
|
||||
size_t hn = (size_t)(colon - p), pn = (size_t)(slash - colon - 1);
|
||||
if (hn >= sizeof(host) || pn == 0 || pn >= sizeof(port)) return 0;
|
||||
memcpy(host, p, hn); host[hn] = '\0'; memcpy(port, colon + 1, pn); port[pn] = '\0';
|
||||
} else {
|
||||
size_t hn = (size_t)(slash - p); if (hn >= sizeof(host)) return 0;
|
||||
memcpy(host, p, hn); host[hn] = '\0';
|
||||
}
|
||||
{
|
||||
int base_path_len = (int)strlen(slash);
|
||||
while (base_path_len > 0 && slash[base_path_len - 1] == '/') base_path_len--;
|
||||
snprintf(path, sizeof(path), "%.*s/api/v1/submissions", base_path_len, slash);
|
||||
}
|
||||
|
||||
json_escape(info->cpu, cpu, sizeof(cpu));
|
||||
json_escape(info->model, model, sizeof(model));
|
||||
json_escape(info->operating_system, os, sizeof(os));
|
||||
json_escape(info->compiler, compiler, sizeof(compiler));
|
||||
json_escape(info->kernel, kernel, sizeof(kernel));
|
||||
/* The server still calls this field fossmark_version. */
|
||||
payload_len = snprintf(payload, sizeof(payload),
|
||||
"{\"cpu\":\"%s\",\"model\":\"%s\",\"cpu_cores\":%ld,\"cpu_threads\":%ld,"
|
||||
"\"memory_mb\":%ld,\"operating_system\":\"%s\",\"compiler\":\"%s\","
|
||||
"\"fossmark_version\":\"%s\",\"score\":%.17g,\"duration_ms\":%llu,"
|
||||
"\"score_details\":{\"scoring_method\":\"weighted_geometric_mean\","
|
||||
"\"target_score\":%.17g,\"multicore_score\":%.17g,\"singlecore_score\":%.17g,"
|
||||
"\"minimum_test_seconds\":%.17g,\"repeats\":%d,"
|
||||
"\"system_environment\":{\"kernel\":\"%s\",\"sample_seconds\":%d,"
|
||||
"\"background_cpu_average_percent\":%.17g,\"background_cpu_peak_percent\":%.17g,"
|
||||
"\"available_memory_mb\":%ld,\"process_count\":%ld},\"tests\":[",
|
||||
cpu, model, info->cpu_cores, info->cpu_threads, info->memory_mb, os, compiler,
|
||||
FB_VERSION, score, (unsigned long long)duration_ms, FB_TARGET_SCORE, score,
|
||||
singlecore_score, MIN_SECONDS, REPEATS, kernel, background->samples,
|
||||
background->average_cpu_percent, background->peak_cpu_percent,
|
||||
background->available_memory_mb, background->process_count);
|
||||
if (payload_len < 0 || (size_t)payload_len >= sizeof(payload)) return 0;
|
||||
{
|
||||
size_t used = (size_t)payload_len;
|
||||
size_t i;
|
||||
for (i = 0; i < NTESTS; i++) {
|
||||
int n = snprintf(payload + used, sizeof(payload) - used,
|
||||
"%s{\"name\":\"%s\",\"detail\":\"%s\",\"unit\":\"%s\","
|
||||
"\"start_iterations\":%llu,\"work_per_iteration\":%.17g,"
|
||||
"\"reference_rate\":%.17g,\"weight\":%.17g,"
|
||||
"\"multicore\":{\"display_metric\":%.17g,\"rate\":%.17g,\"score\":%.17g,"
|
||||
"\"seconds\":%.17g,\"iterations\":%llu,\"threads\":%d,\"checksum\":\"%llu\"},"
|
||||
"\"singlecore\":{\"display_metric\":%.17g,\"rate\":%.17g,\"score\":%.17g,"
|
||||
"\"seconds\":%.17g,\"iterations\":%llu,\"threads\":%d,\"checksum\":\"%llu\"}}",
|
||||
i ? "," : "", tests[i].name, tests[i].detail, tests[i].unit,
|
||||
(unsigned long long)tests[i].start_n, tests[i].work_per_n,
|
||||
tests[i].ref_rate, tests[i].weight,
|
||||
display_metric(&tests[i], &multi[i]), multi[i].rate, multi[i].score,
|
||||
multi[i].seconds, (unsigned long long)multi[i].iters, multi[i].threads,
|
||||
(unsigned long long)multi[i].checksum,
|
||||
display_metric(&tests[i], &single[i]), single[i].rate, single[i].score,
|
||||
single[i].seconds, (unsigned long long)single[i].iters, single[i].threads,
|
||||
(unsigned long long)single[i].checksum);
|
||||
if (n < 0 || (size_t)n >= sizeof(payload) - used) return 0;
|
||||
used += (size_t)n;
|
||||
}
|
||||
if (used + 3 >= sizeof(payload)) return 0;
|
||||
memcpy(payload + used, "]}}", 4);
|
||||
payload_len = (int)(used + 3);
|
||||
}
|
||||
|
||||
auth_header[0] = '\0';
|
||||
if (token && token[0]) {
|
||||
int n = snprintf(auth_header, sizeof(auth_header),
|
||||
"Authorization: Bearer %s\r\n", token);
|
||||
if (n < 0 || (size_t)n >= sizeof(auth_header)) {
|
||||
fprintf(stderr, " upload error: API token too long\n");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
#if !defined(_WIN32)
|
||||
request_len = snprintf(request, sizeof(request),
|
||||
"POST %s HTTP/1.1\r\nHost: %s:%s\r\nContent-Type: application/json\r\n"
|
||||
"Content-Length: %d\r\nConnection: close\r\n%s\r\n%s",
|
||||
path, host, port, payload_len, auth_header, payload);
|
||||
if (request_len < 0 || (size_t)request_len >= sizeof(request)) return 0;
|
||||
|
||||
memset(&hints, 0, sizeof(hints)); hints.ai_socktype = SOCK_STREAM; hints.ai_family = AF_UNSPEC;
|
||||
if (getaddrinfo(host, port, &hints, &addresses) != 0) { fprintf(stderr, " upload error: cannot resolve %s\n", host); return 0; }
|
||||
for (a = addresses; a; a = a->ai_next) {
|
||||
fd = socket(a->ai_family, a->ai_socktype, a->ai_protocol);
|
||||
if (fd >= 0 && connect(fd, a->ai_addr, a->ai_addrlen) == 0) break;
|
||||
if (fd >= 0) close(fd);
|
||||
fd = -1;
|
||||
}
|
||||
freeaddrinfo(addresses);
|
||||
if (fd < 0) { fprintf(stderr, " upload error: cannot connect to %s:%s\n", host, port); return 0; }
|
||||
if (use_tls) {
|
||||
tls_ctx = SSL_CTX_new(TLS_client_method());
|
||||
if (!tls_ctx) {
|
||||
fprintf(stderr, " upload error: cannot initialize TLS trust store\n");
|
||||
goto upload_failed;
|
||||
}
|
||||
SSL_CTX_set_default_verify_paths(tls_ctx); /* Try system certificates too. */
|
||||
if (!load_embedded_ca_bundle(tls_ctx)) {
|
||||
fprintf(stderr, " upload error: cannot initialize TLS trust store\n");
|
||||
goto upload_failed;
|
||||
}
|
||||
SSL_CTX_set_verify(tls_ctx, SSL_VERIFY_PEER, NULL);
|
||||
tls = SSL_new(tls_ctx);
|
||||
if (!tls || !SSL_set_tlsext_host_name(tls, host) ||
|
||||
!SSL_set1_host(tls, host) || !SSL_set_fd(tls, fd) ||
|
||||
SSL_connect(tls) != 1) {
|
||||
fprintf(stderr, " upload error: TLS connection or certificate verification failed\n");
|
||||
goto upload_failed;
|
||||
}
|
||||
}
|
||||
{
|
||||
size_t sent = 0;
|
||||
while (sent < (size_t)request_len) {
|
||||
int n = use_tls ? SSL_write(tls, request + sent, (int)((size_t)request_len - sent)) :
|
||||
(int)send(fd, request + sent, (size_t)request_len - sent, 0);
|
||||
if (n <= 0) { fprintf(stderr, " upload error: send failed\n"); goto upload_failed; }
|
||||
sent += (size_t)n;
|
||||
}
|
||||
}
|
||||
{
|
||||
int n = use_tls ? SSL_read(tls, response, sizeof(response) - 1) :
|
||||
(int)recv(fd, response, sizeof(response) - 1, 0);
|
||||
if (n <= 0) { fprintf(stderr, " upload error: no server response\n"); goto upload_failed; }
|
||||
response[n] = '\0';
|
||||
if (sscanf(response, "HTTP/%*s %d", &status) != 1) status = 0;
|
||||
}
|
||||
if (tls) { SSL_shutdown(tls); SSL_free(tls); }
|
||||
if (tls_ctx) SSL_CTX_free(tls_ctx);
|
||||
close(fd);
|
||||
#else
|
||||
if (!winhttp_post(host, port, path, use_tls, payload, payload_len, auth_header, &status))
|
||||
return 0;
|
||||
#endif
|
||||
if (status == 401) {
|
||||
fprintf(stderr, " upload failed: API token was rejected (HTTP 401)\n");
|
||||
return 0;
|
||||
}
|
||||
if (status == 422) {
|
||||
fprintf(stderr, " upload failed: server rejected the submission as invalid (HTTP 422)\n");
|
||||
return 0;
|
||||
}
|
||||
if (status < 200 || status >= 300) { fprintf(stderr, " upload failed: server returned HTTP %d\n", status); return 0; }
|
||||
if (token)
|
||||
printf(" Results uploaded and published to your profile (HTTP %d).\n", status);
|
||||
else
|
||||
printf(" Results uploaded, pending administrator review (HTTP %d).\n", status);
|
||||
return 1;
|
||||
|
||||
#if !defined(_WIN32)
|
||||
upload_failed:
|
||||
if (tls) SSL_free(tls);
|
||||
if (tls_ctx) SSL_CTX_free(tls_ctx);
|
||||
if (fd >= 0) close(fd);
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
+2
-23
@@ -1,26 +1,5 @@
|
||||
/*
|
||||
* Embedded CA trust bundle (fallback for upload_results() TLS).
|
||||
*
|
||||
* Statically linking against a machine's OpenSSL is not enough to verify
|
||||
* TLS certificates on an arbitrary target machine: SSL_CTX_set_default_verify_paths()
|
||||
* only works if OpenSSL's compiled-in default CA directory/file happens to exist on
|
||||
* the machine running the binary - a path baked in wherever OpenSSL itself was built,
|
||||
* not the release binary. That almost never matches the end user's machine (macOS has
|
||||
* no such path at all outside Homebrew; Linux distros disagree on the location), so
|
||||
* uploads failed with a TLS/certificate error on nearly every machine except the one
|
||||
* that built the release binaries.
|
||||
*
|
||||
* This bundle is carried as a fallback trust source so upload_results() can verify
|
||||
* fossbench.net's certificate chain unconditionally, without relying on the host having
|
||||
* a usable system trust store. It is tried in addition to (not instead of) the system's
|
||||
* own default verify paths, so locally-trusted/corporate CAs still work where present.
|
||||
*
|
||||
* Contents: the Mozilla CA root program's included/trusted certificate set, as shipped
|
||||
* by the ca-certificates-mozilla distro package (/etc/ssl/certs/ca-certificates.crt),
|
||||
* the same root program curl/Go/Python(certifi) bundle for the identical reason.
|
||||
* Regenerate by re-running the script that produced this file against a current
|
||||
* ca-certificates package; do this roughly yearly as roots rotate.
|
||||
*/
|
||||
/* Backup certificates for uploads.
|
||||
* These come from Mozilla's CA list. */
|
||||
|
||||
#ifndef FB_CA_BUNDLE_H
|
||||
#define FB_CA_BUNDLE_H
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* fossbench_x86_64.S - x86-64 (AMD64) CPU benchmark kernels
|
||||
* fossbench-amd64.S - x86-64 (AMD64) CPU benchmark kernels
|
||||
*
|
||||
* The AMD64 counterpart to fossbench.S. Same nine routines, same contract: each
|
||||
* The AMD64 counterpart to the ARM64 backend. Same nine routines and contract: each
|
||||
* is a pure function of its arguments under the System V AMD64 ABI, contains no
|
||||
* syscalls, no libc calls and no external data relocations, so it assembles and
|
||||
* runs on Linux (ELF), macOS (Mach-O) and the BSDs. The portable C driver in
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* fossbench.S - AArch64 CPU benchmark kernels
|
||||
* fossbench-arm64.S - AArch64 CPU benchmark kernels
|
||||
*
|
||||
* OS-independent: contains no syscalls, no libc calls, no relocations against
|
||||
* external data. Every routine is a pure function of its arguments under the
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* fossbench_i386.S - x86 32-bit (i386) CPU benchmark kernels
|
||||
* fossbench-i386.S - x86 32-bit (i386) CPU benchmark kernels
|
||||
*
|
||||
* The i386 counterpart to fossbench_x86_64.S. Same nine routines, same
|
||||
* The i386 counterpart to the AMD64 backend. Same nine routines, same
|
||||
* contract: each is a pure function of its arguments, contains no syscalls,
|
||||
* no libc calls and no external data relocations, so it assembles and runs
|
||||
* unmodified under the plain i386 SysV (cdecl) ABI on Linux.
|
||||
@@ -1,10 +1,4 @@
|
||||
/*
|
||||
* Portable kernel backend for big-endian PowerPC.
|
||||
*
|
||||
* Keeping this backend in C lets the compiler implement 64-bit arguments and
|
||||
* returns according to the platform ABI. All byte-oriented formats
|
||||
* are decoded explicitly, so the code is correct on big-endian systems.
|
||||
*/
|
||||
/* PowerPC kernel code. */
|
||||
#include <math.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
@@ -80,9 +74,7 @@ static uint64_t fb_simd_scalar(uint64_t iters, void *memory)
|
||||
#endif
|
||||
|
||||
#if defined(__powerpc64__)
|
||||
/* The PowerPC 970 in every iMac G5 implements AltiVec. Using GCC's vector
|
||||
* type here lets the compiler handle whichever PPC64 ELF ABI the system uses;
|
||||
* both PPC64 ABIs differ from the PPC32 assembly convention below. */
|
||||
/* The iMac G5 has AltiVec. */
|
||||
typedef uint32_t fb_vec_u32 __attribute__((vector_size(16)));
|
||||
|
||||
uint64_t fb_simd(uint64_t iters, void *memory)
|
||||
@@ -110,8 +102,7 @@ uint64_t fb_simd(uint64_t iters, void *memory)
|
||||
return sum;
|
||||
}
|
||||
#else
|
||||
/* These are kept in fossbench_ppc32_ext.S so this translation unit, and thus
|
||||
* the executable's default code path, only requires baseline PPC32. */
|
||||
/* The optional PowerPC code is in the assembly file. */
|
||||
extern void fb_simd_ps_kernel(uint64_t iters, void *memory);
|
||||
extern void fb_simd_vsx_kernel(uint64_t iters, void *memory);
|
||||
extern void fb_simd_altivec_kernel(uint64_t iters, void *memory);
|
||||
@@ -147,8 +138,7 @@ static int device_is_nintendo(void)
|
||||
|
||||
static fb_simd_kernel detect_simd_kernel(void)
|
||||
{
|
||||
/* Linux exposes these in AT_HWCAP on both 32- and 64-bit PowerPC.
|
||||
* Spell out the ABI values instead of depending on kernel-only headers. */
|
||||
/* Linux tells us which PowerPC features are available. */
|
||||
#if defined(__linux__) && defined(AT_HWCAP)
|
||||
const unsigned long hwcap = getauxval(AT_HWCAP);
|
||||
const unsigned long has_altivec = 0x10000000UL;
|
||||
@@ -1,5 +1,5 @@
|
||||
/* Optional PPC32 extended-instruction kernels. No instruction in this file
|
||||
* is reached until fossbench_ppc32.c has checked the device tree or AT_HWCAP.
|
||||
* is reached until fossbench-powerpc.c has checked the device tree or AT_HWCAP.
|
||||
* Arguments use the PPC32 ABI: iters in r3:r4 and memory in r5. */
|
||||
|
||||
.text
|
||||
+8
-1460
File diff suppressed because it is too large
Load Diff
+28
-65
@@ -1,21 +1,4 @@
|
||||
/*
|
||||
* test_kernels.c - correctness checks for the fossbench assembly kernels
|
||||
*
|
||||
* The benchmark's own best-of-N run guards against non-determinism, but a
|
||||
* kernel can be perfectly deterministic and still wrong. This file is the
|
||||
* "single C file to poke at and test with": it validates each kernel against
|
||||
* an independent reference or an invariant, so a mistake in the assembly is
|
||||
* caught here rather than silently skewing a score.
|
||||
*
|
||||
* Every check (except the single-threaded pointer-chase) is run concurrently
|
||||
* on all available cores. The kernels take their buffers as arguments and hold
|
||||
* no shared state, so a correct kernel must give identical, correct results no
|
||||
* matter how many copies run at once; a hidden global or a reentrancy bug would
|
||||
* survive a single-threaded run but fail here.
|
||||
*
|
||||
* Build: cc -O2 -pthread test_kernels.c fossbench.S -o test_kernels -lm
|
||||
* Exit status is 0 iff every check passes.
|
||||
*/
|
||||
/* The actual tests. */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -40,14 +23,7 @@ extern uint64_t fb_chase(void **ptrs, uint64_t steps);
|
||||
static int failures = 0;
|
||||
static int checks = 0;
|
||||
|
||||
/*
|
||||
* Concurrency plumbing. Each check runs on every core at once; the counters and
|
||||
* stdout are shared, so ok()/note() serialise on this lock. `fb_primary` is set
|
||||
* on exactly one thread per check (the one running on the main thread): it owns
|
||||
* the human-readable output so the "[ ok ]" lines and diagnostics appear once,
|
||||
* not once per core. Every thread still evaluates every assertion, so a failure
|
||||
* on any core - even a silent secondary - is reported and counted.
|
||||
*/
|
||||
/* Concurrency plumbing. */
|
||||
static pthread_mutex_t io_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
static __thread int fb_primary = 1;
|
||||
static long fb_ncores = 1;
|
||||
@@ -64,14 +40,14 @@ static void ok(const char *what, int cond)
|
||||
failures++;
|
||||
}
|
||||
} else if (!cond) {
|
||||
/* a secondary core disagrees: surface it explicitly */
|
||||
/* Show if another thread failed. */
|
||||
printf(" [FAIL] %s (concurrent core)\n", what);
|
||||
failures++;
|
||||
}
|
||||
pthread_mutex_unlock(&io_lock);
|
||||
}
|
||||
|
||||
/* Diagnostic output that should appear once per check, not once per core. */
|
||||
/* Only print this once. */
|
||||
static void note(const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
@@ -85,8 +61,7 @@ static void note(const char *fmt, ...)
|
||||
pthread_mutex_unlock(&io_lock);
|
||||
}
|
||||
|
||||
/* Run `check` on every core simultaneously. The main thread is the primary;
|
||||
* fb_ncores-1 workers run the same check as silent secondaries. */
|
||||
/* Run the same check on every core. */
|
||||
static void *fb_worker(void *arg)
|
||||
{
|
||||
void (*check)(void) = *(void (**)(void))arg;
|
||||
@@ -112,14 +87,14 @@ static void parallel(void (*check)(void))
|
||||
}
|
||||
}
|
||||
|
||||
check(); /* primary runs on this thread */
|
||||
check(); /* Run the first check here. */
|
||||
|
||||
for (i = 0; i < spawned; i++)
|
||||
pthread_join(th[i], NULL);
|
||||
free(th);
|
||||
}
|
||||
|
||||
/* ---------- reference implementations ---------- */
|
||||
/* Small C versions used for comparison. */
|
||||
|
||||
static uint64_t ref_prime_count(uint64_t limit)
|
||||
{
|
||||
@@ -137,9 +112,7 @@ static uint64_t ref_prime_count(uint64_t limit)
|
||||
return count;
|
||||
}
|
||||
|
||||
/* A textbook scalar ChaCha20 block function, used both to anchor against the
|
||||
* RFC 8439 known-answer vector and to validate the NEON kernel block-for-block.
|
||||
* `out` receives 64 keystream bytes for the given counter and 12-byte nonce. */
|
||||
/* Basic ChaCha20 used to check the kernel. */
|
||||
#define ROTL32(x, n) (((x) << (n)) | ((x) >> (32 - (n))))
|
||||
|
||||
static void ref_chacha_block(uint32_t out_words[16], const uint8_t key[32],
|
||||
@@ -180,12 +153,11 @@ static void ref_chacha_block(uint32_t out_words[16], const uint8_t key[32],
|
||||
out_words[i] = x[i] + s[i];
|
||||
}
|
||||
|
||||
/* ---------- checks ---------- */
|
||||
/* The actual tests. */
|
||||
|
||||
static void check_int(void)
|
||||
{
|
||||
/* determinism and non-triviality: the checksum must be stable and
|
||||
* must actually change with the iteration count */
|
||||
/* The actual tests. */
|
||||
uint64_t a = fb_int_math(1000);
|
||||
uint64_t b = fb_int_math(1000);
|
||||
uint64_t c = fb_int_math(2000);
|
||||
@@ -217,7 +189,7 @@ static void check_primes(void)
|
||||
note(" primes < %d: got %llu, expected %llu\n",
|
||||
LIM, (unsigned long long)got, (unsigned long long)ref);
|
||||
ok("primes matches reference sieve", got == ref);
|
||||
ok("primes < 10 == 4", fb_primes(10, sieve) == 4); /* 2,3,5,7 */
|
||||
ok("primes < 10 == 4", fb_primes(10, sieve) == 4); /* The primes are 2, 3, 5, and 7. */
|
||||
ok("primes < 2 == 0", fb_primes(2, sieve) == 0);
|
||||
free(sieve);
|
||||
}
|
||||
@@ -244,8 +216,7 @@ static void check_compress(void)
|
||||
uint64_t incompressible, compressible;
|
||||
size_t i;
|
||||
|
||||
/* genuinely incompressible data (splitmix64 output): with no matches
|
||||
* to exploit, an LZ coder's output must be at least the input size */
|
||||
/* Random data should not compress much. */
|
||||
{
|
||||
uint64_t st = 0x1234567890abcdefULL;
|
||||
for (i = 0; i < N; i++) {
|
||||
@@ -257,7 +228,7 @@ static void check_compress(void)
|
||||
}
|
||||
incompressible = fb_compress(src, N, ht);
|
||||
|
||||
/* all-zero data is maximally compressible: it must shrink hugely */
|
||||
/* Zeros should compress a lot. */
|
||||
memset(src, 0, N);
|
||||
compressible = fb_compress(src, N, ht);
|
||||
|
||||
@@ -276,9 +247,7 @@ static void check_crypto(void)
|
||||
uint8_t key[32];
|
||||
size_t i;
|
||||
|
||||
/* (1) anchor the scalar reference to the RFC 8439 s.2.3.2 vector:
|
||||
* key = 00,01,...,1f; counter = 1; nonce = 00,00,00,09,...,4a,...
|
||||
* serialised keystream begins 10 f1 e7 e4. */
|
||||
/* Check the C version with the RFC example. */
|
||||
{
|
||||
uint32_t w[16];
|
||||
uint8_t rnonce[12] = {0,0,0,9, 0,0,0,0x4a, 0,0,0,0};
|
||||
@@ -296,9 +265,7 @@ static void check_crypto(void)
|
||||
ks0[2] == 0xe7 && ks0[3] == 0xe4);
|
||||
}
|
||||
|
||||
/* (2) validate the NEON kernel against that reference. The kernel
|
||||
* hardwires nonce = 0 and starts the block counter at 0, so we
|
||||
* compare its keystream to the reference block-for-block. */
|
||||
/* Compare the kernel with the C version. */
|
||||
{
|
||||
uint8_t buf[128];
|
||||
uint8_t zero_nonce[12] = {0};
|
||||
@@ -307,7 +274,7 @@ static void check_crypto(void)
|
||||
|
||||
for (i = 0; i < 32; i++)
|
||||
key[i] = (uint8_t)(i * 5 + 1);
|
||||
memset(buf, 0, sizeof buf); /* zeros -> raw keystream */
|
||||
memset(buf, 0, sizeof buf); /* Zeros give the keystream. */
|
||||
fb_chacha20(buf, sizeof buf, key, 1);
|
||||
|
||||
ref_chacha_block(ref0, key, 0, zero_nonce);
|
||||
@@ -327,7 +294,7 @@ static void check_crypto(void)
|
||||
ok("NEON ChaCha20 matches scalar reference (2 blocks)", match);
|
||||
}
|
||||
|
||||
/* (3) the cipher is a real XOR stream: applying it twice is identity */
|
||||
/* Running it twice should restore the data. */
|
||||
{
|
||||
uint8_t plain[128], work[128], k2[32];
|
||||
for (i = 0; i < 128; i++)
|
||||
@@ -345,18 +312,17 @@ static void check_crypto(void)
|
||||
|
||||
static void check_physics(void)
|
||||
{
|
||||
/* two equal masses released from rest must accelerate toward each
|
||||
* other: symmetric, momentum-conserving, and bounded. */
|
||||
/* The two bodies should move toward each other. */
|
||||
double bodies[2 * 8] = {0};
|
||||
double total_p;
|
||||
|
||||
bodies[0] = -1.0; bodies[3] = 1.0; /* body 0 at x=-1, mass 1 */
|
||||
bodies[8] = 1.0; bodies[11] = 1.0; /* body 1 at x=+1, mass 1 */
|
||||
bodies[0] = -1.0; bodies[3] = 1.0; /* First body. */
|
||||
bodies[8] = 1.0; bodies[11] = 1.0; /* Second body. */
|
||||
|
||||
fb_physics(bodies, 2, 200);
|
||||
|
||||
/* velocities must be equal and opposite (Newton's third law) */
|
||||
total_p = bodies[4] + bodies[12]; /* vx0 + vx1 */
|
||||
/* The velocities should cancel out. */
|
||||
total_p = bodies[4] + bodies[12]; /* Add both x velocities. */
|
||||
note(" 2-body: vx0=%.6f vx1=%.6f (sum should be ~0)\n",
|
||||
bodies[4], bodies[12]);
|
||||
ok("physics conserves momentum", fabs(total_p) < 1e-9);
|
||||
@@ -396,13 +362,12 @@ static void check_sort(void)
|
||||
s = fb_sort(a, N);
|
||||
ok("sort produces sorted output", is_sorted(a, N));
|
||||
|
||||
/* multiset is preserved: sort the reference with the C library and
|
||||
* compare element by element */
|
||||
/* Compare it with the C library sort. */
|
||||
qsort(b, N, sizeof(uint32_t), cmp_u32);
|
||||
ok("sort is a permutation of the input",
|
||||
memcmp(a, b, N * sizeof(uint32_t)) == 0);
|
||||
|
||||
/* already-sorted input stays sorted and gives the same checksum */
|
||||
/* The actual tests. */
|
||||
{
|
||||
uint64_t s2 = fb_sort(a, N);
|
||||
ok("sort is idempotent on sorted data",
|
||||
@@ -416,8 +381,7 @@ static void check_sort(void)
|
||||
|
||||
static void check_chase(void)
|
||||
{
|
||||
/* build a tiny 4-node cycle by hand and confirm the walk returns to
|
||||
* the start after exactly `n` steps (offset 0 relative to entry) */
|
||||
/* Make a small pointer loop. */
|
||||
void *nodes[4];
|
||||
|
||||
nodes[0] = &nodes[1];
|
||||
@@ -425,11 +389,10 @@ static void check_chase(void)
|
||||
nodes[2] = &nodes[3];
|
||||
nodes[3] = &nodes[0];
|
||||
|
||||
/* 4 hops from &nodes[0] returns to &nodes[0]; fb_chase returns the
|
||||
* final pointer minus the starting pointer, so a full loop gives 0 */
|
||||
/* Four hops should return to the start. */
|
||||
ok("chase completes a full cycle", fb_chase(nodes, 4) == 0);
|
||||
ok("chase(0) is zero", fb_chase(nodes, 0) == 0);
|
||||
/* one hop lands on &nodes[1], i.e. one pointer-width past the start */
|
||||
/* One hop should move to the next pointer. */
|
||||
ok("chase single hop offset",
|
||||
fb_chase(nodes, 1) == (uint64_t)((char *)&nodes[1] - (char *)&nodes[0]));
|
||||
}
|
||||
@@ -453,7 +416,7 @@ int main(void)
|
||||
printf("Encryption:\n"); parallel(check_crypto);
|
||||
printf("Physics:\n"); parallel(check_physics);
|
||||
printf("Sorting:\n"); parallel(check_sort);
|
||||
/* the pointer chase is the single-threaded test: run it on one core */
|
||||
/* Run the pointer test on one core. */
|
||||
printf("Single-Threaded (chase):\n"); check_chase();
|
||||
|
||||
printf("\n=================================\n");
|
||||
|
||||
Reference in New Issue
Block a user