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