tests/checkasm: switch to shared libcheckasm implementation

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ö <martin@martin.st>
Signed-off-by: Niklas Haas <git@haasn.dev>
This commit is contained in:
Niklas Haas
2026-06-04 11:06:44 +02:00
parent 21ac0b276e
commit 3b1d7cd1f7
13 changed files with 142 additions and 2318 deletions
+2
View File
@@ -39,6 +39,8 @@
/ffmpeg
/ffplay
/ffprobe
/checkasm_config_generated.asm
/checkasm_config_generated.h
/config.asm
/config.h
/config_components.asm
+1 -1
View File
@@ -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 \
Vendored
+66
View File
@@ -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 <<EOF
/* Automatically generated by configure - do not modify! */
#ifndef FFMPEG_CHECKASM_CONFIG_GENERATED_H
#define FFMPEG_CHECKASM_CONFIG_GENERATED_H
#define CHECKASM_VERSION "1.2.0"
EOF
print_config HAVE_ "$TMPH" elf_aux_info getauxval ioctl isatty linux_perf pthread_np_h pthread_setaffinity_np sigaction siglongjmp clock_gettime prctl
print_config CHECKASM_ARCH_ "$TMPH" $ARCH_LIST
# The callcheck harness requires x86asm/rv support on these platforms, so
# override arch-autodetection in the header to avoid tests depending on them
if enabled x86 && disabled x86asm; then
cat >> $TMPH <<EOF
#undef CHECKASM_ARCH_X86
#undef CHECKASM_ARCH_X86_32
#undef CHECKASM_ARCH_X86_64
#define CHECKASM_ARCH_X86 0
EOF
fi
if enabled riscv && disabled rv; then
cat >> $TMPH <<EOF
#undef CHECKASM_ARCH_RISCV
#define CHECKASM_ARCH_RISCV 0
EOF
fi
if enabled aarch64; then
print_config HAVE_ "$TMPH" as_archext_sve_directive sve
print_config HAVE_ "$TMPH" as_archext_sme_directive sme
elif enabled riscv; then
print_config HAVE_ "$TMPH" sys_hwprobe_h asm_hwprobe_h
fi
cat >> $TMPH <<EOF
#endif /* FFMPEG_CHECKASM_CONFIG_H */
EOF
cp_if_changed $TMPH checkasm_config_generated.h
if enabled x86asm; then
append config_files $TMPASM
cat > $TMPASM <<EOF
; Automatically generated by configure - do not modify!
EOF
# We pass -DPREFIX on the command line when necessary; we don't need
# to include it in the generated config header.
print_config "" "$TMPASM" pic
cp_if_changed $TMPASM checkasm_config_generated.asm
fi
cat > $TMPH <<EOF
/* Generated by ffmpeg configure */
#ifndef AVUTIL_AVCONFIG_H
+38 -7
View File
@@ -106,26 +106,57 @@ AVUTILOBJS-$(CONFIG_PIXELUTILS) += pixelutils.o
CHECKASMOBJS-$(CONFIG_AVUTIL) += $(AVUTILOBJS) $(AVUTILOBJS-yes)
CHECKASMOBJS-$(ARCH_AARCH64) += aarch64/checkasm.o
CHECKASMOBJS-$(HAVE_ARMV5TE_EXTERNAL) += arm/checkasm.o
CHECKASMOBJS-$(HAVE_RV) += riscv/checkasm.o
CHECKASMOBJS-$(HAVE_X86ASM) += x86/checkasm.o
EXT_CHECKASMOBJS-$(ARCH_AARCH64) += ext/src/arm/checkasm_64.o
EXT_CHECKASMOBJS-$(ARCH_ARM) += ext/src/arm/checkasm_32.o
EXT_CHECKASMOBJS-$(ARCH_LOONGARCH) += ext/src/loongarch/checkasm.o
EXT_CHECKASMOBJS-$(HAVE_RV) += ext/src/riscv/callcheck.o
EXT_CHECKASMOBJS-$(HAVE_X86ASM) += ext/src/x86/checkasm.o
CHECKASMOBJS += $(CHECKASMOBJS-yes) checkasm.o
EXT_CHECKASMOBJS += ext/src/arm/cpu.o \
ext/src/checkasm.o \
ext/src/cpu.o \
ext/src/function.o \
ext/src/perf.o \
ext/src/perf/arm.o \
ext/src/perf/linux.o \
ext/src/perf/macos_kperf.o \
ext/src/riscv/cpu.o \
ext/src/signal.o \
ext/src/stackguard.o \
ext/src/stats.o \
ext/src/utils.o \
ext/src/x86/cpu.o \
EXT_CHECKASMOBJS += $(EXT_CHECKASMOBJS-yes)
$(EXT_CHECKASMOBJS:%=tests/checkasm/%): CFLAGS += -I$(SRC_PATH)/tests/checkasm/ext/src
$(EXT_CHECKASMOBJS:%=tests/checkasm/%): ASFLAGS += -I$(SRC_PATH)/tests/checkasm/ext/src
$(EXT_CHECKASMOBJS:%=tests/checkasm/%): X86ASMFLAGS += -I$(SRC_PATH)/tests/checkasm/ext/src
CHECKASMOBJS += $(CHECKASMOBJS-yes) checkasm.o $(EXT_CHECKASMOBJS)
CHECKASMOBJS := $(sort $(CHECKASMOBJS:%=tests/checkasm/%))
-include $(CHECKASMOBJS:.o=.d)
CHECKASMDIRS := $(sort $(dir $(CHECKASMOBJS)))
$(CHECKASMOBJS): | $(CHECKASMDIRS)
# Prepend the local checkasm include path before any potential external
# (possibly outdated) version of libcheckasm.
$(CHECKASMOBJS): CFLAGS := -I$(SRC_PATH)/tests/checkasm/ext/include $(CFLAGS)
$(CHECKASMOBJS): ASFLAGS := -I$(SRC_PATH)/tests/checkasm/ext/include $(ASFLAGS)
OUTDIRS += $(CHECKASMDIRS)
# Expose checkasm_config_generated.h/asm to checkasm tests as well, as it
# modifies the behavior of include/checkasm/header_config.h
$(CHECKASMOBJS): CFLAGS += -DCHECKASM_HAVE_GENERATED_H
$(CHECKASMOBJS): ASFLAGS += -DCHECKASM_HAVE_GENERATED_H
$(CHECKASMOBJS): X86ASMFLAGS += -DCHECKASM_HAVE_GENERATED_H
tests/checkasm/checkasm.o: CFLAGS += -Umain
CHECKASM := tests/checkasm/checkasm$(EXESUF)
$(CHECKASM): $(CHECKASMOBJS) $(FF_STATIC_DEP_LIBS)
$(call LINK,$(LDFLAGS) $(LDEXEFLAGS) $(LD_O) $(CHECKASMOBJS) $(FF_STATIC_DEP_LIBS) $(EXTRALIBS-avcodec) $(EXTRALIBS-avfilter) $(EXTRALIBS-avformat) $(EXTRALIBS-avutil) $(EXTRALIBS-swresample) $(EXTRALIBS))
$(call LINK,$(LDFLAGS) $(LDEXEFLAGS) $(LD_O) $(CHECKASMOBJS) $(FF_STATIC_DEP_LIBS) $(EXTRALIBS-avcodec) $(EXTRALIBS-avfilter) $(EXTRALIBS-avformat) $(EXTRALIBS-avutil) $(EXTRALIBS-swresample) $(EXTRALIBS) $(EXTRALIBS-checkasm))
run-checkasm: $(CHECKASM)
run-checkasm:
@@ -136,6 +167,6 @@ checkasm: $(CHECKASM)
testclean:: checkasmclean
checkasmclean:
$(RM) $(CHECKASM) $(CLEANSUFFIXES:%=tests/checkasm/%) $(CLEANSUFFIXES:%=tests/checkasm/$(ARCH)/%)
$(RM) $(CHECKASM) $(CLEANSUFFIXES:%=tests/checkasm/%) $(CLEANSUFFIXES:%=tests/checkasm/$(ARCH)/%) $(CHECKASMOBJS)
.PHONY: checkasm
-198
View File
@@ -1,198 +0,0 @@
/****************************************************************************
* Assembly testing and benchmarking tool
* Copyright (c) 2015 Martin Storsjo
* Copyright (c) 2015 Janne Grunau
*
* 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/aarch64/asm.S"
const register_init, align=4
.quad 0x21f86d66c8ca00ce
.quad 0x75b6ba21077c48ad
.quad 0xed56bb2dcb3c7736
.quad 0x8bda43d3fd1a7e06
.quad 0xb64a9c9e5d318408
.quad 0xdf9a54b303f1d3a3
.quad 0x4a75479abd64e097
.quad 0x249214109d5d1c88
.quad 0x1a1b2550a612b48c
.quad 0x79445c159ce79064
.quad 0x2eed899d5a28ddcd
.quad 0x86b2536fcd8cf636
.quad 0xb0856806085e7943
.quad 0x3f2bf84fc0fcca4e
.quad 0xacbd382dcf5b8de2
.quad 0xd229e1f5b281303f
.quad 0x71aeaff20b095fd9
.quad 0xab63e2e11fa38ed9
endconst
const error_message_register
.asciz "failed to preserve register"
error_message_stack:
.asciz "stack clobbered"
endconst
// max number of args used by any asm function.
#define MAX_ARGS 15
#define CLOBBER_STACK ((8*MAX_ARGS + 15) & ~15)
function checkasm_stack_clobber, export=1
mov x3, sp
mov x2, #CLOBBER_STACK
1:
stp x0, x1, [sp, #-16]!
subs x2, x2, #16
b.gt 1b
mov sp, x3
ret
endfunc
// + 16 for stack canary reference
#define ARG_STACK (((8*(MAX_ARGS - 8) + 15) & ~15) + 16)
function checkasm_checked_call, export=1
stp x29, x30, [sp, #-16]!
mov x29, sp
stp x19, x20, [sp, #-16]!
stp x21, x22, [sp, #-16]!
stp x23, x24, [sp, #-16]!
stp x25, x26, [sp, #-16]!
stp x27, x28, [sp, #-16]!
stp d8, d9, [sp, #-16]!
stp d10, d11, [sp, #-16]!
stp d12, d13, [sp, #-16]!
stp d14, d15, [sp, #-16]!
movrel x9, register_init
ldp d8, d9, [x9], #16
ldp d10, d11, [x9], #16
ldp d12, d13, [x9], #16
ldp d14, d15, [x9], #16
ldp x19, x20, [x9], #16
ldp x21, x22, [x9], #16
ldp x23, x24, [x9], #16
ldp x25, x26, [x9], #16
ldp x27, x28, [x9], #16
sub sp, sp, #ARG_STACK
.equ pos, 0
.rept MAX_ARGS-8
// Skip the first 8 args, that are loaded into registers
ldr x9, [x29, #16 + 8*8 + pos]
str x9, [sp, #pos]
.equ pos, pos + 8
.endr
// Fill x8-x17 with garbage. This doesn't have to be preserved,
// but avoids relying on them having any particular value.
movrel x9, register_init
ldp x10, x11, [x9], #32
ldp x12, x13, [x9], #32
ldp x14, x15, [x9], #32
ldp x16, x17, [x9], #32
ldp x8, x9, [x9]
// For stack overflows, the callee is free to overwrite the parameters
// that were passed on the stack (if any), so we can only check after
// that point. First figure out how many parameters the function
// really took on the stack:
ldr w2, [x29, #16 + 8*8 + (MAX_ARGS-8)*8]
// Load the first non-parameter value from the stack, that should be
// left untouched by the function. Store a copy of it inverted, so that
// e.g. overwriting everything with zero would be noticed.
ldr x2, [sp, x2, lsl #3]
mvn x2, x2
str x2, [sp, #ARG_STACK-8]
// Load the in-register arguments
mov x12, x0
ldp x0, x1, [x29, #16]
ldp x2, x3, [x29, #32]
ldp x4, x5, [x29, #48]
ldp x6, x7, [x29, #64]
// Call the target function
blr x12
// Load the number of stack parameters, stack canary and its reference
ldr w2, [x29, #16 + 8*8 + (MAX_ARGS-8)*8]
ldr x2, [sp, x2, lsl #3]
ldr x3, [sp, #ARG_STACK-8]
add sp, sp, #ARG_STACK
stp x0, x1, [sp, #-16]!
mvn x3, x3
cmp x2, x3
b.ne 2f
movrel x9, register_init
movi v3.8h, #0
.macro check_reg_neon reg1, reg2
ldr q1, [x9], #16
uzp1 v2.2d, v\reg1\().2d, v\reg2\().2d
eor v1.16b, v1.16b, v2.16b
orr v3.16b, v3.16b, v1.16b
.endm
check_reg_neon 8, 9
check_reg_neon 10, 11
check_reg_neon 12, 13
check_reg_neon 14, 15
uqxtn v3.8b, v3.8h
umov x3, v3.d[0]
.macro check_reg reg1, reg2
ldp x0, x1, [x9], #16
eor x0, x0, \reg1
eor x1, x1, \reg2
orr x3, x3, x0
orr x3, x3, x1
.endm
check_reg x19, x20
check_reg x21, x22
check_reg x23, x24
check_reg x25, x26
check_reg x27, x28
cbz x3, 0f
movrel x0, error_message_register
b 1f
2:
movrel x0, error_message_stack
1:
bl X(checkasm_fail_func)
0:
ldp x0, x1, [sp], #16
ldp d14, d15, [sp], #16
ldp d12, d13, [sp], #16
ldp d10, d11, [sp], #16
ldp d8, d9, [sp], #16
ldp x27, x28, [sp], #16
ldp x25, x26, [sp], #16
ldp x23, x24, [sp], #16
ldp x21, x22, [sp], #16
ldp x19, x20, [sp], #16
ldp x29, x30, [sp], #16
ret
endfunc
-197
View File
@@ -1,197 +0,0 @@
/****************************************************************************
* Assembly testing and benchmarking tool
* Copyright (c) 2015 Martin Storsjo
* Copyright (c) 2015 Janne Grunau
*
* 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/arm/asm.S"
/* override fpu so that NEON instructions are rejected */
#if HAVE_VFP
FPU .fpu vfp
ELF .eabi_attribute 10, 0 @ suppress Tag_FP_arch
#endif
const register_init, align=3
.quad 0x21f86d66c8ca00ce
.quad 0x75b6ba21077c48ad
.quad 0xed56bb2dcb3c7736
.quad 0x8bda43d3fd1a7e06
.quad 0xb64a9c9e5d318408
.quad 0xdf9a54b303f1d3a3
.quad 0x4a75479abd64e097
.quad 0x249214109d5d1c88
endconst
const error_message_fpscr
.asciz "failed to preserve register FPSCR, changed bits: %x"
error_message_gpr:
.asciz "failed to preserve register r%d"
error_message_vfp:
.asciz "failed to preserve register d%d"
error_message_stack:
.asciz "failed to preserve stack"
endconst
@ max number of args used by any asm function.
#define MAX_ARGS 15
#define ARG_STACK 4*(MAX_ARGS - 4)
@ Align the used stack space to 8 to preserve the stack alignment.
@ +8 for stack canary reference.
#define ARG_STACK_A (((ARG_STACK + pushed + 7) & ~7) - pushed + 8)
.macro clobbercheck variant
.equ pushed, 4*9
function checkasm_checked_call_\variant, export=1
push {r4-r11, lr}
.ifc \variant, vfp
vpush {d8-d15}
fmrx r4, FPSCR
push {r4}
.equ pushed, pushed + 16*4 + 4
.endif
movrel r12, register_init
.ifc \variant, vfp
vldm r12, {d8-d15}
.endif
ldm r12, {r4-r11}
sub sp, sp, #ARG_STACK_A
.equ pos, 0
.rept MAX_ARGS-4
ldr r12, [sp, #ARG_STACK_A + pushed + 8 + pos]
str r12, [sp, #pos]
.equ pos, pos + 4
.endr
@ For stack overflows, the callee is free to overwrite the parameters
@ that were passed on the stack (if any), so we can only check after
@ that point. First figure out how many parameters the function
@ really took on the stack:
ldr r12, [sp, #ARG_STACK_A + pushed + 8 + 4*(MAX_ARGS-4)]
@ Load the first non-parameter value from the stack, that should be
@ left untouched by the function. Store a copy of it inverted, so that
@ e.g. overwriting everything with zero would be noticed.
ldr r12, [sp, r12, lsl #2]
mvn r12, r12
str r12, [sp, #ARG_STACK_A - 4]
mov r12, r0
mov r0, r2
mov r1, r3
ldrd r2, r3, [sp, #ARG_STACK_A + pushed]
@ Call the target function
blx r12
@ Load the number of stack parameters, stack canary and its reference
ldr r12, [sp, #ARG_STACK_A + pushed + 8 + 4*(MAX_ARGS-4)]
ldr r2, [sp, r12, lsl #2]
ldr r3, [sp, #ARG_STACK_A - 4]
add sp, sp, #ARG_STACK_A
push {r0, r1}
mvn r3, r3
cmp r2, r3
bne 5f
movrel r12, register_init
.ifc \variant, vfp
.macro check_reg_vfp, dreg, offset
ldrd r2, r3, [r12, #8 * (\offset)]
vmov r0, lr, \dreg
eor r2, r2, r0
eor r3, r3, lr
orrs r2, r2, r3
bne 4f
.endm
.irp n, 8, 9, 10, 11, 12, 13, 14, 15
@ keep track of the checked double/SIMD register
mov r1, #\n
check_reg_vfp d\n, \n-8
.endr
.purgem check_reg_vfp
fmrx r1, FPSCR
ldr r3, [sp, #8]
eor r1, r1, r3
@ Ignore changes in bits 0-4 and 7
bic r1, r1, #0x9f
@ Ignore changes in the topmost 5 bits
bics r1, r1, #0xf8000000
bne 3f
.endif
@ keep track of the checked GPR
mov r1, #4
.macro check_reg reg1, reg2=
ldrd r2, r3, [r12], #8
eors r2, r2, \reg1
bne 2f
add r1, r1, #1
.ifnb \reg2
eors r3, r3, \reg2
bne 2f
.endif
add r1, r1, #1
.endm
check_reg r4, r5
check_reg r6, r7
@ r9 is a volatile register in the ios ABI
#ifdef __APPLE__
check_reg r8
#else
check_reg r8, r9
#endif
check_reg r10, r11
.purgem check_reg
b 0f
5:
movrel r0, error_message_stack
b 1f
4:
movrel r0, error_message_vfp
b 1f
3:
movrel r0, error_message_fpscr
b 1f
2:
movrel r0, error_message_gpr
1:
bl X(checkasm_fail_func)
0:
pop {r0, r1}
.ifc \variant, vfp
pop {r2}
fmxr FPSCR, r2
vpop {d8-d15}
.endif
pop {r4-r11, pc}
endfunc
.endm
#if HAVE_VFP || HAVE_NEON
clobbercheck vfp
#endif
clobbercheck novfp
+13 -975
View File
File diff suppressed because it is too large Load Diff
+18 -355
View File
@@ -24,17 +24,10 @@
#define TESTS_CHECKASM_CHECKASM_H
#include <stdint.h>
#include <checkasm/test.h>
#include <checkasm/utils.h>
#include "config.h"
#if CONFIG_LINUX_PERF
#include <unistd.h> // read(3)
#include <sys/ioctl.h>
#include <asm/unistd.h>
#include <linux/perf_event.h>
#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 <windows.h>
#if defined(__i386__) || defined(_M_IX86)
#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
#elif defined(_WASI_EMULATED_SIGNAL)
#define checkasm_context void*
#define checkasm_save_context() 0
#define checkasm_load_context() do {} while (0)
#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);
@@ -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 */
+1 -1
View File
@@ -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;
-337
View File
@@ -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
+2 -2
View File
@@ -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);
}
+1 -1
View File
@@ -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);
}
}
-244
View File
@@ -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