From 3b1d7cd1f7308a1b5a6ed31a6b8dd4e4d73fe742 Mon Sep 17 00:00:00 2001 From: Niklas Haas Date: Thu, 4 Jun 2026 11:06:44 +0200 Subject: [PATCH] tests/checkasm: switch to shared libcheckasm implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The checkasm tool originated in x264. It was later rewritten and modernized for FFmpeg (and relicensed to LGPL). For the dav1d project, it was relicensed again to 2-clause BSD (with permission from the relevant authors). The FFmpeg and dav1d implementations of checkasm have since evolved independently (with some amount of ported code between the two, with relicensing permission where relevant). To synchronize the development, and to make it possible to easily adopt checkasm in other projects, it has been split out into a standalone project/library on its own, developed at https://code.videolan.org/videolan/checkasm/. That version has all the features of checkasm in both FFmpeg and dav1d, and has got a number of extra improvements on top: - More/fixed tests (e.g. properly clobbering high bits of 32-bit registers on most platforms), - Vastly improved overall performance / runtime for benchmarking, due primarily to the ability to scale the runtime of each test to that test's complexity. - Much more robust statistical analysis of benchmarking results; including robust outlier rejection, an estimation of the histogram, and the ability to report the variance / stddev in addition to the (trimmed) mean. - Interactive HTML and JSON output formats in addition to CSV/TSV. - More readable and user-friendly output across the board, especially for failures and data dumps (e.g. also showing errors inside padding bytes). - Better cross-platform support, including dynamic fallback of timer implementations on ARM platforms, a better RISC-V and AArch64 harness, and more. On AArch64, it tests which timer out of pmccntr_el0, linux perf, macos kperf, cntvct_el0 is available, without the user needing to configure things, and falling back on clock_gettime if neither of them can be used. This means one automatically gets the best available timer, if userspace access to pmccntr_el0 has been unlocked with a kernel module, or if one has permission to use the perf API, or if the cntvct_el0 is exact enough to be useful. On AArch64 macOS, there is now a test harness that catches clobbered registers and stack clobbering, like on other platforms. - An option for setting affinity, for benchmarking on heterogenous core systems. (On Linux, this is already easily done through taskset, but on Windows, the checkasm built in option makes it possible there as well, and portable.) - Printing of the tested CPU core name, where possible. To integrate this external implementation of checkasm into FFmpeg, without having to build libcheckasm as an external library, the upstream sources are added as a git subtree, and integrated into the FFmpeg build system as a foreign source. For the long and storied history of how we arrived at this solution, see: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/22546 The relevant config headers for checkasm are generated by configure, and the sources are built as part of the main ffmpeg build. The upstream sources, while they use meson as primary build system, are structured to make it easy to build as part of a foreign build system. The existing testcases are mostly kept untouched (only three minor changes are required, in crc.c, sw_ops.c and vp8dsp.c), while the majority of the logic from checkasm.c, checkasm.h and the arch specific assembly files are removed, replaced with the external implementation. Co-Authored-By: Martin Storsjö Signed-off-by: Niklas Haas --- .gitignore | 2 + Makefile | 2 +- configure | 66 ++ tests/checkasm/Makefile | 45 +- tests/checkasm/aarch64/checkasm.S | 198 ------ tests/checkasm/arm/checkasm.S | 197 ------ tests/checkasm/checkasm.c | 988 +----------------------------- tests/checkasm/checkasm.h | 373 +---------- tests/checkasm/crc.c | 2 +- tests/checkasm/riscv/checkasm.S | 337 ---------- tests/checkasm/sw_ops.c | 4 +- tests/checkasm/vp8dsp.c | 2 +- tests/checkasm/x86/checkasm.asm | 244 -------- 13 files changed, 142 insertions(+), 2318 deletions(-) delete mode 100644 tests/checkasm/aarch64/checkasm.S delete mode 100644 tests/checkasm/arm/checkasm.S delete mode 100644 tests/checkasm/riscv/checkasm.S delete mode 100644 tests/checkasm/x86/checkasm.asm diff --git a/.gitignore b/.gitignore index 073bbf8e65..48182e7780 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,8 @@ /ffmpeg /ffplay /ffprobe +/checkasm_config_generated.asm +/checkasm_config_generated.h /config.asm /config.h /config_components.asm diff --git a/Makefile b/Makefile index f296e87ed4..131d2b62ef 100644 --- a/Makefile +++ b/Makefile @@ -187,7 +187,7 @@ clean:: $(RM) -rf coverage.info coverage.info.in lcov distclean:: clean - $(RM) .version config.asm config.h config_components.* mapfile \ + $(RM) .version checkasm_config_generated.* config.asm config.h config_components.* mapfile \ ffbuild/.config ffbuild/config.* libavutil/avconfig.h \ version.h libavutil/ffversion.h libavcodec/codec_names.h \ libavcodec/bsf_list.c libavformat/protocol_list.c \ diff --git a/configure b/configure index 7ef7adc53d..a67ed344ae 100755 --- a/configure +++ b/configure @@ -2188,6 +2188,7 @@ HWACCEL_AUTODETECT_LIBRARY_LIST=" # catchall list of things that require external libs to link EXTRALIBS_LIST=" + checkasm cpu_init cws2fws " @@ -2572,6 +2573,7 @@ SYSTEM_FUNCS=" glXGetProcAddress gmtime_r inet_aton + ioctl isatty kbhit localtime_r @@ -2589,6 +2591,7 @@ SYSTEM_FUNCS=" prctl pthread_cancel pthread_set_name_np + pthread_setaffinity_np pthread_setname_np sched_getaffinity SecItemImport @@ -2597,6 +2600,8 @@ SYSTEM_FUNCS=" SetDllDirectory setmode setrlimit + sigaction + siglongjmp Sleep strerror_r sysconf @@ -4356,6 +4361,7 @@ vaapi_transcode_example_deps="avcodec avformat avutil h264_vaapi_encoder" qsv_transcode_example_deps="avcodec avformat avutil h264_qsv_encoder" # EXTRALIBS_LIST +checkasm_extralibs="advapi32_extralibs pthreads_extralibs" cpu_init_extralibs="pthreads_extralibs" cws2fws_extralibs="zlib_extralibs" @@ -7010,7 +7016,10 @@ check_func_headers conio.h kbhit check_func_headers io.h setmode check_func_headers lzo/lzo1x.h lzo1x_999_compress check_func_headers mach/mach_time.h mach_absolute_time +check_func_headers setjmp.h siglongjmp +check_func_headers signal.h sigaction check_func_headers stdlib.h getenv +check_func_headers sys/ioctl.h ioctl check_func_headers sys/stat.h lstat check_func_headers sys/auxv.h getauxval check_func_headers sys/auxv.h elf_aux_info @@ -7258,6 +7267,9 @@ if ! disabled pthreads && ! enabled w32threads && ! enabled os2threads; then fi check_lib pthread_set_name_np "$hdrs" pthread_set_name_np -lpthread check_lib pthread_setname_np "$hdrs" pthread_setname_np -lpthread + # _GNU_SOURCE gets defined locally in checkasm where + # pthread_setaffinity_np gets used. + check_lib pthread_setaffinity_np "$hdrs" pthread_setaffinity_np -lpthread -D_GNU_SOURCE fi fi @@ -8844,6 +8856,60 @@ echo "endif # FFMPEG_CONFIG_MAK" >> ffbuild/config.mak cp_if_changed $TMPH config_components.h enabled x86asm && cp_if_changed $TMPASM config_components.asm +# Reopen a new TMPH for checkasm_config_generated.h +cat > $TMPH <> $TMPH <> $TMPH <> $TMPH < $TMPASM < $TMPH < -#include -#include -#include -#include -#include #include "checkasm.h" -#include "libavutil/avassert.h" -#include "libavutil/common.h" -#include "libavutil/cpu.h" -#include "libavutil/intfloat.h" -#include "libavutil/random_seed.h" - -#if HAVE_IO_H -#include -#endif -#if HAVE_PRCTL -#include -#endif - -#if defined(_WIN32) && !defined(SIGBUS) -/* non-standard, use the same value as mingw-w64 */ -#define SIGBUS 10 -#endif - -#if HAVE_SETCONSOLETEXTATTRIBUTE && HAVE_GETSTDHANDLE -#include -#define COLOR_RED FOREGROUND_RED -#define COLOR_GREEN FOREGROUND_GREEN -#define COLOR_YELLOW (FOREGROUND_RED|FOREGROUND_GREEN) -#else -#define COLOR_RED 1 -#define COLOR_GREEN 2 -#define COLOR_YELLOW 3 -#endif - -#if HAVE_UNISTD_H -#include -#endif - -#if !HAVE_ISATTY -#define isatty(fd) 1 -#endif - -#if ARCH_AARCH64 -#include "libavutil/aarch64/cpu.h" -#elif ARCH_RISCV -#include "libavutil/riscv/cpu.h" -#endif - -#if ARCH_ARM && HAVE_ARMV5TE_EXTERNAL -#include "libavutil/arm/cpu.h" - -void (*checkasm_checked_call)(void *func, int dummy, ...) = checkasm_checked_call_novfp; -#endif - -/* Trade-off between speed and accuracy */ -uint64_t bench_runs = 1U << 10; /* List of tests to invoke */ -static const struct { - const char *name; - void (*func)(void); -} tests[] = { +static const CheckasmTest tests[] = { /* NOTE: When adding a new test to this list here, it also needs to be * added in tests/fate/checkasm.mak, otherwise it doesn't get executed * as part of "make fate" or "make fate-checkasm". */ @@ -380,11 +320,7 @@ static const struct { }; /* List of cpu flags to check */ -static const struct { - const char *name; - const char *suffix; - int flag; -} cpus[] = { +static const CheckasmCpuInfo cpuflags[] = { #if ARCH_AARCH64 { "ARMV8", "armv8", AV_CPU_FLAG_ARMV8 }, { "NEON", "neon", AV_CPU_FLAG_NEON }, @@ -449,917 +385,19 @@ static const struct { { NULL } }; -typedef struct CheckasmFuncVersion { - struct CheckasmFuncVersion *next; - void *func; - int ok; - int cpu; - CheckasmPerf perf; -} CheckasmFuncVersion; - -/* Binary search tree node */ -typedef struct CheckasmFunc { - struct CheckasmFunc *child[2]; - CheckasmFuncVersion versions; - uint8_t color; /* 0 = red, 1 = black */ - char name[1]; -} CheckasmFunc; - -/* Internal state */ -static struct { - CheckasmFunc *funcs; - CheckasmFunc *current_func; - CheckasmFuncVersion *current_func_ver; - const char *current_test_name; - const char *bench_pattern; - int bench_pattern_len; - int num_checked; - int num_failed; - - /* perf */ - int nop_time; - int sysfd; - - int cpu_flag; - const char *cpu_flag_name; - const char *test_pattern; - int verbose; - int csv; - int tsv; - volatile sig_atomic_t catch_signals; -} state; - -/* PRNG state */ -AVLFG checkasm_lfg; - -/* float compare support code */ -static int is_negative(union av_intfloat32 u) +static void set_cpu_flags(uint64_t flags) { - return u.i >> 31; + av_force_cpu_flags((int) flags); } -int float_near_ulp(float a, float b, unsigned max_ulp) +int main(int argc, const char *argv[]) { - union av_intfloat32 x, y; - - x.f = a; - y.f = b; - - if (is_negative(x) != is_negative(y)) { - // handle -0.0 == +0.0 - return a == b; - } - - if (llabs((int64_t)x.i - y.i) <= max_ulp) - return 1; - - return 0; -} - -int float_near_ulp_array(const float *a, const float *b, unsigned max_ulp, - unsigned len) -{ - unsigned i; - - for (i = 0; i < len; i++) { - if (!float_near_ulp(a[i], b[i], max_ulp)) - return 0; - } - return 1; -} - -int float_near_abs_eps(float a, float b, float eps) -{ - float abs_diff = fabsf(a - b); - if (abs_diff < eps) - return 1; - - fprintf(stderr, "test failed comparing %g with %g (abs diff=%g with EPS=%g)\n", a, b, abs_diff, eps); - - return 0; -} - -int float_near_abs_eps_array(const float *a, const float *b, float eps, - unsigned len) -{ - unsigned i; - - for (i = 0; i < len; i++) { - if (!float_near_abs_eps(a[i], b[i], eps)) - return 0; - } - return 1; -} - -int float_near_abs_eps_ulp(float a, float b, float eps, unsigned max_ulp) -{ - return float_near_ulp(a, b, max_ulp) || float_near_abs_eps(a, b, eps); -} - -int float_near_abs_eps_array_ulp(const float *a, const float *b, float eps, - unsigned max_ulp, unsigned len) -{ - unsigned i; - - for (i = 0; i < len; i++) { - if (!float_near_abs_eps_ulp(a[i], b[i], eps, max_ulp)) - return 0; - } - return 1; -} - -int double_near_abs_eps(double a, double b, double eps) -{ - double abs_diff = fabs(a - b); - - return abs_diff < eps; -} - -int double_near_abs_eps_array(const double *a, const double *b, double eps, - unsigned len) -{ - unsigned i; - - for (i = 0; i < len; i++) { - if (!double_near_abs_eps(a[i], b[i], eps)) - return 0; - } - return 1; -} - -/* Print colored text to stderr if the terminal supports it */ -static void color_printf(int color, const char *fmt, ...) -{ - static int use_color = -1; - va_list arg; - -#if HAVE_SETCONSOLETEXTATTRIBUTE && HAVE_GETSTDHANDLE - static HANDLE con; - static WORD org_attributes; - - if (use_color < 0) { - CONSOLE_SCREEN_BUFFER_INFO con_info; - con = GetStdHandle(STD_ERROR_HANDLE); - if (con && con != INVALID_HANDLE_VALUE && GetConsoleScreenBufferInfo(con, &con_info)) { - org_attributes = con_info.wAttributes; - use_color = 1; - } else - use_color = 0; - } - if (use_color) - SetConsoleTextAttribute(con, (org_attributes & 0xfff0) | (color & 0x0f)); -#else - if (use_color < 0) { - const char *term = getenv("TERM"); - use_color = term && strcmp(term, "dumb") && isatty(2); - } - if (use_color) - fprintf(stderr, "\x1b[%d;3%dm", (color & 0x08) >> 3, color & 0x07); -#endif - - va_start(arg, fmt); - vfprintf(stderr, fmt, arg); - va_end(arg); - - if (use_color) { -#if HAVE_SETCONSOLETEXTATTRIBUTE && HAVE_GETSTDHANDLE - SetConsoleTextAttribute(con, org_attributes); -#else - fprintf(stderr, "\x1b[0m"); -#endif - } -} - -/* Deallocate a tree */ -static void destroy_func_tree(CheckasmFunc *f) -{ - if (f) { - CheckasmFuncVersion *v = f->versions.next; - while (v) { - CheckasmFuncVersion *next = v->next; - free(v); - v = next; - } - - destroy_func_tree(f->child[0]); - destroy_func_tree(f->child[1]); - free(f); - } -} - -/* Allocate a zero-initialized block, clean up and exit on failure */ -static void *checkasm_malloc(size_t size) -{ - void *ptr = calloc(1, size); - if (!ptr) { - fprintf(stderr, "checkasm: malloc failed\n"); - destroy_func_tree(state.funcs); - exit(1); - } - return ptr; -} - -/* Get the suffix of the specified cpu flag */ -static const char *cpu_suffix(int cpu) -{ - int i = FF_ARRAY_ELEMS(cpus); - - while (--i >= 0) - if (cpu & cpus[i].flag) - return cpus[i].suffix; - - return "c"; -} - -static int cmp_nop(const void *a, const void *b) -{ - return *(const uint16_t*)a - *(const uint16_t*)b; -} - -/* Measure the overhead of the timing code (in decicycles) */ -static int measure_nop_time(void) -{ - uint16_t nops[10000]; - int i, nop_sum = 0; - av_unused const int sysfd = state.sysfd; - - uint64_t t = 0; - for (i = 0; i < 10000; i++) { - PERF_START(t); - PERF_STOP(t); - nops[i] = t; - } - - qsort(nops, 10000, sizeof(uint16_t), cmp_nop); - for (i = 2500; i < 7500; i++) - nop_sum += nops[i]; - - return nop_sum / 500; -} - -static inline double avg_cycles_per_call(const CheckasmPerf *const p) -{ - if (p->iterations) { - const double cycles = (double)(10 * p->cycles) / p->iterations - state.nop_time; - if (cycles > 0.0) - return cycles / 32.0; /* 32 calls per iteration */ - } - return 0.0; -} - -/* Print benchmark results */ -static void print_benchs(CheckasmFunc *f) -{ - if (f) { - CheckasmFuncVersion *v = &f->versions; - const CheckasmPerf *p = &v->perf; - const double baseline = avg_cycles_per_call(p); - double decicycles; - - print_benchs(f->child[0]); - - do { - if (p->iterations) { - p = &v->perf; - decicycles = avg_cycles_per_call(p); - if (state.csv || state.tsv) { - const char sep = state.csv ? ',' : '\t'; - printf("%s%c%s%c%.1f\n", f->name, sep, - cpu_suffix(v->cpu), sep, - decicycles / 10.0); - } else { - const int pad_length = 10 + 50 - - printf("%s_%s:", f->name, cpu_suffix(v->cpu)); - const double ratio = decicycles ? - baseline / decicycles : 0.0; - printf("%*.1f (%5.2fx)\n", FFMAX(pad_length, 0), - decicycles / 10.0, ratio); - } - } - } while ((v = v->next)); - - print_benchs(f->child[1]); - } -} - -/* ASCIIbetical sort except preserving natural order for numbers */ -static int cmp_func_names(const char *a, const char *b) -{ - const char *start = a; - int ascii_diff, digit_diff; - - for (; !(ascii_diff = *(const unsigned char*)a - *(const unsigned char*)b) && *a; a++, b++); - for (; av_isdigit(*a) && av_isdigit(*b); a++, b++); - - if (a > start && av_isdigit(a[-1]) && (digit_diff = av_isdigit(*a) - av_isdigit(*b))) - return digit_diff; - - return ascii_diff; -} - -/* Perform a tree rotation in the specified direction and return the new root */ -static CheckasmFunc *rotate_tree(CheckasmFunc *f, int dir) -{ - CheckasmFunc *r = f->child[dir^1]; - f->child[dir^1] = r->child[dir]; - r->child[dir] = f; - r->color = f->color; - f->color = 0; - return r; -} - -#define is_red(f) ((f) && !(f)->color) - -/* Balance a left-leaning red-black tree at the specified node */ -static void balance_tree(CheckasmFunc **root) -{ - CheckasmFunc *f = *root; - - if (is_red(f->child[0]) && is_red(f->child[1])) { - f->color ^= 1; - f->child[0]->color = f->child[1]->color = 1; - } - - if (!is_red(f->child[0]) && is_red(f->child[1])) - *root = rotate_tree(f, 0); /* Rotate left */ - else if (is_red(f->child[0]) && is_red(f->child[0]->child[0])) - *root = rotate_tree(f, 1); /* Rotate right */ -} - -/* Get a node with the specified name, creating it if it doesn't exist */ -static CheckasmFunc *get_func(CheckasmFunc **root, const char *name) -{ - CheckasmFunc *f = *root; - - if (f) { - /* Search the tree for a matching node */ - int cmp = cmp_func_names(name, f->name); - if (cmp) { - f = get_func(&f->child[cmp > 0], name); - - /* Rebalance the tree on the way up if a new node was inserted */ - if (!f->versions.func) - balance_tree(root); - } - } else { - /* Allocate and insert a new node into the tree */ - int name_length = strlen(name); - f = *root = checkasm_malloc(sizeof(CheckasmFunc) + name_length); - memcpy(f->name, name, name_length + 1); - } - - return f; -} - -checkasm_context checkasm_context_buf; - -/* Crash handling: attempt to catch crashes and handle them - * gracefully instead of just aborting abruptly. */ -#ifdef _WIN32 -#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) -static LONG NTAPI signal_handler(EXCEPTION_POINTERS *e) { - int s; - - if (!state.catch_signals) - return EXCEPTION_CONTINUE_SEARCH; - - switch (e->ExceptionRecord->ExceptionCode) { - case EXCEPTION_FLT_DIVIDE_BY_ZERO: - case EXCEPTION_INT_DIVIDE_BY_ZERO: - s = SIGFPE; - break; - case EXCEPTION_ILLEGAL_INSTRUCTION: - case EXCEPTION_PRIV_INSTRUCTION: - s = SIGILL; - break; - case EXCEPTION_ACCESS_VIOLATION: - case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: - case EXCEPTION_DATATYPE_MISALIGNMENT: - case EXCEPTION_STACK_OVERFLOW: - s = SIGSEGV; - break; - case EXCEPTION_IN_PAGE_ERROR: - s = SIGBUS; - break; - default: - return EXCEPTION_CONTINUE_SEARCH; - } - state.catch_signals = 0; - checkasm_load_context(s); - return EXCEPTION_CONTINUE_EXECUTION; /* never reached, but shuts up gcc */ -} -#endif -#elif !defined(_WASI_EMULATED_SIGNAL) -static void signal_handler(int s); - -static const struct sigaction signal_handler_act = { - .sa_handler = signal_handler, - .sa_flags = SA_RESETHAND, -}; - -static void signal_handler(int s) { - if (state.catch_signals) { - state.catch_signals = 0; - sigaction(s, &signal_handler_act, NULL); - checkasm_load_context(s); - } -} -#endif - -/* Compares a string with a wildcard pattern. */ -static int wildstrcmp(const char *str, const char *pattern) -{ - const char *wild = strchr(pattern, '*'); - if (wild) { - const size_t len = wild - pattern; - if (strncmp(str, pattern, len)) return 1; - while (*++wild == '*'); - if (!*wild) return 0; - str += len; - while (*str && wildstrcmp(str, wild)) str++; - return !*str; - } - return strcmp(str, pattern); -} - -/* Perform tests and benchmarks for the specified cpu flag if supported by the host */ -static void check_cpu_flag(const char *name, int flag) -{ - int old_cpu_flag = state.cpu_flag; - - flag |= old_cpu_flag; - av_force_cpu_flags(-1); - state.cpu_flag = flag & av_get_cpu_flags(); - av_force_cpu_flags(state.cpu_flag); - - if (!flag || state.cpu_flag != old_cpu_flag) { - int i; - - state.cpu_flag_name = name; - for (i = 0; tests[i].func; i++) { - if (state.test_pattern && wildstrcmp(tests[i].name, state.test_pattern)) - continue; - state.current_test_name = tests[i].name; - tests[i].func(); - } - } -} - -/* Print the name of the current CPU flag, but only do it once */ -static void print_cpu_name(void) -{ - if (state.cpu_flag_name) { - color_printf(COLOR_YELLOW, "%s:\n", state.cpu_flag_name); - state.cpu_flag_name = NULL; - } -} - -#if CONFIG_LINUX_PERF -static int bench_init_linux(void) -{ - struct perf_event_attr attr = { - .type = PERF_TYPE_HARDWARE, - .size = sizeof(struct perf_event_attr), - .config = PERF_COUNT_HW_CPU_CYCLES, - .disabled = 1, // start counting only on demand - .exclude_kernel = 1, - .exclude_hv = 1, -#if !ARCH_X86 - .exclude_guest = 1, -#endif + CheckasmConfig cfg = { + .cpu_flags = cpuflags, + .tests = tests, + .set_cpu_flags = set_cpu_flags, + .cpu = av_get_cpu_flags(), }; - fprintf(stderr, "benchmarking with Linux Perf Monitoring API\n"); - - state.sysfd = syscall(__NR_perf_event_open, &attr, 0, -1, -1, 0); - if (state.sysfd == -1) { - perror("perf_event_open"); - return -1; - } - return 0; -} -#elif CONFIG_MACOS_KPERF -static int bench_init_kperf(void) -{ - ff_kperf_init(); - return 0; -} -#else -static int bench_init_ffmpeg(void) -{ -#ifdef AV_READ_TIME - if (!checkasm_save_context()) { - checkasm_set_signal_handler_state(1); - AV_READ_TIME(); - checkasm_set_signal_handler_state(0); - } else { - fprintf(stderr, "checkasm: unable to execute platform specific timer\n"); - return -1; - } - fprintf(stderr, "benchmarking with native FFmpeg timers\n"); - return 0; -#else - fprintf(stderr, "checkasm: --bench is not supported on your system\n"); - return -1; -#endif -} -#endif - -static int bench_init(void) -{ -#if CONFIG_LINUX_PERF - int ret = bench_init_linux(); -#elif CONFIG_MACOS_KPERF - int ret = bench_init_kperf(); -#else - int ret = bench_init_ffmpeg(); -#endif - if (ret < 0) - return ret; - - state.nop_time = measure_nop_time(); - fprintf(stderr, "nop: %d.%d\n", state.nop_time/10, state.nop_time%10); - return 0; -} - -static void bench_uninit(void) -{ -#if CONFIG_LINUX_PERF - close(state.sysfd); -#endif -} - -static int usage(const char *path) -{ - fprintf(stderr, - "Usage: %s [options...] [seed]\n" - " --test= Run specific test.\n" - " --bench Run benchmark.\n" - " --csv, --tsv Output results in rows of comma or tab separated values.\n" - " --runs= Manual number of benchmark iterations to run 2**.\n" - " --verbose Increase verbosity.\n", - path); - return 1; -} - -int main(int argc, char *argv[]) -{ - unsigned int seed = av_get_random_seed(); - int i, ret = 0; - char arch_info_buf[50] = ""; - -#ifdef _WIN32 -#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) - AddVectoredExceptionHandler(0, signal_handler); -#endif -#elif !defined(_WASI_EMULATED_SIGNAL) - sigaction(SIGBUS, &signal_handler_act, NULL); - sigaction(SIGFPE, &signal_handler_act, NULL); - sigaction(SIGILL, &signal_handler_act, NULL); - sigaction(SIGSEGV, &signal_handler_act, NULL); -#endif -#if HAVE_PRCTL && defined(PR_SET_UNALIGN) - prctl(PR_SET_UNALIGN, PR_UNALIGN_SIGBUS); -#endif -#if ARCH_ARM && HAVE_ARMV5TE_EXTERNAL - if (have_vfp(av_get_cpu_flags()) || have_neon(av_get_cpu_flags())) - checkasm_checked_call = checkasm_checked_call_vfp; -#endif - - if (!tests[0].func || !cpus[0].flag) { - fprintf(stderr, "checkasm: no tests to perform\n"); - return 0; - } - - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; - unsigned long l; - char *end; - - if (!strncmp(arg, "--bench", 7)) { - if (bench_init() < 0) - return 1; - if (arg[7] == '=') { - state.bench_pattern = arg + 8; - state.bench_pattern_len = strlen(state.bench_pattern); - } else - state.bench_pattern = "*"; - } else if (!strncmp(arg, "--test=", 7)) { - state.test_pattern = arg + 7; - } else if (!strcmp(arg, "--csv")) { - state.csv = 1; state.tsv = 0; - } else if (!strcmp(arg, "--tsv")) { - state.csv = 0; state.tsv = 1; - } else if (!strcmp(arg, "--verbose") || !strcmp(arg, "-v")) { - state.verbose = 1; - } else if (!strncmp(arg, "--runs=", 7)) { - l = strtoul(arg + 7, &end, 10); - if (*end == '\0') { - if (l > 30) { - fprintf(stderr, "checkasm: error: runs exponent must be within the range 0 <= 30\n"); - usage(argv[0]); - } - bench_runs = 1U << l; - } else { - return usage(argv[0]); - } - } else if ((l = strtoul(arg, &end, 10)) <= UINT_MAX && - *end == '\0') { - seed = l; - } else { - return usage(argv[0]); - } - } - -#if ARCH_AARCH64 && HAVE_SVE - if (have_sve(av_get_cpu_flags())) - snprintf(arch_info_buf, sizeof(arch_info_buf), - "SVE %d bits, ", 8 * ff_aarch64_sve_length()); -#endif -#if ARCH_AARCH64 && HAVE_SME - if (have_sme(av_get_cpu_flags())) - av_strlcatf(arch_info_buf, sizeof(arch_info_buf), - "SME %d bits, ", 8 * ff_aarch64_sme_length()); -#endif -#if ARCH_RISCV && HAVE_RVV - if (av_get_cpu_flags() & AV_CPU_FLAG_RVV_I32) - snprintf(arch_info_buf, sizeof (arch_info_buf), - "%zu-bit vectors, ", 8 * ff_get_rv_vlenb()); -#endif - fprintf(stderr, "checkasm: %susing random seed %u\n", arch_info_buf, seed); - av_lfg_init(&checkasm_lfg, seed); - - if (state.bench_pattern) - fprintf(stderr, "checkasm: bench runs %" PRIu64 " (1 << %i)\n", bench_runs, av_log2(bench_runs)); - - check_cpu_flag(NULL, 0); - for (i = 0; cpus[i].flag; i++) - check_cpu_flag(cpus[i].name, cpus[i].flag); - - if (state.num_failed) { - fprintf(stderr, "checkasm: %d of %d tests have failed\n", state.num_failed, state.num_checked); - ret = 1; - } else { - fprintf(stderr, "checkasm: all %d tests passed\n", state.num_checked); - if (state.bench_pattern) { - print_benchs(state.funcs); - } - } - - destroy_func_tree(state.funcs); - bench_uninit(); - return ret; -} - -/* Decide whether or not the specified function needs to be tested and - * allocate/initialize data structures if needed. Returns a pointer to a - * reference function if the function should be tested, otherwise NULL */ -void *checkasm_check_func(void *func, const char *name, ...) -{ - char name_buf[256]; - void *ref = func; - CheckasmFuncVersion *v; - int name_length; - va_list arg; - - va_start(arg, name); - name_length = vsnprintf(name_buf, sizeof(name_buf), name, arg); - va_end(arg); - - if (!func || name_length <= 0 || name_length >= sizeof(name_buf)) - return NULL; - - state.current_func = get_func(&state.funcs, name_buf); - state.funcs->color = 1; - v = &state.current_func->versions; - - if (v->func) { - CheckasmFuncVersion *prev; - do { - /* Only test functions that haven't already been tested */ - if (v->func == func) - return NULL; - - if (v->ok) - ref = v->func; - - prev = v; - } while ((v = v->next)); - - v = prev->next = checkasm_malloc(sizeof(CheckasmFuncVersion)); - } - - v->func = func; - v->ok = 1; - v->cpu = state.cpu_flag; - state.current_func_ver = v; - - if (state.cpu_flag) - state.num_checked++; - - return ref; -} - -/* Decide whether or not the current function needs to be benchmarked */ -int checkasm_bench_func(void) -{ - return !state.num_failed && state.bench_pattern && - !wildstrcmp(state.current_func->name, state.bench_pattern); -} - -/* Indicate that the current test has failed, return whether verbose printing - * is requested. */ -int checkasm_fail_func(const char *msg, ...) -{ - if (state.current_func_ver && state.current_func_ver->cpu && - state.current_func_ver->ok) - { - va_list arg; - - print_cpu_name(); - fprintf(stderr, " %s_%s (", state.current_func->name, cpu_suffix(state.current_func_ver->cpu)); - va_start(arg, msg); - vfprintf(stderr, msg, arg); - va_end(arg); - fprintf(stderr, ")\n"); - - state.current_func_ver->ok = 0; - state.num_failed++; - } - return state.verbose; -} - -void checkasm_set_signal_handler_state(int enabled) { - state.catch_signals = enabled; -} - -int checkasm_handle_signal(int s) { - if (s) { -#ifdef __GLIBC__ - checkasm_fail_func("fatal signal %d: %s", s, strsignal(s)); -#else - checkasm_fail_func(s == SIGFPE ? "fatal arithmetic error" : - s == SIGILL ? "illegal instruction" : - s == SIGBUS ? "bus error" : - "segmentation fault"); -#endif - } - return s; -} - -/* Get the benchmark context of the current function */ -CheckasmPerf *checkasm_get_perf_context(void) -{ - CheckasmPerf *perf = &state.current_func_ver->perf; - memset(perf, 0, sizeof(*perf)); - perf->sysfd = state.sysfd; - return perf; -} - -/* Print the outcome of all tests performed since the last time this function was called */ -void checkasm_report(const char *name, ...) -{ - static int prev_checked, prev_failed, max_length; - - if (state.num_checked > prev_checked) { - int pad_length = max_length + 4; - va_list arg; - - print_cpu_name(); - pad_length -= fprintf(stderr, " - %s.", state.current_test_name); - va_start(arg, name); - pad_length -= vfprintf(stderr, name, arg); - va_end(arg); - fprintf(stderr, "%*c", FFMAX(pad_length, 0) + 2, '['); - - if (state.num_failed == prev_failed) - color_printf(COLOR_GREEN, "OK"); - else - color_printf(COLOR_RED, "FAILED"); - fprintf(stderr, "]\n"); - - prev_checked = state.num_checked; - prev_failed = state.num_failed; - } else if (!state.cpu_flag) { - /* Calculate the amount of padding required to make the output vertically aligned */ - int length = strlen(state.current_test_name); - va_list arg; - - va_start(arg, name); - length += vsnprintf(NULL, 0, name, arg); - va_end(arg); - - if (length > max_length) - max_length = length; - } -} - -static int check_err(const char *file, int line, - const char *name, int w, int h, - int *err) -{ - if (*err) - return 0; - if (!checkasm_fail_func("%s:%d", file, line)) - return 1; - *err = 1; - fprintf(stderr, "%s (%dx%d):\n", name, w, h); - return 0; -} - -#define DEF_CHECKASM_CHECK_BODY(compare, type, fmt) \ -do { \ - int64_t aligned_w = (w - 1LL + align_w) & ~(align_w - 1); \ - int64_t aligned_h = (h - 1LL + align_h) & ~(align_h - 1); \ - int err = 0; \ - int y = 0; \ - av_assert0(aligned_w == (int32_t)aligned_w);\ - av_assert0(aligned_h == (int32_t)aligned_h);\ - stride1 /= sizeof(*buf1); \ - stride2 /= sizeof(*buf2); \ - for (y = 0; y < h; y++) \ - if (!compare(&buf1[y*stride1], &buf2[y*stride2], w)) \ - break; \ - if (y != h) { \ - if (check_err(file, line, name, w, h, &err)) \ - return 1; \ - for (y = 0; y < h; y++) { \ - for (int x = 0; x < w; x++) \ - fprintf(stderr, " " fmt, buf1[x]); \ - fprintf(stderr, " "); \ - for (int x = 0; x < w; x++) \ - fprintf(stderr, " " fmt, buf2[x]); \ - fprintf(stderr, " "); \ - for (int x = 0; x < w; x++) \ - fprintf(stderr, "%c", buf1[x] != buf2[x] ? 'x' : '.'); \ - buf1 += stride1; \ - buf2 += stride2; \ - fprintf(stderr, "\n"); \ - } \ - buf1 -= h*stride1; \ - buf2 -= h*stride2; \ - } \ - for (y = -padding; y < 0; y++) \ - if (!compare(&buf1[y*stride1 - padding], &buf2[y*stride2 - padding], \ - w + 2*padding)) { \ - if (check_err(file, line, name, w, h, &err)) \ - return 1; \ - fprintf(stderr, " overwrite above\n"); \ - break; \ - } \ - for (y = aligned_h; y < aligned_h + padding; y++) \ - if (!compare(&buf1[y*stride1 - padding], &buf2[y*stride2 - padding], \ - w + 2*padding)) { \ - if (check_err(file, line, name, w, h, &err)) \ - return 1; \ - fprintf(stderr, " overwrite below\n"); \ - break; \ - } \ - for (y = 0; y < h; y++) \ - if (!compare(&buf1[y*stride1 - padding], &buf2[y*stride2 - padding], \ - padding)) { \ - if (check_err(file, line, name, w, h, &err)) \ - return 1; \ - fprintf(stderr, " overwrite left\n"); \ - break; \ - } \ - for (y = 0; y < h; y++) \ - if (!compare(&buf1[y*stride1 + aligned_w], &buf2[y*stride2 + aligned_w], \ - padding)) { \ - if (check_err(file, line, name, w, h, &err)) \ - return 1; \ - fprintf(stderr, " overwrite right\n"); \ - break; \ - } \ - return err; \ -} while (0) - -#define cmp_int(a, b, len) (!memcmp(a, b, (len) * sizeof(*(a)))) -#define DEF_CHECKASM_CHECK_FUNC(type, fmt) \ -int checkasm_check_##type(const char *file, int line, \ - const type *buf1, ptrdiff_t stride1, \ - const type *buf2, ptrdiff_t stride2, \ - int w, int h, const char *name, \ - int align_w, int align_h, \ - int padding) \ -{ \ - DEF_CHECKASM_CHECK_BODY(cmp_int, type, fmt); \ -} - -DEF_CHECKASM_CHECK_FUNC(uint8_t, "%02x") -DEF_CHECKASM_CHECK_FUNC(uint16_t, "%04x") -DEF_CHECKASM_CHECK_FUNC(uint32_t, "%08x") -DEF_CHECKASM_CHECK_FUNC(int16_t, "%6d") -DEF_CHECKASM_CHECK_FUNC(int32_t, "%9d") - -int checkasm_check_float_ulp(const char *file, int line, - const float *buf1, ptrdiff_t stride1, - const float *buf2, ptrdiff_t stride2, - int w, int h, const char *name, - unsigned max_ulp, int align_w, int align_h, - int padding) -{ - #define cmp_float(a, b, len) float_near_ulp_array(a, b, max_ulp, len) - DEF_CHECKASM_CHECK_BODY(cmp_float, float, "%g"); - #undef cmp_float + return checkasm_main(&cfg, argc, argv); } diff --git a/tests/checkasm/checkasm.h b/tests/checkasm/checkasm.h index e48f53e3e6..612ad0f9b9 100644 --- a/tests/checkasm/checkasm.h +++ b/tests/checkasm/checkasm.h @@ -24,17 +24,10 @@ #define TESTS_CHECKASM_CHECKASM_H #include +#include +#include + #include "config.h" - -#if CONFIG_LINUX_PERF -#include // read(3) -#include -#include -#include -#elif CONFIG_MACOS_KPERF -#include "libavutil/macos_kperf.h" -#endif - #include "libavutil/avstring.h" #include "libavutil/cpu.h" #include "libavutil/emms.h" @@ -42,41 +35,6 @@ #include "libavutil/lfg.h" #include "libavutil/timer.h" -#ifdef _WIN32 -#include -#if defined(__i386__) || defined(_M_IX86) -#include -typedef jmp_buf checkasm_context; -#define checkasm_save_context() checkasm_handle_signal(setjmp(checkasm_context_buf)) -#define checkasm_load_context(s) longjmp(checkasm_context_buf, s) -#elif WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) -/* setjmp/longjmp on Windows on architectures using SEH (all except x86_32) - * will try to use SEH to unwind the stack, which doesn't work for assembly - * functions without unwind information. */ -typedef struct { CONTEXT c; int status; } checkasm_context; -#define checkasm_save_context() \ - (checkasm_context_buf.status = 0, \ - RtlCaptureContext(&checkasm_context_buf.c), \ - checkasm_handle_signal(checkasm_context_buf.status)) -#define checkasm_load_context(s) \ - (checkasm_context_buf.status = s, \ - RtlRestoreContext(&checkasm_context_buf.c, NULL)) -#else -#define checkasm_context void* -#define checkasm_save_context() 0 -#define checkasm_load_context() do {} while (0) -#endif -#elif defined(_WASI_EMULATED_SIGNAL) -#define checkasm_context void* -#define checkasm_save_context() 0 -#define checkasm_load_context() do {} while (0) -#else -#include -typedef sigjmp_buf checkasm_context; -#define checkasm_save_context() checkasm_handle_signal(sigsetjmp(checkasm_context_buf, 1)) -#define checkasm_load_context(s) siglongjmp(checkasm_context_buf, s) -#endif - void checkasm_check_aacencdsp(void); void checkasm_check_aacpsdsp(void); void checkasm_check_ac3dsp(void); @@ -179,309 +137,23 @@ void checkasm_check_vvc_alf(void); void checkasm_check_vvc_mc(void); void checkasm_check_vvc_sao(void); -struct CheckasmPerf; +#define rnd checkasm_rand +#define declare_func_float declare_func +#define bench(...) checkasm_bench(__VA_ARGS__) -void *checkasm_check_func(void *func, const char *name, ...) av_printf_format(2, 3); -int checkasm_bench_func(void); -int checkasm_fail_func(const char *msg, ...) av_printf_format(1, 2); -struct CheckasmPerf *checkasm_get_perf_context(void); -void checkasm_report(const char *name, ...) av_printf_format(1, 2); -void checkasm_set_signal_handler_state(int enabled); -int checkasm_handle_signal(int s); -extern checkasm_context checkasm_context_buf; +#define randomize_stddev(buf, size, stddev) \ + checkasm_randomize_distf(buf, size, (CheckasmDist){ 0.0, stddev }) +#define randomize_stddev_dbl(buf, size, stddev) \ + checkasm_randomize_dist(buf, size, (CheckasmDist){ 0.0, stddev }) -/* float compare utilities */ -int float_near_ulp(float a, float b, unsigned max_ulp); -int float_near_abs_eps(float a, float b, float eps); -int float_near_abs_eps_ulp(float a, float b, float eps, unsigned max_ulp); -int float_near_ulp_array(const float *a, const float *b, unsigned max_ulp, - unsigned len); -int float_near_abs_eps_array(const float *a, const float *b, float eps, - unsigned len); -int float_near_abs_eps_array_ulp(const float *a, const float *b, float eps, - unsigned max_ulp, unsigned len); -int double_near_abs_eps(double a, double b, double eps); -int double_near_abs_eps_array(const double *a, const double *b, double eps, - unsigned len); +#define PIXEL_RECT(name, w, h) \ + BUF_RECT(uint16_t, name##_16, w, h); \ + av_unused ptrdiff_t name##_stride = name##_16_stride; \ + av_unused int name##_buf_h = name##_16_buf_h; \ + av_unused uint8_t* name##_buf = (uint8_t*)name##_16_buf; \ + uint8_t* name = (uint8_t*)name##_16 -extern AVLFG checkasm_lfg; -#define rnd() av_lfg_get(&checkasm_lfg) - -av_unused static void *func_ref, *func_new; - -extern uint64_t bench_runs; - -/* Decide whether or not the specified function needs to be tested */ -#define check_func(func, ...) (checkasm_save_context(), func_ref = checkasm_check_func((func_new = func), __VA_ARGS__)) -#define check_key(key, ...) (checkasm_save_context(), checkasm_check_func(key, __VA_ARGS__)) - -/* Declare the function prototype. The first argument is the return value, the remaining - * arguments are the function parameters. Naming parameters is optional. */ -#define declare_func(ret, ...) declare_new(ret, __VA_ARGS__) typedef ret func_type(__VA_ARGS__) -#define declare_func_float(ret, ...) declare_new_float(ret, __VA_ARGS__) typedef ret func_type(__VA_ARGS__) -#define declare_func_emms(cpu_flags, ret, ...) declare_new_emms(cpu_flags, ret, __VA_ARGS__) typedef ret func_type(__VA_ARGS__) - -/* Indicate that the current test has failed */ -#define fail() checkasm_fail_func("%s:%d", av_basename(__FILE__), __LINE__) - -/* Print the test outcome */ -#define report checkasm_report - -/* Call the reference function */ -#define call_ref(...)\ - checkasm_call((func_type *)func_ref, __VA_ARGS__) - -#define checkasm_call(func, ...) \ - (checkasm_set_signal_handler_state(1),\ - (func)(__VA_ARGS__));\ - checkasm_set_signal_handler_state(0) - -#define call_new(...) checkasm_call_checked(((func_type *)func_new), __VA_ARGS__) - -#if ARCH_X86 && HAVE_X86ASM -/* Verifies that clobbered callee-saved registers are properly saved and restored - * and that either no MMX registers are touched or emms is issued */ -void checkasm_checked_call(void *func, ...); -/* Verifies that clobbered callee-saved registers are properly saved and restored - * and issues emms for asm functions which are not required to do so */ -void checkasm_checked_call_emms(void *func, ...); -/* Verifies that clobbered callee-saved registers are properly saved and restored - * but doesn't issue emms. Meant for dsp functions returning float or double */ -void checkasm_checked_call_float(void *func, ...); - -#if ARCH_X86_64 -/* Evil hack: detect incorrect assumptions that 32-bit ints are zero-extended to 64-bit. - * This is done by clobbering the stack with junk around the stack pointer and calling the - * assembly function through checked_call() with added dummy arguments which forces all - * real arguments to be passed on the stack and not in registers. For 32-bit arguments the - * upper half of the 64-bit register locations on the stack will now contain junk which will - * cause misbehaving functions to either produce incorrect output or segfault. Note that - * even though this works extremely well in practice, it's technically not guaranteed - * and false negatives is theoretically possible, but there can never be any false positives. - */ -void checkasm_stack_clobber(uint64_t clobber, ...); -#define declare_new(ret, ...) ret (*checked_call)(void *, int, int, int, int, int, __VA_ARGS__)\ - = (void *)checkasm_checked_call; -#define declare_new_float(ret, ...) ret (*checked_call)(void *, int, int, int, int, int, __VA_ARGS__)\ - = (void *)checkasm_checked_call_float; -#define declare_new_emms(cpu_flags, ret, ...) \ - ret (*checked_call)(void *, int, int, int, int, int, __VA_ARGS__) = \ - ((cpu_flags) & av_get_cpu_flags()) ? (void *)checkasm_checked_call_emms : \ - (void *)checkasm_checked_call; -#define CLOB (UINT64_C(0xdeadbeefdeadbeef)) -#define checkasm_call_checked(func, ...) \ - (checkasm_set_signal_handler_state(1),\ - checkasm_stack_clobber(CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,\ - CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB),\ - checked_call((func), 0, 0, 0, 0, 0, __VA_ARGS__));\ - checkasm_set_signal_handler_state(0) -#elif ARCH_X86_32 -#define declare_new(ret, ...) ret (*checked_call)(void *, __VA_ARGS__) = (void *)checkasm_checked_call; -#define declare_new_float(ret, ...) ret (*checked_call)(void *, __VA_ARGS__) = (void *)checkasm_checked_call_float; -#define declare_new_emms(cpu_flags, ret, ...) ret (*checked_call)(void *, __VA_ARGS__) = \ - ((cpu_flags) & av_get_cpu_flags()) ? (void *)checkasm_checked_call_emms : \ - (void *)checkasm_checked_call; -#define checkasm_call_checked(func, ...)\ - (checkasm_set_signal_handler_state(1),\ - checked_call((func), __VA_ARGS__));\ - checkasm_set_signal_handler_state(0) -#endif -#elif ARCH_ARM && HAVE_ARMV5TE_EXTERNAL -/* Use a dummy argument, to offset the real parameters by 2, not only 1. - * This makes sure that potential 8-byte-alignment of parameters is kept the same - * even when the extra parameters have been removed. */ -void checkasm_checked_call_vfp(void *func, int dummy, ...); -void checkasm_checked_call_novfp(void *func, int dummy, ...); -extern void (*checkasm_checked_call)(void *func, int dummy, ...); -#define declare_new(ret, ...) ret (*checked_call)(void *, int dummy, __VA_ARGS__, \ - int, int, int, int, int, int, int, int, \ - int, int, int, int, int, int, int) = (void *)checkasm_checked_call; -#define checkasm_call_checked(func, ...) \ - (checkasm_set_signal_handler_state(1),\ - checked_call((func), 0, __VA_ARGS__, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0));\ - checkasm_set_signal_handler_state(0) -#elif ARCH_AARCH64 && !defined(__APPLE__) -void checkasm_stack_clobber(uint64_t clobber, ...); -void checkasm_checked_call(void *func, ...); -#define declare_new(ret, ...) ret (*checked_call)(void *, int, int, int, int, int, int, int, __VA_ARGS__,\ - int, int, int, int, int, int, int, int,\ - int, int, int, int, int, int, int)\ - = (void *)checkasm_checked_call; -#define CLOB (UINT64_C(0xdeadbeefdeadbeef)) -#define checkasm_call_checked(func, ...) (checkasm_set_signal_handler_state(1),\ - checkasm_stack_clobber(CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,\ - CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB),\ - checked_call((func), 0, 0, 0, 0, 0, 0, 0, __VA_ARGS__,\ - 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0));\ - checkasm_set_signal_handler_state(0) -#elif ARCH_RISCV -void checkasm_set_function(void *); -void *checkasm_get_wrapper(void); - -#if HAVE_RV -#define declare_new(ret, ...) \ - ret (*checked_call)(__VA_ARGS__) = checkasm_get_wrapper(); -#define checkasm_call_checked(func, ...) \ - (checkasm_set_signal_handler_state(1),\ - checkasm_set_function(func), checked_call(__VA_ARGS__));\ - checkasm_set_signal_handler_state(0) -#else -#define declare_new(ret, ...) -#define checkasm_call_checked(func, ...)\ - (checkasm_set_signal_handler_state(1),\ - (func)(__VA_ARGS__));\ - checkasm_set_signal_handler_state(0) -#endif -#else -#define declare_new(ret, ...) -#define declare_new_float(ret, ...) -#define declare_new_emms(cpu_flags, ret, ...) -/* Call the function */ -#define checkasm_call_checked(func, ...)\ - (checkasm_set_signal_handler_state(1),\ - (func)(__VA_ARGS__));\ - checkasm_set_signal_handler_state(0) -#endif - -#ifndef declare_new_emms -#define declare_new_emms(cpu_flags, ret, ...) declare_new(ret, __VA_ARGS__) -#endif -#ifndef declare_new_float -#define declare_new_float(ret, ...) declare_new(ret, __VA_ARGS__) -#endif - -typedef struct CheckasmPerf { - int sysfd; - uint64_t cycles; - int iterations; -} CheckasmPerf; - -#if defined(AV_READ_TIME) || CONFIG_LINUX_PERF || CONFIG_MACOS_KPERF - -#if CONFIG_LINUX_PERF -#define PERF_START(t) do { \ - ioctl(sysfd, PERF_EVENT_IOC_RESET, 0); \ - ioctl(sysfd, PERF_EVENT_IOC_ENABLE, 0); \ -} while (0) -#define PERF_STOP(t) do { \ - int ret; \ - ioctl(sysfd, PERF_EVENT_IOC_DISABLE, 0); \ - ret = read(sysfd, &t, sizeof(t)); \ - (void)ret; \ -} while (0) -#elif CONFIG_MACOS_KPERF -#define PERF_START(t) t = ff_kperf_cycles() -#define PERF_STOP(t) t = ff_kperf_cycles() - t -#else -#define PERF_START(t) t = AV_READ_TIME() -#define PERF_STOP(t) t = AV_READ_TIME() - t -#endif - -#define CALL4(...)\ - do {\ - tfunc(__VA_ARGS__); \ - tfunc(__VA_ARGS__); \ - tfunc(__VA_ARGS__); \ - tfunc(__VA_ARGS__); \ - } while (0) - -#define CALL16(...)\ - do {\ - CALL4(__VA_ARGS__); \ - CALL4(__VA_ARGS__); \ - CALL4(__VA_ARGS__); \ - CALL4(__VA_ARGS__); \ - } while (0) - -/* Benchmark the function */ -#define bench(func, ...)\ - do {\ - if (checkasm_bench_func()) {\ - struct CheckasmPerf *perf = checkasm_get_perf_context();\ - av_unused const int sysfd = perf->sysfd;\ - func_type *tfunc = func;\ - uint64_t tsum = 0;\ - uint64_t ti, tcount = 0;\ - uint64_t t = 0; \ - const uint64_t truns = FFMAX(bench_runs >> 3, 1);\ - checkasm_set_signal_handler_state(1);\ - for (ti = 0; ti < truns; ti++) {\ - PERF_START(t);\ - CALL16(__VA_ARGS__);\ - CALL16(__VA_ARGS__);\ - PERF_STOP(t);\ - if (t*tcount <= tsum*4 && ti > 0) {\ - tsum += t;\ - tcount++;\ - }\ - }\ - emms_c();\ - perf->cycles += tsum;\ - perf->iterations += tcount;\ - checkasm_set_signal_handler_state(0);\ - }\ - } while (0) -#else -#define bench(func, ...) while(0) -#define PERF_START(t) while(0) -#define PERF_STOP(t) while(0) -#endif - -#define bench_new(...) bench(func_new, __VA_ARGS__) - -#define BUF_RECT(type, name, w, h) \ - LOCAL_ALIGNED_32(type, name##_buf, [((h)+32)*(FFALIGN(w,64)+64) + 64]); \ - av_unused ptrdiff_t name##_stride = sizeof(type)*(FFALIGN(w,64)+64); \ - av_unused int name##_buf_h = (h)+32; \ - type *name = name##_buf + (FFALIGN(w,64)+64)*16 + 64 - -#define PIXEL_RECT(name, w, h) \ - LOCAL_ALIGNED_32(uint8_t, name##_buf, [sizeof(uint16_t) * (((h)+32)*(FFALIGN(w,64)+64) + 64)],); \ - av_unused ptrdiff_t name##_stride = sizeof(uint16_t) * (FFALIGN(w,64)+64); \ - av_unused int name##_buf_h = (h)+32; \ - uint8_t *name = name##_buf + (FFALIGN(w,64)+64)*16 + 64 - -#define CLEAR_BUF_RECT(name) \ - memset(name##_buf, 0x99, name##_stride * name##_buf_h + 64) -#define CLEAR_PIXEL_RECT(name) \ - CLEAR_BUF_RECT(name) - -#define DECL_CHECKASM_CHECK_FUNC(type) \ -int checkasm_check_##type(const char *file, int line, \ - const type *buf1, ptrdiff_t stride1, \ - const type *buf2, ptrdiff_t stride2, \ - int w, int h, const char *name, \ - int align_w, int align_h, \ - int padding) - -DECL_CHECKASM_CHECK_FUNC(uint8_t); -DECL_CHECKASM_CHECK_FUNC(uint16_t); -DECL_CHECKASM_CHECK_FUNC(uint32_t); -DECL_CHECKASM_CHECK_FUNC(int16_t); -DECL_CHECKASM_CHECK_FUNC(int32_t); - -int checkasm_check_float_ulp(const char *file, int line, - const float *buf1, ptrdiff_t stride1, - const float *buf2, ptrdiff_t stride2, - int w, int h, const char *name, - unsigned max_ulp, int align_w, int align_h, - int padding); - -#define PASTE(a,b) a ## b -#define CONCAT(a,b) PASTE(a,b) - -#define checkasm_check2(prefix, ...) CONCAT(checkasm_check_, prefix)(__FILE__, __LINE__, __VA_ARGS__) -#define checkasm_check(prefix, ...) checkasm_check2(prefix, __VA_ARGS__, 0, 0, 0) -/* Check a pointer from BUF_RECT, checking whether there have been - * writes outside of the designated area. */ -#define checkasm_check_padded(...) \ - checkasm_check2(__VA_ARGS__, 1, 1, 8) -/* Check a pointer from BUF_RECT, checking whether there have been - * writes outside of the designated area. Allow writing slightly past the - * end of the buffer, by aligning w/h to align_w/align_h, and checking - * for overwrites outside of that. */ -#define checkasm_check_padded_align(...) \ - checkasm_check2(__VA_ARGS__, 8) +#define CLEAR_PIXEL_RECT(name) CLEAR_BUF_RECT(name##_16) /* This assumes that there is a local variable named "bit_depth". * For tests that don't have that and only operate on a single @@ -515,15 +187,6 @@ int checkasm_check_float_ulp(const char *file, int line, buf2 ## _16, stride2, \ __VA_ARGS__)) -#define randomize_stddev(buf, size, stddev) \ -do { \ - double bmg[2]; \ - for (int i = 0; i < size; i += 2) { \ - av_bmg_get(&checkasm_lfg, bmg); \ - (buf)[i] = bmg[0] * (stddev); \ - (buf)[i + 1] = bmg[1] * (stddev); \ - } \ -} while (0); -#define randomize_stddev_dbl(...) randomize_stddev(__VA_ARGS__) +typedef uint8_t pixel; #endif /* TESTS_CHECKASM_CHECKASM_H */ diff --git a/tests/checkasm/crc.c b/tests/checkasm/crc.c index 4202c7784c..94ade9ce37 100644 --- a/tests/checkasm/crc.c +++ b/tests/checkasm/crc.c @@ -38,7 +38,7 @@ static void check_crc(const AVCRC *table_new, const char *name, unsigned idx) { declare_func(uint32_t, const AVCRC *ctx, uint32_t crc, const uint8_t *buffer, size_t length); - const AVCRC *table_ref = check_key((AVCRC*)table_new, "crc_%s", name); + const AVCRC *table_ref = (const AVCRC *) check_key((CheckasmKey) table_new, "crc_%s", name); if (!table_ref) return; diff --git a/tests/checkasm/riscv/checkasm.S b/tests/checkasm/riscv/checkasm.S deleted file mode 100644 index 44f32e440b..0000000000 --- a/tests/checkasm/riscv/checkasm.S +++ /dev/null @@ -1,337 +0,0 @@ -/**************************************************************************** - * Copyright © 2022 Rémi Denis-Courmont. - * - * This file is part of FFmpeg. - * - * FFmpeg is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * FFmpeg is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111, USA. - *****************************************************************************/ - -#include "libavutil/riscv/asm.S" - -#if (__riscv_xlen == 32) -.macro lx rd, addr - lw \rd, \addr -.endm - -.macro sx rs, addr - sw \rs, \addr -.endm -#define REG_MAGIC 0xdeadbeef -#elif (__riscv_xlen == 64) -.macro lx rd, addr - ld \rd, \addr -.endm - -.macro sx rs, addr - sd \rs, \addr -.endm -#define REG_MAGIC 0xdeadbeef0badf00d -#else -.macro lx rd, addr - lq \rd, \addr -.endm - -.macro sx rs, addr - sq \rs, \addr -.endm -#define REG_MAGIC 0xdeadbeef0badf00daaaabbbbccccdddd -#endif -#define XSZ (__riscv_xlen / 8) -#define STACK_ALIGN 16 -#define STACK_SPACE(sz) (((sz) + (STACK_ALIGN - 1)) & -STACK_ALIGN) - -#if defined(__riscv_float_abi_soft) -.macro flf rd, addr -.endm -.macro fsf rs, addr -.endm -#define FSZ 0 -#elif defined(__riscv_float_abi_single) -.macro flf rd, addr - flw \rd, \addr -.endm -.macro fsf rs, addr - fsw \rs, \addr -.endm -#define FSZ 4 -#elif defined(__riscv_float_abi_double) -.macro flf rd, addr - fld \rd, \addr -.endm -.macro fsf rs, addr - fsd \rs, \addr -.endm -#define FSZ 8 -#elif defined(__riscv_float_abi_quad) -.macro flf rd, addr - flq \rd, \addr -.endm -.macro fsf rs, addr - fsq \rs, \addr -.endm -#define FSZ 16 -#else -#error "Unknown float ABI" -#endif - - .pushsection .tbss, "waT" - .align 4 -.Lchecked_func: - .fill 1, XSZ, 0 - .align 4 -.Lsaved_xregs: - .fill 4 + 12, XSZ, 0 // RA, SP, GP, TP, S0-S11 - .align 4 -.Lsaved_fregs: - .fill 12, FSZ, 0 // FS0-FS11 - .fill 1, XSZ, 0 // RA - .popsection - -func checkasm_set_function - lpad 0 - la.tls.ie t0, .Lchecked_func - add t0, tp, t0 - sx a0, (t0) - ret -endfunc - -func checkasm_get_wrapper, v - lpad 0 - addi sp, sp, -STACK_SPACE(2 * XSZ) - sx fp, (sp) - sx ra, XSZ(sp) - addi fp, sp, STACK_SPACE(2 * XSZ) - - call av_get_cpu_flags - andi t0, a0, 8 /* AV_CPU_FLAG_RVV_I32 */ -#ifdef __riscv_float_abi_soft - andi t1, a0, 16 /* AV_CPU_FLAG_RVV_F32 (implies F and Zve32x) */ - lla a0, checkasm_checked_call_i - beqz t0, 1f - lla a0, checkasm_checked_call_iv - beqz t1, 1f -#else - lla a0, checkasm_checked_call_if - beqz t0, 1f -#endif - lla a0, checkasm_checked_call_ifv -1: - lx ra, XSZ(sp) - lx fp, (sp) - addi sp, sp, 16 - ret -endfunc - - .pushsection ".rodata", "a" -.Lfail_s_reg: - .asciz "callee-saved integer register S%d clobbered" -.Lfail_fs_reg: - .asciz "callee-saved floating-point register FS%d clobbered" -.Lfail_rsvd_reg: - .asciz "unallocatable register %cP clobbered" -#if defined(__riscv_float_abi_soft) || defined(__riscv_float_abi_single) - .align 2 -.Lbad_float: - .single 123456789 -#elif defined(__riscv_float_abi_double) - .align 3 -.Lbad_float: - .double 123456789 -#elif defined(__riscv_float_abi_quad) - .align 4 -.Lbad_float: - .ldouble 123456789 -#endif - .popsection - -func checkasm_checked_call_i - /* <-- Entry point without the Vector extension --> */ - lpad 0 - /* Save RA, unallocatable and callee-saved registers */ - la.tls.ie t0, .Lsaved_xregs - add t0, tp, t0 - sx ra, (t0) - sx sp, 1 * XSZ(t0) - sx gp, 2 * XSZ(t0) - sx tp, 3 * XSZ(t0) - .irp n, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 - sx s\n, (4 + \n) * XSZ(t0) - .endr - - /* Clobber the stack space right below SP */ - li t1, REG_MAGIC - li t0, 16 -1: - addi sp, sp, -XSZ - addi t0, t0, -1 - sx t1, (sp) - bnez t0, 1b - - addi sp, sp, 16 * XSZ - # Clobber temporary registers (except T2, FE-CFI label) - .irp n, 0, 1, 3, 4, 5, 6 - mv t\n, t1 - .endr - # Clobber the saved registers - .irp n, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 - mv s\n, t1 - .endr - - /* Call the tested function */ - la.tls.ie t0, .Lchecked_func - add t0, tp, t0 - lx t3, (t0) - sx zero, (t0) - jalr t3 - - /* Check special register values */ - la.tls.ie t0, .Lsaved_xregs - add t0, tp, t0 - lx t2, 1 * XSZ(t0) // SP - lx t3, 2 * XSZ(t0) // GP - lx t4, 3 * XSZ(t0) // TP - li t1, 'S' - bne t2, sp, .Lfail_xp - li t1, 'G' - bne t3, gp, .Lfail_xp - li t1, 'T' - bne t4, tp, .Lfail_xp - - /* Check value of saved registers */ - li t0, REG_MAGIC - .irp n, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 - li t1, \n - bne t0, s\n, .Lfail_s - .endr - -4: - /* Restore RA and saved registers */ - la.tls.ie t0, .Lsaved_xregs - add t0, tp, t0 - lx ra, (t0) - .irp n, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 - lx s\n, (4 + \n) * XSZ(t0) - .endr - ret - -.Lfail_xp: - # checkasm_fail_func() needs valid SP, GP and TP. Restore them. - lx sp, 1 * XSZ(t0) - lx gp, 2 * XSZ(t0) - lx tp, 3 * XSZ(t0) - lla a0, .Lfail_rsvd_reg - mv a1, t1 - call checkasm_fail_func - j 4b - -.Lfail_s: - lla a0, .Lfail_s_reg - mv a1, t1 - call checkasm_fail_func - j 4b -endfunc - -#ifndef __riscv_float_abi_soft -func checkasm_checked_call_if, f - lpad 0 - # Save callee-saved floating point registers and RA - la.tls.ie t0, .Lsaved_fregs - add t0, t0, tp - lla t1, .Lbad_float - sd ra, 12 * FSZ(t0) - .irp n, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 - fsf fs\n, \n * FSZ(t0) - .endr - # Clobber the saved and temporary floating point registers - .irp n, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 - flf ft\n, (t1) - flf fs\n, (t1) - .endr - - jal checkasm_checked_call_i - - # Check value of saved registers - lla t1, .Lbad_float - flf ft0, (t1) - .irp n, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 - li t1, \n -#if defined(__riscv_float_abi_single) - feq.s t0, ft0, fs\n -#elif defined(__riscv_float_abi_double) - feq.d t0, ft0, fs\n -#else - feq.q t0, ft0, fs\n -#endif - beqz t0, .Lfail_fs - .endr - -1: # Restore callee-saved floating point registers and RA - la.tls.ie t0, .Lsaved_fregs - add t0, t0, tp - .irp n, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 - flf fs\n, \n * FSZ(t0) - .endr - ld ra, 12 * FSZ(t0) - ret - -.Lfail_fs: - lla a0, .Lfail_fs_reg - mv a1, t1 - call checkasm_fail_func - j 1b -endfunc -#else -func checkasm_checked_call_if, f - lpad 0 - lla t1, .Lbad_float - # Clobber all floating point registers (soft float ABI). - .irp n, 0, 1, 2, 3, 4, 5, 6, 7 - flw fa\n, (t1) - .endr - .irp n, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 - flw ft\n, (t1) - flw fs\n, (t1) - .endr - j checkasm_checked_call_i -endfunc - -func checkasm_checked_call_iv, zve32x - lpad 0 - jal t0, .Lclobber_v - j checkasm_checked_call_i -endfunc -#endif - -func checkasm_checked_call_ifv, zve32x - lpad 0 - jal t0, .Lclobber_v - j checkasm_checked_call_if - -.Lclobber_v: - # Clobber the vector registers - vsetvli t1, zero, e32, m8, ta, ma - li t1, -0xdeadbeef - vmv.v.x v0, t1 - vmv.v.x v8, t1 - vmv.v.x v16, t1 - vmv.v.x v24, t1 - # Clobber the vector configuration - li t1, 0 /* Vector length: zero */ - li t3, -4 /* Vector type: illegal */ - vsetvl zero, t1, t3 - csrwi vxrm, 3 /* Rounding mode: round-to-odd */ - csrwi vxsat, 1 /* Saturation: encountered */ - jr t0 -endfunc diff --git a/tests/checkasm/sw_ops.c b/tests/checkasm/sw_ops.c index 0c2071f21e..ca6882dae7 100644 --- a/tests/checkasm/sw_ops.c +++ b/tests/checkasm/sw_ops.c @@ -124,7 +124,7 @@ static void check_compiled(const char *name, const SwsOpBackend *backend, */ uintptr_t id = (uintptr_t) comp_new->func; id ^= (id << 6) + (id >> 2) + 0x9e3779b97f4a7c15 + comp_new->cpu_flags; - if (!check_key((void *) id, "%s/%s", name, backend->name)) + if (!check_key(id, "%s/%s", name, backend->name)) return; declare_func(void, const SwsOpExec *, const void *, int bx, int y, int bx_end, int y_end); @@ -310,7 +310,7 @@ static void check_ops(const char *name, const unsigned ranges[NB_PLANES], if (backend->hw_format != AV_PIX_FMT_NONE || backend == backend_ref) continue; - if (!av_get_cpu_flags()) { + if (!checkasm_get_cpu_info()) { /* Also test once with the existing C reference to set the baseline */ check_compiled(name, backend, read_op, write_op, ranges, &comp_ref, &comp_ref); } diff --git a/tests/checkasm/vp8dsp.c b/tests/checkasm/vp8dsp.c index 4d6704d5a9..3081820c59 100644 --- a/tests/checkasm/vp8dsp.c +++ b/tests/checkasm/vp8dsp.c @@ -309,7 +309,7 @@ static void check_mc(VP8DSPContext *d) CLEAR_BUF_RECT(dst1); call_ref(dst0, dst0_stride, src, SRC_BUF_STRIDE, height, mx, my); call_new(dst1, dst1_stride, src, SRC_BUF_STRIDE, height, mx, my); - checkasm_check_padded(uint8_t, dst0, dst0_stride, dst1, dst1_stride, size, height, "dst"); + checkasm_check_rect_padded(dst0, dst0_stride, dst1, dst1_stride, size, height, "dst"); bench_new(dst1, size, src, SRC_BUF_STRIDE, height, mx, my); } } diff --git a/tests/checkasm/x86/checkasm.asm b/tests/checkasm/x86/checkasm.asm deleted file mode 100644 index ab11bcba64..0000000000 --- a/tests/checkasm/x86/checkasm.asm +++ /dev/null @@ -1,244 +0,0 @@ -;***************************************************************************** -;* Assembly testing and benchmarking tool -;* Copyright (c) 2008 Loren Merritt -;* Copyright (c) 2012 Henrik Gramner -;* -;* This file is part of FFmpeg. -;* -;* FFmpeg is free software; you can redistribute it and/or modify -;* it under the terms of the GNU General Public License as published by -;* the Free Software Foundation; either version 2 of the License, or -;* (at your option) any later version. -;* -;* FFmpeg is distributed in the hope that it will be useful, -;* but WITHOUT ANY WARRANTY; without even the implied warranty of -;* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -;* GNU General Public License for more details. -;* -;* You should have received a copy of the GNU General Public License -;* along with this program; if not, write to the Free Software -;* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111, USA. -;***************************************************************************** - -%define private_prefix checkasm -%include "libavutil/x86/x86inc.asm" - -SECTION_RODATA - -error_message: db "failed to preserve register", 0 -error_message_emms: db "failed to issue emms", 0 - -%if ARCH_X86_64 -; just random numbers to reduce the chance of incidental match -ALIGN 16 -x6: dq 0x1a1b2550a612b48c,0x79445c159ce79064 -x7: dq 0x2eed899d5a28ddcd,0x86b2536fcd8cf636 -x8: dq 0xb0856806085e7943,0x3f2bf84fc0fcca4e -x9: dq 0xacbd382dcf5b8de2,0xd229e1f5b281303f -x10: dq 0x71aeaff20b095fd9,0xab63e2e11fa38ed9 -x11: dq 0x89b0c0765892729a,0x77d410d5c42c882d -x12: dq 0xc45ea11a955d8dd5,0x24b3c1d2a024048b -x13: dq 0x2e8ec680de14b47c,0xdd7b8919edd42786 -x14: dq 0x135ce6888fa02cbf,0x11e53e2b2ac655ef -x15: dq 0x011ff554472a7a10,0x6de8f4c914c334d5 -n7: dq 0x21f86d66c8ca00ce -n8: dq 0x75b6ba21077c48ad -n9: dq 0xed56bb2dcb3c7736 -n10: dq 0x8bda43d3fd1a7e06 -n11: dq 0xb64a9c9e5d318408 -n12: dq 0xdf9a54b303f1d3a3 -n13: dq 0x4a75479abd64e097 -n14: dq 0x249214109d5d1c88 -%endif - -SECTION .text - -cextern fail_func - -; max number of args used by any asm function. -; (max_args % 4) must equal 3 for stack alignment -%define max_args 15 - -%if ARCH_X86_64 - -;----------------------------------------------------------------------------- -; int checkasm_stack_clobber(uint64_t clobber, ...) -;----------------------------------------------------------------------------- -cglobal stack_clobber, 1,2 - ; Clobber the stack with junk below the stack pointer - %define argsize (max_args+6)*8 - SUB rsp, argsize - mov r1, argsize-8 -.loop: - mov [rsp+r1], r0 - sub r1, 8 - jge .loop - ADD rsp, argsize - RET - -%if WIN64 - %assign free_regs 7 - DECLARE_REG_TMP 4 -%else - %assign free_regs 9 - DECLARE_REG_TMP 7 -%endif - -%macro report_fail 1 - mov r9, rax - mov r10, rdx - lea r0, [%1] - xor eax, eax - call fail_func - mov rdx, r10 - mov rax, r9 -%endmacro - -;----------------------------------------------------------------------------- -; void checkasm_checked_call(void *func, ...) -;----------------------------------------------------------------------------- -INIT_XMM -%macro CHECKED_CALL 0-1 -cglobal checked_call%1, 2,15,16,max_args*8+8 - mov t0, r0 - - ; All arguments have been pushed on the stack instead of registers in order to - ; test for incorrect assumptions that 32-bit ints are zero-extended to 64-bit. - mov r0, r6mp - mov r1, r7mp - mov r2, r8mp - mov r3, r9mp -%if UNIX64 - mov r4, r10mp - mov r5, r11mp - %assign i 6 - %rep max_args-6 - mov r9, [rsp+stack_offset+(i+1)*8] - mov [rsp+(i-6)*8], r9 - %assign i i+1 - %endrep -%else ; WIN64 - %assign i 4 - %rep max_args-4 - mov r9, [rsp+stack_offset+(i+7)*8] - mov [rsp+i*8], r9 - %assign i i+1 - %endrep - - ; Move possible floating-point arguments to the correct registers - movq m0, r0 - movq m1, r1 - movq m2, r2 - movq m3, r3 - - %assign i 6 - %rep 16-6 - mova m %+ i, [x %+ i] - %assign i i+1 - %endrep -%endif - -%assign i 14 -%rep 15-free_regs - mov r %+ i, [n %+ i] - %assign i i-1 -%endrep - call t0 -%assign i 14 -%rep 15-free_regs - xor r %+ i, [n %+ i] - or r14, r %+ i - %assign i i-1 -%endrep - -%if WIN64 - %assign i 6 - %rep 16-6 - pxor m %+ i, [x %+ i] - por m6, m %+ i - %assign i i+1 - %endrep - packsswb m6, m6 - movq r5, m6 - or r14, r5 -%endif - - ; Call fail_func() with a descriptive message to mark it as a failure - ; if the called function didn't preserve all callee-saved registers. - ; Save the return value located in rdx:rax first to prevent clobbering. - jz .clobber_ok - report_fail error_message -.clobber_ok: -%ifidn %1, _emms - emms -%elifnidn %1, _float - fstenv [rsp] - cmp word [rsp + 8], 0xffff - je .emms_ok - report_fail error_message_emms - emms -.emms_ok: -%endif - RET -%endmacro - -%else - -; just random numbers to reduce the chance of incidental match -%define n3 dword 0x6549315c -%define n4 dword 0xe02f3e23 -%define n5 dword 0xb78d0d1d -%define n6 dword 0x33627ba7 - -%macro report_fail 1 - mov r3, eax - mov r4, edx - lea r0, [%1] - mov [esp], r0 - call fail_func - mov edx, r4 - mov eax, r3 -%endmacro - -%macro CHECKED_CALL 0-1 -;----------------------------------------------------------------------------- -; void checkasm_checked_call(void *func, ...) -;----------------------------------------------------------------------------- -cglobal checked_call%1, 1,7 - mov r3, n3 - mov r4, n4 - mov r5, n5 - mov r6, n6 -%rep max_args - PUSH dword [esp+20+max_args*4] -%endrep - call r0 - xor r3, n3 - xor r4, n4 - xor r5, n5 - xor r6, n6 - or r3, r4 - or r5, r6 - or r3, r5 - jz .clobber_ok - report_fail error_message -.clobber_ok: -%ifidn %1, _emms - emms -%elifnidn %1, _float - fstenv [esp] - cmp word [esp + 8], 0xffff - je .emms_ok - report_fail error_message_emms - emms -.emms_ok: -%endif - add esp, max_args*4 - RET -%endmacro - -%endif ; ARCH_X86_64 - -CHECKED_CALL -CHECKED_CALL _emms -CHECKED_CALL _float