aboutsummaryrefslogtreecommitdiffstats
path: root/tests/checkasm/checkasm.h
diff options
context:
space:
mode:
authorMartin Storsjö <martin@martin.st>2023-12-14 14:57:36 +0200
committerMartin Storsjö <martin@martin.st>2024-01-11 14:48:53 +0200
commit65739691b90012c9d93b2e5e0e89a55d8de9eb7b (patch)
tree85fbbc9153914ca62ba808f20c3894157ef9bf9b /tests/checkasm/checkasm.h
parent5e751dabc5d5e53c80e32e56f154ee5944e8f263 (diff)
downloadffmpeg-65739691b90012c9d93b2e5e0e89a55d8de9eb7b.tar.gz
checkasm: Generalize crash handling
This replaces the riscv specific handling from 7212466e735aa187d82f51dadbce957fe3da77f0 (which essentially is reverted), with a different implementation of the same (plus a bit more), based on the corresponding feature in dav1d's checkasm, supporting both Unix and Windows. See in particular the dav1d commits 0b6ee30eab2400e4f85b735ad29a68a842c34e21, 0421f787ea592fd2cc74c887f20b8dc31393788b, 8501a4b20135f93a4c3b426468e2240e872949c5 and d23e87f7aee26ddcf5f7a2e185112031477599a7, authored by Henrik Gramner. The overall approach compared to the existing implementation for riscv is the same; set up a signal handler, store the state with sigsetjmp, jump out of the crashing function with siglongjmp. The main difference is in what happens when the signal handler is invoked. In the previous implementation, it would resume from right before calling the crashing function, and then skip that call based on the setjmp return value. In the imported implementation from dav1d, we return to right before the check_func() call, which will skip testing the current function (as the pointer is the same as it was before). Other differences are: - Support for other signal handling mechanisms (Windows AddVectoredExceptionHandler) - Using RtlCaptureContext/RtlRestoreContext instead of setjmp/longjmp on Windows with SEH - Only catching signals once per function - if more than one signal is delivered before signal handling is reenabled, any signal is handled as it would without our handler - Not using an arch specific signal handler written in assembly Signed-off-by: Martin Storsjö <martin@martin.st>
Diffstat (limited to 'tests/checkasm/checkasm.h')
-rw-r--r--tests/checkasm/checkasm.h89
1 files changed, 69 insertions, 20 deletions
diff --git a/tests/checkasm/checkasm.h b/tests/checkasm/checkasm.h
index 11f0487731..bd4d084bcb 100644
--- a/tests/checkasm/checkasm.h
+++ b/tests/checkasm/checkasm.h
@@ -23,7 +23,6 @@
#ifndef TESTS_CHECKASM_CHECKASM_H
#define TESTS_CHECKASM_CHECKASM_H
-#include <setjmp.h>
#include <stdint.h>
#include "config.h"
@@ -43,6 +42,37 @@
#include "libavutil/lfg.h"
#include "libavutil/timer.h"
+#ifdef _WIN32
+#include <windows.h>
+#if ARCH_X86_32
+#include <setjmp.h>
+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
+#else
+#include <setjmp.h>
+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);
@@ -106,9 +136,11 @@ struct CheckasmPerf;
void *checkasm_check_func(void *func, const char *name, ...) av_printf_format(2, 3);
int checkasm_bench_func(void);
void checkasm_fail_func(const char *msg, ...) av_printf_format(1, 2);
-void checkasm_fail_signal(int signum);
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;
/* float compare utilities */
int float_near_ulp(float a, float b, unsigned max_ulp);
@@ -132,7 +164,7 @@ static av_unused void *func_ref, *func_new;
#define BENCH_RUNS 1000 /* Trade-off between accuracy and speed */
/* Decide whether or not the specified function needs to be tested */
-#define check_func(func, ...) (func_ref = checkasm_check_func((func_new = func), __VA_ARGS__))
+#define check_func(func, ...) (checkasm_save_context(), func_ref = checkasm_check_func((func_new = func), __VA_ARGS__))
/* Declare the function prototype. The first argument is the return value, the remaining
* arguments are the function parameters. Naming parameters is optional. */
@@ -147,7 +179,10 @@ static av_unused void *func_ref, *func_new;
#define report checkasm_report
/* Call the reference function */
-#define call_ref(...) ((func_type *)func_ref)(__VA_ARGS__)
+#define call_ref(...)\
+ (checkasm_set_signal_handler_state(1),\
+ ((func_type *)func_ref)(__VA_ARGS__));\
+ checkasm_set_signal_handler_state(0)
#if ARCH_X86 && HAVE_X86ASM
/* Verifies that clobbered callee-saved registers are properly saved and restored
@@ -180,16 +215,21 @@ void checkasm_stack_clobber(uint64_t clobber, ...);
((cpu_flags) & av_get_cpu_flags()) ? (void *)checkasm_checked_call_emms : \
(void *)checkasm_checked_call;
#define CLOB (UINT64_C(0xdeadbeefdeadbeef))
-#define call_new(...) (checkasm_stack_clobber(CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,\
+#define call_new(...) (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_new, 0, 0, 0, 0, 0, __VA_ARGS__))
+ checked_call(func_new, 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 call_new(...) checked_call(func_new, __VA_ARGS__)
+#define call_new(...)\
+ (checkasm_set_signal_handler_state(1),\
+ checked_call(func_new, __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.
@@ -201,7 +241,10 @@ 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 call_new(...) checked_call(func_new, 0, __VA_ARGS__, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0)
+#define call_new(...) \
+ (checkasm_set_signal_handler_state(1),\
+ checked_call(func_new, 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, ...);
@@ -210,35 +253,39 @@ void checkasm_checked_call(void *func, ...);
int, int, int, int, int, int, int)\
= (void *)checkasm_checked_call;
#define CLOB (UINT64_C(0xdeadbeefdeadbeef))
-#define call_new(...) (checkasm_stack_clobber(CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,\
+#define call_new(...) (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_new, 0, 0, 0, 0, 0, 0, 0, __VA_ARGS__,\
- 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0))
+ 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 *, sigjmp_buf);
+void checkasm_set_function(void *);
void *checkasm_get_wrapper(void);
-void checkasm_handle_signal(int signum);
#if HAVE_RV && (__riscv_xlen == 64) && defined (__riscv_d)
#define declare_new(ret, ...) \
- int checked_call_signum = 0; \
- sigjmp_buf checked_call_jb; \
ret (*checked_call)(__VA_ARGS__) = checkasm_get_wrapper();
#define call_new(...) \
- (checkasm_set_function(func_new, checked_call_jb), \
- (checked_call_signum = sigsetjmp(checked_call_jb, 1)) == 0 \
- ? checked_call(__VA_ARGS__) \
- : (checkasm_fail_signal(checked_call_signum), 0))
+ (checkasm_set_signal_handler_state(1),\
+ checkasm_set_function(func_new), checked_call(__VA_ARGS__));\
+ checkasm_set_signal_handler_state(0)
#else
#define declare_new(ret, ...)
-#define call_new(...) ((func_type *)func_new)(__VA_ARGS__)
+#define call_new(...)\
+ (checkasm_set_signal_handler_state(1),\
+ ((func_type *)func_new)(__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 call_new(...) ((func_type *)func_new)(__VA_ARGS__)
+#define call_new(...)\
+ (checkasm_set_signal_handler_state(1),\
+ ((func_type *)func_new)(__VA_ARGS__));\
+ checkasm_set_signal_handler_state(0)
#endif
#ifndef declare_new_emms
@@ -285,6 +332,7 @@ typedef struct CheckasmPerf {
uint64_t tsum = 0;\
int ti, tcount = 0;\
uint64_t t = 0; \
+ checkasm_set_signal_handler_state(1);\
for (ti = 0; ti < BENCH_RUNS; ti++) {\
PERF_START(t);\
tfunc(__VA_ARGS__);\
@@ -300,6 +348,7 @@ typedef struct CheckasmPerf {
emms_c();\
perf->cycles += t;\
perf->iterations++;\
+ checkasm_set_signal_handler_state(0);\
}\
} while (0)
#else