FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/tests/checkasm/ext/src/checkasm.c
Date: 2026-09-11 23:04:01
Exec Total Coverage
Lines: 237 700 33.9%
Functions: 19 45 42.2%
Branches: 133 404 32.9%

Line Branch Exec Source
1 /*
2 * Copyright © 2025, Niklas Haas
3 * Copyright © 2018, VideoLAN and dav1d authors
4 * Copyright © 2018, Two Orioles, LLC
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions are met:
9 *
10 * 1. Redistributions of source code must retain the above copyright notice,
11 * this list of conditions and the following disclaimer.
12 *
13 * 2. Redistributions in binary form must reproduce the above copyright notice,
14 * this list of conditions and the following disclaimer in the documentation
15 * and/or other materials provided with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
21 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 * POSSIBILITY OF SUCH DAMAGE.
28 */
29
30 #include "checkasm_config.h"
31
32 #if HAVE_PTHREAD_SETAFFINITY_NP
33 /* _GNU_SOURCE is required for pthread_setaffinity_np and CPU_SET on glibc. */
34 #ifndef _GNU_SOURCE
35 #define _GNU_SOURCE
36 #endif
37 #endif
38
39 #include <assert.h>
40 #include <errno.h>
41 #include <inttypes.h>
42 #include <limits.h>
43 #include <stdarg.h>
44 #include <stdio.h>
45 #include <stdlib.h>
46 #include <string.h>
47
48 #include "checkasm/checkasm.h"
49 #include "checkasm/test.h"
50 #include "cpu.h"
51 #include "function.h"
52 #include "html_data.h"
53 #include "internal.h"
54 #include "stats.h"
55
56 #ifndef _WIN32
57 #if HAVE_PTHREAD_SETAFFINITY_NP
58 #include <pthread.h>
59 #if HAVE_PTHREAD_NP_H
60 #include <pthread_np.h>
61 #endif
62 #endif
63 #endif
64
65 #ifdef _WIN32
66 #include <windows.h>
67 #endif
68
69 #if HAVE_PRCTL
70 #include <sys/prctl.h>
71 #endif
72
73 /* Internal state */
74 static CheckasmConfig cfg;
75 static CheckasmStats stats; /* temporary buffer for function measurements */
76
77 /* Current function/test state, reset after each test run */
78 static struct {
79 CheckasmFuncTree tree;
80
81 /* (Re)set by check_cpu_flag() */
82 const CheckasmCpuInfo *cpu;
83 int cpu_name_printed;
84 CheckasmCpu cpu_flags;
85 int cpu_suffix_length;
86 const char *test_name;
87 int should_fail;
88 int report_idx;
89
90 /* (Re)set per function (check_func, bench_finish) */
91 CheckasmFunc *func;
92 CheckasmFuncVersion *func_ver;
93 char *func_variant;
94 uint64_t cycles;
95
96 /* Overall stats for this test run */
97 int num_funcs; /* known functions */
98 int num_checked; /* checked versions */
99 int num_failed; /* failed versions */
100 int num_benched; /* benched versions */
101 int prev_checked, prev_failed; /* reset by report() */
102 int saved_checked, saved_failed; /* for restoring after a crash */
103 double var_sum, var_max;
104 } current;
105
106 /* Global state for the entire checkasm_run() call */
107 static struct {
108 /* Miscellaneous global state (cosmetic) */
109 int max_function_name_length;
110 int max_report_name_length;
111 unsigned test_iter;
112
113 /* Timing code measurements (aggregated over multiple trials) */
114 CheckasmMeasurement nop_cycles;
115 CheckasmMeasurement perf_scale;
116
117 /* Runtime constants */
118 uint64_t target_cycles;
119 int skip_tests;
120 } state;
121
122 1024 CheckasmCpu checkasm_get_cpu_flags(void)
123 {
124 1024 return current.cpu_flags;
125 }
126
127 1205 const CheckasmCpuInfo *checkasm_get_cpu_info(void)
128 {
129 1205 return current.cpu;
130 }
131
132 /* Get the suffix of the specified cpu flag */
133 31625 static const char *cpu_suffix(const CheckasmCpuInfo *cpu)
134 {
135
2/2
✓ Branch 0 taken 17997 times.
✓ Branch 1 taken 13628 times.
31625 return cpu ? cpu->suffix : "c";
136 }
137
138 static const char *ver_suffix(const CheckasmFuncVersion *ver)
139 {
140 return ver->suffix ? ver->suffix : cpu_suffix(ver->cpu);
141 }
142
143 /* Returns the coefficient of variation (CV) */
144 static double relative_error(double lvar)
145 {
146 return sqrt(exp(lvar) - 1.0);
147 }
148
149 static inline char separator(CheckasmFormat format)
150 {
151 switch (format) {
152 case CHECKASM_FORMAT_CSV: return ',';
153 case CHECKASM_FORMAT_TSV: return '\t';
154 default: return 0;
155 }
156 }
157
158 static void json_var(CheckasmJson *json, const char *key, const char *unit,
159 const CheckasmVar var)
160 {
161 if (key)
162 checkasm_json_push(json, key, '{');
163 if (unit)
164 checkasm_json_str(json, "unit", unit);
165 checkasm_json(json, "mode", "%g", checkasm_mode(var));
166 checkasm_json(json, "median", "%g", checkasm_median(var));
167 checkasm_json(json, "mean", "%g", checkasm_mean(var));
168 checkasm_json(json, "lowerCI", "%g", checkasm_sample(var, -1.96));
169 checkasm_json(json, "upperCI", "%g", checkasm_sample(var, 1.96));
170 checkasm_json(json, "stdDev", "%g", checkasm_stddev(var));
171 checkasm_json(json, "logMean", "%g", var.lmean);
172 checkasm_json(json, "logVar", "%g", var.lvar);
173 if (key)
174 checkasm_json_pop(json, '}');
175 }
176
177 static void json_measurement(CheckasmJson *json, const char *key, const char *unit,
178 const CheckasmMeasurement measurement)
179 {
180 const CheckasmVar result = checkasm_measurement_result(measurement);
181 if (key)
182 checkasm_json_push(json, key, '{');
183 json_var(json, NULL, unit, result);
184 checkasm_json(json, "numMeasurements", "%d", measurement.nb_measurements);
185
186 if (measurement.stats.nb_samples) {
187 json_var(json, "regressionSlope", unit,
188 checkasm_stats_estimate(&measurement.stats));
189 checkasm_json_push(json, "rawData", '[');
190 for (int i = 0; i < measurement.stats.nb_samples; i++) {
191 const CheckasmSample s = measurement.stats.samples[i];
192 checkasm_json(json, NULL, "{ \"iters\": %d, \"cycles\": %" PRIu64 " }",
193 s.count, s.sum);
194 }
195 checkasm_json_pop(json, ']');
196 }
197
198 if (key)
199 checkasm_json_pop(json, '}');
200 }
201
202 static void cpu_info_json(void *priv, const char *fmt, ...)
203 {
204 CheckasmJson *json = priv;
205 char buf[128];
206 va_list ap;
207 va_start(ap, fmt);
208 vsnprintf(buf, sizeof(buf), fmt, ap);
209 va_end(ap);
210
211 checkasm_json_str(json, NULL, buf);
212 }
213
214 static void cpu_mask_json(CheckasmJson *json, const CheckasmConfig cfg,
215 const CheckasmCpu cpu_mask)
216 {
217 if (!cpu_mask)
218 return;
219
220 checkasm_json_push(json, "mask", '[');
221 for (const CheckasmCpuInfo *info = cfg.cpu_flags; info->flag; info++) {
222 if ((cpu_mask & info->flag) != info->flag)
223 continue;
224 checkasm_json_str(json, NULL, info->suffix);
225 }
226 checkasm_json_pop(json, ']');
227 }
228
229 struct IterState {
230 const char *test;
231 const char *report;
232 CheckasmJson json;
233 };
234
235 static void print_bench_header(struct IterState *const iter)
236 {
237 const CheckasmVar nop_cycles = checkasm_measurement_result(state.nop_cycles);
238 const CheckasmVar perf_scale = checkasm_measurement_result(state.perf_scale);
239 const CheckasmVar nop_time = checkasm_var_mul(nop_cycles, perf_scale);
240 CheckasmJson *const json = &iter->json;
241
242 switch (cfg.format) {
243 case CHECKASM_FORMAT_TSV:
244 case CHECKASM_FORMAT_CSV:
245 if (cfg.verbose) {
246 const char sep = separator(cfg.format);
247 printf("name%csuffix%c%ss%cstddev%cnanoseconds\n", sep, sep,
248 checkasm_perf.unit, sep, sep);
249 printf("nop%c%c%.4f%c%.5f%c%.4f\n", sep, sep, checkasm_mode(nop_cycles), sep,
250 checkasm_stddev(nop_cycles), sep, checkasm_mode(nop_time));
251 }
252 break;
253 case CHECKASM_FORMAT_HTML:
254 printf("<!doctype html>\n"
255 "<html>\n"
256 "<head>\n"
257 " <meta charset=\"utf-8\"/>\n"
258 " <title>checkasm report</title>\n"
259 " <script type=\"module\">\n"
260 " %s"
261 " %s"
262 " </script>\n"
263 " <style>\n"
264 " %s"
265 " </style>\n"
266 " <script type=\"application/json\" id=\"report-data\">\n",
267 checkasm_chart_js, checkasm_js, checkasm_css);
268 FALLTHROUGH;
269 case CHECKASM_FORMAT_JSON:
270 checkasm_json_push(json, NULL, '{');
271 checkasm_json_str(json, "checkasmVersion", CHECKASM_VERSION);
272 checkasm_json(json, "numChecked", "%d", current.num_checked);
273 checkasm_json(json, "numFailed", "%d", current.num_failed);
274 checkasm_json(json, "targetCycles", "%" PRIu64, state.target_cycles);
275 checkasm_json(json, "numBenchmarks", "%d", current.num_benched);
276 checkasm_json_push(json, "config", '{');
277 if (cfg.test_pattern)
278 checkasm_json_str(json, "testPattern", cfg.test_pattern);
279 if (cfg.function_pattern)
280 checkasm_json_str(json, "functionPattern", cfg.function_pattern);
281 checkasm_json(json, "benchUsec", "%u", cfg.bench_usec);
282 checkasm_json(json, "seed", "%u", cfg.seed);
283 checkasm_json(json, "repeat", "%u", cfg.repeat);
284 if (cfg.cpu_affinity_set)
285 checkasm_json(json, "cpuAffinity", "%u", cfg.cpu_affinity);
286 checkasm_json_pop(json, '}'); /* close config */
287 checkasm_json_push(json, "cpuInfo", '[');
288 checkasm_cpu_info(cpu_info_json, json, &cfg);
289 checkasm_json_pop(json, ']');
290 checkasm_json_push(json, "cpuFlags", '{');
291 for (const CheckasmCpuInfo *info = cfg.cpu_flags; info->flag; info++) {
292 const int available = (cfg.cpu & info->flag) == info->flag;
293 checkasm_json_push(json, info->suffix, '{');
294 checkasm_json_str(json, "name", info->name);
295 checkasm_json(json, "available", available ? "true" : "false");
296 cpu_mask_json(json, cfg, info->mask);
297 checkasm_json_pop(json, '}');
298 }
299 checkasm_json_pop(json, '}'); /* close cpuFlags */
300 checkasm_json_push(json, "tests", '[');
301 for (const CheckasmTest *test = cfg.tests; test->func; test++)
302 checkasm_json_str(json, NULL, test->name);
303 checkasm_json_pop(json, ']'); /* close tests */
304 char perf_scale_unit[32];
305 snprintf(perf_scale_unit, sizeof(perf_scale_unit), "nsec/%s", checkasm_perf.unit);
306 json_measurement(json, "nopCycles", checkasm_perf.unit, state.nop_cycles);
307 json_measurement(json, "timerScale", perf_scale_unit, state.perf_scale);
308 json_var(json, "nopTime", checkasm_perf.unit, nop_time);
309 checkasm_json(json, "numFunctions", "%d", current.num_funcs);
310 checkasm_json_push(json, "functions", '{');
311 break;
312 case CHECKASM_FORMAT_PRETTY:
313 checkasm_fprintf(stdout, COLOR_YELLOW, "Benchmark results:\n");
314 checkasm_fprintf(stdout, COLOR_GREEN, " name%*ss",
315 5 + state.max_function_name_length, checkasm_perf.unit);
316 if (cfg.verbose) {
317 checkasm_fprintf(stdout, COLOR_GREEN, " +/- stddev %*s", 26,
318 "time (nanoseconds)");
319 }
320 checkasm_fprintf(stdout, COLOR_GREEN, " (vs ref)\n");
321 if (cfg.verbose) {
322 printf(" nop:%*.1f +/- %-7.1f %11.1f ns +/- %-6.1f\n",
323 6 + state.max_function_name_length, checkasm_mode(nop_cycles),
324 checkasm_stddev(nop_cycles), checkasm_mode(nop_time),
325 checkasm_stddev(nop_time));
326 }
327 break;
328 }
329 }
330
331 static void print_bench_footer(struct IterState *const iter)
332 {
333 const double err_rel = relative_error(current.var_sum / current.num_benched);
334 const double err_max = relative_error(current.var_max);
335 CheckasmJson *const json = &iter->json;
336
337 switch (cfg.format) {
338 case CHECKASM_FORMAT_TSV:
339 case CHECKASM_FORMAT_CSV: break;
340 case CHECKASM_FORMAT_PRETTY:
341 if (cfg.verbose) {
342 printf(" - average timing error: %.3f%% across %d benchmarks "
343 "(maximum %.3f%%)\n",
344 100.0 * err_rel, current.num_benched, 100.0 * err_max);
345 }
346 break;
347 case CHECKASM_FORMAT_HTML:
348 case CHECKASM_FORMAT_JSON:
349 checkasm_json_pop(json, '}'); /* close functions */
350 checkasm_json(json, "averageError", "%g", err_rel);
351 checkasm_json(json, "maximumError", "%g", err_max);
352 checkasm_json_pop(json, '}'); /* close root */
353
354 if (cfg.format == CHECKASM_FORMAT_HTML) {
355 printf(" </script>\n"
356 " <meta name=\"viewport\" content=\"width=device-width, "
357 "initial-scale=1\">\n"
358 "</head>\n"
359 "%s"
360 "</html>\n",
361 checkasm_html_body);
362 }
363 break;
364 }
365 }
366
367 static void print_bench_iter(const CheckasmFunc *const f, struct IterState *const iter)
368 {
369 CheckasmJson *const json = &iter->json;
370 const char sep = separator(cfg.format);
371 if (!f)
372 return;
373
374 print_bench_iter(f->child[0], iter);
375
376 const CheckasmFuncVersion *ref = &f->versions;
377 const CheckasmFuncVersion *v = ref;
378 const CheckasmVar nop_cycles = checkasm_measurement_result(state.nop_cycles);
379 const CheckasmVar perf_scale = checkasm_measurement_result(state.perf_scale);
380
381 /* Defer pushing the function header until we know that we have at least one
382 * benchmark to report */
383 int json_func_pushed = 0;
384
385 do {
386 if (v->cycles.nb_measurements) {
387 const CheckasmVar raw = checkasm_measurement_result(v->cycles);
388 const CheckasmVar raw_ref = checkasm_measurement_result(ref->cycles);
389
390 const CheckasmVar cycles = checkasm_var_sub(raw, nop_cycles);
391 const CheckasmVar cycles_ref = checkasm_var_sub(raw_ref, nop_cycles);
392 const CheckasmVar ratio = checkasm_var_div(cycles_ref, cycles);
393 const CheckasmVar raw_time = checkasm_var_mul(raw, perf_scale);
394 const CheckasmVar time = checkasm_var_mul(cycles, perf_scale);
395
396 switch (cfg.format) {
397 case CHECKASM_FORMAT_HTML:
398 case CHECKASM_FORMAT_JSON:
399 if (!json_func_pushed) {
400 checkasm_json_push(json, f->name, '{');
401 checkasm_json_str(json, "testName", f->test_name);
402 if (f->report_name)
403 checkasm_json_str(json, "reportName", f->report_name);
404 checkasm_json_push(json, "versions", '{');
405 json_func_pushed = 1;
406 }
407
408 checkasm_json_push(json, ver_suffix(v), '{');
409 json_measurement(json, "rawCycles", checkasm_perf.unit, v->cycles);
410 json_var(json, "rawTime", "nsec", raw_time);
411 json_var(json, "adjustedCycles", checkasm_perf.unit, cycles);
412 json_var(json, "adjustedTime", "nsec", time);
413 if (v != ref && ref->cycles.nb_measurements)
414 json_var(json, "ratio", NULL, checkasm_var_div(cycles_ref, cycles));
415 checkasm_json_pop(json, '}'); /* close version */
416 break;
417 case CHECKASM_FORMAT_TSV:
418 case CHECKASM_FORMAT_CSV:
419 printf("%s%c%s%c%.4f%c%.5f%c%.4f\n", f->name, sep, ver_suffix(v),
420 sep, checkasm_mode(cycles), sep, checkasm_stddev(cycles), sep,
421 checkasm_mode(time));
422 break;
423 case CHECKASM_FORMAT_PRETTY:;
424 const int pad = 12 + state.max_function_name_length
425 - printf(" %s_%s:", f->name, ver_suffix(v));
426 printf("%*.1f", imax(pad, 0), checkasm_mode(cycles));
427 if (cfg.verbose) {
428 printf(" +/- %-7.1f %11.1f ns +/- %-6.1f", checkasm_stddev(cycles),
429 checkasm_mode(time), checkasm_stddev(time));
430 }
431 if (v != ref && ref->cycles.nb_measurements) {
432 const double ratio_lo = checkasm_sample(ratio, -1.0);
433 const double ratio_hi = checkasm_sample(ratio, 1.0);
434 const int color = ratio_lo >= 10.0 ? COLOR_GREEN
435 : ratio_hi >= 1.1 && ratio_lo >= 1.0 ? COLOR_DEFAULT
436 : ratio_hi >= 1.0 ? COLOR_YELLOW
437 : COLOR_RED;
438 printf(" (");
439 checkasm_fprintf(stdout, color, "%5.2fx", checkasm_mode(ratio));
440 printf(")");
441 }
442 printf("\n");
443 break;
444 }
445 }
446 } while ((v = v->next));
447
448 if (json_func_pushed) {
449 checkasm_json_pop(json, '}'); /* close versions */
450 checkasm_json_pop(json, '}'); /* close function */
451 }
452
453 print_bench_iter(f->child[1], iter);
454 }
455
456 static void print_benchmarks(void)
457 {
458 struct IterState iter = { .json.file = stdout };
459 print_bench_header(&iter);
460 print_bench_iter(current.tree.root, &iter);
461 print_bench_footer(&iter);
462 assert(iter.json.level == 0);
463 }
464
465 /* Decide whether or not the current function needs to be benchmarked */
466 37533 int checkasm_bench_func(void)
467 {
468
2/6
✓ Branch 0 taken 37533 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 37533 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
37533 return !current.num_failed && cfg.bench && !checkasm_interrupted;
469 }
470
471 int checkasm_bench_runs(void)
472 {
473 if (checkasm_interrupted)
474 return 0;
475
476 /* This limit should be impossible to hit in practice */
477 if (stats.nb_samples == CHECKASM_STATS_SAMPLES)
478 return 0;
479
480 /* Try and gather at least 30 samples for statistical validity, even if
481 * it means exceeding the time budget */
482 if (current.cycles < state.target_cycles || stats.nb_samples < 30)
483 return stats.next_count;
484 else
485 return 0;
486 }
487
488 /* Update benchmark results of the current function */
489 void checkasm_bench_update(const int iterations, const uint64_t cycles)
490 {
491 checkasm_stats_add(&stats, (CheckasmSample) { cycles, iterations });
492 checkasm_stats_count_grow(&stats, cycles, state.target_cycles);
493 current.cycles += cycles;
494
495 /* Emit this periodically while benchmarking, to avoid the SIMD
496 * units turning on and off during long bench runs of non-SIMD
497 * functions */
498 #if ARCH_X86
499 checkasm_simd_warmup();
500 #endif
501 }
502
503 void checkasm_bench_finish(void)
504 {
505 CheckasmFuncVersion *const v = current.func_ver;
506 if (v && current.cycles) {
507 const CheckasmVar cycles = checkasm_stats_estimate(&stats);
508
509 /* Accumulate multiple bench_new() calls */
510 checkasm_measurement_update(&v->cycles, stats);
511
512 /* Keep track of min/max/avg (log) variance */
513 current.var_sum += cycles.lvar;
514 current.var_max = fmax(current.var_max, cycles.lvar);
515 current.num_benched++;
516 }
517
518 checkasm_stats_reset(&stats);
519 current.cycles = 0;
520 }
521
522 /* Compares a string with a wildcard pattern. */
523 134652 static int wildstrcmp(const char *str, const char *pattern)
524 {
525 134652 const char *wild = strchr(pattern, '*');
526
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 134652 times.
134652 if (wild) {
527 const size_t len = wild - pattern;
528 if (strncmp(str, pattern, len))
529 return 1;
530 while (*++wild == '*')
531 ;
532 if (!*wild)
533 return 0;
534 str += len;
535 while (*str && wildstrcmp(str, wild))
536 str++;
537 return !*str;
538 }
539 134652 return strcmp(str, pattern);
540 }
541
542 static void handle_interrupt(void);
543 static void update_statusline(void);
544
545 134652 static int test_enabled(const CheckasmTest *test)
546 {
547
3/4
✓ Branch 0 taken 134652 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 1374 times.
✓ Branch 4 taken 133278 times.
134652 return !cfg.test_pattern || !wildstrcmp(test->name, cfg.test_pattern);
548 }
549
550 /* Perform tests and benchmarks for the specified
551 * cpu flag if supported by the host */
552 1764 static void check_cpu_flag(const CheckasmCpuInfo *cpu)
553 {
554 1764 const CheckasmCpu prev_cpu_flags = current.cpu_flags;
555
2/2
✓ Branch 0 taken 1666 times.
✓ Branch 1 taken 98 times.
1764 if (cpu) {
556 1666 current.cpu_flags &= ~cpu->mask;
557 1666 current.cpu_flags |= cpu->flag & cfg.cpu;
558 } else {
559 /* Also include any CPU flags not related to the CPU flags list */
560 98 current.cpu_flags = cfg.cpu;
561
2/2
✓ Branch 0 taken 1666 times.
✓ Branch 1 taken 98 times.
1764 for (const CheckasmCpuInfo *info = cfg.cpu_flags; info->flag; info++)
562 1666 current.cpu_flags &= ~info->flag;
563 }
564
565
4/4
✓ Branch 0 taken 1666 times.
✓ Branch 1 taken 98 times.
✓ Branch 2 taken 392 times.
✓ Branch 3 taken 1274 times.
1764 if (cpu && current.cpu_flags == prev_cpu_flags)
566 392 return;
567
568 1372 current.func = NULL;
569 1372 current.report_idx = 1;
570 1372 current.cpu = cpu;
571 1372 current.cpu_name_printed = 0;
572 1372 current.cpu_suffix_length = (int) strlen(cpu_suffix(cpu)) + 1;
573
1/2
✓ Branch 0 taken 1372 times.
✗ Branch 1 not taken.
1372 if (cfg.set_cpu_flags)
574 1372 cfg.set_cpu_flags(current.cpu_flags);
575
576
2/2
✓ Branch 0 taken 134456 times.
✓ Branch 1 taken 1372 times.
135828 for (const CheckasmTest *test = cfg.tests; test->func; test++) {
577
2/2
✓ Branch 1 taken 133084 times.
✓ Branch 2 taken 1372 times.
134456 if (!test_enabled(test))
578 133084 continue;
579 1372 current.test_name = test->name;
580 1372 update_statusline();
581
582
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1372 times.
1372 if (checkasm_save_context(checkasm_context)) {
583 const char *signal = checkasm_get_last_signal_desc();
584 handle_interrupt();
585 checkasm_fail_func("%s", signal);
586
587 /* We want to associate this (and any prior) failures with the
588 * correct report group, so remember the failure state until we
589 * reach the same position in the test() function again */
590 current.func_ver->state = CHECKASM_FUNC_CRASHED;
591 current.saved_checked = current.num_checked - current.prev_checked;
592 current.saved_failed = current.num_failed - current.prev_failed;
593 current.num_failed = current.prev_failed;
594 current.num_checked = current.prev_checked;
595 current.func = NULL;
596 }
597
598 1372 checkasm_srand(cfg.seed);
599 1372 current.should_fail = 0; // reset between tests
600 1372 test->func();
601 1372 checkasm_report(NULL); // catch any un-reported functions
602
603
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 1372 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1372 if (cfg.bench && !state.skip_tests) {
604 /* Measure NOP and perf scale after each test+CPU flag configuration */
605 handle_interrupt();
606 checkasm_measure_nop_cycles(&state.nop_cycles, state.target_cycles);
607 handle_interrupt();
608 checkasm_measure_perf_scale(&state.perf_scale);
609 }
610
611 1372 free(current.func_variant);
612 1372 current.func_variant = NULL;
613 }
614 }
615
616 /* Print the name of the current CPU flag, but only do it once */
617 524 static void print_cpu_name(void)
618 {
619
2/2
✓ Branch 0 taken 232 times.
✓ Branch 1 taken 292 times.
524 if (!current.cpu_name_printed) {
620
1/2
✓ Branch 0 taken 232 times.
✗ Branch 1 not taken.
232 LOG_COLOR(COLOR_YELLOW, "%s:\n", current.cpu ? current.cpu->name : "C");
621 232 current.cpu_name_printed = 1;
622 }
623 524 }
624
625 int checkasm_run_on_all_cores(void (*func)(void))
626 {
627 #if HAVE_PTHREAD_SETAFFINITY_NP && defined(CPU_SET)
628 cpu_set_t mask;
629 if (pthread_getaffinity_np(pthread_self(), sizeof(mask), &mask))
630 return 1;
631
632 int ret = 0;
633 for (int c = 0; c < CPU_SETSIZE; c++) {
634 if (CPU_ISSET(c, &mask)) {
635 cpu_set_t set;
636 CPU_ZERO(&set);
637 CPU_SET(c, &set);
638 if (pthread_setaffinity_np(pthread_self(), sizeof(set), &set)) {
639 ret = 1;
640 break;
641 }
642 func();
643 }
644 }
645 pthread_setaffinity_np(pthread_self(), sizeof(mask), &mask);
646 return ret;
647 #else
648 return 1;
649 #endif
650 }
651
652 static int set_cpu_affinity(const unsigned affinity)
653 {
654 int affinity_err;
655
656 #ifdef _WIN32
657 HANDLE process = GetCurrentProcess();
658 #if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
659 BOOL(WINAPI * spdcs)(HANDLE, const ULONG *, ULONG) = (void *) GetProcAddress(
660 GetModuleHandleW(L"kernel32.dll"), "SetProcessDefaultCpuSets");
661 if (spdcs)
662 affinity_err = !spdcs(process, (ULONG[]) { (ULONG) affinity + 256 }, 1);
663 else
664 #endif
665 {
666 if (affinity < sizeof(DWORD_PTR) * 8)
667 affinity_err = !SetProcessAffinityMask(process, (DWORD_PTR) 1 << affinity);
668 else
669 affinity_err = 1;
670 }
671 #elif HAVE_PTHREAD_SETAFFINITY_NP && defined(CPU_SET)
672 cpu_set_t set;
673 CPU_ZERO(&set);
674 CPU_SET(affinity, &set);
675 affinity_err = pthread_setaffinity_np(pthread_self(), sizeof(set), &set);
676 #else
677 (void) affinity;
678 (void) affinity_err;
679 LOG("checkasm: --affinity is not supported on your system\n");
680 return 1;
681 #endif
682
683 if (affinity_err) {
684 LOG("checkasm: invalid cpu affinity (%u)\n", affinity);
685 return 1;
686 } else {
687 LOG("checkasm: running on cpu %u\n", affinity);
688 return 0;
689 }
690 }
691
692 void checkasm_list_cpu_flags(const CheckasmConfig *cfg)
693 {
694 checkasm_setup_fprintf();
695
696 for (const CheckasmCpuInfo *info = cfg->cpu_flags; info->flag; info++) {
697 if ((cfg->cpu & info->flag) == info->flag)
698 checkasm_fprintf(stdout, COLOR_GREEN, "%s", info->suffix);
699 else
700 checkasm_fprintf(stdout, COLOR_RED, "~%s", info->suffix);
701 printf(info[1].flag ? ", " : "\n");
702 }
703 }
704
705 void checkasm_list_tests(const CheckasmConfig *config)
706 {
707 for (const CheckasmTest *test = config->tests; test->func; test++)
708 printf("%s\n", test->name);
709 }
710
711 static void print_functions(const CheckasmFunc *const f)
712 {
713 if (f) {
714 print_functions(f->child[0]);
715 const CheckasmFuncVersion *v = &f->versions;
716 printf("%s (%s", f->name, ver_suffix(v));
717 while ((v = v->next))
718 printf(", %s", ver_suffix(v));
719 printf(")\n");
720 print_functions(f->child[1]);
721 }
722 }
723
724 98 static void run_all_tests(void)
725 {
726
2/2
✓ Branch 0 taken 9604 times.
✓ Branch 1 taken 98 times.
9702 for (const CheckasmTest *test = cfg.tests; test->func; test++) {
727
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 9604 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
9604 if (test->init && test_enabled(test)) {
728 checkasm_srand(cfg.seed);
729 test->init();
730 }
731 }
732
733 98 check_cpu_flag(NULL);
734
2/2
✓ Branch 0 taken 1666 times.
✓ Branch 1 taken 98 times.
1764 for (const CheckasmCpuInfo *info = cfg.cpu_flags; info->flag; info++)
735 1666 check_cpu_flag(info);
736
737
2/2
✓ Branch 0 taken 9604 times.
✓ Branch 1 taken 98 times.
9702 for (const CheckasmTest *test = cfg.tests; test->func; test++) {
738
4/4
✓ Branch 0 taken 196 times.
✓ Branch 1 taken 9408 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 194 times.
9604 if (test->uninit && test_enabled(test))
739 2 test->uninit();
740 }
741 98 }
742
743 void checkasm_list_functions(const CheckasmConfig *config)
744 {
745 memset(&state, 0, sizeof(state));
746 memset(&current, 0, sizeof(current));
747 state.skip_tests = 1;
748 cfg = *config;
749
750 run_all_tests();
751
752 print_functions(current.tree.root);
753 checkasm_func_tree_uninit(&current.tree);
754 }
755
756 98 static void cpu_fprintf(void *priv, const char *fmt, ...)
757 {
758 98 FILE *f = priv;
759
760 va_list ap;
761 98 va_start(ap, fmt);
762 98 fprintf(f, " - CPU: ");
763 98 vfprintf(f, fmt, ap);
764 98 fprintf(f, "\n");
765 98 va_end(ap);
766 98 }
767
768 98 static COLD void print_info(void)
769 {
770 98 LOG_COLOR(COLOR_YELLOW, "checkasm:\n");
771 98 checkasm_cpu_info(cpu_fprintf, stderr, &cfg);
772
773
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 98 times.
98 if (cfg.bench) {
774 LOG(" - Timing source: %s\n", checkasm_perf.name);
775 if (cfg.verbose) {
776 const CheckasmVar perf_scale = checkasm_measurement_result(state.perf_scale);
777 const CheckasmVar nop_cycles = checkasm_measurement_result(state.nop_cycles);
778 const CheckasmVar mhz = checkasm_var_div(checkasm_var_const(1e3), perf_scale);
779 LOG(" - Timing resolution: %.4f +/- %.3f ns/%s (%.0f +/- %.1f "
780 "MHz) (provisional)\n",
781 checkasm_mode(perf_scale), checkasm_stddev(perf_scale),
782 checkasm_perf.unit, checkasm_mode(mhz), checkasm_stddev(mhz));
783
784 LOG(" - No-op overhead: %.2f +/- %.3f %ss per call (provisional)\n",
785 checkasm_mode(nop_cycles), checkasm_stddev(nop_cycles),
786 checkasm_perf.unit);
787 }
788 LOG(" - Bench duration: %d µs per function (%" PRIu64 " %ss)\n", cfg.bench_usec,
789 state.target_cycles, checkasm_perf.unit);
790 }
791 98 LOG(" - Random seed: %u\n", cfg.seed);
792 98 }
793
794 30253 static void update_statusline(void)
795 {
796
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 30253 times.
30253 if (state.skip_tests)
797 return;
798
799 char status[256];
800 30253 int len = 0;
801
802 30253 len += snprintf(status, sizeof(status), "checkasm: iter=%d/%d cpu=%s test=%s",
803 30253 state.test_iter + 1, cfg.repeat, cpu_suffix(current.cpu),
804 current.test_name);
805
806
4/4
✓ Branch 0 taken 28881 times.
✓ Branch 1 taken 1372 times.
✓ Branch 2 taken 15449 times.
✓ Branch 3 taken 13432 times.
30253 if (current.func && current.func->report_name) {
807 15449 snprintf(status + len, sizeof(status) - len, " func=%s",
808 15449 current.func->report_name);
809 }
810
811 30253 checkasm_statusline(status);
812 }
813
814 98 static int print_summary(int interrupted)
815 {
816 98 checkasm_statusline(NULL);
817
818 98 LOG("checkasm: ");
819
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 98 times.
98 if (interrupted)
820 LOG_COLOR(COLOR_BLUE, "(interrupted) ");
821
822 /* Exclude C/ref versions from count reported to user */
823 98 const int num_checked_asm = current.num_checked - current.num_funcs;
824
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 98 times.
98 if (current.num_failed) {
825 LOG_COLOR(COLOR_RED, "%d ", current.num_failed);
826 LOG("of %d tests failed", num_checked_asm);
827
2/2
✓ Branch 0 taken 95 times.
✓ Branch 1 taken 3 times.
98 } else if (num_checked_asm) {
828
1/2
✓ Branch 0 taken 95 times.
✗ Branch 1 not taken.
95 if (!interrupted)
829 95 LOG("all ");
830 95 LOG("%d tests passed", num_checked_asm);
831
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 } else if (interrupted) {
832 LOG("no tests performed");
833 } else {
834 /* User did not define any tests */
835 3 LOG_COLOR(COLOR_YELLOW, "no tests to perform");
836 }
837
838
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 98 times.
98 if (cfg.repeat > 1)
839 LOG(", iteration %d of %d, seed %u\n", state.test_iter + 1, cfg.repeat, cfg.seed);
840 else
841 98 LOG("\n");
842
843
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 98 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
98 if (current.num_benched && !current.num_failed)
844 print_benchmarks();
845
846 98 return current.num_failed > 0;
847 }
848
849 6364 static void handle_interrupt(void)
850 {
851
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6364 times.
6364 if (checkasm_interrupted) {
852 print_summary(1);
853 exit(128 + checkasm_interrupted);
854 }
855 6364 }
856
857 98 int checkasm_run(const CheckasmConfig *config)
858 {
859 #if !HAVE_HTML_DATA
860
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 98 times.
98 if (cfg.format == CHECKASM_FORMAT_HTML) {
861 LOG("checkasm: built without HTML support\n");
862 return 1;
863 }
864 #endif
865
866 98 memset(&state, 0, sizeof(state));
867 98 memset(&current, 0, sizeof(current));
868 98 cfg = *config;
869
870 98 checkasm_set_signal_handlers();
871 #if HAVE_PRCTL && defined(PR_SET_UNALIGN)
872 98 prctl(PR_SET_UNALIGN, PR_UNALIGN_SIGBUS);
873 #endif
874
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 98 times.
98 if (cfg.cpu_affinity_set)
875 set_cpu_affinity(cfg.cpu_affinity);
876 98 checkasm_setup_fprintf();
877
878
2/4
✓ Branch 0 taken 98 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 98 times.
✗ Branch 3 not taken.
98 if (!cfg.seed && !cfg.seed_set)
879 98 cfg.seed = checkasm_seed();
880
1/2
✓ Branch 0 taken 98 times.
✗ Branch 1 not taken.
98 if (!cfg.repeat)
881 98 cfg.repeat = 1;
882
1/2
✓ Branch 0 taken 98 times.
✗ Branch 1 not taken.
98 if (!cfg.bench_usec)
883 98 cfg.bench_usec = 1000;
884
885
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 98 times.
98 if (cfg.bench) {
886 if (checkasm_perf_init())
887 return 1;
888
889 checkasm_perf_warmup();
890
891 checkasm_stats_reset(&stats);
892 checkasm_measurement_init(&state.nop_cycles);
893 checkasm_measurement_init(&state.perf_scale);
894 checkasm_measure_perf_scale(&state.perf_scale);
895
896 /* Use the low estimate to compute the number of target cycles, to
897 * ensure we reach the required number of cycles with confidence */
898 const CheckasmVar perf_scale = checkasm_measurement_result(state.perf_scale);
899 const double low_estimate = checkasm_sample(perf_scale, -1.0);
900 if (low_estimate <= 0.0) {
901 fprintf(stderr,
902 "checkasm: cycle counter seems to be non-functional "
903 "(invalid timer scale: %.4f %ss/nsec)\n",
904 checkasm_mode(perf_scale), checkasm_perf.unit);
905 return 1;
906 }
907
908 state.target_cycles = (uint64_t) (1e3 * cfg.bench_usec / low_estimate);
909 checkasm_measure_nop_cycles(&state.nop_cycles, state.target_cycles);
910 }
911
912 98 checkasm_init_cpu();
913
914 98 print_info();
915
916
2/2
✓ Branch 0 taken 98 times.
✓ Branch 1 taken 98 times.
196 for (state.test_iter = 0; state.test_iter < cfg.repeat; state.test_iter++) {
917 98 run_all_tests();
918
919 98 int res = print_summary(0);
920 98 checkasm_func_tree_uninit(&current.tree);
921
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 98 times.
98 if (res)
922 return res;
923
924 98 memset(&current, 0, sizeof(current));
925 98 cfg.seed++;
926 }
927
928 98 return 0;
929 }
930
931 /* Decide whether or not the specified function needs to be tested and
932 * allocate/initialize data structures if needed. Returns a pointer to a
933 * reference function if the function should be tested, otherwise NULL */
934 197871 CheckasmKey checkasm_check_key(const CheckasmKey version, const char *const name, ...)
935 {
936 char name_buf[256];
937 va_list arg;
938
939
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 197871 times.
197871 if (checkasm_interrupted)
940 goto skip;
941
942 197871 va_start(arg, name);
943 197871 int name_length = vsnprintf(name_buf, sizeof(name_buf), name, arg);
944 197871 va_end(arg);
945
946
4/6
✓ Branch 0 taken 195827 times.
✓ Branch 1 taken 2044 times.
✓ Branch 2 taken 195827 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 195827 times.
✗ Branch 5 not taken.
197871 if (!version || name_length <= 0 || (size_t) name_length >= sizeof(name_buf)
947
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 195827 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
195827 || (cfg.function_pattern && wildstrcmp(name_buf, cfg.function_pattern)))
948 2044 goto skip;
949
950 195827 CheckasmFunc *const f = checkasm_func_get(&current.tree, name_buf);
951 195827 CheckasmFuncVersion *v = &f->versions;
952 195827 CheckasmKey ref = version;
953
954
2/2
✓ Branch 0 taken 182395 times.
✓ Branch 1 taken 13432 times.
195827 if (v->key) {
955 CheckasmFuncVersion *prev;
956 do {
957
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 243534 times.
243534 if (v->state == CHECKASM_FUNC_CRASHED) {
958 /* This function threw a signal last time; so restore the
959 * retained test state for the next report() call */
960 v->state = CHECKASM_FUNC_FAILED;
961 current.num_checked += current.saved_checked;
962 current.num_failed += current.saved_failed;
963 current.saved_checked = 0;
964 current.saved_failed = 0;
965 current.func = f;
966 current.func_ver = v;
967 for (CheckasmFunc *fp = f; fp; fp = fp->prev)
968 fp->report_idx = current.report_idx;
969 }
970
971 /* Skip functions without a working reference */
972
3/4
✓ Branch 0 taken 182395 times.
✓ Branch 1 taken 61139 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 182395 times.
243534 if (!v->cpu && v->state != CHECKASM_FUNC_OK)
973 goto skip;
974
975 /* Only test functions that haven't already been tested */
976
2/2
✓ Branch 0 taken 166946 times.
✓ Branch 1 taken 76588 times.
243534 if (v->key == version)
977 166946 goto skip;
978
979 /* Exclude failed or variant functions from being used as ref */
980
3/4
✓ Branch 0 taken 76588 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 76490 times.
✓ Branch 3 taken 98 times.
76588 if (v->state == CHECKASM_FUNC_OK && !v->suffix)
981 76490 ref = v->key;
982
983 76588 prev = v;
984
2/2
✓ Branch 0 taken 61139 times.
✓ Branch 1 taken 15449 times.
76588 } while ((v = v->next));
985
986 15449 v = prev->next = checkasm_mallocz(sizeof(CheckasmFuncVersion));
987 }
988
989
2/2
✓ Branch 0 taken 715 times.
✓ Branch 1 taken 28166 times.
28881 if (current.func_variant) {
990 715 v->suffix = current.func_variant;
991 715 current.func_variant = NULL;
992 715 name_length += (int) strlen(v->suffix) + 1;
993 } else {
994 28166 name_length += current.cpu_suffix_length;
995 }
996
997
2/2
✓ Branch 0 taken 488 times.
✓ Branch 1 taken 28393 times.
28881 if (name_length > state.max_function_name_length)
998 488 state.max_function_name_length = name_length;
999
1000 28881 v->key = version;
1001 28881 v->state = CHECKASM_FUNC_OK;
1002 28881 v->cpu = current.cpu;
1003
2/2
✓ Branch 0 taken 13432 times.
✓ Branch 1 taken 15449 times.
28881 if (ref == version)
1004 13432 current.num_funcs++;
1005
1006
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 28881 times.
28881 if (state.skip_tests)
1007 goto skip;
1008
1009 /* Associate this function with each other function that was last used
1010 * as part of the same report group */
1011
2/2
✓ Branch 0 taken 13447 times.
✓ Branch 1 taken 15434 times.
28881 if (f->report_idx < current.report_idx) {
1012 13447 f->report_idx = current.report_idx;
1013 13447 f->prev = current.func;
1014 13447 f->test_name = current.test_name;
1015 }
1016
1017 28881 current.func = f;
1018 28881 current.func_ver = v;
1019 28881 current.num_checked++;
1020 28881 checkasm_srand(cfg.seed);
1021 28881 update_statusline();
1022
1023
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 28881 times.
28881 if (cfg.bench) {
1024 #if ARCH_X86
1025 checkasm_simd_warmup();
1026 #endif
1027 checkasm_measurement_init(&v->cycles);
1028 }
1029
1030 28881 return ref;
1031
1032 168990 skip:
1033 168990 free(current.func_variant);
1034 168990 current.func_variant = NULL;
1035 168990 return 0;
1036 }
1037
1038 1205 void checkasm_set_func_variant(const char *id_fmt, ...)
1039 {
1040 va_list arg;
1041 1205 va_start(arg, id_fmt);
1042
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1205 times.
1205 assert(!current.func_variant);
1043 1205 current.func_variant = checkasm_vasprintf(id_fmt, arg);
1044 1205 va_end(arg);
1045 1205 }
1046
1047 /* Indicate that the current test has failed, return whether verbose printing
1048 * is requested. */
1049 static int fail_internal(const char *const msg, va_list arg)
1050 {
1051 CheckasmFuncVersion *const v = current.func_ver;
1052 if (v && v->state == CHECKASM_FUNC_OK) {
1053 if (!current.should_fail) {
1054 print_cpu_name();
1055 LOG_COLOR(COLOR_RED, "FAILURE:");
1056 LOG(" %s_%s (", current.func->name, ver_suffix(v));
1057 vfprintf(stderr, msg, arg);
1058 fputs(")\n", stderr);
1059 }
1060
1061 v->state = CHECKASM_FUNC_FAILED;
1062 current.num_failed++;
1063 }
1064 return cfg.verbose && !current.should_fail;
1065 }
1066
1067 int checkasm_fail_func(const char *const msg, ...)
1068 {
1069 va_list arg;
1070 va_start(arg, msg);
1071 const int ret = fail_internal(msg, arg);
1072 va_end(arg);
1073 return ret;
1074 }
1075
1076 void checkasm_fail_abort(const char *const msg, ...)
1077 {
1078 va_list arg;
1079 va_start(arg, msg);
1080 fail_internal(msg, arg);
1081 va_end(arg);
1082
1083 checkasm_load_context(checkasm_context);
1084 abort(); // in case we don't have a longjmp implementation
1085 }
1086
1087 int checkasm_should_fail(CheckasmCpu cpu_flags)
1088 {
1089 current.should_fail = !!(current.cpu_flags & cpu_flags);
1090
1091 #if CHECKASM_HAVE_LONGJMP
1092 return 1; /* we can catch any crashes */
1093 #else
1094 /* If our signal handler isn't working, we shouldn't run tests that
1095 * are expected to fail, as they may rely on the signal handler. */
1096 return !current.should_fail;
1097 #endif
1098 }
1099
1100 /* Print the outcome of all tests performed since
1101 * the last time this function was called */
1102 6364 void checkasm_report(const char *const name, ...)
1103 {
1104 char report_name[256];
1105
1106 /* Calculate the amount of padding required to make the output vertically aligned */
1107 6364 int length = (int) strlen(current.test_name);
1108
2/2
✓ Branch 0 taken 4992 times.
✓ Branch 1 taken 1372 times.
6364 if (name) {
1109 va_list arg;
1110 4992 va_start(arg, name);
1111 4992 length += 1 + vsnprintf(report_name, sizeof(report_name), name, arg);
1112 4992 va_end(arg);
1113 }
1114
1115
2/2
✓ Branch 0 taken 166 times.
✓ Branch 1 taken 6198 times.
6364 if (length > state.max_report_name_length)
1116 166 state.max_report_name_length = length;
1117
1118 6364 const int new_checked = current.num_checked - current.prev_checked;
1119
2/2
✓ Branch 0 taken 862 times.
✓ Branch 1 taken 5502 times.
6364 if (new_checked) {
1120 862 int pad_length = (int) state.max_report_name_length + 3; // strlen(" - ")
1121
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 862 times.
862 assert(!state.skip_tests);
1122
1123 862 int fails = current.num_failed - current.prev_failed;
1124
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 862 times.
862 if (current.should_fail)
1125 current.num_failed = current.prev_failed + (new_checked - fails);
1126
1127 /* Omit 'OK' after the first run, unless failed or verbose */
1128
2/4
✓ Branch 0 taken 862 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 862 times.
862 int want_print = current.num_failed != current.prev_failed || cfg.verbose;
1129
1/2
✓ Branch 0 taken 862 times.
✗ Branch 1 not taken.
862 if (state.test_iter == 0)
1130
3/4
✓ Branch 0 taken 862 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 524 times.
✓ Branch 3 taken 338 times.
862 want_print |= current.should_fail || current.cpu;
1131
1132
2/2
✓ Branch 0 taken 524 times.
✓ Branch 1 taken 338 times.
862 if (want_print) {
1133 524 print_cpu_name();
1134
1/2
✓ Branch 0 taken 524 times.
✗ Branch 1 not taken.
524 if (name) {
1135 524 pad_length -= LOG(" - %s.%s", current.test_name, report_name);
1136 } else {
1137 pad_length -= LOG(" - %s", current.test_name);
1138 }
1139 524 LOG("%*c", imax(pad_length, 0) + 2, '[');
1140
1141
1/2
✓ Branch 0 taken 524 times.
✗ Branch 1 not taken.
524 if (current.num_failed == current.prev_failed)
1142
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 524 times.
524 LOG_COLOR(COLOR_GREEN, current.should_fail ? "EXPECTED" : "OK");
1143 else if (!current.should_fail)
1144 LOG_COLOR(COLOR_RED, "FAILED");
1145 else
1146 LOG_COLOR(COLOR_RED, "%d/%d EXPECTED", fails, new_checked);
1147 524 LOG("]\n");
1148 }
1149
1150 862 current.prev_checked = current.num_checked;
1151 862 current.prev_failed = current.num_failed;
1152 }
1153
1154 /* Store the report name with each function in this report group */
1155 6364 CheckasmFunc *func = current.func;
1156
2/2
✓ Branch 0 taken 34557 times.
✓ Branch 1 taken 6364 times.
40921 while (func) {
1157
3/4
✓ Branch 0 taken 34557 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13432 times.
✓ Branch 3 taken 21125 times.
34557 if (name && !func->report_name)
1158 13432 func->report_name = checkasm_strdup(report_name);
1159 34557 func = func->prev;
1160 }
1161
1162 6364 current.func = NULL; /* reset current function for new report */
1163 6364 current.report_idx++;
1164 6364 handle_interrupt();
1165 6364 }
1166
1167 static void print_usage(const char *const progname)
1168 {
1169 fprintf(stderr,
1170 "Usage: %s [options...] <random seed>\n"
1171 " <random seed> Use fixed value to seed the PRNG\n"
1172 "Options:\n"
1173 " --affinity=<cpu> Run the process on CPU <cpu>\n"
1174 " --bench -b Benchmark the tested functions\n"
1175 " --csv, --tsv, --json, Choose output format for benchmarks\n"
1176 " --html\n"
1177 " --function=<pattern> -f Test only the functions matching "
1178 "<pattern>\n"
1179 " --help -h Print this usage info\n"
1180 " --list-cpu-flags List available cpu flags\n"
1181 " --list-functions List available functions\n"
1182 " --list-tests List available tests\n"
1183 " --duration=<μs> Benchmark duration (per function) in "
1184 "μs\n"
1185 " --repeat[=<N>] Repeat tests N times, on successive seeds\n"
1186 " --test=<pattern> -t Test only <pattern>\n"
1187 " --verbose -v Print verbose timing info and failure "
1188 "data\n",
1189 progname);
1190 }
1191
1192 static int parseu(unsigned *const dst, const char *const str, const int base)
1193 {
1194 unsigned long val;
1195 char *end;
1196 errno = 0;
1197 val = strtoul(str, &end, base);
1198 if (errno || end == str || *end)
1199 return 0;
1200 #if !defined(__SIZEOF_LONG__) || !defined(__SIZEOF_INT__) || __SIZEOF_LONG__ > __SIZEOF_INT__
1201 /* This condition is split out; it can cause -Wtype-limits warnings on
1202 * 32 bit platforms and on Windows:
1203 * warning: result of comparison 'unsigned long' > 4294967295 is always false [-Wtautological-type-limit-compare] */
1204 if (val > (unsigned) -1)
1205 return 0;
1206 #endif
1207 *dst = (unsigned) val;
1208 return 1;
1209 }
1210
1211 98 int checkasm_main(CheckasmConfig *config, int argc, const char *argv[])
1212 {
1213
2/2
✓ Branch 0 taken 98 times.
✓ Branch 1 taken 98 times.
196 while (argc > 1) {
1214
2/4
✓ Branch 0 taken 98 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 98 times.
98 if (!strncmp(argv[1], "--help", 6) || !strcmp(argv[1], "-h")) {
1215 print_usage(argv[0]);
1216 return 0;
1217
1/2
✓ Branch 0 taken 98 times.
✗ Branch 1 not taken.
98 } else if (!strcmp(argv[1], "--list-cpu-flags")
1218
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 98 times.
98 || !strcmp(argv[1], "--list-cpuflags")) {
1219 checkasm_list_cpu_flags(config);
1220 return 0;
1221
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 98 times.
98 } else if (!strcmp(argv[1], "--list-tests")) {
1222 checkasm_list_tests(config);
1223 return 0;
1224
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 98 times.
98 } else if (!strcmp(argv[1], "--list-functions")) {
1225 checkasm_list_functions(config);
1226 return 0;
1227
2/4
✓ Branch 0 taken 98 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 98 times.
98 } else if (!strcmp(argv[1], "--bench") || !strcmp(argv[1], "-b")) {
1228 config->bench = 1;
1229
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 98 times.
98 } else if (!strncmp(argv[1], "--bench=", 8)) {
1230 config->bench = 1;
1231 config->function_pattern = argv[1] + 8;
1232
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 98 times.
98 } else if (!strcmp(argv[1], "--csv")) {
1233 config->format = CHECKASM_FORMAT_CSV;
1234
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 98 times.
98 } else if (!strcmp(argv[1], "--tsv")) {
1235 config->format = CHECKASM_FORMAT_TSV;
1236
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 98 times.
98 } else if (!strcmp(argv[1], "--json")) {
1237 config->format = CHECKASM_FORMAT_JSON;
1238
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 98 times.
98 } else if (!strcmp(argv[1], "--html")) {
1239 #if HAVE_HTML_DATA
1240 config->format = CHECKASM_FORMAT_HTML;
1241 #else
1242 LOG("checkasm: built without HTML support\n");
1243 return 1;
1244 #endif
1245
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 98 times.
98 } else if (!strncmp(argv[1], "--duration=", 11)) {
1246 const char *const s = argv[1] + 11;
1247 if (!parseu(&config->bench_usec, s, 10)) {
1248 LOG("checkasm: invalid duration (%s)\n", s);
1249 print_usage(argv[0]);
1250 return 1;
1251 }
1252
1/2
✓ Branch 0 taken 98 times.
✗ Branch 1 not taken.
98 } else if (!strncmp(argv[1], "--test=", 7)) {
1253 98 config->test_pattern = argv[1] + 7;
1254 } else if (!strcmp(argv[1], "-t")) {
1255 config->test_pattern = argc > 1 ? argv[2] : "";
1256 argc--;
1257 argv++;
1258 } else if (!strncmp(argv[1], "--function=", 11)) {
1259 config->function_pattern = argv[1] + 11;
1260 } else if (!strcmp(argv[1], "-f")) {
1261 config->function_pattern = argc > 1 ? argv[2] : "";
1262 argc--;
1263 argv++;
1264 } else if (!strcmp(argv[1], "--verbose") || !strcmp(argv[1], "-v")) {
1265 config->verbose = 1;
1266 } else if (!strncmp(argv[1], "--affinity=", 11)) {
1267 const char *const s = argv[1] + 11;
1268 config->cpu_affinity_set = 1;
1269 if (!parseu(&config->cpu_affinity, s, 16)) {
1270 LOG("checkasm: invalid cpu affinity (%s)\n", s);
1271 print_usage(argv[0]);
1272 return 1;
1273 }
1274 } else if (!strncmp(argv[1], "--repeat=", 9)) {
1275 const char *const s = argv[1] + 9;
1276 if (!parseu(&config->repeat, s, 10)) {
1277 LOG("checkasm: invalid number of repetitions (%s)\n", s);
1278 print_usage(argv[0]);
1279 return 1;
1280 }
1281 } else if (!strcmp(argv[1], "--repeat")) {
1282 config->repeat = UINT_MAX;
1283 } else {
1284 config->seed_set = 1;
1285 if (!parseu(&config->seed, argv[1], 10)) {
1286 LOG("checkasm: unknown option (%s)\n", argv[1]);
1287 print_usage(argv[0]);
1288 return 1;
1289 }
1290 }
1291
1292 98 argc--;
1293 98 argv++;
1294 }
1295
1296 98 return checkasm_run(config);
1297 }
1298