@@ -42,6 +42,11 @@
#include <io.h>
#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 <windows.h>
#define COLOR_RED FOREGROUND_RED
@@ -329,6 +334,7 @@ static struct {
const char *cpu_flag_name;
const char *test_name;
int verbose;
+ volatile sig_atomic_t catch_signals;
} state;
/* PRNG state */
@@ -630,6 +636,61 @@ static CheckasmFunc *get_func(CheckasmFunc **root, const char *name)
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
+#else
+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
+
/* Perform tests and benchmarks for the specified cpu flag if supported by the host */
static void check_cpu_flag(const char *name, int flag)
{
@@ -740,18 +801,20 @@ int main(int argc, char *argv[])
unsigned int seed = av_get_random_seed();
int i, ret = 0;
+#ifdef _WIN32
+#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
+ AddVectoredExceptionHandler(0, signal_handler);
+#endif
+#else
+ 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 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 ARCH_RISCV && HAVE_RV
- struct sigaction act = {
- .sa_handler = checkasm_handle_signal,
- .sa_flags = 0,
- };
-
- sigaction(SIGILL, &act, NULL);
-#endif
if (!tests[0].func || !cpus[0].flag) {
fprintf(stderr, "checkasm: no tests to perform\n");
@@ -879,13 +942,22 @@ void checkasm_fail_func(const char *msg, ...)
}
}
-void checkasm_fail_signal(int signum)
-{
+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", signum, strsignal(signum));
+ checkasm_fail_func("fatal signal %d: %s", s, strsignal(s));
#else
- checkasm_fail_func("fatal signal %d", signum);
+ 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 */
@@ -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
@@ -41,7 +41,6 @@ endconst
checked_func:
.quad 0
- .quad 0
saved_regs:
/* Space to spill RA, SP, GP, TP, S0-S11 and FS0-FS11 */
@@ -53,7 +52,6 @@ func checkasm_set_function
la.tls.ie t0, checked_func
add t0, tp, t0
sd a0, (t0)
- sd a1, 8(t0)
ret
endfunc
@@ -177,14 +175,4 @@ func checkasm_get_wrapper, v
call checkasm_fail_func
j 4b
endfunc
-
-func checkasm_handle_signal
- mv a1, a0
- la.tls.ie a0, checked_func
- add a0, tp, a0
- ld a0, 8(a0)
- beqz a0, 8f
- tail siglongjmp
-8: tail abort /* No jump buffer to go to */
-endfunc
#endif