]> git.sesse.net Git - ffmpeg/blob - tests/checkasm/checkasm.c
checkasm: Add unit tests for bswapdsp
[ffmpeg] / tests / checkasm / checkasm.c
1 /*
2  * Assembly testing and benchmarking tool
3  * Copyright (c) 2015 Henrik Gramner
4  * Copyright (c) 2008 Loren Merritt
5  *
6  * This file is part of Libav.
7  *
8  * Libav is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * Libav is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License along
19  * with Libav; if not, write to the Free Software Foundation, Inc.,
20  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21  */
22
23 #include <stdarg.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include "checkasm.h"
28 #include "libavutil/common.h"
29 #include "libavutil/cpu.h"
30 #include "libavutil/random_seed.h"
31
32 #if ARCH_X86
33 #include "libavutil/x86/cpu.h"
34 #endif
35
36 #if HAVE_SETCONSOLETEXTATTRIBUTE
37 #include <windows.h>
38 #define COLOR_RED    FOREGROUND_RED
39 #define COLOR_GREEN  FOREGROUND_GREEN
40 #define COLOR_YELLOW (FOREGROUND_RED|FOREGROUND_GREEN)
41 #else
42 #define COLOR_RED    1
43 #define COLOR_GREEN  2
44 #define COLOR_YELLOW 3
45 #endif
46
47 #if HAVE_UNISTD_H
48 #include <unistd.h>
49 #endif
50
51 #if !HAVE_ISATTY
52 #define isatty(fd) 1
53 #endif
54
55 /* List of tests to invoke */
56 static void (* const tests[])(void) = {
57 #if CONFIG_BSWAPDSP
58     checkasm_check_bswapdsp,
59 #endif
60 #if CONFIG_H264PRED
61     checkasm_check_h264pred,
62 #endif
63 #if CONFIG_H264QPEL
64     checkasm_check_h264qpel,
65 #endif
66     NULL
67 };
68
69 /* List of cpu flags to check */
70 static const struct {
71     const char *name;
72     const char *suffix;
73     int flag;
74 } cpus[] = {
75 #if ARCH_X86
76     { "MMX",      "mmx",      AV_CPU_FLAG_MMX|AV_CPU_FLAG_CMOV },
77     { "MMXEXT",   "mmxext",   AV_CPU_FLAG_MMXEXT },
78     { "3DNOW",    "3dnow",    AV_CPU_FLAG_3DNOW },
79     { "3DNOWEXT", "3dnowext", AV_CPU_FLAG_3DNOWEXT },
80     { "SSE",      "sse",      AV_CPU_FLAG_SSE },
81     { "SSE2",     "sse2",     AV_CPU_FLAG_SSE2|AV_CPU_FLAG_SSE2SLOW },
82     { "SSE3",     "sse3",     AV_CPU_FLAG_SSE3|AV_CPU_FLAG_SSE3SLOW },
83     { "SSSE3",    "ssse3",    AV_CPU_FLAG_SSSE3|AV_CPU_FLAG_ATOM },
84     { "SSE4.1",   "sse4",     AV_CPU_FLAG_SSE4 },
85     { "SSE4.2",   "sse42",    AV_CPU_FLAG_SSE42 },
86     { "AVX",      "avx",      AV_CPU_FLAG_AVX },
87     { "XOP",      "xop",      AV_CPU_FLAG_XOP },
88     { "FMA3",     "fma3",     AV_CPU_FLAG_FMA3 },
89     { "FMA4",     "fma4",     AV_CPU_FLAG_FMA4 },
90     { "AVX2",     "avx2",     AV_CPU_FLAG_AVX2 },
91 #endif
92     { NULL }
93 };
94
95 typedef struct CheckasmFuncVersion {
96     struct CheckasmFuncVersion *next;
97     intptr_t (*func)();
98     int ok;
99     int cpu;
100     int iterations;
101     uint64_t cycles;
102 } CheckasmFuncVersion;
103
104 /* Binary search tree node */
105 typedef struct CheckasmFunc {
106     struct CheckasmFunc *child[2];
107     CheckasmFuncVersion versions;
108     char name[1];
109 } CheckasmFunc;
110
111 /* Internal state */
112 static struct {
113     CheckasmFunc *funcs;
114     CheckasmFunc *current_func;
115     CheckasmFuncVersion *current_func_ver;
116     const char *bench_pattern;
117     int bench_pattern_len;
118     int num_checked;
119     int num_failed;
120     int nop_time;
121     int cpu_flag;
122     const char *cpu_flag_name;
123 } state;
124
125 /* PRNG state */
126 AVLFG checkasm_lfg;
127
128 /* Print colored text to stderr if the terminal supports it */
129 static void color_printf(int color, const char *fmt, ...)
130 {
131     static int use_color = -1;
132     va_list arg;
133
134 #if HAVE_SETCONSOLETEXTATTRIBUTE
135     static HANDLE con;
136     static WORD org_attributes;
137
138     if (use_color < 0) {
139         CONSOLE_SCREEN_BUFFER_INFO con_info;
140         con = GetStdHandle(STD_ERROR_HANDLE);
141         if (con && con != INVALID_HANDLE_VALUE && GetConsoleScreenBufferInfo(con, &con_info)) {
142             org_attributes = con_info.wAttributes;
143             use_color = 1;
144         } else
145             use_color = 0;
146     }
147     if (use_color)
148         SetConsoleTextAttribute(con, (org_attributes & 0xfff0) | (color & 0x0f));
149 #else
150     if (use_color < 0) {
151         const char *term = getenv("TERM");
152         use_color = term && strcmp(term, "dumb") && isatty(2);
153     }
154     if (use_color)
155         fprintf(stderr, "\x1b[%d;3%dm", (color & 0x08) >> 3, color & 0x07);
156 #endif
157
158     va_start(arg, fmt);
159     vfprintf(stderr, fmt, arg);
160     va_end(arg);
161
162     if (use_color) {
163 #if HAVE_SETCONSOLETEXTATTRIBUTE
164         SetConsoleTextAttribute(con, org_attributes);
165 #else
166         fprintf(stderr, "\x1b[0m");
167 #endif
168     }
169 }
170
171 /* Deallocate a tree */
172 static void destroy_func_tree(CheckasmFunc *f)
173 {
174     if (f) {
175         CheckasmFuncVersion *v = f->versions.next;
176         while (v) {
177             CheckasmFuncVersion *next = v->next;
178             free(v);
179             v = next;
180         }
181
182         destroy_func_tree(f->child[0]);
183         destroy_func_tree(f->child[1]);
184         free(f);
185     }
186 }
187
188 /* Allocate a zero-initialized block, clean up and exit on failure */
189 static void *checkasm_malloc(size_t size)
190 {
191     void *ptr = calloc(1, size);
192     if (!ptr) {
193         fprintf(stderr, "checkasm: malloc failed\n");
194         destroy_func_tree(state.funcs);
195         exit(1);
196     }
197     return ptr;
198 }
199
200 /* Get the suffix of the specified cpu flag */
201 static const char *cpu_suffix(int cpu)
202 {
203     int i = FF_ARRAY_ELEMS(cpus);
204
205     while (--i >= 0)
206         if (cpu & cpus[i].flag)
207             return cpus[i].suffix;
208
209     return "c";
210 }
211
212 #ifdef AV_READ_TIME
213 static int cmp_nop(const void *a, const void *b)
214 {
215     return *(const uint16_t*)a - *(const uint16_t*)b;
216 }
217
218 /* Measure the overhead of the timing code (in decicycles) */
219 static int measure_nop_time(void)
220 {
221     uint16_t nops[10000];
222     int i, nop_sum = 0;
223
224     for (i = 0; i < 10000; i++) {
225         uint64_t t = AV_READ_TIME();
226         nops[i] = AV_READ_TIME() - t;
227     }
228
229     qsort(nops, 10000, sizeof(uint16_t), cmp_nop);
230     for (i = 2500; i < 7500; i++)
231         nop_sum += nops[i];
232
233     return nop_sum / 500;
234 }
235
236 /* Print benchmark results */
237 static void print_benchs(CheckasmFunc *f)
238 {
239     if (f) {
240         print_benchs(f->child[0]);
241
242         /* Only print functions with at least one assembly version */
243         if (f->versions.cpu || f->versions.next) {
244             CheckasmFuncVersion *v = &f->versions;
245             do {
246                 if (v->iterations) {
247                     int decicycles = (10*v->cycles/v->iterations - state.nop_time) / 4;
248                     printf("%s_%s: %d.%d\n", f->name, cpu_suffix(v->cpu), decicycles/10, decicycles%10);
249                 }
250             } while ((v = v->next));
251         }
252
253         print_benchs(f->child[1]);
254     }
255 }
256 #endif
257
258 /* ASCIIbetical sort except preserving natural order for numbers */
259 static int cmp_func_names(const char *a, const char *b)
260 {
261     int ascii_diff, digit_diff;
262
263     for (; !(ascii_diff = *a - *b) && *a; a++, b++);
264     for (; av_isdigit(*a) && av_isdigit(*b); a++, b++);
265
266     return (digit_diff = av_isdigit(*a) - av_isdigit(*b)) ? digit_diff : ascii_diff;
267 }
268
269 /* Get a node with the specified name, creating it if it doesn't exist */
270 static CheckasmFunc *get_func(const char *name, int length)
271 {
272     CheckasmFunc *f, **f_ptr = &state.funcs;
273
274     /* Search the tree for a matching node */
275     while ((f = *f_ptr)) {
276         int cmp = cmp_func_names(name, f->name);
277         if (!cmp)
278             return f;
279
280         f_ptr = &f->child[(cmp > 0)];
281     }
282
283     /* Allocate and insert a new node into the tree */
284     f = *f_ptr = checkasm_malloc(sizeof(CheckasmFunc) + length);
285     memcpy(f->name, name, length+1);
286
287     return f;
288 }
289
290 /* Perform tests and benchmarks for the specified cpu flag if supported by the host */
291 static void check_cpu_flag(const char *name, int flag)
292 {
293     int old_cpu_flag = state.cpu_flag;
294
295     flag |= old_cpu_flag;
296     av_set_cpu_flags_mask(flag);
297     state.cpu_flag = av_get_cpu_flags();
298
299     if (!flag || state.cpu_flag != old_cpu_flag) {
300         int i;
301
302         state.cpu_flag_name = name;
303         for (i = 0; tests[i]; i++)
304             tests[i]();
305     }
306 }
307
308 /* Print the name of the current CPU flag, but only do it once */
309 static void print_cpu_name(void)
310 {
311     if (state.cpu_flag_name) {
312         color_printf(COLOR_YELLOW, "%s:\n", state.cpu_flag_name);
313         state.cpu_flag_name = NULL;
314     }
315 }
316
317 int main(int argc, char *argv[])
318 {
319     int i, seed, ret = 0;
320
321     if (!tests[0] || !cpus[0].flag) {
322         fprintf(stderr, "checkasm: no tests to perform\n");
323         return 1;
324     }
325
326     if (argc > 1 && !strncmp(argv[1], "--bench", 7)) {
327 #ifndef AV_READ_TIME
328         fprintf(stderr, "checkasm: --bench is not supported on your system\n");
329         return 1;
330 #endif
331         if (argv[1][7] == '=') {
332             state.bench_pattern = argv[1] + 8;
333             state.bench_pattern_len = strlen(state.bench_pattern);
334         } else
335             state.bench_pattern = "";
336
337         argc--;
338         argv++;
339     }
340
341     seed = (argc > 1) ? atoi(argv[1]) : av_get_random_seed();
342     fprintf(stderr, "checkasm: using random seed %u\n", seed);
343     av_lfg_init(&checkasm_lfg, seed);
344
345     check_cpu_flag(NULL, 0);
346     for (i = 0; cpus[i].flag; i++)
347         check_cpu_flag(cpus[i].name, cpus[i].flag);
348
349     if (state.num_failed) {
350         fprintf(stderr, "checkasm: %d of %d tests have failed\n", state.num_failed, state.num_checked);
351         ret = 1;
352     } else {
353         fprintf(stderr, "checkasm: all %d tests passed\n", state.num_checked);
354 #ifdef AV_READ_TIME
355         if (state.bench_pattern) {
356             state.nop_time = measure_nop_time();
357             printf("nop: %d.%d\n", state.nop_time/10, state.nop_time%10);
358             print_benchs(state.funcs);
359         }
360 #endif
361     }
362
363     destroy_func_tree(state.funcs);
364     return ret;
365 }
366
367 /* Decide whether or not the specified function needs to be tested and
368  * allocate/initialize data structures if needed. Returns a pointer to a
369  * reference function if the function should be tested, otherwise NULL */
370 intptr_t (*checkasm_check_func(intptr_t (*func)(), const char *name, ...))()
371 {
372     char name_buf[256];
373     intptr_t (*ref)() = func;
374     CheckasmFuncVersion *v;
375     int name_length;
376     va_list arg;
377
378     va_start(arg, name);
379     name_length = vsnprintf(name_buf, sizeof(name_buf), name, arg);
380     va_end(arg);
381
382     if (!func || name_length <= 0 || name_length >= sizeof(name_buf))
383         return NULL;
384
385     state.current_func = get_func(name_buf, name_length);
386     v = &state.current_func->versions;
387
388     if (v->func) {
389         CheckasmFuncVersion *prev;
390         do {
391             /* Only test functions that haven't already been tested */
392             if (v->func == func)
393                 return NULL;
394
395             if (v->ok)
396                 ref = v->func;
397
398             prev = v;
399         } while ((v = v->next));
400
401         v = prev->next = checkasm_malloc(sizeof(CheckasmFuncVersion));
402     }
403
404     v->func = func;
405     v->ok = 1;
406     v->cpu = state.cpu_flag;
407     state.current_func_ver = v;
408
409     if (state.cpu_flag)
410         state.num_checked++;
411
412     return ref;
413 }
414
415 /* Decide whether or not the current function needs to be benchmarked */
416 int checkasm_bench_func(void)
417 {
418     return !state.num_failed && state.bench_pattern &&
419            !strncmp(state.current_func->name, state.bench_pattern, state.bench_pattern_len);
420 }
421
422 /* Indicate that the current test has failed */
423 void checkasm_fail_func(const char *msg, ...)
424 {
425     if (state.current_func_ver->cpu && state.current_func_ver->ok) {
426         va_list arg;
427
428         print_cpu_name();
429         fprintf(stderr, "   %s_%s (", state.current_func->name, cpu_suffix(state.current_func_ver->cpu));
430         va_start(arg, msg);
431         vfprintf(stderr, msg, arg);
432         va_end(arg);
433         fprintf(stderr, ")\n");
434
435         state.current_func_ver->ok = 0;
436         state.num_failed++;
437     }
438 }
439
440 /* Update benchmark results of the current function */
441 void checkasm_update_bench(int iterations, uint64_t cycles)
442 {
443     state.current_func_ver->iterations += iterations;
444     state.current_func_ver->cycles += cycles;
445 }
446
447 /* Print the outcome of all tests performed since the last time this function was called */
448 void checkasm_report(const char *name, ...)
449 {
450     static int prev_checked, prev_failed, max_length;
451
452     if (state.num_checked > prev_checked) {
453         print_cpu_name();
454
455         if (*name) {
456             int pad_length = max_length;
457             va_list arg;
458
459             fprintf(stderr, " - ");
460             va_start(arg, name);
461             pad_length -= vfprintf(stderr, name, arg);
462             va_end(arg);
463             fprintf(stderr, "%*c", FFMAX(pad_length, 0) + 2, '[');
464         } else
465             fprintf(stderr, " - %-*s [", max_length, state.current_func->name);
466
467         if (state.num_failed == prev_failed)
468             color_printf(COLOR_GREEN, "OK");
469         else
470             color_printf(COLOR_RED, "FAILED");
471         fprintf(stderr, "]\n");
472
473         prev_checked = state.num_checked;
474         prev_failed  = state.num_failed;
475     } else if (!state.cpu_flag) {
476         int length;
477
478         /* Calculate the amount of padding required to make the output vertically aligned */
479         if (*name) {
480             va_list arg;
481             va_start(arg, name);
482             length = vsnprintf(NULL, 0, name, arg);
483             va_end(arg);
484         } else
485             length = strlen(state.current_func->name);
486
487         if (length > max_length)
488             max_length = length;
489     }
490 }